Skip to content

Fix problem with type var in a method reference#7729

Open
smillst wants to merge 13 commits into
typetools:masterfrom
smillst:issue7680
Open

Fix problem with type var in a method reference#7729
smillst wants to merge 13 commits into
typetools:masterfrom
smillst:issue7680

Conversation

@smillst
Copy link
Copy Markdown
Member

@smillst smillst commented May 18, 2026

Fixes #7680.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR replaces the concrete InvocationType with an abstract InferenceExecutableType and adds two concrete subclasses: InferenceMethodType (for method and constructor invocations) and CompileTimeDeclarationType (for method-reference compile-time declarations). InferenceFactory is updated to construct and return these concrete types. InvocationTypeInference and constraint-processing code paths are updated to accept and thread the new types. A regression test Issue7680 is added and a small Javadoc link update is included.

Possibly related PRs

  • typetools/checker-framework#7542: Both PRs change method-reference type-inference logic in typeinference8/constraint/Expression.java—the main PR refactors the underlying method-ref/execute-type abstractions (InferenceExecutableType/CompileTimeDeclarationType) while the retrieved PR adjusts the reduceMethodRef B2/B3 control flow based on whether type-argument inference is needed.
  • typetools/checker-framework#7545: Yes—both PRs modify the type-inference handling for method references in InferenceFactory (changing/using compile-time method-reference declaration typing and receiver adjustments), so the underlying method-reference inference code paths are directly connected.

Suggested reviewers

  • mernst
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR modifies type inference abstractions for method references by introducing InferenceExecutableType/InferenceMethodType/CompileTimeDeclarationType to replace InvocationType, enabling proper type variable handling in method references as required by issue #7680.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing type inference for method references: updated InvocationTypeInference, InferenceFactory, constraint/Expression classes, and added new type abstraction classes with a test case for the issue.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@smillst smillst assigned mernst and smillst and unassigned mernst May 19, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/MethodType.java`:
- Around line 43-62: The method getReturnType(Theta map) computes a
branch-specific Java returnType for diamond/new-class cases but then still
passes the stale returnTypeJava into the result constructors; update the
return-site to use the computed local returnType instead of returnTypeJava so
ProperType(...) and InferenceType.create(...) receive the correct underlying
Java type (modify the two return expressions in getReturnType to pass
returnType, keeping annotatedReturnType, map, and context unchanged).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b7686254-7dea-4e95-9208-6c6857a0006c

📥 Commits

Reviewing files that changed from the base of the PR and between aefd9c3 and e381cd1.

📒 Files selected for processing (2)
  • framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/MethodReferenceType.java
  • framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/MethodType.java

@smillst smillst assigned mernst and unassigned smillst May 26, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceExecutableType.java (1)

98-103: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Thread qualifierVars into thrown-type construction.

getThrownTypes currently builds types with the overload that does not reuse this executable’s precomputed qualifierVars. That gives thrown types a different @Poly* variable mapping than the parameters/return type, which can make inference inconsistent for the same executable.

♻️ Proposed fix
-      thrown.add(InferenceType.create(t, iter.next(), map, context));
+      thrown.add(InferenceType.create(t, iter.next(), map, qualifierVars, context, false));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceExecutableType.java`
around lines 98 - 103, getThrownTypes builds thrown types using
InferenceType.create(t, iter.next(), map, context) which ignores this
executable's precomputed qualifierVars and yields inconsistent `@Poly`* mappings;
update getThrownTypes to pass the executable's qualifierVars into the
thrown-type construction (i.e., call the InferenceType.create overload that
accepts qualifierVars, supplying the stored qualifierVars from this class) so
thrown types share the same qualifier variable mapping as the parameters/return.
framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceMethodType.java (1)

71-74: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve executable-level qualifier vars on the return type.

This path bypasses the precomputed qualifierVars, so a polymorphic qualifier on the return can get a fresh variable instead of the one shared with the parameters. That changes inference behavior for @Poly* methods.

♻️ Proposed fix
-    if (map == null) {
-      return new ProperType(annotatedReturnType, returnType, context);
-    }
-    return InferenceType.create(annotatedReturnType, returnType, map, context);
+    return InferenceType.create(
+        annotatedReturnType, returnType, map, qualifierVars, context, false);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceMethodType.java`
around lines 71 - 74, When map == null the code returns a new ProperType which
bypasses this object's precomputed qualifierVars and causes polymorphic
qualifiers on the return to get fresh variables; instead, preserve the existing
qualifierVars by returning an InferenceType constructed with them. Replace the
early return in InferenceMethodType (the branch that currently does `return new
ProperType(annotatedReturnType, returnType, context)`) to call
InferenceType.create(annotatedReturnType, returnType, qualifierVars, context) so
the return type shares the same qualifierVars as the parameters.
framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/CompileTimeDeclarationType.java (2)

