Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion tslang/lib/TypeScript/MLIRGenImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -6074,7 +6074,16 @@ class MLIRGenImpl
rightExprGenContext.receiverFuncType = leftExpressionValue.getType();
}

rightExprGenContext.receiverType = leftExpressionValue.getType();
// a narrowed variable (addSafeCastStatement) takes a value of the type it is declared with:
// after `s.kind === "sq"`, `s = { kind: "ci", r: 1 }` is an `Sq | Ci`, and typed as the narrowed
// `Sq` it became a `{ kind: "sq", r }` that was never stored
auto receiverType = leftExpressionValue.getType();
if (auto safeCastOp = leftExpressionValue.getDefiningOp<mlir_ts::SafeCastOp>())
{
receiverType = safeCastOp.getValue().getType();
}

rightExprGenContext.receiverType = receiverType;

auto result2 = mlirGen(rightExpression, rightExprGenContext);
EXIT_IF_FAILED_OR_NO_VALUE(result2)
Expand Down
21 changes: 19 additions & 2 deletions tslang/lib/TypeScript/MLIRGenStatements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,11 @@ namespace mlirgen
}

auto conditionValue = V(result);
// the condition as written: a type guard's predicate is what narrows, and the boolean cast loses it
auto testedValue = conditionValue;
// a condition known to be false narrows nothing: as in mlirGen(IfStatement), narrowing would cast the
// tested value to a type it cannot have
auto narrowBody = conditionValue && getStaticBoolean(conditionValue).value_or(true);
if (conditionValue)
{
// a truthiness test (`for (let n = head; n; n = n.next)`) is made a boolean as in mlirGen(WhileStatement)
Expand All @@ -859,8 +864,20 @@ namespace mlirgen
// async.execute and waits for it, so one iteration runs at a time (#498). Generated inside an
// async.execute of its own, a `break` or `continue` in it had no loop in its region, and a
// nested `for await` crashed the compiler (#502).
auto bodyResult = mlirGen(forStatementAST->statement, loopGenContext);
EXIT_IF_FAILED(bodyResult)
{
// the body sees the variable the condition narrows, as a `while` body does
// (`for (; typeof v === "string"; v = 1) { v.length }`); the scope ends before the incrementor,
// which assigns the variable as it is declared
SymbolTableScopeT varScopeBody(symbolTable);
SafeTypesMapScopeT safeTypesMapScope(safeTypesMap);
if (narrowBody)
{
checkSafeCast(forStatementAST->condition, testedValue, nullptr, loopGenContext);
}

auto bodyResult = mlirGen(forStatementAST->statement, loopGenContext);
EXIT_IF_FAILED(bodyResult)
}

builder.create<mlir_ts::ResultOp>(location);

Expand Down
6 changes: 6 additions & 0 deletions tslang/test/tester/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,9 @@ tslang_add_test(NAME test-compile-00-await-void COMMAND test-runner "${PROJECT_S
tslang_add_test(NAME test-compile-00-for-await-sequential COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_await_sequential.ts")
tslang_add_test(NAME test-compile-00-for-await-control COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_await_control.ts")
tslang_add_test(NAME test-compile-00-for-optional-class-condition COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_optional_class_condition.ts")
tslang_add_test(NAME test-compile-00-for-condition-narrowing COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_condition_narrowing.ts")
tslang_add_test(NAME test-compile-00-string-empty-falsy COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00string_empty_falsy.ts")
tslang_add_test(NAME test-compile-00-narrowed-assign-other-member COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00narrowed_assign_other_member.ts")
tslang_add_test(NAME test-compile-00-const-record-owned-fields COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_record_owned_fields.ts")
tslang_add_test(NAME test-compile-00-array-length-valid COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_length_valid.ts")
tslang_add_test(NAME test-compile-00-string-view-copy COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00string_view_copy.ts")
Expand Down Expand Up @@ -870,7 +872,9 @@ tslang_add_test(NAME test-jit-00-await-void COMMAND test-runner -jit "${PROJECT_
tslang_add_test(NAME test-jit-00-for-await-sequential COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_await_sequential.ts")
tslang_add_test(NAME test-jit-00-for-await-control COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_await_control.ts")
tslang_add_test(NAME test-jit-00-for-optional-class-condition COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_optional_class_condition.ts")
tslang_add_test(NAME test-jit-00-for-condition-narrowing COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_condition_narrowing.ts")
tslang_add_test(NAME test-jit-00-string-empty-falsy COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00string_empty_falsy.ts")
tslang_add_test(NAME test-jit-00-narrowed-assign-other-member COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00narrowed_assign_other_member.ts")
tslang_add_test(NAME test-jit-00-const-record-owned-fields COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_record_owned_fields.ts")
tslang_add_test(NAME test-jit-00-array-length-valid COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_length_valid.ts")
tslang_add_test(NAME test-jit-00-string-view-copy COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00string_view_copy.ts")
Expand Down Expand Up @@ -1713,7 +1717,9 @@ set(TSLANG_CORPUS
00for_await_sequential.ts
00for_await_control.ts
00for_optional_class_condition.ts
00for_condition_narrowing.ts
00string_empty_falsy.ts
00narrowed_assign_other_member.ts
00const_record_owned_fields.ts
00array_length_valid.ts
00string_view_copy.ts
Expand Down
137 changes: 137 additions & 0 deletions tslang/test/tester/tests/00for_condition_narrowing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// a `for` body sees the variable its condition narrows, as a `while` body does: `typeof v === "string"` made
// `v.length` "Can't resolve property 'length' of type number". The incrementor still assigns the variable as
// it is declared.
class Node {
constructor(public value: number, public next: Node | undefined) {}
}

class A {
constructor(public a: number) {}
onlyA() {
return this.a;
}
}

class B {
constructor(public b: number) {}
}

type Sq = { kind: "sq"; side: number };
type Ci = { kind: "ci"; r: number };

function isStr(v: string | number): v is string {
return typeof v === "string";
}

function typeofIncrementor(v: string | number) {
let n = 0;
for (; typeof v === "string"; v = 1) {
n += v.length;
}

return n;
}

function typeofBodyWrite(v: string | number) {
let n = 0;
for (; typeof v === "string"; ) {
n += v.length;
v = 1;
}

return n;
}

function typeGuard(v: string | number) {
let n = 0;
for (; isStr(v); v = 1) {
n += v.length;
}

return n;
}

function instanceOf(x: A | B) {
let n = 0;
// a local: reassigning the parameter itself in this loop crashes under -mm=rc, narrowed or not (#512)
for (let y = x; y instanceof A; y = new B(1)) {
n += y.onlyA();
}

return n;
}

function discriminant(s: Sq | Ci) {
let n = 0;
for (; s.kind === "sq"; ) {
n += s.side;
s = { kind: "ci", r: 1 };
}

return n;
}

function listSum(head: Node | undefined) {
let sum = 0;
for (let node = head; node; node = node.next) {
const n: Node = node;
sum += n.value;
}

return sum;
}

function listSkip(head: Node | undefined) {
let sum = 0;
for (let node = head; node; node = node.next) {
if (node.value == 2) {
continue;
}

sum += node.value;
}

return sum;
}

function nested(head: Node | undefined) {
let sum = 0;
for (let a = head; a; a = a.next) {
for (let b = head; b; b = b.next) {
sum += a.value * b.value;
}
}

return sum;
}

function staticFalse() {
const a = [1, 2];
let n = 0;
for (; typeof a === "string"; ) {
n += a.length;
}

return n;
}

function main() {
assert(typeofIncrementor("abcd") == 4, "typeof, incrementor writes");
assert(typeofIncrementor(7) == 0, "typeof, not entered");
assert(typeofBodyWrite("abcde") == 5, "typeof, body writes");
assert(typeGuard("abc") == 3, "type guard");
assert(instanceOf(new A(5)) == 5, "instanceof");
assert(discriminant({ kind: "sq", side: 4 }) == 4, "discriminant, body writes the other member");

let head: Node | undefined = new Node(1, new Node(2, new Node(3, undefined)));
assert(listSum(head) == 6, "optional list walk");
assert(listSum(undefined) == 0, "empty list");
assert(listSkip(head) == 4, "continue runs the incrementor");
assert(nested(head) == 36, "nested loops");
assert(staticFalse() == 0, "a condition known to be false");

for (let i = 0; i < 1; i++) var x = 5;
assert(x == 5, "var in an unbraced body");

print("done.");
}
50 changes: 50 additions & 0 deletions tslang/test/tester/tests/00narrowed_assign_other_member.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// an object literal assigned to a variable narrowed by a discriminant (`s.kind === "sq"`) was typed as the
// narrowed member, so `s = { kind: "ci", r: 1 }` made a `{ kind: "sq", r }` and stored nothing: an `if` kept
// "sq", and a `while` never ended (or crashed the compiler)
type Sq = { kind: "sq"; side: number };
type Ci = { kind: "ci"; r: number };

function inIf(s: Sq | Ci) {
if (s.kind === "sq") {
s = { kind: "ci", r: 1 };
}

return s.kind;
}

function inWhile(s: Sq | Ci) {
let n = 0;
while (s.kind === "sq") {
n++;
s = { kind: "ci", r: 2 };
}

return n;
}

function sameMember(s: Sq | Ci) {
if (s.kind === "sq") {
s = { kind: "sq", side: 9 };
}

return s.kind === "sq" ? s.side : -1;
}

function readAfter(s: Sq | Ci) {
if (s.kind === "sq") {
s = { kind: "ci", r: 7 };
}

return s.kind === "ci" ? s.r : -1;
}

function main() {
assert(inIf({ kind: "sq", side: 4 }) == "ci", "if: assigned the other member");
assert(inIf({ kind: "ci", r: 3 }) == "ci", "if: not taken");
assert(inWhile({ kind: "sq", side: 4 }) == 1, "while: assigned the other member ends the loop");
assert(inWhile({ kind: "ci", r: 3 }) == 0, "while: not entered");
assert(sameMember({ kind: "sq", side: 4 }) == 9, "assigned the same member");
assert(readAfter({ kind: "sq", side: 4 }) == 7, "the other member's field after the if");

print("done.");
}
Loading