1 /**
2    C++ template translations
3  */
4 module dpp.translation.template_;
5 
6 
7 import dpp.from;
8 
9 
10 string templateParamList(R)(R range) {
11     import std.array: join;
12     return `(` ~ () @trusted { return range.join(", "); }() ~ `)`;
13 }
14 
15 string templateSpelling(R)(in from!"clang".Cursor cursor, R range) {
16     return cursor.spelling ~ templateParamList(range);
17 }
18 
19 
20 // Deal with full and partial template specialisations
21 package string[] translateSpecialisedTemplateParams(
22     in from!"clang".Cursor cursor,
23     ref from!"dpp.runtime.context".Context context)
24     @safe
25     in(cursor.type.numTemplateArguments != -1)
26     do
27 {
28     return isFromVariadicTemplate(cursor)
29         ? translateSpecialisedTemplateParamsVariadic(cursor, context)
30         : translateSpecialisedTemplateParamsFinite(cursor, context);
31 }
32 
33 
34 private string[] translateSpecialisedTemplateParamsFinite(
35     in from!"clang".Cursor cursor,
36     ref from!"dpp.runtime.context".Context context)
37     @safe
38 {
39     import dpp.translation.type: translate, templateArgumentKind, TemplateArgumentKind,
40         translateTemplateParamSpecialisation;
41     import clang: Type;
42     import std.algorithm: map;
43     import std.range: iota;
44     import std.array: array, join;
45     import std.typecons: No;
46     import std.conv: text;
47 
48     // get the original list of template parameters and translate them
49     // e.g. template<bool, bool, typename> -> (bool V0, bool V1, T)
50     const translatedTemplateParams = () @trusted {
51         return translateTemplateParams(cursor, context, No.defaults)
52         .array;
53     }();
54 
55     // e.g. for template<> struct foo<false, true, int32_t>
56     // 0 -> `bool V0: false`, 1 -> `bool V1: true`, 2 -> `T0: int`
57     string element(in Type templateArgType, in int index) {
58 
59         import dpp.translation.exception: UntranslatableException;
60 
61         string errorMsg(in string keyword) {
62             import std.conv: text;
63             return text("Cannot translate ", index, "th template arg of ", cursor, " due to `",
64                         keyword, "` template type parameter specialisation");
65         }
66 
67         if(templateArgType.isConstQualified)
68             throw new UntranslatableException(errorMsg(`const`));
69 
70         if(templateArgType.isVolatileQualified)
71             throw new UntranslatableException(errorMsg(`volatile`));
72 
73         if(index > translatedTemplateParams.length)
74             throw new UntranslatableException(
75                 text("template index (",
76                      index, ") larger than parameter length:\n",
77                      translatedTemplateParams));
78 
79         string ret = translatedTemplateParams[index];  // e.g. `T`,  `bool V0`
80         const maybeSpecialisation = translateTemplateParamSpecialisation(cursor.type, templateArgType, index, context);
81         const templateArgKind = templateArgumentKind(templateArgType);
82 
83         with(TemplateArgumentKind) {
84             const isSpecialised =
85                 templateArgKind == SpecialisedType ||
86                 (templateArgKind == Value && isValueOfType(cursor, context, index, maybeSpecialisation));
87 
88             if(isSpecialised) ret ~= ": " ~ maybeSpecialisation;
89         }
90 
91         return ret;
92     }
93 
94     return () @trusted {
95         return
96             cursor.type.numTemplateArguments
97             .iota
98             .map!(i => element(cursor.type.typeTemplateArgument(i), i))
99             .array
100             ;
101     }();
102 }
103 
104 
105 // FIXME: refactor
106 private auto translateSpecialisedTemplateParamsVariadic(in from!"clang".Cursor cursor,
107                                                         ref from!"dpp.runtime.context".Context context)
108     @safe
109     in(isFromVariadicTemplate(cursor) && cursor.type.numTemplateArguments != -1)
110     do
111 {
112     import dpp.translation.type: translate;
113 
114     string[] ret;
115 
116     foreach(i; 0 .. cursor.type.numTemplateArguments) {
117         ret ~= translate(cursor.type.typeTemplateArgument(i), context);
118     }
119 
120     return ret;
121 }
122 
123 // In the case cursor is a partial or full template specialisation,
124 // check to see if `maybeSpecialisation` can be converted to the
125 // indexth template parameter of the cursor's original template.
126 // If it can, then it's a value of that type.
127 private bool isValueOfType(
128     in from!"clang".Cursor cursor,
129     ref from!"dpp.runtime.context".Context context,
130     in int index,
131     in string maybeSpecialisation,
132     )
133     @safe
134 {
135     import dpp.translation.type: translate;
136     import dpp.translation.exception: UntranslatableException;
137     import std.array: array;
138     import std.exception: collectException;
139     import std.conv: to;
140     import core.stdc.config: c_long, c_ulong;
141 
142     // the original template cursor (no specialisations)
143     const templateCursor = cursor.specializedCursorTemplate;
144     // the type of the indexth template parameter
145     const templateParamCursor = () @trusted { return templateCursor.templateParams.array[index]; }();
146     // the D translation of that type
147     const dtype = translate(templateParamCursor.type, context);
148 
149     Exception conversionException;
150 
151     void tryConvert(T)() {
152         conversionException = collectException(maybeSpecialisation.to!T);
153     }
154 
155     switch(dtype) {
156         default: throw new UntranslatableException("isValueOfType cannot handle type `" ~ dtype ~ "`");
157         case "bool":    tryConvert!bool;    break;
158         case "char":    tryConvert!char;    break;
159         case "wchar":   tryConvert!wchar;   break;
160         case "dchar":   tryConvert!dchar;   break;
161         case "short":   tryConvert!short;   break;
162         case "ushort":  tryConvert!ushort;  break;
163         case "int":     tryConvert!int;     break;
164         case "uint":    tryConvert!uint;    break;
165         case "long":    tryConvert!long;    break;
166         case "ulong":   tryConvert!ulong;   break;
167         case "c_ulong": tryConvert!c_ulong; break;
168         case "c_long":  tryConvert!c_long;  break;
169     }
170 
171     return conversionException is null;
172 }
173 
174 
175 // Translates a C++ template parameter (value or type) to a D declaration
176 // e.g. `template<typename, bool, typename>` -> ["T0", "bool V0", "T1"]
177 // Returns a range of string
178 package auto translateTemplateParams(
179     in from!"clang".Cursor cursor,
180     ref from!"dpp.runtime.context".Context context,
181     from!"std.typecons".Flag!"defaults" defaults = from!"std.typecons".Yes.defaults,
182     ) @safe
183 {
184     import dpp.translation.type: translate, translateString;
185     import clang: Cursor;
186     import std.conv: text;
187     import std.algorithm: map, filter, countUntil;
188     import std.array: array;
189     import std.range: enumerate;
190 
191     int templateParamIndex;  // used to generate names when there are none
192 
193     string newTemplateParamName() {
194         // FIXME
195         // the naming convention is to match what libclang gives, but there's no
196         // guarantee that it'll always match.
197         return text("type_parameter_0_", templateParamIndex++);
198     }
199 
200     // translate a template parameter cursor
201     string translateTemplateParam(in long index, in Cursor templateParam) {
202         import dpp.translation.type: translate;
203         import dpp.translation.tokens: translateTokens;
204         import clang: Token;
205 
206         // The template parameter might be a value (bool, int, etc.)
207         // or a type. If it's a value we get its type here.
208         const maybeType =
209             // a type doesn't have a type
210             templateParam.kind == Cursor.Kind.TemplateTypeParameter
211             // In C++, variadic templates can be values of a type, e.g.
212             // `template<int...>`
213             // The only way to declare this in D would be using a template contraint,
214             // but the main declaration just needs a name and the ellipsis - in D variadic
215             // templates can be types, values, or symbols. To prevent us from trying to
216             // declare in D `int param...`, which isn't valid, we don't use the type of
217             // a value parameter if it's variadic
218             || (cursor.isVariadicTemplate && index == cursor.templateParams.length - 1)
219             ? ""
220             : translate(templateParam.type, context) ~ " ";
221 
222         // D requires template parameters to have names
223         const spelling = templateParam.spelling == "" ? newTemplateParamName : templateParam.spelling;
224 
225         // There's no direct way to extract default template parameters from libclang
226         // so we search for something like `T = Foo` in the tokens
227         const equalIndex = templateParam.tokens.countUntil!(t => t.kind == Token.Kind.Punctuation &&
228                                                                  t.spelling == "=");
229 
230         const maybeDefault = equalIndex == -1 || !defaults
231             ? ""
232             : templateParam.tokens[equalIndex .. $]
233                 .array
234                 .translateTokens
235             ;
236 
237         // e.g. "bool param", "T0"
238         return maybeType ~ spelling ~ maybeDefault;
239     }
240 
241     auto templateParams = cursor.templateParams;
242     context.log("Children: ", cursor.children);
243     context.log("Template Params: ", templateParams);
244     auto translated = templateParams
245         .enumerate
246         .map!(a => translateTemplateParam(a[0], a[1]))
247         ;
248 
249     // might need to be a variadic parameter
250     string maybeVariadic(in long index, in string name) {
251         return cursor.isVariadicTemplate && index == translated.length - 1
252             // If it's variadic, come up with a new name in case it's variadic
253             // values. D doesn't really care.
254             ? name ~ "..."
255             : name;
256     }
257 
258     return () @trusted {
259         return translated
260             .enumerate
261             .map!(a => maybeVariadic(a[0], a[1]))
262         ;
263     }();
264 }
265 
266 // If the original template is variadic
267 private bool isFromVariadicTemplate(in from!"clang".Cursor cursor) @safe {
268     return isVariadicTemplate(cursor.specializedCursorTemplate);
269 }
270 
271 private bool isVariadicTemplate(in from!"clang".Cursor cursor) @safe {
272     import clang: Cursor, Token;
273     import std.array: array;
274     import std.algorithm: canFind, countUntil;
275 
276     const templateParamChildren = () @trusted { return cursor.templateParams.array; }();
277 
278     // There might be a "..." token inside the body of the struct/class, and we don't want to
279     // look at that. So instead we stop looking at tokens when the struct/class definition begins.
280     const closeAngleIndex = cursor.tokens.countUntil!(a => a.kind ==
281                                                       Token.Kind.Punctuation &&
282                                                       (a.spelling == ">" || a.spelling == ">>"));
283     const tokens = closeAngleIndex == -1 ? cursor.tokens : cursor.tokens[0 .. closeAngleIndex];
284 
285     return tokens.canFind(Token(Token.Kind.Punctuation, "..."));
286 }
287 
288 
289 // e.g. `template <typename T> using foo = bar;`
290 string[] translateTypeAliasTemplate(in from!"clang".Cursor cursor,
291                                     ref from!"dpp.runtime.context".Context context)
292     @safe
293     in(cursor.kind == from!"clang".Cursor.Kind.TypeAliasTemplateDecl)
294 do
295 {
296     import dpp.translation.type: translate;
297     import dpp.translation.exception: UntranslatableException;
298     import clang: Cursor, Type;
299     import std.conv: text;
300     import std.algorithm: countUntil;
301     import std.typecons: No;
302     import std.array: join, replace;
303 
304     // see contract.templates.using
305     const typeAliasIndex = cursor.children.countUntil!(c => c.kind == Cursor.Kind.TypeAliasDecl);
306     assert(typeAliasIndex != -1, text(cursor.children));
307     const typeAlias = cursor.children[typeAliasIndex];
308 
309     const underlying = () {
310         if(typeAlias.underlyingType.kind == Type.Kind.Unexposed) {
311 
312             const templateRefIndex = typeAlias
313             .children
314             .countUntil!(c => c.kind == Cursor.Kind.TemplateRef);
315 
316             if(templateRefIndex < 0 || templateRefIndex >= typeAlias.children.length)
317                 throw new UntranslatableException(
318                     text("templateRefIndex (", templateRefIndex, ") out of bounds. Children:\n", typeAlias.children));
319 
320             const templateRef = typeAlias.children[templateRefIndex];
321             return templateRef.spelling;
322 
323         } else
324             return translate(typeAlias.underlyingType, context, No.translatingFunction);
325     }();
326 
327     // FIXME
328     // Not sure what to do here to be able to satisfy both:
329     // ----------
330     // template<typename T> struct bar;
331     // template<typename T> using foo = bar;
332     // ----------
333     // And:
334     // ----------
335     // template<typename...> using void_t = void;
336     // ----------
337     // The first one can be either `alias foo = bar` or `alias foo(T) = bar(T)`
338     // but the 2nd one has to be `alias void_t(T...) = void`.
339     // The first example has to have the same template arguments on both sides,
340     // the second needs it only on the left.
341     const templateParams = isVariadicTemplate(cursor)
342         ? "(" ~ translateTemplateParams(cursor, context).join(", ") ~ ")"
343         : "";
344 
345     return [text("alias ", cursor.spelling, templateParams ~ " = ", underlying, ";")];
346 }