narrow

package
v1.5.6 Latest Latest
Warning

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

Go to latest
Published: Feb 11, 2026 License: MIT Imports: 5 Imported by: 0

Documentation

Overview

Package narrow provides type narrowing operations for control flow refinement.

Type narrowing reduces types based on runtime checks, enabling sound type refinement after conditionals. For example, "if x ~= nil" narrows x's type by removing nil, giving a more precise type in the then-branch.

Lua Truthiness Model

Lua has a simple truthiness model: only nil and false are falsy; all other values (including 0, "", empty tables, and userdata) are truthy. This package implements Lua's truthiness semantics for type narrowing, which differs from many other languages where 0 and "" might be falsy.

Architecture

The package is organized into several functional areas:

  • Core narrowing engine (narrow.go): The [narrowConfig] pattern provides a recursive traversal framework for implementing narrowing operations. Each operation defines handlers for optionals, unions, and leaf types.

  • Truthiness operations (narrow.go): ToTruthy, ToFalsy, RemoveNil, and RemoveFalse implement Lua's truthiness-based narrowing.

  • Kind-based narrowing (narrow.go): FilterByKind and ExcludeKind narrow types based on Lua's typeof results.

  • Type-based narrowing (narrow.go): ExcludeType and Intersect narrow using type relationships.

  • TypeKey narrowing (type_key.go, key_narrow.go): Compact type identifiers enable efficient storage and comparison of type constraints.

  • Filter-based narrowing (filter.go): The TypeMatcher pattern enables flexible predicate-based filtering.

  • Field-based narrowing (filter.go, match.go): ByFieldLiteral and ExcludeByFieldLiteral implement discriminated union narrowing.

  • Resolver interface (resolver.go): Abstracts field and index lookup to decouple narrowing from the full type checker.

Integration with Type System

The narrow package is used by the constraint solver during control flow analysis. When the solver encounters conditionals, it generates narrowing constraints that are applied using these operations:

  • if x then: Apply ToTruthy to x in the then-branch.
  • if not x then: Apply ToFalsy to x in the then-branch.
  • if type(x) == "number" then: Apply FilterByKind with kind.Number.
  • if x.kind == "a" then: Apply ByFieldLiteral with the literal.

Soundness Guarantees

All narrowing operations maintain type system soundness:

  • Never over-narrow: If exclusion would produce Never, preserve original.
  • Respect subtyping: TypesOverlap uses bidirectional subtype checks.
  • Handle placeholders: Any/Unknown types are handled conservatively.
  • Distinguish exact vs contains: Negative field checks use exact matching.

Example Usage

// Narrow optional to non-nil after guard.
optStr := typ.NewOptional(typ.String)
nonNil := narrow.RemoveNil(optStr)  // typ.String

// Narrow union by typeof check.
union := typ.NewUnion(typ.String, typ.Number, typ.Nil)
nums := narrow.FilterByKind(union, kind.Number)  // typ.Number

// Narrow discriminated union by field check.
narrowed := narrow.ByFieldLiteral(eventUnion, "kind", typ.LiteralString("click"), resolver)

Performance Considerations

Narrowing operations are designed for efficiency:

  • TypeKey uses hashing for fast type identity comparison.
  • Narrowing short-circuits when types are unchanged.
  • Union filtering is linear in the number of members.
  • No allocations when narrowing produces the same type.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ByFieldFalsy added in v1.5.2

func ByFieldFalsy(t typ.Type, field string, resolver Resolver) typ.Type

ByFieldFalsy narrows a type to members where a field can be falsy.

func ByFieldLiteral

func ByFieldLiteral(t typ.Type, field string, lit *typ.Literal, resolver Resolver) typ.Type

ByFieldLiteral narrows a type to members where a field equals a literal value.

This function is the primary mechanism for discriminated union narrowing in Lua. When a guard checks a specific field value (e.g., "if x.kind == 'a'"), this function narrows the type to only those union variants where the field matches the literal.

Discriminated Unions

A discriminated union uses a common field (discriminant) to distinguish between variants. For example:

type Event = {kind: "click", x: number, y: number}
           | {kind: "key", code: string}

After checking "event.kind == 'click'", the type narrows to just the click variant, enabling type-safe access to x and y.

Parameters

  • t: The type to narrow (typically a union of records).
  • field: The discriminant field name.
  • lit: The literal value to match against.
  • resolver: Provides field type lookup for the type.

