1 /** 2 Translate aggregates 3 */ 4 module dpp.translation.aggregate; 5 6 import dpp.from; 7 import std.range: isInputRange; 8 9 10 enum MAX_BITFIELD_WIDTH = 64; 11 12 13 string[] translateStruct(in from!"clang".Cursor cursor, 14 ref from!"dpp.runtime.context".Context context) 15 @safe 16 { 17 return translateStrass(cursor, context, "struct"); 18 } 19 20 string[] translateClass(in from!"clang".Cursor cursor, 21 ref from!"dpp.runtime.context".Context context) 22 @safe 23 { 24 import clang: Token; 25 import std.algorithm: canFind; 26 27 const cKeyword = cursor.tokens.canFind(Token(Token.Kind.Keyword, "class")) 28 ? "class" 29 : "struct"; 30 31 return translateStrass(cursor, context, cKeyword); 32 } 33 34 // "strass" is a struct or class 35 private string[] translateStrass(in from!"clang".Cursor cursor, 36 ref from!"dpp.runtime.context".Context context, 37 in string cKeyword) 38 @safe 39 in(isStrass(cursor)) 40 do 41 { 42 import dpp.translation.template_: templateSpelling, translateTemplateParams, 43 translateSpecialisedTemplateParams; 44 import clang: Cursor; 45 import std.typecons: Nullable, nullable; 46 import std.array: join; 47 import std.conv: text; 48 49 Nullable!string spelling() { 50 51 // full template 52 if(cursor.kind == Cursor.Kind.ClassTemplate) 53 return nullable(templateSpelling(cursor, translateTemplateParams(cursor, context))); 54 55 // partial or full template specialisation 56 if(cursor.type.numTemplateArguments != -1) 57 return nullable(templateSpelling(cursor, translateSpecialisedTemplateParams(cursor, context))); 58 59 // non-template class/struct 60 return Nullable!string(); 61 } 62 63 const dKeyword = dKeywordFromStrass(cursor); 64 65 return translateAggregate(context, cursor, cKeyword, dKeyword, spelling); 66 } 67 68 69 private bool isStrass(in from!"clang".Cursor cursor) @safe @nogc pure nothrow { 70 return 71 cursor.kind == from!"clang".Cursor.Kind.StructDecl || 72 cursor.kind == from!"clang".Cursor.Kind.ClassDecl || 73 cursor.kind == from!"clang".Cursor.Kind.ClassTemplate || 74 cursor.kind == from!"clang".Cursor.Kind.ClassTemplatePartialSpecialization 75 ; 76 } 77 78 79 // Decide on whether to emit a D struct or class 80 package string dKeywordFromStrass(in from!"clang".Cursor cursor) @safe nothrow { 81 import dpp.clang: baseClasses; 82 import clang: Cursor; 83 import std.algorithm: any, map, filter; 84 import std.range: walkLength; 85 86 static bool hasVirtuals(in Cursor cursor) { 87 return cursor.children.any!(a => a.isVirtual); 88 } 89 90 static bool anyVirtualInAncestry(in Cursor cursor) @safe nothrow { 91 if(hasVirtuals(cursor)) return true; 92 93 return cursor.baseClasses.any!anyVirtualInAncestry; 94 } 95 96 return anyVirtualInAncestry(cursor) 97 ? "class" 98 : "struct"; 99 } 100 101 102 string[] translateUnion(in from!"clang".Cursor cursor, 103 ref from!"dpp.runtime.context".Context context) 104 @safe 105 in(cursor.kind == from!"clang".Cursor.Kind.UnionDecl) 106 do 107 { 108 import clang: Cursor; 109 return translateAggregate(context, cursor, "union"); 110 } 111 112 113 string[] translateEnum(in from!"clang".Cursor cursor, 114 ref from!"dpp.runtime.context".Context context) 115 @safe 116 in(cursor.kind == from!"clang".Cursor.Kind.EnumDecl) 117 do 118 { 119 import dpp.translation.dlang: maybeRename; 120 import clang: Cursor, Token; 121 import std.typecons: nullable; 122 import std.algorithm: canFind; 123 124 const enumName = context.spellingOrNickname(cursor); 125 string[] lines; 126 127 const isEnumClass = cursor.tokens.canFind(Token(Token.Kind.Keyword, "class")); 128 129 if(!isEnumClass && !context.options.alwaysScopedEnums) { 130 // Translate it twice so that C semantics are the same (global names) 131 // but also have a named version for optional type correctness and 132 // reflection capabilities. 133 // This means that `enum Foo { foo, bar }` in C will become: 134 // `enum Foo { foo, bar }` _and_ 135 // `enum foo = Foo.foo; enum bar = Foo.bar;` in D. 136 137 foreach(member; cursor) { 138 if(!member.isDefinition) continue; 139 auto memName = maybeRename(member, context); 140 lines ~= `enum ` ~ memName ~ ` = ` ~ enumName ~ `.` ~ memName ~ `;`; 141 } 142 } 143 144 return 145 translateAggregate(context, cursor, "enum", nullable(enumName)) ~ 146 lines; 147 } 148 149 // not pure due to Cursor.opApply not being pure 150 string[] translateAggregate( 151 ref from!"dpp.runtime.context".Context context, 152 in from!"clang".Cursor cursor, 153 in string keyword, 154 in from!"std.typecons".Nullable!string spelling = from!"std.typecons".Nullable!string() 155 ) 156 @safe 157 { 158 return translateAggregate(context, cursor, keyword, keyword, spelling); 159 } 160 161 162 // not pure due to Cursor.opApply not being pure 163 string[] translateAggregate( 164 ref from!"dpp.runtime.context".Context context, 165 in from!"clang".Cursor cursor, 166 in string cKeyword, 167 in string dKeyword, 168 in from!"std.typecons".Nullable!string spelling = from!"std.typecons".Nullable!string() 169 ) 170 @safe 171 { 172 import dpp.translation.translation: translate; 173 import dpp.translation.type: hasAnonymousSpelling; 174 import dpp.runtime.context: Language; 175 import clang: Cursor, Type, AccessSpecifier; 176 import std.algorithm: map; 177 import std.array: array; 178 179 // remember all aggregate declarations 180 context.rememberAggregate(cursor); 181 182 const name = spelling.isNull ? context.spellingOrNickname(cursor) : spelling.get; 183 const realDlangKeyword = cursor.semanticParent.type.canonical.kind == Type.Kind.Record 184 ? "static " ~ dKeyword 185 : dKeyword; 186 const parents = maybeParents(cursor, context, dKeyword); 187 const enumBaseType = maybeEnumBaseType(cursor, dKeyword); 188 const firstLine = realDlangKeyword ~ ` ` ~ name ~ parents ~ enumBaseType; 189 190 if(!cursor.isDefinition) return [firstLine ~ `;`]; 191 192 string[] lines; 193 lines ~= firstLine; 194 lines ~= `{`; 195 196 // In C++ classes have private access as default 197 if(cKeyword == "class") { 198 lines ~= "private:"; 199 context.accessSpecifier = AccessSpecifier.Private; 200 } else 201 context.accessSpecifier = AccessSpecifier.Public; 202 203 BitFieldInfo bitFieldInfo; 204 205 lines ~= bitFieldInfo.header(cursor); 206 207 context.log("Children: ", cursor.children); 208 209 foreach(i, child; cursor.children) { 210 211 if(child.kind == Cursor.Kind.PackedAttr) { 212 lines ~= "align(1):"; 213 continue; 214 } 215 216 if(skipMember(child)) continue; 217 218 lines ~= bitFieldInfo.handle(child); 219 220 if (context.language == Language.C 221 && (child.type.kind == Type.Kind.Record || child.type.kind == Type.Kind.Enum) 222 && !child.type.hasAnonymousSpelling) 223 context.rememberAggregateParent(child, cursor); 224 225 const childTranslation = () { 226 227 if(isPrivateField(child, context)) 228 return translatePrivateMember(child); 229 230 if(child.kind == Cursor.Kind.CXXBaseSpecifier && dKeyword == "struct") 231 return translateStructBase(i, child, context); 232 233 return translate(child, context); 234 }(); 235 236 lines ~= childTranslation.map!(a => " " ~ a).array; 237 238 // Possibly deal with C11 anonymous structs/unions. See issue #29. 239 lines ~= maybeC11AnonymousRecords(cursor, child, context); 240 241 bitFieldInfo.update(child); 242 } 243 244 lines ~= bitFieldInfo.finish; 245 lines ~= maybeOperators(cursor, name); 246 lines ~= maybeDisableDefaultCtor(cursor, dKeyword); 247 248 lines ~= `}`; 249 250 return lines; 251 } 252 253 private struct BitFieldInfo { 254 255 import dpp.runtime.context: Context; 256 import clang: Cursor; 257 258 /// if the last seen member was a bitfield 259 private bool lastMemberWasBitField; 260 /// the combined (summed) bitwidths of the bitfields members seen so far 261 private int totalBitWidth; 262 /// to generate new names 263 private int paddingNameIndex; 264 265 string[] header(in Cursor cursor) @safe nothrow { 266 import std.algorithm: any; 267 268 if(cursor.children.any!(a => a.isBitField)) { 269 // The align(4) is to mimic C. There, `struct Foo { int f1: 2; int f2: 3}` 270 // would have sizeof 4, where as the corresponding bit fields in D would have 271 // size 1. So we correct here. See issue #7. 272 return [` import std.bitmanip: bitfields;`, ``, ` align(4):`]; 273 } else 274 return []; 275 276 } 277 278 string[] handle(in Cursor member) @safe pure nothrow { 279 280 string[] lines; 281 282 if(member.isBitField && !lastMemberWasBitField) 283 lines ~= ` mixin(bitfields!(`; 284 285 if(!member.isBitField && lastMemberWasBitField) lines ~= finishBitFields; 286 287 if(member.isBitField && totalBitWidth + member.bitWidth > MAX_BITFIELD_WIDTH) { 288 lines ~= finishBitFields; 289 lines ~= ` mixin(bitfields!(`; 290 } 291 292 return lines; 293 } 294 295 void update(in Cursor member) @safe pure nothrow { 296 lastMemberWasBitField = member.isBitField; 297 if(member.isBitField) totalBitWidth += member.bitWidth; 298 } 299 300 string[] finish() @safe pure nothrow { 301 return lastMemberWasBitField ? finishBitFields : []; 302 } 303 304 private string[] finishBitFields() @safe pure nothrow { 305 import std.conv: text; 306 307 308 int padding(in int totalBitWidth) { 309 310 for(int powerOfTwo = 8; powerOfTwo <= MAX_BITFIELD_WIDTH; powerOfTwo *= 2) { 311 if(powerOfTwo >= totalBitWidth) return powerOfTwo - totalBitWidth; 312 } 313 314 assert(0, text("Could not find powerOfTwo for width ", totalBitWidth)); 315 } 316 317 const paddingBits = padding(totalBitWidth); 318 319 string[] lines; 320 321 if(paddingBits) 322 lines ~= text(` uint, "`, newPaddingName, `", `, padding(totalBitWidth)); 323 324 lines ~= ` ));`; 325 326 totalBitWidth = 0; 327 328 return lines; 329 } 330 331 private string newPaddingName() @safe pure nothrow { 332 import std.conv: text; 333 return text("_padding_", paddingNameIndex++); 334 } 335 } 336 337 private bool isPrivateField(in from!"clang".Cursor cursor, 338 in from!"dpp.runtime.context".Context context) 339 @safe 340 { 341 import clang: Cursor, AccessSpecifier; 342 343 const isField = 344 cursor.kind == Cursor.Kind.FieldDecl || 345 cursor.kind == Cursor.Kind.VarDecl; 346 347 return 348 context.accessSpecifier == AccessSpecifier.Private 349 && isField 350 // The reason for this is templated types have negative sizes according 351 // to libclang, even if they're fully instantiated... 352 // So even though they're private, they can't be declared as opaque 353 // binary blobs because we don't know how large they are. 354 && cursor.type.getSizeof > 0 355 ; 356 357 } 358 359 // Since one can't access private members anyway, why bother translating? 360 // Declare an array of equivalent size in D, helps with the untranslatable 361 // parts of C++ 362 private string[] translatePrivateMember(in from!"clang".Cursor cursor) @safe { 363 import dpp.translation.type: translateOpaque; 364 365 return cursor.type.getSizeof > 0 366 ? [ translateOpaque(cursor.type) ~ ` ` ~ cursor.spelling ~ `;`] 367 : []; 368 } 369 370 371 private bool skipMember(in from!"clang".Cursor member) @safe @nogc pure nothrow { 372 import clang: Cursor; 373 return 374 !member.isDefinition 375 && member.kind != Cursor.Kind.CXXMethod 376 && member.kind != Cursor.Kind.Constructor 377 && member.kind != Cursor.Kind.Destructor 378 && member.kind != Cursor.Kind.VarDecl 379 && member.kind != Cursor.Kind.CXXBaseSpecifier 380 && member.kind != Cursor.Kind.ConversionFunction 381 && member.kind != Cursor.Kind.FunctionTemplate 382 && member.kind != Cursor.Kind.UsingDeclaration 383 ; 384 } 385 386 387 string[] translateField(in from!"clang".Cursor field, 388 ref from!"dpp.runtime.context".Context context) 389 @safe 390 { 391 392 import dpp.translation.dlang: maybeRename; 393 import dpp.translation.type: translate; 394 import clang: Cursor, Type; 395 import std.conv: text; 396 import std.typecons: No; 397 import std.array: replace; 398 399 assert(field.kind == Cursor.Kind.FieldDecl, text("Field of wrong kind: ", field)); 400 401 // The field could be a pointer to an undeclared struct or a function pointer with parameter 402 // or return types that are a pointer to an undeclared struct. We have to remember these 403 // so as to be able to declare the structs for D consumption after the fact. 404 if(field.type.kind == Type.Kind.Pointer) maybeRememberStructsFromType(field.type, context); 405 406 // Remember the field name in case it ends up clashing with a type. 407 context.rememberField(field.spelling); 408 409 const type = translate(field.type, context, No.translatingFunction); 410 411 return field.isBitField 412 ? translateBitField(field, context, type) 413 : [text(type, " ", maybeRename(field, context), ";")]; 414 } 415 416 string[] translateBitField(in from!"clang".Cursor cursor, 417 ref from!"dpp.runtime.context".Context context, 418 in string type) 419 @safe 420 { 421 import dpp.translation.dlang: maybeRename; 422 import std.conv: text; 423 424 auto spelling = maybeRename(cursor, context); 425 // std.bitmanip.bitfield can't handle successive mixins with 426 // no name. See issue #35. 427 if(spelling == "") spelling = context.newAnonymousMemberName; 428 429 return [text(" ", type, `, "`, spelling, `", `, cursor.bitWidth, `,`)]; 430 } 431 432 private from!"clang".Type pointeeTypeFor(in from!"clang".Type type) 433 @safe 434 { 435 import clang: Type; 436 437 Type pointeeType = type.pointee.canonical; 438 while (pointeeType.kind == Type.Kind.Pointer) 439 pointeeType = pointeeType.pointee.canonical; 440 441 return pointeeType; 442 } 443 444 // C allows elaborated types to appear in function parameters and member declarations 445 // if they're pointers and doesn't require a declaration for the referenced type. 446 private void maybeRememberStructsFromType(in from!"clang".Type type, 447 ref from!"dpp.runtime.context".Context context) 448 @safe 449 { 450 import clang: Type; 451 import std.range: only, chain; 452 453 const pointeeType = pointeeTypeFor(type); 454 const isFunction = 455 pointeeType.kind == Type.Kind.FunctionProto || 456 pointeeType.kind == Type.Kind.FunctionNoProto; 457 458 if(pointeeType.kind == Type.Kind.Record) 459 // can't use `only` with `const` for some reason 460 maybeRememberStructs([type], context); 461 else if(isFunction) 462 maybeRememberStructs(chain(only(pointeeType.returnType), pointeeType.paramTypes), 463 context); 464 } 465 466 467 // C allows elaborated types to appear in function parameters and member declarations 468 // if they're pointers and doesn't require a declaration for the referenced type. 469 void maybeRememberStructs(R)(R types, ref from!"dpp.runtime.context".Context context) 470 @safe if(isInputRange!R) 471 { 472 import dpp.translation.type: translate; 473 import clang: Type; 474 import std.algorithm: map, filter; 475 476 auto structTypes = types 477 .filter!(a => a.kind == Type.Kind.Pointer && pointeeTypeFor(a).kind == Type.Kind.Record) 478 .map!(a => pointeeTypeFor(a)); 479 480 void rememberStruct(scope const Type pointeeCanonicalType) @safe { 481 import dpp.translation.type: translateElaborated; 482 import std.array: replace; 483 import std.algorithm: canFind; 484 485 // If it's not a C elaborated type, we don't need to do anything. 486 // The only reason this code exists is because elaborated types 487 // can be used in function signatures or member declarations 488 // without a declaration for the struct itself as long as it's 489 // a pointer. 490 if(!pointeeCanonicalType.spelling.canFind("struct ") && 491 !pointeeCanonicalType.spelling.canFind("union ") && 492 !pointeeCanonicalType.spelling.canFind("enum ")) 493 return; 494 495 const removeConst = pointeeCanonicalType.isConstQualified 496 ? pointeeCanonicalType.spelling.replace("const ", "") 497 : pointeeCanonicalType.spelling; 498 const removeVolatile = pointeeCanonicalType.isVolatileQualified 499 ? removeConst.replace("volatile ", "") 500 : removeConst; 501 502 context.rememberFieldStruct(translateElaborated(removeVolatile, context)); 503 } 504 505 foreach(structType; structTypes) 506 rememberStruct(structType); 507 } 508 509 // if the cursor is an aggregate in C, i.e. struct, union or enum 510 package bool isAggregateC(in from!"clang".Cursor cursor) @safe @nogc pure nothrow { 511 import clang: Cursor; 512 return 513 cursor.kind == Cursor.Kind.StructDecl || 514 cursor.kind == Cursor.Kind.UnionDecl || 515 cursor.kind == Cursor.Kind.EnumDecl; 516 } 517 518 519 private string[] maybeC11AnonymousRecords(in from!"clang".Cursor cursor, 520 in from!"clang".Cursor member, 521 ref from!"dpp.runtime.context".Context context) 522 @safe 523 524 { 525 import dpp.translation.type: translate, hasAnonymousSpelling; 526 import clang: Cursor, Type; 527 import std.algorithm: any, filter; 528 529 if(member.type.kind != Type.Kind.Record || member.spelling != "") return []; 530 531 // Either a field or an array of the type we expect 532 static bool isFieldOfRightType(in Cursor member, in Cursor child) { 533 const isField = 534 child.kind == Cursor.Kind.FieldDecl && 535 (child.type.canonical == member.type.canonical || 536 // If the inner struct declaration is 'const struct {...} X;', 537 // then child.type.canonical would be: 538 // Type(Elaborated, "const struct (anonymous struct at fileY)"), 539 // and member.type.canonical would be: 540 // Type(Record, "struct ParentStruct::(anonymous at fileY)"). 541 // This is the reason why we unelaborate the child.type. 542 child.type.unelaborate == member.type.canonical); 543 544 const isArrayOf = child.type.elementType.canonical == member.type.canonical; 545 return isField || isArrayOf; 546 } 547 548 // Check if the parent cursor has any fields have this type. 549 // If so, we don't need to declare a dummy variable. 550 const anyFields = cursor.children.any!(a => isFieldOfRightType(member, a)); 551 if(anyFields) return []; 552 553 string[] lines; 554 const varName = context.newAnonymousMemberName; 555 556 //lines ~= " " ~ translate(member.type, context) ~ " " ~ varName ~ ";"; 557 const dtype = translate(member.type, context); 558 lines ~= " " ~ dtype ~ " " ~ varName ~ ";"; 559 560 static string[] subMemberAccessors(in Cursor member, 561 in string varName, 562 ref from!"dpp.runtime.context".Context context) { 563 string[] res; 564 foreach(subMember; member.children) { 565 if(subMember.kind == Cursor.Kind.FieldDecl) 566 res ~= innerFieldAccessors(varName, subMember, context); 567 else if(subMember.type.canonical.kind == Type.Kind.Record && 568 hasAnonymousSpelling(subMember.type.canonical) && 569 !member.children.any!(a => isFieldOfRightType(subMember, a))) { 570 res ~= subMemberAccessors(subMember, varName, context); 571 } 572 } 573 return res; 574 } 575 576 lines ~= subMemberAccessors(member, varName, context); 577 578 return lines; 579 } 580 581 582 // functions to emulate C11 anonymous structs/unions 583 private string[] innerFieldAccessors(in string varName, 584 in from !"clang".Cursor subMember, 585 ref from!"dpp.runtime.context".Context context) @safe { 586 import std.format: format; 587 import std.algorithm: map; 588 import std.array: array; 589 590 string[] lines; 591 592 const subMemberSpelling = context.spelling(subMember.spelling); 593 const fieldAccess = varName ~ "." ~ subMemberSpelling; 594 const funcName = subMemberSpelling; 595 596 lines ~= q{auto %s() @property @nogc pure nothrow { return %s; }} 597 .format(funcName, fieldAccess); 598 599 lines ~= q{void %s(_T_)(auto ref _T_ val) @property @nogc pure nothrow { %s = val; }} 600 .format(funcName, fieldAccess); 601 602 return lines.map!(a => " " ~ a).array; 603 } 604 605 // emit a D opCmp if the cursor has operator<, operator> and operator== 606 private string[] maybeOperators(in from!"clang".Cursor cursor, in string name) 607 @safe 608 { 609 import dpp.translation.function_: OPERATOR_PREFIX; 610 import std.algorithm: map, any; 611 import std.array: array, replace; 612 613 string[] lines; 614 615 bool hasOperator(in string op) { 616 return cursor.children.any!(a => a.spelling == OPERATOR_PREFIX ~ op); 617 } 618 619 // if the aggregate has a parenthesis in its name it's because it's a templated 620 // struct/class, in which case the parameter has to have an exclamation mark. 621 const typeName = name.replace("(", "!("); 622 623 if(hasOperator(">") && hasOperator("<") && hasOperator("==")) { 624 lines ~= [ 625 `int opCmp()(` ~ typeName ~ ` other) const`, 626 `{`, 627 ` if(this.opCppLess(other)) return -1;`, 628 ` if(this.opCppMore(other)) return 1;`, 629 ` return 0;`, 630 `}`, 631 ].map!(a => ` ` ~ a).array; 632 } 633 634 if(hasOperator("!")) { 635 lines ~= [ 636 `bool opCast(T: bool)() const`, 637 `{`, 638 ` return !this.opCppBang();`, 639 `}`, 640 ].map!(a => ` ` ~ a).array; 641 } 642 643 return lines; 644 } 645 646 private string[] maybeDisableDefaultCtor(in from!"clang".Cursor cursor, in string dKeyword) 647 @safe 648 { 649 import dpp.translation.function_: numParams; 650 import clang: Cursor; 651 import std.algorithm: any; 652 653 bool hasNoArgsCtor(in Cursor child) { 654 return child.kind == Cursor.Kind.Constructor && 655 numParams(child) == 0; 656 } 657 658 if(dKeyword == "struct" && 659 cursor.children.any!hasNoArgsCtor) { 660 return [` @disable this();`]; 661 } 662 663 return []; 664 } 665 666 667 private string[] translateStructBase(size_t index, 668 in from!"clang".Cursor cursor, 669 ref from!"dpp.runtime.context".Context context) 670 @safe 671 in(cursor.kind == from!"clang".Cursor.Kind.CXXBaseSpecifier) 672 do 673 { 674 import dpp.translation.type: translate; 675 import std.typecons: No; 676 import std.algorithm: canFind; 677 import std.conv: text; 678 679 const type = translate(cursor.type, context, No.translatingFunction); 680 681 // FIXME - see it.cpp.templates.__or_ 682 // Not only would that test fail if this weren't here, but the spelling of 683 // the type parameters would be completely wrong as well. 684 if(type.canFind("...")) return []; 685 686 // FIXME - type traits failures due to inheritance 687 if(type.canFind("&")) return []; 688 689 const fieldName = text("_base", index); 690 auto fieldDecl = type ~ " " ~ fieldName ~ ";"; 691 auto maybeAliasThis = index == 0 692 ? [`alias ` ~ fieldName ~ ` this;`] 693 : []; 694 695 return fieldDecl ~ maybeAliasThis; 696 } 697 698 699 private string maybeParents( 700 in from!"clang".Cursor cursor, 701 ref from!"dpp.runtime.context".Context context, 702 in string dKeyword) 703 @safe 704 { 705 import dpp.translation.type: translate; 706 import clang: Cursor; 707 import std.typecons: No; 708 import std.algorithm: filter, map; 709 import std.array: join; 710 711 if(dKeyword != "class") return ""; 712 713 auto parents = cursor 714 .children 715 .filter!(a => a.kind == Cursor.Kind.CXXBaseSpecifier) 716 .map!(a => translate(a.type, context, No.translatingFunction)) 717 ; 718 719 return parents.empty 720 ? "" 721 : ": " ~ parents.join(", "); 722 } 723 724 private string maybeEnumBaseType(in from!"clang".Cursor cursor, in string dKeyword) 725 @safe 726 { 727 import std.algorithm: map, minElement, maxElement; 728 729 if(dKeyword != "enum") return ""; 730 731 auto enumValues = cursor.children.map!(a => a.enumConstantValue); 732 bool shouldPromote = enumValues.maxElement > int.max || enumValues.minElement < int.min; 733 734 return shouldPromote ? " : long" : ""; 735 }