/** * Builds struct member functions if needed and not defined by the user. * Includes `opEquals`, `opAssign`, post blit, copy constructor and destructor. * * Copyright: Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/clone.d, _clone.d) * Documentation: https://dlang.org/phobos/dmd_clone.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/clone.d */ module dmd.clone; import core.stdc.stdio; import dmd.aggregate; import dmd.arraytypes; import dmd.astenums; import dmd.dclass; import dmd.declaration; import dmd.dscope; import dmd.dstruct; import dmd.dsymbol; import dmd.dsymbolsem; import dmd.dtemplate; import dmd.errors; import dmd.expression; import dmd.expressionsem; import dmd.func; import dmd.globals; import dmd.id; import dmd.identifier; import dmd.init; import dmd.mtype; import dmd.opover; import dmd.semantic2; import dmd.semantic3; import dmd.statement; import dmd.target; import dmd.typesem; import dmd.tokens; /******************************************* * Merge function attributes pure, nothrow, @safe, @nogc, and @disable * from f into s1. * Params: * s1 = storage class to merge into * f = function * Returns: * merged storage class */ StorageClass mergeFuncAttrs(StorageClass s1, const FuncDeclaration f) pure { if (!f) return s1; StorageClass s2 = (f.storage_class & STC.disable); TypeFunction tf = cast(TypeFunction)f.type; if (tf.trust == TRUST.safe) s2 |= STC.safe; else if (tf.trust == TRUST.system) s2 |= STC.system; else if (tf.trust == TRUST.trusted) s2 |= STC.trusted; if (tf.purity != PURE.impure) s2 |= STC.pure_; if (tf.isnothrow) s2 |= STC.nothrow_; if (tf.isnogc) s2 |= STC.nogc; const sa = s1 & s2; const so = s1 | s2; StorageClass stc = (sa & (STC.pure_ | STC.nothrow_ | STC.nogc)) | (so & STC.disable); if (so & STC.system) stc |= STC.system; else if (sa & STC.trusted) stc |= STC.trusted; else if ((so & (STC.trusted | STC.safe)) == (STC.trusted | STC.safe)) stc |= STC.trusted; else if (sa & STC.safe) stc |= STC.safe; return stc; } /******************************************* * Check given aggregate actually has an identity opAssign or not. * Params: * ad = struct or class * sc = current scope * Returns: * if found, returns FuncDeclaration of opAssign, otherwise null */ FuncDeclaration hasIdentityOpAssign(AggregateDeclaration ad, Scope* sc) { Dsymbol assign = search_function(ad, Id.assign); if (assign) { /* check identity opAssign exists */ scope er = new NullExp(ad.loc, ad.type); // dummy rvalue scope el = new IdentifierExp(ad.loc, Id.p); // dummy lvalue el.type = ad.type; Expressions a; a.setDim(1); const errors = global.startGagging(); // Do not report errors, even if the template opAssign fbody makes it. sc = sc.push(); sc.tinst = null; sc.minst = null; a[0] = er; auto f = resolveFuncCall(ad.loc, sc, assign, null, ad.type, &a, FuncResolveFlag.quiet); if (!f) { a[0] = el; f = resolveFuncCall(ad.loc, sc, assign, null, ad.type, &a, FuncResolveFlag.quiet); } sc = sc.pop(); global.endGagging(errors); if (f) { if (f.errors) return null; auto fparams = f.getParameterList(); if (fparams.length) { auto fparam0 = fparams[0]; if (fparam0.type.toDsymbol(null) != ad) f = null; } } // BUGS: This detection mechanism cannot find some opAssign-s like follows: // struct S { void opAssign(ref immutable S) const; } return f; } return null; } /******************************************* * We need an opAssign for the struct if * it has a destructor or a postblit. * We need to generate one if a user-specified one does not exist. */ private bool needOpAssign(StructDeclaration sd) { //printf("StructDeclaration::needOpAssign() %s\n", sd.toChars()); static bool isNeeded() { //printf("\tneed\n"); return true; } if (sd.isUnionDeclaration()) return !isNeeded(); if (sd.hasIdentityAssign || // because has identity==elaborate opAssign sd.dtor || sd.postblit) return isNeeded(); /* If any of the fields need an opAssign, then we * need it too. */ foreach (v; sd.fields) { if (v.storage_class & STC.ref_) continue; if (v.overlapped) // if field of a union continue; // user must handle it themselves Type tv = v.type.baseElemOf(); if (tv.ty == Tstruct) { TypeStruct ts = cast(TypeStruct)tv; if (ts.sym.isUnionDeclaration()) continue; if (needOpAssign(ts.sym)) return isNeeded(); } } return !isNeeded(); } /****************************************** * Build opAssign for a `struct`. * * The generated `opAssign` function has the following signature: *--- *ref S opAssign(S s) // S is the name of the `struct` *--- * * The opAssign function will be built for a struct `S` if the * following constraints are met: * * 1. `S` does not have an identity `opAssign` defined. * * 2. `S` has at least one of the following members: a postblit (user-defined or * generated for fields that have a defined postblit), a destructor * (user-defined or generated for fields that have a defined destructor) * or at least one field that has a defined `opAssign`. * * 3. `S` does not have any non-mutable fields. * * If `S` has a disabled destructor or at least one field that has a disabled * `opAssign`, `S.opAssign` is going to be generated, but marked with `@disable` * * If `S` defines a destructor, the generated code for `opAssign` is: * *--- *S __swap = void; *__swap = this; // bit copy *this = s; // bit copy *__swap.dtor(); *--- * * Otherwise, if `S` defines a postblit, the generated code for `opAssign` is: * *--- *this = s; *--- * * Note that the parameter to the generated `opAssign` is passed by value, which means * that the postblit is going to be called (if it is defined) in both of the above * situations before entering the body of `opAssign`. The assignments in the above generated * function bodies are blit expressions, so they can be regarded as `memcpy`s * (`opAssign` is not called as this will result in an infinite recursion; the postblit * is not called because it has already been called when the parameter was passed by value). * * If `S` does not have a postblit or a destructor, but contains at least one field that defines * an `opAssign` function (which is not disabled), then the body will make member-wise * assignments: * *--- *this.field1 = s.field1; *this.field2 = s.field2; *...; *--- * * In this situation, the assignemnts are actual assign expressions (`opAssign` is used * if defined). * * References: * https://dlang.org/spec/struct.html#assign-overload * Params: * sd = struct to generate opAssign for * sc = context * Returns: * generated `opAssign` function */ FuncDeclaration buildOpAssign(StructDeclaration sd, Scope* sc) { if (FuncDeclaration f = hasIdentityOpAssign(sd, sc)) { sd.hasIdentityAssign = true; return f; } // Even if non-identity opAssign is defined, built-in identity opAssign // will be defined. if (!needOpAssign(sd)) return null; //printf("StructDeclaration::buildOpAssign() %s\n", sd.toChars()); StorageClass stc = STC.safe | STC.nothrow_ | STC.pure_ | STC.nogc; Loc declLoc = sd.loc; Loc loc; // internal code should have no loc to prevent coverage // One of our sub-field might have `@disable opAssign` so we need to // check for it. // In this event, it will be reflected by having `stc` (opAssign's // storage class) include `STC.disabled`. foreach (v; sd.fields) { if (v.storage_class & STC.ref_) continue; if (v.overlapped) continue; Type tv = v.type.baseElemOf(); if (tv.ty != Tstruct) continue; StructDeclaration sdv = (cast(TypeStruct)tv).sym; stc = mergeFuncAttrs(stc, hasIdentityOpAssign(sdv, sc)); } if (sd.dtor || sd.postblit) { // if the type is not assignable, we cannot generate opAssign if (!sd.type.isAssignable()) // https://issues.dlang.org/show_bug.cgi?id=13044 return null; stc = mergeFuncAttrs(stc, sd.dtor); if (stc & STC.safe) stc = (stc & ~STC.safe) | STC.trusted; } auto fparams = new Parameters(); fparams.push(new Parameter(STC.nodtor, sd.type, Id.p, null, null)); auto tf = new TypeFunction(ParameterList(fparams), sd.handleType(), LINK.d, stc | STC.ref_); auto fop = new FuncDeclaration(declLoc, Loc.initial, Id.assign, stc, tf); fop.storage_class |= STC.inference; fop.flags |= FUNCFLAG.generated; Expression e; if (stc & STC.disable) { e = null; } /* Do swap this and rhs. * __swap = this; this = s; __swap.dtor(); */ else if (sd.dtor) { //printf("\tswap copy\n"); TypeFunction tdtor = cast(TypeFunction)sd.dtor.type; assert(tdtor.ty == Tfunction); auto idswap = Identifier.generateId("__swap"); auto swap = new VarDeclaration(loc, sd.type, idswap, new VoidInitializer(loc)); swap.storage_class |= STC.nodtor | STC.temp | STC.ctfe; if (tdtor.isScopeQual) swap.storage_class |= STC.scope_; auto e1 = new DeclarationExp(loc, swap); auto e2 = new BlitExp(loc, new VarExp(loc, swap), new ThisExp(loc)); auto e3 = new BlitExp(loc, new ThisExp(loc), new IdentifierExp(loc, Id.p)); /* Instead of running the destructor on s, run it * on swap. This avoids needing to copy swap back in to s. */ auto e4 = new CallExp(loc, new DotVarExp(loc, new VarExp(loc, swap), sd.dtor, false)); e = Expression.combine(e1, e2, e3, e4); } /* postblit was called when the value was passed to opAssign, we just need to blit the result */ else if (sd.postblit) { e = new BlitExp(loc, new ThisExp(loc), new IdentifierExp(loc, Id.p)); sd.hasBlitAssign = true; } else { /* Do memberwise copy. * * If sd is a nested struct, its vthis field assignment is: * 1. If it's nested in a class, it's a rebind of class reference. * 2. If it's nested in a function or struct, it's an update of void*. * In both cases, it will change the parent context. */ //printf("\tmemberwise copy\n"); e = null; foreach (v; sd.fields) { // this.v = s.v; 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); } } if (e) { Statement s1 = new ExpStatement(loc, e); /* Add: * return this; */ auto er = new ThisExp(loc); Statement s2 = new ReturnStatement(loc, er); fop.fbody = new CompoundStatement(loc, s1, s2); tf.isreturn = true; } sd.members.push(fop); fop.addMember(sc, sd); sd.hasIdentityAssign = true; // temporary mark identity assignable const errors = global.startGagging(); // Do not report errors, even if the template opAssign fbody makes it. Scope* sc2 = sc.push(); sc2.stc = 0; sc2.linkage = LINK.d; fop.dsymbolSemantic(sc2); fop.semantic2(sc2); // https://issues.dlang.org/show_bug.cgi?id=15044 //semantic3(fop, sc2); // isn't run here for lazy forward reference resolution. sc2.pop(); if (global.endGagging(errors)) // if errors happened { // Disable generated opAssign, because some members forbid identity assignment. fop.storage_class |= STC.disable; fop.fbody = null; // remove fbody which contains the error } //printf("-StructDeclaration::buildOpAssign() %s, errors = %d\n", sd.toChars(), (fop.storage_class & STC.disable) != 0); //printf("fop.type: %s\n", fop.type.toPrettyChars()); return fop; } /******************************************* * We need an opEquals for the struct if * any fields has an opEquals. * Generate one if a user-specified one does not exist. */ bool needOpEquals(StructDeclaration sd) { //printf("StructDeclaration::needOpEquals() %s\n", sd.toChars()); if (sd.isUnionDeclaration()) goto Ldontneed; if (sd.hasIdentityEquals) goto Lneed; /* If any of the fields has an opEquals, then we * need it too. */ foreach (VarDeclaration v; sd.fields) { if (v.storage_class & STC.ref_) continue; if (v.overlapped) continue; Type tv = v.type.toBasetype(); auto tvbase = tv.baseElemOf(); if (tvbase.ty == Tstruct) { TypeStruct ts = cast(TypeStruct)tvbase; if (ts.sym.isUnionDeclaration()) continue; if (needOpEquals(ts.sym)) goto Lneed; } if (tvbase.isfloating()) { // This is necessray for: // 1. comparison of +0.0 and -0.0 should be true. // 2. comparison of NANs should be false always. goto Lneed; } if (tvbase.ty == Tarray) goto Lneed; if (tvbase.ty == Taarray) goto Lneed; if (tvbase.ty == Tclass) goto Lneed; } Ldontneed: //printf("\tdontneed\n"); return false; Lneed: //printf("\tneed\n"); return true; } /******************************************* * Check given aggregate actually has an identity opEquals or not. */ private FuncDeclaration hasIdentityOpEquals(AggregateDeclaration ad, Scope* sc) { FuncDeclaration f; if (Dsymbol eq = search_function(ad, Id.eq)) { /* check identity opEquals exists */ scope er = new NullExp(ad.loc, null); // dummy rvalue scope el = new IdentifierExp(ad.loc, Id.p); // dummy lvalue Expressions a; a.setDim(1); bool hasIt(Type tthis) { const errors = global.startGagging(); // Do not report errors, even if the template opAssign fbody makes it sc = sc.push(); sc.tinst = null; sc.minst = null; FuncDeclaration rfc(Expression e) { a[0] = e; a[0].type = tthis; return resolveFuncCall(ad.loc, sc, eq, null, tthis, &a, FuncResolveFlag.quiet); } f = rfc(er); if (!f) f = rfc(el); sc = sc.pop(); global.endGagging(errors); return f !is null; } if (hasIt(ad.type) || hasIt(ad.type.constOf()) || hasIt(ad.type.immutableOf()) || hasIt(ad.type.sharedOf()) || hasIt(ad.type.sharedConstOf())) { if (f.errors) return null; } } return f; } /****************************************** * Build opEquals for struct. * const bool opEquals(const S s) { ... } * * By fixing https://issues.dlang.org/show_bug.cgi?id=3789 * opEquals is changed to be never implicitly generated. * Now, struct objects comparison s1 == s2 is translated to: * s1.tupleof == s2.tupleof * to calculate structural equality. See EqualExp.op_overload. */ FuncDeclaration buildOpEquals(StructDeclaration sd, Scope* sc) { if (hasIdentityOpEquals(sd, sc)) { sd.hasIdentityEquals = true; } return null; } /****************************************** * Build __xopEquals for TypeInfo_Struct * bool __xopEquals(ref const S p) const * { * return this == p; * } * * This is called by TypeInfo.equals(p1, p2). If the struct does not support * const objects comparison, it will throw "not implemented" Error in runtime. */ FuncDeclaration buildXopEquals(StructDeclaration sd, Scope* sc) { if (!needOpEquals(sd)) return null; // bitwise comparison would work //printf("StructDeclaration::buildXopEquals() %s\n", sd.toChars()); if (Dsymbol eq = search_function(sd, Id.eq)) { if (FuncDeclaration fd = eq.isFuncDeclaration()) { TypeFunction tfeqptr; { Scope scx; /* const bool opEquals(ref const S s); */ auto parameters = new Parameters(); parameters.push(new Parameter(STC.ref_ | STC.const_, sd.type, null, null, null)); tfeqptr = new TypeFunction(ParameterList(parameters), Type.tbool, LINK.d); tfeqptr.mod = MODFlags.const_; tfeqptr = cast(TypeFunction)tfeqptr.typeSemantic(Loc.initial, &scx); } fd = fd.overloadExactMatch(tfeqptr); if (fd) return fd; } } if (!sd.xerreq) { // object._xopEquals Identifier id = Identifier.idPool("_xopEquals"); Expression e = new IdentifierExp(sd.loc, Id.empty); e = new DotIdExp(sd.loc, e, Id.object); e = new DotIdExp(sd.loc, e, id); e = e.expressionSemantic(sc); if (!e.isErrorExp()) { Dsymbol s = getDsymbol(e); assert(s); sd.xerreq = s.isFuncDeclaration(); } } Loc declLoc; // loc is unnecessary so __xopEquals is never called directly Loc loc; // loc is unnecessary so errors are gagged auto parameters = new Parameters(); parameters.push(new Parameter(STC.ref_ | STC.const_, sd.type, Id.p, null, null)); auto tf = new TypeFunction(ParameterList(parameters), Type.tbool, LINK.d, STC.const_); tf = tf.addSTC(STC.const_).toTypeFunction(); Identifier id = Id.xopEquals; auto fop = new FuncDeclaration(declLoc, Loc.initial, id, 0, tf); fop.flags |= FUNCFLAG.generated; fop.parent = sd; Expression e1 = new IdentifierExp(loc, Id.This); Expression e2 = new IdentifierExp(loc, Id.p); Expression e = new EqualExp(EXP.equal, loc, e1, e2); fop.fbody = new ReturnStatement(loc, e); uint errors = global.startGagging(); // Do not report errors Scope* sc2 = sc.push(); sc2.stc = 0; sc2.linkage = LINK.d; fop.dsymbolSemantic(sc2); fop.semantic2(sc2); sc2.pop(); if (global.endGagging(errors)) // if errors happened fop = sd.xerreq; return fop; } /****************************************** * Build __xopCmp for TypeInfo_Struct * int __xopCmp(ref const S p) const * { * return this.opCmp(p); * } * * This is called by TypeInfo.compare(p1, p2). If the struct does not support * const objects comparison, it will throw "not implemented" Error in runtime. */ FuncDeclaration buildXopCmp(StructDeclaration sd, Scope* sc) { //printf("StructDeclaration::buildXopCmp() %s\n", toChars()); if (Dsymbol cmp = search_function(sd, Id.cmp)) { if (FuncDeclaration fd = cmp.isFuncDeclaration()) { TypeFunction tfcmpptr; { Scope scx; /* const int opCmp(ref const S s); */ auto parameters = new Parameters(); parameters.push(new Parameter(STC.ref_ | STC.const_, sd.type, null, null, null)); tfcmpptr = new TypeFunction(ParameterList(parameters), Type.tint32, LINK.d); tfcmpptr.mod = MODFlags.const_; tfcmpptr = cast(TypeFunction)tfcmpptr.typeSemantic(Loc.initial, &scx); } fd = fd.overloadExactMatch(tfcmpptr); if (fd) return fd; } } else { version (none) // FIXME: doesn't work for recursive alias this { /* Check opCmp member exists. * Consider 'alias this', but except opDispatch. */ Expression e = new DsymbolExp(sd.loc, sd); e = new DotIdExp(sd.loc, e, Id.cmp); Scope* sc2 = sc.push(); e = e.trySemantic(sc2); sc2.pop(); if (e) { Dsymbol s = null; switch (e.op) { case EXP.overloadSet: s = e.isOverExp().vars; break; case EXP.scope_: s = e.isScopeExp().sds; break; case EXP.variable: s = e.isVarExp().var; break; default: break; } if (!s || s.ident != Id.cmp) e = null; // there's no valid member 'opCmp' } if (!e) return null; // bitwise comparison would work /* Essentially, a struct which does not define opCmp is not comparable. * At this time, typeid(S).compare might be correct that throwing "not implement" Error. * But implementing it would break existing code, such as: * * struct S { int value; } // no opCmp * int[S] aa; // Currently AA key uses bitwise comparison * // (It's default behavior of TypeInfo_Strust.compare). * * Not sure we should fix this inconsistency, so just keep current behavior. */ } else { return null; } } if (!sd.xerrcmp) { // object._xopCmp Identifier id = Identifier.idPool("_xopCmp"); Expression e = new IdentifierExp(sd.loc, Id.empty); e = new DotIdExp(sd.loc, e, Id.object); e = new DotIdExp(sd.loc, e, id); e = e.expressionSemantic(sc); if (!e.isErrorExp()) { Dsymbol s = getDsymbol(e); assert(s); sd.xerrcmp = s.isFuncDeclaration(); } } Loc declLoc; // loc is unnecessary so __xopCmp is never called directly Loc loc; // loc is unnecessary so errors are gagged auto parameters = new Parameters(); parameters.push(new Parameter(STC.ref_ | STC.const_, sd.type, Id.p, null, null)); auto tf = new TypeFunction(ParameterList(parameters), Type.tint32, LINK.d, STC.const_); tf = tf.addSTC(STC.const_).toTypeFunction(); Identifier id = Id.xopCmp; auto fop = new FuncDeclaration(declLoc, Loc.initial, id, 0, tf); fop.flags |= FUNCFLAG.generated; fop.parent = sd; Expression e1 = new IdentifierExp(loc, Id.This); Expression e2 = new IdentifierExp(loc, Id.p); Expression e = new CallExp(loc, new DotIdExp(loc, e1, Id.cmp), e2); fop.fbody = new ReturnStatement(loc, e); uint errors = global.startGagging(); // Do not report errors Scope* sc2 = sc.push(); sc2.stc = 0; sc2.linkage = LINK.d; fop.dsymbolSemantic(sc2); fop.semantic2(sc2); sc2.pop(); if (global.endGagging(errors)) // if errors happened fop = sd.xerrcmp; return fop; } /******************************************* * We need a toHash for the struct if * any fields has a toHash. * Generate one if a user-specified one does not exist. */ private bool needToHash(StructDeclaration sd) { //printf("StructDeclaration::needToHash() %s\n", sd.toChars()); if (sd.isUnionDeclaration()) goto Ldontneed; if (sd.xhash) goto Lneed; /* If any of the fields has an toHash, then we * need it too. */ foreach (VarDeclaration v; sd.fields) { if (v.storage_class & STC.ref_) continue; if (v.overlapped) continue; Type tv = v.type.toBasetype(); auto tvbase = tv.baseElemOf(); if (tvbase.ty == Tstruct) { TypeStruct ts = cast(TypeStruct)tvbase; if (ts.sym.isUnionDeclaration()) continue; if (needToHash(ts.sym)) goto Lneed; } if (tvbase.isfloating()) { /* This is necessary because comparison of +0.0 and -0.0 should be true, * i.e. not a bit compare. */ goto Lneed; } if (tvbase.ty == Tarray) goto Lneed; if (tvbase.ty == Taarray) goto Lneed; if (tvbase.ty == Tclass) goto Lneed; } Ldontneed: //printf("\tdontneed\n"); return false; Lneed: //printf("\tneed\n"); return true; } /****************************************** * Build __xtoHash for non-bitwise hashing * static hash_t xtoHash(ref const S p) nothrow @trusted; */ FuncDeclaration buildXtoHash(StructDeclaration sd, Scope* sc) { if (Dsymbol s = search_function(sd, Id.tohash)) { __gshared TypeFunction tftohash; if (!tftohash) { tftohash = new TypeFunction(ParameterList(), Type.thash_t, LINK.d); tftohash.mod = MODFlags.const_; tftohash = cast(TypeFunction)tftohash.merge(); } if (FuncDeclaration fd = s.isFuncDeclaration()) { fd = fd.overloadExactMatch(tftohash); if (fd) return fd; } } if (!needToHash(sd)) return null; /* The trouble is that the following code relies on .tupleof, but .tupleof * is not allowed for C files. If we allow it for C files, then that turns on * the other D properties, too, such as .dup which will then conflict with allowed * field names. * One way to fix it is to replace the following foreach and .tupleof with C * statements and expressions. * But, it's debatable whether C structs should even need toHash(). * Note that it would only be necessary if it has floating point fields. * For now, we'll just not generate a toHash() for C files. */ if (sc.flags & SCOPE.Cfile) return null; //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(); parameters.push(new Parameter(STC.ref_ | STC.const_, sd.type, Id.p, 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); fop.flags |= FUNCFLAG.generated; /* Do memberwise hashing. * * If sd is a nested struct, and if it's nested in a class, the calculated * hash value will also contain the result of parent class's toHash(). */ const(char)[] code = ".object.size_t h = 0;" ~ "foreach (i, T; typeof(p.tupleof))" ~ // workaround https://issues.dlang.org/show_bug.cgi?id=17968 " static if(is(T* : const(.object.Object)*)) " ~ " h = h * 33 + typeid(const(.object.Object)).getHash(cast(const void*)&p.tupleof[i]);" ~ " else " ~ " h = h * 33 + typeid(T).getHash(cast(const void*)&p.tupleof[i]);" ~ "return h;"; fop.fbody = new CompileStatement(loc, new StringExp(loc, code)); Scope* sc2 = sc.push(); sc2.stc = 0; sc2.linkage = LINK.d; fop.dsymbolSemantic(sc2); fop.semantic2(sc2); sc2.pop(); //printf("%s fop = %s %s\n", sd.toChars(), fop.toChars(), fop.type.toChars()); return fop; } /***************************************** * Create aggregate destructor for struct/class by aggregating * all the destructors in userDtors[] with the destructors for * all the members. * Sets ad's fieldDtor, aggrDtor, dtor and tidtor fields. * Params: * ad = struct or class to build destructor for * sc = context * Note: * Close similarity with StructDeclaration::buildPostBlit(), * and the ordering changes (runs backward instead of forwards). */ void buildDtors(AggregateDeclaration ad, Scope* sc) { //printf("AggregateDeclaration::buildDtor() %s\n", ad.toChars()); if (ad.isUnionDeclaration()) return; // unions don't have destructors StorageClass stc = STC.safe | STC.nothrow_ | STC.pure_ | STC.nogc; Loc declLoc = ad.userDtors.dim ? ad.userDtors[0].loc : ad.loc; Loc loc; // internal code should have no loc to prevent coverage FuncDeclaration xdtor_fwd = null; // Build the field destructor (`ad.fieldDtor`), if needed. // If the user dtor is an extern(C++) prototype, then we expect it performs a full-destruction and skip building. const bool dtorIsCppPrototype = ad.userDtors.dim && ad.userDtors[0]._linkage == LINK.cpp && !ad.userDtors[0].fbody; if (!dtorIsCppPrototype) { Expression e = null; for (size_t i = 0; i < ad.fields.dim; i++) { auto v = ad.fields[i]; if (v.storage_class & STC.ref_) continue; if (v.overlapped) continue; auto tv = v.type.baseElemOf(); if (tv.ty != Tstruct) continue; auto sdv = (cast(TypeStruct)tv).sym; if (!sdv.dtor) continue; // fix: https://issues.dlang.org/show_bug.cgi?id=17257 // braces for shrink wrapping scope of a { xdtor_fwd = sdv.dtor; // this dtor is temporary it could be anything auto a = new AliasDeclaration(Loc.initial, Id.__xdtor, xdtor_fwd); a.addMember(sc, ad); // temporarily add to symbol table } sdv.dtor.functionSemantic(); stc = mergeFuncAttrs(stc, sdv.dtor); if (stc & STC.disable) { e = null; break; } Expression ex; tv = v.type.toBasetype(); if (tv.ty == Tstruct) { // this.v.__xdtor() ex = new ThisExp(loc); ex = new DotVarExp(loc, ex, v); // This is a hack so we can call destructors on const/immutable objects. // Do it as a type 'paint', `cast()` ex = new CastExp(loc, ex, MODFlags.none); if (stc & STC.safe) stc = (stc & ~STC.safe) | STC.trusted; ex = new DotVarExp(loc, ex, sdv.dtor, false); ex = new CallExp(loc, ex); } else { // __ArrayDtor((cast(S*)this.v.ptr)[0 .. n]) const n = tv.numberOfElems(loc); if (n == 0) continue; ex = new ThisExp(loc); ex = new DotVarExp(loc, ex, v); // This is a hack so we can call destructors on const/immutable objects. ex = new DotIdExp(loc, ex, Id.ptr); ex = new CastExp(loc, ex, sdv.type.pointerTo()); if (stc & STC.safe) stc = (stc & ~STC.safe) | STC.trusted; SliceExp se = new SliceExp(loc, ex, new IntegerExp(loc, 0, Type.tsize_t), new IntegerExp(loc, n, Type.tsize_t)); // Prevent redundant bounds check se.upperIsInBounds = true; se.lowerIsLessThanUpper = true; ex = new CallExp(loc, new IdentifierExp(loc, Id.__ArrayDtor), se); } e = Expression.combine(ex, e); // combine in reverse order } if (e || (stc & STC.disable)) { //printf("Building __fieldDtor(), %s\n", e.toChars()); auto dd = new DtorDeclaration(declLoc, Loc.initial, stc, Id.__fieldDtor); dd.flags |= FUNCFLAG.generated; dd.storage_class |= STC.inference; dd.fbody = new ExpStatement(loc, e); ad.members.push(dd); dd.dsymbolSemantic(sc); ad.fieldDtor = dd; } } // Generate list of dtors to call in that order DtorDeclarations dtors; foreach_reverse (userDtor; ad.userDtors[]) dtors.push(userDtor); if (ad.fieldDtor) dtors.push(ad.fieldDtor); if (!dtorIsCppPrototype) { // extern(C++) destructors call into super to destruct the full hierarchy ClassDeclaration cldec = ad.isClassDeclaration(); if (cldec && cldec.classKind == ClassKind.cpp && cldec.baseClass && cldec.baseClass.aggrDtor) dtors.push(cldec.baseClass.aggrDtor); } // Set/build `ad.aggrDtor` switch (dtors.dim) { case 0: break; case 1: // Use the single existing dtor directly as aggregate dtor. // Note that this might be `cldec.baseClass.aggrDtor`. ad.aggrDtor = dtors[0]; break; default: // Build the aggregate destructor, calling all dtors in order. assert(!dtorIsCppPrototype); Expression e = null; e = null; stc = STC.safe | STC.nothrow_ | STC.pure_ | STC.nogc; foreach (FuncDeclaration fd; dtors) { stc = mergeFuncAttrs(stc, fd); if (stc & STC.disable) { e = null; break; } Expression ex = new ThisExp(loc); ex = new DotVarExp(loc, ex, fd, false); CallExp ce = new CallExp(loc, ex); ce.directcall = true; e = Expression.combine(e, ce); } auto dd = new DtorDeclaration(declLoc, Loc.initial, stc, Id.__aggrDtor); dd.flags |= FUNCFLAG.generated; dd.storage_class |= STC.inference; dd.fbody = new ExpStatement(loc, e); ad.members.push(dd); dd.dsymbolSemantic(sc); ad.aggrDtor = dd; break; } // Set/build `ad.dtor`. // On Windows, the dtor in the vtable is a shim with different signature. ad.dtor = (ad.aggrDtor && ad.aggrDtor._linkage == LINK.cpp && !target.cpp.twoDtorInVtable) ? buildWindowsCppDtor(ad, ad.aggrDtor, sc) : ad.aggrDtor; // Add an __xdtor alias to make `ad.dtor` accessible if (ad.dtor) { auto _alias = new AliasDeclaration(Loc.initial, Id.__xdtor, ad.dtor); _alias.dsymbolSemantic(sc); ad.members.push(_alias); if (xdtor_fwd) ad.symtab.update(_alias); // update forward dtor to correct one else _alias.addMember(sc, ad); // add to symbol table } // Set/build `ad.tidtor` ad.tidtor = buildExternDDtor(ad, sc); } /** * build a shim function around the compound dtor that accepts an argument * that is used to implement the deleting C++ destructor * * Params: * ad = the aggregate that contains the destructor to wrap * dtor = the destructor to wrap * sc = the scope in which to analyze the new function * * Returns: * the shim destructor, semantically analyzed and added to the class as a member */ private DtorDeclaration buildWindowsCppDtor(AggregateDeclaration ad, DtorDeclaration dtor, Scope* sc) { auto cldec = ad.isClassDeclaration(); if (!cldec || cldec.cppDtorVtblIndex == -1) // scalar deleting dtor not built for non-virtual dtors return dtor; // generate deleting C++ destructor corresponding to: // void* C::~C(int del) // { // this->~C(); // // TODO: if (del) delete (char*)this; // return (void*) this; // } Parameter delparam = new Parameter(STC.undefined_, Type.tuns32, Identifier.idPool("del"), new IntegerExp(dtor.loc, 0, Type.tuns32), null); Parameters* params = new Parameters; params.push(delparam); auto ftype = new TypeFunction(ParameterList(params), Type.tvoidptr, LINK.cpp, dtor.storage_class); auto func = new DtorDeclaration(dtor.loc, dtor.loc, dtor.storage_class, Id.cppdtor); func.type = ftype; // Always generate the function with body, because it is not exported from DLLs. const loc = dtor.loc; auto stmts = new Statements; auto call = new CallExp(loc, dtor, null); call.directcall = true; stmts.push(new ExpStatement(loc, call)); stmts.push(new ReturnStatement(loc, new CastExp(loc, new ThisExp(loc), Type.tvoidptr))); func.fbody = new CompoundStatement(loc, stmts); func.flags |= FUNCFLAG.generated; auto sc2 = sc.push(); sc2.stc &= ~STC.static_; // not a static destructor sc2.linkage = LINK.cpp; ad.members.push(func); func.addMember(sc2, ad); func.dsymbolSemantic(sc2); sc2.pop(); return func; } /** * build a shim function around the aggregate dtor that translates * a C++ destructor to a destructor with extern(D) calling convention * * Params: * ad = the aggregate that contains the destructor to wrap * sc = the scope in which to analyze the new function * * Returns: * the shim destructor, semantically analyzed and added to the class as a member */ private DtorDeclaration buildExternDDtor(AggregateDeclaration ad, Scope* sc) { auto dtor = ad.aggrDtor; if (!dtor) return null; // Generate shim only when ABI incompatible on target platform if (ad.classKind != ClassKind.cpp || !target.cpp.wrapDtorInExternD) return dtor; // generate member function that adjusts calling convention // (EAX used for 'this' instead of ECX on Windows/stack on others): // extern(D) void __ticppdtor() // { // Class.__dtor(); // } auto ftype = new TypeFunction(ParameterList(), Type.tvoid, LINK.d, dtor.storage_class); auto func = new DtorDeclaration(dtor.loc, dtor.loc, dtor.storage_class, Id.ticppdtor); func.type = ftype; auto call = new CallExp(dtor.loc, dtor, null); call.directcall = true; // non-virtual call Class.__dtor(); func.fbody = new ExpStatement(dtor.loc, call); func.flags |= FUNCFLAG.generated; func.storage_class |= STC.inference; auto sc2 = sc.push(); sc2.stc &= ~STC.static_; // not a static destructor sc2.linkage = LINK.d; ad.members.push(func); func.addMember(sc2, ad); func.dsymbolSemantic(sc2); func.functionSemantic(); // to infer attributes sc2.pop(); return func; } /****************************************** * Create inclusive invariant for struct/class by aggregating * all the invariants in invs[]. * --- * void __invariant() const [pure nothrow @trusted] * { * invs[0](), invs[1](), ...; * } * --- */ FuncDeclaration buildInv(AggregateDeclaration ad, Scope* sc) { switch (ad.invs.dim) { case 0: return null; case 1: // Don't return invs[0] so it has uniquely generated name. goto default; default: Expression e = null; StorageClass stcx = 0; StorageClass stc = STC.safe | STC.nothrow_ | STC.pure_ | STC.nogc; foreach (i, inv; ad.invs) { stc = mergeFuncAttrs(stc, inv); if (stc & STC.disable) { // What should do? } const stcy = (inv.storage_class & STC.synchronized_) | (inv.type.mod & MODFlags.shared_ ? STC.shared_ : 0); if (i == 0) stcx = stcy; else if (stcx ^ stcy) { version (all) { // currently rejects ad.error(inv.loc, "mixing invariants with different `shared`/`synchronized` qualifiers is not supported"); e = null; break; } } e = Expression.combine(e, new CallExp(Loc.initial, new VarExp(Loc.initial, inv, false))); } auto inv = new InvariantDeclaration(ad.loc, Loc.initial, stc | stcx, Id.classInvariant, new ExpStatement(Loc.initial, e)); ad.members.push(inv); inv.dsymbolSemantic(sc); return inv; } } /***************************************** * Create inclusive postblit for struct by aggregating * all the postblits in postblits[] with the postblits for * all the members. * Note the close similarity with AggregateDeclaration::buildDtor(), * and the ordering changes (runs forward instead of backwards). */ FuncDeclaration buildPostBlit(StructDeclaration sd, Scope* sc) { //printf("buildPostBlit() %s\n", sd.toChars()); if (sd.isUnionDeclaration()) return null; const hasUserDefinedPosblit = sd.postblits.dim && !sd.postblits[0].isDisabled ? true : false; // by default, the storage class of the created postblit StorageClass stc = STC.safe | STC.nothrow_ | STC.pure_ | STC.nogc; Loc declLoc = sd.postblits.dim ? sd.postblits[0].loc : sd.loc; Loc loc; // internal code should have no loc to prevent coverage // if any of the postblits are disabled, then the generated postblit // will be disabled foreach (postblit; sd.postblits) stc |= postblit.storage_class & STC.disable; VarDeclaration[] fieldsToDestroy; auto postblitCalls = new Statements(); // iterate through all the struct fields that are not disabled for (size_t i = 0; i < sd.fields.dim && !(stc & STC.disable); i++) { auto structField = sd.fields[i]; if (structField.storage_class & STC.ref_) continue; if (structField.overlapped) continue; // if it's a struct declaration or an array of structs Type tv = structField.type.baseElemOf(); if (tv.ty != Tstruct) continue; auto sdv = (cast(TypeStruct)tv).sym; // which has a postblit declaration if (!sdv.postblit) continue; assert(!sdv.isUnionDeclaration()); // if this field's postblit is not `nothrow`, add a `scope(failure)` // block to destroy any prior successfully postblitted fields should // this field's postblit fail if (fieldsToDestroy.length > 0 && !(cast(TypeFunction)sdv.postblit.type).isnothrow) { // create a list of destructors that need to be called Expression[] dtorCalls; foreach(sf; fieldsToDestroy) { Expression ex; tv = sf.type.toBasetype(); if (tv.ty == Tstruct) { // this.v.__xdtor() ex = new ThisExp(loc); ex = new DotVarExp(loc, ex, sf); // This is a hack so we can call destructors on const/immutable objects. ex = new AddrExp(loc, ex); ex = new CastExp(loc, ex, sf.type.mutableOf().pointerTo()); ex = new PtrExp(loc, ex); if (stc & STC.safe) stc = (stc & ~STC.safe) | STC.trusted; auto sfv = (cast(TypeStruct)sf.type.baseElemOf()).sym; ex = new DotVarExp(loc, ex, sfv.dtor, false); ex = new CallExp(loc, ex); dtorCalls ~= ex; } else { // _ArrayDtor((cast(S*)this.v.ptr)[0 .. n]) const length = tv.numberOfElems(loc); ex = new ThisExp(loc); ex = new DotVarExp(loc, ex, sf); // This is a hack so we can call destructors on const/immutable objects. ex = new DotIdExp(loc, ex, Id.ptr); ex = new CastExp(loc, ex, sdv.type.pointerTo()); if (stc & STC.safe) stc = (stc & ~STC.safe) | STC.trusted; auto se = new SliceExp(loc, ex, new IntegerExp(loc, 0, Type.tsize_t), new IntegerExp(loc, length, Type.tsize_t)); // Prevent redundant bounds check se.upperIsInBounds = true; se.lowerIsLessThanUpper = true; ex = new CallExp(loc, new IdentifierExp(loc, Id.__ArrayDtor), se); dtorCalls ~= ex; } } fieldsToDestroy = []; // aggregate the destructor calls auto dtors = new Statements(); foreach_reverse(dc; dtorCalls) { dtors.push(new ExpStatement(loc, dc)); } // put destructor calls in a `scope(failure)` block postblitCalls.push(new ScopeGuardStatement(loc, TOK.onScopeFailure, new CompoundStatement(loc, dtors))); } // perform semantic on the member postblit in order to // be able to aggregate it later on with the rest of the // postblits sdv.postblit.functionSemantic(); stc = mergeFuncAttrs(stc, sdv.postblit); stc = mergeFuncAttrs(stc, sdv.dtor); // if any of the struct member fields has disabled // its postblit, then `sd` is not copyable, so no // postblit is generated if (stc & STC.disable) { postblitCalls.setDim(0); break; } Expression ex; tv = structField.type.toBasetype(); if (tv.ty == Tstruct) { // this.v.__xpostblit() ex = new ThisExp(loc); ex = new DotVarExp(loc, ex, structField); // This is a hack so we can call postblits on const/immutable objects. ex = new AddrExp(loc, ex); ex = new CastExp(loc, ex, structField.type.mutableOf().pointerTo()); ex = new PtrExp(loc, ex); if (stc & STC.safe) stc = (stc & ~STC.safe) | STC.trusted; ex = new DotVarExp(loc, ex, sdv.postblit, false); ex = new CallExp(loc, ex); } else { // _ArrayPostblit((cast(S*)this.v.ptr)[0 .. n]) const length = tv.numberOfElems(loc); if (length == 0) continue; ex = new ThisExp(loc); ex = new DotVarExp(loc, ex, structField); // This is a hack so we can call postblits on const/immutable objects. ex = new DotIdExp(loc, ex, Id.ptr); ex = new CastExp(loc, ex, sdv.type.pointerTo()); if (stc & STC.safe) stc = (stc & ~STC.safe) | STC.trusted; auto se = new SliceExp(loc, ex, new IntegerExp(loc, 0, Type.tsize_t), new IntegerExp(loc, length, Type.tsize_t)); // Prevent redundant bounds check se.upperIsInBounds = true; se.lowerIsLessThanUpper = true; ex = new CallExp(loc, new IdentifierExp(loc, Id.__ArrayPostblit), se); } postblitCalls.push(new ExpStatement(loc, ex)); // combine in forward order /* https://issues.dlang.org/show_bug.cgi?id=10972 * When subsequent field postblit calls fail, * this field should be destructed for Exception Safety. */ if (sdv.dtor) { sdv.dtor.functionSemantic(); // keep a list of fields that need to be destroyed in case // of a future postblit failure fieldsToDestroy ~= structField; } } void checkShared() { if (sd.type.isShared()) stc |= STC.shared_; } // Build our own "postblit" which executes a, but only if needed. if (postblitCalls.dim || (stc & STC.disable)) { //printf("Building __fieldPostBlit()\n"); checkShared(); auto dd = new PostBlitDeclaration(declLoc, Loc.initial, stc, Id.__fieldPostblit); dd.flags |= FUNCFLAG.generated; dd.storage_class |= STC.inference | STC.scope_; dd.fbody = (stc & STC.disable) ? null : new CompoundStatement(loc, postblitCalls); sd.postblits.shift(dd); sd.members.push(dd); dd.dsymbolSemantic(sc); } // create __xpostblit, which is the generated postblit FuncDeclaration xpostblit = null; switch (sd.postblits.dim) { case 0: break; case 1: xpostblit = sd.postblits[0]; break; default: Expression e = null; stc = STC.safe | STC.nothrow_ | STC.pure_ | STC.nogc; foreach (fd; sd.postblits) { stc = mergeFuncAttrs(stc, fd); if (stc & STC.disable) { e = null; break; } Expression ex = new ThisExp(loc); ex = new DotVarExp(loc, ex, fd, false); ex = new CallExp(loc, ex); e = Expression.combine(e, ex); } checkShared(); auto dd = new PostBlitDeclaration(declLoc, Loc.initial, stc, Id.__aggrPostblit); dd.flags |= FUNCFLAG.generated; dd.storage_class |= STC.inference; dd.fbody = new ExpStatement(loc, e); sd.members.push(dd); dd.dsymbolSemantic(sc); xpostblit = dd; break; } // Add an __xpostblit alias to make the inclusive postblit accessible if (xpostblit) { auto _alias = new AliasDeclaration(Loc.initial, Id.__xpostblit, xpostblit); _alias.dsymbolSemantic(sc); sd.members.push(_alias); _alias.addMember(sc, sd); // add to symbol table } if (sd.hasCopyCtor) { // we have user defined postblit, so we prioritize it if (hasUserDefinedPosblit) { sd.hasCopyCtor = false; return xpostblit; } // we have fields with postblits, so print deprecations if (xpostblit && !xpostblit.isDisabled()) { deprecation(sd.loc, "`struct %s` implicitly-generated postblit hides copy constructor.", sd.toChars); deprecationSupplemental(sd.loc, "The field postblit will have priority over the copy constructor."); deprecationSupplemental(sd.loc, "To change this, the postblit should be disabled for `struct %s`", sd.toChars()); sd.hasCopyCtor = false; } else xpostblit = null; } return xpostblit; } /** * Generates a copy constructor declaration with the specified storage * class for the parameter and the function. * * Params: * 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 * * Returns: * The copy constructor declaration for struct `sd`. */ private CtorDeclaration generateCopyCtorDeclaration(StructDeclaration sd, const StorageClass paramStc, const StorageClass funcStc) { auto fparams = new Parameters(); auto structType = sd.type; fparams.push(new Parameter(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); ccd.storage_class |= funcStc; ccd.storage_class |= STC.inference; ccd.flags |= FUNCFLAG.generated; return ccd; } /** * Generates a trivial copy 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 generateCopyCtorBody(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 copy constructor is needed for struct sd, * if the following conditions are met: * * 1. sd does not define a copy constructor * 2. at least one field of sd defines a copy constructor * * Params: * sd = the `struct` for which the copy constructor is generated * hasCpCtor = set to true if a copy constructor is already present * * Returns: * `true` if one needs to be generated * `false` otherwise */ private bool needCopyCtor(StructDeclaration sd, out bool hasCpCtor) { if (global.errors) return false; auto ctor = sd.search(sd.loc, Id.ctor); if (ctor) { if (ctor.isOverloadSet()) return false; if (auto td = ctor.isTemplateDeclaration()) ctor = td.funcroot; } CtorDeclaration cpCtor; CtorDeclaration rvalueCtor; if (!ctor) goto LcheckFields; overloadApply(ctor, (Dsymbol s) { if (s.isTemplateDeclaration()) return 0; auto ctorDecl = s.isCtorDeclaration(); assert(ctorDecl); if (ctorDecl.isCpCtor) { if (!cpCtor) cpCtor = ctorDecl; return 0; } auto tf = ctorDecl.type.toTypeFunction(); const dim = tf.parameterList.length; if (dim == 1 || (dim > 1 && tf.parameterList[1].defaultArg)) { auto param = tf.parameterList[0]; if (param.type.mutableOf().unSharedOf() == sd.type.mutableOf().unSharedOf()) { rvalueCtor = ctorDecl; } } return 0; }); if (cpCtor) { if (rvalueCtor) { .error(sd.loc, "`struct %s` may not define both a rvalue constructor and a copy constructor", sd.toChars()); errorSupplemental(rvalueCtor.loc,"rvalue constructor defined here"); errorSupplemental(cpCtor.loc, "copy constructor defined here"); } hasCpCtor = true; return false; } LcheckFields: VarDeclaration fieldWithCpCtor; // 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.hasCopyCtor) { fieldWithCpCtor = v; break; } } if (fieldWithCpCtor && rvalueCtor) { .error(sd.loc, "`struct %s` may not define a rvalue constructor and have fields with copy constructors", sd.toChars()); errorSupplemental(rvalueCtor.loc,"rvalue constructor defined here"); errorSupplemental(fieldWithCpCtor.loc, "field with copy constructor defined here"); return false; } else if (!fieldWithCpCtor) return false; return true; } /** * Generates a copy constructor if needCopyCtor() returns true. * The generated copy 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. */ bool buildCopyCtor(StructDeclaration sd, Scope* sc) { bool hasCpCtor; if (!needCopyCtor(sd, hasCpCtor)) return hasCpCtor; //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 copyCtorBody = generateCopyCtorBody(sd); ccd.fbody = copyCtorBody; 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; }