1
/**
2
 * Define the implicit `opEquals`, `opAssign`, post blit, copy constructor and destructor for structs.
3
 *
4
 * Copyright:   Copyright (C) 1999-2021 by The D Language Foundation, All Rights Reserved
5
 * Authors:     $(LINK2 http://www.digitalmars.com, Walter Bright)
6
 * License:     $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
7
 * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/clone.d, _clone.d)
8
 * Documentation:  https://dlang.org/phobos/dmd_clone.html
9
 * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/clone.d
10
 */
11

12
module dmd.clone;
13

14
import core.stdc.stdio;
15
import dmd.aggregate;
16
import dmd.arraytypes;
17
import dmd.dclass;
18
import dmd.declaration;
19
import dmd.dscope;
20
import dmd.dstruct;
21
import dmd.dsymbol;
22
import dmd.dsymbolsem;
23
import dmd.dtemplate;
24
import dmd.expression;
25
import dmd.expressionsem;
26
import dmd.func;
27
import dmd.globals;
28
import dmd.id;
29
import dmd.identifier;
30
import dmd.init;
31
import dmd.mtype;
32
import dmd.opover;
33
import dmd.semantic2;
34
import dmd.statement;
35
import dmd.target;
36
import dmd.typesem;
37
import dmd.tokens;
38

39
/*******************************************
40
 * Merge function attributes pure, nothrow, @safe, @nogc, and @disable
41
 * from f into s1.
42
 * Params:
43
 *      s1 = storage class to merge into
44
 *      f = function
45
 * Returns:
46
 *      merged storage class
47
 */
48
StorageClass mergeFuncAttrs(StorageClass s1, const FuncDeclaration f) pure
49
{
50 5
    if (!f)
51 5
        return s1;
52 5
    StorageClass s2 = (f.storage_class & STC.disable);
53

54 5
    TypeFunction tf = cast(TypeFunction)f.type;
55 5
    if (tf.trust == TRUST.safe)
56 5
        s2 |= STC.safe;
57 5
    else if (tf.trust == TRUST.system)
58 5
        s2 |= STC.system;
59 5
    else if (tf.trust == TRUST.trusted)
60 5
        s2 |= STC.trusted;
61

62 5
    if (tf.purity != PURE.impure)
63 5
        s2 |= STC.pure_;
64 5
    if (tf.isnothrow)
65 5
        s2 |= STC.nothrow_;
66 5
    if (tf.isnogc)
67 5
        s2 |= STC.nogc;
68

69 5
    const sa = s1 & s2;
70 5
    const so = s1 | s2;
71

72 5
    StorageClass stc = (sa & (STC.pure_ | STC.nothrow_ | STC.nogc)) | (so & STC.disable);
73

74 5
    if (so & STC.system)
75 5
        stc |= STC.system;
76 5
    else if (sa & STC.trusted)
77 5
        stc |= STC.trusted;
78 5
    else if ((so & (STC.trusted | STC.safe)) == (STC.trusted | STC.safe))
79 5
        stc |= STC.trusted;
80 5
    else if (sa & STC.safe)
81 5
        stc |= STC.safe;
82

83 5
    return stc;
84
}
85

86
/*******************************************
87
 * Check given aggregate actually has an identity opAssign or not.
88
 * Params:
89
 *      ad = struct or class
90
 *      sc = current scope
91
 * Returns:
92
 *      if found, returns FuncDeclaration of opAssign, otherwise null
93
 */
94
FuncDeclaration hasIdentityOpAssign(AggregateDeclaration ad, Scope* sc)
95
{
96 5
    Dsymbol assign = search_function(ad, Id.assign);
97 5
    if (assign)
98
    {
99
        /* check identity opAssign exists
100
         */
101 5
        scope er = new NullExp(ad.loc, ad.type);    // dummy rvalue
102 5
        scope el = new IdentifierExp(ad.loc, Id.p); // dummy lvalue
103 5
        el.type = ad.type;
104 5
        Expressions a;
105 5
        a.setDim(1);
106 5
        const errors = global.startGagging(); // Do not report errors, even if the template opAssign fbody makes it.
107 5
        sc = sc.push();
108 5
        sc.tinst = null;
109 5
        sc.minst = null;
110

111 5
        a[0] = er;
112 5
        auto f = resolveFuncCall(ad.loc, sc, assign, null, ad.type, &a, FuncResolveFlag.quiet);
113 5
        if (!f)
114
        {
115 5
            a[0] = el;
116 5
            f = resolveFuncCall(ad.loc, sc, assign, null, ad.type, &a, FuncResolveFlag.quiet);
117
        }
118

119 5
        sc = sc.pop();
120 5
        global.endGagging(errors);
121 5
        if (f)
122
        {
123 5
            if (f.errors)
124 0
                return null;
125 5
            auto fparams = f.getParameterList();
126 5
            if (fparams.length)
127
            {
128 5
                auto fparam0 = fparams[0];
129 5
                if (fparam0.type.toDsymbol(null) != ad)
130 5
                    f = null;
131
            }
132
        }
133
        // BUGS: This detection mechanism cannot find some opAssign-s like follows:
134
        // struct S { void opAssign(ref immutable S) const; }
135 5
        return f;
136
    }
137 5
    return null;
138
}
139

140
/*******************************************
141
 * We need an opAssign for the struct if
142
 * it has a destructor or a postblit.
143
 * We need to generate one if a user-specified one does not exist.
144
 */
145
private bool needOpAssign(StructDeclaration sd)
146
{
147
    //printf("StructDeclaration::needOpAssign() %s\n", sd.toChars());
148

149
    static bool isNeeded()
150
    {
151
        //printf("\tneed\n");
152 5
        return true;
153
    }
154

155 5
    if (sd.isUnionDeclaration())
156 5
        return !isNeeded();
157

158 5
    if (sd.hasIdentityAssign || // because has identity==elaborate opAssign
159 5
        sd.dtor ||
160 5
        sd.postblit)
161 5
        return isNeeded();
162

163
    /* If any of the fields need an opAssign, then we
164
     * need it too.
165
     */
166 5
    foreach (v; sd.fields)
167
    {
168 5
        if (v.storage_class & STC.ref_)
169 0
            continue;
170 5
        if (v.overlapped)               // if field of a union
171 5
            continue;                   // user must handle it themselves
172 5
        Type tv = v.type.baseElemOf();
173 5
        if (tv.ty == Tstruct)
174
        {
175 5
            TypeStruct ts = cast(TypeStruct)tv;
176 5
            if (ts.sym.isUnionDeclaration())
177 5
                continue;
178 5
            if (needOpAssign(ts.sym))
179 5
                return isNeeded();
180
        }
181
    }
182 5
    return !isNeeded();
183
}
184

