Add ExplicitTypeToVar recipe to replace explicit type with var keyword.#1009
Add ExplicitTypeToVar recipe to replace explicit type with var keyword.#1009motlin wants to merge 2 commits intoopenrewrite:mainfrom
Conversation
At-scale validation resultsRan the Results summary
~1,167 total transformations across 5 repositories. Correctness checks
Sample transformationsSimple: - StringBuilder manipulated = new StringBuilder(cl.getCanonicalName());
+ var manipulated = new StringBuilder(cl.getCanonicalName());Diamond operator transfer: - final HashMap<String, Integer> argumentIndexes = new HashMap<>();
+ final var argumentIndexes = new HashMap<String, Integer>();Nested generics: - AtomicReference<ResultPair<String>> result = new AtomicReference<>();
+ var result = new AtomicReference<ResultPair<String>>();Multi-line: - TreeMap<String, JsonNode> errorsByLocation =
- new TreeMap<>(Comparator.comparingInt((String s) -> StringUtils.countMatches(s, '/'))
+ var errorsByLocation =
+ new TreeMap<String, JsonNode>(Comparator.comparingInt((String s) -> StringUtils.countMatches(s, '/'))
.thenComparing(Function.identity())); |
Comparison:
|
UseVarForGenericsConstructors |
ExplicitTypeToVar |
|
|---|---|---|
| Target | Generic constructor calls only | Any constructor call |
| Type match | Does NOT check declared type == constructor type | Requires TypeUtils.isOfType exact match |
| Primitives | Explicitly skips | Handled by DeclarationCheck.isVarApplicable |
| Ternaries | Explicitly skips | Not checked (relies on isVarApplicable) |
| Generics required | Yes — skips non-generic declarations | No — handles both generic and non-generic |
| Wildcard bounds | Explicitly skips (? extends, ? super) |
Not checked |
| Interface vs impl | Not checked — will transform List<String> x = new ArrayList<>() |
Rejects — declared type must exactly match constructor type |
Key Behavioral Differences
-
UseVarForGenericsConstructorsis broader on type mismatches: It will transformList<String> x = new ArrayList<>()→var x = new ArrayList<String>(), even thoughList≠ArrayList.ExplicitTypeToVarwould skip this because the types don't match exactly. -
ExplicitTypeToVaris broader on non-generics: It handlesStringBuilder sb = new StringBuilder()→var sb = new StringBuilder().UseVarForGenericsConstructorsskips non-generic declarations entirely. -
ExplicitTypeToVaris more conservative overall: The exact type match requirement means it won't accidentally widen or narrow the inferred type. If you declareMap<K,V> m = new HashMap<>(), it won't transform becauseMap ≠ HashMap.
Type Parameter Transfer (diamond → explicit)
Both transfer type parameters from the LHS to the RHS when the constructor uses a diamond operator (<>), but ExplicitTypeToVar.maybeTransferTypeArguments is more thorough — it checks for J.Empty instances inside the type parameter list (diamond operator representation), while UseVarForGenericsConstructors checks if rightTypes (extracted JavaTypes) is empty.
Structural Differences
UseVarForGenericsConstructorsuses@Getteron individual fields;ExplicitTypeToVaruses@Valueon the classUseVarForGenericsConstructorshas ~60 lines more code due to type parameter extraction helpers and bounds checking- Both share
DeclarationCheck.isVarApplicableandDeclarationCheck.transformToVar
Overlap
For generic constructor calls where the declared type exactly matches the constructor type (e.g., ArrayList<String> x = new ArrayList<>()), both recipes would produce the same transformation. ExplicitTypeToVar is a strict subset of UseVarForGenericsConstructors in the generic case, plus it additionally covers non-generic exact-match constructors.
|
I like the mostly widened scope here as compared to the If we are to merge this one I do think we should probably remove or deprecate and redirect What are your thoughts on the above? |
|
@timtebeek I personally wouldn't want I prefer var only where the type doesn't change and it's very obvious what the type is. That includes constructor calls which is what this recipe does. I agree with you about the rename, this should probably be called |
|
That makes sense, thanks; Indeed best then to keep the logic as it is, or make it opt-in to change interface to concrete type. |
6021842 to
4300f9a
Compare
|
I renamed to UseVarForConstructors. I think it's ready to go? |
What's changed?
Add a new
ExplicitTypeToVarrecipe that replaces explicit type declarations withvarwhen the variable is initialized with a constructor call of exactly the same type.For example:
ArrayList<String> list = new ArrayList<>()→var list = new ArrayList<String>()The recipe transfers type arguments from the declaration to the constructor (replacing diamond operators) so the type remains unambiguous after the transformation.
What's your motivation?
The existing
UseVarForObjectandUseVarForGenericsConstructorsrecipes are broader and may transform declarations where the declared type differs from the constructor type (e.g.,List<String> list = new ArrayList<>()). This recipe takes a more conservative approach, only transforming when the declared type exactly matches the constructor type, making it safer for incrementalvaradoption.Anything in particular you'd like reviewers to focus on?
maybeTransferTypeArguments— ensures diamond operators on the constructor get replaced with the explicit type parameters from the declaration so thevartransformation doesn't lose type information.DeclarationCheck.isVarApplicableandDeclarationCheck.transformToVarutilities cover all edge cases for this recipe's scope.Anyone you would like to review specifically?
Have you considered any alternatives or workarounds?
A configurable option on the
UseVarForObject/UseVarForGenericsConstructorsrecipes to restrict to exact-match only — decided a separate recipe is cleaner and easier to compose.Any additional context
Checklist