diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 39b95fd78aae..106422ad44c9 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -2236,8 +2236,7 @@ public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { List typeargs = mType.typeArgs; List paramBounds = - CollectionsPlume.mapList( - AnnotatedTypeVariable::getBounds, invokedMethod.getTypeVariables()); + atypeFactory.methodTypeVariableBoundsFromUse(tree, invokedMethod); ExecutableElement method = invokedMethod.getElement(); CharSequence methodName = ElementUtils.getSimpleDescription(method); @@ -2638,8 +2637,7 @@ public Void visitNewClass(NewClassTree tree, Void p) { checkVarargs(constructorType, tree); List paramBounds = - CollectionsPlume.mapList( - AnnotatedTypeVariable::getBounds, constructorType.getTypeVariables()); + atypeFactory.constructorTypeVariableBoundsFromUse(tree, constructorType); checkTypeArguments( tree, diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java index 84349678b40c..39ad407aea1a 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java @@ -2446,6 +2446,116 @@ public List typeVariablesFromUse( return res; } + /** + * Returns the method type-variable bounds adapted to the viewpoint of a method invocation. + * + * @param tree a method invocation + * @param invokedMethod the type of the invoked method + * @return the adapted method type parameter bounds + */ + public List methodTypeVariableBoundsFromUse( + MethodInvocationTree tree, AnnotatedExecutableType invokedMethod) { + List bounds = + CollectionsPlume.mapList( + AnnotatedTypeVariable::getBounds, invokedMethod.getTypeVariables()); + + AnnotatedTypeMirror receiverType = getMethodReceiverType(tree); + if (viewpointAdapter != null && receiverType != null) { + viewpointAdapter.viewpointAdaptTypeParameterBounds(receiverType, bounds); + } + return bounds; + } + + /** + * Returns the constructor type-variable bounds adapted to the viewpoint of a constructor + * invocation. + * + * @param tree a constructor invocation + * @param invokedConstructor the type of the invoked constructor + * @return the adapted constructor type parameter bounds + */ + public List constructorTypeVariableBoundsFromUse( + NewClassTree tree, AnnotatedExecutableType invokedConstructor) { + List bounds = + CollectionsPlume.mapList( + AnnotatedTypeVariable::getBounds, invokedConstructor.getTypeVariables()); + + if (viewpointAdapter != null) { + AnnotatedTypeMirror receiverType = getConstructorReceiverType(tree); + viewpointAdapter.viewpointAdaptTypeParameterBounds(receiverType, bounds); + } + return bounds; + } + + /** + * Returns the receiver type used to viewpoint-adapt a constructor invocation. + * + * @param tree a constructor invocation tree + * @return the receiver type + * @see #getReceiverType(ExpressionTree) + * @see #getMethodReceiverType(MethodInvocationTree) + */ + public AnnotatedDeclaredType getConstructorReceiverType(NewClassTree tree) { + // Get the annotations written on the new class tree. + AnnotatedDeclaredType type = + (AnnotatedDeclaredType) toAnnotatedType(TreeUtils.typeOf(tree), false); + if (!TreeUtils.isDiamondTree(tree)) { + if (tree.getClassBody() == null) { + type.setTypeArguments(getExplicitNewClassClassTypeArgs(tree)); + } + } else { + type = getAnnotatedType(TypesUtils.getTypeElement(type.underlyingType)); + // Add explicit annotations below. + type.clearAnnotations(); + } + + AnnotationMirrorSet explicitAnnos = getExplicitNewClassAnnos(tree); + type.addAnnotations(explicitAnnos); + + // Get the enclosing type of the constructor, if one exists. + // this.new InnerClass() + AnnotatedDeclaredType enclosingType = (AnnotatedDeclaredType) getReceiverType(tree); + if (enclosingType != null && enclosingType.isFrozen()) { + // getReceiverType may return a shared frozen cache value; it is embedded in `type` and + // mutated by addComputedTypeAnnotations below, so copy it first. + enclosingType = enclosingType.deepCopy(); + } + type.setEnclosingType(enclosingType); + + // Add computed annotations to the type. + addComputedTypeAnnotations(tree, type); + + return type; + } + + /** + * Returns the receiver type used to viewpoint-adapt a method invocation. + * + * @param tree a method invocation tree + * @return the receiver type, or null if the invocation has no receiver + * @see #getReceiverType(ExpressionTree) + * @see #getConstructorReceiverType(NewClassTree) + */ + public @Nullable AnnotatedTypeMirror getMethodReceiverType(MethodInvocationTree tree) { + ExecutableElement methodElt = TreeUtils.elementFromUse(tree); + if (ElementUtils.isStatic(methodElt)) { + return null; + } + + AnnotatedTypeMirror receiverType = getReceiverType(tree); + if (receiverType == null + && (TreeUtils.isSuperConstructorCall(tree) + || TreeUtils.isThisConstructorCall(tree))) { + // super() and this() calls don't have a receiver, but they should be view-point adapted + // as if "this" is the receiver. + receiverType = getSelfType(tree); + } + if (receiverType != null && receiverType.getKind() == TypeKind.DECLARED) { + receiverType = applyCaptureConversion(receiverType); + } + return receiverType; + } + /** * Creates and returns an AnnotatedNullType qualified with {@code annotations}. * @@ -2660,6 +2770,8 @@ private boolean isSameType(TypeMirror type1, TypeMirror type2) { * * @param expression the expression for which to determine the receiver type * @return the type of the receiver of expression + * @see #getMethodReceiverType(MethodInvocationTree) + * @see #getConstructorReceiverType(NewClassTree) */ public final @Nullable AnnotatedTypeMirror getReceiverType(ExpressionTree expression) { AnnotatedTypeMirror receiverType; @@ -2781,17 +2893,7 @@ public ParameterizedExecutableType methodFromUseWithoutTypeArgInference( protected ParameterizedExecutableType methodFromUse( MethodInvocationTree tree, boolean inferTypeArgs) { ExecutableElement methodElt = TreeUtils.elementFromUse(tree); - AnnotatedTypeMirror receiverType = getReceiverType(tree); - if (receiverType == null - && (TreeUtils.isSuperConstructorCall(tree) - || TreeUtils.isThisConstructorCall(tree))) { - // super() and this() calls don't have a receiver, but they should be view-point adapted - // as if "this" is the receiver. - receiverType = getSelfType(tree); - } - if (receiverType != null && receiverType.getKind() == TypeKind.DECLARED) { - receiverType = applyCaptureConversion(receiverType); - } + AnnotatedTypeMirror receiverType = getMethodReceiverType(tree); ParameterizedExecutableType result = methodFromUse(tree, methodElt, receiverType, inferTypeArgs); @@ -3519,34 +3621,7 @@ public AnnotatedTypeMirror getResultingTypeOfConstructorMemberReference( */ protected ParameterizedExecutableType constructorFromUse( NewClassTree tree, boolean inferTypeArgs) { - // Get the annotations written on the new class tree. - AnnotatedDeclaredType type = - (AnnotatedDeclaredType) toAnnotatedType(TreeUtils.typeOf(tree), false); - if (!TreeUtils.isDiamondTree(tree)) { - if (tree.getClassBody() == null) { - type.setTypeArguments(getExplicitNewClassClassTypeArgs(tree)); - } - } else { - type = getAnnotatedType(TypesUtils.getTypeElement(type.underlyingType)); - // Add explicit annotations below. - type.clearAnnotations(); - } - - AnnotationMirrorSet explicitAnnos = getExplicitNewClassAnnos(tree); - type.addAnnotations(explicitAnnos); - - // Get the enclosing type of the constructor, if one exists. - // this.new InnerClass() - AnnotatedDeclaredType enclosingType = (AnnotatedDeclaredType) getReceiverType(tree); - if (enclosingType != null && enclosingType.isFrozen()) { - // getReceiverType may return a shared frozen cache value; it is embedded in `type` and - // mutated by addComputedTypeAnnotations below, so copy it first. - enclosingType = enclosingType.deepCopy(); - } - type.setEnclosingType(enclosingType); - - // Add computed annotations to the type. - addComputedTypeAnnotations(tree, type); + AnnotatedDeclaredType type = getConstructorReceiverType(tree); ExecutableElement ctor = TreeUtils.elementFromUse(tree); AnnotatedExecutableType con = getAnnotatedType(ctor); // get unsubstituted type @@ -3645,9 +3720,9 @@ protected ParameterizedExecutableType constructorFromUse( addDefaultAnnotations(returnType); con.setReturnType(returnType); } - if (enclosingType != null) { + if (type.getEnclosingType() != null) { // Reset the enclosing type because it can be substituted incorrectly. - ((AnnotatedDeclaredType) con.getReturnType()).setEnclosingType(enclosingType); + ((AnnotatedDeclaredType) con.getReturnType()).setEnclosingType(type.getEnclosingType()); } if (type.isUnderlyingTypeRaw() || TypesUtils.isRaw(TreeUtils.typeOf(tree))) { ((AnnotatedDeclaredType) con.getReturnType()).setIsUnderlyingTypeRaw(); diff --git a/framework/tests/viewpointtest/ConstructorTypeVariableBounds.java b/framework/tests/viewpointtest/ConstructorTypeVariableBounds.java new file mode 100644 index 000000000000..84c059db0c23 --- /dev/null +++ b/framework/tests/viewpointtest/ConstructorTypeVariableBounds.java @@ -0,0 +1,19 @@ +import viewpointtest.quals.*; + +public class ConstructorTypeVariableBounds { + static class MyClass { + MyClass(T t) {} + } + + void test(@Top Object top, @A Object a, @B Object b, @Bottom Object bottom) { + + // :: error: (type.argument.type.incompatible) :: error: (new.class.type.invalid) + new <@Top Object>@Top MyClass(top); + + // :: warning: (cast.unsafe.constructor.invocation) + new <@A Object>@A MyClass(a); + + // :: warning: (cast.unsafe.constructor.invocation) + new <@B Object>@B MyClass(b); + } +} diff --git a/framework/tests/viewpointtest/MethodTypeVariableBounds.java b/framework/tests/viewpointtest/MethodTypeVariableBounds.java new file mode 100644 index 000000000000..e7fe4c948242 --- /dev/null +++ b/framework/tests/viewpointtest/MethodTypeVariableBounds.java @@ -0,0 +1,72 @@ +import viewpointtest.quals.*; + +public class MethodTypeVariableBounds { + static class Methods { + void noArg() {} + + void withArg(T t) {} + } + + void topReceiver( + @Top Methods methods, + @Top Object top, + @A Object a, + @B Object b, + @Bottom Object bottom) { + // @Top viewpoint-adapts @ReceiverDependentQual to @Lost, so only @Bottom is within the + // adapted method type parameter bound. + // :: error: (type.argument.type.incompatible) + methods.noArg(); + + // :: error: (type.argument.type.incompatible) + methods.<@Top Object>withArg(top); + + // :: error: (type.argument.type.incompatible) + methods.<@A Object>withArg(a); + + // :: error: (type.argument.type.incompatible) + methods.<@B Object>withArg(b); + + methods.<@Bottom Object>withArg(bottom); + + // :: error: (type.arguments.not.inferred) + methods.withArg(top); + + // :: error: (type.arguments.not.inferred) + methods.withArg(a); + + // :: error: (type.arguments.not.inferred) + methods.withArg(b); + + methods.withArg(bottom); + } + + void aReceiver( + @A Methods methods, @Top Object top, @A Object a, @B Object b, @Bottom Object bottom) { + // @A viewpoint-adapts @ReceiverDependentQual to @A, so @A and @Bottom are within the + // adapted method type parameter bound. + // :: error: (type.argument.type.incompatible) + methods.noArg(); + + // :: error: (type.argument.type.incompatible) + methods.<@Top Object>withArg(top); + + methods.<@A Object>withArg(a); + + // :: error: (type.argument.type.incompatible) + methods.<@B Object>withArg(b); + + methods.<@Bottom Object>withArg(bottom); + + // :: error: (type.arguments.not.inferred) + methods.withArg(top); + + // :: error: (type.arguments.not.inferred) + methods.withArg(a); + + // :: error: (type.arguments.not.inferred) + methods.withArg(b); + + methods.withArg(bottom); + } +}