From ba6a1e1d1aa2e3497f89dab7226883bb8d12349e Mon Sep 17 00:00:00 2001 From: Walter Bright Date: Tue, 24 Sep 2024 02:13:52 -0700 Subject: [PATCH] implement move constructor --- compiler/src/dmd/aggregate.h | 4 + compiler/src/dmd/astbase.d | 2 +- compiler/src/dmd/backend/debugprint.d | 2 +- compiler/src/dmd/backend/dout.d | 2 +- compiler/src/dmd/clone.d | 216 ++++++++++++++++++++++- compiler/src/dmd/dstruct.d | 10 +- compiler/src/dmd/dsymbolsem.d | 37 +++- compiler/src/dmd/e2ir.d | 6 + compiler/src/dmd/expressionsem.d | 20 ++- compiler/src/dmd/frontend.h | 6 + compiler/src/dmd/func.d | 19 +- compiler/src/dmd/hdrgen.d | 7 +- compiler/src/dmd/id.d | 1 + compiler/src/dmd/identifier.d | 2 + compiler/src/dmd/parse.d | 14 +- compiler/src/dmd/templatesem.d | 4 +- compiler/src/dmd/traits.d | 2 +- compiler/src/dmd/typesem.d | 37 ++-- compiler/test/fail_compilation/movctor.d | 10 ++ 19 files changed, 357 insertions(+), 44 deletions(-) create mode 100644 compiler/test/fail_compilation/movctor.d diff --git a/compiler/src/dmd/aggregate.h b/compiler/src/dmd/aggregate.h index 8fd12e1d168a..0a99e677cc3f 100644 --- a/compiler/src/dmd/aggregate.h +++ b/compiler/src/dmd/aggregate.h @@ -177,6 +177,8 @@ class StructDeclaration : public AggregateDeclaration bool zeroInit(bool v); bool hasIdentityAssign() const; // true if has identity opAssign bool hasIdentityAssign(bool v); + bool hasMoveAssign() const; // true if has identity opAssign + bool hasMoveAssign(bool v); bool hasBlitAssign() const; // true if opAssign is a blit bool hasBlitAssign(bool v); bool hasIdentityEquals() const; // true if has identity opEquals @@ -185,6 +187,8 @@ class StructDeclaration : public AggregateDeclaration bool hasNoFields(bool v); bool hasCopyCtor() const; // copy constructor bool hasCopyCtor(bool v); + bool hasMoveCtor() const; // copy constructor + bool hasMoveCtor(bool v); // Even if struct is defined as non-root symbol, some built-in operations // (e.g. TypeidExp, NewExp, ArrayLiteralExp, etc) request its TypeInfo. // For those, today TypeInfo_Struct is generated in COMDAT. diff --git a/compiler/src/dmd/astbase.d b/compiler/src/dmd/astbase.d index 31b8bdf64f2c..7348e011f836 100644 --- a/compiler/src/dmd/astbase.d +++ b/compiler/src/dmd/astbase.d @@ -703,7 +703,7 @@ struct ASTBase extern (C++) final class CtorDeclaration : FuncDeclaration { - extern (D) this(const ref Loc loc, Loc endloc, StorageClass stc, Type type, bool isCopyCtor = false) + extern (D) this(const ref Loc loc, Loc endloc, StorageClass stc, Type type, bool isCopyCtor = false, bool isMoveCtor = false) { super(loc, endloc, Id.ctor, stc, type); } diff --git a/compiler/src/dmd/backend/debugprint.d b/compiler/src/dmd/backend/debugprint.d index 6ed1943c13a0..a5848d48ed3f 100644 --- a/compiler/src/dmd/backend/debugprint.d +++ b/compiler/src/dmd/backend/debugprint.d @@ -509,7 +509,7 @@ void numberBlocks(block *startblock) @trusted void WRfunc(const char* msg, Symbol* sfunc, block* startblock) { - printf("............%s...%s().............\n", msg, sfunc.Sident.ptr); + printf("............%s...%s()\n", msg, sfunc.Sident.ptr); numberBlocks(startblock); for (block *b = startblock; b; b = b.Bnext) WRblock(b); diff --git a/compiler/src/dmd/backend/dout.d b/compiler/src/dmd/backend/dout.d index 5f6f8c2aacb3..5cf3a04d4478 100644 --- a/compiler/src/dmd/backend/dout.d +++ b/compiler/src/dmd/backend/dout.d @@ -862,7 +862,7 @@ private void writefunc2(Symbol *sfunc, ref GlobalOptimizer go) { func_t *f = sfunc.Sfunc; - //printf("writefunc(%s)\n",sfunc.Sident.ptr); + debugb && printf("===================== writefunc %s =================\n",sfunc.Sident.ptr); //symbol_print(sfunc); debug debugy && printf("writefunc(%s)\n",sfunc.Sident.ptr); diff --git a/compiler/src/dmd/clone.d b/compiler/src/dmd/clone.d index 19987f106adb..dbe59c004a79 100644 --- a/compiler/src/dmd/clone.d +++ b/compiler/src/dmd/clone.d @@ -1544,6 +1544,9 @@ FuncDeclaration buildPostBlit(StructDeclaration sd, Scope* sc) return xpostblit; } +/* ===================================== Copy Constructor ========================== */ +static if (1) { + /** * Generates a copy constructor declaration with the specified storage * class for the parameter and the function. @@ -1552,18 +1555,19 @@ FuncDeclaration buildPostBlit(StructDeclaration sd, Scope* sc) * sd = the `struct` that contains the copy constructor * paramStc = the storage class of the copy constructor parameter * funcStc = the storage class for the copy constructor declaration + * copyCtor = true for copy constructor, false for move constructor * * Returns: * The copy constructor declaration for struct `sd`. */ -private CtorDeclaration generateCopyCtorDeclaration(StructDeclaration sd, const StorageClass paramStc, const StorageClass funcStc) +private CtorDeclaration generateCopyCtorDeclaration(StructDeclaration sd, const StorageClass paramStc, const StorageClass funcStc, bool copyCtor) { auto fparams = new Parameters(); auto structType = sd.type; fparams.push(new Parameter(Loc.initial, paramStc | STC.ref_ | STC.return_ | STC.scope_, structType, Id.p, 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, true); + auto ccd = new CtorDeclaration(sd.loc, Loc.initial, STC.ref_, tf, copyCtor, !copyCtor); ccd.storage_class |= funcStc; ccd.storage_class |= STC.inference; ccd.isGenerated = true; @@ -1726,7 +1730,7 @@ bool buildCopyCtor(StructDeclaration sd, Scope* sc) //printf("generating copy constructor for %s\n", sd.toChars()); const MOD paramMod = MODFlags.wild; const MOD funcMod = MODFlags.wild; - auto ccd = generateCopyCtorDeclaration(sd, ModToStc(paramMod), ModToStc(funcMod)); + auto ccd = generateCopyCtorDeclaration(sd, ModToStc(paramMod), ModToStc(funcMod), true); auto copyCtorBody = generateCopyCtorBody(sd); ccd.fbody = copyCtorBody; sd.members.push(ccd); @@ -1747,3 +1751,209 @@ bool buildCopyCtor(StructDeclaration sd, Scope* sc) } return true; } + +} + +/* ===================================== Move Constructor ========================== */ +static if (1) { + +/** + * Generates a move constructor declaration with the specified storage + * class for the parameter and the function. + * + * Params: + * sd = the `struct` that contains the move constructor + * paramStc = the storage class of the move constructor parameter + * funcStc = the storage class for the move constructor declaration + * + * Returns: + * The move constructor declaration for struct `sd`. + */ +private CtorDeclaration generateMoveCtorDeclaration(StructDeclaration sd, const StorageClass paramStc, const StorageClass funcStc) +{ + /* Although the move constructor is declared as `=this(S s) { ... }`, + * it is implemented as `=this(ref S s) { ... }` + */ + return generateCopyCtorDeclaration(sd, paramStc, funcStc, false); +} + +/** + * Generates a trivial move constructor body that simply does memberwise + * initialization: + * + * this.field1 = rhs.field1; + * this.field2 = rhs.field2; + * ... + * + * Params: + * sd = the `struct` declaration that contains the copy constructor + * + * Returns: + * A `CompoundStatement` containing the body of the copy constructor. + */ +private Statement generateMoveCtorBody(StructDeclaration sd) +{ + Loc loc; + Expression e; + foreach (v; sd.fields) + { + auto ec = new AssignExp(loc, + new DotVarExp(loc, new ThisExp(loc), v), + new DotVarExp(loc, new IdentifierExp(loc, Id.p), v)); + e = Expression.combine(e, ec); + //printf("e.toChars = %s\n", e.toChars()); + } + Statement s1 = new ExpStatement(loc, e); + return new CompoundStatement(loc, s1); +} + +/** + * Determine if a move constructor is needed for struct sd, + * if the following conditions are met: + * + * 1. sd does not define a move constructor + * 2. at least one field of sd defines a move constructor + * + * Params: + * sd = the `struct` for which the move constructor is generated + * hasMoveCtor = set to true if a move constructor is already present + * + * Returns: + * `true` if one needs to be generated + * `false` otherwise + */ +bool needMoveCtor(StructDeclaration sd, out bool hasMoveCtor) +{ + if (global.errors) + return false; + + auto ctor = sd.search(sd.loc, Id.moveCtor); + if (ctor) + { + if (ctor.isOverloadSet()) + return false; + if (auto td = ctor.isTemplateDeclaration()) + ctor = td.funcroot; + } + + CtorDeclaration moveCtor; + CtorDeclaration rvalueCtor; + + if (!ctor) + goto LcheckFields; + + overloadApply(ctor, (Dsymbol s) + { + if (s.isTemplateDeclaration()) + return 0; + auto ctorDecl = s.isCtorDeclaration(); + assert(ctorDecl); + if (ctorDecl.isMoveCtor) + { + if (!moveCtor) + moveCtor = ctorDecl; + return 0; + } + + if (isRvalueConstructor(sd, ctorDecl)) + rvalueCtor = ctorDecl; + return 0; + }); + + if (moveCtor) + { + if (rvalueCtor) + { + .error(sd.loc, "`struct %s` may not define both a rvalue constructor and a move constructor", sd.toChars()); + errorSupplemental(rvalueCtor.loc,"rvalue constructor defined here"); + errorSupplemental(moveCtor.loc, "move constructor defined here"); + } + hasMoveCtor = true; + return false; + } + +LcheckFields: + VarDeclaration fieldWithMoveCtor; + // see if any struct members define a copy constructor + foreach (v; sd.fields) + { + if (v.storage_class & STC.ref_) + continue; + if (v.overlapped) + continue; + + auto ts = v.type.baseElemOf().isTypeStruct(); + if (!ts) + continue; + if (ts.sym.hasMoveCtor) + { + fieldWithMoveCtor = v; + break; + } + } + + if (fieldWithMoveCtor && rvalueCtor) + { + .error(sd.loc, "`struct %s` may not define a rvalue constructor and have fields with move constructors", sd.toChars()); + errorSupplemental(rvalueCtor.loc,"rvalue constructor defined here"); + errorSupplemental(fieldWithMoveCtor.loc, "field with move constructor defined here"); + return false; + } + else if (!fieldWithMoveCtor) + return false; + return true; +} + +/** + * Generates a move constructor if needMoveCtor() returns true. + * The generated move constructor will be of the form: + * this(ref return scope inout(S) rhs) inout + * { + * this.field1 = rhs.field1; + * this.field2 = rhs.field2; + * ... + * } + * + * Params: + * sd = the `struct` for which the copy constructor is generated + * sc = the scope where the copy constructor is generated + * + * Returns: + * `true` if `struct` sd defines a copy constructor (explicitly or generated), + * `false` otherwise. + * References: + * https://dlang.org/spec/struct.html#struct-copy-constructor + */ +bool buildMoveCtor(StructDeclaration sd, Scope* sc) +{ + //printf("buildMoveCtor() %s\n", sd.toChars()); + bool hasMoveCtor; + if (!needMoveCtor(sd, hasMoveCtor)) + return hasMoveCtor; + + //printf("generating move constructor for %s\n", sd.toChars()); + const MOD paramMod = MODFlags.wild; + const MOD funcMod = MODFlags.wild; + auto ccd = generateMoveCtorDeclaration(sd, ModToStc(paramMod), ModToStc(funcMod)); + auto moveCtorBody = generateMoveCtorBody(sd); + ccd.fbody = moveCtorBody; + sd.members.push(ccd); + ccd.addMember(sc, sd); + const errors = global.startGagging(); + Scope* sc2 = sc.push(); + sc2.stc = 0; + sc2.linkage = LINK.d; + ccd.dsymbolSemantic(sc2); + ccd.semantic2(sc2); + ccd.semantic3(sc2); + //printf("ccd semantic: %s\n", ccd.type.toChars()); + sc2.pop(); + if (global.endGagging(errors) || sd.isUnionDeclaration()) + { + ccd.storage_class |= STC.disable; + ccd.fbody = null; + } + return true; +} + +} diff --git a/compiler/src/dmd/dstruct.d b/compiler/src/dmd/dstruct.d index d7b1ace34f0a..f27c972ab4e6 100644 --- a/compiler/src/dmd/dstruct.d +++ b/compiler/src/dmd/dstruct.d @@ -98,9 +98,11 @@ extern (C++) class StructDeclaration : AggregateDeclaration bool zeroInit; // !=0 if initialize with 0 fill bool hasIdentityAssign; // true if has identity opAssign bool hasBlitAssign; // true if opAssign is a blit + bool hasMoveAssign; // true if move assignment bool hasIdentityEquals; // true if has identity opEquals bool hasNoFields; // has no fields bool hasCopyCtor; // copy constructor + bool hasMoveCtor; // move constructor bool hasPointerField; // members with indirections bool hasVoidInitPointers; // void-initialized unsafe fields bool hasUnsafeBitpatterns; // @system members, pointers, bool @@ -303,7 +305,7 @@ extern (C++) class StructDeclaration : AggregateDeclaration * POD is defined as: * $(OL * $(LI not nested) - * $(LI no postblits, destructors, or assignment operators) + * $(LI no postblits, copy constructors, move constructors, destructors, or assignment operators) * $(LI no `ref` fields or fields that are themselves non-POD) * ) * The idea being these are compatible with C structs. @@ -322,10 +324,14 @@ extern (C++) class StructDeclaration : AggregateDeclaration bool hasCpCtorLocal; needCopyCtor(this, hasCpCtorLocal); + bool hasMoveCtorLocal; + needMoveCtor(this, hasMoveCtorLocal); + if (enclosing || // is nested search(this, loc, Id.postblit) || // has postblit search(this, loc, Id.dtor) || // has destructor - hasCpCtorLocal) // has copy constructor + hasCpCtorLocal || // has copy constructor + hasMoveCtorLocal) // has move constructor { ispod = ThreeState.no; return false; diff --git a/compiler/src/dmd/dsymbolsem.d b/compiler/src/dmd/dsymbolsem.d index e7e054930cc1..f79a6d9b2c1a 100644 --- a/compiler/src/dmd/dsymbolsem.d +++ b/compiler/src/dmd/dsymbolsem.d @@ -555,6 +555,7 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor override void visit(VarDeclaration dsym) { + //printf("VarDeclaration %s\n", dsym.toChars()); version (none) { printf("VarDeclaration::semantic('%s', parent = '%s') sem = %d\n", @@ -2400,7 +2401,7 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor override void visit(CtorDeclaration ctd) { - //printf("CtorDeclaration::semantic() %s\n", toChars()); + //printf("CtorDeclaration::semantic() %s\n", ctd.toChars()); if (ctd.semanticRun >= PASS.semanticdone) return; if (ctd._scope) @@ -2440,12 +2441,14 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor return; TypeFunction tf = ctd.type.toTypeFunction(); + //printf("tf: %s\n", tf.toChars()); immutable dim = tf.parameterList.length; auto sd = ad.isStructDeclaration(); /* See if it's the default constructor * But, template constructor should not become a default constructor. */ + bool moveCtor = false; // assume it is not if (ad && (!ctd.parent.isTemplateInstance() || ctd.parent.isTemplateMixin())) { if (!sd) @@ -2481,14 +2484,21 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor .error(ctd.loc, "%s `%s` all parameters have default arguments, "~ "but structs cannot have default constructors.", ctd.kind, ctd.toPrettyChars); } - else if ((dim == 1 || (dim > 1 && tf.parameterList[1].defaultArg))) + else if (dim == 1 || (dim > 1 && tf.parameterList[1].defaultArg)) { - //printf("tf: %s\n", tf.toChars()); auto param = tf.parameterList[0]; - if (param.storageClass & STC.ref_ && param.type.mutableOf().unSharedOf() == sd.type.mutableOf().unSharedOf()) + if (param.type.mutableOf().unSharedOf() == sd.type.mutableOf().unSharedOf()) { - //printf("copy constructor\n"); - ctd.isCpCtor = true; + if (param.storageClass & STC.ref_) + { + //printf("found copy constructor\n"); + ctd.isCpCtor = true; + } + else + { + //printf("found move constructor\n"); + moveCtor = true; + } } } } @@ -2497,6 +2507,18 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor { checkHasBothRvalueAndCpCtor(sd, ctd, ti); } + + if (moveCtor != ctd.isMoveCtor) + { + if (moveCtor) + { + // Just accept this for now as there is existing code with rvalue constructors, + // such as std.typecons.Ternary + //.error(ctd.loc, "move constructor `%s` should be declared as `=this`", ctd.toPrettyChars()); + } + else + .error(ctd.loc, "first parameter to move constructor should be type struct `%s`", sd.toPrettyChars()); + } } override void visit(PostBlitDeclaration pbd) @@ -2980,6 +3002,7 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor buildDtors(sd, sc2); sd.hasCopyCtor = buildCopyCtor(sd, sc2); + sd.hasMoveCtor = buildMoveCtor(sd, sc2); sd.postblit = buildPostBlit(sd, sc2); buildOpAssign(sd, sc2); @@ -3624,7 +3647,7 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor // This is required if other lowerings add code to the generated constructor which // is less strict (e.g. `preview=dtorfields` might introduce a call to a less qualified dtor) - auto ctor = new CtorDeclaration(cldec.loc, Loc.initial, 0, tf); + auto ctor = new CtorDeclaration(cldec.loc, Loc.initial, 0, tf, false, false); ctor.storage_class |= STC.inference | (fd.storage_class & STC.scope_); ctor.isGenerated = true; ctor.fbody = new CompoundStatement(Loc.initial, new Statements()); diff --git a/compiler/src/dmd/e2ir.d b/compiler/src/dmd/e2ir.d index 430644f3fda4..6f91fef3c938 100644 --- a/compiler/src/dmd/e2ir.d +++ b/compiler/src/dmd/e2ir.d @@ -5510,6 +5510,12 @@ elem *callfunc(const ref Loc loc, bool copy = !(v && v.isArgDtorVar); // copy unless the destructor is going to be run on it // then assume the frontend took care of the copying and pass it by ref + if (0 && ea.Eoper == OPind && ea.E1.Eoper == OPcall && arg.type.isTypeStruct()) + { + if (auto ctor = fd.isCtorDeclaration()) + copy = ctor.isCpCtor; + } + elems[i] = addressElem(ea, arg.type, copy); continue; } diff --git a/compiler/src/dmd/expressionsem.d b/compiler/src/dmd/expressionsem.d index 8f748ebf9a6e..23588d658d0a 100644 --- a/compiler/src/dmd/expressionsem.d +++ b/compiler/src/dmd/expressionsem.d @@ -834,6 +834,7 @@ extern(D) bool arrayExpressionSemantic( */ extern (D) Expression doCopyOrMove(Scope *sc, Expression e, Type t = null) { + //printf("doCopyOrMove() %s\n", toChars(e)); if (auto ce = e.isCondExp()) { ce.e1 = doCopyOrMove(sc, ce.e1); @@ -857,6 +858,7 @@ extern (D) Expression doCopyOrMove(Scope *sc, Expression e, Type t = null) */ private Expression callCpCtor(Scope* sc, Expression e, Type destinationType) { + //printf("callCpCtor() %s\n", toChars(e)); auto ts = e.type.baseElemOf().isTypeStruct(); if (!ts) @@ -899,6 +901,7 @@ private Expression callCpCtor(Scope* sc, Expression e, Type destinationType) */ Expression valueNoDtor(Expression e) { + //printf("valueNoDtor()\n"); auto ex = lastComma(e); if (auto ce = ex.isCallExp()) @@ -10940,6 +10943,21 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor return; } } + else if (sd.hasMoveCtor) + { + /* Rewrite as: + * e1.moveCtor(e2); + */ + Expression e; + e = new DotIdExp(exp.loc, e1x, Id.moveCtor); + e = new CallExp(exp.loc, e, e2x); + + //printf("e: %s\n", e.toChars()); + + result = e.expressionSemantic(sc); + //printf("result: %s\n", e.toChars()); + return; + } else { /* The struct value returned from the function is transferred @@ -12258,7 +12276,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor } if (tb1.ty == Tpointer && tb2.ty == Tpointer || - tb1.ty == Tnull && tb2.ty == Tnull) + tb1.ty == Tnull && tb2.ty == Tnull) { result = exp.incompatibleTypes(); return; diff --git a/compiler/src/dmd/frontend.h b/compiler/src/dmd/frontend.h index e3faee687fe5..570587785554 100644 --- a/compiler/src/dmd/frontend.h +++ b/compiler/src/dmd/frontend.h @@ -3792,6 +3792,7 @@ class CtorDeclaration final : public FuncDeclaration { public: bool isCpCtor; + bool isMoveCtor; CtorDeclaration* syntaxCopy(Dsymbol* s) override; const char* kind() const override; const char* toChars() const override; @@ -7256,12 +7257,16 @@ class StructDeclaration : public AggregateDeclaration bool hasIdentityAssign(bool v); bool hasBlitAssign() const; bool hasBlitAssign(bool v); + bool hasMoveAssign() const; + bool hasMoveAssign(bool v); bool hasIdentityEquals() const; bool hasIdentityEquals(bool v); bool hasNoFields() const; bool hasNoFields(bool v); bool hasCopyCtor() const; bool hasCopyCtor(bool v); + bool hasMoveCtor() const; + bool hasMoveCtor(bool v); bool hasPointerField() const; bool hasPointerField(bool v); bool hasVoidInitPointers() const; @@ -8576,6 +8581,7 @@ struct Id final static Identifier* This; static Identifier* _super; static Identifier* ctor; + static Identifier* moveCtor; static Identifier* dtor; static Identifier* cppdtor; static Identifier* ticppdtor; diff --git a/compiler/src/dmd/func.d b/compiler/src/dmd/func.d index 2002237133b9..3f4431dc292b 100644 --- a/compiler/src/dmd/func.d +++ b/compiler/src/dmd/func.d @@ -1365,30 +1365,35 @@ extern (C++) final class FuncLiteralDeclaration : FuncDeclaration */ extern (C++) final class CtorDeclaration : FuncDeclaration { - bool isCpCtor; - extern (D) this(const ref Loc loc, const ref Loc endloc, StorageClass stc, Type type, bool isCpCtor = false) + bool isCpCtor; // it's a copy constructor + bool isMoveCtor; // it's a move constructor + + extern (D) this(const ref Loc loc, const ref Loc endloc, StorageClass stc, Type type, bool isCpCtor, bool isMoveCtor) { - super(loc, endloc, Id.ctor, stc, type); - this.isCpCtor = isCpCtor; + super(loc, endloc, isMoveCtor ? Id.moveCtor : Id.ctor, stc, type); + this.isCpCtor = isCpCtor; + this.isMoveCtor = isMoveCtor; //printf("CtorDeclaration(loc = %s) %s %p\n", loc.toChars(), toChars(), this); } override CtorDeclaration syntaxCopy(Dsymbol s) { assert(!s); - auto f = new CtorDeclaration(loc, endloc, storage_class, type.syntaxCopy()); + auto f = new CtorDeclaration(loc, endloc, storage_class, type.syntaxCopy(), isCpCtor, isMoveCtor); FuncDeclaration.syntaxCopy(f); return f; } override const(char)* kind() const { - return isCpCtor ? "copy constructor" : "constructor"; + return isCpCtor ? "copy constructor" : + isMoveCtor ? "move constructor" : + "constructor"; } override const(char)* toChars() const { - return "this"; + return isMoveCtor ? "=this" : "this"; } override bool isVirtual() const diff --git a/compiler/src/dmd/hdrgen.d b/compiler/src/dmd/hdrgen.d index 8c4f0e4355c7..55cb59e0ce5c 100644 --- a/compiler/src/dmd/hdrgen.d +++ b/compiler/src/dmd/hdrgen.d @@ -1965,7 +1965,10 @@ void toCBuffer(Dsymbol s, ref OutBuffer buf, ref HdrGenState hgs) public void toCharsMaybeConstraints(const TemplateDeclaration td, ref OutBuffer buf, ref HdrGenState hgs) { - buf.writestring(td.ident == Id.ctor ? "this" : td.ident.toString()); + auto id = td.ident == Id.ctor ? "this" : + td.ident == Id.moveCtor ? "=this" : + td.ident.toString(); + buf.writestring(id); buf.writeByte('('); foreach (i, const tp; *td.parameters) { @@ -3941,7 +3944,7 @@ private void visitFuncIdentWithPrefix(TypeFunction t, const Identifier ident, Te if (str != "return" && str != "scope") { // don't write 'ref' for ctors - if ((ident == Id.ctor) && str == "ref") + if ((ident == Id.ctor || ident == Id.moveCtor) && str == "ref") return; buf.writestring(str); buf.writeByte(' '); diff --git a/compiler/src/dmd/id.d b/compiler/src/dmd/id.d index f676361d958b..3e58c7fb026d 100644 --- a/compiler/src/dmd/id.d +++ b/compiler/src/dmd/id.d @@ -71,6 +71,7 @@ immutable Msgtable[] msgtable = { "This", "this" }, { "_super", "super" }, { "ctor", "__ctor" }, + { "moveCtor", "__moveCtor" }, { "dtor", "__dtor" }, { "__xdtor", "__xdtor" }, { "__fieldDtor", "__fieldDtor" }, diff --git a/compiler/src/dmd/identifier.d b/compiler/src/dmd/identifier.d index 6fd0d3ad5ec6..2b0e3cac3a48 100644 --- a/compiler/src/dmd/identifier.d +++ b/compiler/src/dmd/identifier.d @@ -108,6 +108,8 @@ nothrow: const(char)* p = null; if (this == Id.ctor) p = "this"; + else if (this == Id.moveCtor) + p = "=this"; else if (this == Id.dtor) p = "~this"; else if (this == Id.unitTest) diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index 2285f9ae852a..0bb0a31433f0 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -441,7 +441,15 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer case TOK.this_: if (peekNext() == TOK.dot) goto Ldeclaration; - s = parseCtor(pAttrs); + s = parseCtor(pAttrs, false); + break; + + case TOK.assign: + if (peekNext() == TOK.this_) + { + nextToken(); + s = parseCtor(pAttrs, true); + } break; case TOK.tilde: @@ -2428,7 +2436,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer * this(templateparameters)(parameters) { body } * Current token is 'this'. */ - private AST.Dsymbol parseCtor(PrefixAttributes!AST* pAttrs) + private AST.Dsymbol parseCtor(PrefixAttributes!AST* pAttrs, bool isMoveCtor) { AST.Expressions* udas = null; const loc = token.loc; @@ -2496,7 +2504,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer AST.Type tf = new AST.TypeFunction(parameterList, null, linkage, stc); // ReturnType -> auto tf = tf.addSTC(stc); - auto f = new AST.CtorDeclaration(loc, Loc.initial, stc, tf); + auto f = new AST.CtorDeclaration(loc, Loc.initial, stc, tf, false, isMoveCtor); AST.Dsymbol s = parseContracts(f, !!tpl); if (udas) { diff --git a/compiler/src/dmd/templatesem.d b/compiler/src/dmd/templatesem.d index 1358896585c1..6eef1b1685fe 100644 --- a/compiler/src/dmd/templatesem.d +++ b/compiler/src/dmd/templatesem.d @@ -1690,8 +1690,8 @@ FuncDeclaration doHeaderInstantiation(TemplateDeclaration td, TemplateInstance t } // function body and contracts are not need - if (fd.isCtorDeclaration()) - fd = new CtorDeclaration(fd.loc, fd.endloc, fd.storage_class, fd.type.syntaxCopy()); + if (auto ctor = fd.isCtorDeclaration()) + fd = new CtorDeclaration(fd.loc, fd.endloc, fd.storage_class, fd.type.syntaxCopy(), ctor.isCpCtor, ctor.isMoveCtor); else fd = new FuncDeclaration(fd.loc, fd.endloc, fd.ident, fd.storage_class, fd.type.syntaxCopy()); fd.parent = ti; diff --git a/compiler/src/dmd/traits.d b/compiler/src/dmd/traits.d index e065975c67a8..466c81dfceb1 100644 --- a/compiler/src/dmd/traits.d +++ b/compiler/src/dmd/traits.d @@ -1653,7 +1653,7 @@ Expression semanticTraits(TraitsExp e, Scope* sc) // https://issues.dlang.org/show_bug.cgi?id=10096 // https://issues.dlang.org/show_bug.cgi?id=10100 // Skip over internal members in __traits(allMembers) - if ((sm.isCtorDeclaration() && sm.ident != Id.ctor) || + if ((sm.isCtorDeclaration() && sm.ident != Id.ctor && sm.ident != Id.moveCtor) || (sm.isDtorDeclaration() && sm.ident != Id.dtor) || (sm.isPostBlitDeclaration() && sm.ident != Id.postblit) || sm.isInvariantDeclaration() || diff --git a/compiler/src/dmd/typesem.d b/compiler/src/dmd/typesem.d index c6306021c8cf..27f49a4eee99 100644 --- a/compiler/src/dmd/typesem.d +++ b/compiler/src/dmd/typesem.d @@ -884,6 +884,7 @@ extern (D) MATCH callMatch(TypeFunction tf, Type tthis, ArgumentList argumentLis private extern(D) bool isCopyConstructorCallable (StructDeclaration argStruct, Expression arg, Type tprm, Scope* sc, const(char)** pMessage) { + //printf("isCopyConstructorCallable() %s, %s, %s,\n", argStruct.toChars(), toChars(arg), toChars(tprm)); auto tmp = new VarDeclaration(arg.loc, tprm, Identifier.generateId("__copytmp"), null); tmp.storage_class = STC.rvalue | STC.temp | STC.ctfe; tmp.dsymbolSemantic(sc); @@ -891,8 +892,11 @@ private extern(D) bool isCopyConstructorCallable (StructDeclaration argStruct, Expression e = new DotIdExp(arg.loc, ve, Id.ctor); e = new CallExp(arg.loc, e, arg); //printf("e = %s\n", e.toChars()); + if (dmd.expressionsem.trySemantic(e, sc)) + { return true; + } if (!pMessage) return false; @@ -970,7 +974,7 @@ private extern(D) bool isCopyConstructorCallable (StructDeclaration argStruct, * * This function is called by `TypeFunction.callMatch` while iterating over * the list of parameter. Here we check if `arg` is a match for `p`, - * which is mostly about checking if `arg.type` converts to `p`'s type + * which is mostly about checking if `arg.type` converts to the type of `p` * and some check about value reference. * * Params: @@ -978,7 +982,7 @@ private extern(D) bool isCopyConstructorCallable (StructDeclaration argStruct, * p = The parameter of `tf` being matched * arg = Argument being passed (bound) to `p` * wildmatch = Wild (`inout`) matching level, derived from the full argument list - * flag = A non-zero value means we're doing a partial ordering check + * flag = A non-zero value means we are doing a partial ordering check * (no value semantic check) * sc = Scope we are in * pMessage = A buffer to write the error in, or `null` @@ -988,11 +992,15 @@ private extern(D) bool isCopyConstructorCallable (StructDeclaration argStruct, private extern(D) MATCH argumentMatchParameter (TypeFunction tf, Parameter p, Expression arg, ubyte wildmatch, int flag, Scope* sc, const(char)** pMessage) { - //printf("arg: %s, type: %s\n", arg.toChars(), arg.type.toChars()); + //printf("argumentMatchParameter() flag: %d arg: %s, type: %s\n", flag, arg.toChars(), (arg.type ? arg.type.toChars() : "null".ptr)); MATCH m; Type targ = arg.type; Type tprm = wildmatch ? p.type.substWildTo(wildmatch) : p.type; + bool hasMoveCtor; + if (auto ts = targ.isTypeStruct()) + hasMoveCtor = ts.sym.hasMoveCtor; + if (p.isLazy() && tprm.ty == Tvoid && targ.ty != Tvoid) m = MATCH.convert; else if (flag) @@ -1016,9 +1024,10 @@ private extern(D) MATCH argumentMatchParameter (TypeFunction tf, Parameter p, // check if the copy constructor may be called to copy the argument if (argStruct && argStruct == prmStruct && argStruct.hasCopyCtor) { - if (!isCopyConstructorCallable(argStruct, arg, tprm, sc, pMessage)) + if (hasMoveCtor || isCopyConstructorCallable(argStruct, arg, tprm, sc, pMessage)) + m = MATCH.exact; + else return MATCH.nomatch; - m = MATCH.exact; } else { @@ -1077,20 +1086,21 @@ private extern(D) MATCH argumentMatchParameter (TypeFunction tf, Parameter p, // Need to make this a rvalue through a temporary m = MATCH.convert; } - else if (global.params.rvalueRefParam != FeatureState.enabled || - p.storageClass & STC.out_ || - !arg.type.isCopyable()) // can't copy to temp for ref parameter - { - if (pMessage) *pMessage = tf.getParamError(arg, p); - return MATCH.nomatch; - } - else + else if (global.params.rvalueRefParam == FeatureState.enabled && + !hasMoveCtor && + arg.type.isCopyable()) { /* in functionParameters() we'll convert this * rvalue into a temporary */ m = MATCH.convert; } + else + { + // can't copy to temp for ref parameter + if (pMessage) *pMessage = tf.getParamError(arg, p); + return MATCH.nomatch; + } } /* If the match is not already perfect or if the arg @@ -4884,6 +4894,7 @@ Expression dotExp(Type mt, Scope* sc, Expression e, Identifier ident, DotExpFlag // https://issues.dlang.org/show_bug.cgi?id=15045 // Don't forward special built-in member functions. ident != Id.ctor && + ident != Id.moveCtor && ident != Id.dtor && ident != Id.__xdtor && ident != Id.postblit && diff --git a/compiler/test/fail_compilation/movctor.d b/compiler/test/fail_compilation/movctor.d new file mode 100644 index 000000000000..a2234fc4790b --- /dev/null +++ b/compiler/test/fail_compilation/movctor.d @@ -0,0 +1,10 @@ +/* TEST_OUTPUT: +--- +fail_compilation/movctor.d(9): Error: first parameter to move constructor should be type struct `movctor.S` +--- +*/ + +struct S +{ + =this(ref S s); +}