forked from phoedos/pmd
Merge pull request #4925 from oowekyala/typeres-explicitly-typed-lambdas
[java] Support explicitly typed lambdas
This commit is contained in:
@ -292,7 +292,7 @@ public interface JClassType extends JTypeMirror {
|
||||
|
||||
/**
|
||||
* Returns another class type which has the same erasure, but new
|
||||
* type arguments.
|
||||
* type arguments. Note that the bounds on the type arguments are not checked.
|
||||
*
|
||||
* @param args Type arguments of the returned type. If empty, and
|
||||
* this type is generic, returns a raw type.
|
||||
|
@ -16,6 +16,7 @@ import static net.sourceforge.pmd.lang.java.types.internal.infer.InferenceVar.Bo
|
||||
import static net.sourceforge.pmd.lang.java.types.internal.infer.InferenceVar.BoundKind.LOWER;
|
||||
import static net.sourceforge.pmd.lang.java.types.internal.infer.InferenceVar.BoundKind.UPPER;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@ -25,6 +26,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import net.sourceforge.pmd.lang.java.types.JClassType;
|
||||
import net.sourceforge.pmd.lang.java.types.JMethodSig;
|
||||
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
|
||||
import net.sourceforge.pmd.lang.java.types.JTypeVar;
|
||||
import net.sourceforge.pmd.lang.java.types.JWildcardType;
|
||||
import net.sourceforge.pmd.lang.java.types.Substitution;
|
||||
import net.sourceforge.pmd.lang.java.types.TypeOps;
|
||||
@ -583,14 +585,72 @@ final class ExprCheckHelper {
|
||||
}
|
||||
|
||||
if (lambda.isExplicitlyTyped() && lambda.getParamCount() > 0) {
|
||||
// TODO infer, normally also for lambdas with no param, i'm just lazy
|
||||
// https://docs.oracle.com/javase/specs/jls/se9/html/jls-18.html#jls-18.5.3
|
||||
return null;
|
||||
return inferGroundTargetTypeForExplicitlyTypedLambda(type, lambda);
|
||||
} else {
|
||||
return nonWildcardParameterization(type);
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable JClassType inferGroundTargetTypeForExplicitlyTypedLambda(JClassType targetType, LambdaExprMirror lambda) {
|
||||
List<JTypeMirror> explicitParamTypes = lambda.getExplicitParameterTypes();
|
||||
assert explicitParamTypes != null : "Expecting explicitly typed lambda";
|
||||
// https://docs.oracle.com/javase/specs/jls/se22/html/jls-18.html#jls-18.5.3
|
||||
// > For example:
|
||||
// > Predicate<? super Integer> p = (Number n) -> n.equals(23);
|
||||
// > The lambda expression is a Predicate<Number>, which is a subtype of Predicate<? super Integer> but not
|
||||
// > Predicate<Integer>. The analysis in this section is used to infer that Number is an appropriate choice
|
||||
// > for the type argument to Predicate.
|
||||
|
||||
// Let `targetType = F<A1, ..., Am>`
|
||||
// Let `'a1, ..., 'am` be fresh inference variables.
|
||||
JClassType targetGTD = targetType.getGenericTypeDeclaration();
|
||||
List<JTypeVar> formalTypeParams = targetGTD.getFormalTypeParams();
|
||||
InferenceContext ctx = infer.newContextFor(formalTypeParams, false);
|
||||
|
||||
// let `inferenceTarget = F<'a1, ..., 'am>`
|
||||
JClassType inferenceTarget = (JClassType) ctx.mapToIVars(targetGTD);
|
||||
|
||||
JMethodSig msig = findFunctionalInterfaceMethod(inferenceTarget);
|
||||
if (msig == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<JTypeMirror> formals = msig.getFormalParameters();
|
||||
|
||||
// Now match formal params of the lambda with those of the signature (which contains ivars)
|
||||
// this adds constraints
|
||||
if (!TypeOps.areSameTypesInInference(formals, explicitParamTypes)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ctx.solve(true); // may throw ResolutionFailedException
|
||||
|
||||
// If we are here then solving succeeded.
|
||||
// Build type arguments with instantiated vars. Vars that were not bound (meaning,
|
||||
// they don't depend on the parameter types of the lambda) are just taken from the
|
||||
// provided type args.
|
||||
|
||||
int numTyArgs = formalTypeParams.size();
|
||||
List<JTypeMirror> typeArgs = targetType.getTypeArgs();
|
||||
List<JTypeMirror> newTyArgs = new ArrayList<>(numTyArgs);
|
||||
for (int i = 0; i < numTyArgs; i++) {
|
||||
InferenceVar ivarI = (InferenceVar) ctx.mapToIVars(formalTypeParams.get(i));
|
||||
if (ivarI.getInst() != null) {
|
||||
newTyArgs.add(ivarI.getInst());
|
||||
} else {
|
||||
newTyArgs.add(typeArgs.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
// Now check that the primary bounds are valid.
|
||||
ctx.addPrimaryBounds();
|
||||
ctx.solve(); // may throw ResolutionFailedException
|
||||
|
||||
// This is our type
|
||||
JClassType inferredTy = targetGTD.withTypeArguments(newTyArgs);
|
||||
return nonWildcardParameterization(inferredTy);
|
||||
}
|
||||
|
||||
|
||||
@FunctionalInterface
|
||||
interface ExprChecker {
|
||||
|
@ -121,7 +121,11 @@ public final class Infer {
|
||||
}
|
||||
|
||||
InferenceContext newContextFor(List<JTypeVar> tvars) {
|
||||
return new InferenceContext(ts, supertypeCheckCache, tvars, LOG);
|
||||
return newContextFor(tvars, true);
|
||||
}
|
||||
|
||||
InferenceContext newContextFor(List<JTypeVar> tvars, boolean addPrimaryBound) {
|
||||
return new InferenceContext(ts, supertypeCheckCache, tvars, LOG, addPrimaryBound);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -80,8 +80,29 @@ final class InferenceContext {
|
||||
* into ivars
|
||||
* @param logger Logger for events related to ivar bounds
|
||||
*/
|
||||
@SuppressWarnings("PMD.AssignmentToNonFinalStatic") // ctxId
|
||||
InferenceContext(TypeSystem ts, SupertypeCheckCache supertypeCheckCache, List<JTypeVar> tvars, TypeInferenceLogger logger) {
|
||||
this(ts, supertypeCheckCache, tvars, logger, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an inference context from a set of type variables to instantiate.
|
||||
* This creates inference vars and may add the initial bounds as described in
|
||||
*
|
||||
* https://docs.oracle.com/javase/specs/jls/se9/html/jls-18.html#jls-18.1.3
|
||||
*
|
||||
* under the purple rectangle.
|
||||
*
|
||||
* @param ts The global type system
|
||||
* @param supertypeCheckCache Super type check cache, shared by all
|
||||
* inference runs in the same compilation unit
|
||||
* (stored in {@link Infer}).
|
||||
* @param tvars Initial tvars which will be turned
|
||||
* into ivars
|
||||
* @param logger Logger for events related to ivar bounds
|
||||
* @param addPrimaryBound Whether to add the primary bound of the vars.
|
||||
*/
|
||||
@SuppressWarnings("PMD.AssignmentToNonFinalStatic") // ctxId
|
||||
InferenceContext(TypeSystem ts, SupertypeCheckCache supertypeCheckCache, List<JTypeVar> tvars, TypeInferenceLogger logger, boolean addPrimaryBound) {
|
||||
this.ts = ts;
|
||||
this.supertypeCheckCache = supertypeCheckCache;
|
||||
this.logger = logger;
|
||||
@ -91,6 +112,16 @@ final class InferenceContext {
|
||||
addVarImpl(p);
|
||||
}
|
||||
|
||||
if (addPrimaryBound) {
|
||||
addPrimaryBounds();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the primary bounds for the ivars of this context. This is usually done upon construction but may be deferred
|
||||
* in some scenarios (inference of ground target type of an explicitly typed lambda).
|
||||
*/
|
||||
void addPrimaryBounds() {
|
||||
for (InferenceVar ivar : inferenceVars) {
|
||||
addPrimaryBound(ivar);
|
||||
}
|
||||
@ -121,7 +152,9 @@ final class InferenceContext {
|
||||
}
|
||||
}
|
||||
|
||||
/** Add a variable to this context. */
|
||||
/**
|
||||
* Add a variable to this context.
|
||||
*/
|
||||
InferenceVar addVar(JTypeVar tvar) {
|
||||
InferenceVar ivar = addVarImpl(tvar);
|
||||
addPrimaryBound(ivar);
|
||||
@ -133,7 +166,9 @@ final class InferenceContext {
|
||||
return ivar;
|
||||
}
|
||||
|
||||
/** Add a variable to this context. */
|
||||
/**
|
||||
* Add a variable to this context.
|
||||
*/
|
||||
private InferenceVar addVarImpl(@NonNull JTypeVar tvar) {
|
||||
InferenceVar ivar = new InferenceVar(this, tvar, varId++);
|
||||
freeVars.add(ivar);
|
||||
|
@ -74,8 +74,4 @@ class O {
|
||||
*/
|
||||
|
||||
|
||||
/* TODO test explicitly typed lambda (in ExplicitTypesTest)
|
||||
wildcard parameterization inference is not implemented yet.
|
||||
*/
|
||||
|
||||
|
||||
|
@ -4,19 +4,17 @@
|
||||
|
||||
package net.sourceforge.pmd.lang.java.types.internal.infer
|
||||
|
||||
import io.kotest.matchers.shouldBe
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTLambdaExpression
|
||||
import net.sourceforge.pmd.lang.test.ast.shouldBeA
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTMethodCall
|
||||
import net.sourceforge.pmd.lang.java.ast.ProcessorTestSpec
|
||||
import net.sourceforge.pmd.lang.java.types.firstMethodCall
|
||||
import net.sourceforge.pmd.lang.java.types.parseWithTypeInferenceSpy
|
||||
import net.sourceforge.pmd.lang.java.types.shouldHaveType
|
||||
import net.sourceforge.pmd.lang.java.types.shouldMatchMethod
|
||||
import net.sourceforge.pmd.lang.java.types.*
|
||||
import java.util.*
|
||||
|
||||
|
||||
class ExplicitTypesTest : ProcessorTestSpec({
|
||||
|
||||
// todo test explicitly typed lambda
|
||||
// todo test explicit type args on ctor call
|
||||
|
||||
|
||||
@ -54,4 +52,65 @@ class ExplicitTypesTest : ProcessorTestSpec({
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
parserTest("Explicitly typed lambda") {
|
||||
|
||||
val (acu, spy) = parser.parseWithTypeInferenceSpy("""
|
||||
|
||||
interface Function<U,V> {
|
||||
V apply(U u);
|
||||
}
|
||||
interface Comparable {}
|
||||
interface Comparator<T> {
|
||||
static <U, X extends Comparable> Comparator<U> comparing(Function<? super U, ? extends X> fun) {}
|
||||
}
|
||||
interface Foo extends Comparable { Foo foo(); }
|
||||
|
||||
class NodeStream {
|
||||
static {
|
||||
Comparator<Foo> cmp = Comparator.comparing((Foo s) -> s.foo());
|
||||
}
|
||||
|
||||
}
|
||||
""")
|
||||
|
||||
val (t_Function, _, _, t_Foo) = acu.declaredTypeSignatures()
|
||||
val (lambda) = acu.descendants(ASTLambdaExpression::class.java).crossFindBoundaries().toList()
|
||||
val call = acu.firstMethodCall()
|
||||
|
||||
spy.shouldBeOk {
|
||||
lambda shouldHaveType t_Function[t_Foo, t_Foo]
|
||||
call.overloadSelectionInfo.isFailed shouldBe false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
parserTest("Explicitly typed lambda with wildcard") {
|
||||
|
||||
val (acu, spy) = parser.parseWithTypeInferenceSpy("""
|
||||
|
||||
interface Number {}
|
||||
interface Integer extends Number {}
|
||||
interface Predicate<T> { boolean test(T t); }
|
||||
|
||||
class NodeStream {
|
||||
static {
|
||||
// note that the lambda is inferred to Predicate<Number> not Predicate<? super Integer> or something
|
||||
Predicate<? super Integer> p = (Number n) -> n.equals(23);
|
||||
}
|
||||
|
||||
}
|
||||
""")
|
||||
|
||||
val (t_Number, _, t_Predicate) = acu.declaredTypeSignatures()
|
||||
val (lambda) = acu.descendants(ASTLambdaExpression::class.java).crossFindBoundaries().toList()
|
||||
|
||||
spy.shouldBeOk {
|
||||
lambda shouldHaveType t_Predicate[t_Number]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
|
Reference in New Issue
Block a user