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 }