185
/******************************************
186
 * Build opAssign for a `struct`.
187
 *
188
 * The generated `opAssign` function has the following signature:
189
 *---
190
 *ref S opAssign(S s)    // S is the name of the `struct`
191
 *---
192
 *
193
 * The opAssign function will be built for a struct `S` if the
194
 * following constraints are met:
195
 *
196
 * 1. `S` does not have an identity `opAssign` defined.
197
 *
198
 * 2. `S` has at least one of the following members: a postblit (user-defined or
199
 * generated for fields that have a defined postblit), a destructor
200
 * (user-defined or generated for fields that have a defined destructor)
201
 * or at least one field that has a defined `opAssign`.
202
 *
203
 * 3. `S` does not have any non-mutable fields.
204
 *
205
 * If `S` has a disabled destructor or at least one field that has a disabled
206
 * `opAssign`, `S.opAssign` is going to be generated, but marked with `@disable`
207
 *
208
 * If `S` defines a destructor, the generated code for `opAssign` is:
209
 *
210
 *---
211
 *S __swap = void;
212
 *__swap = this;   // bit copy
213
 *this = s;        // bit copy
214
 *__swap.dtor();
215
 *---
216
 *
217
 * Otherwise, if `S` defines a postblit, the generated code for `opAssign` is:
218
 *
219
 *---
220
 *this = s;
221
 *---
222
 *
223
 * Note that the parameter to the generated `opAssign` is passed by value, which means
224
 * that the postblit is going to be called (if it is defined) in both  of the above
225
 * situations before entering the body of `opAssign`. The assignments in the above generated
226
 * function bodies are blit expressions, so they can be regarded as `memcpy`s
227
 * (`opAssign` is not called as this will result in an infinite recursion; the postblit
228
 * is not called because it has already been called when the parameter was passed by value).
229
 *
230
 * If `S` does not have a postblit or a destructor, but contains at least one field that defines
231
 * an `opAssign` function (which is not disabled), then the body will make member-wise
232
 * assignments:
233
 *
234
 *---
235
 *this.field1 = s.field1;
236
 *this.field2 = s.field2;
237
 *...;
238
 *---
239
 *
240
 * In this situation, the assignemnts are actual assign expressions (`opAssign` is used
241
 * if defined).
242
 *
243
 * References:
244
 *      https://dlang.org/spec/struct.html#assign-overload
245
 * Params:
246
 *      sd = struct to generate opAssign for
247
 *      sc = context
248
 * Returns:
249
 *      generated `opAssign` function
250
 */
251
FuncDeclaration buildOpAssign(StructDeclaration sd, Scope* sc)
252
{
253 5
    if (FuncDeclaration f = hasIdentityOpAssign(sd, sc))
254
    {
255 5
        sd.hasIdentityAssign = true;
256 5
        return f;
257
    }
258
    // Even if non-identity opAssign is defined, built-in identity opAssign
259
    // will be defined.
260 5
    if (!needOpAssign(sd))
261 5
        return null;
262

263
    //printf("StructDeclaration::buildOpAssign() %s\n", sd.toChars());
264 5
    StorageClass stc = STC.safe | STC.nothrow_ | STC.pure_ | STC.nogc;
265 5
    Loc declLoc = sd.loc;
266 5
    Loc loc; // internal code should have no loc to prevent coverage
267

268
    // One of our sub-field might have `@disable opAssign` so we need to
269
    // check for it.
270
    // In this event, it will be reflected by having `stc` (opAssign's
271
    // storage class) include `STC.disabled`.
272 5
    foreach (v; sd.fields)
273
    {
274 5
        if (v.storage_class & STC.ref_)
275 0
            continue;
276 5
        if (v.overlapped)
277 5
            continue;
278 5
        Type tv = v.type.baseElemOf();
279 5
        if (tv.ty != Tstruct)
280 5
            continue;
281 5
        StructDeclaration sdv = (cast(TypeStruct)tv).sym;
282 5
        stc = mergeFuncAttrs(stc, hasIdentityOpAssign(sdv, sc));
283
    }
284

285 5
    if (sd.dtor || sd.postblit)
286
    {
287
        // if the type is not assignable, we cannot generate opAssign
288 5
        if (!sd.type.isAssignable()) // https://issues.dlang.org/show_bug.cgi?id=13044
289 5
            return null;
290 5
        stc = mergeFuncAttrs(stc, sd.dtor);
291 5
        if (stc & STC.safe)
292 5
            stc = (stc & ~STC.safe) | STC.trusted;
293
    }
294

295 5
    auto fparams = new Parameters();
296 5
    fparams.push(new Parameter(STC.nodtor, sd.type, Id.p, null, null));
297 5
    auto tf = new TypeFunction(ParameterList(fparams), sd.handleType(), LINK.d, stc | STC.ref_);
298 5
    auto fop = new FuncDeclaration(declLoc, Loc.initial, Id.assign, stc, tf);
299 5
    fop.storage_class |= STC.inference;
300 5
    fop.generated = true;
301 5
    Expression e;
302 5
    if (stc & STC.disable)
303
    {
304 5
        e = null;
305
    }
306
    /* Do swap this and rhs.
307
     *    __swap = this; this = s; __swap.dtor();
308
     */
309 5
    else if (sd.dtor)
310
    {
311
        //printf("\tswap copy\n");
312 5
        TypeFunction tdtor = cast(TypeFunction)sd.dtor.type;
313 5
        assert(tdtor.ty == Tfunction);
314

315 5
        auto idswap = Identifier.generateId("__swap");
316 5
        auto swap = new VarDeclaration(loc, sd.type, idswap, new VoidInitializer(loc));
317 5
        swap.storage_class |= STC.nodtor | STC.temp | STC.ctfe;
318 5
        if (tdtor.isScopeQual)
319 5
            swap.storage_class |= STC.scope_;
320 5
        auto e1 = new DeclarationExp(loc, swap);
321

322 5
        auto e2 = new BlitExp(loc, new VarExp(loc, swap), new ThisExp(loc));
323 5
        auto e3 = new BlitExp(loc, new ThisExp(loc), new IdentifierExp(loc, Id.p));
324

325
        /* Instead of running the destructor on s, run it
326
         * on swap. This avoids needing to copy swap back in to s.
327
         */
328 5
        auto e4 = new CallExp(loc, new DotVarExp(loc, new VarExp(loc, swap), sd.dtor, false));
329

330 5
        e = Expression.combine(e1, e2, e3, e4);
331
    }
332
    /* postblit was called when the value was passed to opAssign, we just need to blit the result */
333 5
    else if (sd.postblit)
334
    {
335 5
        e = new BlitExp(loc, new ThisExp(loc), new IdentifierExp(loc, Id.p));
336 5
        sd.hasBlitAssign = true;
337
    }
338
    else
339
    {
340
        /* Do memberwise copy.
341
         *
342
         * If sd is a nested struct, its vthis field assignment is:
343
         * 1. If it's nested in a class, it's a rebind of class reference.
344
         * 2. If it's nested in a function or struct, it's an update of void*.
345
         * In both cases, it will change the parent context.
346
         */
347
        //printf("\tmemberwise copy\n");
348 5
        e = null;
349 5
        foreach (v; sd.fields)
350
        {
351
            // this.v = s.v;
352 5
            auto ec = new AssignExp(loc,
353
                new DotVarExp(loc, new ThisExp(loc), v),
354
                new DotVarExp(loc, new IdentifierExp(loc, Id.p), v));
355 5
            e = Expression.combine(e, ec);
356
        }
357
    }
358 5
    if (e)
359
    {
360 5
        Statement s1 = new ExpStatement(loc, e);
361
        /* Add:
362
         *   return this;
363
         */
364 5
        auto er = new ThisExp(loc);
365 5
        Statement s2 = new ReturnStatement(loc, er);
366 5
        fop.fbody = new CompoundStatement(loc, s1, s2);
367 5
        tf.isreturn = true;
368
    }
369 5
    sd.members.push(fop);
370 5
    fop.addMember(sc, sd);
371 5
    sd.hasIdentityAssign = true; // temporary mark identity assignable
372 5
    const errors = global.startGagging(); // Do not report errors, even if the template opAssign fbody makes it.
373 5
    Scope* sc2 = sc.push();
374 5
    sc2.stc = 0;
375 5
    sc2.linkage = LINK.d;
376 5
    fop.dsymbolSemantic(sc2);
377 5
    fop.semantic2(sc2);
378
    // https://issues.dlang.org/show_bug.cgi?id=15044
379
    //semantic3(fop, sc2); // isn't run here for lazy forward reference resolution.
380

381 5
    sc2.pop();
382 5
    if (global.endGagging(errors)) // if errors happened
383
    {
384
        // Disable generated opAssign, because some members forbid identity assignment.
385 0
        fop.storage_class |= STC.disable;
386 0
        fop.fbody = null; // remove fbody which contains the error
387
    }
388

389
    //printf("-StructDeclaration::buildOpAssign() %s, errors = %d\n", sd.toChars(), (fop.storage_class & STC.disable) != 0);
390
    //printf("fop.type: %s\n", fop.type.toPrettyChars());
391 5
    return fop;
392
}
393

