Skip to content

Retain load-bearing type witness in select position in UnnecessaryExplicitTypeArguments#911

Merged
knutwannheden merged 1 commit into
mainfrom
fix-unnecessaryexplicittypearguments-false-positive
May 28, 2026
Merged

Retain load-bearing type witness in select position in UnnecessaryExplicitTypeArguments#911
knutwannheden merged 1 commit into
mainfrom
fix-unnecessaryexplicittypearguments-false-positive

Conversation

@knutwannheden
Copy link
Copy Markdown
Contributor

Motivation

UnnecessaryExplicitTypeArguments removes an explicit type witness from a generic method invocation even when that witness is load-bearing — specifically when the invocation is the select (receiver) of another method invocation. In that position there is no target type to drive type inference, so the witness is required, and removing it produces non-compiling code.

Observed in the wild on rewrite-spring's ImplicitWebAnnotationNames:

// before — compiles
J.VariableDeclarations.NamedVariable namedVariable =
        varDecsCursor.<J.VariableDeclarations>getValue().getVariables().get(0);

// after recipe — does NOT compile
J.VariableDeclarations.NamedVariable namedVariable =
        varDecsCursor.getValue().getVariables().get(0);

Cursor#getValue() is declared <T> T getValue(). With the <J.VariableDeclarations> witness removed and the result immediately chained into .getVariables(), there is no target type, so T infers to Object, and Object has no getVariables() method.

Summary

  • Split the enclosing instanceof J.MethodInvocation handling into select vs. argument positions (enclosingMethod.getSelect() == method).
  • In select position the enclosing call provides no target type, so the witness is retained unless the type variables are inferable from the call's own arguments.
  • Extracted that inference check into canInferTypeArgumentsFromArguments(...), reused by shouldRetainOnStaticMethod so existing static-method and argument-position behavior is unchanged.

Test plan

  • Added retainsWitnessWhenResultIsSelectOfMethodInvocation reproducing the chained-call case (recipe makes no change).
  • Full UnnecessaryExplicitTypeArgumentsTest suite passes, including the pre-existing select-position case GenericClass.<T>typedBuilderWithClass(clazz).build() where the witness is still correctly removed (type variable inferable from the Class<T> argument).

…plicitTypeArguments`

`UnnecessaryExplicitTypeArguments` removed an explicit type witness from a
generic method invocation even when that invocation was the select (receiver)
of another method invocation. In that position there is no target type to drive
inference, so the witness is load-bearing and removing it produces non-compiling
code (the type variable infers to `Object`).

Split the enclosing-`J.MethodInvocation` handling into select vs. argument
positions. In select position the witness is retained unless the type variables
are inferable from the call's own arguments; the existing argument-position and
static-method behavior is preserved by reusing the same inference check.
@github-project-automation github-project-automation Bot moved this to In Progress in OpenRewrite May 28, 2026
@knutwannheden knutwannheden merged commit 421318a into main May 28, 2026
1 check passed
@knutwannheden knutwannheden deleted the fix-unnecessaryexplicittypearguments-false-positive branch May 28, 2026 18:49
@github-project-automation github-project-automation Bot moved this from In Progress to Done in OpenRewrite May 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant