ops

package
v1.5.2 Latest Latest
Warning

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

Go to latest
Published: Feb 3, 2026 License: MIT Imports: 12 Imported by: 0

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:

  1. InferCall: Resolve callee, infer type arguments, compute ExpectedArgs
  2. Re-synthesize function literal arguments using ExpectedArgs from phase 1
  3. ReInfer: Re-infer type arguments with updated argument types
  4. 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

Constants

This section is empty.

Variables

This section is empty.

Functions

func CanBeFalsy

func CanBeFalsy(t typ.Type) bool

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

func ExtractFirstValue(t typ.Type) typ.Type

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

func HasLength(t typ.Type) bool

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

func InstantiateFunction(fn *typ.Function, typeArgs []typ.Type) *typ.Function

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

func IsBitwiseNumeric(t typ.Type) bool

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

func IsFalsy(t typ.Type) bool

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

func IsIntegerLiteral(value string) bool

IsIntegerLiteral checks if the string represents an integer literal.

func IsLiteralBoolean

func IsLiteralBoolean(t typ.Type) bool

IsLiteralBoolean checks if a type is a literal boolean (true or false).

func IsNumeric

func IsNumeric(t typ.Type) bool

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

func IsOrderable(t typ.Type) bool

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

func IsStringOnly(t typ.Type) bool

IsStringOnly checks if type is string (not number).

func IsStringable

func IsStringable(t typ.Type) bool

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

func IsTruthy(t typ.Type) bool

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

func LogicalAndTyped(left, right typ.Type) typ.Type

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

func LogicalOrTyped(left, right typ.Type) typ.Type

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

func ParseNumber(value string) typ.Type

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

func ParseNumberValue(value string) (float64, bool)

ParseNumberValue extracts the numeric value from a literal string. Returns the value and true for integers, or the value and false for floats.

func SubstituteTypeVars

func SubstituteTypeVars(t typ.Type, vars map[string]*typ.TypeVar) typ.Type

SubstituteTypeVars replaces TypeParam with TypeVar in a type.

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

func CheckTable(fields []FieldDef, arrayElems []typ.Type, expected typ.Type) CheckResult

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 FieldDef

type FieldDef struct {
	Name     string
	Type     typ.Type
	Optional bool
}

FieldDef describes a field in a table constructor.

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.

Jump to

Keyboard shortcuts

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