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