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 if (!f) 51 return s1; 52 StorageClass s2 = (f.storage_class & STC.disable); 53 54 TypeFunction tf = cast(TypeFunction)f.type; 55 if (tf.trust == TRUST.safe) 56 s2 |= STC.safe; 57 else if (tf.trust == TRUST.system) 58 s2 |= STC.system; 59 else if (tf.trust == TRUST.trusted) 60 s2 |= STC.trusted; 61 62 if (tf.purity != PURE.impure) 63 s2 |= STC.pure_; 64 if (tf.isnothrow) 65 s2 |= STC.nothrow_; 66 if (tf.isnogc) 67 s2 |= STC.nogc; 68 69 const sa = s1 & s2; 70 const so = s1 | s2; 71 72 StorageClass stc = (sa & (STC.pure_ | STC.nothrow_ | STC.nogc)) | (so & STC.disable); 73 74 if (so & STC.system) 75 stc |= STC.system; 76 else if (sa & STC.trusted) 77 stc |= STC.trusted; 78 else if ((so & (STC.trusted | STC.safe)) == (STC.trusted | STC.safe)) 79 stc |= STC.trusted; 80 else if (sa & STC.safe) 81 stc |= STC.safe; 82 83 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 Dsymbol assign = search_function(ad, Id.assign); 97 if (assign) 98 { 99 /* check identity opAssign exists 100 */ 101 scope er = new NullExp(ad.loc, ad.type); // dummy rvalue 102 scope el = new IdentifierExp(ad.loc, Id.p); // dummy lvalue 103 el.type = ad.type; 104 Expressions a; 105 a.setDim(1); 106 const errors = global.startGagging(); // Do not report errors, even if the template opAssign fbody makes it. 107 sc = sc.push(); 108 sc.tinst = null; 109 sc.minst = null; 110 111 a[0] = er; 112 auto f = resolveFuncCall(ad.loc, sc, assign, null, ad.type, &a, FuncResolveFlag.quiet); 113 if (!f) 114 { 115 a[0] = el; 116 f = resolveFuncCall(ad.loc, sc, assign, null, ad.type, &a, FuncResolveFlag.quiet); 117 } 118 119 sc = sc.pop(); 120 global.endGagging(errors); 121 if (f) 122 { 123 if (f.errors) 124 return null; 125 auto fparams = f.getParameterList(); 126 if (fparams.length) 127 { 128 auto fparam0 = fparams[0]; 129 if (fparam0.type.toDsymbol(null) != ad) 130 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 return f; 136 } 137 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 return true; 153 } 154 155 if (sd.isUnionDeclaration()) 156 return !isNeeded(); 157 158 if (sd.hasIdentityAssign || // because has identity==elaborate opAssign 159 sd.dtor || 160 sd.postblit) 161 return isNeeded(); 162 163 /* If any of the fields need an opAssign, then we 164 * need it too. 165 */ 166 foreach (v; sd.fields) 167 { 168 if (v.storage_class & STC.ref_) 169 continue; 170 if (v.overlapped) // if field of a union 171 continue; // user must handle it themselves 172 Type tv = v.type.baseElemOf(); 173 if (tv.ty == Tstruct) 174 { 175 TypeStruct ts = cast(TypeStruct)tv; 176 if (ts.sym.isUnionDeclaration()) 177 continue; 178 if (needOpAssign(ts.sym)) 179 return isNeeded(); 180 } 181 } 182 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 if (FuncDeclaration f = hasIdentityOpAssign(sd, sc)) 254 { 255 sd.hasIdentityAssign = true; 256 return f; 257 } 258 // Even if non-identity opAssign is defined, built-in identity opAssign 259 // will be defined. 260 if (!needOpAssign(sd)) 261 return null; 262 263 //printf("StructDeclaration::buildOpAssign() %s\n", sd.toChars()); 264 StorageClass stc = STC.safe | STC.nothrow_ | STC.pure_ | STC.nogc; 265 Loc declLoc = sd.loc; 266 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 foreach (v; sd.fields) 273 { 274 if (v.storage_class & STC.ref_) 275 continue; 276 if (v.overlapped) 277 continue; 278 Type tv = v.type.baseElemOf(); 279 if (tv.ty != Tstruct) 280 continue; 281 StructDeclaration sdv = (cast(TypeStruct)tv).sym; 282 stc = mergeFuncAttrs(stc, hasIdentityOpAssign(sdv, sc)); 283 } 284 285 if (sd.dtor || sd.postblit) 286 { 287 // if the type is not assignable, we cannot generate opAssign 288 if (!sd.type.isAssignable()) // https://issues.dlang.org/show_bug.cgi?id=13044 289 return null; 290 stc = mergeFuncAttrs(stc, sd.dtor); 291 if (stc & STC.safe) 292 stc = (stc & ~STC.safe) | STC.trusted; 293 } 294 295 auto fparams = new Parameters(); 296 fparams.push(new Parameter(STC.nodtor, sd.type, Id.p, null, null)); 297 auto tf = new TypeFunction(ParameterList(fparams), sd.handleType(), LINK.d, stc | STC.ref_); 298 auto fop = new FuncDeclaration(declLoc, Loc.initial, Id.assign, stc, tf); 299 fop.storage_class |= STC.inference; 300 fop.generated = true; 301 Expression e; 302 if (stc & STC.disable) 303 { 304 e = null; 305 } 306 /* Do swap this and rhs. 307 * __swap = this; this = s; __swap.dtor(); 308 */ 309 else if (sd.dtor) 310 { 311 //printf("\tswap copy\n"); 312 TypeFunction tdtor = cast(TypeFunction)sd.dtor.type; 313 assert(tdtor.ty == Tfunction); 314 315 auto idswap = Identifier.generateId("__swap"); 316 auto swap = new VarDeclaration(loc, sd.type, idswap, new VoidInitializer(loc)); 317 swap.storage_class |= STC.nodtor | STC.temp | STC.ctfe; 318 if (tdtor.isScopeQual) 319 swap.storage_class |= STC.scope_; 320 auto e1 = new DeclarationExp(loc, swap); 321 322 auto e2 = new BlitExp(loc, new VarExp(loc, swap), new ThisExp(loc)); 323 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 auto e4 = new CallExp(loc, new DotVarExp(loc, new VarExp(loc, swap), sd.dtor, false)); 329 330 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 else if (sd.postblit) 334 { 335 e = new BlitExp(loc, new ThisExp(loc), new IdentifierExp(loc, Id.p)); 336 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 e = null; 349 foreach (v; sd.fields) 350 { 351 // this.v = s.v; 352 auto ec = new AssignExp(loc, 353 new DotVarExp(loc, new ThisExp(loc), v), 354 new DotVarExp(loc, new IdentifierExp(loc, Id.p), v)); 355 e = Expression.combine(e, ec); 356 } 357 } 358 if (e) 359 { 360 Statement s1 = new ExpStatement(loc, e); 361 /* Add: 362 * return this; 363 */ 364 auto er = new ThisExp(loc); 365 Statement s2 = new ReturnStatement(loc, er); 366 fop.fbody = new CompoundStatement(loc, s1, s2); 367 tf.isreturn = true; 368 } 369 sd.members.push(fop); 370 fop.addMember(sc, sd); 371 sd.hasIdentityAssign = true; // temporary mark identity assignable 372 const errors = global.startGagging(); // Do not report errors, even if the template opAssign fbody makes it. 373 Scope* sc2 = sc.push(); 374 sc2.stc = 0; 375 sc2.linkage = LINK.d; 376 fop.dsymbolSemantic(sc2); 377 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 sc2.pop(); 382 if (global.endGagging(errors)) // if errors happened 383 { 384 // Disable generated opAssign, because some members forbid identity assignment. 385 fop.storage_class |= STC.disable; 386 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 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 if (sd.isUnionDeclaration()) 403 goto Ldontneed; 404 if (sd.hasIdentityEquals) 405 goto Lneed; 406 /* If any of the fields has an opEquals, then we 407 * need it too. 408 */ 409 for (size_t i = 0; i < sd.fields.dim; i++) 410 { 411 VarDeclaration v = sd.fields[i]; 412 if (v.storage_class & STC.ref_) 413 continue; 414 if (v.overlapped) 415 continue; 416 Type tv = v.type.toBasetype(); 417 auto tvbase = tv.baseElemOf(); 418 if (tvbase.ty == Tstruct) 419 { 420 TypeStruct ts = cast(TypeStruct)tvbase; 421 if (ts.sym.isUnionDeclaration()) 422 continue; 423 if (needOpEquals(ts.sym)) 424 goto Lneed; 425 if (ts.sym.aliasthis) // https://issues.dlang.org/show_bug.cgi?id=14806 426 goto Lneed; 427 } 428 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 goto Lneed; 434 } 435 if (tvbase.ty == Tarray) 436 goto Lneed; 437 if (tvbase.ty == Taarray) 438 goto Lneed; 439 if (tvbase.ty == Tclass) 440 goto Lneed; 441 } 442 Ldontneed: 443 //printf("\tdontneed\n"); 444 return false; 445 Lneed: 446 //printf("\tneed\n"); 447 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 FuncDeclaration f; 456 if (Dsymbol eq = search_function(ad, Id.eq)) 457 { 458 /* check identity opEquals exists 459 */ 460 scope er = new NullExp(ad.loc, null); // dummy rvalue 461 scope el = new IdentifierExp(ad.loc, Id.p); // dummy lvalue 462 Expressions a; 463 a.setDim(1); 464 465 bool hasIt(Type tthis) 466 { 467 const errors = global.startGagging(); // Do not report errors, even if the template opAssign fbody makes it 468 sc = sc.push(); 469 sc.tinst = null; 470 sc.minst = null; 471 472 FuncDeclaration rfc(Expression e) 473 { 474 a[0] = e; 475 a[0].type = tthis; 476 return resolveFuncCall(ad.loc, sc, eq, null, tthis, &a, FuncResolveFlag.quiet); 477 } 478 479 f = rfc(er); 480 if (!f) 481 f = rfc(el); 482 483 sc = sc.pop(); 484 global.endGagging(errors); 485 486 return f !is null; 487 } 488 489 if (hasIt(ad.type) || 490 hasIt(ad.type.constOf()) || 491 hasIt(ad.type.immutableOf()) || 492 hasIt(ad.type.sharedOf()) || 493 hasIt(ad.type.sharedConstOf())) 494 { 495 if (f.errors) 496 return null; 497 } 498 } 499 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 if (hasIdentityOpEquals(sd, sc)) 515 { 516 sd.hasIdentityEquals = true; 517 } 518 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 if (!needOpEquals(sd)) 534 return null; // bitwise comparison would work 535 536 //printf("StructDeclaration::buildXopEquals() %s\n", sd.toChars()); 537 if (Dsymbol eq = search_function(sd, Id.eq)) 538 { 539 if (FuncDeclaration fd = eq.isFuncDeclaration()) 540 { 541 TypeFunction tfeqptr; 542 { 543 Scope scx; 544 /* const bool opEquals(ref const S s); 545 */ 546 auto parameters = new Parameters(); 547 parameters.push(new Parameter(STC.ref_ | STC.const_, sd.type, null, null, null)); 548 tfeqptr = new TypeFunction(ParameterList(parameters), Type.tbool, LINK.d); 549 tfeqptr.mod = MODFlags.const_; 550 tfeqptr = cast(TypeFunction)tfeqptr.typeSemantic(Loc.initial, &scx); 551 } 552 fd = fd.overloadExactMatch(tfeqptr); 553 if (fd) 554 return fd; 555 } 556 } 557 if (!sd.xerreq) 558 { 559 // object._xopEquals 560 Identifier id = Identifier.idPool("_xopEquals"); 561 Expression e = new IdentifierExp(sd.loc, Id.empty); 562 e = new DotIdExp(sd.loc, e, Id.object); 563 e = new DotIdExp(sd.loc, e, id); 564 e = e.expressionSemantic(sc); 565 Dsymbol s = getDsymbol(e); 566 assert(s); 567 sd.xerreq = s.isFuncDeclaration(); 568 } 569 Loc declLoc; // loc is unnecessary so __xopEquals is never called directly 570 Loc loc; // loc is unnecessary so errors are gagged 571 auto parameters = new Parameters(); 572 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 auto tf = new TypeFunction(ParameterList(parameters), Type.tbool, LINK.d); 575 Identifier id = Id.xopEquals; 576 auto fop = new FuncDeclaration(declLoc, Loc.initial, id, STC.static_, tf); 577 fop.generated = true; 578 Expression e1 = new IdentifierExp(loc, Id.p); 579 Expression e2 = new IdentifierExp(loc, Id.q); 580 Expression e = new EqualExp(TOK.equal, loc, e1, e2); 581 fop.fbody = new ReturnStatement(loc, e); 582 uint errors = global.startGagging(); // Do not report errors 583 Scope* sc2 = sc.push(); 584 sc2.stc = 0; 585 sc2.linkage = LINK.d; 586 fop.dsymbolSemantic(sc2); 587 fop.semantic2(sc2); 588 sc2.pop(); 589 if (global.endGagging(errors)) // if errors happened 590 fop = sd.xerreq; 591 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 if (Dsymbol cmp = search_function(sd, Id.cmp)) 608 { 609 if (FuncDeclaration fd = cmp.isFuncDeclaration()) 610 { 611 TypeFunction tfcmpptr; 612 { 613 Scope scx; 614 /* const int opCmp(ref const S s); 615 */ 616 auto parameters = new Parameters(); 617 parameters.push(new Parameter(STC.ref_ | STC.const_, sd.type, null, null, null)); 618 tfcmpptr = new TypeFunction(ParameterList(parameters), Type.tint32, LINK.d); 619 tfcmpptr.mod = MODFlags.const_; 620 tfcmpptr = cast(TypeFunction)tfcmpptr.typeSemantic(Loc.initial, &scx); 621 } 622 fd = fd.overloadExactMatch(tfcmpptr); 623 if (fd) 624 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 return null; 675 } 676 } 677 if (!sd.xerrcmp) 678 { 679 // object._xopCmp 680 Identifier id = Identifier.idPool("_xopCmp"); 681 Expression e = new IdentifierExp(sd.loc, Id.empty); 682 e = new DotIdExp(sd.loc, e, Id.object); 683 e = new DotIdExp(sd.loc, e, id); 684 e = e.expressionSemantic(sc); 685 Dsymbol s = getDsymbol(e); 686 assert(s); 687 sd.xerrcmp = s.isFuncDeclaration(); 688 } 689 Loc declLoc; // loc is unnecessary so __xopCmp is never called directly 690 Loc loc; // loc is unnecessary so errors are gagged 691 auto parameters = new Parameters(); 692 parameters.push(new Parameter(STC.ref_ | STC.const_, sd.type, Id.p, null, null)); 693 parameters.push(new Parameter(STC.ref_ | STC.const_, sd.type, Id.q, null, null)); 694 auto tf = new TypeFunction(ParameterList(parameters), Type.tint32, LINK.d); 695 Identifier id = Id.xopCmp; 696 auto fop = new FuncDeclaration(declLoc, Loc.initial, id, STC.static_, tf); 697 fop.generated = true; 698 Expression e1 = new IdentifierExp(loc, Id.p); 699 Expression e2 = new IdentifierExp(loc, Id.q); 700 Expression e = new CallExp(loc, new DotIdExp(loc, e2, Id.cmp), e1); 701 fop.fbody = new ReturnStatement(loc, e); 702 uint errors = global.startGagging(); // Do not report errors 703 Scope* sc2 = sc.push(); 704 sc2.stc = 0; 705 sc2.linkage = LINK.d; 706 fop.dsymbolSemantic(sc2); 707 fop.semantic2(sc2); 708 sc2.pop(); 709 if (global.endGagging(errors)) // if errors happened 710 fop = sd.xerrcmp; 711 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 if (sd.isUnionDeclaration()) 723 goto Ldontneed; 724 if (sd.xhash) 725 goto Lneed; 726 727 /* If any of the fields has an opEquals, then we 728 * need it too. 729 */ 730 for (size_t i = 0; i < sd.fields.dim; i++) 731 { 732 VarDeclaration v = sd.fields[i]; 733 if (v.storage_class & STC.ref_) 734 continue; 735 if (v.overlapped) 736 continue; 737 Type tv = v.type.toBasetype(); 738 auto tvbase = tv.baseElemOf(); 739 if (tvbase.ty == Tstruct) 740 { 741 TypeStruct ts = cast(TypeStruct)tvbase; 742 if (ts.sym.isUnionDeclaration()) 743 continue; 744 if (needToHash(ts.sym)) 745 goto Lneed; 746 if (ts.sym.aliasthis) // https://issues.dlang.org/show_bug.cgi?id=14948 747 goto Lneed; 748 } 749 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 goto Lneed; 755 } 756 if (tvbase.ty == Tarray) 757 goto Lneed; 758 if (tvbase.ty == Taarray) 759 goto Lneed; 760 if (tvbase.ty == Tclass) 761 goto Lneed; 762 } 763 Ldontneed: 764 //printf("\tdontneed\n"); 765 return false; 766 Lneed: 767 //printf("\tneed\n"); 768 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 if (Dsymbol s = search_function(sd, Id.tohash)) 778 { 779 __gshared TypeFunction tftohash; 780 if (!tftohash) 781 { 782 tftohash = new TypeFunction(ParameterList(), Type.thash_t, LINK.d); 783 tftohash.mod = MODFlags.const_; 784 tftohash = cast(TypeFunction)tftohash.merge(); 785 } 786 if (FuncDeclaration fd = s.isFuncDeclaration()) 787 { 788 fd = fd.overloadExactMatch(tftohash); 789 if (fd) 790 return fd; 791 } 792 } 793 if (!needToHash(sd)) 794 return null; 795 796 //printf("StructDeclaration::buildXtoHash() %s\n", sd.toPrettyChars()); 797 Loc declLoc; // loc is unnecessary so __xtoHash is never called directly 798 Loc loc; // internal code should have no loc to prevent coverage 799 auto parameters = new Parameters(); 800 parameters.push(new Parameter(STC.ref_ | STC.const_, sd.type, Id.p, null, null)); 801 auto tf = new TypeFunction(ParameterList(parameters), Type.thash_t, LINK.d, STC.nothrow_ | STC.trusted); 802 Identifier id = Id.xtoHash; 803 auto fop = new FuncDeclaration(declLoc, Loc.initial, id, STC.static_, tf); 804 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 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 fop.fbody = new CompileStatement(loc, new StringExp(loc, code)); 821 Scope* sc2 = sc.push(); 822 sc2.stc = 0; 823 sc2.linkage = LINK.d; 824 fop.dsymbolSemantic(sc2); 825 fop.semantic2(sc2); 826 sc2.pop(); 827 828 //printf("%s fop = %s %s\n", sd.toChars(), fop.toChars(), fop.type.toChars()); 829 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 if (ad.isUnionDeclaration()) 849 return null; // unions don't have destructors 850 851 StorageClass stc = STC.safe | STC.nothrow_ | STC.pure_ | STC.nogc; 852 Loc declLoc = ad.dtors.dim ? ad.dtors[0].loc : ad.loc; 853 Loc loc; // internal code should have no loc to prevent coverage 854 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 const bool dtorIsCppPrototype = ad.dtors.dim == 1 && ad.dtors[0].linkage == LINK.cpp && !ad.dtors[0].fbody; 858 if (!dtorIsCppPrototype) 859 { 860 Expression e = null; 861 for (size_t i = 0; i < ad.fields.dim; i++) 862 { 863 auto v = ad.fields[i]; 864 if (v.storage_class & STC.ref_) 865 continue; 866 if (v.overlapped) 867 continue; 868 auto tv = v.type.baseElemOf(); 869 if (tv.ty != Tstruct) 870 continue; 871 auto sdv = (cast(TypeStruct)tv).sym; 872 if (!sdv.dtor) 873 continue; 874 875 // fix: https://issues.dlang.org/show_bug.cgi?id=17257 876 // braces for shrink wrapping scope of a 877 { 878 xdtor_fwd = sdv.dtor; // this dtor is temporary it could be anything 879 auto a = new AliasDeclaration(Loc.initial, Id.__xdtor, xdtor_fwd); 880 a.addMember(sc, ad); // temporarily add to symbol table 881 } 882 883 sdv.dtor.functionSemantic(); 884 885 stc = mergeFuncAttrs(stc, sdv.dtor); 886 if (stc & STC.disable) 887 { 888 e = null; 889 break; 890 } 891 892 Expression ex; 893 tv = v.type.toBasetype(); 894 if (tv.ty == Tstruct) 895 { 896 // this.v.__xdtor() 897 898 ex = new ThisExp(loc); 899 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 ex = new CastExp(loc, ex, v.type.mutableOf()); 904 if (stc & STC.safe) 905 stc = (stc & ~STC.safe) | STC.trusted; 906 907 ex = new DotVarExp(loc, ex, sdv.dtor, false); 908 ex = new CallExp(loc, ex); 909 } 910 else 911 { 912 // __ArrayDtor((cast(S*)this.v.ptr)[0 .. n]) 913 914 const n = tv.numberOfElems(loc); 915 if (n == 0) 916 continue; 917 918 ex = new ThisExp(loc); 919 ex = new DotVarExp(loc, ex, v); 920 921 // This is a hack so we can call destructors on const/immutable objects. 922 ex = new DotIdExp(loc, ex, Id.ptr); 923 ex = new CastExp(loc, ex, sdv.type.pointerTo()); 924 if (stc & STC.safe) 925 stc = (stc & ~STC.safe) | STC.trusted; 926 927 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 (cast(SliceExp)ex).upperIsInBounds = true; 931 (cast(SliceExp)ex).lowerIsLessThanUpper = true; 932 933 ex = new CallExp(loc, new IdentifierExp(loc, Id.__ArrayDtor), ex); 934 } 935 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 ClassDeclaration cldec = ad.isClassDeclaration(); 941 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 cldec.baseClass.dtor.functionSemantic(); 945 946 stc = mergeFuncAttrs(stc, cldec.baseClass.primaryDtor); 947 if (!(stc & STC.disable)) 948 { 949 // super.__xdtor() 950 951 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 ex = new CastExp(loc, ex, cldec.baseClass.type.mutableOf()); 956 if (stc & STC.safe) 957 stc = (stc & ~STC.safe) | STC.trusted; 958 959 ex = new DotVarExp(loc, ex, cldec.baseClass.primaryDtor, false); 960 ex = new CallExp(loc, ex); 961 962 e = Expression.combine(e, ex); // super dtor last 963 } 964 } 965 966 /* Build our own "destructor" which executes e 967 */ 968 if (e || (stc & STC.disable)) 969 { 970 //printf("Building __fieldDtor(), %s\n", e.toChars()); 971 auto dd = new DtorDeclaration(declLoc, Loc.initial, stc, Id.__fieldDtor); 972 dd.generated = true; 973 dd.storage_class |= STC.inference; 974 dd.fbody = new ExpStatement(loc, e); 975 ad.dtors.shift(dd); 976 ad.members.push(dd); 977 dd.dsymbolSemantic(sc); 978 ad.fieldDtor = dd; 979 } 980 } 981 982 DtorDeclaration xdtor = null; 983 switch (ad.dtors.dim) 984 { 985 case 0: 986 break; 987 988 case 1: 989 xdtor = ad.dtors[0]; 990 break; 991 992 default: 993 assert(!dtorIsCppPrototype); 994 Expression e = null; 995 e = null; 996 stc = STC.safe | STC.nothrow_ | STC.pure_ | STC.nogc; 997 for (size_t i = 0; i < ad.dtors.dim; i++) 998 { 999 FuncDeclaration fd = ad.dtors[i]; 1000 stc = mergeFuncAttrs(stc, fd); 1001 if (stc & STC.disable) 1002 { 1003 e = null; 1004 break; 1005 } 1006 Expression ex = new ThisExp(loc); 1007 ex = new DotVarExp(loc, ex, fd, false); 1008 ex = new CallExp(loc, ex); 1009 e = Expression.combine(ex, e); 1010 } 1011 auto dd = new DtorDeclaration(declLoc, Loc.initial, stc, Id.__aggrDtor); 1012 dd.generated = true; 1013 dd.storage_class |= STC.inference; 1014 dd.fbody = new ExpStatement(loc, e); 1015 ad.members.push(dd); 1016 dd.dsymbolSemantic(sc); 1017 xdtor = dd; 1018 break; 1019 } 1020 1021 ad.primaryDtor = xdtor; 1022 1023 if (xdtor && xdtor.linkage == LINK.cpp && !target.cpp.twoDtorInVtable) 1024 xdtor = buildWindowsCppDtor(ad, xdtor, sc); 1025 1026 // Add an __xdtor alias to make the inclusive dtor accessible 1027 if (xdtor) 1028 { 1029 auto _alias = new AliasDeclaration(Loc.initial, Id.__xdtor, xdtor); 1030 _alias.dsymbolSemantic(sc); 1031 ad.members.push(_alias); 1032 if (xdtor_fwd) 1033 ad.symtab.update(_alias); // update forward dtor to correct one 1034 else 1035 _alias.addMember(sc, ad); // add to symbol table 1036 } 1037 1038 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 auto cldec = ad.isClassDeclaration(); 1056 if (!cldec || cldec.cppDtorVtblIndex == -1) // scalar deleting dtor not built for non-virtual dtors 1057 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 Parameter delparam = new Parameter(STC.undefined_, Type.tuns32, Identifier.idPool("del"), new IntegerExp(dtor.loc, 0, Type.tuns32), null); 1067 Parameters* params = new Parameters; 1068 params.push(delparam); 1069 auto ftype = new TypeFunction(ParameterList(params), Type.tvoidptr, LINK.cpp, dtor.storage_class); 1070 auto func = new DtorDeclaration(dtor.loc, dtor.loc, dtor.storage_class, Id.cppdtor); 1071 func.type = ftype; 1072 if (dtor.fbody) 1073 { 1074 const loc = dtor.loc; 1075 auto stmts = new Statements; 1076 auto call = new CallExp(loc, dtor, null); 1077 call.directcall = true; 1078 stmts.push(new ExpStatement(loc, call)); 1079 stmts.push(new ReturnStatement(loc, new CastExp(loc, new ThisExp(loc), Type.tvoidptr))); 1080 func.fbody = new CompoundStatement(loc, stmts); 1081 func.generated = true; 1082 } 1083 1084 auto sc2 = sc.push(); 1085 sc2.stc &= ~STC.static_; // not a static destructor 1086 sc2.linkage = LINK.cpp; 1087 1088 ad.members.push(func); 1089 func.addMember(sc2, ad); 1090 func.dsymbolSemantic(sc2); 1091 1092 sc2.pop(); 1093 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 auto dtor = ad.primaryDtor; 1110 if (!dtor) 1111 return null; 1112 1113 // ABI incompatible on all (?) x86 32-bit platforms 1114 if (ad.classKind != ClassKind.cpp || global.params.is64bit) 1115 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 auto ftype = new TypeFunction(ParameterList(), Type.tvoid, LINK.d, dtor.storage_class); 1124 auto func = new DtorDeclaration(dtor.loc, dtor.loc, dtor.storage_class, Id.ticppdtor); 1125 func.type = ftype; 1126 1127 auto call = new CallExp(dtor.loc, dtor, null); 1128 call.directcall = true; // non-virtual call Class.__dtor(); 1129 func.fbody = new ExpStatement(dtor.loc, call); 1130 func.generated = true; 1131 func.storage_class |= STC.inference; 1132 1133 auto sc2 = sc.push(); 1134 sc2.stc &= ~STC.static_; // not a static destructor 1135 sc2.linkage = LINK.d; 1136 1137 ad.members.push(func); 1138 func.addMember(sc2, ad); 1139 func.dsymbolSemantic(sc2); 1140 func.functionSemantic(); // to infer attributes 1141 1142 sc2.pop(); 1143 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 switch (ad.invs.dim) 1159 { 1160 case 0: 1161 return null; 1162 1163 case 1: 1164 // Don't return invs[0] so it has uniquely generated name. 1165 goto default; 1166 1167 default: 1168 Expression e = null; 1169 StorageClass stcx = 0; 1170 StorageClass stc = STC.safe | STC.nothrow_ | STC.pure_ | STC.nogc; 1171 foreach (i, inv; ad.invs) 1172 { 1173 stc = mergeFuncAttrs(stc, inv); 1174 if (stc & STC.disable) 1175 { 1176 // What should do? 1177 } 1178 const stcy = (inv.storage_class & STC.synchronized_) | 1179 (inv.type.mod & MODFlags.shared_ ? STC.shared_ : 0); 1180 if (i == 0) 1181 stcx = stcy; 1182 else if (stcx ^ stcy) 1183 { 1184 version (all) 1185 { 1186 // currently rejects 1187 ad.error(inv.loc, "mixing invariants with different `shared`/`synchronized` qualifiers is not supported"); 1188 e = null; 1189 break; 1190 } 1191 } 1192 e = Expression.combine(e, new CallExp(Loc.initial, new VarExp(Loc.initial, inv, false))); 1193 } 1194 auto inv = new InvariantDeclaration(ad.loc, Loc.initial, stc | stcx, 1195 Id.classInvariant, new ExpStatement(Loc.initial, e)); 1196 ad.members.push(inv); 1197 inv.dsymbolSemantic(sc); 1198 return inv; 1199 } 1200 }