Returns

Returns the narrowed type containing only variants where the field can match the literal. Returns the original type unchanged if any parameter is nil/empty or if the resolver is not provided.

Example

recA := typ.NewRecord().Field("kind", typ.LiteralString("a")).Build()
recB := typ.NewRecord().Field("kind", typ.LiteralString("b")).Build()
union := typ.NewUnion(recA, recB)
narrowed := ByFieldLiteral(union, "kind", typ.LiteralString("a"), resolver)
// narrowed is recA

func ByFieldTruthy added in v1.5.2

func ByFieldTruthy(t typ.Type, field string, resolver Resolver) typ.Type

ByFieldTruthy narrows a type to members where a field can be truthy.

func ByTypeKey

func ByTypeKey(t typ.Type, key TypeKey, resolve TypeResolver) typ.Type

ByTypeKey narrows a type to match a specific TypeKey identity.

This function is the primary entry point for type-based narrowing after typeof checks in Lua. It handles both built-in type checks (type(x) == "number") and user-defined type checks (via hash-based keys).

Builtin Keys

For built-in type keys (TypeKeyBuiltin), the function:

  1. Converts the key name to a kind via kind.FromString.
  2. Delegates to FilterByKind to narrow the type.

This handles Lua's typeof semantics where "table" matches multiple kinds (Record, Map, Array, etc.) and "number" matches both Number and Integer.

Hash Keys

For hash-based type keys (TypeKeyHash), the function:

  1. Uses the resolver to recover the full type.
  2. Computes the intersection of the base type and resolved type.

This enables narrowing to specific record or interface types.

Edge Cases

  • Nil input: Returns nil.
  • Zero key: Returns original type unchanged.
  • Unknown builtin kind: Returns original type unchanged.
  • Nil resolver for hash key: Returns original type unchanged.
  • Nil resolution: Returns original type unchanged.

Examples

// Narrow union to only number members.
key := BuiltinTypeKey("number")
narrowed := ByTypeKey(typ.NewUnion(typ.String, typ.Number), key, nil)
// narrowed is typ.Number

// Narrow to a specific record type.
key := HashTypeKey(myRecord.Hash())
narrowed := ByTypeKey(union, key, resolver)

func ExcludeByFieldLiteral

func ExcludeByFieldLiteral(t typ.Type, field string, lit *typ.Literal, resolver Resolver) typ.Type

ExcludeByFieldLiteral excludes union members where a field exactly equals a literal.

This function handles negative discriminant checks (field ~= value). Unlike ByFieldLiteral, it only excludes types where the field IS exactly the literal, not types where the field might contain it.

Exact vs Contains Semantics

The distinction between exact and contains matching is crucial for soundness:

  • Exact: field type is the singleton literal (e.g., kind: "a").
  • Contains: field type includes the literal (e.g., kind: string).

For negative checks (x.kind ~= "a"), we can only exclude variants where kind is exactly "a". A variant where kind is string cannot be excluded because it might hold any string value, not just "a".

Parameters

  • t: The type to narrow.
  • field: The discriminant field name.
  • lit: The literal value to exclude.
  • resolver: Provides field type lookup.

Returns

Returns the type with variants removed where the field is exactly the literal. Variants with broader field types (like string) are preserved.

Example

recA := typ.NewRecord().Field("kind", typ.LiteralString("a")).Build()
recB := typ.NewRecord().Field("kind", typ.String).Build()
union := typ.NewUnion(recA, recB)
narrowed := ExcludeByFieldLiteral(union, "kind", typ.LiteralString("a"), resolver)
// narrowed is recB (recA excluded, recB kept because kind: string is broader)

func ExcludeByTypeKey

func ExcludeByTypeKey(t typ.Type, key TypeKey, resolve TypeResolver) typ.Type

ExcludeByTypeKey removes types matching a TypeKey from the input type.

This function is the negative counterpart to ByTypeKey. It handles exclusion after negative typeof checks (type(x) ~= "number").

Builtin Keys

For built-in type keys, delegates to ExcludeKind to remove matching members.

Hash Keys

For hash-based keys, uses the resolver to recover the full type and excludes it via ExcludeType.

Never Preservation

If exclusion would result in Never (all members excluded), returns the original type unchanged. This prevents over-narrowing when the type check cannot definitively exclude all possibilities.

