effect

package
v1.5.10 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 20, 2026 License: MIT Imports: 4 Imported by: 0

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

View Source
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"
	KeyCorrelatedReturn  = "correlated_return"
	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

View Source
var Empty = Row{}

Empty is the empty effect row (pure function).

View Source
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

func LabelKey(l Label) string

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

func ResolveParamIndex(ref ParamRef, argCount int) (int, bool)

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

func Subset(r1, r2 Row) bool

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.

func (Borrow) Equals

func (b Borrow) Equals(other Label) bool

func (Borrow) String

func (b Borrow) String() string

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.

func (BorrowAll) Equals

func (BorrowAll) Equals(other Label) bool

func (BorrowAll) String

func (BorrowAll) String() string

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

type ContainerElementUnion struct {
	Container ParamRef
	Value     ParamRef
}

ContainerElementUnion widens a container element type with a value type.

func (ContainerElementUnion) String

func (c ContainerElementUnion) String() string

type CorrelatedReturn

type CorrelatedReturn struct {
	Indices []int // Return positions that are correlated (0-based)
}

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 Diverge

type Diverge struct{}

Diverge indicates the function may not terminate.

func (Diverge) Equals

func (Diverge) Equals(other Label) bool

func (Diverge) String

func (Diverge) String() string

type ElementOf

type ElementOf struct {
	Source ParamRef
}

ElementOf returns the element type of an array parameter.

func (ElementOf) String

func (e ElementOf) String() string

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".

func (FlowInto) Equals

func (f FlowInto) Equals(other Label) bool

func (FlowInto) String

func (f FlowInto) String() string

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.

func (Freeze) Equals

func (f Freeze) Equals(other Label) bool

func (Freeze) String

func (f Freeze) String() string

type IO

type IO struct{}

IO indicates the function performs I/O operations.

func (IO) Equals

func (IO) Equals(other Label) bool

func (IO) String

func (IO) String() string

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)

func (Iterator) Equals

func (i Iterator) Equals(other Label) bool

func (Iterator) String

func (i Iterator) String() string

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
	CorrelatedReturn  func(CorrelatedReturn) 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

type LengthChange struct {
	Target ParamRef
	Delta  int // +1, -1, or 0 for unknown
}

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.

func (Mutate) Equals

func (m Mutate) Equals(other Label) bool

func (Mutate) String

func (m Mutate) String() string

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)

func (ParamRef) String

func (p ParamRef) String() string

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}}}

func (Return) Equals

func (r Return) Equals(other Label) bool

func (Return) String

func (r Return) String() string

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

func Intersect(r1, r2 Row) Row

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 MayDiverge

func MayDiverge() Row

MayDiverge creates a divergence effect row.

func Mutates

func Mutates(paramIdx int, transform TypeTransform) Row

Mutates creates a row with a mutation effect.

func Open

func Open(name string, labels ...Label) Row

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

func StoresParam(paramIdx int, intoIdx int) Row

StoresParam creates a row indicating function stores a param.

func Throws

func Throws() Row

Throws creates a row with just the throw effect.

func Union

func Union(r1, r2 Row) Row

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 WithIO

func WithIO() Row

WithIO creates an I/O effect row.

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

func (r Row) BorrowsAllParams() bool

BorrowsAllParams returns true if function has BorrowAll effect.

func (Row) Equals

func (r Row) Equals(other any) bool

Equals checks if two rows are equal (implements internal.Equaler interface).

func (Row) GetBorrow

func (r Row) GetBorrow(paramIdx int) *Borrow

GetBorrow returns borrow effect for a specific parameter, if any.

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

func (r Row) GetIterator() *Iterator

GetIterator returns the first iterator effect in the row.

func (Row) GetMutate

func (r Row) GetMutate(paramIdx int) *Mutate

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

func (r Row) GetReturn(retIdx int) *Return

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) GetStore

func (r Row) GetStore(paramIdx int) *Store

GetStore returns store effect for a specific parameter, if any.

func (Row) GetTableMutator

func (r Row) GetTableMutator() *TableMutator

