diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index 83d40b0cd..b51705180 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -1661,6 +1661,19 @@ class MLIRGenImpl auto actualType = variableDeclarationInfo.typeProvided == TypeProvided::Yes ? type : mth.wideStorageType(type); + // A global other modules reach holds a function the way a local `let` does + // (adjustLocalVariableType): as a hybrid function. Its declaration in __decls, + // `let f : (p0: number) => number`, reads back as one, so a library inferring a plain + // function from its initializer stored a smaller value than its importers loaded - the + // call's arguments landed in the wrong place and it returned garbage. + if (variableDeclarationInfo.isExport || variableDeclarationInfo.isImport || variableDeclarationInfo.isExternal) + { + if (auto funcType = dyn_cast(actualType)) + { + actualType = mlir_ts::HybridFunctionType::get(builder.getContext(), funcType); + } + } + variableDeclarationInfo.setType(actualType); if (variableDeclarationInfo.initial && actualType != type) @@ -1819,7 +1832,9 @@ class MLIRGenImpl GenContext genContextWithNameReceiver(genContext); if (variableDeclarationInfo.isConst) { - genContextWithNameReceiver.receiverName = variableDeclarationInfo.fullName; + // the short name: getNameOfFunction qualifies it with the namespace itself, and the + // function's name-without-namespace is what __decls prints inside `namespace X { }` + genContextWithNameReceiver.receiverName = variableDeclarationInfo.variableName; } else { @@ -2029,11 +2044,23 @@ class MLIRGenImpl mlir::LogicalResult isGlobalConstLambda(mlir::Location location, struct VariableDeclarationInfo &variableDeclarationInfo, const GenContext &genContext) { - if (variableDeclarationInfo.isConst - && variableDeclarationInfo.initial + // Only when the function IS the const: its initializer named the function after it (an + // arrow function or function expression, see getNameWithArguments), so erasing the global + // leaves the name resolving to that function. A const that merely holds a function made + // under another name - `const alias = plainFn`, or a generator's wrapper - must keep its + // global, or nothing named after it is left: "can't resolve name" in the module itself, + // an undefined symbol in its importers. + if (variableDeclarationInfo.isConst + && variableDeclarationInfo.initial && mth.isAnyFunctionType(variableDeclarationInfo.type)) { - return mlir::success(); + if (auto symbolRefOp = variableDeclarationInfo.initial.getDefiningOp()) + { + if (symbolRefOp.getIdentifier() == variableDeclarationInfo.fullName) + { + return mlir::success(); + } + } } return mlir::failure(); @@ -2169,7 +2196,12 @@ class MLIRGenImpl // so if arrow is part of call, it will be considered as receiver of initialization which is wrong, // example: const seq = f( (x) => x + 1 ); // seq will become name of function - if (initializer != SyntaxKind::ArrowFunction) + // a generator function expression is rewritten into a wrapper (mlirGenFunctionGenerator), + // which must not take the receiver's name + auto isNamedByReceiver = initializer == SyntaxKind::ArrowFunction + || (initializer == SyntaxKind::FunctionExpression + && !initializer.template as()->asteriskToken); + if (!isNamedByReceiver) { genContextWithTypeReceiver.receiverName = StringRef(); genContextWithTypeReceiver.isGlobalVarReceiver = false; @@ -10242,7 +10274,17 @@ class MLIRGenImpl } else if (declarationAST == SyntaxKind::FunctionExpression) { - name = MLIRHelper::getAnonymousName(loc_check(declarationAST), ".fe", ""); + // like an arrow function: `const f = function () {...}` is the function `f`, which + // is how another module refers to it (see mlirGen(VariableDeclaration)). A generator + // arrives without a receiver name: the variable's initializer clears it for one. + if (!genContext.receiverName.empty()) + { + name = genContext.receiverName.str(); + } + else + { + name = MLIRHelper::getAnonymousName(loc_check(declarationAST), ".fe", ""); + } } else if (declarationAST == SyntaxKind::ClassExpression) { diff --git a/tslang/lib/TypeScript/MLIRGenVariables.cpp b/tslang/lib/TypeScript/MLIRGenVariables.cpp index eaac4d56c..20d765054 100644 --- a/tslang/lib/TypeScript/MLIRGenVariables.cpp +++ b/tslang/lib/TypeScript/MLIRGenVariables.cpp @@ -933,6 +933,15 @@ namespace mlirgen }); } + // An exported module-level const is a variable, as an exported `let` is. A const may be + // folded away inside its module - a const function becomes the function and its global is + // erased (isGlobalConstLambda) - but other modules reach it by symbol, so it has to be the + // global they import: an importer reads `export const f = () => ...` out of variable `f`. + if (varClass.type == VariableType::Const && !isUsing && varClass.isExport && !varClass.isImport && !genContext.funcOp) + { + varClass.type = VariableType::Let; + } + for (auto &item : variableDeclarationListAST->declarations) { // we need it for support "undefined type" in 'let' without initialization diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 31037cd0f..414af6d33 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -976,6 +976,7 @@ add_test(NAME test-compile-export-import-class-interface COMMAND test-runner "${ # went unmeasured for so long. add_test(NAME test-compile-export-import-owned-returns COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/import_owned_returns.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_owned_returns.ts") add_test(NAME test-compile-export-import-const-locals COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/import_const_locals.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_const_locals.ts") +add_test(NAME test-compile-export-import-const-functions COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/import_const_functions.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_const_functions.ts") add_test(NAME test-compile-rc-export-import-owned-returns COMMAND test-runner -mm=rc "${PROJECT_SOURCE_DIR}/test/tester/tests/import_owned_returns.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_owned_returns.ts") add_test(NAME test-compile-none-export-import-owned-returns COMMAND test-runner -mm=none "${PROJECT_SOURCE_DIR}/test/tester/tests/import_owned_returns.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_owned_returns.ts") add_test(NAME test-compile-export-import-class-extends COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_extends.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_extends.ts") @@ -1060,6 +1061,7 @@ add_test(NAME test-compile-shared-export-import-owned-returns COMMAND test-runne # linked Boehm statically beside a library on gc.dll. add_test(NAME test-compile-shared-export-import-gc-single-collector COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_gc_single_collector.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_gc_single_collector.ts") add_test(NAME test-compile-shared-export-import-const-locals COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_const_locals.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_const_locals.ts") +add_test(NAME test-compile-shared-export-import-const-functions COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_const_functions.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_const_functions.ts") add_test(NAME test-compile-shared-export-import-class-extends COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_extends.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_extends.ts") add_test(NAME test-compile-shared-export-import-class-extends-implements-diamond COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_extends_implements_diamond.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_extends_implements_diamond.ts") add_test(NAME test-compile-shared-export-import-class-extends-multilevel COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_extends_multilevel.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_extends_multilevel.ts") @@ -1143,6 +1145,7 @@ add_test(NAME test-jit-shared-export-import-object-literal-with-class-types COMM add_test(NAME test-jit-shared-export-import-owned-returns COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_owned_returns.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_owned_returns.ts") add_test(NAME test-jit-shared-export-import-gc-single-collector COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_gc_single_collector.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_gc_single_collector.ts") add_test(NAME test-jit-shared-export-import-const-locals COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_const_locals.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_const_locals.ts") +add_test(NAME test-jit-shared-export-import-const-functions COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_const_functions.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_const_functions.ts") add_test(NAME test-jit-shared-export-import-class-extends COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_extends.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_extends.ts") add_test(NAME test-jit-shared-export-import-class-extends-multilevel COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_extends_multilevel.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_extends_multilevel.ts") add_test(NAME test-jit-shared-export-import-class-extends-implements-diamond COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_class_extends_implements_diamond.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_class_extends_implements_diamond.ts") diff --git a/tslang/test/tester/tests/export_const_functions.ts b/tslang/test/tester/tests/export_const_functions.ts new file mode 100644 index 000000000..2b1c46ed1 --- /dev/null +++ b/tslang/test/tester/tests/export_const_functions.ts @@ -0,0 +1,55 @@ +// The library side of import_const_functions.ts. An exported module-level `const` holding a +// function is a variable, as an exported `let` is: importers read the function pointer out of it. +// Importers used to look for such a variable while the library had turned the const into a +// function and erased it - an undefined symbol when linked, a call through the function's code +// bytes under the JIT. + +export const arrowBlock = (n: number) => { + let s = n * 3; + return s; +}; + +export const arrowExpression = (n: number) => n * 4; + +export const functionExpression = function (n: number) { + return n * 5; +}; + +export const arrowWithConstLocal = (n: number) => { + const s = `v-${n}`; + return s; +}; + +export function namedFunction(n: number) { + return n * 7; +} + +// holds a function made under another name +export const alias = namedFunction; + +export let letArrow = (n: number) => n * 8; + +const notExported = (n: number) => n + 100; + +export function usesNotExported(n: number) { + return notExported(n); +} + +// a generator's wrapper is not named after the const, so the const must keep its global +const localGenerator = function* () { + yield 3; + yield 4; +}; + +export function usesLocalGenerator() { + let sum = 0; + for (const v of localGenerator()) { + sum = sum + v; + } + + return sum; +} + +namespace NS { + export const inNamespace = (n: number) => n * 6; +} diff --git a/tslang/test/tester/tests/import_const_functions.ts b/tslang/test/tester/tests/import_const_functions.ts new file mode 100644 index 000000000..dc6e3d6ea --- /dev/null +++ b/tslang/test/tester/tests/import_const_functions.ts @@ -0,0 +1,18 @@ +import './export_const_functions' + +// Calling functions held by exported module-level consts from another module. +// See export_const_functions.ts. + +function main() { + assert(arrowBlock(2) == 6, "arrowBlock"); + assert(arrowExpression(2) == 8, "arrowExpression"); + assert(functionExpression(2) == 10, "functionExpression"); + assert(arrowWithConstLocal(7) == "v-7", "arrowWithConstLocal"); + assert(alias(2) == 14, "alias"); + assert(letArrow(2) == 16, "letArrow"); + assert(usesNotExported(1) == 101, "usesNotExported"); + assert(usesLocalGenerator() == 7, "usesLocalGenerator"); + assert(NS.inNamespace(2) == 12, "NS.inNamespace"); + + print("done."); +}