Skip to content

[WIP] Fable Beam Initial Scaffolding#4340

Draft
dbrattli wants to merge 133 commits intomainfrom
fable-beam
Draft

[WIP] Fable Beam Initial Scaffolding#4340
dbrattli wants to merge 133 commits intomainfrom
fable-beam

Conversation

@dbrattli
Copy link
Collaborator

@dbrattli dbrattli commented Feb 8, 2026

Summary

Add a new Erlang/BEAM compilation target to Fable, enabling F# code to be compiled to Erlang source (.erl files) and run on the BEAM virtual machine. This leverages the BEAM's lightweight process model and massive concurrency capabilities from F#, with constructs like MailboxProcessor naturally mapping to the actor-based runtime they were inspired by.

1913 Erlang tests passing, 0 failures. Run with ./build.sh test beam.

Progress Checklist

Compiler Infrastructure

  • Language.Beam added to Plugins.fs
  • Beam AST definitions (Beam.AST.fs)
  • F# → Erlang transform (Fable2Beam.fs)
  • Erlang source code printer (ErlangPrinter.fs)
  • Beam-specific BCL replacements (Beam/Replacements.fs)
  • CLI integration (--lang beam / --lang erlang)
  • Build system (./build.sh test beam, quicktest, fable-library)
  • Replacements.Api.fs dispatch for all API functions
  • Erlang keyword escaping in sanitizeErlangName (maybe, receive, etc. → append _)
  • Auto-imported BIF clash detection (apply/2, now/0, etc. → auto no_auto_import)
  • BIF qualification in ErlangPrinter — Call(None, ...) nodes auto-prefixed with erlang: for known BIFs
  • Stray ok atom removal — strips unit values from non-final positions in all body contexts

Core Language (Phase 2) — Complete

  • Arithmetic, comparison, bitwise, logical operators
  • If/else, lambda, delegates, curried apply
  • Pattern matching (DUs, options, lists, tuples, guards)
  • DecisionTree / DecisionTreeSuccess
  • Let/LetRec bindings, mutual recursion (named funs)
  • Tail call optimization (native in Erlang)
  • Mutable variables via process dictionary (with ref erasure at scope exit)
  • For/while loops as tail-recursive funs
  • CurriedApply via simple List.fold (matches JS/Python targets)
  • Custom operator dispatch via SRTP (CustomOp active pattern)

Types (Phase 3) — Complete

  • Discriminated unions → tagged tuples {Tag, Field1, ...}
  • Records → Erlang maps #{field => value}
  • Anonymous records → Erlang maps
  • Structural equality via native =:= (deep comparison)
  • Classes with constructors, properties, methods
  • Object expressions → map-of-closures #{method => fun(...) -> ... end}
  • Interface dispatch via maps:get

Collections (Phase 4) — Complete

  • list<T> → Erlang linked lists (natural fit)
  • array<T> → process dict refs wrapping Erlang lists (mutable via put/get)
  • byte[]atomics module for O(1) mutable read/write ({byte_array, Size, AtomicsRef})
  • Map<K,V> → Erlang native #{} maps
  • Set<T> → Erlang ordsets (sorted lists)
  • Seq<T> → eager Erlang lists + compiled seq.erl for scalar operations
  • Dictionary<K,V> → process dictionary + Erlang maps
  • HashSet<T> → process dictionary + Erlang maps
  • Queue<T> → process dictionary + Erlang lists
  • Stack<T> → process dictionary + Erlang lists
  • ResizeArray<T> → process dictionary + Erlang lists
  • Range expressions, array indexing, array comprehensions

Standard Library — Complete

  • Seq module (156 tests) — scalar ops via compiled seq.erl with auto-uncurried callbacks
  • String module (153 tests)
  • Array module (146 tests) — incl. fill, blit, sortInPlace, byte array atomics
  • Arithmetic / Int64 / BigInt (139 tests)
  • List module (137 tests)
  • Conversions (100 tests) — incl. base conversion, Boolean.Parse, Decimal.Parse/ToString, radix prefixes
  • Applicative (84 tests)
  • TimeSpan (83 tests)
  • Comparison / hash / equality (66 tests) — incl. Unchecked, LanguagePrimitives, Set comparison
  • Misc (59 tests)
  • DateTime (53 tests)
  • Types (50 tests) — classes, interfaces, object expressions, type testing
  • Option module (50 tests)
  • Map module (50 tests)
  • Set module (46 tests)
  • ResizeArray (41 tests)
  • Char module (38 tests)
  • Functions (38 tests)
  • Regex (37 tests) — via Erlang re module (PCRE), named groups
  • Encoding (35 tests) — UTF8, Stopwatch, Nullable, DateTimeOffset
  • Pattern matching (28 tests)
  • Records (22 tests)
  • Result (21 tests)
  • Uri (21 tests)
  • HashSet (20 tests)
  • Enum (20 tests)
  • Union types (18 tests)
  • Interop (17 tests) — emitErl, Import, module calls
  • Dictionary (17 tests)
  • Async (17 tests) — incl. cancellation
  • Queue (17 tests)
  • Tail calls (15 tests)
  • Tuples (15 tests)
  • Reflection (11 tests)
  • And more: loops, GUIDs, exceptions, stacks, seq expressions, custom operators, anonymous records, try/catch, tasks, DateTimeOffset, nullness, MailboxProcessor, Sudoku integration test

Error Handling (Phase 6) — Complete

  • try/catch with erlang:error
  • failwitherlang:error(<<"message">>)
  • Exception .Message access
  • Result<T,E>{ok,V}/{error,E}
  • Custom F# exceptions (exception MyError of string) → maps with exn_type tag
  • Exception type discrimination in catch clauses
  • Multi-field exceptions with named fields

Types & Type Testing (Phase 6b) — Complete

  • Enums → Erlang integers (native, no special handling)
  • Type testing (:?) → is_integer/is_binary/is_float/is_boolean/is_list/is_tuple/is_map
  • Exception type testing via exn_type atom tag
  • box/unbox erased (TypeCast)
  • String interpolation generic formatting (fable_string:to_string/1)
  • Curry expressions — compile-time nested lambda wrapping via curryExprAtRuntime
  • sprintf, printfn, String.Format support
  • System.Tuple/ValueTuple Item1-Item7 access

Async, Task & Processes (Phase 7) — Complete

  • async { } computation expressions via CPS (continuation-passing style)
  • Async.RunSynchronously — runs in same process (preserves process dict)
  • Async.StartImmediate — fire-and-forget in current process
  • Async.Parallel — spawns Erlang processes, collects via message passing
  • Async.Sleeptimer:sleep/1 (cancellation-aware via receive)
  • Async.Ignore, let!, do!, return!, try/with in async
  • Task CE support (alias for Async)
  • MailboxProcessorStart, Post, PostAndAsyncReply via in-process CPS
  • CancellationToken — process dict pattern, cancel/register/cancel_after
  • Runtime: fable_async_builder.erl + fable_async.erl + fable_mailbox.erl + fable_cancellation.erl

BCL / Interop Support — Complete

  • System.Text.Encoding.UTF8 — GetBytes/GetString (identity, Erlang strings are UTF-8 binaries)
  • System.Diagnostics.Stopwatch — StartNew/Elapsed/ElapsedMilliseconds/Stop/Reset via erlang:monotonic_time
  • Nullable<T> — erased (value identity)
  • Unchecked.hash/equals/compare — native Erlang operators
  • Convert.ToInt32/ToString with base parameter — via fable_convert.erl
  • Boolean.Parse, radix-aware int parsing (0x/0o/0b prefixes)
  • DateTime — 2-tuple {Ticks, Kind}, fable_date.erl (53 tests)
  • DateTimeOffset — 3-tuple {Ticks, OffsetTicks, Kind}, fable_date_offset.erl (7 tests)
  • TimeSpan — plain integer (ticks), fable_timespan.erl (83 tests)
  • Guid — UUID v4 via crypto:strong_rand_bytes, fable_guid.erl (10 tests)
  • Uri — parsing and manipulation, fable_uri.erl (21 tests)
  • BitConverter — byte conversion, fable_bit_converter.erl
  • use/IDisposable support
  • StringBuilder — Length, Clear, ToString with index/length
  • Decimal — fixed-scale integer (value × 10^28), fable_decimal.erl

Runtime Library (src/fable-library-beam/)

30 Erlang modules — fable_list.erl, fable_map.erl, fable_string.erl, fable_option.erl, fable_result.erl, fable_seq.erl, fable_set.erl, fable_char.erl, fable_comparison.erl, fable_convert.erl, fable_reflection.erl, fable_regex.erl, fable_resize_array.erl, fable_dictionary.erl, fable_hashset.erl, fable_queue.erl, fable_stack.erl, fable_async_builder.erl, fable_async.erl, fable_mailbox.erl, fable_timespan.erl, fable_date.erl, fable_date_offset.erl, fable_guid.erl, fable_uri.erl, fable_utils.erl, fable_bit_converter.erl, fable_decimal.erl, fable_cancellation.erl, fable_stopwatch.erl

Plus Fable-compiled modules from src/fable-library-beam/: Seq.fsseq.erl, Range.fsrange.erl, System.Text.fssystem_text.erl

Not Yet Implemented

  • OTP patterns (supervision, hot code reload)
  • Nested modules

Design Decisions

F# Concept Erlang Representation
Records Maps #{field => value}
DU cases Tagged tuples {tag, Field1, Field2}
Options None = undefined, Some(x) = x (erased)
Result {ok, V} / {error, E}
Classes make_ref() + process dictionary
Interfaces Map-of-closures #{method => fun}
Exceptions Maps with exn_type atom tag
Sets ordsets (sorted lists)
Arrays Process dict refs wrapping Erlang lists
Byte arrays atomics module ({byte_array, Size, Ref}) for O(1) read/write
Sequences Eager lists + compiled seq.erl for scalar ops
Enums Plain integers
Integers Native arbitrary-precision
Decimal Fixed-scale integer (value × 10^28)
Mutability Process dictionary (with ref erasure at scope exit)
DateTime 2-tuple {Ticks, Kind} (100ns intervals from Jan 1, 0001)
TimeSpan Plain integer (ticks)
Async CPS funs fun(Ctx) -> ok end with #{on_success, on_error, on_cancel}
Task CE Alias for Async (same CPS infrastructure)
MailboxProcessor In-process CPS continuation model
CancellationToken Process dict pattern with timer-based auto-cancel
Curry Compile-time nested lambda wrapping (no runtime module)
Dictionary/HashSet make_ref() + process dictionary + Erlang maps
Queue/Stack make_ref() + process dictionary + Erlang lists
ResizeArray make_ref() + process dictionary + Erlang lists
Regex Erlang re module (PCRE-based)
Encoding.UTF8 Identity (Erlang strings are UTF-8 binaries)
Stopwatch erlang:monotonic_time(microsecond)

Test plan

  • 1913 Erlang tests passing via ./build.sh test beam (48 test files)
  • .NET tests pass in parallel (same test files run on both)
  • Integration test: Sudoku solver using Seq, Array, ranges
  • Integration test: 2048×2048 raytracer (~40s on BEAM, atomics byte arrays)
  • CI: GitHub Actions build-beam job

🤖 Generated with Claude Code

dbrattli and others added 26 commits February 7, 2026 07:24
Implements an F# to Erlang compiler target for the BEAM VM. Includes
AST definitions, Fable-to-Beam transform, Erlang printer, build system
integration, and a test suite covering arithmetic, unions, records,
pattern matching, functions, and lists.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…6 tests passing)

Intercept List module imports in Fable2Beam.fs to map head/tail/length/map/filter/fold/rev/append/sum
to Erlang's built-in hd/tl/length and lists:* functions. Handle fold arg order swap (F# acc,item vs
Erlang item,acc) and drop injected IGenericAdder from sum. Add unary negation in Beam Replacements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… tests passing)

Erlang has native arbitrary-precision integers, so Int64/UInt64/BigInt arithmetic
can use direct binary operators instead of library calls. Intercept standard
arithmetic operators for big integer types in Beam Replacements, same approach
as Python where bigint/nativeint go straight to native int.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…91 tests passing)

Uses a single named fun that dispatches on atom tags to resolve
forward references between mutually recursive functions in Erlang.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… tests passing)

Map String.IsNullOrEmpty to (S =:= undefined) orelse (S =:= <<"">>)
and String.Contains (via indexOf) to binary:match with nomatch fallback.
This fixes util.erl compilation failure from unknown_call.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ules (91 tests, 0 failures)

Add TryCatch to Beam AST and Erlang printer. Caught exceptions are
wrapped in a map with "message" field so e.Message field access works.
Fix test runner to only discover modules ending in _tests, avoiding
false failures from calling util helper functions as tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… 0 failures)

Handle Extended/Throw AST node by generating erlang:error(msg). Intercept
FailWith/InvalidOp/InvalidArg/Raise in Beam Replacements to pass the message
string directly instead of wrapping in a non-existent Exception constructor.
Fix TryCatch message extraction to pass binary reasons through directly using
is_binary guard, avoiding ~p formatting that wraps binaries in <<>> delimiters.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tests, 0 failures)

Add Beam-specific replacements for String instance methods (Length, ToUpper, ToLower,
Trim, StartsWith, EndsWith, Substring, Replace) and Option module functions (defaultValue,
map, bind, isSome, isNone). Implement Emit AST node for inline Erlang code generation
with $0/$1 substitution, and fix guard printing in Case clauses.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… tests, 0 failures)

Implement ForLoop/WhileLoop as tail-recursive named funs, mutable variables
via process dictionary (put/get), type conversions (int/float/string), and
fix string + operator to use iolist_to_binary instead of arithmetic +.
Also fix float literal printing to always include decimal point and Let
value hoisting to prevent incorrect nested match expressions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement arrays as Erlang lists with full Array module support (map, filter,
fold, sort, reduce, etc.), array indexing via lists:nth with 0-to-1 base
conversion, TypeTest using Erlang is_* guards, and NewArray value handling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rators (229 tests, 0 failures)

- Add F# Map → Erlang native #{} maps (ofList, add, find, tryFind, containsKey,
  remove, isEmpty, count, toList, map, filter, fold, exists, forall, empty,
  instance methods)
- Add 22 new List module operations (contains, exists, forall, find, tryFind,
  choose, collect, sort, sortBy, sortDescending, partition, zip, unzip, min,
  max, reduce, concat, singleton, foldBack, indexed)
- Add String.Split, String.Join, String.concat, String.replicate, and more
- Add math operators (abs, sqrt, sin, cos, tan, exp, log, floor, ceil, round,
  pow, sign, min, max) to prevent JS math module fallthrough
- Fix emitExpr variable scoping by wrapping case patterns in (fun() -> end)()
- Add map module import handler in Fable2Beam for JS fallthrough

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Migrate ~46 complex emitExpr patterns (IIFEs, case expressions, arg-reordering
wrappers) from Beam/Replacements.fs to native Erlang modules in
src/fable-library-beam/. Simple 1-to-1 BIF mappings remain as emitExpr.

New library modules:
- fable_list.erl: fold, fold_back, reduce, map_indexed, sort_by/with, find,
  try_find, choose, collect, sum_by, min_by, max_by, indexed, zip
- fable_map.erl: try_find, fold, fold_back, map, filter, exists, forall,
  iterate, find_key, try_find_key, partition, try_get_value
- fable_string.erl: insert, remove, starts_with, ends_with, pad_left/right,
  replace, join, concat, replicate, is_null_or_empty, is_null_or_white_space
- fable_option.erl: default_value, default_with, map, bind

Library functions use curried application (Fn(A))(B) to match Fable's curried
lambda compilation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ailures)

Implement comprehensive Seq module for the Beam target using eager Erlang lists.
Add fable_seq.erl runtime library for Seq-specific operations (delay, unfold, init,
take, skip, distinct, etc.) and seqModule handler in Beam Replacements dispatching
SeqModule + RuntimeHelpers. Also fix map_indexed using element(2,...) instead of
element(1,...) to extract result list from lists:mapfoldl.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…xing (273 tests, 0 failures)

Add range expression support (op_Range/op_RangeStep -> lists:seq), array indexing
(IntrinsicFunctions.GetArray -> lists:nth), and Array.mapi. Add Sudoku solver as
integration test exercising Seq, Array, ranges, and array comprehensions. Update
test runner to filter by test_ prefix and rename RecordTests accordingly. Update
FABLE-BEAM.md to reflect current status (Phase 4 Collections complete, 273 tests).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ts, 0 failures)

Add fable_result.erl runtime library with 17 Result module functions
(map, mapError, bind, isOk, isError, contains, count, defaultValue,
defaultWith, exists, fold, foldBack, forall, iter, toArray, toList,
toOption). Result values use Erlang tagged tuples: Ok x = {0, X},
Error e = {1, E}.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…hods (302 tests, 0 failures)

Implement classes as unique refs with state stored in process dictionary.
Constructors create a ref via make_ref() and store field values in a map.
Instance members receive the ref as first arg and access state via get/put.

Key changes:
- Add transformClassDeclaration for constructor generation from FieldSet IR
- Handle ThisValue/ThisArg/ThisIdentNames for proper this-reference mapping
- Modify FieldGet/FieldSet to use process dict for class instances
- Add isClassType helper excluding BCL/exception types to avoid badmap errors
- Sanitize $ and @ in function/variable names (sanitizeErlangVar)
- Prepend ThisArg to args in transformCall for instance method dispatch

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… tests, 0 failures)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ve instance methods (347 tests, 0 failures)

Add fable_comparison.erl library with compare/2 returning -1/0/1. Handle comparison
operators (< > <= >=), compare, hash, isNull, nullArg, Object.ReferenceEquals,
PhysicalEquality, and .Equals/.CompareTo/.GetHashCode instance methods on strings,
numerics, booleans, chars, System.Object, and System.ValueType. Route System.Math
through operators for Max/Min/Abs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…, 0 failures)

Add fable_char.erl library with character classification (IsLetter, IsDigit, IsUpper,
IsLower, IsWhiteSpace, IsControl, IsPunctuation, IsSeparator, IsSymbol, IsNumber,
IsLetterOrDigit), case conversion (ToUpper, ToLower), ToString, Parse, and
GetUnicodeCategory. Support both single-char and (string, index) overloads.

Fix Char.ToString to emit <<C/utf8>> instead of integer_to_binary for char types.
Expand comparison tests with exception equality, map/array option equality, string
array comparison, hash on tuples/lists/records, and more primitive type coverage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… and parameterless constructors (413 tests, 0 failures)

- Add 14 tail-call tests (factorial, nested recursion, class methods, IIFE, tree search, state preservation)
- Add 9 seq expression tests (yield, yield!, for, combine, multiple yields, recursive traverse, array/list expressions)
- Fix LetRec single-binding to generate Erlang named funs (was only handled for mutual recursion)
- Fix containsIdentRef to handle Emit expressions (seq compilation uses Emit for Node branches)
- Fix parameterless class constructors to accept unit arg (_UnitVar) matching call-site arity

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tests, 0 failures)

- Option: Add 26 tests (orElse, defaultWith, iter, map2/3, contains, filter, fold/foldBack, toArray/toList, flatten, count, forAll, exists, side-effects, defaultArg)
- PatternMatch: Add 17 tests (or-patterns, union matching, guard expressions, nested matching, result/list/char matching, tuple-bool-guard patterns)
- Record: Add 8 tests (recursive records, reserved word fields, mutating records, optional field equality, camel/pascal casing, anonymous record functions/equality)
- UnionType: Add 8 tests (many-arg cases, common targets, Tag field, active patterns, equality in filter)
- Add defaultArg handling in Beam Replacements operators
- Expand fable_option.erl with 14 new functions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…sts, 0 failures)

- Add 50+ new List functions to fable_list.erl: init, replicate, scan, scanBack,
  tryHead, tryLast, tryItem, exactlyOne, tryExactlyOne, distinct, distinctBy,
  pairwise, exists2, forall2, map2, map3, mapi2, iter2, iteri, iteri2, average,
  averageBy, countBy, groupBy, unfold, splitAt, chunkBySize, windowed, splitInto,
  except, allPairs, permute, mapFold, mapFoldBack, pick, tryPick, reduceBack,
  findIndex, tryFindIndex, findBack, tryFindBack, zip3
- Add corresponding Replacements entries for all new List functions
- Expand ListTests.fs from 35 to 94 tests covering all new operations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ic handling (664 tests, 0 failures)

- Add native bitwise operators (band/bor/bxor/bsl/bsr/bnot) instead of JS BigInt fallback
- Fix BigInt literal handling (FromZero/FromOne/FromString) for Erlang native integers
- Add all missing NumberConstant variants (Int8/UInt8/Int16/UInt16/UInt32/UInt64/Float32/etc.)
- Fix float literal precision (%.17g for full double precision)
- Expand System.Convert support for all numeric types
- Expand fable_map.erl with pick/try_pick/min_key_value/max_key_value/change
- Add 95 arithmetic tests, 43 conversion tests, 41 map tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…principles

- Create fable_convert.erl with robust to_float/1 that handles edge cases
  like "1." that Erlang's binary_to_float/1 rejects
- Replace all raw binary_to_float calls with fable_convert:to_float library calls
- Restore original F# test (don't modify tests to accommodate Erlang quirks)
- Update FABLE-BEAM.md: add "Design Principles" section emphasizing Beam as
  an independent target, not inheriting JS/Python patterns unnecessarily
- Update runtime library table and test counts (664 tests, 0 failures)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ilures)

Add comprehensive string support: String module functions (forall, exists, init,
collect, iter, iteri, map, mapi, filter), string constructors, IndexOf/LastIndexOf
with offsets, Trim with chars, Split with options, Contains, Compare, and string
indexing via binary:at.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@github-advanced-security github-advanced-security bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ionide.Analyzers.Cli found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

dbrattli and others added 2 commits February 8, 2026 06:14
…ests, 0 failures)

Add 50+ new Array operations: init, copy, map2/map3, mapi/mapi2, mapFold/mapFoldBack,
scan/scanBack, reduceBack, findIndex, findBack/findIndexBack, tryFindBack/tryFindIndexBack,
pick/tryPick, partition, permute, distinct, skip/skipWhile, take/takeWhile, truncate,
countBy, groupBy, windowed, pairwise, splitInto, transpose, compareWith, updateAt,
insertAt/insertManyAt, removeAt/removeManyAt, average/averageBy, sortDescending, indexed.

Also fix fable_string split functions to handle char separators and options,
fix iteri argument order, and fix trim functions to handle single char arguments.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add build-beam job to CI workflow with Erlang/OTP 27 setup
- Add Beam.AST.fs and Beam/Replacements.fs to Fable.Standalone.fsproj
  so Replacements.Api.fs can resolve Beam.Replacements references
- Add Beam case to standalone Main.fs pattern match (with error message
  since standalone doesn't include the Beam code generator)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@dbrattli dbrattli marked this pull request as draft February 8, 2026 05:49
dbrattli and others added 15 commits February 14, 2026 18:41
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…78 tests, 0 failures)

Add Fable.Core.BeamInterop module with emitErlExpr, emitErlStatement, and import
helpers. Add interop test suite covering Emit attributes, emitErlExpr, Import
attributes (both function declarations and value bindings), erased unions, and
StringEnum. Include native Erlang test module (native_code.erl) for import tests.

Fix CurriedApply handling for UserImport nodes: Erlang requires known arity for
remote calls (module:function/N), unlike JS/Python/Dart where imports produce
first-class function references. When CurriedApply wraps a UserImport, emit a
direct remote call with all args instead of the default incremental fold.

Extract resolveImportModuleName helper to deduplicate module name resolution
across standalone Import, transformCall Import, and CurriedApply Import handlers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…0 failures)

Add SetArray handling for mutable arrays via process dict + fable_resize_array:set_item.
Add Queue`1.Enumerator type dispatch for for-in loop enumeration over queues.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…s, 0 failures)

Lazy values now use the process dictionary pattern (like Ref/Dictionary) instead
of plain thunks, enabling proper memoization and IsValueCreated support.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…of fixed atoms

Use make_ref() via fable_utils:new_ref/1 for process dict keys instead of
fixed atom keys derived from variable names. This prevents state corruption
when nested closures declare mutable variables with the same name.

(1886 tests, 0 failures)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… in containsIdentRef (1887 tests, 0 failures)

containsIdentRef missed self-references inside anonymous records, records,
unions, tuples, and ObjectExpr members, causing let rec to generate broken
module-level calls instead of named funs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add type guard to IsSurrogatePair pattern to fix unreachable match rule
- Use StringComparison.Ordinal in StartsWith call
- Add %s format specifier to interpolated string
- Suppress expected warnings in test files (FS0064, FS3370)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove unused default_ctx/0 in fable_async.erl
- Merge unreachable clause in fable_convert.erl to_float
- Add no_auto_import for ref_to_list/1 in fable_list.erl
- Prefix unused variables with _ in fable_list.erl and fable_queue.erl

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… (1887 tests, 0 failures)

- Add Infinity, InfinitySingle, NaN, NaNSingle via IEEE 754 binary construction
- Add System.Console.WriteLine and Write replacements
- Add console_writeline/1 and console_write/1 to fable_string.erl

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…inary match (1890 tests, 0 failures)

Erlang BEAM VM doesn't support IEEE 754 special float values - the binary
pattern match <<F/float>> = <<0:1, 2047:11, 0:52>> crashes with badmatch.
Use 1.7976931348623157e308 (max finite float) as practical stand-in for
infinity and atom nan for NaN. Updated in Fable2Beam.fs (Float64/Float32
constants), Replacements.fs (operator Infinity/NaN), and fable_utils.erl.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…(1891 tests, 0 failures)

Auto-wrap non-mutable array bindings in process dict refs when the body
contains indexed-set operations (arr.[i] <- v), since F# arrays are
mutable objects even with `let` bindings. Also add binary-aware set_item
for byte arrays and using/2 for IDisposable resource management.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…896 tests, 0 failures)

Broaden mutation detection from containsIndexedSet to containsArrayMutation to
catch ValueSet (from SortInPlace/Fill/Blit) in addition to ExprSet (indexed
assignment). Make in-place array ops return Fable.Set so the Let handler stores
results back to the process dict ref. Add fill/4 and blit/5 to fable_resize_array.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…support

Add fallback ExprSet handler in Fable2Beam.fs so array indexed sets on
non-ref-wrapped bindings emit fable_resize_array:set_item instead of
todo_set. Add fable_stopwatch.erl runtime module and expand Replacements
to handle Stopwatch instance methods (StartNew, Start, Stop, Reset,
ElapsedMilliseconds, etc.). 1896 tests, 0 failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Test StartNew, Start/Stop, IsRunning, ElapsedTicks, ElapsedMilliseconds,
and Reset to verify the fable_stopwatch.erl runtime module.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…07 tests, 0 failures)

Array mutations on function parameters (arr.[i] <- v) were silently discarded
because params weren't in MutableVars. Added wrapMutatedParams helper that
detects array-mutated params and ref-wraps them at Lambda, Delegate,
MemberDeclaration, and LetRec sites. Also un-commented non-mutable let binding
array setter tests that now work, and added 4 new param-mutation tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dbrattli and others added 14 commits February 16, 2026 17:25
…ion (1907 tests, 0 failures)

F# arrays are mutable reference types but Erlang lists are immutable values.
Represent all non-byte arrays as process-dict refs (make_ref + put/get) from
creation, matching the existing ResizeArray/Dictionary pattern. This ensures
mutations in called functions are visible to callers.

Key changes:
- NewArray emits fable_utils:new_ref([...]) instead of bare lists
- derefArr/wrapArr helpers in Replacements for ~80 array operations
- Binary comparison operators on arrays use fable_comparison:compare
- TypeTest uses is_reference instead of is_list for arrays
- Deep structural hash/compare in fable_comparison.erl
- Runtime ref guards in fable_seq, fable_string, fable_async
- Removed old wrapMutatedParams/containsArrayMutation machinery

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tests, 0 failures)

Prefix class constructor field keys with `field_` in the process-dict state
map to avoid collisions with interface method keys that share the same name
after sanitization (e.g., F# `normal` param and `Normal` interface method).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ict leaks (1908 tests, 0 failures)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ailures)

Byte arrays (byte[]) are now backed by Erlang's atomics module instead of
immutable binaries, giving O(1) indexed read and write. Represented as
{byte_array, Size, AtomicsRef} tuples.

Key changes:
- fable_utils.erl: new_byte_array, new_byte_array_zeroed, byte_array_get/set/length
  helpers that handle both direct tuples and process-dict ref-wrapped byte arrays
- Fable2Beam.fs: NewArray, ExprGet, ExprSet, TypeTest updated for atomics
- Replacements.fs: Array.ZeroCreate/Create/Singleton, OfList/OfSeq, GetArray,
  get_Length/get_Item, Encoding.GetBytes/GetString all byte-array-aware
- Runtime: fable_comparison, fable_bit_converter, fable_guid, fable_convert
  updated to handle {byte_array,...} tuples

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…e arrays (1913 tests, 0 failures)

Array.create<byte>(n, v) now emits fable_utils:new_byte_array_filled(N, V)
which populates atomics directly without creating an intermediate Erlang list.
For value 0 (the Array.zeroCreate case), it delegates to new_byte_array_zeroed
which skips population entirely since atomics are zero-initialized.

This eliminates a ~1-2s startup delay for the raytracer's 16M-element buffer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… failures)

F# unit values compile to Erlang `ok` atoms which clutter generated code
as no-op expressions. Filter these from non-final positions in all body
contexts: top-level functions, fun/named-fun clauses, case clauses,
try/catch bodies, and block expressions. Handles both Literal(AtomLit)
and Emit("ok") forms (the latter from System.Object..ctor).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…lation, stopwatch

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add BitConverter.ToString/ToBoolean/GetBytes(bool), String.IndexOfAny,
String.Split with count, String.Join with indices and BigInt support,
Uri.UnescapeDataString, Guid.TryParse, StringBuilder.Chars/Replace,
and fix Convert.ToString for floats.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add booleanOr/booleanAnd operators, IntrinsicOperators dispatch,
List.GetSlice, ResizeArray.RemoveRange, StringBuilder.AppendFormat 2-arg,
Int32.Parse hex, Convert.ToString Decimal, fix Regex.Split with count.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…0 failures)

Add new tests: Map (isEmpty, xs.Count/Add/ContainsKey/Remove/TryFind,
ofList/ofArray/ofSeq, toList/toArray/toSeq), Set (toSeq, Seq.isEmpty,
comparing large sets), Tuple (constructor, ValueTuple, single element),
Dictionary (Keys), Seq (char range). Re-comment 4 delegate tests with
TODO notes for unit arg mismatch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…fixes (1965 tests, 0 failures)

- Implement Int64BitsToDouble/DoubleToInt64Bits in Replacements + fable_bit_converter.erl
- Fix Dictionary KeyCollection/ValueCollection Count routing (use erlang:length)
- Add 9 BitConverter tests (GetBytes, ToInt/UInt, ToSingle, bit reinterpretation)
- Add Dictionary tests (Keys.Count, Values.Count, KeyValuePattern, iteration, from IDictionary)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Implement ValueOption static methods (Bind, Map, DefaultValue, etc.) via options→optionModule fallthrough
- Add Microsoft.FSharp.Core.ValueOption to tryCall dispatch table
- Add OfOption/ToOption/OfValueOption/ToValueOption identity pass-through
- Add 12 ValueOption tests, Set option equality, isNull with objects, using function, String.forall

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Implement HashSet.CopyTo in Replacements + fable_hashset.erl runtime
- Add tests for IsSubsetOf, IsSupersetOf, IsProperSubsetOf, IsProperSupersetOf, CopyTo

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…res)

Leverage BEAM's process model for true parallelism via spawn_monitor.
Adds fable_parallel.erl runtime with map, mapi, init, iter, iteri,
collect, choose, and parallel_for. Handles curried callbacks, array
ref deref in child processes, and smart-constructor option unwrapping.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

1 participant