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

pmd / pmd / 167

19 Sep 2025 02:08PM UTC coverage: 78.657% (+0.003%) from 78.654%
167

push

github

adangel
[java] Fix #5878: DontUseFloatTypeForLoopIndices now checks the UpdateStatement as well (#6024)

18176 of 23959 branches covered (75.86%)

Branch coverage included in aggregate %.

39694 of 49614 relevant lines covered (80.01%)

0.81 hits per line

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

91.91
/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.ASTMethodCall;
35
import net.sourceforge.pmd.lang.java.ast.ASTMethodReference;
36
import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
37
import net.sourceforge.pmd.lang.java.ast.BinaryOp;
38
import net.sourceforge.pmd.lang.java.ast.InvocationNode;
39
import net.sourceforge.pmd.lang.java.ast.JavaNode;
40
import net.sourceforge.pmd.lang.java.ast.internal.JavaAstUtils;
41
import net.sourceforge.pmd.lang.java.ast.internal.PrettyPrintingUtil;
42
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
43
import net.sourceforge.pmd.lang.java.symbols.JExecutableSymbol;
44
import net.sourceforge.pmd.lang.java.types.JClassType;
45
import net.sourceforge.pmd.lang.java.types.JMethodSig;
46
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
47
import net.sourceforge.pmd.lang.java.types.JTypeVar;
48
import net.sourceforge.pmd.lang.java.types.OverloadSelectionResult;
49
import net.sourceforge.pmd.lang.java.types.Substitution;
50
import net.sourceforge.pmd.lang.java.types.TypeConversion;
51
import net.sourceforge.pmd.lang.java.types.TypeOps;
52
import net.sourceforge.pmd.lang.java.types.TypeOps.Convertibility;
53
import net.sourceforge.pmd.lang.java.types.TypeTestUtil;
54
import net.sourceforge.pmd.lang.java.types.ast.ExprContext;
55
import net.sourceforge.pmd.lang.java.types.ast.ExprContext.ExprContextKind;
56
import net.sourceforge.pmd.util.OptionalBool;
57

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

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

67
    public UnnecessaryCastRule() {
68
        super(ASTCastExpression.class);
1✔
69
    }
1✔
70

71
    @Override
