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 pure; 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 pure 23 { 24 import std.conv: text; 25 if(type.kind !in translators) 26 throw new Exception(text("Type kind ", type.kind, " not supported: ", type)); 27 28 return translators[type.kind](type, context, translatingFunction); 29 } 30 31 32 Translators translators() @safe pure { 33 import clang: Type; 34 35 with(Type.Kind) { 36 return [ 37 Void: &simple!"void", 38 NullPtr: &simple!"void*", 39 40 Bool: &simple!"bool", 41 42 WChar: &simple!"wchar", 43 SChar: &simple!"byte", 44 Char16: &simple!"wchar", 45 Char32: &simple!"dchar", 46 UChar: &simple!"ubyte", 47 Char_U: &simple!"ubyte", 48 Char_S: &simple!"char", 49 50 UShort: &simple!"ushort", 51 Short: &simple!"short", 52 Int: &simple!"int", 53 UInt: &simple!"uint", 54 Long: &simple!"c_long", 55 ULong: &simple!"c_ulong", 56 LongLong: &simple!"long", 57 ULongLong: &simple!"ulong", 58 Int128: &simple!"Int128", 59 UInt128: &simple!"UInt128", 60 61 Float: &simple!"float", 62 Double: &simple!"double", 63 Float128: &simple!"real", 64 Half: &simple!"float", 65 LongDouble: &simple!"real", 66 67 Enum: &translateAggregate, 68 Pointer: &translatePointer, 69 FunctionProto: &translateFunctionProto, 70 Record: &translateRecord, 71 FunctionNoProto: &translateFunctionProto, 72 Elaborated: &translateAggregate, 73 ConstantArray: &translateConstantArray, 74 IncompleteArray: &translateIncompleteArray, 75 Typedef: &translateTypedef, 76 LValueReference: &translateLvalueRef, 77 RValueReference: &translateRvalueRef, 78 Complex: &translateComplex, 79 DependentSizedArray: &translateDependentSizedArray, 80 Vector: &translateSimdVector, 81 MemberPointer: &translatePointer, // FIXME #83 82 Invalid: &ignore, // FIXME C++ stdlib <type_traits> 83 Unexposed: &translateUnexposed, 84 ]; 85 } 86 } 87 88 private string ignore(in from!"clang".Type type, 89 ref from!"dpp.runtime.context".Context context, 90 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 91 @safe pure 92 { 93 return ""; 94 } 95 96 97 private string simple(string translation) 98 (in from!"clang".Type type, 99 ref from!"dpp.runtime.context".Context context, 100 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 101 @safe pure 102 { 103 return addModifiers(type, translation); 104 } 105 106 107 private string translateRecord(in from!"clang".Type type, 108 ref from!"dpp.runtime.context".Context context, 109 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 110 @safe pure 111 { 112 113 // see it.compile.projects.va_list 114 return type.spelling == "struct __va_list_tag" 115 ? "va_list" 116 : translateAggregate(type, context, translatingFunction); 117 } 118 119 private string translateAggregate(in from!"clang".Type type, 120 ref from!"dpp.runtime.context".Context context, 121 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 122 @safe pure 123 { 124 import std.array: replace, join; 125 import std.algorithm: canFind, countUntil, map; 126 import std.range: iota; 127 128 // if it's anonymous, find the nickname, otherwise return the spelling 129 string spelling() { 130 // clang names anonymous types with a long name indicating where the type 131 // was declared, so we check here with `hasAnonymousSpelling` 132 if(hasAnonymousSpelling(type)) return context.spellingOrNickname(type.declaration); 133 134 // A struct in a namespace will have a type of kind Record with the fully 135 // qualified name (e.g. std::random_access_iterator_tag), but the cursor 136 // itself has only the name (e.g. random_access_iterator_tag), so we get 137 // the spelling from the type's declaration instead of from the type itself. 138 // See it.cpp.templates.__copy_move and contract.namespace.struct. 139 if(type.spelling.canFind(":")) return type.declaration.spelling; 140 141 // Clang template types have a spelling such as `Foo<unsigned int, unsigned short>`. 142 // We need to extract the "base" name (e.g. Foo above) then translate each type 143 // template argument (`unsigned long` is not a D type) 144 if(type.numTemplateArguments > 0) { 145 const openAngleBracketIndex = type.spelling.countUntil("<"); 146 const baseName = type.spelling[0 .. openAngleBracketIndex]; 147 const templateArgsTranslation = type 148 .numTemplateArguments 149 .iota 150 .map!((i) { 151 const kind = templateArgumentKind(type.typeTemplateArgument(i)); 152 final switch(kind) with(TemplateArgumentKind) { 153 case GenericType: 154 case SpecialisedType: 155 return translate(type.typeTemplateArgument(i), context, translatingFunction); 156 case Value: 157 return templateParameterSpelling(type, i); 158 } 159 }) 160 .join(", "); 161 return baseName ~ "!(" ~ templateArgsTranslation ~ ")"; 162 } 163 164 return type.spelling; 165 } 166 167 return addModifiers(type, spelling) 168 // "struct Foo" -> Foo, "union Foo" -> Foo, "enum Foo" -> Foo 169 .replace("struct ", "") 170 .replace("union ", "") 171 .replace("enum ", "") 172 .replace("<", "!(") 173 .replace(">", ")") 174 ; 175 } 176 177 178 private string translateConstantArray(in from!"clang".Type type, 179 ref from!"dpp.runtime.context".Context context, 180 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 181 @safe pure 182 { 183 import std.conv: text; 184 185 context.indent.log("Constant array of # ", type.numElements); 186 187 return translatingFunction 188 ? translate(type.elementType, context) ~ `*` 189 : translate(type.elementType, context) ~ `[` ~ type.numElements.text ~ `]`; 190 } 191 192 193 private string translateDependentSizedArray( 194 in from!"clang".Type type, 195 ref from!"dpp.runtime.context".Context context, 196 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 197 @safe pure 198 { 199 import std.conv: text; 200 import std.algorithm: find, countUntil; 201 202 // FIXME: hacky, only works for the only test in it.cpp.class_.template (array) 203 auto start = type.spelling.find("["); start = start[1 .. $]; 204 auto endIndex = start.countUntil("]"); 205 206 return translate(type.elementType, context) ~ `[` ~ start[0 .. endIndex] ~ `]`; 207 } 208 209 210 private string translateIncompleteArray(in from!"clang".Type type, 211 ref from!"dpp.runtime.context".Context context, 212 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 213 @safe pure 214 { 215 const dType = translate(type.elementType, context); 216 // if translating a function, we want C's T[] to translate 217 // to T*, otherwise we want a flexible array 218 return translatingFunction ? dType ~ `*` : dType ~ "[0]"; 219 220 } 221 222 private string translateTypedef(in from!"clang".Type type, 223 ref from!"dpp.runtime.context".Context context, 224 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 225 @safe pure 226 { 227 const translation = translate(type.declaration.underlyingType, context, translatingFunction); 228 return addModifiers(type, translation); 229 } 230 231 private string translatePointer(in from!"clang".Type type, 232 ref from!"dpp.runtime.context".Context context, 233 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 234 @safe pure 235 { 236 import clang: Type; 237 import std.conv: text; 238 239 assert(type.kind == Type.Kind.Pointer || type.kind == Type.Kind.MemberPointer, "type kind not Pointer"); 240 assert(!type.pointee.isInvalid, "pointee is invalid"); 241 242 const isFunction = 243 type.pointee.canonical.kind == Type.Kind.FunctionProto || 244 type.pointee.canonical.kind == Type.Kind.FunctionNoProto; 245 246 // usually "*" but sometimes not needed if already a reference type 247 const maybeStar = isFunction ? "" : "*"; 248 context.log("Pointee: ", type.pointee); 249 context.log("Pointee canonical: ", type.pointee.canonical); 250 251 const translateCanonical = 252 type.pointee.kind == Type.Kind.Unexposed && !isTypeParameter(type.pointee.canonical) 253 ; 254 context.log("Translate canonical? ", translateCanonical); 255 256 const indentation = context.indentation; 257 const rawType = translateCanonical 258 ? translate(type.pointee.canonical, context.indent) 259 : translate(type.pointee, context.indent); 260 context.setIndentation(indentation); 261 262 context.log("Raw type: ", rawType); 263 264 // Only add top-level const if it's const all the way down 265 bool addConst() @trusted { 266 auto ptr = Type(type); 267 while(ptr.kind == Type.Kind.Pointer) { 268 if(!ptr.isConstQualified || !ptr.pointee.isConstQualified) 269 return false; 270 ptr = ptr.pointee; 271 } 272 273 return true; 274 } 275 276 const ptrType = addConst 277 ? `const(` ~ rawType ~ maybeStar ~ `)` 278 : rawType ~ maybeStar; 279 280 return ptrType; 281 } 282 283 // currently only getting here from function pointer variables 284 // with have kind unexposed but canonical kind FunctionProto 285 private string translateFunctionProto(in from!"clang".Type type, 286 ref from!"dpp.runtime.context".Context context, 287 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 288 @safe pure 289 { 290 import std.conv: text; 291 import std.algorithm: map; 292 import std.array: join, array; 293 294 const params = type.paramTypes.map!(a => translate(a, context)).array; 295 const isVariadic = params.length > 0 && type.isVariadicFunction; 296 const variadicParams = isVariadic ? ["..."] : []; 297 const allParams = params ~ variadicParams; 298 return text(translate(type.returnType, context), " function(", allParams.join(", "), ")"); 299 } 300 301 private string translateLvalueRef(in from!"clang".Type type, 302 ref from!"dpp.runtime.context".Context context, 303 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 304 @safe pure 305 { 306 307 // See it.cpp.templates.__normal_iterator.base 308 const typeToUse = type.canonical.isTypeParameter ? type : type.canonical; 309 const pointeeTranslation = translate(typeToUse.pointee, context, translatingFunction); 310 return translatingFunction 311 ? "ref " ~ pointeeTranslation 312 : pointeeTranslation ~ "*"; 313 } 314 315 // we cheat and pretend it's a value 316 private string translateRvalueRef(in from!"clang".Type type, 317 ref from!"dpp.runtime.context".Context context, 318 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 319 @safe pure 320 { 321 const dtype = translate(type.canonical.pointee, context, translatingFunction); 322 return `dpp.Move!(` ~ dtype ~ `)`; 323 } 324 325 326 private string translateComplex(in from!"clang".Type type, 327 ref from!"dpp.runtime.context".Context context, 328 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 329 @safe pure 330 { 331 return "c" ~ translate(type.elementType, context, translatingFunction); 332 } 333 334 private string translateUnexposed(in from!"clang".Type type, 335 ref from!"dpp.runtime.context".Context context, 336 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 337 @safe pure 338 { 339 import std..string: replace; 340 341 const translation = type.spelling 342 .translateString 343 // we might get template arguments here (e.g. `type-parameter-0-0`) 344 .replace("-", "_") 345 ; 346 347 return addModifiers(type, translation); 348 } 349 350 /** 351 Translate possibly problematic C++ spellings 352 */ 353 string translateString(in string spelling) @safe pure nothrow { 354 import std..string: replace; 355 import std.algorithm: canFind; 356 357 string maybeTranslateTemplateBrackets(in string str) { 358 return str.canFind("<") && str.canFind(">") 359 ? str.replace("<", "!(").replace(">", ")") 360 : str; 361 } 362 363 return 364 maybeTranslateTemplateBrackets(spelling) 365 .replace("decltype", "typeof") 366 .replace("typename ", "") 367 .replace("template ", "") 368 .replace("::", ".") 369 .replace("volatile ", "") 370 .replace("long long", "long") 371 .replace("unsigned ", "u") 372 .replace("&&", "") 373 ; 374 } 375 376 377 private string translateSimdVector(in from!"clang".Type type, 378 ref from!"dpp.runtime.context".Context context, 379 in from!"std.typecons".Flag!"translatingFunction" translatingFunction) 380 @safe pure 381 { 382 import std.conv: text; 383 import std.algorithm: canFind; 384 385 const numBytes = type.numElements; 386 const dtype = 387 translate(type.elementType, context, translatingFunction) ~ 388 text(type.getSizeof / numBytes); 389 390 const isUnsupportedType = 391 [ 392 "long8", "short2", "char1", "double8", "ubyte1", "ushort2", 393 "ulong8", "byte1", 394 ].canFind(dtype); 395 396 return isUnsupportedType ? "int /* FIXME: unsupported SIMD type */" : "core.simd." ~ dtype; 397 } 398 399 400 private string addModifiers(in from!"clang".Type type, in string translation) @safe pure { 401 import std.array: replace; 402 const realTranslation = translation.replace("const ", "").replace("volatile ", ""); 403 return type.isConstQualified 404 ? `const(` ~ realTranslation ~ `)` 405 : realTranslation; 406 } 407 408 bool hasAnonymousSpelling(in from!"clang".Type type) @safe pure nothrow { 409 import std.algorithm: canFind; 410 return type.spelling.canFind("(anonymous"); 411 } 412 413 414 bool isTypeParameter(in from!"clang".Type type) @safe pure nothrow { 415 import std.algorithm: canFind; 416 // See contract.typedef_.typedef to a template type parameter 417 return type.spelling.canFind("type-parameter-"); 418 } 419 420 /** 421 libclang doesn't offer a lot of functionality when it comes to extracting 422 template arguments from structs - this enum is the best we can do. 423 */ 424 enum TemplateArgumentKind { 425 GenericType, 426 SpecialisedType, 427 Value, // could be specialised or not 428 } 429 430 // type template arguments may be: 431 // Invalid - value (could be specialised or not) 432 // Unexposed - non-specialised type or 433 // anything else - specialised type 434 // The trick is figuring out if a value is specialised or not 435 TemplateArgumentKind templateArgumentKind(in from!"clang".Type type) @safe pure nothrow { 436 import clang: Type; 437 if(type.kind == Type.Kind.Invalid) return TemplateArgumentKind.Value; 438 if(type.kind == Type.Kind.Unexposed) return TemplateArgumentKind.GenericType; 439 return TemplateArgumentKind.SpecialisedType; 440 } 441 442 // e.g. `template<> struct foo<false, true, int32_t>` -> 0: false, 1: true, 2: int 443 string translateTemplateParamSpecialisation( 444 in from!"clang".Type templateType, 445 in int index, 446 ref from!"dpp.runtime.context".Context context) @safe pure 447 { 448 return translateTemplateParamSpecialisation(templateType, templateType, index, context); 449 } 450 451 452 // e.g. `template<> struct foo<false, true, int32_t>` -> 0: false, 1: true, 2: int 453 string translateTemplateParamSpecialisation( 454 in from!"clang".Type cursorType, 455 in from!"clang".Type type, 456 in int index, 457 ref from!"dpp.runtime.context".Context context) @safe pure 458 { 459 import clang: Type; 460 return type.kind == Type.Kind.Invalid 461 ? templateParameterSpelling(cursorType, index) 462 : translate(type, context); 463 } 464 465 466 // returns the indexth template parameter value from a specialised 467 // template struct/class cursor (full or partial) 468 // e.g. template<> struct Foo<int, 42, double> -> 1: 42 469 string templateParameterSpelling(in from!"clang".Type cursorType, int index) @safe pure { 470 import std.algorithm: findSkip, until, OpenRight; 471 import std.array: empty, save, split, array; 472 import std.conv: text; 473 474 auto spelling = cursorType.spelling.dup; 475 if(!spelling.findSkip("<")) return ""; 476 477 auto templateParams = spelling.until(">", OpenRight.yes).array.split(", "); 478 479 return templateParams[index].text; 480 }