From 779e975aef229608604541b7c4026e1b9d13b3ca Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Thu, 24 Sep 2026 00:09:06 +0100 Subject: [PATCH] Erase the half-built ts.If when an optional call fails `f?.(1)` for a callee that needs two arguments failed with TS2554 but left its ts.If in the IR, with a then-block holding no ResultOp and an unfilled else. #352 fixed the same shape in conditionalValue. The if is now erased before the failure is returned. No caller carries on after this failure today: at every site tried (local, module level, class field, template literal, ??, typeof) the compile stops with the TS2554 error alone, on main as well. This keeps it that way for a caller that does. The new call-arity case, optional_call_value, uses the result inside an expression at module level, in a global's initializer. Co-Authored-By: Claude Opus 5.5 --- tslang/lib/TypeScript/MLIRGenAccessCall.cpp | 10 ++++++++-- tslang/test/tester/CMakeLists.txt | 3 ++- tslang/test/tester/call-arity/optional_call_value.ts | 8 ++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 tslang/test/tester/call-arity/optional_call_value.ts diff --git a/tslang/lib/TypeScript/MLIRGenAccessCall.cpp b/tslang/lib/TypeScript/MLIRGenAccessCall.cpp index a5d5fb8ab..5bbf0edac 100644 --- a/tslang/lib/TypeScript/MLIRGenAccessCall.cpp +++ b/tslang/lib/TypeScript/MLIRGenAccessCall.cpp @@ -1427,8 +1427,14 @@ namespace mlirgen auto result = mlirGenCallExpression(location, innerFuncRef, typeArguments, operands, genContext); // a failed call (e.g. too few arguments) must fail the expression, not leave an - // empty branch behind - for a void callee that silently dropped the call - EXIT_IF_FAILED(result) + // empty branch behind - for a void callee that silently dropped the call. Nor may the + // half-built if stay in the IR for a caller that carries on (as in conditionalValue) + if (mlir::failed(result)) + { + builder.setInsertionPoint(ifOp); + ifOp.erase(); + return mlir::failure(); + } auto value = V(result); if (value) { diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index d429687fa..f5fd78178 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -2215,7 +2215,8 @@ set(call_arity_cases "closure|Expected 2 arguments, but got 1\\." "declared|Expected 2 arguments, but got 1\\." "varargs|Expected at least 1 arguments, but got 0\\." - "optional_call|Expected 2 arguments, but got 1\\.") + "optional_call|Expected 2 arguments, but got 1\\." + "optional_call_value|Expected 2 arguments, but got 1\\.") foreach(call_arity_case ${call_arity_cases}) string(FIND "${call_arity_case}" "|" call_arity_sep) string(SUBSTRING "${call_arity_case}" 0 ${call_arity_sep} call_arity_name) diff --git a/tslang/test/tester/call-arity/optional_call_value.ts b/tslang/test/tester/call-arity/optional_call_value.ts new file mode 100644 index 000000000..f175fb044 --- /dev/null +++ b/tslang/test/tester/call-arity/optional_call_value.ts @@ -0,0 +1,8 @@ +// An optional call whose result is used, inside a larger expression, at module level: the +// failed call must take its half-built `if` with it, not leave it in the global's initializer. +let f: ((a: number, b: number) => number) | undefined = (a: number, b: number) => a + b; +const r = `${f?.(1) ?? 0}`; + +function main() { + print(r); +}