394
/*******************************************
395
 * We need an opEquals for the struct if
396
 * any fields has an opEquals.
397
 * Generate one if a user-specified one does not exist.
398
 */
399
bool needOpEquals(StructDeclaration sd)
400
{
401
    //printf("StructDeclaration::needOpEquals() %s\n", sd.toChars());
402 5
    if (sd.isUnionDeclaration())
403 5
        goto Ldontneed;
404 5
    if (sd.hasIdentityEquals)
405 5
        goto Lneed;
406
    /* If any of the fields has an opEquals, then we
407
     * need it too.
408
     */
409 5
    for (size_t i = 0; i < sd.fields.dim; i++)
410
    {
411 5
        VarDeclaration v = sd.fields[i];
412 5
        if (v.storage_class & STC.ref_)
413 0
            continue;
414 5
        if (v.overlapped)
415 5
            continue;
416 5
        Type tv = v.type.toBasetype();
417 5
        auto tvbase = tv.baseElemOf();
418 5
        if (tvbase.ty == Tstruct)
419
        {
420 5
            TypeStruct ts = cast(TypeStruct)tvbase;
421 5
            if (ts.sym.isUnionDeclaration())
422 5
                continue;
423 5
            if (needOpEquals(ts.sym))
424 5
                goto Lneed;
425 5
            if (ts.sym.aliasthis) // https://issues.dlang.org/show_bug.cgi?id=14806
426 5
                goto Lneed;
427
        }
428 5
        if (tvbase.isfloating())
429
        {
430
            // This is necessray for:
431
            //  1. comparison of +0.0 and -0.0 should be true.
432
            //  2. comparison of NANs should be false always.
433 5
            goto Lneed;
434
        }
435 5
        if (tvbase.ty == Tarray)
436 5
            goto Lneed;
437 5
        if (tvbase.ty == Taarray)
438 5
            goto Lneed;
439 5
        if (tvbase.ty == Tclass)
440 5
            goto Lneed;
441
    }
442
Ldontneed:
443
    //printf("\tdontneed\n");
444 5
    return false;
445
Lneed:
446
    //printf("\tneed\n");
447 5
    return true;
448
}
449

450
/*******************************************
451
 * Check given aggregate actually has an identity opEquals or not.
452
 */
453
private FuncDeclaration hasIdentityOpEquals(AggregateDeclaration ad, Scope* sc)
454
{
455 5
    FuncDeclaration f;
456 5
    if (Dsymbol eq = search_function(ad, Id.eq))
457
    {
458
        /* check identity opEquals exists
459
         */
460 5
        scope er = new NullExp(ad.loc, null); // dummy rvalue
461 5
        scope el = new IdentifierExp(ad.loc, Id.p); // dummy lvalue
462 5
        Expressions a;
463 5
        a.setDim(1);
464

465
        bool hasIt(Type tthis)
466
        {
467 5
            const errors = global.startGagging(); // Do not report errors, even if the template opAssign fbody makes it
468 5
            sc = sc.push();
469 5
            sc.tinst = null;
470 5
            sc.minst = null;
471

472
            FuncDeclaration rfc(Expression e)
473
            {
474 5
                a[0] = e;
475 5
                a[0].type = tthis;
476 5
                return resolveFuncCall(ad.loc, sc, eq, null, tthis, &a, FuncResolveFlag.quiet);
477
            }
478

479 5
            f = rfc(er);
480 5
            if (!f)
481 5
                f = rfc(el);
482

483 5
            sc = sc.pop();
484 5
            global.endGagging(errors);
485

486 5
            return f !is null;
487
        }
488

489 5
        if (hasIt(ad.type)               ||
490 5
            hasIt(ad.type.constOf())     ||
491 5
            hasIt(ad.type.immutableOf()) ||
492 5
            hasIt(ad.type.sharedOf())    ||
493 5
            hasIt(ad.type.sharedConstOf()))
494
        {
495 5
            if (f.errors)
496 0
                return null;
497
        }
498
    }
499 5
    return f;
500
}
501

502
/******************************************
503
 * Build opEquals for struct.
504
 *      const bool opEquals(const S s) { ... }
505
 *
506
 * By fixing https://issues.dlang.org/show_bug.cgi?id=3789
507
 * opEquals is changed to be never implicitly generated.
508
 * Now, struct objects comparison s1 == s2 is translated to:
509
 *      s1.tupleof == s2.tupleof
510
 * to calculate structural equality. See EqualExp.op_overload.
511
 */
512
FuncDeclaration buildOpEquals(StructDeclaration sd, Scope* sc)
513
{
514 5
    if (hasIdentityOpEquals(sd, sc))
515
    {
516 5
        sd.hasIdentityEquals = true;
517
    }
518 5
    return null;
519
}
520

521
/******************************************
522
 * Build __xopEquals for TypeInfo_Struct
523
 *      static bool __xopEquals(ref const S p, ref const S q)
524
 *      {
525
 *          return p == q;
526
 *      }
527
 *
528
 * This is called by TypeInfo.equals(p1, p2). If the struct does not support
529
 * const objects comparison, it will throw "not implemented" Error in runtime.
530
 */
531
FuncDeclaration buildXopEquals(StructDeclaration sd, Scope* sc)
532
{
533 5
    if (!needOpEquals(sd))
534 5
        return null; // bitwise comparison would work
535

536
    //printf("StructDeclaration::buildXopEquals() %s\n", sd.toChars());
537 5
    if (Dsymbol eq = search_function(sd, Id.eq))
538
    {
539 5
        if (FuncDeclaration fd = eq.isFuncDeclaration())
540
        {
541 5
            TypeFunction tfeqptr;
542
            {
543 5
                Scope scx;
544
                /* const bool opEquals(ref const S s);
545
                 */
546 5
                auto parameters = new Parameters();
547 5
                parameters.push(new Parameter(STC.ref_ | STC.const_, sd.type, null, null, null));
548 5
                tfeqptr = new TypeFunction(ParameterList(parameters), Type.tbool, LINK.d);
549 5
                tfeqptr.mod = MODFlags.const_;
550 5
                tfeqptr = cast(TypeFunction)tfeqptr.typeSemantic(Loc.initial, &scx);
551
            }
552 5
            fd = fd.overloadExactMatch(tfeqptr);
553 5
            if (fd)
554 5
                return fd;
555
        }
556
    }
557 5
    if (!sd.xerreq)
558
    {
559
        // object._xopEquals
560 5
        Identifier id = Identifier.idPool("_xopEquals");
561 5
        Expression e = new IdentifierExp(sd.loc, Id.empty);
562 5
        e = new DotIdExp(sd.loc, e, Id.object);
563 5
        e = new DotIdExp(sd.loc, e, id);
564 5
        e = e.expressionSemantic(sc);
565 5
        Dsymbol s = getDsymbol(e);
566 5
        assert(s);
567 5
        sd.xerreq = s.isFuncDeclaration();
568
    }
569 5
    Loc declLoc; // loc is unnecessary so __xopEquals is never called directly
570 5
    Loc loc; // loc is unnecessary so errors are gagged
571 5
    auto parameters = new Parameters();
572 5
    parameters.push(new Parameter(STC.ref_ | STC.const_, sd.type, Id.p, null, null))
573
              .push(new Parameter(STC.ref_ | STC.const_, sd.type, Id.q, null, null));
574 5
    auto tf = new TypeFunction(ParameterList(parameters), Type.tbool, LINK.d);
575 5
    Identifier id = Id.xopEquals;
576 5
    auto fop = new FuncDeclaration(declLoc, Loc.initial, id, STC.static_, tf);
577 5
    fop.generated = true;
578 5
    Expression e1 = new IdentifierExp(loc, Id.p);
579 5
    Expression e2 = new IdentifierExp(loc, Id.q);
580 5
    Expression e = new EqualExp(TOK.equal, loc, e1, e2);
581 5
    fop.fbody = new ReturnStatement(loc, e);
582 5
    uint errors = global.startGagging(); // Do not report errors
583 5
    Scope* sc2 = sc.push();
584 5
    sc2.stc = 0;
585 5
    sc2.linkage = LINK.d;
586 5
    fop.dsymbolSemantic(sc2);
587 5
    fop.semantic2(sc2);
588 5
    sc2.pop();
589 5
    if (global.endGagging(errors)) // if errors happened
590 0
        fop = sd.xerreq;
591 5
    return fop;
592
}
593

594
/******************************************
595
 * Build __xopCmp for TypeInfo_Struct
596
 *      static bool __xopCmp(ref const S p, ref const S q)
597
 *      {
598
 *          return p.opCmp(q);
599
 *      }
600
 *
601
 * This is called by TypeInfo.compare(p1, p2). If the struct does not support
602
 * const objects comparison, it will throw "not implemented" Error in runtime.
603
 */
