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
14 changes: 13 additions & 1 deletion tslang/include/TypeScript/DOM.h
Original file line number Diff line number Diff line change
Expand Up @@ -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>;

FunctionPrototypeDOM(StringRef name, std::vector<FunctionParamDOM::TypePtr> 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)
{
}

Expand Down Expand Up @@ -310,6 +313,15 @@ class FunctionPrototypeDOM
discovered = discovered_;
}

bool getDiscardsReturnValue() const
{
return discardsReturnValue;
}
void setDiscardsReturnValue(bool discardsReturnValue_)
{
discardsReturnValue = discardsReturnValue_;
}

bool getHasCapturedVars() const
{
return hasCapturedVars;
Expand Down
3 changes: 3 additions & 0 deletions tslang/include/TypeScript/MLIRLogic/MLIRGenContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h
Original file line number Diff line number Diff line change
Expand Up @@ -1264,6 +1264,10 @@ struct NamespaceInfo

llvm::StringMap<mlir_ts::FunctionType> 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<bool> functionsDiscardingReturnValue;

llvm::StringMap<FunctionEntry> functionMap;

llvm::StringMap<GenericFunctionInfo::TypePtr> genericFunctionMap;
Expand Down
43 changes: 42 additions & 1 deletion tslang/lib/TypeScript/MLIRGenExpressions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1174,8 +1174,49 @@ namespace mlirgen
return mlir::failure();
}

// `f<number>(() => 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<mlir_ts::SymbolRefOp>();
symbolOp && symbolOp->hasAttrOfType<mlir::BoolAttr>(GENERIC_ATTR_NAME))
{
auto functionGenericTypeInfo = getGenericFunctionInfoByFullName(symbolOp.getIdentifierAttr().getValue());
// a type argument that is itself not known yet (`__is<V, T>(...)` 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<mlir::Value, 4> 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;

Expand Down
27 changes: 26 additions & 1 deletion tslang/lib/TypeScript/MLIRGenFunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<mlir_ts::VoidType>(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<mlir_ts::VoidType>(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))
{
Expand All @@ -519,6 +535,8 @@ namespace mlirgen
}
}

getFunctionsDiscardingReturnValue()[fullName] = funcProto->getDiscardsReturnValue();

// create funcType
if (funcProto->getReturnType())
{
Expand All @@ -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())
{
Expand Down Expand Up @@ -1807,6 +1830,7 @@ namespace mlirgen

GenContext funcGenContext(genContext);
funcGenContext.funcOp = funcOp;
funcGenContext.discardReturnValue = funcProto->getDiscardsReturnValue();

auto *blockPtr = funcOp.addEntryBlock();
auto &entryBlock = *blockPtr;
Expand Down Expand Up @@ -2015,6 +2039,7 @@ namespace mlirgen

GenContext funcGenContext(genContext);
funcGenContext.funcOp = funcOp;
funcGenContext.discardReturnValue = false;

auto *blockPtr = funcOp.addEntryBlock();
auto &entryBlock = *blockPtr;
Expand Down
28 changes: 22 additions & 6 deletions tslang/lib/TypeScript/MLIRGenGenerics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -974,12 +974,28 @@ namespace mlirgen
}
else
{
// e.g. `function foo<T>(): 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<T = string>()` or `function foo<T extends {}>()` 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
Expand Down
11 changes: 11 additions & 0 deletions tslang/lib/TypeScript/MLIRGenImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}

Expand Down Expand Up @@ -15539,6 +15545,11 @@ class MLIRGenImpl
return currentNamespace->functionTypeMap;
}

auto getFunctionsDiscardingReturnValue() -> llvm::StringMap<bool> &
{
return currentNamespace->functionsDiscardingReturnValue;
}

auto lookupFunctionTypeMap(StringRef name) -> mlir_ts::FunctionType
{
lookupLogic(functionTypeMap);
Expand Down
15 changes: 14 additions & 1 deletion tslang/lib/TypeScript/MLIRGenStatements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ namespace mlirgen
auto result = mlirGen(body.as<Expression>(), 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
Expand Down Expand Up @@ -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<mlir_ts::ReturnOp>(location);
return mlir::success();
}

if (auto expression = returnStatementAST->expression)
{
GenContext receiverTypeGenContext(genContext);
Expand Down
11 changes: 11 additions & 0 deletions tslang/lib/TypeScript/MLIRGenTypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down Expand Up @@ -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 }
};

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions tslang/test/tester/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand Down
58 changes: 58 additions & 0 deletions tslang/test/tester/tests/00callback_result_ignored.ts
Original file line number Diff line number Diff line change
@@ -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<T extends string>(n: (x: T) => void) {
n("a" as T);
}

function someGenerics2b<T extends string, U extends number>(n: (x: T, y: U) => void) {
n("b" as T, 2 as U);
}

function someGenerics4<T, U extends number>(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<T>(producer: () => T) {
return producer();
}

function main() {
someGenerics2a((n: string) => n);
someGenerics2a<string>((n: string) => n);
someGenerics2b((n: string, x: number) => n);
someGenerics2b<string, number>((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<any>(() => undefined) === undefined, "any");
assert(produce<number | undefined>(() => undefined) === undefined, "number | undefined");
assert(produce<number>(() => 2) == 2, "number");

print("done.");
}
Loading
Loading