Repository navigation
Keep java.lang imports that disambiguate a simple name - #174
Merged
Merged
Conversation
`RemoveUnusedImports` deleted every import qualified by `java.lang`
unconditionally. That is unsound: per JLS 6.4.1 a single-type-import
declaration shadows both types declared elsewhere in the current package
and types brought in by an import-on-demand, so `import java.lang.Byte;`
can be the only thing binding `Byte` to the JDK class.
Removing it either makes the reference ambiguous:
error: reference to Byte is ambiguous
both class com.company.project.foo.Byte in com.company.project.foo
and class java.lang.Byte in java.lang match
or silently rebinds the name to a same-package class, which then fails to
compile against the wrong type.
`RemoveUnusedImports` analyses one compilation unit with no classpath and
no symbol resolution, so it cannot prove that no clashing type exists.
Only remove a `java.lang` single-type-import when its simple name is not
referenced at all.
Unchanged: `import java.lang.*;` is still removed (java.lang is implicitly
imported on demand), unused `java.lang` imports are still removed, and
imports of same-package types are still removed -- package members already
shadow import-on-demand declarations, so those can never be disambiguating.
One existing test case encoded the old behaviour and has been updated.
Fixes palantir#1334
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit c9d7b75)
RemoveUnusedImportsTest was a table inherited from google-java-format: each row an array of input lines and one of output lines, joined with \n and run through RemoveUnusedImports alone. 19 of its 23 rows are now cases under test-cases/imports, one file each, named after what it checks, and so is the one test of RemoveUnusedImportsCaseLabelsTest. A case runs the whole formatter, so its after block is the row's expected output, formatted: for every row the formatted input and the formatted expectation are the same text, and a second run changes nothing. One row's input was formatted already, so its case lays the same code out another way and has something to format. Four rows stay in RemoveUnusedImportsTest, as plain tests with text blocks. The three of #37 cannot be cases: a case fixes the imports before it formats, and formatting takes a doubled blank line away by itself, while the command line formats first. The module import parses from JDK 23 on, and the cases run on JDK 21 as well. With the RemoveUnusedImports of 21.X.X, the same four cases fail as the rows did: the ones with a used import from java.lang. Tests on JDK 21: FormatterCasesTest 887 -> 907, RemoveUnusedImportsTest 23 -> 4 (the module import skipped), RemoveUnusedImportsCaseLabelsTest 1 -> gone; 1413 tests in the module, as before. On JDK 25 the four tests of RemoveUnusedImportsTest all run, and the 907 cases pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #149.
What was wrong
Removing unused imports dropped every import from
java.lang, used or not. A single-type import shadows a type with the same simple name declared in the file's own package, and one brought in by an import on demand (JLS 6.4.1). So a usedimport java.lang.Byte;can be the only thing that bindsByteto the JDK class, and removing it makes the name ambiguous or binds it to the other class:Until now the
java.lang.Byteimport was removed. I checked this with acom.company.project.foo.Byteclass next to the file: the input compiles, but the output of 21.X.X fails withreference to Byte is ambiguous. Now the import stays, becauseByteis used. Unusedjava.langimports andimport java.lang.*;are still removed, and so are imports from the file's own package, which can never disambiguate anything.The change
The commit is palantir#1778 by Zayan Khan, cherry-picked with
-x(c9d7b75), with the author and the message kept. Upstream closed the PR unmerged; it fixes palantir#1334. Its CLAUDE.md and changelog commits are left out. The only conflict was inRemoveUnusedImportsTest, whose table has more rows here; upstream's rows come after ours.Tests
The first commit adds upstream's five rows to
RemoveUnusedImportsTestand changes the existing row whose output now keepsimport java.lang.Foo;.The second commit is ours. It moves the tests of unused import removal into case files under
test-cases/imports/, one file per test, named after what it checks:RemoveUnusedImportsCaseLabelsTestbecame cases. A case runs the whole formatter, so its after block is the row's expected output, formatted.RemoveUnusedImportsTestas plain tests with text blocks:With the
RemoveUnusedImportsof 21.X.X, four cases fail: each loses ajava.langimport it uses. With the fix all of them pass.FormatterCasesTest: 887 → 907RemoveUnusedImportsTest: 18 → 4 (the module import is skipped on JDK 21; on JDK 25 all four run)RemoveUnusedImportsCaseLabelsTest: removed, its test is now a caseopen-java-format: 1408 → 1413 tests./gradlew test --rerunpasses for all modules (Gradle plugin 51, IntelliJ plugin 10), and the 907 cases also pass on JDK 25Effect on output
Of the 15,747 files of the JDK 21 sources, 13 format differently. Each of them keeps a
java.langimport that it uses (String,Integer,Character,RuntimePermissionand others), and a second run changes none of them. In two of these files a comment that belongs to such an import no longer ends up somewhere else:That is
javax/management/openmbean/OpenMBeanParameterInfo.java. Injava/util/prefs/Preferences.java, the comment// These imports needed only as a workaround for a JavaDoc bugstays with its import, where before it was left alone after the import block.Because this changes output, it counts toward the second number of the version.