View Source cure_types (cure v0.1.0)

Cure Programming Language - Type System Core

The core type system module implementing Cure's advanced dependent type system with constraint solving, type inference, and support for higher-kinded types. This module provides the foundational type operations that power Cure's static type checking and dependent type verification.

Key Features

Dependent Types

  • Value Dependencies: Types that depend on runtime values (e.g., Vector(T, n))
  • Constraint Solving: SMT-based constraint solving for dependent type verification
  • Type-Level Computation: Evaluation of type expressions with value parameters

Advanced Type System

  • Higher-Kinded Types: Support for type constructors and type families
  • Recursive Types: μ-types with cycle detection and well-formedness checking
  • Union Types: Discriminated union types with exhaustiveness checking
  • Generic Types: Full parametric polymorphism with constraint-based inference

Type Inference Engine

  • Bidirectional Inference: Combines bottom-up and top-down type inference
  • Constraint Generation: Generates and solves complex type constraints
  • Alternative Generation: Provides multiple type possibilities with confidence scores
  • Local Inference: Context-aware type inference for improved accuracy

Unification Algorithm

  • Robinson Unification: Extended Robinson unification for dependent types
  • Occurs Check: Prevents infinite types with comprehensive cycle detection
  • Constraint Propagation: Propagates type constraints through unification
  • Substitution Management: Efficient substitution composition and application

Core Operations

Type Variables

%% Create new type variables
TVar1 = cure_types:new_type_var(),
TVar2 = cure_types:new_type_var(custom_name).

%% Check type variable properties
true = cure_types:is_type_var(TVar1),
false = cure_types:occurs_check(TVar1, IntType).

Type Unification

%% Unify two types
{ok, Substitution} = cure_types:unify(Type1, Type2),
{ok, Sub, Constraints} = cure_types:unify(Type1, Type2, Environment).

Type Inference

%% Basic type inference
{ok, InferredType} = cure_types:infer_type(Expression, Environment),

%% Enhanced inference with alternatives
{ok, Result} = cure_types:enhanced_infer_type(Expression, Environment),
Confidence = Result#enhanced_inference_result.confidence,
Alternatives = Result#enhanced_inference_result.alternatives.

Type Environment

The type environment maintains variable bindings and constraints:

  • Hierarchical Scoping: Supports nested scopes with parent environments
  • Constraint Accumulation: Collects and manages type constraints
  • Efficient Lookup: Fast variable resolution with scope traversal
%% Environment operations
Env1 = cure_types:new_env(),
Env2 = cure_types:extend_env(Env1, variable_name, VariableType),
{ok, Type} = cure_types:lookup_env(Env2, variable_name).

Constraint Solving

Supports various constraint types:

  • Equality: T = U
  • Subtyping: T <: U
  • Element Membership: x elem_of T
  • Length Constraints: length(xs) = n
  • Logical Constraints: implies, iff
  • Variance Constraints: covariant, contravariant

Higher-Kinded Types

%% Create type constructors
ListKind = cure_types:create_kind('*', [], '*'),
FunctorKind = cure_types:create_kind('->', [ListKind], ListKind),

%% Type families
Family = cure_types:create_type_family(map, [F, T], Dependencies, Constraints),
Result = cure_types:evaluate_type_family(Family, Arguments).

Performance Characteristics

  • Type Inference: O(n log n) for most expressions
  • Unification: O(n) for structural types, O(n²) worst case
  • Constraint Solving: Depends on constraint complexity, uses SMT solver
  • Memory Usage: Efficient substitution sharing reduces memory overhead

Integration

This module integrates with:

  • Type Checker: Provides core type operations for checking
  • SMT Solver: Delegates complex constraint solving
  • Type Optimizer: Provides types for optimization decisions
  • Parser: Processes type annotations and expressions

Error Handling

Returns structured errors for:

  • Unification Failures: Detailed mismatch information
  • Constraint Violations: Specific constraint failure reasons
  • Infinite Types: Occurs check violations
  • Kind Errors: Higher-kinded type mismatches

Thread Safety

Type variables use a global counter that should be accessed safely in concurrent environments. The module is otherwise stateless and thread-safe.

Summary

Functions

Checks if recursive constraint solving has converged.

Extends a type environment with a new variable binding.

Extract type parameter names from a list that may contain atoms or records.

Find all free type variables in a type.

Find all free type variables in a type environment.

Create fresh type variables for a list of type parameters.

Generalize a type by quantifying over free type variables.

Performs type inference for recursive function calls with dependent type tracking.

Instantiate a poly_type record with fresh type variables.

Instantiate a poly_type record with provided type arguments.

Instantiate a polymorphic type with fresh type variables.

Instantiate a polymorphic type if needed, handling both poly_type and regular types.

Checks if a type expression represents the Nat type.

Checks if a term is a type variable.

Looks up a variable binding in the type environment.

Converts an Erlang integer to Peano-encoded Nat.

Constructs the successor of a natural number.

Converts a Peano-encoded Nat to an Erlang integer.

Constructs the Zero value of the Nat type.

Creates a new empty type environment.

Creates a new recursive inference state for tracking recursive function calls.

Creates a new unique type variable without a specific name.

Creates a new unique type variable with an optional name.

Performs an occurs check to prevent infinite types during unification.

Pops a recursive function call context from the call stack.

Pushes a recursive function call context onto the call stack.

Solves recursive type constraints using fixed-point computation.

Tracks dependent constraints within recursive function calls.

Unifies two types using the Robinson unification algorithm.

Unifies two types with an existing substitution.

Performs unification with recursive context tracking.

Functions

apply_type_constructor(TypeConstructor, Args, Location)

bidirectional_infer(Expr, ExpectedType, Env)

check_constraint_satisfaction(Constraint, KindEnv)

check_dependent_constraint(Constraint, Value, Env)

check_higher_kinded_well_formed(HKType)

check_kind(Type, ExpectedKind, KindEnv)

check_recursive_convergence(OldSubst, NewSubst, RecState)

Checks if recursive constraint solving has converged.

Convergence is determined by comparing the change in substitutions between iterations.

Arguments

  • OldSubst - Previous substitution
  • NewSubst - Current substitution
  • RecState - Recursive inference state with convergence threshold

Returns

  • {converged, FinalSubst} - If convergence achieved
  • {not_converged, MergedSubst} - If more iterations needed

check_recursive_type_well_formed/1

check_type(Expr, ExpectedType, Env)

check_type/4

constraint_propagation(Constraints, InitialSubst)

create_kind(Constructor, Args, Location)

create_recursive_type(Name, Params, Definition, Location)

create_type_constructor(Name, Kind, Params, Definition, Location)

create_type_family(Name, Kind, Equations, Location)

detect_cycles/2

enhanced_constraint_solving(Constraints, InitialSubst)

enhanced_infer_type(Expr, Env)

enhanced_infer_type(Expr, Env, Constraints)

evaluate_type_family(TypeFamily, Args)

evaluate_type_predicate/2

extend_env/3

Extends a type environment with a new variable binding.

Supports multiple environment representations for different use cases.

Arguments

  • Env - Type environment (type_env(), map(), or list())
  • Var - Variable name (atom())
  • Type - Type expression to bind to the variable

Returns

  • Updated environment with the new binding

Supported Environment Types

  • type_env(): Full environment with constraints and parent scopes
  • map(): Simple map for lightweight inference
  • list(): Association list for basic scoping

Example

Env1 = cure_types:new_env(),
Env2 = cure_types:extend_env(Env1, x, {primitive_type, 'Int'}),
Env3 = cure_types:extend_env(Env2, y, {primitive_type, 'String'}).

extract_param_info(Param1, Param2)

extract_type_param_names(TypeParams)

Extract type parameter names from a list that may contain atoms or records.

Arguments

  • TypeParams - List of atoms or #type_param_decl{} records

Returns

List of type parameter name atoms

extract_vector_params/1

fold_recursive_type(Type, Name)

free_type_vars/1

Find all free type variables in a type.

Arguments

  • Type - Type expression to analyze

Returns

Set of type variable names/ids that appear free in the type

free_type_vars_in_env/1

Find all free type variables in a type environment.

Arguments

  • Env - Type environment

Returns

Set of type variable names/ids that appear free in any type in the environment

fresh_type_vars_for_params(TypeParams)

Create fresh type variables for a list of type parameters.

Arguments

  • TypeParams - List of type parameter atoms or #type_param_decl{} records

Returns

A map from type parameter names to fresh type variables

Example

FreshVars = fresh_type_vars_for_params(['T', 'U']).
% Result: #{'T' => TypeVar1, 'U' => TypeVar2}

generalize_type(Type, Env)

Generalize a type by quantifying over free type variables.

Implements Hindley-Milner let-polymorphism by creating a poly_type that quantifies over type variables that are free in the type but not free in the environment.

Arguments

  • Type - Type to generalize
  • Env - Current type environment

Returns

Either a poly_type (if there are free type variables) or the original type

Example

% Type: T -> T where T is free
% Env: {x: Int}
% Result: forall T. T -> T
Type = {function_type, [TypeVar], TypeVar},
Generalized = generalize_type(Type, Env).

generate_list_alternatives/3

generate_type_alternatives(Expr, InferredType, Env)

get_tuple_param_info/1

infer_dependent_type(Expr, Env)

infer_kind(Type, KindEnv)

infer_pattern_type/3

infer_recursive_function_call(FunctionName, Args, Env, RecState)

Performs type inference for recursive function calls with dependent type tracking.

This is the main entry point for recursive function call type inference that properly handles dependent types across recursive boundaries.

Arguments

  • FunctionName - Name of the recursive function
  • Args - Argument expressions
  • Env - Type environment
  • RecState - Recursive inference state

Returns

  • {ok, ResultType, Constraints, UpdatedState} - Successful inference
  • {error, Reason} - Type inference failure

Features

  • Dependent Type Tracking: Maintains dependent relationships across calls
  • Fixed-Point Computation: Iterates until type constraints converge
  • Constraint Propagation: Propagates constraints between recursive levels
  • Cycle Detection: Prevents infinite recursion in type inference

infer_type(Expr, Env)

infer_type(Expr, Env, Constraints)

infer_with_alternatives(Expr, ExpectedType, Env)

instantiate_poly_type/1

Instantiate a poly_type record with fresh type variables.

Handles the poly_type AST record which explicitly represents polymorphic types with forall quantification.

Arguments

  • PolyType - A #poly_type{} record

Returns

The instantiated body type with fresh type variables

instantiate_poly_type/2

Instantiate a poly_type record with provided type arguments.

Handles explicit type application where concrete types are provided for type parameters (e.g., id<Int> applies Int to identity function).

Arguments

  • PolyType - A #poly_type{} record
  • TypeArgs - List of concrete types to substitute for type parameters

Returns

The instantiated body type with type arguments substituted

instantiate_polymorphic_type/2

Instantiate a polymorphic type with fresh type variables.

Given a type with type parameters (e.g., forall T. T -> T), creates a new type with fresh type variables replacing all type parameters.

Arguments

  • Type - The polymorphic type to instantiate
  • TypeParams - List of type parameter names to instantiate (e.g., ['T', 'U'])

Returns

The instantiated type with fresh type variables

Example

PolyType = {function_type, [{primitive_type, 'T'}], {primitive_type, 'T'}},
InstType = instantiate_polymorphic_type(PolyType, ['T']).
% Result: {function_type, [TypeVar1], TypeVar1} where TypeVar1 is fresh

instantiate_polymorphic_type_if_needed/1

Instantiate a polymorphic type if needed, handling both poly_type and regular types.

This is the main entry point for type instantiation during inference. It handles:

  • poly_type records: Instantiates with fresh type variables
  • Regular function types: Uses existing instantiate_type_if_function
  • Other types: Returns as-is

Arguments

  • Type - Any type expression

Returns

Instantiated type with fresh type variables for polymorphic types

instantiate_type_if_function(Type)

is_generic_type_variable_name(Name)

is_nat_type/1

Checks if a type expression represents the Nat type.

Recognizes both the algebraic Nat type (union type with Zero/Succ) and the refined Nat type (non-negative integers).

Arguments

  • Type - Type expression to check

Returns

  • true - If the type is Nat or Nat-related
  • false - Otherwise

Example

true = cure_types:is_nat_type({primitive_type, 'Nat'}),
true = cure_types:is_nat_type({union_type, 'Nat', _, _}),
true = cure_types:is_nat_type({refined_type, 'Int', ...}).

is_saturated_type(Type)

is_type_var/1

Checks if a term is a type variable.

Arguments

  • Term - Any term to check

Returns

  • true - If the term is a type_var record
  • false - Otherwise

Example

TVar = cure_types:new_type_var(),
true = cure_types:is_type_var(TVar),
false = cure_types:is_type_var(my_atom).

is_well_formed_type/1

kind_arity(Kind)

local_type_inference(Expr, InferredType, Env)

lookup_env/2

Looks up a variable binding in the type environment.

Searches the current environment and parent environments if available.

Arguments

  • Env - Type environment (type_env(), map(), or list())
  • Var - Variable name to look up (atom())

