Skip to content

Add subtyping check to receiver type argument for method invocation#793

Open
aosen-xiong wants to merge 60 commits intoeisop:masterfrom
aosen-xiong:type-argument
Open

Add subtyping check to receiver type argument for method invocation#793
aosen-xiong wants to merge 60 commits intoeisop:masterfrom
aosen-xiong:type-argument

Conversation

@aosen-xiong
Copy link
Copy Markdown
Collaborator

@aosen-xiong aosen-xiong commented Jun 25, 2024

Fixes #104

Note that the implementation ignores the subtyping check when the receiver type argument has a poly annotation. See PolyQualifierOnTypeArgument.java

Copy link
Copy Markdown
Member

@wmdietl wmdietl left a comment

Choose a reason for hiding this comment

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

As discussed, please add a jtreg test.

@wmdietl wmdietl assigned aosen-xiong and unassigned wmdietl Jun 26, 2024
@wmdietl
Copy link
Copy Markdown
Member

wmdietl commented Jun 26, 2024

Maybe this would fix #104? Can you try with tests from that issue?

@aosen-xiong
Copy link
Copy Markdown
Collaborator Author

Maybe this would fix #104? Can you try with tests from that issue?

Just checked, this did not fix it.

@aosen-xiong aosen-xiong requested a review from wmdietl June 27, 2024 04:08
@aosen-xiong aosen-xiong assigned wmdietl and unassigned aosen-xiong Jun 27, 2024
Copy link
Copy Markdown
Member

@wmdietl wmdietl left a comment

Choose a reason for hiding this comment

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

Thanks for starting to look into this. We should look at this properly and fix #104 while we touch this code.

@wmdietl wmdietl assigned aosen-xiong and unassigned wmdietl Jun 27, 2024
@aosen-xiong aosen-xiong requested a review from wmdietl June 27, 2024 18:57
@aosen-xiong aosen-xiong assigned wmdietl and unassigned aosen-xiong Jun 27, 2024
@wmdietl wmdietl assigned aosen-xiong and unassigned wmdietl Jun 28, 2024
@aosen-xiong aosen-xiong requested a review from wmdietl July 3, 2024 18:36
@aosen-xiong aosen-xiong removed their assignment Jul 3, 2024
@@ -2183,7 +2183,6 @@ public Void visitMethodInvocation(MethodInvocationTree tree, Void p) {
Copy link
Copy Markdown
Member

@wmdietl wmdietl Mar 25, 2026

Choose a reason for hiding this comment

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

Maybe these checks should also go into checkMethodInvocability?
In that method, there is a comment Static methods don't have a receiver to check. The check here already excludes static methods, so we should raise an error if the receiver is still null.

(And undo the white-space only change.)

Copy link
Copy Markdown
Collaborator Author

@aosen-xiong aosen-xiong Mar 25, 2026

Choose a reason for hiding this comment

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

is the purpose for !ElementUtils.isStatic(invokedMethodElement) && !TreeUtils.isSuperConstructorCall(tree)

the same as

methodReceiver != null and method.getElement().getKind() != ElementKind.CONSTRUCTOR?

I am wondering which checks should be kept in checkMethodInvocability.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

They seem different.
ElementUtils.isStatic(invokedMethodElement) seems higher-level and more declarative, whereas the null check is lower-level and implementation focussed. Maybe something went wrong and the receiver is null? So I would prefer the first one.

For the constructors, they are different and we need to figure out which one we actually want. The first one only excludes super(...) calls, not this(...) calls. Why? The second one checks whether the element is any constructor. Can you think through which is better?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Okay.

Use the second constructor filter seems safer to me.

I don't understand the todo comment We must not use the self type, but instead should find a way to determine the receiver of the enclosing constructor.

Does it mean the self.something() call below should use @B for the receiver subtyping checking instead of @A? Or is it a problem for the inner class's constructor?

 class Foo {
      @A Foo() { 
          this(42);
          self.something(); 
      }
      @B Foo(int x) { ... }
  }

@aosen-xiong aosen-xiong requested a review from wmdietl March 25, 2026 23:23
@aosen-xiong aosen-xiong assigned wmdietl and unassigned aosen-xiong Mar 25, 2026
Copy link
Copy Markdown
Member

@wmdietl wmdietl left a comment

Choose a reason for hiding this comment

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

Thanks, some follow-up comments.

AnnotatedTypeMirror erasedTreeReceiver = erasedMethodReceiver.shallowCopy(false);
AnnotatedTypeMirror treeReceiver = atypeFactory.getReceiverType(tree);
ParameterizedExecutableType methodDef =
atypeFactory.methodFromUseWithoutTypeArgInference(tree);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why can't this not simply use methodReceiver or treeReceiver that we calculate below? What is different about this receiver? Or maybe you can use invokedMethodElement?
In any case, the variable names are rather non-descriptive, making the distinction invisible.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I renamed the variable to methodDefPreSubstitution.

methodReceiver or method's poly annotation already got substituted. I need the receiver before substitution.

@wmdietl wmdietl assigned aosen-xiong and unassigned wmdietl Mar 26, 2026
Updated closed issues in CHANGELOG.md to reflect changes.
@aosen-xiong
Copy link
Copy Markdown
Collaborator Author

@wmdietl We have two options for this PR.

  1. Enable all method receiver type argument subtyping checks and add error/suppression to daikon and plume-lib.
  2. Disable the method receiver type argument when the declared method has a poly type argument.

Which one do you like?

Copilot AI review requested due to automatic review settings April 2, 2026 20:50
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes #104 by ensuring that method invocations validate the receiver’s annotated type arguments via a full subtyping check, so annotations like Box<@NonNull T> this are no longer ignored at call sites.

Changes:

  • Update receiver invocability checking to perform a subtype check on the full receiver type (including type arguments) rather than on erased types.
  • Add Nullness tests that (a) reproduces the original bug and (b) documents/guards the special-case behavior for polymorphic annotations on receiver type arguments.
  • Document the user-visible behavior change in the changelog and list the closed issue.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.

File Description
framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java Implements receiver type-argument subtyping check during method invocation, with a poly-annotation skip.
docs/CHANGELOG.md Notes the new receiver type-argument subtyping behavior and adds the closed issue entry.
checker/tests/nullness/ReceiverTypeArgs.java New regression test demonstrating the previously-missed invalid receiver invocation.
checker/tests/nullness-genericwildcard/PolyQualifierOnTypeArgument.java New test covering the “skip when receiver type arg is polymorphic” behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +3984 to +3987
ParameterizedExecutableType methodDefPreSubstitution =
atypeFactory.methodFromUseWithoutTypeArgInference(tree);
List<AnnotatedTypeMirror> declaredTypeArgs =
methodDefPreSubstitution.executableType.getReceiverType().getTypeArguments();
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

visitMethodInvocation already computes preInference = atypeFactory.methodFromUseWithoutTypeArgInference(tree); checkMethodInvocability recomputes the same value, which duplicates non-trivial work per method call and risks a noticeable performance regression. Consider reusing the already-computed preInference (e.g., pass it in via an overload or refactor the poly-receiver check to avoid a second factory call).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good point. But we haven't decide we want to exclude the check when poly is present. Let's leave it for now.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
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.

Annotations on any type arguments of a method receiver are ignored

3 participants