Skip to content

Predicate Reflection

Accepted

Accepted for V1 conformance, structural-satisfaction, and primitive type-kind predicates; negative facts, runtime value narrowing, and broader predicate logic are deferred.

Type.Predicate is a comptime-only value for reflection checks that can carry facts into a guarded declaration or comptime branch.

V1 fact-bearing predicates include:

  • semantic conformance facts from T.implements(C)
  • structural satisfaction facts from T.satisfies(Shape)
  • type-kind facts from primitive Type classifiers such as T.is_concrete(), T.is_constraint(), T.is_sized(), T.has_fields(), and T.is_dyn()
  • dyn-safe contract facts from C.is_dyn_safe()

Simple property checks return bool when a true result does not grant sema a reusable fact. Primitive Type kind classifiers may return Type.Predicate because branch-local facts such as concrete, constraint, sized, aggregate, field-bearing type, structural constraint, semantic contract, or dyn erased type can make later reflection and type checking more precise.

Dyn-safety is a property of an applied semantic contract surface. C.is_dyn_safe() returns a Type.Predicate; when true, it carries a .dyn_safe_contract fact for the checked contract C. For wrong targets, including non-contract types, structural constraints, mixed intersections, not-fully-applied contracts, and already erased dyn C types, is_dyn_safe() returns false and carries no fact. The metadata API C.dyn_safety() reports structured wrong-target and failure details.

Predicate and Metadata APIs

Conformance lookup has a predicate API and a metadata API:

T.implements(Contract, scope: ?Scope = null) Type.Predicate

T.conformance(Contract, scope: ?Scope = null) Type.Conformance!Type.ConformanceLookupError

Structural satisfaction follows the same split:

T.satisfies(Shape, scope: ?Scope = null) Type.Predicate

T.satisfaction(Shape, scope: ?Scope = null) Type.Satisfaction!Type.SatisfactionError

Predicate APIs answer whether a fact is true and may carry that fact into sema. Metadata APIs return resolved details and structured failures for diagnostics, tooling, and deeper comptime logic.

In V1, T must be a concrete type for implements and satisfies. Checking whether one constraint implies another is deferred.

Lookup Scope

Visibility-dependent reflection APIs take an optional comptime Scope value with a default of null. This is a default parameter, not a function overload set:

T.implements(C)      // omits the defaulted scope argument
T.implements(C, scope)

When the argument is null, the public reflection API resolves the lookup scope by evaluating Scope.caller() in the called API body before it delegates further. Scope.caller() is not used as a default parameter expression; default parameter expressions cannot call Scope.caller().

Scope.caller() is available only inside functions evaluated at comptime. It returns the lexical/module visibility scope of the direct call expression. It is one frame only; library code that wants to preserve caller visibility through internal calls must capture Scope.caller() at the public boundary and pass the scope explicitly.

Scope.current() returns the lexical/module visibility scope where it appears. At top level, use Scope.current().

Scope is a comptime-only value. It may be stored in consts, compared for identity, and passed to reflection APIs. It may expose stable diagnostic metadata such as module or declaration names, but not unstable compiler-internal IDs. It is not a runtime value and does not grant mutation authority.

Fact Model

Type.Predicate is inspectable and passable through comptime code:

const Predicate = struct {
  fn value(self: Self) bool
  fn facts_when_true(self: Self) Type.PredicateFacts
  fn facts_when_false(self: Self) Type.PredicateFacts
}

Fact-bearing predicates are not directly aggregate-constructible. They are produced by compiler-checked reflection operations such as T.implements(...) and T.satisfies(...).

A factless predicate may be created from a boolean:

Type.Predicate.from_bool(condition)

Predicate facts are ordinary read-only reflection metadata:

const PredicateFacts = struct {
  implements: []const Type.ImplementsFact
  satisfies: []const Type.SatisfiesFact
  type_kinds: []const Type.TypeKindFact
  dyn_safe_contracts: []const Type.DynSafeContractFact
}

