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 Ctypes
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
Selfreturn type usesoperation_returns_self - a by-value
Selfparameter usesoperation_takes_self_by_value, even if matching implementations are copyable or small - nested
Selfin a return or parameter type, andSelfoutside the receiver position, useself_in_non_receiver_position; this includes second parameters such asother: *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.