This is important for soundness: if we have type "any" and check "type(x) ~= 'number'", we cannot narrow to anything useful because "any" could be anything, and excluding number from any still leaves everything except number (which we cannot represent precisely).

Edge Cases

  • Nil input: Returns nil.
  • Zero key: Returns original type unchanged.
  • Unknown builtin kind: Returns original type unchanged.
  • Nil resolver for hash key: Returns original type unchanged.
  • Nil resolution: Returns original type unchanged.
  • Exclusion produces Never: Returns original type (preservation).

Examples

// Exclude string from a union.
key := BuiltinTypeKey("string")
narrowed := ExcludeByTypeKey(typ.NewUnion(typ.String, typ.Number), key, nil)
// narrowed is typ.Number

// Excluding the only member preserves original.
narrowed := ExcludeByTypeKey(typ.String, key, nil)
// narrowed is typ.String (not Never)

func ExcludeKind

func ExcludeKind(t typ.Type, target kind.Kind) typ.Type

ExcludeKind removes types matching the target kind from a type.

This operation narrows after a negative typeof check like "type(x) ~= 'string'". It removes all union members whose kind matches the target.

Lua Kind Mapping

In Lua, typeof returns: "nil", "boolean", "number", "string", "function", "table", "thread", "userdata". The kind.Record matches all table-like types (Record, Map, Array, Tuple, Interface, Intersection).

Behavior by Type

  • Union: Removes members matching target kind.
  • Optional: If target is Nil, returns inner; else recurses.
  • Placeholder (Any, Unknown): Returns unchanged.
  • Interface: Returns unchanged (interfaces need type-based exclusion).
  • Other: Returns Never if kind matches; else unchanged.

Examples

ExcludeKind(typ.NewUnion(typ.String, typ.Number), kind.String) // Number
ExcludeKind(typ.NewOptional(typ.String), kind.Nil)              // String

func ExcludeType

func ExcludeType(t typ.Type, excluded typ.Type) typ.Type

ExcludeType removes union members that overlap with the excluded type.

For discriminated union narrowing, this operation removes variants that match a specific type after a negative type check.

Behavior by Type

  • Union: Removes members that overlap with excluded; returns remaining.
  • Optional<T>: If T overlaps with excluded, returns nil; else unchanged.
  • Placeholder (Any, Unknown): Returns unchanged (cannot narrow).
  • Other: Returns Never if overlaps with excluded; else unchanged.

Examples

ExcludeType(typ.NewUnion(typ.String, typ.Number), typ.String) // Number
ExcludeType(typ.String, typ.String)                           // Never
ExcludeType(typ.String, typ.Number)                           // String

func FieldIsExactlyLiteral

func FieldIsExactlyLiteral(t typ.Type, field string, lit *typ.Literal, resolver Resolver) bool

FieldIsExactlyLiteral returns true only if the field type is exactly the literal.

This function checks whether a type's field has a singleton literal type that matches the given literal exactly. It returns false for broader types that contain the literal as a possible value.

Use Case

This is used by ExcludeByFieldLiteral to implement sound negative narrowing. Only types where the field is provably the excluded literal can be removed; types where the field might be something else must be kept.

Parameters

  • t: The type to check (typically a record).
  • field: The field name to look up.
  • lit: The literal to compare against.
  • resolver: Provides field type lookup.

Returns

Returns true if:

  1. The field exists on the type.
  2. The field's type is exactly the literal (via TypeIsExactlyLiteral).

Returns false if any parameter is nil, the field doesn't exist, or the field type is broader than the literal.

func FieldMatchesLiteral

func FieldMatchesLiteral(t typ.Type, field string, lit *typ.Literal, resolver Resolver) bool

FieldMatchesLiteral checks if a type's field can match a literal value.

This function is the core predicate for discriminated union narrowing. It determines whether a specific union variant should be kept when narrowing based on a field value check.

Matching Semantics

A field "matches" a literal if the literal is a valid value for that field. This is determined by typ.TypeMatchesLiteral, which checks if the literal is a subtype of the field type or vice versa.

For example:

  • Field type "a" matches literal "a" (exact match).
  • Field type string matches literal "a" (literal is subtype).
  • Field type "a" | "b" matches literal "a" (literal in union).
  • Field type number does NOT match literal "a" (incompatible).

Use Case

