diff --git a/compiler/include/dmd/expression.h b/compiler/include/dmd/expression.h index 6d135576e86b..c69f57fe8d26 100644 --- a/compiler/include/dmd/expression.h +++ b/compiler/include/dmd/expression.h @@ -688,8 +688,9 @@ class DotIdExp final : public UnaExp d_bool noderef; // true if the result of the expression will never be dereferenced d_bool wantsym; // do not replace Symbol with its initializer during semantic() d_bool arrow; // ImportC: if -> instead of . + Loc identLoc; // location of `ident` itself (may differ from `loc`, e.g. across multiple lines) - static DotIdExp *create(Loc loc, Expression *e, Identifier *ident); + static DotIdExp *create(Loc loc, Expression *e, Identifier *ident, Loc identLoc = Loc()); void accept(Visitor *v) override { v->visit(this); } }; diff --git a/compiler/src/dmd/astbase.d b/compiler/src/dmd/astbase.d index dfa88c6517c9..83b511104c0c 100644 --- a/compiler/src/dmd/astbase.d +++ b/compiler/src/dmd/astbase.d @@ -5617,11 +5617,13 @@ struct ASTBase extern (C++) final class DotIdExp : UnaExp { Identifier ident; + Loc identLoc; - extern (D) this(Loc loc, Expression e, Identifier ident) + extern (D) this(Loc loc, Expression e, Identifier ident, Loc identLoc = Loc.init) { super(loc, EXP.dotIdentifier, __traits(classInstanceSize, DotIdExp), e); this.ident = ident; + this.identLoc = identLoc.isValid() ? identLoc : loc; } override void accept(Visitor v) diff --git a/compiler/src/dmd/expression.d b/compiler/src/dmd/expression.d index 94bd2d2b7af1..f02f5436980c 100644 --- a/compiler/src/dmd/expression.d +++ b/compiler/src/dmd/expression.d @@ -2191,16 +2191,18 @@ extern (C++) final class DotIdExp : UnaExp bool noderef; // true if the result of the expression will never be dereferenced bool wantsym; // do not replace Symbol with its initializer during semantic() bool arrow; // ImportC: if -> instead of . + Loc identLoc; // location of `ident` itself (may differ from `loc`, e.g. across multiple lines) - extern (D) this(Loc loc, Expression e, Identifier ident) @safe + extern (D) this(Loc loc, Expression e, Identifier ident, Loc identLoc = Loc.init) @safe { super(loc, EXP.dotIdentifier, e); this.ident = ident; + this.identLoc = identLoc.isValid() ? identLoc : loc; } - static DotIdExp create(Loc loc, Expression e, Identifier ident) @safe + static DotIdExp create(Loc loc, Expression e, Identifier ident, Loc identLoc = Loc.init) @safe { - return new DotIdExp(loc, e, ident); + return new DotIdExp(loc, e, ident, identLoc); } override void accept(Visitor v) diff --git a/compiler/src/dmd/expressionsem.d b/compiler/src/dmd/expressionsem.d index c82f939f5327..f107a7181c91 100644 --- a/compiler/src/dmd/expressionsem.d +++ b/compiler/src/dmd/expressionsem.d @@ -16528,7 +16528,7 @@ Expression dotIdSemanticProp(DotIdExp exp, Scope* sc, bool gag) const flag = cast(DotExpFlag) (exp.noderef * DotExpFlag.noDeref | gag * DotExpFlag.gag); - Expression e = dotExp(exp.e1.type, sc, exp.e1, exp.ident, flag); + Expression e = dotExp(exp.e1.type, sc, exp.e1, exp.ident, flag, exp.identLoc); if (e) { e = e.expressionSemantic(sc); diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index 49674ec5216e..096e847de6b5 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -9371,6 +9371,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer if (token.value == TOK.identifier) { Identifier id = token.ident; + const identLoc = token.loc; nextToken(); if (token.value == TOK.not && peekNext() != TOK.is_ && peekNext() != TOK.in_) @@ -9379,7 +9380,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer e = new AST.DotTemplateInstanceExp(loc, e, id, tiargs); } else - e = new AST.DotIdExp(loc, e, id); + e = new AST.DotIdExp(loc, e, id, identLoc); continue; } if (token.value == TOK.new_) diff --git a/compiler/src/dmd/typesem.d b/compiler/src/dmd/typesem.d index 34c67ada05cb..421fa15d0141 100644 --- a/compiler/src/dmd/typesem.d +++ b/compiler/src/dmd/typesem.d @@ -6190,11 +6190,14 @@ void resolve(Type mt, Loc loc, Scope* sc, out Expression pe, out Type pt, out Ds * e = expression to convert * ident = identifier being used * flag = DotExpFlag bit flags + * identLoc = location of `ident` itself, used for accurate error reporting + * when it differs from `e.loc` (e.g. across multiple lines); + * defaults to `e.loc` when not specified * * Returns: * resulting expression with e.ident resolved */ -Expression dotExp(Type mt, Scope* sc, Expression e, Identifier ident, DotExpFlag flag) +Expression dotExp(Type mt, Scope* sc, Expression e, Identifier ident, DotExpFlag flag, Loc identLoc = Loc.init) { enum LOGDOTEXP = false; if (LOGDOTEXP) @@ -7045,11 +7048,12 @@ Expression dotExp(Type mt, Scope* sc, Expression e, Identifier ident, DotExpFlag Expression res = mt.sym.getMemtype(Loc.initial).dotExp(sc, e, ident, DotExpFlag.gag); if (!(flag & 1) && !res) { + const errLoc = identLoc.isValid() ? identLoc : e.loc; if (auto ns = mt.sym.search_correct(ident)) - eSink.error(e.loc, "no property `%s` for type `%s`. Did you mean `%s.%s` ?", ident.toErrMsg(), mt.toErrMsg(), mt.toErrMsg(), + eSink.error(errLoc, "no property `%s` for type `%s`. Did you mean `%s.%s` ?", ident.toErrMsg(), mt.toErrMsg(), mt.toErrMsg(), ns.toErrMsg()); else - eSink.error(e.loc, "no property `%s` for type `%s`", ident.toErrMsg(), + eSink.error(errLoc, "no property `%s` for type `%s`", ident.toErrMsg(), mt.toErrMsg()); eSink.errorSupplemental(mt.sym.loc, "%s `%s` defined here", diff --git a/compiler/test/fail_compilation/diag21284.d b/compiler/test/fail_compilation/diag21284.d new file mode 100644 index 000000000000..23077647853b --- /dev/null +++ b/compiler/test/fail_compilation/diag21284.d @@ -0,0 +1,17 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/diag21284.d(16): Error: no property `three` for type `E` +fail_compilation/diag21284.d(9): enum `E` defined here +--- +*/ +// https://issues.dlang.org/show_bug.cgi?id=21284 (github issue) +enum E { one, two } + +void test21284() +{ + auto x = + E + . + three; +}