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)
StateMutableStruct class
- LCA/GCD/FitsInto overloads (6 methods)
- IStateFunction expansion for MutStruct combinations
- Pull/Push/Destruction for MutStruct
- StagesExtension dispatch
- GraphBuilder.SetFieldAssign
- Parser:
s.field = expr syntax
- ExpressionBuilder: field assignment node
- FunnyMutableStruct runtime class
- 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
Struct Mutability — Design and Implementation Plan
Currently all structs are immutable. Need
s.field = exprfor 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 invariantSubtyping:
MutStruct <: Struct(read-only view of mutable is safe). Same pattern asList[T] <: T[].Algebra:
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
StateMutableStructclass (not a flag). Clean dispatch in algebra.Pull/Push: treat MutStruct fields as covariant (safe approximation). Destruction enforces invariance via Unify.
Runtime:
FunnyMutableStruct : FunnyStructwithSetValue(field, value).Phase 2: Per-field val/var
Per-field
IsMutableflag on struct fields. Algebra:varfields can be viewed asval(read-only downgrade is safe).Phase 3: Auto-inference
If TIC sees
s.field = expr, mark field asvar. Otherwise unconstrained.Implementation steps (Phase 1)
StateMutableStructclasss.field = exprsyntaxEdge cases
s?.field = expr— reject in Phase 1 (Optional + mutation)if(cond) {a=1} else mut({a=2})→ Struct (LCA upcasts to immutable)arr[0].a = 42— works (arr immutable, element mutable)