@@ -75,14 +75,18 @@ final readonly class ValidatorBuilder extends Append
7575
7676### AssuranceParameter
7777
78- Marks a parameter for assurance type resolution. Contextual based on where it
79- appears:
78+ Selects ** which** argument carries the assurance information — purely an index,
79+ defaulting to the first parameter when absent. It does not itself imply any
80+ particular derivation; ` from: ` decides how the selected argument maps to the
81+ assured type. Contextual based on where it appears:
8082
81- ** On a constructor parameter** — the parameter's value determines the assurance
82- type (replaces the old ` parameter: ` string reference):
83+ ** On a constructor parameter** — selects which argument is the type source. The
84+ example below pairs it with ` from: TypeString ` so the class-string argument
85+ narrows to an instance of that class (replaces the old ` parameter: ` string
86+ reference):
8387
8488``` php
85- #[Assurance]
89+ #[Assurance(from: AssuranceFrom::TypeString, exact: true) ]
8690final readonly class Instance implements Validator
8791{
8892 public function __construct(
@@ -302,8 +306,9 @@ FluentAnalysis reads this to determine how each node narrows a type:
302306#[Assurance(type: 'int')]
303307final readonly class IntType implements Validator { /* ... */ }
304308
305- // Type from a constructor parameter (use #[AssuranceParameter])
306- #[Assurance]
309+ // Instance of the class named by a class-string argument
310+ // (#[AssuranceParameter] selects which argument; from: TypeString the derivation)
311+ #[Assurance(from: AssuranceFrom::TypeString, exact: true)]
307312final readonly class Instance implements Validator
308313{
309314 public function __construct(
@@ -346,34 +351,43 @@ final readonly class When implements Validator
346351 public function __construct(Validator $when, Validator $then, Validator $else) {}
347352}
348353
349- // Modifier: exclude type instead of asserting it
354+ // Exact: passes if and only if the input is of the declared type
355+ #[Assurance(type: 'int', exact: true)]
356+ final readonly class IntType implements Validator { /* ... */ }
357+
358+ // Modifier on a Wrap prefix: negate the wrapped node's assurance
350359#[Assurance(modifier: AssuranceModifier::Exclude)]
360+ #[AssuranceSubject(AssuranceSubjectMode::Wrap)]
351361final readonly class Not implements Validator { /* ... */ }
352362
353- // Modifier: add null to the assured type
354- #[Assurance(modifier: AssuranceModifier::Nullable)]
363+ // Bypass set on a Wrap prefix: 'null' is admitted in union with the
364+ // wrapped node's assurance (nullOrIntType() assures int|null)
365+ #[Assurance(type: 'null', exact: true)]
366+ #[AssuranceSubject(AssuranceSubjectMode::Wrap)]
355367final readonly class NullOr implements Validator { /* ... */ }
356368```
357369
358370Properties:
359371
360- | Property | Type | Purpose |
361- | ----------------| --------------------------| ------------------------------------------------------------------|
362- | ` type ` | ` ?string ` | Fixed type string (e.g. ` 'int' ` , ` 'string' ` ) |
363- | ` from ` | ` ?AssuranceFrom ` | Derive type from a method argument |
364- | ` compose ` | ` ?AssuranceCompose ` | Combine assurances from child validators |
365- | ` composeRange ` | ` ?array{int, int\|null} ` | Subset of arguments to compose (` [from, to] ` , null = open-ended) |
366- | ` modifier ` | ` ?AssuranceModifier ` | Modify how the assurance is applied |
372+ | Property | Type | Purpose |
373+ | ----------------| ------------------------------| ------------------------------------------------------------------|
374+ | ` type ` | ` string\|list<string>\|null ` | Fixed type string (e.g. ` 'int' ` ); a list means their union |
375+ | ` from ` | ` ?AssuranceFrom ` | Derive type from a method argument |
376+ | ` compose ` | ` ?AssuranceCompose ` | Combine assurances from child validators |
377+ | ` composeRange ` | ` ?array{int, int\|null} ` | Subset of arguments to compose (` [from, to] ` , null = open-ended) |
378+ | ` modifier ` | ` ?AssuranceModifier ` | Modify how the assurance is applied |
379+ | ` exact ` | ` bool ` | The node passes * iff* the input is of the declared type |
367380
368381### AssuranceFrom (enum)
369382
370383Determines how the assured type is derived from a method argument:
371384
372385| Case | Meaning |
373386| ------------| ---------------------------------------------------------|
374- | ` Value ` | The argument's literal type (e.g. ` 42 ` → ` 42 ` ) |
375- | ` Member ` | The iterable value type (e.g. ` ['a','b'] ` → ` 'a'\|'b' ` ) |
376- | ` Elements ` | An array of the inner assurance type |
387+ | ` Value ` | The argument's literal type (e.g. ` 42 ` → ` 42 ` ) |
388+ | ` Member ` | The iterable value type (e.g. ` ['a','b'] ` → ` 'a'\|'b' ` ) |
389+ | ` Elements ` | An array of the inner assurance type |
390+ | ` TypeString ` | An instance of the class named by a class-string argument |
377391
378392### AssuranceCompose (enum)
379393
@@ -388,7 +402,37 @@ Determines how child assurances are combined:
388402
389403Modifies how an assurance is applied:
390404
391- | Case | Meaning |
392- | ------------| ------------------------------------------|
393- | ` Exclude ` | Removes the type instead of asserting it |
394- | ` Nullable ` | Adds ` null ` to the assured type |
405+ | Case | Meaning |
406+ | -----------| -----------------------------------------------------------------------|
407+ | ` Exclude ` | The wrapped node's assurance is negated: passing implies NOT the type |
408+
409+ ### AssuranceSubject
410+
411+ Declares how a ` #[Composable] ` prefix relates to its wrapped node's subject.
412+ A prefix without it yields no assurance for composed names: tools must drop,
413+ not copy, the wrapped node's assurance.
414+
415+ ``` php
416+ // Same subject, modified: notEmail() negates Email's assurance
417+ #[Assurance(modifier: AssuranceModifier::Exclude)]
418+ #[AssuranceSubject(AssuranceSubjectMode::Wrap)]
419+ final readonly class Not implements Validator { /* ... */ }
420+
421+ // Derived subject: keyEmail('name') assures only the container type
422+ #[Assurance(type: ['array', 'ArrayAccess'])]
423+ #[AssuranceSubject(AssuranceSubjectMode::Container)]
424+ final readonly class Key implements Validator { /* ... */ }
425+ ```
426+
427+ ### AssuranceSubjectMode (enum)
428+
429+ | Case | Meaning |
430+ | -------------| ----------------------------------------------------------------------------------|
431+ | ` Wrap ` | Same subject as the wrapped node: its assurance passes through, modified |
432+ | ` Elements ` | The wrapped node validates each element: assurance becomes ` iterable<T> ` |
433+ | ` Container ` | The wrapped node validates a derived subject: only the container type is assured |
434+
435+ A ` Wrap ` prefix's own ` #[Assurance(type:)] ` declares its * bypass set* : inputs
436+ it admits itself, in union with whatever the wrapped node assures. It is only
437+ meaningful in composition, never as a claim about direct calls; ` exact ` on it
438+ means the bypass set is an exact characterization.
0 commit comments