604
FuncDeclaration buildXopCmp(StructDeclaration sd, Scope* sc)
605
{
606
    //printf("StructDeclaration::buildXopCmp() %s\n", toChars());
607 5
    if (Dsymbol cmp = search_function(sd, Id.cmp))
608
    {
609 5
        if (FuncDeclaration fd = cmp.isFuncDeclaration())
610
        {
611 5
            TypeFunction tfcmpptr;
612
            {
613 5
                Scope scx;
614
                /* const int opCmp(ref const S s);
615
                 */
616 5
                auto parameters = new Parameters();
617 5
                parameters.push(new Parameter(STC.ref_ | STC.const_, sd.type, null, null, null));
618 5
                tfcmpptr = new TypeFunction(ParameterList(parameters), Type.tint32, LINK.d);
619 5
                tfcmpptr.mod = MODFlags.const_;
620 5
                tfcmpptr = cast(TypeFunction)tfcmpptr.typeSemantic(Loc.initial, &scx);
621
            }
622 5
            fd = fd.overloadExactMatch(tfcmpptr);
623 5
            if (fd)
624 5
                return fd;
625
        }
626
    }
627
    else
628
    {
629
        version (none) // FIXME: doesn't work for recursive alias this
630
        {
631
            /* Check opCmp member exists.
632
             * Consider 'alias this', but except opDispatch.
633
             */
634
            Expression e = new DsymbolExp(sd.loc, sd);
635
            e = new DotIdExp(sd.loc, e, Id.cmp);
636
            Scope* sc2 = sc.push();
637
            e = e.trySemantic(sc2);
638
            sc2.pop();
639
            if (e)
640
            {
641
                Dsymbol s = null;
642
                switch (e.op)
643
                {
644
                case TOK.overloadSet:
645
                    s = (cast(OverExp)e).vars;
646
                    break;
647
                case TOK.scope_:
648
                    s = (cast(ScopeExp)e).sds;
649
                    break;
650
                case TOK.variable:
651
                    s = (cast(VarExp)e).var;
652
                    break;
653
                default:
654
                    break;
655
                }
656
                if (!s || s.ident != Id.cmp)
657
                    e = null; // there's no valid member 'opCmp'
658
            }
659
            if (!e)
660
                return null; // bitwise comparison would work
661
            /* Essentially, a struct which does not define opCmp is not comparable.
662
             * At this time, typeid(S).compare might be correct that throwing "not implement" Error.
663
             * But implementing it would break existing code, such as:
664
             *
665
             * struct S { int value; }  // no opCmp
666
             * int[S] aa;   // Currently AA key uses bitwise comparison
667
             *              // (It's default behavior of TypeInfo_Strust.compare).
668
             *
669
             * Not sure we should fix this inconsistency, so just keep current behavior.
670
             */
671
        }
672
        else
673
        {
674 5
            return null;
675
        }
676
    }
677 5
    if (!sd.xerrcmp)
678
    {
679
        // object._xopCmp
680 5
        Identifier id = Identifier.idPool("_xopCmp");
681 5
        Expression e = new IdentifierExp(sd.loc, Id.empty);
682 5
        e = new DotIdExp(sd.loc, e, Id.object);
683 5
        e = new DotIdExp(sd.loc, e, id);
684 5
        e = e.expressionSemantic(sc);
685 5
        Dsymbol s = getDsymbol(e);
686 5
        assert(s);
687 5
        sd.xerrcmp = s.isFuncDeclaration();
688
    }
689 5
    Loc declLoc; // loc is unnecessary so __xopCmp is never called directly
690 5
    Loc loc; // loc is unnecessary so errors are gagged
691 5
    auto parameters = new Parameters();
692 5
    parameters.push(new Parameter(STC.ref_ | STC.const_, sd.type, Id.p, null, null));
693 5
    parameters.push(new Parameter(STC.ref_ | STC.const_, sd.type, Id.q, null, null));
694 5
    auto tf = new TypeFunction(ParameterList(parameters), Type.tint32, LINK.d);
695 5
    Identifier id = Id.xopCmp;
696 5
    auto fop = new FuncDeclaration(declLoc, Loc.initial, id, STC.static_, tf);
697 5
    fop.generated = true;
698 5
    Expression e1 = new IdentifierExp(loc, Id.p);
699 5
    Expression e2 = new IdentifierExp(loc, Id.q);
700 5
    Expression e = new CallExp(loc, new DotIdExp(loc, e2, Id.cmp), e1);
701 5
    fop.fbody = new ReturnStatement(loc, e);
702 5
    uint errors = global.startGagging(); // Do not report errors
703 5
    Scope* sc2 = sc.push();
704 5
    sc2.stc = 0;
705 5
    sc2.linkage = LINK.d;
706 5
    fop.dsymbolSemantic(sc2);
707 5
    fop.semantic2(sc2);
708 5
    sc2.pop();
709 5
    if (global.endGagging(errors)) // if errors happened
710 0
        fop = sd.xerrcmp;
711 5
    return fop;
712
}
713

714
/*******************************************
715
 * We need a toHash for the struct if
716
 * any fields has a toHash.
717
 * Generate one if a user-specified one does not exist.
718
 */
719
private bool needToHash(StructDeclaration sd)
720
{
721
    //printf("StructDeclaration::needToHash() %s\n", sd.toChars());
722 5
    if (sd.isUnionDeclaration())
723 5
        goto Ldontneed;
724 5
    if (sd.xhash)
725 5
        goto Lneed;
726

727
    /* If any of the fields has an opEquals, then we
728
     * need it too.
729
     */
730 5
    for (size_t i = 0; i < sd.fields.dim; i++)
731
    {
732 5
        VarDeclaration v = sd.fields[i];
733 5
        if (v.storage_class & STC.ref_)
734 0
            continue;
735 5
        if (v.overlapped)
736 5
            continue;
737 5
        Type tv = v.type.toBasetype();
738 5
        auto tvbase = tv.baseElemOf();
739 5
        if (tvbase.ty == Tstruct)
740
        {
741 5
            TypeStruct ts = cast(TypeStruct)tvbase;
742 5
            if (ts.sym.isUnionDeclaration())
743 5
                continue;
744 5
            if (needToHash(ts.sym))
745 5
                goto Lneed;
746 5
            if (ts.sym.aliasthis) // https://issues.dlang.org/show_bug.cgi?id=14948
747 5
                goto Lneed;
748
        }
749 5
        if (tvbase.isfloating())
750
        {
751
            /* This is necessary because comparison of +0.0 and -0.0 should be true,
752
             * i.e. not a bit compare.
753
             */
754 5
            goto Lneed;
755
        }
756 5
        if (tvbase.ty == Tarray)
757 5
            goto Lneed;
758 5
        if (tvbase.ty == Taarray)
759 5
            goto Lneed;
760 5
        if (tvbase.ty == Tclass)
761 5
            goto Lneed;
762
    }
763
Ldontneed:
764
    //printf("\tdontneed\n");
765 5
    return false;
766
Lneed:
767
    //printf("\tneed\n");
768 5
    return true;
769
}
770

771
/******************************************
772
 * Build __xtoHash for non-bitwise hashing
773
 *      static hash_t xtoHash(ref const S p) nothrow @trusted;
774
 */
