Documentation
¶
Overview ¶
Package ops implements type synthesis operations for the type checker. The call.go file handles function call type synthesis, including generic type inference, method resolution, and argument type checking.
TWO-PHASE CALL SYNTHESIS ¶
Function calls with callback arguments require two-phase synthesis to enable contextual typing. The phases are:
- InferCall: Resolve callee, infer type arguments, compute ExpectedArgs
- Re-synthesize function literal arguments using ExpectedArgs from phase 1
- ReInfer: Re-infer type arguments with updated argument types
- FinishCall: Check arguments against parameters, compute return type
For simple calls without callbacks, CallWithGenericInference combines phases.
GENERIC TYPE INFERENCE ¶
Generic functions have their type parameters inferred from argument types. The inference algorithm:
- Collects constraints from argument-to-parameter matching
- Uses ExpectedReturn for bidirectional inference when available
- Instantiates the function with inferred type arguments
Example:
function get<T>(): T? end local x: string? = get() -- T inferred as string from ExpectedReturn
METHOD CALL HANDLING ¶
Method calls (obj:method(args)) are resolved by:
- Looking up the method on the receiver type
- Detecting explicit self parameters (first param named 'self' or matching receiver type)
- Applying Self type substitution in parameter and return types
UNION/INTERSECTION CALLEES
Union callees succeed if any member function accepts the arguments. The return type is the union of successful member return types.
Intersection callees require all members to accept the arguments. The return type is the intersection of all member return types.
ERROR HANDLING ¶
Call errors are accumulated in CallResult.Errors without stopping synthesis. This allows partial type information even when calls have type errors. Error kinds include: arity mismatches, type mismatches, optional calls, and generic inference failures.
Package ops implements type operations for expression synthesis.
This package provides the core type operations used during expression type synthesis: function calls, field access, indexing, and operators.
Call Operations ¶
[Call] handles function call type computation:
- Match arguments to parameters
- Instantiate generic type parameters
- Compute return types
- Validate argument compatibility
Field Access ¶
Field access on records and interfaces:
obj.field -- lookup field type in obj's type
Index Operations ¶
Index operations on arrays and maps:
arr[1] -- array element type map[key] -- map value type
Operators ¶
Binary and unary operators:
a + b -- numeric addition a .. b -- string concatenation not x -- boolean negation
Type Checking ¶
[Check] validates that an expression type is compatible with an expected type, producing diagnostics for mismatches.
Generic Instantiation ¶
[Generic] handles generic function instantiation, binding type parameters to concrete types based on argument types.
Package ops provides type synthesis for expressions and function calls.
For function call synthesis, use the two-phase approach:
infer := ops.InferCall(ctx, def) // Phase 1: resolve callee, infer type args // ... optionally re-synthesize args using infer.ExpectedArgs ... infer = ops.ReInfer(ctx, def, infer) // Re-infer with updated args result := ops.FinishCall(ctx, def, infer) // Phase 2: check args, compute return
For simple cases, CallWithGenericInference wraps the full flow.
Index ¶
- func CanBeFalsy(t typ.Type) bool
- func ExtractFirstValue(t typ.Type) typ.Type
- func HasLength(t typ.Type) bool
- func InferTypeArgs(fn *typ.Function, args []typ.Type, isMethod bool, receiver typ.Type) ([]typ.Type, error)
- func InferTypeArgsWithExpected(fn *typ.Function, args []typ.Type, isMethod bool, receiver typ.Type, ...) ([]typ.Type, error)
- func InstantiateFunction(fn *typ.Function, typeArgs []typ.Type) *typ.Function
- func IsBitwiseNumeric(t typ.Type) bool
- func IsFalsy(t typ.Type) bool
- func IsIntegerLiteral(value string) bool
- func IsLiteralBoolean(t typ.Type) bool
- func IsNumeric(t typ.Type) bool
- func IsOrderable(t typ.Type) bool
- func IsStringOnly(t typ.Type) bool
- func IsStringable(t typ.Type) bool
- func IsTruthy(t typ.Type) bool
- func LogicalAndTyped(left, right typ.Type) typ.Type
- func LogicalOrTyped(left, right typ.Type) typ.Type
- func ParseNumber(value string) typ.Type
- func ParseNumberValue(value string) (float64, bool)
- func SubstituteTypeVars(t typ.Type, vars map[string]*typ.TypeVar) typ.Type
- type CallDef
- type CallError
- type CallErrorKind
- type CallResult
- type CheckError
- type CheckResult
- type FieldDef
- type InferKind
- type InferResult
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CanBeFalsy ¶
CanBeFalsy reports whether a type can represent a falsy value (nil or false).
In Lua, only two values are falsy: nil and false. All other values (including 0, empty string, empty table) are truthy.
Type analysis:
- Optional types: Always falsy (can be nil)
- Union types: Falsy if ANY member can be falsy
- Intersection types: Falsy only if ALL members can be falsy
- Literal false: Falsy
- Boolean type: Falsy (could be false)
- Nil type: Falsy
- Arrays, maps, records, functions: Never falsy
- Placeholder types: Conservatively falsy
func ExtractFirstValue ¶
ExtractFirstValue returns the first element if t is a Tuple, otherwise returns t unchanged. In Lua, when a multi-return function is used in a context expecting single value, only the first is used.
func HasLength ¶
HasLength checks if a type supports the length operator (#).
In Lua, the following types have length:
- Strings (byte count)
- Arrays (element count)
- Tables/records (via __len metamethod or table length)
- Tuples (element count)
- Maps (entry count)
func InferTypeArgs ¶
func InferTypeArgs(fn *typ.Function, args []typ.Type, isMethod bool, receiver typ.Type) ([]typ.Type, error)
InferTypeArgs infers type arguments for a generic function call from argument types.
Uses local type inference with contravariant matching: argument types flow up to constrain type parameters. For each argument/parameter pair, creates constraints that the argument type is a subtype of the parameter type (after substituting type variables).
Example: For `function get<T>(x: T): T`, calling `get("hello")` infers T=string.
Returns the inferred type arguments in parameter order, or error if inference fails. Unresolved type parameters default to typ.Unknown.
func InferTypeArgsWithExpected ¶
func InferTypeArgsWithExpected(fn *typ.Function, args []typ.Type, isMethod bool, receiver typ.Type, expectedReturn typ.Type) ([]typ.Type, error)
InferTypeArgsWithExpected performs bidirectional type argument inference.
In addition to contravariant matching from arguments, uses covariant matching from the expected return type. This enables inference in cases where type parameters appear only in return position.
Example: For `function get<T>(): T?`, given expected type `string?`, infers T=string by matching return T? against expected string?.
The expectedReturn is matched covariantly: the function's return type should be a subtype of what's expected. For union expected types, matches against each union member.
func InstantiateFunction ¶
InstantiateFunction creates a concrete function type by substituting type arguments.
Given a generic function and type arguments (one per type parameter), replaces all occurrences of type parameters with their corresponding type arguments throughout parameter types, return types, and constraints.
Returns the original function if it's not generic or if typeArgs length doesn't match the number of type parameters.
func IsBitwiseNumeric ¶
IsBitwiseNumeric checks if a type supports bitwise operators (&, |, ~, <<, >>).
Only integer and number types (and placeholders) support bitwise operations. Unlike IsNumeric, this does not recursively check unions/aliases.
func IsFalsy ¶
IsFalsy reports whether a type is definitely falsy (always nil or false).
Returns true only for:
- typ.Nil
- Literal false
For types that may or may not be falsy (like boolean or optional), returns false. Use CanBeFalsy for that check.
func IsIntegerLiteral ¶
IsIntegerLiteral checks if the string represents an integer literal.
func IsLiteralBoolean ¶
IsLiteralBoolean checks if a type is a literal boolean (true or false).
func IsNumeric ¶
IsNumeric checks if a type supports arithmetic operations (+, -, *, /, %).
A type is numeric if:
- It has kind Number or Integer
- It's a literal number (int64 or float64)
- It's a union/intersection where all members are numeric
- It's an alias to a numeric type
- It's a type parameter with a numeric constraint
Optional types are NOT numeric - they must be narrowed first. Placeholder types (any, unknown) are considered numeric for flexibility.
func IsOrderable ¶
IsOrderable checks if a type supports comparison operators (<, <=, >, >=).
A type is orderable if:
- It has kind Number, Integer, or String
- It's a literal number or string
- It's a union/intersection where all members are orderable
- It's an alias to an orderable type
Notably, booleans and tables are NOT orderable in Lua.
func IsStringOnly ¶
IsStringOnly checks if type is string (not number).
func IsStringable ¶
IsStringable checks if a type can be used with string concatenation (..).
In Lua, both strings and numbers can be concatenated (numbers are coerced). Types implementing Error interface or with __tostring metamethod also work.
A type is stringable if:
- It has kind String, Number, or Integer
- It's the Error interface type
- It's a subtype of string (has __tostring)
- It's a literal string or number
- It's a union/intersection where all members are stringable
func IsTruthy ¶
IsTruthy reports whether a type is definitely truthy (can never be nil or false).
Returns true for types like string, number, tables, functions, etc. Returns false for optional types, boolean, nil, and types that could be falsy.
func LogicalAndTyped ¶
LogicalAndTyped synthesizes the type of a Lua 'and' expression.
Lua semantics: `a and b` returns `a` if `a` is falsy, otherwise returns `b`. The expression short-circuits: `b` is not evaluated if `a` is falsy.
Type analysis:
- If left is definitely truthy: result is right's type
- If left is definitely falsy: result is left's type
- Otherwise: union of (falsy part of left) and right
Examples:
- `true and x` -> type of x
- `nil and x` -> nil
- `string? and number` -> nil | number
func LogicalOrTyped ¶
LogicalOrTyped synthesizes the type of a Lua 'or' expression.
Lua semantics: `a or b` returns `a` if `a` is truthy, otherwise returns `b`. The expression short-circuits: `b` is not evaluated if `a` is truthy.
Type analysis:
- If left is definitely truthy: result is left's type
- If left is definitely falsy: result is right's type
- Otherwise: union of (truthy part of left) and right
Special case: For `any? or concrete`, prefers the concrete type. This handles the common default value pattern: `x = x or default`.
func ParseNumber ¶
ParseNumber parses a Lua numeric literal and returns its type.
Distinguishes between integer and float literals to enable integer-specific operations (bitwise operators, integer division).
Recognized formats:
- Hex: 0x1A, 0XFF -> LiteralInt
- Integer: 42, -7 -> LiteralInt
- Float: 3.14, 1e10, 2.5e-3 -> LiteralNumber
Returns typ.Number if parsing fails.
func ParseNumberValue ¶
ParseNumberValue extracts the numeric value from a literal string. Returns the value and true for integers, or the value and false for floats.
Types ¶
type CallDef ¶
type CallDef struct {
Callee typ.Type // The function being called
Args []typ.Type // Argument types
TypeArgs []typ.Type // Explicit type arguments for generic calls
IsMethod bool // True if this is a method call (obj:method)
Receiver typ.Type // Receiver type for method calls
MethodName string // Method name for method calls
Query core.TypeOps // Method/field resolver
// ExpectedReturn provides contextual typing for generic inference.
// When set, the expected return type guides type parameter inference
// for generic functions (e.g., inferring T in `get<T>(): T?` from `local x: string? = get()`).
ExpectedReturn typ.Type
}
CallDef describes a function call for type synthesis.
For direct calls (fn(args)):
- Set Callee to the function type
- Set Args to synthesized argument types
- Leave IsMethod=false
For method calls (obj:method(args)):
- Set Receiver to the object type
- Set MethodName to the method name
- Set Args to synthesized argument types (excluding receiver)
- Set IsMethod=true
- Set Query for method lookup
For generic calls, TypeArgs can provide explicit type arguments. If empty, type arguments are inferred from Args.
type CallError ¶
type CallError struct {
Kind CallErrorKind
Message string
ArgIdx int // For argument errors (1-based)
}
CallError describes a type error in a function call.
type CallErrorKind ¶
type CallErrorKind int
CallErrorKind identifies the type of call error.
const ( ErrNotCallable CallErrorKind = iota ErrWrongArity ErrTypeMismatch ErrOptionalCall ErrTypeInference )
type CallResult ¶
type CallResult struct {
Type typ.Type // Return type (or tuple for multiple returns)
Errors []CallError // Type errors detected
}
CallResult contains the synthesized return type and any errors from call checking.
Type is the computed return type (typ.Tuple for multiple returns, typ.Nil for void). Errors contains type mismatches, arity problems, and other call-related issues.
func CallWithGenericInference ¶
func CallWithGenericInference(ctx *db.QueryContext, def CallDef) CallResult
CallWithGenericInference synthesizes a call result with generic type inference. Wraps InferCall + FinishCall for cases where contextual re-synthesis is not needed. For the full two-phase flow with re-synthesis, use InferCall -> ReInfer -> FinishCall.
func FinishCall ¶
func FinishCall(ctx *db.QueryContext, def CallDef, infer InferResult) CallResult
FinishCall completes the call synthesis using the inference result. If InferResult.ShortCircuit is non-nil, this returns immediately with that type. Otherwise, performs full argument checking and return type computation.
type CheckError ¶
type CheckError struct {
Message string
Expected typ.Type
Got typ.Type
Field string // For field-specific errors
}
CheckError describes a type error during checking.
type CheckResult ¶
type CheckResult struct {
Type typ.Type // The checked/inferred type
Errors []CheckError // Type errors detected
}
CheckResult contains the result of bidirectional type checking.
Type is the checked type (may be the expected type if compatible, or synthesized type if checking fails). Errors contains field mismatches, missing required fields, and type incompatibilities.
func CheckTable ¶
CheckTable performs bidirectional type checking for table constructors.
Given a table constructor with named fields and array elements, checks compatibility with an expected type and infers the result type.
Behavior varies by expected type:
- nil: Pure synthesis mode, builds type from fields/elements
- Record: Checks field names/types match, reports missing/extra fields
- Array: Checks all elements are subtypes of element type
- Map: Checks keys and values against map types
- Tuple: Checks element count and position types
- Union: Finds best matching member, checks against it
- Intersection: Checks against all members
- Optional: Unwraps and checks inner type
Returns the checked type and any errors found.
type InferKind ¶
type InferKind int
InferKind identifies the type of callee after resolution.
const ( InferKindFunction InferKind = iota // Callee is a function InferKindUnion // Callee is a union of functions InferKindIntersection // Callee is an intersection of functions InferKindAny // Callee is any type InferKindUnknown // Callee is unknown type InferKindNotCallable // Callee is not callable )
type InferResult ¶
type InferResult struct {
// Kind indicates the type of resolved callee.
Kind InferKind
// Callee is the resolved callee type after method lookup and unwrapping.
Callee typ.Type
// Receiver is the resolved receiver type (after optional unwrapping).
Receiver typ.Type
// IsMethod indicates whether this is a method call.
IsMethod bool
// TypeArgs contains the type arguments (explicit or inferred).
TypeArgs []typ.Type
// Instantiated is the function after generic instantiation (nil if not generic).
Instantiated *typ.Function
// Function is the base function (before or after instantiation).
Function *typ.Function
// ExpectedArgs contains the expected type for each argument position.
// For method calls with explicit self, the receiver is NOT included.
// Self substitution is already applied.
ExpectedArgs []typ.Type
// ExpectedVariadic is the expected type for variadic arguments (nil if not variadic).
ExpectedVariadic typ.Type
// Errors contains errors accumulated during inference (e.g., optional call errors).
Errors []CallError
// ShortCircuit is the return type when callee is any/unknown (non-nil means skip FinishCall).
ShortCircuit typ.Type
}
InferResult contains results from the first phase of two-phase call synthesis.
The two-phase approach enables contextual typing for callback arguments: 1. InferCall: Resolve callee, infer type arguments, compute expected arg types 2. Re-synthesize function literal arguments using ExpectedArgs 3. ReInfer: Re-infer type arguments with updated argument types 4. FinishCall: Check arguments and compute return type
For non-callback cases, use CallWithGenericInference for single-phase synthesis.
Fields:
- Kind: Classification of the resolved callee
- Callee: The resolved callee type after method lookup
- ExpectedArgs: Expected types for each argument position (for contextual typing)
- ExpectedVariadic: Expected type for variadic arguments
- ShortCircuit: If non-nil, FinishCall returns this immediately (for any/unknown)
func InferCall ¶
func InferCall(ctx *db.QueryContext, def CallDef) InferResult
InferCall performs the first phase of call synthesis: callee resolution, type argument inference, and expected argument type computation. The returned InferResult contains ExpectedArgs that can be used to re-synthesize function literal arguments with contextual typing. Call FinishCall with updated arguments to complete the call.
func ReInfer ¶
func ReInfer(ctx *db.QueryContext, def CallDef, prev InferResult) InferResult
ReInfer performs re-inference after arguments have been updated. This is used when function literal arguments are re-synthesized with contextual typing, requiring fresh type argument inference.
func (*InferResult) ExpectedArgType ¶
func (r *InferResult) ExpectedArgType(idx int) typ.Type
ExpectedArgType returns the expected type for an argument at the given index. Uses ExpectedArgs for regular params and ExpectedVariadic for extra args. Returns nil if the index is out of range or no expected type is available.