Documentation
¶
Overview ¶
Effect label codecs for serialization and deserialization.
This file registers all built-in effect label codecs with the effect system. Each codec handles encoding and decoding of a specific effect label type to/from the manifest format.
Codecs are registered at package initialization via Register and are looked up by key string during manifest reading. Each codec implements:
- Key(): Returns the unique string identifier for this effect type
- Encode(): Serializes the label to the manifest writer
- Decode(): Deserializes the label from the manifest reader
The registered effect types cover:
- Control flow: Throw, Diverge
- I/O: IO, ModuleLoad, Send
- Mutation: Mutate, TableMutator, LengthChange, Store, Freeze
- Borrowing: Borrow, BorrowAll
- Returns: Return, ErrorReturn, ReturnLength, CorrelatedReturn
- Type system: TypePredicate, TypeValueMethod, CallableType
- Data flow: PassThrough, FlowInto, VariadicTransform
- Iteration: Iterator
Package effect provides row-polymorphic effect tracking for Lua type checking.
Effect rows describe the observable behaviors of functions beyond their type signatures. The system is inspired by Koka's row-polymorphic effect types, adapted for gradual typing in Lua.
Row Polymorphism ¶
An effect row is a set of effect labels with an optional tail variable:
- Closed row {throw, io}: exactly these effects, no polymorphism
- Open row {throw | e}: throw plus whatever effects e contains
- Unknown row {?}: gradual typing for untyped Lua (assumes any effect)
- Empty row {}: pure function with no effects
The tail variable enables effect polymorphism: a function can accept callbacks with arbitrary additional effects and propagate those effects to its result.
Effect Labels ¶
Labels describe specific function behaviors:
Control effects track exceptional control flow:
- Throw: function may raise an error via error()
- Diverge: function may not terminate (infinite loops)
- IO: function performs I/O operations
Mutation effects track type-level state changes:
- Mutate: modifies a parameter's type (e.g., table.insert widens array element type)
- LengthChange: modifies array length (+1 for insert, -1 for remove)
- TableMutator: specialized mutation for table operations
Ownership effects track value lifecycle:
- Borrow: temporary read access, value can be released after call
- Store: persistent storage, value escapes the call
- Send: cross-actor transfer, value becomes frozen
- Freeze: immutability marker for shared values
Return effects track type derivations:
- Return: describes how return type derives from parameters
- ErrorReturn: encodes the Lua (value, error) return pattern
- ReturnLength: relates return array length to parameter lengths
- CorrelatedReturn: marks return positions that are nil/non-nil together
Iterator effects describe iteration semantics:
- Iterator: marks ipairs/pairs style iteration with source and kind
Flow effects track value identity:
- PassThrough: parameter flows unchanged to return position
- FlowInto: parameter flows into a field of the returned value
Semantic effects enable special handling:
- ModuleLoad: require-like module loading
- TypePredicate: type()-like type inspection
- VariadicTransform: select-like variadic manipulation
- CallableType: TypeName(x) constructor pattern
Row Operations ¶
The package provides set-theoretic operations on effect rows:
- Union: sequential composition, combines effects from both rows
- Intersect: common effects, keeps only labels in both rows
- Subset: containment check for effect compatibility
Serialization ¶
Effect labels support binary serialization via the codec registry for cross-module type manifest storage. Each label type registers its codec in init().
Usage ¶
Effects are attached to function types via the builder pattern:
fn := typ.Func().
Param("t", typ.NewArray(typ.String)).
Param("val", typ.String).
Returns(typ.Nil).
Effects(effect.Row{Labels: []effect.Label{
effect.Mutate{Target: effect.ParamRef{Index: 0}, Transform: effect.ElementUnion{Source: effect.ParamRef{Index: 1}}},
effect.LengthChange{Target: effect.ParamRef{Index: 0}, Delta: 1},
}}).
Build()
During type checking, effect rows are unified and propagated through call sites to verify effect compatibility and derive precise types.
Index ¶
- Constants
- Variables
- func LabelKey(l Label) string
- func Register(c LabelCodec)
- func ResolveParamIndex(ref ParamRef, argCount int) (int, bool)
- func Subset(r1, r2 Row) bool
- func VisitLabel[R any](l Label, v LabelVisitor[R]) R
- func VisitReturnType[R any](t ReturnType, v ReturnTypeVisitor[R]) R
- func VisitTransform[R any](t TypeTransform, v TypeTransformVisitor[R]) R
- type ArrayOfCallbackReturn
- type Borrow
- type BorrowAll
- type CallableType
- type CallbackReturn
- type ContainerElementUnion
- type CorrelatedReturn
- type DeepElementOf
- type Diverge
- type ElementOf
- type ElementUnion
- type ErrorReturn
- type FlowInto
- type Freeze
- type IO
- type Iterator
- type IteratorKind
- type Label
- type LabelCodec
- type LabelVisitor
- type LengthChange
- type ModuleLoad
- type Mutate
- type OptionalElementOf
- type ParamRef
- type PassThrough
- type Reader
- type Return
- type ReturnLength
- type ReturnType
- type ReturnTypeVisitor
- type Row
- func BorrowsOnly() Row
- func Intersect(r1, r2 Row) Row
- func MayDiverge() Row
- func Mutates(paramIdx int, transform TypeTransform) Row
- func Open(name string, labels ...Label) Row
- func Returns(retIdx int, derive ReturnType) Row
- func StoresParam(paramIdx int, intoIdx int) Row
- func Throws() Row
- func Union(r1, r2 Row) Row
- func WithCallableType() Row
- func WithIO() Row
- func WithModuleLoad() Row
- func WithTypePredicate() Row
- func WithTypeValueMethod() Row
- func WithVariadicTransform() Row
- func (r Row) BorrowsAllParams() bool
- func (r Row) Equals(other any) bool
- func (r Row) GetBorrow(paramIdx int) *Borrow
- func (r Row) GetCorrelatedReturn(idx int) *CorrelatedReturn
- func (r Row) GetErrorReturn(valueIdx int) *ErrorReturn
- func (r Row) GetIterator() *Iterator
- func (r Row) GetMutate(paramIdx int) *Mutate
- func (r Row) GetReturn(retIdx int) *Return
- func (r Row) GetReturnLength(retIdx int) *ReturnLength
- func (r Row) GetStore(paramIdx int) *Store
- func (r Row) GetTableMutator() *TableMutator
- func (r Row) Has(check func(Label) bool) bool
- func (r Row) HasBorrow() bool
- func (r Row) HasCallableType() bool
- func (r Row) HasDiverge() bool
- func (r Row) HasIO() bool
- func (r Row) HasIterator() bool
- func (r Row) HasModuleLoad() bool
- func (r Row) HasMutate() bool
- func (r Row) HasStore() bool
- func (r Row) HasTableMutator() bool
- func (r Row) HasThrow() bool
- func (r Row) HasTypePredicate() bool
- func (r Row) HasTypeValueMethod() bool
- func (r Row) HasVariadicTransform() bool
- func (r Row) IsClosed() bool
- func (r Row) IsEffectInfo()
- func (r Row) IsIndexedIterator() bool
- func (r Row) IsKeyedIterator() bool
- func (r Row) IsOpen() bool
- func (r Row) IsUnknown() bool
- func (r Row) OnlyBorrows() bool
- func (r Row) Pure() bool
- func (r Row) String() string
- func (r Row) With(labels ...Label) Row
- func (r Row) Without(match func(Label) bool) Row
- type SameAs
- type SelectCaseOfParam
- type SelectResultOfCases
- type Send
- type Store
- type TableMutator
- type Throw
- type ToArray
- type TypePredicate
- type TypeTransform
- type TypeTransformVisitor
- type TypeValueMethod
- type Unchanged
- type Var
- type VariadicTransform
- type Writer
Constants ¶
const ( KeyThrow = "throw" KeyIO = "io" KeyDiverge = "diverge" KeyMutate = "mutate" KeyReturn = "return" KeyErrorReturn = "error_return" KeyReturnLength = "return_length" KeyIterator = "iterator" KeyTableMutator = "table_mutator" KeyLengthChange = "length_change" KeyBorrow = "borrow" KeyStore = "store" KeyBorrowAll = "borrow_all" KeyPassThrough = "passthrough" KeyFlowInto = "flowinto" KeySend = "send" KeyFreeze = "freeze" KeyModuleLoad = "module_load" KeyVariadicTransform = "variadic_transform" KeyTypePredicate = "type_predicate" KeyTypeValueMethod = "type_value_method" KeyCallableType = "callable_type" )
Codec key constants define the serialization identifiers for each label type.
These keys are written to the binary format before each label's payload, enabling the decoder to select the appropriate codec for deserialization. Keys should be stable across versions to maintain backward compatibility.
Variables ¶
var Empty = Row{}
Empty is the empty effect row (pure function).
var Unknown = Row{Tail: &Var{Name: "?"}}
Unknown is the unknown effect row (gradual typing for untyped Lua). Functions with unknown effects are assumed to potentially have any effect.
Functions ¶
func LabelKey ¶
LabelKey returns the canonical codec key for a label instance.
Maps each Label implementation to its corresponding Key constant for serialization. Falls back to the label's String() for unknown types.
func Register ¶
func Register(c LabelCodec)
Register adds a label codec to the global registry.
Call from init() to ensure the codec is available before any serialization occurs. The codecs.go file registers all built-in label codecs.
func ResolveParamIndex ¶ added in v1.5.6
ResolveParamIndex resolves a ParamRef against a runtime argument count.
Non-negative indices are absolute. Negative indices address from the end of the runtime argument list (-1 is last).
func Subset ¶
Subset returns true if r1's effects are a subset of r2's effects.
This is the effect compatibility check: a function with effects r1 can be used where effects r2 are expected if Subset(r1, r2) is true.
Subset rules:
- Empty is a subset of everything (pure functions are universally compatible).
- Everything is a subset of Unknown (unknown accepts any effect).
- Unknown is only a subset of Unknown itself.
- For closed rows: every label in r1 must have a matching label in r2.
- For open r2: labels not in r2's explicit set may be covered by its tail.
Example:
Subset({}, {throw}) = true // pure can be used where throw is expected
Subset({throw}, {throw, io}) = true // fewer effects is compatible
Subset({throw, io}, {throw}) = false // more effects is not compatible
Subset({throw}, {?}) = true // anything is subset of unknown
func VisitLabel ¶
func VisitLabel[R any](l Label, v LabelVisitor[R]) R
VisitLabel applies the first matching handler in v to l.
func VisitReturnType ¶
func VisitReturnType[R any](t ReturnType, v ReturnTypeVisitor[R]) R
VisitReturnType applies the first matching handler in v to t.
func VisitTransform ¶
func VisitTransform[R any](t TypeTransform, v TypeTransformVisitor[R]) R
VisitTransform applies the first matching handler in v to t.
Types ¶
type ArrayOfCallbackReturn ¶
type ArrayOfCallbackReturn struct {
CallbackParam ParamRef
}
ArrayOfCallbackReturn returns an array of callback's return type.
func (ArrayOfCallbackReturn) String ¶
func (a ArrayOfCallbackReturn) String() string
type Borrow ¶
type Borrow struct {
Param ParamRef // Which parameter is borrowed
}
Borrow indicates the function only reads a parameter temporarily.
Borrow semantics enable memory optimization: if a value is only borrowed by called functions and has no other references, it can be released after the last borrowing call returns without needing garbage collection.
Example: print(x) borrows x for the duration of the call, then releases it. If x has no other references, it can be immediately freed.
Contrast with Store, which indicates the value escapes into a data structure and must be kept alive.
type BorrowAll ¶
type BorrowAll struct{}
BorrowAll indicates all parameters are only borrowed (read-only, temporary). Used for functions like print, tostring that inspect but don't store.
type CallableType ¶
type CallableType struct{}
CallableType indicates a TypeName(x) callable type constructor.
func (CallableType) Equals ¶
func (CallableType) Equals(other Label) bool
func (CallableType) String ¶
func (CallableType) String() string
type CallbackReturn ¶
type CallbackReturn struct {
CallbackParam ParamRef
}
CallbackReturn uses the return type of a callback parameter.
func (CallbackReturn) String ¶
func (c CallbackReturn) String() string
type ContainerElementUnion ¶
ContainerElementUnion widens a container element type with a value type.
func (ContainerElementUnion) String ¶
func (c ContainerElementUnion) String() string
type CorrelatedReturn ¶
type CorrelatedReturn struct {
}
CorrelatedReturn indicates same-direction correlation between return values.
This effect marks return positions that are always nil together or always non-nil together. It enables the type checker to narrow multiple returns with a single nil check.
Example for string.find(s, pattern):
-- Returns (start, end) on match, (nil, nil) on no match
CorrelatedReturn{Indices: []int{0, 1}}
After checking just one return:
local start, finish = string.find(s, pattern)
if start then
-- Both start and finish are non-nil here
end
Contrast with ErrorReturn which indicates inverse correlation (one nil means other is non-nil).
func (CorrelatedReturn) Equals ¶
func (c CorrelatedReturn) Equals(other Label) bool
func (CorrelatedReturn) String ¶
func (c CorrelatedReturn) String() string
type DeepElementOf ¶
type DeepElementOf struct {
Source ParamRef
}
DeepElementOf recursively extracts non-array leaf types.
func (DeepElementOf) String ¶
func (d DeepElementOf) String() string
type ElementOf ¶
type ElementOf struct {
Source ParamRef
}
ElementOf returns the element type of an array parameter.
type ElementUnion ¶
type ElementUnion struct {
Source ParamRef
}
ElementUnion widens an array's element type.
func (ElementUnion) String ¶
func (e ElementUnion) String() string
type ErrorReturn ¶
type ErrorReturn struct {
ValueIndex int // Value return position (0-based)
ErrorIndex int // Error return position (0-based)
}
ErrorReturn indicates correlated error-return semantics.
Encodes the common Lua pattern where functions return (value, nil) on success or (nil, error) on failure. The type checker uses this to enable automatic narrowing after error checks.
Example for io.open(filename):
-- Returns (file, nil) on success, (nil, string) on failure
ErrorReturn{ValueIndex: 0, ErrorIndex: 1}
After checking the error:
local file, err = io.open("test.txt")
if err then
-- file is nil here
else
-- file is file here (non-nil)
end
func (ErrorReturn) Equals ¶
func (e ErrorReturn) Equals(other Label) bool
func (ErrorReturn) String ¶
func (e ErrorReturn) String() string
type FlowInto ¶
type FlowInto struct {
ParamIndex int // Which parameter (0-based)
ReturnIndex int // Which return position (0-based)
Path string // Field path, e.g., "inner" or "data.value"
}
FlowInto indicates a parameter flows into a field of a returned table.
When a function wraps a parameter into a record field, FlowInto enables the type checker to derive the record's field type from the parameter type.
Example:
function wrap(val) return {inner = val} end
-- FlowInto{ParamIndex: 0, ReturnIndex: 0, Path: "inner"}
At the call site:
local wrapped = wrap(42) -- wrapped has type {inner: integer}
local wrapped2 = wrap("hello") -- wrapped2 has type {inner: string}
The Path field supports dotted paths for nested fields: "data.inner.value".
type Freeze ¶
type Freeze struct {
Param ParamRef // Which parameter is frozen
}
Freeze indicates the function freezes a parameter (makes it immutable). After freezing, the value can be safely shared across actors.
type Iterator ¶
type Iterator struct {
Source ParamRef // Which parameter is being iterated
Kind IteratorKind // Type of iteration (indexed or keyed)
}
Iterator indicates the function returns an iterator over a parameter.
This effect enables the type checker to understand iterator factories like ipairs and pairs. When a function with Iterator effect is used in a for loop, the loop variable types are derived from the source parameter's type.
IteratorKind determines iteration semantics:
- IterateIndexed (ipairs): iterates with integer indices 1, 2, 3, ... Loop variables are (integer, element_type)
- IterateKeyed (pairs): iterates with arbitrary keys Loop variables are (key_type, value_type)
type IteratorKind ¶
type IteratorKind int
IteratorKind describes the type of iteration.
const ( // IterateIndexed iterates with integer indices (ipairs-style). IterateIndexed IteratorKind = iota // IterateKeyed iterates with arbitrary keys (pairs-style). IterateKeyed )
type Label ¶
type Label interface {
String() string
Equals(other Label) bool
// contains filtered or unexported methods
}
Label represents an atomic effect that a function can have.
Labels are the building blocks of effect rows. Each Label implementation describes a specific observable behavior or type-level side effect. Labels are combined into Rows to describe complete effect signatures for functions.
All Label implementations must satisfy:
- label(): marker method for interface satisfaction
- String(): human-readable representation for diagnostics
- Equals(other Label): structural equality for row deduplication
Effect categories and their labels:
Control effects describe exceptional control flow:
- Throw: function may raise an error via error() or assert()
- Diverge: function may not terminate (infinite loops, os.exit)
- IO: function performs I/O operations (print, file access)
Mutation effects describe type-level state changes:
- Mutate: modifies a parameter's type structure (e.g., widening array elements)
- LengthChange: modifies array length by a known delta
- TableMutator: specialized mutation for table.insert-like operations
Ownership effects describe value lifecycle for memory optimization:
- Borrow: temporary read access, value can be released after call
- BorrowAll: all parameters are borrowed (common for pure functions)
- Store: persistent storage, value escapes into a data structure
- Send: cross-actor transfer, value becomes frozen and shared
- Freeze: marks a value as immutable for safe sharing
Return effects describe type derivations from parameters:
- Return: how a return type derives from parameter types
- ErrorReturn: encodes the Lua (value, error) return pattern
- ReturnLength: relates return array length to parameter lengths
- CorrelatedReturn: marks returns that are nil/non-nil together
Flow effects describe value identity preservation:
- PassThrough: parameter flows unchanged to return position
- FlowInto: parameter flows into a field of the returned value
Iterator effects describe iteration semantics:
- Iterator: marks ipairs/pairs style iteration with kind
Semantic effects enable special type checker handling:
- ModuleLoad: require-like module loading
- TypePredicate: type()-like type name inspection
- TypeValueMethod: Type:is()-like method on type values
- VariadicTransform: select-like variadic manipulation
- CallableType: TypeName(x) constructor pattern
type LabelCodec ¶
type LabelCodec interface {
Key() string
Encode(l Label, w Writer) error
Decode(r Reader) (Label, error)
}
LabelCodec handles binary serialization for a label type.
Each Label implementation has a corresponding codec registered in the global registry. The codec is responsible for encoding and decoding the label's data to/from the binary format.
Implementations:
- Key(): Returns the codec key constant for this label type
- Encode(): Writes the label's fields to the Writer
- Decode(): Reads fields from the Reader and constructs a Label
func CodecFor ¶
func CodecFor(l Label) (LabelCodec, bool)
CodecFor returns the codec for a label instance.
Combines LabelKey and Lookup for convenience when serializing a label.
func Lookup ¶
func Lookup(key string) (LabelCodec, bool)
Lookup returns the codec for the given key, if registered.
Used during deserialization to find the appropriate decoder for each label in the binary stream.
type LabelVisitor ¶
type LabelVisitor[R any] struct { Mutate func(Mutate) R Return func(Return) R ErrorReturn func(ErrorReturn) R ReturnLength func(ReturnLength) R Throw func(Throw) R Diverge func(Diverge) R IO func(IO) R LengthChange func(LengthChange) R Iterator func(Iterator) R TableMutator func(TableMutator) R Borrow func(Borrow) R Store func(Store) R BorrowAll func(BorrowAll) R PassThrough func(PassThrough) R FlowInto func(FlowInto) R Send func(Send) R Freeze func(Freeze) R ModuleLoad func(ModuleLoad) R VariadicTransform func(VariadicTransform) R TypePredicate func(TypePredicate) R TypeValueMethod func(TypeValueMethod) R CallableType func(CallableType) R Default func(Label) R }
LabelVisitor dispatches on label variants. Nil handlers fall back to Default when provided; otherwise return zero.
type LengthChange ¶
LengthChange indicates a length modification.
func (LengthChange) Equals ¶
func (l LengthChange) Equals(other Label) bool
func (LengthChange) String ¶
func (l LengthChange) String() string
type ModuleLoad ¶
type ModuleLoad struct{}
ModuleLoad indicates a require-like module loading function.
func (ModuleLoad) Equals ¶
func (ModuleLoad) Equals(other Label) bool
func (ModuleLoad) String ¶
func (ModuleLoad) String() string
type Mutate ¶
type Mutate struct {
Target ParamRef // Which parameter is mutated
Transform TypeTransform // How the type changes (ElementUnion, ToArray, etc.)
LengthDelta constraint.Expr // Length change expression (+1, +len(param[1]), etc.)
}
Mutate indicates a function mutates a parameter's type structure.
This is the primary effect for functions that modify the type of their arguments in ways visible to the type system. The most common use is for table.insert, which widens the element type of an array.
Example for table.insert(t, value):
Mutate{
Target: ParamRef{Index: 0}, // mutates first param (t)
Transform: ElementUnion{Source: ParamRef{Index: 1}}, // widens element type with value's type
LengthDelta: constraint.Const{Value: 1}, // length increases by 1
}
The type checker applies Mutate effects to update the type environment after the call, reflecting the widened type for subsequent code.
type OptionalElementOf ¶
type OptionalElementOf struct {
Source ParamRef
}
OptionalElementOf returns element type | nil.
func (OptionalElementOf) String ¶
func (e OptionalElementOf) String() string
type ParamRef ¶
type ParamRef struct {
Index int // 0-based index; -1 means the last variadic argument
}
ParamRef references a function parameter by position.
Used throughout effect labels to specify which parameters are affected by an effect. The type checker resolves ParamRef indices to actual parameter types at each call site.
Special index values:
- Non-negative: 0-based index into the fixed parameter list
- Negative: relative from runtime argument tail (-1 is last argument)
type PassThrough ¶
type PassThrough struct {
ParamIndex int // Which parameter (0-based)
ReturnIndex int // Which return position (0-based)
}
PassThrough indicates a parameter flows directly to a return position.
When a function returns one of its parameters unchanged, the PassThrough effect enables the type checker to preserve the parameter's exact type (including refinements) through the call.
This is essential for assertion-style functions:
function assert_not_nil(val) if val == nil then error() end return val end
-- PassThrough{ParamIndex: 0, ReturnIndex: 0}
At the call site:
local x: string? = maybe_get_string() local y = assert_not_nil(x) -- y is string (non-nil), not string?
Without PassThrough, the return type would be inferred from the declared return type, losing the refinement from the nil check.
func (PassThrough) Equals ¶
func (p PassThrough) Equals(other Label) bool
func (PassThrough) String ¶
func (p PassThrough) String() string
type Reader ¶
type Reader interface {
ReadByte() (byte, error)
ReadInt32() (int32, error)
ReadString() (string, error)
ReadType() (any, error)
}
Reader provides methods for deserializing effect data from binary format.
Implementations read from an underlying byte stream in little-endian format. The types/io package provides the concrete implementation used for manifest deserialization.
type Return ¶
type Return struct {
ReturnIndex int // Which return value (0-based)
Transform ReturnType // How to derive the type (ElementOf, SameAs, etc.)
}
Return indicates how a return value's type is derived from parameters.
This effect enables precise typing for built-in functions that act like generics but are implemented in C/native code. The type checker uses Return effects to compute concrete return types based on argument types.
Examples:
table.remove(t): Returns element type of t (or nil)
Return{ReturnIndex: 0, Transform: OptionalElementOf{Source: ParamRef{Index: 0}}}
table.unpack(t): Returns tuple of element type
Return{ReturnIndex: 0, Transform: ElementOf{Source: ParamRef{Index: 0}}}
assert(v): Returns v unchanged (passthrough)
Return{ReturnIndex: 0, Transform: SameAs{Source: ParamRef{Index: 0}}}
type ReturnLength ¶
type ReturnLength struct {
ReturnIndex int // Which return value (0-based)
Length constraint.Expr // Length expression in terms of parameters
}
ReturnLength indicates how a return value's length relates to parameters.
func (ReturnLength) Equals ¶
func (r ReturnLength) Equals(other Label) bool
func (ReturnLength) String ¶
func (r ReturnLength) String() string
type ReturnType ¶
type ReturnType interface {
String() string
// contains filtered or unexported methods
}
ReturnType describes how to derive a return type from function parameters.
Each implementation defines a specific type derivation pattern:
ElementOf: Returns the element type of an array parameter. table.unpack(t) -> element type of t
OptionalElementOf: Returns element type | nil. table.remove(t) -> element type of t, or nil if empty
SameAs: Returns the exact type of a parameter (identity). assert(v) -> typeof(v)
CallbackReturn: Returns what a callback parameter returns. table.sort(t, cmp) where cmp returns boolean -> boolean
ArrayOfCallbackReturn: Returns an array of the callback's return type. table.map(t, fn) -> Array<typeof(fn(element))>
DeepElementOf: Recursively extracts non-array leaf types. For nested arrays, returns the innermost element type.
SelectCaseOfParam: Builds select case from parameter type.
SelectResultOfCases: Builds select result from cases and default.
type ReturnTypeVisitor ¶
type ReturnTypeVisitor[R any] struct { ElementOf func(ElementOf) R OptionalElementOf func(OptionalElementOf) R CallbackReturn func(CallbackReturn) R ArrayOfCallbackReturn func(ArrayOfCallbackReturn) R SameAs func(SameAs) R DeepElementOf func(DeepElementOf) R SelectCaseOfParam func(SelectCaseOfParam) R SelectResultOfCases func(SelectResultOfCases) R Default func(ReturnType) R }
ReturnTypeVisitor dispatches on return type variants. Nil handlers fall back to Default when provided; otherwise return zero.
type Row ¶
type Row struct {
Labels []Label // Concrete effects in this row
Tail *Var // Effect variable for polymorphism (nil = closed row)
}
Row represents an effect row: a set of labels with optional tail variable.
A Row is the fundamental unit of effect tracking. It contains zero or more concrete effect labels describing known behaviors, plus an optional tail variable enabling effect polymorphism.
Row semantics:
Empty row {} (Pure returns true): The function is pure with no observable effects. This is the strongest guarantee and enables optimizations.
Closed row {throw, io} (IsClosed returns true, Tail is nil): The function has exactly these effects and no others. Used for complete specifications.
Open row {throw | e} (IsOpen returns true, Tail is non-nil): The function has at least throw, plus whatever additional effects are bound to e. Used for polymorphic functions that propagate callee effects.
Unknown row {?} (IsUnknown returns true): The function may have any effect. Used for untyped Lua code during gradual typing migration.
Rows support set operations via Union, Intersect, and Subset. The With and Without methods create modified copies without mutating the original.
The Labels slice uses semantic equality via Equals, not pointer equality, so two rows with structurally identical labels compare equal.
func BorrowsOnly ¶
func BorrowsOnly() Row
BorrowsOnly creates a row indicating function only borrows all params.
func Intersect ¶
Intersect returns the intersection of two effect rows.
The result contains only labels present in both input rows. This is useful for computing the guaranteed effects when analyzing multiple code paths.
Intersection rules:
- If either row is pure (empty), returns Empty (no guaranteed effects).
- Labels are matched using semantic equality (Equals method).
- The tail is kept only if both rows have the same tail variable.
Example:
Intersect({throw, io}, {throw, diverge}) = {throw}
Intersect({throw | e}, {io | e}) = {| e} (labels differ, tail matches)
func Mutates ¶
func Mutates(paramIdx int, transform TypeTransform) Row
Mutates creates a row with a mutation effect.
func Open ¶
Open creates an open effect row with the specified tail variable.
Open rows are used for effect-polymorphic functions. The tail variable name is typically a lowercase letter like "e" by convention.
Example:
// A higher-order function that propagates callback effects
row := effect.Open("e", effect.Throw{}) // {throw | e}
func Returns ¶
func Returns(retIdx int, derive ReturnType) Row
Returns creates a row with a return type effect.
func StoresParam ¶
StoresParam creates a row indicating function stores a param.
func Union ¶
Union combines two effect rows via sequential composition.
When a function f calls function g, the combined effect of f is the union of f's own effects with g's effects. This models effect propagation through call chains.
Union rules by row shape:
Closed + Closed: Returns a closed row with all labels from both. Union({a, b}, {c}) = {a, b, c}
Open + Closed (or vice versa): Returns an open row with combined labels and the open row's tail variable. Union({a | e}, {b}) = {a, b | e}
Open + Open: Returns an open row with combined labels and a merged tail. If the tails have the same name, that tail is used; otherwise, a new tail with name "e1∪e2" is created. Union({a | e}, {b | e}) = {a, b | e} Union({a | e1}, {b | e2}) = {a, b | e1∪e2}
Unknown involved: Returns Unknown (the unknown effect subsumes all). Union({a}, {?}) = {?}
Labels are deduplicated using semantic equality (Equals method), not pointer equality, so structurally identical labels appear only once in the result.
func WithCallableType ¶
func WithCallableType() Row
WithCallableType creates a row with the callable type effect.
func WithModuleLoad ¶
func WithModuleLoad() Row
WithModuleLoad creates a row with the module load effect.
func WithTypePredicate ¶
func WithTypePredicate() Row
WithTypePredicate creates a row with the type predicate effect.
func WithTypeValueMethod ¶
func WithTypeValueMethod() Row
WithTypeValueMethod creates a row with the type value method effect.
func WithVariadicTransform ¶
func WithVariadicTransform() Row
WithVariadicTransform creates a row with the variadic transform effect.
func (Row) BorrowsAllParams ¶
BorrowsAllParams returns true if function has BorrowAll effect.
func (Row) GetCorrelatedReturn ¶
func (r Row) GetCorrelatedReturn(idx int) *CorrelatedReturn
GetCorrelatedReturn returns the correlated return effect that includes the given index.
func (Row) GetErrorReturn ¶
func (r Row) GetErrorReturn(valueIdx int) *ErrorReturn
GetErrorReturn returns the error-return correlation for a specific value position.
Encodes the Lua pattern where a function returns (value, nil) on success or (nil, error) on failure. The type checker uses this to narrow types after checking the error return position.
Returns nil if no ErrorReturn label exists for the given value index.
func (Row) GetIterator ¶
GetIterator returns the first iterator effect in the row.
func (Row) GetMutate ¶
GetMutate returns the mutation effect for a specific parameter, if any.
Returns nil if the row contains no Mutate label targeting the given parameter. The paramIdx is 0-based, with -1 indicating the last variadic argument.
func (Row) GetReturn ¶
GetReturn returns the return type derivation effect for a specific return position.
Used by the type checker to derive precise return types based on parameter types. For example, table.remove(t) returns the element type of t, encoded as:
Return{ReturnIndex: 0, Transform: OptionalElementOf{Source: ParamRef{Index: 0}}}
Returns nil if no Return label exists for the given return index.
func (Row) GetReturnLength ¶
func (r Row) GetReturnLength(retIdx int) *ReturnLength
GetReturnLength returns the return length effect for a specific return index.
func (Row) GetTableMutator ¶
func (r Row) GetTableMutator() *TableMutator
GetTableMutator returns the first table mutator effect in the row.
func (Row) HasCallableType ¶
HasCallableType returns true if the row has a callable type effect.
func (Row) HasDiverge ¶
HasDiverge returns true if diverge is in the row.
func (Row) HasIterator ¶
HasIterator returns true if any iterator effect is in the row.
func (Row) HasModuleLoad ¶
HasModuleLoad returns true if the row has a module load effect.
func (Row) HasTableMutator ¶
HasTableMutator returns true if any table mutator effect is in the row.
func (Row) HasTypePredicate ¶
HasTypePredicate returns true if the row has a type predicate effect.
func (Row) HasTypeValueMethod ¶
HasTypeValueMethod returns true if the row has a type value method effect.
func (Row) HasVariadicTransform ¶
HasVariadicTransform returns true if the row has a variadic transform effect.
func (Row) IsIndexedIterator ¶
IsIndexedIterator returns true if the row has an indexed iterator (ipairs-style).
func (Row) IsKeyedIterator ¶
IsKeyedIterator returns true if the row has a keyed iterator (pairs-style).
func (Row) OnlyBorrows ¶
OnlyBorrows returns true if function only borrows params (no store).
func (Row) With ¶
With returns a new row with the given labels added.
Labels are deduplicated using semantic equality (Equals), so adding a label already present in the row has no effect. The original row is not modified.
Example:
pure := effect.Empty
throws := pure.With(effect.Throw{})
throwsAndIO := throws.With(effect.IO{})
func (Row) Without ¶
Without returns a new row excluding labels that match the predicate.
Used to filter out specific effect types, for example when a function handles an effect internally and should not propagate it to callers.
Example:
// Remove all throw effects after wrapping in pcall
handled := row.Without(func(l Label) bool { _, ok := l.(Throw); return ok })
type SelectCaseOfParam ¶
type SelectCaseOfParam struct {
Source ParamRef
}
SelectCaseOfParam builds a select case from a parameter type.
func (SelectCaseOfParam) String ¶
func (s SelectCaseOfParam) String() string
type SelectResultOfCases ¶
SelectResultOfCases builds a select result from a cases parameter.
func (SelectResultOfCases) String ¶
func (s SelectResultOfCases) String() string
type Send ¶
type Send struct {
FromParam int // First param index that is sent (e.g., 2 for msg... in send(pid, topic, msg...))
}
Send indicates the function sends parameters to another actor/process. The values become frozen (immutable) and shared across actors. For process:send(pid, topic, msg...), FromParam marks where msg starts.
type Store ¶
type Store struct {
Param ParamRef // Which parameter is stored
Into ParamRef // Where it's stored (if known, -1 for unknown)
}
Store indicates the function stores a parameter persistently.
When a value is stored, it escapes the call site and cannot be released without reference counting or garbage collection. The type checker tracks Store effects to determine which values may have long lifetimes.
The Into field, when non-negative, indicates which parameter receives the stored value (e.g., table.insert stores value into table). When -1, the storage destination is unknown (e.g., closure capture).
type TableMutator ¶
type TableMutator struct {
Target ParamRef // Which parameter is mutated
Value ParamRef // Which parameter is the new value
}
TableMutator indicates the function mutates a table parameter. This is the semantic effect for table.insert and similar.
func (TableMutator) Equals ¶
func (t TableMutator) Equals(other Label) bool
func (TableMutator) String ¶
func (t TableMutator) String() string
type TypePredicate ¶
type TypePredicate struct{}
TypePredicate indicates a type()-like type name predicate function.
func (TypePredicate) Equals ¶
func (TypePredicate) Equals(other Label) bool
func (TypePredicate) String ¶
func (TypePredicate) String() string
type TypeTransform ¶
type TypeTransform interface {
String() string
// contains filtered or unexported methods
}
TypeTransform describes how a type is transformed by a Mutate effect.
Implementations define specific transformation patterns:
ElementUnion: Widens an array's element type by unioning with another param's type. Used for table.insert to track that inserted values expand the element type.
ContainerElementUnion: Like ElementUnion but for container types with both key and value. Used for map-like insertion operations.
ToArray: Converts an empty table {} to a typed array T[]. Used when the first insert determines the array element type.
Unchanged: No type transformation, used when only length changes.
type TypeTransformVisitor ¶
type TypeTransformVisitor[R any] struct { Unchanged func(Unchanged) R ElementUnion func(ElementUnion) R ContainerElementUnion func(ContainerElementUnion) R ToArray func(ToArray) R Default func(TypeTransform) R }
TypeTransformVisitor dispatches on transform variants. Nil handlers fall back to Default when provided; otherwise return zero.
type TypeValueMethod ¶
type TypeValueMethod struct{}
TypeValueMethod indicates a Type:is()-like method on type values.
func (TypeValueMethod) Equals ¶
func (TypeValueMethod) Equals(other Label) bool
func (TypeValueMethod) String ¶
func (TypeValueMethod) String() string
type Var ¶
type Var struct {
Name string
}
Var represents an effect variable for row polymorphism.
Effect variables enable functions to be polymorphic over their effects, similar to how type parameters enable polymorphism over types. A function with effect row {throw | e} can be called with any callback that throws, and the variable e captures additional effects from the callback.
The special variable name "?" represents the unknown effect for gradual typing, indicating the function may have any effect.
During effect unification, variables with the same name are considered equal. Different variables create merged tails with names like "e1∪e2".
type VariadicTransform ¶
type VariadicTransform struct{}
VariadicTransform indicates a select-like variadic transform function.
func (VariadicTransform) Equals ¶
func (VariadicTransform) Equals(other Label) bool
func (VariadicTransform) String ¶
func (VariadicTransform) String() string
type Writer ¶
type Writer interface {
WriteByte(b byte) error
WriteInt32(v int32) error
WriteString(s string) error
WriteType(t any) error
}
Writer provides methods for serializing effect data to binary format.
Implementations write to an underlying byte stream in little-endian format. The types/io package provides the concrete implementation used for manifest serialization.