Celixir.Checker (Celixir v0.2.0)

Copy Markdown View Source

Static type checker for CEL expressions.

Validates that a parsed AST is well-typed given declared variable types. This is an optional step -- CEL expressions can be evaluated without type-checking, but checking catches errors earlier.

Supports full CEL type inference including wrapper types, message types, well-known types, abstract types (optionals), type parameters for overload resolution, and proto field lookups.

Usage

{:ok, ast} = Celixir.parse("x + 1")
declarations = %{"x" => :int}
:ok = Celixir.Checker.check(ast, declarations)

# With full type environment:
env = %{
  variables: %{"x" => :int},
  functions: %{"myFunc" => [%{id: "myFunc_int", params: [:int], result_type: :bool}]},
  container: nil
}
:ok = Celixir.Checker.check(ast, env)

Summary

Functions

Converts internal bottom types to :dyn in the final output. Call this on the result of infer/2 before comparing with expected types.

Returns the checker type for a known proto message field. Falls back to :dyn for unknown messages/fields.

Resolves the best matching overload for a function call given argument types. Returns the result type with type parameter substitution applied.

Unifies two CEL types. Returns the most specific common type.

Types

cel_type()

@type cel_type() ::
  :int
  | :uint
  | :double
  | :bool
  | :string
  | :bytes
  | :null_type
  | :timestamp
  | :duration
  | :dyn
  | {:list, cel_type()}
  | {:map, cel_type(), cel_type()}
  | {:wrapper, atom()}
  | {:message, String.t()}
  | {:well_known, atom()}
  | {:abstract, String.t(), [cel_type()]}
  | {:type_param, String.t()}
  | :type
  | :error

overload()

@type overload() :: %{id: String.t(), params: [cel_type()], result_type: cel_type()}

type_env()

@type type_env() :: %{
  variables: %{required(String.t()) => cel_type()},
  functions: %{required(String.t()) => [overload()]},
  container: String.t() | nil
}

Functions

check(ast, declarations \\ %{})

@spec check(Celixir.AST.expr(), map()) :: :ok | {:error, String.t()}

finalize_type(t)

Converts internal bottom types to :dyn in the final output. Call this on the result of infer/2 before comparing with expected types.

infer(ast, declarations \\ %{})

@spec infer(Celixir.AST.expr(), map()) :: cel_type() | {:error, String.t()}

proto_field_type(arg1, field)

@spec proto_field_type(String.t(), String.t()) :: cel_type()

Returns the checker type for a known proto message field. Falls back to :dyn for unknown messages/fields.

resolve_overload(overloads, arg_types)

@spec resolve_overload([overload()], [cel_type()]) :: cel_type()

Resolves the best matching overload for a function call given argument types. Returns the result type with type parameter substitution applied.

unify_types(a, a)

@spec unify_types(cel_type(), cel_type()) :: cel_type()

Unifies two CEL types. Returns the most specific common type.

CEL-specific: :dyn is the least specific type — when one side is :dyn, the other side wins.