diff --git a/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileParser.java b/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileParser.java index df52782f5b0..245878543fa 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileParser.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileParser.java @@ -1560,6 +1560,9 @@ private void clearAnnotations(AnnotatedTypeMirror atype, Type typeDef) { // Clear existing annotations, which only makes a difference for // type variables, but doesn't hurt in other cases. atype.clearAnnotations(); + if (atype instanceof AnnotatedTypeVariable) { + ((AnnotatedTypeVariable) atype).markAsSubTypeVariableUse(); + } } /** 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 253f6d1ce7a..79f43c90cc5 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java @@ -8,7 +8,9 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable.TypeVariableUseKind; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; +import org.checkerframework.javacutil.AnnotationMirrorSet; import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.ElementUtils; import org.plumelib.util.IPair; @@ -275,6 +277,10 @@ protected AnnotatedTypeMirror combineAnnotationWithType( apt.replaceAnnotation(resultAnnotation); return apt; } else if (declared.getKind() == TypeKind.TYPEVAR) { + AnnotatedTypeVariable declaredTypeVariable = (AnnotatedTypeVariable) declared; + if (declaredTypeVariable.getTypeVariableUseKind() == TypeVariableUseKind.SUB) { + return declared; + } if (!isTypeVarExtends) { isTypeVarExtends = true; AnnotatedTypeVariable atv = (AnnotatedTypeVariable) declared.shallowCopy(); @@ -292,6 +298,11 @@ protected AnnotatedTypeMirror combineAnnotationWithType( AnnotatedTypeMirror result = AnnotatedTypeCopierWithReplacement.replace(atv, mappings); + AnnotationMirror resultAnnotation = + combineAnnotationWithAnnotation( + receiverAnnotation, extractAnnotationMirror(atv)); + result.replaceAnnotation(resultAnnotation); + ((AnnotatedTypeVariable) result).markAsConcreteTypeVariableUse(resultAnnotation); isTypeVarExtends = false; return result; @@ -520,6 +531,12 @@ private AnnotatedTypeMirror getTypeVariableSubstitution( List tas = decltype.getTypeArguments(); // return a copy, as we want to modify the type later. AnnotatedTypeMirror result = tas.get(foundindex).shallowCopy(true); + if (var.getTypeVariableUseKind() == TypeVariableUseKind.CONCRETE) { + AnnotationMirrorSet concreteAnnotations = var.getConcreteTypeVariableUseAnnotations(); + if (!concreteAnnotations.isEmpty()) { + result.replaceAnnotations(concreteAnnotations); + } + } if (result.getKind() == TypeKind.WILDCARD) { AnnotatedWildcardType wildcard = (AnnotatedWildcardType) result; // When substituting an unbounded wildcard for a bounded type variable, the shallow diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopier.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopier.java index 1636b72e870..6e6b73d4417 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopier.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopier.java @@ -291,6 +291,9 @@ public AnnotatedTypeMirror visitTypeVariable( } AnnotatedTypeVariable copy = makeOrReturnCopy(original, originalToCopy); + copy.setTypeVariableUseKind(original.getTypeVariableUseKind()); + copy.setConcreteTypeVariableUseAnnotations( + original.getConcreteTypeVariableUseAnnotations()); if (original.getUpperBoundField() != null) { copy.setUpperBound(visit(original.getUpperBoundField(), originalToCopy)); diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopierWithReplacement.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopierWithReplacement.java index 42e44fb0a8c..91f0a6c3d54 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopierWithReplacement.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeCopierWithReplacement.java @@ -66,6 +66,9 @@ public AnnotatedTypeMirror visitTypeVariable( original.atypeFactory, original.isDeclaration()); maybeCopyPrimaryAnnotations(original, copy); + copy.setTypeVariableUseKind(original.getTypeVariableUseKind()); + copy.setConcreteTypeVariableUseAnnotations( + original.getConcreteTypeVariableUseAnnotations()); originalToCopy.put(original, copy); if (original.getUpperBoundField() != null) { diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java index aa56faa2d81..814bab10652 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java @@ -21,6 +21,7 @@ import java.lang.annotation.Annotation; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -2151,6 +2152,29 @@ private static void checkBound( */ public static class AnnotatedTypeVariable extends AnnotatedTypeMirror { + /** How this type-variable use should be substituted. */ + public enum TypeVariableUseKind { + /** + * Substitute the full actual type argument. This is the internal representation of + * {@code @Sub E}. + */ + SUB, + + /** + * Substitute the actual type argument, but replace its top-level annotations with the + * annotations on this type-variable use. This is the internal representation of + * {@code @Concrete q E}. + */ + CONCRETE + } + + /** + * Creates a new {@link AnnotatedTypeVariable}. + * + * @param type the underlying type + * @param atypeFactory the type factory + * @param declaration whether this represents a type-variable declaration + */ private AnnotatedTypeVariable( TypeVariable type, AnnotatedTypeFactory atypeFactory, boolean declaration) { super(type, atypeFactory); @@ -2168,16 +2192,133 @@ public TypeKind getKind() { /** The upper bound of the type variable. */ private AnnotatedTypeMirror upperBound; + /** Whether this represents a type-variable declaration. */ private boolean declaration; + /** How this type-variable use should be substituted. */ + private TypeVariableUseKind useKind = TypeVariableUseKind.SUB; + + /** The explicitly written annotations for a {@link TypeVariableUseKind#CONCRETE} use. */ + private AnnotationMirrorSet concreteTypeVariableUseAnnotations = new AnnotationMirrorSet(); + @Override public boolean isDeclaration() { return declaration; } + /** + * Sets whether this type-variable use should be substituted as {@code @Sub E} or + * {@code @Concrete q E}. + * + * @param useKind how this type-variable use should be substituted + */ + public void setTypeVariableUseKind(TypeVariableUseKind useKind) { + this.useKind = useKind; + if (useKind == TypeVariableUseKind.SUB) { + concreteTypeVariableUseAnnotations.clear(); + } + } + + /** + * Returns whether this type-variable use should be substituted as {@code @Sub E} or + * {@code @Concrete q E}. + * + * @return how this type-variable use should be substituted + */ + public TypeVariableUseKind getTypeVariableUseKind() { + return useKind; + } + + /** Marks this type-variable use as {@code @Sub E}. */ + public void markAsSubTypeVariableUse() { + setTypeVariableUseKind(TypeVariableUseKind.SUB); + } + + /** + * Marks this type-variable use as {@code @Concrete q E}. + * + * @param annotations the explicitly written annotations that are {@code q} + */ + public void markAsConcreteTypeVariableUse( + Collection annotations) { + AnnotationMirrorSet supportedAnnotations = new AnnotationMirrorSet(); + for (AnnotationMirror annotation : annotations) { + AnnotationMirror supportedAnnotation = + canonicalSupportedTypeVariableUseAnnotation(annotation); + if (supportedAnnotation != null) { + supportedAnnotations.add(supportedAnnotation); + } + } + if (!supportedAnnotations.isEmpty()) { + useKind = TypeVariableUseKind.CONCRETE; + concreteTypeVariableUseAnnotations = supportedAnnotations; + } + } + + /** + * Marks this type-variable use as {@code @Concrete q E}. + * + * @param annotation the explicitly written annotation that is {@code q} + */ + public void markAsConcreteTypeVariableUse(AnnotationMirror annotation) { + AnnotationMirror supportedAnnotation = + canonicalSupportedTypeVariableUseAnnotation(annotation); + if (supportedAnnotation == null) { + return; + } + useKind = TypeVariableUseKind.CONCRETE; + AnnotationMirror previous = + atypeFactory + .getQualifierHierarchy() + .findAnnotationInSameHierarchy( + concreteTypeVariableUseAnnotations, supportedAnnotation); + if (previous != null) { + concreteTypeVariableUseAnnotations.remove(previous); + } + concreteTypeVariableUseAnnotations.add(supportedAnnotation); + } + + /** + * Returns the supported canonical form of {@code annotation}, or null if it is not a + * qualifier for this type factory. + * + * @param annotation an annotation + * @return the supported canonical form of {@code annotation}, or null + */ + private @Nullable AnnotationMirror canonicalSupportedTypeVariableUseAnnotation( + AnnotationMirror annotation) { + if (atypeFactory.isSupportedQualifier(annotation)) { + return annotation; + } + AnnotationMirror canonical = atypeFactory.canonicalAnnotation(annotation); + return atypeFactory.isSupportedQualifier(canonical) ? canonical : null; + } + + /** + * Sets the explicitly written annotations for a {@link TypeVariableUseKind#CONCRETE} use. + * + * @param annotations the explicitly written annotations + */ + public void setConcreteTypeVariableUseAnnotations(AnnotationMirrorSet annotations) { + concreteTypeVariableUseAnnotations = new AnnotationMirrorSet(annotations); + } + + /** + * Returns the explicitly written annotations for a {@link TypeVariableUseKind#CONCRETE} + * use. + * + * @return the explicitly written annotations for a {@link TypeVariableUseKind#CONCRETE} use + */ + public AnnotationMirrorSet getConcreteTypeVariableUseAnnotations() { + return concreteTypeVariableUseAnnotations; + } + @Override public void addAnnotation(AnnotationMirror annotation) { super.addAnnotation(annotation); + if (!isDeclaration()) { + markAsConcreteTypeVariableUse(annotation); + } fixupBoundAnnotations(); } diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeFromMemberVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/TypeFromMemberVisitor.java index a67453c52c8..323055af98c 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypeFromMemberVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypeFromMemberVisitor.java @@ -9,11 +9,13 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; import org.checkerframework.framework.util.AnnotatedTypes; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypesUtils; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -111,6 +113,10 @@ public AnnotatedTypeMirror visitVariable(VariableTree variableTree, AnnotatedTyp } else { // Add the primary annotation from the variableTree.getModifiers(); AnnotatedTypeMirror innerType = AnnotatedTypes.innerMostType(result); + List explicitTypeVariableUseAnnotations = + innerType.getKind() == TypeKind.TYPEVAR + ? new ArrayList<>() + : Collections.emptyList(); for (AnnotationMirror anno : modifierAnnos) { // The code here is similar to // org.checkerframework.framework.util.element.ElementAnnotationUtil.addDeclarationAnnotationsFromElement. @@ -120,11 +126,18 @@ public AnnotatedTypeMirror visitVariable(VariableTree variableTree, AnnotatedTyp .startsWith("org.checkerframework")) { // Type annotations apply to the innermost type. innerType.addAnnotation(anno); + if (innerType.getKind() == TypeKind.TYPEVAR) { + explicitTypeVariableUseAnnotations.add(anno); + } } else { // Declaration annotations apply to the outer type. result.addAnnotation(anno); } } + if (!explicitTypeVariableUseAnnotations.isEmpty()) { + ((AnnotatedTypeVariable) innerType) + .markAsConcreteTypeVariableUse(explicitTypeVariableUseAnnotations); + } } AnnotatedTypeMirror lambdaParamType = inferLambdaParamAnnotations(f, result, elt); @@ -154,6 +167,17 @@ public AnnotatedTypeMirror visitMethod(MethodTree tree, AnnotatedTypeFactory f) // This would be similar to // org.checkerframework.framework.util.element.ElementAnnotationUtil.addDeclarationAnnotationsFromElement. ElementAnnotationApplier.apply(result, elt, f); + if (result.getReturnType().getKind() == TypeKind.TYPEVAR && tree.getReturnType() != null) { + List explicitAnnotationTrees = + TreeUtils.getExplicitAnnotationTrees( + tree.getModifiers().getAnnotations(), tree.getReturnType()); + if (!explicitAnnotationTrees.isEmpty()) { + ((AnnotatedTypeVariable) result.getReturnType()) + .markAsConcreteTypeVariableUse( + TreeUtils.annotationsFromTypeAnnotationTrees( + explicitAnnotationTrees)); + } + } return result; } diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeFromTypeTreeVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/TypeFromTypeTreeVisitor.java index 22748e133ea..60e9f8c1b55 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypeFromTypeTreeVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypeFromTypeTreeVisitor.java @@ -105,6 +105,9 @@ public AnnotatedTypeMirror visitAnnotatedType(AnnotatedTypeTree tree, AnnotatedT } } else { type.addAnnotations(annos); + if (type.getKind() == TypeKind.TYPEVAR && !annos.isEmpty()) { + ((AnnotatedTypeVariable) type).markAsConcreteTypeVariableUse(annos); + } } return type; diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeVariableSubstitutor.java b/framework/src/main/java/org/checkerframework/framework/type/TypeVariableSubstitutor.java index a9e60ec45d2..12205f136c5 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypeVariableSubstitutor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypeVariableSubstitutor.java @@ -1,6 +1,8 @@ package org.checkerframework.framework.type; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable.TypeVariableUseKind; +import org.checkerframework.javacutil.AnnotationMirrorSet; import org.checkerframework.javacutil.TypesUtils; import java.util.ArrayList; @@ -59,8 +61,9 @@ public AnnotatedTypeMirror substituteWithoutCopyingTypeArguments( * correct annotations. * *

To determine what primary annotations are correct for the substitute the following rules - * are used: If the type variable use has a primary annotation then apply that primary - * annotation to the substitute. Otherwise, use the annotations of the argument. + * are used: if the type variable use represents {@code @Sub E}, use the annotations of the + * argument. If it represents {@code @Concrete q E}, apply the type variable use's primary + * annotations to the substitute. * * @param argument the argument to declaration (this will be a value in typeParamToArg) * @param use the use that is being replaced @@ -69,8 +72,11 @@ public AnnotatedTypeMirror substituteWithoutCopyingTypeArguments( protected AnnotatedTypeMirror substituteTypeVariable( AnnotatedTypeMirror argument, AnnotatedTypeVariable use) { AnnotatedTypeMirror substitute = argument.deepCopy(true); - if (!use.getAnnotationsField().isEmpty()) { - substitute.replaceAnnotations(use.getAnnotationsField()); + if (use.getTypeVariableUseKind() == TypeVariableUseKind.CONCRETE) { + AnnotationMirrorSet concreteAnnotations = use.getConcreteTypeVariableUseAnnotations(); + if (!concreteAnnotations.isEmpty()) { + substitute.replaceAnnotations(concreteAnnotations); + } } return substitute; } diff --git a/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java b/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java index 24fcc50e90d..814f8c57727 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java +++ b/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java @@ -1044,7 +1044,19 @@ protected boolean shouldBeAnnotated(AnnotatedTypeMirror type) { protected void addAnnotation(AnnotatedTypeMirror type, AnnotationMirror qual) { // Add the default annotation, but only if no other annotation is present. if (type.getKind() != TypeKind.EXECUTABLE) { - type.addMissingAnnotation(qual); + if (type instanceof AnnotatedTypeVariable) { + AnnotatedTypeVariable typeVariable = (AnnotatedTypeVariable) type; + AnnotatedTypeVariable.TypeVariableUseKind useKind = + typeVariable.getTypeVariableUseKind(); + AnnotationMirrorSet concreteAnnotations = + new AnnotationMirrorSet( + typeVariable.getConcreteTypeVariableUseAnnotations()); + type.addMissingAnnotation(qual); + typeVariable.setTypeVariableUseKind(useKind); + typeVariable.setConcreteTypeVariableUseAnnotations(concreteAnnotations); + } else { + type.addMissingAnnotation(qual); + } } } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/MethodApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/MethodApplier.java index 14bd8b248e5..cc605ba2d59 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/MethodApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/MethodApplier.java @@ -200,6 +200,11 @@ protected void handleTargeted(List targeted) methodType.getReceiverType(), targetTypeToAnno.get(TargetType.METHOD_RECEIVER)); ElementAnnotationUtil.annotateViaTypeAnnoPosition( methodType.getReturnType(), targetTypeToAnno.get(TargetType.METHOD_RETURN)); + if (methodType.getReturnType() instanceof AnnotatedTypeVariable + && !targetTypeToAnno.get(TargetType.METHOD_RETURN).isEmpty()) { + ((AnnotatedTypeVariable) methodType.getReturnType()) + .markAsConcreteTypeVariableUse(targetTypeToAnno.get(TargetType.METHOD_RETURN)); + } applyThrowsAnnotations(targetTypeToAnno.get(TargetType.THROWS)); if (!unmatched.isEmpty()) { diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/TypeVarUseApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/TypeVarUseApplier.java index 240b7d9eed3..c3ee08ee5ea 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/TypeVarUseApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/TypeVarUseApplier.java @@ -163,6 +163,9 @@ public void extractAndApply() throws UnexpectedAnnotationLocationException { for (Attribute.TypeCompound annotation : typeVarAnnotations) { typeVariable.replaceAnnotation(annotation); } + if (!typeVarAnnotations.isEmpty()) { + typeVariable.markAsConcreteTypeVariableUse(typeVarAnnotations); + } } /** diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/VariableApplier.java b/framework/src/main/java/org/checkerframework/framework/util/element/VariableApplier.java index 27012830236..79e3e826773 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/VariableApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/VariableApplier.java @@ -6,6 +6,7 @@ import com.sun.tools.javac.code.TargetType; import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException; import org.checkerframework.javacutil.BugInCF; @@ -124,6 +125,9 @@ protected boolean isAccepted() { protected void handleTargeted(List targeted) throws UnexpectedAnnotationLocationException { ElementAnnotationUtil.annotateViaTypeAnnoPosition(type, targeted); + if (type instanceof AnnotatedTypeVariable && !targeted.isEmpty()) { + ((AnnotatedTypeVariable) type).markAsConcreteTypeVariableUse(targeted); + } } @Override diff --git a/framework/tests/viewpointtest/TypeVariableUseRequalification.java b/framework/tests/viewpointtest/TypeVariableUseRequalification.java new file mode 100644 index 00000000000..fc2de592fec --- /dev/null +++ b/framework/tests/viewpointtest/TypeVariableUseRequalification.java @@ -0,0 +1,48 @@ +import viewpointtest.quals.*; + +@SuppressWarnings("cast.unsafe.constructor.invocation") +class TypeVariableUseRequalification { + static class Element {} + + static class Fields { + @ReceiverDependentQual E receiverDependent; + @A E concreteA; + E bare; + } + + void fieldTypeVariableUses() { + @A Fields<@B Element> fields = new @A Fields<>(); + + fields.receiverDependent = new @A Element(); + // :: error: (assignment.type.incompatible) + fields.receiverDependent = new @B Element(); + + fields.concreteA = new @A Element(); + // :: error: (assignment.type.incompatible) + fields.concreteA = new @B Element(); + + fields.bare = new @B Element(); + // :: error: (assignment.type.incompatible) + fields.bare = new @A Element(); + } + + static class Methods { + @ReceiverDependentQual E receiverDependent() { + return null; + } + + @A E concreteA() { + return null; + } + + E bare() { + return null; + } + } + + void methodReturnTypeVariableUses(@A Methods<@B Object> methods) { + @A Object receiverDependent = methods.receiverDependent(); + @A Object concreteA = methods.concreteA(); + @B Object bare = methods.bare(); + } +}