const ImplementsFact = struct {
  subject: Type
  contract: Type
  scope: Scope
}

const SatisfiesFact = struct {
  subject: Type
  shape: Type
  scope: Scope
}

const TypeKind = enum {
  concrete,
  constraint,
  sized,
  structural,
  contract,
  aggregate,
  has_fields,
  dyn_erased,
  optional,
  plain_enum,
  enum_union,
}

const TypeKindFact = struct {
  subject: Type
  kind: Type.TypeKind
}

const DynSafeContractFact = struct {
  contract: Type
}

Each fact category has its own metadata shape, so V1 does not use nullable fields whose validity depends on a separate tag. Empty categories are represented by empty slices.

Fact identity is category-specific:

  • implements facts use (subject, contract, scope);
  • satisfies facts use (subject, shape, scope);
  • type-kind facts use (subject, kind);
  • dyn-safe contract facts use (contract).

Combining predicates deduplicates facts within each category deterministically, preserving first occurrence where possible. Category display order is implements, satisfies, type_kinds, then dyn_safe_contracts.

A future concrete type union design may replace Type.PredicateFacts with a flat fact list, such as []const Type.Fact where Type.Fact = ImplementsFact | SatisfiesFact | TypeKindFact | DynSafeContractFact. See CEP-0001: Concrete Type Unions.

facts_when_false exists for future negative or alternative narrowing. V1 producers normally leave it empty, and V1 sema does not use negative facts.

V1 does not define equality or hashing for Type.Predicate. Tests and diagnostics should inspect the predicate boolean value and deterministic fact metadata instead.

Fact Preservation

Facts are preserved while the static type remains Type.Predicate:

const p = T.implements(PartialEq(T))       // Type.Predicate; facts preserved
const b: bool = p                          // bool; facts discarded

Facts survive storage in consts, comptime parameters, and return values:

fn comparable(comptime T: Type) Type.Predicate {
  return T.implements(PartialEq(T))
}

Facts are discarded when a predicate coerces to bool or is wrapped in a type that does not preserve predicate identity.

Type.Predicate is valid only in comptime positions: comptime constants, comptime parameters, and return values of comptime-evaluated functions. It is invalid in runtime layout, runtime fields, arrays, ordinary runtime parameters, or runtime returns.

Narrowing

If a Type.Predicate controls a comptime if condition or declaration guard without first being coerced to bool, sema may apply its true facts to the controlled branch or guarded declaration body.

Facts can narrow stable comptime Type bindings and parameters:

fn maybe_contains(comptime T: Type, comptime S: Sequence(T), xs: *const S, value: *const T) bool {
  if T.implements(PartialEq(T)) {
    return xs.contains(value)
  }

  return false
}

Facts do not attach to arbitrary re-evaluated expressions. Bind the type first if a branch needs narrowing.

Narrowing facts are lexical and branch-local:

  • if facts apply only in the true branch.
  • declaration guard facts apply only to that declaration body or surface.
  • else receives no negative fact in V1.
  • facts do not leak after the branch.

Declaration guards accept comptime-known bool or Type.Predicate. Boolean guards decide availability but do not add facts.

Boolean Operators

For Type.Predicate values:

  • p and q short-circuits.
  • the RHS of q is checked under facts from p.
  • the true branch of p and q receives facts from both predicates.
  • p or q has boolean behavior but does not add positive facts in V1.
  • not p has boolean behavior but does not add negative facts in V1.
  • mixed Type.Predicate and bool expressions are allowed, but only fact-bearing predicate subexpressions connected by and contribute facts.

Boundaries

Deferred:

  • negative facts
  • or and not fact reasoning
  • enum-union or error-set narrowing
  • runtime type/pattern facts from is
  • layout, realtime, allocation, and aliasing facts
  • equality, ordering, or hashing for Type.Predicate
  • runtime representation, formatting, serialization, or ABI for predicates

Comptime diagnostics may format predicates for human-readable messages, but this is not runtime reflection or stable serialization.