diff --git a/src/checker/checks.h b/src/checker/checks.h index 4de368c..3851a93 100644 --- a/src/checker/checks.h +++ b/src/checker/checks.h @@ -15,6 +15,41 @@ namespace ts::vm { if (right->kind == TypeKind::Literal) return to(right)->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 (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::Union) { + if (right->kind != TypeKind::Union) { + for (auto &&l: to(left)->types) { + if (isAssignable(l, right)) return true; + } + return false; + } else { + //e.g.: string|number = string|boolean + const auto leftTypes = to(left)->types; + const auto rightTypes = to(right)->types; + + for (auto &&r: rightTypes) { + bool valid = false; + for (auto &&l: leftTypes) { + if (isAssignable(l, r)) { + valid = true; + break; + }; + } + if (!valid) return false; + } + return true; + } + } + return false; } } \ No newline at end of file diff --git a/src/checker/compiler.h b/src/checker/compiler.h index 4953e07..19e325a 100644 --- a/src/checker/compiler.h +++ b/src/checker/compiler.h @@ -18,7 +18,8 @@ namespace ts::checker { Variable, Function, Class, - Type, + TypeFunction, //parts of conditional type, mapped type, .. + Type, //type alias TypeVariable //template variable }; @@ -27,11 +28,12 @@ namespace ts::checker { struct Subroutine { vector ops; //OPs, and its parameters unordered_map opSourceMap; - string_view identifier; + string_view identifier{}; unsigned int address{}; //during compilation the index address, after the final address in the binary unsigned int pos{}; SymbolType type = SymbolType::Type; + explicit Subroutine() {} explicit Subroutine(string_view &identifier): identifier(identifier) {} }; @@ -43,7 +45,7 @@ namespace ts::checker { const unsigned int index{}; //symbol index of the current frame const unsigned int pos; unsigned int declarations = 1; - Subroutine *routine = nullptr; + sharedOpt routine = nullptr; shared frame = nullptr; }; @@ -81,8 +83,8 @@ namespace ts::checker { shared frame = make_shared(); //tracks which subroutine is active (end() is), so that pushOp calls are correctly assigned. - vector activeSubroutines; - vector subroutines; + vector> activeSubroutines; + vector> subroutines; //implicit is when a OP itself triggers in the VM a new frame, without having explicitly a OP::Frame shared pushFrame(bool implicit = false) { @@ -93,6 +95,22 @@ namespace ts::checker { return frame; } + /** + * Creates a new nameless subroutine, used for example in mapped-type, conditional type + * @return + */ + unsigned int pushSubroutine2(unsigned int pos) { + auto routine = make_shared(); + routine->pos = pos; + routine->type = SymbolType::TypeFunction; + routine->address = subroutines.size(); + + pushFrame(true); //subroutines have implicit stack frames due to call convention + subroutines.push_back(routine); + activeSubroutines.push_back(subroutines.back()); + return routine->address; + } + /** * Push the subroutine from the symbol as active. This means it will now be populated with OPs. */ @@ -108,10 +126,7 @@ namespace ts::checker { throw runtime_error(fmt::format("no symbol found for {}", name)); } - /** - * Returns the index of the sub routine. Will be replaced in build() with the real address. - */ - void popSubroutine() { + shared popSubroutine() { if (activeSubroutines.empty()) throw runtime_error("No active subroutine found"); popFrameImplicit(); auto subroutine = activeSubroutines.back(); @@ -120,11 +135,9 @@ namespace ts::checker { } subroutine->ops.push_back(OP::Return); activeSubroutines.pop_back(); + return subroutine; } - /** - * The returning index will be replaced for all Call OP with the real subroutine address. - */ Symbol &findSymbol(const string_view &identifier) { Frame *current = frame.get(); @@ -160,10 +173,22 @@ namespace ts::checker { writeUint32(ops, ops.size(), address); } + void pushUint16(unsigned int v) { + auto &ops = getOPs(); + writeUint16(ops, ops.size(), v); + } + void pushSymbolAddress(Symbol &symbol) { auto &ops = getOPs(); - writeUint32(ops, ops.size(), symbol.frame->id); - writeUint32(ops, ops.size(), symbol.index); + unsigned int frameOffset = 0; + auto current = frame; + while (current) { + if (current == symbol.frame) break; + frameOffset++; + current = current->previous; + } + writeUint16(ops, ops.size(), frameOffset); + writeUint16(ops, ops.size(), symbol.index); } vector &getOPs() { @@ -210,6 +235,7 @@ namespace ts::checker { frameToUse->symbols.push_back(Symbol{ .frame = frameToUse, .name = name, + .type = type, .pos = pos, .index = (unsigned int) frameToUse->symbols.size() }); @@ -220,12 +246,12 @@ namespace ts::checker { auto &symbol = pushSymbol(name, type, pos, frameToUse); if (symbol.routine) return symbol; - Subroutine routine{name}; - routine.pos = pos; - routine.type = type; - routine.address = subroutines.size(); - subroutines.push_back(std::move(routine)); - symbol.routine = &subroutines.back(); + auto routine = make_shared(name); + routine->pos = pos; + routine->type = type; + routine->address = subroutines.size(); + subroutines.push_back(routine); + symbol.routine = routine; return symbol; } @@ -272,19 +298,19 @@ namespace ts::checker { //detect final binary address of all subroutines unsigned int routineAddress = address; for (auto &&routine: subroutines) { - routine.address = routineAddress; - routineAddress += routine.ops.size(); + routine->address = routineAddress; + routineAddress += routine->ops.size(); } //go through all OPs and adjust CALL parameter to the final binary address setFinalBinaryAddress(ops); for (auto &&routine: subroutines) { - setFinalBinaryAddress(routine.ops); + setFinalBinaryAddress(routine->ops); } for (auto &&routine: subroutines) { - bin.insert(bin.end(), routine.ops.begin(), routine.ops.end()); - address += routine.ops.size(); + bin.insert(bin.end(), routine->ops.begin(), routine->ops.end()); + address += routine->ops.size(); } writeUint32(bin, 1, address); @@ -299,11 +325,27 @@ namespace ts::checker { for (unsigned int i = 0; i < end; i++) { auto op = (OP) ops[i]; switch (op) { + case OP::Distribute: { + auto index = readUint32(ops, i + 1); + auto &routine = subroutines[index]; + writeUint32(ops, i + 1, routine->address); + i += 4; + break; + } case OP::Call: { //adjust binary address auto index = readUint32(ops, i + 1); auto &routine = subroutines[index]; - writeUint32(ops, i + 1, routine.address); + writeUint32(ops, i + 1, routine->address); + i += 6; + break; + } + case OP::JumpCondition: { + //adjust binary address + auto first = readUint16(ops, i + 1); + auto second = readUint16(ops, i + 3); + writeUint16(ops, i + 1, subroutines[first]->address); + writeUint16(ops, i + 3, subroutines[second]->address); i += 4; break; } @@ -325,14 +367,25 @@ namespace ts::checker { std::string params = ""; switch (op) { case OP::Call: { + params += fmt::format(" &{}[{}]", readUint32(ops, i + 1), readUint16(ops, i + 5)); + i += 6; + break; + } + case OP::JumpCondition: { + params += fmt::format(" &{}:&{}", readUint16(ops, i + 1), readUint16(ops, i + 3)); + i += 4; + break; + } + case OP::Distribute: { params += fmt::format(" &{}", readUint32(ops, i + 1)); i += 4; break; } - case OP::Loads: + case OP::Loads: { params += fmt::format(" &{}:{}", readUint16(ops, i + 1), readUint16(ops, i + 3)); i += 4; break; + } case OP::NumberLiteral: case OP::BigIntLiteral: case OP::StringLiteral: { @@ -354,8 +407,8 @@ namespace ts::checker { void print() { int i = 0; for (auto &&subroutine: subroutines) { - fmt::print("Subroutine {} &{}, {} bytes: ", subroutine.identifier, i++, subroutine.ops.size()); - printOps(subroutine.ops); + fmt::print("Subroutine {} &{}, {} bytes: ", subroutine->identifier, i++, subroutine->ops.size()); + printOps(subroutine->ops); } debug("Main {} bytes: {}", ops.size(), ops); @@ -400,6 +453,11 @@ namespace ts::checker { break; case types::SyntaxKind::FalseKeyword: program.pushOp(OP::False); break; + case types::SyntaxKind::LiteralType: { + const auto n = to(node); + handle(n->literal, program); + break; + } case types::SyntaxKind::UnionType: { const auto n = to(node); program.pushFrame(); @@ -416,15 +474,28 @@ namespace ts::checker { //todo: search in symbol table and get address // debug("type reference {}", to(node)->typeName->to().escapedText); // program.pushOp(OP::Number); - - const auto name = to(node)->typeName->to().escapedText; + const auto n = to(node); + const auto name = n->typeName->to().escapedText; auto &symbol = program.findSymbol(name); if (symbol.type == SymbolType::TypeVariable) { program.pushOp(OP::Loads); program.pushSymbolAddress(symbol); } else { + if (n->typeArguments) { + for (auto &&p: n->typeArguments->list) { + handle(p, program); + } + } program.pushOp(OP::Call); + if (!symbol.routine) { + throw runtime_error("Reference is not a reference to a existing routine."); + } program.pushAddress(symbol.routine->address); + if (n->typeArguments) { + program.pushUint16(n->typeArguments->length()); + } else { + program.pushUint16(0); + } } break; } @@ -438,11 +509,11 @@ namespace ts::checker { //populate routine program.pushSubroutine(n->name->escapedText); - //todo: extract type parameters if (n->typeParameters) { for (auto &&p: n->typeParameters->list) { - auto symbol = program.pushSymbol(n->name->escapedText, SymbolType::TypeVariable, n->pos); - program.pushOp(instructions::Var); + auto &symbol = program.pushSymbol(to(p)->name->escapedText, SymbolType::TypeVariable, n->pos); + program.pushOp(instructions::TypeArgument); + //todo constraints + default } } @@ -482,6 +553,83 @@ namespace ts::checker { break; } + case types::SyntaxKind::ConditionalExpression: { + const auto n = to(node); + debug("ConditionalExpression"); + break; + } + case types::SyntaxKind::ConditionalType: { + const auto n = to(node); + //Depending on whether this a distributive conditional type or not, the whole conditional type has to be moved to its own function + //so it can be executed for each union member. + // - the `checkType` is a simple identifier (just `T`, no `[T]`, no `T | x`, no `{a: T}`, etc) +// let distributiveOverIdentifier: Identifier | undefined = isTypeReferenceNode(narrowed.checkType) && isIdentifier(narrowed.checkType.typeName) ? narrowed.checkType.typeName : undefined; + sharedOpt distributiveOverIdentifier = isTypeReferenceNode(n->checkType) && isIdentifier(to(n->checkType)->typeName) ? to(to(n->checkType)->typeName) : nullptr; + + if (distributiveOverIdentifier) { +// program.pushSymbol(distributiveOverIdentifier->escapedText, SymbolType::TypeVariable, distributiveOverIdentifier->pos); +// handle(n->checkType, program); +// program.pushFrame(); +// //first we add to the stack the origin type we distribute over. +// handle(narrowed.checkType, program); +// +// //since the distributive conditional type is a loop that changes only the found `T`, it is necessary to add that as variable, +// //so call convention can take over. +// program.pushVariable(getIdentifierName(distributiveOverIdentifier)); +// program.pushCoRoutine(); + program.pushSubroutine2(node->pos); + + //in the subroutine of the conditional type we place a new type variable, which acts as input. + //the `Distribute` OP makes then sure that the current stack entry is used as input + program.pushSymbol(distributiveOverIdentifier->escapedText, SymbolType::TypeVariable, distributiveOverIdentifier->pos); + program.pushOp(instructions::Var); + } + + handle(n->checkType, program); + handle(n->extendsType, program); + program.pushOp(instructions::Extends); + + auto trueProgram = program.pushSubroutine2(n->trueType->pos); + handle(n->trueType, program); + program.popSubroutine(); + + auto falseProgram = program.pushSubroutine2(n->falseType->pos); + handle(n->falseType, program); + program.popSubroutine(); + + program.pushOp(OP::JumpCondition); + //todo increase to 32bit each + program.pushUint16(trueProgram); + program.pushUint16(falseProgram); + + if (distributiveOverIdentifier) { + auto routine = program.popSubroutine(); + handle(n->checkType, program); //LOADS the input type onto the stack. Distribute pops it then. + program.pushOp(OP::Distribute); + program.pushAddress(routine->address); + } + + debug("ConditionalType {}", !!distributiveOverIdentifier); + break; + } + case types::SyntaxKind::ParenthesizedType: { + handle(to(node)->type, program); + break; + } + case types::SyntaxKind::TupleType: { + program.pushFrame(); + auto n = to(node); + for (auto &&e: n->elements->list) { + if (auto tm = to(e)) { +// tm-> + } else { + handle(e, program); + } + } + program.pushOp(OP::Tuple); + program.popFrameImplicit(); + break; + } case types::SyntaxKind::VariableStatement: { for (auto &&s: to(node)->declarationList->declarations->list) { handle(s, program); @@ -509,6 +657,7 @@ namespace ts::checker { handle(n->initializer, program); program.pushOp(OP::Call); program.pushAddress(subroutineIndex); + program.pushUint16(0); program.pushOp(OP::Assign); } } diff --git a/src/checker/instructions.h b/src/checker/instructions.h index 59f53c2..832e6f2 100644 --- a/src/checker/instructions.h +++ b/src/checker/instructions.h @@ -37,9 +37,16 @@ namespace ts::instructions { Class, + Tuple, + TupleMember, + Union, Intersection, + Extends, //expected 2 entries on the stack + Condition, //expected 3 entries on the stack + JumpCondition, //expected 1 entry on the stack + two uint16 parameters + /** * Stack parameter. For each JS variable, JS function, as well as type variables (mapped-type variable for example). * @@ -50,6 +57,17 @@ namespace ts::instructions { */ Var, + /** + * 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. + * ```typescript + * type A = T; + * ``` + */ + TypeArgument, + /** * Reserves a type variable on the stack, which contains a type object. Unknown as default. * @@ -63,7 +81,9 @@ namespace ts::instructions { Return, Jump, + Distribute, //calls a subroutine for each union member. one parameter (address to subroutine) Call //call a subroutine and push the result on the stack + }; } diff --git a/src/checker/type_objects.h b/src/checker/type_objects.h index 22473c1..102240f 100644 --- a/src/checker/type_objects.h +++ b/src/checker/type_objects.h @@ -81,18 +81,19 @@ namespace ts::vm { 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 TypeUnion: BrandKind { + struct TypeUnion: BrandKind { vector> types; }; enum class TypeLiteralType { Number, String, + Boolean, Bigint }; @@ -116,13 +117,13 @@ namespace ts::vm { }; template - sharedOpt to(sharedOpt p) { + sharedOpt to(const sharedOpt &p) { if (!p) return nullptr; if (T::KIND != TypeKind::Unknown && p->kind != T::KIND) return nullptr; return reinterpret_pointer_cast(p); } - string_view stringify(shared < Type > type) { + string stringify(shared < Type > type) { //todo: recursive types switch (type->kind) { @@ -139,8 +140,11 @@ namespace ts::vm { auto literal = to(type); switch (literal->type) { case TypeLiteralType::String: return string("\"").append(literal->literal).append("\""); - case TypeLiteralType::Number: return literal->literal; + case TypeLiteralType::Number: return string(literal->literal); + case TypeLiteralType::Bigint: return string(literal->literal); + case TypeLiteralType::Boolean: return string(literal->literal); } + return "unknown-literal"; } case TypeKind::Union: { auto t = to(type); diff --git a/src/checker/vm.h b/src/checker/vm.h index 38b9c89..9e3dc72 100644 --- a/src/checker/vm.h +++ b/src/checker/vm.h @@ -16,16 +16,39 @@ namespace ts::vm { using std::vector; using instructions::OP; - string_view readStorage(const string_view &bin, const uint32_t offset) { + inline string_view readStorage(const string_view &bin, const uint32_t offset) { const auto size = readUint16(bin, offset); return string_view(reinterpret_cast(bin.data() + offset + 2), size); } + inline bool isConditionTruthy(shared node) { + if (auto n = to(node)) return n->literal == "true"; + return false; + } + + struct LoopHelper { + vector> types; + unsigned int i = 0; + + LoopHelper(const shared &type) { + if (auto t = to(type)) { + types = t->types; + } else { + types = {type}; + } + } + + sharedOpt next() { + if (i == types.size()) return nullptr; + return types[i++]; + } + }; + struct Frame { 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() - bool isMappedType = false; + sharedOpt loop = nullptr; sharedOpt previous = nullptr; Frame(sharedOpt previous = nullptr): previous(previous) {} }; @@ -44,35 +67,49 @@ namespace ts::vm { class VM { public: + vector errors; /** * Linked list of subroutines to execute. For each external call this subroutine will this be changed. */ sharedOpt subroutine; vector> stack; - vector errors; - void call(const string_view &bin, unsigned int address = 0) { + void printErrors() { + debug("errors ({}):", errors.size()); + for (auto &&e: errors) { + debug(" " + e); + } + } + + shared 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 next = make_shared(bin); next->ip = address; +// 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; + next->frame->initialSp = next->frame->sp - arguments; //we reuse the types that were prepared as arguments + if (loopRunning) subroutine->ip++; //`subroutine` is set to something new in next line, so for() increments its ip, not ours subroutine = next; + if (loopRunning) subroutine->ip--; //`subroutine` is set to something new, so for() increments its ip, which we don't want if (!loopRunning) this->process(); - } - shared pop() { - if (stack.size() == 0) throw runtime_error("stack is empty. Can not pop"); - auto r = stack.back(); - stack.pop_back(); - return r; +// print(); + return next->resultType; } vector> popFrame() { @@ -89,11 +126,50 @@ namespace ts::vm { subroutine->frame->initialSp = subroutine->frame->sp; } + 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) { + throw runtime_error("Popped through frame"); + } + return r; + } void push(const shared &type) { subroutine->frame->sp++; stack.push_back(type); } + void print() { + debug(""); + debug(""); + debug("----------------------------------"); + 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; + } + } + void process() { while (subroutine) { const auto &bin = subroutine->bin; @@ -101,18 +177,96 @@ namespace ts::vm { for (; subroutine->active && subroutine->ip < end; subroutine->ip++) { const auto op = (OP) bin[subroutine->ip]; -// debug("[{}] OP {} ({})", subroutine->depth, op, bin[subroutine->ip]); +// debug("[{}] OP {} ({} -> {})", subroutine->depth, op, subroutine->ip, (unsigned int)bin[subroutine->ip]); switch (op) { case OP::Jump: { subroutine->ip = readUint32(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)); + break; + } + case OP::Distribute: { + if (!subroutine->frame->loop) { + auto type = pop(); + pushFrame(); + subroutine->frame->loop = make_shared(type); + } + + auto next = subroutine->frame->loop->next(); + if (!next) { + //done + auto types = popFrame(); + if (types.size() == 0) { + push(make_shared()); + } else if (types.size() == 1) { + push(types[0]); + } else { + auto result = make_shared(); + for (auto &&v: types) { + if (v->kind != TypeKind::Never) result->types.push_back(v); + } + push(result); + } + //jump over parameter + subroutine->ip += 4; + } else { + //next + const auto loopProgram = readUint32(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); + } + break; + } + case OP::JumpCondition: { + auto condition = pop(); + const auto leftProgram = readUint16(bin, subroutine->ip + 1); + const auto rightProgram = readUint16(bin, subroutine->ip + 3); + subroutine->ip += 4; +// debug("{} ? {} : {}", stringify(condition), leftProgram, rightProgram); + call(bin, isConditionTruthy(condition) ? leftProgram : rightProgram); + break; + } case OP::Return: { auto t = pop(); - stack.resize(subroutine->frame->sp); - subroutine = subroutine->previous; - push(t); + stack.resize(subroutine->frame->initialSp); 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++; + break; + } + case OP::TypeArgument: { + break; + } + case OP::Var: { + subroutine->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); + subroutine->ip += 4; + if (frameOffset == 0) { + push(stack[subroutine->frame->initialSp + varIndex]); + } else if (frameOffset == 1) { + push(stack[subroutine->frame->previous->initialSp + varIndex]); + } else { + throw runtime_error("frame offset not implement"); + } +// debug("load var {}/{}", frameOffset, varIndex); break; } case OP::Frame: { @@ -121,30 +275,45 @@ namespace ts::vm { } case OP::Call: { const auto address = readUint32(bin, subroutine->ip + 1); - subroutine->ip += 4; - subroutine->ip++; //`subroutine` is set to something new in next line, so for() increments its ip, not ours - call(bin, address); - subroutine->ip--; //`subroutine` is set to something new, so for() increments its ip, which we don't want + const auto arguments = readUint16(bin, subroutine->ip + 5); + subroutine->ip += 6; +// subroutine->ip++; + call(bin, address, arguments); +// subroutine->ip--; break; } case OP::Assign: { const auto lvalue = pop(); const auto rvalue = pop(); if (!isAssignable(lvalue, rvalue)) { -// debug("{}={} not assignable!", stringify(lvalue), stringify(rvalue)); + errors.push_back(fmt::format("{}={} not assignable!", stringify(lvalue), stringify(rvalue))); } break; } - case OP::String: push(make_shared()); break; - case OP::Number: push(make_shared()); break; - case OP::Boolean: push(make_shared()); break; - case OP::Unknown: push(make_shared()); break; - case OP::Undefined: push(make_shared()); break; - case OP::Void: push(make_shared()); break; - case OP::Never: push(make_shared()); break; - case OP::Any: push(make_shared()); break; - case OP::Symbol: push(make_shared()); break; - case OP::Object: push(make_shared()); break; + case OP::True: push(make_shared("true", TypeLiteralType::Boolean)); + break; + case OP::False: push(make_shared("false", TypeLiteralType::Boolean)); + break; + case OP::String: push(make_shared()); + break; + case OP::Number: push(make_shared()); + break; + case OP::Boolean: push(make_shared()); + break; + case OP::Unknown: push(make_shared()); + break; + case OP::Undefined: push(make_shared()); + break; + case OP::Void: push(make_shared()); + break; + case OP::Never: push(make_shared()); + break; + case OP::Any: push(make_shared()); + break; + case OP::Symbol: push(make_shared()); + break; + case OP::Object: push(make_shared()); + break; case OP::Union: { auto t = make_shared(); t->types = popFrame(); @@ -169,6 +338,9 @@ namespace ts::vm { push(make_shared(readStorage(bin, address), TypeLiteralType::String)); break; } + default: { + throw runtime_error("unhandeled op"); + } } } diff --git a/src/factory.h b/src/factory.h index ea48dc6..bccbf1d 100644 --- a/src/factory.h +++ b/src/factory.h @@ -1152,14 +1152,14 @@ namespace ts { // : node; // } // -// // @api -// function createOptionalTypeNode(shared type) { -// auto node = createBaseNode(SyntaxKind::OptionalType); -// node->type = parenthesizer.parenthesizeTypeOfOptionalType(type); -// node->transformFlags = (int)TransformFlags::ContainsTypeScript; -// return node; -// } -// + // @api + shared createOptionalTypeNode(shared type) { + auto node = createBaseNode(SyntaxKind::OptionalType); + node->type = parenthesizer.parenthesizeTypeOfOptionalType(type); + node->transformFlags = (int)TransformFlags::ContainsTypeScript; + return node; + } + // // @api // function updateOptionalTypeNode(node: OptionalTypeNode, shared type): OptionalTypeNode { // return node->type != type @@ -2864,23 +2864,26 @@ namespace ts { // return createBaseNode(kind); // } // -// // @api -// // createJSDocNullableType -// // createJSDocNonNullableType -// function createJSDocPrePostfixUnaryTypeWorker type; readonly postfix: boolean }>(SyntaxKind kind, type: T["type"], postfix = false): T { -// auto node = createJSDocUnaryTypeWorker( -// kind, -// postfix ? type && parenthesizer.parenthesizeNonArrayTypeOfPostfixType(type) : type -// ) as Mutable; -// node->postfix = postfix; -// return node; -// } -// -// // @api -// // createJSDocOptionalType -// // createJSDocVariadicType -// // createJSDocNamepathType -// function createJSDocUnaryTypeWorker type; }>(SyntaxKind kind, type: T["type"]): T { + // @api + shared createJSDocNonNullableType(shared type, bool postfix = false) { + auto node = createBaseNode(); + node->type = postfix ? type ? parenthesizer.parenthesizeNonArrayTypeOfPostfixType(type) : nullptr : type; + node->postfix = postfix; + return node; + } + + shared createJSDocNullableType(shared type, bool postfix = false) { + auto node = createBaseNode(); + node->type = postfix ? type ? parenthesizer.parenthesizeNonArrayTypeOfPostfixType(type) : nullptr : type; + node->postfix = postfix; + return node; + } + + // @api + // createJSDocOptionalType + // createJSDocVariadicType + // createJSDocNamepathType +// shared createJSDocUnaryTypeWorker(SyntaxKind kind, shared type) { // auto node = createBaseNode(kind); // node->type = type; // return node; diff --git a/src/parenthesizer.cpp b/src/parenthesizer.cpp index a23c386..c211b38 100644 --- a/src/parenthesizer.cpp +++ b/src/parenthesizer.cpp @@ -34,10 +34,16 @@ namespace ts { } sharedOpt Parenthesizer::parenthesizeTypeArguments(sharedOpt typeArguments) { - if (typeArguments && (*typeArguments).empty()) { + if (typeArguments && !typeArguments->empty()) { auto m = sameMap(typeArguments, CALLBACK(parenthesizeOrdinalTypeArgument)); return factory->createNodeArray(m); } + return typeArguments; + } + + shared Parenthesizer::parenthesizeTypeOfOptionalType(shared type) { + if (hasJSDocPostfixQuestion(type)) return factory->createParenthesizedType(type); + return parenthesizeNonArrayTypeOfPostfixType(type); } shared Parenthesizer::parenthesizeBranchOfConditionalExpression(shared branch) { diff --git a/src/parenthesizer.h b/src/parenthesizer.h index 33514b0..585c770 100644 --- a/src/parenthesizer.h +++ b/src/parenthesizer.h @@ -374,22 +374,20 @@ namespace ts { // shared parenthesizeElementTypesOfTupleType(shared types); -// function hasJSDocPostfixQuestion(shared type | NamedTupleMember): boolean { -// 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); -// return false; -// } -// -// function parenthesizeTypeOfOptionalType(shared type): TypeNode { -// if (hasJSDocPostfixQuestion(type)) return factory->createParenthesizedType(type); -// return parenthesizeNonArrayTypeOfPostfixType(type); -// } -// + 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); + return false; + } + + shared parenthesizeTypeOfOptionalType(shared type); + // // function parenthesizeMemberOfElementType(member: TypeNode): TypeNode { // // switch (member.kind) { // // case SyntaxKind::UnionType: diff --git a/src/parser2.h b/src/parser2.h index d91c280..ff898f7 100644 --- a/src/parser2.h +++ b/src/parser2.h @@ -5936,13 +5936,12 @@ namespace ts { return finishNode(factory.createRestTypeNode(parseType()), pos); } auto type = parseType(); - //no JSDoc support -// if (isJSDocNullableType(type) && type->pos == type->type->pos) { -// auto node = factory.createOptionalTypeNode(type->type); -// setTextRange(node, type); -// Node->flags = type->flags; -// return node; -// } + if (isJSDocNullableType(type) && type->pos == to(type)->type->pos) { + auto node = factory.createOptionalTypeNode(to(type)->type); + setTextRange(node, type); + node->flags = type->flags; + return node; + } return type; } @@ -6104,18 +6103,18 @@ namespace ts { while (!scanner.hasPrecedingLineBreak()) { switch (token()) { case SyntaxKind::ExclamationToken: - throw runtime_error("No JSDoc support"); -// nextToken(); -// type = finishNode(factory.createJSDocNonNullableType(type, /*postfix*/ true), pos); + nextToken(); + //although it's called "JSDoc" it will be filtered out later on, e.g. for optional tuple member, see parseTupleElementType() + type = finishNode(factory.createJSDocNonNullableType(type, /*postfix*/ true), pos); break; case SyntaxKind::QuestionToken: // If next token is start of a type we have a conditional type if (lookAhead(CALLBACK(nextTokenIsStartOfType))) { return type; } - throw runtime_error("No JSDoc support"); -// nextToken(); -// type = finishNode(factory.createJSDocNullableType(type, /*postfix*/ true), pos); + nextToken(); + //although it's called "JSDoc" it will be filtered out later on, e.g. for optional tuple member, see parseTupleElementType() + type = finishNode(factory.createJSDocNullableType(type, /*postfix*/ true), pos); break; case SyntaxKind::OpenBracketToken: parseExpected(SyntaxKind::OpenBracketToken); diff --git a/src/tests/test_checker.cpp b/src/tests/test_checker.cpp index 4c87c7f..e9b5766 100644 --- a/src/tests/test_checker.cpp +++ b/src/tests/test_checker.cpp @@ -26,7 +26,8 @@ TEST(checker, program) { TEST(checker, type) { Parser parser; - std:string code = R"( + std: + string code = R"( const v1: string = "abc"; const v2: number = 123; )"; @@ -49,7 +50,8 @@ TEST(checker, type) { TEST(checker, type2) { Parser parser; - std:string code = R"( + std: + string code = R"( type a = number; type b = string | a; const v1: b = 'abc'; @@ -68,6 +70,62 @@ TEST(checker, type2) { vm.call(bin); } +TEST(checker, type3) { + Parser parser; + + string code = R"( + type a = K | (T extends string ? 'yes' : 'no'); + const v1: a = 'no'; + const v2: a = 'yes'; + const v3: a = true; + const v4: a = 'yes'; + const v5: a = 'nope'; + )"; + + auto result = parser.parseSourceFile("app.ts", code, ScriptTarget::Latest, false, ScriptKind::TS, {}); + + checker::Compiler compiler; + auto program = compiler.compileSourceFile(result); + program.print(); + auto bin = program.build(); + + vm::VM vm; + vm.call(bin); + vm.printErrors(); + EXPECT_EQ(vm.errors.size(), 1); + + bench(10, [&] { + vm::VM vm; + vm.call(bin); + }); +} + +TEST(checker, tuple) { + Parser parser; + + string code = R"( + type a = [1?, T?]; + const v1: a = [1, 'abc']; + )"; + + auto result = parser.parseSourceFile("app.ts", code, ScriptTarget::Latest, false, ScriptKind::TS, {}); + + checker::Compiler compiler; + auto program = compiler.compileSourceFile(result); + program.print(); + auto bin = program.build(); + + vm::VM vm; + vm.call(bin); + vm.printErrors(); + EXPECT_EQ(vm.errors.size(), 1); + + bench(10, [&] { + vm::VM vm; + vm.call(bin); + }); +} + TEST(checker, assign) { Parser parser; diff --git a/src/types.h b/src/types.h index 4536605..0ad0d47 100644 --- a/src/types.h +++ b/src/types.h @@ -2222,6 +2222,20 @@ namespace ts { OptionalProperty(assertClause, AssertClause); }; + //Those JSDoc* types are used because TypeScript tsc uses them to wrap information like optional/non-nullable tuple member. Only god knows why. + struct JSDocNullableType: BrandKind { + Property(type, TypeNode); + bool postfix = false; + }; + struct JSDocNonNullableType: BrandKind { + Property(type, TypeNode); + bool postfix = false; + }; + struct JSDocOptionalType: BrandKind { + Property(type, TypeNode); + }; + + #define NamedImportsOrExports NamedImports, NamedExports #define ImportOrExportSpecifier ImportSpecifier, ExportSpecifier #define TypeOnlyCompatibleAliasDeclaration ImportClause, ImportEqualsDeclaration, NamespaceImport, ImportOrExportSpecifier;