This function is used by ByFieldLiteral to filter union variants. For a check like "x.kind == 'a'", we want to keep variants where the kind field can possibly equal "a".

Parameters

  • t: The type to check (typically a record in a discriminated union).
  • field: The name of the discriminant field.
  • lit: The literal value being checked.
  • resolver: Provides field type lookup for the type.

Returns

Returns true if:

  1. All parameters are non-nil.
  2. The field exists on the type.
  3. The field's type is compatible with the literal.

Returns false if any parameter is nil, the field doesn't exist, or the field type is incompatible with the literal.

Example

rec := typ.NewRecord().Field("kind", typ.LiteralString("a")).Build()
lit := typ.LiteralString("a")
matches := FieldMatchesLiteral(rec, "kind", lit, resolver) // true

rec := typ.NewRecord().Field("kind", typ.Number).Build()
matches := FieldMatchesLiteral(rec, "kind", lit, resolver) // false

func FilterByKind

func FilterByKind(t typ.Type, target kind.Kind) typ.Type

FilterByKind narrows a type to keep only parts matching the target kind.

This operation narrows after a positive typeof check like "type(x) == 'string'". It is the inverse of ExcludeKind.

Behavior by Type

  • Placeholder (Any, Unknown): Returns the canonical type for the kind.
  • Union: Keeps members matching target kind.
  • Optional: If target is Nil, returns nil; else recurses into inner.
  • Other: Returns type if kind matches; else Never.

Examples

FilterByKind(typ.NewUnion(typ.String, typ.Number), kind.String) // String
FilterByKind(typ.Any, kind.Number)                               // Number
FilterByKind(typ.NewOptional(typ.String), kind.Nil)              // nil

func FilterByMatch

func FilterByMatch(t typ.Type, matches TypeMatcher, exclude bool) typ.Type

FilterByMatch filters a type by applying a matcher predicate with configurable polarity (narrow vs exclude).

This is the core filtering function used by field-based and type-key-based narrowing. It provides a flexible way to narrow types based on arbitrary predicates.

Narrowing Mode (exclude=false)

Keeps only type members where the matcher returns true:

  • Union: keeps members where matcher(member) == true.
  • Optional: returns inner type if matcher(inner) == true, else Never.
  • Intersection: returns type if matcher(intersection) == true, else Never.
  • Other: returns type if matcher(type) == true, else Never.

Exclusion Mode (exclude=true)

Removes type members where the matcher returns true:

  • Union: keeps members where matcher(member) == false.
  • Optional: returns Nil if matcher(inner) == true, else unchanged.
  • Intersection: returns Never if matcher(intersection) == true, else unchanged.
  • Other: returns Never if matcher(type) == true, else unchanged.

Type Unwrapping

Aliases and instantiated generics are unwrapped before matching. The matcher receives the underlying concrete type, not wrapper types. This ensures consistent behavior regardless of how types are aliased or instantiated.

Intersection Handling

Intersections are treated atomically: the matcher is applied to the whole intersection, not distributed over members. This is because intersection members cannot be independently filtered without changing the type's meaning.

Examples

// Keep only string members from a union.
result := FilterByMatch(union, func(t typ.Type) bool {
    return t == typ.String
}, false)

// Exclude number members from a union.
result := FilterByMatch(union, func(t typ.Type) bool {
    return t.Kind() == kind.Number
}, true)

func Intersect

func Intersect(a, b typ.Type) typ.Type

Intersect computes the intersection of two types.

Type intersection produces the type containing values that belong to both input types. This is used for narrowing when multiple constraints apply to the same variable.

Algorithm

  1. If either type is a placeholder (Any, Unknown), return the other.
  2. Unwrap aliases and instantiated generics.
  3. If either is an intersection, merge members.
  4. If a <: b, return a (more specific); if b <: a, return b.
  5. For unions, filter to overlapping members.
  6. Otherwise, create a new intersection type.

Examples

Intersect(typ.String, typ.Any)                              // String
Intersect(typ.NewUnion(typ.String, typ.Number), typ.String) // String
Intersect(typ.String, typ.Number)                           // String & Number

func KindMatches

func KindMatches(t typ.Type, target kind.Kind) bool

KindMatches checks if a type matches the target Lua typeof kind.

This function implements Lua's typeof semantics for type narrowing. It handles special cases where multiple type system kinds map to a single Lua typeof result.

