1 /**
2    Type translations
3  */
4 module dpp.translation.type;
5 
6 
7 import dpp.from: from;
8 
9 
10 alias Translator = string function(
11     in from!"clang".Type type,
12     ref from!"dpp.runtime.context".Context context,
13     in from!"std.typecons".Flag!"translatingFunction" translatingFunction
14 ) @safe;
15 
16 alias Translators = Translator[from!"clang".Type.Kind];
17 
18 
19 string translate(in from!"clang".Type type,
20                  ref from!"dpp.runtime.context".Context context,
21                  in from!"std.typecons".Flag!"translatingFunction" translatingFunction = from!"std.typecons".No.translatingFunction)
22     @safe
23 {
24     import dpp.translation.exception: UntranslatableException;
25     import std.conv: text;
26     import std.array: replace;
27 
28     if(type.kind !in translators)
29         throw new UntranslatableException(text("Type kind ", type.kind, " not supported: ", type));
30 
31     const translation = translators[type.kind](type, context, translatingFunction);
32 
33     // hack for std::function since function is a D keyword
34     return translation.replace(`function!`, `function_!`);
35 }
36 
37 
38 private Translators translators() @safe {
39     static Translators ret;
40     if(ret == ret.init) ret = translatorsImpl;
41     return ret;
42 }
43 
44 
45 private Translators translatorsImpl() @safe pure {
46     import clang: Type;
47 
48     with(Type.Kind) {
49         return [
50             Void: &simple!"void",
51             NullPtr: &simple!"void*",
52 
53             Bool: &simple!"bool",
54 
55             WChar: &simple!"wchar",
56             SChar: &simple!"byte",
57             Char16: &simple!"wchar",
58             Char32: &simple!"dchar",
59             UChar: &simple!"ubyte",
60             Char_U: &simple!"ubyte",
61             Char_S: &simple!"char",
62 
63             UShort: &simple!"ushort",
64             Short: &simple!"short",
65             Int: &simple!"int",
66             UInt: &simple!"uint",
67             Long: &simple!"c_long",
68             ULong: &simple!"c_ulong",
69             LongLong: &simple!"long",
70             ULongLong: &simple!"ulong",
71             Int128: &simple!"Int128",
72             UInt128: &simple!"UInt128",
73 
74             Float: &simple!"float",
75             Double: &simple!"double",
76             Float128: &simple!"real",
77             Half: &simple!"float",
78             LongDouble: &simple!"real",
79 
80             Enum: &translateAggregate,
81             Pointer: &translatePointer,
82             FunctionProto: &translateFunctionProto,
83             Record: &translateRecord,
84             FunctionNoProto: &translateFunctionProto,
85             Elaborated: &translateAggregate,
86             ConstantArray: &translateConstantArray,
87             IncompleteArray: &translateIncompleteArray,
88             Typedef: &translateTypedef,
89             LValueReference: &translateLvalueRef,
90             RValueReference: &translateRvalueRef,
91             Complex: &translateComplex,
92             DependentSizedArray: &translateDependentSizedArray,
93             Vector: &translateSimdVector,
94             MemberPointer: &translatePointer, // FIXME #83
95             Invalid: &ignore, // FIXME C++ stdlib <type_traits>
96             Unexposed: &translateUnexposed,
97         ];
98     }
99 }
100 
101 private string ignore(in from!"clang".Type type,
102                       ref from!"dpp.runtime.context".Context context,
103                       in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
104 @safe
105 {
106     return "";
107 }
108 
109 
110 private string simple(string translation)
111                      (in from!"clang".Type type,
112                       ref from!"dpp.runtime.context".Context context,
113                       in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
114 @safe
115 {
116     return addModifiers(type, translation);
117 }
118 
119 
120 private string translateRecord(in from!"clang".Type type,
121                                ref from!"dpp.runtime.context".Context context,
122                                in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
123 @safe
124 {
125 
126     // see it.compile.projects.va_list
127     return type.spelling == "struct __va_list_tag"
128         ? "va_list"
129         : translateAggregate(type, context, translatingFunction);
130 }
131 
132 private string translateAggregate(in from!"clang".Type type,
133                                   ref from!"dpp.runtime.context".Context context,
134                                   in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
135     @safe
136 {
137     import dpp.clang: namespace, typeNameNoNs;
138     import std.array: replace, join;
139     import std.algorithm: canFind, countUntil, map;
140     import std.range: iota;
141     import std.typecons: No;
142 
143     // if it's anonymous, find the nickname, otherwise return the spelling
144     string spelling() {
145         // clang names anonymous types with a long name indicating where the type
146         // was declared, so we check here with `hasAnonymousSpelling`
147         if(hasAnonymousSpelling(type)) return context.spellingOrNickname(type.declaration);
148 
149         // If there's a namespace in the name, we have to remove it. To find out
150         // what the namespace is called, we look at the type's declaration.
151         // In libclang, the type has the FQN, but the cursor only has the name
152         // without namespaces.
153         const tentative = () {
154 
155             const ns = type.declaration.namespace;
156             // no namespace, no problem
157             if(ns.isInvalid) {
158                 import std.array : split;
159 
160                 string[] elems = type.spelling.split(" ");
161                 string typeName = elems[$ - 1];
162                 string spelling = context.spelling(typeName);
163                 context.rememberSpelling(typeName, spelling);
164                 elems[$ - 1] = spelling;
165                 return elems.join(" ");
166             }
167 
168             // look for the namespace name in the declaration
169             const startOfNsIndex = type.spelling.countUntil(ns.spelling);
170 
171             // The namespace spelling is always what's considered the namespace in the FQN.
172             // The spelling we get from the cursor itself might not contain this namespace
173             // spelling if there's an alias.
174             // See it.cpp.opaque.paramater.exception_ptr
175             const hiddenNS = !type.spelling.canFind(ns.spelling);
176 
177             if(startOfNsIndex != -1) {
178                 // +2 due to `::`
179                 const endOfNsIndex = startOfNsIndex + ns.spelling.length + 2;
180                 // "subtract" the namespace away
181                 return type.spelling[endOfNsIndex .. $];
182             } else if(hiddenNS) {
183                 // this block deals with cases where there's a name alias
184                 // and the NS doesn't show up how it's spelt but does show up
185                 // in the FQN.
186                 // See it.cpp.opaque.paramater.exception_ptr
187                 const noNs = type.declaration.typeNameNoNs;
188                 const endOfNsIndex = type.spelling.countUntil(noNs);
189 
190                 if(endOfNsIndex == -1)
191                     throw new Exception("Could not find namespaceless '" ~ noNs ~ "' in type '" ~ type.spelling ~ "'");
192                 return type.spelling[endOfNsIndex .. $];
193             } else {
194                 return type.spelling;
195             }
196         }()
197          // FIXME - why doesn't `translateString` work here?
198          .replace("::", ".")
199          .replace("typename ", "")
200          ;
201 
202         // Clang template types have a spelling such as `Foo<unsigned int, unsigned short>`.
203         // We need to extract the "base" name (e.g. Foo above) then translate each type
204         // template argument (e.g. `unsigned long` is not a D type)
205         if(type.numTemplateArguments > 0) {
206             const openAngleBracketIndex = tentative.countUntil("<");
207             // this might happen because of alises, e.g. std::string is really std::basic_stream<char>
208             if(openAngleBracketIndex == -1) return tentative;
209             const baseName = tentative[0 .. openAngleBracketIndex];
210             const templateArgsTranslation = type
211                 .numTemplateArguments
212                 .iota
213                 .map!((i) {
214                     const kind = templateArgumentKind(type.typeTemplateArgument(i));
215                     final switch(kind) with(TemplateArgumentKind) {
216                         case GenericType:
217                         case SpecialisedType:
218                             // Never translating function if translating a type template argument
219                             return translate(type.typeTemplateArgument(i), context, No.translatingFunction);
220                         case Value:
221                             return templateParameterSpelling(type, i);
222                     }
223                  })
224                 .join(", ");
225             return baseName ~ "!(" ~ templateArgsTranslation ~ ")";
226         }
227 
228         return tentative;
229     }
230 
231     return addModifiers(type, spelling)
232         .translateElaborated(context)
233         .replace("<", "!(")
234         .replace(">", ")")
235         ;
236 }
237 
238 
239 private string translateConstantArray(in from!"clang".Type type,
240                                       ref from!"dpp.runtime.context".Context context,
241                                       in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
242 @safe
243 {
244     import std.conv: text;
245 
246     context.indent.log("Constant array of # ", type.numElements);
247 
248     return translatingFunction
249         ? translate(type.elementType, context) ~ `*`
250         : translate(type.elementType, context) ~ `[` ~ type.numElements.text ~ `]`;
251 }
252 
253 
254 private string translateDependentSizedArray(
255     in from!"clang".Type type,
256     ref from!"dpp.runtime.context".Context context,
257     in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
258 @safe
259 {
260     import std.conv: text;
261     import std.algorithm: find, countUntil;
262 
263     // FIXME: hacky, only works for the only test in it.cpp.class_.template (array)
264     auto start = type.spelling.find("["); start = start[1 .. $];
265     auto endIndex = start.countUntil("]");
266 
267     return translate(type.elementType, context) ~ `[` ~ start[0 .. endIndex] ~ `]`;
268 }
269 
270 
271 private string translateIncompleteArray(in from!"clang".Type type,
272                                         ref from!"dpp.runtime.context".Context context,
273                                         in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
274 @safe
275 {
276     const dType = translate(type.elementType, context);
277     // if translating a function, we want C's T[] to translate
278     // to T*, otherwise we want a flexible array
279     return translatingFunction ? dType ~ `*` : dType ~ "[0]";
280 
281 }
282 
283 private string translateTypedef(in from!"clang".Type type,
284                                 ref from!"dpp.runtime.context".Context context,
285                                 in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
286 @safe
287 {
288     const translation = translate(type.declaration.underlyingType, context, translatingFunction);
289     return addModifiers(type, translation);
290 }
291 
292 private string translatePointer(in from!"clang".Type type,
293                                 ref from!"dpp.runtime.context".Context context,
294                                 in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
295     @safe
296     in(type.kind == from!"clang".Type.Kind.Pointer || type.kind == from!"clang".Type.Kind.MemberPointer)
297     in(!type.pointee.isInvalid)
298     do
299 {
300     import clang: Type;
301     import std.conv: text;
302     import std.typecons: Yes;
303 
304     const isFunction =
305         type.pointee.canonical.kind == Type.Kind.FunctionProto ||
306         type.pointee.canonical.kind == Type.Kind.FunctionNoProto;
307 
308     // `function` in D is already a pointer, so no need to add a `*`.
309     // Otherwise, add `*`.
310     const maybeStar = isFunction ? "" : "*";
311     context.log("Pointee:           ", type.pointee);
312     context.log("Pointee canonical: ", type.pointee.canonical);
313 
314     // FIXME:
315     // If the kind is unexposed, we want to get the canonical type.
316     // Unless it's a type parameter, but that part I don't remember why anymore.
317     const translateCanonical =
318         type.pointee.kind == Type.Kind.Unexposed &&
319         !isTypeParameter(type.pointee.canonical)
320         ;
321     context.log("Translate canonical? ", translateCanonical);
322     const pointee = translateCanonical ? type.pointee.canonical : type.pointee;
323 
324     const indentation = context.indentation;
325     // We always pretend that we're translating a function because from here it's
326     // always a pointer
327     const rawType = translate(pointee, context.indent, Yes.translatingFunction);
328     context.setIndentation(indentation);
329 
330     context.log("Raw type: ", rawType);
331 
332     // Only add top-level const if it's const all the way down
333     bool addConst() @trusted {
334         auto ptr = Type(type);
335         while(ptr.kind == Type.Kind.Pointer) {
336             if(!ptr.isConstQualified || !ptr.pointee.isConstQualified)
337                 return false;
338             ptr = ptr.pointee;
339         }
340 
341         return true;
342     }
343 
344     version(Windows) {
345         // Microsoft extension for pointers that doesn't compile
346         // elsewhere. It tells the pointer may point to an unaligned
347         // structure, for platforms where that is an optimization. Just
348         // ignoring so it works here.
349         import std..string;
350         auto typePart = replace(rawType, "__unaligned ", "");
351     } else {
352         auto typePart = rawType;
353    }
354 
355     const ptrType = addConst
356         ? `const(` ~ typePart ~ maybeStar ~ `)`
357         : typePart ~ maybeStar;
358     return ptrType;
359 }
360 
361 // FunctionProto is the type of a C/C++ function.
362 // We usually get here translating function pointers, since this would be the
363 // pointee type, but it could also be a C++ type template parameter such as
364 // in the case of std::function.
365 private string translateFunctionProto(
366     in from!"clang".Type type,
367     ref from!"dpp.runtime.context".Context context,
368     in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
369     @safe
370 {
371     import std.conv: text;
372     import std.algorithm: map;
373     import std.array: join, array;
374 
375     const params = type.paramTypes.map!(a => translate(a, context)).array;
376     const isVariadic = params.length > 0 && type.isVariadicFunction;
377     const variadicParams = isVariadic ? ["..."] : [];
378     const allParams = params ~ variadicParams;
379     const returnType = translate(type.returnType, context);
380 
381     // The D equivalent of a function pointer (e.g. `int function(double, short)`)
382     const funcPtrTransl = text(returnType, ` function(`, allParams.join(", "), `)`);
383 
384     // The D equivalent of a function type. There is no dedicate syntax for this.
385     // In C/C++ it would be e.g. `int(double, short)`.
386     const funcTransl = `typeof(*(` ~ funcPtrTransl ~ `).init)`;
387 
388     // In functions, function prototypes as parameters decay to
389     // pointers similarly to how arrays do, so just return the
390     // function pointer type. Otherwise return the function type.
391     return translatingFunction ? funcPtrTransl : funcTransl;
392 }
393 
394 
395 private string translateLvalueRef(in from!"clang".Type type,
396                                   ref from!"dpp.runtime.context".Context context,
397                                   in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
398     @safe
399 {
400     const pointeeTranslation = translate(type.pointee, context, translatingFunction);
401     return translatingFunction
402         ? "ref " ~ pointeeTranslation
403         : pointeeTranslation ~ "*";
404 }
405 
406 
407 private string translateRvalueRef(in from!"clang".Type type,
408                                   ref from!"dpp.runtime.context".Context context,
409                                   in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
410     @safe
411 {
412     const dtype = translate(type.canonical.pointee, context, translatingFunction);
413     return `dpp.Move!(` ~ dtype ~ `)`;
414 }
415 
416 
417 private string translateComplex(in from!"clang".Type type,
418                                 ref from!"dpp.runtime.context".Context context,
419                                 in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
420     @safe
421 {
422     return "c" ~ translate(type.elementType, context, translatingFunction);
423 }
424 
425 private string translateUnexposed(in from!"clang".Type type,
426                                   ref from!"dpp.runtime.context".Context context,
427                                   in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
428     @safe
429 {
430     import clang: Type;
431     import std..string: replace;
432     import std.algorithm: canFind;
433 
434     const canonical = type.canonical;
435 
436     // Deal with kinds we know how to deal with here
437     if(canonical.kind != Type.Kind.Unexposed)
438         return translate(canonical, context, translatingFunction);
439 
440     // FIXME: there should be a better way
441     const spelling = type.spelling.canFind(" &&...")
442         ? "auto ref " ~ type.spelling.replace(" &&...", "")
443         : type.spelling;
444 
445     const translation =  translateString(spelling, context)
446         // We might get template arguments here (e.g. `type-parameter-0-0`)
447         // FIXME: this is a hack to get around libclang
448         .replace("type-parameter-0-", "type_parameter_0_")
449         ;
450 
451     return addModifiers(type, translation);
452 }
453 
454 /**
455    Translate possibly problematic C++ spellings
456  */
457 string translateString(scope const string spelling,
458                        in from!"dpp.runtime.context".Context context)
459     @safe nothrow
460 {
461     import std..string: replace;
462     import std.algorithm: canFind;
463 
464     string maybeTranslateTemplateBrackets(in string str) {
465         return str.canFind("<") && str.canFind(">")
466             ? str.replace("<", "!(").replace(">", ")")
467             : str;
468     }
469 
470     return
471         maybeTranslateTemplateBrackets(spelling)
472         .replace(context.namespace, "")
473         .replace("decltype", "typeof")
474         .replace("typename ", "")
475         .replace("template ", "")
476         .replace("::", ".")
477         .replace("volatile ", "")
478         .replace("long long", "long")
479         .replace("long double", "double")
480         .replace("unsigned ", "u")
481         .replace("signed char", "char")  // FIXME?
482         .replace("&&", "")
483         .replace("...", "")  // variadics work differently in D
484         ;
485 }
486 
487 string removeDppDecorators(in string spelling) @safe {
488     import std..string : replace;
489     return spelling.replace("__dpp_aggregate__ ", "");
490 }
491 
492 // "struct Foo" -> Foo, "union Foo" -> Foo, "enum Foo" -> Foo
493 string translateElaborated(const scope string spelling,
494                            ref from!"dpp.runtime.context".Context context) @safe {
495     import dpp.runtime.context: Language;
496     import std.array: replace;
497     import std.algorithm : find;
498     import std..string : split;
499     import std.range.primitives;
500 
501     void remember(in string recordType) @safe pure {
502         // '(' and ')' because of the "const(...)" modifier
503         string[] name = spelling.split!(a => a == '(' || a == ')' || a == ' ').find(recordType);
504         while (!name.empty) {
505             context.rememberAggregateTypeLine(name[1]);
506             name = name[1..$-1].find(recordType);
507         }
508     }
509 
510     const rep = context.language == Language.C ? "__dpp_aggregate__ " : "";
511 
512     if (context.language == Language.C) {
513         remember("struct");
514         remember("union");
515         remember("enum");
516     }
517 
518     return spelling
519         .replace("struct ", rep)
520         .replace("union ", rep)
521         .replace("enum ", rep)
522     ;
523 }
524 
525 private string translateSimdVector(in from!"clang".Type type,
526                                    ref from!"dpp.runtime.context".Context context,
527                                    in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
528     @safe
529 {
530     import std.conv: text;
531     import std.algorithm: canFind;
532     import std..string: replace;
533 
534     const numBytes = type.numElements;
535     const dtype =
536         translate(type.elementType, context, translatingFunction).replace("c_", "") ~
537         text(numBytes);
538 
539     const isUnsupportedType =
540         [
541             "long1", "char8", "short4", "ubyte8", "byte8", "ushort4", "short4",
542             "uint2", "int2", "ulong1", "float2", "char16",
543         ].canFind(dtype);
544 
545     return isUnsupportedType ? "int /* FIXME: unsupported SIMD type */" : "core.simd." ~ dtype;
546 }
547 
548 
549 private string addModifiers(in from!"clang".Type type, in string translation) @safe pure {
550     import std.array: replace;
551     const realTranslation = translation.replace("const ", "").replace("volatile ", "");
552     return type.isConstQualified
553         ? `const(` ~  realTranslation ~ `)`
554         : realTranslation;
555 }
556 
557 bool hasAnonymousSpelling(in from!"clang".Type type) @safe pure nothrow {
558     import std.algorithm: canFind;
559     return type.spelling.canFind("(anonymous");
560 }
561 
562 
563 bool isTypeParameter(in from!"clang".Type type) @safe pure nothrow {
564     import std.algorithm: canFind;
565     // See contract.typedef_.typedef to a template type parameter
566     return type.spelling.canFind("type-parameter-");
567 }
568 
569 /**
570    libclang doesn't offer a lot of functionality when it comes to extracting
571    template arguments from structs - this enum is the best we can do.
572  */
573 enum TemplateArgumentKind {
574     GenericType,
575     SpecialisedType,
576     Value,  // could be specialised or not
577 }
578 
579 // type template arguments may be:
580 // Invalid - value (could be specialised or not)
581 // Unexposed - non-specialised type or
582 // anything else - specialised type
583 // The trick is figuring out if a value is specialised or not
584 TemplateArgumentKind templateArgumentKind(in from!"clang".Type type) @safe pure nothrow {
585     import clang: Type;
586     if(type.kind == Type.Kind.Invalid) return TemplateArgumentKind.Value;
587     if(type.kind == Type.Kind.Unexposed) return TemplateArgumentKind.GenericType;
588     return TemplateArgumentKind.SpecialisedType;
589 }
590 
591 
592 // e.g. `template<> struct foo<false, true, int32_t>`  ->  0: false, 1: true, 2: int
593 string translateTemplateParamSpecialisation(
594     in from!"clang".Type cursorType,
595     in from!"clang".Type type,
596     in int index,
597     ref from!"dpp.runtime.context".Context context)
598     @safe
599 {
600     import clang: Type;
601     return type.kind == Type.Kind.Invalid
602         ? templateParameterSpelling(cursorType, index)
603         : translate(type, context);
604 }
605 
606 
607 // returns the indexth template parameter value from a specialised
608 // template struct/class cursor (full or partial)
609 // e.g. template<> struct Foo<int, 42, double> -> 1: 42
610 string templateParameterSpelling(in from!"clang".Type cursorType,
611                                  int index)
612     @safe
613 {
614     import dpp.translation.exception: UntranslatableException;
615     import std.algorithm: findSkip, startsWith;
616     import std.array: split;
617     import std.conv: text;
618 
619     auto spelling = cursorType.spelling.dup;
620     // If we pass this spelling has had everyting leading up to the opening
621     // angle bracket removed.
622     if(!spelling.findSkip("<")) return "";
623     assert(spelling[$-1] == '>');
624 
625     const templateParams = spelling[0 .. $-1].split(", ");
626 
627     if(index < 0 || index >= templateParams.length)
628         throw new UntranslatableException(
629             text("index (", index, ") out of bounds for template params of length ",
630                  templateParams.length, ":\n", templateParams));
631 
632     return templateParams[index].text;
633 }
634 
635 
636 string translateOpaque(in from!"clang".Type type)
637     @safe
638 {
639     import std.conv: text;
640     return text(`dpp.Opaque!(`, type.getSizeof, `)`);
641 }