fix: preserve explicit inherited fields - #10
Merged
goduni merged 3 commits intoAug 16, 2026
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #10 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 31 31
Lines 2706 2726 +20
=========================================
+ Hits 2706 2726 +20 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Preserving every explicit child declaration made the narrowing predicate load-bearing in a way it was not written for. It is deliberately conservative because a false "no" used to mean only a slightly less precise inherited type; now a false "no" emits `# type: ignore[assignment]` on a line mypy is happy with, and `--strict` reports the unused ignore as an error of its own. Two ordinary spec shapes hit this and made `--inheritance --check` fail: - a subtype narrowing a `$ref` to a schema that inherits from the base's (`companion: Pet` over `companion: Creature`) -- nothing in either annotation says the two are related; - `boolean` over `integer`, which mypy accepts via the numeric tower. Both are now recognised as narrowings: `_is_narrowing` consults the declaration map for `$ref` overrides and a promotion table for primitives. The suppression comment additionally lists `unused-ignore`, so the remaining gap between the IR's view of a type and mypy's costs a redundant comment rather than a failed build. Both shapes join the hierarchy fixture, so the compile gate holds them under `mypy --strict` for every serializer and layout. The flag itself moves to `IRField.incompatible_override`: the IR records the fact about the spec, and the serializer strategies decide what to emit for it in one shared place instead of three copies. An incompatible override is a spec defect -- the subtype is not substitutable for its base -- so the warning now says that, and points at the schema and property to fix upstream.
8 tasks
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.
Description
Inheritance mode removed properties explicitly declared by a child schema when they
repeated an inherited property. This discarded child-specific metadata and prevented
downstream profiles from recognizing the declaration.
This PR changes the contract:
its type, description, constraints, requiredness, and default match the base field;
$refnarrowed to a schema thatinherits from the base's (
companion: Petovercompanion: Creature) andintegerover
number;# type: ignore[assignment, unused-ignore], plus a warning naming the schema andproperty, because a subtype that is not substitutable for its base is a defect worth
fixing in the spec;
The marker is stored on
IRField.incompatible_overrideand turned into a suppressioncomment in one place shared by the Adaptix, Pydantic, and msgspec renderers. This keeps
generated packages clean under
mypy --strictwithout losing explicit OpenAPIdeclarations.
unused-ignorerides along withassignmenton purpose: the builder judgescompatibility from the IR, which is a coarser view of a type than mypy's, so wherever
the builder is the stricter of the two a bare
[assignment]ignore would be unused —which
--strictreports as an error of its own.Behaviour change (breaking for consumers)
Keeping a subtype's widening restatement changes decoding, not just typing. Where a
subtype re-declares an inherited property as nullable, the generated model now accepts
nullfor it, on every serializer:LinkButtonwithtext: nullAggregateLoadError/ValidationError)text=NoneThis matches what the spec declares, and sibling subtypes are unaffected — a subtype
that does not restate the property still requires it. But the annotation consumers see
widens with it: code that read
button.textas astrnow getsstr | Noneand failsmypyuntil it handles theNone. Regenerating a client against such a spec istherefore a breaking change for its callers.
Type of change
work as expected) — see the section above
Verification
uv run pytest --cov src --cov-fail-under=80 --cov-report=term- 334 passed,100% coverage
uv run ruff checkuv run ruff format --checkuv run mypyincluding the two shapes only the base chain or the numeric tower can justify
generated package on both branches
Checklist
uv run mypy