Fix various starting position bugs relating to implicitly typed variables.

This commit is contained in:
Archie L. Cobbs 2025-06-12 14:01:05 -05:00
parent c793de989f
commit 1272b52d19
12 changed files with 141 additions and 60 deletions

View File

@ -1264,14 +1264,14 @@ public class Attr extends JCTree.Visitor {
if (tree.init == null) { if (tree.init == null) {
//cannot use 'var' without initializer //cannot use 'var' without initializer
log.error(tree, Errors.CantInferLocalVarType(tree.name, Fragments.LocalMissingInit)); log.error(tree, Errors.CantInferLocalVarType(tree.name, Fragments.LocalMissingInit));
tree.vartype = make.Erroneous(); tree.vartype = make.at(tree.pos()).Erroneous();
} else { } else {
Fragment msg = canInferLocalVarType(tree); Fragment msg = canInferLocalVarType(tree);
if (msg != null) { if (msg != null) {
//cannot use 'var' with initializer which require an explicit target //cannot use 'var' with initializer which require an explicit target
//(e.g. lambda, method reference, array initializer). //(e.g. lambda, method reference, array initializer).
log.error(tree, Errors.CantInferLocalVarType(tree.name, msg)); log.error(tree, Errors.CantInferLocalVarType(tree.name, msg));
tree.vartype = make.Erroneous(); tree.vartype = make.at(tree.pos()).Erroneous();
} }
} }
} }
@ -5717,6 +5717,9 @@ public class Attr extends JCTree.Visitor {
private void setSyntheticVariableType(JCVariableDecl tree, Type type) { private void setSyntheticVariableType(JCVariableDecl tree, Type type) {
if (type.isErroneous()) { if (type.isErroneous()) {
tree.vartype = make.at(tree.pos()).Erroneous(); tree.vartype = make.at(tree.pos()).Erroneous();
} else if (tree.declaredUsingVar()) {
Assert.check(tree.typePos != Position.NOPOS);
tree.vartype = make.at(tree.typePos).Type(type);
} else { } else {
tree.vartype = make.at(tree.pos()).Type(type); tree.vartype = make.at(tree.pos()).Type(type);
} }

View File

@ -1005,10 +1005,12 @@ public class JavacParser implements Parser {
pattern = toP(F.at(token.pos).AnyPattern()); pattern = toP(F.at(token.pos).AnyPattern());
} }
else { else {
int varTypePos = Position.NOPOS;
if (parsedType == null) { if (parsedType == null) {
boolean var = token.kind == IDENTIFIER && token.name() == names.var; boolean var = token.kind == IDENTIFIER && token.name() == names.var;
e = unannotatedType(allowVar, TYPE | NOLAMBDA); e = unannotatedType(allowVar, TYPE | NOLAMBDA);
if (var) { if (var) {
varTypePos = e.pos;
e = null; e = null;
} }
} else { } else {
@ -1046,9 +1048,10 @@ public class JavacParser implements Parser {
if (Feature.UNNAMED_VARIABLES.allowedInSource(source) && name == names.underscore) { if (Feature.UNNAMED_VARIABLES.allowedInSource(source) && name == names.underscore) {
name = names.empty; name = names.empty;
} }
JCVariableDecl var = toP(F.at(varPos).VarDef(mods, name, e, null)); JCVariableDecl var = toP(F.at(varPos).VarDef(mods, name, e, null,
varTypePos != Position.NOPOS ? JCVariableDecl.DeclKind.VAR : JCVariableDecl.DeclKind.EXPLICIT,
varTypePos));
if (e == null) { if (e == null) {
var.startPos = pos;
if (var.name == names.underscore && !allowVar) { if (var.name == names.underscore && !allowVar) {
log.error(DiagnosticFlag.SYNTAX, varPos, Errors.UseOfUnderscoreNotAllowed); log.error(DiagnosticFlag.SYNTAX, varPos, Errors.UseOfUnderscoreNotAllowed);
} }
@ -2190,7 +2193,8 @@ public class JavacParser implements Parser {
if (param.vartype != null if (param.vartype != null
&& restrictedTypeName(param.vartype, true) != null) { && restrictedTypeName(param.vartype, true) != null) {
checkSourceLevel(param.pos, Feature.VAR_SYNTAX_IMPLICIT_LAMBDAS); checkSourceLevel(param.pos, Feature.VAR_SYNTAX_IMPLICIT_LAMBDAS);
param.startPos = TreeInfo.getStartPos(param.vartype); param.declKind = JCVariableDecl.DeclKind.VAR;
param.typePos = TreeInfo.getStartPos(param.vartype);
param.vartype = null; param.vartype = null;
} }
} }
@ -3830,7 +3834,7 @@ public class JavacParser implements Parser {
syntaxError(token.pos, Errors.Expected(EQ)); syntaxError(token.pos, Errors.Expected(EQ));
} }
int startPos = Position.NOPOS; int varTypePos = Position.NOPOS;
JCTree elemType = TreeInfo.innermostType(type, true); JCTree elemType = TreeInfo.innermostType(type, true);
if (elemType.hasTag(IDENT)) { if (elemType.hasTag(IDENT)) {
Name typeName = ((JCIdent) elemType).name; Name typeName = ((JCIdent) elemType).name;
@ -3842,19 +3846,17 @@ public class JavacParser implements Parser {
reportSyntaxError(elemType.pos, Errors.RestrictedTypeNotAllowedArray(typeName)); reportSyntaxError(elemType.pos, Errors.RestrictedTypeNotAllowedArray(typeName));
} else { } else {
declaredUsingVar = true; declaredUsingVar = true;
varTypePos = elemType.pos;
if (compound) if (compound)
//error - 'var' in compound local var decl //error - 'var' in compound local var decl
reportSyntaxError(elemType.pos, Errors.RestrictedTypeNotAllowedCompound(typeName)); reportSyntaxError(elemType.pos, Errors.RestrictedTypeNotAllowedCompound(typeName));
startPos = TreeInfo.getStartPos(mods);
if (startPos == Position.NOPOS)
startPos = TreeInfo.getStartPos(type);
//implicit type //implicit type
type = null; type = null;
} }
} }
} }
JCVariableDecl result = toP(F.at(pos).VarDef(mods, name, type, init, declaredUsingVar)); JCVariableDecl result = toP(F.at(pos).VarDef(mods, name, type, init,
result.startPos = startPos; declaredUsingVar ? JCVariableDecl.DeclKind.VAR : JCVariableDecl.DeclKind.EXPLICIT, varTypePos));
return attach(result, dc); return attach(result, dc);
} }
@ -3968,8 +3970,11 @@ public class JavacParser implements Parser {
name = names.empty; name = names.empty;
} }
return toP(F.at(pos).VarDef(mods, name, type, null, boolean declaredUsingVar = type != null && type.hasTag(IDENT) && ((JCIdent)type).name == names.var;
type != null && type.hasTag(IDENT) && ((JCIdent)type).name == names.var)); JCVariableDecl.DeclKind declKind = declaredUsingVar ? JCVariableDecl.DeclKind.VAR :
type != null ? JCVariableDecl.DeclKind.EXPLICIT : JCVariableDecl.DeclKind.IMPLICIT;
int typePos = type != null ? type.pos : pos;
return toP(F.at(pos).VarDef(mods, name, type, null, declKind, typePos));
} }
/** Resources = Resource { ";" Resources } /** Resources = Resource { ";" Resources }

View File

@ -1002,6 +1002,13 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
* A variable definition. * A variable definition.
*/ */
public static class JCVariableDecl extends JCStatement implements VariableTree { public static class JCVariableDecl extends JCStatement implements VariableTree {
public enum DeclKind {
EXPLICIT, // "SomeType name"
IMPLICIT, // "name"
VAR, // "var name"
}
/** variable modifiers */ /** variable modifiers */
public JCModifiers mods; public JCModifiers mods;
/** variable name */ /** variable name */
@ -1014,17 +1021,17 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
public JCExpression init; public JCExpression init;
/** symbol */ /** symbol */
public VarSymbol sym; public VarSymbol sym;
/** explicit start pos */ /** how the variable's type was declared */
public int startPos = Position.NOPOS; public DeclKind declKind;
/** declared using `var` */ /** a source code position to use for "vartype" when null (can happen if declKind != EXPLICIT) */
private boolean declaredUsingVar; public int typePos;
protected JCVariableDecl(JCModifiers mods, protected JCVariableDecl(JCModifiers mods,
Name name, Name name,
JCExpression vartype, JCExpression vartype,
JCExpression init, JCExpression init,
VarSymbol sym) { VarSymbol sym) {
this(mods, name, vartype, init, sym, false); this(mods, name, vartype, init, sym, DeclKind.EXPLICIT, Position.NOPOS);
} }
protected JCVariableDecl(JCModifiers mods, protected JCVariableDecl(JCModifiers mods,
@ -1032,19 +1039,21 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
JCExpression vartype, JCExpression vartype,
JCExpression init, JCExpression init,
VarSymbol sym, VarSymbol sym,
boolean declaredUsingVar) { DeclKind declKind,
int typePos) {
this.mods = mods; this.mods = mods;
this.name = name; this.name = name;
this.vartype = vartype; this.vartype = vartype;
this.init = init; this.init = init;
this.sym = sym; this.sym = sym;
this.declaredUsingVar = declaredUsingVar; this.declKind = declKind;
this.typePos = typePos;
} }
protected JCVariableDecl(JCModifiers mods, protected JCVariableDecl(JCModifiers mods,
JCExpression nameexpr, JCExpression nameexpr,
JCExpression vartype) { JCExpression vartype) {
this(mods, null, vartype, null, null, false); this(mods, null, vartype, null, null, DeclKind.EXPLICIT, Position.NOPOS);
this.nameexpr = nameexpr; this.nameexpr = nameexpr;
if (nameexpr.hasTag(Tag.IDENT)) { if (nameexpr.hasTag(Tag.IDENT)) {
this.name = ((JCIdent)nameexpr).name; this.name = ((JCIdent)nameexpr).name;
@ -1059,7 +1068,7 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
} }
public boolean declaredUsingVar() { public boolean declaredUsingVar() {
return declaredUsingVar; return declKind == DeclKind.VAR;
} }
@Override @Override

View File

@ -724,7 +724,10 @@ public class Pretty extends JCTree.Visitor {
print("... "); print("... ");
print(tree.name); print(tree.name);
} else { } else {
printExpr(tree.vartype); if (tree.vartype == null && tree.declaredUsingVar())
print("var");
else
printExpr(tree.vartype);
print(' '); print(' ');
if (tree.name.isEmpty()) { if (tree.name.isEmpty()) {
print('_'); print('_');

View File

@ -551,7 +551,7 @@ public class TreeCopier<P> implements TreeVisitor<JCTree,P> {
JCExpression vartype = copy(t.vartype, p); JCExpression vartype = copy(t.vartype, p);
if (t.nameexpr == null) { if (t.nameexpr == null) {
JCExpression init = copy(t.init, p); JCExpression init = copy(t.init, p);
return M.at(t.pos).VarDef(mods, t.name, vartype, init); return M.at(t.pos).VarDef(mods, t.name, vartype, init, t.declKind, t.typePos);
} else { } else {
JCExpression nameexpr = copy(t.nameexpr, p); JCExpression nameexpr = copy(t.nameexpr, p);
return M.at(t.pos).ReceiverVarDef(mods, nameexpr, vartype); return M.at(t.pos).ReceiverVarDef(mods, nameexpr, vartype);

View File

@ -610,17 +610,14 @@ public class TreeInfo {
} }
case VARDEF: { case VARDEF: {
JCVariableDecl node = (JCVariableDecl)tree; JCVariableDecl node = (JCVariableDecl)tree;
if (node.startPos != Position.NOPOS) { if (node.mods.pos != Position.NOPOS) {
return node.startPos;
} else if (node.mods.pos != Position.NOPOS) {
return node.mods.pos; return node.mods.pos;
} else if (node.vartype == null || node.vartype.pos == Position.NOPOS) { } else if (node.vartype != null) {
//if there's no type (partially typed lambda parameter)
//simply return node position
return node.pos;
} else {
return getStartPos(node.vartype); return getStartPos(node.vartype);
} else if (node.typePos != Position.NOPOS) {
return node.typePos;
} }
break;
} }
case BINDINGPATTERN: { case BINDINGPATTERN: {
JCBindingPattern node = (JCBindingPattern)tree; JCBindingPattern node = (JCBindingPattern)tree;

View File

@ -237,8 +237,9 @@ public class TreeMaker implements JCTree.Factory {
return tree; return tree;
} }
public JCVariableDecl VarDef(JCModifiers mods, Name name, JCExpression vartype, JCExpression init, boolean declaredUsingVar) { public JCVariableDecl VarDef(JCModifiers mods, Name name, JCExpression vartype, JCExpression init,
JCVariableDecl tree = new JCVariableDecl(mods, name, vartype, init, null, declaredUsingVar); JCVariableDecl.DeclKind declKind, int typePos) {
JCVariableDecl tree = new JCVariableDecl(mods, name, vartype, init, null, declKind, typePos);
tree.pos = pos; tree.pos = pos;
return tree; return tree;
} }

View File

@ -49,7 +49,7 @@ import javax.tools.ToolProvider;
public class DeclarationEndPositions { public class DeclarationEndPositions {
public static void checkEndPosition(Class<? extends JCTree> nodeType, String input, String marker) throws IOException { public static void checkPositions(Class<? extends JCTree> nodeType, String input, String markers) throws IOException {
// Create source // Create source
var source = new SimpleJavaFileObject(URI.create("file://T.java"), JavaFileObject.Kind.SOURCE) { var source = new SimpleJavaFileObject(URI.create("file://T.java"), JavaFileObject.Kind.SOURCE) {
@ -71,11 +71,26 @@ public class DeclarationEndPositions {
public Void scan(Tree node, Void aVoid) { public Void scan(Tree node, Void aVoid) {
if (nodeType.isInstance(node)) { if (nodeType.isInstance(node)) {
JCTree tree = (JCTree)node; JCTree tree = (JCTree)node;
int actual = TreeInfo.getEndPos(tree, unit.endPositions);
int expected = marker.indexOf('^') + 1; // Verify declaration start and end positions
if (actual != expected) { int start = tree.getStartPosition();
if (markers.charAt(start) != '<') {
throw new AssertionError(String.format( throw new AssertionError(String.format(
"wrong end pos %d != %d for \"%s\" @ %d", actual, expected, input, tree.pos)); "wrong %s pos %d for \"%s\" in \"%s\"", "start", start, tree, input));
}
int end = TreeInfo.getEndPos(tree, unit.endPositions);
if (markers.charAt(end - 1) != '>') {
throw new AssertionError(String.format(
"wrong %s pos %d for \"%s\" in \"%s\"", "end", end, tree, input));
}
// For variable declarations using "var", verify the "var" position
if (tree instanceof JCVariableDecl varDecl && varDecl.declaredUsingVar()) {
int vpos = varDecl.typePos;
if (!input.substring(vpos).startsWith("var")) {
throw new AssertionError(String.format(
"wrong %s pos %d for \"%s\" in \"%s\"", "var", vpos, tree, input));
}
} }
} }
return super.scan(node, aVoid); return super.scan(node, aVoid);
@ -86,34 +101,71 @@ public class DeclarationEndPositions {
public static void main(String... args) throws Exception { public static void main(String... args) throws Exception {
// JCModuleDecl // JCModuleDecl
checkEndPosition(JCModuleDecl.class, checkPositions(JCModuleDecl.class,
"/* comment */ module fred { /* comment */ } /* comment */", "/* comment */ module fred { /* comment */ } /* comment */",
" ^ "); " <---------------------------> ");
// JCPackageDecl // JCPackageDecl
checkEndPosition(JCPackageDecl.class, checkPositions(JCPackageDecl.class,
"/* comment */ package fred; /* comment */", "/* comment */ package fred; /* comment */",
" ^ "); " <-----------> ");
// JCClassDecl // JCClassDecl
checkEndPosition(JCClassDecl.class, checkPositions(JCClassDecl.class,
"/* comment */ class Fred { /* comment */ } /* comment */", "/* comment */ class Fred { /* comment */ } /* comment */",
" ^ "); " <--------------------------> ");
// JCMethodDecl // JCMethodDecl
checkEndPosition(JCMethodDecl.class, checkPositions(JCMethodDecl.class,
"/* comment */ class Fred { void m() { /* comment */ } } /* comment */", "/* comment */ class Fred { void m() { /* comment */ } } /* comment */",
" ^ "); " <------------------------> ");
// JCVariableDecl // JCVariableDecl
checkEndPosition(JCVariableDecl.class, checkPositions(JCVariableDecl.class,
"/* comment */ class Fred { int x; } /* comment */", "/* comment */ class Fred { int x; } /* comment */",
" ^ "); " <----> ");
checkEndPosition(JCVariableDecl.class, checkPositions(JCVariableDecl.class,
"/* comment */ class Fred { int x = 123; } /* comment */", "/* comment */ class Fred { int x = 123; } /* comment */",
" ^ "); " <----------> ");
checkEndPosition(JCVariableDecl.class, checkPositions(JCVariableDecl.class,
"/* comment */ class A { try {} catch (Error err) {} } /* comment */", "/* comment */ class Fred { final int x = 123; } /* comment */",
" ^ "); " <----------------> ");
checkPositions(JCVariableDecl.class,
"/* comment */ class Fred { final int x = 123, y = 456; } /* comment */",
" <---------------->--------> ");
checkPositions(JCVariableDecl.class,
"/* comment */ class A { void m() { try {} catch (Error err) {} } } /* comment */",
" <-------> ");
// JCVariableDecl with "var" declarations
checkPositions(JCVariableDecl.class,
"class A { void m() { var foo; } }",
" <------> ");
checkPositions(JCVariableDecl.class,
"class A { void m() { var foo = 42; } }",
" <-----------> ");
checkPositions(JCVariableDecl.class,
"class A { void m() { final var foo = 42; } }",
" <-----------------> ");
checkPositions(JCVariableDecl.class,
"class A { void m() { java.util.function.Consumer<Byte> = foo -> { } } }",
" <-> ");
checkPositions(JCVariableDecl.class,
"class A { void m() { java.util.function.Consumer<Byte> = (foo) -> { } } }",
" <-> ");
checkPositions(JCVariableDecl.class,
"class A { void m() { java.util.function.Consumer<Byte> = (var foo) -> { } } }",
" <-----> ");
checkPositions(JCVariableDecl.class,
"class A { void m() { java.util.function.Consumer<Byte> = (final var foo) -> { } } }",
" <-----------> ");
checkPositions(JCVariableDecl.class,
"class A { record R(int x) { } void m() { switch (null) { case R(var x) -> {} default -> {} } } }",
" <---> <---> ");
checkPositions(JCVariableDecl.class,
"class A { record R(int x) { } void m() { switch (null) { case R(final var x) -> {} default -> {} } } }",
" <---> <---------> ");
} }
} }

View File

@ -72,12 +72,12 @@ public class PrettyTest {
boolean _ = true; boolean _ = true;
b = o instanceof String s; b = o instanceof String s;
b = o instanceof R(String s); b = o instanceof R(String s);
b = o instanceof R(/*missing*/ s); b = o instanceof R(var s);
b = o instanceof R2(R(/*missing*/ s), String t); b = o instanceof R2(R(var s), String t);
b = o instanceof R2(R(/*missing*/ s), /*missing*/ t); b = o instanceof R2(R(var s), var t);
b = o instanceof R(String _); b = o instanceof R(String _);
b = o instanceof R2(R(/*missing*/ _), /*missing*/ _); b = o instanceof R2(R(var _), var _);
b = o instanceof R2(R(_), /*missing*/ t); b = o instanceof R2(R(_), var t);
} }
\n\ \n\
class R { class R {

View File

@ -67,6 +67,12 @@ public class VarTree {
"java.lang.String testVar"); "java.lang.String testVar");
test.run("java.util.function.Consumer<String> c = (|var testVar|) -> {};", test.run("java.util.function.Consumer<String> c = (|var testVar|) -> {};",
"java.lang.String testVar"); "java.lang.String testVar");
test.run("java.util.function.Consumer<String> c = (|final var testVar|) -> {};",
"final java.lang.String testVar");
test.run("record Rec(int x) { }; switch (null) { case Rec(|var testVar|) -> {} default -> {} };",
"int testVar");
test.run("record Rec(int x) { }; switch (null) { case Rec(|final var testVar|) -> {} default -> {} };",
"final int testVar");
} }
void run(String code, String expected) throws IOException { void run(String code, String expected) throws IOException {

View File

@ -22,6 +22,9 @@ public class VarWarnPosition {
// Test 3 // Test 3
Consumer<Depr> c2 = (var d) -> { }; Consumer<Depr> c2 = (var d) -> { };
// Test 4
Consumer<Depr> c3 = (final var d) -> { };
} }
} }

View File

@ -3,4 +3,6 @@ VarWarnPosition.java:21:18: compiler.warn.has.been.deprecated: Depr, compiler.mi
VarWarnPosition.java:21:28: compiler.warn.has.been.deprecated: Depr, compiler.misc.unnamed.package VarWarnPosition.java:21:28: compiler.warn.has.been.deprecated: Depr, compiler.misc.unnamed.package
VarWarnPosition.java:24:18: compiler.warn.has.been.deprecated: Depr, compiler.misc.unnamed.package VarWarnPosition.java:24:18: compiler.warn.has.been.deprecated: Depr, compiler.misc.unnamed.package
VarWarnPosition.java:24:30: compiler.warn.has.been.deprecated: Depr, compiler.misc.unnamed.package VarWarnPosition.java:24:30: compiler.warn.has.been.deprecated: Depr, compiler.misc.unnamed.package
5 warnings VarWarnPosition.java:27:18: compiler.warn.has.been.deprecated: Depr, compiler.misc.unnamed.package
VarWarnPosition.java:27:36: compiler.warn.has.been.deprecated: Depr, compiler.misc.unnamed.package
7 warnings