Special Cases

  • kind.Record matches: Record, Map, Array, Tuple, Interface, Intersection (all are "table" in Lua typeof).
  • kind.Number matches: Number and Integer (integer is a subtype of number).
  • Instantiated types: Match based on the underlying generic body's kind.

Examples

KindMatches(typ.String, kind.String)           // true
KindMatches(typ.Integer, kind.Number)          // true
KindMatches(typ.NewArray(typ.String), kind.Record) // true (arrays are tables)

func RemoveFalse

func RemoveFalse(t typ.Type) typ.Type

RemoveFalse removes literal false from a type, keeping only truthy boolean values.

This operation narrows boolean types after checks that exclude false. Combined with RemoveNil, it implements full truthiness narrowing.

Behavior by Type

  • Literal false: Returns Never.
  • Literal true: Returns true (unchanged).
  • Boolean: Returns true (the truthy boolean literal).
  • Optional<T>: Recurses into T; if T becomes Never, returns nil.
  • Union: Filters out members that become Never after recursion.
  • Other types: Returns unchanged (non-boolean types are unaffected).

Examples

RemoveFalse(typ.LiteralBool(false))  // Never
RemoveFalse(typ.Boolean)             // true
RemoveFalse(typ.String)              // String (unchanged)

func RemoveNil

func RemoveNil(t typ.Type) typ.Type

RemoveNil removes nil from a type, producing the non-nullable subset.

This operation is used when narrowing after a "x ~= nil" or "x" truthiness check in Lua. It handles all type structures that can contain nil:

Behavior by Type

  • nil: Returns Never (removing nil from nil leaves nothing).
  • Optional<T>: Returns T (the inner non-nil type).
  • Union containing nil: Returns union without nil members.
  • Union containing Optional<T>: Unwraps optional, keeps T.
  • Other types: Returns unchanged (already non-nullable).

Examples

RemoveNil(typ.Nil)                    // Never
RemoveNil(typ.NewOptional(typ.String)) // String
RemoveNil(typ.NewUnion(typ.String, typ.Nil)) // String
RemoveNil(typ.Number)                 // Number (unchanged)

func ToFalsy

func ToFalsy(t typ.Type) typ.Type

ToFalsy narrows a type to its falsy subset, keeping only nil and false.

In Lua, only nil and false are falsy. This operation produces the type that remains after a truthiness check fails (the "else" branch of "if x then").

Behavior by Type

  • nil: Returns nil.
  • Literal false: Returns false.
  • Literal true: Returns Never (true is truthy).
  • Boolean: Returns false (the falsy boolean literal).
  • Optional<T>: Returns nil | ToFalsy(T).
  • Union: Collects falsy parts from each member.
  • Placeholder types (Any, Unknown): Returns nil | false (conservative).
  • FieldAccess, IndexAccess: Returns nil | false (deferred types).
  • Other types: Returns Never (all other types are truthy).

Examples

ToFalsy(typ.NewOptional(typ.String))  // nil
ToFalsy(typ.Boolean)                  // false
ToFalsy(typ.String)                   // Never
ToFalsy(typ.Any)                      // nil | false

func ToTruthy

func ToTruthy(t typ.Type) typ.Type

ToTruthy narrows a type to its truthy subset by removing nil and false.

In Lua, only nil and false are falsy. This operation produces the type that remains after a truthiness check succeeds (the "then" branch of "if x then").

ToTruthy is equivalent to RemoveFalse(RemoveNil(t)).

Examples

ToTruthy(typ.NewOptional(typ.String))          // String
ToTruthy(typ.Boolean)                          // true
ToTruthy(typ.NewUnion(typ.String, typ.Nil))    // String
ToTruthy(typ.NewUnion(typ.Nil, typ.False))     // Never

func TypeForKind

func TypeForKind(k kind.Kind) typ.Type

TypeForKind returns the canonical type for a Lua typeof kind.

This mapping is used when narrowing placeholder types (Any, Unknown) by kind. It returns the broadest type for each Lua typeof result.

Mapping

  • Nil: typ.Nil
  • Boolean: typ.Boolean
  • Number: typ.Number
  • Integer: typ.Integer
  • String: typ.String
  • Function: fun(...any) -> any
  • Any: typ.Any
  • Other: typ.Unknown (no canonical type available)

func TypeIsExactlyLiteral

func TypeIsExactlyLiteral(t typ.Type, lit *typ.Literal) bool

