Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions checker/jtreg/stubs/recordstubclass/Outer.astub
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package recordstubclass;

import org.checkerframework.checker.nullness.qual.Nullable;

public class Outer {
// The class is a record here but a plain class in the library, as when the annotated JDK
// (written for a newer JDK) declares a record that the JDK running the checker has as a class.
// Component "missing" has no field in the library.
public record Inner(Object f, Object missing) {}

// Declared after the record: it was lost when the record aborted the processing of Outer.
public @Nullable Object get();
}
12 changes: 12 additions & 0 deletions checker/jtreg/stubs/recordstubclass/Outer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package recordstubclass;

/** A library class whose nested class is a record in the annotated stub (see Outer.astub). */
public class Outer {
public static class Inner {
public final Object f = new Object();
}

public Object get() {
return new Object();
}
}
18 changes: 18 additions & 0 deletions checker/jtreg/stubs/recordstubclass/Use.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* @test
* @summary A record in a stub file whose library type is a class must not stop the processing of
* the members that follow it.
*
* @compile Outer.java
* @compile/fail/ref=Use.out -XDrawDiagnostics -processor org.checkerframework.checker.nullness.NullnessChecker -Anomsgtext -Astubs=Outer.astub Use.java
*/

package recordstubclass;

import org.checkerframework.checker.nullness.qual.NonNull;

public class Use {
void g(Outer o) {
@NonNull Object x = o.get();
}
}
4 changes: 4 additions & 0 deletions checker/jtreg/stubs/recordstubclass/Use.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- compiler.warn.proc.messager: Outer.astub:(line 9,col 35): field missing not found in type recordstubclass.Outer.Inner
Use.java:16:34: compiler.err.proc.messager: (assignment.type.incompatible)
1 error
1 warning
4 changes: 4 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ Command-line option additions and changes:
- Invalid option arguments (e.g. malformed regex) now produce compiler errors
instead of stack traces.

A stub file that declares a record where the running JDK has a class (for example
the JDK 21 annotated JDK on JDK 8 to 17) no longer loses the annotations of the
members that follow the record.

### Implementation details

Performance optimizations:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1194,14 +1194,21 @@ private String javaParserNodeToStringTruncated(Node n, int length) {
Map<String, RecordComponentStub> byName =
ArrayMap.newArrayMapOrLinkedHashMap(recordMembers.size());
for (Parameter recordMember : recordMembers) {
RecordComponentStub stub =
processRecordField(
recordMember,
findFieldElement(
typeElt, recordMember.getNameAsString(), recordMember));
byName.put(recordMember.getNameAsString(), stub);
VariableElement componentElt =
findFieldElement(typeElt, recordMember.getNameAsString(), recordMember);
// Null if the type is a class without a field for the component; see
// findFieldElement, which already reported it.
if (componentElt != null) {
byName.put(
recordMember.getNameAsString(),
processRecordField(recordMember, componentElt));
}
}
// A record in the stub file can be a class in the running JDK; only a record element
// has components, and the binary stub files likewise store no record for a class.
if (ElementUtils.isRecordElement(typeElt)) {
putMergeRecords(recordDecl.getFullyQualifiedName().get(), new RecordStub(byName));
}
putMergeRecords(recordDecl.getFullyQualifiedName().get(), new RecordStub(byName));
}

Pair<Map<Element, BodyDeclaration<?>>, Map<Element, List<BodyDeclaration<?>>>> members =
Expand Down Expand Up @@ -1236,8 +1243,11 @@ private String javaParserNodeToStringTruncated(Node n, int length) {
break;
case CLASS:
case INTERFACE:
// The declaration is a record when the stub file was written for a newer JDK
// than the one that is running: a class that became a record keeps its name,
// and its element is still a class here.
// Not processing an ajava file, so ignore the return value.
processTypeDecl((ClassOrInterfaceDeclaration) decl, innerName, null);
processTypeDecl((TypeDeclaration<?>) decl, innerName, null);
break;
case ENUM:
// Not processing an ajava file, so ignore the return value.
Expand Down
Loading