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 }