TypeIsExactlyLiteral returns true only if the type is exactly the given literal.

This function distinguishes between a singleton literal type and broader types that contain the literal as a member. It is fundamental to sound negative narrowing.

Behavior by Type

  • Literal: Returns true if the literal values are equal.
  • Alias: Unwraps and checks the target type.
  • Union: Returns true only if ALL members are exactly the literal.
  • Optional: Returns true if the inner type is exactly the literal.
  • Other: Returns false (primitives, records, etc. are broader).

Examples

TypeIsExactlyLiteral(typ.LiteralString("a"), typ.LiteralString("a")) // true
TypeIsExactlyLiteral(typ.String, typ.LiteralString("a"))             // false
TypeIsExactlyLiteral(typ.NewOptional(typ.LiteralString("a")), ...)   // true

Union Semantics

For unions, all members must be the exact literal. This handles the case of duplicate literal types in unions. A union like "a" | "b" would return false for either literal because not all members match.

func TypesOverlap

func TypesOverlap(a, b typ.Type) bool

TypesOverlap checks if two types have any potential runtime overlap.

Two types overlap if either is a subtype of the other. This is used by ExcludeType to determine which union members to remove.

Returns false if either argument is nil.

Examples

TypesOverlap(typ.String, typ.String)                    // true
TypesOverlap(typ.String, typ.NewUnion(typ.String, typ.Number)) // true
TypesOverlap(typ.String, typ.Number)                    // false

Types

type Resolver

type Resolver interface {
	// Field returns the type of field 'name' on type t.
	//
	// Returns (fieldType, true) if the field exists and is accessible.
	// Returns (nil, false) if the field does not exist or t is not a
	// structured type that supports field access.
	//
	// For records, this looks up named fields.
	// For interfaces, this looks up methods.
	Field(t typ.Type, name string) (typ.Type, bool)

	// Index returns the element type when indexing t with key.
	//
	// Returns (elementType, true) if the index operation is valid.
	// Returns (nil, false) if t does not support indexing with the given key.
	//
	// For arrays, key should be an integer type.
	// For maps, key should be compatible with the map's key type.
	// For tuples, key should be an integer literal within bounds.
	Index(t typ.Type, key typ.Type) (typ.Type, bool)
}

Resolver provides context-free type queries for field and index access.

Narrowing operations need to resolve field types (for discriminant checks) and index types (for array/map access) without access to full semantic context. Resolver abstracts these operations to decouple narrowing logic from the full type checker.

Purpose

The Resolver interface enables the narrow package to work with structured types (records, interfaces, arrays, maps) without depending on the full type checking infrastructure. This separation allows:

  • Testing narrowing logic in isolation.
  • Using narrowing from different contexts (checker, solver).
  • Avoiding circular dependencies between packages.

Implementation

Implementors should handle the following type structures:

For Field:

  • Record: Return the field's type if it exists.
  • Interface: Return the method's type if it exists.
  • Intersection: Try each member, return first match.
  • Union: Only if all members have the field with same type.

For Index:

  • Array: Return element type for integer keys.
  • Map: Return value type if key is compatible.
  • Tuple: Return element type for integer literal keys.
  • Record with map component: Return map value type.

Thread Safety

Resolver implementations should be safe for concurrent use if the narrowing operations may be called from multiple goroutines.

Example Implementation

type MyResolver struct {
    // ... fields for type environment ...
}

func (r *MyResolver) Field(t typ.Type, name string) (typ.Type, bool) {
    if rec, ok := t.(*typ.Record); ok {
        if f := rec.GetField(name); f != nil {
            return f.Type, true
        }
    }
    return nil, false
}

func (r *MyResolver) Index(t typ.Type, key typ.Type) (typ.Type, bool) {
    if arr, ok := t.(*typ.Array); ok {
        return arr.Element, true
    }
    return nil, false
}

type TypeKey

type TypeKey struct {
	Kind TypeKeyKind // Discriminant for key type.
	Name string      // Type name for built-in types (e.g., "number", "string").
	Hash uint64      // Content hash for user-defined types.
}

TypeKey identifies a type for narrowing operations using either a built-in type name or a content hash.

Type keys enable efficient type discrimination without carrying full type information. They are used by the constraint solver to represent type checks (HasType, NotHasType) in a compact form that can be stored in constraints and compared quickly.

