diff --git a/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java b/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java index a2121ad4bf0..f8a73222a7a 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java @@ -8,6 +8,7 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; +import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.ElementUtils; import org.plumelib.util.IPair; @@ -284,7 +285,12 @@ protected AnnotatedTypeMirror combineAnnotationWithType( AnnotatedTypeMirror resLower = combineAnnotationWithType(receiverAnnotation, atv.getLowerBound()); mappings.put(atv.getLowerBound(), resLower); - + // The values of the mappings are the viewpoint adapted lower and upper bounds, + // and we wish to replace the old bounds of atv with the new mappings. + // However, we need to first remove the primary annotations of atv, otherwise + // in later replacement, the primary annotations would override our computed + // new mappings (see method fixupBoundAnnotations). + atv.clearAnnotations(); AnnotatedTypeMirror result = AnnotatedTypeCopierWithReplacement.replace(atv, mappings); @@ -404,7 +410,16 @@ private AnnotatedTypeMirror substituteTVars(AnnotatedTypeMirror lhs, AnnotatedTy // Base case where actual type argument is extracted if (lhs.getKind() == TypeKind.DECLARED) { + // Replace type variable with its actual type argument rhs = getTypeVariableSubstitution((AnnotatedDeclaredType) lhs, atv); + // If the type variable use is annotated, the upperbound and lowerbound annotation + // on the type variable are the same. Replace the primary annotation of the + // substituted result (rhs) with annotation on type variable use. + if (AnnotationUtils.areSame( + atv.getLowerBound().getAnnotations(), + atv.getUpperBound().getAnnotations())) { + rhs.replaceAnnotations(atv.getLowerBound().getAnnotations()); + } } } else if (rhs.getKind() == TypeKind.DECLARED) { AnnotatedDeclaredType adt = (AnnotatedDeclaredType) rhs.shallowCopy(); diff --git a/framework/tests/viewpointtest/AnnoOnTypeVariableUse.java b/framework/tests/viewpointtest/AnnoOnTypeVariableUse.java new file mode 100644 index 00000000000..ca530274e20 --- /dev/null +++ b/framework/tests/viewpointtest/AnnoOnTypeVariableUse.java @@ -0,0 +1,31 @@ +import viewpointtest.quals.*; + +@SuppressWarnings("cast.unsafe.constructor.invocation") +class AnnoOnTypeVariableUse { + @ReceiverDependentQual E rdq; + @A E a; + @B E b; + E c; + + void test() { + AnnoOnTypeVariableUse<@B Element> d = new @A AnnoOnTypeVariableUse<>(); + // d.element = @A |> @RDQ = @A + d.rdq = new @A Element(); + // :: error: (assignment.type.incompatible) + d.rdq = new @B Element(); + // d.a = @A |> @A = @A + d.a = new @A Element(); + // :: error: (assignment.type.incompatible) + d.a = new @B Element(); + // d.b = @A |> @B = @B + d.b = new @B Element(); + // :: error: (assignment.type.incompatible) + d.b = new @A Element(); + // :: error: (assignment.type.incompatible) + d.c = new @A Element(); + // d.c = @B type argument subsitution with unannotated field + d.c = new @B Element(); + } + + class Element {} +}