Skip to content

Do not apply var where the declaration cannot legally carry it - #1221

Merged
jkschneider merged 1 commit into
mainfrom
var-declaration-legality
Aug 24, 2026
Merged

Do not apply var where the declaration cannot legally carry it#1221
jkschneider merged 1 commit into
mainfrom
var-declaration-legality

Conversation

@jkschneider

@jkschneider jkschneider commented Aug 24, 2026

Copy link
Copy Markdown
Member

Two declaration shapes cannot legally carry var, and the lang.var recipes rewrite both into source that does not compile.

C-style array declarators

A declarator that keeps its brackets after the variable name:

int address[] = new int[8];

becomes

var address[] = new int[8];

which is a compile-time error — a var declarator may not have extra array dimension brackets (JLS 14.4.1).

The guard goes in DeclarationCheck.isVarApplicable rather than in UseVarForPrimitive, for two reasons. UseVarForObject reaches the same shape (String s[] = getStrings(); produced var s[] = ... just as badly), and isVarApplicable is already where every sibling recipe bails out for fields, parameters, multiple declarators and ternaries — this is the same class of "var is not legal here" constraint.

Bailing out rather than normalizing to var address = new int[8] is deliberate: normalizing is not uniformly safe, because int a[] = {1, 2, 3}; cannot become var a = {1, 2, 3}; — a bare array initializer has no target type to infer from. A normalizing fix would need its own additional guard; bailing needs none.

Type witnesses on unqualified invocations

A type witness is only legal on a qualified invocation — this.<T>foo(), Type.<T>foo(), super.<T>foo(). UseVarForGenericMethodInvocations emitted one on an unqualified call:

static <T extends HttpMessage> BodyReplacer<T> binary() {
    final Predicate<T> contentTypes = contentType("application/octet-stream");

became

    final var contentTypes = <T>contentType("application/octet-stream");

which does not parse (JLS 15.12 — type arguments follow the . of a qualified invocation).

The guard is narrower than "skip when there is no select". That would regress the existing withOwnFactoryMethods case, where List<String> strs = myList("one", "two") legally becomes var because T is inferable from the arguments and no witness is needed. The witness is only ever synthesized when the resolved return type still mentions a type variable, so that is what the guard keys on.

Suppressing only the witness would not have been enough either: without the declared target type, T re-infers to Object and the variable silently changes type. Skipping the transformation is the correct outcome.

Tests

Added to the existing NotApplicable nested classes in both test files. Full lang.var package: 113 tests, 0 failures. Both new tests fail without the corresponding fix.

Found by compiling the output of UpgradeToJava25 across a set of open source repositories.

A C-style array declarator keeps its brackets after the variable name, so
replacing the type with `var` produces `var address[] = new int[8]`, which is
not legal. The check belongs in the shared applicability gate rather than one
recipe, since object declarations reach it the same way. Normalizing the
declarator instead would need a further guard, because a bare array initializer
has no target type to infer from.

A type witness is only legal on a qualified invocation, so an unqualified
generic call whose return type still mentions a type variable cannot be given
one. Suppressing only the witness would not be enough either: without the
declared target type the variable would silently re-infer to Object.
@github-project-automation github-project-automation Bot moved this to In Progress in OpenRewrite Aug 24, 2026
@jkschneider
jkschneider merged commit 2e5f35f into main Aug 24, 2026
1 check failed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in OpenRewrite Aug 24, 2026
@jkschneider
jkschneider deleted the var-declaration-legality branch August 24, 2026 12:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

1 participant