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) {
//cannot use 'var' without initializer
log.error(tree, Errors.CantInferLocalVarType(tree.name, Fragments.LocalMissingInit));
tree.vartype = make.Erroneous();
tree.vartype = make.at(tree.pos()).Erroneous();
} else {
Fragment msg = canInferLocalVarType(tree);
if (msg != null) {
//cannot use 'var' with initializer which require an explicit target
//(e.g. lambda, method reference, array initializer).
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) {
if (type.isErroneous()) {
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 {
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());
}
else {
int varTypePos = Position.NOPOS;
if (parsedType == null) {
boolean var = token.kind == IDENTIFIER && token.name() == names.var;
e = unannotatedType(allowVar, TYPE | NOLAMBDA);
if (var) {
varTypePos = e.pos;
e = null;
}
} else {
@ -1046,9 +1048,10 @@ public class JavacParser implements Parser {
if (Feature.UNNAMED_VARIABLES.allowedInSource(source) && name == names.underscore) {
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) {
var.startPos = pos;
if (var.name == names.underscore && !allowVar) {
log.error(DiagnosticFlag.SYNTAX, varPos, Errors.UseOfUnderscoreNotAllowed);
}
@ -2190,7 +2193,8 @@ public class JavacParser implements Parser {
if (param.vartype != null
&& restrictedTypeName(param.vartype, true) != null) {
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;
}
}
@ -3830,7 +3834,7 @@ public class JavacParser implements Parser {
syntaxError(token.pos, Errors.Expected(EQ));
}
int startPos = Position.NOPOS;
int varTypePos = Position.NOPOS;
JCTree elemType = TreeInfo.innermostType(type, true);
if (elemType.hasTag(IDENT)) {
Name typeName = ((JCIdent) elemType).name;
@ -3842,19 +3846,17 @@ public class JavacParser implements Parser {
reportSyntaxError(elemType.pos, Errors.RestrictedTypeNotAllowedArray(typeName));
} else {
declaredUsingVar = true;
varTypePos = elemType.pos;
if (compound)
//error - 'var' in compound local var decl
reportSyntaxError(elemType.pos, Errors.RestrictedTypeNotAllowedCompound(typeName));
startPos = TreeInfo.getStartPos(mods);
if (startPos == Position.NOPOS)
startPos = TreeInfo.getStartPos(type);
//implicit type
type = null;
}
}
}
JCVariableDecl result = toP(F.at(pos).VarDef(mods, name, type, init, declaredUsingVar));
result.startPos = startPos;
JCVariableDecl result = toP(F.at(pos).VarDef(mods, name, type, init,
declaredUsingVar ? JCVariableDecl.DeclKind.VAR : JCVariableDecl.DeclKind.EXPLICIT, varTypePos));
return attach(result, dc);
}
@ -3968,8 +3970,11 @@ public class JavacParser implements Parser {
name = names.empty;
}
return toP(F.at(pos).VarDef(mods, name, type, null,
type != null && type.hasTag(IDENT) && ((JCIdent)type).name == names.var));
boolean declaredUsingVar = 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 }

View File

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

View File

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

View File

@ -551,7 +551,7 @@ public class TreeCopier<P> implements TreeVisitor<JCTree,P> {
JCExpression vartype = copy(t.vartype, p);
if (t.nameexpr == null) {
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 {
JCExpression nameexpr = copy(t.nameexpr, p);
return M.at(t.pos).ReceiverVarDef(mods, nameexpr, vartype);

View File

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

View File

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

View File

@ -49,7 +49,7 @@ import javax.tools.ToolProvider;
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
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) {
if (nodeType.isInstance(node)) {
JCTree tree = (JCTree)node;
int actual = TreeInfo.getEndPos(tree, unit.endPositions);
int expected = marker.indexOf('^') + 1;
if (actual != expected) {
// Verify declaration start and end positions
int start = tree.getStartPosition();
if (markers.charAt(start) != '<') {
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);
@ -86,34 +101,71 @@ public class DeclarationEndPositions {
public static void main(String... args) throws Exception {
// JCModuleDecl
checkEndPosition(JCModuleDecl.class,
checkPositions(JCModuleDecl.class,
"/* comment */ module fred { /* comment */ } /* comment */",
" ^ ");
" <---------------------------> ");
// JCPackageDecl
checkEndPosition(JCPackageDecl.class,
checkPositions(JCPackageDecl.class,
"/* comment */ package fred; /* comment */",
" ^ ");
" <-----------> ");
// JCClassDecl
checkEndPosition(JCClassDecl.class,
checkPositions(JCClassDecl.class,
"/* comment */ class Fred { /* comment */ } /* comment */",
" ^ ");
" <--------------------------> ");
// JCMethodDecl
checkEndPosition(JCMethodDecl.class,
checkPositions(JCMethodDecl.class,
"/* comment */ class Fred { void m() { /* comment */ } } /* comment */",
" ^ ");
" <------------------------> ");
// JCVariableDecl
checkEndPosition(JCVariableDecl.class,
checkPositions(JCVariableDecl.class,
"/* comment */ class Fred { int x; } /* comment */",
" ^ ");
checkEndPosition(JCVariableDecl.class,
" <----> ");
checkPositions(JCVariableDecl.class,
"/* comment */ class Fred { int x = 123; } /* comment */",
" ^ ");
checkEndPosition(JCVariableDecl.class,
"/* comment */ class A { try {} catch (Error err) {} } /* comment */",
" ^ ");
" <----------> ");
checkPositions(JCVariableDecl.class,
"/* 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;
b = o instanceof String s;
b = o instanceof R(String s);
b = o instanceof R(/*missing*/ s);
b = o instanceof R2(R(/*missing*/ s), String t);
b = o instanceof R2(R(/*missing*/ s), /*missing*/ t);
b = o instanceof R(var s);
b = o instanceof R2(R(var s), String t);
b = o instanceof R2(R(var s), var t);
b = o instanceof R(String _);
b = o instanceof R2(R(/*missing*/ _), /*missing*/ _);
b = o instanceof R2(R(_), /*missing*/ t);
b = o instanceof R2(R(var _), var _);
b = o instanceof R2(R(_), var t);
}
\n\
class R {

View File

@ -67,6 +67,12 @@ public class VarTree {
"java.lang.String testVar");
test.run("java.util.function.Consumer<String> c = (|var 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 {

View File

@ -22,6 +22,9 @@ public class VarWarnPosition {
// Test 3
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: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
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