From b8dce2552293feeb94babf7b08a8f6fc78980f53 Mon Sep 17 00:00:00 2001 From: Timon Gehr Date: Sat, 5 May 2018 18:47:30 +0200 Subject: [PATCH 01/35] Parse UnpackDeclaration. --- compiler/include/dmd/declaration.h | 13 ++ compiler/include/dmd/dsymbol.h | 2 + compiler/src/dmd/astbase.d | 38 ++++ compiler/src/dmd/declaration.d | 24 ++- compiler/src/dmd/dsymbol.d | 2 + compiler/src/dmd/dsymbolsem.d | 2 + compiler/src/dmd/lexer.d | 47 +++++ compiler/src/dmd/parse.d | 264 +++++++++++++++++++++++---- compiler/src/dmd/visitor/parsetime.d | 1 + 9 files changed, 357 insertions(+), 36 deletions(-) diff --git a/compiler/include/dmd/declaration.h b/compiler/include/dmd/declaration.h index 01bad3b093d2..a55d610e308a 100644 --- a/compiler/include/dmd/declaration.h +++ b/compiler/include/dmd/declaration.h @@ -195,6 +195,19 @@ class TupleDeclaration final : public Declaration /**************************************************************/ +class UnpackDeclaration final : public Declaration +{ +public: + Dsymbols *vars; + Expression *_init; + + UnpackDeclaration *syntaxCopy(Dsymbol *) override; + UnpackDeclaration *isUnpackDeclaration() override { return this; } + void accept(Visitor *v) override { v->visit(this); } +}; + +/**************************************************************/ + class AliasDeclaration final : public Declaration { public: diff --git a/compiler/include/dmd/dsymbol.h b/compiler/include/dmd/dsymbol.h index c76fe8ca7bb9..da7105339322 100644 --- a/compiler/include/dmd/dsymbol.h +++ b/compiler/include/dmd/dsymbol.h @@ -25,6 +25,7 @@ class ThisDeclaration; class BitFieldDeclaration; class TypeInfoDeclaration; class TupleDeclaration; +class UnpackDeclaration; class AliasDeclaration; class AggregateDeclaration; class EnumDeclaration; @@ -266,6 +267,7 @@ class Dsymbol : public ASTNode BitFieldDeclaration *isBitFieldDeclaration(); TypeInfoDeclaration *isTypeInfoDeclaration(); TupleDeclaration *isTupleDeclaration(); + UnpackDeclaration *isUnpackDeclaration(); AliasDeclaration *isAliasDeclaration(); AggregateDeclaration *isAggregateDeclaration(); FuncDeclaration *isFuncDeclaration(); diff --git a/compiler/src/dmd/astbase.d b/compiler/src/dmd/astbase.d index 9a2567ceb71f..28daea31e1b8 100644 --- a/compiler/src/dmd/astbase.d +++ b/compiler/src/dmd/astbase.d @@ -262,6 +262,20 @@ struct ASTBase { v.visit(this); } + + extern (D) static Dsymbols* arraySyntaxCopy(Dsymbols* a) + { + Dsymbols* b = null; + if (a) + { + b = a.copy(); + for (size_t i = 0; i < b.length; i++) + { + (*b)[i] = (*b)[i].syntaxCopy(null); + } + } + return b; + } } extern (C++) class AliasThis : Dsymbol @@ -660,6 +674,30 @@ struct ASTBase } } + extern (C++) final class UnpackDeclaration : Declaration + { + Dsymbols* vars; + Expression _init; + extern (D) this(const ref Loc loc, Dsymbols* vars, Expression _init, StorageClass storage_class) + { + super(null); + this.loc = loc; + this.vars = vars; + this._init = _init; + this.storage_class = storage_class; + } + + override UnpackDeclaration syntaxCopy(Dsymbol s) + { + return new UnpackDeclaration(loc, Dsymbol.arraySyntaxCopy(vars), _init ? _init.syntaxCopy() : null, storage_class); + } + + override void accept(Visitor v) + { + v.visit(this); + } + } + extern (C++) final class FuncLiteralDeclaration : FuncDeclaration { TOK tok; diff --git a/compiler/src/dmd/declaration.d b/compiler/src/dmd/declaration.d index e464040bdc56..f22b2cf51f53 100644 --- a/compiler/src/dmd/declaration.d +++ b/compiler/src/dmd/declaration.d @@ -324,6 +324,29 @@ extern (C++) final class TupleDeclaration : Declaration } } +extern (C++) final class UnpackDeclaration : Declaration +{ + Dsymbols* vars; + Expression _init; + extern (D) this(const ref Loc loc, Dsymbols* vars, Expression _init, STC storage_class) + { + super(DSYM.unpackDeclaration, loc, null); + this.vars = vars; + this._init = _init; + this.storage_class = storage_class; + } + + override UnpackDeclaration syntaxCopy(Dsymbol s) + { + return new UnpackDeclaration(loc, Dsymbol.arraySyntaxCopy(vars), _init ? _init.syntaxCopy() : null, storage_class); + } + + override void accept(Visitor v) + { + v.visit(this); + } +} + /*********************************************************** * https://dlang.org/spec/declaration.html#AliasDeclaration */ @@ -495,7 +518,6 @@ extern (C++) class VarDeclaration : Declaration } } - assert(type || _init); this.type = type; this._init = _init; ctfeAdrOnStack = AdrOnStackNone; diff --git a/compiler/src/dmd/dsymbol.d b/compiler/src/dmd/dsymbol.d index 80ade7a777f9..bd48a9ad41d0 100644 --- a/compiler/src/dmd/dsymbol.d +++ b/compiler/src/dmd/dsymbol.d @@ -319,6 +319,7 @@ enum DSYM : ubyte bitFieldDeclaration, typeInfoDeclaration, tupleDeclaration, + unpackDeclaration, aliasDeclaration, aggregateDeclaration, funcDeclaration, @@ -966,6 +967,7 @@ extern (C++) class Dsymbol : ASTNode inout(BitFieldDeclaration) isBitFieldDeclaration() inout { return dsym == DSYM.bitFieldDeclaration ? cast(inout(BitFieldDeclaration)) cast(void*) this : null; } inout(TypeInfoDeclaration) isTypeInfoDeclaration() inout { return dsym == DSYM.typeInfoDeclaration ? cast(inout(TypeInfoDeclaration)) cast(void*) this : null; } inout(TupleDeclaration) isTupleDeclaration() inout { return dsym == DSYM.tupleDeclaration ? cast(inout(TupleDeclaration)) cast(void*) this : null; } + inout(UnpackDeclaration) isUnpackDeclaration() inout { return dsym == DSYM.unpackDeclaration ? cast(inout(UnpackDeclaration)) cast(void*) this : null; } inout(AliasDeclaration) isAliasDeclaration() inout { return dsym == DSYM.aliasDeclaration ? cast(inout(AliasDeclaration)) cast(void*) this : null; } inout(AggregateDeclaration) isAggregateDeclaration() inout { switch (dsym) diff --git a/compiler/src/dmd/dsymbolsem.d b/compiler/src/dmd/dsymbolsem.d index b91211e7230d..d266279c93b2 100644 --- a/compiler/src/dmd/dsymbolsem.d +++ b/compiler/src/dmd/dsymbolsem.d @@ -2210,6 +2210,8 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor // return; //semanticRun = PSSsemantic; + assert(dsym.type || dsym._init); + if (dsym.semanticRun >= PASS.semanticdone) return; diff --git a/compiler/src/dmd/lexer.d b/compiler/src/dmd/lexer.d index 1e88240e7614..aef75e20620e 100644 --- a/compiler/src/dmd/lexer.d +++ b/compiler/src/dmd/lexer.d @@ -1287,6 +1287,53 @@ class Lexer } } + /********************************* + * tk is on an opening (. + * Look ahead and determine if there is a comma at paren level 1. + */ + final bool isTupleNotation(Token* tk) + { + int parens = 1; + int curlynest = 0; + while (1) + { + tk = peek(tk); + switch (tk.value) + { + case TOK.leftParenthesis: + parens++; + continue; + case TOK.rightParenthesis: + --parens; + if (parens) + continue; + break; + case TOK.comma: + if (curlynest) + continue; + if (parens == 1) + return true; + continue; + case TOK.leftCurly: + curlynest++; + continue; + case TOK.rightCurly: + if (--curlynest >= 0) + continue; + break; + case TOK.semicolon: + if (curlynest) + continue; + break; + case TOK.endOfFile: + break; + default: + continue; + } + return false; + } + } + /******************************************* * Parse escape sequence. */ diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index b9ce3bdf104a..f0ac6a735d45 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -323,6 +323,29 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer return true; } + /************************************ + * Determine if current token is a type constructor or a storage class. + * Assumes: + * - `t` is on a `const`, `immutable`, `inout`, or `shared` token. + * - Trying to parse a declaration or definition. + * - Not parsing a parameter. (`void foo(const(int) = 2){ ... }` case not handled.) + * Returns: + * `true` if token is a type constructor. + */ + bool isTypeConstructor(Token* t) + { + auto next = peek(t); + if (next.value != TOK.leftParenthesis) + return false; + if (isTupleNotation(next)) + return false; + return true; + } + bool isTypeConstructor() + { + return isTypeConstructor(&token); + } + /************************************ * Parse declarations and definitions * Params: @@ -633,25 +656,25 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer break; } case TOK.const_: - if (peekNext() == TOK.leftParenthesis) + if (isTypeConstructor()) goto Ldeclaration; stc = STC.const_; goto Lstc; case TOK.immutable_: - if (peekNext() == TOK.leftParenthesis) + if (isTypeConstructor()) goto Ldeclaration; stc = STC.immutable_; goto Lstc; case TOK.shared_: { - const next = peekNext(); - if (next == TOK.leftParenthesis) + if (isTypeConstructor()) goto Ldeclaration; - if (next == TOK.static_) + auto next = peek(&token); + if (next.value == TOK.static_) { - TOK next2 = peekNext2(); + TOK next2 = peek(next).value; if (next2 == TOK.this_) { s = parseSharedStaticCtor(pAttrs); @@ -667,7 +690,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer goto Lstc; } case TOK.inout_: - if (peekNext() == TOK.leftParenthesis) + if (isTypeConstructor()) goto Ldeclaration; stc = STC.wild; goto Lstc; @@ -820,7 +843,9 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer } case TOK.extern_: { - if (peekNext() != TOK.leftParenthesis) + auto next = peek(&token); + if (next.value != TOK.leftParenthesis || + peekPastParen(next).value == TOK.assign) { stc = STC.extern_; goto Lstc; @@ -1071,6 +1096,11 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer nextToken(); continue; + case TOK.leftParenthesis: + if (peekPastParen(&token).value == TOK.assign) // (confirm unpacking for better error messages) + goto Ldeclaration; + goto default; + // The following are all errors, the cases are just for better error messages than the default case case TOK.return_: case TOK.goto_: @@ -1095,6 +1125,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer continue; } goto Lerror; + default: error("declaration expected, not `%s`", token.toChars()); Lerror: @@ -1124,11 +1155,128 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer return decldefs; } + static bool isVariableStorageClass(TOK tok) + { + switch (tok) + { + case TOK.const_: + case TOK.auto_: + case TOK.extern_: + case TOK.align_: + case TOK.immutable_: + case TOK.shared_: + case TOK.inout_: + case TOK.deprecated_: + case TOK.nothrow_: + case TOK.pure_: + case TOK.ref_: + case TOK.gshared: + case TOK.at: + case TOK.static_: + case TOK.enum_: + return true; + default: + return false; + } + } + + AST.UnpackDeclaration parseUnpackDeclaration(STC storage_class) + in + { + assert(token.value == TOK.leftParenthesis); + } + do + { + nextToken(); + const unpackLoc = token.loc; + bool hasComma = false; + auto vars = new AST.Dsymbols(); + while (token.value != TOK.rightParenthesis) + { + const loc = token.loc; + auto link = linkage; // (ignored) + auto setAlignment = false; + AST.Expression ealign = null; + AST.Expressions* udas = null; + Loc linkloc = this.linkLoc; // (ignored) + parseStorageClasses(storage_class, link, setAlignment, ealign, udas, linkloc); + + /+if (link) + error("linkage specification not allowed within unpack declarations");+/ + if (udas) // TODO + error("user defined attributes not allowed within unpack declarations"); + if (token.value == TOK.leftParenthesis) + { + vars.push(parseUnpackDeclaration(storage_class)); + } + else + { + TOK tkv; + AST.Type t = null; + Identifier i = null; + if (token.value == TOK.identifier && ((tkv = peek(&token).value) == TOK.comma || tkv == TOK.rightParenthesis)) + { + i = token.ident; + nextToken(); + } + else + { + t = parseBasicType(); + t = parseTypeSuffixes(t); + + if (t == AST.Type.terror) + break; + + if (token.value != TOK.identifier) + { + error("expected identifier in unpack declaration"); + break; + } + i = token.ident; + nextToken(); + } + if (storage_class & STC.autoref) + { + error("`auto ref` unpacked variables are not supported"); + } + if (!t && storage_class == STC.none) + { + error("unpacked variable needs at least one storage class, did you mean `auto %s`?", i.toChars()); + } + vars.push(new AST.VarDeclaration(loc, t, i, null, storage_class)); // TODO: UDAs + } + + if (token.value == TOK.rightParenthesis) + { + break; + } + hasComma = true; + if (token.value != TOK.comma) + { + error("expected comma to separate unpack declarators"); + break; + } + nextToken(); + } + if (!hasComma) + { + error("need a trailing comma to unpack a single variable"); + } + if (token.value != TOK.rightParenthesis) + { + error("expected ')' to close unpack declarators"); + } + nextToken(); + check(TOK.assign); + auto _init = parseAssignExp(); + return new AST.UnpackDeclaration(unpackLoc, vars, _init, storage_class); + } + /***************************************** * Parse auto declarations of the form: * storageClass ident = init, ident = init, ... ; * and return the array of them. - * Starts with token on the first ident. + * Starts with token on the first ident or '('. * Ends with scanner past closing ';' */ private AST.Dsymbols* parseAutoDeclarations(STC storageClass, const(char)* comment) @@ -1139,25 +1287,34 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer while (1) { const loc = token.loc; - Identifier ident = token.ident; - nextToken(); // skip over ident + AST.Dsymbol s; + if (token.value == TOK.leftParenthesis && peekPastParen(&token).value == TOK.assign) + { + s = parseUnpackDeclaration(storageClass); + } + else + { + Identifier ident = token.ident; + nextToken(); // skip over ident - AST.TemplateParameters* tpl = null; - if (token.value == TOK.leftParenthesis) - tpl = parseTemplateParameterList(); + AST.TemplateParameters* tpl = null; + if (token.value == TOK.leftParenthesis) + tpl = parseTemplateParameterList(); - check(TOK.assign); // skip over '=' - AST.Initializer _init = parseInitializer(); - auto v = new AST.VarDeclaration(loc, null, ident, _init, storageClass); + check(TOK.assign); // skip over '=' + AST.Initializer _init = parseInitializer(); + auto v = new AST.VarDeclaration(loc, null, ident, _init, storageClass); - AST.Dsymbol s = v; - if (tpl) - { - auto a2 = new AST.Dsymbols(); - a2.push(v); - auto tempdecl = new AST.TemplateDeclaration(loc, ident, tpl, null, a2, 0); - s = tempdecl; + s = v; + if (tpl) + { + auto a2 = new AST.Dsymbols(); + a2.push(v); + auto tempdecl = new AST.TemplateDeclaration(loc, ident, tpl, null, a2, 0); + s = tempdecl; + } } + a.push(s); switch (token.value) { @@ -1168,7 +1325,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer case TOK.comma: nextToken(); - if (!(token.value == TOK.identifier && hasOptionalParensThen(peek(&token), TOK.assign))) + if (!(token.value == TOK.leftParenthesis || token.value == TOK.identifier && hasOptionalParensThen(peek(&token), TOK.assign))) { error("identifier expected following comma"); break; @@ -4332,25 +4489,25 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer switch (token.value) { case TOK.const_: - if (peekNext() == TOK.leftParenthesis) + if (isTypeConstructor()) break; // const as type constructor stc = STC.const_; // const as storage class goto L1; case TOK.immutable_: - if (peekNext() == TOK.leftParenthesis) + if (isTypeConstructor()) break; stc = STC.immutable_; goto L1; case TOK.shared_: - if (peekNext() == TOK.leftParenthesis) + if (isTypeConstructor()) break; stc = STC.shared_; goto L1; case TOK.inout_: - if (peekNext() == TOK.leftParenthesis) + if (isTypeConstructor()) break; stc = STC.wild; goto L1; @@ -4434,7 +4591,9 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer case TOK.extern_: { - if (peekNext() != TOK.leftParenthesis) + auto next = peek(&token); + if (next.value != TOK.leftParenthesis || + peekPastParen(next).value == TOK.assign) { stc = STC.extern_; goto L1; @@ -4591,10 +4750,27 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer return a; } - /* Look for auto initializers: + /* Look for unpack declarations: + * (int x, auto y) = initializer; + * storage_class (a, b, ...) = initializer; + */ + if (token.value == TOK.leftParenthesis && isTupleNotation(&token)) + { + // TODO: can we merge this with the branch below? + AST.Dsymbols* a = parseAutoDeclarations(storage_class | (pAttrs ? pAttrs.storageClass : STC.none), comment); + if (udas) + { + AST.Dsymbol s = new AST.UserAttributeDeclaration(udas, a); + a = new AST.Dsymbols(); + a.push(s); + } + return a; + } + + /* Look for auto initializers and auto unpack declarations: * storage_class identifier = initializer; * storage_class identifier(...) = initializer; - */ + */ if ((storage_class || udas) && token.value == TOK.identifier && hasOptionalParensThen(peek(&token), TOK.assign)) { AST.Dsymbols* a = parseAutoDeclarations(storage_class, comment); @@ -5966,12 +6142,29 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer case TOK.typeof_: case TOK.vector: case TOK.traits: + case TOK.leftParenthesis: /* https://issues.dlang.org/show_bug.cgi?id=15163 * If tokens can be handled as * old C-style declaration or D expression, prefer the latter. */ if (isDeclaration(&token, NeedDeclaratorId.mustIfDstyle, TOK.reserved, null)) goto Ldeclaration; + + if (token.value != TOK.leftParenthesis) + goto Lexp; + + /* This may be the start of an UnpackDeclaration. + */ + auto next = peek(&token); + auto nonLeft = next; + while (nonLeft.value == TOK.leftParenthesis) + nonLeft = peek(nonLeft); + if ((isVariableStorageClass(nonLeft.value) || + isDeclaration(next, NeedDeclaratorId.mustIfDstyle, TOK.reserved, null) || + isDeclaration(nonLeft, NeedDeclaratorId.mustIfDstyle, TOK.reserved, null)) && + peekPastParen(&token).value == TOK.assign) + goto Ldeclaration; + goto Lexp; case TOK.assert_: @@ -5998,7 +6191,6 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer case TOK.string_: case TOK.interpolated: case TOK.hexadecimalString: - case TOK.leftParenthesis: case TOK.cast_: case TOK.mul: case TOK.min: @@ -6411,7 +6603,9 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer goto Lerror; case TOK.scope_: - if (peekNext() != TOK.leftParenthesis) + auto next = peek(&token); + if (next.value != TOK.leftParenthesis || + peekPastParen(next).value == TOK.assign) goto Ldeclaration; // scope used as storage class nextToken(); check(TOK.leftParenthesis); @@ -7335,7 +7529,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer while (1) { - if ((t.value == TOK.const_ || t.value == TOK.immutable_ || t.value == TOK.inout_ || t.value == TOK.shared_) && peek(t).value != TOK.leftParenthesis) + if ((t.value == TOK.const_ || t.value == TOK.immutable_ || t.value == TOK.inout_ || t.value == TOK.shared_) && !isTypeConstructor(t)) { /* const type * immutable type diff --git a/compiler/src/dmd/visitor/parsetime.d b/compiler/src/dmd/visitor/parsetime.d index cbf7b1789762..f06e744b04e6 100644 --- a/compiler/src/dmd/visitor/parsetime.d +++ b/compiler/src/dmd/visitor/parsetime.d @@ -52,6 +52,7 @@ public: void visit(AST.FuncDeclaration s) { visit(cast(AST.Declaration)s); } void visit(AST.AliasDeclaration s) { visit(cast(AST.Declaration)s); } void visit(AST.TupleDeclaration s) { visit(cast(AST.Declaration)s); } + void visit(AST.UnpackDeclaration s) { visit(cast(AST.Declaration)s); } // FuncDeclarations void visit(AST.FuncLiteralDeclaration s) { visit(cast(AST.FuncDeclaration)s); } From bdcfafea68445920bac72294bc1746fabca9d091 Mon Sep 17 00:00:00 2001 From: Timon Gehr Date: Mon, 7 May 2018 21:40:31 +0200 Subject: [PATCH 02/35] Semantic analysis for UnpackDeclaration. --- compiler/include/dmd/attrib.h | 24 +++ compiler/include/dmd/declaration.h | 13 -- compiler/src/dmd/astbase.d | 51 ++++--- compiler/src/dmd/attrib.d | 217 +++++++++++++++++++++++++++ compiler/src/dmd/declaration.d | 23 --- compiler/src/dmd/dsymbolsem.d | 43 ++++++ compiler/src/dmd/expressionsem.d | 26 ++++ compiler/src/dmd/hdrgen.d | 5 + compiler/src/dmd/parse.d | 15 +- compiler/src/dmd/statement.d | 1 - compiler/src/dmd/visitor/parsetime.d | 2 +- 11 files changed, 353 insertions(+), 67 deletions(-) diff --git a/compiler/include/dmd/attrib.h b/compiler/include/dmd/attrib.h index 2bac463ed25e..a7d531d96a26 100644 --- a/compiler/include/dmd/attrib.h +++ b/compiler/include/dmd/attrib.h @@ -198,3 +198,27 @@ class UserAttributeDeclaration final : public AttribDeclaration const char *kind() const override; void accept(Visitor *v) override { v->visit(this); } }; + +/*********************************************************** + * Unpack declarations look like, e.g.: + * auto (a, b) = init; + * (int a, string b) = init; + */ +class UnpackDeclaration final : public AttribDeclaration +{ +public: + Expression *_init; + ScopeDsymbol *scopesym; + StorageClass declared_storage_class; + StorageClass storage_class; + bool onStack; + bool lowered; + + Dsymbols *include(Scope *sc) override; + void addComment(const utf8_t *comment) override; + UnpackDeclaration *syntaxCopy(Dsymbol *) override; + const char *toChars() const override; + const char *kind() const override; + UnpackDeclaration *isUnpackDeclaration() override { return this; } + void accept(Visitor *v) override { v->visit(this); } +}; diff --git a/compiler/include/dmd/declaration.h b/compiler/include/dmd/declaration.h index a55d610e308a..01bad3b093d2 100644 --- a/compiler/include/dmd/declaration.h +++ b/compiler/include/dmd/declaration.h @@ -195,19 +195,6 @@ class TupleDeclaration final : public Declaration /**************************************************************/ -class UnpackDeclaration final : public Declaration -{ -public: - Dsymbols *vars; - Expression *_init; - - UnpackDeclaration *syntaxCopy(Dsymbol *) override; - UnpackDeclaration *isUnpackDeclaration() override { return this; } - void accept(Visitor *v) override { v->visit(this); } -}; - -/**************************************************************/ - class AliasDeclaration final : public Declaration { public: diff --git a/compiler/src/dmd/astbase.d b/compiler/src/dmd/astbase.d index 28daea31e1b8..0c9562a56e89 100644 --- a/compiler/src/dmd/astbase.d +++ b/compiler/src/dmd/astbase.d @@ -674,30 +674,6 @@ struct ASTBase } } - extern (C++) final class UnpackDeclaration : Declaration - { - Dsymbols* vars; - Expression _init; - extern (D) this(const ref Loc loc, Dsymbols* vars, Expression _init, StorageClass storage_class) - { - super(null); - this.loc = loc; - this.vars = vars; - this._init = _init; - this.storage_class = storage_class; - } - - override UnpackDeclaration syntaxCopy(Dsymbol s) - { - return new UnpackDeclaration(loc, Dsymbol.arraySyntaxCopy(vars), _init ? _init.syntaxCopy() : null, storage_class); - } - - override void accept(Visitor v) - { - v.visit(this); - } - } - extern (C++) final class FuncLiteralDeclaration : FuncDeclaration { TOK tok; @@ -1384,6 +1360,33 @@ struct ASTBase } } + extern (C++) final class UnpackDeclaration : AttribDeclaration + { + Dsymbols* vars; + Expression _init; + StorageClass storage_class; + + extern (D) this(const ref Loc loc, Dsymbols* vars, Expression _init, StorageClass storage_class) + { + super(null); + this.loc = loc; + this.vars = vars; + this._init = _init; + this.storage_class = storage_class; + } + + override UnpackDeclaration syntaxCopy(Dsymbol s) + { + return new UnpackDeclaration(loc, Dsymbol.arraySyntaxCopy(vars), _init ? _init.syntaxCopy() : null, storage_class); + } + + override void accept(Visitor v) + { + v.visit(this); + } + } + + extern (C++) final class EnumMember : VarDeclaration { Expression origValue; diff --git a/compiler/src/dmd/attrib.d b/compiler/src/dmd/attrib.d index b62f536b9e02..caa81cc593ed 100644 --- a/compiler/src/dmd/attrib.d +++ b/compiler/src/dmd/attrib.d @@ -37,6 +37,7 @@ import dmd.identifier; import dmd.location; import dmd.common.outbuffer; import dmd.visitor; +import dmd.init; // TODO: maybe remove this? /*********************************************************** * Abstract attribute applied to Dsymbol's used as a common @@ -749,6 +750,222 @@ extern (C++) final class UserAttributeDeclaration : AttribDeclaration } } +/*********************************************************** + * Unpack declarations look like, e.g.: + * auto (a, b) = init; + * (int a, string b) = init; + */ +extern (C++) final class UnpackDeclaration : AttribDeclaration +{ + Expression _init; + ScopeDsymbol scopesym; + STC declared_storage_class; + STC storage_class; + bool onStack = false; + bool lowered = false; + + final extern (D) this(const ref Loc loc, Dsymbols* vars, Expression _init, STC storage_class) + { + super(loc, null, vars); + this._init = _init; + this.declared_storage_class = storage_class; + this.storage_class = storage_class; + this.dsym = DSYM.unpackDeclaration; + } + + bool propagateStorageClasses() + { + static import dmd.errors; + foreach (d; *decl) + { + STC d_storage_class; + if (auto vd = d.isVarDeclaration()) + { + vd.storage_class |= declared_storage_class; + d_storage_class = vd.storage_class; + } + else if (auto up = d.isUnpackDeclaration()) + { + if (!up.propagateStorageClasses()) + return false; + d_storage_class = up.storage_class; + } + else + { + assert(0); + } + if (d_storage_class & STC.static_ && !(storage_class & STC.static_)) + { + dmd.errors.error(loc, "cannot specify `static` for individual components of an unpack declaration"); + return false; + } + if (d_storage_class & STC.manifest && !(storage_class & STC.manifest)) + { + dmd.errors.error(loc, "cannot specify `enum` for individual components of an unpack declaration"); + return false; + } + if (d_storage_class & (STC.ref_ | STC.out_)) + { + storage_class |= d_storage_class & (STC.ref_ | STC.out_); + } + } + + return true; + } + + final void lower(Scope* sc) + { + if (lowered) + return; + if (!sc) + return; + + void fail() + { + decl = null; + lowered = true; + } + + import dmd.expressionsem; + bool needctfe = (storage_class & (STC.manifest | STC.static_)) != 0; + if (needctfe) + { + sc.condition = true; + sc = sc.startCTFE(); + } + // _init = _init.inferType(sc); // TODO? + _init = _init.expressionSemantic(sc); + _init = resolveProperties(sc, _init); + if (needctfe) + { + sc = sc.endCTFE(); + import dmd.dinterpret; + _init = _init.ctfeInterpret(); + } + + if (_init.type.ty == Terror) + { + return fail(); + } + + import dmd.mtype: TypeTuple; + TypeTuple typ = null; + TupleExp tup = null; + + import dmd.tokens: EXP; + if (_init.type.ty == Ttuple && _init.op == EXP.tuple) + { + tup = cast(TupleExp)_init; + } + else + { + import dmd.dsymbolsem : resolveAliasThis; + _init = resolveAliasThis(sc, _init); + if (_init.type.ty == Ttuple && _init.op == EXP.tuple) + { + tup = cast(TupleExp)_init; + } + } + + static import dmd.errors; + if (!tup) + { + dmd.errors.error(loc, "right hand side of unpack declaration must be a tuple or expression sequence"); + return fail(); + } + if (decl.length != tup.exps.length) + { + dmd.errors.error(loc, "incompatible number of components for unpack declaration (`%d` vs. `%d`)", cast(int)decl.length, cast(int)tup.exps.length); + return fail(); + } + + if (!propagateStorageClasses()) + return fail(); + + Expressions* exps = null; + if (tup.isAliasThisTuple()) + { + assert(decl.length != 0); + import dmd.sideeffect; + auto v = copyToTemp(storage_class, "__tup", tup); + import dmd.dsymbolsem : dsymbolSemantic; + v.dsymbolSemantic(sc); + auto ve = new VarExp(loc, v); + ve.type = tup.type; + + exps = new Expressions(); + exps.setDim(1); + (*exps)[0] = ve; + expandAliasThisTuples(exps, 0); + } + else + { + exps = tup.exps; + expandTuples(exps); + } + assert(exps.length == decl.length); + + foreach (i, d; *decl) + { + auto exp = (*exps)[i]; + if (i == 0) + { + exp = Expression.combine(tup.e0, exp); + } + if (auto var = d.isVarDeclaration()) + { + assert (!var._init); + var._init = new ExpInitializer(exp.loc, exp); + } + else if (auto unp = d.isUnpackDeclaration()) + { + assert (!unp._init); + unp._init = exp; + } + else + { + assert(0); + } + if (_scope) + { + import dmd.dsymbolsem : addMember; + d.addMember(_scope, scopesym); + } + } + if (_scope) + { + foreach (d; *decl) + { + import dmd.dsymbolsem : setScope; + d.setScope(_scope); + } + } + lowered = true; + } + + override final void addComment(const(char)* comment) + { + // do nothing + // change this to give semantics to documentation comments on unpack declarations + } + + override UnpackDeclaration syntaxCopy(Dsymbol s) + { + return new UnpackDeclaration(loc, Dsymbol.arraySyntaxCopy(decl), _init ? _init.syntaxCopy() : null, storage_class); + } + + override const(char)* kind() const + { + return "unpack declaration"; + } + + override void accept(Visitor v) + { + v.visit(this); + } +} + + /** * Returns `true` if the given symbol is a symbol declared in * `core.attribute` and has the given identifier. diff --git a/compiler/src/dmd/declaration.d b/compiler/src/dmd/declaration.d index f22b2cf51f53..e2837580e667 100644 --- a/compiler/src/dmd/declaration.d +++ b/compiler/src/dmd/declaration.d @@ -324,29 +324,6 @@ extern (C++) final class TupleDeclaration : Declaration } } -extern (C++) final class UnpackDeclaration : Declaration -{ - Dsymbols* vars; - Expression _init; - extern (D) this(const ref Loc loc, Dsymbols* vars, Expression _init, STC storage_class) - { - super(DSYM.unpackDeclaration, loc, null); - this.vars = vars; - this._init = _init; - this.storage_class = storage_class; - } - - override UnpackDeclaration syntaxCopy(Dsymbol s) - { - return new UnpackDeclaration(loc, Dsymbol.arraySyntaxCopy(vars), _init ? _init.syntaxCopy() : null, storage_class); - } - - override void accept(Visitor v) - { - v.visit(this); - } -} - /*********************************************************** * https://dlang.org/spec/declaration.html#AliasDeclaration */ diff --git a/compiler/src/dmd/dsymbolsem.d b/compiler/src/dmd/dsymbolsem.d index d266279c93b2..6663c8180123 100644 --- a/compiler/src/dmd/dsymbolsem.d +++ b/compiler/src/dmd/dsymbolsem.d @@ -3849,6 +3849,11 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor attribSemantic(sfd); } + override void visit(UnpackDeclaration upd) + { + attribSemantic(upd); + } + private Dsymbols* compileIt(MixinDeclaration cd) { //printf("MixinDeclaration::compileIt(loc = %d) %s\n", cd.loc.linnum, cd.exp.toChars()); @@ -6576,6 +6581,12 @@ private extern(C++) class AddMemberVisitor : Visitor attribAddMember(visd, sc, sds); } + override void visit(UnpackDeclaration upd) + { + // used only for caching the enclosing symbol + upd.scopesym = sds; + } + override void visit(StaticIfDeclaration sid) { //printf("StaticIfDeclaration::addMember() '%s'\n", sid.toChars()); @@ -8463,6 +8474,11 @@ private extern(C++) class SetScopeVisitor : Visitor visit(cast(Dsymbol)uad); visit(cast(AttribDeclaration)uad); } + + override void visit(UnpackDeclaration upd) + { + visit(cast(Dsymbol)upd); + } } void importAll(Dsymbol d, Scope* sc) @@ -8602,6 +8618,9 @@ extern(C++) class ImportAllVisitor : Visitor } } + // do not evaluate variable declarations before semantic pass + override void visit(UnpackDeclaration _) {} + // do not evaluate condition before semantic pass override void visit(StaticIfDeclaration _) {} // do not evaluate aggregate before semantic pass @@ -9390,6 +9409,30 @@ extern(C++) class ConditionIncludeVisitor : Visitor sfd.cache = d; symbols = d; } + + override void visit(UnpackDeclaration upd) + { + if (upd.errors) + { + symbols = null; + return; + } + if (upd.onStack) + { + symbols = null; + return; + } + upd.onStack = true; + scope(exit) upd.onStack = false; + upd.lower(upd._scope ? upd._scope : sc); + if (!upd.lowered) + { + symbols = null; + return; + } + // TODO: call include recursively? + symbols = upd.decl; + } } /** diff --git a/compiler/src/dmd/expressionsem.d b/compiler/src/dmd/expressionsem.d index 995a003aad96..18fbc90fc6db 100644 --- a/compiler/src/dmd/expressionsem.d +++ b/compiler/src/dmd/expressionsem.d @@ -8690,6 +8690,32 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor const olderrors = global.errors; + UnpackDeclaration u = e.declaration.isUnpackDeclaration(); + + if (u) + { + Expression c = null; + import dmd.dsymbolsem : include; + auto d = u.include(sc); + if (d) + { + foreach (var; *d) + { + auto de = new DeclarationExp(var.loc, var); + c = c ? new CommaExp(e.loc, c, de) : de; + } + if (c) + { + result = c.expressionSemantic(sc); + } + } + else + { + result = ErrorExp.get(); + } + return; + } + /* This is here to support extern(linkage) declaration, * where the extern(linkage) winds up being an AttribDeclaration * wrapper. diff --git a/compiler/src/dmd/hdrgen.d b/compiler/src/dmd/hdrgen.d index 3e264d88d1c5..a63a5e5a4fa7 100644 --- a/compiler/src/dmd/hdrgen.d +++ b/compiler/src/dmd/hdrgen.d @@ -255,6 +255,11 @@ public const(char)* toChars(const Dsymbol d) return buf.extractChars(); } + if (auto upd = d.isUnpackDeclaration()) + { + return "unpack declaration"; + } + return d.ident ? d.ident.toHChars2() : "__anonymous"; } diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index f0ac6a735d45..6b743206b8f8 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -1180,7 +1180,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer } } - AST.UnpackDeclaration parseUnpackDeclaration(STC storage_class) + AST.UnpackDeclaration parseUnpackDeclaration(STC g_storage_class, bool parseInitializer = true) in { assert(token.value == TOK.leftParenthesis); @@ -1199,6 +1199,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer AST.Expression ealign = null; AST.Expressions* udas = null; Loc linkloc = this.linkLoc; // (ignored) + auto storage_class = g_storage_class; parseStorageClasses(storage_class, link, setAlignment, ealign, udas, linkloc); /+if (link) @@ -1207,7 +1208,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer error("user defined attributes not allowed within unpack declarations"); if (token.value == TOK.leftParenthesis) { - vars.push(parseUnpackDeclaration(storage_class)); + vars.push(parseUnpackDeclaration(storage_class, false)); } else { @@ -1267,9 +1268,13 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer error("expected ')' to close unpack declarators"); } nextToken(); - check(TOK.assign); - auto _init = parseAssignExp(); - return new AST.UnpackDeclaration(unpackLoc, vars, _init, storage_class); + AST.Expression _init = null; + if (parseInitializer) + { + check(TOK.assign); + _init = parseAssignExp(); + } + return new AST.UnpackDeclaration(unpackLoc, vars, _init, g_storage_class); } /***************************************** diff --git a/compiler/src/dmd/statement.d b/compiler/src/dmd/statement.d index 59b419ce73f1..885f26d16628 100644 --- a/compiler/src/dmd/statement.d +++ b/compiler/src/dmd/statement.d @@ -364,7 +364,6 @@ extern (C++) final class PeelStatement : Statement } } - /*********************************************************** * https://dlang.org/spec/statement.html#ExpressionStatement */ diff --git a/compiler/src/dmd/visitor/parsetime.d b/compiler/src/dmd/visitor/parsetime.d index f06e744b04e6..8109a599701a 100644 --- a/compiler/src/dmd/visitor/parsetime.d +++ b/compiler/src/dmd/visitor/parsetime.d @@ -52,7 +52,6 @@ public: void visit(AST.FuncDeclaration s) { visit(cast(AST.Declaration)s); } void visit(AST.AliasDeclaration s) { visit(cast(AST.Declaration)s); } void visit(AST.TupleDeclaration s) { visit(cast(AST.Declaration)s); } - void visit(AST.UnpackDeclaration s) { visit(cast(AST.Declaration)s); } // FuncDeclarations void visit(AST.FuncLiteralDeclaration s) { visit(cast(AST.FuncDeclaration)s); } @@ -80,6 +79,7 @@ public: void visit(AST.StorageClassDeclaration s) { visit(cast(AST.AttribDeclaration)s); } void visit(AST.ConditionalDeclaration s) { visit(cast(AST.AttribDeclaration)s); } void visit(AST.StaticForeachDeclaration s) { visit(cast(AST.AttribDeclaration)s); } + void visit(AST.UnpackDeclaration s) { visit(cast(AST.AttribDeclaration)s); } //============================================================================================== // Miscellaneous From 46877fba11d6b861dafde4b03777d675e7b0b061 Mon Sep 17 00:00:00 2001 From: Timon Gehr Date: Fri, 1 Sep 2023 14:39:55 +0200 Subject: [PATCH 03/35] Add UnpackDeclaration to Parameter. --- compiler/include/dmd/mtype.h | 6 +++++- compiler/src/dmd/astbase.d | 10 ++++++---- compiler/src/dmd/attrib.d | 9 ++------- compiler/src/dmd/clone.d | 16 ++++++++-------- compiler/src/dmd/cparse.d | 4 ++-- compiler/src/dmd/dsymbol.d | 1 + compiler/src/dmd/dsymbolsem.d | 2 +- compiler/src/dmd/expressionsem.d | 8 ++++---- compiler/src/dmd/funcsem.d | 2 +- compiler/src/dmd/mtype.d | 19 +++++++++++-------- compiler/src/dmd/parse.d | 10 +++++----- compiler/src/dmd/statementsem.d | 17 +++++++++-------- compiler/src/dmd/typesem.d | 8 +++++--- 13 files changed, 60 insertions(+), 52 deletions(-) diff --git a/compiler/include/dmd/mtype.h b/compiler/include/dmd/mtype.h index 2ebb3f60d21d..b6e08000006f 100644 --- a/compiler/include/dmd/mtype.h +++ b/compiler/include/dmd/mtype.h @@ -33,6 +33,8 @@ class TemplateDeclaration; class TypeBasic; class Parameter; +class UnpackDeclaration; + // Back end #ifdef IN_GCC typedef union tree_node type; @@ -428,9 +430,11 @@ class Parameter final : public ASTNode Identifier *ident; Expression *defaultArg; UserAttributeDeclaration *userAttribDecl; // user defined attributes + UnpackDeclaration *unpack; static Parameter *create(Loc loc, StorageClass storageClass, Type *type, Identifier *ident, - Expression *defaultArg, UserAttributeDeclaration *userAttribDecl); + Expression *defaultArg, UserAttributeDeclaration *userAttribDecl, + UnpackDeclaration *unpack); Parameter *syntaxCopy(); bool isLazy() const; bool isReference() const; diff --git a/compiler/src/dmd/astbase.d b/compiler/src/dmd/astbase.d index 0c9562a56e89..450fa6c91440 100644 --- a/compiler/src/dmd/astbase.d +++ b/compiler/src/dmd/astbase.d @@ -1366,7 +1366,7 @@ struct ASTBase Expression _init; StorageClass storage_class; - extern (D) this(const ref Loc loc, Dsymbols* vars, Expression _init, StorageClass storage_class) + extern (D) this(Loc loc, Dsymbols* vars, Expression _init, StorageClass storage_class) { super(null); this.loc = loc; @@ -1744,16 +1744,18 @@ struct ASTBase Identifier ident; Expression defaultArg; UserAttributeDeclaration userAttribDecl; // user defined attributes + UnpackDeclaration unpack; extern (D) alias ForeachDg = int delegate(size_t idx, Parameter param); - final extern (D) this(Loc loc, StorageClass storageClass, Type type, Identifier ident, Expression defaultArg, UserAttributeDeclaration userAttribDecl) + final extern (D) this(Loc loc, StorageClass storageClass, Type type, Identifier ident, Expression defaultArg, UserAttributeDeclaration userAttribDecl, UnpackDeclaration unpack) { this.storageClass = storageClass; this.type = type; this.ident = ident; this.defaultArg = defaultArg; this.userAttribDecl = userAttribDecl; + this.unpack = unpack; } static size_t dim(Parameters* parameters) @@ -1820,7 +1822,7 @@ struct ASTBase Parameter syntaxCopy() { - return new Parameter(loc, storageClass, type ? type.syntaxCopy() : null, ident, defaultArg ? defaultArg.syntaxCopy() : null, userAttribDecl ? userAttribDecl.syntaxCopy(null) : null); + return new Parameter(loc, storageClass, type ? type.syntaxCopy() : null, ident, defaultArg ? defaultArg.syntaxCopy() : null, userAttribDecl ? userAttribDecl.syntaxCopy(null) : null, unpack ? unpack.syntaxCopy(null) : null); } override void accept(Visitor v) @@ -3679,7 +3681,7 @@ struct ASTBase Expression e = (*exps)[i]; if (e.type.ty == Ttuple) error(e.loc, "cannot form sequence of sequences"); - auto arg = new Parameter(e.loc, STC.none, e.type, null, null, null); + auto arg = new Parameter(e.loc, STC.none, e.type, null, null, null, null); (*arguments)[i] = arg; } } diff --git a/compiler/src/dmd/attrib.d b/compiler/src/dmd/attrib.d index caa81cc593ed..1beb7d359d5c 100644 --- a/compiler/src/dmd/attrib.d +++ b/compiler/src/dmd/attrib.d @@ -794,6 +794,7 @@ extern (C++) final class UnpackDeclaration : AttribDeclaration { assert(0); } + import dmd.errors; if (d_storage_class & STC.static_ && !(storage_class & STC.static_)) { dmd.errors.error(loc, "cannot specify `static` for individual components of an unpack declaration"); @@ -886,7 +887,7 @@ extern (C++) final class UnpackDeclaration : AttribDeclaration if (tup.isAliasThisTuple()) { assert(decl.length != 0); - import dmd.sideeffect; + import dmd.sideeffect: copyToTemp; auto v = copyToTemp(storage_class, "__tup", tup); import dmd.dsymbolsem : dsymbolSemantic; v.dsymbolSemantic(sc); @@ -943,12 +944,6 @@ extern (C++) final class UnpackDeclaration : AttribDeclaration lowered = true; } - override final void addComment(const(char)* comment) - { - // do nothing - // change this to give semantics to documentation comments on unpack declarations - } - override UnpackDeclaration syntaxCopy(Dsymbol s) { return new UnpackDeclaration(loc, Dsymbol.arraySyntaxCopy(decl), _init ? _init.syntaxCopy() : null, storage_class); diff --git a/compiler/src/dmd/clone.d b/compiler/src/dmd/clone.d index 5c3b01233e63..fab587eafc36 100644 --- a/compiler/src/dmd/clone.d +++ b/compiler/src/dmd/clone.d @@ -300,7 +300,7 @@ FuncDeclaration buildOpAssign(StructDeclaration sd, Scope* sc) stc = (stc & ~STC.safe) | STC.trusted; } - auto fparams = new Parameters(new Parameter(loc, STC.nodtor, sd.type, Id.p, null, null)); + auto fparams = new Parameters(new Parameter(loc, STC.nodtor, sd.type, Id.p, null, null, null)); auto tf = new TypeFunction(ParameterList(fparams), sd.handleType(), LINK.d, stc | STC.ref_); auto fop = new FuncDeclaration(declLoc, Loc.initial, Id.opAssign, stc, tf); fop.storage_class |= STC.inference; @@ -574,7 +574,7 @@ FuncDeclaration buildXopEquals(StructDeclaration sd, Scope* sc) /* const bool opEquals(ref const S s); */ auto parameters = new Parameters(new Parameter(Loc.initial, STC.ref_ | STC.const_, sd.type, - null, null, null)); + null, null, null, null)); tfeqptr = new TypeFunction(ParameterList(parameters), Type.tbool, LINK.d); tfeqptr.mod = MODFlags.const_; tfeqptr = tfeqptr.typeSemantic(Loc.initial, &scx).isTypeFunction(); @@ -601,7 +601,7 @@ FuncDeclaration buildXopEquals(StructDeclaration sd, Scope* sc) } Loc declLoc; // loc is unnecessary so __xopEquals is never called directly Loc loc; // loc is unnecessary so errors are gagged - auto parameters = new Parameters(new Parameter(loc, STC.ref_ | STC.const_, sd.type, Id.p, null, null)); + auto parameters = new Parameters(new Parameter(loc, STC.ref_ | STC.const_, sd.type, Id.p, null, null, null)); auto tf = new TypeFunction(ParameterList(parameters), Type.tbool, LINK.d, STC.const_); tf = tf.addSTC(STC.const_).toTypeFunction(); Identifier id = Id.xopEquals; @@ -648,7 +648,7 @@ FuncDeclaration buildXopCmp(StructDeclaration sd, Scope* sc) /* const int opCmp(ref const S s); */ auto parameters = new Parameters(new Parameter(Loc.initial, STC.ref_ | STC.const_, sd.type, - null, null, null)); + null, null, null, null)); tfcmpptr = new TypeFunction(ParameterList(parameters), Type.tint32, LINK.d); tfcmpptr.mod = MODFlags.const_; tfcmpptr = tfcmpptr.typeSemantic(Loc.initial, &scx).isTypeFunction(); @@ -724,7 +724,7 @@ FuncDeclaration buildXopCmp(StructDeclaration sd, Scope* sc) } Loc declLoc; // loc is unnecessary so __xopCmp is never called directly Loc loc; // loc is unnecessary so errors are gagged - auto parameters = new Parameters(new Parameter(loc, STC.ref_ | STC.const_, sd.type, Id.p, null, null)); + auto parameters = new Parameters(new Parameter(loc, STC.ref_ | STC.const_, sd.type, Id.p, null, null, null)); auto tf = new TypeFunction(ParameterList(parameters), Type.tint32, LINK.d, STC.const_); tf = tf.addSTC(STC.const_).toTypeFunction(); Identifier id = Id.xopCmp; @@ -851,7 +851,7 @@ FuncDeclaration buildXtoHash(StructDeclaration sd, Scope* sc) //printf("StructDeclaration::buildXtoHash() %s\n", sd.toPrettyChars()); Loc declLoc; // loc is unnecessary so __xtoHash is never called directly Loc loc; // internal code should have no loc to prevent coverage - auto parameters = new Parameters(new Parameter(loc, STC.ref_ | STC.const_, sd.type, Id.p, null, null)); + auto parameters = new Parameters(new Parameter(loc, STC.ref_ | STC.const_, sd.type, Id.p, null, null, null)); auto tf = new TypeFunction(ParameterList(parameters), Type.thash_t, LINK.d, STC.nothrow_ | STC.trusted); Identifier id = Id.xtoHash; auto fop = new FuncDeclaration(declLoc, Loc.initial, id, STC.static_, tf); @@ -1110,7 +1110,7 @@ private DtorDeclaration buildWindowsCppDtor(AggregateDeclaration ad, DtorDeclara // // TODO: if (del) delete (char*)this; // return (void*) this; // } - Parameter delparam = new Parameter(Loc.initial, STC.none, Type.tuns32, Identifier.idPool("del"), new IntegerExp(dtor.loc, 0, Type.tuns32), null); + Parameter delparam = new Parameter(Loc.initial, STC.none, Type.tuns32, Identifier.idPool("del"), new IntegerExp(dtor.loc, 0, Type.tuns32), null, null); Parameters* params = new Parameters; params.push(delparam); const stc = dtor.storage_class & ~STC.scope_; // because we add the `return this;` later @@ -1564,7 +1564,7 @@ private CtorDeclaration generateCtorDeclaration(StructDeclaration sd, const STC { auto structType = sd.type; STC stc = move ? STC.none : STC.ref_; // the only difference between copy or move - auto fparams = new Parameters(new Parameter(Loc.initial, paramStc | stc, structType, Id.p, null, null)); + auto fparams = new Parameters(new Parameter(Loc.initial, paramStc | stc, structType, Id.p, null, null, null)); ParameterList pList = ParameterList(fparams); auto tf = new TypeFunction(pList, structType, LINK.d, STC.ref_); auto ccd = new CtorDeclaration(sd.loc, Loc.initial, STC.ref_, tf); diff --git a/compiler/src/dmd/cparse.d b/compiler/src/dmd/cparse.d index ee7ae468f4f0..a76b196587aa 100644 --- a/compiler/src/dmd/cparse.d +++ b/compiler/src/dmd/cparse.d @@ -3278,7 +3278,7 @@ final class CParser(AST) : Parser!AST t = toConst(t); auto param = new AST.Parameter(id.name ? id.loc : typeLoc, specifiersToSTC(LVL.parameter, specifier), - t, id.name, null, null); + t, id.name, null, null, null); parameters.push(param); if (token.value == TOK.rightParenthesis || token.value == TOK.endOfFile) break; @@ -6018,7 +6018,7 @@ final class CParser(AST) : Parser!AST if (token.value != TOK.identifier) break Lswitch; - auto param = new AST.Parameter(token.loc, STC.none, null, token.ident, null, null); + auto param = new AST.Parameter(token.loc, STC.none, null, token.ident, null, null, null); parameters.push(param); nextToken(); if (token.value == TOK.comma) diff --git a/compiler/src/dmd/dsymbol.d b/compiler/src/dmd/dsymbol.d index bd48a9ad41d0..7e5e47dc4c36 100644 --- a/compiler/src/dmd/dsymbol.d +++ b/compiler/src/dmd/dsymbol.d @@ -151,6 +151,7 @@ extern (C++) private class AddCommentVisitor: Visitor } } override void visit(StaticForeachDeclaration sfd) {} + override void visit(UnpackDeclaration upd) {} } diff --git a/compiler/src/dmd/dsymbolsem.d b/compiler/src/dmd/dsymbolsem.d index 6663c8180123..031e79e2efaa 100644 --- a/compiler/src/dmd/dsymbolsem.d +++ b/compiler/src/dmd/dsymbolsem.d @@ -966,7 +966,7 @@ private Type tupleDeclGetType(TupleDeclaration _this) } else { - auto arg = new Parameter(Loc.initial, STC.none, t, null, null, null); + auto arg = new Parameter(Loc.initial, STC.none, t, null, null, null, null); } (*args)[i] = arg; if (!t.deco) diff --git a/compiler/src/dmd/expressionsem.d b/compiler/src/dmd/expressionsem.d index 18fbc90fc6db..f449c6659fe8 100644 --- a/compiler/src/dmd/expressionsem.d +++ b/compiler/src/dmd/expressionsem.d @@ -4927,7 +4927,7 @@ private bool functionParameters(Loc loc, Scope* sc, for (size_t i = 0; i < arguments.length - nparams; i++) { Expression earg = (*arguments)[nparams + i]; - auto arg = new Parameter(earg.loc, STC.in_, earg.type, null, null, null); + auto arg = new Parameter(earg.loc, STC.in_, earg.type, null, null, null, null); (*args)[i] = arg; } auto tup = new TypeTuple(args); @@ -9129,7 +9129,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor for (size_t i = 0; i < cd.baseclasses.length; i++) { BaseClass* b = (*cd.baseclasses)[i]; - args.push(new Parameter(Loc.initial, STC.in_, b.type, null, null, null)); + args.push(new Parameter(Loc.initial, STC.in_, b.type, null, null, null, null)); } tded = new TypeTuple(args); } @@ -9175,7 +9175,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor */ if (e.tok2 == TOK.parameters && arg.defaultArg && arg.defaultArg.op == EXP.error) return setError(); - args.push(new Parameter(arg.loc, arg.storageClass, arg.type, (e.tok2 == TOK.parameters) ? arg.ident : null, (e.tok2 == TOK.parameters) ? arg.defaultArg : null, arg.userAttribDecl)); + args.push(new Parameter(arg.loc, arg.storageClass, arg.type, (e.tok2 == TOK.parameters) ? arg.ident : null, (e.tok2 == TOK.parameters) ? arg.defaultArg : null, arg.userAttribDecl, (e.tok2 == TOK.parameters) ? arg.unpack : null)); } tded = new TypeTuple(args); break; @@ -19255,7 +19255,7 @@ void lowerNonArrayAggregate(StaticForeach sfe, Scope* sc) { auto p = sfe.aggrfe ? (*sfe.aggrfe.parameters)[i] : sfe.rangefe.param; auto storageClass = j == 2 ? p.storageClass : p.storageClass & ~(STC.manifest | STC.alias_); - params.push(new Parameter(aloc, storageClass, p.type, p.ident, null, null)); + params.push(new Parameter(aloc, storageClass, p.type, p.ident, null, null, p.unpack)); } } Expression[2] res; diff --git a/compiler/src/dmd/funcsem.d b/compiler/src/dmd/funcsem.d index 4133822f2b63..f633987b49fc 100644 --- a/compiler/src/dmd/funcsem.d +++ b/compiler/src/dmd/funcsem.d @@ -3060,7 +3060,7 @@ void buildEnsureRequire(FuncDeclaration thisfd) auto fparams = new Parameters(); if (thisfd.canBuildResultVar()) { - Parameter p = new Parameter(loc, STC.ref_ | STC.const_, f.nextOf(), Id.result, null, null); + Parameter p = new Parameter(loc, STC.ref_ | STC.const_, f.nextOf(), Id.result, null, null, null); fparams.push(p); } auto fo = cast(TypeFunction)(thisfd.originalType ? thisfd.originalType : f); diff --git a/compiler/src/dmd/mtype.d b/compiler/src/dmd/mtype.d index 3dc6871abf80..09e742b164a1 100644 --- a/compiler/src/dmd/mtype.d +++ b/compiler/src/dmd/mtype.d @@ -17,6 +17,7 @@ import core.stdc.stdio; import core.stdc.stdlib; import core.stdc.string; +import dmd.attrib; import dmd.arraytypes; import dmd.astenums; import dmd.ast_node; @@ -1964,7 +1965,7 @@ extern (C++) final class TypeTuple : Type { Expression e = (*exps)[i]; assert(e.type.ty != Ttuple); - auto arg = new Parameter(e.loc, STC.none, e.type, null, null, null); + auto arg = new Parameter(e.loc, STC.none, e.type, null, null, null, null); (*arguments)[i] = arg; } this.arguments = arguments; @@ -1988,14 +1989,14 @@ extern (C++) final class TypeTuple : Type extern (D) this(Type t1) { super(Ttuple); - arguments = new Parameters(new Parameter(Loc.initial, STC.none, t1, null, null, null)); + arguments = new Parameters(new Parameter(Loc.initial, STC.none, t1, null, null, null, null)); } extern (D) this(Type t1, Type t2) { super(Ttuple); - arguments = new Parameters(new Parameter(Loc.initial, STC.none, t1, null, null, null), - new Parameter(Loc.initial, STC.none, t2, null, null, null)); + arguments = new Parameters(new Parameter(Loc.initial, STC.none, t1, null, null, null, null), + new Parameter(Loc.initial, STC.none, t2, null, null, null, null)); } static TypeTuple create() @safe @@ -2292,8 +2293,9 @@ extern (C++) final class Parameter : ASTNode Identifier ident; Expression defaultArg; UserAttributeDeclaration userAttribDecl; // user defined attributes + UnpackDeclaration unpack; - extern (D) this(Loc loc, STC storageClass, Type type, Identifier ident, Expression defaultArg, UserAttributeDeclaration userAttribDecl) @safe + extern (D) this(Loc loc, STC storageClass, Type type, Identifier ident, Expression defaultArg, UserAttributeDeclaration userAttribDecl, UnpackDeclaration unpack) @safe { this.loc = loc; this.type = type; @@ -2301,16 +2303,17 @@ extern (C++) final class Parameter : ASTNode this.storageClass = storageClass; this.defaultArg = defaultArg; this.userAttribDecl = userAttribDecl; + this.unpack = unpack; } - static Parameter create(Loc loc, StorageClass storageClass, Type type, Identifier ident, Expression defaultArg, UserAttributeDeclaration userAttribDecl) @safe + static Parameter create(Loc loc, StorageClass storageClass, Type type, Identifier ident, Expression defaultArg, UserAttributeDeclaration userAttribDecl, UnpackDeclaration unpack) @safe { - return new Parameter(loc, cast(STC) storageClass, type, ident, defaultArg, userAttribDecl); + return new Parameter(loc, cast(STC) storageClass, type, ident, defaultArg, userAttribDecl, unpack); } Parameter syntaxCopy() { - return new Parameter(loc, storageClass, type ? type.syntaxCopy() : null, ident, defaultArg ? defaultArg.syntaxCopy() : null, userAttribDecl ? userAttribDecl.syntaxCopy(null) : null); + return new Parameter(loc, storageClass, type ? type.syntaxCopy() : null, ident, defaultArg ? defaultArg.syntaxCopy() : null, userAttribDecl ? userAttribDecl.syntaxCopy(null) : null, unpack ? unpack.syntaxCopy(null) : null); } /// Returns: Whether the function parameter is lazy diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index 6b743206b8f8..38c4b6c2912f 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -3228,7 +3228,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer nextToken(); ae = parseAssignExp(); } - auto param = new AST.Parameter(loc, storageClass | STC.parameter, at, ai, ae, null); + auto param = new AST.Parameter(loc, storageClass | STC.parameter, at, ai, ae, null, null); if (udas) { auto a = new AST.Dsymbols(); @@ -5505,7 +5505,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer parameterList.parameters = new AST.Parameters(); Identifier id = Identifier.generateId("__T"); AST.Type t = new AST.TypeIdentifier(loc, id); - parameterList.parameters.push(new AST.Parameter(loc, STC.parameter, t, token.ident, null, null)); + parameterList.parameters.push(new AST.Parameter(loc, STC.parameter, t, token.ident, null, null, null)); tpl = new AST.TemplateParameters(); AST.TemplateParameter tp = new AST.TemplateTypeParameter(loc, id, null, null); @@ -5916,7 +5916,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer if (!ai) noIdentifierForDeclarator(at, token); Larg: - auto p = new AST.Parameter(aloc, storageClass, at, ai, null, null); + auto p = new AST.Parameter(aloc, storageClass, at, ai, null, null, null); parameters.push(p); if (token.value == TOK.comma) { @@ -6072,7 +6072,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer const aloc = token.loc; nextToken(); check(TOK.assign); - return new AST.Parameter(aloc, storageClass, at, ai, null, null); + return new AST.Parameter(aloc, storageClass, at, ai, null, null, null); } else if (isDeclaration(&token, NeedDeclaratorId.must, TOK.assign, null)) { @@ -6080,7 +6080,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer const aloc = token.loc; AST.Type at = parseType(&ai); check(TOK.assign); - return new AST.Parameter(aloc, storageClass, at, ai, null, null); + return new AST.Parameter(aloc, storageClass, at, ai, null, null, null); } else if (storageClass != 0 && !_with) { diff --git a/compiler/src/dmd/statementsem.d b/compiler/src/dmd/statementsem.d index c74f05f71e61..1fd8e640023b 100644 --- a/compiler/src/dmd/statementsem.d +++ b/compiler/src/dmd/statementsem.d @@ -3234,7 +3234,7 @@ Statement statementSemanticVisit(Statement s, Scope* sc) auto cs = new Statements(new ExpStatement(ss.loc, tmp)); auto args = new Parameters(new Parameter(Loc.initial, STC.none, ClassDeclaration.object.type, - null, null, null)); + null, null, null, null)); FuncDeclaration fdenter = genCfunc(args, Type.tvoid, Id.monitorenter); Expression e = new CallExp(ss.loc, fdenter, new VarExp(ss.loc, tmp)); @@ -3274,7 +3274,7 @@ Statement statementSemanticVisit(Statement s, Scope* sc) v.dsymbolSemantic(sc); cs.push(new ExpStatement(ss.loc, v)); - auto enterArgs = new Parameters(new Parameter(Loc.initial, STC.none, t.pointerTo(), null, null, null)); + auto enterArgs = new Parameters(new Parameter(Loc.initial, STC.none, t.pointerTo(), null, null, null, null)); FuncDeclaration fdenter = genCfunc(enterArgs, Type.tvoid, Id.criticalenter, STC.nothrow_); Expression e = new AddrExp(ss.loc, tmpExp); @@ -3283,7 +3283,7 @@ Statement statementSemanticVisit(Statement s, Scope* sc) e.type = Type.tvoid; // do not run semantic on e cs.push(new ExpStatement(ss.loc, e)); - auto exitArgs = new Parameters(new Parameter(Loc.initial, STC.none, t, null, null, null)); + auto exitArgs = new Parameters(new Parameter(Loc.initial, STC.none, t, null, null, null, null)); FuncDeclaration fdexit = genCfunc(exitArgs, Type.tvoid, Id.criticalexit, STC.nothrow_); e = new CallExp(ss.loc, fdexit, tmpExp); @@ -4020,12 +4020,12 @@ private extern(D) Expression applyArray(ForeachStatement fs, Expression flde, FuncDeclaration fdapply; TypeDelegate dgty; - auto params = new Parameters(new Parameter(Loc.initial, STC.in_, tn.arrayOf(), null, null, null)); - auto dgparams = new Parameters(new Parameter(Loc.initial, STC.none, Type.tvoidptr, null, null, null)); + auto params = new Parameters(new Parameter(Loc.initial, STC.in_, tn.arrayOf(), null, null, null, null)); + auto dgparams = new Parameters(new Parameter(Loc.initial, STC.none, Type.tvoidptr, null, null, null, null)); if (dim == 2) - dgparams.push(new Parameter(Loc.initial, STC.none, Type.tvoidptr, null, null, null)); + dgparams.push(new Parameter(Loc.initial, STC.none, Type.tvoidptr, null, null, null, null)); dgty = new TypeDelegate(new TypeFunction(ParameterList(dgparams), Type.tint32, LINK.d)); - params.push(new Parameter(Loc.initial, STC.none, dgty, null, null, null)); + params.push(new Parameter(Loc.initial, STC.none, dgty, null, null, null, null)); fdapply = genCfunc(params, Type.tint32, fdname.ptr); if (tab.isTypeSArray()) @@ -4077,6 +4077,7 @@ private extern(D) Expression applyAssocArray(ForeachStatement fs, Expression fld * or * int _d_aaApply2(V[K] aa, int delegate(K*, V*)) */ + auto loc = fs.loc; Identifier hook = dim == 2 ? Id._d_aaApply2 : Id._d_aaApply; Expression func = new IdentifierExp(loc, Id.empty); @@ -4198,7 +4199,7 @@ private FuncExp foreachBodyToFunction(Scope* sc, ForeachStatement fs, TypeFuncti Statement s = new ExpStatement(fs.loc, v); fs._body = new CompoundStatement(fs.loc, s, fs._body); } - params.push(new Parameter(fs.loc, stc, p.type, id, null, null)); + params.push(new Parameter(fs.loc, stc, p.type, id, null, null, null)); } // https://issues.dlang.org/show_bug.cgi?id=13840 // Throwable nested function inside nothrow function is acceptable. diff --git a/compiler/src/dmd/typesem.d b/compiler/src/dmd/typesem.d index 065a71a4c4e0..6f956cc683f8 100644 --- a/compiler/src/dmd/typesem.d +++ b/compiler/src/dmd/typesem.d @@ -3971,7 +3971,9 @@ Type typeSemantic(Type type, Loc loc, Scope* sc) } (*newparams)[j] = new Parameter( loc, stc, narg.type, narg.ident, narg.defaultArg, - narg.userAttribDecl ? narg.userAttribDecl : fparam.userAttribDecl); + narg.userAttribDecl ? narg.userAttribDecl : fparam.userAttribDecl, + narg.unpack, + ); } fparam.type = new TypeTuple(newparams); fparam.type = fparam.type.typeSemantic(loc, argsc); @@ -9030,7 +9032,7 @@ Type substWildTo(Type type, uint mod) continue; if (params == tf.parameterList.parameters) params = tf.parameterList.parameters.copy(); - (*params)[i] = new Parameter(p.loc, p.storageClass, t, null, null, null); + (*params)[i] = new Parameter(p.loc, p.storageClass, t, null, null, null, null); } if (tf.next == tret && params == tf.parameterList.parameters) return tf; @@ -9455,7 +9457,7 @@ Type stripDefaultArgs(Type t) { Type t = stripDefaultArgs(p.type); return (t != p.type || p.defaultArg || p.ident || p.userAttribDecl) - ? new Parameter(p.loc, p.storageClass, t, null, null, null) + ? new Parameter(p.loc, p.storageClass, t, null, null, null, null) : null; } From 3b3c828b289bc5ac3ee25edd0270d7d1131dba9a Mon Sep 17 00:00:00 2001 From: Timon Gehr Date: Fri, 1 Sep 2023 16:03:26 +0200 Subject: [PATCH 04/35] Parse UnpackDeclaration as foreach variable. --- compiler/src/dmd/parse.d | 58 +++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index 38c4b6c2912f..c67cfba46109 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -1180,7 +1180,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer } } - AST.UnpackDeclaration parseUnpackDeclaration(STC g_storage_class, bool parseInitializer = true) + AST.UnpackDeclaration parseUnpackDeclaration(STC g_storage_class, bool parseInitializer = true, bool isParameter = false) in { assert(token.value == TOK.leftParenthesis); @@ -1208,7 +1208,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer error("user defined attributes not allowed within unpack declarations"); if (token.value == TOK.leftParenthesis) { - vars.push(parseUnpackDeclaration(storage_class, false)); + vars.push(parseUnpackDeclaration(storage_class, false, isParameter)); } else { @@ -5829,6 +5829,8 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer STC storageClass = STC.none; STC stc = STC.none; + + AST.UnpackDeclaration unpack = null; Lagain: if (stc) { @@ -5865,36 +5867,28 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer break; case TOK.const_: - if (peekNext() != TOK.leftParenthesis) - { - stc = STC.const_; - goto Lagain; - } - break; + if (isTypeConstructor(&token)) + break; + stc = STC.const_; + goto Lagain; case TOK.immutable_: - if (peekNext() != TOK.leftParenthesis) - { - stc = STC.immutable_; - goto Lagain; - } - break; + if (isTypeConstructor(&token)) + break; + stc = STC.immutable_; + goto Lagain; case TOK.shared_: - if (peekNext() != TOK.leftParenthesis) - { - stc = STC.shared_; - goto Lagain; - } - break; + if (isTypeConstructor(&token)) + break; + stc = STC.shared_; + goto Lagain; case TOK.inout_: - if (peekNext() != TOK.leftParenthesis) - { - stc = STC.wild; - goto Lagain; - } - break; + if (isTypeConstructor(&token)) + break; + stc = STC.wild; + goto Lagain; default: break; @@ -5912,11 +5906,21 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer goto Larg; } } + else if (token.value == TOK.leftParenthesis) + { + TOK after = peekPastParen(&token).value; + if (after == TOK.comma || after == TOK.semicolon) + { + unpack = parseUnpackDeclaration(storageClass | STC.temp | STC.ctfe, false, true); + ai = Identifier.generateId("__unpack"); + goto Larg; + } + } at = parseType(&ai); if (!ai) noIdentifierForDeclarator(at, token); Larg: - auto p = new AST.Parameter(aloc, storageClass, at, ai, null, null, null); + auto p = new AST.Parameter(aloc, storageClass, at, ai, null, null, unpack); parameters.push(p); if (token.value == TOK.comma) { From 3887a3d24c2281c8eb4328f7ab230de75d357835 Mon Sep 17 00:00:00 2001 From: Timon Gehr Date: Fri, 1 Sep 2023 17:36:30 +0200 Subject: [PATCH 05/35] Semantic analysis for UnpackDeclaration in foreach variable. --- compiler/src/dmd/expressionsem.d | 2 +- compiler/src/dmd/statementsem.d | 71 +++++++++++++++++++++++++++++--- 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/compiler/src/dmd/expressionsem.d b/compiler/src/dmd/expressionsem.d index f449c6659fe8..deec68d222b7 100644 --- a/compiler/src/dmd/expressionsem.d +++ b/compiler/src/dmd/expressionsem.d @@ -19255,7 +19255,7 @@ void lowerNonArrayAggregate(StaticForeach sfe, Scope* sc) { auto p = sfe.aggrfe ? (*sfe.aggrfe.parameters)[i] : sfe.rangefe.param; auto storageClass = j == 2 ? p.storageClass : p.storageClass & ~(STC.manifest | STC.alias_); - params.push(new Parameter(aloc, storageClass, p.type, p.ident, null, null, p.unpack)); + params.push(new Parameter(aloc, storageClass, p.type, p.ident, null, null, p.unpack ? p.unpack.syntaxCopy(null) : null)); } } Expression[2] res; diff --git a/compiler/src/dmd/statementsem.d b/compiler/src/dmd/statementsem.d index 1fd8e640023b..fd1e80c0a6f1 100644 --- a/compiler/src/dmd/statementsem.d +++ b/compiler/src/dmd/statementsem.d @@ -929,6 +929,11 @@ Statement statementSemanticVisit(Statement s, Scope* sc) foreach (Parameter p; *fs.parameters) { + if (p.unpack) + { + p.unpack.propagateStorageClasses(); + p.storageClass |= p.unpack.storage_class; + } if (p.storageClass & STC.manifest) { error(fs.loc, "cannot declare `enum` loop variables for non-unrolled foreach"); @@ -960,6 +965,30 @@ Statement statementSemanticVisit(Statement s, Scope* sc) result = s; } + Statement unpackVariables(Statement _body) + { + Statements* ups = null; + foreach (i; 0 .. dim) + { + Parameter p = (*fs.parameters)[i]; + if (p.unpack) + { + if (ups is null) + { + ups = new Statements(); + } + p.unpack._init = new IdentifierExp(p.loc, p.ident); + ups.push(new ExpStatement(p.unpack.loc, p.unpack)); + } + } + if (ups !is null) + { + ups.push(_body); + return new CompoundStatement(loc, ups); + } + return _body; + } + Type tn = null; Type tnv = null; Statement apply() @@ -994,6 +1023,7 @@ Statement statementSemanticVisit(Statement s, Scope* sc) } } + fs._body = unpackVariables(fs._body); // TODO: translate to unpacked parameters instead FuncExp flde = foreachBodyToFunction(sc2, fs, tfld); if (!flde) return null; @@ -1261,6 +1291,8 @@ Statement statementSemanticVisit(Statement s, Scope* sc) fs.value._init = new ExpInitializer(loc, indexExp); Statement ds = new ExpStatement(loc, fs.value); + fs._body = unpackVariables(fs._body); + if (dim == 2) { Parameter p = (*fs.parameters)[0]; @@ -1478,6 +1510,7 @@ Statement statementSemanticVisit(Statement s, Scope* sc) } } + fs._body = unpackVariables(fs._body); forbody = new CompoundStatement(loc, makeargs, fs._body); Statement s = new ForStatement(loc, _init, condition, increment, forbody, fs.endloc); @@ -1667,6 +1700,13 @@ Statement statementSemanticVisit(Statement s, Scope* sc) //increment = new AddAssignExp(loc, new VarExp(loc, fs.key), IntegerExp.literal!1); increment = new PreExp(EXP.prePlusPlus, loc, new VarExp(loc, fs.key)); } + + if (fs.param.unpack !is null) + { + fs.param.unpack._init = new IdentifierExp(fs.param.loc, fs.param.ident); + fs._body = new CompoundStatement(loc, new ExpStatement(fs.param.unpack.loc, fs.param.unpack), fs._body); + } + if ((fs.param.storageClass & STC.ref_) && fs.param.type.equals(fs.key.type)) { fs.key.range = null; @@ -1696,6 +1736,7 @@ Statement statementSemanticVisit(Statement s, Scope* sc) } auto s = new ForStatement(loc, forinit, cond, increment, fs._body, fs.endloc); + if (LabelStatement ls = checkLabeledLoop(sc, fs)) ls.gotoTarget = s; result = s.statementSemantic(sc); @@ -4585,6 +4626,12 @@ public auto makeTupleForeach(Scope* sc, bool isStatic, bool isDecl, ForeachState return returnEarly(); } + if (p.unpack) + { + error(fs.loc, "foreach: cannot unpack key"); + return returnEarly(); + } + const length = te ? te.exps.length : tuple.arguments.length; IntRange dimrange = IntRange(SignExtendedNumber(length))._cast(Type.tsize_t); // https://issues.dlang.org/show_bug.cgi?id=12504 @@ -4615,12 +4662,14 @@ public auto makeTupleForeach(Scope* sc, bool isStatic, bool isDecl, ForeachState * storageClass = The storage class of the variable. * type = The declared type of the variable. * ident = The name of the variable. + * unpack = Associated unpack declaration. * e = The initializer of the variable (i.e. the current element of the looped over aggregate). * t = The type of the initializer. * Returns: * `true` iff the declaration was successful. */ - bool declareVariable(STC storageClass, Type type, Identifier ident, Expression e, Type t) + import dmd.attrib: UnpackDeclaration; + bool declareVariable(STC storageClass, Type type, Identifier ident, UnpackDeclaration unpack, Expression e, Type t) { if (storageClass & (STC.out_ | STC.lazy_) || storageClass & STC.ref_ && !te) @@ -4743,13 +4792,25 @@ public auto makeTupleForeach(Scope* sc, bool isStatic, bool isDecl, ForeachState decls.push(var); else stmts.push(new ExpStatement(loc, var)); + + if (unpack) + { + auto _init = new IdentifierExp(var.loc, var.ident); + auto ndecls = Dsymbol.arraySyntaxCopy(unpack.decl); + auto nunpack = new UnpackDeclaration(var.loc, ndecls, _init, var.storage_class); + if (isDecl) + decls.push(nunpack); + else + stmts.push(new ExpStatement(loc, nunpack)); + } + return true; } if (!isStatic) { // Declare value - if (!declareVariable(p.storageClass, p.type, p.ident, e, t)) + if (!declareVariable(p.storageClass, p.type, p.ident, p.unpack, e, t)) { return returnEarly(); } @@ -4759,7 +4820,7 @@ public auto makeTupleForeach(Scope* sc, bool isStatic, bool isDecl, ForeachState if (!needExpansion) { // Declare value - if (!declareVariable(p.storageClass, p.type, p.ident, e, t)) + if (!declareVariable(p.storageClass, p.type, p.ident, p.unpack, e, t)) { return returnEarly(); } @@ -4768,7 +4829,7 @@ public auto makeTupleForeach(Scope* sc, bool isStatic, bool isDecl, ForeachState { // expand tuples into multiple `static foreach` variables. assert(e && !t); auto ident = Identifier.generateId("__value"); - declareVariable(STC.none, e.type, ident, e, null); + declareVariable(STC.none, e.type, ident, null, e, null); import dmd.cond: StaticForeach; auto field = Identifier.idPool(StaticForeach.tupleFieldName.ptr,StaticForeach.tupleFieldName.length); Expression access = new DotIdExp(loc, e, field); @@ -4782,7 +4843,7 @@ public auto makeTupleForeach(Scope* sc, bool isStatic, bool isDecl, ForeachState Expression init_ = new IndexExp(loc, access, new IntegerExp(loc, l, Type.tsize_t)); init_ = init_.expressionSemantic(sc); assert(init_.type); - declareVariable(p.storageClass, init_.type, cp.ident, init_, null); + declareVariable(p.storageClass, init_.type, cp.ident, cp.unpack, init_, null); } } } From 7a352f2acf7bdcf550fa862293813db1247c13f8 Mon Sep 17 00:00:00 2001 From: Timon Gehr Date: Thu, 5 Sep 2024 00:08:28 +0200 Subject: [PATCH 06/35] Parse UnpackDeclaration as a function literal parameter. --- compiler/src/dmd/parse.d | 42 +++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index c67cfba46109..3ee14b140fc2 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -3088,25 +3088,25 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer break; case TOK.const_: - if (peekNext() == TOK.leftParenthesis) + if (isTypeConstructor()) goto default; stc = STC.const_; goto L2; case TOK.immutable_: - if (peekNext() == TOK.leftParenthesis) + if (isTypeConstructor()) goto default; stc = STC.immutable_; goto L2; case TOK.shared_: - if (peekNext() == TOK.leftParenthesis) + if (isTypeConstructor()) goto default; stc = STC.shared_; goto L2; case TOK.inout_: - if (peekNext() == TOK.leftParenthesis) + if (isTypeConstructor()) goto default; stc = STC.wild; goto L2; @@ -3202,8 +3202,8 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer const tv = peekNext(); Loc loc; - if (tpl && token.value == TOK.identifier && - (tv == TOK.comma || tv == TOK.rightParenthesis || tv == TOK.dotDotDot)) + AST.UnpackDeclaration unpack = null; + void makeTypeParameter() { Identifier id = Identifier.generateId("__T"); loc = token.loc; @@ -3212,7 +3212,12 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer *tpl = new AST.TemplateParameters(); AST.TemplateParameter tp = new AST.TemplateTypeParameter(loc, id, null, null); (*tpl).push(tp); + } + if (tpl && token.value == TOK.identifier && + (tv == TOK.comma || tv == TOK.rightParenthesis || tv == TOK.dotDotDot)) + { + makeTypeParameter(); ai = token.ident; nextToken(); } @@ -3220,7 +3225,30 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer { if (tpl && !*tpl && hasAutoRefParam) *tpl = new AST.TemplateParameters(); + + if (tpl && token.value == TOK.leftParenthesis) + { + const tv2 = peekPastParen(&token).value; + if (tv2 == TOK.comma || tv2 == TOK.rightParenthesis || tv2 == TOK.dotDotDot) + { + makeTypeParameter(); + if (storageClass & STC.lazy_) + { + error("unpacking `lazy` parameters is not supported"); + } + if (storageClass & STC.autoref) + { + error("unpacking `auto ref` parameters is not supported"); + } + unpack = parseUnpackDeclaration(storageClass & ~STC.lazy_ & ~STC.out_ | STC.temp | STC.ctfe, false, true); + ai = Identifier.generateId("__unpack"); + goto LskipType; + } + } + at = parseType(&ai, &loc); + + LskipType:; } ae = null; if (token.value == TOK.assign) // = defaultArg @@ -3228,7 +3256,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer nextToken(); ae = parseAssignExp(); } - auto param = new AST.Parameter(loc, storageClass | STC.parameter, at, ai, ae, null, null); + auto param = new AST.Parameter(loc, storageClass | STC.parameter, at, ai, ae, null, unpack); if (udas) { auto a = new AST.Dsymbols(); From 47743e0a10aaffd349a766433ad8adaca41687ee Mon Sep 17 00:00:00 2001 From: Timon Gehr Date: Thu, 5 Sep 2024 00:48:58 +0200 Subject: [PATCH 07/35] Semantic analysis for UnpackDeclaration in function literal parameter. --- compiler/src/dmd/funcsem.d | 25 +++++++++++++++++++++++++ compiler/src/dmd/semantic3.d | 2 ++ compiler/src/dmd/typesem.d | 11 +++++++++++ 3 files changed, 38 insertions(+) diff --git a/compiler/src/dmd/funcsem.d b/compiler/src/dmd/funcsem.d index f633987b49fc..657cb2f085d1 100644 --- a/compiler/src/dmd/funcsem.d +++ b/compiler/src/dmd/funcsem.d @@ -2935,6 +2935,31 @@ Statement mergeFrequireInclusivePreview(FuncDeclaration fd, Statement sf, Expres return sf; } +/**************************************************** + * Unpack parameters of function literal. + */ +void unpackFunctionParameters(FuncDeclaration thisfd) +{ + if (!thisfd.fbody || !thisfd.type || thisfd.type.ty != Tfunction) + return; + TypeFunction f = cast(TypeFunction)thisfd.type; + Statements* ups = null; + foreach (i, Parameter p; f.parameterList) + { + if (!p.unpack) + continue; + if (ups is null) + ups = new Statements(); + p.unpack._init = new IdentifierExp(p.loc, p.ident); + ups.push(new ExpStatement(p.unpack.loc, p.unpack)); + } + if (ups !is null) + { + ups.push(thisfd.fbody); + thisfd.fbody = new CompoundStatement(thisfd.fbody.loc, ups); + } +} + /**************************************************** * Rewrite contracts as statements. */ diff --git a/compiler/src/dmd/semantic3.d b/compiler/src/dmd/semantic3.d index 8056a0115672..8f6693185d7a 100644 --- a/compiler/src/dmd/semantic3.d +++ b/compiler/src/dmd/semantic3.d @@ -611,6 +611,8 @@ private extern(C++) final class Semantic3Visitor : Visitor bool inferRef = (f.isRef && (funcdecl.storage_class & STC.auto_)); + unpackFunctionParameters(funcdecl); + funcdecl.fbody = funcdecl.fbody.statementSemantic(sc2); if (!funcdecl.fbody) funcdecl.fbody = new CompoundStatement(Loc.initial, new Statements()); diff --git a/compiler/src/dmd/typesem.d b/compiler/src/dmd/typesem.d index 6f956cc683f8..69028f92ae46 100644 --- a/compiler/src/dmd/typesem.d +++ b/compiler/src/dmd/typesem.d @@ -1943,6 +1943,12 @@ void purityLevel(TypeFunction typeFunction) if (!t) continue; + if (fparam.unpack) + { + fparam.unpack.propagateStorageClasses(); + fparam.storageClass |= fparam.unpack.storage_class; + } + if (fparam.storageClass & (STC.lazy_ | STC.out_)) { typeFunction.purity = PURE.weak; @@ -3908,6 +3914,11 @@ Type typeSemantic(Type type, Loc loc, Scope* sc) for (size_t i = 0; i < dim; i++) { Parameter fparam = tf.parameterList[i]; + if (fparam.unpack) + { + fparam.unpack.propagateStorageClasses(); + fparam.storageClass |= fparam.unpack.storage_class; + } fparam.storageClass |= STC.parameter; mtype.inuse++; fparam.type = fparam.type.typeSemantic(loc, argsc); From eb1145b30a8f1b033613eb9d8f8771bedf15ebb0 Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Mon, 7 Apr 2025 10:48:29 +0100 Subject: [PATCH 08/35] [unpacking] Add test --- compiler/test/runnable/unpacking.d | 58 ++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 compiler/test/runnable/unpacking.d diff --git a/compiler/test/runnable/unpacking.d b/compiler/test/runnable/unpacking.d new file mode 100644 index 000000000000..999c04fc8670 --- /dev/null +++ b/compiler/test/runnable/unpacking.d @@ -0,0 +1,58 @@ +// test unpacking works with custom struct +struct Tuple(T...) +{ + T expand; + alias this = expand; +} +auto tuple(T...)(T args) => Tuple!T(args); + +void main() +{ + auto (x, const y, z) = tuple(1, 2L, "three"); + static assert(is(typeof(x) == int)); + static assert(is(typeof(y) == const long)); + static assert(is(typeof(z) == string)); + assert(x == 1); + assert(y == 2L); + assert(z == "three"); + + (string a, int b) = tuple("four", 5); + assert(a == "four"); + assert(b == 5); + + // foreach unpacking + auto tc = tuple(0, ""); + foreach ((i, s); [tuple(1, "one"), tuple(2, "two")]) + { + tc[0] += i; + tc[1] ~= s; + } + assert(tc[0] == 3); + assert(tc[1] == "onetwo"); + + // foreach over a range + struct R + { + auto a = [tuple(2L, '5'), tuple(4L, '6'), tuple(1L, '7')]; + auto front() => a[0]; + auto empty() => !a.length; + void popFront() { a = a[1..$]; } + } + Tuple!(long[], string) tr; + // specify element types + foreach ((long i, char ch); R()) + { + tr[0] ~= i; + tr[1] ~= ch; + } + assert(tr[0] == [2, 4, 1]); + assert(tr[1] == "567"); + + // unpack sequence + alias Seq(E...) = E; + const (c, d) = Seq!(false, byte(4)); + static assert(is(typeof(c) == const bool)); + static assert(is(typeof(d) == const byte)); + assert(!c); + assert(d == 4); +} From 3f61d130e6b3b2fe18ce058f23d0cbe2604310a8 Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Sun, 13 Apr 2025 20:26:11 +0100 Subject: [PATCH 09/35] [unpacking] Add `-preview=tuples` CLI switch --- compiler/src/dmd/cli.d | 3 +++ compiler/src/dmd/globals.d | 1 + compiler/src/dmd/parse.d | 21 ++++++++++++--------- compiler/test/compilable/previewhelp.d | 1 + compiler/test/runnable/unpacking.d | 4 ++++ 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/compiler/src/dmd/cli.d b/compiler/src/dmd/cli.d index 2f91cde23480..ef39034134f7 100644 --- a/compiler/src/dmd/cli.d +++ b/compiler/src/dmd/cli.d @@ -1100,6 +1100,9 @@ dmd -cov -unittest myprog.d Feature("safer", "safer", "more safety checks by default", "https://github.com/WalterBright/documents/blob/38f0a846726b571f8108f6e63e5e217b91421c86/safer.md", true, false), + Feature("tuples", "tuples", + "enable tuple unpacking", + "https://github.com/tgehr/DIPs/blob/tuple-syntax/DIPs/DIP1xxx-tg.md"), Feature("nosharedaccess", "noSharedAccess", "disable access to shared memory objects", "https://dlang.org/spec/const3.html#shared"), diff --git a/compiler/src/dmd/globals.d b/compiler/src/dmd/globals.d index 0c4f9eb5417c..23291053e41d 100644 --- a/compiler/src/dmd/globals.d +++ b/compiler/src/dmd/globals.d @@ -205,6 +205,7 @@ extern (C++) struct Param // Implementation: https://github.com/dlang/dmd/pull/9817 FeatureState safer; // safer by default (more @safe checks in unattributed code) // https://github.com/WalterBright/documents/blob/38f0a846726b571f8108f6e63e5e217b91421c86/safer.md + FeatureState tuples; // Tuple unpacking FeatureState noSharedAccess; // read/write access to shared memory objects bool previewIn; // `in` means `[ref] scope const`, accepts rvalues bool inclusiveInContracts; // 'in' contracts of overridden methods must be a superset of parent contract diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index 3ee14b140fc2..570f773871de 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -18,6 +18,7 @@ import core.stdc.string; import dmd.astenums; import dmd.errorsink; +import dmd.globals; import dmd.id; import dmd.identifier; import dmd.lexer; @@ -337,7 +338,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer auto next = peek(t); if (next.value != TOK.leftParenthesis) return false; - if (isTupleNotation(next)) + if (global.params.tuples && isTupleNotation(next)) return false; return true; } @@ -845,7 +846,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer { auto next = peek(&token); if (next.value != TOK.leftParenthesis || - peekPastParen(next).value == TOK.assign) + global.params.tuples && peekPastParen(next).value == TOK.assign) { stc = STC.extern_; goto Lstc; @@ -1097,7 +1098,8 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer continue; case TOK.leftParenthesis: - if (peekPastParen(&token).value == TOK.assign) // (confirm unpacking for better error messages) + // confirm unpacking for better error messages: + if (global.params.tuples && peekPastParen(&token).value == TOK.assign) goto Ldeclaration; goto default; @@ -1206,7 +1208,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer error("linkage specification not allowed within unpack declarations");+/ if (udas) // TODO error("user defined attributes not allowed within unpack declarations"); - if (token.value == TOK.leftParenthesis) + if (global.params.tuples && token.value == TOK.leftParenthesis) { vars.push(parseUnpackDeclaration(storage_class, false, isParameter)); } @@ -1293,7 +1295,8 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer { const loc = token.loc; AST.Dsymbol s; - if (token.value == TOK.leftParenthesis && peekPastParen(&token).value == TOK.assign) + if (global.params.tuples && token.value == TOK.leftParenthesis && + peekPastParen(&token).value == TOK.assign) { s = parseUnpackDeclaration(storageClass); } @@ -3226,7 +3229,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer if (tpl && !*tpl && hasAutoRefParam) *tpl = new AST.TemplateParameters(); - if (tpl && token.value == TOK.leftParenthesis) + if (global.params.tuples && tpl && token.value == TOK.leftParenthesis) { const tv2 = peekPastParen(&token).value; if (tv2 == TOK.comma || tv2 == TOK.rightParenthesis || tv2 == TOK.dotDotDot) @@ -4626,7 +4629,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer { auto next = peek(&token); if (next.value != TOK.leftParenthesis || - peekPastParen(next).value == TOK.assign) + global.params.tuples && peekPastParen(next).value == TOK.assign) { stc = STC.extern_; goto L1; @@ -5934,7 +5937,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer goto Larg; } } - else if (token.value == TOK.leftParenthesis) + else if (global.params.tuples && token.value == TOK.leftParenthesis) { TOK after = peekPastParen(&token).value; if (after == TOK.comma || after == TOK.semicolon) @@ -6642,7 +6645,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer case TOK.scope_: auto next = peek(&token); if (next.value != TOK.leftParenthesis || - peekPastParen(next).value == TOK.assign) + global.params.tuples && peekPastParen(next).value == TOK.assign) goto Ldeclaration; // scope used as storage class nextToken(); check(TOK.leftParenthesis); diff --git a/compiler/test/compilable/previewhelp.d b/compiler/test/compilable/previewhelp.d index 94a01ced14bf..339d5669bb5e 100644 --- a/compiler/test/compilable/previewhelp.d +++ b/compiler/test/compilable/previewhelp.d @@ -14,6 +14,7 @@ Upcoming language changes listed by -preview=name: =fixAliasThis when a symbol is resolved, check alias this scope before going to upper scopes (https://github.com/dlang/dmd/pull/8885) =rvaluerefparam enable rvalue arguments to ref parameters (https://gist.github.com/andralex/e5405a5d773f07f73196c05f8339435a) =safer more safety checks by default (https://github.com/WalterBright/documents/blob/38f0a846726b571f8108f6e63e5e217b91421c86/safer.md) + =tuples enable tuple unpacking (https://github.com/tgehr/DIPs/blob/tuple-syntax/DIPs/DIP1xxx-tg.md) =nosharedaccess disable access to shared memory objects (https://dlang.org/spec/const3.html#shared) =in `in` on parameters means `scope const [ref]` and accepts rvalues (https://dlang.org/spec/function.html#in-params) =inclusiveincontracts 'in' contracts of overridden methods must be a superset of parent contract (https://dlang.org/changelog/2.095.0.html#inclusive-incontracts) diff --git a/compiler/test/runnable/unpacking.d b/compiler/test/runnable/unpacking.d index 999c04fc8670..933c35d5ce7f 100644 --- a/compiler/test/runnable/unpacking.d +++ b/compiler/test/runnable/unpacking.d @@ -1,3 +1,7 @@ +/* +REQUIRED_ARGS: -preview=tuples +*/ + // test unpacking works with custom struct struct Tuple(T...) { From 7658534c1de6eff252063cbfa2aa994ae857cfb5 Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Mon, 14 Apr 2025 11:21:29 +0100 Subject: [PATCH 10/35] [unpacking] Tweak lone identifier error message, add test --- compiler/src/dmd/parse.d | 3 ++- compiler/test/fail_compilation/unpacking.d | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 compiler/test/fail_compilation/unpacking.d diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index 570f773871de..6151119df856 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -1244,7 +1244,8 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer } if (!t && storage_class == STC.none) { - error("unpacked variable needs at least one storage class, did you mean `auto %s`?", i.toChars()); + error("unpacked variable `%s` needs a type or at least one storage class, did you mean `auto %s`?", + i.toChars(), i.toChars()); } vars.push(new AST.VarDeclaration(loc, t, i, null, storage_class)); // TODO: UDAs } diff --git a/compiler/test/fail_compilation/unpacking.d b/compiler/test/fail_compilation/unpacking.d new file mode 100644 index 000000000000..db6133137e23 --- /dev/null +++ b/compiler/test/fail_compilation/unpacking.d @@ -0,0 +1,19 @@ +/* +REQUIRED_ARGS: -preview=tuples -vcolumns +TEST_OUTPUT: +--- +fail_compilation/unpacking.d(16,14): Error: unpacked variable `b` needs a type or at least one storage class, did you mean `auto b`? +fail_compilation/unpacking.d(17,15): Error: unpacked variable `b` needs a type or at least one storage class, did you mean `auto b`? +fail_compilation/unpacking.d(17,18): Error: unpacked variable `c` needs a type or at least one storage class, did you mean `auto c`? +fail_compilation/unpacking.d(18,23): Error: unpacked variable `c` needs a type or at least one storage class, did you mean `auto c`? +--- +*/ + +import std.typecons : tuple; + +void main() +{ + (int a, b) = tuple(1, "2"); // error + (int a, (b, c)) = tuple(1, tuple("2", 3.0)); // error + (int a, (auto b, c)) = tuple(1, tuple("2", 3.0)); // error +} From 96dad6061b2e7a36366cd5ce92ff887b9cbf27b6 Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Mon, 14 Apr 2025 20:33:23 +0100 Subject: [PATCH 11/35] [unpacking] Fix parsing malformed auto declarations starting with `(` (#7) * Fix parsing tuple notation without `-preview=tuples` After #5, without `-preview=tuples`, the parser would cause an assert error when finding tokens that satisfy `isTupleNotation`. * Fix parsing malformed auto declaration starting with `(` Change preview check to an assert after last commit. Handle a tuple declaration that doesn't have `=` after the last `)`. The `peekPastParen(&token).value == TOK.assign` check is not needed because the `parseInitializer` parameter is true so `check(TOK.assign)` does the job and provides a good error message if missing. Add test for malformed auto and non-auto tuple declarations. * Remove unnecessary preview check --- compiler/src/dmd/parse.d | 16 +++++++++------- compiler/test/fail_compilation/unpacking.d | 14 ++++++++++---- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index 6151119df856..e4bc47d74ade 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -1208,8 +1208,9 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer error("linkage specification not allowed within unpack declarations");+/ if (udas) // TODO error("user defined attributes not allowed within unpack declarations"); - if (global.params.tuples && token.value == TOK.leftParenthesis) + if (token.value == TOK.leftParenthesis) { + // recurse vars.push(parseUnpackDeclaration(storage_class, false, isParameter)); } else @@ -1274,7 +1275,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer AST.Expression _init = null; if (parseInitializer) { - check(TOK.assign); + check(TOK.assign, "unpack declaration"); _init = parseAssignExp(); } return new AST.UnpackDeclaration(unpackLoc, vars, _init, g_storage_class); @@ -1284,7 +1285,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer * Parse auto declarations of the form: * storageClass ident = init, ident = init, ... ; * and return the array of them. - * Starts with token on the first ident or '('. + * Starts with token on the first ident, or '(' with -preview=tuples. * Ends with scanner past closing ';' */ private AST.Dsymbols* parseAutoDeclarations(STC storageClass, const(char)* comment) @@ -1296,10 +1297,10 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer { const loc = token.loc; AST.Dsymbol s; - if (global.params.tuples && token.value == TOK.leftParenthesis && - peekPastParen(&token).value == TOK.assign) + if (token.value == TOK.leftParenthesis) { - s = parseUnpackDeclaration(storageClass); + assert(global.params.tuples); + s = parseUnpackDeclaration(storageClass, true); } else { @@ -4791,7 +4792,8 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer * (int x, auto y) = initializer; * storage_class (a, b, ...) = initializer; */ - if (token.value == TOK.leftParenthesis && isTupleNotation(&token)) + if (global.params.tuples && token.value == TOK.leftParenthesis && + isTupleNotation(&token)) { // TODO: can we merge this with the branch below? AST.Dsymbols* a = parseAutoDeclarations(storage_class | (pAttrs ? pAttrs.storageClass : STC.none), comment); diff --git a/compiler/test/fail_compilation/unpacking.d b/compiler/test/fail_compilation/unpacking.d index db6133137e23..d207bd515361 100644 --- a/compiler/test/fail_compilation/unpacking.d +++ b/compiler/test/fail_compilation/unpacking.d @@ -2,10 +2,13 @@ REQUIRED_ARGS: -preview=tuples -vcolumns TEST_OUTPUT: --- -fail_compilation/unpacking.d(16,14): Error: unpacked variable `b` needs a type or at least one storage class, did you mean `auto b`? -fail_compilation/unpacking.d(17,15): Error: unpacked variable `b` needs a type or at least one storage class, did you mean `auto b`? -fail_compilation/unpacking.d(17,18): Error: unpacked variable `c` needs a type or at least one storage class, did you mean `auto c`? -fail_compilation/unpacking.d(18,23): Error: unpacked variable `c` needs a type or at least one storage class, did you mean `auto c`? +fail_compilation/unpacking.d(19,14): Error: unpacked variable `b` needs a type or at least one storage class, did you mean `auto b`? +fail_compilation/unpacking.d(20,15): Error: unpacked variable `b` needs a type or at least one storage class, did you mean `auto b`? +fail_compilation/unpacking.d(20,18): Error: unpacked variable `c` needs a type or at least one storage class, did you mean `auto c`? +fail_compilation/unpacking.d(21,23): Error: unpacked variable `c` needs a type or at least one storage class, did you mean `auto c`? +fail_compilation/unpacking.d(23,16): Error: found `,` when expecting `=` following unpack declaration +fail_compilation/unpacking.d(24,10): Error: unexpected identifier `a` in declarator +fail_compilation/unpacking.d(24,17): Error: unexpected identifier `b` in declarator --- */ @@ -16,4 +19,7 @@ void main() (int a, b) = tuple(1, "2"); // error (int a, (b, c)) = tuple(1, tuple("2", 3.0)); // error (int a, (auto b, c)) = tuple(1, tuple("2", 3.0)); // error + + auto (a, b), c = t; // error + (int a, int b), c = t; // error } From aa3c1941579d11c90cf94af5302df9722a7ad65c Mon Sep 17 00:00:00 2001 From: Timon Gehr Date: Mon, 14 Apr 2025 22:50:33 +0200 Subject: [PATCH 12/35] Test extern/scope unpacking. --- compiler/test/fail_compilation/unpacking.d | 21 ++++++++++++------- .../test/fail_compilation/unpacking_extern.d | 20 ++++++++++++++++++ compiler/test/runnable/unpacking.d | 4 ++++ 3 files changed, 37 insertions(+), 8 deletions(-) create mode 100644 compiler/test/fail_compilation/unpacking_extern.d diff --git a/compiler/test/fail_compilation/unpacking.d b/compiler/test/fail_compilation/unpacking.d index d207bd515361..9b727811ee8e 100644 --- a/compiler/test/fail_compilation/unpacking.d +++ b/compiler/test/fail_compilation/unpacking.d @@ -2,17 +2,22 @@ REQUIRED_ARGS: -preview=tuples -vcolumns TEST_OUTPUT: --- -fail_compilation/unpacking.d(19,14): Error: unpacked variable `b` needs a type or at least one storage class, did you mean `auto b`? -fail_compilation/unpacking.d(20,15): Error: unpacked variable `b` needs a type or at least one storage class, did you mean `auto b`? -fail_compilation/unpacking.d(20,18): Error: unpacked variable `c` needs a type or at least one storage class, did you mean `auto c`? -fail_compilation/unpacking.d(21,23): Error: unpacked variable `c` needs a type or at least one storage class, did you mean `auto c`? -fail_compilation/unpacking.d(23,16): Error: found `,` when expecting `=` following unpack declaration -fail_compilation/unpacking.d(24,10): Error: unexpected identifier `a` in declarator -fail_compilation/unpacking.d(24,17): Error: unexpected identifier `b` in declarator +fail_compilation/unpacking.d(24,14): Error: unpacked variable `b` needs a type or at least one storage class, did you mean `auto b`? +fail_compilation/unpacking.d(25,15): Error: unpacked variable `b` needs a type or at least one storage class, did you mean `auto b`? +fail_compilation/unpacking.d(25,18): Error: unpacked variable `c` needs a type or at least one storage class, did you mean `auto c`? +fail_compilation/unpacking.d(26,23): Error: unpacked variable `c` needs a type or at least one storage class, did you mean `auto c`? +fail_compilation/unpacking.d(28,16): Error: found `,` when expecting `=` following unpack declaration +fail_compilation/unpacking.d(29,10): Error: unexpected identifier `a` in declarator +fail_compilation/unpacking.d(29,17): Error: unexpected identifier `b` in declarator --- */ -import std.typecons : tuple; +struct Tuple(T...) +{ + T expand; + alias this = expand; +} +auto tuple(T...)(T args) => Tuple!T(args); void main() { diff --git a/compiler/test/fail_compilation/unpacking_extern.d b/compiler/test/fail_compilation/unpacking_extern.d new file mode 100644 index 000000000000..43f30150064d --- /dev/null +++ b/compiler/test/fail_compilation/unpacking_extern.d @@ -0,0 +1,20 @@ +/* +REQUIRED_ARGS: -preview=tuples -vcolumns +TEST_OUTPUT: +--- +fail_compilation/unpacking_extern.d(19,9): Error: variable `unpacking_extern.a` extern symbols cannot have initializers +fail_compilation/unpacking_extern.d(19,12): Error: variable `unpacking_extern.b` extern symbols cannot have initializers +fail_compilation/unpacking_extern.d(20,14): Error: variable `unpacking_extern.c` extern symbols cannot have initializers +fail_compilation/unpacking_extern.d(20,17): Error: variable `unpacking_extern.d` extern symbols cannot have initializers +--- +*/ + +struct Tuple(T...) +{ + T expand; + alias this = expand; +} +auto tuple(T...)(T args) => Tuple!T(args); + +extern (a, b) = tuple(1,2); // error +auto extern (c, d) = tuple(1,2); // error diff --git a/compiler/test/runnable/unpacking.d b/compiler/test/runnable/unpacking.d index 933c35d5ce7f..64a1f83d2208 100644 --- a/compiler/test/runnable/unpacking.d +++ b/compiler/test/runnable/unpacking.d @@ -24,6 +24,10 @@ void main() assert(a == "four"); assert(b == 5); + scope (u, v) = tuple(1, 2); + assert(u == 1); + assert(v == 2); + // foreach unpacking auto tc = tuple(0, ""); foreach ((i, s); [tuple(1, "one"), tuple(2, "two")]) From dd182319f904e04f48de3531c152910bfa9732a2 Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Mon, 14 Apr 2025 23:05:45 +0100 Subject: [PATCH 13/35] [unpacking] Add test for missing identifier in unpack declaration (#9) --- compiler/src/dmd/parse.d | 3 ++- compiler/test/fail_compilation/unpacking.d | 17 ++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index e4bc47d74ade..3be6be554740 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -1233,7 +1233,8 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer if (token.value != TOK.identifier) { - error("expected identifier in unpack declaration"); + error("expected identifier after type `%s` in unpack declaration", + t.toChars()); break; } i = token.ident; diff --git a/compiler/test/fail_compilation/unpacking.d b/compiler/test/fail_compilation/unpacking.d index 9b727811ee8e..4a898dde807c 100644 --- a/compiler/test/fail_compilation/unpacking.d +++ b/compiler/test/fail_compilation/unpacking.d @@ -2,13 +2,14 @@ REQUIRED_ARGS: -preview=tuples -vcolumns TEST_OUTPUT: --- -fail_compilation/unpacking.d(24,14): Error: unpacked variable `b` needs a type or at least one storage class, did you mean `auto b`? -fail_compilation/unpacking.d(25,15): Error: unpacked variable `b` needs a type or at least one storage class, did you mean `auto b`? -fail_compilation/unpacking.d(25,18): Error: unpacked variable `c` needs a type or at least one storage class, did you mean `auto c`? -fail_compilation/unpacking.d(26,23): Error: unpacked variable `c` needs a type or at least one storage class, did you mean `auto c`? -fail_compilation/unpacking.d(28,16): Error: found `,` when expecting `=` following unpack declaration -fail_compilation/unpacking.d(29,10): Error: unexpected identifier `a` in declarator -fail_compilation/unpacking.d(29,17): Error: unexpected identifier `b` in declarator +fail_compilation/unpacking.d(25,14): Error: unpacked variable `b` needs a type or at least one storage class, did you mean `auto b`? +fail_compilation/unpacking.d(26,15): Error: unpacked variable `b` needs a type or at least one storage class, did you mean `auto b`? +fail_compilation/unpacking.d(26,18): Error: unpacked variable `c` needs a type or at least one storage class, did you mean `auto c`? +fail_compilation/unpacking.d(27,23): Error: unpacked variable `c` needs a type or at least one storage class, did you mean `auto c`? +fail_compilation/unpacking.d(29,16): Error: found `,` when expecting `=` following unpack declaration +fail_compilation/unpacking.d(30,10): Error: unexpected identifier `a` in declarator +fail_compilation/unpacking.d(30,17): Error: unexpected identifier `b` in declarator +fail_compilation/unpacking.d(32,17): Error: expected identifier after type `int` in unpack declaration --- */ @@ -27,4 +28,6 @@ void main() auto (a, b), c = t; // error (int a, int b), c = t; // error + + (char a, int) = t; // error } From 10365396852b53d2956935c00f9d85f161e75aa3 Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Tue, 15 Apr 2025 00:22:59 +0100 Subject: [PATCH 14/35] [unpacking] Test semantic errors, tweak RHS mismatch error (#10) --- compiler/src/dmd/attrib.d | 6 ++-- .../test/fail_compilation/unpack_semantic.d | 28 +++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 compiler/test/fail_compilation/unpack_semantic.d diff --git a/compiler/src/dmd/attrib.d b/compiler/src/dmd/attrib.d index 1beb7d359d5c..2bf97468403b 100644 --- a/compiler/src/dmd/attrib.d +++ b/compiler/src/dmd/attrib.d @@ -849,9 +849,8 @@ extern (C++) final class UnpackDeclaration : AttribDeclaration return fail(); } - import dmd.mtype: TypeTuple; - TypeTuple typ = null; TupleExp tup = null; + auto tinit = _init.type; import dmd.tokens: EXP; if (_init.type.ty == Ttuple && _init.op == EXP.tuple) @@ -871,7 +870,8 @@ extern (C++) final class UnpackDeclaration : AttribDeclaration static import dmd.errors; if (!tup) { - dmd.errors.error(loc, "right hand side of unpack declaration must be a tuple or expression sequence"); + dmd.errors.error(loc, "right hand side of unpack declaration must resolve to a tuple or expression sequence, not `%s`", + tinit.toChars()); return fail(); } if (decl.length != tup.exps.length) diff --git a/compiler/test/fail_compilation/unpack_semantic.d b/compiler/test/fail_compilation/unpack_semantic.d new file mode 100644 index 000000000000..2c5e382c6f94 --- /dev/null +++ b/compiler/test/fail_compilation/unpack_semantic.d @@ -0,0 +1,28 @@ +/* +REQUIRED_ARGS: -preview=tuples +TEST_OUTPUT: +--- +fail_compilation/unpack_semantic.d(18): Error: right hand side of unpack declaration must resolve to a tuple or expression sequence, not `int[]` +fail_compilation/unpack_semantic.d(19): Error: incompatible number of components for unpack declaration (`2` vs. `3`) +fail_compilation/unpack_semantic.d(22): Error: cannot specify `static` for individual components of an unpack declaration +fail_compilation/unpack_semantic.d(23): Error: cannot specify `enum` for individual components of an unpack declaration +fail_compilation/unpack_semantic.d(26): Error: cannot implicitly convert expression `3.0F` of type `float` to `int` +fail_compilation/unpack_semantic.d(27): Error: cannot implicitly convert expression `7` of type `int` to `void*` +--- +*/ + +alias Seq(A...) = A; + +void main() +{ + auto (a, b) = [1, 2]; + (int c, int d) = Seq!(3, 4, 5); + + // check individual storage classes + (auto g, static h) = Seq!(8, 9); + (int i, enum j) = Seq!(10, 11); + + // element type error + (int p,) = Seq!3F; + (int q, void* r) = Seq!(6, 7); +} From 1e531e374af40fdad9c03a25273c12890e8dece6 Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Fri, 18 Apr 2025 03:53:22 +0100 Subject: [PATCH 15/35] [unpacking] Add tests for invalid parameter storage classes (#11) Add error for `out` parameters. Ignore `auto ref` after parameter error to avoid unpack error. --- compiler/src/dmd/parse.d | 6 ++++- compiler/test/fail_compilation/unpacking.d | 26 ++++++++++++++-------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index 3be6be554740..e1c630443666 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -3246,7 +3246,11 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer { error("unpacking `auto ref` parameters is not supported"); } - unpack = parseUnpackDeclaration(storageClass & ~STC.lazy_ & ~STC.out_ | STC.temp | STC.ctfe, false, true); + if (storageClass & STC.out_) + { + error("unpacking `out` parameters is not supported"); + } + unpack = parseUnpackDeclaration(storageClass & ~STC.lazy_ & ~STC.autoref & ~STC.out_ | STC.temp | STC.ctfe, false, true); ai = Identifier.generateId("__unpack"); goto LskipType; } diff --git a/compiler/test/fail_compilation/unpacking.d b/compiler/test/fail_compilation/unpacking.d index 4a898dde807c..08a3fb83a5b8 100644 --- a/compiler/test/fail_compilation/unpacking.d +++ b/compiler/test/fail_compilation/unpacking.d @@ -2,14 +2,18 @@ REQUIRED_ARGS: -preview=tuples -vcolumns TEST_OUTPUT: --- -fail_compilation/unpacking.d(25,14): Error: unpacked variable `b` needs a type or at least one storage class, did you mean `auto b`? -fail_compilation/unpacking.d(26,15): Error: unpacked variable `b` needs a type or at least one storage class, did you mean `auto b`? -fail_compilation/unpacking.d(26,18): Error: unpacked variable `c` needs a type or at least one storage class, did you mean `auto c`? -fail_compilation/unpacking.d(27,23): Error: unpacked variable `c` needs a type or at least one storage class, did you mean `auto c`? -fail_compilation/unpacking.d(29,16): Error: found `,` when expecting `=` following unpack declaration -fail_compilation/unpacking.d(30,10): Error: unexpected identifier `a` in declarator -fail_compilation/unpacking.d(30,17): Error: unexpected identifier `b` in declarator -fail_compilation/unpacking.d(32,17): Error: expected identifier after type `int` in unpack declaration +fail_compilation/unpacking.d(29,14): Error: unpacked variable `b` needs a type or at least one storage class, did you mean `auto b`? +fail_compilation/unpacking.d(30,15): Error: unpacked variable `b` needs a type or at least one storage class, did you mean `auto b`? +fail_compilation/unpacking.d(30,18): Error: unpacked variable `c` needs a type or at least one storage class, did you mean `auto c`? +fail_compilation/unpacking.d(31,23): Error: unpacked variable `c` needs a type or at least one storage class, did you mean `auto c`? +fail_compilation/unpacking.d(33,16): Error: found `,` when expecting `=` following unpack declaration +fail_compilation/unpacking.d(34,10): Error: unexpected identifier `a` in declarator +fail_compilation/unpacking.d(34,17): Error: unexpected identifier `b` in declarator +fail_compilation/unpacking.d(36,17): Error: expected identifier after type `int` in unpack declaration +fail_compilation/unpacking.d(38,16): Error: `auto ref` unpacked variables are not supported +fail_compilation/unpacking.d(39,25): Error: unpacking `auto ref` parameters is not supported +fail_compilation/unpacking.d(40,21): Error: unpacking `lazy` parameters is not supported +fail_compilation/unpacking.d(40,34): Error: unpacking `out` parameters is not supported --- */ @@ -20,7 +24,7 @@ struct Tuple(T...) } auto tuple(T...)(T args) => Tuple!T(args); -void main() +void main() // check parse errors { (int a, b) = tuple(1, "2"); // error (int a, (b, c)) = tuple(1, tuple("2", 3.0)); // error @@ -30,4 +34,8 @@ void main() (int a, int b), c = t; // error (char a, int) = t; // error + + (auto ref a,) = t; // error + alias a = (auto ref (x,)) {}; // error + alias a = (lazy (x,), y, out (z,)) {}; // error } From 2085a6322d8b134eeeb83ea6938214930e372e13 Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Fri, 25 Apr 2025 22:57:06 +0100 Subject: [PATCH 16/35] [unpacking] Support `auto (a, int b) =` (#13) Avoid lowering to `auto Type x =`, which causes a redundant `auto` error. --- compiler/src/dmd/attrib.d | 3 +++ compiler/src/dmd/parse.d | 3 +++ compiler/test/runnable/unpacking.d | 11 +++++++++++ 3 files changed, 17 insertions(+) diff --git a/compiler/src/dmd/attrib.d b/compiler/src/dmd/attrib.d index 2bf97468403b..231d22f59a72 100644 --- a/compiler/src/dmd/attrib.d +++ b/compiler/src/dmd/attrib.d @@ -783,6 +783,9 @@ extern (C++) final class UnpackDeclaration : AttribDeclaration { vd.storage_class |= declared_storage_class; d_storage_class = vd.storage_class; + // allow `auto (a, int b) =` + if (vd.type) + vd.storage_class &= ~STC.auto_; } else if (auto up = d.isUnpackDeclaration()) { diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index e1c630443666..c65731aa59f5 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -1230,6 +1230,9 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer if (t == AST.Type.terror) break; + // specifying type overrides outer `auto` + if (g_storage_class & STC.auto_) + storage_class &= ~STC.auto_; if (token.value != TOK.identifier) { diff --git a/compiler/test/runnable/unpacking.d b/compiler/test/runnable/unpacking.d index 64a1f83d2208..a6eb617fdef9 100644 --- a/compiler/test/runnable/unpacking.d +++ b/compiler/test/runnable/unpacking.d @@ -24,6 +24,17 @@ void main() assert(a == "four"); assert(b == 5); + { + // mix outer storage & pattern component with type + auto ((s, int i), c) = tuple(tuple("hi", 5), 'c'); + static assert(is(typeof(s) == string)); + static assert(is(typeof(i) == int)); + static assert(is(typeof(c) == char)); + assert(s == "hi"); + assert(i == 5); + assert(c == 'c'); + } + scope (u, v) = tuple(1, 2); assert(u == 1); assert(v == 2); From 4ae6e1fff578d6feef4202ac0e1421caad823930 Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Thu, 15 May 2025 17:40:31 +0100 Subject: [PATCH 17/35] [unpacking] Error when unpacking declaration itself has a UDA --- compiler/src/dmd/attrib.d | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/compiler/src/dmd/attrib.d b/compiler/src/dmd/attrib.d index 231d22f59a72..cf9f32811582 100644 --- a/compiler/src/dmd/attrib.d +++ b/compiler/src/dmd/attrib.d @@ -829,6 +829,12 @@ extern (C++) final class UnpackDeclaration : AttribDeclaration decl = null; lowered = true; } + static import dmd.errors; + if (auto uda = userAttribDecl) + { + dmd.errors.error(loc, "user defined attributes are not supported yet on unpack declarations"); + return fail(); + } import dmd.expressionsem; bool needctfe = (storage_class & (STC.manifest | STC.static_)) != 0; @@ -870,7 +876,6 @@ extern (C++) final class UnpackDeclaration : AttribDeclaration } } - static import dmd.errors; if (!tup) { dmd.errors.error(loc, "right hand side of unpack declaration must resolve to a tuple or expression sequence, not `%s`", From 8fce78f202f25e57d726a1a49cf88eead7ba6044 Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Thu, 15 May 2025 17:41:27 +0100 Subject: [PATCH 18/35] Tweak unpack declaration LOC This uses the opening `(` position rather than the first component's. --- compiler/src/dmd/parse.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index c65731aa59f5..8f0a7e79cb11 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -1189,8 +1189,8 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer } do { - nextToken(); const unpackLoc = token.loc; + nextToken(); bool hasComma = false; auto vars = new AST.Dsymbols(); while (token.value != TOK.rightParenthesis) From ead2762c7a555f53cd9bc54b833ff6b496763ce1 Mon Sep 17 00:00:00 2001 From: MetaLang Date: Sat, 20 Jun 2026 00:25:12 -0600 Subject: [PATCH 19/35] Fix ICE in tuple unpack declaration with moved value --- compiler/src/dmd/glue/e2ir.d | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/src/dmd/glue/e2ir.d b/compiler/src/dmd/glue/e2ir.d index 2939c47e4644..13dc76af3af3 100644 --- a/compiler/src/dmd/glue/e2ir.d +++ b/compiler/src/dmd/glue/e2ir.d @@ -1015,7 +1015,10 @@ elem* toElem(Expression e, ref IRState irs) elem* visitDeclaration(DeclarationExp de) { //printf("DeclarationExp.toElem() %s\n", de.toChars()); - return Dsymbol_toElem(de.declaration, irs); + elem* e = Dsymbol_toElem(de.declaration, irs); + if (e && de.type && de.type.toBasetype().ty == Tvoid) + e.Ety = TYvoid; + return e; } /*************************************** From b0d80cf1b04ef837fc565e376f944f9e65815fe7 Mon Sep 17 00:00:00 2001 From: Jared Hanson Date: Wed, 24 Jun 2026 22:13:13 -0600 Subject: [PATCH 20/35] Add changelog entry for new tuple unpacking feature --- changelog/dmd.tuple-unpacking.dd | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 changelog/dmd.tuple-unpacking.dd diff --git a/changelog/dmd.tuple-unpacking.dd b/changelog/dmd.tuple-unpacking.dd new file mode 100644 index 000000000000..357b1d46c53b --- /dev/null +++ b/changelog/dmd.tuple-unpacking.dd @@ -0,0 +1,29 @@ +Tuple unpacking is now supported! + +D now supports tuple unpacking for variable declarations and `foreach` loops. +This feature is available as a preview and can be enabled with the `-preview=tuples` compiler switch. +It allows extracting multiple values from a tuple or compile-time sequence directly into distinct variables. + +------- +// Requires -preview=tuples +import std.typecons : tuple; + +void main() +{ + auto (x, y, z) = tuple(1, 2.5, "three"); + assert(x == 1); + assert(y == 2.5); + assert(z == "three"); + + // Types can be specified explicitly + (int a, string b) = tuple(4, "five"); + + // Unpacking works in foreach loops + foreach ((i, s); [tuple(1, "one"), tuple(2, "two")]) + { + // ... + } +} +------- + +For more information, see the D blog article: $(LINK2 https://blog.dlang.org/2026/04/12/dip-1053-a-tale-of-tuples/, DIP 1053 - A Tale of Tuples). From ff94a51d1f73310a98bb0f64b778d17a51f49ee6 Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Thu, 25 Jun 2026 22:41:56 +0100 Subject: [PATCH 21/35] Disallow `(T x,) = tup, i = 1;` Require semi-colon after TuplePattern = Initializer. Although we could allow a list of multiple tuple pattern/init pairs, let's simplify the grammar to: ``` VarDeclarations: ... TuplePattern = Initializer; TuplePattern: `(` TuplePatternComponents `)` ``` Note AutoDeclaration will still support commas, e.g. `auto (x,) = tup, i = 1;`. --- compiler/src/dmd/parse.d | 5 ++++ compiler/test/fail_compilation/unpacking.d | 35 ++++++++++++---------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index 8f0a7e79cb11..347d40208ef5 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -1305,6 +1305,11 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer { assert(global.params.tuples); s = parseUnpackDeclaration(storageClass, true); + if (!storageClass && token.value == TOK.comma) + { + // prevent `(T x,) = tup, i = 1;` + error("`;` expected after tuple pattern, not `,`"); + } } else { diff --git a/compiler/test/fail_compilation/unpacking.d b/compiler/test/fail_compilation/unpacking.d index 08a3fb83a5b8..006d1e426cb3 100644 --- a/compiler/test/fail_compilation/unpacking.d +++ b/compiler/test/fail_compilation/unpacking.d @@ -2,18 +2,19 @@ REQUIRED_ARGS: -preview=tuples -vcolumns TEST_OUTPUT: --- -fail_compilation/unpacking.d(29,14): Error: unpacked variable `b` needs a type or at least one storage class, did you mean `auto b`? -fail_compilation/unpacking.d(30,15): Error: unpacked variable `b` needs a type or at least one storage class, did you mean `auto b`? -fail_compilation/unpacking.d(30,18): Error: unpacked variable `c` needs a type or at least one storage class, did you mean `auto c`? -fail_compilation/unpacking.d(31,23): Error: unpacked variable `c` needs a type or at least one storage class, did you mean `auto c`? -fail_compilation/unpacking.d(33,16): Error: found `,` when expecting `=` following unpack declaration -fail_compilation/unpacking.d(34,10): Error: unexpected identifier `a` in declarator -fail_compilation/unpacking.d(34,17): Error: unexpected identifier `b` in declarator -fail_compilation/unpacking.d(36,17): Error: expected identifier after type `int` in unpack declaration -fail_compilation/unpacking.d(38,16): Error: `auto ref` unpacked variables are not supported -fail_compilation/unpacking.d(39,25): Error: unpacking `auto ref` parameters is not supported -fail_compilation/unpacking.d(40,21): Error: unpacking `lazy` parameters is not supported -fail_compilation/unpacking.d(40,34): Error: unpacking `out` parameters is not supported +fail_compilation/unpacking.d(30,14): Error: unpacked variable `b` needs a type or at least one storage class, did you mean `auto b`? +fail_compilation/unpacking.d(31,15): Error: unpacked variable `b` needs a type or at least one storage class, did you mean `auto b`? +fail_compilation/unpacking.d(31,18): Error: unpacked variable `c` needs a type or at least one storage class, did you mean `auto c`? +fail_compilation/unpacking.d(32,23): Error: unpacked variable `c` needs a type or at least one storage class, did you mean `auto c`? +fail_compilation/unpacking.d(34,16): Error: found `,` when expecting `=` following unpack declaration +fail_compilation/unpacking.d(35,10): Error: unexpected identifier `a` in declarator +fail_compilation/unpacking.d(35,17): Error: unexpected identifier `b` in declarator +fail_compilation/unpacking.d(37,17): Error: expected identifier after type `int` in unpack declaration +fail_compilation/unpacking.d(39,16): Error: `auto ref` unpacked variables are not supported +fail_compilation/unpacking.d(40,25): Error: unpacking `auto ref` parameters is not supported +fail_compilation/unpacking.d(41,21): Error: unpacking `lazy` parameters is not supported +fail_compilation/unpacking.d(41,34): Error: unpacking `out` parameters is not supported +fail_compilation/unpacking.d(43,23): Error: `;` expected after tuple pattern, not `,` --- */ @@ -26,16 +27,18 @@ auto tuple(T...)(T args) => Tuple!T(args); void main() // check parse errors { - (int a, b) = tuple(1, "2"); // error + (int a, b) = tuple(1, "2"); // error, lone b (int a, (b, c)) = tuple(1, tuple("2", 3.0)); // error (int a, (auto b, c)) = tuple(1, tuple("2", 3.0)); // error - auto (a, b), c = t; // error + auto (a, b), c = t; // error, pattern missing init (int a, int b), c = t; // error - (char a, int) = t; // error + (char a, int) = t; // error, missing ident - (auto ref a,) = t; // error + (auto ref a,) = t; // error, illegal storage alias a = (auto ref (x,)) {}; // error alias a = (lazy (x,), y, out (z,)) {}; // error + + (int a, int b) = t, c = 1; // error, comma requires storage class } From 9f385a42d0acc18d9fb636976476b28528fa376e Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Wed, 24 Jun 2026 16:17:59 +0100 Subject: [PATCH 22/35] [spec/declaration] Unpacking declarations Grammar changes since approved DIP: AutoDeclaration allows interleaved tuple and non-tuple declarators. VarDeclarations now only takes one TuplePattern. Also change Initializer to AssignExpression (`= void` is not allowed). --- spec/declaration.dd | 81 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/spec/declaration.dd b/spec/declaration.dd index 3ee95fe5c64e..2354c0de2de7 100644 --- a/spec/declaration.dd +++ b/spec/declaration.dd @@ -41,6 +41,7 @@ $(GRAMMAR $(GNAME VarDeclarations): $(GLINK StorageClasses)$(OPT) $(GLINK2 type, BasicType) $(GLINK2 type, TypeSuffixes)$(OPT) $(GLINK IdentifierInitializers) $(D ;) $(GLINK AutoDeclaration) + $(GLINK TuplePattern) `=` $(GLINK AssignExpression) $(GNAME IdentifierInitializers): $(LEGACY_LNAME2 Declarators, DeclaratorIdentifierList) $(GLINK IdentifierInitializer) @@ -67,6 +68,8 @@ $(GNAME Declarator): $(LEGACY_LNAME2 VarDeclarator) $(DDSUBLINK spec/struct, bitfields, bit field). * When the *Identifier* has *TemplateParameters*, it is a $(DDSUBLINK spec/template, variable-template, variable template). + * When there's a *TuplePattern*, the *AssignExpression* + $(RELATIVE_LINK2 unpacking-declarations, is unpacked) into component declarations. $(P See also: $(RELATIVE_LINK2 declaration_syntax, Declaration Syntax).) @@ -291,6 +294,7 @@ $(GNAME AutoAssignments): $(GNAME AutoAssignment): $(GLINK_LEX Identifier) $(GLINK2 template, TemplateParameters)$(OPT) $(D =) $(GLINK Initializer) + $(GLINK TuplePattern) `=` $(GLINK AssignExpression) ) $(P If a declaration starts with a $(I StorageClass) and has @@ -323,6 +327,83 @@ auto c = new C(); // c is a handle to an instance of class C auto v = ["resistance", "is", "useless"]; // type is string[], not string[3] --- +$(H3 $(LNAME2 unpacking-declarations, Unpacking Declarations)) + + $(P $(GLINK VarDeclarations) and $(GLINK AutoDeclaration) support initializing + one or more variables in a *tuple pattern* from an $(GLINK AssignExpression). + The *AssignExpression* must implicitly convert to a + $(DDSUBLINK spec/template, homogeneous_sequences, value sequence).) + +$(GRAMMAR +$(GNAME TuplePattern): + `(` $(GLINK TuplePatternComponents) `)` + +$(GNAME TuplePatternComponents): + $(GLINK TuplePatternComponent) `,` + $(GLINK TuplePatternComponent) `,` $(GLINK TuplePatternComponent) + $(GLINK TuplePatternComponent) `,` $(GLINK TuplePatternComponents) + +$(GNAME TuplePatternComponent): + $(GLINK StorageClasses)$(OPT) $(GLINK Identifier) + $(GLINK StorageClasses)$(OPT) $(GLINK BasicType) $(GLINK TypeSuffixes)$(OPT) $(GLINK Identifier) + $(GLINK StorageClasses)$(OPT) $(GLINK TuplePattern) +) + + - Each component of the tuple pattern corresponds to an element of the value sequence. + - Components are separated by commas. + - For a pattern with a single component, a comma must precede the closing parenthesis. + +```d +import std.typecons : tuple; + +(int a, auto b) = tuple(1, "2"); // unpacking declaration +assert(a == 1); +assert(b == "2"); +``` + $(P The unpacking declaration above is lowered to e.g.:) +```d +auto temp = tuple(1, "2"); +int a = temp[0]; +auto b = temp[1]; +``` + $(P **See also:** $(REF Tuple, std,typecons).) + + $(P When the tuple pattern has no storage class, each identifier + must have a type or a component storage class:) +```d +(int a, b) = tuple(1, "2"); // Error: `b` has no type or storage class +``` + $(P When there is a storage class for a pattern, each component of the pattern can + be just an identifier. Each variable's type will be inferred from the corresponding + sequence element:) + +```d +import std.typecons : tuple; + +auto (a, b) = tuple(1, "2"); +static assert(is(typeof(a) == int)); +static assert(is(typeof(b) == string)); + +auto (a, immutable b, c) = tuple(1, "2", 3.0); +static assert(is(typeof(a) == int)); +static assert(is(typeof(b) == immutable string)); +static assert(is(typeof(c) == double)); +``` + + $(P Patterns can be nested:) + +```d +auto t = tuple(1, tuple("2", 3.0)); +// the following are equivalent: +auto (a, (b, c)) = t; +(int a, (string b, double c)) = t; +(int a, auto (b, c)) = t; +(int a, (auto b, auto c)) = t; +``` + + $(P `static` or `enum` can be applied to the whole declaration, but not to individual components.) + + $(H3 $(LNAME2 global_static_init, Global and Static Initializers)) $(P The $(GLINK Initializer) for a global or static variable must be From 70d881572ccf648ba3f56d9d841c77e6c4a4021a Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Fri, 26 Jun 2026 22:20:09 +0100 Subject: [PATCH 23/35] Change markdown examples to ddoc style `---` for spec consistency --- spec/declaration.dd | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/spec/declaration.dd b/spec/declaration.dd index 2354c0de2de7..7e061517779b 100644 --- a/spec/declaration.dd +++ b/spec/declaration.dd @@ -353,31 +353,31 @@ $(GNAME TuplePatternComponent): - Components are separated by commas. - For a pattern with a single component, a comma must precede the closing parenthesis. -```d +--- import std.typecons : tuple; (int a, auto b) = tuple(1, "2"); // unpacking declaration assert(a == 1); assert(b == "2"); -``` +--- $(P The unpacking declaration above is lowered to e.g.:) -```d +--- auto temp = tuple(1, "2"); int a = temp[0]; auto b = temp[1]; -``` +--- $(P **See also:** $(REF Tuple, std,typecons).) $(P When the tuple pattern has no storage class, each identifier - must have a type or a component storage class:) -```d + must either have a type or a storage class applying to it:) +--- (int a, b) = tuple(1, "2"); // Error: `b` has no type or storage class -``` +--- $(P When there is a storage class for a pattern, each component of the pattern can be just an identifier. Each variable's type will be inferred from the corresponding sequence element:) -```d +--- import std.typecons : tuple; auto (a, b) = tuple(1, "2"); @@ -388,18 +388,18 @@ auto (a, immutable b, c) = tuple(1, "2", 3.0); static assert(is(typeof(a) == int)); static assert(is(typeof(b) == immutable string)); static assert(is(typeof(c) == double)); -``` +--- $(P Patterns can be nested:) -```d +--- auto t = tuple(1, tuple("2", 3.0)); // the following are equivalent: auto (a, (b, c)) = t; (int a, (string b, double c)) = t; (int a, auto (b, c)) = t; (int a, (auto b, auto c)) = t; -``` +--- $(P `static` or `enum` can be applied to the whole declaration, but not to individual components.) From fff690ae2b3346275075150cee034c5eab9611d9 Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Sat, 27 Jun 2026 09:42:44 +0100 Subject: [PATCH 24/35] Tweak wording for pattern without storage class --- spec/declaration.dd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/declaration.dd b/spec/declaration.dd index 7e061517779b..773a6ccd0cb4 100644 --- a/spec/declaration.dd +++ b/spec/declaration.dd @@ -368,7 +368,7 @@ auto b = temp[1]; --- $(P **See also:** $(REF Tuple, std,typecons).) - $(P When the tuple pattern has no storage class, each identifier + $(P When the tuple pattern has no storage class, each component that declares a variable must either have a type or a storage class applying to it:) --- (int a, b) = tuple(1, "2"); // Error: `b` has no type or storage class From c5ad4d7a1b090da32eef920d4e15afb01dc3fedb Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Sun, 28 Jun 2026 09:37:51 +0100 Subject: [PATCH 25/35] Fix AssignExpression links --- spec/declaration.dd | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/declaration.dd b/spec/declaration.dd index 773a6ccd0cb4..49c27be090c5 100644 --- a/spec/declaration.dd +++ b/spec/declaration.dd @@ -41,7 +41,7 @@ $(GRAMMAR $(GNAME VarDeclarations): $(GLINK StorageClasses)$(OPT) $(GLINK2 type, BasicType) $(GLINK2 type, TypeSuffixes)$(OPT) $(GLINK IdentifierInitializers) $(D ;) $(GLINK AutoDeclaration) - $(GLINK TuplePattern) `=` $(GLINK AssignExpression) + $(GLINK TuplePattern) `=` $(GLINK2 expression, AssignExpression) $(GNAME IdentifierInitializers): $(LEGACY_LNAME2 Declarators, DeclaratorIdentifierList) $(GLINK IdentifierInitializer) @@ -294,7 +294,7 @@ $(GNAME AutoAssignments): $(GNAME AutoAssignment): $(GLINK_LEX Identifier) $(GLINK2 template, TemplateParameters)$(OPT) $(D =) $(GLINK Initializer) - $(GLINK TuplePattern) `=` $(GLINK AssignExpression) + $(GLINK TuplePattern) `=` $(GLINK2 expression, AssignExpression) ) $(P If a declaration starts with a $(I StorageClass) and has @@ -330,7 +330,7 @@ auto c = new C(); // c is a handle to an instance of class C $(H3 $(LNAME2 unpacking-declarations, Unpacking Declarations)) $(P $(GLINK VarDeclarations) and $(GLINK AutoDeclaration) support initializing - one or more variables in a *tuple pattern* from an $(GLINK AssignExpression). + one or more variables in a *tuple pattern* from an $(GLINK2 expression, AssignExpression). The *AssignExpression* must implicitly convert to a $(DDSUBLINK spec/template, homogeneous_sequences, value sequence).) From 3d8fd03d8affd7c29c728728ee8629884100b943 Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Mon, 29 Jun 2026 21:02:49 +0100 Subject: [PATCH 26/35] Fix lex, type links --- spec/declaration.dd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/declaration.dd b/spec/declaration.dd index 49c27be090c5..a73bcad7894f 100644 --- a/spec/declaration.dd +++ b/spec/declaration.dd @@ -344,8 +344,8 @@ $(GNAME TuplePatternComponents): $(GLINK TuplePatternComponent) `,` $(GLINK TuplePatternComponents) $(GNAME TuplePatternComponent): - $(GLINK StorageClasses)$(OPT) $(GLINK Identifier) - $(GLINK StorageClasses)$(OPT) $(GLINK BasicType) $(GLINK TypeSuffixes)$(OPT) $(GLINK Identifier) + $(GLINK StorageClasses)$(OPT) $(GLINK_LEX Identifier) + $(GLINK StorageClasses)$(OPT) $(GLINK2 type, BasicType) $(GLINK2 type, TypeSuffixes)$(OPT) $(GLINK_LEX Identifier) $(GLINK StorageClasses)$(OPT) $(GLINK TuplePattern) ) From d824cffd48654bae259e47accfbb2d3d45a54270 Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Sat, 27 Jun 2026 20:16:51 +0100 Subject: [PATCH 27/35] [spec/statement] Unpacking Foreach Variables --- spec/statement.dd | 112 ++++++++++++++++++++++++++++++++++++++++++++++ spec/version.dd | 5 ++- 2 files changed, 115 insertions(+), 2 deletions(-) diff --git a/spec/statement.dd b/spec/statement.dd index 3395e0413af4..c9591ce4d42c 100644 --- a/spec/statement.dd +++ b/spec/statement.dd @@ -519,6 +519,7 @@ $(GNAME ForeachType): $(GLINK ForeachTypeAttributes)$(OPT) $(GLINK2 type, BasicType) $(GLINK2 declaration, Declarator) $(GLINK ForeachTypeAttributes)$(OPT) $(GLINK_LEX Identifier) $(GLINK ForeachTypeAttributes)$(OPT) $(D alias) $(GLINK_LEX Identifier) + $(GLINK ForeachTypeAttributes)$(OPT) $(GLINK ForeachTuplePattern) $(GNAME ForeachTypeAttributes): $(GLINK ForeachTypeAttribute) @@ -1312,6 +1313,117 @@ aa = null; // OK `foreach` is still `@safe`. ) +$(H3 $(LNAME2 foreach-unpacking, Unpacking Foreach Variables)) + + $(P Similarly to $(DDSUBLINK spec/declaration, unpacking-declarations, + unpacking variable declarations), $(GLINK AggregateForeach) supports unpacking + *ForeachType* declarations. The `auto` storage class is implied for a tuple pattern unless overridden.) + +$(GRAMMAR +$(GNAME ForeachTuplePattern): + `(` $(GLINK ForeachTupleComponents) `)` + +$(GNAME ForeachTupleComponents): + $(GLINK ForeachTupleComponent) `,` + $(GLINK ForeachTupleComponent) `,` $(GLINK ForeachTupleComponent) + $(GLINK ForeachTupleComponent) `,` $(GLINK ForeachTupleComponents) + +$(GNAME ForeachTupleComponent): + $(GLINK ForeachTypeAttributes)$(OPT) $(GLINK_LEX Identifier) + $(GLINK ForeachTypeAttributes)$(OPT) $(GLINK2 type, BasicType) $(GLINK2 type, TypeSuffixes)$(OPT) $(GLINK_LEX Identifier) + $(GLINK ForeachTypeAttributes)$(OPT) $(GLINK ForeachTuplePattern) +) + $(P The following `foreach` statements are equivalent:) + +--- +import std.typecons : tuple; + +auto arr = [tuple(1, "2"), tuple(3, "4"), tuple(5, "6")]; + +foreach ((x, y); arr) +{ + writeln(x, " ", y); // "1 2\n3 4\n5 6\n" +} + +foreach ((int x, string y); arr) +{ + writeln(x, " ", y);// "1 2\n3 4\n5 6\n" +} +--- + $(P An index variable can be declared alongside + an unpacked element variable (when the $(GLINK ForeachAggregate) supports it):) + +--- +import std.typecons : tuple; + +auto arr = [tuple(1, 2), tuple(3, 4)]; +// declare index for array & unpack tuple elements +foreach (i, (x, y); arr) +{ + assert(x == 2 * i + 1); + assert(y == 2 * i + 2); +} +--- + $(P Nested tuples can be unpacked. Below, $(REF_SHORT enumerate, std,range) returns a + range of element type `Tuple!(size_t, Tuple!(int, int))`. The outer + tuple is $(RELATIVE_LINK2 front-seq, implicitly unwrapped):) +--- +import std.range : enumerate; +import std.typecons : tuple; + +auto arr = [tuple(1, 2), tuple(3, 4)]; +// unpack a range of nested tuples +foreach (i, (j, k); enumerate(arr)) +{ + writeln(i," ",j," ",k); // "0 1 2\n1 3 4\n" +} +--- + $(P The index variable can also be unpacked:) + +--- +import std.typecons : tuple; + +auto aa = [tuple(1, 2): "hi", tuple(3, 4): "bye"]; +// unpack AA tuple keys +foreach ((a, b), s; aa) +{ + writeln(a, b, s); // "12hi\n34bye\n" +} +--- + $(P Normal $(GLINK ForeachTypeAttribute) storage classes are supported, + and they can apply to tuple component variables:) + +--- +import std.typecons : tuple; + +auto arr = [tuple(1, 2), tuple(3, 4)]; +foreach ((ref x, y); arr) +{ + x = 2 * y; +} +assert(arr == [tuple(4, 2), tuple(8, 4)]); + +foreach (const (x, y); arr) +{ + static assert(is(typeof(x) == const(int))); + static assert(is(typeof(y) == const(int))); + assert(x == 2 * y); +} +--- + $(P Unpacking can be used with $(GLINK2 version, StaticForeach):) + +--- +import std.typecons : tuple; + +static foreach ((a, b); [tuple(1, 2), tuple(3, 4)]) +{ + pragma(msg, a, b); // "12\n34\n" +} +--- + + $(P Unpacking can also be used with $(RELATIVE_LINK2 op-apply, `opApply`).) + + $(H3 $(LEGACY_LNAME2 ForeachRangeStatement, foreach-range-statement, Foreach Range Statement)) $(P A foreach range statement loops over the specified range.) diff --git a/spec/version.dd b/spec/version.dd index 8be4d805ffb3..843c13c3150e 100644 --- a/spec/version.dd +++ b/spec/version.dd @@ -701,8 +701,9 @@ void fun() --- ) - $(P `static foreach` supports sequence expansion - $(DDSUBLINK spec/statement, foreach_over_tuples, like `foreach`).) + $(P `static` *AggregateForeach* supports sequence expansion + $(DDSUBLINK spec/statement, foreach_over_tuples, like `foreach`), + as well as unpacking $(GLINK2 statement, ForeachTuplePattern)s.) $(H3 $(LNAME2 break-continue, `break` and `continue`)) From 1105d44c273a7d86a94ff04e72472b7f94cfa1eb Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Sun, 28 Jun 2026 09:37:27 +0100 Subject: [PATCH 28/35] [spec/function] Unpacking Parameters --- spec/function.dd | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/spec/function.dd b/spec/function.dd index 1280500c9991..528fc73b105d 100644 --- a/spec/function.dd +++ b/spec/function.dd @@ -47,6 +47,8 @@ $(GNAME Parameter): $(GLINK ParameterDeclaration) $(D ...) $(GLINK ParameterDeclaration) $(D =) $(ASSIGNEXPRESSION) $(GLINK ParameterDeclaration) $(D =) $(ASSIGNEXPRESSION) $(D ...) + $(GLINK ParameterAttributes)$(OPT) $(GLINK ParameterTuplePattern) + $(GLINK ParameterAttributes)$(OPT) $(GLINK ParameterTuplePattern) = $(GLINK2 expression, AssignExpression) $(GNAME ParameterDeclaration): $(GLINK ParameterAttributes)$(OPT) $(GLINK2 type, BasicType) $(GLINK2 declaration, Declarator) @@ -2282,6 +2284,48 @@ $(H3 $(LNAME2 udas-parameters, User-Defined Attributes for Parameters)) See also: $(GLINK2_ALTTEXT attribute, UserDefinedAttribute, User-Defined Attributes) +$(H3 $(LNAME2 unpacking-parameters, Unpacking Parameters)) + +$(GRAMMAR +$(GNAME ParameterTuplePattern): + `(` $(GLINK ParameterTupleComponents) `)` + +$(GNAME ParameterTupleComponents): + $(GLINK ParameterTupleComponent) `,` + $(GLINK ParameterTupleComponent) `,` $(GLINK ParameterTupleComponent) + $(GLINK ParameterTupleComponent) `,` $(GLINK ParameterTupleComponents) + +$(GNAME ParameterTupleComponent): + $(GLINK ParameterAttributes)$(OPT) $(GLINK_LEX Identifier) + $(GLINK ParameterAttributes)$(OPT) $(GLINK2 type, BasicType) $(GLINK2 type, TypeSuffixes)$(OPT) $(GLINK_LEX Identifier) + $(GLINK ParameterAttributes)$(OPT) $(GLINK ParameterTuplePattern) +) + + $(P A $(DDSUBLINK spec/expression, function-literal-alias, function literal template) + with untyped *ParameterTupleComponents* is supported. The corresponding function + argument will be unpacked into component variables:) + +--- +import std.typecons : tuple; + +alias dg = ((x, y), z) => writeln(x, " ", y, " ", z); +dg(tuple(1, 2), 3); // "1 2 3\n" +--- + $(NOTE A typed *ParameterTupleComponent* is parsed, but cannot be supported without + compiler-recognised tuple types.) + + $(P `((x, y), z) { ... }` is lowered to `(__arg0, z) { auto (x, y) = __arg0; ... }`. + In general, lowering preserves and copies storage classes from + the parameter to an $(DDSUBLINK spec/declaration, unpacking-declarations, + unpacking declaration) in the function body.) + + - The `ref` storage class is copied outwards, e.g., `((ref a, b), c) { ... }` gets lowered to + `(ref __arg0, c) { (ref a, auto b) = __arg0; ... }`. + - `out` can be applied to the whole parameter, but not to individual components. + - `auto ref` and `lazy` cannot be applied to an unpacking parameter. + + $(NOTE `out` unpacking parameters are not supported by the current implementation, but may be added at a future date.) + $(H3 $(LNAME2 variadic, Variadic Functions)) $(P $(I Variadic Functions) take a variable number of arguments. From 7588428288d192d6b0eb451e3269cd87bd2d2799 Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Mon, 29 Jun 2026 21:12:18 +0100 Subject: [PATCH 29/35] [changelog] Mention tuple parameters --- changelog/dmd.tuple-unpacking.dd | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog/dmd.tuple-unpacking.dd b/changelog/dmd.tuple-unpacking.dd index 357b1d46c53b..331f38a8b896 100644 --- a/changelog/dmd.tuple-unpacking.dd +++ b/changelog/dmd.tuple-unpacking.dd @@ -1,6 +1,7 @@ Tuple unpacking is now supported! -D now supports tuple unpacking for variable declarations and `foreach` loops. +D now supports tuple unpacking for variable declarations, `foreach` loops, and function literal +template parameters. This feature is available as a preview and can be enabled with the `-preview=tuples` compiler switch. It allows extracting multiple values from a tuple or compile-time sequence directly into distinct variables. From 04db20aee292364e9e7588670889c876c6301be5 Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Mon, 29 Jun 2026 21:19:29 +0100 Subject: [PATCH 30/35] Mention unpacking in function literal templates --- spec/expression.dd | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/expression.dd b/spec/expression.dd index 7aced6842dcf..1c1e19c74d7f 100644 --- a/spec/expression.dd +++ b/spec/expression.dd @@ -2702,6 +2702,9 @@ $(H4 $(LNAME2 function-literal-alias, Function Literal Templates)) --- ) + $(P A function literal template parameter $(DDSUBLINK spec/function, unpacking-parameters, + can use unpacking).) + $(H4 $(LNAME2 lambda-return-type, Return Type Inference)) $(P The return type of the $(GLINK FunctionLiteral) can be From d946bf5d58eb9806b3ecd235dfee92eafa94397b Mon Sep 17 00:00:00 2001 From: Timon Gehr Date: Mon, 6 Jul 2026 03:21:06 +0200 Subject: [PATCH 31/35] Fix whitespace. --- changelog/dmd.tuple-unpacking.dd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/dmd.tuple-unpacking.dd b/changelog/dmd.tuple-unpacking.dd index 331f38a8b896..7f92dfb062f0 100644 --- a/changelog/dmd.tuple-unpacking.dd +++ b/changelog/dmd.tuple-unpacking.dd @@ -18,7 +18,7 @@ void main() // Types can be specified explicitly (int a, string b) = tuple(4, "five"); - + // Unpacking works in foreach loops foreach ((i, s); [tuple(1, "one"), tuple(2, "two")]) { From 067d047f6c91b69a2e12e9b82e6aaf93d8cdb9b5 Mon Sep 17 00:00:00 2001 From: Timon Gehr Date: Mon, 6 Jul 2026 03:28:23 +0200 Subject: [PATCH 32/35] Update `globals.h`. --- compiler/include/dmd/globals.h | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/include/dmd/globals.h b/compiler/include/dmd/globals.h index 84142e5d6774..caac52dc5c3c 100644 --- a/compiler/include/dmd/globals.h +++ b/compiler/include/dmd/globals.h @@ -225,6 +225,7 @@ struct Param // Implementation: https://github.com/dlang/dmd/pull/9817 FeatureState safer; // safer by default (more @safe checks in unattributed code) // https://github.com/WalterBright/documents/blob/38f0a846726b571f8108f6e63e5e217b91421c86/safer.md + FeatureState tuples; // Tuple unpacking FeatureState noSharedAccess; // read/write access to shared memory objects d_bool previewIn; // `in` means `[ref] scope const`, accepts rvalues From f9a0430ea7fedd38daca43ddfb497364bdab03ee Mon Sep 17 00:00:00 2001 From: Timon Gehr Date: Mon, 6 Jul 2026 03:38:21 +0200 Subject: [PATCH 33/35] Fix cxxfrontend tests. --- compiler/include/dmd/attrib.h | 4 ---- compiler/include/dmd/visitor.h | 2 ++ compiler/src/dmd/attrib.d | 2 +- compiler/src/tests/cxxfrontend.cc | 4 ++-- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/compiler/include/dmd/attrib.h b/compiler/include/dmd/attrib.h index a7d531d96a26..03e9c2252b17 100644 --- a/compiler/include/dmd/attrib.h +++ b/compiler/include/dmd/attrib.h @@ -214,11 +214,7 @@ class UnpackDeclaration final : public AttribDeclaration bool onStack; bool lowered; - Dsymbols *include(Scope *sc) override; - void addComment(const utf8_t *comment) override; UnpackDeclaration *syntaxCopy(Dsymbol *) override; - const char *toChars() const override; const char *kind() const override; - UnpackDeclaration *isUnpackDeclaration() override { return this; } void accept(Visitor *v) override { v->visit(this); } }; diff --git a/compiler/include/dmd/visitor.h b/compiler/include/dmd/visitor.h index 1d2895150d75..674ade585c20 100644 --- a/compiler/include/dmd/visitor.h +++ b/compiler/include/dmd/visitor.h @@ -113,6 +113,7 @@ class StaticIfDeclaration; class MixinDeclaration; class StaticForeachDeclaration; class UserAttributeDeclaration; +class UnpackDeclaration; class ForwardingAttribDeclaration; class ScopeDsymbol; @@ -377,6 +378,7 @@ class ParseTimeVisitor virtual void visit(StorageClassDeclaration *s) { visit((AttribDeclaration *)s); } virtual void visit(ConditionalDeclaration *s) { visit((AttribDeclaration *)s); } virtual void visit(StaticForeachDeclaration *s) { visit((AttribDeclaration *)s); } + virtual void visit(UnpackDeclaration *s) { visit((AttribDeclaration *)s); } // Miscellaneous virtual void visit(DeprecatedDeclaration *s) { visit((StorageClassDeclaration *)s); } diff --git a/compiler/src/dmd/attrib.d b/compiler/src/dmd/attrib.d index cf9f32811582..e262f7d97003 100644 --- a/compiler/src/dmd/attrib.d +++ b/compiler/src/dmd/attrib.d @@ -773,7 +773,7 @@ extern (C++) final class UnpackDeclaration : AttribDeclaration this.dsym = DSYM.unpackDeclaration; } - bool propagateStorageClasses() + final bool propagateStorageClasses() { static import dmd.errors; foreach (d; *decl) diff --git a/compiler/src/tests/cxxfrontend.cc b/compiler/src/tests/cxxfrontend.cc index 339e7de6bf34..fda25b16015d 100644 --- a/compiler/src/tests/cxxfrontend.cc +++ b/compiler/src/tests/cxxfrontend.cc @@ -354,8 +354,8 @@ void test_target() void test_parameters() { Parameters *args = new Parameters; - args->push(Parameter::create(Loc(), STCundefined, Type::tint32, NULL, NULL, NULL)); - args->push(Parameter::create(Loc(), STCundefined, Type::tint64, NULL, NULL, NULL)); + args->push(Parameter::create(Loc(), STCundefined, Type::tint32, NULL, NULL, NULL, NULL)); + args->push(Parameter::create(Loc(), STCundefined, Type::tint64, NULL, NULL, NULL, NULL)); TypeFunction *tf = TypeFunction::create(args, Type::tvoid, VARARGnone, LINK::c); From 61fb6702e33819372813c92dab9b5aba9610a9af Mon Sep 17 00:00:00 2001 From: Timon Gehr Date: Mon, 6 Jul 2026 04:05:13 +0200 Subject: [PATCH 34/35] Fix Ddoc warning. --- compiler/src/dmd/lexer.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dmd/lexer.d b/compiler/src/dmd/lexer.d index aef75e20620e..49814ec48be8 100644 --- a/compiler/src/dmd/lexer.d +++ b/compiler/src/dmd/lexer.d @@ -1288,7 +1288,7 @@ class Lexer } /********************************* - * tk is on an opening (. + * tk is on an opening $(LPAREN). * Look ahead and determine if there is a comma at paren level 1. */ final bool isTupleNotation(Token* tk) From 6bf1826791ebd7251075bb10389e95eb52321638 Mon Sep 17 00:00:00 2001 From: Timon Gehr Date: Mon, 6 Jul 2026 04:27:48 +0200 Subject: [PATCH 35/35] Try to appease D-Scanner. --- compiler/src/dmd/attrib.d | 4 ++-- compiler/src/dmd/parse.d | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/src/dmd/attrib.d b/compiler/src/dmd/attrib.d index e262f7d97003..a5c096606e0e 100644 --- a/compiler/src/dmd/attrib.d +++ b/compiler/src/dmd/attrib.d @@ -773,7 +773,7 @@ extern (C++) final class UnpackDeclaration : AttribDeclaration this.dsym = DSYM.unpackDeclaration; } - final bool propagateStorageClasses() + bool propagateStorageClasses() { static import dmd.errors; foreach (d; *decl) @@ -817,7 +817,7 @@ extern (C++) final class UnpackDeclaration : AttribDeclaration return true; } - final void lower(Scope* sc) + void lower(Scope* sc) { if (lowered) return; diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index 347d40208ef5..81c838715893 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -3266,7 +3266,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer at = parseType(&ai, &loc); - LskipType:; + LskipType:{} } ae = null; if (token.value == TOK.assign) // = defaultArg