Skip to content

Struct mutability: MutableStruct type + field assignment #117

@tmteam

Description

@tmteam

Struct Mutability — Design and Implementation Plan

Currently all structs are immutable. Need s.field = expr for lang mode.

Evolution path: Phase 1 → Phase 2 → Phase 3

Phase 1: Two struct types

Two type constructors:

  • Struct{f1:T1, ..., fn:Tn} — immutable, fields covariant (current)
  • MutStruct{f1:T1, ..., fn:Tn} — mutable, fields invariant

Subtyping: MutStruct <: Struct (read-only view of mutable is safe). Same pattern as List[T] <: T[].

Algebra:

LCA(Struct, MutStruct)              = Struct (upcast to immutable)
LCA(MutStruct{a:A}, MutStruct{a:B}) = MutStruct{a: Unify(A,B)} or Struct{a: LCA(A,B)} if Unify fails
GCD(Struct{a:A}, MutStruct{a:B})    = MutStruct{a:B} if B <: A
FitsInto(MutStruct, Struct)         = true (subtyping)
FitsInto(Struct, MutStruct)         = false (can't upgrade immutable to mutable)

Width subtyping: works for MutStruct (hiding fields is safe, invariance is per-field-type only).

Literal: {a = 1} always creates Struct (immutable). mut({a = 1}) creates MutStruct.

TIC: New StateMutableStruct class (not a flag). Clean dispatch in algebra.

Pull/Push: treat MutStruct fields as covariant (safe approximation). Destruction enforces invariance via Unify.

Runtime: FunnyMutableStruct : FunnyStruct with SetValue(field, value).

Phase 2: Per-field val/var

s = {val name = 'Alice', var age = 25}
# name: read-only (covariant), age: read-write (invariant)

Per-field IsMutable flag on struct fields. Algebra:

LCA({val a:A, var b:B}, {val a:C, var b:D}) = {val a: LCA(A,C), var b: Unify(B,D)}

var fields can be viewed as val (read-only downgrade is safe).

Phase 3: Auto-inference

If TIC sees s.field = expr, mark field as var. Otherwise unconstrained.

fun modify(s):
    s.age = 26       # age inferred as var
    return s.name    # name stays unconstrained (compatible with val or var)
# Inferred: modify({var age: int, name: text, ...}) -> text

Implementation steps (Phase 1)

  1. StateMutableStruct class
  2. LCA/GCD/FitsInto overloads (6 methods)
  3. IStateFunction expansion for MutStruct combinations
  4. Pull/Push/Destruction for MutStruct
  5. StagesExtension dispatch
  6. GraphBuilder.SetFieldAssign
  7. Parser: s.field = expr syntax
  8. ExpressionBuilder: field assignment node
  9. FunnyMutableStruct runtime class
  10. Tests at all levels

Edge cases

  • s?.field = expr — reject in Phase 1 (Optional + mutation)
  • if(cond) {a=1} else mut({a=2}) → Struct (LCA upcasts to immutable)
  • Array of MutStruct: arr[0].a = 42 — works (arr immutable, element mutable)
  • Generic functions preserve mutability through type params

Metadata

Metadata

Assignees

No one assigned

    Labels

    NfunLanguageNFun-Lang full language mode

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions