From 9b5ff3f1311f7fae8dded65c328bba3de73de3f6 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Wed, 28 Oct 2026 16:28:37 +0000 Subject: [PATCH 1/4] A generic call with nothing to infer from takes the default, then the constraint `function noParams()` called as `noParams()` (and even `function f()` as `f()`) failed with "generic type arguments cannot be inferred": instantiateSpecializedFunction rejected a call with no type arguments and no parameters outright. It now fills the type parameters from their defaults, then their constraints (zipTypeParametersWithDefaultArguments, as the inference path already does), and reports only a parameter with neither. Test: 00generic_default_no_inference (docs/not_impl/not_impl27.ts). Co-Authored-By: Claude Opus 5.5 --- tslang/lib/TypeScript/MLIRGenGenerics.cpp | 28 +++++++++++++++---- tslang/test/tester/CMakeLists.txt | 3 ++ .../tests/00generic_default_no_inference.ts | 27 ++++++++++++++++++ 3 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 tslang/test/tester/tests/00generic_default_no_inference.ts diff --git a/tslang/lib/TypeScript/MLIRGenGenerics.cpp b/tslang/lib/TypeScript/MLIRGenGenerics.cpp index e092d15f9..bc3c28349 100644 --- a/tslang/lib/TypeScript/MLIRGenGenerics.cpp +++ b/tslang/lib/TypeScript/MLIRGenGenerics.cpp @@ -974,12 +974,28 @@ namespace mlirgen } else { - // e.g. `function foo(): T { ... }` called as `foo()` - no explicit type - // argument and no parameters/call operands to infer `T` from. Confirmed via - // repro. Real TS rejects this too ("type argument cannot be inferred from - // usage"); fail cleanly here instead of crashing. - emitError(location) << "generic type arguments for '" << name << "' cannot be inferred"; - return {mlir::failure(), mlir_ts::FunctionType(), ""}; + // e.g. `function foo()` or `function foo()` called as + // `foo()` - no explicit type argument and no parameters/call operands to infer + // `T` from: TS takes the default, then the constraint + auto [resultDefArg, hasNamedGenericType] = zipTypeParametersWithDefaultArguments( + location, typeParams, typeArguments, genericTypeGenContext.typeParamsWithArgs, instantiateGenContext); + if (mlir::failed(resultDefArg)) + { + return {mlir::failure(), mlir_ts::FunctionType(), ""}; + } + + if (hasNamedGenericType == IsGeneric::True) + { + anyNamedGenericType = hasNamedGenericType; + } + + if (genericTypeGenContext.typeParamsWithArgs.size() < typeParams.size()) + { + // neither a default nor a constraint: TS infers `unknown`, which a native + // function can't be specialised for + emitError(location) << "generic type arguments for '" << name << "' cannot be inferred"; + return {mlir::failure(), mlir_ts::FunctionType(), ""}; + } } // we need to wide all types when initializing function diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index f0800b4e4..caa5c8653 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -613,6 +613,7 @@ tslang_add_test(NAME test-compile-00-class-super-static COMMAND test-runner "${P tslang_add_test(NAME test-compile-00-class-default-constructor COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_def_constr.ts") tslang_add_test(NAME test-compile-00-class-constructor-fields COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_constr_fields.ts") tslang_add_test(NAME test-compile-00-class-constructor-overloads COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_constructor_overloads.ts") +tslang_add_test(NAME test-compile-00-generic-default-no-inference COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00generic_default_no_inference.ts") tslang_add_test(NAME test-compile-00-class-static-constructor COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_static_constr.ts") tslang_add_test(NAME test-compile-00-class-abstract COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_abstract.ts") tslang_add_test(NAME test-compile-00-class-interface COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_iface.ts") @@ -1225,6 +1226,7 @@ tslang_add_test(NAME test-jit-00-class-static-constructor COMMAND test-runner -j tslang_add_test(NAME test-jit-00-class-constructor-fields COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_constr_fields.ts") tslang_add_test(NAME test-jit-00-class-constructor-overloads COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_constructor_overloads.ts") tslang_add_test(NAME test-jit-00-class-abstract COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_abstract.ts") +tslang_add_test(NAME test-jit-00-generic-default-no-inference COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00generic_default_no_inference.ts") tslang_add_test(NAME test-jit-00-class-interface COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_iface.ts") tslang_add_test(NAME test-jit-00-class-generic COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_generic.ts") tslang_add_test(NAME test-jit-00-class-generic-interface COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_generic_iface.ts") @@ -2172,6 +2174,7 @@ set(TSLANG_CORPUS 00class_constructor_overloads.ts 00class_deconst.ts 00class_def_constr.ts + 00generic_default_no_inference.ts 00class_discover_types.ts 00class_expression.ts 00class_expression2.ts diff --git a/tslang/test/tester/tests/00generic_default_no_inference.ts b/tslang/test/tester/tests/00generic_default_no_inference.ts new file mode 100644 index 000000000..9cca9f541 --- /dev/null +++ b/tslang/test/tester/tests/00generic_default_no_inference.ts @@ -0,0 +1,27 @@ +// a generic function called with nothing to infer its type parameters from takes the +// default, then the constraint (docs/not_impl/not_impl27.ts) + +function noParams() { + print("noParams"); +} + +function withDefault(): T { + return 5 as T; +} + +function twoDefaults(): string { + return typeof (0 as U); +} + +function main() { + noParams(); + noParams(); + noParams<{}>(); + + assert(withDefault() + 1 == 6, "default"); + assert(withDefault() == "5", "explicit"); + + assert(twoDefaults() == "number", "two defaults"); + + print("done."); +} From 51644f600ee6523687eb910e7721d01913539f77 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Wed, 28 Oct 2026 16:29:09 +0000 Subject: [PATCH 2/4] `Promise` is T An async function runs to completion and returns its value (an `await` waits for the expression it awaits), but writing that type down failed: "generic type Promise can't be found", so `async function f(): Promise` - the usual way to write one - did not compile. `Promise`, `PromiseLike` and `Awaited` are now embedded types that stand for their type argument, with and without builtins. A program's own `Promise` is still found first. Test: 00promise_type (docs/not_impl/not_impl7.ts). Co-Authored-By: Claude Opus 5.5 --- tslang/lib/TypeScript/MLIRGenTypes.cpp | 11 +++++++ tslang/test/tester/CMakeLists.txt | 7 +++-- tslang/test/tester/tests/00promise_type.ts | 36 ++++++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 tslang/test/tester/tests/00promise_type.ts diff --git a/tslang/lib/TypeScript/MLIRGenTypes.cpp b/tslang/lib/TypeScript/MLIRGenTypes.cpp index 6dc2ec47f..f4fb74dc9 100644 --- a/tslang/lib/TypeScript/MLIRGenTypes.cpp +++ b/tslang/lib/TypeScript/MLIRGenTypes.cpp @@ -938,6 +938,9 @@ namespace mlirgen {"Partial", true }, {"Required", true }, {"ThisType", true }, + {"Promise", true }, // an async function returns its value, see getEmbeddedTypeWithParam + {"PromiseLike", true }, + {"Awaited", true }, {"NonNullable", true }, //{"Array", true }, //{"ReadonlyArray", true }, @@ -1026,6 +1029,9 @@ namespace mlirgen {"Shared", true }, // a counted handle, own spec 23 {"CVarArgs", true }, // a C variadic function type, see getCVarArgsType {"ThisType", true }, + {"Promise", true }, // an async function returns its value, see getEmbeddedTypeWithParam + {"PromiseLike", true }, + {"Awaited", true }, //{"Array", true } }; @@ -1187,6 +1193,9 @@ namespace mlirgen .Case("Shared", EmbeddedType::Shared) .Case("CVarArgs", EmbeddedType::CVarArgs) .Cases("Readonly", "Partial", "Required", "ThisType", EmbeddedType::FirstTypeArgument) + // an async function runs to completion and returns its value (an `await` waits for + // the expression it awaits), so a promise of T is T + .Cases("Promise", "PromiseLike", "Awaited", EmbeddedType::FirstTypeArgument) .Case("NonNullable", EmbeddedType::NonNullable) #ifdef ARRAY_TYPE_AS_ARRAY_CLASS .Case("Array", EmbeddedType::Array) @@ -1291,6 +1300,8 @@ namespace mlirgen .Case("Shared", EmbeddedType::Shared) .Case("CVarArgs", EmbeddedType::CVarArgs) .Case("ThisType", EmbeddedType::ThisType) + // a promise of T is T, see getEmbeddedTypeWithParamBuiltins + .Cases("Promise", "PromiseLike", "Awaited", EmbeddedType::ThisType) #ifdef ARRAY_TYPE_AS_ARRAY_CLASS .Case("Array", EmbeddedType::Array) #endif diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index caa5c8653..3a128391b 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -614,6 +614,7 @@ tslang_add_test(NAME test-compile-00-class-default-constructor COMMAND test-runn tslang_add_test(NAME test-compile-00-class-constructor-fields COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_constr_fields.ts") tslang_add_test(NAME test-compile-00-class-constructor-overloads COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_constructor_overloads.ts") tslang_add_test(NAME test-compile-00-generic-default-no-inference COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00generic_default_no_inference.ts") +tslang_add_test(NAME test-compile-00-promise-type COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00promise_type.ts") tslang_add_test(NAME test-compile-00-class-static-constructor COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_static_constr.ts") tslang_add_test(NAME test-compile-00-class-abstract COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_abstract.ts") tslang_add_test(NAME test-compile-00-class-interface COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_iface.ts") @@ -1225,8 +1226,9 @@ tslang_add_test(NAME test-jit-00-class-default-constructor COMMAND test-runner - tslang_add_test(NAME test-jit-00-class-static-constructor COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_static_constr.ts") tslang_add_test(NAME test-jit-00-class-constructor-fields COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_constr_fields.ts") tslang_add_test(NAME test-jit-00-class-constructor-overloads COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_constructor_overloads.ts") -tslang_add_test(NAME test-jit-00-class-abstract COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_abstract.ts") tslang_add_test(NAME test-jit-00-generic-default-no-inference COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00generic_default_no_inference.ts") +tslang_add_test(NAME test-jit-00-promise-type COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00promise_type.ts") +tslang_add_test(NAME test-jit-00-class-abstract COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_abstract.ts") tslang_add_test(NAME test-jit-00-class-interface COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_iface.ts") tslang_add_test(NAME test-jit-00-class-generic COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_generic.ts") tslang_add_test(NAME test-jit-00-class-generic-interface COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_generic_iface.ts") @@ -2172,9 +2174,10 @@ set(TSLANG_CORPUS 00class_accessor2.ts 00class_constr_fields.ts 00class_constructor_overloads.ts + 00generic_default_no_inference.ts + 00promise_type.ts 00class_deconst.ts 00class_def_constr.ts - 00generic_default_no_inference.ts 00class_discover_types.ts 00class_expression.ts 00class_expression2.ts diff --git a/tslang/test/tester/tests/00promise_type.ts b/tslang/test/tester/tests/00promise_type.ts new file mode 100644 index 000000000..22ebc20c8 --- /dev/null +++ b/tslang/test/tester/tests/00promise_type.ts @@ -0,0 +1,36 @@ +// an async function runs to completion and returns its value, so `Promise` (and +// `PromiseLike`, `Awaited`) is T (docs/not_impl/not_impl7.ts) + +async function one(): Promise { + return 1; +} + +async function plusOne(): Promise { + const i = await one(); + return 1 + i; +} + +class C { + async m(): Promise { + const i = await plusOne(); + return "m" + i; + } +} + +function main() { + const a = async (): Promise => 1 + await one(); + const fe = async function (): Promise { + const i = await one(); + return 10 + i; + }; + + const w: Awaited> = plusOne(); + const l: PromiseLike = new C().m(); + + assert(w == 2, "plusOne"); + assert(a() == 2, "arrow"); + assert(fe() == 11, "function expression"); + assert(l == "m2", "method"); + + print("done."); +} From dee75f42e5cf57817405b0bfb37ec816f9deca76 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Wed, 28 Oct 2026 16:51:14 +0000 Subject: [PATCH 3/4] A callback whose result is ignored may return one; explicit type arguments type the arguments TS lets an arrow given to a function type that returns nothing return a value: `each(a, s => bump(s))` with `f: (s: string) => void`. Here it failed - "can't find return variable" when the type came from a generic parameter, "one returns a value and the other does not" when it came from a variable's type, and "can't cast from 'null' to 'void'" for `() => null`. - An arrow or function expression with no return type of its own, given to a function type that returns nothing, is generated as returning nothing (FunctionPrototypeDOM::discardsReturnValue, kept per function name so a later prototype of it keeps it). Its concise body and its `return e` statements evaluate the value as a statement does and drop it - no retain, so nothing leaks under rc. - A variable's declared function type is the receiver of its initializer, as an assignment's left side already was. - A call to a generic function with every type argument given specialises the callee before generating the arguments, so `produce(() => undefined)` meets `() => any`, not `() => T` (which no arrow can take its return type from). Test: 00callback_result_ignored (docs/not_impl/not_impl27.ts someGenerics2a/2b/3/4). Co-Authored-By: Claude Opus 5.5 --- tslang/include/TypeScript/DOM.h | 14 ++++- .../TypeScript/MLIRLogic/MLIRGenContext.h | 3 + .../TypeScript/MLIRLogic/MLIRGenStore.h | 4 ++ tslang/lib/TypeScript/MLIRGenExpressions.cpp | 23 +++++++- tslang/lib/TypeScript/MLIRGenFunctions.cpp | 27 ++++++++- tslang/lib/TypeScript/MLIRGenImpl.h | 11 ++++ tslang/lib/TypeScript/MLIRGenStatements.cpp | 15 ++++- tslang/test/tester/CMakeLists.txt | 3 + .../tester/tests/00callback_result_ignored.ts | 58 +++++++++++++++++++ 9 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 tslang/test/tester/tests/00callback_result_ignored.ts diff --git a/tslang/include/TypeScript/DOM.h b/tslang/include/TypeScript/DOM.h index fcfb9cd0b..116c31548 100644 --- a/tslang/include/TypeScript/DOM.h +++ b/tslang/include/TypeScript/DOM.h @@ -249,12 +249,15 @@ class FunctionPrototypeDOM bool hasExtraFields; bool isGenericFunction; bool noBody; + // an arrow or function expression with no return type of its own, given to a function type + // that returns nothing: what its `return` statements return is evaluated and dropped + bool discardsReturnValue; public: using TypePtr = std::shared_ptr; FunctionPrototypeDOM(StringRef name, std::vector params) - : name(name.str()), params(params), funcType(), returnType(), discovered(false), hasCapturedVars(false), hasExtraFields(false), isGenericFunction(false), noBody(false) + : name(name.str()), params(params), funcType(), returnType(), discovered(false), hasCapturedVars(false), hasExtraFields(false), isGenericFunction(false), noBody(false), discardsReturnValue(false) { } @@ -310,6 +313,15 @@ class FunctionPrototypeDOM discovered = discovered_; } + bool getDiscardsReturnValue() const + { + return discardsReturnValue; + } + void setDiscardsReturnValue(bool discardsReturnValue_) + { + discardsReturnValue = discardsReturnValue_; + } + bool getHasCapturedVars() const { return hasCapturedVars; diff --git a/tslang/include/TypeScript/MLIRLogic/MLIRGenContext.h b/tslang/include/TypeScript/MLIRLogic/MLIRGenContext.h index a482e09e4..e4f39aecf 100644 --- a/tslang/include/TypeScript/MLIRLogic/MLIRGenContext.h +++ b/tslang/include/TypeScript/MLIRLogic/MLIRGenContext.h @@ -173,6 +173,9 @@ struct GenContext bool insertIntoParentScope = false; mlir::Operation *currentOperation = nullptr; mlir_ts::FuncOp funcOp; + // funcOp's `return` statements evaluate their value and drop it (an arrow given to a function + // type that returns nothing, see FunctionPrototypeDOM::discardsReturnValue) + bool discardReturnValue = false; // The function a root's own statements are generated in when they are held back from the // module level (generateGlobalEntryCode): its variables are still the module's, and an // exported one is exported (#448). A function nested in them has a funcOp of its own. diff --git a/tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h b/tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h index 4b503f767..3a65c8a45 100644 --- a/tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h +++ b/tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h @@ -1264,6 +1264,10 @@ struct NamespaceInfo llvm::StringMap functionTypeMap; + // arrows and function expressions generated as returning nothing whose returns drop the value, + // see FunctionPrototypeDOM::discardsReturnValue: a later prototype of one keeps it + llvm::StringMap functionsDiscardingReturnValue; + llvm::StringMap functionMap; llvm::StringMap genericFunctionMap; diff --git a/tslang/lib/TypeScript/MLIRGenExpressions.cpp b/tslang/lib/TypeScript/MLIRGenExpressions.cpp index 478cede5d..e6eecebcf 100644 --- a/tslang/lib/TypeScript/MLIRGenExpressions.cpp +++ b/tslang/lib/TypeScript/MLIRGenExpressions.cpp @@ -1174,8 +1174,29 @@ namespace mlirgen return mlir::failure(); } + // `f(() => undefined)`: with every type argument given, specialise first, so that an + // argument meets the parameter types it is passed as (`() => number`), not `() => T` - an + // arrow takes its return type from the parameter it is given to + if (mth.isGenericType(funcType) && callExpression->typeArguments.size() > 0) + { + if (auto symbolOp = funcResult.getDefiningOp(); + symbolOp && symbolOp->hasAttrOfType(GENERIC_ATTR_NAME)) + { + auto functionGenericTypeInfo = getGenericFunctionInfoByFullName(symbolOp.getIdentifierAttr().getValue()); + if (functionGenericTypeInfo + && functionGenericTypeInfo->typeParams.size() == callExpression->typeArguments.size()) + { + SmallVector emptyOperands; + auto specialized = mlirGenSpecialized(location, funcResult, callExpression->typeArguments, emptyOperands, genContext); + EXIT_IF_FAILED_OR_NO_VALUE(specialized) + funcResult = V(specialized); + funcType = funcResult.getType(); + } + } + } + // so if method is generic and you need to infer types you can cast to generic types - auto noReceiverTypesForGenericCall = + auto noReceiverTypesForGenericCall = mth.isGenericType(funcResult.getType()) && callExpression->typeArguments.size() == 0; diff --git a/tslang/lib/TypeScript/MLIRGenFunctions.cpp b/tslang/lib/TypeScript/MLIRGenFunctions.cpp index dfb003a26..648d7a84a 100644 --- a/tslang/lib/TypeScript/MLIRGenFunctions.cpp +++ b/tslang/lib/TypeScript/MLIRGenFunctions.cpp @@ -509,7 +509,23 @@ namespace mlirgen auto retTypeFromReceiver = mth.isAnyFunctionType(argTypeDestFuncType) ? mth.getReturnTypeFromFuncRef(argTypeDestFuncType) : mlir::Type(); - if (retTypeFromReceiver + auto ownReturnType = funcProto->getReturnType(); + if (ownReturnType + && !isa(ownReturnType) + && mth.isAnyFunctionType(argTypeDestFuncType) + // a declared `void` only: a receiver with no result, or a null one, is one whose + // result is not known yet (`(i: T) => R` while R is inferred from this arrow, + // a builtin array method's callback) + && isa_and_nonnull(retTypeFromReceiver) + && (functionLikeDeclarationBaseAST == SyntaxKind::ArrowFunction + || functionLikeDeclarationBaseAST == SyntaxKind::FunctionExpression)) + { + // `(x: string) => void` given `n => n`: TS lets a callback whose result is + // ignored return one. Generated as returning nothing, its returns drop the value. + funcProto->setReturnType(mlir::Type()); + funcProto->setDiscardsReturnValue(true); + } + else if (retTypeFromReceiver && !mth.isNoneType(retTypeFromReceiver) && !mth.isGenericType(retTypeFromReceiver)) { @@ -519,6 +535,8 @@ namespace mlirgen } } + getFunctionsDiscardingReturnValue()[fullName] = funcProto->getDiscardsReturnValue(); + // create funcType if (funcProto->getReturnType()) { @@ -541,6 +559,11 @@ namespace mlirgen funcType = functionDiscovered; } + if (!detectReturnType && getFunctionsDiscardingReturnValue().lookup(fullName)) + { + funcProto->setDiscardsReturnValue(true); + } + // we need it, when we run rediscovery second time if (!funcProto->getHasExtraFields()) { @@ -1807,6 +1830,7 @@ namespace mlirgen GenContext funcGenContext(genContext); funcGenContext.funcOp = funcOp; + funcGenContext.discardReturnValue = funcProto->getDiscardsReturnValue(); auto *blockPtr = funcOp.addEntryBlock(); auto &entryBlock = *blockPtr; @@ -2015,6 +2039,7 @@ namespace mlirgen GenContext funcGenContext(genContext); funcGenContext.funcOp = funcOp; + funcGenContext.discardReturnValue = false; auto *blockPtr = funcOp.addEntryBlock(); auto &entryBlock = *blockPtr; diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index 7bb06b73b..9bc10e244 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -3032,6 +3032,12 @@ class MLIRGenImpl if (type) { genContextWithTypeReceiver.receiverType = type; + // as an assignment does: an arrow takes its return type from the function type + if (mth.isAnyFunctionType(type)) + { + genContextWithTypeReceiver.receiverFuncType = type; + } + LLVM_DEBUG(dbgs() << "\n!! variable receiverType " << type << "\n"); } @@ -15539,6 +15545,11 @@ class MLIRGenImpl return currentNamespace->functionTypeMap; } + auto getFunctionsDiscardingReturnValue() -> llvm::StringMap & + { + return currentNamespace->functionsDiscardingReturnValue; + } + auto lookupFunctionTypeMap(StringRef name) -> mlir_ts::FunctionType { lookupLogic(functionTypeMap); diff --git a/tslang/lib/TypeScript/MLIRGenStatements.cpp b/tslang/lib/TypeScript/MLIRGenStatements.cpp index faaa8db1b..ab5150efa 100644 --- a/tslang/lib/TypeScript/MLIRGenStatements.cpp +++ b/tslang/lib/TypeScript/MLIRGenStatements.cpp @@ -39,7 +39,7 @@ namespace mlirgen auto result = mlirGen(body.as(), genContext); EXIT_IF_FAILED(result) auto resultValue = V(result); - if (resultValue) + if (resultValue && !genContext.discardReturnValue) { // A concise arrow body (`() => expr`) returns without going through the return // statement, so it did not get the retain that makes every function hand back @@ -522,6 +522,19 @@ namespace mlirgen finishedReturn->_end = returnStatementAST->_end; return mlirGen(finishedReturn, genContext); } + if (auto expression = returnStatementAST->expression; expression && genContext.discardReturnValue) + { + // a callback given to a function type that returns nothing: the value is evaluated as + // a statement's is, and dropped + auto result = mlirGen(expression, genContext); + EXIT_IF_FAILED(result) + + EXIT_IF_FAILED(mlirGenScopeExit(location, DisposeDepth::FullStack, {}, &genContext)); + + builder.create(location); + return mlir::success(); + } + if (auto expression = returnStatementAST->expression) { GenContext receiverTypeGenContext(genContext); diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 3a128391b..9a776d402 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -613,6 +613,7 @@ tslang_add_test(NAME test-compile-00-class-super-static COMMAND test-runner "${P tslang_add_test(NAME test-compile-00-class-default-constructor COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_def_constr.ts") tslang_add_test(NAME test-compile-00-class-constructor-fields COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_constr_fields.ts") tslang_add_test(NAME test-compile-00-class-constructor-overloads COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_constructor_overloads.ts") +tslang_add_test(NAME test-compile-00-callback-result-ignored COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00callback_result_ignored.ts") tslang_add_test(NAME test-compile-00-generic-default-no-inference COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00generic_default_no_inference.ts") tslang_add_test(NAME test-compile-00-promise-type COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00promise_type.ts") tslang_add_test(NAME test-compile-00-class-static-constructor COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_static_constr.ts") @@ -1226,6 +1227,7 @@ tslang_add_test(NAME test-jit-00-class-default-constructor COMMAND test-runner - tslang_add_test(NAME test-jit-00-class-static-constructor COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_static_constr.ts") tslang_add_test(NAME test-jit-00-class-constructor-fields COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_constr_fields.ts") tslang_add_test(NAME test-jit-00-class-constructor-overloads COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_constructor_overloads.ts") +tslang_add_test(NAME test-jit-00-callback-result-ignored COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00callback_result_ignored.ts") tslang_add_test(NAME test-jit-00-generic-default-no-inference COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00generic_default_no_inference.ts") tslang_add_test(NAME test-jit-00-promise-type COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00promise_type.ts") tslang_add_test(NAME test-jit-00-class-abstract COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_abstract.ts") @@ -2174,6 +2176,7 @@ set(TSLANG_CORPUS 00class_accessor2.ts 00class_constr_fields.ts 00class_constructor_overloads.ts + 00callback_result_ignored.ts 00generic_default_no_inference.ts 00promise_type.ts 00class_deconst.ts diff --git a/tslang/test/tester/tests/00callback_result_ignored.ts b/tslang/test/tester/tests/00callback_result_ignored.ts new file mode 100644 index 000000000..c29f441f8 --- /dev/null +++ b/tslang/test/tester/tests/00callback_result_ignored.ts @@ -0,0 +1,58 @@ +// a callback whose result is ignored may return one: given to a function type that returns +// nothing, an arrow (or function expression) without a return type of its own evaluates what it +// returns and drops it (docs/not_impl/not_impl27.ts) + +class Box { + v = 1; +} + +let count = 0; + +function bump(s: string) { + count++; + return s.length; +} + +function each(a: string[], f: (s: string) => void) { + for (const s of a) f(s); +} + +function someGenerics2a(n: (x: T) => void) { + n("a" as T); +} + +function someGenerics2b(n: (x: T, y: U) => void) { + n("b" as T, 2 as U); +} + +function someGenerics4(n: T, f: (x: U) => void) { + f(1 as U); +} + +// with every type argument given, an arrow takes its return type from the specialised parameter +function produce(producer: () => T) { + return producer(); +} + +function main() { + someGenerics2a((n: string) => n); + someGenerics2a((n: string) => n); + someGenerics2b((n: string, x: number) => n); + someGenerics2b((n: string, t: number) => n); + someGenerics4(4, (x) => null); + + const cb: (x: string) => void = (n: string) => n; + cb("x"); + + each(["a", "bb"], s => bump(s)); + each(["a", "bb"], s => { return bump(s); }); + each(["a"], function (s) { return new Box(); }); + each(["a"], s => new Box()); + assert(count == 4, "count"); + + assert(produce(() => undefined) === undefined, "any"); + assert(produce(() => undefined) === undefined, "number | undefined"); + assert(produce(() => 2) == 2, "number"); + + print("done."); +} From 692f96dd39e9909d031e7aec9593d7c17ef4f3fe Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Wed, 28 Oct 2026 17:09:53 +0000 Subject: [PATCH 4/4] Specialise ahead of the arguments only with known type arguments, and with the call's own operands The head start dee75f42 gives a call with every type argument given took the GenContext of the expression around it, and with it the operands of the call this one is an argument of: `__is(this[0])` inside DefaultLib's Array.reduce was specialised from reduce's own operands, which broke `[].reduce((x, y) => x + y)` (array_reduce, array_reduce_empty, array_reduce3, map_values_reduce). It now runs with no call operands, only when no type argument is itself still generic, and a failure rolls its diagnostics back and leaves the call to specialise the way it did before. Co-Authored-By: Claude Opus 5.5 --- tslang/lib/TypeScript/MLIRGenExpressions.cpp | 30 ++++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/tslang/lib/TypeScript/MLIRGenExpressions.cpp b/tslang/lib/TypeScript/MLIRGenExpressions.cpp index e6eecebcf..bff71b465 100644 --- a/tslang/lib/TypeScript/MLIRGenExpressions.cpp +++ b/tslang/lib/TypeScript/MLIRGenExpressions.cpp @@ -1183,14 +1183,34 @@ namespace mlirgen symbolOp && symbolOp->hasAttrOfType(GENERIC_ATTR_NAME)) { auto functionGenericTypeInfo = getGenericFunctionInfoByFullName(symbolOp.getIdentifierAttr().getValue()); + // a type argument that is itself not known yet (`__is(...)` in a generic body + // being discovered) leaves it to the call, as before + auto allTypeArgumentsKnown = llvm::all_of(callExpression->typeArguments, [&](TypeNode typeArgument) { + auto type = getType(typeArgument, genContext); + return type && !mth.isGenericType(type); + }); if (functionGenericTypeInfo - && functionGenericTypeInfo->typeParams.size() == callExpression->typeArguments.size()) + && functionGenericTypeInfo->typeParams.size() == callExpression->typeArguments.size() + && allTypeArgumentsKnown + && genContext.postponedMessages) { + // it is only a head start: what fails here fails the same way in the call below, + // which reports it + auto savedErrorMessagesCount = genContext.postponedMessages->size(); + // the operands of a call this one is an argument of are not this call's + GenContext specGenContext(genContext); + specGenContext.callOperands = {}; SmallVector emptyOperands; - auto specialized = mlirGenSpecialized(location, funcResult, callExpression->typeArguments, emptyOperands, genContext); - EXIT_IF_FAILED_OR_NO_VALUE(specialized) - funcResult = V(specialized); - funcType = funcResult.getType(); + auto specialized = mlirGenSpecialized(location, funcResult, callExpression->typeArguments, emptyOperands, specGenContext); + if (mlir::succeeded(specialized.result) && V(specialized)) + { + funcResult = V(specialized); + funcType = funcResult.getType(); + } + else + { + rollbackPostponedErrorMessages(genContext.postponedMessages, savedErrorMessagesCount); + } } } }