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