Skip to content

Treat exported consts as variables; fix const functions across modules - #313

Merged
ASDAlexander77 merged 3 commits into
mainfrom
fix-exported-const-arrow
Sep 13, 2026
Merged

Treat exported consts as variables; fix const functions across modules#313
ASDAlexander77 merged 3 commits into
mainfrom
fix-exported-const-arrow

Conversation

@ASDAlexander77

@ASDAlexander77 ASDAlexander77 commented Sep 13, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes #311. A module-level const holding a function can now be used from another module. The rule: an exported module-level const is a variable, like an exported let, and importers read the function out of it.

Why it broke

The library compiled export const f = (n) => ... into a function named f: the arrow function took the const's name, and isGlobalConstLambda erased the global. Importers still looked for a variable f:

Before
static multi-file undefined symbol: f
-shared wrong result
-jit -shared crash 0xC0000005: the importer loaded a "pointer" from the function's code bytes

export const g = function () {...} had no g symbol at all.

Fix

  • Exported const → let (mlirGen(VariableDeclarationList)): an exported, module-level, non-import const becomes Let.

    • Its global stays.
    • __decls prints let f : ....
    • A module including the source declares the same variable.
    • Non-exported consts keep the fold-into-a-function optimisation.
  • Function-typed globals other modules reach are hybrid functions (adjustGlobalVariableType), as a local let already was.

    • The __decls type (p0: number) => number reads back as a hybrid function.
    • The library had inferred a plain function, which stores a smaller value, so under -shared the arguments landed in the wrong place. Before this, export let f = (n) => n * 3; f(2) printed 0 under -shared; it now prints 6.
  • isGlobalConstLambda only erases a const that is the function: the initializer is a function named after the const. Otherwise the global is kept, which fixes these cases in the module itself:

    • const alias = namedFunction
    • a module-level const g = function* () {}, whose generator wrapper has another name

    Both failed with can't resolve name before.

  • Function expressions take the const's name like arrow functions. The receiver name is the short name, so a namespaced one no longer prints NS.f inside namespace NS { }.

Tests

import_const_functions.ts / export_const_functions.ts cover:

  • block and expression arrows
  • a function expression
  • an arrow with a const local
  • an exported alias of a named function
  • an exported let arrow
  • a non-exported const arrow used by an exported function
  • a non-exported generator const
  • a const arrow in a namespace

They're registered as static multi-file, -shared, and -jit -shared.

Also checked in all three modes:

  • an async arrow const
  • a const function expression
  • an exported number const
  • let values under -shared: a number literal, a computed number, a pointer to a named function, and an arrow

Not covered

An exported generator is still wrong under -shared, whether export function* g() or export const g = function* .... Its __decls return type prints the boxed generator object as a value tuple (() => {.step:s32, next(): ...}). It fails the same way on main and is filed separately.

Test plan

🤖 Generated with Claude Code

ASDAlexander77 and others added 2 commits September 13, 2026 14:54
…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 <noreply@anthropic.com>
@ASDAlexander77 ASDAlexander77 changed the title Export module-level const functions as functions Treat exported consts as variables; fix const functions across modules Sep 13, 2026
getTypeAndInit is a template over the declaration type, so
`initializer.as<FunctionLikeDeclarationBase>()` 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 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit a25e52a into main Sep 13, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the fix-exported-const-arrow branch September 13, 2026 14:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Exported const arrow function is unresolved across modules (undefined symbol / JIT crash)

1 participant