forked from typetools/checker-framework
/
BaseTypeValidator.java
659 lines (592 loc) · 26.7 KB
/
BaseTypeValidator.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
package org.checkerframework.common.basetype;
import com.sun.source.tree.AnnotatedTypeTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.ParameterizedTypeTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TypeParameterTree;
import com.sun.source.tree.VariableTree;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.tools.Diagnostic.Kind;
import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.framework.source.DiagMessage;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
import org.checkerframework.framework.type.AnnotatedTypeParameterBounds;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.framework.type.visitor.AnnotatedTypeScanner;
import org.checkerframework.framework.type.visitor.SimpleAnnotatedTypeScanner;
import org.checkerframework.framework.util.AnnotatedTypes;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.Pair;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypeAnnotationUtils;
import org.checkerframework.javacutil.TypesUtils;
/**
* A visitor to validate the types in a tree.
*
* <p>Note: A TypeValidator (this class and its subclasses) cannot tell whether an annotation was
* written by a programmer or defaulted/inferred/computed by the Checker Framework, because the
* AnnotatedTypeMirror does not make distinctions about which annotations in an AnnotatedTypeMirror
* were explicitly written and which were added by a checker. To issue a warning/error only when a
* programmer writes an annotation, override {@link BaseTypeVisitor#visitAnnotatedType} and {@link
* BaseTypeVisitor#visitVariable}.
*/
public class BaseTypeValidator extends AnnotatedTypeScanner<Void, Tree> implements TypeValidator {
/** Is the type valid? This is side-effected by the visitor, and read at the end of visiting. */
protected boolean isValid = true;
/** Should the primary annotation on the top level type be checked? */
protected boolean checkTopLevelDeclaredOrPrimitiveType = true;
/** BaseTypeChecker. */
protected final BaseTypeChecker checker;
/** BaseTypeVisitor. */
protected final BaseTypeVisitor<?> visitor;
/** AnnotatedTypeFactory. */
protected final AnnotatedTypeFactory atypeFactory;
// TODO: clean up coupling between components
public BaseTypeValidator(
BaseTypeChecker checker, BaseTypeVisitor<?> visitor, AnnotatedTypeFactory atypeFactory) {
this.checker = checker;
this.visitor = visitor;
this.atypeFactory = atypeFactory;
}
/**
* Validate the type against the given tree. This method both issues error messages and also
* returns a boolean value.
*
* <p>This is the entry point to the type validator. Neither this method nor visit should be
* called directly by a visitor, only use {@link BaseTypeVisitor#validateTypeOf(Tree)}.
*
* <p>This method is only called on top-level types, but it validates the entire type including
* components of a compound type. Subclasses should override this only if there is special-case
* behavior that should be performed only on top-level types.
*
* @param type the type to validate
* @param tree the tree from which the type originated. If the tree is a method tree, {@code type}
* is its return type. If the tree is a variable tree, {@code type} is the variable's type.
* @return true if the type is valid
*/
@Override
public boolean isValid(AnnotatedTypeMirror type, Tree tree) {
List<DiagMessage> diagMessages =
isValidStructurally(atypeFactory.getQualifierHierarchy(), type);
if (!diagMessages.isEmpty()) {
for (DiagMessage d : diagMessages) {
checker.report(tree, d);
}
return false;
}
this.isValid = true;
this.checkTopLevelDeclaredOrPrimitiveType =
shouldCheckTopLevelDeclaredOrPrimitiveType(type, tree);
visit(type, tree);
return this.isValid;
}
/**
* Should the top-level declared or primitive type be checked?
*
* <p>If {@code type} is not a declared or primitive type, then this method returns true.
*
* <p>Top-level type is not checked if tree is a local variable or an expression tree.
*
* @param type AnnotatedTypeMirror being validated
* @param tree a Tree whose type is {@code type}
* @return whether or not the top-level type should be checked, if {@code type} is a declared or
* primitive type.
*/
protected boolean shouldCheckTopLevelDeclaredOrPrimitiveType(
AnnotatedTypeMirror type, Tree tree) {
if (type.getKind() != TypeKind.DECLARED && !type.getKind().isPrimitive()) {
return true;
}
return !TreeUtils.isLocalVariable(tree)
&& (!TreeUtils.isExpressionTree(tree) || TreeUtils.isTypeTree(tree));
}
/**
* Performs some well-formedness checks on the given {@link AnnotatedTypeMirror}. Returns a list
* of failures. If successful, returns an empty list. The method will never return failures for a
* valid type, but might not catch all invalid types.
*
* <p>This method ensures that the type is structurally or lexically well-formed, but it does not
* check whether the annotations are semantically sensible. Subclasses should generally override
* visit methods such as {@link #visitDeclared} rather than this method.
*
* <p>Currently, this implementation checks the following (subclasses can extend this behavior):
*
* <ol>
* <li>There should not be multiple annotations from the same qualifier hierarchy.
* <li>There should not be more annotations than the width of the QualifierHierarchy.
* <li>If the type is not a type variable, then the number of annotations should be the same as
* the width of the QualifierHierarchy.
* <li>These properties should also hold recursively for component types of arrays and for
* bounds of type variables and wildcards.
* </ol>
*
* @param qualifierHierarchy the QualifierHierarchy
* @param type the type to test
* @return list of reasons the type is invalid, or empty list if the type is valid
*/
protected List<DiagMessage> isValidStructurally(
QualifierHierarchy qualifierHierarchy, AnnotatedTypeMirror type) {
SimpleAnnotatedTypeScanner<List<DiagMessage>, QualifierHierarchy> scanner =
new SimpleAnnotatedTypeScanner<>(
(atm, q) -> isTopLevelValidType(q, atm),
DiagMessage::mergeLists,
Collections.emptyList());
return scanner.visit(type, qualifierHierarchy);
}
/**
* Checks every property listed in {@link #isValidStructurally}, but only for the top level type.
* If successful, returns an empty list. If not successful, returns diagnostics.
*
* @param qualifierHierarchy the QualifierHierarchy
* @param type the type to be checked
* @return the diagnostics indicating failure, or an empty list if successful
*/
// This method returns a singleton or empyty list. Its return type is List rather than
// DiagMessage (with null indicting success) because its caller, isValidStructurally(), expects
// a list.
protected List<DiagMessage> isTopLevelValidType(
QualifierHierarchy qualifierHierarchy, AnnotatedTypeMirror type) {
// multiple annotations from the same hierarchy
Set<AnnotationMirror> annotations = type.getAnnotations();
Set<AnnotationMirror> seenTops = AnnotationUtils.createAnnotationSet();
for (AnnotationMirror anno : annotations) {
AnnotationMirror top = qualifierHierarchy.getTopAnnotation(anno);
if (AnnotationUtils.containsSame(seenTops, top)) {
return Collections.singletonList(
new DiagMessage(Kind.ERROR, "type.invalid.conflicting.annos", annotations, type));
}
seenTops.add(top);
}
boolean canHaveEmptyAnnotationSet = QualifierHierarchy.canHaveEmptyAnnotationSet(type);
// wrong number of annotations
if (!canHaveEmptyAnnotationSet && seenTops.size() < qualifierHierarchy.getWidth()) {
return Collections.singletonList(
new DiagMessage(Kind.ERROR, "type.invalid.too.few.annotations", annotations, type));
}
// success
return Collections.emptyList();
}
protected void reportValidityResult(
final @CompilerMessageKey String errorType, final AnnotatedTypeMirror type, final Tree p) {
checker.reportError(p, errorType, type.getAnnotations(), type.toString());
isValid = false;
}
/**
* Like {@link #reportValidityResult}, but the type is printed in the error message without
* annotations. This method would print "annotation @NonNull is not permitted on type int",
* whereas {@link #reportValidityResult} would print "annotation @NonNull is not permitted on
* type @NonNull int". In addition, when the underlying type is a compound type such as
* {@code @Bad List<String>}, the erased type will be used, i.e., "{@code List}" will print
* instead of "{@code @Bad List<String>}".
*/
protected void reportValidityResultOnUnannotatedType(
final @CompilerMessageKey String errorType, final AnnotatedTypeMirror type, final Tree p) {
TypeMirror underlying =
TypeAnnotationUtils.unannotatedType(type.getErased().getUnderlyingType());
checker.reportError(p, errorType, type.getAnnotations(), underlying.toString());
isValid = false;
}
/**
* Most errors reported by this class are of the form type.invalid. This method reports when the
* bounds of a wildcard or type variable don't make sense. Bounds make sense when the effective
* annotations on the upper bound are supertypes of those on the lower bounds for all hierarchies.
* To ensure that this subtlety is not lost on users, we report "bound.type.incompatible" and
* print the bounds along with the invalid type rather than a "type.invalid".
*/
protected void reportInvalidBounds(final AnnotatedTypeMirror type, final Tree tree) {
final String label;
final AnnotatedTypeMirror upperBound;
final AnnotatedTypeMirror lowerBound;
switch (type.getKind()) {
case TYPEVAR:
label = "type parameter";
upperBound = ((AnnotatedTypeVariable) type).getUpperBound();
lowerBound = ((AnnotatedTypeVariable) type).getLowerBound();
break;
case WILDCARD:
label = "wildcard";
upperBound = ((AnnotatedWildcardType) type).getExtendsBound();
lowerBound = ((AnnotatedWildcardType) type).getSuperBound();
break;
default:
throw new BugInCF("Type is not bounded.%ntype=%s%ntree=%s", type, tree);
}
checker.reportError(
tree,
"bound.type.incompatible",
label,
type.toString(),
upperBound.toString(true),
lowerBound.toString(true));
isValid = false;
}
protected void reportInvalidType(final AnnotatedTypeMirror type, final Tree p) {
reportValidityResult("type.invalid", type, p);
}
protected void reportInvalidAnnotationsOnUse(final AnnotatedTypeMirror type, final Tree p) {
reportValidityResultOnUnannotatedType("type.invalid.annotations.on.use", type, p);
}
@Override
public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) {
if (visitedNodes.containsKey(type)) {
return visitedNodes.get(type);
}
final boolean skipChecks = checker.shouldSkipUses(type.getUnderlyingType().asElement());
if (checkTopLevelDeclaredOrPrimitiveType && !skipChecks) {
// Ensure that type use is a subtype of the element type
// isValidUse determines the erasure of the types.
Set<AnnotationMirror> bounds =
atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType());
AnnotatedDeclaredType elemType = type.deepCopy();
elemType.clearPrimaryAnnotations();
elemType.addAnnotations(bounds);
if (!visitor.isValidUse(elemType, type, tree)) {
reportInvalidAnnotationsOnUse(type, tree);
}
}
// Set checkTopLevelDeclaredType to true, because the next time visitDeclared is called,
// the type isn't the top level, so always do the check.
checkTopLevelDeclaredOrPrimitiveType = true;
if (TreeUtils.isClassTree(tree)) {
visitedNodes.put(type, null);
visitClassTypeParameters(type, (ClassTree) tree);
return null;
}
/*
* Try to reconstruct the ParameterizedTypeTree from the given tree.
* TODO: there has to be a nicer way to do this...
*/
Pair<ParameterizedTypeTree, AnnotatedDeclaredType> p = extractParameterizedTypeTree(tree, type);
ParameterizedTypeTree typeArgTree = p.first;
type = p.second;
if (typeArgTree == null) {
return super.visitDeclared(type, tree);
} // else
// We put this here because we don't want to put it in visitedNodes before calling
// super (in the else branch) because that would cause the super implementation
// to detect that we've already visited type and to immediately return.
visitedNodes.put(type, null);
// We have a ParameterizedTypeTree -> visit it.
visitParameterizedType(type, typeArgTree);
/*
* Instead of calling super with the unchanged "tree", adapt the
* second argument to be the corresponding type argument tree. This
* ensures that the first and second parameter to this method always
* correspond. visitDeclared is the only method that had this
* problem.
*/
List<? extends AnnotatedTypeMirror> tatypes = type.getTypeArguments();
if (tatypes == null) {
return null;
}
// May be zero for a "diamond" (inferred type args in constructor invocation).
int numTypeArgs = typeArgTree.getTypeArguments().size();
if (numTypeArgs != 0) {
// TODO: this should be an equality, but in
// http://buffalo.cs.washington.edu:8080/job/jdk6-daikon-typecheck/2061/console
// it failed with:
// daikon/Debug.java; message: size mismatch for type arguments:
// @NonNull Object and Class<?>
// but I didn't manage to reduce it to a test case.
assert tatypes.size() <= numTypeArgs || skipChecks
: "size mismatch for type arguments: " + type + " and " + typeArgTree;
for (int i = 0; i < tatypes.size(); ++i) {
scan(tatypes.get(i), typeArgTree.getTypeArguments().get(i));
}
}
// Don't call the super version, because it creates a mismatch
// between the first and second parameters.
// return super.visitDeclared(type, tree);
return null;
}
/**
* Visits the type parameters of a class tree.
*
* @param type type of {@code tree}
* @param tree a class tree
*/
protected void visitClassTypeParameters(AnnotatedDeclaredType type, ClassTree tree) {
for (int i = 0, size = type.getTypeArguments().size(); i < size; i++) {
AnnotatedTypeVariable typeParameter = (AnnotatedTypeVariable) type.getTypeArguments().get(i);
TypeParameterTree typeParameterTree = tree.getTypeParameters().get(i);
scan(typeParameter, typeParameterTree);
}
}
/**
* Visits type parameter bounds.
*
* @param typeParameter type of {@code typeParameterTree}
* @param typeParameterTree a type parameter tree
*/
protected void visitTypeParameterBounds(
AnnotatedTypeVariable typeParameter, TypeParameterTree typeParameterTree) {
List<? extends Tree> boundTrees = typeParameterTree.getBounds();
if (boundTrees.size() == 1) {
scan(typeParameter.getUpperBound(), boundTrees.get(0));
} else if (boundTrees.size() == 0) {
// The upper bound is implicitly Object
scan(typeParameter.getUpperBound(), typeParameterTree);
} else {
AnnotatedIntersectionType intersectionType =
(AnnotatedIntersectionType) typeParameter.getUpperBound();
for (int j = 0; j < intersectionType.getBounds().size(); j++) {
scan(intersectionType.getBounds().get(j), boundTrees.get(j));
}
}
}
/**
* If {@code tree} has a {@link ParameterizedTypeTree}, then the tree and its type is returned.
* Otherwise null and {@code type} are returned.
*
* @param tree tree to search
* @param type type to return if no {@code ParameterizedTypeTree} is found
* @return if {@code tree} has a {@code ParameterizedTypeTree}, then returns the tree and its
* type. Otherwise, returns null and {@code type}.
*/
private Pair<@Nullable ParameterizedTypeTree, AnnotatedDeclaredType> extractParameterizedTypeTree(
Tree tree, AnnotatedDeclaredType type) {
ParameterizedTypeTree typeargtree = null;
switch (tree.getKind()) {
case VARIABLE:
Tree lt = ((VariableTree) tree).getType();
if (lt instanceof ParameterizedTypeTree) {
typeargtree = (ParameterizedTypeTree) lt;
} else {
// System.out.println("Found a: " + lt);
}
break;
case PARAMETERIZED_TYPE:
typeargtree = (ParameterizedTypeTree) tree;
break;
case NEW_CLASS:
NewClassTree nct = (NewClassTree) tree;
ExpressionTree nctid = nct.getIdentifier();
if (nctid.getKind() == Tree.Kind.PARAMETERIZED_TYPE) {
typeargtree = (ParameterizedTypeTree) nctid;
/*
* This is quite tricky... for anonymous class instantiations,
* the type at this point has no type arguments. By doing the
* following, we get the type arguments again.
*/
type = (AnnotatedDeclaredType) atypeFactory.getAnnotatedType(typeargtree);
}
break;
case ANNOTATED_TYPE:
AnnotatedTypeTree tr = (AnnotatedTypeTree) tree;
ExpressionTree undtr = tr.getUnderlyingType();
if (undtr instanceof ParameterizedTypeTree) {
typeargtree = (ParameterizedTypeTree) undtr;
} else if (undtr instanceof IdentifierTree) {
// @Something D -> Nothing to do
} else {
// TODO: add more test cases to ensure that nested types are
// handled correctly,
// e.g. @Nullable() List<@Nullable Object>[][]
Pair<ParameterizedTypeTree, AnnotatedDeclaredType> p =
extractParameterizedTypeTree(undtr, type);
typeargtree = p.first;
type = p.second;
}
break;
case IDENTIFIER:
case ARRAY_TYPE:
case NEW_ARRAY:
case MEMBER_SELECT:
case UNBOUNDED_WILDCARD:
case EXTENDS_WILDCARD:
case SUPER_WILDCARD:
case TYPE_PARAMETER:
// Nothing to do.
break;
case METHOD:
// If a MethodTree is passed, it's just the return type that is validated.
// See BaseTypeVisitor#validateTypeOf.
MethodTree methodTree = (MethodTree) tree;
if (methodTree.getReturnType() instanceof ParameterizedTypeTree) {
typeargtree = (ParameterizedTypeTree) methodTree.getReturnType();
}
break;
default:
// The parameterized type is the result of some expression tree.
// No need to do anything further.
break;
}
return Pair.of(typeargtree, type);
}
@Override
@SuppressWarnings(
"signature:argument.type.incompatible") // PrimitiveType.toString(): @PrimitiveType
public Void visitPrimitive(AnnotatedPrimitiveType type, Tree tree) {
if (!checkTopLevelDeclaredOrPrimitiveType
|| checker.shouldSkipUses(type.getUnderlyingType().toString())) {
return super.visitPrimitive(type, tree);
}
if (!visitor.isValidUse(type, tree)) {
reportInvalidAnnotationsOnUse(type, tree);
}
return super.visitPrimitive(type, tree);
}
@Override
public Void visitArray(AnnotatedArrayType type, Tree tree) {
// TODO: is there already or add a helper method
// to determine the non-array component type
AnnotatedTypeMirror comp = type;
do {
comp = ((AnnotatedArrayType) comp).getComponentType();
} while (comp.getKind() == TypeKind.ARRAY);
if (comp.getKind() == TypeKind.DECLARED
&& checker.shouldSkipUses(((AnnotatedDeclaredType) comp).getUnderlyingType().asElement())) {
return super.visitArray(type, tree);
}
if (!visitor.isValidUse(type, tree)) {
reportInvalidAnnotationsOnUse(type, tree);
}
return super.visitArray(type, tree);
}
/**
* Checks that the annotations on the type arguments supplied to a type or a method invocation are
* within the bounds of the type variables as declared, and issues the
* "type.argument.type.incompatible" error if they are not.
*
* @param type the type to check
* @param tree the type's tree
*/
protected Void visitParameterizedType(AnnotatedDeclaredType type, ParameterizedTypeTree tree) {
// System.out.printf("TypeValidator.visitParameterizedType: type: %s, tree: %s%n", type,
// tree);
if (TreeUtils.isDiamondTree(tree)) {
return null;
}
final TypeElement element = (TypeElement) type.getUnderlyingType().asElement();
if (checker.shouldSkipUses(element)) {
return null;
}
AnnotatedDeclaredType capturedType =
(AnnotatedDeclaredType) atypeFactory.applyCaptureConversion(type);
List<AnnotatedTypeParameterBounds> bounds =
atypeFactory.typeVariablesFromUse(capturedType, element);
visitor.checkTypeArguments(
tree,
bounds,
capturedType.getTypeArguments(),
tree.getTypeArguments(),
element.getSimpleName(),
element.getTypeParameters());
@SuppressWarnings(
"interning:not.interned") // applyCaptureConversion returns the passed type if type
// does not have wildcards.
boolean hasCapturedTypeVariables = capturedType != type;
if (hasCapturedTypeVariables) {
// Check that the extends bound of the captured type variable is a subtype of the
// extends bound of the wildcard.
int numTypeArgs = capturedType.getTypeArguments().size();
// First create a mapping from captured type variable to its wildcard.
Map<TypeVariable, AnnotatedTypeMirror> typeVarToWildcard = new HashMap<>(numTypeArgs);
for (int i = 0; i < numTypeArgs; i++) {
AnnotatedTypeMirror captureTypeArg = capturedType.getTypeArguments().get(i);
if (TypesUtils.isCapturedTypeVariable(captureTypeArg.getUnderlyingType())) {
AnnotatedTypeVariable capturedTypeVar = (AnnotatedTypeVariable) captureTypeArg;
AnnotatedWildcardType wildcard = (AnnotatedWildcardType) type.getTypeArguments().get(i);
typeVarToWildcard.put(capturedTypeVar.getUnderlyingType(), wildcard);
}
}
for (int i = 0; i < numTypeArgs; i++) {
AnnotatedTypeMirror captureTypeArg = capturedType.getTypeArguments().get(i);
if (TypesUtils.isCapturedTypeVariable(captureTypeArg.getUnderlyingType())) {
AnnotatedTypeVariable capturedTypeVar = (AnnotatedTypeVariable) captureTypeArg;
AnnotatedWildcardType wildcard = (AnnotatedWildcardType) type.getTypeArguments().get(i);
// Substitute the captured type variables with their wildcards. Without this,
// the isSubtype check crashes because wildcards aren't comparable with type
// variables.
AnnotatedTypeMirror catpureTypeVarUB =
atypeFactory
.getTypeVarSubstitutor()
.substituteWithoutCopyingTypeArguments(
typeVarToWildcard, capturedTypeVar.getUpperBound());
if (!atypeFactory
.getTypeHierarchy()
.isSubtype(catpureTypeVarUB, wildcard.getExtendsBound())) {
checker.reportError(
tree.getTypeArguments().get(i),
"type.argument.type.incompatible",
element.getTypeParameters().get(i),
element.getSimpleName(),
wildcard.getExtendsBound(),
capturedTypeVar.getUpperBound());
}
}
}
}
return null;
}
@Override
public Void visitTypeVariable(AnnotatedTypeVariable type, Tree tree) {
if (visitedNodes.containsKey(type)) {
return visitedNodes.get(type);
}
if (type.isDeclaration() && !areBoundsValid(type.getUpperBound(), type.getLowerBound())) {
reportInvalidBounds(type, tree);
}
AnnotatedTypeVariable useOfTypeVar = type.asUse();
if (tree instanceof TypeParameterTree) {
TypeParameterTree typeParameterTree = (TypeParameterTree) tree;
visitedNodes.put(useOfTypeVar, defaultResult);
visitTypeParameterBounds(useOfTypeVar, typeParameterTree);
visitedNodes.put(useOfTypeVar, defaultResult);
return null;
}
return super.visitTypeVariable(useOfTypeVar, tree);
}
@Override
public Void visitWildcard(AnnotatedWildcardType type, Tree tree) {
if (visitedNodes.containsKey(type)) {
return visitedNodes.get(type);
}
if (!areBoundsValid(type.getExtendsBound(), type.getSuperBound())) {
reportInvalidBounds(type, tree);
}
return super.visitWildcard(type, tree);
}
/**
* Returns true if the effective annotations on the upperBound are above those on the lowerBound.
*
* @return true if the effective annotations on the upperBound are above those on the lowerBound
*/
public boolean areBoundsValid(
final AnnotatedTypeMirror upperBound, final AnnotatedTypeMirror lowerBound) {
final QualifierHierarchy qualifierHierarchy = atypeFactory.getQualifierHierarchy();
final Set<AnnotationMirror> upperBoundAnnos =
AnnotatedTypes.findEffectiveAnnotations(qualifierHierarchy, upperBound);
final Set<AnnotationMirror> lowerBoundAnnos =
AnnotatedTypes.findEffectiveAnnotations(qualifierHierarchy, lowerBound);
if (upperBoundAnnos.size() == lowerBoundAnnos.size()) {
return qualifierHierarchy.isSubtype(lowerBoundAnnos, upperBoundAnnos);
} // else
// When upperBoundAnnos.size() != lowerBoundAnnos.size() one of the two bound types will
// be reported as invalid. Therefore, we do not do any other comparisons nor do we report
// a bound.type.incompatible
return true;
}
}