From a9730b75c592e990755676984aff969d8f44a1e0 Mon Sep 17 00:00:00 2001 From: "Marc J. Schmidt" Date: Mon, 13 Jun 2022 01:28:46 +0200 Subject: [PATCH] very basic index access, tuple, rest, template literal --- main.cpp | 6 +- src/CMakeLists.txt | 2 +- src/checker/checks.h | 57 ++-- src/checker/compiler.h | 85 ++++- src/checker/instructions.h | 26 +- src/checker/type_objects.h | 124 ++++++- src/checker/utils.h | 2 + src/checker/vm.cpp | 6 + src/checker/vm.h | 646 +++++++++++++++++++++++++++++++------ src/factory.cpp | 2 +- src/factory.h | 2 +- src/parenthesizer.h | 25 +- src/tests/test_checker.cpp | 28 +- src/tests/test_core.cpp | 12 + src/types.h | 14 +- src/utilities.cpp | 2 +- src/utilities.h | 2 +- tests/generic2.ts | 2 + 18 files changed, 884 insertions(+), 159 deletions(-) create mode 100644 src/checker/vm.cpp create mode 100644 tests/generic2.ts diff --git a/main.cpp b/main.cpp index 6fd7f41..b82c506 100644 --- a/main.cpp +++ b/main.cpp @@ -34,7 +34,7 @@ bool exists(const string &file) int main(int argc, char *argv[]) { // std::string cwd = ""; // if (argc > 0) cwd = argv[0]; - std::string file = "/Users/marc/bude/typescript-cpp/tests/basic1.ts"; + std::string file = "/Users/marc/bude/typescript-cpp/tests/generic2.ts"; if (argc > 1) file = argv[1]; // cwd.append("/"); @@ -42,9 +42,10 @@ int main(int argc, char *argv[]) { auto bytecode = file + ".tsbytecode"; if (exists(bytecode)) { - auto buffer = readFile(file); + auto buffer = readFile(bytecode); vm::VM vm; vm.call(buffer); + vm.printErrors(); } else { auto buffer = readFile(file); checker::Compiler compiler; @@ -56,6 +57,7 @@ int main(int argc, char *argv[]) { writeFile(bytecode, bin); vm::VM vm; vm.call(bin); + vm.printErrors(); } return 0; } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3757398..30603ba 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -11,7 +11,7 @@ add_subdirectory(tests) add_library(typescript utf.h utf.cpp core.h core.cpp utilities.h utilities.cpp node_test.h node_test.cpp syntax_cursor.h syntax_cursor.cpp parser2.h parser2.cpp types.h types.cpp path.h path.cpp factory.h factory.cpp parenthesizer.h parenthesizer.cpp scanner.h scanner.cpp - checker/vm.h checker/instructions.h checker/compiler.h checker/type_objects.h checker/utils.h checker/checks.h) + checker/vm.h checker/vm.cpp checker/instructions.h checker/compiler.h checker/type_objects.h checker/utils.h checker/checks.h) # ${CMAKE_CURRENT_SOURCE_DIR}/../libs/tracy/TracyClient.cpp target_link_libraries(typescript fmt) \ No newline at end of file diff --git a/src/checker/checks.h b/src/checker/checks.h index 3851a93..2f16c5d 100644 --- a/src/checker/checks.h +++ b/src/checker/checks.h @@ -5,41 +5,56 @@ namespace ts::vm { /** - * Checks whether two types are assignable. + * Checks whether left extends right. * - * `const value: left = right;` + * `left extends right ? true : false` */ - bool isAssignable(shared left, shared right) { - if (left->kind == TypeKind::String) { - if (right->kind == TypeKind::String) return true; - if (right->kind == TypeKind::Literal) return to(right)->type == TypeLiteralType::String; + bool isExtendable(shared &left, shared &right) { + if (right->kind == TypeKind::String) { + if (left->kind == TypeKind::String) return true; + if (left->kind == TypeKind::Literal) return to(left)->type == TypeLiteralType::String; } - if (left->kind == TypeKind::Number) { - if (right->kind == TypeKind::Number) return true; - if (right->kind == TypeKind::Literal) return to(right)->type == TypeLiteralType::Number; + if (right->kind == TypeKind::Number) { + if (left->kind == TypeKind::Number) return true; + if (left->kind == TypeKind::Literal) return to(left)->type == TypeLiteralType::Number; } - if (left->kind == TypeKind::Literal && right->kind == TypeKind::Literal) { - return to(left)->type == to(right)->type && - to(left)->literal == to(right)->literal; + if (left->kind == TypeKind::Literal && left->kind == TypeKind::Literal) { + return to(left)->type == to(left)->type && + to(right)->text() == to(left)->text(); } - if (left->kind == TypeKind::Union) { - if (right->kind != TypeKind::Union) { - for (auto &&l: to(left)->types) { - if (isAssignable(l, right)) return true; + if (right->kind == TypeKind::Tuple && left->kind == TypeKind::Tuple) { + auto &rtypes = to(right)->types; + auto <ypes = to(left)->types; + for (auto i = 0; i < rtypes.size(); i++) { +// auto rightType = indexAccess(left, { kind: TypeKind::Literal, literal: i }); +// auto leftType = indexAccess(right, { kind: TypeKind::Literal, literal: i }); +// if (rightType->kind == TypeKind::Infer || leftType->kind == TypeKind::Infer) continue; +// auto valid = isExtendable(leftType, rightType, extendStack); +// if (!valid) return false; + } +// inferFromTuple(right, left); + +// return true; + } + + if (right->kind == TypeKind::Union) { + if (left->kind != TypeKind::Union) { + for (auto &&l: to(right)->types) { + if (isExtendable(l, left)) return true; } return false; } else { //e.g.: string|number = string|boolean - const auto leftTypes = to(left)->types; - const auto rightTypes = to(right)->types; + auto rightTypes = to(right)->types; + auto leftTypes = to(left)->types; - for (auto &&r: rightTypes) { + for (auto &&r: leftTypes) { bool valid = false; - for (auto &&l: leftTypes) { - if (isAssignable(l, r)) { + for (auto &&l: rightTypes) { + if (isExtendable(l, r)) { valid = true; break; }; diff --git a/src/checker/compiler.h b/src/checker/compiler.h index 19e325a..d3d24b7 100644 --- a/src/checker/compiler.h +++ b/src/checker/compiler.h @@ -266,7 +266,7 @@ namespace ts::checker { return address; } - void pushStorage(const string_view &s) { + void pushStorage(string_view s) { pushAddress(registerStorage(s)); } @@ -325,6 +325,7 @@ namespace ts::checker { for (unsigned int i = 0; i < end; i++) { auto op = (OP) ops[i]; switch (op) { + case OP::TypeArgumentDefault: case OP::Distribute: { auto index = readUint32(ops, i + 1); auto &routine = subroutines[index]; @@ -376,6 +377,7 @@ namespace ts::checker { i += 4; break; } + case OP::TypeArgumentDefault: case OP::Distribute: { params += fmt::format(" &{}", readUint32(ops, i + 1)); i += 4; @@ -453,11 +455,50 @@ namespace ts::checker { break; case types::SyntaxKind::FalseKeyword: program.pushOp(OP::False); break; + case types::SyntaxKind::IndexedAccessType: { + const auto n = to(node); + + handle(n->objectType, program); + handle(n->indexType, program); + program.pushOp(OP::IndexAccess); + break; + } case types::SyntaxKind::LiteralType: { const auto n = to(node); handle(n->literal, program); break; } + case types::SyntaxKind::TemplateLiteralType: { + auto t = to(node); + + program.pushFrame(); + if (t->head->rawText && *t->head->rawText != "") { + program.pushOp(OP::StringLiteral); + program.pushStorage(*t->head->rawText); + } + + for (auto &&sub: t->templateSpans->list) { + auto span = to(sub); + handle(to(span)->type, program); + + if (auto a = to(span->literal)) { + if (a->rawText && *a->rawText != "") { + program.pushOp(OP::StringLiteral); + program.pushStorage(a->rawText ? *a->rawText : ""); + } + } else if (auto a = to(span->literal)) { + if (a->rawText && *a->rawText != "") { + program.pushOp(OP::StringLiteral); + program.pushStorage(a->rawText ? *a->rawText : ""); + } + } + } + + program.pushOp(OP::TemplateLiteral); + program.popFrameImplicit(); + + break; + } case types::SyntaxKind::UnionType: { const auto n = to(node); program.pushFrame(); @@ -511,8 +552,17 @@ namespace ts::checker { if (n->typeParameters) { for (auto &&p: n->typeParameters->list) { - auto &symbol = program.pushSymbol(to(p)->name->escapedText, SymbolType::TypeVariable, n->pos); + auto tp = to(p); + if (!tp) throw runtime_error("no type parameter"); + auto &symbol = program.pushSymbol(tp->name->escapedText, SymbolType::TypeVariable, n->pos); program.pushOp(instructions::TypeArgument); + if (tp->defaultType) { + program.pushSubroutine2(tp->defaultType->pos); + handle(tp->defaultType, program); + auto routine = program.popSubroutine(); + program.pushOp(instructions::TypeArgumentDefault); + program.pushAddress(routine->address); + } //todo constraints + default } } @@ -616,14 +666,43 @@ namespace ts::checker { handle(to(node)->type, program); break; } + case types::SyntaxKind::RestType: { + handle(to(node)->type, program); + program.pushOp(OP::Rest); + break; + } +// case types::SyntaxKind::OptionalType: { +// +// break; +// } + //value inference + case types::SyntaxKind::ArrayLiteralExpression: { + program.pushFrame(); + for (auto &&v: to(node)->elements->list) { + handle(v, program); + program.pushOp(OP::TupleMember); + } + program.pushOp(OP::Tuple); + program.popFrameImplicit(); + //todo: handle `as const`, widen if not const + break; + } case types::SyntaxKind::TupleType: { program.pushFrame(); auto n = to(node); for (auto &&e: n->elements->list) { if (auto tm = to(e)) { -// tm-> + handle(tm->type, program); + if (tm->dotDotDotToken) program.pushOp(OP::Rest); + program.pushOp(OP::TupleMember); + if (tm->questionToken) program.pushOp(OP::Optional); + } else if (auto ot = to(e)) { + handle(ot->type, program); + program.pushOp(OP::TupleMember); + program.pushOp(OP::Optional); } else { handle(e, program); + program.pushOp(OP::TupleMember); } } program.pushOp(OP::Tuple); diff --git a/src/checker/instructions.h b/src/checker/instructions.h index 832e6f2..698f42e 100644 --- a/src/checker/instructions.h +++ b/src/checker/instructions.h @@ -18,7 +18,6 @@ namespace ts::instructions { Null, Undefined, - StringLiteral, NumberLiteral, BigIntLiteral, @@ -39,6 +38,10 @@ namespace ts::instructions { Tuple, TupleMember, + TupleNamedMember, //has one parameter, the name in the storage + + Optional, + Rest, Union, Intersection, @@ -60,13 +63,24 @@ namespace ts::instructions { /** * Makes sure that in the current variable slot is a type placed if nothing was provided as parameter. * - * For reach type argument like here `T` a TypeArgument OP is generated. - * Different to Var OP since Var does reserve an entry on the stack, whereas TypeArgument does nothing on the stack per default. + * For each type argument like here `T` a TypeArgument OP is generated. + * Different to Var OP since Var does reserve an entry on the stack, + * whereas TypeArgument ensures there is a stack entry if not already provided via call parameters.. * ```typescript * type A = T; * ``` */ TypeArgument, + TypeArgumentDefault, //expects an entry on the stack + TypeArgumentConstraint, //expects an entry on the stack + + + TemplateLiteral, + + IndexAccess, + KeyOf, + Infer, + TypeOf, /** * Reserves a type variable on the stack, which contains a type object. Unknown as default. @@ -87,8 +101,10 @@ namespace ts::instructions { }; } -template <> struct fmt::formatter : formatter { - template auto format(ts::instructions::OP p, FormatContext &ctx) { +template<> +struct fmt::formatter: formatter { + template + auto format(ts::instructions::OP p, FormatContext &ctx) { return formatter::format(magic_enum::enum_name(p), ctx); } }; diff --git a/src/checker/type_objects.h b/src/checker/type_objects.h index 102240f..aad9127 100644 --- a/src/checker/type_objects.h +++ b/src/checker/type_objects.h @@ -1,13 +1,20 @@ #pragma once - +#include "../core.h" #include #include +#include #include #include +#define MAGIC_ENUM_RANGE_MIN 0 +#define MAGIC_ENUM_RANGE_MAX 512 +#include "magic_enum.hpp" + namespace ts::vm { using std::string_view; using std::array; + using std::to_string; + using std::make_shared; using std::reference_wrapper; enum class TypeKind: unsigned char { @@ -62,6 +69,9 @@ namespace ts::vm { struct Type { string_view typeName; TypeKind kind = TypeKind::Never; + + virtual ~Type() { + } // array, 0> typeArguments; }; @@ -75,21 +85,50 @@ namespace ts::vm { struct TypeNever: BrandKind {}; struct TypeAny: BrandKind {}; - struct TypeUnknown: BrandKind {}; + struct TypeUnknown: BrandKind { + bool unprovidedArgument = false; //when used as TypeArgument and nothing was provided + }; struct TypeVoid: BrandKind {}; struct TypeObject: BrandKind {}; struct TypeString: BrandKind {}; struct TypeNumber: BrandKind {}; struct TypeBigint: BrandKind {}; - struct TypeBoolean: BrandKind { }; + struct TypeBoolean: BrandKind {}; struct TypeSymbol: BrandKind {}; struct TypeNull: BrandKind {}; struct TypeUndefined: BrandKind {}; + struct TypeTemplateLiteral: BrandKind { +// types: (TypeString | TypeAny | TypeNumber | TypeStringLiteral | TypeInfer)[] + vector> types; + + explicit TypeTemplateLiteral() {} + explicit TypeTemplateLiteral(const vector> &types): types(types) {} + }; + struct TypeUnion: BrandKind { vector> types; }; + struct TypeRest: BrandKind { + shared type; + explicit TypeRest(shared type): type(std::move(type)) { + } + }; + + struct TypeTupleMember: BrandKind { + shared type; + bool optional = false; + string_view name = ""; + + explicit TypeTupleMember(shared type): type(std::move(type)) { + } + }; + + struct TypeTuple: BrandKind { + vector> types; + }; + enum class TypeLiteralType { Number, String, @@ -98,9 +137,29 @@ namespace ts::vm { }; struct TypeLiteral: BrandKind { - //symbol, string, number, boolean, bigint (regexp) + private: string_view literal; + string *literalText = nullptr; + public: TypeLiteralType type; + void append(const string_view &text) { + if (!literalText) literalText = new string(literal); + literalText->append(text); + } + + void append(int n) { + append(to_string(n)); + } + + virtual ~TypeLiteral() { + if (literalText) delete literalText; + } + + string_view text() { + return literalText ? *literalText : literal; + } + TypeLiteral() {} + TypeLiteral(const string_view literal, TypeLiteralType type): literal(literal), type(type) {} }; @@ -123,7 +182,7 @@ namespace ts::vm { return reinterpret_pointer_cast(p); } - string stringify(shared < Type > type) { + string stringify(shared type) { //todo: recursive types switch (type->kind) { @@ -136,16 +195,44 @@ namespace ts::vm { case TypeKind::Number: return "number"; case TypeKind::Boolean: return "boolean"; case TypeKind::Bigint: return "bigint"; + case TypeKind::Rest: return "..." + stringify(to(type)->type); + case TypeKind::TemplateLiteral: { + return "nop"; + } case TypeKind::Literal: { auto literal = to(type); switch (literal->type) { - case TypeLiteralType::String: return string("\"").append(literal->literal).append("\""); - case TypeLiteralType::Number: return string(literal->literal); - case TypeLiteralType::Bigint: return string(literal->literal); - case TypeLiteralType::Boolean: return string(literal->literal); + case TypeLiteralType::String: return string("\"").append(literal->text()).append("\""); + case TypeLiteralType::Number: return string(literal->text()); + case TypeLiteralType::Bigint: return string(literal->text()); + case TypeLiteralType::Boolean: return string(literal->text()); } return "unknown-literal"; } + case TypeKind::TupleMember: { + auto member = to(type); + string r = ""; + if (member->name != "") { + r += member->name; + if (member->optional) r += "?"; + r += ": "; + r += stringify(member->type); + } else { + if (member->optional) r += "?"; + r += stringify(member->type); + } + return r; + } + case TypeKind::Tuple: { + auto tuple = to(type); + string r = "["; + for (auto &e: tuple->types) { + r += stringify(e); + if (e != tuple->types.back()) r += ", "; + } + r += "]"; + return r; + } case TypeKind::Union: { auto t = to(type); auto i = 0; @@ -161,4 +248,21 @@ namespace ts::vm { return "error"; } -} \ No newline at end of file + + shared unboxUnion(shared type) { + if (type->kind == TypeKind::Union) { + auto types = to(type)->types; + if (types.size() == 0) return make_shared(); //{ kind: ReflectionKind.never }; + if (types.size() == 1) return types[0]; + } + return type; + } +} + +template<> +struct fmt::formatter: formatter { + template + auto format(ts::vm::TypeKind p, FormatContext &ctx) { + return formatter::format(magic_enum::enum_name(p), ctx); + } +}; diff --git a/src/checker/utils.h b/src/checker/utils.h index 26a97ff..d77543e 100644 --- a/src/checker/utils.h +++ b/src/checker/utils.h @@ -2,9 +2,11 @@ #include #include +#include namespace ts::checker { using std::vector; + using std::string_view; uint32_t readUint32(const vector &bin, unsigned int offset) { return *(uint32_t *) (bin.data() + offset); diff --git a/src/checker/vm.cpp b/src/checker/vm.cpp new file mode 100644 index 0000000..745124b --- /dev/null +++ b/src/checker/vm.cpp @@ -0,0 +1,6 @@ +#include "./vm.h" + +namespace ts::vm { + shared stringToNum(VM &vm) { + } +} \ No newline at end of file diff --git a/src/checker/vm.h b/src/checker/vm.h index 9e3dc72..6616c02 100644 --- a/src/checker/vm.h +++ b/src/checker/vm.h @@ -9,11 +9,18 @@ #include #include #include +#include +#include +#include +#include +#include namespace ts::vm { using namespace ts::checker; using std::string_view; + using std::span; using std::vector; + using std::runtime_error; using instructions::OP; inline string_view readStorage(const string_view &bin, const uint32_t offset) { @@ -22,10 +29,204 @@ namespace ts::vm { } inline bool isConditionTruthy(shared node) { - if (auto n = to(node)) return n->literal == "true"; + if (auto n = to(node)) return n->text() == "true"; return false; } + struct CStack { + vector> iterator; + unsigned int i; + unsigned int round; + }; + + class CartesianProduct { + vector stack; + public: + + shared current(CStack &s) { + return s.iterator[s.i]; + } + + bool next(CStack &s) { + return (++s.i == s.iterator.size()) ? (s.i = 0, false) : true; + } + + vector> toGroup(shared type) { + if (type->kind == TypeKind::Boolean) { + return {make_shared("false", TypeLiteralType::String), make_shared("true", TypeLiteralType::String)}; + } else if (type->kind == TypeKind::Null) { + return {make_shared("null", TypeLiteralType::String)}; +// return [{ kind: TypeKind::literal, literal: 'null' }]; + } else if (type->kind == TypeKind::Undefined) { + return {make_shared("undefined", TypeLiteralType::String)}; + // } else if (type->kind == TypeKind::templateLiteral) { + // // //todo: this is wrong + // // return type.types; + // const result: Type[] = []; + // for (const s of type.types) { + // const g = this.toGroup(s); + // result.push(...g); + // } + // + // return result; + } else if (type->kind == TypeKind::Union) { + vector> result; + for (auto &&v: to(type)->types) { + auto g = toGroup(v); + for (auto &&s: g) result.push_back(s); + } + + return result; + } else { + return {type}; + } + } + + void add(shared item) { + stack.push_back({iterator: toGroup(item), i: 0, round: 0}); + } + + vector>> calculate() { + vector>> result; + + outer: + while (true) { + vector> row; + for (auto &&s: stack) { + auto item = current(s); + if (item->kind == TypeKind::TemplateLiteral) { + for (auto &&v: to(item)->types) row.push_back(v); + } else { + row.push_back(item); + } + } + result.push_back(row); + + for (unsigned int i = stack.size() - 1; i >= 0; i--) { + auto active = next(stack[i]); + //when that i stack is active, continue in main loop + if (active) goto outer; + + //i stack was rewinded. If its the first, it means we are done + if (i == 0) { + goto done; + } + } + break; + } + + done: + return result; + } + }; + + /** + * Query a container type and return the result. + * + * container[index] + * + * e.g. {a: string}['a'] => string + * e.g. {a: string, b: number}[keyof T] => string | number + * e.g. [string, number][0] => string + * e.g. [string, number][number] => string | number + */ + shared indexAccess(shared container, shared index) { + if (container->kind == TypeKind::Array) { +// if ((index.kind == TypeKind::literal && 'number' == typeof index.literal) || index.kind == TypeKind::number) return container.type; +// if (index.kind == TypeKind::literal && index.literal == 'length') return { kind: TypeKind::number }; + } else if (container->kind == TypeKind::Tuple) { + if (index->kind == TypeKind::Literal && to(index)->text() == "length") { + auto t = make_shared("", TypeLiteralType::Number); + t->append(to(container)->types.size()); + return t; + } + +// if (index.kind == TypeKind::literal && 'number' == typeof index.literal && index.literal < 0) { +// index = { kind: TypeKind::number }; +// } +// +// if (index.kind == TypeKind::literal && 'number' == typeof index.literal) { +// type b0 = [string, boolean?][0]; //string +// type b1 = [string, boolean?][1]; //boolean|undefined +// type a0 = [string, ...number[], boolean][0]; //string +// type a1 = [string, ...number[], boolean][1]; //number|boolean +// type a2 = [string, ...number[], boolean][2]; //number|boolean +// type a22 = [string, ...number[], boolean][3]; //number|boolean +// // type a23 = [string, number, boolean][4]; //number|boolean +// type a3 = [string, number, ...number[], boolean][1]; //number +// type a4 = [string, number, ...number[], boolean][-2]; //string|number|boolean, minus means all +// type a5 = [string, number, ...number[], boolean][number]; //string|number|boolean +// +// let restPosition = -1; +// for (let i = 0; i < container.types.length; i++) { +// if (container.types[i].type.kind == TypeKind::rest) { +// restPosition = i; +// break; +// } +// } +// +// if (restPosition == -1 || index.literal < restPosition) { +// const sub = container.types[index.literal]; +// if (!sub) return { kind: TypeKind::undefined }; +// if (sub.optional) return { kind: TypeKind::union, types: [sub.type, { kind: TypeKind::undefined }] }; +// return sub.type; +// } +// +// //index beyond a rest, return all beginning from there as big enum +// +// const result: TypeUnion = { kind: TypeKind::union, types: [] }; +// for (let i = restPosition; i < container.types.length; i++) { +// const member = container.types[i]; +// const type = member.type.kind == TypeKind::rest ? member.type.type : member.type; +// if (!isTypeIncluded(result.types, type)) result.types.push(type); +// if (member.optional && !isTypeIncluded(result.types, { kind: TypeKind::undefined })) result.types.push({ kind: TypeKind::undefined }); +// } +// +// return unboxUnion(result); +// } else if (index.kind == TypeKind::number) { +// const union: TypeUnion = { kind: TypeKind::union, types: [] }; +// for (const sub of container.types) { +// if (sub.type.kind == TypeKind::rest) { +// if (isTypeIncluded(union.types, sub.type.type)) continue; +// union.types.push(sub.type.type); +// } else { +// if (isTypeIncluded(union.types, sub.type)) continue; +// union.types.push(sub.type); +// } +// } +// return unboxUnion(union); +// } else { +// return { kind: TypeKind::never }; +// } +// } else if (container.kind == TypeKind::objectLiteral || container.kind == TypeKind::class) { +// if (index.kind == TypeKind::literal) { +// return resolveObjectIndexType(container, index); +// } else if (index.kind == TypeKind::union) { +// const union: TypeUnion = { kind: TypeKind::union, types: [] }; +// for (const t of index.types) { +// const result = resolveObjectIndexType(container, t); +// if (result.kind == TypeKind::never) continue; +// +// if (result.kind == TypeKind::union) { +// for (const resultT of result.types) { +// if (isTypeIncluded(union.types, resultT)) continue; +// union.types.push(resultT); +// } +// } else { +// if (isTypeIncluded(union.types, result)) continue; +// union.types.push(result); +// } +// } +// return unboxUnion(union); +// } else { +// return { kind: TypeKind::never }; +// } +// } else if (container.kind == TypeKind::any) { +// return container; + } + return make_shared(); + } + struct LoopHelper { vector> types; unsigned int i = 0; @@ -48,33 +249,72 @@ namespace ts::vm { unsigned int sp = 0; //stack pointer unsigned int initialSp = 0; //initial stack pointer unsigned int variables = 0; //the amount of registered variable slots on the stack. will be subtracted when doing popFrame() + sharedOpt loop = nullptr; - sharedOpt previous = nullptr; - Frame(sharedOpt previous = nullptr): previous(previous) {} + Frame *previous = nullptr; + + unsigned int size() { + return sp - initialSp; + } + + Frame(Frame *previous = nullptr): previous(previous) { + if (previous) { + sp = previous->sp; + initialSp = previous->sp; + } + } }; struct Subroutine { const string_view &bin; unsigned int ip = 0; //instruction pointer + unsigned int end = 0; //last instruction pointer unsigned int depth = 0; bool active = true; + unsigned int typeArguments = 0; sharedOpt resultType = nullptr; sharedOpt previous = nullptr; - shared frame; Subroutine(const string_view &bin): bin(bin) {} }; + static unsigned char emptyTuple[] = { + OP::Frame, OP::Tuple, OP::Return + }; + static unsigned char stringToNumPattern[] = { + OP::TypeArgument, OP::TypeArgument, OP::TypeArgumentDefault, 1, 0, 0, 0, + OP::Frame, + OP::Loads, 1, 0, 1, 0, + OP::StringLiteral, 0, 0, 0, 0, + OP::IndexAccess, OP::TemplateLiteral, + OP::Loads, 0, 0, 0, 0, + OP::Extends, + OP::JumpCondition, 0, 0, 0, 0, + OP::Return + }; + + class VM; + + shared stringToNum(VM &vm); + class VM { public: vector errors; /** - * Linked list of subroutines to execute. For each external call this subroutine will this be changed. + * Linked list of subroutines to execute. For each external call this subroutine will be changed. */ sharedOpt subroutine; + Frame *frame = nullptr; vector> stack; + unordered_map(VM &)>> optimised; + + VM() { + if (frame) delete frame; +// stack.reserve(1000); + } + void printErrors() { debug("errors ({}):", errors.size()); for (auto &&e: errors) { @@ -82,25 +322,35 @@ namespace ts::vm { } } - shared call(const string_view &bin, unsigned int address = 0, unsigned int arguments = 0) { + void call(const string_view &bin, unsigned int address = 0, unsigned int arguments = 0) { //todo: check if address was already calculated using unordered_map? const auto loopRunning = !!subroutine; if (!loopRunning) { errors.clear(); } - if (subroutine) { - //subtract `arguments` from the stack pointer so popFrame() returns correct entries. - //they do no longer belong to the current frame, but will be moved to the next one as inputs. - subroutine->frame->sp -= arguments; - } +// auto found = std::equal(bin.begin() + address, bin.begin() + address + sizeof(emptyTuple), emptyTuple); +// if (found) { +//// debug("found! {}", address); +// push(make_shared()); +// return; +// } + +// } + auto next = make_shared(bin); next->ip = address; + next->end = bin.size(); // debug("call {}({})", address, arguments); next->previous = subroutine; next->depth = subroutine ? subroutine->depth + 1 : 0; - next->frame = make_shared(); - next->frame->sp = stack.size(); - next->frame->initialSp = next->frame->sp - arguments; //we reuse the types that were prepared as arguments + + frame = new Frame(frame); + + if (arguments) { + //we move x arguments from the old stack to the new one + frame->previous->sp -= arguments; + frame->initialSp -= arguments; + } if (loopRunning) subroutine->ip++; //`subroutine` is set to something new in next line, so for() increments its ip, not ours subroutine = next; @@ -109,36 +359,74 @@ namespace ts::vm { if (!loopRunning) this->process(); // print(); - return next->resultType; +// return next->resultType; } - vector> popFrame() { - auto sub = slice(stack, subroutine->frame->initialSp, subroutine->frame->sp); - stack.resize(subroutine->frame->initialSp); - subroutine->frame = subroutine->frame->previous; - if (!subroutine->frame) throw runtime_error("Invalid pop frame"); +// span> popFrame() { +// span> sub{stack.data() + frame->initialSp, frame->sp - frame->initialSp}; +//// auto sub = slice(stack, frame->initialSp, frame->sp); +// stack.resize(frame->initialSp); +// frame = frame->previous; +// if (!frame) throw runtime_error("Invalid pop frame"); +// return sub; +// } + + span> popFrame() { +// auto frameSize = frame->initialSp - frame->sp; +// while (frameSize--) stack.pop(); + span> sub{stack.data() + frame->initialSp, frame->sp - frame->initialSp}; +// std::span> sub{stack.begin() + frame->initialSp, frameSize}; + auto previous = frame->previous; + delete frame; + frame = previous; return sub; } + unsigned int frameSize() { + return frame->initialSp - frame->sp; + } +// +// void popFrame(function)> callback) { +// auto frameSize = frame->initialSp - frame->sp; +// while (frameSize-- ) { +// auto &item = stack.top(); +// callback(item); +// stack.pop(); +// } +// frame = frame->previous; +// } + void pushFrame() { - subroutine->frame = make_shared(subroutine->frame); - subroutine->frame->sp = stack.size(); - subroutine->frame->initialSp = subroutine->frame->sp; + frame = new Frame(frame); + } + + shared &last() { + if (frame->sp == 0) throw runtime_error("stack is empty. Can not return last"); + return stack[frame->sp - 1]; } - shared pop() { - if (stack.size() == 0) throw runtime_error("stack is empty. Can not pop"); - auto r = stack.back(); - stack.pop_back(); - subroutine->frame->sp--; - if (subroutine->frame->sp < subroutine->frame->initialSp) { + shared &pop() { + if (frame->sp == 0) throw runtime_error("stack is empty. Can not pop"); + frame->sp--; + if (frame->sp < frame->initialSp) { throw runtime_error("Popped through frame"); } - return r; + return stack[frame->sp]; } + + sharedOpt frameEntryAt(unsigned int position) { + auto i = frame->initialSp + position; + if (i > frame->sp) return nullptr; + return stack[i]; + } + void push(const shared &type) { - subroutine->frame->sp++; - stack.push_back(type); + if (frame->sp >= stack.size()) { + stack.push_back(type); + } else { + stack[frame->sp] = type; + } + frame->sp++; } void print() { @@ -148,56 +436,60 @@ namespace ts::vm { auto current = subroutine; debug("# Debug VM: {} stack entries", stack.size()); while (current) { - auto currentFrame = current->frame; debug("# Routine {} ({})", current->depth, current->active); - auto frameId = 0; - while (currentFrame) { - debug(" # Frame {}: {} ({}->{}) stack frame entries ({})", - frameId, currentFrame->sp - currentFrame->initialSp, currentFrame->initialSp, currentFrame->sp, - currentFrame->loop ? "loop" : ""); - if (currentFrame->sp && currentFrame->initialSp != currentFrame->sp) { - auto sub = slice(stack, currentFrame->initialSp, currentFrame->sp); - auto j = currentFrame->initialSp; - for (auto &&i: sub) { - debug(" - {}: {}", j++, i ? stringify(i) : "empty"); - } - } - currentFrame = currentFrame->previous; - frameId++; - } - current = current->previous; } + + auto currentFrame = frame; + auto frameId = 0; + while (currentFrame) { + debug(" # Frame {}: {} ({}->{}) stack frame entries ({})", + frameId, currentFrame->sp - currentFrame->initialSp, currentFrame->initialSp, currentFrame->sp, + currentFrame->loop ? "loop" : ""); + if (currentFrame->sp && currentFrame->initialSp != currentFrame->sp) { +// auto sub = slice(stack, currentFrame->initialSp, currentFrame->sp); +// auto j = currentFrame->initialSp; +// for (auto &&i: sub) { +// debug(" - {}: {}", j++, i ? stringify(i) : "empty"); +// } + } + currentFrame = currentFrame->previous; + frameId++; + } } void process() { - while (subroutine) { - const auto &bin = subroutine->bin; - const auto end = bin.size(); - for (; subroutine->active && subroutine->ip < end; subroutine->ip++) { - const auto op = (OP) bin[subroutine->ip]; + while (subroutine) { + for (; subroutine->active && subroutine->ip < subroutine->end; subroutine->ip++) { + const auto op = (OP) subroutine->bin[subroutine->ip]; // debug("[{}] OP {} ({} -> {})", subroutine->depth, op, subroutine->ip, (unsigned int)bin[subroutine->ip]); + + + if (op == OP::TypeArgument && (subroutine->bin[subroutine->ip + 1]) == OP::TypeArgument) { + } + switch (op) { case OP::Jump: { - subroutine->ip = readUint32(bin, 1) - 1; //minus 1 because for's i++ + subroutine->ip = readUint32(subroutine->bin, 1) - 1; //minus 1 because for's i++ break; } case OP::Extends: { auto right = pop(); auto left = pop(); // debug("{} extends {} => {}", stringify(left), stringify(right), isAssignable(right, left)); - push(make_shared(isAssignable(right, left) ? "true" : "false", TypeLiteralType::Boolean)); + const auto valid = isExtendable(left, right); + push(make_shared(valid ? "true" : "false", TypeLiteralType::Boolean)); break; } case OP::Distribute: { - if (!subroutine->frame->loop) { + if (!frame->loop) { auto type = pop(); pushFrame(); - subroutine->frame->loop = make_shared(type); + frame->loop = make_shared(type); } - auto next = subroutine->frame->loop->next(); + auto next = frame->loop->next(); if (!next) { //done auto types = popFrame(); @@ -216,53 +508,79 @@ namespace ts::vm { subroutine->ip += 4; } else { //next - const auto loopProgram = readUint32(bin, subroutine->ip + 1); + const auto loopProgram = readUint32(subroutine->bin, subroutine->ip + 1); subroutine->ip--; //we jump back if the loop is not done, so that this section is executed when the following call() is done push(next); - call(bin, loopProgram, 1); + call(subroutine->bin, loopProgram, 1); } break; } case OP::JumpCondition: { auto condition = pop(); - const auto leftProgram = readUint16(bin, subroutine->ip + 1); - const auto rightProgram = readUint16(bin, subroutine->ip + 3); + const auto leftProgram = readUint16(subroutine->bin, subroutine->ip + 1); + const auto rightProgram = readUint16(subroutine->bin, subroutine->ip + 3); subroutine->ip += 4; // debug("{} ? {} : {}", stringify(condition), leftProgram, rightProgram); - call(bin, isConditionTruthy(condition) ? leftProgram : rightProgram); + call(subroutine->bin, isConditionTruthy(condition) ? leftProgram : rightProgram); break; } case OP::Return: { - auto t = pop(); - stack.resize(subroutine->frame->initialSp); + auto previous = frame->previous; + delete frame; + frame = previous; + frame->sp++; //consume the new stack entry from the function call, making it part of the previous frame subroutine->active = false; subroutine->ip++; //we jump directly to another subroutine, so for()'s increment doesn't apply subroutine = subroutine->previous; subroutine->ip--; //for()'s increment applies the wrong subroutine, so we decrease first - if (!t) { - throw runtime_error("Stack empty"); - } -// debug("routine result: {}", vm::stringify(t)); - stack.push_back(t); - subroutine->frame->sp++; +// if (!t) { +// throw runtime_error("Stack empty"); +// } +// debug("routine result: {}", vm::stringify(last())); break; } case OP::TypeArgument: { + if (frame->size() <= subroutine->typeArguments) { + auto unknown = make_shared(); + unknown->unprovidedArgument = true; + push(unknown); + } + subroutine->typeArguments++; + break; + } + case OP::TypeArgumentDefault: { + auto t = frameEntryAt(subroutine->typeArguments - 1); + //t is always set because TypeArgument ensures that + if (t->kind == TypeKind::Unknown && to(t)->unprovidedArgument) { + frame->sp--; //remove unknown type from stack + const auto address = readUint32(subroutine->bin, subroutine->ip + 1); + subroutine->ip += 4; //jump over address + call(subroutine->bin, address, 0); //the result is pushed on the stack + } else { + subroutine->ip += 4; //jump over address + } + break; + } + case OP::TemplateLiteral: { + handleTemplateLiteral(); break; } case OP::Var: { - subroutine->frame->variables++; + frame->variables++; push(make_shared()); break; } case OP::Loads: { - const auto frameOffset = readUint16(bin, subroutine->ip + 1); - const auto varIndex = readUint16(bin, subroutine->ip + 3); + const auto frameOffset = readUint16(subroutine->bin, subroutine->ip + 1); + const auto varIndex = readUint16(subroutine->bin, subroutine->ip + 3); subroutine->ip += 4; if (frameOffset == 0) { - push(stack[subroutine->frame->initialSp + varIndex]); + //todo: this does not work with std::stack. what could be an alternative? + push(stack[frame->initialSp + varIndex]); } else if (frameOffset == 1) { - push(stack[subroutine->frame->previous->initialSp + varIndex]); + push(stack[frame->previous->initialSp + varIndex]); + } else if (frameOffset == 2) { + push(stack[frame->previous->previous->initialSp + varIndex]); } else { throw runtime_error("frame offset not implement"); } @@ -274,22 +592,96 @@ namespace ts::vm { break; } case OP::Call: { - const auto address = readUint32(bin, subroutine->ip + 1); - const auto arguments = readUint16(bin, subroutine->ip + 5); + const auto address = readUint32(subroutine->bin, subroutine->ip + 1); + const auto arguments = readUint16(subroutine->bin, subroutine->ip + 5); subroutine->ip += 6; -// subroutine->ip++; - call(bin, address, arguments); -// subroutine->ip--; + call(subroutine->bin, address, arguments); break; } case OP::Assign: { - const auto lvalue = pop(); - const auto rvalue = pop(); - if (!isAssignable(lvalue, rvalue)) { + auto lvalue = pop(); + auto rvalue = pop(); + if (!isExtendable(lvalue, rvalue)) { errors.push_back(fmt::format("{}={} not assignable!", stringify(lvalue), stringify(rvalue))); } break; } + case OP::IndexAccess: { + auto right = pop(); + auto left = pop(); + +// if (!isType(left)) { +// push({ kind: TypeKind::never }); +// } else { + + //todo: we have to put all members of `left` on the stack, since subroutines could be required + // to resolve super classes. + auto t = indexAccess(left, right); +// if (isWithAnnotations(t)) { +// t.indexAccessOrigin = { container: left as TypeObjectLiteral, index: right as Type }; +// } + +// t.parent = undefined; + push(t); +// } + break; + } + case OP::Rest: { + push(make_shared(pop())); + break; + } + case OP::TupleMember: { + push(make_shared(pop())); + break; + } + case OP::TupleNamedMember: { + break; + } + case OP::Tuple: { + auto types = popFrame(); + + //short path for [...x, y]; + if (types.size() == 2 && to(types[0])->type->kind == TypeKind::Rest) { + //todo: we need a heuristic to determine if the array `x` actually can be mutated + auto t = to(to(types[0])->type); + if (t->type->kind == TypeKind::Tuple && to(types[1])->type->kind != TypeKind::Rest) { + auto result = to(t->type); + result->types.push_back(to(types[1])); + push(result); + break; + } + } + + auto tuple = make_shared(); + for (auto &&type: types) { + if (type->kind != TypeKind::TupleMember) { + debug("No tuple member in stack frame, but {}", type->kind); + continue; + } + auto member = reinterpret_pointer_cast(type); + if (member->type->kind == TypeKind::Rest) { + auto rest = to(member->type); + if (rest->type->kind == TypeKind::Tuple) { + for (auto &&sub: to(rest->type)->types) { + tuple->types.push_back(sub); + } + } else { + tuple->types.push_back(member); + } + } else { + tuple->types.push_back(member); + } + } + push(tuple); + break; + } + case OP::Optional: { + auto l = last(); + if (l->kind == TypeKind::TupleMember) { + to(l)->optional = true; + } + break; + } case OP::True: push(make_shared("true", TypeLiteralType::Boolean)); break; case OP::False: push(make_shared("false", TypeLiteralType::Boolean)); @@ -316,30 +708,32 @@ namespace ts::vm { break; case OP::Union: { auto t = make_shared(); - t->types = popFrame(); + auto types = popFrame(); + for (auto &&v: types) t->types.push_back(v); push(t); break; } case OP::NumberLiteral: { - const auto address = readUint32(bin, subroutine->ip + 1); + const auto address = readUint32(subroutine->bin, subroutine->ip + 1); subroutine->ip += 4; - push(make_shared(readStorage(bin, address), TypeLiteralType::Number)); + push(make_shared(readStorage(subroutine->bin, address), TypeLiteralType::Number)); break; } case OP::BigIntLiteral: { - const auto address = readUint32(bin, subroutine->ip + 1); + const auto address = readUint32(subroutine->bin, subroutine->ip + 1); subroutine->ip += 4; - push(make_shared(readStorage(bin, address), TypeLiteralType::Bigint)); + push(make_shared(readStorage(subroutine->bin, address), TypeLiteralType::Bigint)); break; } case OP::StringLiteral: { - const auto address = readUint32(bin, subroutine->ip + 1); + const auto address = readUint32(subroutine->bin, subroutine->ip + 1); subroutine->ip += 4; - push(make_shared(readStorage(bin, address), TypeLiteralType::String)); + auto text = readStorage(subroutine->bin, address); + push(make_shared(text, TypeLiteralType::String)); break; } default: { - throw runtime_error("unhandeled op"); + throw runtime_error(fmt::format("unhandeled op {}", op)); } } } @@ -349,5 +743,71 @@ namespace ts::vm { // return stack.back(); } + + void handleTemplateLiteral() { + auto types = popFrame(); + + //short path for `{'asd'}` + if (types.size() == 1 && types[0]->kind == TypeKind::Literal) { + push(types[0]); + return; + } + + auto result = make_shared(); + CartesianProduct cartesian; + for (auto &&type: types) { + cartesian.add(type); + } + auto product = cartesian.calculate(); + + outer: + for (auto &&combination: product) { + auto templateType = make_shared(); + bool hasPlaceholder = false; +// let lastLiteral: { kind: TypeKind::literal, literal: string, parent?: Type } | undefined = undefined; + sharedOpt lastLiteral; + + //merge a combination of types, e.g. [string, 'abc', '3'] as template literal => `${string}abc3`. + for (auto &&item: combination) { + if (item->kind == TypeKind::Never) { + //template literals that contain a never like `prefix.${never}` are completely ignored + goto outer; + } + + if (item->kind == TypeKind::Literal) { + if (lastLiteral) { + lastLiteral->append(to(item)->text()); + } else { + lastLiteral = make_shared("", TypeLiteralType::String); + lastLiteral->append(to(item)->text()); + templateType->types.push_back(lastLiteral); + } + } else { + hasPlaceholder = true; + lastLiteral = nullptr; +// item.parent = template; + templateType->types.push_back(item); + } + } + + if (hasPlaceholder) { + if (templateType->types.size() == 1 && templateType->types[0]->kind == TypeKind::String) { +// templateType->types[0].parent = result; + result->types.push_back(templateType->types[0]); + } else { +// templateType->parent = result; + result->types.push_back(templateType); + } + } else if (lastLiteral) { +// lastLiteral.parent = result; + result->types.push_back(lastLiteral); + } + } + + auto t = vm::unboxUnion(result); +// if (t.kind == TypeKind::union) for (const member of t.types) member.parent = t; +// debug("handleTemplateLiteral: {}", stringify(t)); + push(t); + } }; } \ No newline at end of file diff --git a/src/factory.cpp b/src/factory.cpp index 2f8087d..5971ffb 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -1869,7 +1869,7 @@ namespace ts { // } // @api - shared Factory::createTaggedTemplateExpression(shared tag, sharedOpt typeArguments, shared templateLiteral) { + shared Factory::createTaggedTemplateExpression(shared tag, sharedOpt typeArguments, shared templateLiteral) { auto node = createBaseExpression(SyntaxKind::TaggedTemplateExpression); node->tag = parenthesizer.parenthesizeLeftSideOfAccess(tag); node->typeArguments = asNodeArray(typeArguments); diff --git a/src/factory.h b/src/factory.h index bccbf1d..bba69b1 100644 --- a/src/factory.h +++ b/src/factory.h @@ -1502,7 +1502,7 @@ namespace ts { // } // @api - shared createTaggedTemplateExpression(shared tag, sharedOpt typeArguments, shared templateLiteral); + shared createTaggedTemplateExpression(shared tag, sharedOpt typeArguments, shared templateLiteral); // // // @api // function updateTaggedTemplateExpression(node: TaggedTemplateExpression, tag: Expression, sharedOpt typeArguments, template: TemplateLiteral) { diff --git a/src/parenthesizer.h b/src/parenthesizer.h index 585c770..50ee123 100644 --- a/src/parenthesizer.h +++ b/src/parenthesizer.h @@ -375,14 +375,23 @@ namespace ts { shared parenthesizeElementTypesOfTupleType(shared types); bool hasJSDocPostfixQuestion(shared type) { - //todo: make this workable - if (isJSDocNullableType(type)) return type.postfix; - if (isNamedTupleMember(type)) return hasJSDocPostfixQuestion(type.type); - if (isFunctionTypeNode(type) || isConstructorTypeNode(type) || isTypeOperatorNode(type)) return hasJSDocPostfixQuestion(type.type); - if (isConditionalTypeNode(type)) return hasJSDocPostfixQuestion(type.falseType); - if (isUnionTypeNode(type)) return hasJSDocPostfixQuestion(last(type.types)); - if (isIntersectionTypeNode(type)) return hasJSDocPostfixQuestion(last(type.types)); - if (isInferTypeNode(type)) return !!type.typeParameter.constraint && hasJSDocPostfixQuestion(type.typeParameter.constraint); + if (isJSDocNullableType(type)) return to(type)->postfix; + if (isNamedTupleMember(type)) return hasJSDocPostfixQuestion(to(type)->type); + + if (auto n = to(type)) { + return hasJSDocPostfixQuestion(n->type); + } else if (auto n = to(type)) { + return hasJSDocPostfixQuestion(n->type); + } else if (auto n = to(type)) { + return hasJSDocPostfixQuestion(n->type); + } + + if (isConditionalTypeNode(type)) return hasJSDocPostfixQuestion(to(type)->falseType); + if (isUnionTypeNode(type)) return hasJSDocPostfixQuestion(reinterpret_pointer_cast(last(to(type)->types))); + if (isIntersectionTypeNode(type)) return hasJSDocPostfixQuestion(reinterpret_pointer_cast(last(to(type)->types))); + if (auto infer = to(type)) { + return infer->typeParameter->constraint && hasJSDocPostfixQuestion(infer->typeParameter->constraint); + } return false; } diff --git a/src/tests/test_checker.cpp b/src/tests/test_checker.cpp index e9b5766..71f80f2 100644 --- a/src/tests/test_checker.cpp +++ b/src/tests/test_checker.cpp @@ -26,7 +26,6 @@ TEST(checker, program) { TEST(checker, type) { Parser parser; - std: string code = R"( const v1: string = "abc"; const v2: number = 123; @@ -41,7 +40,7 @@ TEST(checker, type) { auto bin = program.build(); debug("Code {} chars, to {} bytes: {}", code.size(), bin.size(), bin); - bench(100, [&] { + bench(10, [&] { vm::VM vm; vm.call(bin); }); @@ -50,7 +49,6 @@ TEST(checker, type) { TEST(checker, type2) { Parser parser; - std: string code = R"( type a = number; type b = string | a; @@ -64,7 +62,6 @@ TEST(checker, type2) { auto program = compiler.compileSourceFile(result); program.print(); auto bin = program.build(); - debug("Code {} chars, to {} bytes: {}", code.size(), bin.size(), bin); vm::VM vm; vm.call(bin); @@ -104,12 +101,29 @@ TEST(checker, tuple) { Parser parser; string code = R"( - type a = [1?, T?]; - const v1: a = [1, 'abc']; +// type a = [1, ...T]; +// const v1: a<[string, number]> = [1, 'abc']; + +// type a = [string, number]; +// const var1: a['length'] = 3; +// +// type a = [string, boolean]; +// type b = [...a, number]; +// const var1: b = ['asd', 123]; + +// type a = T; +// const var1: a = 'asd'; + + type StringToNum = `${A['length']}` extends T ? A['length'] : StringToNum; + const var1: StringToNum<'999'> = 1002; )"; auto result = parser.parseSourceFile("app.ts", code, ScriptTarget::Latest, false, ScriptKind::TS, {}); + vector bin2{0, 0, 0, 0}; + checker::writeUint16(bin2, 0, 1); + debug("bin2 {}", bin2); + checker::Compiler compiler; auto program = compiler.compileSourceFile(result); program.print(); @@ -118,7 +132,7 @@ TEST(checker, tuple) { vm::VM vm; vm.call(bin); vm.printErrors(); - EXPECT_EQ(vm.errors.size(), 1); +// EXPECT_EQ(vm.errors.size(), 1); bench(10, [&] { vm::VM vm; diff --git a/src/tests/test_core.cpp b/src/tests/test_core.cpp index 33aba8d..dc4ed7b 100644 --- a/src/tests/test_core.cpp +++ b/src/tests/test_core.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include "../core.h" #include "../types.h" #include "../factory.h" @@ -421,3 +423,13 @@ TEST(core, passFn) { // // auto a = executeFn([]{ return test(); }); } + +TEST(core, span) { + vector ints{1,2,3,4,5}; + span slide1{ints.data() + 1, 3}; + + EXPECT_EQ(slide1.size(), 3); + EXPECT_EQ(slide1[0], 2); + EXPECT_EQ(slide1[2], 4); +// auto a = executeFn([]{ return test(); }); +} diff --git a/src/types.h b/src/types.h index 0ad0d47..0bd88b7 100644 --- a/src/types.h +++ b/src/types.h @@ -1063,7 +1063,6 @@ template <> struct fmt::formatter : formatter last(shared &array) { + return array->list.back(); + } + template bool some(sharedOpt array, function)> callback) { if (!array) return false; @@ -1919,8 +1922,8 @@ namespace ts { // BaseNode *parent; //: DeclarationWithTypeParameterChildren | InferTypeNode; shared name; /** Note: Consider calling `getEffectiveConstraintOfTypeParameter` */ - Property(constraint, TypeNode); - Property(defaultType, TypeNode); + OptionalProperty(constraint, TypeNode); + OptionalProperty(defaultType, TypeNode); // For error recovery purposes. OptionalProperty(expression, Expression); @@ -2231,6 +2234,7 @@ namespace ts { Property(type, TypeNode); bool postfix = false; }; + struct JSDocOptionalType: BrandKind { Property(type, TypeNode); }; @@ -2542,12 +2546,12 @@ namespace ts { shared templateSpans; }; -#define TemplateLiteral TemplateExpression, NoSubstitutionTemplateLiteral +#define TemplateLiteralTypes TemplateExpression, NoSubstitutionTemplateLiteral struct TaggedTemplateExpression: BrandKind { Property(tag, LeftHandSideExpression); sharedOpt typeArguments; - UnionProperty(templateLiteral, TemplateLiteral); + UnionProperty(templateLiteral, TemplateLiteralTypes); /*@internal*/ OptionalProperty(questionDotToken, QuestionDotToken); // NOTE: Invalid syntax, only used to report a grammar error. }; diff --git a/src/utilities.cpp b/src/utilities.cpp index 7234c86..311f29f 100644 --- a/src/utilities.cpp +++ b/src/utilities.cpp @@ -333,7 +333,7 @@ namespace ts { // return getScriptKindFromFileName(fileName) || ScriptKind::TS; } /** @internal */ - bool hasInvalidEscape(shared templateLiteral) { + bool hasInvalidEscape(shared templateLiteral) { if (isNoSubstitutionTemplateLiteral(templateLiteral)) { return !!templateLiteral->to().templateFlags; } diff --git a/src/utilities.h b/src/utilities.h index 2b6450e..46dacf3 100644 --- a/src/utilities.h +++ b/src/utilities.h @@ -309,7 +309,7 @@ namespace ts { ScriptKind ensureScriptKind(string fileName, optional scriptKind); /** @internal */ - bool hasInvalidEscape(shared templateLiteral); + bool hasInvalidEscape(shared templateLiteral); string fileExt(const string &filename); diff --git a/tests/generic2.ts b/tests/generic2.ts new file mode 100644 index 0000000..85a700e --- /dev/null +++ b/tests/generic2.ts @@ -0,0 +1,2 @@ +type StringToNum = `${A['length']}` extends T ? A['length'] : StringToNum; +const var1: StringToNum<'999'> = 999;