forked from phoedos/pmd
Improve treatment of lambdas
This commit is contained in:
@ -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() {
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
Reference in New Issue
Block a user