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 pure;
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 pure
23 {
24     import std.conv: text;
25     if(type.kind !in translators)
26         throw new Exception(text("Type kind ", type.kind, " not supported: ", type));
27 
28     return translators[type.kind](type, context, translatingFunction);
29 }
30 
31 
32 Translators translators() @safe pure {
33     import clang: Type;
34 
35     with(Type.Kind) {
36         return [
37             Void: &simple!"void",
38             NullPtr: &simple!"void*",
39 
40             Bool: &simple!"bool",
41 
42             WChar: &simple!"wchar",
43             SChar: &simple!"byte",
44             Char16: &simple!"wchar",
45             Char32: &simple!"dchar",
46             UChar: &simple!"ubyte",
47             Char_U: &simple!"ubyte",
48             Char_S: &simple!"char",
49 
50             UShort: &simple!"ushort",
51             Short: &simple!"short",
52             Int: &simple!"int",
53             UInt: &simple!"uint",
54             Long: &simple!"c_long",
55             ULong: &simple!"c_ulong",
56             LongLong: &simple!"long",
57             ULongLong: &simple!"ulong",
58             Int128: &simple!"Int128",
59             UInt128: &simple!"UInt128",
60 
61             Float: &simple!"float",
62             Double: &simple!"double",
63             Float128: &simple!"real",
64             Half: &simple!"float",
65             LongDouble: &simple!"real",
66 
67             Enum: &translateAggregate,
68             Pointer: &translatePointer,
69             FunctionProto: &translateFunctionProto,
70             Record: &translateRecord,
71             FunctionNoProto: &translateFunctionProto,
72             Elaborated: &translateAggregate,
73             ConstantArray: &translateConstantArray,
74             IncompleteArray: &translateIncompleteArray,
75             Typedef: &translateTypedef,
76             LValueReference: &translateLvalueRef,
77             RValueReference: &translateRvalueRef,
78             Complex: &translateComplex,
79             DependentSizedArray: &translateDependentSizedArray,
80             Vector: &translateSimdVector,
81             MemberPointer: &translatePointer, // FIXME #83
82             Invalid: &ignore, // FIXME C++ stdlib <type_traits>
83             Unexposed: &translateUnexposed,
84         ];
85     }
86 }
87 
88 private string ignore(in from!"clang".Type type,
89                       ref from!"dpp.runtime.context".Context context,
90                       in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
91 @safe pure
92 {
93     return "";
94 }
95 
96 
97 private string simple(string translation)
98                      (in from!"clang".Type type,
99                       ref from!"dpp.runtime.context".Context context,
100                       in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
101 @safe pure
102 {
103     return addModifiers(type, translation);
104 }
105 
106 
107 private string translateRecord(in from!"clang".Type type,
108                                ref from!"dpp.runtime.context".Context context,
109                                in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
110 @safe pure
111 {
112 
113     // see it.compile.projects.va_list
114     return type.spelling == "struct __va_list_tag"
115         ? "va_list"
116         : translateAggregate(type, context, translatingFunction);
117 }
118 
119 private string translateAggregate(in from!"clang".Type type,
120                                   ref from!"dpp.runtime.context".Context context,
121                                   in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
122     @safe pure
123 {
124     import std.array: replace, join;
125     import std.algorithm: canFind, countUntil, map;
126     import std.range: iota;
127 
128     // if it's anonymous, find the nickname, otherwise return the spelling
129     string spelling() {
130         // clang names anonymous types with a long name indicating where the type
131         // was declared, so we check here with `hasAnonymousSpelling`
132         if(hasAnonymousSpelling(type)) return context.spellingOrNickname(type.declaration);
133 
134         // A struct in a namespace will have a type of kind Record with the fully
135         // qualified name (e.g. std::random_access_iterator_tag), but the cursor
136         // itself has only the name (e.g. random_access_iterator_tag), so we get
137         // the spelling from the type's declaration instead of from the type itself.
138         // See it.cpp.templates.__copy_move and contract.namespace.struct.
139         if(type.spelling.canFind(":")) return type.declaration.spelling;
140 
141         // Clang template types have a spelling such as `Foo<unsigned int, unsigned short>`.
142         // We need to extract the "base" name (e.g. Foo above) then translate each type
143         // template argument (`unsigned long` is not a D type)
144         if(type.numTemplateArguments > 0) {
145             const openAngleBracketIndex = type.spelling.countUntil("<");
146             const baseName = type.spelling[0 .. openAngleBracketIndex];
147             const templateArgsTranslation = type
148                 .numTemplateArguments
149                 .iota
150                 .map!((i) {
151                     const kind = templateArgumentKind(type.typeTemplateArgument(i));
152                     final switch(kind) with(TemplateArgumentKind) {
153                         case GenericType:
154                         case SpecialisedType:
155                             return translate(type.typeTemplateArgument(i), context, translatingFunction);
156                         case Value:
157                             return templateParameterSpelling(type, i);
158                     }
159                  })
160                 .join(", ");
161             return baseName ~ "!(" ~ templateArgsTranslation ~ ")";
162         }
163 
164         return type.spelling;
165     }
166 
167     return addModifiers(type, spelling)
168         // "struct Foo" -> Foo, "union Foo" -> Foo, "enum Foo" -> Foo
169         .replace("struct ", "")
170         .replace("union ", "")
171         .replace("enum ", "")
172         .replace("<", "!(")
173         .replace(">", ")")
174         ;
175 }
176 
177 
178 private string translateConstantArray(in from!"clang".Type type,
179                                       ref from!"dpp.runtime.context".Context context,
180                                       in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
181 @safe pure
182 {
183     import std.conv: text;
184 
185     context.indent.log("Constant array of # ", type.numElements);
186 
187     return translatingFunction
188         ? translate(type.elementType, context) ~ `*`
189         : translate(type.elementType, context) ~ `[` ~ type.numElements.text ~ `]`;
190 }
191 
192 
193 private string translateDependentSizedArray(
194     in from!"clang".Type type,
195     ref from!"dpp.runtime.context".Context context,
196     in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
197 @safe pure
198 {
199     import std.conv: text;
200     import std.algorithm: find, countUntil;
201 
202     // FIXME: hacky, only works for the only test in it.cpp.class_.template (array)
203     auto start = type.spelling.find("["); start = start[1 .. $];
204     auto endIndex = start.countUntil("]");
205 
206     return translate(type.elementType, context) ~ `[` ~ start[0 .. endIndex] ~ `]`;
207 }
208 
209 
210 private string translateIncompleteArray(in from!"clang".Type type,
211                                         ref from!"dpp.runtime.context".Context context,
212                                         in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
213 @safe pure
214 {
215     const dType = translate(type.elementType, context);
216     // if translating a function, we want C's T[] to translate
217     // to T*, otherwise we want a flexible array
218     return translatingFunction ? dType ~ `*` : dType ~ "[0]";
219 
220 }
221 
222 private string translateTypedef(in from!"clang".Type type,
223                                 ref from!"dpp.runtime.context".Context context,
224                                 in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
225 @safe pure
226 {
227     const translation = translate(type.declaration.underlyingType, context, translatingFunction);
228     return addModifiers(type, translation);
229 }
230 
231 private string translatePointer(in from!"clang".Type type,
232                                 ref from!"dpp.runtime.context".Context context,
233                                 in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
234     @safe pure
235 {
236     import clang: Type;
237     import std.conv: text;
238 
239     assert(type.kind == Type.Kind.Pointer || type.kind == Type.Kind.MemberPointer, "type kind not Pointer");
240     assert(!type.pointee.isInvalid, "pointee is invalid");
241 
242     const isFunction =
243         type.pointee.canonical.kind == Type.Kind.FunctionProto ||
244         type.pointee.canonical.kind == Type.Kind.FunctionNoProto;
245 
246     // usually "*" but sometimes not needed if already a reference type
247     const maybeStar = isFunction ? "" : "*";
248     context.log("Pointee:           ", type.pointee);
249     context.log("Pointee canonical: ", type.pointee.canonical);
250 
251     const translateCanonical =
252         type.pointee.kind == Type.Kind.Unexposed && !isTypeParameter(type.pointee.canonical)
253         ;
254     context.log("Translate canonical? ", translateCanonical);
255 
256     const indentation = context.indentation;
257     const rawType = translateCanonical
258         ? translate(type.pointee.canonical, context.indent)
259         : translate(type.pointee, context.indent);
260     context.setIndentation(indentation);
261 
262     context.log("Raw type: ", rawType);
263 
264     // Only add top-level const if it's const all the way down
265     bool addConst() @trusted {
266         auto ptr = Type(type);
267         while(ptr.kind == Type.Kind.Pointer) {
268             if(!ptr.isConstQualified || !ptr.pointee.isConstQualified)
269                 return false;
270             ptr = ptr.pointee;
271         }
272 
273         return true;
274     }
275 
276     const ptrType = addConst
277         ? `const(` ~ rawType ~ maybeStar ~ `)`
278         : rawType ~ maybeStar;
279 
280     return ptrType;
281 }
282 
283 // currently only getting here from function pointer variables
284 // with have kind unexposed but canonical kind FunctionProto
285 private string translateFunctionProto(in from!"clang".Type type,
286                                       ref from!"dpp.runtime.context".Context context,
287                                       in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
288     @safe pure
289 {
290     import std.conv: text;
291     import std.algorithm: map;
292     import std.array: join, array;
293 
294     const params = type.paramTypes.map!(a => translate(a, context)).array;
295     const isVariadic = params.length > 0 && type.isVariadicFunction;
296     const variadicParams = isVariadic ? ["..."] : [];
297     const allParams = params ~ variadicParams;
298     return text(translate(type.returnType, context), " function(", allParams.join(", "), ")");
299 }
300 
301 private string translateLvalueRef(in from!"clang".Type type,
302                                   ref from!"dpp.runtime.context".Context context,
303                                   in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
304     @safe pure
305 {
306 
307     // See it.cpp.templates.__normal_iterator.base
308     const typeToUse = type.canonical.isTypeParameter ? type : type.canonical;
309     const pointeeTranslation = translate(typeToUse.pointee, context, translatingFunction);
310     return translatingFunction
311         ? "ref " ~ pointeeTranslation
312         : pointeeTranslation ~ "*";
313 }
314 
315 // we cheat and pretend it's a value
316 private string translateRvalueRef(in from!"clang".Type type,
317                                   ref from!"dpp.runtime.context".Context context,
318                                   in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
319     @safe pure
320 {
321     const dtype = translate(type.canonical.pointee, context, translatingFunction);
322     return `dpp.Move!(` ~ dtype ~ `)`;
323 }
324 
325 
326 private string translateComplex(in from!"clang".Type type,
327                                 ref from!"dpp.runtime.context".Context context,
328                                 in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
329     @safe pure
330 {
331     return "c" ~ translate(type.elementType, context, translatingFunction);
332 }
333 
334 private string translateUnexposed(in from!"clang".Type type,
335                                   ref from!"dpp.runtime.context".Context context,
336                                   in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
337     @safe pure
338 {
339     import std..string: replace;
340 
341     const translation =  type.spelling
342         .translateString
343         // we might get template arguments here (e.g. `type-parameter-0-0`)
344         .replace("-", "_")
345         ;
346 
347     return addModifiers(type, translation);
348 }
349 
350 /**
351    Translate possibly problematic C++ spellings
352  */
353 string translateString(in string spelling) @safe pure nothrow {
354     import std..string: replace;
355     import std.algorithm: canFind;
356 
357     string maybeTranslateTemplateBrackets(in string str) {
358         return str.canFind("<") && str.canFind(">")
359             ? str.replace("<", "!(").replace(">", ")")
360             : str;
361     }
362 
363     return
364         maybeTranslateTemplateBrackets(spelling)
365         .replace("decltype", "typeof")
366         .replace("typename ", "")
367         .replace("template ", "")
368         .replace("::", ".")
369         .replace("volatile ", "")
370         .replace("long long", "long")
371         .replace("unsigned ", "u")
372         .replace("&&", "")
373         ;
374 }
375 
376 
377 private string translateSimdVector(in from!"clang".Type type,
378                                    ref from!"dpp.runtime.context".Context context,
379                                    in from!"std.typecons".Flag!"translatingFunction" translatingFunction)
380     @safe pure
381 {
382     import std.conv: text;
383     import std.algorithm: canFind;
384 
385     const numBytes = type.numElements;
386     const dtype =
387         translate(type.elementType, context, translatingFunction) ~
388         text(type.getSizeof / numBytes);
389 
390     const isUnsupportedType =
391         [
392             "long8", "short2", "char1", "double8", "ubyte1", "ushort2",
393             "ulong8", "byte1",
394         ].canFind(dtype);
395 
396     return isUnsupportedType ? "int /* FIXME: unsupported SIMD type */" : "core.simd." ~ dtype;
397 }
398 
399 
400 private string addModifiers(in from!"clang".Type type, in string translation) @safe pure {
401     import std.array: replace;
402     const realTranslation = translation.replace("const ", "").replace("volatile ", "");
403     return type.isConstQualified
404         ? `const(` ~  realTranslation ~ `)`
405         : realTranslation;
406 }
407 
408 bool hasAnonymousSpelling(in from!"clang".Type type) @safe pure nothrow {
409     import std.algorithm: canFind;
410     return type.spelling.canFind("(anonymous");
411 }
412 
413 
414 bool isTypeParameter(in from!"clang".Type type) @safe pure nothrow {
415     import std.algorithm: canFind;
416     // See contract.typedef_.typedef to a template type parameter
417     return type.spelling.canFind("type-parameter-");
418 }
419 
420 /**
421    libclang doesn't offer a lot of functionality when it comes to extracting
422    template arguments from structs - this enum is the best we can do.
423  */
424 enum TemplateArgumentKind {
425     GenericType,
426     SpecialisedType,
427     Value,  // could be specialised or not
428 }
429 
430 // type template arguments may be:
431 // Invalid - value (could be specialised or not)
432 // Unexposed - non-specialised type or
433 // anything else - specialised type
434 // The trick is figuring out if a value is specialised or not
435 TemplateArgumentKind templateArgumentKind(in from!"clang".Type type) @safe pure nothrow {
436     import clang: Type;
437     if(type.kind == Type.Kind.Invalid) return TemplateArgumentKind.Value;
438     if(type.kind == Type.Kind.Unexposed) return TemplateArgumentKind.GenericType;
439     return TemplateArgumentKind.SpecialisedType;
440 }
441 
442 // e.g. `template<> struct foo<false, true, int32_t>`  ->  0: false, 1: true, 2: int
443 string translateTemplateParamSpecialisation(
444     in from!"clang".Type templateType,
445     in int index,
446     ref from!"dpp.runtime.context".Context context) @safe pure
447 {
448     return translateTemplateParamSpecialisation(templateType, templateType, index, context);
449 }
450 
451 
452 // e.g. `template<> struct foo<false, true, int32_t>`  ->  0: false, 1: true, 2: int
453 string translateTemplateParamSpecialisation(
454     in from!"clang".Type cursorType,
455     in from!"clang".Type type,
456     in int index,
457     ref from!"dpp.runtime.context".Context context) @safe pure
458 {
459     import clang: Type;
460     return type.kind == Type.Kind.Invalid
461         ? templateParameterSpelling(cursorType, index)
462         : translate(type, context);
463 }
464 
465 
466 // returns the indexth template parameter value from a specialised
467 // template struct/class cursor (full or partial)
468 // e.g. template<> struct Foo<int, 42, double> -> 1: 42
469 string templateParameterSpelling(in from!"clang".Type cursorType, int index) @safe pure {
470     import std.algorithm: findSkip, until, OpenRight;
471     import std.array: empty, save, split, array;
472     import std.conv: text;
473 
474     auto spelling = cursorType.spelling.dup;
475     if(!spelling.findSkip("<")) return "";
476 
477     auto templateParams = spelling.until(">", OpenRight.yes).array.split(", ");
478 
479     return templateParams[index].text;
480 }