From 649e38bf2106c88da7517a29fef9c4565c758343 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Sun, 13 Sep 2026 14:54:54 +0100 Subject: [PATCH 1/3] Enhance const function export handling and add related tests --- tslang/lib/TypeScript/MLIRGenFunctions.cpp | 3 +- tslang/lib/TypeScript/MLIRGenImpl.h | 34 ++++++++++++++++--- tslang/lib/TypeScript/MLIRGenVariables.cpp | 28 ++++++++++++++- tslang/test/tester/CMakeLists.txt | 3 ++ .../tester/tests/export_const_functions.ts | 30 ++++++++++++++++ .../tester/tests/import_const_functions.ts | 14 ++++++++ 6 files changed, 106 insertions(+), 6 deletions(-) create mode 100644 tslang/test/tester/tests/export_const_functions.ts create mode 100644 tslang/test/tester/tests/import_const_functions.ts diff --git a/tslang/lib/TypeScript/MLIRGenFunctions.cpp b/tslang/lib/TypeScript/MLIRGenFunctions.cpp index 28f11cba7..f357c59db 100644 --- a/tslang/lib/TypeScript/MLIRGenFunctions.cpp +++ b/tslang/lib/TypeScript/MLIRGenFunctions.cpp @@ -390,7 +390,8 @@ namespace mlirgen if (dllExport) { if (functionLikeDeclarationBaseAST == SyntaxKind::FunctionDeclaration - || functionLikeDeclarationBaseAST == SyntaxKind::ArrowFunction) + || functionLikeDeclarationBaseAST == SyntaxKind::ArrowFunction + || functionLikeDeclarationBaseAST == SyntaxKind::FunctionExpression) { addFunctionDeclarationToExport(funcProto, currentNamespace); } diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index 83d40b0cd..0fd60bb7a 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -1819,7 +1819,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 { @@ -1939,7 +1941,16 @@ class MLIRGenImpl if (variableDeclarationInfo.isExternal) { - if (mlir::failed(variableDeclarationInfo.getVariableTypeAndInit(location, genContext))) + // A module imported as source declares its globals here. A const function's + // initializer is still generated (see mlirGen(VariableDeclaration)), and has to + // name the function after the const, as the library's initialization does. + GenContext genContextWithNameReceiver(genContext); + if (variableDeclarationInfo.isConst) + { + genContextWithNameReceiver.receiverName = variableDeclarationInfo.variableName; + } + + if (mlir::failed(variableDeclarationInfo.getVariableTypeAndInit(location, genContextWithNameReceiver))) { return mlir::failure(); } @@ -2169,7 +2180,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.as()->asteriskToken); + if (!isNamedByReceiver) { genContextWithTypeReceiver.receiverName = StringRef(); genContextWithTypeReceiver.isGlobalVarReceiver = false; @@ -10242,7 +10258,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..26f7864b7 100644 --- a/tslang/lib/TypeScript/MLIRGenVariables.cpp +++ b/tslang/lib/TypeScript/MLIRGenVariables.cpp @@ -764,6 +764,20 @@ namespace mlirgen } #endif + // `const f = () => ...` / `const f = function () {...}` at module level is not a variable + // once generated: the function takes the name `f` and the global is erased + // (isGlobalConstLambda), so calls go to the function directly. Every module has to agree + // on that, or an importer looks for a variable `f` holding a pointer that the library + // never defines - an undefined symbol when linked, a call through code bytes under the JIT. + auto isModuleLevelConstFunction = !genContext.funcOp + && (varClass.type == VariableType::Const) + && item->name == SyntaxKind::Identifier + && item->initializer + && (item->initializer == SyntaxKind::ArrowFunction || item->initializer == SyntaxKind::FunctionExpression) + && item->initializer.as()->typeParameters.size() == 0 + // a generator is rewritten into a wrapper, which is not named after the const + && !item->initializer.as()->asteriskToken; + auto initFunc = [&](mlir::Location location, const GenContext &genContext) { // A module imported as source only declares what it defines, so a module-level // variable gets its type and no initializer code. Not a local: a function body is only @@ -772,7 +786,10 @@ namespace mlirgen // its name to that value, and a destructuring pattern reads its elements from it: // without one, reading the const failed with "can't resolve name" and destructuring // with "failed statement" or a crash. - if (declarationMode && !genContext.funcOp) + // + // Nor a module-level const function: generating its initializer declares the function + // (declaration mode gives it no body), which is what the library defines. + if (declarationMode && !genContext.funcOp && !isModuleLevelConstFunction) { auto [t, b, p] = evaluateTypeAndInit(item, genContext); return std::make_tuple(t, mlir::Value(), p ? TypeProvided::Yes : TypeProvided::No); @@ -827,6 +844,15 @@ namespace mlirgen valClassItem = VariableType::ConstRef; } + // An exported const function is exported as the function: it gets the `export` attribute + // and a function declaration in __decls (see mlirGenFunctionPrototype). Exporting the + // const as well would declare a variable that no module defines. + if (isModuleLevelConstFunction && valClassItem.isExport) + { + item->initializer->internalFlags |= InternalFlags::DllExport; + valClassItem.isExport = false; + } + if (!genContext.funcOp && (item->name == SyntaxKind::ObjectBindingPattern || item->name == SyntaxKind::ArrayBindingPattern)) { auto name = MLIRHelper::getAnonymousName(location, ".gc", ""); 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..04a19b1fe --- /dev/null +++ b/tslang/test/tester/tests/export_const_functions.ts @@ -0,0 +1,30 @@ +// The library side of import_const_functions.ts. A module-level `const` initialized with an arrow +// function or a function expression compiles to a function of that name, with no variable. Its +// importers used to look for a variable holding a pointer instead: 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; +}; + +const notExported = (n: number) => n + 100; + +export function usesNotExported(n: number) { + return notExported(n); +} + +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..2e282683e --- /dev/null +++ b/tslang/test/tester/tests/import_const_functions.ts @@ -0,0 +1,14 @@ +import './export_const_functions' + +// Calling exported const functions 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(usesNotExported(1) == 101, "usesNotExported"); + assert(NS.inNamespace(2) == 12, "NS.inNamespace"); + + print("done."); +} From 65f5b68b6cd7b01ff200a512e7928da546451037 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Sun, 13 Sep 2026 15:05:40 +0100 Subject: [PATCH 2/3] Treat an exported const as let; keep const-function globals that are not the function Follows the design agreed for #311: an exported module-level const is a variable, like an exported let, instead of being exported as a function. This replaces the DllExport / declaration-mode handling from the previous commit. - mlirGen(VariableDeclarationList): an exported, module-level, non-import const becomes Let. Its global stays, __decls prints `let`, and importers read the value out of it. - adjustGlobalVariableType: a global other modules reach (export, import, external) holds a function as a hybrid function, as a local let does. The library inferred a plain function while importers read the __decls type back as hybrid, so -shared loaded a larger value than was stored: `export let f = () => n * 3; f(2)` printed 0. - isGlobalConstLambda erases a const's global only when its initializer is a function named after the const. `const alias = namedFunction` and a module-level `const g = function* () {}` (a generator wrapper is named otherwise) lost their global and then failed with "can't resolve name". Tests: export_const_functions.ts adds an exported alias, an exported let arrow and a non-exported generator const. Static, -shared and -jit -shared pass; suite 2,713 of 2,713. Not covered: an exported generator (`export function* g()` or `export const g = function* ...`) is still wrong under -shared - its __decls return type prints the boxed generator object as a value tuple. Fails the same way on main. Co-Authored-By: Claude Opus 5 --- tslang/lib/TypeScript/MLIRGenFunctions.cpp | 3 +- tslang/lib/TypeScript/MLIRGenImpl.h | 42 +++++++++++++------ tslang/lib/TypeScript/MLIRGenVariables.cpp | 37 +++++----------- .../tester/tests/export_const_functions.ts | 33 +++++++++++++-- .../tester/tests/import_const_functions.ts | 6 ++- 5 files changed, 74 insertions(+), 47 deletions(-) diff --git a/tslang/lib/TypeScript/MLIRGenFunctions.cpp b/tslang/lib/TypeScript/MLIRGenFunctions.cpp index f357c59db..28f11cba7 100644 --- a/tslang/lib/TypeScript/MLIRGenFunctions.cpp +++ b/tslang/lib/TypeScript/MLIRGenFunctions.cpp @@ -390,8 +390,7 @@ namespace mlirgen if (dllExport) { if (functionLikeDeclarationBaseAST == SyntaxKind::FunctionDeclaration - || functionLikeDeclarationBaseAST == SyntaxKind::ArrowFunction - || functionLikeDeclarationBaseAST == SyntaxKind::FunctionExpression) + || functionLikeDeclarationBaseAST == SyntaxKind::ArrowFunction) { addFunctionDeclarationToExport(funcProto, currentNamespace); } diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index 0fd60bb7a..e1856ad18 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) @@ -1941,16 +1954,7 @@ class MLIRGenImpl if (variableDeclarationInfo.isExternal) { - // A module imported as source declares its globals here. A const function's - // initializer is still generated (see mlirGen(VariableDeclaration)), and has to - // name the function after the const, as the library's initialization does. - GenContext genContextWithNameReceiver(genContext); - if (variableDeclarationInfo.isConst) - { - genContextWithNameReceiver.receiverName = variableDeclarationInfo.variableName; - } - - if (mlir::failed(variableDeclarationInfo.getVariableTypeAndInit(location, genContextWithNameReceiver))) + if (mlir::failed(variableDeclarationInfo.getVariableTypeAndInit(location, genContext))) { return mlir::failure(); } @@ -2040,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(); diff --git a/tslang/lib/TypeScript/MLIRGenVariables.cpp b/tslang/lib/TypeScript/MLIRGenVariables.cpp index 26f7864b7..20d765054 100644 --- a/tslang/lib/TypeScript/MLIRGenVariables.cpp +++ b/tslang/lib/TypeScript/MLIRGenVariables.cpp @@ -764,20 +764,6 @@ namespace mlirgen } #endif - // `const f = () => ...` / `const f = function () {...}` at module level is not a variable - // once generated: the function takes the name `f` and the global is erased - // (isGlobalConstLambda), so calls go to the function directly. Every module has to agree - // on that, or an importer looks for a variable `f` holding a pointer that the library - // never defines - an undefined symbol when linked, a call through code bytes under the JIT. - auto isModuleLevelConstFunction = !genContext.funcOp - && (varClass.type == VariableType::Const) - && item->name == SyntaxKind::Identifier - && item->initializer - && (item->initializer == SyntaxKind::ArrowFunction || item->initializer == SyntaxKind::FunctionExpression) - && item->initializer.as()->typeParameters.size() == 0 - // a generator is rewritten into a wrapper, which is not named after the const - && !item->initializer.as()->asteriskToken; - auto initFunc = [&](mlir::Location location, const GenContext &genContext) { // A module imported as source only declares what it defines, so a module-level // variable gets its type and no initializer code. Not a local: a function body is only @@ -786,10 +772,7 @@ namespace mlirgen // its name to that value, and a destructuring pattern reads its elements from it: // without one, reading the const failed with "can't resolve name" and destructuring // with "failed statement" or a crash. - // - // Nor a module-level const function: generating its initializer declares the function - // (declaration mode gives it no body), which is what the library defines. - if (declarationMode && !genContext.funcOp && !isModuleLevelConstFunction) + if (declarationMode && !genContext.funcOp) { auto [t, b, p] = evaluateTypeAndInit(item, genContext); return std::make_tuple(t, mlir::Value(), p ? TypeProvided::Yes : TypeProvided::No); @@ -844,15 +827,6 @@ namespace mlirgen valClassItem = VariableType::ConstRef; } - // An exported const function is exported as the function: it gets the `export` attribute - // and a function declaration in __decls (see mlirGenFunctionPrototype). Exporting the - // const as well would declare a variable that no module defines. - if (isModuleLevelConstFunction && valClassItem.isExport) - { - item->initializer->internalFlags |= InternalFlags::DllExport; - valClassItem.isExport = false; - } - if (!genContext.funcOp && (item->name == SyntaxKind::ObjectBindingPattern || item->name == SyntaxKind::ArrayBindingPattern)) { auto name = MLIRHelper::getAnonymousName(location, ".gc", ""); @@ -959,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/tests/export_const_functions.ts b/tslang/test/tester/tests/export_const_functions.ts index 04a19b1fe..2b1c46ed1 100644 --- a/tslang/test/tester/tests/export_const_functions.ts +++ b/tslang/test/tester/tests/export_const_functions.ts @@ -1,7 +1,8 @@ -// The library side of import_const_functions.ts. A module-level `const` initialized with an arrow -// function or a function expression compiles to a function of that name, with no variable. Its -// importers used to look for a variable holding a pointer instead: an undefined symbol when linked, -// a call through the function's code bytes under the JIT. +// 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; @@ -19,12 +20,36 @@ export const arrowWithConstLocal = (n: number) => { 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 index 2e282683e..dc6e3d6ea 100644 --- a/tslang/test/tester/tests/import_const_functions.ts +++ b/tslang/test/tester/tests/import_const_functions.ts @@ -1,13 +1,17 @@ import './export_const_functions' -// Calling exported const functions from another module. See export_const_functions.ts. +// 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."); From f6d7a9948e24906ff569e35ed720a1db54612362 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Sun, 13 Sep 2026 15:26:13 +0100 Subject: [PATCH 3/3] Fix the Linux build: template disambiguator in getTypeAndInit getTypeAndInit is a template over the declaration type, so `initializer.as()` is a dependent member template call. GCC rejects it ("expected primary-expression before '>' token"); MSVC accepted it. Spelled `initializer.template as<...>()`. Co-Authored-By: Claude Opus 5 --- tslang/lib/TypeScript/MLIRGenImpl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index e1856ad18..b51705180 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -2200,7 +2200,7 @@ class MLIRGenImpl // which must not take the receiver's name auto isNamedByReceiver = initializer == SyntaxKind::ArrowFunction || (initializer == SyntaxKind::FunctionExpression - && !initializer.as()->asteriskToken); + && !initializer.template as()->asteriskToken); if (!isNamedByReceiver) { genContextWithTypeReceiver.receiverName = StringRef();