Improve treatment of lambdas

This commit is contained in:
Clément Fournier
2024-05-16 20:46:32 +02:00
parent 419ff13fe7
commit 04214b43ca
3 changed files with 89 additions and 16 deletions

View File

@ -63,6 +63,14 @@ public final class ASTLambdaExpression extends AbstractJavaExpr implements Funct
return (ASTLambdaParameterList) getChild(0);
}
public boolean isExplicitlyTyped() {
ASTLambdaParameterList parameters = getParameters();
if (parameters.isEmpty()) {
return true;
}
return parameters.toStream().none(ASTLambdaParameter::isTypeInferred);
}
/** Returns true if this lambda has a block for body. */
public boolean isBlockBody() {

View File

@ -13,10 +13,13 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import net.sourceforge.pmd.lang.java.ast.ASTConstructorCall;
import net.sourceforge.pmd.lang.java.ast.ASTExpression;
import net.sourceforge.pmd.lang.java.ast.ASTLambdaExpression;
import net.sourceforge.pmd.lang.java.ast.ASTList;
import net.sourceforge.pmd.lang.java.ast.ASTMethodCall;
import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
import net.sourceforge.pmd.lang.java.ast.JavaNode;
import net.sourceforge.pmd.lang.java.ast.QualifiableExpression;
import net.sourceforge.pmd.lang.java.ast.internal.JavaAstUtils;
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
import net.sourceforge.pmd.lang.java.types.JMethodSig;
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
@ -150,31 +153,51 @@ public class UnnecessaryBoxingRule extends AbstractJavaRulechainRule {
String reason = null;
if (sourceType.equals(conversionOutput)) {
reason = "boxing of boxed value";
} else if (ctxType != null) {
JTypeMirror conv = implicitConversionResult(sourceType, ctxType, ctx.getKind());
if (conv != null
&& conv.equals(implicitConversionResult(conversionOutput, ctxType, ctx.getKind()))
&& conversionDoesNotChangesValue(sourceType, conversionOutput)) {
if (sourceType.unbox().equals(conversionOutput)) {
reason = "explicit unboxing";
} else if (sourceType.box().equals(conversionOutput)) {
reason = "explicit boxing";
} else {
reason = "explicit conversion from " + TypePrettyPrint.prettyPrintWithSimpleNames(sourceType)
+ " to " + TypePrettyPrint.prettyPrintWithSimpleNames(ctxType);
if (!conversionOutput.equals(ctxType)) {
reason += " through " + TypePrettyPrint.prettyPrintWithSimpleNames(conversionOutput);
}
} else if (isImplicitlyTypedLambdaReturnExpr(conversionExpr)
|| ctxType != null && conversionIsImplicitlyRealisable(sourceType, ctxType, ctx, conversionOutput)) {
if (sourceType.unbox().equals(conversionOutput)) {
reason = "explicit unboxing";
} else if (sourceType.box().equals(conversionOutput)) {
reason = "explicit boxing";
} else if (ctxType != null) {
reason = "explicit conversion from " + TypePrettyPrint.prettyPrintWithSimpleNames(sourceType)
+ " to " + TypePrettyPrint.prettyPrintWithSimpleNames(ctxType);
if (!conversionOutput.equals(ctxType)) {
reason += " through " + TypePrettyPrint.prettyPrintWithSimpleNames(conversionOutput);
}
}
}
}
if (reason != null) {
rctx.addViolation(conversionExpr, reason);
}
}
private static boolean conversionIsImplicitlyRealisable(JTypeMirror sourceType, JTypeMirror ctxType, ExprContext ctx, JTypeMirror conversionOutput) {
JTypeMirror conv = implicitConversionResult(sourceType, ctxType, ctx.getKind());
return conv != null
&& conv.equals(implicitConversionResult(conversionOutput, ctxType, ctx.getKind()))
&& conversionDoesNotChangesValue(sourceType, conversionOutput);
}
private boolean isImplicitlyTypedLambdaReturnExpr(ASTExpression e) {
JavaNode parent = e.getParent();
if (isImplicitlyTypedLambda(parent)) {
return true;
} else if (parent instanceof ASTReturnStatement) {
JavaNode target = JavaAstUtils.getReturnTarget((ASTReturnStatement) parent);
return isImplicitlyTypedLambda(target);
}
return false;
}
private static boolean isImplicitlyTypedLambda(JavaNode e) {
return e instanceof ASTLambdaExpression && !((ASTLambdaExpression) e).isExplicitlyTyped();
}
private boolean isObjectConversionNecessary(ASTExpression e) {
JavaNode parent = e.getParent();
return e.getIndexInParent() == 0 && parent instanceof QualifiableExpression;

View File

@ -548,4 +548,46 @@ public class Foo {
}
]]></code>
</test-code>
<test-code>
<description>Unboxing in lambda return position which returns inferred type</description>
<expected-problems>2</expected-problems>
<expected-linenumbers>2,3</expected-linenumbers>
<expected-messages>
<message>Unnecessary explicit unboxing</message>
<message>Unnecessary explicit unboxing</message>
</expected-messages>
<code><![CDATA[
class Example {
VoidSpecies<Integer> mismatch = payload -> payload.intValue(); // warn: the interface expects void
RefSpecies<Integer, Object> i = t -> t.intValue(); // warn: in this context t->t is equivalent because it will be reboxed
// In this specific situation below, explicit unboxing
// is necessary to distinguish overloads. Note that if
// the lambda was not explicitly typed it would be an
// ambiguity error. Note also that even if the interfaces are
// not generic in the parameter type, but rather take an Integer
// parameter, implicitly typed lambdas will cause an
// ambiguity error.
// In our implementation we consider that the return position of a
// lambda never needs an explicit boxing or unboxing conversion, and
// ignore this corner case, that is unlikely to show up in real code.
static <T> void foo(IntSpecies<T> f) {}
static <T> void foo(RefSpecies<T, Integer> f) {}
static {
foo((Integer i) -> i.intValue());
foo((Integer i) -> i);
}
interface VoidSpecies<T> {
void doSomething(T t);
}
interface RefSpecies<T, X> {
X foo(T t);
}
interface IntSpecies<T> {
int foo(T t);
}
}
]]></code>
</test-code>
</test-data>