121-124: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve executable-level qualifier vars on the method-reference return type.

Like InferenceMethodType, this return-type path drops the shared qualifierVars. Any @Poly* qualifier on the referenced method’s return type can stop matching the qualifier variables already assigned to its parameters.

♻️ Proposed fix
-    if (map == null) {
-      return new ProperType(annotatedReturnType, returnType, context);
-    }
-    return InferenceType.create(annotatedReturnType, returnType, map, context);
+    return InferenceType.create(
+        annotatedReturnType, returnType, map, qualifierVars, context, false);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/CompileTimeDeclarationType.java`
around lines 121 - 124, The return-type path in CompileTimeDeclarationType drops
executable-level qualifier variables: when map != null you call
InferenceType.create(annotatedReturnType, returnType, map, context) which loses
this object's qualifierVars and thus any `@Poly`* on the referenced method's
return type; preserve them by passing the CompileTimeDeclarationType's
qualifierVars into the created InferenceType (either by calling an overload of
InferenceType.create that accepts qualifierVars or by constructing the
InferenceType then assigning its qualifierVars) so that
annotatedReturnType/returnType inference keeps the same executable-level
qualifier variable set.

83-103: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Override the arity-less parameter lookup for unbound member references.

InferenceExecutableType.getParameterTypes(map) passes annotatedExecutableType.getParameterTypes().size(), but this subtype prepends the receiver for unbound references. For an unbound varargs reference, that makes size too small by one, so the vararg slot is removed and never added back.

♻️ Proposed fix
+  `@Override`
+  public List<AbstractType> getParameterTypes(Theta map) {
+    int size = annotatedExecutableType.getParameterTypes().size();
+    if (MemberReferenceKind.getMemberReferenceKind(methodRef).isUnbound()) {
+      size++;
+    }
+    return getParameterTypes(map, size);
+  }
+
   `@Override`
   public List<AbstractType> getParameterTypes(Theta map, int size) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/CompileTimeDeclarationType.java`
around lines 83 - 103, The method getParameterTypes incorrectly uses the
incoming size without accounting for the prepended receiver when
MemberReferenceKind.getMemberReferenceKind(methodRef).isUnbound() is true,
causing varargs removal to drop the last slot; fix by incrementing the effective
arity used for varargs handling when unbound (e.g., adjust size = size + 1 right
after you prepend the receiver) so the subsequent TreeUtils.isVarargsCall branch
and its loops that rely on size/params.size()/paramsJava.size() operate with the
receiver accounted for; update references in getParameterTypes (methodRef,
receiver, size, params, paramsJava, TreeUtils.isVarargsCall) accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/CompileTimeDeclarationType.java`:
- Around line 121-124: The return-type path in CompileTimeDeclarationType drops
executable-level qualifier variables: when map != null you call
InferenceType.create(annotatedReturnType, returnType, map, context) which loses
this object's qualifierVars and thus any `@Poly`* on the referenced method's
return type; preserve them by passing the CompileTimeDeclarationType's
qualifierVars into the created InferenceType (either by calling an overload of
InferenceType.create that accepts qualifierVars or by constructing the
InferenceType then assigning its qualifierVars) so that
annotatedReturnType/returnType inference keeps the same executable-level
qualifier variable set.
- Around line 83-103: The method getParameterTypes incorrectly uses the incoming
size without accounting for the prepended receiver when
MemberReferenceKind.getMemberReferenceKind(methodRef).isUnbound() is true,
causing varargs removal to drop the last slot; fix by incrementing the effective
arity used for varargs handling when unbound (e.g., adjust size = size + 1 right
after you prepend the receiver) so the subsequent TreeUtils.isVarargsCall branch
and its loops that rely on size/params.size()/paramsJava.size() operate with the
receiver accounted for; update references in getParameterTypes (methodRef,
receiver, size, params, paramsJava, TreeUtils.isVarargsCall) accordingly.

In
`@framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceExecutableType.java`:
- Around line 98-103: getThrownTypes builds thrown types using
InferenceType.create(t, iter.next(), map, context) which ignores this
executable's precomputed qualifierVars and yields inconsistent `@Poly`* mappings;
update getThrownTypes to pass the executable's qualifierVars into the
thrown-type construction (i.e., call the InferenceType.create overload that
accepts qualifierVars, supplying the stored qualifierVars from this class) so
thrown types share the same qualifier variable mapping as the parameters/return.

In
`@framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceMethodType.java`:
- Around line 71-74: When map == null the code returns a new ProperType which
bypasses this object's precomputed qualifierVars and causes polymorphic
qualifiers on the return to get fresh variables; instead, preserve the existing
qualifierVars by returning an InferenceType constructed with them. Replace the
early return in InferenceMethodType (the branch that currently does `return new
ProperType(annotatedReturnType, returnType, context)`) to call
InferenceType.create(annotatedReturnType, returnType, qualifierVars, context) so
the return type shares the same qualifierVars as the parameters.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0d1c8640-5e5c-4da0-837c-4f7016bf5643

📥 Commits

Reviewing files that changed from the base of the PR and between 7cb2b23 and 8e8246d.

📒 Files selected for processing (6)
  • framework/src/main/java/org/checkerframework/framework/util/typeinference8/InvocationTypeInference.java
  • framework/src/main/java/org/checkerframework/framework/util/typeinference8/constraint/Expression.java
  • framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/CompileTimeDeclarationType.java
  • framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceExecutableType.java
  • framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceFactory.java
  • framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/InferenceMethodType.java

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/CompileTimeDeclarationType.java`:
- Around line 74-76: Update the Javadoc in class CompileTimeDeclarationType for
the method that returns the method reference (the method whose Javadoc currently
reads "Returns the method reference for which this a compile-time declaration.")
by changing the phrase "for which this a" to "for which this is a" so the
sentence reads "Returns the method reference for which this is a compile-time
declaration."
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8be71a08-0b62-44f4-ac42-04830be751f3

