• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pmd / pmd / 4478

28 Feb 2025 09:39AM UTC coverage: 77.731% (+0.01%) from 77.717%
4478

push

github

adangel
[doc] Update contributors for 7.11.0 (#5551)

Merge pull request #5551 from adangel:doc-update-contributors-7.11.0

17473 of 23424 branches covered (74.59%)

Branch coverage included in aggregate %.

38247 of 48259 relevant lines covered (79.25%)

0.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.3
/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryCastRule.java
1
/*
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.lang.java.rule.codestyle;
6

7
import static net.sourceforge.pmd.lang.java.ast.BinaryOp.ADD;
8
import static net.sourceforge.pmd.lang.java.ast.BinaryOp.DIV;
9
import static net.sourceforge.pmd.lang.java.ast.BinaryOp.GE;
10
import static net.sourceforge.pmd.lang.java.ast.BinaryOp.GT;
11
import static net.sourceforge.pmd.lang.java.ast.BinaryOp.LE;
12
import static net.sourceforge.pmd.lang.java.ast.BinaryOp.LT;
13
import static net.sourceforge.pmd.lang.java.ast.BinaryOp.MOD;
14
import static net.sourceforge.pmd.lang.java.ast.BinaryOp.MUL;
15
import static net.sourceforge.pmd.lang.java.ast.BinaryOp.SHIFT_OPS;
16
import static net.sourceforge.pmd.lang.java.ast.BinaryOp.SUB;
17
import static net.sourceforge.pmd.lang.java.ast.internal.JavaAstUtils.isInfixExprWithOperator;
18
import static net.sourceforge.pmd.util.OptionalBool.NO;
19
import static net.sourceforge.pmd.util.OptionalBool.UNKNOWN;
20
import static net.sourceforge.pmd.util.OptionalBool.YES;
21
import static net.sourceforge.pmd.util.OptionalBool.definitely;
22

23
import java.util.EnumSet;
24
import java.util.Set;
25

26
import org.checkerframework.checker.nullness.qual.NonNull;
27
import org.checkerframework.checker.nullness.qual.Nullable;
28

29
import net.sourceforge.pmd.lang.java.ast.ASTCastExpression;
30
import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression;
31
import net.sourceforge.pmd.lang.java.ast.ASTExpression;
32
import net.sourceforge.pmd.lang.java.ast.ASTInfixExpression;
33
import net.sourceforge.pmd.lang.java.ast.ASTLambdaExpression;
34
import net.sourceforge.pmd.lang.java.ast.ASTMethodReference;
35
import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
36
import net.sourceforge.pmd.lang.java.ast.BinaryOp;
37
import net.sourceforge.pmd.lang.java.ast.InvocationNode;
38
import net.sourceforge.pmd.lang.java.ast.JavaNode;
39
import net.sourceforge.pmd.lang.java.ast.internal.JavaAstUtils;
40
import net.sourceforge.pmd.lang.java.ast.internal.PrettyPrintingUtil;
41
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
42
import net.sourceforge.pmd.lang.java.symbols.JExecutableSymbol;
43
import net.sourceforge.pmd.lang.java.types.JClassType;
44
import net.sourceforge.pmd.lang.java.types.JMethodSig;
45
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
46
import net.sourceforge.pmd.lang.java.types.OverloadSelectionResult;
47
import net.sourceforge.pmd.lang.java.types.TypeConversion;
48
import net.sourceforge.pmd.lang.java.types.TypeOps;
49
import net.sourceforge.pmd.lang.java.types.TypeOps.Convertibility;
50
import net.sourceforge.pmd.lang.java.types.TypeTestUtil;
51
import net.sourceforge.pmd.lang.java.types.ast.ExprContext;
52
import net.sourceforge.pmd.lang.java.types.ast.ExprContext.ExprContextKind;
53
import net.sourceforge.pmd.util.OptionalBool;
54

55
/**
56
 * Detects casts where the operand is already a subtype of the context
57
 * type, or may be converted to it implicitly.
58
 */
59
public class UnnecessaryCastRule extends AbstractJavaRulechainRule {
60

61
    private static final Set<BinaryOp> BINARY_PROMOTED_OPS =
1✔
62
        EnumSet.of(LE, GE, GT, LT, ADD, SUB, MUL, DIV, MOD);
1✔
63

64
    public UnnecessaryCastRule() {
65
        super(ASTCastExpression.class);
1✔
66
    }
1✔
67

68
    @Override
69
    public Object visit(ASTCastExpression castExpr, Object data) {
70
        ASTExpression operand = castExpr.getOperand();
1✔
71

72
        // eg in
73
        // Object o = (Integer) 1;
74

75
        @Nullable ExprContext context = castExpr.getConversionContext();        // Object
1✔
76
        JTypeMirror coercionType = castExpr.getCastType().getTypeMirror();      // Integer
1✔
77
        JTypeMirror operandType = operand.getTypeMirror();                      // int
1✔
78

79
        if (TypeOps.isUnresolvedOrNull(operandType)
1✔
80
            || TypeOps.isUnresolvedOrNull(coercionType)) {
1✔
81
            return null;
1✔
82
        }
83

84
        // Note that we assume that coercionType is convertible to
85
        // contextType because the code must compile
86

87
        if (operand instanceof ASTLambdaExpression || operand instanceof ASTMethodReference) {
1✔
88
            // Then the cast provides a target type for the expression (always).
89
            // We need to check the enclosing context, as if it's invocation we give up for now
90
            if (context.isMissing() || context.hasKind(ExprContextKind.INVOCATION)) {
1!
91
                // Then the cast may be used to determine the overload.
92
                // We need to treat the casted lambda as a whole unit.
93
                // todo see below
94
                return null;
1✔
95
            }
96

97
            // Since the code is assumed to compile we'll just assume that coercionType
98
            // is a functional interface.
99
            if (coercionType.equals(context.getTargetType())) {
1✔
100
                // then we also know that the context is functional
101
                reportCast(castExpr, data);
1✔
102
            }
103
            // otherwise the cast is narrowing, and removing it would
104
            // change the runtime class of the produced lambda.
105
            // Eg `SuperItf obj = (SubItf) ()-> {};`
106
            // If we remove the cast, even if it might compile,
107
            // the object will not implement SubItf anymore.
108
        } else if (isCastUnnecessary(castExpr, context, coercionType, operandType)) {
1✔
109
            reportCast(castExpr, data);
1✔
110
        }
111
        return null;
1✔
112
    }
113

114
    private boolean isCastUnnecessary(ASTCastExpression castExpr, @NonNull ExprContext context, JTypeMirror coercionType, JTypeMirror operandType) {
115
        if (isCastDeterminingReturnOfLambda(castExpr) != NO) {
1✔
116
            return false;
1✔
117
        }
118

119
        if (operandType.equals(coercionType)) {
1✔
120
            // with the exception of the lambda thing above, casts to
121
            // the same type are always unnecessary
122
            return true;
1✔
123
        } else if (context.isMissing()) {
1✔
124
            // then we have fewer violation conditions
125

126
            return !operandType.isBottom() // casts on a null literal are necessary
1!
127
                && operandType.isSubtypeOf(coercionType)
1✔
128
                && !isCastToRawType(coercionType, operandType);
1✔
129
        }
130

131
        return !isCastDeterminingContext(castExpr, context, coercionType, operandType)
1✔
132
            && castIsUnnecessaryToMatchContext(context, coercionType, operandType);
1✔
133
    }
134

135
    /**
136
     * Whether this cast is casting a non-raw type to a raw type.
137
     * This is part of the {@link Convertibility#bySubtyping()} relation,
138
     * and needs to be singled out as operations on the raw type
139
     * behave differently than on the non-raw type. In that case the
140
     * cast may be necessary to avoid compile-errors, even though it
141
     * will be noop at runtime (an _unchecked_ cast).
142
     */
143
    private boolean isCastToRawType(JTypeMirror coercionType, JTypeMirror operandType) {
144
        return coercionType.isRaw() && !operandType.isRaw();
1!
145
    }
146

147
    private void reportCast(ASTCastExpression castExpr, Object data) {
148
        asCtx(data).addViolation(castExpr, PrettyPrintingUtil.prettyPrintType(castExpr.getCastType()));
1✔
149
    }
1✔
150

151
    private static boolean castIsUnnecessaryToMatchContext(ExprContext context,
152
                                                           JTypeMirror coercionType,
153
                                                           JTypeMirror operandType) {
154
        if (context.hasKind(ExprContextKind.INVOCATION)) {
1✔
155
            // todo unsupported for now, the cast may be disambiguating overloads
156
            return false;
1✔
157
        }
158

159
        JTypeMirror contextType = context.getTargetType();
1✔
160
        if (contextType == null) {
1!
161
            return false; // should not occur in valid code
×
162
        } else if (!TypeConversion.isConvertibleUsingBoxing(operandType, coercionType)) {
1✔
163
            // narrowing cast
164
            return false;
1✔
165
        } else if (!context.acceptsType(operandType)) {
1✔
166
            // then removing the cast would produce uncompilable code
167
            return false;
1✔
168
        }
169

170
        boolean isBoxingFollowingCast = contextType.isPrimitive() != coercionType.isPrimitive();
1✔
171
        // means boxing behavior is equivalent
172
        return !isBoxingFollowingCast || operandType.unbox().isSubtypeOf(contextType.unbox());
1✔
173
    }
174

175
    /**
176
     * Returns whether the context type actually depends on the cast.
177
     * This means our analysis as written above won't work, and usually
178
     * that the cast is necessary, because there's some primitive conversions
179
     * happening, or some other corner case.
180
     */
181
    private static boolean isCastDeterminingContext(ASTCastExpression castExpr, ExprContext context, @NonNull JTypeMirror coercionType, JTypeMirror operandType) {
182

183
        if (castExpr.getParent() instanceof ASTConditionalExpression && castExpr.getIndexInParent() != 0) {
1✔
184
            // a branch of a ternary
185
            return true;
1✔
186

187
        } else if (context.hasKind(ExprContextKind.STRING) && isInfixExprWithOperator(castExpr.getParent(), ADD)) {
1!
188

189
            // inside string concatenation
190
            return !TypeTestUtil.isA(String.class, JavaAstUtils.getOtherOperandIfInInfixExpr(castExpr))
1✔
191
                && !TypeTestUtil.isA(String.class, operandType);
1!
192

193
        } else if (context.hasKind(ExprContextKind.NUMERIC) && castExpr.getParent() instanceof ASTInfixExpression) {
1✔
194
            // numeric expr
195
            ASTInfixExpression parent = (ASTInfixExpression) castExpr.getParent();
1✔
196

197
            if (isInfixExprWithOperator(parent, SHIFT_OPS)) {
1✔
198
                // if so, then the cast is determining the width of expr
199
                // the right operand is always int
200
                return castExpr == parent.getLeftOperand()
1✔
201
                        && !TypeOps.isStrictSubtype(operandType.unbox(), operandType.getTypeSystem().INT);
1✔
202
            } else if (isInfixExprWithOperator(parent, BINARY_PROMOTED_OPS)) {
1!
203
                ASTExpression otherOperand = JavaAstUtils.getOtherOperandIfInInfixExpr(castExpr);
1✔
204
                JTypeMirror otherType = otherOperand.getTypeMirror();
1✔
205

206
                // Ie, the type that is taken by the binary promotion
207
                // is the type of the cast, not the type of the operand.
208
                // Eg in
209
                //     int i; ((double) i) * i
210
                // the only reason the mult expr has type double is because of the cast
211
                JTypeMirror promotedTypeWithoutCast = TypeConversion.binaryNumericPromotion(operandType, otherType);
1✔
212
                JTypeMirror promotedTypeWithCast = TypeConversion.binaryNumericPromotion(coercionType, otherType);
1✔
213
                return !promotedTypeWithoutCast.equals(promotedTypeWithCast);
1✔
214
            }
215

216
        }
217
        return false;
1✔
218
    }
219

220
    private static OptionalBool isCastDeterminingReturnOfLambda(ASTCastExpression castExpr) {
221
        ASTLambdaExpression lambda = getLambdaParent(castExpr);
1✔
222
        if (lambda == null) {
1✔
223
            return NO;
1✔
224
        }
225

226
        // The necessary conditions are:
227
        // - The lambda return type mentions type vars that are missing from its arguments
228
        // (lambda is context dependent)
229
        // - The lambda type is inferred:
230
        //   1. its context is missing, or
231
        //   2. it is invocation and the parent method call
232
        //      a. is inferred (generic + no explicit arguments)
233
        //      b. mentions some of its type params in the argument type corresponding to the lambda
234

235
        JExecutableSymbol symbol = lambda.getFunctionalMethod().getSymbol();
1✔
236
        if (symbol.isUnresolved()) {
1!
237
            return UNKNOWN;
×
238
        }
239

240
        // Note we don't test the functional method directly, because it has been instantiated
241
        // We test its generic signature (the symbol).
242
        boolean contextDependent = TypeOps.isContextDependent(symbol);
1✔
243
        if (!contextDependent) {
1✔
244
            return NO;
1✔
245
        }
246

247
        ExprContext lambdaCtx = lambda.getConversionContext();
1✔
248
        if (lambdaCtx.isMissing()) {
1!
249
            return YES;
×
250
        } else if (lambdaCtx.hasKind(ExprContextKind.CAST)) {
1✔
251
            return NO;
1✔
252
        } else if (lambdaCtx.hasKind(ExprContextKind.INVOCATION)) {
1✔
253
            InvocationNode parentCall = (InvocationNode) lambda.getParent().getParent();
1✔
254
            if (parentCall.getExplicitTypeArguments() != null) {
1✔
255
                return NO;
1✔
256
            }
257
            OverloadSelectionResult overload = parentCall.getOverloadSelectionInfo();
1✔
258
            if (overload.isFailed()) {
1!
259
                return UNKNOWN;
×
260
            }
261
            JMethodSig parentMethod = overload.getMethodType();
1✔
262
            if (!parentMethod.getSymbol().isGeneric()) {
1!
263
                return NO;
×
264
            }
265

266
            int argIdx = lambda.getIndexInParent();
1✔
267
            JMethodSig genericSig = parentMethod.getSymbol().getGenericSignature();
1✔
268
            // this is the generic lambda ty as mentioned in the formal parameters
269
            JTypeMirror genericLambdaTy = genericSig.ithFormalParam(argIdx, overload.isVarargsCall());
1✔
270
            if (!(genericLambdaTy instanceof JClassType)) {
1!
271
                return NO;
×
272
            }
273
            // Note that we don't capture this type, which may make the method type malformed (eg mentioning a wildcard
274
            // as return type). We need these bare wildcards for "mentionsAny" to work properly.
275
            // The "correct" approach here to remove wildcards would be to infer the ground non-wildcard parameterization
276
            // of the lambda but this is pretty deep inside the inference code and not readily usable.
277
            JClassType lambdaTyCapture = (JClassType) genericLambdaTy;
1✔
278

279
            // This is the method signature of the lambda, given the formal parameter type of the parent call.
280
            // The formal type is not instantiated, it may contain type variables of the parent method...
281
            JMethodSig expectedLambdaMethod = genericLambdaTy.getTypeSystem().sigOf(
1✔
282
                lambda.getFunctionalMethod().getSymbol(),
1✔
283
                lambdaTyCapture.getTypeParamSubst()
1✔
284
            );
285
            // but if the return type does not contain such tvars, then the parent method type does
286
            // not depend on the lambda type :)
287
            return definitely(
1✔
288
                TypeOps.mentionsAny(
1✔
289
                    expectedLambdaMethod.getReturnType(),
1✔
290
                    parentMethod.getTypeParameters()
1✔
291
                )
292
            );
293
        }
294
        return UNKNOWN;
1✔
295
    }
296

297
    private static @Nullable ASTLambdaExpression getLambdaParent(ASTCastExpression castExpr) {
298
        if (castExpr.getParent() instanceof ASTLambdaExpression) {
1✔
299
            return (ASTLambdaExpression) castExpr.getParent();
1✔
300
        }
301
        if (castExpr.getParent() instanceof ASTReturnStatement) {
1✔
302
            JavaNode returnTarget = JavaAstUtils.getReturnTarget((ASTReturnStatement) castExpr.getParent());
1✔
303

304
            if (returnTarget instanceof ASTLambdaExpression) {
1✔
305
                return (ASTLambdaExpression) returnTarget;
1✔
306
            }
307
        }
308
        return null;
1✔
309
    }
310

311
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc