From 03d80fdad7b5c351497f3c22e923ba77a900fa30 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sat, 7 Mar 2026 15:32:24 +0100 Subject: [PATCH 1/3] [Beam] Fix double-quoted atoms for uppercase CompiledName DU cases quoteErlangAtom was called in Fable2Beam.fs before storing atom names into the Atom AST node, but the printer also calls quoteErlangAtom when rendering AtomLit. This caused uppercase atoms like EXIT to be emitted as ''EXIT'' (double single-quotes) instead of 'EXIT'. Fix: remove quoteErlangAtom calls from Fable2Beam.fs so quoting only happens once at print time in ErlangPrinter.fs. Co-Authored-By: Claude Opus 4.6 --- src/Fable.Transforms/Beam/Fable2Beam.fs | 12 +++++------- tests/Beam/InteropTests.fs | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/Fable.Transforms/Beam/Fable2Beam.fs b/src/Fable.Transforms/Beam/Fable2Beam.fs index 3720f6749..10deefd0b 100644 --- a/src/Fable.Transforms/Beam/Fable2Beam.fs +++ b/src/Fable.Transforms/Beam/Fable2Beam.fs @@ -104,9 +104,8 @@ let private getUnionCaseAtomExpr (com: IBeamCompiler) (ref: EntityRef) (tag: int | Some name -> name | None -> sanitizeErlangName uci.Name - let atomStr = quoteErlangAtom atomName let isFieldless = uci.UnionCaseFields.IsEmpty - Some(atomStr, isFieldless) + Some(atomName, isFieldless) | None -> None let private getDecisionTarget (ctx: Context) targetIndex = @@ -1919,7 +1918,6 @@ and transformReceive (com: IBeamCompiler) (ctx: Context) (emitInfo: EmitInfo) (t | Some name -> name | None -> sanitizeErlangName uci.Name - let atomStr = quoteErlangAtom atomName let fields = uci.UnionCaseFields let fieldCount = fields.Length @@ -1928,20 +1926,20 @@ and transformReceive (com: IBeamCompiler) (ctx: Context) (emitInfo: EmitInfo) (t let pattern = if fieldCount = 0 then - Beam.ErlPattern.PLiteral(Beam.ErlLiteral.AtomLit(Beam.Atom atomStr)) + Beam.ErlPattern.PLiteral(Beam.ErlLiteral.AtomLit(Beam.Atom atomName)) else Beam.ErlPattern.PTuple( - Beam.ErlPattern.PLiteral(Beam.ErlLiteral.AtomLit(Beam.Atom atomStr)) + Beam.ErlPattern.PLiteral(Beam.ErlLiteral.AtomLit(Beam.Atom atomName)) :: (fieldVars |> List.map Beam.ErlPattern.PVar) ) // Build body: atom-tagged DU representation let body = if fieldCount = 0 then - Beam.ErlExpr.Literal(Beam.ErlLiteral.AtomLit(Beam.Atom atomStr)) // bare atom + Beam.ErlExpr.Literal(Beam.ErlLiteral.AtomLit(Beam.Atom atomName)) // bare atom else Beam.ErlExpr.Tuple( - Beam.ErlExpr.Literal(Beam.ErlLiteral.AtomLit(Beam.Atom atomStr)) + Beam.ErlExpr.Literal(Beam.ErlLiteral.AtomLit(Beam.Atom atomName)) :: (fieldVars |> List.map Beam.ErlExpr.Variable) ) diff --git a/tests/Beam/InteropTests.fs b/tests/Beam/InteropTests.fs index 5baa1a289..8f253827d 100644 --- a/tests/Beam/InteropTests.fs +++ b/tests/Beam/InteropTests.fs @@ -97,6 +97,7 @@ type RecvMsg = | Ping | [] CustomTag of value: int | DataMsg of x: int * y: string + | [] Exit of pid: int * reason: string #endif @@ -336,6 +337,20 @@ let ``test Erlang receive with multi-field DU case`` () = () #endif +[] +let ``test Erlang receive with uppercase CompiledName atom tag`` () = +#if FABLE_COMPILER + // Send {'EXIT', 1, <<"normal">>} to self — uppercase atoms must be single-quoted + emitErlExpr () "erlang:self() ! {'EXIT', 1, <<\"normal\">>}" + match Erlang.receive 1000 with + | Some(Exit(pid, reason)) -> + equal 1 pid + equal "normal" reason + | _ -> equal 0 1 // fail +#else + () +#endif + [] let ``test Erlang receive blocking with self-send`` () = #if FABLE_COMPILER From abe05f28812e4a0838bb6b249167d661f82bbe52 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sat, 7 Mar 2026 15:34:51 +0100 Subject: [PATCH 2/3] Update changelogs for double-quoted atom fix Co-Authored-By: Claude Opus 4.6 --- src/Fable.Cli/CHANGELOG.md | 1 + src/Fable.Compiler/CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Fable.Cli/CHANGELOG.md b/src/Fable.Cli/CHANGELOG.md index ce4f2c654..cbf972b88 100644 --- a/src/Fable.Cli/CHANGELOG.md +++ b/src/Fable.Cli/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * [Beam] Fix dropped top-level side effects — multiple `ActionDeclaration` `main/0` functions are now merged (by @dbrattli) * [Beam] Fix `%%` escape in string interpolation producing double `%` instead of single (by @dbrattli) +* [Beam] Fix double-quoted atoms for uppercase `CompiledName` on DU cases (e.g. `''EXIT''` → `'EXIT'`) (by @dbrattli) * [Beam] Support `Type.GetGenericArguments` and `Type.GetInterface` for reflection (by @dbrattli) * [TS] Correctly resolve type references for `TypeScriptTaggedUnion` (by @MangelMaxime and @jrwone0) * [TS] Expose optional `stack` property on `Exception` (by @MangelMaxime) diff --git a/src/Fable.Compiler/CHANGELOG.md b/src/Fable.Compiler/CHANGELOG.md index d69bd0448..5d160d93a 100644 --- a/src/Fable.Compiler/CHANGELOG.md +++ b/src/Fable.Compiler/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * [Beam] Fix dropped top-level side effects — multiple `ActionDeclaration` `main/0` functions are now merged (by @dbrattli) * [Beam] Fix `%%` escape in string interpolation producing double `%` instead of single (by @dbrattli) +* [Beam] Fix double-quoted atoms for uppercase `CompiledName` on DU cases (e.g. `''EXIT''` → `'EXIT'`) (by @dbrattli) * [Beam] Support `Type.GetGenericArguments` and `Type.GetInterface` for reflection (by @dbrattli) * [TS] Correctly resolve type references for `TypeScriptTaggedUnion` (by @MangelMaxime and @jrwone0) * [Python] Fix `nonlocal`/`global` declarations generated inside `match/case` bodies causing `SyntaxError` (by @dbrattli) From c1ae1921e90e8769c5bc6a0bec96a5e399ff3145 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sat, 7 Mar 2026 16:12:59 +0100 Subject: [PATCH 3/3] [Beam] Fix cross-project imports with same-named source files resolveImportModuleName compared derived module names (e.g., "types") to decide if an import is local. When two projects both have Types.fs, the import was incorrectly treated as a local call, producing infinite self-recursion (e.g., empty_handle() -> empty_handle()). Fix: compare resolved absolute file paths instead of module names. Co-Authored-By: Claude Opus 4.6 --- src/Fable.Cli/CHANGELOG.md | 1 + src/Fable.Compiler/CHANGELOG.md | 1 + src/Fable.Transforms/Beam/Fable2Beam.fs | 17 +++++++++++++++-- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Fable.Cli/CHANGELOG.md b/src/Fable.Cli/CHANGELOG.md index cbf972b88..c7bc3c3f8 100644 --- a/src/Fable.Cli/CHANGELOG.md +++ b/src/Fable.Cli/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * [Beam] Fix dropped top-level side effects — multiple `ActionDeclaration` `main/0` functions are now merged (by @dbrattli) * [Beam] Fix `%%` escape in string interpolation producing double `%` instead of single (by @dbrattli) * [Beam] Fix double-quoted atoms for uppercase `CompiledName` on DU cases (e.g. `''EXIT''` → `'EXIT'`) (by @dbrattli) +* [Beam] Fix cross-project imports producing self-recursive calls when source files share the same name (by @dbrattli) * [Beam] Support `Type.GetGenericArguments` and `Type.GetInterface` for reflection (by @dbrattli) * [TS] Correctly resolve type references for `TypeScriptTaggedUnion` (by @MangelMaxime and @jrwone0) * [TS] Expose optional `stack` property on `Exception` (by @MangelMaxime) diff --git a/src/Fable.Compiler/CHANGELOG.md b/src/Fable.Compiler/CHANGELOG.md index 5d160d93a..ec176b098 100644 --- a/src/Fable.Compiler/CHANGELOG.md +++ b/src/Fable.Compiler/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * [Beam] Fix dropped top-level side effects — multiple `ActionDeclaration` `main/0` functions are now merged (by @dbrattli) * [Beam] Fix `%%` escape in string interpolation producing double `%` instead of single (by @dbrattli) * [Beam] Fix double-quoted atoms for uppercase `CompiledName` on DU cases (e.g. `''EXIT''` → `'EXIT'`) (by @dbrattli) +* [Beam] Fix cross-project imports producing self-recursive calls when source files share the same name (by @dbrattli) * [Beam] Support `Type.GetGenericArguments` and `Type.GetInterface` for reflection (by @dbrattli) * [TS] Correctly resolve type references for `TypeScriptTaggedUnion` (by @MangelMaxime and @jrwone0) * [Python] Fix `nonlocal`/`global` declarations generated inside `match/case` bodies causing `SyntaxError` (by @dbrattli) diff --git a/src/Fable.Transforms/Beam/Fable2Beam.fs b/src/Fable.Transforms/Beam/Fable2Beam.fs index 10deefd0b..645f3672c 100644 --- a/src/Fable.Transforms/Beam/Fable2Beam.fs +++ b/src/Fable.Transforms/Beam/Fable2Beam.fs @@ -124,9 +124,22 @@ let private matchTargetIdentAndValues idents values = /// Resolve the Erlang module name for an import, returning None if it's the current module. let resolveImportModuleName (com: IBeamCompiler) (importPath: string) = let name = moduleNameFromFile importPath - let currentModule = moduleNameFromFile com.CurrentFile - if name = currentModule then + // Resolve the import path to an absolute path so we can reliably compare + // against the current file. Import paths may be relative (e.g., "../Foo/Types.fs") + // or absolute. Without resolving, two different files with the same base name + // (e.g., Agent/Types.fs vs Reactive/Types.fs) would both produce module name "types" + // and the import would be incorrectly treated as a local (self-recursive) call. + let resolvedImportPath = + if System.IO.Path.IsPathRooted(importPath) then + System.IO.Path.GetFullPath(importPath) + else + let currentDir = System.IO.Path.GetDirectoryName(com.CurrentFile) + System.IO.Path.GetFullPath(System.IO.Path.Combine(currentDir, importPath)) + + let currentFileFull = System.IO.Path.GetFullPath(com.CurrentFile) + + if resolvedImportPath = currentFileFull then None else Some name