diff options
14 files changed, 435 insertions, 402 deletions
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/RecordsRestrictedClassTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/RecordsRestrictedClassTest.java index aa549b1844..b91b9ad53a 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/RecordsRestrictedClassTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/RecordsRestrictedClassTest.java @@ -33,7 +33,7 @@ public class RecordsRestrictedClassTest extends AbstractRegressionTest { static { // TESTS_NUMBERS = new int [] { 40 }; // TESTS_RANGE = new int[] { 1, -1 }; -// TESTS_NAMES = new String[] { "testBug553567" }; +// TESTS_NAMES = new String[] { "testBug550750_037" }; } public static Class<?> testClass() { @@ -1016,16 +1016,16 @@ public class RecordsRestrictedClassTest extends AbstractRegressionTest { " }\n"+ "}\n"+ "record Point(int myInt, int myZ) implements I {\n"+ - " public int myInt() throws IOException {;\n" + + " public int myInt() throws Exception {;\n" + " return this.myInt;\n" + " }\n"+ "}\n" + "interface I {}\n" }, "----------\n" + - "1. ERROR in X.java (at line 0)\n" + - " public class X {\n" + - " ^\n" + + "1. ERROR in X.java (at line 7)\n" + + " public int myInt() throws Exception {;\n" + + " ^^^^^^^^^^^^^^^^^^^^^^^^\n" + "Throws clause not allowed for explicitly declared accessor method\n" + "----------\n"); } @@ -1628,11 +1628,12 @@ public void testBug558494_003() throws Exception { "public class X {\n"+ " public static void main(String[] args) {\n"+ " Forts p = new Forts(new String[] {\"Amber\", \"Nahargarh\", \"Jaigarh\"});\n"+ - " System.out.println(p.toString());\n"+ + " if (!p.toString().startsWith(\"Forts[wonders=[Ljava.lang.String;@\"))\n"+ + " System.out.println(\"Error\");\n"+ " }\n"+ "}\n" }, - "Forts@c77c674d"); + ""); String expectedOutput = "Record: #Record\n" + "Components:\n" + " \n"; @@ -1642,24 +1643,25 @@ public void testBug558494_004() throws Exception { runConformTest( new String[] { "X.java", - "record Forts(String[] wonders, int x){\n"+ + "record Forts(int x, String[] wonders){\n"+ "}\n"+ "public class X {\n"+ " public static void main(String[] args) {\n"+ - " Forts p = new Forts(new String[] {\"Amber\", \"Nahargarh\", \"Jaigarh\"}, 3);\n"+ - " System.out.println(p.toString());\n"+ + " Forts p = new Forts(3, new String[] {\"Amber\", \"Nahargarh\", \"Jaigarh\"});\n"+ + " if (!p.toString().startsWith(\"Forts[x=3, wonders=[Ljava.lang.String;@\"))\n"+ + " System.out.println(\"Error\");\n"+ " }\n"+ "}\n" }, - "Forts@28108256"); + ""); String expectedOutput = - "Record: #Record\n" + - "Components:\n" + - " \n" + - "// Component descriptor #6 [Ljava/lang/String;\n" + - "java.lang.String[] wonders;\n" + - "// Component descriptor #8 I\n" + - "int x;\n"; + "Record: #Record\n" + + "Components:\n" + + " \n" + + "// Component descriptor #6 I\n" + + "int x;\n" + + "// Component descriptor #8 [Ljava/lang/String;\n" + + "java.lang.String[] wonders;\n"; RecordsRestrictedClassTest.verifyClassFile(expectedOutput, "Forts.class", ClassFileBytesDisassembler.SYSTEM); } public void testBug558764_001() { diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ClassFile.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ClassFile.java index 71de6cf5eb..d1590219b5 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ClassFile.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ClassFile.java @@ -46,7 +46,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; - import org.eclipse.jdt.core.compiler.CategorizedProblem; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.core.compiler.IProblem; @@ -164,6 +163,7 @@ public class ClassFile implements TypeConstants, TypeIds { public int headerOffset; public Map<TypeBinding, Boolean> innerClassesBindings; public List bootstrapMethods = null; + public List<TypeBinding> recordBootstrapMethods = null; public int methodCount; public int methodCountOffset; // pool managment @@ -424,9 +424,10 @@ public class ClassFile implements TypeConstants, TypeIds { } attributesNumber += generateHierarchyInconsistentAttribute(); } - // Functional expression and lambda bootstrap methods - if (this.bootstrapMethods != null && !this.bootstrapMethods.isEmpty()) { - attributesNumber += generateBootstrapMethods(this.bootstrapMethods); + // Functional expression, lambda bootstrap methods and record bootstrap methods + if ((this.bootstrapMethods != null && !this.bootstrapMethods.isEmpty()) || + (this.recordBootstrapMethods != null && !this.recordBootstrapMethods.isEmpty())) { + attributesNumber += generateBootstrapMethods(this.bootstrapMethods, this.recordBootstrapMethods); } // Inner class attribute int numberOfInnerClasses = this.innerClassesBindings == null ? 0 : this.innerClassesBindings.size(); @@ -1102,6 +1103,11 @@ public class ClassFile implements TypeConstants, TypeIds { case SyntheticMethodBinding.SerializableMethodReference: // Nothing to be done break; + case SyntheticMethodBinding.RecordOverrideEquals: + case SyntheticMethodBinding.RecordOverrideHashCode: + case SyntheticMethodBinding.RecordOverrideToString: + addSyntheticRecordOverrideMethods(syntheticMethod, syntheticMethod.purpose); + break; } } emittedSyntheticsCount = currentSyntheticsCount; @@ -1132,6 +1138,48 @@ public class ClassFile implements TypeConstants, TypeIds { } } + private void addSyntheticRecordOverrideMethods(SyntheticMethodBinding methodBinding, int purpose) { + if (this.recordBootstrapMethods == null) + this.recordBootstrapMethods = new ArrayList<>(3); + if (!this.recordBootstrapMethods.contains(methodBinding.declaringClass)) + this.recordBootstrapMethods.add(methodBinding.declaringClass); + int index = this.bootstrapMethods != null ? this.bootstrapMethods.size() + + this.recordBootstrapMethods.size() - 1 : 0; + generateMethodInfoHeader(methodBinding); + int methodAttributeOffset = this.contentsOffset; + // this will add exception attribute, synthetic attribute, deprecated attribute,... + int attributeNumber = generateMethodInfoAttributes(methodBinding); + // Code attribute + int codeAttributeOffset = this.contentsOffset; + attributeNumber++; // add code attribute + generateCodeAttributeHeader(); + this.codeStream.init(this); + switch (purpose) { + case SyntheticMethodBinding.RecordOverrideEquals: + this.codeStream.generateSyntheticBodyForRecordEquals(methodBinding, index); + break; + case SyntheticMethodBinding.RecordOverrideHashCode: + this.codeStream.generateSyntheticBodyForRecordHashCode(methodBinding, index); + break; + case SyntheticMethodBinding.RecordOverrideToString: + this.codeStream.generateSyntheticBodyForRecordToString(methodBinding, index); + break; + default: + break; + } + completeCodeAttributeForSyntheticMethod( + methodBinding, + codeAttributeOffset, + ((SourceTypeBinding) methodBinding.declaringClass) + .scope + .referenceCompilationUnit() + .compilationResult + .getLineSeparatorPositions()); + // update the number of attributes + this.contents[methodAttributeOffset++] = (byte) (attributeNumber >> 8); + this.contents[methodAttributeOffset] = (byte) attributeNumber; + } + public void addSyntheticArrayConstructor(SyntheticMethodBinding methodBinding) { generateMethodInfoHeader(methodBinding); int methodAttributeOffset = this.contentsOffset; @@ -2814,12 +2862,12 @@ public class ClassFile implements TypeConstants, TypeIds { if (record == null || !record.isRecord()) return 0; int localContentsOffset = this.contentsOffset; - List<FieldBinding> recordComponents = this.referenceBinding.getRecordComponents(); + FieldBinding[] recordComponents = this.referenceBinding.getRecordComponents(); if (recordComponents == null) return 0; // could be an empty record also, account for zero components as well. - int numberOfRecordComponents = recordComponents.size() ; + int numberOfRecordComponents = recordComponents.length; int exSize = 8 + 2 * numberOfRecordComponents; if (exSize + localContentsOffset >= this.contents.length) { @@ -2843,7 +2891,7 @@ public class ClassFile implements TypeConstants, TypeIds { this.contents[localContentsOffset++] = (byte) numberOfRecordComponents; this.contentsOffset = localContentsOffset; for (int i = 0; i < numberOfRecordComponents; i++) { - addComponentInfo(recordComponents.get(i)); + addComponentInfo(recordComponents[i]); } int attrLength = this.contentsOffset - base; this.contents[attrLengthOffset++] = (byte) (attrLength >> 24); @@ -3469,7 +3517,7 @@ public class ClassFile implements TypeConstants, TypeIds { return 1; } - private int generateBootstrapMethods(List functionalExpressionList) { + private int generateBootstrapMethods(List functionalExpressionList, List<TypeBinding> recordBootstrapMethods2) { /* See JVM spec 4.7.21 The BootstrapMethods attribute has the following format: BootstrapMethods_attribute { @@ -3486,13 +3534,10 @@ public class ClassFile implements TypeConstants, TypeIds { ReferenceBinding methodHandlesLookup = this.referenceBinding.scope.getJavaLangInvokeMethodHandlesLookup(); if (methodHandlesLookup == null) return 0; // skip bootstrap section, class path problem already reported, just avoid NPE. recordInnerClasses(methodHandlesLookup); // Should be done, it's what javac does also - ReferenceBinding javaLangInvokeLambdaMetafactory = this.referenceBinding.scope.getJavaLangInvokeLambdaMetafactory(); - - // Depending on the complexity of the expression it may be necessary to use the altMetafactory() rather than the metafactory() - int indexForMetaFactory = 0; - int indexForAltMetaFactory = 0; - int numberOfBootstraps = functionalExpressionList.size(); + int nfunctionalExpressions = functionalExpressionList != null ? functionalExpressionList.size() : 0; + int nRecordBootStraps = recordBootstrapMethods2 != null ? recordBootstrapMethods2.size() : 0; + int numberOfBootstraps = nfunctionalExpressions + nRecordBootStraps; int localContentsOffset = this.contentsOffset; // Generate the boot strap attribute - since we are only making lambdas and // functional expressions, we know the size ahead of time - this less general @@ -3511,8 +3556,35 @@ public class ClassFile implements TypeConstants, TypeIds { // leave space for attribute_length and remember where to insert it int attributeLengthPosition = localContentsOffset; localContentsOffset += 4; + this.contents[localContentsOffset++] = (byte) (numberOfBootstraps >> 8); this.contents[localContentsOffset++] = (byte) numberOfBootstraps; + + if (nfunctionalExpressions > 0) + localContentsOffset = generateLambdaMetaFactoryBootStrapMethods(functionalExpressionList, + localContentsOffset, contentsEntries); + if (nRecordBootStraps > 0) + localContentsOffset = generateObjectMethodsBootStrapMethods(recordBootstrapMethods2, localContentsOffset, + contentsEntries); + + int attributeLength = localContentsOffset - attributeLengthPosition - 4; + this.contents[attributeLengthPosition++] = (byte) (attributeLength >> 24); + this.contents[attributeLengthPosition++] = (byte) (attributeLength >> 16); + this.contents[attributeLengthPosition++] = (byte) (attributeLength >> 8); + this.contents[attributeLengthPosition++] = (byte) attributeLength; + this.contentsOffset = localContentsOffset; + return 1; + } + + private int generateLambdaMetaFactoryBootStrapMethods(List functionalExpressionList, + int localContentsOffset, final int contentsEntries) { + ReferenceBinding javaLangInvokeLambdaMetafactory = this.referenceBinding.scope.getJavaLangInvokeLambdaMetafactory(); + int numberOfBootstraps = functionalExpressionList.size(); + + // Depending on the complexity of the expression it may be necessary to use the altMetafactory() rather than the metafactory() + int indexForMetaFactory = 0; + int indexForAltMetaFactory = 0; + for (int i = 0; i < numberOfBootstraps; i++) { FunctionalExpression functional = (FunctionalExpression) functionalExpressionList.get(i); MethodBinding [] bridges = functional.getRequiredBridges(); @@ -3625,14 +3697,68 @@ public class ClassFile implements TypeConstants, TypeIds { this.contents[localContentsOffset++] = (byte) methodTypeIndex; } } - - int attributeLength = localContentsOffset - attributeLengthPosition - 4; - this.contents[attributeLengthPosition++] = (byte) (attributeLength >> 24); - this.contents[attributeLengthPosition++] = (byte) (attributeLength >> 16); - this.contents[attributeLengthPosition++] = (byte) (attributeLength >> 8); - this.contents[attributeLengthPosition++] = (byte) attributeLength; - this.contentsOffset = localContentsOffset; - return 1; + return localContentsOffset; + } + private int generateObjectMethodsBootStrapMethods(List<TypeBinding> recordList, + int localContentsOffset, final int contentsEntries) { + ReferenceBinding javaLangRuntimeObjectMethods = this.referenceBinding.scope.getJavaLangRuntimeObjectMethods(); + int numberOfBootstraps = recordList.size(); + int indexForObjectMethodBootStrap = 0; + for (int i = 0; i < numberOfBootstraps; i++) { + if (contentsEntries + localContentsOffset >= this.contents.length) { + resizeContents(contentsEntries); + } + if (indexForObjectMethodBootStrap == 0) { + indexForObjectMethodBootStrap = this.constantPool.literalIndexForMethodHandle(ClassFileConstants.MethodHandleRefKindInvokeStatic, javaLangRuntimeObjectMethods, + ConstantPool.BOOTSTRAP, ConstantPool.JAVA_LANG_RUNTIME_OBJECTMETHOD_BOOTSTRAP_SIGNATURE, false); + } + this.contents[localContentsOffset++] = (byte) (indexForObjectMethodBootStrap >> 8); + this.contents[localContentsOffset++] = (byte) indexForObjectMethodBootStrap; + + // u2 num_bootstrap_arguments + int numArgsLocation = localContentsOffset; + localContentsOffset += 2; + + TypeBinding type = recordList.get(i); + assert type.isRecord(); // sanity check + int recordIndex = this.constantPool.literalIndexForType(type.constantPoolName()); + this.contents[localContentsOffset++] = (byte) (recordIndex >> 8); + this.contents[localContentsOffset++] = (byte) recordIndex; + + assert type instanceof SourceTypeBinding; + SourceTypeBinding sourceType = (SourceTypeBinding) type; + FieldBinding[] recordComponents = sourceType.getRecordComponents(); + + int numArgs = 2 + recordComponents.length; + this.contents[numArgsLocation++] = (byte) (numArgs >> 8); + this.contents[numArgsLocation] = (byte) numArgs; + + String names = + Arrays.stream(recordComponents) + .map(f -> new String(f.name)) + .reduce((s1, s2) -> { return s1 + ";" + s2;}) //$NON-NLS-1$ + .orElse(Util.EMPTY_STRING); + int namesIndex = this.constantPool.literalIndex(names); + this.contents[localContentsOffset++] = (byte) (namesIndex >> 8); + this.contents[localContentsOffset++] = (byte) namesIndex; + + List<MethodBinding> getters = new ArrayList<>(); + for (FieldBinding field : recordComponents) { + MethodBinding[] candidates = sourceType.getMethods(field.name); + for (MethodBinding candidate : candidates) { + if (candidate.parameters == null || candidate.parameters.length == 0) { + getters.add(candidate); + break; + } + } + } + for (MethodBinding getter : getters) { + int getterIndex = this.constantPool.literalIndexForMethodHandle(getter); + this.contents[localContentsOffset++] = (byte) (getterIndex >> 8); + this.contents[localContentsOffset++] = (byte) getterIndex; + } + } + return localContentsOffset; } private int generateLineNumberAttribute() { int localContentsOffset = this.contentsOffset; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CompactConstructorDeclaration.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CompactConstructorDeclaration.java index 0bf3a2e61e..5973d44db9 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CompactConstructorDeclaration.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CompactConstructorDeclaration.java @@ -27,7 +27,6 @@ import org.eclipse.jdt.internal.compiler.parser.Parser; public class CompactConstructorDeclaration extends ConstructorDeclaration { - public boolean isImplicit; public RecordDeclaration recordDeclaration; public CompactConstructorDeclaration(CompilationResult compilationResult) { @@ -35,12 +34,6 @@ public class CompactConstructorDeclaration extends ConstructorDeclaration { } @Override public void parseStatements(Parser parser, CompilationUnitDeclaration unit) { - if (this.isImplicit && this.constructorCall == null) { - this.constructorCall = SuperReference.implicitSuperConstructorCall(); - this.constructorCall.sourceStart = this.sourceStart; - this.constructorCall.sourceEnd = this.sourceEnd; - return; - } parser.parse(this, unit, false); ASTVisitor visitor = new ASTVisitor() { @Override @@ -63,8 +56,7 @@ public class CompactConstructorDeclaration extends ConstructorDeclaration { return false; } }; - if (!this.isImplicit) - unit.traverse(visitor, unit.scope); + unit.traverse(visitor, unit.scope); } @Override protected void checkAndGenerateFieldAssignment(FlowContext flowContext, FlowInfo flowInfo, FieldBinding field) { diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FieldDeclaration.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FieldDeclaration.java index 7bda6fe890..1ef006e78f 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FieldDeclaration.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FieldDeclaration.java @@ -77,7 +77,8 @@ public FieldDeclaration( char[] name, int sourceStart, int sourceEnd) { public FlowInfo analyseCode(MethodScope initializationScope, FlowContext flowContext, FlowInfo flowInfo) { if (this.binding != null && !this.binding.isUsed() && this.binding.isOrEnclosedByPrivateType()) { if (!initializationScope.referenceCompilationUnit().compilationResult.hasSyntaxError) { - initializationScope.problemReporter().unusedPrivateField(this); + if (!this.isARecordComponent) // record component used by implicit methods + initializationScope.problemReporter().unusedPrivateField(this); } } // cannot define static non-constant field inside nested class diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/RecordDeclaration.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/RecordDeclaration.java index 5b96270e07..ed8e1f6aae 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/RecordDeclaration.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/RecordDeclaration.java @@ -17,10 +17,8 @@ package org.eclipse.jdt.internal.compiler.ast; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; import org.eclipse.jdt.core.compiler.CharOperation; @@ -29,17 +27,12 @@ import org.eclipse.jdt.internal.compiler.CompilationResult; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.lookup.Scope; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; -import org.eclipse.jdt.internal.compiler.lookup.TypeIds; import org.eclipse.jdt.internal.compiler.parser.Parser; -import org.eclipse.jdt.internal.compiler.problem.ProblemReporter; public class RecordDeclaration extends TypeDeclaration { private Argument[] args; public int nRecordComponents; - private long defaultPosition = 0L; - private long[] default_long_pos = {0L, 0L, 0L}; - public static Set<String> disallowedComponentNames; static { disallowedComponentNames = new HashSet<>(6); @@ -80,10 +73,7 @@ public class RecordDeclaration extends TypeDeclaration { this.typeParameters = t.typeParameters; this.sourceStart = t.sourceStart; this.sourceEnd = t.sourceEnd; - this.restrictedIdentifierStart = t.restrictedIdentifierStart; - this.defaultPosition = this.sourceStart << 32 + (this.sourceStart + 1); - this.default_long_pos = new long[]{this.defaultPosition, this.defaultPosition, this.defaultPosition}; - } +} public ConstructorDeclaration getConstructor(Parser parser) { if (this.methods != null) { for (int i = this.methods.length; --i >= 0;) { @@ -156,7 +146,7 @@ public class RecordDeclaration extends TypeDeclaration { /* The body of the implicitly declared canonical constructor initializes each field corresponding * to a record component with the corresponding formal parameter in the order that they appear * in the record component list.*/ - int l = this.args != null ? this.args.length : 0; + int l = this.args.length; Statement[] statements = new Statement[l]; for (int i = 0; i < l; ++i) { Argument arg = this.args[i]; @@ -185,113 +175,10 @@ public class RecordDeclaration extends TypeDeclaration { return constructor; } - public void createImplicitAccessors(ProblemReporter problemReporter) { - // JLS 14 8.10.3 Item 2 create the accessors for the fields if required - /* - * An implicitly declared public accessor method with the same name as the record component, - * whose return type is the declared type of the record component, - * unless a public method with the same signature is explicitly declared in the body of the declaration of R. - */ - - if (this.fields == null) - return; - Map<String, Set<AbstractMethodDeclaration>> accessors = new HashMap<>(); - for (int i = 0; i < this.nRecordComponents; i++) { - FieldDeclaration f = this.fields[i] ; - if (f != null && f.name != null && f.name.length > 0) { - accessors.put(new String(f.name), new HashSet<>()); - } - } - if (this.methods != null) { - for (int i = 0; i < this.methods.length; i++) { - AbstractMethodDeclaration m = this.methods[i]; - if (m != null && m.selector != null & m.selector.length > 0) { - String name1 = new String(m.selector); - Set<AbstractMethodDeclaration> acc = accessors.get(name1); - if (acc != null) - acc.add(m); - } - } - } - for (int i = this.nRecordComponents - 1; i >= 0; i--) { - FieldDeclaration f = this.fields[i] ; - if (f != null && f.name != null && f.name.length > 0) { - String name1 = new String(f.name); - Set<AbstractMethodDeclaration> acc = accessors.get(name1); - MethodDeclaration m = null; - if (acc.size() > 0) { - for (AbstractMethodDeclaration amd : acc) { - m = (MethodDeclaration) amd; - /* JLS 14 Sec 8.10.3 Item 1, Subitem 2 - * An implicitly declared public accessor method with the same name as the record component, whose return - * type is the declared type of the record component, unless a public method with the same signature is - * explicitly declared in the body of the declaration of R - */ - // Here the assumption is method signature implies the method signature in source ie the return type - // is not being considered - Given this, type resolution is not required and hence its a simple name and - // parameter number check. - if (m.arguments == null || m.arguments.length == 0) { - // found the explicitly declared accessor. - /* - * JLS 14 Sec 8.10.3 Item 1 Sub-item 2 Para 3 - * It is a compile-time error if an explicitly declared accessor method has a throws clause. - */ - if (m.thrownExceptions != null && m.thrownExceptions.length > 0) - problemReporter.recordAccessorMethodHasThrowsClause(m); - break; // found - } - m = null; - } - } - if (m == null) // no explicit accessor method found - declare one. - createNewMethod(f); - } - } - } @Override public void generateCode(ClassFile enclosingClassFile) { super.generateCode(enclosingClassFile); } - private AbstractMethodDeclaration createNewMethod(FieldDeclaration f) { - MethodDeclaration m = new MethodDeclaration(this.compilationResult); - m.selector = f.name; - m.bits |= ASTNode.IsImplicit; - m.modifiers = ClassFileConstants.AccPublic; - - m.returnType = f.type; - FieldReference fr = new FieldReference(f.name, -1); - fr.receiver = new ThisReference(-1, -1); - ReturnStatement ret = new ReturnStatement(fr, -1, -1); - m.statements = new Statement[] { ret }; - m.isImplicit = true; - /* - * JLS 14 Sec 8.10.3 Item 2 states that: - * "The implicitly declared accessor method is annotated with the annotation - * that appears on the corresponding record component, if this annotation type - * is applicable to a method declaration or type context." - * - * However, at this point in compilation, sufficient information to determine - * the ElementType targeted by the annotation doesn't exist and hence a blanket - * copy of annotation is done for now, and later (binding stage) irrelevant ones - * are weeded out. - */ - m.annotations = f.annotations; - - if (this.methods == null) { // Where is the constructor? - this.methods = new AbstractMethodDeclaration[] { m }; - } else { - AbstractMethodDeclaration[] newMethods; - System.arraycopy( - this.methods, - 0, - newMethods = new AbstractMethodDeclaration[this.methods.length + 1], - 1, - this.methods.length); - newMethods[0] = m; - this.methods = newMethods; - } - return m; - } @Override public boolean isRecord() { return true; @@ -381,16 +268,6 @@ public class RecordDeclaration extends TypeDeclaration { } } } - public void createImplicitRecordOverrideMethods(ProblemReporter problemReporter) { - TypeReference superClass = new QualifiedTypeReference(TypeConstants.JAVA_LANG_RECORD, new long[] {0}); - superClass.bits |= ASTNode.IsSuperType; - this.superclass = superClass; - - // TODO Auto-generated method stub - checkAndCreateImplicitequals(problemReporter); - checkAndCreateImplicitHashCode(problemReporter); - checkAndCreateImplicitToString(problemReporter); - } AbstractMethodDeclaration[] getMethod(char[] name1) { if (name1 == null || name1.length == 0 || this.methods == null) return null; @@ -401,203 +278,4 @@ public class RecordDeclaration extends TypeDeclaration { } return amList.toArray(new AbstractMethodDeclaration[0]); } - private void checkAndCreateImplicitToString(ProblemReporter problemReporter) { - if (null != getMethodByName(TypeConstants.TOSTRING)) - return; - QualifiedTypeReference returnType = new QualifiedTypeReference(JAVA_LANG_STRING, this.default_long_pos); - MethodDeclaration md = createMethodDeclaration(TypeConstants.TOSTRING, returnType); - MarkerAnnotation overrideAnnotation = new MarkerAnnotation(new SingleTypeReference(TypeConstants.JAVA_LANG_OVERRIDE[2], 0), 0); - md.annotations = new Annotation[] { overrideAnnotation }; - // getClass().getName() + "@" + Integer.toHexString(hashCode()) - Expression left = new StringLiteral(this.name, -1, -1, -1); - Expression right = new StringLiteral(new char[] {'@'}, -1, -1, -1); - left = new BinaryExpression(left, right, OperatorIds.PLUS); - MessageSend m = new MessageSend(); - m.receiver = new QualifiedNameReference( - TypeConstants.JAVA_LANG_INTEGER, - this.default_long_pos, -1, -1); - m.selector = "toHexString".toCharArray(); //$NON-NLS-1$ - MessageSend hc = new MessageSend(); - hc.receiver = new ThisReference(-1, -1); - hc.selector = TypeConstants.HASHCODE; - m.arguments = new Expression[] {hc}; - right = m; - CombinedBinaryExpression cbe = new CombinedBinaryExpression(left, right, OperatorIds.PLUS, 1); - md.statements = new Statement[] { new ReturnStatement(cbe, -1, -1) }; - md.isImplicit = true; - } - private static char[][] getBoxedName(char[] token) { - return - token == BYTE ? JAVA_LANG_BYTE : - token == SHORT ? JAVA_LANG_SHORT : - token == CHAR ? JAVA_LANG_CHARACTER : - token == INT ? JAVA_LANG_INTEGER : - token == LONG ? JAVA_LANG_LONG : - token == FLOAT ? JAVA_LANG_FLOAT : - token == DOUBLE ? JAVA_LANG_DOUBLE : - token == BOOLEAN ? JAVA_LANG_BOOLEAN : null; - } - private void checkAndCreateImplicitHashCode(ProblemReporter problemReporter) { - if (null != getMethodByName(TypeConstants.HASHCODE)) - return; - MethodDeclaration md = createMethodDeclaration(TypeConstants.HASHCODE, TypeReference.baseTypeReference(TypeIds.T_int, 0)); - MarkerAnnotation overrideAnnotation = new MarkerAnnotation(new SingleTypeReference(TypeConstants.JAVA_LANG_OVERRIDE[2], 0), 0); - md.annotations = new Annotation[] { overrideAnnotation }; - - List<Expression> initVals = new ArrayList<>(); - if (this.args != null) { - for (Argument arg : this.args) { - if (RecordDeclaration.disallowedComponentNames.contains(new String(arg.name))) - continue; - FieldReference fr = new FieldReference(arg.name, -1); - fr.receiver = new ThisReference(-1, -1); - Expression receiver = null; - if (arg.type instanceof ArrayTypeReference) { - MessageSend arraysHashCode = new MessageSend(); - arraysHashCode.selector = HASHCODE; - arraysHashCode.receiver = - new QualifiedNameReference(JAVA_UTIL_ARRAYS, - this.default_long_pos, -1, -1); - arraysHashCode.arguments = new Expression[] { fr}; - initVals.add(arraysHashCode); - continue; - } else if (arg.type.isBaseTypeReference()) { - QualifiedNameReference boxReference = new QualifiedNameReference( - RecordDeclaration.getBoxedName(arg.type.getLastToken()), - this.default_long_pos, -1, -1); - MessageSend m = new MessageSend(); - m.receiver = boxReference; - m.selector = TypeConstants.VALUEOF; // Integer.valueOf(int) - m.arguments = new Expression[] { fr }; - receiver = m; - } else { - receiver = fr; //this.field - } - MessageSend messageSend = new MessageSend(); - messageSend.receiver = receiver; - messageSend.selector = HASHCODE; - initVals.add(messageSend); - } - } - ArrayInitializer ai = new ArrayInitializer(); - ai.expressions = initVals.toArray(new Expression[0]); - ArrayAllocationExpression aae = new ArrayAllocationExpression(); - aae.dimensions = new Expression[1]; - aae.dimensions[0] = null; - aae.initializer = ai; - aae.type = new SingleTypeReference(INT, -1); - MessageSend arraysHashCode = new MessageSend(); - arraysHashCode.selector = HASHCODE; - arraysHashCode.receiver = - new QualifiedNameReference(JAVA_UTIL_ARRAYS, - this.default_long_pos, -1, -1); - arraysHashCode.arguments = new Expression[] { aae }; - md.statements = new Statement[] { new ReturnStatement(arraysHashCode, -1, -1) }; - md.isImplicit = true; - } - private AbstractMethodDeclaration getMethodByName(char[] name1) { - AbstractMethodDeclaration[] ams = getMethod(name1); - for (AbstractMethodDeclaration amd : ams) { - Argument[] args1 = amd.arguments; - if (args1 == null || args1.length == 0) - return amd; // explicit method exists, no need to create an implicit one. - } - return null; - } - private void checkAndCreateImplicitequals(ProblemReporter problemReporter) { - AbstractMethodDeclaration[] ams = getMethod(TypeConstants.EQUALS); - for (AbstractMethodDeclaration amd : ams) { - Argument[] args1 = amd.arguments; - if (args1 == null || args1.length != 1) - continue; - char[][] typeRef = args1[0].type != null ? args1[0].type.getTypeName() : null; - if (typeRef == null) - continue; - if (CharOperation.equals(typeRef, JAVA_LANG_OBJECT)) { - return; // explicit method exists, no need to create an implicit one. - } - } - // In case of just object, we can never be sure - so create one implicit method - // and at resolution time, remove the implicit one if there is a conflict. - // so, at this point we create one anyway. - MethodDeclaration md = createMethodDeclaration(TypeConstants.EQUALS, TypeReference.baseTypeReference(TypeIds.T_boolean, 0)); - MarkerAnnotation overrideAnnotation = new MarkerAnnotation(new SingleTypeReference(TypeConstants.JAVA_LANG_OVERRIDE[2], 0), 0); - md.annotations = new Annotation[] { overrideAnnotation }; - TypeReference objectTypeReference = new QualifiedTypeReference(JAVA_LANG_OBJECT, new long[] {0L, 0L, 0L}); - char[] objName = new char[] {'o','b','j'}; - md.arguments = new Argument[] { new Argument(objName, 0, objectTypeReference, 0)}; - - List<Statement> stmts = new ArrayList<>(); - // if (!(obj instanceof recordName)) - // return false; - InstanceOfExpression ioe = new InstanceOfExpression(new SingleNameReference(objName, 0), new SingleTypeReference(this.name, 0L)); - Expression condition = new UnaryExpression(ioe, OperatorIds.NOT); - Statement thenStatement = new ReturnStatement(new FalseLiteral(-1, -1), -1, -1); - IfStatement ifStatement = new IfStatement(condition, thenStatement, -1, -1); - stmts.add(ifStatement); - - // recordType o = (recordType) obj; - LocalDeclaration ld = new LocalDeclaration(new char[] {'o'}, -1, -1); - ld.type = new SingleTypeReference(this.name, -1); - ld.initialization = new CastExpression(new SingleNameReference(objName, -1), new SingleTypeReference(this.name, 0L)); - stmts.add(ld); - - // check each field - ref javadoc of java.lang.Record#equals - /* - * Impl Spec: The implicitly provided implementation returns true if and only if the argument is an - * instance of the same record type as this object, and each component of this record is equal to the - * corresponding component of the argument, according to java.util.Objects.equals(Object, Object) for - * components whose types are reference types, and according to the semantics of the equals method on - * the corresponding primitive wrapper type. - */ - if (this.args != null) { - for (Argument arg : this.args) { - if (RecordDeclaration.disallowedComponentNames.contains(new String(arg.name))) - continue; - FieldReference fr = new FieldReference(arg.name, -1); - fr.receiver = new ThisReference(-1, -1); - char [][] qfrName = new char[][] { {'o'}, arg.name }; - long [] qfrPos = {-1, -1}; - QualifiedNameReference ofr = new QualifiedNameReference(qfrName, qfrPos, -1, -1); - if (arg.type.isBaseTypeReference()) { - condition = new EqualExpression(fr, ofr, OperatorIds.NOT_EQUAL); - } else { - MessageSend messageSend = new MessageSend(); - messageSend.selector = TypeConstants.EQUALS; - messageSend.arguments = new Expression[] {fr, ofr}; - messageSend.receiver = new QualifiedNameReference(JAVA_UTIL_OBJECTS, new long[] {-1, -1, -1}, -1, -1); - condition = new UnaryExpression(messageSend, OperatorIds.NOT); - } - thenStatement = new ReturnStatement(new FalseLiteral(-1, -1), -1, -1); - ifStatement = new IfStatement(condition, thenStatement, -1, -1); - stmts.add(ifStatement); - } - } - stmts.add(new ReturnStatement(new TrueLiteral(-1, -1), -1, -1)); - md.statements = stmts.toArray(new Statement[0]); - md.isImplicit = true; - } - private MethodDeclaration createMethodDeclaration(char[] name1, TypeReference returnType) { - MethodDeclaration m = new MethodDeclaration(this.compilationResult); - m.selector = name1; - m.modifiers = ClassFileConstants.AccPublic; - - m.returnType = returnType; - - if (this.methods == null) { // Where is the constructor? - this.methods = new AbstractMethodDeclaration[] { m }; - } else { - AbstractMethodDeclaration[] newMethods; - System.arraycopy( - this.methods, - 0, - newMethods = new AbstractMethodDeclaration[this.methods.length + 1], - 1, - this.methods.length); - newMethods[0] = m; - this.methods = newMethods; - } - m.bits |= ASTNode.IsImplicit; - return m; - } } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/codegen/CodeStream.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/codegen/CodeStream.java index 42b9d2600a..fc295e1f29 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/codegen/CodeStream.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/codegen/CodeStream.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2019 IBM Corporation and others. + * Copyright (c) 2000, 2020 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -8,6 +8,10 @@ * * SPDX-License-Identifier: EPL-2.0 * + * This is an implementation of an early-draft specification developed under the Java + * Community Process (JCP) and is made available for testing and evaluation purposes + * only. The code is not compatible with any specification of the JCP. + * * Contributors: * IBM Corporation - initial API and implementation * Stephan Herrmann - Contribution for @@ -1972,7 +1976,6 @@ public void generateEmulationForMethod(Scope scope, MethodBinding methodBinding) iconst_1(); invokeAccessibleObjectSetAccessible(); } - /** * Generates the sequence of instructions which will perform the conversion of the expression * on the stack into a different type (e.g. long l = someInt; --> i2l must be inserted). @@ -3171,6 +3174,31 @@ public void generateSyntheticOuterArgumentValues(BlockScope currentScope, Refere } } } +public void generateSyntheticBodyForRecordEquals(SyntheticMethodBinding methodBinding, int index) { + initializeMaxLocals(methodBinding); + aload_0(); + aload_1(); + String sig = new String(methodBinding.signature()); + sig = sig.substring(0, 1)+ new String(methodBinding.declaringClass.signature()) + sig.substring(1); + invokeDynamic(index, methodBinding.parameters.length, 1, methodBinding.selector, sig.toCharArray()); + ireturn(); +} +public void generateSyntheticBodyForRecordHashCode(SyntheticMethodBinding methodBinding, int index) { + initializeMaxLocals(methodBinding); + aload_0(); + String sig = new String(methodBinding.signature()); + sig = sig.substring(0, 1)+ new String(methodBinding.declaringClass.signature()) + sig.substring(1); + invokeDynamic(index, methodBinding.parameters.length, 4, methodBinding.selector, sig.toCharArray()); + ireturn(); +} +public void generateSyntheticBodyForRecordToString(SyntheticMethodBinding methodBinding, int index) { + initializeMaxLocals(methodBinding); + aload_0(); + String sig = new String(methodBinding.signature()); + sig = sig.substring(0, 1)+ new String(methodBinding.declaringClass.signature()) + sig.substring(1); + invokeDynamic(index, methodBinding.parameters.length, 8, methodBinding.selector, sig.toCharArray()); + areturn(); +} public void generateUnboxingConversion(int unboxedTypeID) { switch (unboxedTypeID) { diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/codegen/ConstantPool.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/codegen/ConstantPool.java index 700a70daa5..9170fe1425 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/codegen/ConstantPool.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/codegen/ConstantPool.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2019 IBM Corporation and others. + * Copyright (c) 2000, 2020 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -8,6 +8,10 @@ * * SPDX-License-Identifier: EPL-2.0 * + * This is an implementation of an early-draft specification developed under the Java + * Community Process (JCP) and is made available for testing and evaluation purposes + * only. The code is not compatible with any specification of the JCP. + * * Contributors: * IBM Corporation - initial API and implementation * Jesper S Moller - Contributions for @@ -306,6 +310,8 @@ public class ConstantPool implements ClassFileConstants, TypeIds { public static final char[] AddSuppressedSignature = "(Ljava/lang/Throwable;)V".toCharArray(); //$NON-NLS-1$ public static final char[] Clone = "clone".toCharArray(); //$NON-NLS-1$ public static final char[] CloneSignature = "()Ljava/lang/Object;".toCharArray(); //$NON-NLS-1$ + public static final char[] BOOTSTRAP = "bootstrap".toCharArray(); //$NON-NLS-1$ + public static final char[] JAVA_LANG_RUNTIME_OBJECTMETHOD_BOOTSTRAP_SIGNATURE = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;".toCharArray(); //$NON-NLS-1$ /** * ConstantPool constructor comment. diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ClassScope.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ClassScope.java index a4359d4f43..00e777a48a 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ClassScope.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ClassScope.java @@ -331,7 +331,7 @@ public class ClassScope extends Scope { if (sourceType.areMethodsInitialized()) return; boolean isEnum = TypeDeclaration.kind(this.referenceContext.modifiers) == TypeDeclaration.ENUM_DECL; - if (this.referenceContext.methods == null && !isEnum) { + if (this.referenceContext.methods == null && !(isEnum || sourceType.isRecord())) { this.referenceContext.binding.setMethods(Binding.NO_METHODS); return; } @@ -384,6 +384,11 @@ public class ClassScope extends Scope { if (hasAbstractMethods) problemReporter().abstractMethodInConcreteClass(sourceType); } + if (sourceType.isRecord()) { + assert this.referenceContext instanceof RecordDeclaration; + methodBindings = sourceType.checkAndAddSyntheticRecordMethods(methodBindings, count); + count = methodBindings.length; + } if (count != methodBindings.length) System.arraycopy(methodBindings, 0, methodBindings = new MethodBinding[count], 0, count); sourceType.tagBits &= ~(TagBits.AreMethodsSorted|TagBits.AreMethodsComplete); // in case some static imports reached already into this type @@ -1007,6 +1012,8 @@ public class ClassScope extends Scope { } else if (superclass.erasure().id == TypeIds.T_JavaLangRecord) { if (!(this.referenceContext instanceof RecordDeclaration)) { problemReporter().recordCannotExtendRecord(sourceType, superclassRef, superclass); + } else { + return connectRecordSuperclass(); } } else if ((superclass.tagBits & TagBits.HierarchyHasProblems) != 0 || !superclassRef.resolvedType.isValidBinding()) { @@ -1024,7 +1031,7 @@ public class ClassScope extends Scope { } } sourceType.tagBits |= TagBits.HierarchyHasProblems; - sourceType.setSuperClass(getJavaLangObject()); + sourceType.setSuperClass(sourceType.isRecord() ? getJavaLangRecord() : getJavaLangObject()); if ((sourceType.superclass.tagBits & TagBits.BeginHierarchyCheck) == 0) detectHierarchyCycle(sourceType, sourceType.superclass, null); return false; // reported some error against the source type @@ -1067,6 +1074,16 @@ public class ClassScope extends Scope { return !foundCycle; } + private boolean connectRecordSuperclass() { + SourceTypeBinding sourceType = this.referenceContext.binding; + ReferenceBinding rootRecordType = getJavaLangRecord(); + sourceType.setSuperClass(rootRecordType); + if ((rootRecordType.tagBits & TagBits.HasMissingType) != 0) { + sourceType.tagBits |= TagBits.HierarchyHasProblems; // mark missing supertpye + return false; + } + return !detectHierarchyCycle(sourceType, rootRecordType, null); + } /* Our current belief based on available JCK 1.3 tests is: inherited member types are visible as a potential superclass. diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java index 12446b0c1c..dab1383591 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java @@ -2120,7 +2120,7 @@ protected int applyCloseableClassWhitelists(CompilerOptions options) { protected boolean hasMethodWithNumArgs(char[] selector, int numArgs) { for (MethodBinding methodBinding : unResolvedMethods()) { - if (CharOperation.equals(methodBinding.selector, TypeConstants.CLOSE) + if (CharOperation.equals(methodBinding.selector, selector) && methodBinding.parameters.length == numArgs) { return true; } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/Scope.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/Scope.java index 0742835abe..525857c264 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/Scope.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/Scope.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2019 IBM Corporation and others. + * Copyright (c) 2000, 2020 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -8,6 +8,10 @@ * * SPDX-License-Identifier: EPL-2.0 * + * This is an implementation of an early-draft specification developed under the Java + * Community Process (JCP) and is made available for testing and evaluation purposes + * only. The code is not compatible with any specification of the JCP. + * * Contributors: * IBM Corporation - initial API and implementation * Stephan Herrmann - Contributions for @@ -2859,6 +2863,11 @@ public abstract class Scope { return unitScope.environment.getResolvedJavaBaseType(TypeConstants.JAVA_LANG_ENUM, this); } + public final ReferenceBinding getJavaLangRuntimeObjectMethods() { + CompilationUnitScope unitScope = compilationUnitScope(); + unitScope.recordQualifiedReference(TypeConstants.JAVA_LANG_RUNTIME_OBJECTMETHODS); + return unitScope.environment.getResolvedJavaBaseType(TypeConstants.JAVA_LANG_RUNTIME_OBJECTMETHODS, this); + } public final ReferenceBinding getJavaLangInvokeLambdaMetafactory() { CompilationUnitScope unitScope = compilationUnitScope(); unitScope.recordQualifiedReference(TypeConstants.JAVA_LANG_INVOKE_LAMBDAMETAFACTORY); @@ -2889,6 +2898,12 @@ public abstract class Scope { return unitScope.environment.getResolvedJavaBaseType(TypeConstants.JAVA_LANG_OBJECT, this); } + public final ReferenceBinding getJavaLangRecord() { + CompilationUnitScope unitScope = compilationUnitScope(); + unitScope.recordQualifiedReference(TypeConstants.JAVA_LANG_RECORD); + return unitScope.environment.getResolvedJavaBaseType(TypeConstants.JAVA_LANG_RECORD, this); + } + public final ReferenceBinding getJavaLangString() { CompilationUnitScope unitScope = compilationUnitScope(); unitScope.recordQualifiedReference(TypeConstants.JAVA_LANG_STRING); diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java index 1ccd6b5eb6..23415bf888 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2019 IBM Corporation and others. + * Copyright (c) 2000, 2020 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -58,6 +58,7 @@ package org.eclipse.jdt.internal.compiler.lookup; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; @@ -133,6 +134,7 @@ public class SourceTypeBinding extends ReferenceBinding { public HashSet<SourceTypeBinding> nestMembers; private boolean isRecordDeclaration = false; + private FieldBinding[] recordComponents; // cache public SourceTypeBinding(char[][] compoundName, PackageBinding fPackage, ClassScope scope) { this.compoundName = compoundName; this.fPackage = fPackage; @@ -844,6 +846,110 @@ public SyntheticMethodBinding addSyntheticBridgeMethod(MethodBinding inheritedMe } return accessMethod; } +/* JLS 14 Record - Preview - begin */ +public MethodBinding[] checkAndAddSyntheticRecordMethods(MethodBinding[] methodBindings, int count) { + if (!this.isRecordDeclaration) + return methodBindings; + List<MethodBinding> implicitMethods = checkAndAddSyntheticRecordComponentAccessors(methodBindings); + implicitMethods = checkAndAddSyntheticRecordOverrideMethods(methodBindings, implicitMethods); + for (int i = 0; i < count; ++i) + implicitMethods.add(methodBindings[i]); + return implicitMethods.toArray(new MethodBinding[0]); +} +public List<MethodBinding> checkAndAddSyntheticRecordOverrideMethods(MethodBinding[] methodBindings, List<MethodBinding> implicitMethods) { + if (!hasMethodWithNumArgs(TypeConstants.TOSTRING, 0)) { + MethodBinding m = addSyntheticRecordOverrideMethod(TypeConstants.TOSTRING, implicitMethods.size()); + implicitMethods.add(m); + } + if (!hasMethodWithNumArgs(TypeConstants.HASHCODE, 0)) { + MethodBinding m = addSyntheticRecordOverrideMethod(TypeConstants.HASHCODE, implicitMethods.size()); + implicitMethods.add(m); + } + boolean isEqualsPresent = Arrays.stream(methodBindings) + .filter(m -> CharOperation.equals(TypeConstants.EQUALS, m.selector)) + .anyMatch(m -> m.parameters != null || m.parameters.length == 1 && + m.parameters[0].equals(this.scope.getJavaLangObject())); + if (!isEqualsPresent) { + MethodBinding m = addSyntheticRecordOverrideMethod(TypeConstants.EQUALS, implicitMethods.size()); + implicitMethods.add(m); + } + return implicitMethods; +} +public List<MethodBinding> checkAndAddSyntheticRecordComponentAccessors(MethodBinding[] methodBindings) { + List<MethodBinding> implicitMethods = new ArrayList<>(0); + if (this.fields == null) + return implicitMethods; + // JLS 14 8.10.3 Item 2 create the accessors for the fields if required + /* + * An implicitly declared public accessor method with the same name as the record component, + * whose return type is the declared type of the record component, + * unless a public method with the same signature is explicitly declared in the body of the declaration of R. + */ + + List<String> missingNames = Arrays.stream(this.fields) // initialize with all the record components + .filter(f -> f.isRecordComponent()) + .map(f -> new String(f.name)) + .collect(Collectors.toList()); + + if (this.methods != null) { + List<String> candidates = + Arrays.stream(methodBindings) + .filter(m -> m.selector != null && m.selector.length > 0) + .filter(m -> missingNames.contains(new String(m.selector))) + .filter(m -> m.parameters == null || m.parameters.length == 0) + .map(m -> new String(m.selector)) + .collect(Collectors.toList()); + missingNames.removeAll(candidates); + } + int missingCount = missingNames.size(); + for (int i = 0; i < missingCount; ++i) { + implicitMethods.add(addSyntheticRecordComponentAccessor(missingNames.get(i).toCharArray(), i)); + } + return implicitMethods; +} +/* Add a new synthetic component accessor for the recordtype. Selector should be identical to component name. + * char[] component name of the record +*/ +public SyntheticMethodBinding addSyntheticRecordComponentAccessor(char[] selector, int index) { + if (!isPrototype()) throw new IllegalStateException(); + if (this.synthetics == null) + this.synthetics = new HashMap[MAX_SYNTHETICS]; + if (this.synthetics[SourceTypeBinding.METHOD_EMUL] == null) + this.synthetics[SourceTypeBinding.METHOD_EMUL] = new HashMap(5); + + SyntheticMethodBinding accessMethod = null; + SyntheticMethodBinding[] accessors = (SyntheticMethodBinding[]) this.synthetics[SourceTypeBinding.METHOD_EMUL].get(selector); + accessMethod = new SyntheticMethodBinding(this, getField(selector, true), index); + if (accessors == null) { + this.synthetics[SourceTypeBinding.METHOD_EMUL].put(selector, accessors = new SyntheticMethodBinding[2]); + accessors[0] = accessMethod; + } else { + if ((accessMethod = accessors[0]) == null) { + accessors[0] = accessMethod; + } + } + return accessMethod; +} +public SyntheticMethodBinding addSyntheticRecordOverrideMethod(char[] selector, int index) { + if (this.synthetics == null) + this.synthetics = new HashMap[MAX_SYNTHETICS]; + if (this.synthetics[SourceTypeBinding.METHOD_EMUL] == null) + this.synthetics[SourceTypeBinding.METHOD_EMUL] = new HashMap(5); + + SyntheticMethodBinding accessMethod = null; + SyntheticMethodBinding[] accessors = (SyntheticMethodBinding[]) this.synthetics[SourceTypeBinding.METHOD_EMUL].get(selector); + accessMethod = new SyntheticMethodBinding(this, selector, index); + if (accessors == null) { + this.synthetics[SourceTypeBinding.METHOD_EMUL].put(selector, accessors = new SyntheticMethodBinding[2]); + accessors[0] = accessMethod; + } else { + if ((accessMethod = accessors[0]) == null) { + accessors[0] = accessMethod; + } + } + return accessMethod; +} +/* JLS 14 Record - Preview - end */ boolean areFieldsInitialized() { if (!isPrototype()) return this.prototype.areFieldsInitialized(); @@ -987,6 +1093,7 @@ public FieldBinding[] fields() { } } this.tagBits |= TagBits.AreFieldsComplete; + computeRecordComponents(); return this.fields; } /** @@ -2375,7 +2482,7 @@ protected boolean hasMethodWithNumArgs(char[] selector, int numArgs) { // otherwise don't trigger unResolvedMethods() which would actually resolve! if (this.scope != null) { for (AbstractMethodDeclaration method : this.scope.referenceContext.methods) { - if (CharOperation.equals(method.selector, TypeConstants.CLOSE)) { + if (CharOperation.equals(method.selector, selector)) { if (numArgs == 0) { if (method.arguments == null) return true; @@ -2811,24 +2918,26 @@ public List<String> getNestMembers() { .collect(Collectors.toList()); return list; } -/* Get the field bindings in the order of record component declaration */ -public List<FieldBinding> getRecordComponents() { - if (!this.isRecordDeclaration) - return null; - RecordDeclaration rd = (RecordDeclaration) this.scope.referenceContext; +/* Get the field bindings in the order of record component declaration + * should be called only after a called to fields() */ +public FieldBinding[] getRecordComponents() { + return this.recordComponents; +} +public void computeRecordComponents() { + if (!this.isRecordDeclaration || this.recordComponents != null) + return; + List<String> recordComponentNames = Arrays.stream(((RecordDeclaration) this.scope.referenceContext).getArgs()) + .map(arg -> new String(arg.name)) + .collect(Collectors.toList()); List<FieldBinding> list = new ArrayList<>(); - Argument[] args = rd.getArgs(); - if (args != null) { - for (Argument arg : args) { - for (FieldBinding f : this.fields) { - if (CharOperation.equals(f.name, arg.name)) { - list.add(f); - continue; - } + for (String rc : recordComponentNames) { + for (FieldBinding f : this.fields) { + if (rc.equals(new String(f.name))) { + list.add(f); } } } - return list; + this.recordComponents = list.toArray(new FieldBinding[0]); } public void cleanUp() { diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SyntheticMethodBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SyntheticMethodBinding.java index fd03c25fbb..e91eee8ce6 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SyntheticMethodBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SyntheticMethodBinding.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2017 IBM Corporation and others. + * Copyright (c) 2000, 2020 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -8,6 +8,10 @@ * * SPDX-License-Identifier: EPL-2.0 * + * This is an implementation of an early-draft specification developed under the Java + * Community Process (JCP) and is made available for testing and evaluation purposes + * only. The code is not compatible with any specification of the JCP. + * * Contributors: * IBM Corporation - initial API and implementation * Stephan Herrmann - Contribution for @@ -72,13 +76,15 @@ public class SyntheticMethodBinding extends MethodBinding { * Is never directly materialized in bytecode */ public static final int SerializableMethodReference = 18; + public static final int RecordOverrideToString = 19; + public static final int RecordOverrideHashCode = 20; + public static final int RecordOverrideEquals = 21; public int sourceStart = 0; // start position of the matching declaration public int index; // used for sorting access methods in the class file public int fakePaddedParameters = 0; // added in synthetic constructor to avoid name clash. public SyntheticMethodBinding(FieldBinding targetField, boolean isReadAccess, boolean isSuperAccess, ReferenceBinding declaringClass) { - this.modifiers = ClassFileConstants.AccDefault | ClassFileConstants.AccStatic | ClassFileConstants.AccSynthetic; this.tagBits |= (TagBits.AnnotationResolved | TagBits.DeprecatedAnnotationResolved); SourceTypeBinding declaringSourceType = (SourceTypeBinding) declaringClass; @@ -151,7 +157,6 @@ public class SyntheticMethodBinding extends MethodBinding { setSelector(CharOperation.concat(TypeConstants.SYNTHETIC_ACCESS_METHOD_PREFIX, String.valueOf(++methodId).toCharArray())); } } while (needRename); - // retrieve sourceStart position for the target field for line number attributes FieldDeclaration[] fieldDecls = declaringSourceType.scope.referenceContext.fields; if (fieldDecls != null) { @@ -440,6 +445,56 @@ public class SyntheticMethodBinding extends MethodBinding { this.index = methodId; } + public SyntheticMethodBinding(ReferenceBinding declaringClass, FieldBinding targetField, int index) { + SourceTypeBinding declaringSourceType = (SourceTypeBinding) declaringClass; + assert declaringSourceType.isRecord(); + this.modifiers = ClassFileConstants.AccPublic; + this.tagBits |= (TagBits.AnnotationResolved | TagBits.DeprecatedAnnotationResolved); + this.parameters = Binding.NO_PARAMETERS; + this.returnType = targetField.type; + this.selector = targetField.name; + this.targetReadField = targetField; + this.purpose = SyntheticMethodBinding.FieldReadAccess; + this.thrownExceptions = Binding.NO_EXCEPTIONS; + this.declaringClass = declaringSourceType; + this.setTypeAnnotations(targetField.getAnnotations()); + this.index = index; + + // retrieve sourceStart position for the target field for line number attributes + FieldDeclaration[] fieldDecls = declaringSourceType.scope.referenceContext.fields; + if (fieldDecls != null) { + for (int i = 0, max = fieldDecls.length; i < max; i++) { + if (fieldDecls[i].binding == targetField) { + this.sourceStart = fieldDecls[i].sourceStart; + return; + } + } + } + this.sourceStart = declaringSourceType.scope.referenceContext.sourceStart; + } + public SyntheticMethodBinding(ReferenceBinding declaringClass, char[] selector, int index) { + SourceTypeBinding declaringSourceType = (SourceTypeBinding) declaringClass; + assert declaringSourceType.isRecord(); + this.declaringClass = declaringSourceType; + this.modifiers = ClassFileConstants.AccPublic; + this.tagBits |= (TagBits.AnnotationResolved | TagBits.DeprecatedAnnotationResolved); + this.selector = selector; + this.thrownExceptions = Binding.NO_EXCEPTIONS; + if (selector == TypeConstants.TOSTRING) { + this.returnType = declaringSourceType.scope.getJavaLangString(); + this.parameters = Binding.NO_PARAMETERS; + this.purpose = SyntheticMethodBinding.RecordOverrideToString; + } else if (selector == TypeConstants.HASHCODE) { + this.returnType = TypeBinding.INT; + this.parameters = Binding.NO_PARAMETERS; + this.purpose = SyntheticMethodBinding.RecordOverrideHashCode; + } else if (selector == TypeConstants.EQUALS) { + this.returnType = TypeBinding.BOOLEAN; + this.parameters = new TypeBinding[] {declaringSourceType.scope.getJavaLangObject()}; + this.purpose = SyntheticMethodBinding.RecordOverrideEquals; + } + this.index = index; + } /** * An constructor accessor is a constructor with an extra argument (declaringClass), in case of * collision with an existing constructor, then add again an extra argument (declaringClass again). diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeConstants.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeConstants.java index 34dc674f0d..00c2ab08a8 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeConstants.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeConstants.java @@ -237,6 +237,7 @@ public interface TypeConstants { char[][] JAVA_LANG_INVOKE_METHODHANDLES = {JAVA, LANG, INVOKE, "MethodHandles".toCharArray()}; //$NON-NLS-1$ char[][] JAVA_LANG_AUTOCLOSEABLE = {JAVA, LANG, "AutoCloseable".toCharArray()}; //$NON-NLS-1$ char[] CLOSE = "close".toCharArray(); //$NON-NLS-1$ + char[][] JAVA_LANG_RUNTIME_OBJECTMETHODS = {JAVA, LANG, RUNTIME, "ObjectMethods".toCharArray()}; //$NON-NLS-1$ // known helper functions for closing a Closeable (all receive a Closeable as their first argument): public static class CloseMethodRecord { public char[][] typeName; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Parser.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Parser.java index 9cbb80d014..5b4254fa7f 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Parser.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/Parser.java @@ -10710,8 +10710,9 @@ protected void consumeRecordDeclaration() { if (length == 0 && !containsComment(rd.bodyStart, rd.bodyEnd)) { rd.bits |= ASTNode.UndocumentedEmptyBlock; } - rd.createImplicitAccessors(this.problemReporter); - rd.createImplicitRecordOverrideMethods(this.problemReporter); + TypeReference superClass = new QualifiedTypeReference(TypeConstants.JAVA_LANG_RECORD, new long[] {0}); + superClass.bits |= ASTNode.IsSuperType; + rd.superclass = superClass; rd.declarationSourceEnd = flushCommentsDefinedPriorTo(this.endStatementPosition); } protected void consumeRecordHeaderPart() { @@ -10747,6 +10748,8 @@ protected void consumeRecordComponentHeaderRightParen() { length); rd.setArgs(args); convertToFields(rd, args); + } else { + rd.setArgs(ASTNode.NO_ARGUMENTS); } rd.bodyStart = this.rParenPos+1; this.listLength = 0; // reset this.listLength after having read all parameters |