1 /**
2    Function translations.
3  */
4 module dpp.translation.function_;
5 
6 import dpp.from;
7 
8 
9 enum OPERATOR_PREFIX = "operator";
10 
11 
12 string[] translateFunction(in from!"clang".Cursor cursor,
13                            ref from!"dpp.runtime.context".Context context)
14     @safe
15     in(
16         cursor.kind == from!"clang".Cursor.Kind.FunctionDecl ||
17         cursor.kind == from!"clang".Cursor.Kind.CXXMethod ||
18         cursor.kind == from!"clang".Cursor.Kind.Constructor ||
19         cursor.kind == from!"clang".Cursor.Kind.Destructor ||
20         cursor.kind == from!"clang".Cursor.Kind.ConversionFunction ||
21         cursor.kind == from!"clang".Cursor.Kind.FunctionTemplate
22     )
23     do
24 {
25     import dpp.translation.dlang: maybeRename, maybePragma;
26     import dpp.translation.aggregate: maybeRememberStructs;
27     import dpp.translation.type: translate;
28     import clang: Cursor, Type;
29     import std.array: join, array;
30     import std.conv: text;
31     import std.algorithm: any, endsWith, canFind;
32     import std.typecons: Yes;
33 
34     if(ignoreFunction(cursor)) return [];
35 
36     // FIXME - stop special casing the move ctor
37     auto moveCtorLines = maybeMoveCtor(cursor, context);
38     if(moveCtorLines.length) return moveCtorLines;
39 
40     string[] lines;
41 
42     lines ~= maybeCopyCtor(cursor, context);
43     lines ~= maybeOperator(cursor, context);
44 
45     // never declared types might lurk here
46     maybeRememberStructs(cursor.type.paramTypes, context);
47 
48     const spelling = functionSpelling(cursor, context);
49 
50     lines ~= [
51         maybePragma(cursor, context) ~ functionDecl(cursor, context, spelling)
52     ];
53 
54     context.log("");
55 
56     return lines;
57 }
58 
59 private bool ignoreFunction(in from!"clang".Cursor cursor) @safe {
60     import clang: Cursor, Type, Token;
61     import std.algorithm: countUntil, any, canFind, startsWith;
62 
63     // C++ partial specialisation function bodies
64     if(cursor.semanticParent.kind == Cursor.Kind.ClassTemplatePartialSpecialization)
65         return true;
66 
67     // C++ deleted functions
68     const deleteIndex = cursor.tokens.countUntil(Token(Token.Kind.Keyword, "delete"));
69     if(deleteIndex != -1 && deleteIndex > 1) {
70         if(cursor.tokens[deleteIndex - 1] == Token(Token.Kind.Punctuation, "="))
71             return true;
72     }
73 
74     // C++ member functions defined "outside the class", e.g.
75     // `int Foo::bar() const { return 42; }`
76     // This first condition checks if the function cursor has a body (compound statement)
77     if(cursor.children.any!(a => a.kind == Cursor.Kind.CompoundStmt)) {
78 
79         // If it has a body, we check that its tokens contain "::" in the right place
80 
81         const tokens = cursor.tokens;
82         const doubleColonIndex = tokens.countUntil(Token(Token.Kind.Punctuation, "::"));
83 
84         if(doubleColonIndex != -1) {
85             const nextToken = tokens[doubleColonIndex + 1];
86             // The reason we're not checking the next token's spelling exactly is
87             // because for templated types the cursor's spelling might be `Foo<T>`
88             // but the token is `Foo`.
89             if(nextToken.kind == Token.Kind.Identifier &&
90                cursor.spelling.startsWith(nextToken.spelling))
91                 return true;
92         }
93     }
94 
95     // FIXME - no default contructors for structs in D
96     // We're not even checking if it's a struct here, so classes are being
97     // affected for no reason.
98     if(cursor.kind == Cursor.Kind.Constructor && numParams(cursor) == 0) return true;
99 
100     return false;
101 }
102 
103 private string functionDecl(
104     in from!"clang".Cursor cursor,
105     ref from!"dpp.runtime.context".Context context,
106     in string spelling,
107     in from!"std.typecons".Flag!"names" names = from!"std.typecons".No.names
108 )
109     @safe
110 {
111     import dpp.translation.template_: translateTemplateParams;
112     import dpp.translation.exception: UntranslatableException;
113     import std.conv: text;
114     import std.algorithm: endsWith, canFind;
115     import std.array: join;
116 
117     context.log("Function return type (raw):        ", cursor.type.returnType);
118     context.log("Function children: ", cursor.children);
119     context.log("Function paramTypes: ", cursor.type.paramTypes);
120     const returnType = returnType(cursor, context);
121     context.log("Function return type (translated): ", returnType);
122 
123     const params = translateAllParamTypes(cursor, context, names).join(", ");
124     context.log("Translated parameters: '", params, "'");
125     // const C++ method?
126     const const_ = cursor.isConstCppMethod ? " const" : "";
127 
128     auto templateParams = translateTemplateParams(cursor, context);
129     const ctParams = templateParams.empty
130         ? ""
131         : "(" ~ templateParams.join(", ") ~ ")"
132         ;
133 
134     // FIXME: avoid opBinary(string op: )(CT params)(RT params)
135     // See it.cpp.function_.opBinary
136     if(ctParams != "" && spelling.canFind("("))
137         throw new UntranslatableException("BUG with templated operators");
138 
139     return text(returnType, " ", spelling, ctParams, "(", params, ") @nogc nothrow", const_, ";");
140 }
141 
142 private string returnType(in from!"clang".Cursor cursor,
143                           ref from!"dpp.runtime.context".Context context)
144     @safe
145 {
146     import dpp.translation.type: translate;
147     import clang: Cursor;
148     import std.typecons: Yes;
149 
150     const blob = blob(cursor.returnType, context);
151     if(blob != "") return blob;
152 
153     const indentation = context.indentation;
154 
155     const isCtorOrDtor = isConstructor(cursor) || cursor.kind == Cursor.Kind.Destructor;
156     const dType = isCtorOrDtor
157         ? ""
158         : translate(cursor.returnType, context, Yes.translatingFunction);
159 
160     context.setIndentation(indentation);
161 
162     const maybeStatic = cursor.storageClass == Cursor.StorageClass.Static ? "static " : "";
163 
164     return maybeStatic ~ dType;
165 }
166 
167 private string[] maybeOperator(in from!"clang".Cursor cursor,
168                                ref from!"dpp.runtime.context".Context context)
169     @safe
170 {
171     import std.algorithm: map;
172     import std.array: join, array;
173     import std.typecons: Yes;
174     import std.range: iota;
175     import std.conv: text;
176 
177     if(!isSupportedOperatorInD(cursor)) return [];
178 
179     const params = translateAllParamTypes(cursor, context).array;
180 
181     return [
182         // remove semicolon from the end with [0..$-1]
183         `extern(D) ` ~ functionDecl(cursor, context, operatorSpellingD(cursor, context), Yes.names)[0..$-1],
184         `{`,
185         `    return ` ~ operatorSpellingCpp(cursor, context) ~ `(` ~ params.length.iota.map!(a => text("arg", a)).join(", ") ~ `);`,
186         `}`,
187     ];
188 }
189 
190 private bool isSupportedOperatorInD(in from!"clang".Cursor cursor) @safe nothrow {
191     import clang: Cursor;
192     import std.algorithm: map, canFind;
193 
194     if(!isOperator(cursor)) return false;
195     // No D support for free function operator overloads
196     if(cursor.semanticParent.kind == Cursor.Kind.TranslationUnit) return false;
197 
198     const cppOperator = cursor.spelling[OPERATOR_PREFIX.length .. $];
199     const unsupportedSpellings = [`!`, `,`, `&&`, `||`, `->`, `->*`];
200     if(unsupportedSpellings.canFind(cppOperator)) return false;
201 
202     if(isUnaryOperator(cursor) && cppOperator == "&") return false;
203     if(!isUnaryOperator(cursor) && !isBinaryOperator(cursor)) return false;
204 
205      return true;
206 }
207 
208 private bool isOperator(in from!"clang".Cursor cursor) @safe pure nothrow {
209     import std.algorithm: startsWith;
210     return
211         cursor.spelling.startsWith(OPERATOR_PREFIX)
212         && cursor.spelling.length > OPERATOR_PREFIX.length
213         && cursor.spelling[OPERATOR_PREFIX.length] != '_'
214         ;
215 }
216 
217 
218 private string functionSpelling(in from!"clang".Cursor cursor,
219                                 ref from!"dpp.runtime.context".Context context)
220     @safe
221 {
222     import clang: Cursor;
223 
224     if(isConstructor(cursor)) return "this";
225     if(cursor.kind == Cursor.Kind.Destructor) return "~this";
226 
227     if(isOperator(cursor)) return operatorSpellingCpp(cursor, context);
228 
229     // if no special case
230     return context.rememberLinkable(cursor);
231 }
232 
233 private bool isConstructor(in from!"clang".Cursor cursor) @safe nothrow {
234     import clang: Cursor;
235     import std.algorithm: startsWith;
236 
237     return cursor.kind == Cursor.Kind.Constructor ||
238         cursor.spelling.startsWith(cursor.semanticParent.spelling ~ "<");
239 }
240 
241 private string operatorSpellingD(in from!"clang".Cursor cursor,
242                                  ref from!"dpp.runtime.context".Context context)
243     @safe
244 {
245     import clang: Cursor;
246     import std.range: walkLength;
247     import std.algorithm: canFind;
248     import std.conv: text;
249 
250     const cppOperator = cursor.spelling[OPERATOR_PREFIX.length .. $];
251 
252     if(cursor.kind == Cursor.Kind.ConversionFunction) {
253         return `opCast(T: ` ~ returnType(cursor, context) ~ `)`;
254     }
255 
256     if(cppOperator.length > 1 &&
257        cppOperator[$-1] == '=' &&
258        (cppOperator.length != 2 || !['=', '!', '<', '>'].canFind(cppOperator[0])))
259         return `opOpAssign(string op: "` ~ cppOperator[0 .. $-1] ~ `")`;
260 
261     assert(isUnaryOperator(cursor) || isBinaryOperator(cursor),
262            text("Cursor is neither a unary or binary operator: ", cursor, "@", cursor.sourceRange.start));
263     const dFunction = isBinaryOperator(cursor) ? "opBinary" : "opUnary";
264 
265     // to avoid problems, some C++ operators are translated as template functions,
266     // but as seen in #114, don't do this if they're already templates!
267     const templateParens = cursor.templateParams.length
268         ? ""
269         : "()";
270 
271     // Some of the operators here have empty parentheses around them. This is to
272     // to make them templates and only be instantiated if needed. See #102.
273     switch(cppOperator) {
274         default:
275             return dFunction ~ `(string op: "` ~ cppOperator ~ `")`;
276 
277         case "=": return `opAssign` ~ templateParens;
278         case "()": return `opCall` ~ templateParens;
279         case "[]": return `opIndex` ~ templateParens;
280         case "==": return `opEquals` ~ templateParens;
281     }
282 }
283 
284 private bool isUnaryOperator(in from!"clang".Cursor cursor) @safe pure nothrow {
285     return isOperator(cursor) && numParams(cursor) == 0;
286 }
287 
288 private bool isBinaryOperator(in from!"clang".Cursor cursor) @safe pure nothrow {
289     return isOperator(cursor) && numParams(cursor) == 1;
290 }
291 
292 package long numParams(in from!"clang".Cursor cursor) @safe pure nothrow {
293     import std.range: walkLength;
294     return walkLength(cursor.type.paramTypes);
295 }
296 
297 private string operatorSpellingCpp(in from!"clang".Cursor cursor,
298                                    ref from!"dpp.runtime.context".Context context)
299     @safe
300     in(isOperator(cursor))
301 do
302 {
303     import dpp.translation.type: translate;
304     import dpp.translation.exception: UntranslatableException;
305     import clang: Cursor;
306     import std.string: replace;
307     import std.algorithm: startsWith;
308 
309     const operator = cursor.spelling[OPERATOR_PREFIX.length .. $];
310 
311     if(cursor.kind == Cursor.Kind.ConversionFunction) {
312         return "opCppCast_" ~ translate(cursor.returnType, context).replace(".", "_");
313     }
314 
315     switch(operator) {
316         default:
317             if(operator.startsWith(`""`))  // user-defined string literal
318                 throw new UntranslatableException("Cannot translate user-defined literals");
319 
320             throw new UntranslatableException("Unknown C++ spelling for operator '" ~ operator ~ "'");
321 
322         case        "+":  return `opCppPlus`;
323         case        "-":  return `opCppMinus`;
324         case       "++":  return `opCppIncrement`;
325         case       "--":  return `opCppDecrement`;
326         case        "*":  return `opCppMul`;
327         case        "/":  return `opCppDiv`;
328         case        "&":  return `opCppAmpersand`;
329         case        "~":  return `opCppTilde`;
330         case        "%":  return `opCppMod`;
331         case        "^":  return `opCppCaret`;
332         case        "|":  return `opCppPipe`;
333         case        "=":  return `opCppAssign`;
334         case       ">>":  return `opCppRShift`;
335         case       "<<":  return `opCppLShift`;
336         case       "->":  return `opCppArrow`;
337         case        "!":  return `opCppBang`;
338         case       "&&":  return `opCppAnd`;
339         case       "||":  return `opCppOr`;
340         case        ",":  return `opCppComma`;
341         case      "->*":  return `opCppArrowStar`;
342         case       "+=":  return `opCppPlusAssign`;
343         case       "-=":  return `opCppMinusAssign`;
344         case       "*=":  return `opCppMulAssign`;
345         case       "/=":  return `opCppDivAssign`;
346         case       "%=":  return `opCppModAssign`;
347         case       "^=":  return `opCppCaretAssign`;
348         case       "&=":  return `opCppAmpersandAssign`;
349         case       "|=":  return `opCppPipeAssign`;
350         case      ">>=":  return `opCppRShiftAssign`;
351         case      "<<=":  return `opCppLShiftAssign`;
352         case       "()":  return `opCppCall`;
353         case       "[]":  return `opCppIndex`;
354         case       "==":  return `opCppEquals`;
355         case       "!=":  return `opCppNotEquals`;
356         case       "<=":  return `opCppLessEquals`;
357         case       ">=":  return `opCppMoreEquals`;
358         case        "<":  return `opCppLess`;
359         case        ">":  return `opCppMore`;
360         case      " new": return `opCppNew`;
361         case    " new[]": return `opCppNewArray`;
362         case   " delete": return `opCppDelete`;
363         case " delete[]": return `opCppDeleteArray`;
364     }
365 
366     assert(0);
367 }
368 
369 
370 // Add a non-const ref that forwards to the const ref copy ctor
371 // so that lvalues don't match the by-value ctor
372 private string[] maybeCopyCtor(in from!"clang".Cursor cursor,
373                                ref from!"dpp.runtime.context".Context context)
374     @safe
375 {
376 
377     import dpp.translation.dlang: maybeRename, maybePragma;
378     import dpp.translation.type: translate;
379     import clang: Cursor, Type;
380     import std.array: front;
381 
382     if(!cursor.isCopyConstructor) return [];
383 
384     const param = cursor.type.paramTypes.front;
385     const translated = translateFunctionParam(cursor, param, context);
386     const dType = translated["ref const(".length .. $ - 1];  // remove the constness
387 
388     return [
389         `this(ref ` ~ dType ~ ` other)`,
390         `{`,
391         `   this(*cast(const ` ~ dType ~ `*) &other);`,
392         `}`,
393     ];
394 }
395 
396 
397 private string[] maybeMoveCtor(in from!"clang".Cursor cursor,
398                                ref from!"dpp.runtime.context".Context context)
399     @safe
400 {
401     import dpp.translation.dlang: maybeRename, maybePragma;
402     import dpp.translation.type: translate;
403     import clang: Cursor, Type;
404     import std.array: front;
405 
406     if(!cursor.isMoveConstructor) return [];
407 
408     const paramType = cursor.type.paramTypes.front;
409     const pointee = translate(paramType.pointee, context);
410 
411     return [
412         // The actual C++ move constructor but declared to take a pointer
413         maybePragma(cursor, context) ~ " this(" ~ pointee ~ "*);",
414         // The fake D move constructor
415         "this(" ~ translate(paramType, context) ~ " wrapper) {",
416         "    this(wrapper.ptr);",
417         // Hollow out moved-from value
418         "    static if(is(typeof( { typeof(*wrapper.ptr) _; }   )))",
419         "    {",
420         "        typeof(*wrapper.ptr) init;",
421         "        *wrapper.ptr = init;",
422         "    }",
423         "    else",
424         "    {",
425         "        import core.stdc.string: memset;",
426         "        memset(wrapper.ptr, 0, typeof(*wrapper.ptr).sizeof);",
427         "    }",
428         "}",
429         // The fake D by-value constructor imitating C++ semantics
430         "this(" ~ pointee ~ " other)",
431         "{",
432         // Forward the call to the C++ move constructor
433         "    this(&other);",
434         "}",
435     ];
436 }
437 
438 // includes C variadic params
439 private auto translateAllParamTypes(
440     in from!"clang".Cursor cursor,
441     ref from!"dpp.runtime.context".Context context,
442     in from!"std.typecons".Flag!"names" names = from!"std.typecons".No.names,
443 )
444     @safe
445 {
446     import clang: Cursor;
447     import std.algorithm: endsWith, map;
448     import std.range: enumerate, chain;
449     import std.conv: text;
450 
451     // Here we used to check that if there were no parameters and the language is C,
452     // then the correct translation in D would be (...);
453     // However, that's not allowed in D. It just so happens that C production code
454     // exists that doesn't bother with (void), so instead of producing something that
455     // doesn't compile, we compromise and assume the user meant (void)
456 
457     auto paramTypes = translateParamTypes(cursor, cursor.type, context);
458 
459     const isVariadic =
460         cursor.type.spelling.endsWith("...)")
461         && cursor.kind != Cursor.Kind.FunctionTemplate
462         ;
463     const variadicParams = isVariadic ? ["..."] : [];
464 
465     return enumerate(chain(paramTypes, variadicParams))
466         .map!(a => names ? a[1] ~ text(" arg", a[0]) : a[1])
467         ;
468 }
469 
470 // does not include C variadic params
471 auto translateParamTypes(in from!"clang".Cursor cursor,
472                          in from!"clang".Type cursorType,
473                          ref from!"dpp.runtime.context".Context context)
474     @safe
475 {
476     import std.algorithm: map;
477     import std.range: tee, enumerate;
478 
479     return cursorType
480         .paramTypes
481         .enumerate
482         .tee!((a) {
483             context.log("    Function param #", a[0],
484                         " type: ", a[1], "  canonical ", a[1].canonical);
485         })
486         .map!(t => translateFunctionParam(cursor, t[1], context))
487         ;
488 }
489 
490 
491 // translate a ParmDecl
492 private string translateFunctionParam(in from!"clang".Cursor function_,
493                                       in from!"clang".Type paramType,
494                                       ref from!"dpp.runtime.context".Context context)
495     @safe
496 {
497 
498     import dpp.translation.type: translate;
499     import clang: Type, Language;
500     import std.typecons: Yes;
501     import std.array: replace;
502 
503     // Could be an opaque type
504     const blob = blob(paramType, context);
505     if(blob != "") return blob;
506 
507     // See #43
508     const(Type) deunexpose(in Type type) {
509         return type.kind == Type.Kind.Unexposed && function_.language != Language.CPlusPlus
510             ? type.canonical
511             : type;
512     }
513 
514     // See contract.ctor.copy.definition.declartion for libclang silliness leading to this.
515     // If the enclosing struct/class is templated and the function isn't, then we might
516     // get "type-parameter-0-0" spellings even when the actual name is e.g. `T`.
517     const numAggTemplateParams = function_.semanticParent.templateParams.length;
518     const numFunTemplateParams = function_.templateParams.length;
519 
520     // HACK
521     // FIXME: not sure what to do if the numbers aren't exactly 1 and 0
522     const useAggTemplateParamSpelling = numAggTemplateParams == 1 && numFunTemplateParams == 0;
523     const aggTemplateParamSpelling = useAggTemplateParamSpelling
524         ? function_.semanticParent.templateParams[0].spelling
525         : "";
526     const translation = translate(deunexpose(paramType), context, Yes.translatingFunction);
527 
528     return useAggTemplateParamSpelling
529         ? translation.replace("type_parameter_0_0", aggTemplateParamSpelling)
530         : translation;
531 }
532 
533 
534 private string blob(in from!"clang".Type type,
535                     in from!"dpp.runtime.context".Context context)
536     @safe
537 {
538     import dpp.translation.type: translateOpaque;
539     import clang: Type;
540     import std.conv: text;
541 
542     // if the type is from an ignored namespace, use an opaque type, but not
543     // if it's a pointer or reference - in that case the user can always
544     // declare the type with no definition.
545     if(context.isFromIgnoredNs(type) &&
546        type.kind != Type.Kind.LValueReference &&
547        type.kind != Type.Kind.Pointer)
548     {
549         return translateOpaque(type);
550     }
551 
552     return "";
553 }