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