1 /**
2 * Builds struct member functions if needed and not defined by the user.
3 * Includes `opEquals`, `opAssign`, post blit, copy constructor and destructor.
4 *
5 * Copyright: Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved
6 * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright)
7 * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
8 * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/clone.d, _clone.d)
9 * Documentation: https://dlang.org/phobos/dmd_clone.html
10 * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/clone.d
11 */
12
13 module dmd.clone;
14
15 import core.stdc.stdio;
16 import dmd.aggregate;
17 import dmd.arraytypes;
18 import dmd.astenums;
19 import dmd.dclass;
20 import dmd.declaration;
21 import dmd.dscope;
22 import dmd.dstruct;
23 import dmd.dsymbol;
24 import dmd.dsymbolsem;
25 import dmd.dtemplate;
26 import dmd.errors;
27 import dmd.expression;
28 import dmd.expressionsem;
29 import dmd.func;
30 import dmd.globals;
31 import dmd.id;
32 import dmd.identifier;
33 import dmd.init;
34 import dmd.mtype;
35 import dmd.opover;
36 import dmd.semantic2;
37 import dmd.semantic3;
38 import dmd.statement;
39 import dmd.target;
40 import dmd.typesem;
41 import dmd.tokens;
42
43 /*******************************************
44 * Merge function attributes pure, nothrow, @safe, @nogc, and @disable
45 * from f into s1.
46 * Params:
47 * s1 = storage class to merge into
48 * f = function
49 * Returns:
50 * merged storage class
51 */
mergeFuncAttrs(StorageClass s1,const FuncDeclaration f)52 StorageClass mergeFuncAttrs(StorageClass s1, const FuncDeclaration f) pure
53 {
54 if (!f)
55 return s1;
56 StorageClass s2 = (f.storage_class & STC.disable);
57
58 TypeFunction tf = cast(TypeFunction)f.type;
59 if (tf.trust == TRUST.safe)
60 s2 |= STC.safe;
61 else if (tf.trust == TRUST.system)
62 s2 |= STC.system;
63 else if (tf.trust == TRUST.trusted)
64 s2 |= STC.trusted;
65
66 if (tf.purity != PURE.impure)
67 s2 |= STC.pure_;
68 if (tf.isnothrow)
69 s2 |= STC.nothrow_;
70 if (tf.isnogc)
71 s2 |= STC.nogc;
72
73 const sa = s1 & s2;
74 const so = s1 | s2;
75
76 StorageClass stc = (sa & (STC.pure_ | STC.nothrow_ | STC.nogc)) | (so & STC.disable);
77
78 if (so & STC.system)
79 stc |= STC.system;
80 else if (sa & STC.trusted)
81 stc |= STC.trusted;
82 else if ((so & (STC.trusted | STC.safe)) == (STC.trusted | STC.safe))
83 stc |= STC.trusted;
84 else if (sa & STC.safe)
85 stc |= STC.safe;
86
87 return stc;
88 }
89
90 /*******************************************
91 * Check given aggregate actually has an identity opAssign or not.
92 * Params:
93 * ad = struct or class
94 * sc = current scope
95 * Returns:
96 * if found, returns FuncDeclaration of opAssign, otherwise null
97 */
hasIdentityOpAssign(AggregateDeclaration ad,Scope * sc)98 FuncDeclaration hasIdentityOpAssign(AggregateDeclaration ad, Scope* sc)
99 {
100 Dsymbol assign = search_function(ad, Id.assign);
101 if (assign)
102 {
103 /* check identity opAssign exists
104 */
105 scope er = new NullExp(ad.loc, ad.type); // dummy rvalue
106 scope el = new IdentifierExp(ad.loc, Id.p); // dummy lvalue
107 el.type = ad.type;
108 Expressions a;
109 a.setDim(1);
110 const errors = global.startGagging(); // Do not report errors, even if the template opAssign fbody makes it.
111 sc = sc.push();
112 sc.tinst = null;
113 sc.minst = null;
114
115 a[0] = er;
116 auto f = resolveFuncCall(ad.loc, sc, assign, null, ad.type, &a, FuncResolveFlag.quiet);
117 if (!f)
118 {
119 a[0] = el;
120 f = resolveFuncCall(ad.loc, sc, assign, null, ad.type, &a, FuncResolveFlag.quiet);
121 }
122
123 sc = sc.pop();
124 global.endGagging(errors);
125 if (f)
126 {
127 if (f.errors)
128 return null;
129 auto fparams = f.getParameterList();
130 if (fparams.length)
131 {
132 auto fparam0 = fparams[0];
133 if (fparam0.type.toDsymbol(null) != ad)
134 f = null;
135 }
136 }
137 // BUGS: This detection mechanism cannot find some opAssign-s like follows:
138 // struct S { void opAssign(ref immutable S) const; }
139 return f;
140 }
141 return null;
142 }
143
144 /*******************************************
145 * We need an opAssign for the struct if
146 * it has a destructor or a postblit.
147 * We need to generate one if a user-specified one does not exist.
148 */
needOpAssign(StructDeclaration sd)149 private bool needOpAssign(StructDeclaration sd)
150 {
151 //printf("StructDeclaration::needOpAssign() %s\n", sd.toChars());
152
153 static bool isNeeded()
154 {
155 //printf("\tneed\n");
156 return true;
157 }
158
159 if (sd.isUnionDeclaration())
160 return !isNeeded();
161
162 if (sd.hasIdentityAssign || // because has identity==elaborate opAssign
163 sd.dtor ||
164 sd.postblit)
165 return isNeeded();
166
167 /* If any of the fields need an opAssign, then we
168 * need it too.
169 */
170 foreach (v; sd.fields)
171 {
172 if (v.storage_class & STC.ref_)
173 continue;
174 if (v.overlapped) // if field of a union
175 continue; // user must handle it themselves
176 Type tv = v.type.baseElemOf();
177 if (tv.ty == Tstruct)
178 {
179 TypeStruct ts = cast(TypeStruct)tv;
180 if (ts.sym.isUnionDeclaration())
181 continue;
182 if (needOpAssign(ts.sym))
183 return isNeeded();
184 }
185 }
186 return !isNeeded();
187 }
188
189 /******************************************
190 * Build opAssign for a `struct`.
191 *
192 * The generated `opAssign` function has the following signature:
193 *---
194 *ref S opAssign(S s) // S is the name of the `struct`
195 *---
196 *
197 * The opAssign function will be built for a struct `S` if the
198 * following constraints are met:
199 *
200 * 1. `S` does not have an identity `opAssign` defined.
201 *
202 * 2. `S` has at least one of the following members: a postblit (user-defined or
203 * generated for fields that have a defined postblit), a destructor
204 * (user-defined or generated for fields that have a defined destructor)
205 * or at least one field that has a defined `opAssign`.
206 *
207 * 3. `S` does not have any non-mutable fields.
208 *
209 * If `S` has a disabled destructor or at least one field that has a disabled
210 * `opAssign`, `S.opAssign` is going to be generated, but marked with `@disable`
211 *
212 * If `S` defines a destructor, the generated code for `opAssign` is:
213 *
214 *---
215 *S __swap = void;
216 *__swap = this; // bit copy
217 *this = s; // bit copy
218 *__swap.dtor();
219 *---
220 *
221 * Otherwise, if `S` defines a postblit, the generated code for `opAssign` is:
222 *
223 *---
224 *this = s;
225 *---
226 *
227 * Note that the parameter to the generated `opAssign` is passed by value, which means
228 * that the postblit is going to be called (if it is defined) in both of the above
229 * situations before entering the body of `opAssign`. The assignments in the above generated
230 * function bodies are blit expressions, so they can be regarded as `memcpy`s
231 * (`opAssign` is not called as this will result in an infinite recursion; the postblit
232 * is not called because it has already been called when the parameter was passed by value).
233 *
234 * If `S` does not have a postblit or a destructor, but contains at least one field that defines
235 * an `opAssign` function (which is not disabled), then the body will make member-wise
236 * assignments:
237 *
238 *---
239 *this.field1 = s.field1;
240 *this.field2 = s.field2;
241 *...;
242 *---
243 *
244 * In this situation, the assignemnts are actual assign expressions (`opAssign` is used
245 * if defined).
246 *
247 * References:
248 * https://dlang.org/spec/struct.html#assign-overload
249 * Params:
250 * sd = struct to generate opAssign for
251 * sc = context
252 * Returns:
253 * generated `opAssign` function
254 */
buildOpAssign(StructDeclaration sd,Scope * sc)255 FuncDeclaration buildOpAssign(StructDeclaration sd, Scope* sc)
256 {
257 if (FuncDeclaration f = hasIdentityOpAssign(sd, sc))
258 {
259 sd.hasIdentityAssign = true;
260 return f;
261 }
262 // Even if non-identity opAssign is defined, built-in identity opAssign
263 // will be defined.
264 if (!needOpAssign(sd))
265 return null;
266
267 //printf("StructDeclaration::buildOpAssign() %s\n", sd.toChars());
268 StorageClass stc = STC.safe | STC.nothrow_ | STC.pure_ | STC.nogc;
269 Loc declLoc = sd.loc;
270 Loc loc; // internal code should have no loc to prevent coverage
271
272 // One of our sub-field might have `@disable opAssign` so we need to
273 // check for it.
274 // In this event, it will be reflected by having `stc` (opAssign's
275 // storage class) include `STC.disabled`.
276 foreach (v; sd.fields)
277 {
278 if (v.storage_class & STC.ref_)
279 continue;
280 if (v.overlapped)
281 continue;
282 Type tv = v.type.baseElemOf();
283 if (tv.ty != Tstruct)
284 continue;
285 StructDeclaration sdv = (cast(TypeStruct)tv).sym;
286 stc = mergeFuncAttrs(stc, hasIdentityOpAssign(sdv, sc));
287 }
288
289 if (sd.dtor || sd.postblit)
290 {
291 // if the type is not assignable, we cannot generate opAssign
292 if (!sd.type.isAssignable()) // https://issues.dlang.org/show_bug.cgi?id=13044
293 return null;
294 stc = mergeFuncAttrs(stc, sd.dtor);
295 if (stc & STC.safe)
296 stc = (stc & ~STC.safe) | STC.trusted;
297 }
298
299 auto fparams = new Parameters();
300 fparams.push(new Parameter(STC.nodtor, sd.type, Id.p, null, null));
301 auto tf = new TypeFunction(ParameterList(fparams), sd.handleType(), LINK.d, stc | STC.ref_);
302 auto fop = new FuncDeclaration(declLoc, Loc.initial, Id.assign, stc, tf);
303 fop.storage_class |= STC.inference;
304 fop.flags |= FUNCFLAG.generated;
305 Expression e;
306 if (stc & STC.disable)
307 {
308 e = null;
309 }
310 /* Do swap this and rhs.
311 * __swap = this; this = s; __swap.dtor();
312 */
313 else if (sd.dtor)
314 {
315 //printf("\tswap copy\n");
316 TypeFunction tdtor = cast(TypeFunction)sd.dtor.type;
317 assert(tdtor.ty == Tfunction);
318
319 auto idswap = Identifier.generateId("__swap");
320 auto swap = new VarDeclaration(loc, sd.type, idswap, new VoidInitializer(loc));
321 swap.storage_class |= STC.nodtor | STC.temp | STC.ctfe;
322 if (tdtor.isScopeQual)
323 swap.storage_class |= STC.scope_;
324 auto e1 = new DeclarationExp(loc, swap);
325
326 auto e2 = new BlitExp(loc, new VarExp(loc, swap), new ThisExp(loc));
327 auto e3 = new BlitExp(loc, new ThisExp(loc), new IdentifierExp(loc, Id.p));
328
329 /* Instead of running the destructor on s, run it
330 * on swap. This avoids needing to copy swap back in to s.
331 */
332 auto e4 = new CallExp(loc, new DotVarExp(loc, new VarExp(loc, swap), sd.dtor, false));
333
334 e = Expression.combine(e1, e2, e3, e4);
335 }
336 /* postblit was called when the value was passed to opAssign, we just need to blit the result */
337 else if (sd.postblit)
338 {
339 e = new BlitExp(loc, new ThisExp(loc), new IdentifierExp(loc, Id.p));
340 sd.hasBlitAssign = true;
341 }
342 else
343 {
344 /* Do memberwise copy.
345 *
346 * If sd is a nested struct, its vthis field assignment is:
347 * 1. If it's nested in a class, it's a rebind of class reference.
348 * 2. If it's nested in a function or struct, it's an update of void*.
349 * In both cases, it will change the parent context.
350 */
351 //printf("\tmemberwise copy\n");
352 e = null;
353 foreach (v; sd.fields)
354 {
355 // this.v = s.v;
356 auto ec = new AssignExp(loc,
357 new DotVarExp(loc, new ThisExp(loc), v),
358 new DotVarExp(loc, new IdentifierExp(loc, Id.p), v));
359 e = Expression.combine(e, ec);
360 }
361 }
362 if (e)
363 {
364 Statement s1 = new ExpStatement(loc, e);
365 /* Add:
366 * return this;
367 */
368 auto er = new ThisExp(loc);
369 Statement s2 = new ReturnStatement(loc, er);
370 fop.fbody = new CompoundStatement(loc, s1, s2);
371 tf.isreturn = true;
372 }
373 sd.members.push(fop);
374 fop.addMember(sc, sd);
375 sd.hasIdentityAssign = true; // temporary mark identity assignable
376 const errors = global.startGagging(); // Do not report errors, even if the template opAssign fbody makes it.
377 Scope* sc2 = sc.push();
378 sc2.stc = 0;
379 sc2.linkage = LINK.d;
380 fop.dsymbolSemantic(sc2);
381 fop.semantic2(sc2);
382 // https://issues.dlang.org/show_bug.cgi?id=15044
383 //semantic3(fop, sc2); // isn't run here for lazy forward reference resolution.
384
385 sc2.pop();
386 if (global.endGagging(errors)) // if errors happened
387 {
388 // Disable generated opAssign, because some members forbid identity assignment.
389 fop.storage_class |= STC.disable;
390 fop.fbody = null; // remove fbody which contains the error
391 }
392
393 //printf("-StructDeclaration::buildOpAssign() %s, errors = %d\n", sd.toChars(), (fop.storage_class & STC.disable) != 0);
394 //printf("fop.type: %s\n", fop.type.toPrettyChars());
395 return fop;
396 }
397
398 /*******************************************
399 * We need an opEquals for the struct if
400 * any fields has an opEquals.
401 * Generate one if a user-specified one does not exist.
402 */
needOpEquals(StructDeclaration sd)403 bool needOpEquals(StructDeclaration sd)
404 {
405 //printf("StructDeclaration::needOpEquals() %s\n", sd.toChars());
406 if (sd.isUnionDeclaration())
407 goto Ldontneed;
408 if (sd.hasIdentityEquals)
409 goto Lneed;
410 /* If any of the fields has an opEquals, then we
411 * need it too.
412 */
413 foreach (VarDeclaration v; sd.fields)
414 {
415 if (v.storage_class & STC.ref_)
416 continue;
417 if (v.overlapped)
418 continue;
419 Type tv = v.type.toBasetype();
420 auto tvbase = tv.baseElemOf();
421 if (tvbase.ty == Tstruct)
422 {
423 TypeStruct ts = cast(TypeStruct)tvbase;
424 if (ts.sym.isUnionDeclaration())
425 continue;
426 if (needOpEquals(ts.sym))
427 goto Lneed;
428 }
429 if (tvbase.isfloating())
430 {
431 // This is necessray for:
432 // 1. comparison of +0.0 and -0.0 should be true.
433 // 2. comparison of NANs should be false always.
434 goto Lneed;
435 }
436 if (tvbase.ty == Tarray)
437 goto Lneed;
438 if (tvbase.ty == Taarray)
439 goto Lneed;
440 if (tvbase.ty == Tclass)
441 goto Lneed;
442 }
443 Ldontneed:
444 //printf("\tdontneed\n");
445 return false;
446 Lneed:
447 //printf("\tneed\n");
448 return true;
449 }
450
451 /*******************************************
452 * Check given aggregate actually has an identity opEquals or not.
453 */
hasIdentityOpEquals(AggregateDeclaration ad,Scope * sc)454 private FuncDeclaration hasIdentityOpEquals(AggregateDeclaration ad, Scope* sc)
455 {
456 FuncDeclaration f;
457 if (Dsymbol eq = search_function(ad, Id.eq))
458 {
459 /* check identity opEquals exists
460 */
461 scope er = new NullExp(ad.loc, null); // dummy rvalue
462 scope el = new IdentifierExp(ad.loc, Id.p); // dummy lvalue
463 Expressions a;
464 a.setDim(1);
465
466 bool hasIt(Type tthis)
467 {
468 const errors = global.startGagging(); // Do not report errors, even if the template opAssign fbody makes it
469 sc = sc.push();
470 sc.tinst = null;
471 sc.minst = null;
472
473 FuncDeclaration rfc(Expression e)
474 {
475 a[0] = e;
476 a[0].type = tthis;
477 return resolveFuncCall(ad.loc, sc, eq, null, tthis, &a, FuncResolveFlag.quiet);
478 }
479
480 f = rfc(er);
481 if (!f)
482 f = rfc(el);
483
484 sc = sc.pop();
485 global.endGagging(errors);
486
487 return f !is null;
488 }
489
490 if (hasIt(ad.type) ||
491 hasIt(ad.type.constOf()) ||
492 hasIt(ad.type.immutableOf()) ||
493 hasIt(ad.type.sharedOf()) ||
494 hasIt(ad.type.sharedConstOf()))
495 {
496 if (f.errors)
497 return null;
498 }
499 }
500 return f;
501 }
502
503 /******************************************
504 * Build opEquals for struct.
505 * const bool opEquals(const S s) { ... }
506 *
507 * By fixing https://issues.dlang.org/show_bug.cgi?id=3789
508 * opEquals is changed to be never implicitly generated.
509 * Now, struct objects comparison s1 == s2 is translated to:
510 * s1.tupleof == s2.tupleof
511 * to calculate structural equality. See EqualExp.op_overload.
512 */
buildOpEquals(StructDeclaration sd,Scope * sc)513 FuncDeclaration buildOpEquals(StructDeclaration sd, Scope* sc)
514 {
515 if (hasIdentityOpEquals(sd, sc))
516 {
517 sd.hasIdentityEquals = true;
518 }
519 return null;
520 }
521
522 /******************************************
523 * Build __xopEquals for TypeInfo_Struct
524 * bool __xopEquals(ref const S p) const
525 * {
526 * return this == p;
527 * }
528 *
529 * This is called by TypeInfo.equals(p1, p2). If the struct does not support
530 * const objects comparison, it will throw "not implemented" Error in runtime.
531 */
buildXopEquals(StructDeclaration sd,Scope * sc)532 FuncDeclaration buildXopEquals(StructDeclaration sd, Scope* sc)
533 {
534 if (!needOpEquals(sd))
535 return null; // bitwise comparison would work
536
537 //printf("StructDeclaration::buildXopEquals() %s\n", sd.toChars());
538 if (Dsymbol eq = search_function(sd, Id.eq))
539 {
540 if (FuncDeclaration fd = eq.isFuncDeclaration())
541 {
542 TypeFunction tfeqptr;
543 {
544 Scope scx;
545 /* const bool opEquals(ref const S s);
546 */
547 auto parameters = new Parameters();
548 parameters.push(new Parameter(STC.ref_ | STC.const_, sd.type, null, null, null));
549 tfeqptr = new TypeFunction(ParameterList(parameters), Type.tbool, LINK.d);
550 tfeqptr.mod = MODFlags.const_;
551 tfeqptr = cast(TypeFunction)tfeqptr.typeSemantic(Loc.initial, &scx);
552 }
553 fd = fd.overloadExactMatch(tfeqptr);
554 if (fd)
555 return fd;
556 }
557 }
558 if (!sd.xerreq)
559 {
560 // object._xopEquals
561 Identifier id = Identifier.idPool("_xopEquals");
562 Expression e = new IdentifierExp(sd.loc, Id.empty);
563 e = new DotIdExp(sd.loc, e, Id.object);
564 e = new DotIdExp(sd.loc, e, id);
565 e = e.expressionSemantic(sc);
566 if (!e.isErrorExp())
567 {
568 Dsymbol s = getDsymbol(e);
569 assert(s);
570 sd.xerreq = s.isFuncDeclaration();
571 }
572 }
573 Loc declLoc; // loc is unnecessary so __xopEquals is never called directly
574 Loc loc; // loc is unnecessary so errors are gagged
575 auto parameters = new Parameters();
576 parameters.push(new Parameter(STC.ref_ | STC.const_, sd.type, Id.p, null, null));
577 auto tf = new TypeFunction(ParameterList(parameters), Type.tbool, LINK.d, STC.const_);
578 tf = tf.addSTC(STC.const_).toTypeFunction();
579 Identifier id = Id.xopEquals;
580 auto fop = new FuncDeclaration(declLoc, Loc.initial, id, 0, tf);
581 fop.flags |= FUNCFLAG.generated;
582 fop.parent = sd;
583 Expression e1 = new IdentifierExp(loc, Id.This);
584 Expression e2 = new IdentifierExp(loc, Id.p);
585 Expression e = new EqualExp(EXP.equal, loc, e1, e2);
586 fop.fbody = new ReturnStatement(loc, e);
587 uint errors = global.startGagging(); // Do not report errors
588 Scope* sc2 = sc.push();
589 sc2.stc = 0;
590 sc2.linkage = LINK.d;
591 fop.dsymbolSemantic(sc2);
592 fop.semantic2(sc2);
593 sc2.pop();
594 if (global.endGagging(errors)) // if errors happened
595 fop = sd.xerreq;
596 return fop;
597 }
598
599 /******************************************
600 * Build __xopCmp for TypeInfo_Struct
601 * int __xopCmp(ref const S p) const
602 * {
603 * return this.opCmp(p);
604 * }
605 *
606 * This is called by TypeInfo.compare(p1, p2). If the struct does not support
607 * const objects comparison, it will throw "not implemented" Error in runtime.
608 */
buildXopCmp(StructDeclaration sd,Scope * sc)609 FuncDeclaration buildXopCmp(StructDeclaration sd, Scope* sc)
610 {
611 //printf("StructDeclaration::buildXopCmp() %s\n", toChars());
612 if (Dsymbol cmp = search_function(sd, Id.cmp))
613 {
614 if (FuncDeclaration fd = cmp.isFuncDeclaration())
615 {
616 TypeFunction tfcmpptr;
617 {
618 Scope scx;
619 /* const int opCmp(ref const S s);
620 */
621 auto parameters = new Parameters();
622 parameters.push(new Parameter(STC.ref_ | STC.const_, sd.type, null, null, null));
623 tfcmpptr = new TypeFunction(ParameterList(parameters), Type.tint32, LINK.d);
624 tfcmpptr.mod = MODFlags.const_;
625 tfcmpptr = cast(TypeFunction)tfcmpptr.typeSemantic(Loc.initial, &scx);
626 }
627 fd = fd.overloadExactMatch(tfcmpptr);
628 if (fd)
629 return fd;
630 }
631 }
632 else
633 {
634 version (none) // FIXME: doesn't work for recursive alias this
635 {
636 /* Check opCmp member exists.
637 * Consider 'alias this', but except opDispatch.
638 */
639 Expression e = new DsymbolExp(sd.loc, sd);
640 e = new DotIdExp(sd.loc, e, Id.cmp);
641 Scope* sc2 = sc.push();
642 e = e.trySemantic(sc2);
643 sc2.pop();
644 if (e)
645 {
646 Dsymbol s = null;
647 switch (e.op)
648 {
649 case EXP.overloadSet:
650 s = e.isOverExp().vars;
651 break;
652 case EXP.scope_:
653 s = e.isScopeExp().sds;
654 break;
655 case EXP.variable:
656 s = e.isVarExp().var;
657 break;
658 default:
659 break;
660 }
661 if (!s || s.ident != Id.cmp)
662 e = null; // there's no valid member 'opCmp'
663 }
664 if (!e)
665 return null; // bitwise comparison would work
666 /* Essentially, a struct which does not define opCmp is not comparable.
667 * At this time, typeid(S).compare might be correct that throwing "not implement" Error.
668 * But implementing it would break existing code, such as:
669 *
670 * struct S { int value; } // no opCmp
671 * int[S] aa; // Currently AA key uses bitwise comparison
672 * // (It's default behavior of TypeInfo_Strust.compare).
673 *
674 * Not sure we should fix this inconsistency, so just keep current behavior.
675 */
676 }
677 else
678 {
679 return null;
680 }
681 }
682 if (!sd.xerrcmp)
683 {
684 // object._xopCmp
685 Identifier id = Identifier.idPool("_xopCmp");
686 Expression e = new IdentifierExp(sd.loc, Id.empty);
687 e = new DotIdExp(sd.loc, e, Id.object);
688 e = new DotIdExp(sd.loc, e, id);
689 e = e.expressionSemantic(sc);
690 if (!e.isErrorExp())
691 {
692 Dsymbol s = getDsymbol(e);
693 assert(s);
694 sd.xerrcmp = s.isFuncDeclaration();
695 }
696 }
697 Loc declLoc; // loc is unnecessary so __xopCmp is never called directly
698 Loc loc; // loc is unnecessary so errors are gagged
699 auto parameters = new Parameters();
700 parameters.push(new Parameter(STC.ref_ | STC.const_, sd.type, Id.p, null, null));
701 auto tf = new TypeFunction(ParameterList(parameters), Type.tint32, LINK.d, STC.const_);
702 tf = tf.addSTC(STC.const_).toTypeFunction();
703 Identifier id = Id.xopCmp;
704 auto fop = new FuncDeclaration(declLoc, Loc.initial, id, 0, tf);
705 fop.flags |= FUNCFLAG.generated;
706 fop.parent = sd;
707 Expression e1 = new IdentifierExp(loc, Id.This);
708 Expression e2 = new IdentifierExp(loc, Id.p);
709 Expression e = new CallExp(loc, new DotIdExp(loc, e1, Id.cmp), e2);
710 fop.fbody = new ReturnStatement(loc, e);
711 uint errors = global.startGagging(); // Do not report errors
712 Scope* sc2 = sc.push();
713 sc2.stc = 0;
714 sc2.linkage = LINK.d;
715 fop.dsymbolSemantic(sc2);
716 fop.semantic2(sc2);
717 sc2.pop();
718 if (global.endGagging(errors)) // if errors happened
719 fop = sd.xerrcmp;
720 return fop;
721 }
722
723 /*******************************************
724 * We need a toHash for the struct if
725 * any fields has a toHash.
726 * Generate one if a user-specified one does not exist.
727 */
needToHash(StructDeclaration sd)728 private bool needToHash(StructDeclaration sd)
729 {
730 //printf("StructDeclaration::needToHash() %s\n", sd.toChars());
731 if (sd.isUnionDeclaration())
732 goto Ldontneed;
733 if (sd.xhash)
734 goto Lneed;
735
736 /* If any of the fields has an toHash, then we
737 * need it too.
738 */
739 foreach (VarDeclaration v; sd.fields)
740 {
741 if (v.storage_class & STC.ref_)
742 continue;
743 if (v.overlapped)
744 continue;
745 Type tv = v.type.toBasetype();
746 auto tvbase = tv.baseElemOf();
747 if (tvbase.ty == Tstruct)
748 {
749 TypeStruct ts = cast(TypeStruct)tvbase;
750 if (ts.sym.isUnionDeclaration())
751 continue;
752 if (needToHash(ts.sym))
753 goto Lneed;
754 }
755 if (tvbase.isfloating())
756 {
757 /* This is necessary because comparison of +0.0 and -0.0 should be true,
758 * i.e. not a bit compare.
759 */
760 goto Lneed;
761 }
762 if (tvbase.ty == Tarray)
763 goto Lneed;
764 if (tvbase.ty == Taarray)
765 goto Lneed;
766 if (tvbase.ty == Tclass)
767 goto Lneed;
768 }
769 Ldontneed:
770 //printf("\tdontneed\n");
771 return false;
772 Lneed:
773 //printf("\tneed\n");
774 return true;
775 }
776
777 /******************************************
778 * Build __xtoHash for non-bitwise hashing
779 * static hash_t xtoHash(ref const S p) nothrow @trusted;
780 */
buildXtoHash(StructDeclaration sd,Scope * sc)781 FuncDeclaration buildXtoHash(StructDeclaration sd, Scope* sc)
782 {
783 if (Dsymbol s = search_function(sd, Id.tohash))
784 {
785 __gshared TypeFunction tftohash;
786 if (!tftohash)
787 {
788 tftohash = new TypeFunction(ParameterList(), Type.thash_t, LINK.d);
789 tftohash.mod = MODFlags.const_;
790 tftohash = cast(TypeFunction)tftohash.merge();
791 }
792 if (FuncDeclaration fd = s.isFuncDeclaration())
793 {
794 fd = fd.overloadExactMatch(tftohash);
795 if (fd)
796 return fd;
797 }
798 }
799 if (!needToHash(sd))
800 return null;
801
802 /* The trouble is that the following code relies on .tupleof, but .tupleof
803 * is not allowed for C files. If we allow it for C files, then that turns on
804 * the other D properties, too, such as .dup which will then conflict with allowed
805 * field names.
806 * One way to fix it is to replace the following foreach and .tupleof with C
807 * statements and expressions.
808 * But, it's debatable whether C structs should even need toHash().
809 * Note that it would only be necessary if it has floating point fields.
810 * For now, we'll just not generate a toHash() for C files.
811 */
812 if (sc.flags & SCOPE.Cfile)
813 return null;
814
815 //printf("StructDeclaration::buildXtoHash() %s\n", sd.toPrettyChars());
816 Loc declLoc; // loc is unnecessary so __xtoHash is never called directly
817 Loc loc; // internal code should have no loc to prevent coverage
818 auto parameters = new Parameters();
819 parameters.push(new Parameter(STC.ref_ | STC.const_, sd.type, Id.p, null, null));
820 auto tf = new TypeFunction(ParameterList(parameters), Type.thash_t, LINK.d, STC.nothrow_ | STC.trusted);
821 Identifier id = Id.xtoHash;
822 auto fop = new FuncDeclaration(declLoc, Loc.initial, id, STC.static_, tf);
823 fop.flags |= FUNCFLAG.generated;
824
825 /* Do memberwise hashing.
826 *
827 * If sd is a nested struct, and if it's nested in a class, the calculated
828 * hash value will also contain the result of parent class's toHash().
829 */
830 const(char)[] code =
831 ".object.size_t h = 0;" ~
832 "foreach (i, T; typeof(p.tupleof))" ~
833 // workaround https://issues.dlang.org/show_bug.cgi?id=17968
834 " static if(is(T* : const(.object.Object)*)) " ~
835 " h = h * 33 + typeid(const(.object.Object)).getHash(cast(const void*)&p.tupleof[i]);" ~
836 " else " ~
837 " h = h * 33 + typeid(T).getHash(cast(const void*)&p.tupleof[i]);" ~
838 "return h;";
839 fop.fbody = new CompileStatement(loc, new StringExp(loc, code));
840 Scope* sc2 = sc.push();
841 sc2.stc = 0;
842 sc2.linkage = LINK.d;
843 fop.dsymbolSemantic(sc2);
844 fop.semantic2(sc2);
845 sc2.pop();
846
847 //printf("%s fop = %s %s\n", sd.toChars(), fop.toChars(), fop.type.toChars());
848 return fop;
849 }
850
851 /*****************************************
852 * Create aggregate destructor for struct/class by aggregating
853 * all the destructors in userDtors[] with the destructors for
854 * all the members.
855 * Sets ad's fieldDtor, aggrDtor, dtor and tidtor fields.
856 * Params:
857 * ad = struct or class to build destructor for
858 * sc = context
859 * Note:
860 * Close similarity with StructDeclaration::buildPostBlit(),
861 * and the ordering changes (runs backward instead of forwards).
862 */
buildDtors(AggregateDeclaration ad,Scope * sc)863 void buildDtors(AggregateDeclaration ad, Scope* sc)
864 {
865 //printf("AggregateDeclaration::buildDtor() %s\n", ad.toChars());
866 if (ad.isUnionDeclaration())
867 return; // unions don't have destructors
868
869 StorageClass stc = STC.safe | STC.nothrow_ | STC.pure_ | STC.nogc;
870 Loc declLoc = ad.userDtors.dim ? ad.userDtors[0].loc : ad.loc;
871 Loc loc; // internal code should have no loc to prevent coverage
872 FuncDeclaration xdtor_fwd = null;
873
874 // Build the field destructor (`ad.fieldDtor`), if needed.
875 // If the user dtor is an extern(C++) prototype, then we expect it performs a full-destruction and skip building.
876 const bool dtorIsCppPrototype = ad.userDtors.dim && ad.userDtors[0]._linkage == LINK.cpp && !ad.userDtors[0].fbody;
877 if (!dtorIsCppPrototype)
878 {
879 Expression e = null;
880 for (size_t i = 0; i < ad.fields.dim; i++)
881 {
882 auto v = ad.fields[i];
883 if (v.storage_class & STC.ref_)
884 continue;
885 if (v.overlapped)
886 continue;
887 auto tv = v.type.baseElemOf();
888 if (tv.ty != Tstruct)
889 continue;
890 auto sdv = (cast(TypeStruct)tv).sym;
891 if (!sdv.dtor)
892 continue;
893
894 // fix: https://issues.dlang.org/show_bug.cgi?id=17257
895 // braces for shrink wrapping scope of a
896 {
897 xdtor_fwd = sdv.dtor; // this dtor is temporary it could be anything
898 auto a = new AliasDeclaration(Loc.initial, Id.__xdtor, xdtor_fwd);
899 a.addMember(sc, ad); // temporarily add to symbol table
900 }
901
902 sdv.dtor.functionSemantic();
903
904 stc = mergeFuncAttrs(stc, sdv.dtor);
905 if (stc & STC.disable)
906 {
907 e = null;
908 break;
909 }
910
911 Expression ex;
912 tv = v.type.toBasetype();
913 if (tv.ty == Tstruct)
914 {
915 // this.v.__xdtor()
916
917 ex = new ThisExp(loc);
918 ex = new DotVarExp(loc, ex, v);
919
920 // This is a hack so we can call destructors on const/immutable objects.
921 // Do it as a type 'paint', `cast()`
922 ex = new CastExp(loc, ex, MODFlags.none);
923 if (stc & STC.safe)
924 stc = (stc & ~STC.safe) | STC.trusted;
925
926 ex = new DotVarExp(loc, ex, sdv.dtor, false);
927 ex = new CallExp(loc, ex);
928 }
929 else
930 {
931 // __ArrayDtor((cast(S*)this.v.ptr)[0 .. n])
932
933 const n = tv.numberOfElems(loc);
934 if (n == 0)
935 continue;
936
937 ex = new ThisExp(loc);
938 ex = new DotVarExp(loc, ex, v);
939
940 // This is a hack so we can call destructors on const/immutable objects.
941 ex = new DotIdExp(loc, ex, Id.ptr);
942 ex = new CastExp(loc, ex, sdv.type.pointerTo());
943 if (stc & STC.safe)
944 stc = (stc & ~STC.safe) | STC.trusted;
945
946 SliceExp se = new SliceExp(loc, ex, new IntegerExp(loc, 0, Type.tsize_t),
947 new IntegerExp(loc, n, Type.tsize_t));
948 // Prevent redundant bounds check
949 se.upperIsInBounds = true;
950 se.lowerIsLessThanUpper = true;
951
952 ex = new CallExp(loc, new IdentifierExp(loc, Id.__ArrayDtor), se);
953 }
954 e = Expression.combine(ex, e); // combine in reverse order
955 }
956
957 if (e || (stc & STC.disable))
958 {
959 //printf("Building __fieldDtor(), %s\n", e.toChars());
960 auto dd = new DtorDeclaration(declLoc, Loc.initial, stc, Id.__fieldDtor);
961 dd.flags |= FUNCFLAG.generated;
962 dd.storage_class |= STC.inference;
963 dd.fbody = new ExpStatement(loc, e);
964 ad.members.push(dd);
965 dd.dsymbolSemantic(sc);
966 ad.fieldDtor = dd;
967 }
968 }
969
970 // Generate list of dtors to call in that order
971 DtorDeclarations dtors;
972 foreach_reverse (userDtor; ad.userDtors[])
973 dtors.push(userDtor);
974 if (ad.fieldDtor)
975 dtors.push(ad.fieldDtor);
976 if (!dtorIsCppPrototype)
977 {
978 // extern(C++) destructors call into super to destruct the full hierarchy
979 ClassDeclaration cldec = ad.isClassDeclaration();
980 if (cldec && cldec.classKind == ClassKind.cpp && cldec.baseClass && cldec.baseClass.aggrDtor)
981 dtors.push(cldec.baseClass.aggrDtor);
982 }
983
984 // Set/build `ad.aggrDtor`
985 switch (dtors.dim)
986 {
987 case 0:
988 break;
989
990 case 1:
991 // Use the single existing dtor directly as aggregate dtor.
992 // Note that this might be `cldec.baseClass.aggrDtor`.
993 ad.aggrDtor = dtors[0];
994 break;
995
996 default:
997 // Build the aggregate destructor, calling all dtors in order.
998 assert(!dtorIsCppPrototype);
999 Expression e = null;
1000 e = null;
1001 stc = STC.safe | STC.nothrow_ | STC.pure_ | STC.nogc;
1002 foreach (FuncDeclaration fd; dtors)
1003 {
1004 stc = mergeFuncAttrs(stc, fd);
1005 if (stc & STC.disable)
1006 {
1007 e = null;
1008 break;
1009 }
1010 Expression ex = new ThisExp(loc);
1011 ex = new DotVarExp(loc, ex, fd, false);
1012 CallExp ce = new CallExp(loc, ex);
1013 ce.directcall = true;
1014 e = Expression.combine(e, ce);
1015 }
1016 auto dd = new DtorDeclaration(declLoc, Loc.initial, stc, Id.__aggrDtor);
1017 dd.flags |= FUNCFLAG.generated;
1018 dd.storage_class |= STC.inference;
1019 dd.fbody = new ExpStatement(loc, e);
1020 ad.members.push(dd);
1021 dd.dsymbolSemantic(sc);
1022 ad.aggrDtor = dd;
1023 break;
1024 }
1025
1026 // Set/build `ad.dtor`.
1027 // On Windows, the dtor in the vtable is a shim with different signature.
1028 ad.dtor = (ad.aggrDtor && ad.aggrDtor._linkage == LINK.cpp && !target.cpp.twoDtorInVtable)
1029 ? buildWindowsCppDtor(ad, ad.aggrDtor, sc)
1030 : ad.aggrDtor;
1031
1032 // Add an __xdtor alias to make `ad.dtor` accessible
1033 if (ad.dtor)
1034 {
1035 auto _alias = new AliasDeclaration(Loc.initial, Id.__xdtor, ad.dtor);
1036 _alias.dsymbolSemantic(sc);
1037 ad.members.push(_alias);
1038 if (xdtor_fwd)
1039 ad.symtab.update(_alias); // update forward dtor to correct one
1040 else
1041 _alias.addMember(sc, ad); // add to symbol table
1042 }
1043
1044 // Set/build `ad.tidtor`
1045 ad.tidtor = buildExternDDtor(ad, sc);
1046 }
1047
1048 /**
1049 * build a shim function around the compound dtor that accepts an argument
1050 * that is used to implement the deleting C++ destructor
1051 *
1052 * Params:
1053 * ad = the aggregate that contains the destructor to wrap
1054 * dtor = the destructor to wrap
1055 * sc = the scope in which to analyze the new function
1056 *
1057 * Returns:
1058 * the shim destructor, semantically analyzed and added to the class as a member
1059 */
buildWindowsCppDtor(AggregateDeclaration ad,DtorDeclaration dtor,Scope * sc)1060 private DtorDeclaration buildWindowsCppDtor(AggregateDeclaration ad, DtorDeclaration dtor, Scope* sc)
1061 {
1062 auto cldec = ad.isClassDeclaration();
1063 if (!cldec || cldec.cppDtorVtblIndex == -1) // scalar deleting dtor not built for non-virtual dtors
1064 return dtor;
1065
1066 // generate deleting C++ destructor corresponding to:
1067 // void* C::~C(int del)
1068 // {
1069 // this->~C();
1070 // // TODO: if (del) delete (char*)this;
1071 // return (void*) this;
1072 // }
1073 Parameter delparam = new Parameter(STC.undefined_, Type.tuns32, Identifier.idPool("del"), new IntegerExp(dtor.loc, 0, Type.tuns32), null);
1074 Parameters* params = new Parameters;
1075 params.push(delparam);
1076 auto ftype = new TypeFunction(ParameterList(params), Type.tvoidptr, LINK.cpp, dtor.storage_class);
1077 auto func = new DtorDeclaration(dtor.loc, dtor.loc, dtor.storage_class, Id.cppdtor);
1078 func.type = ftype;
1079
1080 // Always generate the function with body, because it is not exported from DLLs.
1081 const loc = dtor.loc;
1082 auto stmts = new Statements;
1083 auto call = new CallExp(loc, dtor, null);
1084 call.directcall = true;
1085 stmts.push(new ExpStatement(loc, call));
1086 stmts.push(new ReturnStatement(loc, new CastExp(loc, new ThisExp(loc), Type.tvoidptr)));
1087 func.fbody = new CompoundStatement(loc, stmts);
1088 func.flags |= FUNCFLAG.generated;
1089
1090 auto sc2 = sc.push();
1091 sc2.stc &= ~STC.static_; // not a static destructor
1092 sc2.linkage = LINK.cpp;
1093
1094 ad.members.push(func);
1095 func.addMember(sc2, ad);
1096 func.dsymbolSemantic(sc2);
1097
1098 sc2.pop();
1099 return func;
1100 }
1101
1102 /**
1103 * build a shim function around the aggregate dtor that translates
1104 * a C++ destructor to a destructor with extern(D) calling convention
1105 *
1106 * Params:
1107 * ad = the aggregate that contains the destructor to wrap
1108 * sc = the scope in which to analyze the new function
1109 *
1110 * Returns:
1111 * the shim destructor, semantically analyzed and added to the class as a member
1112 */
buildExternDDtor(AggregateDeclaration ad,Scope * sc)1113 private DtorDeclaration buildExternDDtor(AggregateDeclaration ad, Scope* sc)
1114 {
1115 auto dtor = ad.aggrDtor;
1116 if (!dtor)
1117 return null;
1118
1119 // Generate shim only when ABI incompatible on target platform
1120 if (ad.classKind != ClassKind.cpp || !target.cpp.wrapDtorInExternD)
1121 return dtor;
1122
1123 // generate member function that adjusts calling convention
1124 // (EAX used for 'this' instead of ECX on Windows/stack on others):
1125 // extern(D) void __ticppdtor()
1126 // {
1127 // Class.__dtor();
1128 // }
1129 auto ftype = new TypeFunction(ParameterList(), Type.tvoid, LINK.d, dtor.storage_class);
1130 auto func = new DtorDeclaration(dtor.loc, dtor.loc, dtor.storage_class, Id.ticppdtor);
1131 func.type = ftype;
1132
1133 auto call = new CallExp(dtor.loc, dtor, null);
1134 call.directcall = true; // non-virtual call Class.__dtor();
1135 func.fbody = new ExpStatement(dtor.loc, call);
1136 func.flags |= FUNCFLAG.generated;
1137 func.storage_class |= STC.inference;
1138
1139 auto sc2 = sc.push();
1140 sc2.stc &= ~STC.static_; // not a static destructor
1141 sc2.linkage = LINK.d;
1142
1143 ad.members.push(func);
1144 func.addMember(sc2, ad);
1145 func.dsymbolSemantic(sc2);
1146 func.functionSemantic(); // to infer attributes
1147
1148 sc2.pop();
1149 return func;
1150 }
1151
1152 /******************************************
1153 * Create inclusive invariant for struct/class by aggregating
1154 * all the invariants in invs[].
1155 * ---
1156 * void __invariant() const [pure nothrow @trusted]
1157 * {
1158 * invs[0](), invs[1](), ...;
1159 * }
1160 * ---
1161 */
buildInv(AggregateDeclaration ad,Scope * sc)1162 FuncDeclaration buildInv(AggregateDeclaration ad, Scope* sc)
1163 {
1164 switch (ad.invs.dim)
1165 {
1166 case 0:
1167 return null;
1168
1169 case 1:
1170 // Don't return invs[0] so it has uniquely generated name.
1171 goto default;
1172
1173 default:
1174 Expression e = null;
1175 StorageClass stcx = 0;
1176 StorageClass stc = STC.safe | STC.nothrow_ | STC.pure_ | STC.nogc;
1177 foreach (i, inv; ad.invs)
1178 {
1179 stc = mergeFuncAttrs(stc, inv);
1180 if (stc & STC.disable)
1181 {
1182 // What should do?
1183 }
1184 const stcy = (inv.storage_class & STC.synchronized_) |
1185 (inv.type.mod & MODFlags.shared_ ? STC.shared_ : 0);
1186 if (i == 0)
1187 stcx = stcy;
1188 else if (stcx ^ stcy)
1189 {
1190 version (all)
1191 {
1192 // currently rejects
1193 ad.error(inv.loc, "mixing invariants with different `shared`/`synchronized` qualifiers is not supported");
1194 e = null;
1195 break;
1196 }
1197 }
1198 e = Expression.combine(e, new CallExp(Loc.initial, new VarExp(Loc.initial, inv, false)));
1199 }
1200 auto inv = new InvariantDeclaration(ad.loc, Loc.initial, stc | stcx,
1201 Id.classInvariant, new ExpStatement(Loc.initial, e));
1202 ad.members.push(inv);
1203 inv.dsymbolSemantic(sc);
1204 return inv;
1205 }
1206 }
1207
1208 /*****************************************
1209 * Create inclusive postblit for struct by aggregating
1210 * all the postblits in postblits[] with the postblits for
1211 * all the members.
1212 * Note the close similarity with AggregateDeclaration::buildDtor(),
1213 * and the ordering changes (runs forward instead of backwards).
1214 */
buildPostBlit(StructDeclaration sd,Scope * sc)1215 FuncDeclaration buildPostBlit(StructDeclaration sd, Scope* sc)
1216 {
1217 //printf("buildPostBlit() %s\n", sd.toChars());
1218 if (sd.isUnionDeclaration())
1219 return null;
1220
1221 const hasUserDefinedPosblit = sd.postblits.dim && !sd.postblits[0].isDisabled ? true : false;
1222
1223 // by default, the storage class of the created postblit
1224 StorageClass stc = STC.safe | STC.nothrow_ | STC.pure_ | STC.nogc;
1225 Loc declLoc = sd.postblits.dim ? sd.postblits[0].loc : sd.loc;
1226 Loc loc; // internal code should have no loc to prevent coverage
1227
1228 // if any of the postblits are disabled, then the generated postblit
1229 // will be disabled
1230 foreach (postblit; sd.postblits)
1231 stc |= postblit.storage_class & STC.disable;
1232
1233 VarDeclaration[] fieldsToDestroy;
1234 auto postblitCalls = new Statements();
1235 // iterate through all the struct fields that are not disabled
1236 for (size_t i = 0; i < sd.fields.dim && !(stc & STC.disable); i++)
1237 {
1238 auto structField = sd.fields[i];
1239 if (structField.storage_class & STC.ref_)
1240 continue;
1241 if (structField.overlapped)
1242 continue;
1243 // if it's a struct declaration or an array of structs
1244 Type tv = structField.type.baseElemOf();
1245 if (tv.ty != Tstruct)
1246 continue;
1247 auto sdv = (cast(TypeStruct)tv).sym;
1248 // which has a postblit declaration
1249 if (!sdv.postblit)
1250 continue;
1251 assert(!sdv.isUnionDeclaration());
1252
1253 // if this field's postblit is not `nothrow`, add a `scope(failure)`
1254 // block to destroy any prior successfully postblitted fields should
1255 // this field's postblit fail
1256 if (fieldsToDestroy.length > 0 && !(cast(TypeFunction)sdv.postblit.type).isnothrow)
1257 {
1258 // create a list of destructors that need to be called
1259 Expression[] dtorCalls;
1260 foreach(sf; fieldsToDestroy)
1261 {
1262 Expression ex;
1263 tv = sf.type.toBasetype();
1264 if (tv.ty == Tstruct)
1265 {
1266 // this.v.__xdtor()
1267
1268 ex = new ThisExp(loc);
1269 ex = new DotVarExp(loc, ex, sf);
1270
1271 // This is a hack so we can call destructors on const/immutable objects.
1272 ex = new AddrExp(loc, ex);
1273 ex = new CastExp(loc, ex, sf.type.mutableOf().pointerTo());
1274 ex = new PtrExp(loc, ex);
1275 if (stc & STC.safe)
1276 stc = (stc & ~STC.safe) | STC.trusted;
1277
1278 auto sfv = (cast(TypeStruct)sf.type.baseElemOf()).sym;
1279
1280 ex = new DotVarExp(loc, ex, sfv.dtor, false);
1281 ex = new CallExp(loc, ex);
1282
1283 dtorCalls ~= ex;
1284 }
1285 else
1286 {
1287 // _ArrayDtor((cast(S*)this.v.ptr)[0 .. n])
1288
1289 const length = tv.numberOfElems(loc);
1290
1291 ex = new ThisExp(loc);
1292 ex = new DotVarExp(loc, ex, sf);
1293
1294 // This is a hack so we can call destructors on const/immutable objects.
1295 ex = new DotIdExp(loc, ex, Id.ptr);
1296 ex = new CastExp(loc, ex, sdv.type.pointerTo());
1297 if (stc & STC.safe)
1298 stc = (stc & ~STC.safe) | STC.trusted;
1299
1300 auto se = new SliceExp(loc, ex, new IntegerExp(loc, 0, Type.tsize_t),
1301 new IntegerExp(loc, length, Type.tsize_t));
1302 // Prevent redundant bounds check
1303 se.upperIsInBounds = true;
1304 se.lowerIsLessThanUpper = true;
1305
1306 ex = new CallExp(loc, new IdentifierExp(loc, Id.__ArrayDtor), se);
1307
1308 dtorCalls ~= ex;
1309 }
1310 }
1311 fieldsToDestroy = [];
1312
1313 // aggregate the destructor calls
1314 auto dtors = new Statements();
1315 foreach_reverse(dc; dtorCalls)
1316 {
1317 dtors.push(new ExpStatement(loc, dc));
1318 }
1319
1320 // put destructor calls in a `scope(failure)` block
1321 postblitCalls.push(new ScopeGuardStatement(loc, TOK.onScopeFailure, new CompoundStatement(loc, dtors)));
1322 }
1323
1324 // perform semantic on the member postblit in order to
1325 // be able to aggregate it later on with the rest of the
1326 // postblits
1327 sdv.postblit.functionSemantic();
1328
1329 stc = mergeFuncAttrs(stc, sdv.postblit);
1330 stc = mergeFuncAttrs(stc, sdv.dtor);
1331
1332 // if any of the struct member fields has disabled
1333 // its postblit, then `sd` is not copyable, so no
1334 // postblit is generated
1335 if (stc & STC.disable)
1336 {
1337 postblitCalls.setDim(0);
1338 break;
1339 }
1340
1341 Expression ex;
1342 tv = structField.type.toBasetype();
1343 if (tv.ty == Tstruct)
1344 {
1345 // this.v.__xpostblit()
1346
1347 ex = new ThisExp(loc);
1348 ex = new DotVarExp(loc, ex, structField);
1349
1350 // This is a hack so we can call postblits on const/immutable objects.
1351 ex = new AddrExp(loc, ex);
1352 ex = new CastExp(loc, ex, structField.type.mutableOf().pointerTo());
1353 ex = new PtrExp(loc, ex);
1354 if (stc & STC.safe)
1355 stc = (stc & ~STC.safe) | STC.trusted;
1356
1357 ex = new DotVarExp(loc, ex, sdv.postblit, false);
1358 ex = new CallExp(loc, ex);
1359 }
1360 else
1361 {
1362 // _ArrayPostblit((cast(S*)this.v.ptr)[0 .. n])
1363
1364 const length = tv.numberOfElems(loc);
1365 if (length == 0)
1366 continue;
1367
1368 ex = new ThisExp(loc);
1369 ex = new DotVarExp(loc, ex, structField);
1370
1371 // This is a hack so we can call postblits on const/immutable objects.
1372 ex = new DotIdExp(loc, ex, Id.ptr);
1373 ex = new CastExp(loc, ex, sdv.type.pointerTo());
1374 if (stc & STC.safe)
1375 stc = (stc & ~STC.safe) | STC.trusted;
1376
1377 auto se = new SliceExp(loc, ex, new IntegerExp(loc, 0, Type.tsize_t),
1378 new IntegerExp(loc, length, Type.tsize_t));
1379 // Prevent redundant bounds check
1380 se.upperIsInBounds = true;
1381 se.lowerIsLessThanUpper = true;
1382 ex = new CallExp(loc, new IdentifierExp(loc, Id.__ArrayPostblit), se);
1383 }
1384 postblitCalls.push(new ExpStatement(loc, ex)); // combine in forward order
1385
1386 /* https://issues.dlang.org/show_bug.cgi?id=10972
1387 * When subsequent field postblit calls fail,
1388 * this field should be destructed for Exception Safety.
1389 */
1390 if (sdv.dtor)
1391 {
1392 sdv.dtor.functionSemantic();
1393
1394 // keep a list of fields that need to be destroyed in case
1395 // of a future postblit failure
1396 fieldsToDestroy ~= structField;
1397 }
1398 }
1399
1400 void checkShared()
1401 {
1402 if (sd.type.isShared())
1403 stc |= STC.shared_;
1404 }
1405
1406 // Build our own "postblit" which executes a, but only if needed.
1407 if (postblitCalls.dim || (stc & STC.disable))
1408 {
1409 //printf("Building __fieldPostBlit()\n");
1410 checkShared();
1411 auto dd = new PostBlitDeclaration(declLoc, Loc.initial, stc, Id.__fieldPostblit);
1412 dd.flags |= FUNCFLAG.generated;
1413 dd.storage_class |= STC.inference | STC.scope_;
1414 dd.fbody = (stc & STC.disable) ? null : new CompoundStatement(loc, postblitCalls);
1415 sd.postblits.shift(dd);
1416 sd.members.push(dd);
1417 dd.dsymbolSemantic(sc);
1418 }
1419
1420 // create __xpostblit, which is the generated postblit
1421 FuncDeclaration xpostblit = null;
1422 switch (sd.postblits.dim)
1423 {
1424 case 0:
1425 break;
1426
1427 case 1:
1428 xpostblit = sd.postblits[0];
1429 break;
1430
1431 default:
1432 Expression e = null;
1433 stc = STC.safe | STC.nothrow_ | STC.pure_ | STC.nogc;
1434 foreach (fd; sd.postblits)
1435 {
1436 stc = mergeFuncAttrs(stc, fd);
1437 if (stc & STC.disable)
1438 {
1439 e = null;
1440 break;
1441 }
1442 Expression ex = new ThisExp(loc);
1443 ex = new DotVarExp(loc, ex, fd, false);
1444 ex = new CallExp(loc, ex);
1445 e = Expression.combine(e, ex);
1446 }
1447
1448 checkShared();
1449 auto dd = new PostBlitDeclaration(declLoc, Loc.initial, stc, Id.__aggrPostblit);
1450 dd.flags |= FUNCFLAG.generated;
1451 dd.storage_class |= STC.inference;
1452 dd.fbody = new ExpStatement(loc, e);
1453 sd.members.push(dd);
1454 dd.dsymbolSemantic(sc);
1455 xpostblit = dd;
1456 break;
1457 }
1458
1459 // Add an __xpostblit alias to make the inclusive postblit accessible
1460 if (xpostblit)
1461 {
1462 auto _alias = new AliasDeclaration(Loc.initial, Id.__xpostblit, xpostblit);
1463 _alias.dsymbolSemantic(sc);
1464 sd.members.push(_alias);
1465 _alias.addMember(sc, sd); // add to symbol table
1466 }
1467
1468 if (sd.hasCopyCtor)
1469 {
1470 // we have user defined postblit, so we prioritize it
1471 if (hasUserDefinedPosblit)
1472 {
1473 sd.hasCopyCtor = false;
1474 return xpostblit;
1475 }
1476 // we have fields with postblits, so print deprecations
1477 if (xpostblit && !xpostblit.isDisabled())
1478 {
1479 deprecation(sd.loc, "`struct %s` implicitly-generated postblit hides copy constructor.", sd.toChars);
1480 deprecationSupplemental(sd.loc, "The field postblit will have priority over the copy constructor.");
1481 deprecationSupplemental(sd.loc, "To change this, the postblit should be disabled for `struct %s`", sd.toChars());
1482 sd.hasCopyCtor = false;
1483 }
1484 else
1485 xpostblit = null;
1486 }
1487
1488 return xpostblit;
1489 }
1490
1491 /**
1492 * Generates a copy constructor declaration with the specified storage
1493 * class for the parameter and the function.
1494 *
1495 * Params:
1496 * sd = the `struct` that contains the copy constructor
1497 * paramStc = the storage class of the copy constructor parameter
1498 * funcStc = the storage class for the copy constructor declaration
1499 *
1500 * Returns:
1501 * The copy constructor declaration for struct `sd`.
1502 */
generateCopyCtorDeclaration(StructDeclaration sd,const StorageClass paramStc,const StorageClass funcStc)1503 private CtorDeclaration generateCopyCtorDeclaration(StructDeclaration sd, const StorageClass paramStc, const StorageClass funcStc)
1504 {
1505 auto fparams = new Parameters();
1506 auto structType = sd.type;
1507 fparams.push(new Parameter(paramStc | STC.ref_ | STC.return_ | STC.scope_, structType, Id.p, null, null));
1508 ParameterList pList = ParameterList(fparams);
1509 auto tf = new TypeFunction(pList, structType, LINK.d, STC.ref_);
1510 auto ccd = new CtorDeclaration(sd.loc, Loc.initial, STC.ref_, tf, true);
1511 ccd.storage_class |= funcStc;
1512 ccd.storage_class |= STC.inference;
1513 ccd.flags |= FUNCFLAG.generated;
1514 return ccd;
1515 }
1516
1517 /**
1518 * Generates a trivial copy constructor body that simply does memberwise
1519 * initialization:
1520 *
1521 * this.field1 = rhs.field1;
1522 * this.field2 = rhs.field2;
1523 * ...
1524 *
1525 * Params:
1526 * sd = the `struct` declaration that contains the copy constructor
1527 *
1528 * Returns:
1529 * A `CompoundStatement` containing the body of the copy constructor.
1530 */
generateCopyCtorBody(StructDeclaration sd)1531 private Statement generateCopyCtorBody(StructDeclaration sd)
1532 {
1533 Loc loc;
1534 Expression e;
1535 foreach (v; sd.fields)
1536 {
1537 auto ec = new AssignExp(loc,
1538 new DotVarExp(loc, new ThisExp(loc), v),
1539 new DotVarExp(loc, new IdentifierExp(loc, Id.p), v));
1540 e = Expression.combine(e, ec);
1541 //printf("e.toChars = %s\n", e.toChars());
1542 }
1543 Statement s1 = new ExpStatement(loc, e);
1544 return new CompoundStatement(loc, s1);
1545 }
1546
1547 /**
1548 * Determine if a copy constructor is needed for struct sd,
1549 * if the following conditions are met:
1550 *
1551 * 1. sd does not define a copy constructor
1552 * 2. at least one field of sd defines a copy constructor
1553 *
1554 * Params:
1555 * sd = the `struct` for which the copy constructor is generated
1556 * hasCpCtor = set to true if a copy constructor is already present
1557 *
1558 * Returns:
1559 * `true` if one needs to be generated
1560 * `false` otherwise
1561 */
needCopyCtor(StructDeclaration sd,out bool hasCpCtor)1562 private bool needCopyCtor(StructDeclaration sd, out bool hasCpCtor)
1563 {
1564 if (global.errors)
1565 return false;
1566
1567 auto ctor = sd.search(sd.loc, Id.ctor);
1568 if (ctor)
1569 {
1570 if (ctor.isOverloadSet())
1571 return false;
1572 if (auto td = ctor.isTemplateDeclaration())
1573 ctor = td.funcroot;
1574 }
1575
1576 CtorDeclaration cpCtor;
1577 CtorDeclaration rvalueCtor;
1578
1579 if (!ctor)
1580 goto LcheckFields;
1581
1582 overloadApply(ctor, (Dsymbol s)
1583 {
1584 if (s.isTemplateDeclaration())
1585 return 0;
1586 auto ctorDecl = s.isCtorDeclaration();
1587 assert(ctorDecl);
1588 if (ctorDecl.isCpCtor)
1589 {
1590 if (!cpCtor)
1591 cpCtor = ctorDecl;
1592 return 0;
1593 }
1594
1595 auto tf = ctorDecl.type.toTypeFunction();
1596 const dim = tf.parameterList.length;
1597 if (dim == 1 || (dim > 1 && tf.parameterList[1].defaultArg))
1598 {
1599 auto param = tf.parameterList[0];
1600 if (param.type.mutableOf().unSharedOf() == sd.type.mutableOf().unSharedOf())
1601 {
1602 rvalueCtor = ctorDecl;
1603 }
1604 }
1605 return 0;
1606 });
1607
1608 if (cpCtor)
1609 {
1610 if (rvalueCtor)
1611 {
1612 .error(sd.loc, "`struct %s` may not define both a rvalue constructor and a copy constructor", sd.toChars());
1613 errorSupplemental(rvalueCtor.loc,"rvalue constructor defined here");
1614 errorSupplemental(cpCtor.loc, "copy constructor defined here");
1615 }
1616 hasCpCtor = true;
1617 return false;
1618 }
1619
1620 LcheckFields:
1621 VarDeclaration fieldWithCpCtor;
1622 // see if any struct members define a copy constructor
1623 foreach (v; sd.fields)
1624 {
1625 if (v.storage_class & STC.ref_)
1626 continue;
1627 if (v.overlapped)
1628 continue;
1629
1630 auto ts = v.type.baseElemOf().isTypeStruct();
1631 if (!ts)
1632 continue;
1633 if (ts.sym.hasCopyCtor)
1634 {
1635 fieldWithCpCtor = v;
1636 break;
1637 }
1638 }
1639
1640 if (fieldWithCpCtor && rvalueCtor)
1641 {
1642 .error(sd.loc, "`struct %s` may not define a rvalue constructor and have fields with copy constructors", sd.toChars());
1643 errorSupplemental(rvalueCtor.loc,"rvalue constructor defined here");
1644 errorSupplemental(fieldWithCpCtor.loc, "field with copy constructor defined here");
1645 return false;
1646 }
1647 else if (!fieldWithCpCtor)
1648 return false;
1649 return true;
1650 }
1651
1652 /**
1653 * Generates a copy constructor if needCopyCtor() returns true.
1654 * The generated copy constructor will be of the form:
1655 * this(ref return scope inout(S) rhs) inout
1656 * {
1657 * this.field1 = rhs.field1;
1658 * this.field2 = rhs.field2;
1659 * ...
1660 * }
1661 *
1662 * Params:
1663 * sd = the `struct` for which the copy constructor is generated
1664 * sc = the scope where the copy constructor is generated
1665 *
1666 * Returns:
1667 * `true` if `struct` sd defines a copy constructor (explicitly or generated),
1668 * `false` otherwise.
1669 */
buildCopyCtor(StructDeclaration sd,Scope * sc)1670 bool buildCopyCtor(StructDeclaration sd, Scope* sc)
1671 {
1672 bool hasCpCtor;
1673 if (!needCopyCtor(sd, hasCpCtor))
1674 return hasCpCtor;
1675
1676 //printf("generating copy constructor for %s\n", sd.toChars());
1677 const MOD paramMod = MODFlags.wild;
1678 const MOD funcMod = MODFlags.wild;
1679 auto ccd = generateCopyCtorDeclaration(sd, ModToStc(paramMod), ModToStc(funcMod));
1680 auto copyCtorBody = generateCopyCtorBody(sd);
1681 ccd.fbody = copyCtorBody;
1682 sd.members.push(ccd);
1683 ccd.addMember(sc, sd);
1684 const errors = global.startGagging();
1685 Scope* sc2 = sc.push();
1686 sc2.stc = 0;
1687 sc2.linkage = LINK.d;
1688 ccd.dsymbolSemantic(sc2);
1689 ccd.semantic2(sc2);
1690 ccd.semantic3(sc2);
1691 //printf("ccd semantic: %s\n", ccd.type.toChars());
1692 sc2.pop();
1693 if (global.endGagging(errors) || sd.isUnionDeclaration())
1694 {
1695 ccd.storage_class |= STC.disable;
1696 ccd.fbody = null;
1697 }
1698 return true;
1699 }
1700