mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-06 03:21:12 +07:00
[java-decompiler] IDEA-246839 Support java records in decompiler
Also: support preview levels in ClsFileImpl Also fixes: IDEA-247551 Exception on first opening of record .class file GitOrigin-RevId: 4362d669d1c16b8230d6d8ab803465b6a7476803
This commit is contained in:
committed by
intellij-monorepo-bot
parent
9e8efd889f
commit
10299a7f0f
@@ -557,8 +557,8 @@ public class ClsFileImpl extends PsiBinaryFileImpl
|
||||
String className = file.getNameWithoutExtension();
|
||||
String internalName = reader.getClassName();
|
||||
boolean module = internalName.equals("module-info") && BitUtil.isSet(reader.getAccess(), Opcodes.ACC_MODULE);
|
||||
JavaSdkVersion jdkVersion = ClsParsingUtil.getJdkVersionByBytecode(reader.readShort(6));
|
||||
LanguageLevel level = jdkVersion != null ? jdkVersion.getMaxLanguageLevel() : null;
|
||||
int majorMinor = reader.readInt(4);
|
||||
LanguageLevel level = ClsParsingUtil.getLanguageLevelFromBytecode(majorMinor & 0xFFFF, (majorMinor >>> 16));
|
||||
|
||||
if (module) {
|
||||
PsiJavaFileStub stub = new PsiJavaFileStubImpl(null, "", level, true);
|
||||
|
||||
@@ -161,6 +161,19 @@ public final class ClsParsingUtil {
|
||||
return StringUtil.isJavaIdentifier(identifier) && !JavaLexer.isKeyword(identifier, level);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static LanguageLevel getLanguageLevelFromBytecode(int major, int minor) {
|
||||
JavaSdkVersion version = getJdkVersionByBytecode(major);
|
||||
if (version != null) {
|
||||
LanguageLevel level = version.getMaxLanguageLevel();
|
||||
if (minor == 0xFFFF && level.getPreviewLevel() != null) {
|
||||
return level.getPreviewLevel();
|
||||
}
|
||||
return level;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static JavaSdkVersion getJdkVersionByBytecode(int major) {
|
||||
if (major == Opcodes.V1_1 || major == 45) {
|
||||
|
||||
@@ -14,10 +14,7 @@ import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement;
|
||||
import org.jetbrains.java.decompiler.modules.decompiler.vars.VarTypeProcessor;
|
||||
import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPair;
|
||||
import org.jetbrains.java.decompiler.modules.renamer.PoolInterceptor;
|
||||
import org.jetbrains.java.decompiler.struct.StructClass;
|
||||
import org.jetbrains.java.decompiler.struct.StructField;
|
||||
import org.jetbrains.java.decompiler.struct.StructMember;
|
||||
import org.jetbrains.java.decompiler.struct.StructMethod;
|
||||
import org.jetbrains.java.decompiler.struct.*;
|
||||
import org.jetbrains.java.decompiler.struct.attr.*;
|
||||
import org.jetbrains.java.decompiler.struct.consts.PrimitiveConstant;
|
||||
import org.jetbrains.java.decompiler.struct.gen.FieldDescriptor;
|
||||
@@ -28,6 +25,7 @@ import org.jetbrains.java.decompiler.util.InterpreterUtil;
|
||||
import org.jetbrains.java.decompiler.util.TextBuffer;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ClassWriter {
|
||||
private final PoolInterceptor interceptor;
|
||||
@@ -162,11 +160,19 @@ public class ClassWriter {
|
||||
|
||||
dummy_tracer.incrementCurrentSourceLine(buffer.countLines(start_class_def));
|
||||
|
||||
List<StructRecordComponent> components = cl.getRecordComponents();
|
||||
|
||||
for (StructField fd : cl.getFields()) {
|
||||
boolean hide = fd.isSynthetic() && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_SYNTHETIC) ||
|
||||
wrapper.getHiddenMembers().contains(InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor()));
|
||||
if (hide) continue;
|
||||
|
||||
if (components != null && fd.getAccessFlags() == (CodeConstants.ACC_FINAL | CodeConstants.ACC_PRIVATE) &&
|
||||
components.stream().anyMatch(c -> c.getName().equals(fd.getName()) && c.getDescriptor().equals(fd.getDescriptor()))) {
|
||||
// Record component field: skip it
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean isEnum = fd.hasModifier(CodeConstants.ACC_ENUM) && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM);
|
||||
if (isEnum) {
|
||||
if (enumFields) {
|
||||
@@ -302,6 +308,13 @@ public class ClassWriter {
|
||||
flags &= ~CodeConstants.ACC_FINAL;
|
||||
}
|
||||
|
||||
List<StructRecordComponent> components = cl.getRecordComponents();
|
||||
|
||||
if (components != null) {
|
||||
// records are implicitly final
|
||||
flags &= ~CodeConstants.ACC_FINAL;
|
||||
}
|
||||
|
||||
appendModifiers(buffer, flags, CLASS_ALLOWED, isInterface, CLASS_EXCLUDED);
|
||||
|
||||
if (isEnum) {
|
||||
@@ -313,6 +326,9 @@ public class ClassWriter {
|
||||
}
|
||||
buffer.append("interface ");
|
||||
}
|
||||
else if (components != null) {
|
||||
buffer.append("record ");
|
||||
}
|
||||
else {
|
||||
buffer.append("class ");
|
||||
}
|
||||
@@ -324,9 +340,22 @@ public class ClassWriter {
|
||||
appendTypeParameters(buffer, descriptor.fparameters, descriptor.fbounds);
|
||||
}
|
||||
|
||||
if (components != null) {
|
||||
buffer.append('(');
|
||||
for (int i = 0; i < components.size(); i++) {
|
||||
StructRecordComponent cd = components.get(i);
|
||||
if (i > 0) {
|
||||
buffer.append(", ");
|
||||
}
|
||||
boolean varArgComponent = i == components.size() - 1 && isVarArgRecord(cl);
|
||||
recordComponentToJava(cd, buffer, varArgComponent);
|
||||
}
|
||||
buffer.append(')');
|
||||
}
|
||||
|
||||
buffer.append(' ');
|
||||
|
||||
if (!isEnum && !isInterface && cl.superClass != null) {
|
||||
if (!isEnum && !isInterface && components == null && cl.superClass != null) {
|
||||
VarType supertype = new VarType(cl.superClass.getString(), true);
|
||||
if (!VarType.VARTYPE_OBJECT.equals(supertype)) {
|
||||
buffer.append("extends ");
|
||||
@@ -362,6 +391,13 @@ public class ClassWriter {
|
||||
buffer.append('{').appendLineSeparator();
|
||||
}
|
||||
|
||||
private boolean isVarArgRecord(StructClass cl) {
|
||||
String canonicalConstructorDescriptor =
|
||||
cl.getRecordComponents().stream().map(c -> c.getDescriptor()).collect(Collectors.joining("", "(", ")V"));
|
||||
StructMethod ctor = cl.getMethod(CodeConstants.INIT_NAME, canonicalConstructorDescriptor);
|
||||
return ctor != null && ctor.hasModifier(CodeConstants.ACC_VARARGS);
|
||||
}
|
||||
|
||||
private void fieldToJava(ClassWrapper wrapper, StructClass cl, StructField fd, TextBuffer buffer, int indent, BytecodeMappingTracer tracer) {
|
||||
int start = buffer.length();
|
||||
boolean isInterface = cl.hasModifier(CodeConstants.ACC_INTERFACE);
|
||||
@@ -452,6 +488,33 @@ public class ClassWriter {
|
||||
}
|
||||
}
|
||||
|
||||
private static void recordComponentToJava(StructRecordComponent cd, TextBuffer buffer, boolean varArgComponent) {
|
||||
appendAnnotations(buffer, -1, cd, TypeAnnotation.FIELD);
|
||||
|
||||
VarType fieldType = new VarType(cd.getDescriptor(), false);
|
||||
|
||||
GenericFieldDescriptor descriptor = null;
|
||||
if (DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES)) {
|
||||
StructGenericSignatureAttribute attr = cd.getAttribute(StructGeneralAttribute.ATTRIBUTE_SIGNATURE);
|
||||
if (attr != null) {
|
||||
descriptor = GenericMain.parseFieldSignature(attr.getSignature());
|
||||
}
|
||||
}
|
||||
|
||||
if (descriptor != null) {
|
||||
buffer.append(GenericMain.getGenericCastTypeName(varArgComponent ? descriptor.type.decreaseArrayDim() : descriptor.type));
|
||||
}
|
||||
else {
|
||||
buffer.append(ExprProcessor.getCastTypeName(varArgComponent ? fieldType.decreaseArrayDim() : fieldType));
|
||||
}
|
||||
if (varArgComponent) {
|
||||
buffer.append("...");
|
||||
}
|
||||
buffer.append(' ');
|
||||
|
||||
buffer.append(cd.getName());
|
||||
}
|
||||
|
||||
private static void methodLambdaToJava(ClassNode lambdaNode,
|
||||
ClassWrapper classWrapper,
|
||||
StructMethod mt,
|
||||
@@ -959,7 +1022,13 @@ public class ClassWriter {
|
||||
for (AnnotationExprent annotation : attribute.getAnnotations()) {
|
||||
String text = annotation.toJava(indent, BytecodeMappingTracer.DUMMY).toString();
|
||||
filter.add(text);
|
||||
buffer.append(text).appendLineSeparator();
|
||||
buffer.append(text);
|
||||
if (indent < 0) {
|
||||
buffer.append(' ');
|
||||
}
|
||||
else {
|
||||
buffer.appendLineSeparator();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
package org.jetbrains.java.decompiler.struct;
|
||||
|
||||
import org.jetbrains.java.decompiler.code.CodeConstants;
|
||||
import org.jetbrains.java.decompiler.struct.attr.StructGeneralAttribute;
|
||||
import org.jetbrains.java.decompiler.struct.attr.StructRecordAttribute;
|
||||
import org.jetbrains.java.decompiler.struct.consts.ConstantPool;
|
||||
import org.jetbrains.java.decompiler.struct.consts.PrimitiveConstant;
|
||||
import org.jetbrains.java.decompiler.struct.lazy.LazyLoader;
|
||||
@@ -10,6 +12,7 @@ import org.jetbrains.java.decompiler.util.InterpreterUtil;
|
||||
import org.jetbrains.java.decompiler.util.VBStyleCollection;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
class_file {
|
||||
@@ -132,6 +135,15 @@ public class StructClass extends StructMember {
|
||||
return pool;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list of record components; null if this class is not a record
|
||||
*/
|
||||
public List<StructRecordComponent> getRecordComponents() {
|
||||
StructRecordAttribute recordAttr = getAttribute(StructGeneralAttribute.ATTRIBUTE_RECORD);
|
||||
if (recordAttr == null) return null;
|
||||
return recordAttr.getComponents();
|
||||
}
|
||||
|
||||
public int[] getInterfaces() {
|
||||
return interfaces;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package org.jetbrains.java.decompiler.struct;
|
||||
|
||||
import org.jetbrains.java.decompiler.struct.consts.ConstantPool;
|
||||
import org.jetbrains.java.decompiler.struct.consts.PrimitiveConstant;
|
||||
import org.jetbrains.java.decompiler.util.DataInputFullStream;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/*
|
||||
record_component_info {
|
||||
u2 name_index;
|
||||
u2 descriptor_index;
|
||||
u2 attributes_count;
|
||||
attribute_info attributes[attributes_count];
|
||||
}
|
||||
*/
|
||||
public class StructRecordComponent extends StructMember {
|
||||
|
||||
private final String name;
|
||||
private final String descriptor;
|
||||
|
||||
|
||||
public StructRecordComponent(DataInputFullStream in, ConstantPool pool) throws IOException {
|
||||
accessFlags = 0;
|
||||
int nameIndex = in.readUnsignedShort();
|
||||
int descriptorIndex = in.readUnsignedShort();
|
||||
|
||||
name = ((PrimitiveConstant)pool.getConstant(nameIndex)).getString();
|
||||
descriptor = ((PrimitiveConstant)pool.getConstant(descriptorIndex)).getString();
|
||||
|
||||
attributes = readAttributes(in, pool);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getDescriptor() {
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ public class StructGeneralAttribute {
|
||||
public static final Key<StructGeneralAttribute> ATTRIBUTE_DEPRECATED = new Key<>("Deprecated");
|
||||
public static final Key<StructLineNumberTableAttribute> ATTRIBUTE_LINE_NUMBER_TABLE = new Key<>("LineNumberTable");
|
||||
public static final Key<StructMethodParametersAttribute> ATTRIBUTE_METHOD_PARAMETERS = new Key<>("MethodParameters");
|
||||
public static final Key<StructRecordAttribute> ATTRIBUTE_RECORD = new Key<>("Record");
|
||||
|
||||
public static class Key<T extends StructGeneralAttribute> {
|
||||
private final String name;
|
||||
@@ -97,6 +98,9 @@ public class StructGeneralAttribute {
|
||||
else if (ATTRIBUTE_METHOD_PARAMETERS.getName().equals(name)) {
|
||||
attr = new StructMethodParametersAttribute();
|
||||
}
|
||||
else if (ATTRIBUTE_RECORD.getName().equals(name)) {
|
||||
attr = new StructRecordAttribute();
|
||||
}
|
||||
else {
|
||||
// unsupported attribute
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package org.jetbrains.java.decompiler.struct.attr;
|
||||
|
||||
import org.jetbrains.java.decompiler.struct.StructRecordComponent;
|
||||
import org.jetbrains.java.decompiler.struct.consts.ConstantPool;
|
||||
import org.jetbrains.java.decompiler.util.DataInputFullStream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
Record_attribute {
|
||||
u2 attribute_name_index;
|
||||
u4 attribute_length;
|
||||
u2 components_count;
|
||||
record_component_info components[components_count];
|
||||
}
|
||||
*/
|
||||
public class StructRecordAttribute extends StructGeneralAttribute {
|
||||
List<StructRecordComponent> components;
|
||||
|
||||
@Override
|
||||
public void initContent(DataInputFullStream data,
|
||||
ConstantPool pool) throws IOException {
|
||||
int componentCount = data.readUnsignedShort();
|
||||
StructRecordComponent[] components = new StructRecordComponent[componentCount];
|
||||
for (int i = 0; i < componentCount; i++) {
|
||||
components[i] = new StructRecordComponent(data, pool);
|
||||
}
|
||||
this.components = Arrays.asList(components);
|
||||
}
|
||||
|
||||
public List<StructRecordComponent> getComponents() {
|
||||
return Collections.unmodifiableList(components);
|
||||
}
|
||||
}
|
||||
@@ -129,6 +129,11 @@ public class SingleClassesTest {
|
||||
@Test public void testSuspendLambda() { doTest("pkg/TestSuspendLambdaKt"); }
|
||||
@Test public void testNamedSuspendFun2Kt() { doTest("pkg/TestNamedSuspendFun2Kt"); }
|
||||
@Test public void testGenericArgs() { doTest("pkg/TestGenericArgs"); }
|
||||
@Test public void testRecordEmpty() { doTest("records/TestRecordEmpty"); }
|
||||
@Test public void testRecordSimple() { doTest("records/TestRecordSimple"); }
|
||||
@Test public void testRecordVararg() { doTest("records/TestRecordVararg"); }
|
||||
@Test public void testRecordGenericVararg() { doTest("records/TestRecordGenericVararg"); }
|
||||
@Test public void testRecordAnno() { doTest("records/TestRecordAnno"); }
|
||||
|
||||
private void doTest(String testFile, String... companionFiles) {
|
||||
ConsoleDecompiler decompiler = fixture.getDecompiler();
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,66 @@
|
||||
package records;
|
||||
|
||||
public record TestRecordAnno(@RC @TA int x, int y) {
|
||||
public TestRecordAnno(@TA int x, @P int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public final String toString() {
|
||||
return this.toString<invokedynamic>(this);
|
||||
}
|
||||
|
||||
public final int hashCode() {
|
||||
return this.hashCode<invokedynamic>(this);
|
||||
}
|
||||
|
||||
public final boolean equals(Object o) {
|
||||
return this.equals<invokedynamic>(this, o);
|
||||
}
|
||||
|
||||
@TA
|
||||
public int x() {
|
||||
return this.x;
|
||||
}
|
||||
|
||||
@M
|
||||
public int y() {
|
||||
return this.y;// 5
|
||||
}
|
||||
}
|
||||
|
||||
class 'records/TestRecordAnno' {
|
||||
method '<init> (II)V' {
|
||||
6 4
|
||||
b 5
|
||||
e 6
|
||||
}
|
||||
|
||||
method 'toString ()Ljava/lang/String;' {
|
||||
1 9
|
||||
6 9
|
||||
}
|
||||
|
||||
method 'hashCode ()I' {
|
||||
1 13
|
||||
6 13
|
||||
}
|
||||
|
||||
method 'equals (Ljava/lang/Object;)Z' {
|
||||
2 17
|
||||
7 17
|
||||
}
|
||||
|
||||
method 'x ()I' {
|
||||
1 22
|
||||
4 22
|
||||
}
|
||||
|
||||
method 'y ()I' {
|
||||
1 27
|
||||
4 27
|
||||
}
|
||||
}
|
||||
|
||||
Lines mapping:
|
||||
5 <-> 28
|
||||
@@ -0,0 +1,35 @@
|
||||
package records;
|
||||
|
||||
public record TestRecordEmpty() {
|
||||
public final String toString() {
|
||||
return this.toString<invokedynamic>(this);
|
||||
}
|
||||
|
||||
public final int hashCode() {
|
||||
return this.hashCode<invokedynamic>(this);
|
||||
}
|
||||
|
||||
public final boolean equals(Object o) {
|
||||
return this.equals<invokedynamic>(this, o);// 3
|
||||
}
|
||||
}
|
||||
|
||||
class 'records/TestRecordEmpty' {
|
||||
method 'toString ()Ljava/lang/String;' {
|
||||
1 4
|
||||
6 4
|
||||
}
|
||||
|
||||
method 'hashCode ()I' {
|
||||
1 8
|
||||
6 8
|
||||
}
|
||||
|
||||
method 'equals (Ljava/lang/Object;)Z' {
|
||||
2 12
|
||||
7 12
|
||||
}
|
||||
}
|
||||
|
||||
Lines mapping:
|
||||
3 <-> 13
|
||||
@@ -0,0 +1,66 @@
|
||||
package records;
|
||||
|
||||
public record TestRecordGenericVararg<T>(T first, T... other) {
|
||||
@SafeVarargs
|
||||
public TestRecordGenericVararg(T first, T... other) {
|
||||
this.first = first;// 5
|
||||
this.other = other;
|
||||
}
|
||||
|
||||
public final String toString() {
|
||||
return this.toString<invokedynamic>(this);
|
||||
}
|
||||
|
||||
public final int hashCode() {
|
||||
return this.hashCode<invokedynamic>(this);
|
||||
}
|
||||
|
||||
public final boolean equals(Object o) {
|
||||
return this.equals<invokedynamic>(this, o);
|
||||
}
|
||||
|
||||
public T first() {
|
||||
return this.first;
|
||||
}
|
||||
|
||||
public T[] other() {
|
||||
return this.other;// 3
|
||||
}
|
||||
}
|
||||
|
||||
class 'records/TestRecordGenericVararg' {
|
||||
method '<init> (Ljava/lang/Object;[Ljava/lang/Object;)V' {
|
||||
6 5
|
||||
b 6
|
||||
e 7
|
||||
}
|
||||
|
||||
method 'toString ()Ljava/lang/String;' {
|
||||
1 10
|
||||
6 10
|
||||
}
|
||||
|
||||
method 'hashCode ()I' {
|
||||
1 14
|
||||
6 14
|
||||
}
|
||||
|
||||
method 'equals (Ljava/lang/Object;)Z' {
|
||||
2 18
|
||||
7 18
|
||||
}
|
||||
|
||||
method 'first ()Ljava/lang/Object;' {
|
||||
1 22
|
||||
4 22
|
||||
}
|
||||
|
||||
method 'other ()[Ljava/lang/Object;' {
|
||||
1 26
|
||||
4 26
|
||||
}
|
||||
}
|
||||
|
||||
Lines mapping:
|
||||
3 <-> 27
|
||||
5 <-> 6
|
||||
@@ -0,0 +1,64 @@
|
||||
package records;
|
||||
|
||||
public record TestRecordSimple(int x, int y) {
|
||||
public TestRecordSimple(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public final String toString() {
|
||||
return this.toString<invokedynamic>(this);
|
||||
}
|
||||
|
||||
public final int hashCode() {
|
||||
return this.hashCode<invokedynamic>(this);
|
||||
}
|
||||
|
||||
public final boolean equals(Object o) {
|
||||
return this.equals<invokedynamic>(this, o);
|
||||
}
|
||||
|
||||
public int x() {
|
||||
return this.x;
|
||||
}
|
||||
|
||||
public int y() {
|
||||
return this.y;// 3
|
||||
}
|
||||
}
|
||||
|
||||
class 'records/TestRecordSimple' {
|
||||
method '<init> (II)V' {
|
||||
6 4
|
||||
b 5
|
||||
e 6
|
||||
}
|
||||
|
||||
method 'toString ()Ljava/lang/String;' {
|
||||
1 9
|
||||
6 9
|
||||
}
|
||||
|
||||
method 'hashCode ()I' {
|
||||
1 13
|
||||
6 13
|
||||
}
|
||||
|
||||
method 'equals (Ljava/lang/Object;)Z' {
|
||||
2 17
|
||||
7 17
|
||||
}
|
||||
|
||||
method 'x ()I' {
|
||||
1 21
|
||||
4 21
|
||||
}
|
||||
|
||||
method 'y ()I' {
|
||||
1 25
|
||||
4 25
|
||||
}
|
||||
}
|
||||
|
||||
Lines mapping:
|
||||
3 <-> 26
|
||||
@@ -0,0 +1,64 @@
|
||||
package records;
|
||||
|
||||
public record TestRecordVararg(int x, int[]... y) {
|
||||
public TestRecordVararg(int x, int[]... y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public final String toString() {
|
||||
return this.toString<invokedynamic>(this);
|
||||
}
|
||||
|
||||
public final int hashCode() {
|
||||
return this.hashCode<invokedynamic>(this);
|
||||
}
|
||||
|
||||
public final boolean equals(Object o) {
|
||||
return this.equals<invokedynamic>(this, o);
|
||||
}
|
||||
|
||||
public int x() {
|
||||
return this.x;
|
||||
}
|
||||
|
||||
public int[][] y() {
|
||||
return this.y;// 3
|
||||
}
|
||||
}
|
||||
|
||||
class 'records/TestRecordVararg' {
|
||||
method '<init> (I[[I)V' {
|
||||
6 4
|
||||
b 5
|
||||
e 6
|
||||
}
|
||||
|
||||
method 'toString ()Ljava/lang/String;' {
|
||||
1 9
|
||||
6 9
|
||||
}
|
||||
|
||||
method 'hashCode ()I' {
|
||||
1 13
|
||||
6 13
|
||||
}
|
||||
|
||||
method 'equals (Ljava/lang/Object;)Z' {
|
||||
2 17
|
||||
7 17
|
||||
}
|
||||
|
||||
method 'x ()I' {
|
||||
1 21
|
||||
4 21
|
||||
}
|
||||
|
||||
method 'y ()[[I' {
|
||||
1 25
|
||||
4 25
|
||||
}
|
||||
}
|
||||
|
||||
Lines mapping:
|
||||
3 <-> 26
|
||||
@@ -0,0 +1,17 @@
|
||||
package records;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
public record TestRecordAnno(@TA @RC int x, @M @P int y) {}
|
||||
|
||||
@Target(ElementType.TYPE_USE)
|
||||
@interface TA {}
|
||||
|
||||
@Target(ElementType.RECORD_COMPONENT)
|
||||
@interface RC {}
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@interface M {}
|
||||
|
||||
@Target(ElementType.PARAMETER)
|
||||
@interface P {}
|
||||
@@ -0,0 +1,3 @@
|
||||
package records;
|
||||
|
||||
public record TestRecordEmpty() {}
|
||||
@@ -0,0 +1,6 @@
|
||||
package records;
|
||||
|
||||
public record TestRecordGenericVararg<T>(T first, T... other) {
|
||||
@SafeVarargs
|
||||
public TestRecordGenericVararg {}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package records;
|
||||
|
||||
public record TestRecordSimple(int x, int y) {}
|
||||
@@ -0,0 +1,3 @@
|
||||
package records;
|
||||
|
||||
public record TestRecordVararg(int x, int[]... y) {}
|
||||
Reference in New Issue
Block a user