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..bff71b465 100644 --- a/tslang/lib/TypeScript/MLIRGenExpressions.cpp +++ b/tslang/lib/TypeScript/MLIRGenExpressions.cpp @@ -1174,8 +1174,49 @@ 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()); + // 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() + && 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, specGenContext); + if (mlir::succeeded(specialized.result) && V(specialized)) + { + funcResult = V(specialized); + funcType = funcResult.getType(); + } + else + { + rollbackPostponedErrorMessages(genContext.postponedMessages, savedErrorMessagesCount); + } + } + } + } + // 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/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/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/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 f0800b4e4..9a776d402 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -613,6 +613,9 @@ 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") 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") @@ -1224,6 +1227,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-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") 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") @@ -2170,6 +2176,9 @@ 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 00class_def_constr.ts 00class_discover_types.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."); +} 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."); +} 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."); +}