72
    public Object visit(ASTCastExpression castExpr, Object data) {
73
        ASTExpression operand = castExpr.getOperand();
1✔
74

75
        // eg in
76
        // Object o = (Integer) 1;
77

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

82
        if (TypeOps.isUnresolvedOrNull(operandType)
1✔
83
            || TypeOps.isUnresolvedOrNull(coercionType)) {
1✔
84
            return null;
1✔
85
        }
86

87
        // Note that we assume that coercionType is convertible to
88
        // contextType because the code must compile
89

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

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

121
    private void handleMethodCall(ASTCastExpression castExpr, JMethodSig methodType,
122
            JTypeMirror operandType, Object data) {
123
        boolean generic = methodType.getSymbol().getFormalParameters().stream()
1✔
124
            .anyMatch(fp -> isTypeExpression(fp.getTypeMirror(Substitution.EMPTY)));
1✔
125
        if (!generic) {
1✔
126
            JTypeMirror declaringType = methodType.getDeclaringType();
1✔
127
            if (!isTypeExpression(methodType.getSymbol().getReturnType(Substitution.EMPTY))) {
1✔
128
                // declaring type of List<T>::size is List<T>, but since the return type
129
                // is not generic, it's enough to check that operand is a List
130
                declaringType = declaringType.getErasure();
1✔
131
            }
132
            if (TypeTestUtil.isA(declaringType, operandType)) {
1✔
133
                reportCast(castExpr, data);
1✔
134
            }
135
        }
136
    }
1✔
137

138
    private boolean isTypeExpression(JTypeMirror type) {
139
        return type.isGeneric() || type instanceof JTypeVar;
1✔
140
    }
141

142
    private boolean isCastUnnecessary(ASTCastExpression castExpr, @NonNull ExprContext context, JTypeMirror coercionType, JTypeMirror operandType) {
143
        if (isCastDeterminingReturnOfLambda(castExpr) != NO) {
1✔
144
            return false;
1✔
145
        }
146

147
        if (operandType.equals(coercionType)) {
1✔
148
            // with the exception of the lambda thing above, casts to
149
            // the same type are always unnecessary
150
            return true;
1✔
151
        } else if (context.isMissing()) {
1✔
152
            // then we have fewer violation conditions
153

154
            return !operandType.isBottom() // casts on a null literal are necessary
1!
155
                && operandType.isSubtypeOf(coercionType)
1✔
156
                && !isCastToRawType(coercionType, operandType);
1✔
157
        }
158

159
        return !isCastDeterminingContext(castExpr, context, coercionType, operandType)
1✔
160
            && castIsUnnecessaryToMatchContext(context, coercionType, operandType);
1✔
161
    }
162

163
    /**
164
     * Whether this cast is casting a non-raw type to a raw type.
165
     * This is part of the {@link Convertibility#bySubtyping()} relation,
166
     * and needs to be singled out as operations on the raw type
167
     * behave differently than on the non-raw type. In that case the
168
     * cast may be necessary to avoid compile-errors, even though it
169
     * will be noop at runtime (an _unchecked_ cast).
170
     */
171
    private boolean isCastToRawType(JTypeMirror coercionType, JTypeMirror operandType) {
172
        return coercionType.isRaw() && !operandType.isRaw();
1!
173
    }
174

175
    private void reportCast(ASTCastExpression castExpr, Object data) {
176
        asCtx(data).addViolation(castExpr, PrettyPrintingUtil.prettyPrintType(castExpr.getCastType()));
1✔
177
    }
1✔
178

179
    private static boolean castIsUnnecessaryToMatchContext(ExprContext context,
180
                                                           JTypeMirror coercionType,
181
                                                           JTypeMirror operandType) {
182
        if (context.hasKind(ExprContextKind.INVOCATION)) {
1✔
183
            // todo unsupported for now, the cast may be disambiguating overloads
184
            return false;
1✔
185
        }
186

187
        JTypeMirror contextType = context.getTargetType();
1✔
188
        if (contextType == null) {
1!
189
            return false; // should not occur in valid code
×
190
        } else if (!TypeConversion.isConvertibleUsingBoxing(operandType, coercionType)) {
1✔
191
            // narrowing cast
192
            return false;
1✔
193
        } else if (!context.acceptsType(operandType)) {
1✔
194
            // then removing the cast would produce uncompilable code
195
            return false;
1✔
196
        }
197

198
        boolean isBoxingFollowingCast = contextType.isPrimitive() != coercionType.isPrimitive();
1✔
199
        // means boxing behavior is equivalent
200
        return !isBoxingFollowingCast || operandType.unbox().isSubtypeOf(contextType.unbox());
1✔
201
    }
202

203
    /**
204
     * Returns whether the context type actually depends on the cast.
205
     * This means our analysis as written above won't work, and usually
206
     * that the cast is necessary, because there's some primitive conversions
207
     * happening, or some other corner case.
208
     */
209
    private static boolean isCastDeterminingContext(ASTCastExpression castExpr, ExprContext context, @NonNull JTypeMirror coercionType, JTypeMirror operandType) {
210

211
        if (castExpr.getParent() instanceof ASTConditionalExpression && castExpr.getIndexInParent() != 0) {
1✔
212
            // a branch of a ternary
213
            return true;
1✔
214

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

217
            // inside string concatenation
218
            return !TypeTestUtil.isA(String.class, JavaAstUtils.getOtherOperandIfInInfixExpr(castExpr))
1✔
219
                && !TypeTestUtil.isA(String.class, operandType);
1!
220

221
        } else if (context.hasKind(ExprContextKind.NUMERIC) && castExpr.getParent() instanceof ASTInfixExpression) {
1✔
222
            // numeric expr
223
            ASTInfixExpression parent = (ASTInfixExpression) castExpr.getParent();
1✔
224

225
            if (isInfixExprWithOperator(parent, SHIFT_OPS)) {
1✔
226
                // if so, then the cast is determining the width of expr
227
                // the right operand is always int
228
                return castExpr == parent.getLeftOperand()
1✔
229
                        && !TypeOps.isStrictSubtype(operandType.unbox(), operandType.getTypeSystem().INT);
1✔
230
            } else if (isInfixExprWithOperator(parent, BINARY_PROMOTED_OPS)) {
1!
231
                ASTExpression otherOperand = JavaAstUtils.getOtherOperandIfInInfixExpr(castExpr);
1✔
232
                JTypeMirror otherType = otherOperand.getTypeMirror();
1✔
233

234
                // Ie, the type that is taken by the binary promotion
235
                // is the type of the cast, not the type of the operand.
236
                // Eg in
237
                //     int i; ((double) i) * i
238
                // the only reason the mult expr has type double is because of the cast
239
                JTypeMirror promotedTypeWithoutCast = TypeConversion.binaryNumericPromotion(operandType, otherType);
1✔
240
                JTypeMirror promotedTypeWithCast = TypeConversion.binaryNumericPromotion(coercionType, otherType);
1✔
241
                return !promotedTypeWithoutCast.equals(promotedTypeWithCast);
1✔
242
            }
243

244
        }
245
        return false;
1✔
246
    }
247

248
    private static OptionalBool isCastDeterminingReturnOfLambda(ASTCastExpression castExpr) {
249
        ASTLambdaExpression lambda = getLambdaParent(castExpr);
1✔
250
        if (lambda == null) {
1✔
251
            return NO;
1✔
252
        }
253

254
        // The necessary conditions are:
255
        // - The lambda return type mentions type vars that are missing from its arguments
256
        // (lambda is context dependent)
257
        // - The lambda type is inferred:
258
        //   1. its context is missing, or
259
        //   2. it is invocation and the parent method call
260
        //      a. is inferred (generic + no explicit arguments)
261
        //      b. mentions some of its type params in the argument type corresponding to the lambda
262

263
        JExecutableSymbol symbol = lambda.getFunctionalMethod().getSymbol();
1✔
264
        if (symbol.isUnresolved()) {
1!
265
            return UNKNOWN;
×
266
        }
267

268
        // Note we don't test the functional method directly, because it has been instantiated
269
        // We test its generic signature (the symbol).
270
        boolean contextDependent = TypeOps.isContextDependent(symbol);
1✔
271
        if (!contextDependent) {
1✔
272
            return NO;
1✔
273
        }
274

275
        ExprContext lambdaCtx = lambda.getConversionContext();
1✔
276
        if (lambdaCtx.isMissing()) {
1!
277
            return YES;
×
278
        } else if (lambdaCtx.hasKind(ExprContextKind.CAST)) {
1✔
279
            return NO;
1✔
280
        } else if (lambdaCtx.hasKind(ExprContextKind.INVOCATION)) {
1✔
281
            InvocationNode parentCall = (InvocationNode) lambda.getParent().getParent();
1✔
282
            if (parentCall.getExplicitTypeArguments() != null) {
1✔
283
                return NO;
1✔
284
            }
285
            OverloadSelectionResult overload = parentCall.getOverloadSelectionInfo();
1✔
286
            if (overload.isFailed()) {
1!
287
                return UNKNOWN;
×
288
            }
289
            JMethodSig parentMethod = overload.getMethodType();
1✔
290
            if (!parentMethod.getSymbol().isGeneric()) {
1!
291
                return NO;
×
292
            }
293

294
            int argIdx = lambda.getIndexInParent();
1✔
295
            JMethodSig genericSig = parentMethod.getSymbol().getGenericSignature();
1✔
296
            // this is the generic lambda ty as mentioned in the formal parameters
297
            JTypeMirror genericLambdaTy = genericSig.ithFormalParam(argIdx, overload.isVarargsCall());
1✔
298
            if (!(genericLambdaTy instanceof JClassType)) {
1!
299
                return NO;
×
300
            }
301
            // Note that we don't capture this type, which may make the method type malformed (eg mentioning a wildcard
302
            // as return type). We need these bare wildcards for "mentionsAny" to work properly.
303
            // The "correct" approach here to remove wildcards would be to infer the ground non-wildcard parameterization
304
            // of the lambda but this is pretty deep inside the inference code and not readily usable.
305
            JClassType lambdaTyCapture = (JClassType) genericLambdaTy;
1✔
306

307
            // This is the method signature of the lambda, given the formal parameter type of the parent call.
308
            // The formal type is not instantiated, it may contain type variables of the parent method...
309
            JMethodSig expectedLambdaMethod = genericLambdaTy.getTypeSystem().sigOf(
1✔
310
                lambda.getFunctionalMethod().getSymbol(),
1✔
311
                lambdaTyCapture.getTypeParamSubst()
1✔
312
            );
313
            // but if the return type does not contain such tvars, then the parent method type does
314
            // not depend on the lambda type :)
315
            return definitely(
1✔
316
                TypeOps.mentionsAny(
1✔
317
                    expectedLambdaMethod.getReturnType(),
1✔
318
                    parentMethod.getTypeParameters()
1✔
319
                )
320
            );
321
        }
322
        return UNKNOWN;
1✔
323
    }
324

325
    private static @Nullable ASTLambdaExpression getLambdaParent(ASTCastExpression castExpr) {
326
        if (castExpr.getParent() instanceof ASTLambdaExpression) {
1✔
327
            return (ASTLambdaExpression) castExpr.getParent();
1✔
328
        }
329
        if (castExpr.getParent() instanceof ASTReturnStatement) {
1✔
330
            JavaNode returnTarget = JavaAstUtils.getReturnTarget((ASTReturnStatement) castExpr.getParent());
1✔
331

332
            if (returnTarget instanceof ASTLambdaExpression) {
1✔
333
                return (ASTLambdaExpression) returnTarget;
1✔
334
            }
335
        }
336
        return null;
1✔
337
    }
338

339
}
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

© 2025 Coveralls, Inc