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