lib/alloc/phase/src/capacity/shape.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 //! This module validates owner declarations, fields, lifecycle, method
2 //! capabilities, exhaustion, and optional or required claims in that exact
3 //! order. The `require` functions convert validation failures into compile
4 //! errors and inspect the claim first to produce detailed diagnostics. A
5 //! successful present claim records typed references through the Stardust
6 //! observer, performing structural and type inspection without function body
7 //! effects or bound proofs.
8
9 const std = @import("std");
10 const capability = @import("capability.zig");
11 const declaration = @import("declaration.zig");
12 const observer = @import("observer");
13 const owner = @import("owner.zig");
14 const phase = @import("phase.zig");
15 const signature = @import("signature.zig");
16
17 const OwnerShape = owner.OwnerShape;
18 const Phase = phase.Phase;
19
20 /// Enumerates the first structural check failure detected when validating an
21 /// owner type against an expected `OwnerShape`. Tags cover missing or malformed
22 /// declarations, incorrect runtime fields, mismatched lifecycle signatures,
23 /// prohibited allocator capabilities, invalid exhaustion declarations, and
24 /// missing or invalid claims. When an owner declares an invalid claim,
25 /// `validateOwnerShape` collapses the underlying `DeclarationViolation` into
26 /// the single tag `invalid_claim`. In contrast, `requireOwnerShape` validates
27 /// the claim directly through `declaration.require` before running shape
28 /// checks, emitting detailed compile errors for specific declaration failures.
29 /// Because compile-time reflection can encounter malformed syntax or illegal
30 /// type definitions in user declarations, passing arbitrary malformed types to
31 /// validation functions may produce compiler errors rather than returning an
32 /// optional violation cleanly.
33 pub const OwnerShapeViolation = enum {
34 owner_not_struct,
35 missing_phase,
36 wrong_phase_type,
37 comptime_phase,
38 missing_capacity,
39 wrong_capacity_type,
40 comptime_capacity,
41 missing_limits_declaration,
42 wrong_limits_declaration,
43 missing_capacity_declaration,
44 wrong_capacity_declaration,
45 missing_storage_alignment,
46 wrong_storage_alignment,
47 invalid_storage_alignment,
48 missing_storage_declaration,
49 wrong_storage_declaration,
50 missing_storage,
51 wrong_storage_type,
52 comptime_storage,
53 missing_storage_bytes,
54 wrong_storage_bytes,
55 comptime_storage_bytes,
56 missing_work_limits,
57 wrong_work_limits,
58 invalid_work_limits,
59 missing_claim_declaration,
60 wrong_claim_declaration,
61 invalid_claim,
62 missing_exhaustion_declaration,
63 wrong_exhaustion_declaration,
64 exhaustion_not_observable,
65 stored_allocator,
66 missing_capacity_derive,
67 wrong_capacity_derive,
68 missing_init,
69 wrong_init,
70 missing_activate,
71 wrong_activate,
72 missing_deinit,
73 wrong_deinit,
74 steady_allocator_parameter,
75 steady_allocator_result,
76 };
77
78 /// This compile-time check validates an allocator_exact owner shape. It
79 /// inspects Limits and Capacity declarations, runtime phase and capacity
80 /// declarations, and allocator-backed lifecycle signatures. An immediate
81 /// capability scanner examines stored fields as well as known parameters and
82 /// results of non-lifecycle receiver methods, while lifecycle init and deinit
83 /// intentionally accept an Allocator. An optional claim is validated and
84 /// recorded through the Stardust observer. Any invalid declaration or shape
85 /// causes a compile error. The check does not run owner code.
86 pub fn requireAllocatorExactOwnerShape(comptime Owner: type) void {
87 requireOwnerShape(Owner, OwnerShape.allocator_exact);
88 }
89
90 /// This compile-time check validates an allocator_rejecting owner shape. It
91 /// shares allocator-backed fields and lifecycle signatures with allocator_exact
92 /// but enforces different overload requirements rather than all exact
93 /// requirements. The owner must expose a finite nonempty Exhaustion error set
94 /// on a non-lifecycle pointer-receiver method. Any optional claim is checked
95 /// for compatibility under the rejecting shape and recorded. The validation
96 /// performs structural and type checks only.
97 pub fn requireAllocatorRejectingOwnerShape(comptime Owner: type) void {
98 requireOwnerShape(Owner, OwnerShape.allocator_rejecting);
99 }
100
101 /// Enforces at compile time that an owner type conforms to the
102 /// caller-provisioned exact protocol (`OwnerShape.provisioned_exact`). The
103 /// owner must declare a positive power-of-two `storage_alignment`, matching
104 /// aligned slice `Storage`, and typed `work_limits`. Runtime fields must
105 /// include `phase: Phase`, `capacity: Capacity` (containing a runtime
106 /// `storage_bytes: usize`), and `storage: Storage`. Lifecycle functions must
107 /// implement specific signatures: `Capacity.derive` and `init` must return
108 /// finite nonempty error unions, `activate` must return `void` exactly, and
109 /// `deinit` must return `Storage` exactly. A typed `claim` declaration is
110 /// mandatory and validated. Recursive capability checks verify that stored
111 /// fields, `Limits`, `Capacity`, and non-lifecycle method signatures contain no
112 /// allocator capabilities. Any violation causes a compile error. Conformance
113 /// does not execute owner code or verify runtime buffer management.
114 pub fn requireProvisionedExactOwnerShape(comptime Owner: type) void {
115 requireOwnerShape(Owner, OwnerShape.provisioned_exact);
116 }
117
118 /// This compile-time check validates a provisioned_rejecting owner shape. It
119 /// shares provisioned storage, fields, lifecycle, and work rules with
120 /// provisioned_exact, but enforces distinct overload requirements instead of
121 /// all exact requirements. The owner must expose a finite nonempty Exhaustion
122 /// error set on a non-lifecycle pointer-receiver method, and its claim overload
123 /// kind must be reject_before_mutation. The validator verifies this
124 /// classification and records a valid claim without inspecting actual protected
125 /// payload or diagnostic mutation. Callers should read the owner claim detail
126 /// to identify protected state. Any failed declaration or shape triggers a
127 /// compile error.
128 pub fn requireProvisionedRejectingOwnerShape(comptime Owner: type) void {
129 requireOwnerShape(Owner, OwnerShape.provisioned_rejecting);
130 }
131
132 fn requireOwnerShape(comptime Owner: type, comptime expected: OwnerShape) void {
133 @setEvalBranchQuota(1_000_000);
134 if (comptime @typeInfo(Owner) == .@"struct" and
135 @hasDecl(Owner, "Limits") and
136 @hasDecl(Owner, "claim") and
137 @TypeOf(Owner.claim) == declaration.Declaration)
138 {
139 declaration.require(Owner, Owner.claim, expected);
140 }
141 if (comptime validateOwnerShape(Owner, expected)) |violation| {
142 @compileError("invalid owner shape: " ++ @tagName(violation));
143 }
144 }
145
146 /// Validates that an owner type conforms to `OwnerShape.allocator_exact`,
147 /// returning an optional `OwnerShapeViolation`. It uses an immediate shallow
148 /// capability scanner that inspects stored fields and method signatures for
149 /// direct references to `std.mem.Allocator` or container allocator factories.
150 /// If a valid typed `claim` is declared, the function records claim metadata at
151 /// compile time. A return value of `null` confirms that all structural checks
152 /// passed. It does not prove that owner methods avoid runtime heap allocation
153 /// or that dynamic memory behavior is infallible.
154 pub fn validateAllocatorExactOwnerShape(comptime Owner: type) ?OwnerShapeViolation {
155 return validateOwnerShape(Owner, OwnerShape.allocator_exact);
156 }
157
158 /// This function validates an allocator_rejecting owner shape, returning the
159 /// first OwnerShapeViolation or null. It shares allocator-backed lifecycle and
160 /// field rules with the exact shape but does not require all exact claim
161 /// policies. It instead adds an Exhaustion surface requirement and verifies
162 /// claim compatibility for the rejecting shape when a claim is present. The
163 /// function executes a shallow capability scan, records any valid present
164 /// claim, and does not prove error reachability.
165 pub fn validateAllocatorRejectingOwnerShape(comptime Owner: type) ?OwnerShapeViolation {
166 return validateOwnerShape(Owner, OwnerShape.allocator_rejecting);
167 }
168
169 /// This function validates a provisioned_exact owner shape, returning the first
170 /// violation or null. It verifies runtime phase, capacity, and storage
171 /// declarations, ensuring an aligned Storage type and a Capacity.storage_bytes
172 /// field. It also verifies a typed claim and a work_limits declaration, noting
173 /// that work_limits is a compile-time declaration rather than a runtime field.
174 /// A recursive capability scan inspects the owner including Limits and
175 /// Capacity, and any valid claim is recorded. The function does not execute
176 /// lifecycle methods.
177 pub fn validateProvisionedExactOwnerShape(comptime Owner: type) ?OwnerShapeViolation {
178 return validateOwnerShape(Owner, OwnerShape.provisioned_exact);
179 }
180
181 /// This function validates a provisioned_rejecting owner shape, returning the
182 /// first violation or null. It applies shared provisioned storage, lifecycle,
183 /// and work rules while enforcing a different overload classification. The
184 /// owner must expose a finite nonempty Exhaustion error set on a
185 /// pointer-receiver method and declare a reject_before_mutation claim overload.
186 /// A recursive capability scan is performed, and any valid claim is recorded.
187 /// The function does not itself check payload preservation.
188 pub fn validateProvisionedRejectingOwnerShape(comptime Owner: type) ?OwnerShapeViolation {
189 return validateOwnerShape(Owner, OwnerShape.provisioned_rejecting);
190 }
191
192 /// Inspects a type at compile time against an expected `OwnerShape`, returning
193 /// `null` if the type satisfies the protocol or the first `OwnerShapeViolation`
194 /// if a check fails. If the owner provides a valid typed `claim`, the function
195 /// records claim metadata through the Stardust observer, linking the claim to
196 /// `init`, default limits, and lifecycle family declarations. Allocator-backed
197 /// owners may omit a claim, in which case validation succeeds without recording
198 /// claim metadata. Provisioned owners require a valid claim. Validation
199 /// examines types, declarations, and signatures without executing owner
200 /// functions, allocating resources, or verifying that declared formal
201 /// obligations are mathematically discharged.
202 pub fn validateOwnerShape(
203 comptime Owner: type,
204 comptime expected: OwnerShape,
205 ) ?OwnerShapeViolation {
206 const owner_info = @typeInfo(Owner);
207 if (comptime owner_info != .@"struct") return .owner_not_struct;
208 if (comptime validateDeclarations(Owner, expected)) |violation| return violation;
209 if (comptime validateFields(Owner, expected)) |violation| return violation;
210 if (comptime validateLifecycle(Owner, expected)) |violation| return violation;
211 if (comptime validateMethodCapabilities(Owner, expected)) |violation| {
212 return violation;
213 }
214 if (expected.overload_shape == .rejecting) {
215 if (comptime validateExhaustion(Owner)) |violation| return violation;
216 }
217 if (comptime validateOwnerClaim(Owner, expected)) |violation| return violation;
218 return null;
219 }
220
221 fn validateDeclarations(
222 comptime Owner: type,
223 comptime expected: OwnerShape,
224 ) ?OwnerShapeViolation {
225 if (comptime !@hasDecl(Owner, "Limits")) return .missing_limits_declaration;
226 if (comptime @TypeOf(Owner.Limits) != type) return .wrong_limits_declaration;
227 if (comptime !signature.declarationContainer(Owner.Limits)) {
228 return .wrong_limits_declaration;
229 }
230 if (comptime !@hasDecl(Owner, "Capacity")) return .missing_capacity_declaration;
231 if (comptime @TypeOf(Owner.Capacity) != type) return .wrong_capacity_declaration;
232 if (comptime !signature.declarationContainer(Owner.Capacity)) {
233 return .wrong_capacity_declaration;
234 }
235 if (expected.storage_source == .caller_provisioned) {
236 if (comptime @typeInfo(Owner.Capacity) != .@"struct") {
237 return .wrong_capacity_declaration;
238 }
239 if (comptime capability.typeHasAllocatorCapability(Owner.Limits)) {
240 return .stored_allocator;
241 }
242 if (comptime capability.typeHasAllocatorCapability(Owner.Capacity)) {
243 return .stored_allocator;
244 }
245 if (comptime validateProvisionedDeclarations(Owner)) |violation| {
246 return violation;
247 }
248 }
249 return null;
250 }
251
252 fn validateProvisionedDeclarations(comptime Owner: type) ?OwnerShapeViolation {
253 if (comptime !@hasDecl(Owner, "storage_alignment")) {
254 return .missing_storage_alignment;
255 }
256 if (comptime @TypeOf(Owner.storage_alignment) != usize) {
257 return .wrong_storage_alignment;
258 }
259 if (comptime Owner.storage_alignment == 0) return .invalid_storage_alignment;
260 if (comptime !std.math.isPowerOfTwo(Owner.storage_alignment)) {
261 return .invalid_storage_alignment;
262 }
263 if (comptime !@hasDecl(Owner, "Storage")) return .missing_storage_declaration;
264 if (comptime @TypeOf(Owner.Storage) != type) return .wrong_storage_declaration;
265 if (comptime !signature.provisionedStorageTypeValid(Owner)) {
266 return .wrong_storage_declaration;
267 }
268 if (comptime !@hasDecl(Owner, "work_limits")) return .missing_work_limits;
269 if (comptime @TypeOf(Owner.work_limits) != owner.WorkLimits) {
270 return .wrong_work_limits;
271 }
272 if (comptime !workLimitsValid(Owner.work_limits)) return .invalid_work_limits;
273 return null;
274 }
275
276 fn validateFields(
277 comptime Owner: type,
278 comptime expected: OwnerShape,
279 ) ?OwnerShapeViolation {
280 if (comptime !@hasField(Owner, "phase")) return .missing_phase;
281 if (comptime @FieldType(Owner, "phase") != Phase) return .wrong_phase_type;
282 if (comptime !runtimeField(Owner, "phase")) return .comptime_phase;
283 if (comptime !@hasField(Owner, "capacity")) return .missing_capacity;
284 if (comptime @FieldType(Owner, "capacity") != Owner.Capacity) {
285 return .wrong_capacity_type;
286 }
287 if (comptime !runtimeField(Owner, "capacity")) return .comptime_capacity;
288 if (expected.storage_source == .caller_provisioned) {
289 if (comptime !@hasField(Owner.Capacity, "storage_bytes")) {
290 return .missing_storage_bytes;
291 }
292 if (comptime @FieldType(Owner.Capacity, "storage_bytes") != usize) {
293 return .wrong_storage_bytes;
294 }
295 if (comptime !runtimeField(Owner.Capacity, "storage_bytes")) {
296 return .comptime_storage_bytes;
297 }
298 if (comptime !@hasField(Owner, "storage")) return .missing_storage;
299 if (comptime @FieldType(Owner, "storage") != Owner.Storage) {
300 return .wrong_storage_type;
301 }
302 if (comptime !runtimeField(Owner, "storage")) return .comptime_storage;
303 }
304 inline for (@typeInfo(Owner).@"struct".field_types) |field_type| {
305 if (comptime ownerTypeHasAllocatorCapability(field_type, expected)) {
306 return .stored_allocator;
307 }
308 }
309 return null;
310 }
311
312 fn runtimeField(comptime Container: type, comptime name: []const u8) bool {
313 const info = @typeInfo(Container).@"struct";
314 inline for (info.field_names, info.field_attrs) |field_name, field_attrs| {
315 if (comptime std.mem.eql(u8, field_name, name)) {
316 return !field_attrs.@"comptime";
317 }
318 }
319 return false;
320 }
321
322 fn validateLifecycle(
323 comptime Owner: type,
324 comptime expected: OwnerShape,
325 ) ?OwnerShapeViolation {
326 if (comptime !@hasDecl(Owner.Capacity, "derive")) return .missing_capacity_derive;
327 const derive_valid = comptime switch (expected.storage_source) {
328 .allocator_backed => signature.capacityDeriveValid(Owner),
329 .caller_provisioned => signature.provisionedCapacityDeriveValid(Owner),
330 };
331 if (comptime !derive_valid) return .wrong_capacity_derive;
332 if (comptime !@hasDecl(Owner, "init")) return .missing_init;
333 const init_valid = comptime switch (expected.storage_source) {
334 .allocator_backed => signature.allocatorOwnerInitValid(Owner),
335 .caller_provisioned => signature.provisionedOwnerInitValid(Owner),
336 };
337 if (comptime !init_valid) return .wrong_init;
338 if (comptime !@hasDecl(Owner, "activate")) return .missing_activate;
339 const activate_valid = comptime switch (expected.storage_source) {
340 .allocator_backed => signature.ownerActivateValid(Owner),
341 .caller_provisioned => signature.provisionedOwnerActivateValid(Owner),
342 };
343 if (comptime !activate_valid) return .wrong_activate;
344 if (comptime !@hasDecl(Owner, "deinit")) return .missing_deinit;
345 const deinit_valid = comptime switch (expected.storage_source) {
346 .allocator_backed => signature.allocatorOwnerDeinitValid(Owner),
347 .caller_provisioned => signature.provisionedOwnerDeinitValid(Owner),
348 };
349 if (comptime !deinit_valid) return .wrong_deinit;
350 return null;
351 }
352
353 fn validateMethodCapabilities(
354 comptime Owner: type,
355 comptime expected: OwnerShape,
356 ) ?OwnerShapeViolation {
357 inline for (@typeInfo(Owner).@"struct".decl_names) |declaration_name| {
358 if (comptime std.mem.eql(u8, declaration_name, "init")) continue;
359 if (comptime std.mem.eql(u8, declaration_name, "activate")) continue;
360 if (comptime std.mem.eql(u8, declaration_name, "deinit")) continue;
361 const declaration_type = @TypeOf(@field(Owner, declaration_name));
362 if (comptime @typeInfo(declaration_type) != .@"fn") continue;
363 const function_info = @typeInfo(declaration_type).@"fn";
364 if (comptime function_info.param_types.len == 0) continue;
365 const receiver = function_info.param_types[0] orelse continue;
366 if (comptime receiver != Owner and
367 !signature.ownerReceiver(receiver, Owner))
368 {
369 continue;
370 }
371 inline for (function_info.param_types[1..]) |parameter_type_optional| {
372 if (parameter_type_optional) |parameter_type| {
373 if (ownerTypeHasAllocatorCapability(parameter_type, expected)) {
374 return .steady_allocator_parameter;
375 }
376 }
377 }
378 if (function_info.return_type) |return_type| {
379 if (ownerTypeHasAllocatorCapability(return_type, expected)) {
380 return .steady_allocator_result;
381 }
382 }
383 }
384 return null;
385 }
386
387 fn ownerTypeHasAllocatorCapability(
388 comptime T: type,
389 comptime expected: OwnerShape,
390 ) bool {
391 return switch (expected.storage_source) {
392 .allocator_backed => capability.typeHasImmediateAllocatorCapability(T),
393 .caller_provisioned => capability.typeHasAllocatorCapability(T),
394 };
395 }
396
397 fn validateExhaustion(comptime Owner: type) ?OwnerShapeViolation {
398 if (comptime !@hasDecl(Owner, "Exhaustion")) {
399 return .missing_exhaustion_declaration;
400 }
401 if (comptime @TypeOf(Owner.Exhaustion) != type) {
402 return .wrong_exhaustion_declaration;
403 }
404 if (comptime !signature.exhaustionSetValid(Owner.Exhaustion)) {
405 return .wrong_exhaustion_declaration;
406 }
407 if (comptime !exhaustionObservable(Owner)) return .exhaustion_not_observable;
408 return null;
409 }
410
411 fn validateOwnerClaim(
412 comptime Owner: type,
413 comptime expected: OwnerShape,
414 ) ?OwnerShapeViolation {
415 if (comptime !@hasDecl(Owner, "claim")) {
416 if (expected.storage_source == .caller_provisioned) {
417 return .missing_claim_declaration;
418 }
419 return null;
420 }
421 @setEvalBranchQuota(1_000_000);
422 if (comptime @TypeOf(Owner.claim) != declaration.Declaration) {
423 return .wrong_claim_declaration;
424 }
425 if (comptime declaration.validate(Owner, Owner.claim, expected) != null) {
426 return .invalid_claim;
427 }
428 recordTypedClaim(Owner);
429 return null;
430 }
431
432 fn recordTypedClaim(comptime Owner: type) void {
433 const value = Owner.claim;
434 if (comptime value.bindings.default_limits) |DefaultLimits| {
435 recordTypedClaimReferences(Owner, DefaultLimits.declaration);
436 } else {
437 recordTypedClaimReferences(Owner, null);
438 }
439 }
440
441 fn recordTypedClaimReferences(
442 comptime Owner: type,
443 comptime default_limits: anytype,
444 ) void {
445 const value = Owner.claim;
446 if (comptime value.bindings.seal != null and value.bindings.teardown != null) {
447 const Seal = value.bindings.seal.?.family.?;
448 const Teardown = value.bindings.teardown.?.family.?;
449 comptime observer.declaration(
450 value,
451 Owner.init,
452 default_limits,
453 Seal.declaration,
454 value.bindings.seal.?.premise,
455 Teardown.declaration,
456 value.bindings.teardown.?.premise,
457 );
458 } else if (comptime value.bindings.seal != null) {
459 const Seal = value.bindings.seal.?.family.?;
460 comptime observer.declaration(
461 value,
462 Owner.init,
463 default_limits,
464 Seal.declaration,
465 value.bindings.seal.?.premise,
466 null,
467 null,
468 );
469 } else if (comptime value.bindings.teardown != null) {
470 const Teardown = value.bindings.teardown.?.family.?;
471 comptime observer.declaration(
472 value,
473 Owner.init,
474 default_limits,
475 null,
476 null,
477 Teardown.declaration,
478 value.bindings.teardown.?.premise,
479 );
480 } else {
481 comptime observer.declaration(
482 value,
483 Owner.init,
484 default_limits,
485 null,
486 null,
487 null,
488 null,
489 );
490 }
491 }
492
493 fn exhaustionObservable(comptime Owner: type) bool {
494 inline for (@typeInfo(Owner).@"struct".decl_names) |declaration_name| {
495 if (comptime std.mem.eql(u8, declaration_name, "init")) continue;
496 if (comptime std.mem.eql(u8, declaration_name, "activate")) continue;
497 if (comptime std.mem.eql(u8, declaration_name, "deinit")) continue;
498 const declaration_type = @TypeOf(@field(Owner, declaration_name));
499 if (comptime @typeInfo(declaration_type) != .@"fn") continue;
500 const function_info = @typeInfo(declaration_type).@"fn";
501 if (comptime function_info.param_types.len == 0) continue;
502 if (comptime !signature.ownerReceiver(function_info.param_types[0], Owner)) continue;
503 if (signature.functionReturnsExhaustion(declaration_type, Owner.Exhaustion)) return true;
504 }
505 return false;
506 }
507
508 fn workLimitsValid(limits: owner.WorkLimits) bool {
509 if (limits.transition_steps_max == 0) return false;
510 const cleanup_steps_zero = limits.cleanup_steps_per_call_max == 0;
511 const cleanup_calls_zero = limits.cleanup_calls_at_capacity_max == 0;
512 if (cleanup_steps_zero != cleanup_calls_zero) return false;
513 _ = std.math.mul(
514 usize,
515 limits.cleanup_steps_per_call_max,
516 limits.cleanup_calls_at_capacity_max,
517 ) catch return false;
518 return true;
519 }