Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
1322f70
Use adapted bounds for method type arguments
aosen-xiong Jul 4, 2026
8060201
Merge branch 'master' into method-typevariables-from-use
aosen-xiong Jul 4, 2026
f7cbccc
Reuse method receiver handling for type-variable bounds
aosen-xiong Jul 4, 2026
f49d910
Merge branch 'master' into method-typevariables-from-use
wmdietl Jul 16, 2026
df99a83
Fix doclint warnings
aosen-xiong Jul 6, 2026
b02a8e7
Install adapted executable type variables for viewpoint adaptation
aosen-xiong Jul 16, 2026
c3808e7
Merge branch 'master' into method-typevariables-from-use
aosen-xiong Jul 16, 2026
c23bd13
Leave AnnotatedTypeFactory unchanged
aosen-xiong Jul 16, 2026
46e83c8
Leave BaseTypeVisitor unchanged
aosen-xiong Jul 16, 2026
ef1442d
Format ConstructorTypeVariableBounds with Spotless
aosen-xiong Jul 16, 2026
16f7c80
Adapt executable type variable bounds via AnnotatedTypeCopierWithRepl…
aosen-xiong Jul 24, 2026
6173058
Merge branch 'master' into method-typevariables-from-use
aosen-xiong Jul 24, 2026
a4e94e1
Pin ruff version in GitHub Actions setup and fix ruff 0.16.0 lint fin…
wmdietl Jul 24, 2026
2328a58
Update dependency openjdk/jdk to v32 (#1885)
renovate[bot] Jul 24, 2026
89cf608
Expand executable type-variable bound tests
aosen-xiong Jul 28, 2026
c0913b8
Merge branch 'master' into method-typevariables-from-use
aosen-xiong Jul 28, 2026
f732953
Trigger CI
aosen-xiong Jul 28, 2026
2315211
Merge branch 'master' into method-typevariables-from-use
wmdietl Aug 4, 2026
d3ce2de
Simplify executable type variable bounds adaptation loops
wmdietl Aug 5, 2026
5f46cc0
Merge branch 'master' into method-typevariables-from-use
wmdietl Aug 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -106,32 +106,45 @@ public void viewpointAdaptConstructor(
AnnotatedTypeMirror receiverType,
ExecutableElement constructorElt,
AnnotatedExecutableType constructorType) {
// constructorType's typevar are not substituted when calling viewpointAdaptConstructor
// 1. Make a copy of constructorType before type variables are substituted.
AnnotatedExecutableType unsubstitutedConstructorType = constructorType.deepCopy();

// For constructors, we adapt parameter types, return type and type parameters
// 2. Viewpoint-adapt constructor parameter types, type variable bounds, and return type.
List<AnnotatedTypeMirror> parameterTypes = unsubstitutedConstructorType.getParameterTypes();
List<AnnotatedTypeVariable> typeVariables = unsubstitutedConstructorType.getTypeVariables();
AnnotatedTypeMirror constructorReturn = unsubstitutedConstructorType.getReturnType();

IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> mappings =
new IdentityHashMap<>();

// 2a. Adapt parameter types.
for (AnnotatedTypeMirror parameterType : parameterTypes) {
AnnotatedTypeMirror p = combineTypeWithType(receiverType, parameterType);
mappings.put(parameterType, p);
}
for (AnnotatedTypeMirror typeVariable : typeVariables) {
AnnotatedTypeMirror tv = combineTypeWithType(receiverType, typeVariable);
mappings.put(typeVariable, tv);

// 2b. Adapt upper and lower bounds of constructor type variables.
for (AnnotatedTypeVariable typeVariable : typeVariables) {
AnnotatedTypeMirror adaptedUpper =
combineTypeWithType(receiverType, typeVariable.getUpperBound());
mappings.put(typeVariable.getUpperBound(), adaptedUpper);

AnnotatedTypeMirror adaptedLower =
combineTypeWithType(receiverType, typeVariable.getLowerBound());
mappings.put(typeVariable.getLowerBound(), adaptedLower);
}

// 2c. Adapt constructor return type.
AnnotatedTypeMirror cr = combineTypeWithType(receiverType, constructorReturn);
mappings.put(constructorReturn, cr);

// 3. Replace components using AnnotatedTypeCopierWithReplacement.
unsubstitutedConstructorType =
(AnnotatedExecutableType)
AnnotatedTypeCopierWithReplacement.replace(
unsubstitutedConstructorType, mappings);

// 4. Update target constructor type in place with adapted components.
constructorType.setParameterTypes(unsubstitutedConstructorType.getParameterTypes());
constructorType.setTypeVariables(unsubstitutedConstructorType.getTypeVariables());
constructorType.setReturnType(unsubstitutedConstructorType.getReturnType());
Expand All @@ -142,14 +155,15 @@ public void viewpointAdaptMethod(
AnnotatedTypeMirror receiverType,
ExecutableElement methodElt,
AnnotatedExecutableType methodType) {
// 1. Check whether the method should be viewpoint-adapted (e.g. skip static methods).
if (!shouldAdaptMethod(methodElt)) {
return;
}

// methodType's typevar are not substituted when calling viewpointAdaptMethod
// 2. Make a copy of methodType before type variables are substituted.
AnnotatedExecutableType unsubstitutedMethodType = methodType.deepCopy();

// For methods, we additionally adapt method receiver compared to constructors
// 3. Viewpoint-adapt parameter types, type variable bounds, return type, and receiver.
List<AnnotatedTypeMirror> parameterTypes = unsubstitutedMethodType.getParameterTypes();
List<AnnotatedTypeVariable> typeVariables = unsubstitutedMethodType.getTypeVariables();
AnnotatedTypeMirror returnType = unsubstitutedMethodType.getReturnType();
Expand All @@ -158,33 +172,44 @@ public void viewpointAdaptMethod(
IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> mappings =
new IdentityHashMap<>();

// 3a. Adapt parameter types.
for (AnnotatedTypeMirror parameterType : parameterTypes) {
AnnotatedTypeMirror p = combineTypeWithType(receiverType, parameterType);
mappings.put(parameterType, p);
}

// 3b. Adapt upper and lower bounds of method type variables.
for (AnnotatedTypeVariable typeVariable : typeVariables) {
AnnotatedTypeMirror tv = combineTypeWithType(receiverType, typeVariable);
mappings.put(typeVariable, tv);
AnnotatedTypeMirror adaptedUpper =
combineTypeWithType(receiverType, typeVariable.getUpperBound());
mappings.put(typeVariable.getUpperBound(), adaptedUpper);

AnnotatedTypeMirror adaptedLower =
combineTypeWithType(receiverType, typeVariable.getLowerBound());
mappings.put(typeVariable.getLowerBound(), adaptedLower);
}

// 3c. Adapt non-void return type.
if (returnType.getKind() != TypeKind.VOID) {
AnnotatedTypeMirror r = combineTypeWithType(receiverType, returnType);
mappings.put(returnType, r);
}

// 3d. Adapt method receiver type.
if (methodReceiver != null) {
AnnotatedTypeMirror mr = combineTypeWithType(receiverType, methodReceiver);
mappings.put(methodReceiver, mr);
}

// 4. Replace components using AnnotatedTypeCopierWithReplacement.
unsubstitutedMethodType =
(AnnotatedExecutableType)
AnnotatedTypeCopierWithReplacement.replace(
unsubstitutedMethodType, mappings);

// 5. Update target method type in place with adapted components.
// 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());
Expand Down
199 changes: 199 additions & 0 deletions framework/tests/viewpointtest/ConstructorTypeVariableBounds.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
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"})
<T extends @ReceiverDependentQual Object> C() {}

@SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"})
<T extends @ReceiverDependentQual Object> C(T t) {}
}

static class LowerBoundC {
// The @ReceiverDependentQual annotation on T is its explicit lower bound. The upper bound
// is the implicit Object bound.
@SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"})
<@ReceiverDependentQual T> LowerBoundC() {}

@SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"})
<@ReceiverDependentQual T> LowerBoundC(T t) {}
}

static class LowerAndUpperBoundC {
@SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"})
<@ReceiverDependentQual T extends @ReceiverDependentQual Object> LowerAndUpperBoundC() {}

@SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"})
<@ReceiverDependentQual T extends @ReceiverDependentQual Object> LowerAndUpperBoundC(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);

// The lower bound @ReceiverDependentQual viewpoint-adapts to @Lost. Explicit type
// arguments must be supertypes of that lower bound, so only @Top is valid.
// :: error: (new.class.type.invalid)
new @Top LowerBoundC();

// :: error: (new.class.type.invalid)
new <@Top Object>@Top LowerBoundC(top);

// :: error: (new.class.type.invalid) :: error: (type.argument.type.incompatible)
new <@A Object>@Top LowerBoundC(a);

// :: error: (new.class.type.invalid) :: error: (type.argument.type.incompatible)
new <@B Object>@Top LowerBoundC(b);

// :: error: (new.class.type.invalid) :: error: (type.argument.type.incompatible)
new <@Bottom Object>@Top LowerBoundC(bottom);

// Inference can choose @Top, which is above both the adapted lower bound and the argument.
// :: error: (new.class.type.invalid)
new @Top LowerBoundC(top);

// :: error: (new.class.type.invalid)
new @Top LowerBoundC(a);

// :: error: (new.class.type.invalid)
new @Top LowerBoundC(b);

// :: error: (new.class.type.invalid) :: error: (type.arguments.not.inferred)
new @Top LowerBoundC(bottom);

// Both bounds viewpoint-adapt to @Lost. Because @Lost is non-reflexive, no type argument
// can be both above the lower bound and below the upper bound.
// :: error: (new.class.type.invalid) :: error: (type.arguments.not.inferred)
new @Top LowerAndUpperBoundC();

// :: error: (new.class.type.invalid) :: error: (type.arguments.not.inferred)
new <@Top Object>@Top LowerAndUpperBoundC(top);

// :: error: (new.class.type.invalid) :: error: (type.arguments.not.inferred)
new <@A Object>@Top LowerAndUpperBoundC(a);

// :: error: (new.class.type.invalid) :: error: (type.arguments.not.inferred)
new <@B Object>@Top LowerAndUpperBoundC(b);

// :: error: (new.class.type.invalid) :: error: (type.arguments.not.inferred)
new <@Bottom Object>@Top LowerAndUpperBoundC(bottom);

// :: error: (new.class.type.invalid) :: error: (type.arguments.not.inferred)
new @Top LowerAndUpperBoundC(top);

// :: error: (new.class.type.invalid) :: error: (type.arguments.not.inferred)
new @Top LowerAndUpperBoundC(a);

// :: error: (new.class.type.invalid) :: error: (type.arguments.not.inferred)
new @Top LowerAndUpperBoundC(b);

// :: error: (new.class.type.invalid) :: error: (type.arguments.not.inferred)
new @Top LowerAndUpperBoundC(bottom);
}

@SuppressWarnings("cast.unsafe.constructor.invocation")
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.
new @A C();

// :: error: (type.argument.type.incompatible)
new <@Top Object>@A C(top);

new <@A Object>@A C(a);

// :: error: (type.argument.type.incompatible)
new <@B Object>@A C(b);

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.
new @A C(a);

// :: error: (type.arguments.not.inferred)
new @A C(b);

new @A C(bottom);

// The lower bound @ReceiverDependentQual viewpoint-adapts to @A. Explicit type arguments
// must be supertypes of @A, so @Top and @A are valid.
new @A LowerBoundC();

new <@Top Object>@A LowerBoundC(top);

new <@A Object>@A LowerBoundC(a);

// :: error: (type.argument.type.incompatible)
new <@B Object>@A LowerBoundC(b);

// :: error: (type.argument.type.incompatible)
new <@Bottom Object>@A LowerBoundC(bottom);

// Inference chooses a type argument that is above both @A and the invocation argument.
new @A LowerBoundC(top);

new @A LowerBoundC(a);

new @A LowerBoundC(b);

new @A LowerBoundC(bottom);

// Both bounds viewpoint-adapt to @A, so an explicit type argument must be exactly @A.
new @A LowerAndUpperBoundC();

// :: error: (type.argument.type.incompatible)
new <@Top Object>@A LowerAndUpperBoundC(top);

new <@A Object>@A LowerAndUpperBoundC(a);

// :: error: (type.argument.type.incompatible)
new <@B Object>@A LowerAndUpperBoundC(b);

// :: error: (type.argument.type.incompatible)
new <@Bottom Object>@A LowerAndUpperBoundC(bottom);

// :: error: (type.arguments.not.inferred)
new @A LowerAndUpperBoundC(top);

// Inference chooses T = @A.
new @A LowerAndUpperBoundC(a);

// :: error: (type.arguments.not.inferred)
new @A LowerAndUpperBoundC(b);

// Inference chooses T = @A, which accepts the @Bottom argument.
new @A LowerAndUpperBoundC(bottom);
}
}
Loading
Loading