775
FuncDeclaration buildXtoHash(StructDeclaration sd, Scope* sc)
776
{
777 5
    if (Dsymbol s = search_function(sd, Id.tohash))
778
    {
779 5
        __gshared TypeFunction tftohash;
780 5
        if (!tftohash)
781
        {
782 5
            tftohash = new TypeFunction(ParameterList(), Type.thash_t, LINK.d);
783 5
            tftohash.mod = MODFlags.const_;
784 5
            tftohash = cast(TypeFunction)tftohash.merge();
785
        }
786 5
        if (FuncDeclaration fd = s.isFuncDeclaration())
787
        {
788 5
            fd = fd.overloadExactMatch(tftohash);
789 5
            if (fd)
790 5
                return fd;
791
        }
792
    }
793 5
    if (!needToHash(sd))
794 5
        return null;
795

796
    //printf("StructDeclaration::buildXtoHash() %s\n", sd.toPrettyChars());
797 5
    Loc declLoc; // loc is unnecessary so __xtoHash is never called directly
798 5
    Loc loc; // internal code should have no loc to prevent coverage
799 5
    auto parameters = new Parameters();
800 5
    parameters.push(new Parameter(STC.ref_ | STC.const_, sd.type, Id.p, null, null));
801 5
    auto tf = new TypeFunction(ParameterList(parameters), Type.thash_t, LINK.d, STC.nothrow_ | STC.trusted);
802 5
    Identifier id = Id.xtoHash;
803 5
    auto fop = new FuncDeclaration(declLoc, Loc.initial, id, STC.static_, tf);
804 5
    fop.generated = true;
805

806
    /* Do memberwise hashing.
807
     *
808
     * If sd is a nested struct, and if it's nested in a class, the calculated
809
     * hash value will also contain the result of parent class's toHash().
810
     */
811 5
    const(char)[] code =
812
        ".object.size_t h = 0;" ~
813
        "foreach (i, T; typeof(p.tupleof))" ~
814
        // workaround https://issues.dlang.org/show_bug.cgi?id=17968
815
        "    static if(is(T* : const(.object.Object)*)) " ~
816
        "        h = h * 33 + typeid(const(.object.Object)).getHash(cast(const void*)&p.tupleof[i]);" ~
817
        "    else " ~
818
        "        h = h * 33 + typeid(T).getHash(cast(const void*)&p.tupleof[i]);" ~
819
        "return h;";
820 5
    fop.fbody = new CompileStatement(loc, new StringExp(loc, code));
821 5
    Scope* sc2 = sc.push();
822 5
    sc2.stc = 0;
823 5
    sc2.linkage = LINK.d;
824 5
    fop.dsymbolSemantic(sc2);
825 5
    fop.semantic2(sc2);
826 5
    sc2.pop();
827

828
    //printf("%s fop = %s %s\n", sd.toChars(), fop.toChars(), fop.type.toChars());
829 5
    return fop;
830
}
831

832
/*****************************************
833
 * Create inclusive destructor for struct/class by aggregating
834
 * all the destructors in dtors[] with the destructors for
835
 * all the members.
836
 * Params:
837
 *      ad = struct or class to build destructor for
838
 *      sc = context
839
 * Returns:
840
 *      generated function, null if none needed
841
 * Note:
842
 * Close similarity with StructDeclaration::buildPostBlit(),
843
 * and the ordering changes (runs backward instead of forwards).
844
 */
