blob: c999048dc49a8cdc0629228839ce838a32a546ae [file] [log] [blame]
/**
* 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;
auto a = Expressions(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.isGenerated = true;
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())
{
/* If a union has only one field, treat it like a struct
*/
if (sd.fields.length != 1)
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() && ts.sym.fields.length != 1)
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
auto a = Expressions(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.isGenerated = true;
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.isGenerated = true;
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.isGenerated = true;
/* 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.isGenerated = true;
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.isGenerated = true;
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.isGenerated = true;
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;
// Don't try to call `@disable`d dtors
if (dtor.storage_class & STC.disable)
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.isGenerated = true;
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.isGenerated = true;
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.isGenerated = true;
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.isGenerated = true;
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;
}