Repository navigation
Conversation
…l-only property types Tests only; they fail against the current implementation. Filter in an object-typed allOf branch: a conditional (or anyOf/oneOf/not) nested in an allOf branch of an object schema is rejected with "A filter keyword inside a allOf composition branch is not supported" although the filter sits on an object property of a branch which is instantiated as its own class. The same shape without the allOf wrapper is accepted. Detection by the nested level: pin that a filter in a scalar composition is still rejected when it is nested in allOf/anyOf/not/if, at any depth, and that the string-typed branch with a filter in a composition under its inert "properties" is accepted. The messages of three existing rows name the innermost composition once the enclosing level no longer descends into it. TypeError: a typed property which is only declared in the then (or else) of a conditional keeps a typed slot, although a value of any type is valid while the branch is inactive. Reading it throws a TypeError from the getter; inside an allOf branch the constructor throws it, in direct and collect-errors mode. Includes a guard that a property which is also declared at the root keeps its type. Adds assertMessageMatchesTemplate()/assertInstantiationFailsWithMessage() to the base test case to assert complete messages with only the generated class names being dynamic. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y7cwkHFG9yLwdmxU9pPrbR
…only properties to mixed Filters in nested compositions of an allOf branch: branchContainsFilter() descended into nested compositions of a branch before the nested level had inherited the object type of its parent, so the untyped "then" of a conditional in an object-typed allOf branch was taken for a scalar branch and the filter on its object property was rejected. The same schema without the allOf wrapper was accepted. The recursion is removed: every nested composition builds its own branch property and runs the check on its own branches after its own type inheritance, so a filter in a scalar composition is still rejected at every depth (the message now names the innermost composition). Passing the inherited object-ness down manually was rejected as it would duplicate the inheritance rules. A composition below "properties" of a string-typed branch is no longer inspected; such properties can never apply to a string, so the filter can never run. TypeError for conditional-only properties: a property declared only in the then (or only in the else) of an if/then/else kept a typed slot although the missing branch constrains nothing and a value of any type is valid while the declaring branch is inactive. Reading it threw a TypeError from the getter, and for a conditional nested in an allOf branch the constructor threw it. A conditional with only one branch is now treated like a composition with an implicit unconstrained branch, so the property is widened to mixed as documented for properties exclusive to a single branch. A property which is also declared at the root keeps its type. This changes the generated accessors of then-only/else-only properties from ?T to mixed. Updates the documentation of the filter rule, the then-only type hint and the widening rule. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y7cwkHFG9yLwdmxU9pPrbR
Coverage Report for CI Build 37928178898Coverage increased (+0.01%) to 98.892%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
The static acceptance rows for a conditional, anyOf, oneOf and not nested in an allOf object branch only asserted that generation succeeds. The runtime tests generate supersets of these schemas (the playlist conditional and a combined anyOf/oneOf/not schema) and fail with the same SchemaException if that regresses, naming the branch index of the affected composition. The allOf nested three levels deep exercises the same per-level check as the allOf nested two levels deep. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y7cwkHFG9yLwdmxU9pPrbR
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.
Summary
Two related defects showed up with an object schema that puts independent
if/thenblocks into anallOfand uses filters (e.g.trim) on thethenproperties.1. Filter on an object property inside a conditional nested in an
allOfbranch was rejectedA filter keyword inside a allOf composition branch is not supportedwas thrown, although the filter sits on a property of a branch that is instantiated as its own class. The identical conditional without theallOfwrapper was accepted.Cause:
CompositionCompatibilityChecker::branchContainsFilter()descended into the nestedthenbefore that level had inheritedtype: objectfrom its parent, so the untypedthenlooked like a scalar branch.Fix: the recursion into nested compositions is removed. Every nested composition builds its own branch property and runs the check on its own branches after its own type inheritance, so a filter in a scalar composition is still rejected at every depth (verified for
allOf>allOf, three levels,allOf>anyOf,allOf>not,allOf>if/then). The message now names the innermost composition (three pinned messages updated; each row still asserts a rejection). Passing the inherited object-ness down manually was rejected because it would duplicate the inheritance rules.One shape becomes legal: a composition under
propertiesof a string-typed branch. Thosepropertiescan never apply to a string, so the filter can never run (pinned by a test with an explanatory comment).2.
TypeErrorfor properties declared only inthen(or only inelse)A typed property that exists only in one branch of a conditional kept a typed slot (
?int), although a value of any type is valid while that branch is inactive. Reading it threw aTypeErrorfrom the getter; for a conditional nested in anallOfbranch the constructor threw it (direct and collect-errors mode), i.e. a rawTypeErrorinstead of aValidationExceptionfor a schema-valid input.Cause:
exclusiveBranchPropertyNeedsWidening()only compares with other existing branches; the missingelsewas never considered, contradicting the rule documented incrossTypedComposition.rst("properties exclusive to a single branch are widened tomixed";anyOf/oneOfalready do this).Fix: a conditional with only one of
then/elseis treated as having an implicit unconstrained branch, so the property is widened tomixed. A property that is additionally declared at the root keeps its type (guarded by a test). Rejecting wrong-typed values outside the active branch instead was considered and rejected: it would invalidate instances the schema allows.Generated getter/setter types for properties that exist only in a
thenor only in anelsechange from?Ttomixed. This reverses whatif.rstand two tests documented before (testCrossTypedThenOnlyProducesNullableHint, an assertion intestIfOnlyPropertyIsTransferredToParentSchema); both were updated with their justification, and the docs were corrected. Worth a release note.Documentation
filter.rst(filter rule, new section on filters in object branches),if.rst(then-only type hint),crossTypedComposition.rst(conditional with one branch).filter.rstalso now documents an existing behaviour that may surprise: a filter declared on a conditional branch property also runs while the branch is inactive, because the property belongs to the merged class.Test plan
Developed test-first; the commits show it:
Add failing tests ...– tests only. Against the old code 16 of 3245 cases fail, all of them the new/updated tests; everything else passes.Accept filters in nested allOf object branches and widen ...– the implementation, the docs and the one assertion that depends on the new behaviour.Remove redundant test schemas– pruning of acceptance rows that the runtime tests already cover with superset schemas.phpcs --standard=phpcs.xmlon all changed PHP files: cleanassertMessageMatchesTemplate()/assertInstantiationFailsWithMessage()assert complete exception messages with only generated class names dynamicNot part of this PR
else: {"properties": {"x": false}}(orunevaluatedProperties, once available) is a schema-level way to forbid values outside the active branch;additionalProperties: falseat the root does not work for this because it rejects the branch properties.type(e.g.{"minimum": 10}) is ignored even without any composition.🤖 Generated with Claude Code
https://claude.ai/code/session_01Y7cwkHFG9yLwdmxU9pPrbR