eclipse-jdt / eclipse.jdt.core

Eclipse Public License 2.0
164 stars 130 forks source link

[Sealed types] ParameterizedTypeBinding.permittedTypes() needs more rigor #3038

Closed srikanth-sankaran closed 2 weeks ago

srikanth-sankaran commented 1 month ago

In order to fix https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3009, I have made some major changes to org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding.permittedTypes() that are in the right direction - the prior implementation is highly suspect.

However, we may have to iteratively evolve a more rigorous implementation that would handle all complex cases.

In private conversations with @stephan-herrmann, three things were discussed that would/could need follow up:

  1. Do we need the counterpart of org.eclipse.jdt.internal.compiler.lookup.TypeBinding.findSuperTypeOriginatingFrom(TypeBinding) say findSubtypeResultingIn that would answer given a supertype possibly parameterized, what subtype possibly parameterized would have the given supertype as its supertype.
  2. Do we even need to compute the subtype parameterization at all to determine switch exhaustiveness ?
  3. To quote Stephan: "As this is a statement about all possible instantiations of a generic type, I really feel that type inference is the suitable means to find the unknown type, or prove its absence. Inference also has all the means for expressing in its solution type arguments that are not or only partially determined. I don't see how else you can handle arbitrary type parameters introduced in the subclass, with arbitrary dependencies etc."

Quite plausibly.

This ticket is raised to follow up on these

srikanth-sankaran commented 2 weeks ago

In order to fix #3009, I have made some major changes to org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding.permittedTypes() that are in the right direction - the prior implementation is highly suspect.

However, we may have to iteratively evolve a more rigorous implementation that would handle all complex cases.

In private conversations with @stephan-herrmann, three things were discussed that would/could need follow up:

  1. Do we need the counterpart of org.eclipse.jdt.internal.compiler.lookup.TypeBinding.findSuperTypeOriginatingFrom(TypeBinding) say findSubtypeResultingIn that would answer given a supertype possibly parameterized, what subtype possibly parameterized would have the given supertype as its supertype.

We need the functionality - yes - but may be not such an internal API - this function is now buried into org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding.permittedTypes()

  1. Do we even need to compute the subtype parameterization at all to determine switch exhaustiveness ?

Yes, not only to determine switch exhaustiveness (14.11.1.1), but also to determine disjointness in narrowing conversions (5.1.6.1)

14.11.1.1 states:

The fact that a permitted direct subclass or subinterface may only extend a particular
parameterization of a generic sealed superclass or superinterface means that it may not
always need to be considered when determining whether a switch block is exhaustive.
For example:
          sealed interface J<X> permits D, E {}
          final class D<Y> implements J<String> {}
          final class E<X> implements J<X> {}
          static int testExhaustive2(J<Integer> ji) {
          return switch(ji) { // Exhaustive!
          case E<Integer> e -> 42;
          };
          }
As the selector expression has type J<Integer> the permitted direct subclass D need
not be considered as there is no possibility that the value of ji can be an instance of D.

This requires PTB.permittedTypes to prune implausbile subtypes.

As for whether we need to hook into and leverage the type inference infrastructure, the early indication is we don't need to. But it can be tackled separately in future if the need arises. The current improved implementation of org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding.permittedTypes() prunes incompatible types by weeding out cases with provably distinct type arguments.