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

pmd / pmd / 196

16 Oct 2025 08:33AM UTC coverage: 78.642% (-0.02%) from 78.661%
196

push

github

web-flow
chore: fix dogfood issues from new rules (#6056)

18180 of 23973 branches covered (75.84%)

Branch coverage included in aggregate %.

2 of 27 new or added lines in 14 files covered. (7.41%)

2 existing lines in 1 file now uncovered.

39693 of 49617 relevant lines covered (80.0%)

0.81 hits per line

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

93.77
/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ConstantFolder.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.ast;
6

7

8
import org.apache.commons.lang3.StringEscapeUtils;
9
import org.apache.commons.lang3.tuple.Pair;
10
import org.checkerframework.checker.nullness.qual.NonNull;
11
import org.checkerframework.checker.nullness.qual.Nullable;
12

13
import net.sourceforge.pmd.lang.document.Chars;
14
import net.sourceforge.pmd.lang.java.ast.ASTExpression.ConstResult;
15
import net.sourceforge.pmd.lang.java.symbols.JFieldSymbol;
16
import net.sourceforge.pmd.lang.java.symbols.JVariableSymbol;
17
import net.sourceforge.pmd.lang.java.types.JPrimitiveType;
18
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
19
import net.sourceforge.pmd.lang.java.types.TypeTestUtil;
20
import net.sourceforge.pmd.util.AssertionUtil;
21

22
/**
23
 * Computes constant expression values.
24
 */
25
// strictfp because constant expressions are FP-strict (not sure if this is really important)
26
final strictfp class ConstantFolder extends JavaVisitorBase<Void, @NonNull ConstResult> {
27

28
    static final ConstantFolder INSTANCE = new ConstantFolder();
1✔
29
    private static final Pair<Object, Object> FAILED_BIN_PROMOTION = Pair.of(null, null);
1✔
30

31
    private ConstantFolder() {
32

33
    }
34

35
    @Override
36
    public @NonNull ConstResult visitJavaNode(JavaNode node, Void data) {
37
        return ConstResult.NO_CONST_VALUE;
1✔
38
    }
39

40
    @Override
41
    public @NonNull ConstResult visitLiteral(ASTLiteral num, Void data) {
42
        throw new AssertionError("Literal nodes implement getConstValue directly");
×
43
    }
44

45
    @Override
46
    public @NonNull ConstResult visit(ASTNumericLiteral node, Void data) {
47
        // don't use ternaries, the compiler messes up autoboxing.
48
        Object result;
49
        if (node.isIntegral()) {
1✔
50
            if (node.isIntLiteral()) {
1✔
51
                result = node.getValueAsInt();
1✔
52
            } else {
53
                result = node.getValueAsLong();
1✔
54
            }
55
        } else {
56
            if (node.isFloatLiteral()) {
1✔
57
                result = node.getValueAsFloat();
1✔
58
            } else {
59
                result = node.getValueAsDouble();
1✔
60
            }
61
        }
62
        return ConstResult.ctConst(result);
1✔
63
    }
64

65
    @Override
66
    public @NonNull ConstResult visit(ASTBooleanLiteral node, Void data) {
67
        return node.isTrue() ? ConstResult.BOOL_TRUE : ConstResult.BOOL_FALSE;
1✔
68
    }
69

70
    @Override
71
    public @NonNull ConstResult visit(ASTStringLiteral node, Void data) {
72
        String result;
73
        if (node.isTextBlock()) {
1✔
74
            result = ASTStringLiteral.determineTextBlockContent(node.getLiteralText());
1✔
75
        } else {
76
            result = ASTStringLiteral.determineStringContent(node.getLiteralText());
1✔
77
        }
78
        return ConstResult.ctConst(result);
1✔
79
    }
80

81
    @Override
82
    public @NonNull ConstResult visit(ASTNullLiteral node, Void data) {
83
        return ConstResult.NO_CONST_VALUE;
1✔
84
    }
85

86
    @Override
87
    public @NonNull ConstResult visit(ASTCharLiteral node, Void data) {
88
        Chars image = node.getLiteralText();
1✔
89
        Chars woDelims = image.subSequence(1, image.length() - 1);
1✔
90
        Character result = StringEscapeUtils.UNESCAPE_JAVA.translate(woDelims).charAt(0);
1✔
91
        return ConstResult.ctConst(result);
1✔
92
    }
93

94
    @Override
95
    public @NonNull ConstResult visit(ASTVariableAccess node, Void data) {
96
        JVariableSymbol symbol = node.getReferencedSym();
1✔
97
        if (symbol == null || !symbol.isFinal()) {
1✔
98
            return ConstResult.NO_CONST_VALUE;
1✔
99
        }
100

101
        if (symbol instanceof JFieldSymbol) {
1✔
102
            @Nullable Object cv = ((JFieldSymbol) symbol).getConstValue();
1✔
103
            if (cv != null) {
1✔
104
                return ConstResult.ctConst(cv);
1✔
105
            }
106
        }
107

108
        @Nullable
109
        ASTVariableId declaratorId = symbol.tryGetNode();
1✔
110
        if (declaratorId != null) {
1✔
111
            ASTExpression initializer = declaratorId.getInitializer();
1✔
112
            if (initializer != null) {
1✔
113
                ConstResult initRes = initializer.getConstFoldingResult();
1✔
114
                if (initRes.hasValue()) {
1✔
115
                    boolean isCompileTimeConstant = symbol instanceof JFieldSymbol
1✔
116
                        && ((JFieldSymbol) symbol).isStatic();
1!
117
                    return new ConstResult(isCompileTimeConstant, initRes.getValue());
1✔
118
                }
119
                return initRes;
1✔
120
            }
121
        }
122

123
        return ConstResult.NO_CONST_VALUE;
1✔
124
    }
125

126
    @Override
127
    public @NonNull ConstResult visit(ASTFieldAccess node, Void data) {
128
        JFieldSymbol symbol = node.getReferencedSym();
1✔
129
        if (symbol != null) {
1✔
130
            return ConstResult.ctConstIfNotNull(symbol.getConstValue());
1✔
131
        }
132
        return ConstResult.NO_CONST_VALUE;
1✔
133
    }
134

135
    @Override
136
    public @NonNull ConstResult visit(ASTArrayInitializer node, Void data) {
137
        int length = node.length();
1✔
138
        boolean isCtConst = true;
1✔
139
        Object[] result = new Object[length];
1✔
140
        int index = 0;
1✔
141
        for (ASTExpression expr : node) {
1✔
142
            ConstResult itemResult = expr.getConstFoldingResult();
1✔
143
            if (!itemResult.hasValue()) {
1✔
144
                return ConstResult.NO_CONST_VALUE;
1✔
145
            }
146
            result[index++] = itemResult.getValue();
1✔
147
            isCtConst &= itemResult.isCompileTimeConstant();
1✔
148
        }
1✔
149

150
        return new ConstResult(isCtConst, result);
1✔
151
    }
152

153
    @Override
154
    public @NonNull ConstResult visit(ASTConditionalExpression node, Void data) {
155
        ConstResult condition = node.getCondition().getConstFoldingResult();
1✔
156
        if (condition.hasValue()) {
1✔
157
            ConstResult thenValue = node.getThenBranch().getConstFoldingResult();
1✔
158
            ConstResult elseValue = node.getElseBranch().getConstFoldingResult();
1✔
159
            boolean ctConst = condition.isCompileTimeConstant()
1!
160
                && thenValue.isCompileTimeConstant()
1✔
161
                && elseValue.isCompileTimeConstant();
1✔
162
            if (!thenValue.hasValue() || !elseValue.hasValue()) {
1✔
163
                return ConstResult.NO_CONST_VALUE; // not a constexpr
1✔
164
            }
165
            if (condition.getValue() instanceof Boolean && (boolean) condition.getValue()) {
1!
166
                return new ConstResult(ctConst, thenValue.getValue());
1✔
167
            } else {
168
                return new ConstResult(ctConst, elseValue.getValue());
1✔
169
            }
170
        }
171
        return ConstResult.NO_CONST_VALUE;
1✔
172
    }
173

174
    @Override
175
    public @NonNull ConstResult visit(ASTCastExpression node, Void data) {
176
        JTypeMirror t = node.getCastType().getTypeMirror();
1✔
177
        ConstResult castValue = node.getOperand().getConstFoldingResult();
1✔
178
        if (!castValue.hasValue()) {
1✔
179
            return ConstResult.NO_CONST_VALUE;
1✔
180
        }
181
        Object res;
182
        if (t.isNumeric()) {
1✔
183
            res = numericCoercion(castValue.getValue(), t);
1✔
184
        } else if (TypeTestUtil.isExactlyA(String.class, node.getCastType())) {
1!
185
            res = stringCoercion(castValue.getValue());
×
186
        } else {
187
            return ConstResult.NO_CONST_VALUE;
1✔
188
        }
189

190
        return new ConstResult(castValue.isCompileTimeConstant(), res);
1✔
191
    }
192

193
    @Override
194
    public @NonNull ConstResult visit(ASTUnaryExpression node, Void data) {
195
        UnaryOp operator = node.getOperator();
1✔
196
        if (!operator.isPure()) {
1✔
197
            return ConstResult.NO_CONST_VALUE;
1✔
198
        }
199

200
        ASTExpression operand = node.getOperand();
1✔
201
        ConstResult operandValue = operand.getConstFoldingResult();
1✔
202
        if (!operandValue.hasValue()) {
1✔
203
            return ConstResult.NO_CONST_VALUE;
1✔
204
        }
205
        Object value = computeUnary(operandValue.getValue(), node);
1✔
206
        return new ConstResult(operandValue.isCompileTimeConstant(), value);
1✔
207
    }
208

209
    private @Nullable Object computeUnary(Object operandValue, ASTUnaryExpression node) {
210

211
        switch (node.getOperator()) {
1!
212
        case UNARY_PLUS:
213
            return unaryPromotion(operandValue);
1✔
214
        case UNARY_MINUS: {
215
            Number promoted = unaryPromotion(operandValue);
1✔
216
            if (promoted == null) {
1✔
217
                return null; // compile-time error
1✔
218
            } else if (promoted instanceof Integer) {
1✔
219
                return -promoted.intValue();
1✔
220
            } else if (promoted instanceof Long) {
1✔
221
                return -promoted.longValue();
1✔
222
            } else if (promoted instanceof Float) {
1✔
223
                return -promoted.floatValue();
1✔
224
            } else {
225
                assert promoted instanceof Double;
1!
226
                return -promoted.doubleValue();
1✔
227
            }
228
        }
229
        case COMPLEMENT: {
230
            Number promoted = unaryPromotion(operandValue);
1✔
231
            if (promoted instanceof Integer) {
1✔
232
                return ~promoted.intValue();
1✔
233
            } else if (promoted instanceof Long) {
1✔
234
                return ~promoted.longValue();
1✔
235
            } else {
236
                return null; // compile-time error
1✔
237
            }
238
        }
239
        case NEGATION: {
240
            return booleanInvert(operandValue);
1✔
241
        }
242

243
        default: // increment ops
244
            throw new AssertionError("unreachable");
×
245
        }
246
    }
247

248
    @Override
249
    public strictfp ConstResult visit(ASTInfixExpression node, Void data) {
250
        ConstResult left = node.getLeftOperand().getConstFoldingResult();
1✔
251
        ConstResult right = node.getRightOperand().getConstFoldingResult();
1✔
252
        if (!left.hasValue() || !right.hasValue()) {
1✔
253
            return ConstResult.NO_CONST_VALUE;
1✔
254
        }
255
        Object res = computeInfix(left.getValue(), right.getValue(), node);
1✔
256
        if (res == null) {
1✔
257
            return ConstResult.NO_CONST_VALUE;
1✔
258
        }
259

260
        boolean ctConst = left.isCompileTimeConstant() && right.isCompileTimeConstant();
1✔
261
        return new ConstResult(ctConst, res);
1✔
262
    }
263

264
    private strictfp @Nullable Object computeInfix(Object left, Object right, ASTInfixExpression node) {
265
        switch (node.getOperator()) {
1!
266
        case CONDITIONAL_OR: {
267
            if (left instanceof Boolean && right instanceof Boolean) {
1✔
268
                return (Boolean) left || (Boolean) right;
1✔
269
            }
270
            return null;
1✔
271
        }
272

273
        case CONDITIONAL_AND: {
274
            if (left instanceof Boolean && right instanceof Boolean) {
1✔
275
                return (Boolean) left && (Boolean) right;
1✔
276
            }
277
            return null;
1✔
278
        }
279

280
        case OR: {
281
            Pair<Object, Object> promoted = booleanAwareBinaryPromotion(left, right);
1✔
282
            left = promoted.getLeft();
1✔
283
            right = promoted.getRight();
1✔
284

285
            if (left instanceof Integer) {
1✔
286
                return intValue(left) | intValue(right);
1✔
287
            } else if (left instanceof Long) {
1✔
288
                return longValue(left) | longValue(right);
1✔
289
            } else if (left instanceof Boolean) {
1✔
290
                return booleanValue(left) | booleanValue(right);
1✔
291
            }
292
            return null;
1✔
293
        }
294
        case XOR: {
295
            Pair<Object, Object> promoted = booleanAwareBinaryPromotion(left, right);
1✔
296
            left = promoted.getLeft();
1✔
297
            right = promoted.getRight();
1✔
298

299
            if (left instanceof Integer) {
1✔
300
                return intValue(left) ^ intValue(right);
1✔
301
            } else if (left instanceof Long) {
1✔
302
                return longValue(left) ^ longValue(right);
1✔
303
            } else if (left instanceof Boolean) {
1✔
304
                return booleanValue(left) ^ booleanValue(right);
1✔
305
            }
306
            return null;
1✔
307
        }
308
        case AND: {
309
            Pair<Object, Object> promoted = booleanAwareBinaryPromotion(left, right);
1✔
310
            left = promoted.getLeft();
1✔
311
            right = promoted.getRight();
1✔
312

313
            if (left instanceof Integer) {
1✔
314
                return intValue(left) & intValue(right);
1✔
315
            } else if (left instanceof Long) {
1✔
316
                return longValue(left) & longValue(right);
1✔
317
            } else if (left instanceof Boolean) {
1✔
318
                return booleanValue(left) & booleanValue(right);
1✔
319
            }
320
            return null;
1✔
321
        }
322

323
        case EQ:
324
            return eqResult(left, right);
1✔
325
        case NE:
326
            return booleanInvert(eqResult(left, right));
1✔
327

328
        case LE:
329
            return compLE(left, right);
1✔
330
        case GT:
331
            return booleanInvert(compLE(left, right));
1✔
332
        case LT:
333
            return compLT(left, right);
1✔
334
        case GE:
335
            return booleanInvert(compLT(left, right));
1✔
336

337
        case INSTANCEOF:
338
            // disallowed, actually dead code because the
339
            // right operand is the type, which is no constexpr
340
            return null;
×
341

342
        // for shift operators, unary promotion is performed on operators separately
343
        case LEFT_SHIFT: {
344
            left = unaryPromotion(left);
1✔
345
            right = unaryPromotion(right);
1✔
346
            if (!(right instanceof Integer) && !(right instanceof Long)) {
1✔
347
                return null; // shift distance must be integral
1✔
348
            }
349

350
            // only use intValue for the left operand
351
            if (left instanceof Integer) {
1✔
352
                return intValue(left) << intValue(right);
1✔
353
            } else if (left instanceof Long) {
1✔
354
                return longValue(left) << intValue(right);
1✔
355
            }
356
            return null;
1✔
357
        }
358
        case RIGHT_SHIFT: {
359
            left = unaryPromotion(left);
1✔
360
            right = unaryPromotion(right);
1✔
361
            if (!(right instanceof Integer) && !(right instanceof Long)) {
1✔
362
                return null; // shift distance must be integral
1✔
363
            }
364

365
            // only use intValue for the left operand
366
            if (left instanceof Integer) {
1✔
367
                return intValue(left) >> intValue(right);
1✔
368
            } else if (left instanceof Long) {
1✔
369
                return longValue(left) >> intValue(right);
1✔
370
            }
371
            return null;
1✔
372
        }
373
        case UNSIGNED_RIGHT_SHIFT: {
374
            left = unaryPromotion(left);
1✔
375
            right = unaryPromotion(right);
1✔
376
            if (!(right instanceof Integer) && !(right instanceof Long)) {
1✔
377
                return null; // shift distance must be integral
1✔
378
            }
379

380
            // only use intValue for the left operand
381
            if (left instanceof Integer) {
1✔
382
                return intValue(left) >>> intValue(right);
1✔
383
            } else if (left instanceof Long) {
1✔
384
                return longValue(left) >>> intValue(right);
1✔
385
            }
386
            return null;
1✔
387
        }
388
        case ADD: {
389
            if (isConvertibleToNumber(left) && isConvertibleToNumber(right)) {
1✔
390
                Pair<Object, Object> promoted = binaryNumericPromotion(left, right);
1✔
391
                left = promoted.getLeft();
1✔
392
                right = promoted.getRight();
1✔
393

394
                if (left instanceof Integer) {
1✔
395
                    return intValue(left) + intValue(right);
1✔
396
                } else if (left instanceof Long) {
1✔
397
                    return longValue(left) + longValue(right);
1✔
398
                } else if (left instanceof Float) {
1✔
399
                    return floatValue(left) + floatValue(right);
1✔
400
                } else {
401
                    return doubleValue(left) + doubleValue(right);
1✔
402
                }
403
            } else if (left instanceof String) {
1✔
404
                // string concat
405
                return (String) left + right;
1✔
406
            } else if (right instanceof String) {
1✔
407
                // string concat
408
                return left + (String) right;
1✔
409
            }
410
            return null;
1✔
411
        }
412
        case SUB: {
413
            if (isConvertibleToNumber(left) && isConvertibleToNumber(right)) {
1!
414
                Pair<Object, Object> promoted = binaryNumericPromotion(left, right);
1✔
415
                left = promoted.getLeft();
1✔
416
                right = promoted.getRight();
1✔
417

418
                if (left instanceof Integer) {
1✔
419
                    return intValue(left) - intValue(right);
1✔
420
                } else if (left instanceof Long) {
1✔
421
                    return longValue(left) - longValue(right);
1✔
422
                } else if (left instanceof Float) {
1✔
423
                    return floatValue(left) - floatValue(right);
1✔
424
                } else {
425
                    return doubleValue(left) - doubleValue(right);
1✔
426
                }
427
            }
428
            return null;
1✔
429
        }
430
        case MUL: {
431
            if (isConvertibleToNumber(left) && isConvertibleToNumber(right)) {
1✔
432
                Pair<Object, Object> promoted = binaryNumericPromotion(left, right);
1✔
433
                left = promoted.getLeft();
1✔
434
                right = promoted.getRight();
1✔
435

436
                if (left instanceof Integer) {
1✔
437
                    return intValue(left) * intValue(right);
1✔
438
                } else if (left instanceof Long) {
1✔
439
                    return longValue(left) * longValue(right);
1✔
440
                } else if (left instanceof Float) {
1✔
441
                    return floatValue(left) * floatValue(right);
1✔
442
                } else {
443
                    return doubleValue(left) * doubleValue(right);
1✔
444
                }
445
            }
446
            return null;
1✔
447
        }
448
        case DIV: {
449
            if (isConvertibleToNumber(left) && isConvertibleToNumber(right)) {
1✔
450
                Pair<Object, Object> promoted = binaryNumericPromotion(left, right);
1✔
451
                left = promoted.getLeft();
1✔
452
                right = promoted.getRight();
1✔
453

454
                if (left instanceof Integer) {
1✔
455
                    return intValue(left) / intValue(right);
1✔
456
                } else if (left instanceof Long) {
1✔
457
                    return longValue(left) / longValue(right);
1✔
458
                } else if (left instanceof Float) {
1✔
459
                    return floatValue(left) / floatValue(right);
1✔
460
                } else {
461
                    return doubleValue(left) / doubleValue(right);
1✔
462
                }
463
            }
464
            return null;
1✔
465
        }
466
        case MOD: {
467
            if (isConvertibleToNumber(left) && isConvertibleToNumber(right)) {
1✔
468
                Pair<Object, Object> promoted = binaryNumericPromotion(left, right);
1✔
469
                left = promoted.getLeft();
1✔
470
                right = promoted.getRight();
1✔
471

472
                if (left instanceof Integer) {
1✔
473
                    return intValue(left) % intValue(right);
1✔
474
                } else if (left instanceof Long) {
1✔
475
                    return longValue(left) % longValue(right);
1✔
476
                } else if (left instanceof Float) {
1✔
477
                    return floatValue(left) % floatValue(right);
1✔
478
                } else {
479
                    return doubleValue(left) % doubleValue(right);
1✔
480
                }
481
            }
482
            return null;
1✔
483
        }
484
        }
NEW
485
        throw AssertionUtil.shouldNotReachHere("Unknown operator '" + node.getOperator() + "' in " + node);
×
486
    }
487

488
    private static @Nullable Object compLE(Object left, Object right) {
489
        if (isConvertibleToNumber(left) && isConvertibleToNumber(right)) {
1✔
490
            Pair<Object, Object> promoted = binaryNumericPromotion(left, right);
1✔
491
            left = promoted.getLeft();
1✔
492
            right = promoted.getRight();
1✔
493

494
            if (left instanceof Integer) {
1✔
495
                return intValue(left) <= intValue(right);
1✔
496
            } else if (left instanceof Long) {
1✔
497
                return longValue(left) <= longValue(right);
1!
498
            } else if (left instanceof Float) {
1✔
499
                return floatValue(left) <= floatValue(right);
1!
500
            } else if (left instanceof Double) {
1!
501
                return doubleValue(left) <= doubleValue(right);
1✔
502
            }
503
        }
504
        return null;
1✔
505
    }
506

507
    private static @Nullable Boolean compLT(Object left, Object right) {
508
        if (isConvertibleToNumber(left) && isConvertibleToNumber(right)) {
1✔
509
            Pair<Object, Object> promoted = binaryNumericPromotion(left, right);
1✔
510
            left = promoted.getLeft();
1✔
511
            right = promoted.getRight();
1✔
512

513
            if (left instanceof Integer) {
1✔
514
                return intValue(left) < intValue(right);
1✔
515
            } else if (left instanceof Long) {
1✔
516
                return longValue(left) < longValue(right);
1!
517
            } else if (left instanceof Float) {
1✔
518
                return floatValue(left) < floatValue(right);
1!
519
            } else if (left instanceof Double) {
1!
520
                return doubleValue(left) < doubleValue(right);
1!
521
            }
522
        }
523
        return null;
1✔
524
    }
525

526
    private static @Nullable Boolean booleanInvert(@Nullable Object b) {
527
        if (b instanceof Boolean) {
1✔
528
            return !(Boolean) b;
1✔
529
        }
530
        return null;
1✔
531
    }
532

533
    private static @Nullable Boolean eqResult(Object left, Object right) {
534
        if (isConvertibleToNumber(left) && isConvertibleToNumber(right)) {
1✔
535
            Pair<Object, Object> promoted = binaryNumericPromotion(left, right);
1✔
536
            return promoted.getLeft().equals(promoted.getRight()); // fixme Double.NaN
1✔
537
        } else {
538
            return null; // not a constant expr on reference types
1✔
539
        }
540
    }
541

542
    private static boolean isConvertibleToNumber(Object o) {
543
        return o instanceof Number || o instanceof Character;
1✔
544
    }
545

546

547
    private static @Nullable Number unaryPromotion(Object t) {
548
        if (t instanceof Character) {
1✔
549
            return (int) (Character) t;
1✔
550
        } else if (t instanceof Number) {
1✔
551
            if (t instanceof Byte || t instanceof Short) {
1!
552
                return intValue(t);
×
553
            } else {
554
                return (Number) t;
1✔
555
            }
556
        }
557
        return null;
1✔
558
    }
559

560
    /**
561
     * This returns a pair in which both numbers have the dynamic type.
562
     * Both right and left need to be {@link #isConvertibleToNumber(Object)},
563
     * otherwise fails with ClassCastException.
564
     */
565
    private static Pair<Object, Object> binaryNumericPromotion(Object left, Object right) {
566
        left = projectCharOntoInt(left);
1✔
567
        right = projectCharOntoInt(right);
1✔
568
        if (left instanceof Double || right instanceof Double) {
1✔
569
            return Pair.of(doubleValue(left), doubleValue(right));
1✔
570
        } else if (left instanceof Float || right instanceof Float) {
1✔
571
            return Pair.of(floatValue(left), floatValue(right));
1✔
572
        } else if (left instanceof Long || right instanceof Long) {
1✔
573
            return Pair.of(longValue(left), longValue(right));
1✔
574
        } else {
575
            return Pair.of(intValue(left), intValue(right));
1✔
576
        }
577
    }
578

579
    private static Pair<Object, Object> booleanAwareBinaryPromotion(Object left, Object right) {
580
        if (left instanceof Boolean || right instanceof Boolean) {
1!
581
            if (left instanceof Boolean && right instanceof Boolean) {
1!
582
                return Pair.of(left, right);
1✔
583
            }
584
            return FAILED_BIN_PROMOTION;
1✔
585
        } else if (!isConvertibleToNumber(left) || !isConvertibleToNumber(right)) {
1!
586
            return FAILED_BIN_PROMOTION;
×
587
        } else {
588
            return binaryNumericPromotion(left, right);
1✔
589
        }
590
    }
591

592
    private static Object projectCharOntoInt(Object v) {
593
        if (v instanceof Character) {
1✔
594
            return (int) (Character) v;
1✔
595
        }
596
        return v;
1✔
597
    }
598

599
    private static Object numericCoercion(Object v, JTypeMirror target) {
600
        v = projectCharOntoInt(v); // map chars to a Number (widen it to int, which may be narrowed in the switch)
1✔
601

602
        if (target.isNumeric() && v instanceof Number) {
1!
603
            switch (((JPrimitiveType) target).getKind()) {
1!
604
            case BOOLEAN:
605
                throw new AssertionError("unreachable");
×
606
            case CHAR:
607
                return (char) intValue(v);
1✔
608
            case BYTE:
609
                return (byte) intValue(v);
1✔
610
            case SHORT:
611
                return (short) intValue(v);
1✔
612
            case INT:
613
                return intValue(v);
1✔
614
            case LONG:
615
                return longValue(v);
1✔
616
            case FLOAT:
617
                return floatValue(v);
1✔
618
            case DOUBLE:
619
                return doubleValue(v);
1✔
620
            }
NEW
621
            throw AssertionUtil.shouldNotReachHere("exhaustive enum: " + target);
×
622
        }
623
        return null;
×
624
    }
625

626
    private static Object stringCoercion(Object v) {
627
        if (v instanceof String) {
×
628
            return v;
×
629
        }
630
        return null;
×
631
    }
632

633
    private static boolean booleanValue(Object x) {
634
        return (Boolean) x;
1✔
635
    }
636

637
    private static int intValue(Object x) {
638
        return ((Number) x).intValue();
1✔
639
    }
640

641
    private static long longValue(Object x) {
642
        return ((Number) x).longValue();
1✔
643
    }
644

645
    private static float floatValue(Object x) {
646
        return ((Number) x).floatValue();
1✔
647
    }
648

649
    private static double doubleValue(Object x) {
650
        return ((Number) x).doubleValue();
1✔
651
    }
652

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