8332600: javac uses record components source position during compilation

Reviewed-by: jlahoda
This commit is contained in:
Vicente Romero 2024-07-18 15:54:41 +00:00
parent 5f7b0072cf
commit 245c086648
3 changed files with 31 additions and 24 deletions
src/jdk.compiler/share/classes/com/sun/tools/javac
test/langtools/tools/javac/records

@ -1564,29 +1564,21 @@ public abstract class Symbol extends AnnoConstruct implements PoolConstant, Elem
return null; return null;
} }
public RecordComponent findRecordComponentToRemove(JCVariableDecl var) {
RecordComponent toRemove = null;
for (RecordComponent rc : recordComponents) {
/* it could be that a record erroneously declares two record components with the same name, in that
* case we need to use the position to disambiguate, but if we loaded the record from a class file
* all positions will be -1, in that case we have to ignore the position and match only based on the
* name
*/
if (rc.name == var.name && (var.pos == rc.pos || rc.pos == -1)) {
toRemove = rc;
}
}
return toRemove;
}
/* creates a record component if non is related to the given variable and recreates a brand new one /* creates a record component if non is related to the given variable and recreates a brand new one
* in other case * in other case
*/ */
public RecordComponent createRecordComponent(RecordComponent existing, JCVariableDecl rcDecl, VarSymbol varSym) { public RecordComponent createRecordComponent(RecordComponent existing, JCVariableDecl rcDecl, VarSymbol varSym) {
RecordComponent rc = null; RecordComponent rc = null;
if (existing != null) { if (existing != null && !recordComponents.isEmpty()) {
recordComponents = List.filter(recordComponents, existing); ListBuffer<RecordComponent> newRComps = new ListBuffer<>();
recordComponents = recordComponents.append(rc = new RecordComponent(varSym, existing.ast, existing.isVarargs)); for (RecordComponent rcomp : recordComponents) {
if (existing == rcomp) {
newRComps.add(rc = new RecordComponent(varSym, existing.ast, existing.isVarargs));
} else {
newRComps.add(rcomp);
}
}
recordComponents = newRComps.toList();
} else { } else {
// Didn't find the record component: create one. // Didn't find the record component: create one.
recordComponents = recordComponents.append(rc = new RecordComponent(varSym, rcDecl)); recordComponents = recordComponents.append(rc = new RecordComponent(varSym, rcDecl));

@ -1048,6 +1048,7 @@ public class TypeEnter implements Completer {
if ((sym.flags_field & RECORD) != 0) { if ((sym.flags_field & RECORD) != 0) {
List<JCVariableDecl> fields = TreeInfo.recordFields(tree); List<JCVariableDecl> fields = TreeInfo.recordFields(tree);
int fieldPos = 0;
for (JCVariableDecl field : fields) { for (JCVariableDecl field : fields) {
/** Some notes regarding the code below. Annotations applied to elements of a record header are propagated /** Some notes regarding the code below. Annotations applied to elements of a record header are propagated
* to other elements which, when applicable, not explicitly declared by the user: the canonical constructor, * to other elements which, when applicable, not explicitly declared by the user: the canonical constructor,
@ -1063,24 +1064,24 @@ public class TypeEnter implements Completer {
* copying the original annotations from the record component to the corresponding field, again this applies * copying the original annotations from the record component to the corresponding field, again this applies
* only if APs are present. * only if APs are present.
* *
* First, we find the record component by comparing its name and position with current field, * First, we get the record component matching the field position. Then we copy the annotations
* if any, and we mark it. Then we copy the annotations to the field so that annotations applicable only to the record component * to the field so that annotations applicable only to the record component
* can be attributed, as if declared in the field, and then stored in the metadata associated to the record * can be attributed, as if declared in the field, and then stored in the metadata associated to the record
* component. The invariance we need to keep here is that record components must be scheduled for * component. The invariance we need to keep here is that record components must be scheduled for
* annotation only once during this process. * annotation only once during this process.
*/ */
RecordComponent rc = sym.findRecordComponentToRemove(field); RecordComponent rc = getRecordComponentAt(sym, fieldPos);
if (rc != null && (rc.getOriginalAnnos().length() != field.mods.annotations.length())) { if (rc != null && (rc.getOriginalAnnos().length() != field.mods.annotations.length())) {
TreeCopier<JCTree> tc = new TreeCopier<>(make.at(field.pos)); TreeCopier<JCTree> tc = new TreeCopier<>(make.at(field.pos));
List<JCAnnotation> originalAnnos = tc.copy(rc.getOriginalAnnos()); field.mods.annotations = tc.copy(rc.getOriginalAnnos());
field.mods.annotations = originalAnnos;
} }
memberEnter.memberEnter(field, env); memberEnter.memberEnter(field, env);
JCVariableDecl rcDecl = new TreeCopier<JCTree>(make.at(field.pos)).copy(field); JCVariableDecl rcDecl = new TreeCopier<JCTree>(make.at(field.pos)).copy(field);
sym.createRecordComponent(rc, rcDecl, field.sym); sym.createRecordComponent(rc, rcDecl, field.sym);
fieldPos++;
} }
enterThisAndSuper(sym, env); enterThisAndSuper(sym, env);
@ -1095,6 +1096,18 @@ public class TypeEnter implements Completer {
} }
} }
// where
private RecordComponent getRecordComponentAt(ClassSymbol sym, int componentPos) {
int i = 0;
for (RecordComponent rc : sym.getRecordComponents()) {
if (i == componentPos) {
return rc;
}
i++;
}
return null;
}
/** Enter member fields and methods of a class /** Enter member fields and methods of a class
*/ */
private final class MembersPhase extends AbstractMembersPhase { private final class MembersPhase extends AbstractMembersPhase {

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -26,6 +26,7 @@
* *
* @test * @test
* @bug 8250629 8252307 8247352 8241151 8246774 8259025 8288130 8282714 8289647 8294020 * @bug 8250629 8252307 8247352 8241151 8246774 8259025 8288130 8282714 8289647 8294020
* 8332600
* @summary Negative compilation tests, and positive compilation (smoke) tests for records * @summary Negative compilation tests, and positive compilation (smoke) tests for records
* @library /lib/combo /tools/lib /tools/javac/lib * @library /lib/combo /tools/lib /tools/javac/lib
* @enablePreview * @enablePreview
@ -136,6 +137,7 @@ class RecordCompilationTests extends CompilationTestCase {
assertFail("compiler.err.repeated.modifier", "public public record R(String foo) { }"); assertFail("compiler.err.repeated.modifier", "public public record R(String foo) { }");
assertFail("compiler.err.repeated.modifier", "private private record R(String foo) { }"); assertFail("compiler.err.repeated.modifier", "private private record R(String foo) { }");
assertFail("compiler.err.already.defined", "record R(int x, int x) {}"); assertFail("compiler.err.already.defined", "record R(int x, int x) {}");
assertFail("compiler.err.already.defined", "record R(int x, int x, int x) {}");
for (String s : List.of("var", "record")) for (String s : List.of("var", "record"))
assertFail("compiler.err.restricted.type.not.allowed.here", "record R(# x) { }", s); assertFail("compiler.err.restricted.type.not.allowed.here", "record R(# x) { }", s);
for (String s : List.of("public", "protected", "private", "static", "final", "transient", "volatile", for (String s : List.of("public", "protected", "private", "static", "final", "transient", "volatile",