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 }