GetTableMutator returns the first table mutator effect in the row.

func (Row) Has

func (r Row) Has(check func(Label) bool) bool

Has checks if the row contains a specific label type.

func (Row) HasBorrow

func (r Row) HasBorrow() bool

HasBorrow returns true if the row has any borrow effect.

func (Row) HasCallableType

func (r Row) HasCallableType() bool

HasCallableType returns true if the row has a callable type effect.

func (Row) HasDiverge

func (r Row) HasDiverge() bool

HasDiverge returns true if diverge is in the row.

func (Row) HasIO

func (r Row) HasIO() bool

HasIO returns true if io is in the row.

func (Row) HasIterator

func (r Row) HasIterator() bool

HasIterator returns true if any iterator effect is in the row.

func (Row) HasModuleLoad

func (r Row) HasModuleLoad() bool

HasModuleLoad returns true if the row has a module load effect.

func (Row) HasMutate

func (r Row) HasMutate() bool

HasMutate returns true if any mutation is in the row.

func (Row) HasStore

func (r Row) HasStore() bool

HasStore returns true if the row has any store effect.

func (Row) HasTableMutator

func (r Row) HasTableMutator() bool

HasTableMutator returns true if any table mutator effect is in the row.

func (Row) HasThrow

func (r Row) HasThrow() bool

HasThrow returns true if throw is in the row.

func (Row) HasTypePredicate

func (r Row) HasTypePredicate() bool

HasTypePredicate returns true if the row has a type predicate effect.

func (Row) HasTypeValueMethod

func (r Row) HasTypeValueMethod() bool

HasTypeValueMethod returns true if the row has a type value method effect.

func (Row) HasVariadicTransform

func (r Row) HasVariadicTransform() bool

HasVariadicTransform returns true if the row has a variadic transform effect.

func (Row) IsClosed

func (r Row) IsClosed() bool

IsClosed returns true if this row has no tail variable.

func (Row) IsEffectInfo

func (r Row) IsEffectInfo()

IsEffectInfo implements typ.EffectInfo.

func (Row) IsIndexedIterator

func (r Row) IsIndexedIterator() bool

IsIndexedIterator returns true if the row has an indexed iterator (ipairs-style).

func (Row) IsKeyedIterator

func (r Row) IsKeyedIterator() bool

IsKeyedIterator returns true if the row has a keyed iterator (pairs-style).

func (Row) IsOpen

func (r Row) IsOpen() bool

IsOpen returns true if this row has a tail variable.

func (Row) IsUnknown

func (r Row) IsUnknown() bool

IsUnknown returns true if this is the unknown effect row.

func (Row) OnlyBorrows

func (r Row) OnlyBorrows() bool

OnlyBorrows returns true if function only borrows params (no store).

func (Row) Pure

func (r Row) Pure() bool

Pure returns true if this row has no effects.

func (Row) String

func (r Row) String() string

String formats the effect row for display.

func (Row) With

func (r Row) With(labels ...Label) Row

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

func (r Row) Without(match func(Label) bool) Row

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 SameAs

type SameAs struct {
	Source ParamRef
}

SameAs returns the exact type of a parameter.

func (SameAs) String

func (s SameAs) String() string

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

type SelectResultOfCases struct {
	Cases   ParamRef
	Default ParamRef
}

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.

func (Send) Equals

func (s Send) Equals(other Label) bool

func (Send) String

func (s Send) String() string

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).

func (Store) Equals

func (s Store) Equals(other Label) bool

func (Store) String

func (s Store) String() string

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 Throw

type Throw struct{}

Throw indicates the function may throw an error.

func (Throw) Equals

func (Throw) Equals(other Label) bool

func (Throw) String

func (Throw) String() string

type ToArray

type ToArray struct {
	Element ParamRef
}

ToArray transforms {} into T[].

func (ToArray) String

func (t ToArray) 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 Unchanged

type Unchanged struct{}

Unchanged indicates no type transformation.

func (Unchanged) String

func (Unchanged) 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".

func (*Var) String

func (v *Var) String() string

String returns the variable name.

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL