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