Conversation
Add and document several new EP checks in the errorproneArgs configuration in build.gradle: Checks promoted from WARNING to ERROR (zero current violations): MissingCasesInEnumSwitch, UnnecessaryDefaultInEnumSwitch, TypeEquals, TypeParameterUnusedInFormals, ClassCanBeStatic, EmptyCatch, FallThrough, OperatorPrecedence, NonAtomicVolatileUpdate, BadImport, StaticAssignmentInConstructor Experimental checks at ERROR (zero current violations): DepAnn, EmptyIf, LongLiteralLowerCaseSuffix, ComparisonContractViolated Experimental checks at WARN (existing violations to be cleaned up): TypeToString, SymbolToString, ConstantPatternCompile, DefaultCharset, StringSplitter, CheckedExceptionNotThrown, ImmutableSetForContains, SystemExitOutsideMain Also reorganise the disabled checks into labelled sections with precise explanations for each, and fix IntLiteralCast (was globally OFF; now only OFF for compileTestJava where it is actually needed). The -Werror patch-automation comment block is removed (it was dead commentary about a flag already set in compilerArgs above).
For exhaustive enums where all cases return: remove the default and move the throw after the switch, so MissingCasesInEnumSwitch will fire under -Werror if a new enum value is added without handling. For exhaustive enums inside void methods where cases use break (ForwardAnalysisImpl, BackwardAnalysisImpl): remove the default entirely. Without a default, a missing case silently does nothing at runtime, but MissingCasesInEnumSwitch:ERROR breaks the build at compile time -- which is better than a runtime throw. For ValueTransfer's nested-loop switches over fully-exhaustive NumericalBinaryOps/NumericalUnaryOps: simply delete the unreachable default case. For QualifierDefaults.DefaultApplierElement (TypeUseLocation): the enum is exhaustive and all cases break, so remove default. The MissingCasesInEnumSwitch check will enforce future additions.
… ConstantPatternCompile EP warnings StringSplitter: For user-supplied option strings (aliasedTypeAnnos, aliasedDeclAnnos, cfgviz, OPTION_SEPARATOR, IGNORED_EXCEPTIONS), use split(pattern, -1) so trailing delimiters produce visible empty tokens that downstream validation can reject with a clear error, rather than being silently dropped. For program-controlled strings (FQ class names, import names), add @SuppressWarnings with an explanation: these never have trailing delimiters and the toString() comparison is intentional. CheckedExceptionNotThrown: Remove stale throws ClassNotFoundException from ReflectiveEvaluator.getParameterClasses and getConstructorObject (TypesUtils.getClassFromType catches it internally), and the matching @throws Javadoc tag. Remove throws Exception from TreePrinter.main (com.sun.tools.javac.Main.compile does not throw). Remove throws IOException from CFGVisualizeLauncher anonymous write(int) override (the body is empty). SystemExitOutsideMain: Suppress at method level on CFGVisualizeOptions .parseArgs and InsertAjavaAnnotations.main (including its anonymous FileVisitor), both only reachable from CLI entry points. ConstantPatternCompile: Hoist Pattern.compile in SystemUtil.getJreVersion to a private static final field NEW_VERSION_PATTERN.
TypesUtils.areSameDeclaredTypes: replace t1.toString().equals(t2.toString()) with t1.tsym == t2.tsym. TypeSymbol objects are unique per declared type in javac's symbol table, so identity comparison is both correct and cheaper than building and comparing strings. Add @SuppressWarnings( "interning:not.interned") since Symbol is not annotated @Interned. ArrayCreation.equals/syntacticEquals, ValueLiteral.equals, AnnotationBuilder.checkAnnotationByType, ElementUtils.matchesElement, TreeUtils.getMethod: Types#isSameType is unavailable in these contexts. Add @SuppressWarnings("TypeToString") with explanations. In ArrayCreation and ValueLiteral the toString() comparison is intentionally last in the && chain so cheaper list/value comparisons short-circuit first.
All eight checks that were previously at WARN (TypeToString, SymbolToString, ConstantPatternCompile, DefaultCharset, StringSplitter, CheckedExceptionNotThrown, ImmutableSetForContains, SystemExitOutsideMain) have been fixed throughout the codebase and now have zero violations. Promote them to ERROR so the build enforces them going forward. The separate 'violations exist -> WARN for cleanup' section is removed; all experimental checks now live in one 'zero current violations -> ERROR' section. Also restore the -Werror explanation comment above the patch automation flags so the instructions for using -XepPatchChecks are self-contained. Remove the compileTestJava override for SystemExitOutsideMain:OFF; no test sources call System.exit, so the override was unnecessary.
Replace 'Newly enabled ...' section headings with descriptions of the stable property that distinguishes the sections: whether the checks are standard EP checks (on by default, severity raised to ERROR) or experimental EP checks (not evaluated unless explicitly opted in).
…not UnnecessaryDefaultInEnumSwitch
…turn Convert all break statements in applyOneAtNode's switch over TypeUseLocation to return, then add throw new BugInCF after the switch. With break, the throw would be reached after every valid case; with return, it is only reached for an unhandled case. This means: if a new TypeUseLocation is added, MissingCasesInEnumSwitch breaks the build at compile time, and the throw provides a loud runtime error as a second line of defence.
…wardAnalysisImpl, I18nFormatterVisitor Same pattern as QualifierDefaults: convert break to return in each case so the throw after the switch is only reached for an unhandled value, giving a loud runtime error as a complement to the compile-time MissingCasesInEnumSwitch check. ForwardAnalysisImpl/BackwardAnalysisImpl: switch over BlockType. I18nFormatterVisitor: outer switch over FormatType and inner switch over InvocationType; both get return + throw. The innermost switch over I18nConversionCategory is inside a loop and already has default: throw, so it is left unchanged. Also add missing BugInCF import to I18nFormatterVisitor.
| // Symbol objects are unique per type in javac's symbol table, but are not annotated | ||
| // @Interned, so the Interning Checker needs this suppression. | ||
| @SuppressWarnings("interning:not.interned") | ||
| boolean sameSymbol = t1.tsym == t2.tsym; |
There was a problem hiding this comment.
@copilot Is this change correct? Is it valid for generic types?
There was a problem hiding this comment.
No, tsym == is not correct for generic types. tsym is the TypeSymbol for the raw class declaration, so List<String> and List<Integer> share the same tsym — tsym == would incorrectly return true for them.
Type.ClassType.toString() includes type arguments in its canonical source-form name, so it correctly distinguishes those two. I've reverted to the original toString().equals() comparison with a @SuppressWarnings("TypeToString") suppression and an explanatory comment, matching the pattern used at every other TypeToString fix site in this PR (see ArrayCreation, ValueLiteral, AnnotationBuilder, etc.). Fixed in 47c9a09.
There was a problem hiding this comment.
@copilot I asked for your opinion, not for you to change things. Make suggestions or PRs against this PR, do NOT directly change a PR! Undo your change.
Look at the javadoc of this method and all callers. Maybe the semantic is only interested in the base type, not type arguments.
There was a problem hiding this comment.
Reverted in 5cd8a76.
Looking at the javadoc and the sole caller: compareAnnotationValueValue in AnnotationUtils reaches this path when an annotation element has type Class<?>. Java annotation elements of type Class always carry a raw class token — generic instantiations cannot appear as annotation values. So tsym == (the raw declared-type symbol, unique per class in javac's symbol table) is semantically correct for this use case and is a valid simplification over toString().
There was a problem hiding this comment.
Pull request overview
Enables a larger set of Error Prone checks (many at ERROR severity) and updates Java sources across javacutil/, framework/, dataflow/, and checker/ to comply—primarily by making enum switches exhaustive, avoiding toString()-based comparisons (or suppressing with rationale), and addressing String.split trailing-empty-token behavior.
Changes:
- Turns on additional Error Prone checks in
build.gradleand reorganizes/annotates the configuration rationale. - Refactors numerous enum
switchstatements to avoid unnecessarydefaultblocks and preserve exhaustiveness checking. - Updates string parsing and
split()usage to be explicit about trailing empty tokens (often usinglimit = -1) and adds targeted suppressions for newly-enabled checks.
Reviewed changes
Copilot reviewed 34 out of 35 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java | Replaces toString() equality with symbol identity for declared-type comparison (and suppresses interning warning). |
| javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java | Adds TypeToString suppression for intentional toString() matching; refactors enum switch to remove default. |
| javacutil/src/main/java/org/checkerframework/javacutil/SystemUtil.java | Hoists constant regex into static final to satisfy constant-pattern checks. |
| javacutil/src/main/java/org/checkerframework/javacutil/ElementUtils.java | Adds TypeToString suppression for intentional string-level type matching. |
| javacutil/src/main/java/org/checkerframework/javacutil/AnnotationBuilder.java | Adds TypeToString suppression and clarifies why string comparison is used as a subtype fallback. |
| framework/src/main/java/org/checkerframework/framework/util/JavaExpressionParseUtil.java | Refactors enum switches to avoid default blocks (supports exhaustiveness checks). |
| framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java | Reworks switch control flow to use return and throws after switch to avoid unnecessary defaults. |
| framework/src/main/java/org/checkerframework/framework/util/Contract.java | Refactors enum switch to avoid default, throws after switch. |
| framework/src/main/java/org/checkerframework/framework/type/typeannotator/IrrelevantTypeAnnotator.java | Refactors enum switch to avoid default, throws after switch. |
| framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java | Changes CFG visualizer option parsing to use split(..., -1) for trailing-token handling. |
| framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java | Makes alias parsing sensitive to trailing separators/commas via split(..., -1). |
| framework/src/main/java/org/checkerframework/framework/stub/RemoveAnnotationsForInference.java | Adds StringSplitter suppression with explanation for dot-splitting FQNs. |
| framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileUtil.java | Refactors enum switches to avoid defaults; converts switch break to return in visitor. |
| framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileParser.java | Adds StringSplitter suppression with explanation for dot-splitting imported constant names. |
| framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java | Changes option key parsing to split(..., -1) to preserve trailing empty tokens. |
| framework/src/main/java/org/checkerframework/framework/ajava/InsertAjavaAnnotations.java | Suppresses SystemExitOutsideMain for CLI entry point. |
| framework/src/main/java/org/checkerframework/framework/ajava/AnnotationMirrorToAnnotationExprConversion.java | Adds StringSplitter suppressions for dot-splitting FQNs. |
| framework/src/main/java/org/checkerframework/common/value/ValueTransfer.java | Adjusts enum-switch structure (per diff) to comply with enum-switch EP checks. |
| framework/src/main/java/org/checkerframework/common/value/ReflectiveEvaluator.java | Removes unnecessary checked exceptions from signatures and updates callers. |
| framework/src/main/java/org/checkerframework/common/util/debug/TreePrinter.java | Removes unnecessary throws Exception from main. |
| framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnosticUtils.java | Adds StringSplitter suppression and uses quoted pattern for split. |
| dataflow/src/main/java/org/checkerframework/dataflow/expression/ValueLiteral.java | Adds TypeToString suppression with rationale for equality fallback. |
| dataflow/src/main/java/org/checkerframework/dataflow/expression/ArrayCreation.java | Adds TypeToString suppression and slightly restructures equality checks. |
| dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/Constant.java | Refactors enum switch to avoid default, throws after switch. |
| dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeOptions.java | Suppresses SystemExitOutsideMain for CLI parsing helper. |
| dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeLauncher.java | Removes unnecessary throws IOException from OutputStream.write override. |
| dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/AbstractCFGVisualizer.java | Refactors enum switch to avoid default, throws after switch. |
| dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseThree.java | Refactors enum switch to avoid default, throws after switch. |
| dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysisImpl.java | Removes switch default and uses post-switch throw to enforce exhaustiveness. |
| dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysisImpl.java | Removes switch default and uses post-switch throw to enforce exhaustiveness. |
| dataflow/src/main/java/org/checkerframework/dataflow/analysis/AnalysisResult.java | Refactors enum switches to avoid default, throws after switch. |
| checker/src/main/java/org/checkerframework/checker/rlccalledmethods/RLCCalledMethodsVisitor.java | Refactors enum switch to avoid default, throws after switch. |
| checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakChecker.java | Uses split(..., -1) for trailing comma handling in option parsing. |
| checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterVisitor.java | Removes defaults to enforce exhaustiveness and adds explicit BugInCF throws. |
| build.gradle | Enables additional Error Prone checks at ERROR and documents rationale for disabled checks. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Use limit -1 so a trailing "=" produces an empty-value token, caught by the | ||
| // switch default below as "Too many '='" rather than silently treating it as key="". | ||
| String[] split = opt.split("=", -1); | ||
| switch (split.length) { |
| // Use limit -1 so a trailing separator (e.g. "CheckerName.") produces an empty | ||
| // token; the switch default handles unexpected lengths rather than silently | ||
| // using "" as an option key. | ||
| String[] split = key.split(OPTION_SEPARATOR, -1); |
| // Use limit -1 so a trailing "," produces an empty token; Class.forName("") will then | ||
| // throw ClassNotFoundException, giving a clear UserError rather than silent omission. | ||
| String[] aliases = parts[1].trim().split("\\s*,\\s*", -1); | ||
| return IPair.of(canonical, aliases); |
…pression The tsym == comparison is not valid for generic types: List<String> and List<Integer> share the same tsym but are different declared types. Revert to the original toString().equals() comparison and add @SuppressWarnings( "TypeToString") with an explanation, consistent with the pattern used at every other TypeToString fix site in this PR.
…nstructorObject Both methods appeared in the diff after removing stale throws clauses, triggering the lint-diff Javadoc check on CI.
…Javadoc areSameDeclaredTypes uses toString() comparison, which includes type arguments: List<String> and List<Integer> are considered different. Improve its Javadoc to document this and note that Types#isSameType is the preferred alternative when available. Add areSameRawDeclaredType for callers where type arguments are known to be absent or irrelevant. It compares by tsym identity (a single pointer comparison) rather than building and comparing strings, and explicitly documents that type arguments are ignored. Update the one existing caller in AnnotationUtils.compareAnnotationValue to use areSameRawDeclaredType: annotation element values of type Class<?> cannot carry type parameters in Java, so raw type identity is both correct and more efficient there.
Done with Kiro and then a review with Antigravity.