Repository navigation
✨ Filters follow inherits: c.is_a('req') selects a type and the types inheriting from it - #2401
Merged
Merged
Conversation
…n follow `inherits` `type_ancestors` is a core field modelled on `type_name`: the need's type, then each type it inherits from, nearest first, through the parents `inherits` leaves once it is checked (an `inherits` naming no need type, or on a cycle, is dropped, so its type is a root). `FieldsSchema` holds the parent map (`set_type_parents`, told once by `create_schema`; `type_ancestors(type)` answers, a type with no parent answering itself alone). The field is read-only (no directive option, no needextend, immutable on the need), left out of needs.json by default (`exclude_json`, opt in through `needs_json_exclude_fields`) and recomputed for imported and external needs. `"req" in type_ancestors` is in the portable filter language with no grammar change and is answered by the fast tier; it reads the same in a derived field's `where`, a needextend filter and a needtable column. A schema rule sees it in its `local` properties and, now, in its `select` (it was not among the fields a select could read). An array field's schema rule may give `contains` without `items`, which stopped the build with a raw schema error before (tags too). Corpus: filter case `type-ancestors-follow-inherits` (10 entries; the header now says an `inherits` naming no need type is each tool's uncompared finding, and the runner tolerates `needs.type_inherits`); derive case `where-follows-inherits`, appended at the end. The name is reserved as every core field name is. ENV_DATA_VERSION 14: stored needs gain the key. Refs #2322
…m it
`c.is_a("<type>")` is `"<type>" in type_ancestors`, spelled as a check beside
`c.this_doc()`. The portable tier compiles it with a string literal as its one
argument (the `is_a_check` arm in ubquery.py, reading the need's own
`type_ancestors`); any other shape (`c.is_a(type)`, `c.is_a()`,
`c.is_a('req') == True`) is refused there with the reason `call:c.is_a` and
falls to eval, as the `c.this_doc()` arms do. The eval tier's
`NeedCheckContext.is_a(name)` answers the same. A name that is no need type
selects nothing, unreported. It reads the need alone, so it works with no
document (conf.py filters) and a derived field's `where` accepts it in that one
shape (`filter_problem` still refuses every other `c.` check);
`filter_names` says it reads `type_ancestors` rather than `docname`.
Corpus: 10 entries appended to `type-ancestors-follow-inherits` (tiers pinned:
fast for the literal forms, eval with reason `call:c.is_a` for the rest, one
with no document, one with parts); the derive case `where-follows-inherits`
gains a `c.is_a('spec')` count.
Refs #2322
…n ignored core field, pin order and reads Review follow-ups for #2322. Docs and changelog: neither `type_ancestors` nor `c.is_a()` is available in a needimport `:filter:` (it reads the imported file's records) or in a field's `predicates`, said as for `c.this_doc()`, in place of "wherever a filter string is"; a project that sets `needs_json_exclude_fields` itself now writes `type_ancestors` unless it lists it; code building a `NeedItem` core or a `NeedsInfoType` must give the key; a `needs_filter_data` key of that name shadows the field but not `c.is_a`. create_schema reports a `needs_fields` entry naming a core field a need computes (`type_name`, `type_ancestors`, `docname`, ...) as ignored, with a `needs.config` warning, where it was ignored silently. Tests: the derive case `where-follows-inherits` copies `type_ancestors` into an array field, so the nearest-first order of a three-type chain is in a corpus (the derive header now names the bare-entry rule the case relies on); the filter runner tolerates the `needs.type_inherits` finding of an `inherits` naming no need type alone, not one on a cycle; an external need's value is recomputed; assignment to `type_ancestors` is refused on a built need; the `items` injection skips link types. Refs #2322
Conflicts resolved keeping both sides: the changelog's Unreleased section (both sides' entries), the need type key list in configuration.rst (back_links and inherits), and the derive corpus' case count (91 on master, plus the branch's one case: 92).
…s` field is dropped
`c.is_a('x')` is true for a need whose type is `x` or inherits from `x`, transitively,
as the project's need types declare `inherits` once it is checked; an unknown `x` is
false on every need. No field of a need holds its types any more: the core field
`type_ancestors` is removed from the need data, needs.json, the fields schema, schema
rules, ordering and derive, and `ENV_DATA_VERSION` returns to 13 (no stored need
changes shape against master).
The parse-time handling stays: `c.is_a('<literal>')` compiles to the membership of the
need's own type in the set `create_schema` computes once per build (the type and every
type inheriting from it, the map `target_types` already reads), set on the configuration
as the `var` proxy is. The compiled filter is cached per string and shared by every
project a process builds, so the set is passed per call (`IS_A_KEY`, as `c.this_doc()`'s
document is); both tiers answer through one function, `ubquery.is_a`.
Corpora: the filter case `is-a-follows-inherits` (req <- sys_req <- hw_req, a root, an
ignored inherits, an unknown name, the exact type) replaces `type-ancestors-follow-inherits`
(21 cases / 250 filters, from 253: 20 entries replaced by 17); the derive case
`where-follows-inherits` reads `c.is_a` alone (92 cases). The schema fixtures of the field
are removed; `tags_contains_without_items` and the `needs_fields` core-field warning stay,
being general.
…announces it alone The documentation of the `type_ancestors` field is removed from filter.rst, need_types.rst, derived_fields.rst, schema/index.rst and configuration.rst. filter.rst's section (now `filter_is_a`) defines `c.is_a` by the types' `inherits`, and says that `c.is_a` tests the need's type, while a link type named `is_a` is followed through the link. The changelog's entry announces `c.is_a` only; its breaking-changes section, which named the field, goes.
…er types, a bare `c` reads the document
ENV_DATA_VERSION goes to 14 on the semantics axis, as version 12 did: `c.is_a('req')`, an invalid filter and a refused derive `where` in 9.1.0, now selects and computes, and an upgraded build over an existing `_build` would otherwise keep the empty selections and values on the pages whose sources did not change.
A test builds two projects with different `inherits` in one process, in both orders and back, with the same `c.is_a('req')` in a needextend filter and a derived field's `where`: each selects by its own types, so the per-string filter cache leaks none.
`filter_names` counts a `c` read otherwise than through `c.is_a` (`getattr(c, 'this_doc')()`) as reading `docname` again, as master does.
…s_fields` warning leave this branch Both are general (neither is about `c.is_a`), and move to a pull request of their own, with their tests and changelog entries: the schema rule an array property gives with `contains` alone, and the `needs.config` warning for a `needs_fields` entry naming a core field a need computes. Nothing here depends on either.
Master brought #2400 (extension need types narrowed by a needs_types entry) and #2399 (need source hops), which took ENV_DATA_VERSION 14. This branch's semantics bump is renumbered to 15, its note following #2399's version-14 note; the changelog keeps both sides' entries. #2399 adds no filter name and no need field (`need.source.hops` is an attribute), so `c.is_a` and the fast path need nothing more; a type an extension adds and a needs_types entry gives `inherits` (#2400) is followed by `c.is_a`, as `target_types` follows it.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #2401 +/- ##
=======================================
Coverage 94.73% 94.74%
=======================================
Files 145 145
Lines 25294 25323 +29
=======================================
+ Hits 23962 23992 +30
+ Misses 1332 1331 -1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
chrisjsewell
added a commit
that referenced
this pull request
Oct 30, 2026
…entry naming a computed core field is reported (#2405) ## What `packages/sphinx-needs`. Two small, independent fixes, one commit each. - **A schema rule's property on an array field may give `contains` without `items`.** `"tags": {"contains": {"const": "a"}}`, in a rule's `local` properties or its `select`, failed the meta-schema and stopped the build. The type injection now gives such a property the field's item type as `items`, which every value already satisfies, so the rule applies. A link type's property takes neither and is unchanged. Documented under the array keywords of `docs/schema/index.rst`. - **A `needs_fields` entry naming a core field a need computes is reported.** An entry such as `type_name` or `docname` declared nothing and was ignored in silence. It is now one `needs.config` warning, and still ignored. A core field that takes a declaration (`status`, `tags`, …) is specialised as before. Documented in `needs_fields`. Nothing read such an entry, so no output changes. But **a `-W` build that has one now fails** until the entry is removed, as the changelog says. ## Why Both were found while building #2322 (`c.is_a`, #2401), and were split out of that pull request because neither is about it. No open issue covers either. #2199 is the link-side twin of the second fix (a link type named after a core field stops the build instead of being refused at load). It is related but not fixed here: the warning added here is for `needs_fields` entries alone. ## Tests - Red first: `tests/schema/test_populate_field_type.py::test_field_array_contains_alone_gets_items` and the schema fixture `tags_contains_without_items` fail on master. `test_link_contains_alone_gets_no_items` pins that link types are unchanged. `tests/test_config_unknown_keys.py::test_an_entry_naming_a_core_field_a_need_computes_is_reported` fails on master (no warning). - An independent review reproduced both red-on-master proofs and checked a `contains`-only property in a rule's `select` by probe (it applies; no test pins that shape yet). - After merging master (#2401): `uv run poe lint` 0, `uv run poe typecheck` 0, full `uv run poe test-needs` 0 (4040 passed, 15 skipped), `uv run poe smoke-needs` 0. `docs-needs`: no warning from this repository's pages or docstrings (the sandbox cannot reach the intersphinx hosts, so only those references stay unresolved there). ## Checklist - [ ] I wrote this change myself and have read every line of it; it was not generated automatically from an issue. - [x] I ran the package's tests (`uv run poe test-needs`) and they pass. - [x] Documentation is updated where behaviour or options change. - [x] The package's `docs/changelog.rst` has an entry under *Unreleased*. - [x] `uv run poe lint` and `uv run poe typecheck` pass.
5 tasks done
chrisjsewell
added a commit
that referenced
this pull request
Oct 30, 2026
…ed (#2414) ## What `packages/sphinx-needs` (new internal module `_incremental.py`, `_`-prefixed like the package's other new internal modules). The needs each re-read document gives are compared, as read, with what it gave before: an `env-purge-doc` stash and `env-get-updated`, before the environment is pickled, so post-processing still happens after the pickle. On a change, Sphinx re-writes (never re-reads): - every view page (needtable, needlist, needflow, needgantt, needsequence, needpie, needbar, needextract, needuml/needarch, needreport, need_count, ndf); - the pages of the affected needs: changed, named by a changed id-needextend, or computing a value from an affected need (a dynamic function read by its arguments, whether written or set by a needextend; a derive rule over its links, needextend-added ones included, its back links or `from`), to a fixpoint; - the pages of their link targets, old and new, and of the needs linking to them; - every page whose need role names one. What a computed value reads is decided by an **allow-list**: a value counts as reading only its own need when every argument is statically known to read that need's own fields; anything else (variant conditions, functions of your own, opaque filters, any field other needs decide, such as back links, dead-link flags or constraint results) counts as reading every need. Every core field is classified, and a test fails on an unclassified new one. A need that only moved (lines added above it) re-writes the views and the pages whose roles name it, and only when nothing anywhere reads a line number; prose below the needs re-writes nothing more. Projects with links computed to needs not named before resolution (a dynamic function in a link field or a needextend's links, a `links` rule, `content_links` with `from`) re-write every need-relevant page on each change, as does a changed filter-needextend. The rules err toward re-writing a page, never toward a stale one. Views without a sort now list needs in document order on every build (an incremental build used to list re-read needs last). `ENV_DATA_VERSION` is 16 (master's 14 is the hops on a need's source, #2399; its 15 is `c.is_a`, #2401). Other open branches also claim 16 (#2398's fix, the source-hops slice 3a): whichever lands first keeps it, each later one renumbers at its merge of master and keeps every version paragraph. Docs: a page on incremental builds, saying exactly which pages are re-written and when the fallbacks fire. ### Master's newer readers, checked against the rules Merged with master after #2383–#2406. Each new reader is covered, each by a scratch-vs-incremental case (or, for schema findings, a test of the incremental build's findings), and where a rule of this PR carries it, a mutation of that rule turns the case red: - **`c.is_a('x')`** (#2401) reads the need's type and the need types' `inherits`. In a filter of a rule or a call it reads the candidates' types, which the rules already follow (a type is a field the need's own document gives); a needtable, needextend or any view using it is re-written or re-applied as before. An `inherits` edit is a change of the need types, which re-reads every document (`reread_on_need_types_change`, #2287) — also when it is made in `ubproject.toml` alone. Case: `inherits-changed` (toml only: `req` declares a field with a default, which `spec` takes once it inherits `req`, so the case goes red with that re-read switched off; a needtable and a needextend filtering on `c.is_a`, a count `over links where c.is_a(...)` copied by a need on another page). - **Per-type back-link counts and severity** (#2395) are schema findings, which are validated over every need on every build: removing the one link to a need on another page reports it on the incremental build exactly as on a fresh one (pinned by a test). Per-type `id_regex` is matched at read time, on the need's own document, and is a need-type change too. - **Derive `where` beside `copy`, `empty` on `any`/`all`** (#2384): a `where` narrows the needs the rule already reads (`over` links), and `empty` is a literal. Case: `rule-with-where-and-empty-read` (rules declared on one need type alone in `ubproject.toml`, #2400, read two hops away). - **Source hops** (#2399): nothing renders from them (`need.source.hops` is provisional; the legacy keys derived from them are classified core fields). They do record a need's lines, so the moved-only comparison now leaves out each hop's start and end line, and the source classes' private line field: after the merge it crashed (`NeedItemSourceDirective.__init__() got an unexpected keyword argument '_lineno'`) on every build in which a need moved. Cases: `prose-above-included-need` (a need written in an included file, which its hops record), `prose-above-imported-need` (an imported need, whose line is kept beside its hops) and `need-moved-to-another-included-file` (the same text from another file is a changed need), each with the exact set of pages it writes. - **`create_need`** (#2406): its needs and sources are compared as any other; it adds no reader. A source an extension passes that is not a dataclass and has no value `__eq__` compares by identity, so its need counts as changed on every re-read of its document: pages re-written, never stale. - **Extension types and fields per type** (#2400): declared by the configuration (a need-type change re-reads every document) and read through the schema the rules already ask (`rule_for(type)`); a type-local derived field is covered by the case above. ### For the source-hops work The moved-only comparison (`_without_lines` in `need_item.py`) reads the source classes' private `_lineno` and rebuilds #2399's provisional hop dataclasses from outside `_need_source.py`: a new field there fails safe (a moved need counts as changed), a custom `__init__` on a hop class would raise (the incremental suite goes red); the clean seam is a method on `HopsSource` / `NeedSourceHop` giving that key, left to that work. The source-hops slice 3a (one frozen `NeedItemSource` in place of these classes) was merged onto this branch in a scratch clone by that work's reviewer: `_without_lines` works unchanged there and these tests pass; whichever of the two lands second moves the comment to the new seam. ## Why Fixes #2376, fixes #346. Related: #442. Editing a need's title and rebuilding incrementally re-wrote only the edited page: every needtable/needflow page, every `:need:` role, the need's link targets (back links) and the needs linking to it kept the old values until a full build, as did the needs a needextend changed or stopped changing. Not covered: a changed `needs_external_needs` file (#353), and moving a need between two documents in one incremental build (pre-existing, identical before this change; #2391). A `:need_outgoing:` / `:need_incoming:` naming a need that does not exist ends even a fresh build (#2413); on master an incremental build left that page stale and green, with this change it is re-written and fails the way a fresh build does. ## Tests - A scratch-vs-incremental equivalence oracle: every page on disk after the incremental build equals a fresh build's, over the fixture's scenarios (several also at `-j 2`), plus precise-set tests. Two independent reviews and four validation rounds probed 34 edit shapes; every hole they found is now a test, and every dependency rule fails a test when mutated. After the merge with master: six more scenarios and four tests for master's readers (51 tests in the module). - Full builds at base and head are identical for 14 projects (html and needs builders), apart from the env version in `searchindex.js`. - Cost on a 1000-need / 211-page project: a title edit writes 14 pages in about 1.5–2 s (before: 2 pages, the views stale; re-writing every need page: 211 pages, 22 s); with a field default `[[copy('title')]]` the same. - `uv run poe lint` 0, `uv run poe typecheck` 0, full `uv run poe test-needs` 4119 passed / 15 skipped (all 4134 collected), `uv run poe smoke-needs` 0. After the module's rename to `_incremental.py`: lint, typecheck and smoke-needs 0 again, and the incremental, need-types and need-source modules 103 passed. ## Checklist - [x] I wrote this change myself and have read every line of it; it was not generated automatically from an issue. - [x] I ran the package's tests (`uv run poe test-needs`) and they pass. - [x] Documentation is updated where behaviour or options change. - [x] The package's `docs/changelog.rst` has an entry under *Unreleased*. - [x] `uv run poe lint` and `uv run poe typecheck` pass.
chrisjsewell
added a commit
that referenced
this pull request
Oct 30, 2026
master squash-merged the derive branch this one stacked on (#2384), and has since gained per-type declarations (#2395), extension types (#2400), c.is_a (#2401), the incremental rewrite rules (#2414) and more. Conflicts kept both sides: the changelog's Unreleased bullets; LinkSchema's severity beside summary in the basic-doc snapshot; a path's select default in derive.py; the derive corpus gains master's where-follows-inherits case ahead of this branch's eleven, 103 cases.
5 tasks done
chrisjsewell
added a commit
that referenced
this pull request
Oct 30, 2026
…s far ends as links (#2418) Read by the data-model side before going out of draft: needs.json's `"summary": true` marker and the exchange-contract sentence fit, and nothing collides with its in-flight work. The ubCode side is useblocks/ubcode#4452; this PR merges first. ## What `packages/sphinx-needs` (+ one sentence in `packages/ub-needs-exchange`'s exchange contract, and a reader test). 1. **The stance.** `summary = true` on a link type's `needs_links` entry (any link type, authored or derived) says its links materialise a path, not an edge of the trace. A summary link type is read where it is named (a filter, a needtable column, a rule's `over` / `field`, a needflow's `:link_types:`, a schema rule, a need type's `back_links` count, which counts its computed links too), and a need holds its links and back links as for any link type. It is left out of every view that reads all link types: in sphinx-needs, the needflow default (without `:link_types:` a summary link type is neither drawn nor walked from `:root_id:`, and the `needs_flow_link_types` fallback leaves it out too). A union cannot name one as a member (`needs.derive_invalid`). `summary` beside the deprecated `copy = true` is reported and the copy ignored, so a summary link never leaks into `links`. needs.json's schema marks the link type and its back links `"summary": true`, and ub-needs-exchange passes it through. The stance is one per name: a need type's entry giving it is a `needs.config` finding. 2. **The kinds.** On a link type, `copy` takes `field = "id"` and `over`, and links each need `over` names. `collect` takes `field` (a link type, a derived one included) and `over`, and gives the links `field` holds on each need `over` names. Both take `where` (`c.is_a(...)` included), and both may be declared on need types alone (`[needs.types.<t>.links.<l>]`). Each link appears once by target and part, its condition dropped. A dead link is carried and reported where it is written, rendered as that link type allows, and does not mark the needs below it as having dead links. Such a link type is a summary implicitly. `over` may name a union, read through its members, so a chain can climb a `trace_up`-style union rung by rung. What the link step cannot wait for is refused at load: - an `over` naming back links; - a `collect` of its own link type over a link type another rule computes (it would read itself on every need); - a `field` naming a value field or back links; - a `copy` of a field other than `id`; - a `select` other than `"list"`; - `after`; - `allow_dead_links` beside such a rule. Per need, a path is ordered after the needs it reads; needs reading one another's chain around a ring (through a union too) are `needs.derive_cycle`, and only those. The density guard counts these links; fingerprints leave them out. 3. **Incremental builds.** A `collect` on a link type links needs its need does not name (a chain's far ends), so it is classified as a `links` rule is: in a project with one, every change of a need re-writes every need-relevant page. A `copy` over a link type written in the needs links only the needs its own links name, and stays a read of its links (a `copy` over a computed link type is classified as unnamed too). `ENV_DATA_VERSION` is 19 (17 is #2415's, 18 #2419's): a `copy` / `collect` on a link type, refused by 9.1.0, now computes, and `summary` now changes a needflow's default, with the configuration unchanged. ## Why Closes #2370. The far ends of a chain (the requirements a test result is evidence for, three hops up) were only expressible as a field: strings, with no back links ("the test results that reach me"), no `:need:` rendering, no direct relationship in a graph consumer. The link form adds them. The stance it needs, that such a link is a summary and not a trace edge, applies to authored and other derived link types too. **Bringing it up to master.** The branch was built on the derive branch, since squash-merged as #2384; master was merged in (merge commits, no rebase). The conflicts kept both sides: the changelog's Unreleased bullets, `LinkSchema`'s `severity` (#2395) beside `summary` in the basic-doc snapshot, a path's `select` default, and the corpus (master's `where-follows-inherits` where master has it, this branch's cases after it). Checked against what master gained since: the incremental rules of #2414 (a `collect` on a link type was read as an ordinary `over` and left the old far end's page stale: fixed, red first, with scratch-vs-incremental cases for both kinds); a `[needs.types.<t>.back_links.<l>]` count (#2395) naming a summary or computed link type (allowed, counts every link, pinned); `c.is_a` (#2401) in a path's `where` (pinned in a test and the corpus); a path declared on need types alone and a need type's `summary` (#2400, pinned); the needs.json writer's nullable rule and `""` -> null (#2395, #2400: fields only, beside `summary` on link types), and the needflow change on master (a view category only). Later merges brought in #2415 (hidden-need location) and #2419 (`NeedItemSource`), which took versions 17 and 18, so this branch's bump is 19; every bump stays in `data.py`'s version notes and in the changelog's bump bullet. **Corpus.** `derive_conformance.toml` gains twelve cases on top of master's 92 (104, sha256 `c4721bcc6cb1532b4a39a79c3ba5325c75b006762ca5d25a5e1c78809923ba01`; python_only 2, ubcode_only 1) and an optional `schema.<name>` expectation, documented in the header; master's cases are untouched. ubCode re-vendors it and mirrors the stance and the kinds in useblocks/ubcode#4452, including its own consumers that read all link types: the needflow default, the agent walks, `ubc diff`'s default, the Need Viewer tree and the Cypher schema flag. **Follow-up, not in this PR.** `summary` on a need type's own link entry is a `needs.config` finding today, so an authored link type declared on need types alone cannot be a summary. It can be lifted under the one-name-one-shape rule (#2284): accept it there when every need type declaring the name agrees, and report a disagreement as any other shape mismatch. ## Tests - Red first for both slices and for the incremental fix; two independent reviews reproduced every proof, ruled on the two judgement calls (the refusal of a derived `over` lifted for unions; a call-computed `over` making only that need a cycle, kept) and added mutations of their own, each now killed (the needflow default and fallback, the union refusal, load-time refusals, stratum order, implied summary, dead links, the `forbidden` rendering with both `allow_dead_links` values). After the first merge: mutations of the incremental classification (both directions), of the back links of a computed summary link type, and of `c.is_a` in a `where`, each red. - After the merge, master's unknown-key report (#2388) pins the keys of a link type's entry: `summary` is one now, and its test says so. - `uv run poe lint` 0, `uv run poe typecheck` 0, full `uv run poe test-needs` 4244 passed / 15 skipped (after the #2419 merge), `uv run poe test-ub-needs-exchange` 0 (246 passed), `uv run poe smoke-needs` 0. `docs-needs` warns only on what intersphinx resolves (the inventories are unreachable from the sandbox); none of this PR's references. ## Checklist - [x] I wrote this change myself and have read every line of it; it was not generated automatically from an issue. - [x] I ran the package's tests (`uv run poe test-needs`, `uv run poe test-ub-needs-exchange`) and they pass. - [x] Documentation is updated where behaviour or options change. - [x] The package's `docs/changelog.rst` has an entry under *Unreleased*. - [x] `uv run poe lint` and `uv run poe typecheck` pass.
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.
What
packages/sphinx-needs.c.is_a('req')is true for a need whose type isreq, or a type inheriting fromreq, directly or through other types,as the project's need type declarations give
inherits(once checked: aninheritsnaming no need type, or on a cycle, isignored with
needs.type_inherits, and its type is a root). Withsys_reqinheritingreqandhw_reqinheritingsys_req,c.is_a('req')selects all three, astype in ['req', 'sys_req', 'hw_req']does;type == 'req'still compares the name alone.An unknown name is false on every need, unreported; a type is named by its directive name, never its title.
c.this_doc(), in the portable filter language with a string literal as its argument (the fast tieranswers it), and in the
evaltier for any other shape (call:c.is_a). It reads the need alone, so a filter configured inconf.pyand a derived field'swheretake it; likec.this_doc(), a needimport:filter:and aneeds_fieldsentry'spredicatesdo not (documented).c.is_areads the need's own type and the declarations:create_schemacomputes, onceper build, each type with itself and every type inheriting from it (the map
target_typesalready reads), and sets it on theconfiguration as
prepare_envsets thevarproxy.c.is_a('<literal>')compiles, at parse time, to the membership of theneed's type in that set. The compiled filter is cached per string and shared by every project a process builds, so the set is
passed per call (as
c.this_doc()'s document is) rather than compiled in; both tiers answer through one function.c.is_atests the need's type; to follow a link type namedis_a, use the link.ENV_DATA_VERSION14 -> 15 (above ✨ Every need records where its text came from, as hops on its source (provisional; nothing shown changes) #2399's 14), on the semantics axis as version 12 was:c.is_a('req')was an invalid filter and a refusedderive
wherein 9.1.0, so an upgraded build over an existing_buildwould keep the empty selections and values on thepages whose sources did not change.
Why
Closes #2322.
target_typesfollowsinherits; until now no filter could: a filter or a derived field'swherehad to list every subtype by hand.An earlier revision of this pull request also added a core field
type_ancestors(the type, then each type it inherits from).The maintainer has ruled that the field goes: a core field name is permanent public API on four surfaces (the need, needs.json,
filters, schema rules) and a name a user's own field can collide with.
c.is_astays, re-defined by the declarations. The listedform of the lineage is deferred to the facets work, which will settle its name, whether it includes the type itself, and whether it
is a field or a
type.facet. Two general fixes found on the way (thecontains-without-itemsschema fix and a warning for aneeds_fieldsentry naming a computed core field) moved to a pull request of their own.Merge note.
ENV_DATA_VERSIONis 15 here, above the 14 that #2399 took on master. The incremental-build pull request alsoclaims 14 and renumbers when it lands (to 16 if it lands after this one). #2399 needed nothing beyond the textual merge: it adds no
field or filter name
c.is_areads. An extension's type giveninheritsinneeds_types(#2400) is selected byc.is_alike any other.Contract. Filter corpus: case
is-a-follows-inherits(17 entries: a two-level chainhw_req->sys_req->req, each level,a type with no
inherits, an ignoredinherits, an unknown name, a title, the exact type, parts, no document, theevalshapes;tiers pinned), and a header line saying an
inheritsnaming no need type is each tool's own, uncompared finding (the runner'stolerance is narrowed to that message). Derive corpus: case
where-follows-inherits(c.is_ain awhere, an unknown name, theexact type), plus one header line on bare type entries. ubCode mirrors
c.is_ain its own pull request and re-vendors both corpora.Tests
field and of the needs.json key, a field listing types that
c.is_amust not read, what a check reads for ordering).Mutations each turn a corpus case or test red: no transitivity, an unknown name selecting everything, the fast tier ignoring
the declarations, a per-need call passing none, and one process reusing the first project's types (a test builds two projects
with different
inheritsand the samec.is_a('req')in one process, in both orders)._buildmade at master with this branch: with the version bump the pages re-read and show thec.is_aselections and derived values; without it they kept the empty ones.needs_typesentry narrows an extension's type,add_field(types=…), and sphinx-test-reports' fields on their own types #2400):uv run poe lint0,uv run poe typecheck0, fulluv run poe test-needs0(4036 passed, 15 skipped),
uv run poe smoke-needs0.docs-needs: no warning from this repository's pages or docstrings(the sandbox cannot reach the intersphinx hosts, so only those references stay unresolved there).
Checklist
uv run poe test-needs) and they pass.docs/changelog.rsthas an entry under Unreleased.uv run poe lintanduv run poe typecheckpass.