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 }