From 8cc27077c5e83dcf2783ea8294cb6f72639bb367 Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Mon, 19 Dec 2022 14:06:48 +0000 Subject: [PATCH] Allow multiple message arguments for static assert (#14611) Allow multiple message arguments for static assert Signed-off-by: Razvan Nitu Merged-on-behalf-of: Razvan Nitu --- changelog/dmd.static-assert.dd | 13 +++++++ compiler/src/dmd/astbase.d | 13 +++++-- compiler/src/dmd/frontend.h | 2 +- compiler/src/dmd/hdrgen.d | 9 +++-- compiler/src/dmd/parse.d | 15 +++---- compiler/src/dmd/semantic2.d | 39 +++++++++++++------ compiler/src/dmd/staticassert.d | 15 +++++-- compiler/src/dmd/staticassert.h | 2 +- compiler/src/dmd/transitivevisitor.d | 5 ++- .../test/fail_compilation/staticassertargs.d | 9 +++++ .../fail_compilation/staticassertargsfail.d | 10 +++++ 11 files changed, 100 insertions(+), 32 deletions(-) create mode 100644 changelog/dmd.static-assert.dd create mode 100644 compiler/test/fail_compilation/staticassertargs.d create mode 100644 compiler/test/fail_compilation/staticassertargsfail.d diff --git a/changelog/dmd.static-assert.dd b/changelog/dmd.static-assert.dd new file mode 100644 index 0000000000..3c58ddc767 --- /dev/null +++ b/changelog/dmd.static-assert.dd @@ -0,0 +1,13 @@ +`static assert` now supports multiple message arguments + +When the condition evaluates to false, any subsequent expressions will +each be converted to string and then concatenated. The resulting string +will be printed out along with the error diagnostic. +--- +enum e = 3; +static assert(false, "a = ", e); +--- +Will print: +$(CONSOLE +file.d(2): Error: static assert: a = 3 +) diff --git a/compiler/src/dmd/astbase.d b/compiler/src/dmd/astbase.d index adc9b9214e..d1c86e5ed5 100644 --- a/compiler/src/dmd/astbase.d +++ b/compiler/src/dmd/astbase.d @@ -452,12 +452,19 @@ struct ASTBase extern (C++) final class StaticAssert : Dsymbol { Expression exp; - Expression msg; + Expressions* msg; extern (D) this(const ref Loc loc, Expression exp, Expression msg) { - super(Id.empty); - this.loc = loc; + super(loc, Id.empty); + this.exp = exp; + this.msg = new Expressions(1); + (*this.msg)[0] = msg; + } + + extern (D) this(const ref Loc loc, Expression exp, Expressions* msg) + { + super(loc, Id.empty); this.exp = exp; this.msg = msg; } diff --git a/compiler/src/dmd/frontend.h b/compiler/src/dmd/frontend.h index d79f34ccc2..ec02554bdc 100644 --- a/compiler/src/dmd/frontend.h +++ b/compiler/src/dmd/frontend.h @@ -4607,7 +4607,7 @@ class StaticAssert final : public Dsymbol { public: Expression* exp; - Expression* msg; + Array* msgs; StaticAssert* syntaxCopy(Dsymbol* s) override; void addMember(Scope* sc, ScopeDsymbol* sds) override; bool oneMember(Dsymbol** ps, Identifier* ident) override; diff --git a/compiler/src/dmd/hdrgen.d b/compiler/src/dmd/hdrgen.d index 6b51a81acc..ef1b2468c1 100644 --- a/compiler/src/dmd/hdrgen.d +++ b/compiler/src/dmd/hdrgen.d @@ -823,10 +823,13 @@ public: buf.writestring(s.kind()); buf.writeByte('('); s.exp.expressionToBuffer(buf, hgs); - if (s.msg) + if (s.msgs) { - buf.writestring(", "); - s.msg.expressionToBuffer(buf, hgs); + foreach (m; (*s.msgs)[]) + { + buf.writestring(", "); + m.expressionToBuffer(buf, hgs); + } } buf.writestring(");"); buf.writenl(); diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index 93c7ea0eea..2400d84eb3 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -1982,7 +1982,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer { const loc = token.loc; AST.Expression exp; - AST.Expression msg = null; + AST.Expressions* msg = null; //printf("parseStaticAssert()\n"); nextToken(); @@ -1991,15 +1991,16 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer exp = parseAssignExp(); if (token.value == TOK.comma) { - nextToken(); - if (token.value != TOK.rightParenthesis) + if (peekNext() == TOK.rightParenthesis) { - msg = parseAssignExp(); - if (token.value == TOK.comma) - nextToken(); + nextToken(); // consume `,` + nextToken(); // consume `)` } + else + msg = parseArguments(); } - check(TOK.rightParenthesis); + else + check(TOK.rightParenthesis); check(TOK.semicolon, "static assert"); return new AST.StaticAssert(loc, exp, msg); } diff --git a/compiler/src/dmd/semantic2.d b/compiler/src/dmd/semantic2.d index 4795bcfcf6..0801e3471f 100644 --- a/compiler/src/dmd/semantic2.d +++ b/compiler/src/dmd/semantic2.d @@ -110,21 +110,36 @@ private extern(C++) final class Semantic2Visitor : Visitor else if (result) return; - if (sa.msg) + if (sa.msgs) { - sc = sc.startCTFE(); - sa.msg = sa.msg.expressionSemantic(sc); - sa.msg = resolveProperties(sc, sa.msg); - sc = sc.endCTFE(); - sa.msg = sa.msg.ctfeInterpret(); - if (StringExp se = sa.msg.toStringExp()) + OutBuffer msgbuf; + for (size_t i = 0; i < sa.msgs.length; i++) { - // same with pragma(msg) - const slice = se.toUTF8(sc).peekString(); - error(sa.loc, "static assert: \"%.*s\"", cast(int)slice.length, slice.ptr); + Expression e = (*sa.msgs)[i]; + sc = sc.startCTFE(); + e = e.expressionSemantic(sc); + e = resolveProperties(sc, e); + sc = sc.endCTFE(); + e = ctfeInterpretForPragmaMsg(e); + if (e.op == EXP.error) + { + errorSupplemental(sa.loc, "while evaluating `static assert` argument `%s`", (*sa.msgs)[i].toChars()); + return; + } + StringExp se = e.toStringExp(); + if (se) + { + const slice = se.toUTF8(sc).peekString(); + // Hack to keep old formatting to avoid changing error messages everywhere + if (sa.msgs.length == 1) + msgbuf.printf("\"%.*s\"", cast(int)slice.length, slice.ptr); + else + msgbuf.printf("%.*s", cast(int)slice.length, slice.ptr); + } + else + msgbuf.printf("%s", e.toChars()); } - else - error(sa.loc, "static assert: %s", sa.msg.toChars()); + error(sa.loc, "static assert: %s", msgbuf.extractChars()); } else error(sa.loc, "static assert: `%s` is false", sa.exp.toChars()); diff --git a/compiler/src/dmd/staticassert.d b/compiler/src/dmd/staticassert.d index c7d314808e..6c091c9e21 100644 --- a/compiler/src/dmd/staticassert.d +++ b/compiler/src/dmd/staticassert.d @@ -13,6 +13,7 @@ module dmd.staticassert; +import dmd.arraytypes; import dmd.dscope; import dmd.dsymbol; import dmd.expression; @@ -27,19 +28,27 @@ import dmd.visitor; extern (C++) final class StaticAssert : Dsymbol { Expression exp; - Expression msg; + Expressions* msgs; extern (D) this(const ref Loc loc, Expression exp, Expression msg) { super(loc, Id.empty); this.exp = exp; - this.msg = msg; + this.msgs = new Expressions(1); + (*this.msgs)[0] = msg; + } + + extern (D) this(const ref Loc loc, Expression exp, Expressions* msgs) + { + super(loc, Id.empty); + this.exp = exp; + this.msgs = msgs; } override StaticAssert syntaxCopy(Dsymbol s) { assert(!s); - return new StaticAssert(loc, exp.syntaxCopy(), msg ? msg.syntaxCopy() : null); + return new StaticAssert(loc, exp.syntaxCopy(), msgs ? Expression.arraySyntaxCopy(msgs) : null); } override void addMember(Scope* sc, ScopeDsymbol sds) diff --git a/compiler/src/dmd/staticassert.h b/compiler/src/dmd/staticassert.h index d938990146..922e7ec7c9 100644 --- a/compiler/src/dmd/staticassert.h +++ b/compiler/src/dmd/staticassert.h @@ -18,7 +18,7 @@ class StaticAssert : public Dsymbol { public: Expression *exp; - Expression *msg; + Expressions *msg; StaticAssert *syntaxCopy(Dsymbol *s) override; void addMember(Scope *sc, ScopeDsymbol *sds) override; diff --git a/compiler/src/dmd/transitivevisitor.d b/compiler/src/dmd/transitivevisitor.d index 7aaf0b8726..5844911bc6 100644 --- a/compiler/src/dmd/transitivevisitor.d +++ b/compiler/src/dmd/transitivevisitor.d @@ -490,8 +490,9 @@ package mixin template ParseVisitMethods(AST) { //printf("Visiting StaticAssert\n"); s.exp.accept(this); - if (s.msg) - s.msg.accept(this); + if (s.msgs) + foreach (m; (*s.msgs)[]) + m.accept(this); } override void visit(AST.EnumMember em) diff --git a/compiler/test/fail_compilation/staticassertargs.d b/compiler/test/fail_compilation/staticassertargs.d new file mode 100644 index 0000000000..9591fd809a --- /dev/null +++ b/compiler/test/fail_compilation/staticassertargs.d @@ -0,0 +1,9 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/staticassertargs.d(9): Error: static assert: abcxe3!! +--- +*/ + +enum e = "!!"; +static assert(false, "abc", ['x', 'e'], 3, e); diff --git a/compiler/test/fail_compilation/staticassertargsfail.d b/compiler/test/fail_compilation/staticassertargsfail.d new file mode 100644 index 0000000000..911d588756 --- /dev/null +++ b/compiler/test/fail_compilation/staticassertargsfail.d @@ -0,0 +1,10 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/staticassertargsfail.d(10): Error: incompatible types for `('x') : (new Object)`: `char` and `object.Object` +fail_compilation/staticassertargsfail.d(10): while evaluating `static assert` argument `['x', new Object] ~ ""` +--- +*/ + + +static assert(0, "abc", ['x', new Object] ~ "");