1 /** 2 Type translations 3 */ 4 module dpp.translation.type; 5 6 7 import dpp.from: from; 8 9 10 alias Translator = string function( 11 in from!"clang".Type type, 12 ref from!"dpp.runtime.context".Context context, 13 in from!"std.typecons".Flag!"translatingFunction" translatingFunction 14 ) @safe; 15 16 alias Translators = Translator[from!"clang".Type.Kind]; 17 18 19 string translate(in from!"clang".Type type, 20 ref from!"dpp.runtime.context".Context context, 21 in from!"std.typecons".Flag!"translatingFunction" translatingFunction = from!"std.typecons".No.translatingFunction) 22 @safe 23 { 24 import dpp.translation.exception: UntranslatableException; 25 import std.conv: text; 26 import std.array: replace; 27 28 if(type.kind !in translators) 29 throw new UntranslatableException(text("Type kind ", type.kind, " not supported: ", type)); 30 31 const translation = translators[type.kind](type, context, translatingFunction); 32 33 // hack for std::function since function is a D keyword 34 return translation.replace(`function!`, `function_!`); 35 } 36 37 38 private Translators translators() @safe { 39 static Translators ret; 40 if(ret == ret.init) ret = translatorsImpl; 41 return ret; 42 } 43 44 45 private Translators translatorsImpl() @safe pure { 46 import clang: Type; 47 48 with(Type.Kind) { 49 return [ 50 Void: &simple!"void", 51 NullPtr: &simple!"void*", 52 53 Bool: &simple!"bool", 54 55 WChar: &simple!"wchar", 56 SChar: &simple!"byte", 57 Char16: &simple!"wchar", 58 Char32: &simple!"dchar", 59 UChar: &simple!"ubyte", 60 Char_U: &simple!"ubyte", 61 Char_S: &simple!"char", 62 63 UShort: &simple!"ushort", 64 Short: &simple!"short", 65 Int: &simple!"int", 66 UInt: &simple!"uint", 67 Long: &simple!"c_long", 68 ULong: &simple!"c_ulong", 69 LongLong: &simple!"long", 70 ULongLong: &simple!"ulong", 71 Int128: &simple!"Int128", 72 UInt128: &simple!"UInt128", 73 74 Float: &simple!"float", 75 Double: &simple!"double", 76 Float128: &simple!"real", 77 Half: &simple!"float", 78 LongDouble: &simple!"real", 79 80 Enum: &translateAggregate, 81 Pointer: &translatePointer, 82 FunctionProto: &translateFunctionProto, 83 Record: &translateRecord, 84 FunctionNoProto: &translateFunctionProto, 85 Elaborated: &translateAggregate, 86 ConstantArray: &translateConstantArray, 87 IncompleteArray: &translateIncompleteArray, 88 Typedef: &translateTypedef, 89 LValueReference: &translateLvalueRef, 90 RValueReference: &translateRvalueRef, 91 Complex: &translateComplex, 92 DependentSizedArray: &translateDependentSizedArray, 93 Vector: &translateSimdVector, 94 MemberPointer: &translatePointer, // FIXME #83 95 Invalid: &ignore, // FIXME C++ stdlib <type_traits> 96 Unexposed: &translateUnexposed, 97 ]; 98 } 99 } 100 101 private string ignore(in from!"clang".Type type, 102 ref from!"dpp.runtime.context".Context context, 103 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 104 @safe 105 { 106 return ""; 107 } 108 109 110 private string simple(string translation) 111 (in from!"clang".Type type, 112 ref from!"dpp.runtime.context".Context context, 113 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 114 @safe 115 { 116 return addModifiers(type, translation); 117 } 118 119 120 private string translateRecord(in from!"clang".Type type, 121 ref from!"dpp.runtime.context".Context context, 122 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 123 @safe 124 { 125 126 // see it.compile.projects.va_list 127 return type.spelling == "struct __va_list_tag" 128 ? "va_list" 129 : translateAggregate(type, context, translatingFunction); 130 } 131 132 private string translateAggregate(in from!"clang".Type type, 133 ref from!"dpp.runtime.context".Context context, 134 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 135 @safe 136 { 137 import dpp.clang: namespace, typeNameNoNs; 138 import std.array: replace, join; 139 import std.algorithm: canFind, countUntil, map; 140 import std.range: iota; 141 import std.typecons: No; 142 143 // if it's anonymous, find the nickname, otherwise return the spelling 144 string spelling() { 145 // clang names anonymous types with a long name indicating where the type 146 // was declared, so we check here with `hasAnonymousSpelling` 147 if(hasAnonymousSpelling(type)) return context.spellingOrNickname(type.declaration); 148 149 // If there's a namespace in the name, we have to remove it. To find out 150 // what the namespace is called, we look at the type's declaration. 151 // In libclang, the type has the FQN, but the cursor only has the name 152 // without namespaces. 153 const tentative = () { 154 155 const ns = type.declaration.namespace; 156 // no namespace, no problem 157 if(ns.isInvalid) { 158 import std.array : split; 159 160 string[] elems = type.spelling.split(" "); 161 string typeName = elems[$ - 1]; 162 string spelling = context.spelling(typeName); 163 context.rememberSpelling(typeName, spelling); 164 elems[$ - 1] = spelling; 165 return elems.join(" "); 166 } 167 168 // look for the namespace name in the declaration 169 const startOfNsIndex = type.spelling.countUntil(ns.spelling); 170 171 // The namespace spelling is always what's considered the namespace in the FQN. 172 // The spelling we get from the cursor itself might not contain this namespace 173 // spelling if there's an alias. 174 // See it.cpp.opaque.paramater.exception_ptr 175 const hiddenNS = !type.spelling.canFind(ns.spelling); 176 177 if(startOfNsIndex != -1) { 178 // +2 due to `::` 179 const endOfNsIndex = startOfNsIndex + ns.spelling.length + 2; 180 // "subtract" the namespace away 181 return type.spelling[endOfNsIndex .. $]; 182 } else if(hiddenNS) { 183 // this block deals with cases where there's a name alias 184 // and the NS doesn't show up how it's spelt but does show up 185 // in the FQN. 186 // See it.cpp.opaque.paramater.exception_ptr 187 const noNs = type.declaration.typeNameNoNs; 188 const endOfNsIndex = type.spelling.countUntil(noNs); 189 190 if(endOfNsIndex == -1) 191 throw new Exception("Could not find namespaceless '" ~ noNs ~ "' in type '" ~ type.spelling ~ "'"); 192 return type.spelling[endOfNsIndex .. $]; 193 } else { 194 return type.spelling; 195 } 196 }() 197 // FIXME - why doesn't `translateString` work here? 198 .replace("::", ".") 199 .replace("typename ", "") 200 ; 201 202 // Clang template types have a spelling such as `Foo<unsigned int, unsigned short>`. 203 // We need to extract the "base" name (e.g. Foo above) then translate each type 204 // template argument (e.g. `unsigned long` is not a D type) 205 if(type.numTemplateArguments > 0) { 206 const openAngleBracketIndex = tentative.countUntil("<"); 207 // this might happen because of alises, e.g. std::string is really std::basic_stream<char> 208 if(openAngleBracketIndex == -1) return tentative; 209 const baseName = tentative[0 .. openAngleBracketIndex]; 210 const templateArgsTranslation = type 211 .numTemplateArguments 212 .iota 213 .map!((i) { 214 const kind = templateArgumentKind(type.typeTemplateArgument(i)); 215 final switch(kind) with(TemplateArgumentKind) { 216 case GenericType: 217 case SpecialisedType: 218 // Never translating function if translating a type template argument 219 return translate(type.typeTemplateArgument(i), context, No.translatingFunction); 220 case Value: 221 return templateParameterSpelling(type, i); 222 } 223 }) 224 .join(", "); 225 return baseName ~ "!(" ~ templateArgsTranslation ~ ")"; 226 } 227 228 return tentative; 229 } 230 231 return addModifiers(type, spelling) 232 .translateElaborated(context) 233 .replace("<", "!(") 234 .replace(">", ")") 235 ; 236 } 237 238 239 private string translateConstantArray(in from!"clang".Type type, 240 ref from!"dpp.runtime.context".Context context, 241 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 242 @safe 243 { 244 import std.conv: text; 245 246 context.indent.log("Constant array of # ", type.numElements); 247 248 return translatingFunction 249 ? translate(type.elementType, context) ~ `*` 250 : translate(type.elementType, context) ~ `[` ~ type.numElements.text ~ `]`; 251 } 252 253 254 private string translateDependentSizedArray( 255 in from!"clang".Type type, 256 ref from!"dpp.runtime.context".Context context, 257 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 258 @safe 259 { 260 import std.conv: text; 261 import std.algorithm: find, countUntil; 262 263 // FIXME: hacky, only works for the only test in it.cpp.class_.template (array) 264 auto start = type.spelling.find("["); start = start[1 .. $]; 265 auto endIndex = start.countUntil("]"); 266 267 return translate(type.elementType, context) ~ `[` ~ start[0 .. endIndex] ~ `]`; 268 } 269 270 271 private string translateIncompleteArray(in from!"clang".Type type, 272 ref from!"dpp.runtime.context".Context context, 273 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 274 @safe 275 { 276 const dType = translate(type.elementType, context); 277 // if translating a function, we want C's T[] to translate 278 // to T*, otherwise we want a flexible array 279 return translatingFunction ? dType ~ `*` : dType ~ "[0]"; 280 281 } 282 283 private string translateTypedef(in from!"clang".Type type, 284 ref from!"dpp.runtime.context".Context context, 285 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 286 @safe 287 { 288 const translation = translate(type.declaration.underlyingType, context, translatingFunction); 289 return addModifiers(type, translation); 290 } 291 292 private string translatePointer(in from!"clang".Type type, 293 ref from!"dpp.runtime.context".Context context, 294 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 295 @safe 296 in(type.kind == from!"clang".Type.Kind.Pointer || type.kind == from!"clang".Type.Kind.MemberPointer) 297 in(!type.pointee.isInvalid) 298 do 299 { 300 import clang: Type; 301 import std.conv: text; 302 import std.typecons: Yes; 303 304 const isFunction = 305 type.pointee.canonical.kind == Type.Kind.FunctionProto || 306 type.pointee.canonical.kind == Type.Kind.FunctionNoProto; 307 308 // `function` in D is already a pointer, so no need to add a `*`. 309 // Otherwise, add `*`. 310 const maybeStar = isFunction ? "" : "*"; 311 context.log("Pointee: ", type.pointee); 312 context.log("Pointee canonical: ", type.pointee.canonical); 313 314 // FIXME: 315 // If the kind is unexposed, we want to get the canonical type. 316 // Unless it's a type parameter, but that part I don't remember why anymore. 317 const translateCanonical = 318 type.pointee.kind == Type.Kind.Unexposed && 319 !isTypeParameter(type.pointee.canonical) 320 ; 321 context.log("Translate canonical? ", translateCanonical); 322 const pointee = translateCanonical ? type.pointee.canonical : type.pointee; 323 324 const indentation = context.indentation; 325 // We always pretend that we're translating a function because from here it's 326 // always a pointer 327 const rawType = translate(pointee, context.indent, Yes.translatingFunction); 328 context.setIndentation(indentation); 329 330 context.log("Raw type: ", rawType); 331 332 // Only add top-level const if it's const all the way down 333 bool addConst() @trusted { 334 auto ptr = Type(type); 335 while(ptr.kind == Type.Kind.Pointer) { 336 if(!ptr.isConstQualified || !ptr.pointee.isConstQualified) 337 return false; 338 ptr = ptr.pointee; 339 } 340 341 return true; 342 } 343 344 version(Windows) { 345 // Microsoft extension for pointers that doesn't compile 346 // elsewhere. It tells the pointer may point to an unaligned 347 // structure, for platforms where that is an optimization. Just 348 // ignoring so it works here. 349 import std..string; 350 auto typePart = replace(rawType, "__unaligned ", ""); 351 } else { 352 auto typePart = rawType; 353 } 354 355 const ptrType = addConst 356 ? `const(` ~ typePart ~ maybeStar ~ `)` 357 : typePart ~ maybeStar; 358 return ptrType; 359 } 360 361 // FunctionProto is the type of a C/C++ function. 362 // We usually get here translating function pointers, since this would be the 363 // pointee type, but it could also be a C++ type template parameter such as 364 // in the case of std::function. 365 private string translateFunctionProto( 366 in from!"clang".Type type, 367 ref from!"dpp.runtime.context".Context context, 368 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 369 @safe 370 { 371 import std.conv: text; 372 import std.algorithm: map; 373 import std.array: join, array; 374 375 const params = type.paramTypes.map!(a => translate(a, context)).array; 376 const isVariadic = params.length > 0 && type.isVariadicFunction; 377 const variadicParams = isVariadic ? ["..."] : []; 378 const allParams = params ~ variadicParams; 379 const returnType = translate(type.returnType, context); 380 381 // The D equivalent of a function pointer (e.g. `int function(double, short)`) 382 const funcPtrTransl = text(returnType, ` function(`, allParams.join(", "), `)`); 383 384 // The D equivalent of a function type. There is no dedicate syntax for this. 385 // In C/C++ it would be e.g. `int(double, short)`. 386 const funcTransl = `typeof(*(` ~ funcPtrTransl ~ `).init)`; 387 388 // In functions, function prototypes as parameters decay to 389 // pointers similarly to how arrays do, so just return the 390 // function pointer type. Otherwise return the function type. 391 return translatingFunction ? funcPtrTransl : funcTransl; 392 } 393 394 395 private string translateLvalueRef(in from!"clang".Type type, 396 ref from!"dpp.runtime.context".Context context, 397 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 398 @safe 399 { 400 const pointeeTranslation = translate(type.pointee, context, translatingFunction); 401 return translatingFunction 402 ? "ref " ~ pointeeTranslation 403 : pointeeTranslation ~ "*"; 404 } 405 406 407 private string translateRvalueRef(in from!"clang".Type type, 408 ref from!"dpp.runtime.context".Context context, 409 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 410 @safe 411 { 412 const dtype = translate(type.canonical.pointee, context, translatingFunction); 413 return `dpp.Move!(` ~ dtype ~ `)`; 414 } 415 416 417 private string translateComplex(in from!"clang".Type type, 418 ref from!"dpp.runtime.context".Context context, 419 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 420 @safe 421 { 422 return "c" ~ translate(type.elementType, context, translatingFunction); 423 } 424 425 private string translateUnexposed(in from!"clang".Type type, 426 ref from!"dpp.runtime.context".Context context, 427 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 428 @safe 429 { 430 import clang: Type; 431 import std..string: replace; 432 import std.algorithm: canFind; 433 434 const canonical = type.canonical; 435 436 // Deal with kinds we know how to deal with here 437 if(canonical.kind != Type.Kind.Unexposed) 438 return translate(canonical, context, translatingFunction); 439 440 // FIXME: there should be a better way 441 const spelling = type.spelling.canFind(" &&...") 442 ? "auto ref " ~ type.spelling.replace(" &&...", "") 443 : type.spelling; 444 445 const translation = translateString(spelling, context) 446 // We might get template arguments here (e.g. `type-parameter-0-0`) 447 // FIXME: this is a hack to get around libclang 448 .replace("type-parameter-0-", "type_parameter_0_") 449 ; 450 451 return addModifiers(type, translation); 452 } 453 454 /** 455 Translate possibly problematic C++ spellings 456 */ 457 string translateString(scope const string spelling, 458 in from!"dpp.runtime.context".Context context) 459 @safe nothrow 460 { 461 import std..string: replace; 462 import std.algorithm: canFind; 463 464 string maybeTranslateTemplateBrackets(in string str) { 465 return str.canFind("<") && str.canFind(">") 466 ? str.replace("<", "!(").replace(">", ")") 467 : str; 468 } 469 470 return 471 maybeTranslateTemplateBrackets(spelling) 472 .replace(context.namespace, "") 473 .replace("decltype", "typeof") 474 .replace("typename ", "") 475 .replace("template ", "") 476 .replace("::", ".") 477 .replace("volatile ", "") 478 .replace("long long", "long") 479 .replace("long double", "double") 480 .replace("unsigned ", "u") 481 .replace("signed char", "char") // FIXME? 482 .replace("&&", "") 483 .replace("...", "") // variadics work differently in D 484 ; 485 } 486 487 string removeDppDecorators(in string spelling) @safe { 488 import std..string : replace; 489 return spelling.replace("__dpp_aggregate__ ", ""); 490 } 491 492 // "struct Foo" -> Foo, "union Foo" -> Foo, "enum Foo" -> Foo 493 string translateElaborated(const scope string spelling, 494 ref from!"dpp.runtime.context".Context context) @safe { 495 import dpp.runtime.context: Language; 496 import std.array: replace; 497 import std.algorithm : find; 498 import std..string : split; 499 import std.range.primitives; 500 501 void remember(in string recordType) @safe pure { 502 // '(' and ')' because of the "const(...)" modifier 503 string[] name = spelling.split!(a => a == '(' || a == ')' || a == ' ').find(recordType); 504 while (!name.empty) { 505 context.rememberAggregateTypeLine(name[1]); 506 name = name[1..$-1].find(recordType); 507 } 508 } 509 510 const rep = context.language == Language.C ? "__dpp_aggregate__ " : ""; 511 512 if (context.language == Language.C) { 513 remember("struct"); 514 remember("union"); 515 remember("enum"); 516 } 517 518 return spelling 519 .replace("struct ", rep) 520 .replace("union ", rep) 521 .replace("enum ", rep) 522 ; 523 } 524 525 private string translateSimdVector(in from!"clang".Type type, 526 ref from!"dpp.runtime.context".Context context, 527 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 528 @safe 529 { 530 import std.conv: text; 531 import std.algorithm: canFind; 532 import std..string: replace; 533 534 const numBytes = type.numElements; 535 const dtype = 536 translate(type.elementType, context, translatingFunction).replace("c_", "") ~ 537 text(numBytes); 538 539 const isUnsupportedType = 540 [ 541 "long1", "char8", "short4", "ubyte8", "byte8", "ushort4", "short4", 542 "uint2", "int2", "ulong1", "float2", "char16", 543 ].canFind(dtype); 544 545 return isUnsupportedType ? "int /* FIXME: unsupported SIMD type */" : "core.simd." ~ dtype; 546 } 547 548 549 private string addModifiers(in from!"clang".Type type, in string translation) @safe pure { 550 import std.array: replace; 551 const realTranslation = translation.replace("const ", "").replace("volatile ", ""); 552 return type.isConstQualified 553 ? `const(` ~ realTranslation ~ `)` 554 : realTranslation; 555 } 556 557 bool hasAnonymousSpelling(in from!"clang".Type type) @safe pure nothrow { 558 import std.algorithm: canFind; 559 return type.spelling.canFind("(anonymous"); 560 } 561 562 563 bool isTypeParameter(in from!"clang".Type type) @safe pure nothrow { 564 import std.algorithm: canFind; 565 // See contract.typedef_.typedef to a template type parameter 566 return type.spelling.canFind("type-parameter-"); 567 } 568 569 /** 570 libclang doesn't offer a lot of functionality when it comes to extracting 571 template arguments from structs - this enum is the best we can do. 572 */ 573 enum TemplateArgumentKind { 574 GenericType, 575 SpecialisedType, 576 Value, // could be specialised or not 577 } 578 579 // type template arguments may be: 580 // Invalid - value (could be specialised or not) 581 // Unexposed - non-specialised type or 582 // anything else - specialised type 583 // The trick is figuring out if a value is specialised or not 584 TemplateArgumentKind templateArgumentKind(in from!"clang".Type type) @safe pure nothrow { 585 import clang: Type; 586 if(type.kind == Type.Kind.Invalid) return TemplateArgumentKind.Value; 587 if(type.kind == Type.Kind.Unexposed) return TemplateArgumentKind.GenericType; 588 return TemplateArgumentKind.SpecialisedType; 589 } 590 591 592 // e.g. `template<> struct foo<false, true, int32_t>` -> 0: false, 1: true, 2: int 593 string translateTemplateParamSpecialisation( 594 in from!"clang".Type cursorType, 595 in from!"clang".Type type, 596 in int index, 597 ref from!"dpp.runtime.context".Context context) 598 @safe 599 { 600 import clang: Type; 601 return type.kind == Type.Kind.Invalid 602 ? templateParameterSpelling(cursorType, index) 603 : translate(type, context); 604 } 605 606 607 // returns the indexth template parameter value from a specialised 608 // template struct/class cursor (full or partial) 609 // e.g. template<> struct Foo<int, 42, double> -> 1: 42 610 string templateParameterSpelling(in from!"clang".Type cursorType, 611 int index) 612 @safe 613 { 614 import dpp.translation.exception: UntranslatableException; 615 import std.algorithm: findSkip, startsWith; 616 import std.array: split; 617 import std.conv: text; 618 619 auto spelling = cursorType.spelling.dup; 620 // If we pass this spelling has had everyting leading up to the opening 621 // angle bracket removed. 622 if(!spelling.findSkip("<")) return ""; 623 assert(spelling[$-1] == '>'); 624 625 const templateParams = spelling[0 .. $-1].split(", "); 626 627 if(index < 0 || index >= templateParams.length) 628 throw new UntranslatableException( 629 text("index (", index, ") out of bounds for template params of length ", 630 templateParams.length, ":\n", templateParams)); 631 632 return templateParams[index].text; 633 } 634 635 636 string translateOpaque(in from!"clang".Type type) 637 @safe 638 { 639 import std.conv: text; 640 return text(`dpp.Opaque!(`, type.getSizeof, `)`); 641 }