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