📥 Commits

Reviewing files that changed from the base of the PR and between 8e8246d and ba795b2.

📒 Files selected for processing (1)
  • framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/CompileTimeDeclarationType.java

@mernst mernst assigned smillst and unassigned mernst May 27, 2026
@mernst
Copy link
Copy Markdown
Member

mernst commented May 27, 2026

@smillst Please look at the CodeRabbit "outside the diff" suggestions, then re-assign me. Thanks!

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/CompileTimeDeclarationType.java (1)

82-104: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix unbound-varargs sizing when getParameterTypes(map) is called without explicit size.

Line 94 and Line 98 expand varargs using size, but for unbound references the receiver is prepended at Line 88 while inherited default getParameterTypes(map) still uses the pre-receiver parameter count. In the exact method-reference path (Expression.reduceMethodRef), this can yield too few formals for unbound varargs references.

Suggested fix
+  `@Override`
+  public List<AbstractType> getParameterTypes(Theta map) {
+    int size = annotatedExecutableType.getParameterTypes().size();
+    if (MemberReferenceKind.getMemberReferenceKind(methodRef).isUnbound()) {
+      size++;
+    }
+    return getParameterTypes(map, size);
+  }
+
   `@Override`
   public List<AbstractType> getParameterTypes(Theta map, int size) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/CompileTimeDeclarationType.java`
around lines 82 - 104, The varargs expansion uses the incoming size parameter
but the overload getParameterTypes(Theta map) calls the two-arg
getParameterTypes without accounting for the extra receiver inserted for unbound
method references; update the no-size overload to compute an effective size that
equals the annotatedExecutableType parameter count plus 1 if
MemberReferenceKind.getMemberReferenceKind(methodRef).isUnbound() before
delegating to getParameterTypes(Theta map, int size) (so the receiver-added
parameter is reflected when expanding varargs), leaving the existing
getParameterTypes(Theta map, int size), TreeUtils.isVarargsCall(methodRef),
receiver handling, and InferenceType.create(...) intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/CompileTimeDeclarationType.java`:
- Around line 82-104: The varargs expansion uses the incoming size parameter but
the overload getParameterTypes(Theta map) calls the two-arg getParameterTypes
without accounting for the extra receiver inserted for unbound method
references; update the no-size overload to compute an effective size that equals
the annotatedExecutableType parameter count plus 1 if
MemberReferenceKind.getMemberReferenceKind(methodRef).isUnbound() before
delegating to getParameterTypes(Theta map, int size) (so the receiver-added
parameter is reflected when expanding varargs), leaving the existing
getParameterTypes(Theta map, int size), TreeUtils.isVarargsCall(methodRef),
receiver handling, and InferenceType.create(...) intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d7ec1a77-6851-4407-8776-7c6fcdd18371

📥 Commits

Reviewing files that changed from the base of the PR and between ba795b2 and db9d8d3.

📒 Files selected for processing (1)
  • framework/src/main/java/org/checkerframework/framework/util/typeinference8/types/CompileTimeDeclarationType.java

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

StructuralEqualityComparer: unexpected combination

2 participants