Skip to content

Conversation

@oowekyala
Copy link
Member

@oowekyala oowekyala commented Oct 21, 2025

#6146 (comment)

public class Foo {

  @Override
  public void bar() {
      Iterable<Bar> local = filter(unknown(), input -> input.debug());
  }

  public static <T> Iterable<T> filter(final Iterable<T> unfiltered,
    final java.util.function.Predicate<? super T> retainIfTrue) {
    return null;
  }
}

(thanks for the test case @zbynek)

In this example, the problem was that

  1. We find the method filter is applicable
  2. When inferring the invocation type, we first replace T with an inference variable 'a. We set the type of the lambda to be Predicate<? super 'a>, which means the type of its parameter input is 'a.
  3. We encounter both the constraints T = (*unknown*) and T = Bar (from the argument unknown() and the context type Iterable<Bar> respectively). Those are considered incompatible so we fail.
  4. After failing invocation, we don't clean up the type of the lambda or of input so the inference variable stay in the tree
  5. Later on during rule execution, TypeTestUtil finds the inference variable and tries to cast its symbol it to JClassSymbol and fails

This fix has three parts

  1. We now properly clean up the tree when invocation fails, that is here we will type the lambda as Predicate<(*error*)> and type input as (*error*), removing the 'a from AST nodes. This was the root problem really.
  2. We also now consider constraints like T = (* unknown *) and T = Bar to be compatible. This improves type inference when types are missing from the classpath, and will allow the invocation in this example not to fail. To be complete, {T = (*unknown*), T = Bar} will solve to Bar, and {T = (*error*), T = Bar} will solve to (*error*), that is, the error type is contagious.
  3. Lastly I made TypeTestUtil more defensive so that it cannot throw a ClassCastException anymore. This will hide future issues... 🤷

Related issues

Ready?

  • Added unit tests for fixed bug/feature
  • Passing all unit tests
  • Complete build ./mvnw clean verify passes (checked automatically by github actions)
  • Added (in-code) documentation (if needed)

@oowekyala oowekyala added this to the 7.18.0 milestone Oct 21, 2025
@pmd-actions-helper
Copy link
Contributor

pmd-actions-helper bot commented Oct 22, 2025

Documentation Preview

No regression tested rules have been changed.

(comment created at 2025-10-28 08:02:48+00:00 for 76126d5)

@oowekyala oowekyala marked this pull request as ready for review October 22, 2025 19:43
@adangel adangel self-requested a review October 27, 2025 17:23
Copy link
Member

@adangel adangel left a comment

Choose a reason for hiding this comment

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

Thanks!

oowekyala and others added 8 commits October 28, 2025 08:42
…le with other constraints

To fix pmd#6146 we still need to fix the underlying issue,
which is that a resolution failure (ie, the `filter(...)`)
call here fails resolving after having tried out the overload,
and that doesn't clean up the whole tree from inference variables.

As a last step we can check that the cast is feasible in TypeTestUtil,
although it will brush future issues under the rug.
This does not need to be done when testing
for applicability as this really only affects
lambdas, and they are not relevant to
applicability
Will not throw if an inference variable is found
@adangel adangel force-pushed the issue6146-classcastexception branch from 92162f9 to 76126d5 Compare October 28, 2025 07:43
@adangel adangel merged commit 7ea02e8 into pmd:main Oct 28, 2025
1 check passed
@oowekyala oowekyala deleted the issue6146-classcastexception branch October 28, 2025 08:13
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.

[java] ClassCastException: class InferenceVarSym cannot be cast to class JClassSymbol

2 participants