1 /** 2 A D API for dealing with Python's PyTypeObject 3 */ 4 module python.type; 5 6 7 import autowrap.reflection: isParameter; 8 import python.raw: PyObject; 9 import std.traits: Unqual, isArray, isIntegral, isBoolean, isFloatingPoint, isAggregateType; 10 import std.datetime: DateTime, Date; 11 import std.typecons: Tuple; 12 import std.range.primitives: isInputRange; 13 import std.meta: allSatisfy; 14 15 16 package enum isPhobos(T) = isDateOrDateTime!T || isTuple!T; 17 package enum isDateOrDateTime(T) = is(Unqual!T == DateTime) || is(Unqual!T == Date); 18 package enum isTuple(T) = is(T: Tuple!A, A...); 19 package enum isUserAggregate(T) = isAggregateType!T && !isPhobos!(T); 20 package enum isNonRangeUDT(T) = isUserAggregate!T && !isInputRange!T; 21 22 23 /** 24 A wrapper for `PyTypeObject`. 25 26 This struct does all of the necessary boilerplate to intialise 27 a `PyTypeObject` for a Python extension type that mimics the D 28 type `T`. 29 */ 30 struct PythonType(T) { 31 import python.raw: PyTypeObject, PySequenceMethods; 32 import std.traits: FieldNameTuple, Fields; 33 import std.meta: Alias, staticMap; 34 35 alias fieldNames = FieldNameTuple!T; 36 alias fieldTypes = Fields!T; 37 enum hasLength = is(typeof({ size_t len = T.init.length; })); 38 39 static PyTypeObject _pyType; 40 static bool failedToReady; 41 42 static PyObject* pyObject() { 43 initialise; 44 return failedToReady ? null : cast(PyObject*) &_pyType; 45 } 46 47 static PyTypeObject* pyType() { 48 initialise; 49 return failedToReady ? null : &_pyType; 50 } 51 52 private static void initialise() { 53 import python.raw: PyType_GenericNew, PyType_Ready, TypeFlags, 54 PyErr_SetString, PyExc_TypeError; 55 56 if(_pyType != _pyType.init) return; 57 58 _pyType.tp_name = T.stringof; 59 _pyType.tp_basicsize = PythonClass!T.sizeof; 60 _pyType.tp_flags = TypeFlags.Default; 61 _pyType.tp_new = &PyType_GenericNew; 62 _pyType.tp_getset = getsetDefs; 63 _pyType.tp_methods = methodDefs; 64 _pyType.tp_repr = &_py_repr; 65 _pyType.tp_init = &_py_init; 66 _pyType.tp_new = &_py_new; 67 68 static if(hasLength) { 69 if(_pyType.tp_as_sequence is null) 70 _pyType.tp_as_sequence = new PySequenceMethods; 71 _pyType.tp_as_sequence.sq_length = &_py_length; 72 } 73 74 if(PyType_Ready(&_pyType) < 0) { 75 PyErr_SetString(PyExc_TypeError, &"not ready"[0]); 76 failedToReady = true; 77 } 78 } 79 80 private static auto getsetDefs() { 81 import autowrap.reflection: Properties; 82 import python.raw: PyGetSetDef; 83 import std.meta: staticMap, Filter, Alias; 84 import std.traits: isFunction, ReturnType; 85 86 alias AggMember(string memberName) = Alias!(__traits(getMember, T, memberName)); 87 alias memberNames = __traits(allMembers, T); 88 enum isPublic(string memberName) = 89 __traits(getProtection, __traits(getMember, T, memberName)) == "public"; 90 alias publicMemberNames = Filter!(isPublic, memberNames); 91 alias members = staticMap!(AggMember, publicMemberNames); 92 alias memberFunctions = Filter!(isFunction, members); 93 alias properties = Properties!memberFunctions; 94 95 // +1 due to the sentinel 96 static PyGetSetDef[fieldNames.length + properties.length + 1] getsets; 97 98 // don't bother if already initialised 99 if(getsets != getsets.init) return &getsets[0]; 100 101 // first deal with the public fields 102 static foreach(i; 0 .. fieldNames.length) { 103 getsets[i].name = cast(typeof(PyGetSetDef.name)) fieldNames[i]; 104 static if(__traits(getProtection, __traits(getMember, T, fieldNames[i])) == "public") { 105 getsets[i].get = &PythonClass!T.get!i; 106 getsets[i].set = &PythonClass!T.set!i; 107 } 108 } 109 110 // then deal with the property functions 111 static foreach(j, property; properties) {{ 112 enum i = fieldNames.length + j; 113 114 getsets[i].name = cast(typeof(PyGetSetDef.name)) __traits(identifier, property); 115 116 static foreach(overload; __traits(getOverloads, T, __traits(identifier, property))) { 117 static if(is(ReturnType!overload == void)) 118 getsets[i].set = &PythonClass!T.propertySet!(overload); 119 else 120 getsets[i].get = &PythonClass!T.propertyGet!(overload); 121 } 122 }} 123 124 return &getsets[0]; 125 } 126 127 private static auto methodDefs()() { 128 import autowrap.reflection: isProperty; 129 import python.raw: PyMethodDef, MethodArgs; 130 import python.cooked: pyMethodDef, defaultMethodFlags; 131 import std.meta: AliasSeq, Alias, staticMap, Filter, templateNot; 132 import std.traits: isSomeFunction; 133 import std.algorithm: startsWith; 134 135 alias memberNames = AliasSeq!(__traits(allMembers, T)); 136 enum ispublic(string name) = isPublic!(T, name); 137 alias publicMemberNames = Filter!(ispublic, memberNames); 138 enum isRegular(string name) = 139 name != "this" 140 && name != "toHash" 141 && name != "factory" 142 && !name.startsWith("op") 143 && name != "__ctor" 144 ; 145 alias regularMemberNames = Filter!(isRegular, publicMemberNames); 146 alias Member(string name) = Alias!(__traits(getMember, T, name)); 147 alias members = staticMap!(Member, regularMemberNames); 148 alias memberFunctions = Filter!(templateNot!isProperty, Filter!(isSomeFunction, members)); 149 150 // +1 due to sentinel 151 static PyMethodDef[memberFunctions.length + 1] methods; 152 153 if(methods != methods.init) return &methods[0]; 154 155 static foreach(i, memberFunction; memberFunctions) {{ 156 157 static if(__traits(isStaticFunction, memberFunction)) 158 enum flags = defaultMethodFlags | MethodArgs.Static; 159 else 160 enum flags = defaultMethodFlags; 161 162 methods[i] = pyMethodDef!(__traits(identifier, memberFunction), flags) 163 (&PythonMethod!(T, memberFunction)._py_method_impl); 164 }} 165 166 return &methods[0]; 167 } 168 169 import python.raw: Py_ssize_t; 170 private static extern(C) Py_ssize_t _py_length(PyObject* self_) nothrow { 171 172 return noThrowable!({ 173 assert(self_ !is null); 174 static if(hasLength) { 175 import python.conv: to; 176 return self_.to!T.length; 177 } else 178 return -1; 179 }); 180 } 181 182 private static extern(C) PyObject* _py_repr(PyObject* self_) nothrow { 183 184 return noThrowable!({ 185 186 import python: pyUnicodeDecodeUTF8; 187 import python.conv: to; 188 import std.string: toStringz; 189 import std.conv: text; 190 191 assert(self_ !is null); 192 auto ret = text(self_.to!T); 193 return pyUnicodeDecodeUTF8(ret.ptr, ret.length, null /*errors*/); 194 }); 195 } 196 197 private static extern(C) int _py_init(PyObject* self_, PyObject* args, PyObject* kwargs) nothrow { 198 // nothing to do 199 return 0; 200 } 201 202 private static extern(C) PyObject* _py_new(PyTypeObject *type, PyObject* args, PyObject* kwargs) nothrow { 203 return noThrowable!({ 204 import autowrap.reflection: FunctionParameters, Parameter; 205 import python.conv: toPython, to; 206 import python.raw: PyTuple_Size, PyTuple_GetItem; 207 import std.traits: hasMember, Unqual; 208 import std.meta: AliasSeq; 209 210 const numArgs = PyTuple_Size(args); 211 212 if(numArgs == 0) { 213 return toPython(userAggregateInit!T); 214 } 215 216 static if(hasMember!(T, "__ctor")) 217 alias constructors = AliasSeq!(__traits(getOverloads, T, "__ctor")); 218 else 219 alias constructors = AliasSeq!(); 220 221 static if(constructors.length == 0) { 222 alias parameter(FieldType) = Parameter!( 223 FieldType, 224 "", 225 void, 226 ); 227 alias parameters = staticMap!(parameter, fieldTypes); 228 return pythonConstructor!(T, false /*is variadic*/, parameters)(args, kwargs); 229 } else { 230 import python.raw: PyErr_SetString, PyExc_TypeError; 231 import std.traits: Parameters, variadicFunctionStyle, Variadic; 232 233 static foreach(constructor; constructors) {{ 234 235 enum isVariadic = variadicFunctionStyle!constructor == Variadic.typesafe; 236 237 if(Parameters!constructor.length == numArgs) { 238 return pythonConstructor!(T, isVariadic, FunctionParameters!constructor)(args, kwargs); 239 } else if(numArgs >= NumRequiredParameters!constructor 240 && numArgs <= Parameters!constructor.length) 241 { 242 return pythonConstructor!(T, isVariadic, FunctionParameters!constructor)(args, kwargs); 243 } 244 }} 245 246 PyErr_SetString(PyExc_TypeError, "Could not find a suitable constructor"); 247 return null; 248 } 249 250 }); 251 } 252 253 // Creates a python object from the given arguments by converting them to D 254 // types, calling the D constructor and converting the result to a Python 255 // object. 256 private static auto pythonConstructor(T, bool isVariadic, P...)(PyObject* args, PyObject* kwargs) { 257 import python.conv: toPython; 258 import std.traits: hasMember; 259 260 auto dArgs = pythonArgsToDArgs!(isVariadic, P)(args, kwargs); 261 262 static if(is(T == class)) { 263 static if(hasMember!(T, "__ctor")) { 264 // When immutable dmd prints an odd error message about not being 265 // able to modify dobj 266 static if(is(T == immutable)) 267 auto dobj = new T(dArgs.expand); 268 else 269 scope dobj = new T(dArgs.expand); 270 } else 271 T dobj; 272 } else 273 auto dobj = T(dArgs.expand); 274 275 return toPython(dobj); 276 } 277 } 278 279 280 private auto pythonArgsToDArgs(bool isVariadic, P...)(PyObject* args, PyObject* kwargs) 281 if(allSatisfy!(isParameter, P)) 282 { 283 import python.raw: PyTuple_Size, PyTuple_GetItem, PyTuple_GetSlice, pyUnicodeDecodeUTF8, PyDict_GetItem; 284 import python.conv: to; 285 import std.typecons: Tuple; 286 import std.meta: staticMap; 287 import std.traits: Unqual; 288 import std.conv: text; 289 import std.exception: enforce; 290 291 const argsLength = args is null ? 0 : PyTuple_Size(args); 292 293 alias Type(alias Param) = Param.Type; 294 alias Types = staticMap!(Type, P); 295 296 // If one or more of the parameters is const/immutable, 297 // it'll be hard to construct it as such, so we Unqual 298 // the types for construction and cast to the appropriate 299 // type when returning. 300 alias MutableTuple = Tuple!(staticMap!(Unqual, Types)); 301 alias RetTuple = Tuple!(Types); 302 303 MutableTuple dArgs; 304 305 int pythonArgIndex = 0; 306 static foreach(i; 0 .. P.length) { 307 308 static if(i == P.length - 1 && isVariadic) { // last parameter and it's a typesafe variadic one 309 // slice the remaining arguments 310 auto remainingArgs = PyTuple_GetSlice(args, i, PyTuple_Size(args)); 311 dArgs[i] = remainingArgs.to!(P[i].Type); 312 } else static if(is(P[i].Default == void)) { 313 // ith parameter is required 314 enforce(i < argsLength, 315 text(__FUNCTION__, ": not enough Python arguments")); 316 dArgs[i] = PyTuple_GetItem(args, i).to!(typeof(dArgs[i])); 317 } else { 318 319 if(i < argsLength) { // regular case 320 dArgs[i] = PyTuple_GetItem(args, i).to!(P[i].Type); 321 } else { 322 // Here it gets tricky. The user could have supplied it in 323 // args positionally or via kwargs 324 auto key = pyUnicodeDecodeUTF8(&P[i].identifier[0], 325 P[i].identifier.length, 326 null /*errors*/); 327 enforce(key, "Errors converting '" ~ P[i].identifier ~ "' to Python object"); 328 auto val = kwargs ? PyDict_GetItem(kwargs, key) : null; 329 dArgs[i] = val 330 ? val.to!(P[i].Type) // use kwargs 331 : P[i].Default; // use default value 332 } 333 } 334 } 335 336 return cast(RetTuple) dArgs; 337 } 338 339 340 private alias Type(alias A) = typeof(A); 341 342 343 /** 344 The C API implementation of a Python method F of aggregate type T 345 */ 346 struct PythonMethod(T, alias F) { 347 static extern(C) PyObject* _py_method_impl(PyObject* self, 348 PyObject* args, 349 PyObject* kwargs) 350 nothrow 351 { 352 return noThrowable!({ 353 import python.raw: pyDecRef; 354 import python.conv: toPython, to; 355 import std.traits: Parameters, FunctionAttribute, functionAttributes, Unqual, hasFunctionAttributes; 356 357 static if(!__traits(isStaticFunction, F)) 358 assert(self !is null, 359 "Cannot call PythonMethod!" ~ __traits(identifier, F) ~ " on null self"); 360 361 static if(functionAttributes!F & FunctionAttribute.const_) 362 alias Aggregate = const T; 363 else static if(functionAttributes!F & FunctionAttribute.immutable_) 364 alias Aggregate = immutable T; 365 else 366 alias Aggregate = Unqual!T; 367 368 auto dAggregate = { 369 // could be null for static member functions 370 return self is null ? Aggregate.init : self.to!Aggregate; 371 }(); 372 373 // Not sure how else to take `dAggregate` and `F` and call the member 374 // function other than a mixin 375 mixin(`auto ret = callDlangFunction!((Parameters!F args) => dAggregate.`, 376 __traits(identifier, F), `(args), F)(self, args, kwargs);`); 377 378 // The member function could have side-effects, we need to copy the changes 379 // back to the Python object. 380 static if(!(functionAttributes!F & FunctionAttribute.const_)) { 381 auto newSelf = { 382 return self is null ? self : toPython(dAggregate); 383 }(); 384 scope(exit) { 385 if(self !is null) pyDecRef(newSelf); 386 } 387 auto pyClassSelf = cast(PythonClass!T*) self; 388 auto pyClassNewSelf = cast(PythonClass!T*) newSelf; 389 390 static foreach(i; 0 .. PythonClass!T.fieldNames.length) { 391 if(self !is null) 392 pyClassSelf.set!i(self, pyClassNewSelf.get!i(newSelf)); 393 } 394 } 395 396 return ret; 397 }); 398 } 399 } 400 401 402 /** 403 The C API implementation that calls a D function F. 404 */ 405 struct PythonFunction(alias F) { 406 static extern(C) PyObject* _py_function_impl(PyObject* self, PyObject* args, PyObject* kwargs) nothrow { 407 return noThrowable!(() => callDlangFunction!F(self, args, kwargs)); 408 } 409 } 410 411 412 private auto noThrowable(alias F, A...)(auto ref A args) { 413 import python.raw: PyErr_SetString, PyExc_RuntimeError; 414 import std.string: toStringz; 415 import std.traits: ReturnType; 416 417 try { 418 return F(args); 419 } catch(Exception e) { 420 PyErr_SetString(PyExc_RuntimeError, e.msg.toStringz); 421 return ReturnType!F.init; 422 } catch(Error e) { 423 import std.conv: text; 424 try 425 PyErr_SetString(PyExc_RuntimeError, ("FATAL ERROR: " ~ e.text).toStringz); 426 catch(Exception _) 427 PyErr_SetString(PyExc_RuntimeError, ("FATAL ERROR: " ~ e.msg).toStringz); 428 429 return ReturnType!F.init; 430 } 431 } 432 433 // simple, regular version for functions 434 private auto callDlangFunction(alias F) 435 (PyObject* self, PyObject* args, PyObject* kwargs) 436 { 437 return callDlangFunction!(F, F)(self, args, kwargs); 438 } 439 440 // Takes two functions due to how we're calling methods. 441 // One is the original function to reflect on, the other 442 // is the closure that's actually going to be called. 443 private auto callDlangFunction(alias callable, alias originalFunction) 444 (PyObject* self, PyObject* args, PyObject* kwargs) 445 { 446 import autowrap.reflection: FunctionParameters; 447 import python.raw: PyTuple_Size; 448 import python.conv: toPython; 449 import std.traits: Parameters, variadicFunctionStyle, Variadic; 450 import std.conv: text; 451 import std.string: toStringz; 452 import std.exception: enforce; 453 454 enum numDefaults = NumDefaultParameters!originalFunction; 455 enum numRequired = NumRequiredParameters!originalFunction; 456 enum isVariadic = variadicFunctionStyle!originalFunction == Variadic.typesafe; 457 458 const numArgs = args is null ? 0 : PyTuple_Size(args); 459 if(!isVariadic) 460 enforce(numArgs >= numRequired 461 && numArgs <= Parameters!originalFunction.length, 462 text("Received ", numArgs, " parameters but ", 463 __traits(identifier, originalFunction), " takes ", Parameters!originalFunction.length)); 464 465 auto dArgs = pythonArgsToDArgs!(isVariadic, FunctionParameters!originalFunction)(args, kwargs); 466 return callDlangFunction!callable(dArgs); 467 } 468 469 470 private auto callDlangFunction(alias F, A)(auto ref A argTuple) { 471 import python.raw: pyIncRef, pyNone; 472 import python.conv: toPython; 473 import std.traits: ReturnType; 474 475 // TODO - side-effects on parameters? 476 static if(is(ReturnType!F == void)) { 477 F(argTuple.expand); 478 pyIncRef(pyNone); 479 return pyNone; 480 } else { 481 auto dret = F(argTuple.expand); 482 return dret.toPython; 483 } 484 } 485 486 487 private template NumDefaultParameters(alias F) { 488 import std.meta: Filter; 489 import std.traits: ParameterDefaults; 490 491 template notVoid(T...) if(T.length == 1) { 492 enum notVoid = !is(T[0] == void); 493 } 494 495 enum NumDefaultParameters = Filter!(notVoid, ParameterDefaults!F).length; 496 } 497 498 499 private template NumRequiredParameters(alias F) { 500 import std.traits: Parameters; 501 enum NumRequiredParameters = Parameters!F.length - NumDefaultParameters!F; 502 } 503 504 /** 505 Creates an instance of a Python class that is equivalent to the D type `T`. 506 Return PyObject*. 507 */ 508 PyObject* pythonClass(T)(auto ref T dobj) { 509 510 import python.conv: toPython; 511 import python.raw: pyObjectNew; 512 513 static if(is(T == class)) { 514 if(dobj is null) 515 throw new Exception("Cannot create Python class from null D class"); 516 } 517 518 auto ret = pyObjectNew!(PythonClass!T)(PythonType!T.pyType); 519 520 static foreach(fieldName; PythonType!T.fieldNames) { 521 static if(isPublic!(T, fieldName)) 522 mixin(`ret.`, fieldName, ` = dobj.`, fieldName, `.toPython;`); 523 } 524 525 return cast(PyObject*) ret; 526 } 527 528 529 private template isPublic(T, string memberName) { 530 531 static if(__traits(compiles, __traits(getProtection, __traits(getMember, T, memberName)))) { 532 enum protection = __traits(getProtection, __traits(getMember, T, memberName)); 533 enum isPublic = protection == "public" || protection == "export"; 534 } else 535 enum isPublic = false; 536 } 537 538 539 /** 540 A Python class that mirrors the D type `T`. 541 For instance, this struct: 542 ---------- 543 struct Foo { 544 int i; 545 string s; 546 } 547 ---------- 548 549 Will generate a Python class called `Foo` with two members, and trying to 550 assign anything but an integer to `Foo.i` or a string to `Foo.s` in Python 551 will raise `TypeError`. 552 */ 553 struct PythonClass(T) if(isUserAggregate!T) { 554 import python.raw: PyObjectHead, PyGetSetDef; 555 import std.traits: Unqual; 556 557 alias fieldNames = PythonType!(Unqual!T).fieldNames; 558 alias fieldTypes = PythonType!(Unqual!T).fieldTypes; 559 560 // Every python object must have this 561 mixin PyObjectHead; 562 563 // Field members 564 // Generate a python object field for every field in T 565 static foreach(fieldName; fieldNames) { 566 mixin(`PyObject* `, fieldName, `;`); 567 } 568 569 // The function pointer for PyGetSetDef.get 570 private static extern(C) PyObject* get(int FieldIndex) 571 (PyObject* self_, void* closure = null) 572 nothrow 573 in(self_ !is null) 574 { 575 import python.raw: pyIncRef; 576 577 auto self = cast(PythonClass*) self_; 578 579 auto field = self.getField!FieldIndex; 580 assert(field !is null, "Cannot increase reference count on null field"); 581 pyIncRef(field); 582 583 return field; 584 } 585 586 // The function pointer for PyGetSetDef.set 587 static extern(C) int set(int FieldIndex) 588 (PyObject* self_, PyObject* value, void* closure = null) 589 nothrow 590 in(self_ !is null) 591 { 592 import python.raw: pyIncRef, pyDecRef, PyErr_SetString, PyExc_TypeError; 593 594 if(value is null) { 595 enum deleteErrStr = "Cannot delete " ~ fieldNames[FieldIndex]; 596 PyErr_SetString(PyExc_TypeError, deleteErrStr); 597 return -1; 598 } 599 600 // FIXME 601 // if(!checkPythonType!(fieldTypes[FieldIndex])(value)) { 602 // return -1; 603 // } 604 605 auto self = cast(PythonClass!T*) self_; 606 auto tmp = self.getField!FieldIndex; 607 608 pyIncRef(value); 609 mixin(`self.`, fieldNames[FieldIndex], ` = value;`); 610 pyDecRef(tmp); 611 612 return 0; 613 } 614 615 PyObject* getField(int FieldIndex)() { 616 mixin(`return this.`, fieldNames[FieldIndex], `;`); 617 } 618 619 static extern(C) PyObject* propertyGet(alias F) 620 (PyObject* self_, void* closure = null) 621 nothrow 622 in(self_ !is null) 623 { 624 return PythonMethod!(T, F)._py_method_impl(self_, null /*args*/, null /*kwargs*/); 625 } 626 627 static extern(C) int propertySet(alias F) 628 (PyObject* self_, PyObject* value, void* closure = null) 629 nothrow 630 in(self_ !is null) 631 { 632 import python.raw: PyTuple_New, PyTuple_SetItem, pyDecRef; 633 634 auto args = PyTuple_New(1); 635 PyTuple_SetItem(args, 0, value); 636 scope(exit) pyDecRef(args); 637 638 PythonMethod!(T, F)._py_method_impl(self_, args, null /*kwargs*/); 639 640 return 0; 641 } 642 } 643 644 645 private bool checkPythonType(T)(PyObject* value) if(isArray!T) { 646 import python.raw: pyListCheck; 647 const ret = pyListCheck(value); 648 if(!ret) setPyErrTypeString!"list"; 649 return ret; 650 } 651 652 653 private bool checkPythonType(T)(PyObject* value) if(isIntegral!T) { 654 import python.raw: pyIntCheck, pyLongCheck; 655 const ret = pyLongCheck(value) || pyIntCheck(value); 656 if(!ret) setPyErrTypeString!"long"; 657 return ret; 658 } 659 660 661 private bool checkPythonType(T)(PyObject* value) if(isFloatingPoint!T) { 662 import python.raw: pyFloatCheck; 663 const ret = pyFloatCheck(value); 664 if(!ret) setPyErrTypeString!"float"; 665 return ret; 666 } 667 668 669 private bool checkPythonType(T)(PyObject* value) if(is(T == DateTime)) { 670 import python.raw: pyDateTimeCheck; 671 const ret = pyDateTimeCheck(value); 672 if(!ret) setPyErrTypeString!"DateTime"; 673 return ret; 674 } 675 676 677 private bool checkPythonType(T)(PyObject* value) if(is(T == Date)) { 678 import python.raw: pyDateCheck; 679 const ret = pyDateCheck(value); 680 if(!ret) setPyErrTypeString!"Date"; 681 return ret; 682 } 683 684 685 private void setPyErrTypeString(string type)() @trusted @nogc nothrow { 686 import python.raw: PyErr_SetString, PyExc_TypeError; 687 enum str = "must be a " ~ type; 688 PyErr_SetString(PyExc_TypeError, &str[0]); 689 } 690 691 // Generalises T.init for classes since null isn't a value we want to use 692 T userAggregateInit(T)() { 693 static if(is(T == class)) { 694 auto buffer = new void[__traits(classInstanceSize, T)]; 695 // this is needed for the vtable to work 696 buffer[] = typeid(T).initializer[]; 697 return cast(T) buffer.ptr; 698 } else 699 return T.init; 700 }