845
DtorDeclaration buildDtor(AggregateDeclaration ad, Scope* sc)
846
{
847
    //printf("AggregateDeclaration::buildDtor() %s\n", ad.toChars());
848 5
    if (ad.isUnionDeclaration())
849 5
        return null;                    // unions don't have destructors
850

851 5
    StorageClass stc = STC.safe | STC.nothrow_ | STC.pure_ | STC.nogc;
852 5
    Loc declLoc = ad.dtors.dim ? ad.dtors[0].loc : ad.loc;
853 5
    Loc loc; // internal code should have no loc to prevent coverage
854 5
    FuncDeclaration xdtor_fwd = null;
855

856
    // if the dtor is an extern(C++) prototype, then we expect it performs a full-destruction; we don't need to build a full-dtor
857 5
    const bool dtorIsCppPrototype = ad.dtors.dim == 1 && ad.dtors[0].linkage == LINK.cpp && !ad.dtors[0].fbody;
858 5
    if (!dtorIsCppPrototype)
859
    {
860 5
        Expression e = null;
861 5
        for (size_t i = 0; i < ad.fields.dim; i++)
862
        {
863 5
            auto v = ad.fields[i];
864 5
            if (v.storage_class & STC.ref_)
865 0
                continue;
866 5
            if (v.overlapped)
867 5
                continue;
868 5
            auto tv = v.type.baseElemOf();
869 5
            if (tv.ty != Tstruct)
870 5
                continue;
871 5
            auto sdv = (cast(TypeStruct)tv).sym;
872 5
            if (!sdv.dtor)
873 5
                continue;
874

875
            // fix: https://issues.dlang.org/show_bug.cgi?id=17257
876
            // braces for shrink wrapping scope of a
877
            {
878 5
                xdtor_fwd = sdv.dtor; // this dtor is temporary it could be anything
879 5
                auto a = new AliasDeclaration(Loc.initial, Id.__xdtor, xdtor_fwd);
880 5
                a.addMember(sc, ad); // temporarily add to symbol table
881
            }
882

883 5
            sdv.dtor.functionSemantic();
884

885 5
            stc = mergeFuncAttrs(stc, sdv.dtor);
886 5
            if (stc & STC.disable)
887
            {
888 5
                e = null;
889 5
                break;
890
            }
891

892 5
            Expression ex;
893 5
            tv = v.type.toBasetype();
894 5
            if (tv.ty == Tstruct)
895
            {
896
                // this.v.__xdtor()
897

898 5
                ex = new ThisExp(loc);
899 5
                ex = new DotVarExp(loc, ex, v);
900

901
                // This is a hack so we can call destructors on const/immutable objects.
902
                // Do it as a type 'paint'.
903 5
                ex = new CastExp(loc, ex, v.type.mutableOf());
904 5
                if (stc & STC.safe)
905 5
                    stc = (stc & ~STC.safe) | STC.trusted;
906

907 5
                ex = new DotVarExp(loc, ex, sdv.dtor, false);
908 5
                ex = new CallExp(loc, ex);
909
            }
910
            else
911
            {
912
                // __ArrayDtor((cast(S*)this.v.ptr)[0 .. n])
913

914 5
                const n = tv.numberOfElems(loc);
915 5
                if (n == 0)
916 5
                    continue;
917

918 5
                ex = new ThisExp(loc);
919 5
                ex = new DotVarExp(loc, ex, v);
920

921
                // This is a hack so we can call destructors on const/immutable objects.
922 5
                ex = new DotIdExp(loc, ex, Id.ptr);
923 5
                ex = new CastExp(loc, ex, sdv.type.pointerTo());
924 5
                if (stc & STC.safe)
925 5
                    stc = (stc & ~STC.safe) | STC.trusted;
926

927 5
                ex = new SliceExp(loc, ex, new IntegerExp(loc, 0, Type.tsize_t),
928
                                           new IntegerExp(loc, n, Type.tsize_t));
929
                // Prevent redundant bounds check
930 5
                (cast(SliceExp)ex).upperIsInBounds = true;
931 5
                (cast(SliceExp)ex).lowerIsLessThanUpper = true;
932

933 5
                ex = new CallExp(loc, new IdentifierExp(loc, Id.__ArrayDtor), ex);
934
            }
935 5
            e = Expression.combine(ex, e); // combine in reverse order
936
        }
937

938
        /* extern(C++) destructors call into super to destruct the full hierarchy
939
        */
940 5
        ClassDeclaration cldec = ad.isClassDeclaration();
941 5
        if (cldec && cldec.classKind == ClassKind.cpp && cldec.baseClass && cldec.baseClass.primaryDtor)
942
        {
943
            // WAIT BUT: do I need to run `cldec.baseClass.dtor` semantic? would it have been run before?
944 5
            cldec.baseClass.dtor.functionSemantic();
945

946 5
            stc = mergeFuncAttrs(stc, cldec.baseClass.primaryDtor);
947 5
            if (!(stc & STC.disable))
948
            {
949
                // super.__xdtor()
950

951 5
                Expression ex = new SuperExp(loc);
952

953
                // This is a hack so we can call destructors on const/immutable objects.
954
                // Do it as a type 'paint'.
955 5
                ex = new CastExp(loc, ex, cldec.baseClass.type.mutableOf());
956 5
                if (stc & STC.safe)
957 2
                    stc = (stc & ~STC.safe) | STC.trusted;
958

959 5
                ex = new DotVarExp(loc, ex, cldec.baseClass.primaryDtor, false);
960 5
                ex = new CallExp(loc, ex);
961

962 5
                e = Expression.combine(e, ex); // super dtor last
963
            }
964
        }
965

966
        /* Build our own "destructor" which executes e
967
         */
968 5
        if (e || (stc & STC.disable))
969
        {
970
            //printf("Building __fieldDtor(), %s\n", e.toChars());
971 5
            auto dd = new DtorDeclaration(declLoc, Loc.initial, stc, Id.__fieldDtor);
972 5
            dd.generated = true;
973 5
            dd.storage_class |= STC.inference;
974 5
            dd.fbody = new ExpStatement(loc, e);
975 5
            ad.dtors.shift(dd);
976 5
            ad.members.push(dd);
977 5
            dd.dsymbolSemantic(sc);
978 5
            ad.fieldDtor = dd;
979
        }
980
    }
981

982 5
    DtorDeclaration xdtor = null;
983 5
    switch (ad.dtors.dim)
984
    {
985 5
    case 0:
986 5
        break;
987

988 5
    case 1:
989 5
        xdtor = ad.dtors[0];
990 5
        break;
991

992 5
    default:
993 5
        assert(!dtorIsCppPrototype);
994 5
        Expression e = null;
995 5
        e = null;
996 5
        stc = STC.safe | STC.nothrow_ | STC.pure_ | STC.nogc;
997 5
        for (size_t i = 0; i < ad.dtors.dim; i++)
998
        {
999 5
            FuncDeclaration fd = ad.dtors[i];
1000 5
            stc = mergeFuncAttrs(stc, fd);
1001 5
            if (stc & STC.disable)
1002
            {
1003 5
                e = null;
1004 5
                break;
1005
            }
1006 5
            Expression ex = new ThisExp(loc);
1007 5
            ex = new DotVarExp(loc, ex, fd, false);
1008 5
            ex = new CallExp(loc, ex);
1009 5
            e = Expression.combine(ex, e);
1010
        }
1011 5
        auto dd = new DtorDeclaration(declLoc, Loc.initial, stc, Id.__aggrDtor);
1012 5
        dd.generated = true;
1013 5
        dd.storage_class |= STC.inference;
1014 5
        dd.fbody = new ExpStatement(loc, e);
1015 5
        ad.members.push(dd);
1016 5
        dd.dsymbolSemantic(sc);
1017 5
        xdtor = dd;
1018 5
        break;
1019
    }
1020

1021 5
    ad.primaryDtor = xdtor;
1022

1023 5
    if (xdtor && xdtor.linkage == LINK.cpp && !target.cpp.twoDtorInVtable)
1024 1
        xdtor = buildWindowsCppDtor(ad, xdtor, sc);
1025

1026
    // Add an __xdtor alias to make the inclusive dtor accessible
1027 5
    if (xdtor)
1028
    {
1029 5
        auto _alias = new AliasDeclaration(Loc.initial, Id.__xdtor, xdtor);
1030 5
        _alias.dsymbolSemantic(sc);
1031 5
        ad.members.push(_alias);
1032 5
        if (xdtor_fwd)
1033 5
            ad.symtab.update(_alias); // update forward dtor to correct one
1034
        else
1035 5
            _alias.addMember(sc, ad); // add to symbol table
1036
    }
1037

1038 5
    return xdtor;
1039
}
1040

1041
/**
1042
 * build a shim function around the compound dtor that accepts an argument
1043
 *  that is used to implement the deleting C++ destructor
1044
 *
1045
 * Params:
1046
 *  ad = the aggregate that contains the destructor to wrap
1047
 *  dtor = the destructor to wrap
1048
 *  sc = the scope in which to analyze the new function
1049
 *
1050
 * Returns:
1051
 *  the shim destructor, semantically analyzed and added to the class as a member
1052
 */
