diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index 610f523fe..c183761b0 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -3395,6 +3395,14 @@ class MLIRGenImpl if (typeToken) { + if (text == S("function") || text == S("class") || text == S("interface") || text == S("object") || text == S("array")) + { + if (auto membersType = getUnionMembersOfTypeOf(expr, wstos(text), genContext)) + { + return addSafeCastStatement(expr, membersType, inverse, elseSafeCase, genContext); + } + } + return addSafeCastStatement(expr, typeToken, inverse, elseSafeCase, genContext); } @@ -3405,6 +3413,35 @@ class MLIRGenImpl return mlir::failure(); } + // "function", "class", "interface", "object" and "array" name a kind, not a type, so they narrow to + // Opaque (or Opaque[]). A union already lists its members of that kind - the members whose runtime + // tag, typeOfAsString, is that name - and when there is exactly one, the union is narrowed to it: + // through Opaque a function could not be called and a class field not resolved. + // With several members of the kind it stays Opaque. The union cast helper castFromUnion generates + // (`if (typeof t == 'function') return t;`) depends on that: narrowed to a smaller union, `return t` + // would be a union cast again and instantiate the helper without end. + // Returns no type when the value is not a union or does not have exactly one member of the kind. + mlir::Type getUnionMembersOfTypeOf(Expression expr, llvm::StringRef typeOfName, const GenContext &genContext) + { + auto unionType = dyn_cast_or_null(evaluate(expr, genContext)); + if (!unionType) + { + return mlir::Type(); + } + + TypeOfOpHelper toh(builder); + SmallVector members; + for (auto member : unionType.getTypes()) + { + if (toh.typeOfAsString(member) == typeOfName) + { + members.push_back(member); + } + } + + return members.size() == 1 ? members.front() : mlir::Type(); + } + mlir::LogicalResult checkSafeCastUndefined(Expression optVal, Expression undefVal, bool inverse, ElseSafeCase *elseSafeCase, const GenContext &genContext) { auto expr = stripParentheses(undefVal); diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index e6ccc6218..b9e469c0d 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -173,6 +173,7 @@ add_test(NAME test-compile-00-funcs-hybrid-null-this COMMAND test-runner "${PROJ add_test(NAME test-compile-00-typeof-static-fold COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00typeof_static_fold.ts") add_test(NAME test-compile-00-typeof-function-narrowing COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00typeof_function_narrowing.ts") add_test(NAME test-compile-00-typeof-static-fold-conditions COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00typeof_static_fold_conditions.ts") +add_test(NAME test-compile-00-typeof-union-narrowing COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00typeof_union_narrowing.ts") add_test(NAME test-compile-00-array-move-wide-elements COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_move_wide_elements.ts") add_test(NAME test-compile-00-funcs-expression-generic COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_expression_generic.ts") add_test(NAME test-compile-00-funcs-expression-iterator COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_expression_iterator.ts") @@ -592,6 +593,7 @@ add_test(NAME test-jit-00-funcs-hybrid-null-this COMMAND test-runner -jit "${PRO add_test(NAME test-jit-00-typeof-static-fold COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00typeof_static_fold.ts") add_test(NAME test-jit-00-typeof-function-narrowing COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00typeof_function_narrowing.ts") add_test(NAME test-jit-00-typeof-static-fold-conditions COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00typeof_static_fold_conditions.ts") +add_test(NAME test-jit-00-typeof-union-narrowing COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00typeof_union_narrowing.ts") add_test(NAME test-jit-00-array-move-wide-elements COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_move_wide_elements.ts") add_test(NAME test-jit-00-funcs-expression-generic COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_expression_generic.ts") add_test(NAME test-jit-00-funcs-expression-iterator COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_expression_iterator.ts") @@ -1581,6 +1583,7 @@ set(TSLANG_CORPUS 00typeof_function_narrowing.ts 00typeof_static_fold.ts 00typeof_static_fold_conditions.ts + 00typeof_union_narrowing.ts 00types_indexedaccesstype.ts 00types_keyof_enum.ts 00types_mappedtype.ts diff --git a/tslang/test/tester/tests/00typeof_union_narrowing.ts b/tslang/test/tester/tests/00typeof_union_narrowing.ts new file mode 100644 index 000000000..06d264ab7 --- /dev/null +++ b/tslang/test/tester/tests/00typeof_union_narrowing.ts @@ -0,0 +1,44 @@ +// `typeof u === "function"` (also "class", "array") names a kind, not a type. For a union the +// members of that kind are known, so `u` is narrowed to them. It was narrowed to Opaque instead: +// a function could not be called and a class field could not be resolved. +class Box { + value = 5; +} + +function one(): number { + return 1; +} + +function main() { + let f: (() => number) | string = one; + if (typeof f === "function") { + assert(f() == 1, "function member is callable"); + } else { + assert(false, "function: wrong branch"); + } + + f = "abc"; + if (typeof f !== "function") { + assert(f.length == 3, "string member after !=="); + } else { + assert(false, "string: wrong branch"); + } + + let c: Box | number = new Box(); + if (typeof c === "class") { + assert(c.value == 5, "class member field"); + } else { + assert(false, "class: wrong branch"); + } + + const numbers: number[] = [1, 2, 3]; + let a: number[] | string = numbers; + if (typeof a === "array") { + assert(a.length == 3, "array member length"); + assert(a[2] == 3, "array member element"); + } else { + assert(false, "array: wrong branch"); + } + + print("done."); +}