1
1
package com .uber .nullaway ;
2
2
3
- import static com .uber .nullaway .NullabilityUtil .castToNonNull ;
4
-
5
- import com .google .common .base .Preconditions ;
6
3
import com .google .errorprone .VisitorState ;
7
- import com .google .errorprone .suppliers .Supplier ;
8
- import com .google .errorprone .suppliers .Suppliers ;
9
4
import com .google .errorprone .util .ASTHelpers ;
10
5
import com .sun .source .tree .AnnotatedTypeTree ;
11
6
import com .sun .source .tree .AnnotationTree ;
12
- import com .sun .source .tree .AssignmentTree ;
13
- import com .sun .source .tree .NewClassTree ;
14
7
import com .sun .source .tree .ParameterizedTypeTree ;
15
8
import com .sun .source .tree .Tree ;
16
- import com .sun .source .tree .VariableTree ;
17
9
import com .sun .tools .javac .code .Attribute ;
18
10
import com .sun .tools .javac .code .Type ;
19
- import com .sun .tools .javac .code .TypeMetadata ;
20
- import com .sun .tools .javac .code .Types ;
21
- import java .util .ArrayList ;
22
- import java .util .Collections ;
23
11
import java .util .HashMap ;
24
12
import java .util .List ;
25
13
import java .util .Map ;
26
14
27
15
/** Methods for performing checks related to generic types and nullability. */
28
16
public final class GenericsChecks {
29
17
30
- private static final String NULLABLE_NAME = "org.jspecify.annotations.Nullable" ;
31
-
32
- private static final Supplier <Type > NULLABLE_TYPE_SUPPLIER =
33
- Suppliers .typeFromString (NULLABLE_NAME );
34
- private VisitorState state ;
35
- private Config config ;
36
- private NullAway analysis ;
37
-
38
- public GenericsChecks (VisitorState state , Config config , NullAway analysis ) {
39
- this .state = state ;
40
- this .config = config ;
41
- this .analysis = analysis ;
18
+ private GenericsChecks () {
19
+ // just utility methods
42
20
}
43
21
44
22
/**
@@ -92,14 +70,14 @@ public static void checkInstantiationForParameterizedTypedTree(
92
70
// if base type argument does not have @Nullable annotation then the instantiation is
93
71
// invalid
94
72
if (!hasNullableAnnotation ) {
95
- reportInvalidInstantiationError (
73
+ invalidInstantiationError (
96
74
nullableTypeArguments .get (i ), baseType , typeVariable , state , analysis );
97
75
}
98
76
}
99
77
}
100
78
}
101
79
102
- private static void reportInvalidInstantiationError (
80
+ private static void invalidInstantiationError (
103
81
Tree tree , Type baseType , Type baseTypeVariable , VisitorState state , NullAway analysis ) {
104
82
ErrorBuilder errorBuilder = analysis .getErrorBuilder ();
105
83
ErrorMessage errorMessage =
@@ -112,194 +90,4 @@ private static void reportInvalidInstantiationError(
112
90
errorBuilder .createErrorDescription (
113
91
errorMessage , analysis .buildDescription (tree ), state , null ));
114
92
}
115
-
116
- private static void reportInvalidAssignmentInstantiationError (
117
- Tree tree , Type lhsType , Type rhsType , VisitorState state , NullAway analysis ) {
118
- ErrorBuilder errorBuilder = analysis .getErrorBuilder ();
119
- ErrorMessage errorMessage =
120
- new ErrorMessage (
121
- ErrorMessage .MessageTypes .ASSIGN_GENERIC_NULLABLE ,
122
- String .format (
123
- "Cannot assign from type "
124
- + rhsType
125
- + " to type "
126
- + lhsType
127
- + " due to mismatched nullability of type parameters" ));
128
- state .reportMatch (
129
- errorBuilder .createErrorDescription (
130
- errorMessage , analysis .buildDescription (tree ), state , null ));
131
- }
132
-
133
- /**
134
- * For a tree representing an assignment, ensures that from the perspective of type parameter
135
- * nullability, the type of the right-hand side is assignable to (a subtype of) the type of the
136
- * left-hand side. This check ensures that for every parameterized type nested in each of the
137
- * types, the type parameters have identical nullability.
138
- *
139
- * @param tree the tree to check, which must be either an {@link AssignmentTree} or a {@link
140
- * VariableTree}
141
- */
142
- public void checkTypeParameterNullnessForAssignability (Tree tree ) {
143
- if (!config .isJSpecifyMode ()) {
144
- return ;
145
- }
146
- Tree lhsTree ;
147
- Tree rhsTree ;
148
- if (tree instanceof VariableTree ) {
149
- VariableTree varTree = (VariableTree ) tree ;
150
- lhsTree = varTree .getType ();
151
- rhsTree = varTree .getInitializer ();
152
- } else {
153
- AssignmentTree assignmentTree = (AssignmentTree ) tree ;
154
- lhsTree = assignmentTree .getVariable ();
155
- rhsTree = assignmentTree .getExpression ();
156
- }
157
- // rhsTree can be null for a VariableTree. Also, we don't need to do a check
158
- // if rhsTree is the null literal
159
- if (rhsTree == null || rhsTree .getKind ().equals (Tree .Kind .NULL_LITERAL )) {
160
- return ;
161
- }
162
- Type lhsType = ASTHelpers .getType (lhsTree );
163
- Type rhsType = ASTHelpers .getType (rhsTree );
164
- // For NewClassTrees with annotated type parameters, javac does not preserve the annotations in
165
- // its computed type for the expression. As a workaround, we construct a replacement Type
166
- // object with the appropriate annotations.
167
- if (rhsTree instanceof NewClassTree
168
- && ((NewClassTree ) rhsTree ).getIdentifier () instanceof ParameterizedTypeTree ) {
169
- ParameterizedTypeTree paramTypedTree =
170
- (ParameterizedTypeTree ) ((NewClassTree ) rhsTree ).getIdentifier ();
171
- if (paramTypedTree .getTypeArguments ().isEmpty ()) {
172
- // no explicit type parameters
173
- return ;
174
- }
175
- rhsType = typeWithPreservedAnnotations (paramTypedTree );
176
- }
177
- if (lhsType instanceof Type .ClassType && rhsType instanceof Type .ClassType ) {
178
- compareNullabilityAnnotations ((Type .ClassType ) lhsType , (Type .ClassType ) rhsType , tree );
179
- }
180
- }
181
-
182
- /**
183
- * Compare two types from an assignment for identical type parameter nullability, recursively
184
- * checking nested generic types. See <a
185
- * href="https://jspecify.dev/docs/spec/#nullness-delegating-subtyping">the JSpecify
186
- * specification</a> and <a
187
- * href="https://docs.oracle.com/javase/specs/jls/se14/html/jls-4.html#jls-4.10.2">the JLS
188
- * subtyping rules for class and interface types</a>.
189
- *
190
- * @param lhsType type for the lhs of the assignment
191
- * @param rhsType type for the rhs of the assignment
192
- * @param tree tree representing the assignment
193
- */
194
- private void compareNullabilityAnnotations (
195
- Type .ClassType lhsType , Type .ClassType rhsType , Tree tree ) {
196
- Types types = state .getTypes ();
197
- // The base type of rhsType may be a subtype of lhsType's base type. In such cases, we must
198
- // compare lhsType against the supertype of rhsType with a matching base type.
199
- rhsType = (Type .ClassType ) types .asSuper (rhsType , lhsType .tsym );
200
- // This is impossible, considering the fact that standard Java subtyping succeeds before running
201
- // NullAway
202
- if (rhsType == null ) {
203
- throw new RuntimeException ("Did not find supertype of " + rhsType + " matching " + lhsType );
204
- }
205
- List <Type > lhsTypeArguments = lhsType .getTypeArguments ();
206
- List <Type > rhsTypeArguments = rhsType .getTypeArguments ();
207
- // This is impossible, considering the fact that standard Java subtyping succeeds before running
208
- // NullAway
209
- if (lhsTypeArguments .size () != rhsTypeArguments .size ()) {
210
- throw new RuntimeException (
211
- "Number of types arguments in " + rhsType + " does not match " + lhsType );
212
- }
213
- for (int i = 0 ; i < lhsTypeArguments .size (); i ++) {
214
- Type lhsTypeArgument = lhsTypeArguments .get (i );
215
- Type rhsTypeArgument = rhsTypeArguments .get (i );
216
- boolean isLHSNullableAnnotated = false ;
217
- List <Attribute .TypeCompound > lhsAnnotations = lhsTypeArgument .getAnnotationMirrors ();
218
- // To ensure that we are checking only jspecify nullable annotations
219
- for (Attribute .TypeCompound annotation : lhsAnnotations ) {
220
- if (annotation .getAnnotationType ().toString ().equals (NULLABLE_NAME )) {
221
- isLHSNullableAnnotated = true ;
222
- break ;
223
- }
224
- }
225
- boolean isRHSNullableAnnotated = false ;
226
- List <Attribute .TypeCompound > rhsAnnotations = rhsTypeArgument .getAnnotationMirrors ();
227
- // To ensure that we are checking only jspecify nullable annotations
228
- for (Attribute .TypeCompound annotation : rhsAnnotations ) {
229
- if (annotation .getAnnotationType ().toString ().equals (NULLABLE_NAME )) {
230
- isRHSNullableAnnotated = true ;
231
- break ;
232
- }
233
- }
234
- if (isLHSNullableAnnotated != isRHSNullableAnnotated ) {
235
- reportInvalidAssignmentInstantiationError (tree , lhsType , rhsType , state , analysis );
236
- return ;
237
- }
238
- // nested generics
239
- if (lhsTypeArgument .getTypeArguments ().length () > 0 ) {
240
- compareNullabilityAnnotations (
241
- (Type .ClassType ) lhsTypeArgument , (Type .ClassType ) rhsTypeArgument , tree );
242
- }
243
- }
244
- }
245
-
246
- /**
247
- * For the Parameterized typed trees, ASTHelpers.getType(tree) does not return a Type with
248
- * preserved annotations. This method takes a Parameterized typed tree as an input and returns the
249
- * Type of the tree with the annotations.
250
- *
251
- * @param tree A parameterized typed tree for which we need class type with preserved annotations.
252
- * @return A Type with preserved annotations.
253
- */
254
- private Type .ClassType typeWithPreservedAnnotations (ParameterizedTypeTree tree ) {
255
- Type .ClassType type = (Type .ClassType ) ASTHelpers .getType (tree );
256
- Preconditions .checkNotNull (type );
257
- Type nullableType = NULLABLE_TYPE_SUPPLIER .get (state );
258
- List <? extends Tree > typeArguments = tree .getTypeArguments ();
259
- List <Type > newTypeArgs = new ArrayList <>();
260
- boolean hasNullableAnnotation = false ;
261
- for (int i = 0 ; i < typeArguments .size (); i ++) {
262
- AnnotatedTypeTree annotatedType = null ;
263
- Tree curTypeArg = typeArguments .get (i );
264
- // If the type argument has an annotation, it will either be an AnnotatedTypeTree, or a
265
- // ParameterizedTypeTree in the case of a nested generic type
266
- if (curTypeArg instanceof AnnotatedTypeTree ) {
267
- annotatedType = (AnnotatedTypeTree ) curTypeArg ;
268
- } else if (curTypeArg instanceof ParameterizedTypeTree
269
- && ((ParameterizedTypeTree ) curTypeArg ).getType () instanceof AnnotatedTypeTree ) {
270
- annotatedType = (AnnotatedTypeTree ) ((ParameterizedTypeTree ) curTypeArg ).getType ();
271
- }
272
- List <? extends AnnotationTree > annotations =
273
- annotatedType != null ? annotatedType .getAnnotations () : Collections .emptyList ();
274
- for (AnnotationTree annotation : annotations ) {
275
- if (ASTHelpers .isSameType (
276
- nullableType , ASTHelpers .getType (annotation .getAnnotationType ()), state )) {
277
- hasNullableAnnotation = true ;
278
- break ;
279
- }
280
- }
281
- // construct a TypeMetadata object containing a nullability annotation if needed
282
- com .sun .tools .javac .util .List <Attribute .TypeCompound > nullableAnnotationCompound =
283
- hasNullableAnnotation
284
- ? com .sun .tools .javac .util .List .from (
285
- Collections .singletonList (
286
- new Attribute .TypeCompound (
287
- nullableType , com .sun .tools .javac .util .List .nil (), null )))
288
- : com .sun .tools .javac .util .List .nil ();
289
- TypeMetadata typeMetadata =
290
- new TypeMetadata (new TypeMetadata .Annotations (nullableAnnotationCompound ));
291
- Type currentTypeArgType = castToNonNull (ASTHelpers .getType (curTypeArg ));
292
- if (currentTypeArgType .getTypeArguments ().size () > 0 ) {
293
- // nested generic type; recursively preserve its nullability type argument annotations
294
- currentTypeArgType = typeWithPreservedAnnotations ((ParameterizedTypeTree ) curTypeArg );
295
- }
296
- Type .ClassType newTypeArgType =
297
- (Type .ClassType ) currentTypeArgType .cloneWithMetadata (typeMetadata );
298
- newTypeArgs .add (newTypeArgType );
299
- }
300
- Type .ClassType finalType =
301
- new Type .ClassType (
302
- type .getEnclosingType (), com .sun .tools .javac .util .List .from (newTypeArgs ), type .tsym );
303
- return finalType ;
304
- }
305
93
}
0 commit comments