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