Skip to content

Conditional Declarations

Accepted

Accepted for V1 compile-time declaration guards and predicate-fact use.

Declarations may use an if clause as a compile-time availability guard:

fn contains(self: *const Self, value: *const T) bool
  if T.implements(PartialEq(T))
{
}

The if clause is not a runtime branch. It controls whether the declaration participates in lookup for the current comptime facts. The guard expression must be a comptime-known bool or Type.Predicate.

Non-comptime guard expressions are semantic errors. A plain bool guard controls availability but does not create narrowing facts.

Narrowing predicates are comptime-only predicate values that also narrow the type environment. Initial narrowing predicates include method calls on comptime Type values:

TypeExpr.implements(ContractExpr)
TypeExpr.satisfies(ShapeExpr)

TypeExpr.implements(ContractExpr) returns a comptime-only Type.Predicate. It coerces to bool, but if it reaches an if condition or declaration guard as a predicate, sema can use its facts.

Predicate Reflection owns the Type.Predicate, fact preservation, and scope rules.

const can_compare = T.implements(PartialEq(T))       // Type.Predicate
const can_compare_bool: bool = can_compare           // bool, facts discarded

Predicate values may be stored in consts and passed through comptime functions without losing facts. The narrowing effect applies when sema sees a Type.Predicate controlling an if condition or declaration guard.

The guarded declaration body is checked with the guard facts in scope. For example, a method declared with if T.implements(PartialEq(T)) may use == on T values in its body.

The same predicate works in comptime if conditions. In the true branch, sema narrows the type environment with that conformance fact:

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
}

Guarded declarations that require the conformance are available inside the narrowed branch. This applies to both static generic values and dynamic contract values whose contract arguments are those narrowed comptime type facts.

V1 narrowing for type predicates is intentionally narrow:

  • TypeExpr.implements(ContractExpr) and TypeExpr.satisfies(ShapeExpr) return comptime-only Type.Predicate values
  • the narrowed type expression must be a stable comptime Type binding or parameter
  • the true branch implicitly records the conformance fact
  • and on predicates short-circuits normally; the RHS is checked under the LHS true facts
  • a top-level and of predicates records all true facts in the true branch
  • the fact is scoped to that branch and discarded afterward
  • or and not have boolean behavior but do not add positive or negative narrowing facts in V1
  • arbitrary bool function calls do not produce narrowing facts in V1 unless they return a fact-bearing Type.Predicate
  • negative reasoning in else branches is diagnostic-only at most

This is implicit refinement for conformance facts. Capture syntax is reserved for future value or pattern tests that actually bind new values.

Arbitrary comptime bool guards are allowed, but they do not create narrowing facts. Public API declarations guarded by build configuration or other unstable facts should produce lints because they make exported API shape configuration-dependent.

Guard expressions are evaluated in the declaration's lexical scope. For visibility-dependent predicates such as T.implements(C) and T.satisfies(Shape), the omitted scope is resolved to Scope.caller() by the called predicate API, so the guard uses the declaration scope as the caller scope. Public APIs whose guarded surface depends on optional imports, adapter modules, build configuration, or other unstable facts should produce lints unless the dependency is explicit and stable.

Runtime value/type narrowing should use is:

if value is SomeType {
}

This keeps as for implementation declarations and is for runtime type or pattern tests.