Returns

  • Type - The type bound to the variable if found
  • undefined - If the variable is not bound in the environment

Scoping

For type_env() records, searches parent environments if the variable is not found in the current scope.

Example

Env = cure_types:extend_env(cure_types:new_env(), x, IntType),
IntType = cure_types:lookup_env(Env, x),
undefined = cure_types:lookup_env(Env, unbound_var).

nat_from_int/1

Converts an Erlang integer to Peano-encoded Nat.

Creates a chain of Succ constructors wrapping Zero, representing the given non-negative integer.

Arguments

  • N - Non-negative integer

Returns

  • {ok, NatExpr} - Peano-encoded natural number
  • {error, negative_integer} - If N < 0

Example

{ok, Three} = cure_types:nat_from_int(3),
%% Results in Succ(Succ(Succ(Zero)))

nat_succ(Nat)

Constructs the successor of a natural number.

In Peano encoding, Succ(n) represents n+1, similar to Idris's S constructor.

Arguments

  • Nat - A natural number expression

Returns

  • An expression representing Succ(Nat) : Nat

Example

One = cure_types:nat_succ(cure_types:nat_zero()),
Two = cure_types:nat_succ(One),
%% Results in Succ(Succ(Zero))

nat_to_int/1

Converts a Peano-encoded Nat to an Erlang integer.

Unwraps the chain of Succ constructors to compute the integer value.

Arguments

  • NatExpr - Peano-encoded natural number expression

Returns

  • {ok, Integer} - The integer value
  • {error, invalid_nat} - If not a valid Nat expression

Example

{ok, Three} = cure_types:nat_from_int(3),
{ok, 3} = cure_types:nat_to_int(Three).

nat_zero()

Constructs the Zero value of the Nat type.

In Peano encoding, Zero is the base case for natural numbers, similar to Idris's Z constructor.

Returns

  • An expression representing Zero : Nat

Example

Zero = cure_types:nat_zero(),
%% Results in an identifier expression representing Zero

new_env()

Creates a new empty type environment.

The type environment maintains variable bindings, constraints, and supports hierarchical scoping through parent environments.

Returns

  • type_env() - A new empty type environment

Example

Env = cure_types:new_env(),
Env2 = cure_types:extend_env(Env, x, {primitive_type, 'Int'}),
{ok, Type} = cure_types:lookup_env(Env2, x).

Features

  • Hierarchical Scoping: Supports nested environments
  • Constraint Tracking: Accumulates type constraints
  • Efficient Lookup: Fast variable resolution

new_recursive_state()

Creates a new recursive inference state for tracking recursive function calls.

Returns

  • recursive_inference_state() - New state with empty call stack

Example

State = cure_types:new_recursive_state(),
UpdatedState = cure_types:push_recursive_call(factorial, ParamTypes, RetType, State).

new_type_var()

Creates a new unique type variable without a specific name.

This is a convenience function that calls new_type_var/1 with undefined name.

Returns

  • type_var() - A new unique type variable

Example

TVar = cure_types:new_type_var(),
true = cure_types:is_type_var(TVar).

Note

Uses a process dictionary counter to ensure uniqueness within a process. For concurrent use, external synchronization may be required.

new_type_var(Name)

Creates a new unique type variable with an optional name.

Arguments

  • Name - Optional name for the type variable (atom() | undefined)

Returns

  • type_var() - A new unique type variable with the given name

Example

TVar1 = cure_types:new_type_var(my_var),
TVar2 = cure_types:new_type_var(undefined),
true = TVar1#type_var.name =:= my_var.

Note

The name is primarily for debugging and error reporting. The unique ID ensures type variable identity regardless of name.

normalize_type(Type)

occurs_check/2

Performs an occurs check to prevent infinite types during unification.

The occurs check ensures that a type variable does not occur within the type it would be unified with, preventing infinite type structures.

Arguments

  • TypeVar - Type variable to check for
  • Type - Type expression to check within

Returns

  • true - If the type variable occurs in the type (would create infinite type)
  • false - If the unification would be safe

Example

TVar = cure_types:new_type_var(),
ListType = {list_type, TVar},
true = cure_types:occurs_check(TVar, ListType),  % Would create infinite list
false = cure_types:occurs_check(TVar, {primitive_type, 'Int'}).

Note

This is essential for preventing infinite types like T = List(T) during unification.

occurs_check_recursive/2

pop_recursive_call(State)

Pops a recursive function call context from the call stack.