1053
private DtorDeclaration buildWindowsCppDtor(AggregateDeclaration ad, DtorDeclaration dtor, Scope* sc)
1054
{
1055 1
    auto cldec = ad.isClassDeclaration();
1056 1
    if (!cldec || cldec.cppDtorVtblIndex == -1) // scalar deleting dtor not built for non-virtual dtors
1057 1
        return dtor;
1058

1059
    // generate deleting C++ destructor corresponding to:
1060
    // void* C::~C(int del)
1061
    // {
1062
    //   this->~C();
1063
    //   // TODO: if (del) delete (char*)this;
1064
    //   return (void*) this;
1065
    // }
1066 1
    Parameter delparam = new Parameter(STC.undefined_, Type.tuns32, Identifier.idPool("del"), new IntegerExp(dtor.loc, 0, Type.tuns32), null);
1067 1
    Parameters* params = new Parameters;
1068 1
    params.push(delparam);
1069 1
    auto ftype = new TypeFunction(ParameterList(params), Type.tvoidptr, LINK.cpp, dtor.storage_class);
1070 1
    auto func = new DtorDeclaration(dtor.loc, dtor.loc, dtor.storage_class, Id.cppdtor);
1071 1
    func.type = ftype;
1072 1
    if (dtor.fbody)
1073
    {
1074 1
        const loc = dtor.loc;
1075 1
        auto stmts = new Statements;
1076 1
        auto call = new CallExp(loc, dtor, null);
1077 1
        call.directcall = true;
1078 1
        stmts.push(new ExpStatement(loc, call));
1079 1
        stmts.push(new ReturnStatement(loc, new CastExp(loc, new ThisExp(loc), Type.tvoidptr)));
1080 1
        func.fbody = new CompoundStatement(loc, stmts);
1081 1
        func.generated = true;
1082
    }
1083

1084 1
    auto sc2 = sc.push();
1085 1
    sc2.stc &= ~STC.static_; // not a static destructor
1086 1
    sc2.linkage = LINK.cpp;
1087

1088 1
    ad.members.push(func);
1089 1
    func.addMember(sc2, ad);
1090 1
    func.dsymbolSemantic(sc2);
1091

1092 1
    sc2.pop();
1093 1
    return func;
1094
}
1095

1096
/**
1097
 * build a shim function around the compound dtor that translates
1098
 *  a C++ destructor to a destructor with extern(D) calling convention
1099
 *
1100
 * Params:
1101
 *  ad = the aggregate that contains the destructor to wrap
1102
 *  sc = the scope in which to analyze the new function
1103
 *
1104
 * Returns:
1105
 *  the shim destructor, semantically analyzed and added to the class as a member
1106
 */
1107
DtorDeclaration buildExternDDtor(AggregateDeclaration ad, Scope* sc)
1108
{
1109 5
    auto dtor = ad.primaryDtor;
1110 5
    if (!dtor)
1111 5
        return null;
1112

1113
    // ABI incompatible on all (?) x86 32-bit platforms
1114 5
    if (ad.classKind != ClassKind.cpp || target.is64bit)
1115 5
        return dtor;
1116

1117
    // generate member function that adjusts calling convention
1118
    // (EAX used for 'this' instead of ECX on Windows/stack on others):
1119
    // extern(D) void __ticppdtor()
1120
    // {
1121
    //     Class.__dtor();
1122
    // }
1123 1
    auto ftype = new TypeFunction(ParameterList(), Type.tvoid, LINK.d, dtor.storage_class);
1124 1
    auto func = new DtorDeclaration(dtor.loc, dtor.loc, dtor.storage_class, Id.ticppdtor);
1125 1
    func.type = ftype;
1126

1127 1
    auto call = new CallExp(dtor.loc, dtor, null);
1128 1
    call.directcall = true;                   // non-virtual call Class.__dtor();
1129 1
    func.fbody = new ExpStatement(dtor.loc, call);
1130 1
    func.generated = true;
1131 1
    func.storage_class |= STC.inference;
1132

1133 1
    auto sc2 = sc.push();
1134 1
    sc2.stc &= ~STC.static_; // not a static destructor
1135 1
    sc2.linkage = LINK.d;
1136

1137 1
    ad.members.push(func);
1138 1
    func.addMember(sc2, ad);
1139 1
    func.dsymbolSemantic(sc2);
1140 1
    func.functionSemantic(); // to infer attributes
1141

1142 1
    sc2.pop();
1143 1
    return func;
1144
}
1145

1146
/******************************************
1147
 * Create inclusive invariant for struct/class by aggregating
1148
 * all the invariants in invs[].
1149
 * ---
1150
 * void __invariant() const [pure nothrow @trusted]
1151
 * {
1152
 *     invs[0](), invs[1](), ...;
1153
 * }
1154
 * ---
1155
 */
1156
FuncDeclaration buildInv(AggregateDeclaration ad, Scope* sc)
1157
{
1158 5
    switch (ad.invs.dim)
1159
    {
1160 5
    case 0:
1161 5
        return null;
1162

1163 5
    case 1:
1164
        // Don't return invs[0] so it has uniquely generated name.
1165 5
        goto default;
1166

1167 5
    default:
1168 5
        Expression e = null;
1169 5
        StorageClass stcx = 0;
1170 5
        StorageClass stc = STC.safe | STC.nothrow_ | STC.pure_ | STC.nogc;
1171 5
        foreach (i, inv; ad.invs)
1172
        {
1173 5
            stc = mergeFuncAttrs(stc, inv);
1174 5
            if (stc & STC.disable)
1175
            {
1176
                // What should do?
1177
            }
1178 5
            const stcy = (inv.storage_class & STC.synchronized_) |
1179 5
                         (inv.type.mod & MODFlags.shared_ ? STC.shared_ : 0);
1180 5
            if (i == 0)
1181 5
                stcx = stcy;
1182 5
            else if (stcx ^ stcy)
1183
            {
1184
                version (all)
1185
                {
1186
                    // currently rejects
1187 3
                    ad.error(inv.loc, "mixing invariants with different `shared`/`synchronized` qualifiers is not supported");
1188 3
                    e = null;
1189 3
                    break;
1190
                }
1191
            }
1192 5
            e = Expression.combine(e, new CallExp(Loc.initial, new VarExp(Loc.initial, inv, false)));
1193
        }
1194 5
        auto inv = new InvariantDeclaration(ad.loc, Loc.initial, stc | stcx,
1195
                Id.classInvariant, new ExpStatement(Loc.initial, e));
1196 5
        ad.members.push(inv);
1197 5
        inv.dsymbolSemantic(sc);
1198 5
        return inv;
1199
    }
1200
}

Read our documentation on viewing source code .

Loading