Skip to content

Commit c4dbf02

Browse files
SONARJAVA-5573 Properly distinguish type parameter annotations from annotations that are applied on the symbol.
- Ensure that the SymbolMetadata.annotation() interface returns exactly the same content - Create a new SymbolMetadata.symbolAnnotation() to filter out type parameter annotations. This is used by the nullability computation.
1 parent 911dd47 commit c4dbf02

File tree

8 files changed

+96
-16
lines changed

8 files changed

+96
-16
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package annotations.nullability.no_default;
2+
3+
4+
import java.util.List;
5+
import java.util.Map;
6+
import org.jspecify.annotations.NonNull;
7+
import org.jspecify.annotations.Nullable;
8+
9+
/**
10+
* This check ensures that type parameters and nested annotations are properly handled by JSymbolMetadata
11+
*/
12+
public class NullabilityOfParametrizedTypes {
13+
14+
public Map<Object, Object> id1000_type_NO_ANNOTATION_level_PACKAGE;
15+
public List<Object> id1001_type_NO_ANNOTATION_level_PACKAGE;
16+
17+
public Map<Object, @Nullable Object> id1002_type_NO_ANNOTATION_level_PACKAGE;
18+
public List<@Nullable Object> id1003_type_NO_ANNOTATION_level_PACKAGE;
19+
20+
public Map<Object, @NonNull Object> id1004_type_NO_ANNOTATION_level_PACKAGE;
21+
public List<@NonNull Object> id1005_type_NO_ANNOTATION_level_PACKAGE;
22+
23+
public Map<Object, @org.eclipse.jdt.annotation.Nullable Object> id1006_type_NO_ANNOTATION_level_PACKAGE;
24+
public List<@org.eclipse.jdt.annotation.Nullable Object> id1007_type_NO_ANNOTATION_level_PACKAGE;
25+
26+
public Map.@Nullable Entry<@NonNull Object, @NonNull Object> id1008_type_STRONG_NULLABLE_level_VARIABLE;
27+
28+
public List<@Nullable Object> id1009_type_NO_ANNOTATION_level_PACKAGE(
29+
Map<Object, @NonNull Object> id10010_type_NO_ANNOTATION_level_PACKAGE
30+
) {
31+
return List.of();
32+
}
33+
}

java-frontend/src/main/java/org/sonar/java/model/JSymbol.java

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -383,29 +383,41 @@ private SymbolMetadata convertMetadata() {
383383
return new JSymbolMetadata(sema, this, sema.resolvePackageAnnotations(binding.getName()));
384384
case IBinding.VARIABLE:
385385
ITypeBinding type = ((IVariableBinding) binding).getType();
386-
return new JSymbolMetadata(
387-
sema,
388-
this,
389-
type == null ? new IAnnotationBinding[0] : getAnnotations(type),
390-
binding.getAnnotations());
386+
if (type == null) {
387+
return new JSymbolMetadata(sema, this, binding.getAnnotations());
388+
}
389+
return convertMetadata(type);
391390
case IBinding.METHOD:
392391
ITypeBinding returnType = ((IMethodBinding) binding).getReturnType();
393392
// In rare circumstances, when the semantic information is incomplete, returnType can be null.
394393
if (returnType == null) {
395394
return Symbols.EMPTY_METADATA;
396395
}
397-
return new JSymbolMetadata(sema, this, getAnnotations(returnType), binding.getAnnotations());
396+
return convertMetadata(returnType);
398397
default:
399398
return new JSymbolMetadata(sema, this, binding.getAnnotations());
400399
}
401400
}
402401