Key Kinds

There are two kinds of type keys:

  • Builtin (TypeKeyBuiltin): Identifies primitive types by name. Used for typeof checks like "type(x) == 'number'". The Name field contains the Lua typeof string.

  • Hash (TypeKeyHash): Identifies user-defined types by structural hash. Used for interface/record type checks. The Hash field contains the type's content hash.

Thread Safety

TypeKey is a value type and safe to copy and use concurrently.

Examples

BuiltinTypeKey("number")   // identifies the number type
HashTypeKey(record.Hash()) // identifies a specific record type

func BuiltinTypeKey

func BuiltinTypeKey(name string) TypeKey

BuiltinTypeKey creates a TypeKey for a built-in Lua type name.

Built-in type keys are used for typeof-based narrowing. The name should be a valid Lua typeof result: "nil", "boolean", "number", "string", "function", "table", "thread", or "userdata".

Returns the zero TypeKey if name is empty.

func HashTypeKey

func HashTypeKey(hash uint64) TypeKey

HashTypeKey creates a TypeKey for a hash-based type identity.

Hash-based type keys are used for user-defined types (records, interfaces) where typeof would just return "table". The hash uniquely identifies the type's structure.

Returns the zero TypeKey if hash is 0.

func KnownBuiltinTypeKey added in v1.5.6

func KnownBuiltinTypeKey(name string) (TypeKey, bool)

KnownBuiltinTypeKey creates a TypeKey for a recognized Lua type() result.

Returns false when name is not a supported built-in type string.

func (TypeKey) BuiltinKind added in v1.5.6

func (k TypeKey) BuiltinKind() (kind.Kind, bool)

BuiltinKind resolves a built-in key name to a Kind.

Returns false when k is not a built-in key or when its name is unknown.

func (TypeKey) Equal

func (k TypeKey) Equal(other TypeKey) bool

Equal reports whether two type keys are identical.

Keys must match in kind, name (for builtins), and hash (for user types). Two zero keys are considered equal.

func (TypeKey) Hash64

func (k TypeKey) Hash64() uint64

Hash64 computes a 64-bit hash for the type key suitable for use in hash tables.

The hash combines the key kind with either the name hash (for builtins) or the type hash (for user types). This enables efficient storage in hash-based data structures.

Returns 0 for invalid/zero keys.

func (TypeKey) IsZero

func (k TypeKey) IsZero() bool

IsZero reports whether the key is the zero value (invalid/unset).

A zero key represents no type and should not be used for narrowing.

type TypeKeyKind

type TypeKeyKind uint8

TypeKeyKind distinguishes between type key variants.

const (
	// TypeKeyInvalid represents an uninitialized or invalid type key.
	// This is the zero value and indicates no type.
	TypeKeyInvalid TypeKeyKind = iota

	// TypeKeyBuiltin identifies a built-in Lua type by name.
	// The Name field contains the typeof string (e.g., "number", "string").
	TypeKeyBuiltin

	// TypeKeyHash identifies a user-defined type by structural hash.
	// The Hash field contains the type's content hash for equality comparison.
	TypeKeyHash
)

type TypeMatcher

type TypeMatcher func(t typ.Type) bool

TypeMatcher is a predicate function that determines whether a type satisfies a specific criterion for narrowing purposes.

Type matchers enable flexible narrowing by abstracting the matching logic. They are used with FilterByMatch to selectively keep or exclude union members based on arbitrary type properties such as:

  • Kind matching (type(x) == "number")
  • Discriminant checks (x.kind == "a")
  • Structural properties (has certain fields)

The matcher receives individual union members (not the union itself) and returns true if the member should be kept (for narrowing) or removed (for exclusion).

Example

matcher := func(t typ.Type) bool {
    return t.Kind() == kind.Number
}
result := FilterByMatch(union, matcher, false)  // keep numbers
result := FilterByMatch(union, matcher, true)   // exclude numbers

type TypeResolver

type TypeResolver func(TypeKey) typ.Type

TypeResolver resolves a concrete type from a type key.

Used during constraint application to recover full type information from the compact key representation. The resolver maps:

  • Builtin keys to their corresponding primitive types.
  • Hash keys to their registered user-defined types.

The resolver is typically provided by the type environment that tracks all defined types in the program.

Returns nil if the key cannot be resolved (unknown type).

Jump to

Keyboard shortcuts

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