Skip to content

Dyn-Safety Reflection

Accepted

Accepted for the V1 dyn-safety predicate and metadata APIs.

Dyn-safety reflection describes whether a fully applied semantic contract surface can be erased into dyn C. It reflects the language rule owned by Contract Dispatch; it does not redefine dyn-safety.

Predicate API

Dyn-safety has a predicate API and a metadata API:

C.is_dyn_safe() Type.Predicate

C.dyn_safety() Type.DynSafety!Type.ReflectionError

is_dyn_safe() is valid as a boolean-testable predicate on any Type value. It returns true only when C is a fully applied dyn-safe semantic contract constraint, including contract-only intersections. When true, it carries a .dyn_safe_contract fact for the checked contract.

Wrong targets return false and carry no fact. Wrong targets include:

  • non-contract concrete types
  • structural constraints
  • mixed structural/semantic intersections
  • not-fully-applied contract factories
  • already erased dyn C types

Use dyn_safety() when diagnostics or tooling need structured wrong-target or failure details.

Metadata API

dyn_safety() reports structured metadata for valid fully applied contract surfaces:

const DynSafety = struct {
  ok: bool
  contract: Type
  active_operations: []const Type.ContractOperation
  failures: []const Type.DynSafetyFailure
  surface_failures: []const Type.DynSafetySurfaceFailure
}

contract is the normalized fully applied semantic contract constraint that was checked. dyn_safety() requires a fully applied semantic contract constraint; generic contract factories or not-fully-applied contracts are wrong targets and report Type.ReflectionError. Redundant source spelling belongs in source metadata or diagnostics, not in this semantic reflection result.

active_operations is the flattened dynamic-mode active surface after substituting contract parameters, normalizing contract intersections, and evaluating guards with ContractSurface.current().is_dyn() = true. It includes inherited/base operations and default methods that are active for the checked dynamic surface, including operations that later fail dyn-safety. Static-only guarded operations are absent and do not appear in active_operations. For contract-only intersections, this list describes the final normalized dynamic surface as a whole; duplicate components, reordered components, and redundant implied-base spellings do not create duplicate reflection entries.

active_operations preserves operation identity, not just unqualified operation names. If two visible intersection components contribute distinct same-name operations, both operations appear as separate Type.ContractOperation entries with their declaring contract identity preserved. Tooling should use that identity to render or require qualified calls.

failures and surface_failures are empty when ok = true. A valid applied contract that is not dyn-safe returns Type.DynSafety { ok = false, failures = ..., surface_failures = ... }; it is not a reflection error. Wrong-target cases are Type.ReflectionError results from dyn_safety(), not failure entries.

Failure Metadata

Dyn-safety failures are operation-centered:

const DynSafetyFailureKind = enum {
  operation_returns_self,
  operation_takes_self_by_value,
  self_in_non_receiver_position,
  operation_comptime_parameter,
  missing_receiver,
  non_concrete_runtime_parameter,
  non_concrete_runtime_return,
  static_only_type_in_vtable,
  unknown_error_type,
}

const DynSafetyFailure = struct {
  operation: Type.ContractOperation
  kind: Type.DynSafetyFailureKind
  param_index: ?usize
  ty: ?Type
  source: ?SourceLocation
  message: ?[]const u8
}

operation is always present because a DynSafetyFailure describes a failure in a valid applied contract surface. For parameter failures, param_index identifies the parameter in the active operation signature and ty records the failing type when available. For return failures, param_index = null and ty records the return type. For operation-level failures such as missing_receiver, both may be null.

Operation-level comptime parameters use operation_comptime_parameter, whether constrained or unconstrained. Contract-level comptime parameters are already substituted when the checked contract surface is fully applied.

Unknown or inferred error types use unknown_error_type. This includes explicit-success inferred-error forms such as T! and valueless inferred-error forms such as void! when they appear in the checked dynamic surface.

Self-related failures use these V1 classifications:

  • a bare Self return type uses operation_returns_self
  • a by-value Self parameter uses operation_takes_self_by_value, even if matching implementations are copyable or small
  • nested Self in a return or parameter type, and Self outside the receiver position, use self_in_non_receiver_position; this includes second parameters such as other: *const Self

A first parameter such as self: Box(Self) is not a V1 receiver form, so the primary failure kind is missing_receiver. Tooling may still report the nested Self in that parameter as diagnostic detail.

message is optional human-readable diagnostic help. Tooling should use kind, operation, param_index, ty, and source for deterministic behavior rather than parsing message.

Surface-level dyn-safety failures describe valid applied contract surfaces that fail as a whole rather than through one operation:

const DynSafetySurfaceFailureKind = enum {
  empty_dynamic_surface,
}

const DynSafetySurfaceFailure = struct {
  kind: Type.DynSafetySurfaceFailureKind
  source: ?SourceLocation
  message: ?[]const u8
}

An empty final dynamic surface reports empty_dynamic_surface in surface_failures. For contract-only intersections, this check applies to the final normalized surface as a whole, not to each component independently. Operation-centered failures remain in failures and always carry an operation.