diff --git a/README.md b/README.md index 0f17e11d0..2c9a0f43c 100644 --- a/README.md +++ b/README.md @@ -682,7 +682,7 @@ declare function add(a: s32, b: s32): s32; declare function dist(p: Reference | null): f64; ``` -Structs, enums and `extern "C"` functions are emitted, and polymorphic classes (below). A struct or a class without virtual methods, in any namespace or class, is a tuple when its fields are where a tuple's are: constructors, methods and member initializers don't move them, so `struct Color { float r, g, b, a; Color(); }` is `type Color = [r: f32, g: f32, b: f32, a: f32];`. It is declared under its own name, and `--filter` matches that or the qualified name (`--filter 'nvrhi::*'`); of two of one name in different namespaces, the next is named with its namespaces (`rt_Desc` for `nvrhi::rt::Desc` under `--namespace nvrhi`). A derived struct has its bases' fields first, as C++ lays them out (an empty base has none; MSVC's tail padding after a base is a `_pad` field). A field holding a template's specialization by value declares the specialization, named by its arguments, so a library's types are laid out as that library has them: `std::string` is `basic_string_char` (MSVC's `[_Mypair: ...]` down to its `_Bx` buffer words, size and capacity; libc++'s `[__rep_: [_0: u64, _1: u64, _2: u64]]`), `nvrhi::static_vector` is `static_vector_IBindingSet_8` (its private `std::array`'s elements, then `current_size`), `RefCountPtr` is `RefCountPtr_IShader = [ptr_: IShader | null]`. Their member names are the C++ library's own, which differ between MSVC's and libc++; a pointer to a specialization alone stays `Opaque`. One that isn't a tuple is `type Name = Opaque;`, with the reason in a `// skipped:` line: a polymorphic class, a virtual base, a packed or over-aligned layout, or a field tsbindgen can't spell (`long double`, a huge array). Namespace functions (`lib::helper`) and functions with C++ linkage (`cxx_only`) are left out, without a warning. To bind those, expose them through `extern "C"` wrapper functions (with an opaque handle for each class) and run tsbindgen on the header that declares them. +Structs, enums and `extern "C"` functions are emitted, and polymorphic classes (below). A struct or a class without virtual methods, in any namespace or class, is a tuple when its fields are where a tuple's are: constructors, methods and member initializers don't move them, so `struct Color { float r, g, b, a; Color(); }` is `type Color = [r: f32, g: f32, b: f32, a: f32];`. It is declared under its own name, and `--filter` matches that or the qualified name (`--filter 'nvrhi::*'`); of two of one name in different namespaces, the next is named with its namespaces (`rt_Desc` for `nvrhi::rt::Desc` under `--namespace nvrhi`). A derived struct has its bases' fields first, as C++ lays them out (an empty base has none; MSVC's tail padding after a base is a `_pad` field). A field holding a template's specialization by value is laid out as the library has it, and written once for the template as a generic tuple, its type arguments the parameters and its integer arguments part of the name: `std::vector` is `vector`, with `type vector = [_Mypair: [_Myval2: [_Myfirst: Reference | null, ...]]]` under MSVC (the specializations its pointers are in, `_Compressed_pair` and `_Vector_val`, inlined) and `[__begin_: Reference | null, ...]` under libc++; `nvrhi::static_vector` is `static_vector8` (its private `std::array`'s elements, then `current_size`); `RefCountPtr` is `RefCountPtr`, with `type RefCountPtr = [ptr_: T | null]`. A specialization whose layout doesn't show its type argument, or that is laid out unlike its template's others (an explicit specialization), is declared on its own, named by its arguments: `std::string` is `basic_string_char` (MSVC's `[_Mypair: ...]` down to its `_Bx` buffer words, size and capacity; libc++'s `[__rep_: [_0: u64, _1: u64, _2: u64]]`). Their member names are the C++ library's own, which differ between MSVC's and libc++; a pointer to a specialization alone stays `Opaque`. One that isn't a tuple is `type Name = Opaque;`, with the reason in a `// skipped:` line: a polymorphic class, a virtual base, a packed or over-aligned layout, or a field tsbindgen can't spell (`long double`, a huge array). Namespace functions (`lib::helper`) and functions with C++ linkage (`cxx_only`) are left out, without a warning. To bind those, expose them through `extern "C"` wrapper functions (with an opaque handle for each class) and run tsbindgen on the header that declares them. #### C++ classes diff --git a/TSLANG_ISSUES.md b/TSLANG_ISSUES.md index 5c7f9c3c6..d0bb669f3 100644 --- a/TSLANG_ISSUES.md +++ b/TSLANG_ISSUES.md @@ -18,6 +18,7 @@ https://github.com/ASDAlexander77/native_ts_graphics | 7 | `Opaque` compared with `Opaque \| null` crashed the compiler | - | **fixed** by #536 (re-checked with the original drawScene shape) | | 8 | A ternary of number literals in arithmetic truncated to the other operand's integer type | - | **fixed** by #560 (still wrong on pre-alpha97) | | 9-12 | Bool const arrays, `Math.max`, `f32` array literal, `let x = 0` | - | fixed (#537, DefaultLib#31, works, #544 warning) | +| 13 | A generic function's `T` isn't inferred from an argument of a generic tuple alias (`Vec`) | - | open (pre-alpha100) | Verified downstream: native_ts_graphics dropped its 71 forwarding constructors (PR #49) and rebuilt everything with pre-alpha97 (all objects deleted first): Windows build clean, CTest 61/61, Android @@ -114,6 +115,31 @@ to TypeScript later; the project now returns pointers to storage that lives as l Decide whether this is by design; if so, the C interop docs should say the string must outlive its use, otherwise copying on return (or an explicit borrowed annotation) would be safer. Not re-checked. +### 13. `T` not inferred from a generic tuple alias argument + +Found on pre-alpha100, writing `stdVectorSize(items: vector)` for native_ts_graphics' NVRHI +bindings (`vector` a generic tuple alias, as tsbindgen now writes C++ template specializations). +A call with a `Vec` argument doesn't infer `T`; giving it explicitly works: + +```ts +type Vec = [first: Ref | null, last: Ref | null]; +type Item = [id: s32, size: s32]; + +function count(v: Vec): int { + return ((v.last - v.first) / sizeof()); +} + +function main() { + let items: Item[] = [[1, 2], [3, 4]]; + const v: Vec = [Ref(items[0]), >(Ref(items[0]) + 2 * sizeof())]; + print(count(v)); // 2 + print(count(v)); // error: not all types could be inferred +} +``` + +TypeScript infers `T = Item` here (the alias is expanded and matched structurally). native_ts_graphics +passes the type (`nvrhi.stdVectorSize(...)`) until it is. + --- ## 8. A union of literals in arithmetic (found live on pre-alpha97) diff --git a/tslang/include/TsClangImporter/BindingModel.h b/tslang/include/TsClangImporter/BindingModel.h index 25a61bd14..b5d86b066 100644 --- a/tslang/include/TsClangImporter/BindingModel.h +++ b/tslang/include/TsClangImporter/BindingModel.h @@ -23,6 +23,7 @@ struct TsType Kind kind = Kind::Builtin; std::string name; // Builtin: the tslang spelling; Named: the key of a Decl std::vector pointee; // Reference: exactly one element; Tuple: the field types + std::vector typeArguments; // Named: a generic's arguments, `vector` std::vector fieldNames; // Tuple std::string comment; // shown next to the type: a function pointer's C signature, ... bool functionPointer = false; @@ -44,6 +45,9 @@ struct TsType // Spells `type`, asking `nameOf` for the TS name of each named declaration it refers to. std::string renderType(const TsType &type, const std::function &nameOf); +// Whether two types are spelled alike (keys compared, not TS names) +bool sameType(const TsType &a, const TsType &b); + // The declaration keys `type` refers to, in order, each once. void collectKeys(const TsType &type, std::vector &keys); @@ -73,7 +77,7 @@ enum class DeclKind { Macro, // const N = 42; OpaqueStruct, // type S = Opaque; - Struct, // type S = [a: T1, b: T2]; (type S = Opaque; when skipped) + Struct, // type S = [a: T1, b: T2]; (type S = Opaque; when skipped); type V = [...] for a generic Enum, // enum E { A = 0 } FixedEnum, // type E = u8; const A = 1; Typedef, // type T = ...; @@ -102,9 +106,11 @@ struct Decl std::string symbol; // Function: the symbol it links as, when an asm label makes it differ from the name bool inMainFile = false; bool inSystemHeader = false; + bool instantiation = false; // a template's specialization, or a class inside one: only declared for its users std::string skipReason; // non-empty: left out, with a `// skipped:` line in its place std::vector fields; // Struct, CppClass: fields; Function: parameters + std::vector typeParameters; // Struct: a generic's, `type vector = [...]` TsType type; // Function: result; Typedef: aliased type; FixedEnum: underlying bool varargs = false; // Function std::vector enumerators; // Enum, FixedEnum diff --git a/tslang/include/TsClangImporter/TypeMapper.h b/tslang/include/TsClangImporter/TypeMapper.h index 2b9538523..27b93cdfb 100644 --- a/tslang/include/TsClangImporter/TypeMapper.h +++ b/tslang/include/TsClangImporter/TypeMapper.h @@ -12,6 +12,7 @@ class Sema; } #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringSet.h" #include @@ -68,11 +69,14 @@ class TypeMapper // Whether a struct can have a TS declaration of its own: in C, any; in C++, one in a namespace or // a class too (declared under its own name), but not a template, nor a class local to a - // function. A template's specialization (std::string, nvrhi::static_vector) or a class - // inside one is declared once a field holds one by value, whose layout needs it; a pointer to - // one alone stays Opaque. + // function. A template's specialization (std::string) or a class inside one is declared once a + // field holds one by value, whose layout needs it, unless it is a generic's instance + // (nvrhi::static_vector: genericInstance); a pointer to one alone stays Opaque. bool isDeclarable(const clang::RecordDecl *record); + // a template's specialization, or a class inside one, complete: declarable when a field holds it + bool isInstantiated(const clang::RecordDecl *record); + // Why a C++ class can't be a @cpp class ("" if it can): it has to be polymorphic, not a // template, on a 64-bit target, with at most one base, itself bindable and not virtual - the // layout a @cpp class has (the vtable pointer first, a base object the prefix of a derived one). @@ -93,6 +97,24 @@ class TypeMapper sema = value; } + // A template written once as a generic tuple, `type static_vector8 = [...]`: a field holding + // one of its specializations is an instance of it, `static_vector8`. + struct Generic + { + std::string key; // "generic:nvrhi::static_vector<8>": the template, its non-type arguments + std::string name; // `static_vector8`: the template's name, then its non-type arguments + std::string qualifiedName; // `nvrhi::static_vector` + std::vector parameters; + std::vector fields; // in terms of the parameters + const clang::Decl *at; // the template, where its declaration is from + }; + + // The generics the specializations mapped so far are instances of, in the order they were met. + const std::vector &generics() const + { + return genericList; + } + // Every struct and enum a mapped type has named, by key. The AST traversal does not reach a // struct first named inside a prototype (`void f(struct S *s);`), so the parser adds these. const std::vector> &referencedTags() const @@ -123,8 +145,20 @@ class TypeMapper Slot unionSlot(std::string name, const clang::RecordDecl *unionRecord, uint64_t offset); // `[_0: u64, _1: u64, ...]`: `bytes` as words of the largest size up to `align` that fits static TsType rawWords(uint64_t bytes, uint64_t align); - // a template's specialization, or a class inside one, complete: declarable when a field holds it - bool isInstantiated(const clang::RecordDecl *record); + // A specialization a field holds as an instance of a generic: its template's type arguments are + // its parameters, wherever they are in its layout (a nested specialization holding one inlined: + // MSVC's std::vector, through _Compressed_pair and _Vector_val). A skip when it can't be one: a + // parameter its layout doesn't show (the raw words of an std::string's characters), an argument + // that isn't a type or an integer, or a layout other than its template's other specializations'. + TsType genericInstance(const clang::RecordDecl *definition); + // what a type argument is in a layout: a @cpp class itself (pointers to it are the class), any + // other type as a field of it is + TsType argumentType(clang::QualType argument); + // `type` with each part that is `arguments[i]` replaced by `parameters[i]` (`used[i]` set), a + // specialization holding one inlined + TsType abstractType(const TsType &type, const std::vector &arguments, const std::vector ¶meters, + std::vector &used, int depth); + const clang::RecordDecl *recordOfKey(const std::string &key); TsType mapType(clang::QualType type, Use use); std::string referTo(const clang::TagDecl *tag); @@ -143,6 +177,10 @@ class TypeMapper llvm::StringSet<> referencedKeys; std::vector owners; // structs whose fields are being mapped llvm::SmallPtrSet heldByValue; // instantiated records a field holds + std::vector genericList; + llvm::StringMap genericIndex; // a generic's key -> genericList + llvm::DenseMap instances; // a specialization -> its instance, or a skip + llvm::SmallPtrSet generalizing; // specializations being made instances }; } // namespace tsbindgen diff --git a/tslang/lib/TsClangImporter/BindingModel.cpp b/tslang/lib/TsClangImporter/BindingModel.cpp index 9c41f9b71..8881bd531 100644 --- a/tslang/lib/TsClangImporter/BindingModel.cpp +++ b/tslang/lib/TsClangImporter/BindingModel.cpp @@ -59,8 +59,21 @@ std::string renderType(const TsType &type, const std::function"; + } + + return text; + } case TsType::Kind::Reference: return "Reference<" + renderType(type.pointee.front(), nameOf) + ">"; case TsType::Kind::Tuple: { @@ -77,6 +90,16 @@ std::string renderType(const TsType &type, const std::function &x, const std::vector &y) { + return x.size() == y.size() && std::equal(x.begin(), x.end(), y.begin(), sameType); + }; + + return a.kind == b.kind && a.name == b.name && a.nullable == b.nullable && a.fieldNames == b.fieldNames && + a.skipReason == b.skipReason && same(a.pointee, b.pointee) && same(a.typeArguments, b.typeArguments); +} + void collectKeys(const TsType &type, std::vector &keys) { if (type.kind == TsType::Kind::Named && std::find(keys.begin(), keys.end(), type.name) == keys.end()) @@ -88,6 +111,11 @@ void collectKeys(const TsType &type, std::vector &keys) { collectKeys(pointee, keys); } + + for (auto &argument : type.typeArguments) + { + collectKeys(argument, keys); + } } std::string nameFromKey(const std::string &key) @@ -129,7 +157,7 @@ bool isTsBuiltinTypeName(const std::string &name) "i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64", "s8", "s16", "s32", "s64", "f16", "f32", "f64", "f128", "half", "float", "double", "boolean", "number", "string", "bigint", "any", "void", "never", "unknown", - "object", "symbol", "undefined", "null", "Opaque", "Reference", "TypeOf", "Array", "String", + "object", "symbol", "undefined", "null", "Opaque", "Ref", "Reference", "TypeOf", "Array", "String", "Number", "Boolean", "Object", "Function"}; for (auto *builtin : builtins) diff --git a/tslang/lib/TsClangImporter/BindingPrinter.cpp b/tslang/lib/TsClangImporter/BindingPrinter.cpp index c3acead49..38bf17694 100644 --- a/tslang/lib/TsClangImporter/BindingPrinter.cpp +++ b/tslang/lib/TsClangImporter/BindingPrinter.cpp @@ -12,7 +12,8 @@ namespace tsbindgen namespace { -// The selected declarations: every seed, then whatever a non-skipped selected declaration uses. +// The selected declarations: every seed, then whatever a non-skipped selected declaration uses. A +// specialization is no seed: it is declared for what holds it (or inlined in a generic). std::vector selectDecls(const HeaderModel &model, const std::vector &globs) { llvm::StringMap byKey; @@ -34,9 +35,9 @@ std::vector selectDecls(const HeaderModel &model, const std::vector printBindings(const HeaderModel &model, const PrintO fields += (fields.empty() ? "" : ", ") + withComment(field); } - line(exported + "type " + tsName + " = [" + fields + "];"); + std::string parameters; + for (auto ¶meter : decl.typeParameters) + { + parameters += (parameters.empty() ? "<" : ", ") + parameter; + } + + line(exported + "type " + tsName + (parameters.empty() ? "" : parameters + ">") + " = [" + fields + "];"); break; } case DeclKind::Enum: { diff --git a/tslang/lib/TsClangImporter/HeaderParser.cpp b/tslang/lib/TsClangImporter/HeaderParser.cpp index d93bc76ff..c6e3b95c6 100644 --- a/tslang/lib/TsClangImporter/HeaderParser.cpp +++ b/tslang/lib/TsClangImporter/HeaderParser.cpp @@ -269,6 +269,7 @@ class Collector : public clang::RecursiveASTVisitor // in a namespace or a class is declared under its own name; --filter matches either. pendingRecords.push_back({model.decls.size(), record}); auto &decl = add(DeclKind::OpaqueStruct, key, TypeMapper::tagName(record), record); + decl.instantiation = mapper.isInstantiated(record); auto qualified = TypeMapper::qualifiedTagName(record); if (qualified != decl.name) { @@ -459,6 +460,18 @@ class Collector : public clang::RecursiveASTVisitor } } + // the generic tuples the specializations mapped so far are instances of + for (auto &generic : mapper.generics()) + { + if (keys.insert(generic.key).second) + { + auto &decl = add(DeclKind::Struct, generic.key, generic.name, generic.at); + decl.qualifiedName = generic.qualifiedName; + decl.typeParameters = generic.parameters; + decl.fields = generic.fields; + } + } + if (completed == pendingRecords.size()) { break; diff --git a/tslang/lib/TsClangImporter/TypeMapper.cpp b/tslang/lib/TsClangImporter/TypeMapper.cpp index b25f8ea80..7bdae0c50 100644 --- a/tslang/lib/TsClangImporter/TypeMapper.cpp +++ b/tslang/lib/TsClangImporter/TypeMapper.cpp @@ -476,6 +476,214 @@ bool TypeMapper::isSystem(const clang::Decl *decl) return sourceManager.isInSystemHeader(sourceManager.getExpansionLoc(decl->getLocation())); } +TsType TypeMapper::argumentType(clang::QualType argument) +{ + if (auto *cxxRecord = argument.getCanonicalType()->getAsCXXRecordDecl(); + cxxRecord && cxxRecord->getDefinition() && classSkipReason(cxxRecord).empty()) + { + return TsType::named(referTo(cxxRecord->getDefinition())); + } + + return map(argument, Use::Field); +} + +const clang::RecordDecl *TypeMapper::recordOfKey(const std::string &key) +{ + for (auto &[referencedKey, tag] : referenced) + { + if (referencedKey == key) + { + return llvm::dyn_cast(tag); + } + } + + return nullptr; +} + +TsType TypeMapper::abstractType(const TsType &type, const std::vector &arguments, + const std::vector ¶meters, std::vector &used, int depth) +{ + // an argument, nullable or not: `T`; `T | null` where the argument itself isn't nullable (a + // RefCountPtr's `IShader *`) + for (size_t index = 0; index < arguments.size(); ++index) + { + auto nonNull = type; + nonNull.nullable = arguments[index].nullable; + if (sameType(nonNull, arguments[index])) + { + used[index] = true; + auto parameter = TsType::builtin(parameters[index]); + parameter.nullable = type.nullable && !arguments[index].nullable; + return parameter; + } + } + + auto abstracted = type; + for (auto &pointee : abstracted.pointee) + { + pointee = abstractType(pointee, arguments, parameters, used, depth); + } + + for (auto &argument : abstracted.typeArguments) + { + argument = abstractType(argument, arguments, parameters, used, depth); + } + + // a specialization holding an argument (MSVC's _Compressed_pair, _Vector_val<...>>): + // its fields inline, as a tuple, as it has no name of the parameters + if (type.kind == TsType::Kind::Named && type.typeArguments.empty() && depth < 16) + { + auto *record = recordOfKey(type.name); + if (record && isInstantiated(record) && recordSkipReason(record->getDefinition()).empty()) + { + std::vector inner(arguments.size(), false); + std::vector names; + std::vector types; + for (auto &field : mapFields(record->getDefinition())) + { + names.push_back(field.name); + types.push_back(abstractType(field.type, arguments, parameters, inner, depth + 1)); + } + + if (llvm::is_contained(inner, true)) + { + for (size_t index = 0; index < used.size(); ++index) + { + used[index] = used[index] || inner[index]; + } + + auto tuple = TsType::tuple(std::move(names), std::move(types)); + tuple.nullable = type.nullable; + return tuple; + } + } + } + + return abstracted; +} + +TsType TypeMapper::genericInstance(const clang::RecordDecl *definition) +{ + auto *specialization = llvm::dyn_cast(definition); + if (!specialization) + { + return TsType::skip("not a template's specialization"); + } + + auto found = instances.find(definition); + if (found != instances.end()) + { + return found->second; + } + + // a specialization reached again through its own arguments' layouts + if (!generalizing.insert(definition).second) + { + return TsType::skip("a specialization holding itself"); + } + + auto instance = [&]() -> TsType { + auto *classTemplate = specialization->getSpecializedTemplate(); + auto *templateParameters = classTemplate->getTemplateParameters(); + auto templateArguments = specialization->getTemplateArgs().asArray(); + + // the arguments given (not a defaulted one, std::vector's allocator): the types become the + // parameters, the integers part of the name (static_vector: `static_vector8`) + std::vector arguments; + std::string name = classTemplate->getName().str(); + std::string values; + for (size_t index = 0; index < templateArguments.size(); ++index) + { + auto &argument = templateArguments[index]; + auto *parameter = templateParameters->getParam(std::min(index, templateParameters->size() - 1)); + if (clang::isSubstitutedDefaultArgument(context, argument, parameter, templateArguments, + templateParameters->getDepth())) + { + continue; + } + + if (argument.getKind() == clang::TemplateArgument::Type) + { + auto type = argumentType(argument.getAsType()); + if (type.skipped()) + { + return TsType::skip("type argument: " + type.skipReason); + } + + arguments.push_back(type); + } + else if (argument.getKind() == clang::TemplateArgument::Integral) + { + auto value = llvm::toString(argument.getAsIntegral(), 10); + name += (std::isdigit(static_cast(name.back())) ? "_" : "") + value; + values += (values.empty() ? "" : ", ") + value; + } + else + { + return TsType::skip("a template argument that is neither a type nor an integer"); + } + } + + if (arguments.empty()) + { + return TsType::skip("no type argument"); + } + + auto reason = recordSkipReason(definition); + if (!reason.empty()) + { + return TsType::skip(reason); + } + + std::vector parameters; + for (size_t index = 0; index < arguments.size(); ++index) + { + parameters.push_back(arguments.size() == 1 ? "T" : "T" + std::to_string(index + 1)); + } + + std::vector used(arguments.size(), false); + auto fields = mapFields(definition); + for (auto &field : fields) + { + field.type = abstractType(field.type, arguments, parameters, used, 0); + } + + if (llvm::is_contained(used, false)) + { + return TsType::skip("a type argument its layout doesn't show"); + } + + auto qualified = classTemplate->getQualifiedNameAsString(); + Generic generic{"generic:" + qualified + "<" + values + ">", name, qualified, parameters, fields, classTemplate}; + auto known = genericIndex.find(generic.key); + if (known == genericIndex.end()) + { + genericIndex[generic.key] = genericList.size(); + genericList.push_back(generic); + } + else + { + // another specialization's layout, in terms of the parameters, has to be this one's + auto &other = genericList[known->second]; + auto same = other.fields.size() == fields.size() && + std::equal(fields.begin(), fields.end(), other.fields.begin(), + [](const Field &a, const Field &b) { return a.name == b.name && sameType(a.type, b.type); }); + if (!same) + { + return TsType::skip("laid out unlike " + other.name + "'s other specializations"); + } + } + + auto type = TsType::named(generic.key); + type.typeArguments = arguments; + return type; + }(); + + generalizing.erase(definition); + instances[definition] = instance; + return instance; +} + TsType TypeMapper::map(clang::QualType type, Use use) { auto mapped = mapType(type, use); @@ -583,9 +791,16 @@ TsType TypeMapper::mapType(clang::QualType type, Use use) } // a field holding a specialization (std::string, nvrhi::static_vector) needs its layout: - // it is declared, under a name of its arguments + // an instance of its template's generic tuple (static_vector8), or, when + // it can't be one, declared under a name of its arguments (basic_string_char) if (use == Use::Field && isInstantiated(record)) { + auto instance = genericInstance(record->getDefinition()); + if (!instance.skipped()) + { + return instance; + } + heldByValue.insert(record->getDefinition()); } diff --git a/tslang/unittests/TsClangImporter/LayoutTest.cpp b/tslang/unittests/TsClangImporter/LayoutTest.cpp index 2e1ce1cdd..9a75278c4 100644 --- a/tslang/unittests/TsClangImporter/LayoutTest.cpp +++ b/tslang/unittests/TsClangImporter/LayoutTest.cpp @@ -49,8 +49,9 @@ TEST(Layout, Unions) EXPECT_THAT(out, HasSubstr("type Pt = [x: s32, y: s32, z: s32];")); } -// A specialization a field holds is declared under a name of its arguments; its bases' fields come -// first, an empty base has none. A pointer to one alone stays Opaque. +// A specialization a field holds is an instance of its template's generic tuple, its integer +// arguments in the name; its bases' fields come first, an empty base has none. A template named as +// one of tslang's types (Ref) gets a `_`. A pointer to a specialization alone stays Opaque. TEST(Layout, SpecializationHeldByValue) { const char *header = "#include \n" @@ -69,17 +70,57 @@ TEST(Layout, SpecializationHeldByValue) for (auto *target : {windowsTarget, linuxTarget}) { auto out = cpp(header, target); - EXPECT_THAT(out, HasSubstr("type State = [owner: Ref_Item, items: static_vector_Item_2];")) << target; - EXPECT_THAT(out, HasSubstr("type Ref_Item = [ptr_: Item | null];")) << target; - EXPECT_THAT(out, HasSubstr("type static_vector_Item_2 = [elems: [_0: Item | null, _1: Item | null], current_size: index];")) - << target; + EXPECT_THAT(out, HasSubstr("type State = [owner: Ref_, items: static_vector2];")) << target; + EXPECT_THAT(out, HasSubstr("type Ref_ = [ptr_: T | null];")) << target; + EXPECT_THAT(out, HasSubstr("type static_vector2 = [elems: [_0: T, _1: T], current_size: index];")) << target; EXPECT_THAT(out, HasSubstr("use(state: Reference | null, other: Opaque | null /* lib::Other */)")) << target; EXPECT_THAT(out, Not(HasSubstr("type Other"))) << target; } } +// An std::vector shaped as MSVC's: its pointers two specializations down (_Compressed_pair, whose +// allocator base is empty, then _Vector_val of _Simple_types), which are inlined in its generic +// tuple: one for every element type, the helpers declared nowhere +TEST(Layout, MsvcShapedVectorIsOneGeneric) +{ + auto out = cpp("namespace stdx {\n" + "template struct allocator {};\n" + "template struct simple_types { using pointer = T *; };\n" + "template struct vector_val { typename V::pointer first, last, end; };\n" + "template struct compressed_pair : private A { V value; };\n" + "template > class vector {\n" + " compressed_pair>> pair; };\n" + "}\n" + "struct Item { int id; };\n" + "struct Desc { stdx::vector items; stdx::vector ids; };\n" + "extern \"C\" void use(const Desc *desc);\n"); + EXPECT_THAT(out, HasSubstr("type Desc = [items: vector, ids: vector];")); + EXPECT_THAT(out, HasSubstr("type vector = [pair: [value: [first: Reference | null, last: Reference | null, " + "end: Reference | null]]];")); + EXPECT_THAT(out, Not(HasSubstr("vector_val"))); + EXPECT_THAT(out, Not(HasSubstr("compressed_pair"))); +} + +// A specialization laid out unlike its template's others (an explicit specialization), or whose +// layout doesn't show its type argument, keeps a name of its arguments +TEST(Layout, SpecializationThatIsNoInstance) +{ + auto out = cpp("namespace lib {\n" + "template struct Box { T value; };\n" + "template <> struct Box { double value; int extra; };\n" + "template struct Tag { int id; };\n" + "}\n" + "struct Desc { lib::Box a; lib::Box b; lib::Tag c; };\n" + "extern \"C\" void use(const Desc *desc);\n"); + EXPECT_THAT(out, HasSubstr("type Desc = [a: Box, b: Box_double, c: Tag_float];")); + EXPECT_THAT(out, HasSubstr("type Box = [value: T];")); + EXPECT_THAT(out, HasSubstr("type Box_double = [value: f64, extra: s32];")); + EXPECT_THAT(out, HasSubstr("type Tag_float = [id: s32];")); +} + // An std::string shaped as MSVC's: an empty allocator base (no storage), its value in a member -// whose union holds a char buffer or a pointer +// whose union holds a char buffer or a pointer. Its characters are raw words, which don't show its +// argument: no generic, a name of its arguments. TEST(Layout, MsvcShapedString) { auto out = cpp("#include \n" @@ -94,6 +135,7 @@ TEST(Layout, MsvcShapedString) EXPECT_THAT(out, HasSubstr("type basic_string_char = [pair: compressed_pair_allocator_char_string_val_char];")); EXPECT_THAT(out, HasSubstr("type compressed_pair_allocator_char_string_val_char = [value: string_val_char];")); EXPECT_THAT(out, HasSubstr("type string_val_char = [bx: [_0: u64, _1: u64], size: index, res: index];")); + EXPECT_THAT(out, Not(HasSubstr("basic_string<"))); } // An std::string shaped as libc++'s: its representation a union of structs with bitfields (raw diff --git a/tslang/unittests/TsClangImporter/NamespacedStructTest.cpp b/tslang/unittests/TsClangImporter/NamespacedStructTest.cpp index 6b49986bb..65ffdfa94 100644 --- a/tslang/unittests/TsClangImporter/NamespacedStructTest.cpp +++ b/tslang/unittests/TsClangImporter/NamespacedStructTest.cpp @@ -87,8 +87,8 @@ TEST(NamespacedStruct, NestedInAClass) EXPECT_THAT(out, HasSubstr("declare function use(i: Reference | null, o: Reference | null): s32;")); } -// A derived struct has its base's fields first; a template's specialization a field holds is -// declared under a name of its arguments (LayoutTest.cpp) +// A derived struct has its base's fields first; a template's specialization a field holds is an +// instance of the template's generic tuple (LayoutTest.cpp) TEST(NamespacedStruct, DerivedAndSpecializationFields) { auto out = cpp("template struct Vec { T *data; };\n" @@ -99,8 +99,8 @@ TEST(NamespacedStruct, DerivedAndSpecializationFields) "}\n" "extern \"C\" void use(n::Derived *d, n::Holder *h);\n"); EXPECT_THAT(out, HasSubstr("type Derived = [a: s32, b: s32];")); - EXPECT_THAT(out, HasSubstr("type Holder = [values: Vec_int];")); - EXPECT_THAT(out, HasSubstr("type Vec_int = [data: Reference | null];")); + EXPECT_THAT(out, HasSubstr("type Holder = [values: Vec];")); + EXPECT_THAT(out, HasSubstr("type Vec = [data: Reference | null];")); EXPECT_THAT(out, HasSubstr("declare function use(d: Reference | null, h: Reference | null): void;")); }