Unevaluated properties#157
Open
wol-soft wants to merge 22 commits into
Open
Conversation
Introduces the scaffolding needed for unevaluatedProperties/unevaluatedItems tracking without changing any existing validation behaviour: - AbstractComposedPropertyValidator: add trackEvaluation flag (getter/setter) so later phases can gate _compositionEvaluations cache emission per validator. - Schema: add postCompositionValidators list (addPostCompositionValidator / getPostCompositionValidators) for validators that must run after all composition validators complete. - Model.phptpl: extend executeBaseValidators with a third bucket that iterates schema.getPostCompositionValidators(); update both guard conditions to trigger the method when either the base-validator list or the post-composition list is non-empty. Template variables are not used for the new bucket — the schema object is already in context and the method is called directly. - UnevaluatedPropertiesPostProcessor (new internal post processor): activation walk via DFS over composition branches and nested schemas, breaking cycles through a file+pointer seen-set; sets trackEvaluation on matching validators and emits the _compositionEvaluations cache property when activation triggers. - ModelGenerator: register UnevaluatedPropertiesPostProcessor immediately after CompositionValidationPostProcessor. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
# Conflicts: # tests/AbstractPHPModelGeneratorTestCase.php # tests/PostProcessor/EnumPostProcessorTest.php
When unevaluatedProperties or unevaluatedItems is reachable from a schema, each composition validator now records the per-branch evaluation outcome in a typed _compositionEvaluations slot: null when the branch failed, true when the branch evaluated every property (non-false additionalProperties), an array of evaluated property names for an inline branch, or the instantiated branch object for a nested-schema branch. The shared evaluation logic moved to the production-library CompositionEvaluationTrait so the slot writes don't duplicate the same loop across every generated class. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three improvements layered on the Phase 2 tracking infrastructure: Pre-decode patternProperties patterns at generation time. CompositionPropertyDecorator now exposes getBranchDeclaredPropertyNamesPhpLiteral() and getBranchPatternPropertyPatternsPhpLiteral() returning var_export'd PHP array literals ready for direct template embedding. Templates drop the per-name quoting foreach loop, and the trait no longer calls base64_decode on every preg_match. var_export also handles all string escaping, so property names containing single quotes or backslashes no longer produce broken generated code. Skip the tracking write on a cache hit. ComposedItem.phptpl initializes $shouldTrack = true per branch iteration; the mutable-validator cache-hit branch sets it to false so the success-tracking block is bypassed and the slot value from the previous run is preserved. On a cache hit $value still holds the pre-validator array rather than the nested-schema object, so the old unconditional write would overwrite a valid object slot with null. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
# Conflicts: # src/Model/Validator/ConditionalPropertyValidator.php # src/Templates/Model.phptpl # tests/Basic/PhpAttributeTest.php
Adds the runtime validator for `unevaluatedProperties: false` and `unevaluatedProperties: <schema>` on top of the existing composition evaluation tracking. The validator iterates the model keys left over after subtracting the local `properties`/`patternProperties` set plus the contributions of every successful sibling composition branch recorded in `_compositionEvaluations`. The factory skips emitting any validator when the same schema declares a non-false `additionalProperties`: that keyword already claims every remaining key, so unevaluatedProperties would be a no-op. The cross-schema modification needed for nested branch classes (getEvaluatedProperties) is now applied during `process()`. To keep that visible at render time, `RenderQueue::execute` runs every job's `process()` pass before any `render()` begins. The shared evaluated/unevaluated computation lives on `CompositionEvaluationTrait` in the production library; both validator templates collapse to a single trait-method call. Adds `RenderHelper::varExportPcrePatterns()` to wrap raw patternProperties keys with `/`-delimiters and escape embedded `/`. The existing patternProperties consumers (`AdditionalPropertiesValidator`, `AdditionalPropertiesPostProcessor`, `CompositionPropertyDecorator`) are migrated to it, fixing a latent crash on patterns containing slashes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
# Conflicts: # tests/PostProcessor/AdditionalPropertiesAccessorPostProcessorTest.php # tests/PostProcessor/PatternPropertiesAccessorPostProcessorTest.php
# Conflicts: # src/Model/Validator/AbstractComposedPropertyValidator.php # src/Model/Validator/ComposedPropertyValidator.php # src/Templates/Validator/ComposedItem.phptpl
The dev-jsonSchemaDraft2019 production library widened MaxItemsException to require an actual count alongside the maxItems limit. The items: false branch of ItemsValidatorFactory still passed only the maxItems literal (via [0]), producing a 3-arg call that broke against the 4-arg constructor. Mirror the working pattern from MaxItems / MinItems factories: define $count inside the validation expression and pass it as the fourth constructor argument by reference. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Introduces UnevaluatedPropertiesAccessorPostProcessor, an opt-in post
processor that exposes the keys claimed by `unevaluatedProperties` via a
single getter on the model: `$model->unevaluatedProperties()` returns either
the bare production-library accessor (untyped schema) or a generated
companion class `{ModelName}UnevaluatedProperties` with narrowed get/set/
getAll/remove signatures (typed schema). Private mutator shims
`_setUnevaluatedProperty` / `_removeUnevaluatedProperty` host the runtime
guard and per-call validation; the accessor's closures bind to them via
first-class callable syntax. The shim guard rejects keys that match a
directly-declared property name, a local `patternProperties` pattern, or
any property name/pattern declared inside a composition branch (allOf /
anyOf / oneOf / if / then / else, walked recursively) — those keys belong
to a different contract and routing them through the unevaluated bucket
would silently bypass the typed validator.
Accessor emission honours the keyword's reachability at this schema level:
when `additionalProperties: false` rejects every extra or
`additionalProperties: {schema}` claims every extra, the unevaluated bucket
is unreachable and nothing is emitted (no backing field, accessor method,
shims, companion, or collect flag) — the validator continues to run as a
pure assertion.
UnevaluatedPropertiesValidator gains a `setCollectUnevaluatedProperties`
flag (mirroring the additional-properties pattern) so the validator writes
into `$this->_unevaluatedProperties` after per-key validation succeeds,
with a rollback on whole-validator failure to preserve the prior bucket
state. SerializableTrait in the production library learns to flatten
`_unevaluatedProperties` back into `toArray()` / `toJSON()` output via a
new `_serializeUnevaluatedProperties()` method.
Populate.phptpl's hardcoded rollback / accessor-cache property lists are
replaced with two registries on Schema (`addRollbackProperty`,
`addAccessorCacheProperty`). Existing internal post processors register
their fields into the registries; the new accessor registers
`_unevaluatedProperties` and `_unevaluatedPropertiesAccessor`. The change
is a no-op for any schema that does not opt into the unevaluated accessor.
Test suite (UnevaluatedPropertiesAccessorPostProcessorTest, 15 tests, all
Drafts 2019-09 and 2020-12): typed round-trip; constraint-violation
rejection in direct-exception mode with rollback verification; runtime
guards (declared property, composition-branch property); companion-class
naming and method surface; immutable read-only variant; serialization
round-trip; dead-code suppression; collect-errors-mode setter isolation
(proves the per-call error-registry reset is load-bearing); coexistence
matrix asserting the additionalProperties × unevaluatedProperties emission
policy when both accessor post processors are configured simultaneously.
CLAUDE.md gains an expanded patterns list and recovery procedure for the
"no implementation-plan references in code" rule so that section numbers,
decision identifiers, and similar artifacts of working notes can be
caught before they land.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Splits _executeBaseValidators into two phase methods so the constructor, setters, and populate can invoke whichever phases they need against whichever input they need. The schema-level validators that depend on the merged state of every property — currently just unevaluatedProperties — now live in _executePostCompositionValidators, and the property-level plus composition validators stay in _executeBaseValidators. Constructor calls each method in turn against the raw input. Setters call _executePostCompositionValidators against a candidate raw input (current state with the new value plugged in) before commit, so a setter that flips a oneOf discriminator and would orphan a key from the previous branch's contract is rejected before the model state changes. populate() runs the delta check via _executeBaseValidators (which no longer includes the post-composition phase) and then runs _executePostCompositionValidators against the would-be merged state; failure rolls every committed property field back and leaves _rawModelDataInput untouched. The accessor's _setUnevaluatedProperty shim now calls _executePostCompositionValidators directly: a single dynamic key only ever needs the post-composition phase, not the schema's full base + composition chain. Each call resets its own error registry so a caller catching a failed set() can continue to use the model. Test class UnevaluatedPropertiesMutabilityTest covers the setter rollback path on each composition shape (oneOf, anyOf overlap, anyOf sole-coverer, if/then/else), the no-op early-return path, populate's merged-state rollback, the collect-errors mode collation of property-validator failures together with unevaluated-properties failures in a single registry, and the cross-state revalidation reading through nested-branch class instances. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Wire cross-state revalidation into _setAdditionalProperty and
_removeAdditionalProperty so dynamic-key mutations see the post-mutation
state. Set runs base validators followed by _executePostCompositionValidators
against a candidate raw input. Remove additionally re-runs every composition
validator against the post-removal raw input so _compositionEvaluations
reflects the new branch outcomes, then runs the post-composition phase. Both
share a single _errorRegistry across phases in collect-errors mode and
snapshot/rollback _additionalProperties, _rawModelDataInput,
_compositionEvaluations, and _propertyValidationState on rejection.
Fix the PHP 8.4 dynamic-property deprecation triggered when a composition
branch declares additionalProperties:{schema}: setupBranchDefaultHelpers no
longer propagates internal properties from a branch's nested schema into
allBranchDefaultAttributeMap. Internal properties have no getter and the
parent never declares the corresponding field, so the propagation loop's
$this->$attr = ... writes were creating dynamic properties on the parent.
Emit a generation-time warning when unevaluatedProperties is suppressed
because a sibling additionalProperties is non-false (the dead-code cell from
the §4.1 matrix). Gate the warning on isOutputEnabled() for parity with the
rest of the warning channel; gate the previously-unconditional echo in
EnumPostProcessor::filterValuesByDeclaredType the same way; let the test
infrastructure respect the caller's outputEnabled so warning-assertion tests
can opt in.
Test coverage added for §5.1.20 (required + unevaluated error precedence in
both direct and collect modes), §5.1.A1 (defaults are not part of the
evaluated set), §5.1.A3/A4 (branch-level additionalProperties per-key
validity), §5.1.25 (pattern + unevaluated bucket coexistence), §5.1.27
(empty allOf), §5.1.M33-34 (additionalProperties accessor under outer
unevaluatedProperties), §5.1.M37 (remove clears backing field plus raw
input), §5.1.M42 (claimed-via-unevaluated key persists after sibling later
covers), the dead-code suppression warning, the Set cross-state revalidation
rejection path, and the dual-error collect-mode aggregation for both Set
(pattern + unevaluated, paired with the per-key validity fix on the
production library) and Remove (min-property + unevaluated via composition
revalidation flipping an anyOf branch). Both dual-error tests assert the
full ErrorRegistryException message via heredoc.
CLAUDE.md adds a rule requiring multi-line exception-message assertions to
use heredoc inlined into the assertSame call.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
A composition branch with its own unevaluatedProperties:{schema} now
contributes the keys it evaluates against that schema to an enclosing
unevaluatedProperties through the same nested-class read-through the trait
already uses for declared properties.
UnevaluatedPropertiesPostProcessor adds an internal _evaluatedPropertyKeys
field to any schema carrying the schema-form UnevaluatedPropertiesValidator.
UnevaluatedProperties.phptpl writes the key into this field on per-key
validation success, with snapshot/rollback mirroring the existing
_unevaluatedProperties discipline.
Nested branch classes gain an _getEvaluatedProperties() method (underscore
prefix + #[Internal]) returning the union of declared properties present in
the raw input and keys recorded in _evaluatedPropertyKeys. The underscore
sidesteps a name collision with a user-declared evaluatedProperties
property's auto-generated getter and the attribute documents the role.
testNestedUnevaluatedInBranchPropagatesClaimsToOuter runs in collect-errors
mode so a single ErrorRegistryException carries the full chain: the outer
allOf failure, the nested class's invalid-unevaluated-property cause, and
the outer unevaluatedProperties: false rejecting the orphaned foo and bar.
The assertion uses heredoc to compare the full message, with an inline
comment explaining why foo appears as an orphan despite passing the
branch's local properties.foo validator — failed composition branches
contribute no annotations to the outer accumulator under JSON Schema
2019-09's applicator rules.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Introduces UnevaluatedItemsValidator and NoUnevaluatedItemsValidator on the
array side, mirroring the property-side flavours. The validator runs at
priority 101 on the array property's validator chain (after composition at
100), wrapping its check in is_array($value) so it short-circuits cleanly
for union types like ["array", "null"]. Both flavours render through a
new collectUnevaluatedIndices() trait method that reads
\$this->_evaluatedItemIndices defensively (the field arrives in a later
stage) and walks \$this->_compositionEvaluations slots filtered by
'kind' => 'array' so the property-side and array-side rebuilds cannot
contaminate each other on mixed-type oneOf.
UnevaluatedItemsValidatorFactory skips emission for absent and true
keywords, throws a SchemaException for non-bool/non-object values, and
warns through the existing echo channel for the three array-side dead-code
shapes — items:false, items:{schema}, and additionalItems:false with tuple
items — that make the keyword unreachable. The property-side factory gains
the same SchemaException guard for non-bool/non-object unevaluatedProperties
values (an oversight that the array-side test surfaced).
Draft_2019_09 registers the factory on the array type. The unevaluated post
processor's needsActivation walk now inspects each property's own JSON
before recursing into its nested schema, so a schema like
{type: object, properties: {tags: {type: array, unevaluatedItems: false}}}
activates the outer object's evaluation tracking (array properties carry
no nested schema, so the previous loop missed them).
The production library gains UnevaluatedItemsException and
InvalidUnevaluatedItemsException. The false-form message reports indices
with the # prefix used by the rest of the array-side family
(InvalidItemException, InvalidTupleException); the schema-form wrapper
preserves the per-index inner exceptions verbatim, so consumers catching
the wrapper can still surface the underlying InvalidTypeException reason.
CLAUDE.md adds two enforcement rules. "Always review the diff against the
repo rules before staging" prescribes a grep recipe for plan/stage/
phase/section references and applies symmetrically to coordinated
production-library checkouts. "Pre-existing rule violations in touched
files" requires sweeping every visible CLAUDE.md violation in any file
edited as part of a task, with one escape hatch when cleanup would balloon
into an unrelated refactor.
UnevaluatedItemsValidatorTest covers the bare false and schema forms
(per-index # notation), uniqueItems-precedence ordering, the four dead-code
warning shapes via expectOutputRegex, and the SchemaException path for
non-bool/non-object values on both keywords. The schema-form test asserts
the full multi-line exception message via heredoc using two failing indices
and pins getInvalidItems() to confirm each failing index's
InvalidTypeException survives the wrapper.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Property-level allOf/anyOf/oneOf/if-then-else compositions on array
properties now contribute their successful branches' claimed indices to a
sibling unevaluatedItems validator. The bridge is a single transient
_compositionAnnotated field indexed by slot key, written wholesale at end
of each composition IIFE, read by the unevaluatedItems template via the
prod-lib trait method.
The activation walk in UnevaluatedPropertiesPostProcessor descends
recursively through composition branches' wrapped properties (so nested
compositions get tracking too) and flags any ArrayContains validators
inside tracked branches with trackBranchMatches, which makes the contains
template export a per-index match map captured by the surrounding
composition body.
Sharp edges fixed along the way:
* ExtractedMethodValidator method-name hash mixed in spl_object_hash so
two compositions on the same property no longer collide into one
method body
* Activation walk reads source validators via getWrappedProperty()
because PropertyProxy::getOrderedValidators() returns fresh
withProperty() clones on every call — flag mutations on the clones
never reached the rendered instance
* Composition IIFE leaves $value untouched when slotKey is set so a
failing composition does not null the caller's array value and
suppress the downstream unevaluatedItems check in collectErrors mode
* activateArrayComposition has a hash-keyed cycle guard so a self-
referencing schema does not recurse indefinitely through $ref-induced
composition cycles
Requires php-micro-template 1.11.0 for the {# ... #} template-time
comment syntax used by ComposedItem.phptpl. CLAUDE.md adds two rules
surfaced by the work: never prefix local variables with underscore (the
prefix is reserved for class members); when a bug is found, write a
reproducing test before fixing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The post processor previously added `_compositionEvaluations` and `_compositionAnnotated` unconditionally on every activation-triggering schema, justified by a comment claiming the trait reads them on no-composition schemas and PHP 8.2 would deprecate the undeclared- property access. Both halves of that justification were wrong: the trait's reads go through `?? []`, which does not trigger the dynamic- property deprecation, and the read sites only execute when the caller passes composition slot keys — empty on no-composition schemas. The fields were padding every activation-triggering generated class with two unused arrays. Each field now follows the precedent set by `addEvaluatedPropertyKeysField` and is declared only when something will write to it. The two activation passes have been extracted into `activateSchemaLevelTracking` (handles the root-level composition activation and `_compositionEvaluations`) and `activateArrayPropertyTracking` (handles the array-property walk plus `_compositionAnnotated` and `_evaluatedItemIndices`); `process()` now just orchestrates them. - `_compositionEvaluations` — only when at least one `AbstractComposedPropertyValidator` at the schema level had evaluation tracking enabled. - `_compositionAnnotated` — only when the array-property activation walk actually enabled tracking on a property-level composition. - `_evaluatedItemIndices` — new field, declared only when at least one array property carries `unevaluatedItems` (the future write site is in `UnevaluatedItems.phptpl`; the field is empty until then). `UnevaluatedItemsMutabilityTest` exercises what Stage 5.3's transient- field design relies on without any new validator wiring: the whole-array setter rebuilds composition annotations against the candidate array and re-runs `unevaluatedItems` against it (rejecting an array whose tail index no branch claims, preserving pre-setter state on rejection); `populate()` with array data of a different length flows through `PopulatePostProcessor`'s revalidation hook and behaves the same. Both tests use `setCollectErrors(false)` so the raw `UnevaluatedItemsException` surfaces directly, mirroring the existing `UnevaluatedPropertiesMutability Test` pattern. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The unevaluatedItems template now writes _evaluatedItemIndices per successfully-validated index and snapshots/restores the field around the IIFE so a nested unevaluatedItems inside a composition branch contributes its claims to the enclosing accumulator on success and leaves no partial writes behind on failure. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Verifies the activation walk already handles $ref-resolved schemas through
the property tree it traverses (via getNestedSchema on properties and
composition branches). No generator changes required.
Coverage:
- unevaluatedProperties expressed as {$ref: "#/$defs/foo"}
- $ref-resolved allOf branch contributes annotations to outer accumulator
- Recursive self-$ref terminates and enforces unevaluatedProperties at
every nesting depth (validated with full-message assertions unwrapping
through NestedObjectException::getNestedException)
- External $ref to ExternalSchema placeholder used as composition branch:
markTestIncomplete, blocked by pre-existing composition-processor
requirement that every composed branch surface a nested schema (same
limitation as the array-side self-referencing test)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…uated-properties # Conflicts: # src/Model/Validator/Factory/Arrays/ItemsValidatorFactory.php
The merge from jsonSchemaDraft2019 landed pointer stamping on every validator factory but surfaced two follow-up gaps in the unevaluated work. ConditionalPropertyValidator did not update templateValues['compositionValidator'] at render time. withJsonPointer() clones the validator, but the clone's template still pointed at the pre-clone original whose evaluationTrackingEnabled flag is false. Every if/then/else composition consequently emitted no _compositionEvaluations writes, so an if-branch that claimed a property left the outer unevaluatedProperties accumulator empty and the key was reported as unevaluated. Late-bind in getCheck() the same way ComposedPropertyValidator already does. Neither unevaluated factory (object nor array) chained ->withJsonPointer() on the emitted validator, so UnevaluatedItems / UnevaluatedProperties / InvalidUnevaluated* exceptions carried an empty pointer. SetUnevaluatedProperty.phptpl's two RegularPropertyAsUnevaluated throw-sites emitted no pointer at all. Wire real pointers through three shapes: - Factory-level: /path/to/property/unevaluatedItems and /unevaluatedProperties, matching the sibling stamping in additional/pattern factories. - Object-property harvest (root declaration): resolved via each property's synthesized #[JsonPointer] attribute, matching how AdditionalPropertiesAccessorPostProcessor resolves it. - Composition-branch harvest (inline branch declaration): walked recursively with per-branch pointers, so a property declared inside /allOf/0 gets /allOf/0/properties/<name>. Pattern-based entries carry their /patternProperties/<encoded-regex> pointer verbatim. Pointer utilities extracted to Utils\JsonSchema. encodePointer and decodePointer moved off the Model\SchemaDefinition\JsonSchema data class where they had been static bystanders; resolvePrimaryJsonPointer extracted from the two accessor post processors that had duplicated it. All call sites aliased-import as JsonSchemaUtil to avoid a name clash with the model class. RenderHelper::varExportPcrePatternMap added so a template can foreach over a regex => pointer map when the pattern-guard needs both fields at runtime. Pointer assertions added to the acceptance tests that already caught the message text and payload. The pattern-focused accessor test's fixture regex changed from `^x_` to `^s/~` so the emitted pointer becomes `/patternProperties/^s~1~0`, exercising both RFC 6901 replacements and their ordering in a single assertion. The three RegularPropertyAsUnevaluatedPropertyException catches now also assert the full "Couldn't add regular property … as unevaluated property to object …" message. Requires the coordinating production-library commit 7bfa040 (jsonPointer arg added to UnevaluatedItems / UnevaluatedProperties / RegularPropertyAsUnevaluatedProperty / MinContains / MaxContains). Push that commit and \`composer update\` to lock the new SHA. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
UnevaluatedPropertiesValidatorFactory now consolidates all four
sibling shapes that leave the unevaluatedProperties bucket
permanently empty into a single deadCodeReason() helper:
- additionalProperties: true — every extra flows to the model but
the accumulator does not credit it, so the validator would still
fire and defeat the intent of `additionalProperties: true`.
- additionalProperties: {schema} — every extra is claimed and
validated by additionalProperties; the unevaluated set is empty.
- additionalProperties: false — every extra is rejected before the
post-composition phase, so the unevaluated validator never runs.
- denyAdditionalProperties() generator flag with additionalProperties
absent — synthesises the same false shape at configuration time.
Each cell emits a distinct warning via the existing echo channel
so build-output greps can identify the specific dead shape, and
skips validator emission entirely.
Tests added:
- Object-side dead-code data provider covering all four shapes plus
the denyAdditionalProperties() variant. Rows pass the
GeneratorConfiguration directly (setOutputEnabled(true) on the
base, chained with setDenyAdditionalProperties(true) on the deny
row) so the test signature drops the closure/fallback boilerplate.
- testAdditionalFalseRejectsExtrasEvenWhenUnevaluatedSchemaWouldAccept
proves the factory suppressed the validator: an extra that would
satisfy the unevaluated integer schema is still rejected because
additionalProperties: false runs first.
- testContradictoryInnerSchemaThrowsSchemaExceptionPointingAtFile
pins that an unevaluatedProperties schema with contradictory allOf
types surfaces via the existing "conflicting types" SchemaException
path, with the file identifier preserved.
- testPropertyNamesRejectionPrecedesUnevaluated data-provider variant
covering both direct-exception and error-collection modes. The
registry message pin includes the base-phase propertyNames block
followed by the post-composition InvalidUnevaluatedPropertiesException
block; template uses `{className}` + str_replace for interpolation.
- testDependentSchemasContributionCreditsDependentPropertiesToAccumulator
is skipped (dependentSchemas applicator not yet implemented) but
asserts both the intended success path (kind + covered `extra`)
and the failure path (kind + non-covered `stray`) so removing the
skip line makes the applicator's contract live.
- Array-side testContainsWithMinContainsZeroCreditsMatchedIndicesOnly
and testOneOfBranchesOfDifferentTupleLengthsControlEvaluatedSet
cover contains + minContains: 0 and mixed-tuple-length composition.
Object-side dead-code fixtures cover all six sibling shapes;
denyAdditional variant paired with the config row.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.