403-
private static IAnnotationBinding[] getAnnotations(ITypeBinding type) {
402+
private SymbolMetadata convertMetadata(ITypeBinding type) {
403+
var symbolAnnotations = new IAnnotationBinding[binding.getAnnotations().length + type.getTypeAnnotations().length];
404+
System.arraycopy(binding.getAnnotations(), 0, symbolAnnotations, 0, binding.getAnnotations().length);
405+
System.arraycopy(type.getTypeAnnotations(), 0, symbolAnnotations, binding.getAnnotations().length, type.getTypeAnnotations().length);
406+
407+
var parameterAnnotations = getParamAnnotations(type);
408+
409+
return new JSymbolMetadata(
410+
sema,
411+
this,
412+
symbolAnnotations, parameterAnnotations
413+
);
414+
}
415+
416+
private static IAnnotationBinding[] getParamAnnotations(ITypeBinding type) {
404417
List<IAnnotationBinding> iAnnotationBindings = new ArrayList<>();
405418
for (ITypeBinding typeArgument : type.getTypeArguments()) {
406419
Collections.addAll(iAnnotationBindings, typeArgument.getTypeAnnotations());
407420
}
408-
Collections.addAll(iAnnotationBindings, type.getTypeAnnotations());
409421
return iAnnotationBindings.toArray(new IAnnotationBinding[0]);
410422
}
411423

java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadata.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,26 +48,34 @@ final class JSymbolMetadata implements SymbolMetadata {
4848
private final JSema sema;
4949
private final Symbol symbol;
5050
private final IAnnotationBinding[] annotationBindings;
51+
private final IAnnotationBinding[] symbolAnnotation;
5152

5253
/**
5354
* Cache for {@link #annotations()}.
5455
*/
5556
private List<AnnotationInstance> annotations;
5657

58+
/**
59+
* Cache for {@link #symbolAnnotations()}.
60+
*/
61+
private List<AnnotationInstance> symbolAnnotations;
62+
5763
private final Map<NullabilityTarget, NullabilityData> nullabilityCache = new EnumMap<>(NullabilityTarget.class);
5864

5965
JSymbolMetadata(JSema sema, Symbol symbol, IAnnotationBinding[] annotationBindings) {
6066
this.sema = Objects.requireNonNull(sema);
6167
this.symbol = symbol;
68+
this.symbolAnnotation = annotationBindings;
6269
this.annotationBindings = annotationBindings;
6370
}
6471

65-
JSymbolMetadata(JSema sema, Symbol symbol, IAnnotationBinding[] typeAnnotationBindings, IAnnotationBinding[] annotationBindings) {
72+
JSymbolMetadata(JSema sema, Symbol symbol, IAnnotationBinding[] symbolAnnotation, IAnnotationBinding[] paramAnnotations) {
6673
this.sema = Objects.requireNonNull(sema);
6774
this.symbol = symbol;
68-
this.annotationBindings = new IAnnotationBinding[typeAnnotationBindings.length + annotationBindings.length];
69-
System.arraycopy(typeAnnotationBindings, 0, this.annotationBindings, 0, typeAnnotationBindings.length);
70-
System.arraycopy(annotationBindings, 0, this.annotationBindings, typeAnnotationBindings.length, annotationBindings.length);
75+
this.symbolAnnotation = symbolAnnotation;
76+
this.annotationBindings = new IAnnotationBinding[symbolAnnotation.length + paramAnnotations.length];
77+
System.arraycopy(symbolAnnotation, 0, this.annotationBindings, 0, symbolAnnotation.length);
78+
System.arraycopy(paramAnnotations, 0, this.annotationBindings, symbolAnnotation.length, paramAnnotations.length);
7179
}
7280

7381
private static NullabilityData[] forEachLevel(Function<NullabilityLevel, NullabilityData> initializer) {
@@ -92,6 +100,16 @@ public List<AnnotationInstance> annotations() {
92100
return annotations;
93101
}
94102

103+
@Override
104+
public List<AnnotationInstance> symbolAnnotations() {
105+
if (symbolAnnotations == null) {
106+
symbolAnnotations = Arrays.stream(symbolAnnotation)
107+
.map(sema::annotation)
108+
.collect(Collectors.toList());
109+
}
110+
return symbolAnnotations;
111+
}
112+
95113
@Override
96114
public final boolean isAnnotatedWith(String fullyQualifiedName) {
97115
for (AnnotationInstance a : annotations()) {

java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadataNullabilityHelper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ private static NullabilityData getNullabilityData(SymbolMetadata metadata,
317317
TypesForAnnotations typeForAnnotations) {
318318
NullabilityType nullabilityType = NullabilityType.NO_ANNOTATION;
319319
AnnotationInstance annotationInstance = null;
320-
for (AnnotationInstance annotation : metadata.annotations()) {
320+
for (AnnotationInstance annotation : metadata.symbolAnnotations()) {
321321
NullabilityType typeFromAnnotation = typeForAnnotations.getTypeFromAnnotation(annotation);
322322
if (typeFromAnnotation.ordinal() > nullabilityType.ordinal()) {
323323
nullabilityType = typeFromAnnotation;

java-frontend/src/main/java/org/sonar/java/model/JVariableSymbol.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,7 @@ static class ParameterPlaceholderSymbol extends Symbols.DefaultSymbol implements
9898
metadata = new JSymbolMetadata(
9999
sema,
100100
this,
101-
typeBinding.getTypeAnnotations(),
102-
owner.getParameterAnnotations(index)
101+
owner.getParameterAnnotations(index), typeBinding.getTypeAnnotations()
103102
);
104103
}
105104

java-frontend/src/main/java/org/sonar/java/model/Symbols.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ public List<AnnotationInstance> annotations() {
5959
return Collections.emptyList();
6060
}
6161

62+
@Override
63+
public List<AnnotationInstance> symbolAnnotations() {
64+
return Collections.emptyList();
65+
}
66+
6267
@Override
6368
public NullabilityData nullabilityData() {
6469
return JSymbolMetadata.unknownNullabilityAt(NullabilityLevel.UNKNOWN);

java-frontend/src/main/java/org/sonar/plugins/java/api/semantic/SymbolMetadata.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,17 @@ public interface SymbolMetadata {
4444
List<AnnotationValue> valuesForAnnotation(String fullyQualifiedNameOfAnnotation);
4545

4646
/**
47-
* The list of annotations found on this symbol.
47+
* The list of all annotations found on this symbol, including annotations that are present on the parametrized types.
4848
* @return A list of {@link AnnotationInstance}
4949
*/
5050
List<AnnotationInstance> annotations();
5151

52+
/**
53+
* The list of annotations that applies exactly on this symbol.
54+
* @return A list of {@link AnnotationInstance}
55+
*/
56+
List<AnnotationInstance> symbolAnnotations();
57+
5258
/**
5359
* @return the nullability definition for metadata() of:
5460
* - fields

java-frontend/src/test/java/org/sonar/java/model/JSymbolMetadataTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,13 @@ void variable_level_nullability() throws IOException {
185185
);
186186
}
187187

188+
@Test
189+
void parametrized_type_level_nullability() throws IOException {
190+
assertNullability(
191+
NULLABILITY_SOURCE_DIR.resolve(Paths.get("no_default", "NullabilityOfParametrizedTypes.java"))
192+
);
193+
}
194+
188195
@Test
189196
void unsupported_nullability() throws IOException {
190197
assertNullability(

0 commit comments

Comments
 (0)