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 }