Arguments

  • State - Current recursive inference state

Returns

  • {ok, Context, NewState} - Popped context and updated state
  • {error, empty_stack} - If the call stack is empty

push_recursive_call(FunctionName, ParameterTypes, ReturnType, State)

Pushes a recursive function call context onto the call stack.

This tracks recursive calls to enable proper dependent type handling across recursive boundaries.

Arguments

  • FunctionName - Name of the function being called recursively
  • ParameterTypes - Types of the parameters in this call
  • ReturnType - Expected return type of the function
  • State - Current recursive inference state

Returns

  • {ok, NewState} - Updated state with the call pushed
  • {error, Reason} - If maximum recursion depth exceeded

Example

{ok, NewState} = cure_types:push_recursive_call(
    factorial, [NType], FactorialRetType, State
).

safe_extract_param_value/1

solve_bounds_constraint(Type, Bounds, Subst)

solve_constraints(Constraints)

solve_constraints/2

solve_equivalence_constraint(Left, Right, Subst)

solve_implication_constraint(Left, Right, Subst)

solve_invariant_constraint(Type, InvariantSpec, Subst)

solve_recursive_constraints_fixed_point(Constraints, RecState)

Solves recursive type constraints using fixed-point computation.

This function iteratively refines type constraints until they converge, handling dependent types that may change across recursive calls.

Arguments

  • Constraints - List of type constraints to solve
  • RecState - Recursive inference state

Returns

  • {ok, Substitution, Iterations} - Converged solution
  • {error, Reason} - If convergence fails or max iterations exceeded

Algorithm

  1. Apply current substitution to constraints
  2. Solve constraints to get new substitution
  3. Check for convergence by comparing substitutions
  4. Repeat until convergence or max iterations reached

solve_type_family_equation(Equation, Args, KindEnv)

solve_variance_constraint(TypeConstructor, ParameterType, Variance, Subst)

substitute(Type, Subst)

track_dependent_constraints_in_recursion(Constraints, Context, RecState)

Tracks dependent constraints within recursive function calls.

This function maintains the relationships between dependent types across recursive call boundaries, ensuring type safety.

Arguments

  • Constraints - Type constraints from the current call
  • Context - Recursive call context
  • RecState - Current recursive inference state

Returns

  • {ok, TrackedConstraints, UpdatedState} - Success with tracked constraints
  • {error, Reason} - If constraint tracking fails

type_to_string/1

unfold_recursive_type/1

unfold_recursive_type/2

unify(Type1, Type2)

Unifies two types using the Robinson unification algorithm.

This is a convenience function that calls unify/3 with an empty substitution.

Arguments

  • Type1 - First type expression to unify
  • Type2 - Second type expression to unify

Returns

  • {ok, Substitution} - Successful unification with substitution map
  • {error, Reason} - Unification failure with detailed error

Example

{ok, Subst} = cure_types:unify(IntType, IntType),
{ok, Subst2} = cure_types:unify(TVar, IntType),
{error, _} = cure_types:unify(IntType, StringType).

Error Cases

  • Type mismatch (e.g., Int vs String)
  • Occurs check failure (infinite types)
  • Constraint violations

unify(Type1, Type2, Subst)

Unifies two types with an existing substitution.

Applies the existing substitution to both types before unification and composes the results.

Arguments

  • Type1 - First type expression to unify
  • Type2 - Second type expression to unify
  • Subst - Existing substitution map to compose with

Returns

  • {ok, NewSubstitution} - Combined substitution after unification
  • {error, Reason} - Unification failure with detailed error

Example

{ok, Subst1} = cure_types:unify(TVar1, IntType),
{ok, Subst2} = cure_types:unify(TVar2, StringType, Subst1),
%% Subst2 now contains bindings for both TVar1 and TVar2

Substitution Composition

The function applies the input substitution to both types before unification and composes the result with the input substitution.

unify_kinds(Kind1, Kind2)

unify_recursive_types(RecType1, RecType2, Subst)

unify_with_recursive_context(Types1, Types2, Context, RecState)

Performs unification with recursive context tracking.

This extends the standard unification algorithm to properly handle type variables and dependent types across recursive call boundaries.

Arguments

  • Types1 - First set of types to unify
  • Types2 - Second set of types to unify
  • Context - Recursive call context
  • RecState - Recursive inference state

Returns

  • {ok, Constraints, UpdatedState} - Successful unification with constraints
  • {error, Reason} - Unification failure