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 253f6d1ce7a0..1bfd778fa4cd 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java @@ -120,9 +120,15 @@ public void viewpointAdaptConstructor( AnnotatedTypeMirror p = combineTypeWithType(receiverType, parameterType); mappings.put(parameterType, p); } - for (AnnotatedTypeMirror typeVariable : typeVariables) { - AnnotatedTypeMirror tv = combineTypeWithType(receiverType, typeVariable); - mappings.put(typeVariable, tv); + // Adapt type-variable declarations separately. AnnotatedTypeCopierWithReplacement does not + // replace executable type parameters (see its visitTypeVariable), so install the adapted + // declarations explicitly below. + List adaptedTypeVariables = new ArrayList<>(typeVariables.size()); + for (AnnotatedTypeVariable typeVariable : typeVariables) { + AnnotatedTypeVariable adapted = + (AnnotatedTypeVariable) combineTypeWithType(receiverType, typeVariable); + mappings.put(typeVariable, adapted); + adaptedTypeVariables.add(adapted); } AnnotatedTypeMirror cr = combineTypeWithType(receiverType, constructorReturn); mappings.put(constructorReturn, cr); @@ -133,7 +139,7 @@ public void viewpointAdaptConstructor( unsubstitutedConstructorType, mappings); constructorType.setParameterTypes(unsubstitutedConstructorType.getParameterTypes()); - constructorType.setTypeVariables(unsubstitutedConstructorType.getTypeVariables()); + constructorType.setTypeVariables(adaptedTypeVariables); constructorType.setReturnType(unsubstitutedConstructorType.getReturnType()); } @@ -163,9 +169,17 @@ public void viewpointAdaptMethod( mappings.put(parameterType, p); } + // Adapt type-variable declarations separately. AnnotatedTypeCopierWithReplacement does not + // replace executable type parameters (see its visitTypeVariable), so install the adapted + // declarations explicitly below. Without this, inference and checkTypeArguments would see + // the unadapted declaration bounds (e.g. @ReceiverDependentQual instead of the + // receiver-adapted qualifier). + List adaptedTypeVariables = new ArrayList<>(typeVariables.size()); for (AnnotatedTypeVariable typeVariable : typeVariables) { - AnnotatedTypeMirror tv = combineTypeWithType(receiverType, typeVariable); - mappings.put(typeVariable, tv); + AnnotatedTypeVariable adapted = + (AnnotatedTypeVariable) combineTypeWithType(receiverType, typeVariable); + mappings.put(typeVariable, adapted); + adaptedTypeVariables.add(adapted); } if (returnType.getKind() != TypeKind.VOID) { @@ -184,11 +198,11 @@ public void viewpointAdaptMethod( unsubstitutedMethodType, mappings); // Because we can't viewpoint adapt asMemberOf result, we adapt the declared method first, - // and sets the corresponding parts to asMemberOf result + // and set the corresponding parts on the asMemberOf result. methodType.setReturnType(unsubstitutedMethodType.getReturnType()); methodType.setReceiverType(unsubstitutedMethodType.getReceiverType()); methodType.setParameterTypes(unsubstitutedMethodType.getParameterTypes()); - methodType.setTypeVariables(unsubstitutedMethodType.getTypeVariables()); + methodType.setTypeVariables(adaptedTypeVariables); } /** diff --git a/framework/tests/viewpointtest/ConstructorTypeVariableBounds.java b/framework/tests/viewpointtest/ConstructorTypeVariableBounds.java new file mode 100644 index 000000000000..1328c4f347a3 --- /dev/null +++ b/framework/tests/viewpointtest/ConstructorTypeVariableBounds.java @@ -0,0 +1,79 @@ +import viewpointtest.quals.*; + +public class ConstructorTypeVariableBounds { + static class C { + // No-arg generic constructor: type argument is unused, so inference instantiates T to + // the adapted upper bound. + @SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"}) + C() {} + + @SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"}) + C(T t) {} + } + + void topViewpoint(@Top Object top, @A Object a, @B Object b, @Bottom Object bottom) { + // Constructed type @Top adapts @ReceiverDependentQual to @Lost. Creating @Top is also + // forbidden by the viewpoint test checker. + // :: error: (new.class.type.invalid) :: error: (type.argument.type.incompatible) + new @Top C(); + + // :: error: (new.class.type.invalid) :: error: (type.argument.type.incompatible) + new <@Top Object>@Top C(top); + + // :: error: (new.class.type.invalid) :: error: (type.argument.type.incompatible) + new <@A Object>@Top C(a); + + // :: error: (new.class.type.invalid) :: error: (type.argument.type.incompatible) + new <@B Object>@Top C(b); + + // :: error: (new.class.type.invalid) + new <@Bottom Object>@Top C(bottom); + + // :: error: (new.class.type.invalid) :: error: (type.arguments.not.inferred) + new @Top C(top); + + // :: error: (new.class.type.invalid) :: error: (type.arguments.not.inferred) + new @Top C(a); + + // :: error: (new.class.type.invalid) :: error: (type.arguments.not.inferred) + new @Top C(b); + + // :: error: (new.class.type.invalid) + new @Top C(bottom); + } + + void aViewpoint(@Top Object top, @A Object a, @B Object b, @Bottom Object bottom) { + // Constructed type @A adapts @ReceiverDependentQual to @A, so @A and @Bottom are within + // the adapted constructor type parameter bound. Inference instantiates T to @A for the + // no-arg constructor. + // :: warning: (cast.unsafe.constructor.invocation) + new @A C(); + + // :: error: (type.argument.type.incompatible) :: warning: + // (cast.unsafe.constructor.invocation) + new <@Top Object>@A C(top); + + // :: warning: (cast.unsafe.constructor.invocation) + new <@A Object>@A C(a); + + // :: error: (type.argument.type.incompatible) :: warning: + // (cast.unsafe.constructor.invocation) + new <@B Object>@A C(b); + + // :: warning: (cast.unsafe.constructor.invocation) + new <@Bottom Object>@A C(bottom); + + // :: error: (type.arguments.not.inferred) + new @A C(top); + + // Inference succeeds: argument @A is within the adapted bound @A. + // :: warning: (cast.unsafe.constructor.invocation) + new @A C(a); + + // :: error: (type.arguments.not.inferred) + new @A C(b); + + // :: warning: (cast.unsafe.constructor.invocation) + new @A C(bottom); + } +} diff --git a/framework/tests/viewpointtest/MethodTypeVariableBounds.java b/framework/tests/viewpointtest/MethodTypeVariableBounds.java new file mode 100644 index 000000000000..26693dc0fa34 --- /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. Inference instantiates T to the adapted upper + // bound @A, which is a valid type argument. + 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); + + // Inference succeeds: argument @A is within the adapted bound @A. + methods.withArg(a); + + // :: error: (type.arguments.not.inferred) + methods.withArg(b); + + methods.withArg(bottom); + } +}