diff --git a/changelog/AliasAssign.md b/changelog/AliasAssign.md new file mode 100644 index 000000000000..59c9ae11c285 --- /dev/null +++ b/changelog/AliasAssign.md @@ -0,0 +1,53 @@ +# Add Alias Assignment + +This adds the ability for an alias declaration inside a template to be +assigned a new value. For example, the recursive template: + +--- +template staticMap(alias F, T...) +{ + static if (T.length == 0) + alias staticMap = AliasSym!(); + else + alias staticMap = AliasSym!(F!(T[0]), staticMap!(T[0 .. T.length])); +} +--- + +can now be reworked into an iterative template: + +--- +template staticMap(alias F, T...) +{ + alias A = AliasSeq!(); + static foreach (t; T) + A = AliasSeq!(A, F!t); // alias assignment here + alias staticMap = A; +} +--- + +Using the iterative approach will eliminate the combinatorial explosion of recursive +template instantiations, eliminating the associated high memory and runtime costs, +as well as eliminating the issues with limits on the nesting depth of templates. +It will eliminate the obtuse error messages generated when deep in recursion. + +The grammar: + +--- +AliasAssign: + Identifier = Type; +--- + +is added to the expansion of DeclDef. The Identifier must resolve to a lexically +preceding AliasDeclaration: + +--- +alias Identifier = Type; +--- + +where the Identifier's match, and both are members of the same TemplateDeclaration. +Upon semantic processing, when the AliasAssign is encountered the Type in the +AliasAssign replaces the Type from the corresponding AliasDeclaration or any previous matching +AliasAssign. + +The AliasAssign grammar was previously rejected by the parser, so adding it +should not break existing code. diff --git a/src/dmd/astbase.d b/src/dmd/astbase.d index 2311b68c2e45..cb39a107409d 100644 --- a/src/dmd/astbase.d +++ b/src/dmd/astbase.d @@ -477,6 +477,11 @@ struct ASTBase return null; } + inout(AliasAssign) isAliasAssign() inout + { + return null; + } + inout(ClassDeclaration) isClassDeclaration() inout { return null; @@ -520,12 +525,38 @@ struct ASTBase } } + extern (C++) final class AliasAssign : Dsymbol + { + Identifier ident; + Type type; + + extern (D) this(const ref Loc loc, Identifier ident, Type type) + { + super(null); + this.loc = loc; + this.ident = ident; + this.type = type; + } + + override inout(AliasAssign) isAliasAssign() inout + { + return this; + } + + override void accept(Visitor v) + { + v.visit(this); + } + } + extern (C++) abstract class Declaration : Dsymbol { StorageClass storage_class; Prot protection; LINK linkage; Type type; + short inuse; + ubyte adFlags; final extern (D) this(Identifier id) { diff --git a/src/dmd/declaration.d b/src/dmd/declaration.d index 81ca59efc632..4c75ed3814fd 100644 --- a/src/dmd/declaration.d +++ b/src/dmd/declaration.d @@ -294,7 +294,11 @@ extern (C++) abstract class Declaration : Dsymbol StorageClass storage_class = STC.undefined_; Prot protection; LINK linkage = LINK.default_; - int inuse; // used to detect cycles + short inuse; // used to detect cycles + + ubyte adFlags; // control re-assignment of AliasDeclaration (put here for packing reasons) + enum wasRead = 1; // set if AliasDeclaration was read + enum ignoreRead = 2; // ignore any reads of AliasDeclaration // overridden symbol with pragma(mangle, "...") const(char)[] mangleOverride; @@ -869,6 +873,11 @@ extern (C++) final class AliasDeclaration : Declaration // loc.toChars(), toChars(), this, aliassym, aliassym ? aliassym.kind() : "", inuse); assert(this != aliassym); //static int count; if (++count == 10) *(char*)0=0; + + // Reading the AliasDeclaration + if (!(adFlags & ignoreRead)) + adFlags |= wasRead; // can never assign to this AliasDeclaration again + if (inuse == 1 && type && _scope) { inuse = 2; diff --git a/src/dmd/declaration.h b/src/dmd/declaration.h index 8f13b220e8d3..fb42ce34cb8e 100644 --- a/src/dmd/declaration.h +++ b/src/dmd/declaration.h @@ -100,7 +100,8 @@ class Declaration : public Dsymbol StorageClass storage_class; Prot protection; LINK linkage; - int inuse; // used to detect cycles + short inuse; // used to detect cycles + uint8_t adFlags; DString mangleOverride; // overridden symbol with pragma(mangle, "...") const char *kind() const; diff --git a/src/dmd/dscope.d b/src/dmd/dscope.d index dbaa3c074d52..f1586318a89f 100644 --- a/src/dmd/dscope.d +++ b/src/dmd/dscope.d @@ -138,6 +138,9 @@ struct Scope uint[void*] anchorCounts; /// lookup duplicate anchor name count Identifier prevAnchor; /// qualified symbol name of last doc anchor + AliasDeclaration aliasAsg; /// if set, then aliasAsg is being assigned a new value, + /// do not set wasRead for it + extern (D) __gshared Scope* freelist; extern (D) static Scope* alloc() diff --git a/src/dmd/dsymbol.d b/src/dmd/dsymbol.d index 071fd89047d9..46fc94e64a03 100644 --- a/src/dmd/dsymbol.d +++ b/src/dmd/dsymbol.d @@ -1219,6 +1219,7 @@ extern (C++) class Dsymbol : ASTNode inout(Declaration) isDeclaration() inout { return null; } inout(StorageClassDeclaration) isStorageClassDeclaration() inout { return null; } inout(ExpressionDsymbol) isExpressionDsymbol() inout { return null; } + inout(AliasAssign) isAliasAssign() inout { return null; } inout(ThisDeclaration) isThisDeclaration() inout { return null; } inout(TypeInfoDeclaration) isTypeInfoDeclaration() inout { return null; } inout(TupleDeclaration) isTupleDeclaration() inout { return null; } @@ -2113,7 +2114,7 @@ extern (C++) final class ForwardingScopeDsymbol : ScopeDsymbol } /** - * Class that holds an expression in a Dsymbol wraper. + * Class that holds an expression in a Dsymbol wrapper. * This is not an AST node, but a class used to pass * an expression as a function parameter of type Dsymbol. */ @@ -2132,6 +2133,45 @@ extern (C++) final class ExpressionDsymbol : Dsymbol } } +/********************************************** + * Encapsulate assigning to an alias: + * `identifier = type;` + * where `identifier` is an AliasDeclaration in scope. + */ +extern (C++) final class AliasAssign : Dsymbol +{ + Identifier ident; /// Dsymbol's ident will be null, as this class is anonymous + Type type; /// replace previous RHS of AliasDeclaration with `type` + + extern (D) this(const ref Loc loc, Identifier ident, Type type) + { + super(loc, null); + this.ident = ident; + this.type = type; + } + + override Dsymbol syntaxCopy(Dsymbol s) + { + assert(!s); + AliasAssign aa = new AliasAssign(loc, ident, type.syntaxCopy()); + return aa; + } + + override inout(AliasAssign) isAliasAssign() inout + { + return this; + } + + override const(char)* kind() const + { + return "aliasAssign"; + } + + override void accept(Visitor v) + { + v.visit(this); + } +} /*********************************************************** * Table of Dsymbol's diff --git a/src/dmd/dsymbol.h b/src/dmd/dsymbol.h index 623552b36e4b..cfac9ebe2386 100644 --- a/src/dmd/dsymbol.h +++ b/src/dmd/dsymbol.h @@ -68,6 +68,7 @@ class ArrayScopeSymbol; class SymbolDeclaration; class Expression; class ExpressionDsymbol; +class AliasAssign; class OverloadSet; struct AA; #ifdef IN_GCC @@ -238,6 +239,7 @@ class Dsymbol : public ASTNode virtual Declaration *isDeclaration() { return NULL; } virtual StorageClassDeclaration *isStorageClassDeclaration(){ return NULL; } virtual ExpressionDsymbol *isExpressionDsymbol() { return NULL; } + virtual AliasAssign *isAliasAssign() { return NULL; } virtual ThisDeclaration *isThisDeclaration() { return NULL; } virtual TypeInfoDeclaration *isTypeInfoDeclaration() { return NULL; } virtual TupleDeclaration *isTupleDeclaration() { return NULL; } diff --git a/src/dmd/dsymbolsem.d b/src/dmd/dsymbolsem.d index 0ddfc1cccab4..3971c50c42f8 100644 --- a/src/dmd/dsymbolsem.d +++ b/src/dmd/dsymbolsem.d @@ -754,6 +754,19 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor aliasSemantic(dsym, sc); } + override void visit(AliasAssign dsym) + { + //printf("visit(AliasAssign)\n"); + if (dsym.semanticRun >= PASS.semanticdone) + return; + assert(dsym.semanticRun <= PASS.semantic); + + if (!sc.func && dsym.inNonRoot()) + return; + + aliasAssignSemantic(dsym, sc); + } + override void visit(VarDeclaration dsym) { version (none) @@ -6711,3 +6724,154 @@ void aliasSemantic(AliasDeclaration ds, Scope* sc) } normalRet(); } + +// function used to perform semantic on AliasDeclaration +private void aliasAssignSemantic(AliasAssign ds, Scope* sc) +{ + //printf("AliasAssign::semantic() %p, %s\n", ds, ds.ident.toChars()); + + void errorRet() + { + ds.errors = true; + ds.type = Type.terror; + ds.semanticRun = PASS.semanticdone; + return; + } + + Dsymbol scopesym; + Dsymbol as = sc.search(ds.loc, ds.ident, &scopesym); + if (!as) + { + ds.error("undefined identifier `%s`", ds.ident.toChars()); + return errorRet(); + } + if (as.errors) + { + return errorRet(); + } + + AliasDeclaration aliassym = as.isAliasDeclaration(); + if (!aliassym) + { + ds.error("identifier `%s` must be an alias declaration", as.toChars()); + return errorRet(); + } + + if (aliassym.overnext) + { + ds.error("cannot reassign overloaded alias"); + return errorRet(); + } + + auto aliassymParent = aliassym.toParent(); + if (aliassymParent != ds.toParent()) + { + ds.error("must have same parent `%s` as alias `%s`", aliassymParent.toChars(), aliassym.toChars()); + return errorRet(); + } + if (!aliassymParent.isTemplateInstance()) + { + ds.error("must be a member of a template"); + return errorRet(); + } + + if (!aliassym.type) + { + ds.error("alias assignment can only apply to type aliases"); + return errorRet(); + } + + if (aliassym.adFlags & Declaration.wasRead) + { + if (!aliassym.errors) + error(ds.loc, "%s was read, so cannot reassign", aliassym.toChars()); + aliassym.errors = true; + return errorRet(); + } + + const errors = global.errors; + aliassym.adFlags |= Declaration.ignoreRead; // temporarilly allow reads of aliassym + + const storage_class = sc.stc & (STC.deprecated_ | STC.ref_ | STC.nothrow_ | STC.nogc | STC.pure_ | STC.shared_ | STC.disable); + +//<<>> + /* This section is needed because Type.resolve() will: + * const x = 3; + * alias y = x; + * try to convert identifier x to 3. + */ + auto s = ds.type.toDsymbol(sc); + if (errors != global.errors) + return errorRet(); + if (s == aliassym) + { + ds.error("cannot resolve"); + return errorRet(); + } + + if (!s || !s.isEnumMember()) + { + Type t; + Expression e; + Scope* sc2 = sc; + if (storage_class & (STC.ref_ | STC.nothrow_ | STC.nogc | STC.pure_ | STC.shared_ | STC.disable)) + { + // For 'ref' to be attached to function types, and picked + // up by Type.resolve(), it has to go into sc. + sc2 = sc.push(); + sc2.stc |= storage_class & (STC.ref_ | STC.nothrow_ | STC.nogc | STC.pure_ | STC.shared_ | STC.disable); + } + ds.type = ds.type.addSTC(storage_class); + ds.type.resolve(ds.loc, sc2, e, t, s); + if (sc2 != sc) + sc2.pop(); + + if (e) // Try to convert Expression to Dsymbol + { + s = getDsymbol(e); + if (!s) + { + if (e.op != TOK.error) + ds.error("cannot alias an expression `%s`", e.toChars()); + return errorRet(); + } + } + ds.type = t; + } + if (s == aliassym) + { + assert(global.errors); + return errorRet(); + } + + if (s) // it's a symbolic alias + { + //printf("alias %s resolved to %s %s\n", toChars(), s.kind(), s.toChars()); + aliassym.type = null; + aliassym.aliassym = s; + + aliassym.storage_class |= sc.stc & STC.deprecated_; + aliassym.protection = sc.protection; + aliassym.userAttribDecl = sc.userAttribDecl; + } + else // it's a type alias + { + //printf("alias %s resolved to type %s\n", toChars(), type.toChars()); + aliassym.type = ds.type.typeSemantic(ds.loc, sc); + aliassym.aliassym = null; + } + + + aliassym.adFlags &= ~Declaration.ignoreRead; + + if (aliassym.type && aliassym.type.ty == Terror || + global.gag && errors != global.errors) + { + aliassym.type = Type.terror; + aliassym.aliassym = null; + return errorRet(); + } + + ds.semanticRun = PASS.semanticdone; +} + diff --git a/src/dmd/frontend.h b/src/dmd/frontend.h index 46dcedf68529..631f8aed4c57 100644 --- a/src/dmd/frontend.h +++ b/src/dmd/frontend.h @@ -95,6 +95,7 @@ class Nspace; class Declaration; class StorageClassDeclaration; class ExpressionDsymbol; +class AliasAssign; class ThisDeclaration; class TypeInfoDeclaration; class TupleDeclaration; @@ -879,6 +880,7 @@ class Dsymbol : public ASTNode virtual Declaration* isDeclaration(); virtual StorageClassDeclaration* isStorageClassDeclaration(); virtual ExpressionDsymbol* isExpressionDsymbol(); + virtual AliasAssign* isAliasAssign(); virtual ThisDeclaration* isThisDeclaration(); virtual TypeInfoDeclaration* isTypeInfoDeclaration(); virtual TupleDeclaration* isTupleDeclaration(); @@ -1411,6 +1413,7 @@ class ParseTimeVisitor virtual void visit(StaticAssert* s); virtual void visit(DebugSymbol* s); virtual void visit(VersionSymbol* s); + virtual void visit(AliasAssign* s); virtual void visit(Package* s); virtual void visit(EnumDeclaration* s); virtual void visit(AggregateDeclaration* s); @@ -2549,7 +2552,12 @@ class Declaration : public Dsymbol StorageClass storage_class; Prot protection; LINK linkage; - int32_t inuse; + int16_t inuse; + uint8_t adFlags; + enum : int32_t { wasRead = 1 }; + + enum : int32_t { ignoreRead = 2 }; + _d_dynamicArray< const char > mangleOverride; const char* kind() const; d_uns64 size(const Loc& loc); @@ -3219,6 +3227,17 @@ class ExpressionDsymbol final : public Dsymbol ExpressionDsymbol* isExpressionDsymbol(); }; +class AliasAssign final : public Dsymbol +{ +public: + Identifier* ident; + Type* type; + Dsymbol* syntaxCopy(Dsymbol* s); + AliasAssign* isAliasAssign(); + const char* kind() const; + void accept(Visitor* v); +}; + class DsymbolTable final : public RootObject { public: @@ -5771,6 +5790,7 @@ class ParseTimeVisitor virtual void visit(typename AST::StaticAssert s); virtual void visit(typename AST::DebugSymbol s); virtual void visit(typename AST::VersionSymbol s); + virtual void visit(typename AST::AliasAssign s); virtual void visit(typename AST::Package s); virtual void visit(typename AST::EnumDeclaration s); virtual void visit(typename AST::AggregateDeclaration s); diff --git a/src/dmd/hdrgen.d b/src/dmd/hdrgen.d index ece5f0779f29..82edab643e38 100644 --- a/src/dmd/hdrgen.d +++ b/src/dmd/hdrgen.d @@ -1444,6 +1444,15 @@ public: buf.writenl(); } + override void visit(AliasAssign d) + { + buf.writestring(d.ident.toString()); + buf.writestring(" = "); + typeToBuffer(d.type, null, buf, hgs); + buf.writeByte(';'); + buf.writenl(); + } + override void visit(VarDeclaration d) { if (d.storage_class & STC.local) diff --git a/src/dmd/parse.d b/src/dmd/parse.d index 5de96b50cd07..d89640dc6cf3 100644 --- a/src/dmd/parse.d +++ b/src/dmd/parse.d @@ -4435,6 +4435,24 @@ final class Parser(AST) : Lexer if (!comment) comment = token.blockComment.ptr; + /* Look for AliasAssignment: + * identifier = type; + */ + if (token.value == TOK.identifier && peekNext() == TOK.assign) + { + const loc = token.loc; + auto ident = token.ident; + nextToken(); + nextToken(); // advance past = + auto t = parseType(); + AST.Dsymbol s = new AST.AliasAssign(loc, ident, t); + check(TOK.semicolon); + addComment(s, comment); + auto a = new AST.Dsymbols(); + a.push(s); + return a; + } + if (token.value == TOK.alias_) { const loc = token.loc; diff --git a/src/dmd/parsetimevisitor.d b/src/dmd/parsetimevisitor.d index 27441516a326..e946fd340047 100644 --- a/src/dmd/parsetimevisitor.d +++ b/src/dmd/parsetimevisitor.d @@ -35,6 +35,7 @@ public: void visit(AST.StaticAssert s) { visit(cast(AST.Dsymbol)s); } void visit(AST.DebugSymbol s) { visit(cast(AST.Dsymbol)s); } void visit(AST.VersionSymbol s) { visit(cast(AST.Dsymbol)s); } + void visit(AST.AliasAssign s) { visit(cast(AST.Dsymbol)s); } // ScopeDsymbols void visit(AST.Package s) { visit(cast(AST.ScopeDsymbol)s); } diff --git a/src/dmd/scope.h b/src/dmd/scope.h index 923803c8243f..6ee647b72d49 100644 --- a/src/dmd/scope.h +++ b/src/dmd/scope.h @@ -120,6 +120,8 @@ struct Scope AA *anchorCounts; // lookup duplicate anchor name count Identifier *prevAnchor; // qualified symbol name of last doc anchor + AliasDeclaration aliasAsg; /// if set, then aliasAsg is being assigned a new value, + /// do not set wasRead for it Scope(); Scope *copy(); diff --git a/src/dmd/strictvisitor.d b/src/dmd/strictvisitor.d index d29dcb6ad8b1..92248753b3e4 100644 --- a/src/dmd/strictvisitor.d +++ b/src/dmd/strictvisitor.d @@ -26,6 +26,7 @@ extern(C++) class StrictVisitor(AST) : ParseTimeVisitor!AST override void visit(AST.VarDeclaration) { assert(0); } override void visit(AST.FuncDeclaration) { assert(0); } override void visit(AST.AliasDeclaration) { assert(0); } + override void visit(AST.AliasAssign) { assert(0); } override void visit(AST.TupleDeclaration) { assert(0); } override void visit(AST.FuncLiteralDeclaration) { assert(0); } override void visit(AST.PostBlitDeclaration) { assert(0); } diff --git a/src/dmd/transitivevisitor.d b/src/dmd/transitivevisitor.d index 2eb870fd9b13..6499b781d7fa 100644 --- a/src/dmd/transitivevisitor.d +++ b/src/dmd/transitivevisitor.d @@ -781,6 +781,12 @@ package mixin template ParseVisitMethods(AST) visitType(d.type); } + override void visit(AST.AliasAssign d) + { + //printf("Visting AliasAssign\n"); + visitType(d.type); + } + override void visit(AST.VarDeclaration d) { //printf("Visiting VarDeclaration\n"); diff --git a/src/dmd/visitor.h b/src/dmd/visitor.h index 1f673508d2a5..b6d8250a3a55 100644 --- a/src/dmd/visitor.h +++ b/src/dmd/visitor.h @@ -122,6 +122,7 @@ class Module; class WithScopeSymbol; class ArrayScopeSymbol; class Nspace; +class AliasAssign; class AggregateDeclaration; class StructDeclaration; @@ -324,6 +325,7 @@ class ParseTimeVisitor virtual void visit(StaticAssert *s) { visit((Dsymbol *)s); } virtual void visit(DebugSymbol *s) { visit((Dsymbol *)s); } virtual void visit(VersionSymbol *s) { visit((Dsymbol *)s); } + virtual void visit(AliasAssign *s) { visit((Dsymbol *)s); } // ScopeDsymbols virtual void visit(Package *s) { visit((ScopeDsymbol *)s); } diff --git a/test/compilable/aliasassign.d b/test/compilable/aliasassign.d new file mode 100644 index 000000000000..e355e49837f7 --- /dev/null +++ b/test/compilable/aliasassign.d @@ -0,0 +1,41 @@ +template AliasSeq(T...) { alias AliasSeq = T; } + +template Unqual(T) +{ + static if (is(T U == const U)) + alias Unqual = U; + else static if (is(T U == immutable U)) + alias Unqual = U; + else + alias Unqual = T; +} + +template staticMap(alias F, T...) +{ + alias A = AliasSeq!(); + static foreach (t; T) + A = AliasSeq!(A, F!t); // what's tested + alias staticMap = A; +} + +alias TK = staticMap!(Unqual, int, const uint); +//pragma(msg, TK); +static assert(is(TK == AliasSeq!(int, uint))); + +/**************************************************/ + +template reverse(T...) +{ + alias A = AliasSeq!(); + static foreach (t; T) + A = AliasSeq!(t, A); // what's tested + alias reverse = A; +} + +enum X2 = 3; +alias TK2 = reverse!(int, const uint, X2); +//pragma(msg, TK2); +static assert(TK2[0] == 3); +static assert(is(TK2[1] == const(uint))); +static assert(is(TK2[2] == int)); + diff --git a/test/fail_compilation/aliasassign1.d b/test/fail_compilation/aliasassign1.d new file mode 100644 index 000000000000..df3f43a18b3a --- /dev/null +++ b/test/fail_compilation/aliasassign1.d @@ -0,0 +1,34 @@ +/* TEST_OUTPUT: +--- +fail_compilation/aliasassign1.d(106): Error: A was read, so cannot reassign +fail_compilation/aliasassign1.d(110): Error: template instance `aliasassign1.staticMap!(Unqual, int, const(uint))` error instantiating +fail_compilation/aliasassign1.d(112): Error: static assert: `is(TK == AliasSeq!(int, uint))` is false +--- + */ + +template AliasSeq(T...) { alias AliasSeq = T; } + +template Unqual(T) +{ + static if (is(T U == const U)) + alias Unqual = U; + else static if (is(T U == immutable U)) + alias Unqual = U; + else + alias Unqual = T; +} + +#line 100 + +template staticMap(alias F, T...) +{ + alias A = AliasSeq!(); + alias B = A; + static foreach (t; T) + A = AliasSeq!(A, F!t); // what's tested + alias staticMap = A; +} + +alias TK = staticMap!(Unqual, int, const uint); +//pragma(msg, TK); +static assert(is(TK == AliasSeq!(int, uint)));