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 }