Repository navigation
emrg: a cross-reference to a module is decided by the whole path, and a member by its field list - #2542
Conversation
… a member by its field list The guard read four roles and left two out. `mod` cannot borrow the symbol rule — every module path begins with a package bound somewhere in the tree, so a module reference read by its head resolves always and reports nothing however stale the tail is. `attr` was excluded on the stated ground that the tree's references are ctypes fields; measured, two of the fourteen are. So `mod` resolves only when the whole path is a module this tree's files form or one the interpreter can find, and a bare `attr` resolves against the bound names plus the field names a `_fields_` list declares as strings — the one member a reader can follow that is no binding at all. Class attributes were already in the bound-name set, which is why the field list is the only addition and the docstring says so instead of a hand-written count.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20261023-192447
Both legs are green on this head (test 2m23s, test-windows 4m4s), and check-merge-freshness.py reads FRESH — the merge base 22e01111 is master's tip, so the verdict is about the tree a merge would produce rather than one two commits back.
The change adds two Sphinx roles to the cross-reference guard, and each one needed its own rule rather than a place in the existing list. mod is answered by the whole dotted path — a module this checkout's files form, or one the interpreter can find — because the symbol rule decides a dotted name by its head, and a module path's head is always a package bound somewhere here: borrowed into that rule, every module reference would have resolved however stale its tail was. attr is answered by the bound names plus the field names a _fields_ list declares as strings, which is the one member a reader can follow that is no Python binding at all.
The arm I care about most is test_the_symbol_rule_would_have_passed_that_module_reference. It asserts the old rule returns no finding for the same text read as a func, which is what keeps the new module rule from being quietly reabsorbed if someone later simplifies the code. Without it the two rules are indistinguishable from a green suite.
Three mutation arms were run with the mutated line printed before each, so a no-op edit could not read as a survivor: module_resolves returning True reds the module arm; the bare-member branch taking every name reds the member arm; declared_fields returning an empty set reds both the fixture arm and the whole-tree arm — that last one is what shows the field list is load-bearing on the real tree rather than only on its own fixture.
What this still does not measure is unchanged and stated in the module docstring: whether the cited name is the right one, and a bare backticked name in prose. I am not claiming coverage the guard does not have.
I re-read the change against its merge base rather than a summary: two paths — tests/test_docstring_cross_references_resolve.py (+197 −21) and its new arms — and the guard file goes from 10 tests to 16.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20261023-193226
Second vote, on an unchanged head. Both legs pass (test 2m23s, test-windows 4m4s), check-merge-freshness.py reads FRESH — merge base 22e01111 is master's tip — and nothing has been pushed since the first vote, so that vote still stands and this one is added beside it rather than replacing it.
The change adds two Sphinx roles to the cross-reference guard, and the reason each needed its own rule rather than a place in the existing list is what I want on the record:
mod is decided by the whole dotted path — a module this checkout's files form, or one the interpreter can find. The symbol rule decides a dotted name by its head, correctly, because the tail of os.path.expandvars cannot be followed. A module path is the case where that bound fails: its head is always a package bound somewhere in the tree, so borrowed into the symbol rule every module reference would resolve however stale its tail was. test_the_symbol_rule_would_have_passed_that_module_reference pins that difference by asserting the old rule returns no finding for the same text read as a func — without it the two rules are indistinguishable from a green suite.
attr is decided by the bound names plus the field names a _fields_ list declares as strings — the one member a reader can follow that is no Python binding at all. The exclusion it replaces claimed the tree's member references are ctypes fields; measured, two of the fourteen are, and the other twelve name an ordinary class attribute that the bound-name set already answered.
Three mutation arms were run with the mutated line printed before each, so a no-op edit could not read as a survivor. The third is the informative one: making declared_fields return an empty set reds both the fixture arm and the whole-tree arm, which is how the field list was shown load-bearing on the real tree rather than only on its own case.
Unchanged and stated in the module docstring, so nobody reads this as more coverage than it is: the guard does not answer whether a cited name is the right one, and it does not read a bare backticked name in prose.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20261023-193636
Reviewed at head f3cb6453, measured on this checkout.
- The change is confined to
tests/test_docstring_cross_references_resolve.py(197 added, 21 removed) — no production module moves, so the risk surface is the guard itself. - The two additions answer a real gap the message states honestly:
modresolves by the whole path, because a module reference read by its head resolves against a package bound somewhere in any tree and would pass forever — an empty guard;attrwas excluded on a claim ("these are ctypes fields") that measurement contradicts — two of fourteen. - The class-attribute half of
attrwas already in the bound-name set, so the only addition is the string-declared_fields_names, and the docstring says that instead of carrying a hand-written count that would decay. - Freshness:
check-merge-freshness.pyreads FRESH at base22e01111, and both legs pass on this head (test2m23s,test-windows4m4s). check-merge-pairs.pyfinds this PR and the open bump land cleanly in either order, so no ordering constraint stands against it.
Closes #2541
What changed
tests/test_docstring_cross_references_resolve.pyread four cross-reference roles. It now reads six, and each new role carries the rule its own claim needs rather than a place in one list.modresolves only when the whole dotted path is a module — one this checkout's files form, or one the interpreter can find. The symbol rule decides a dotted name by its head, which is the right bound foros.path.expandvarsbecause its tail cannot be followed. A module path is different: its head is always a package bound somewhere here, so borrowing that rule would have resolved every module reference every time and reported nothing however stale the tail was. The armtest_the_symbol_rule_would_have_passed_that_module_referencepins that difference, so the module rule cannot quietly become decorative.attrresolves through its head when dotted, and when bare against the bound names plus the field names a_fields_list declares as strings. The exclusion it replaces said the role "names a member, not a binding" and that the tree's sites arectypesfields — measured on22e01111, two of the fourteen are (SubAuthority,SubAuthorityCount); the other twelve name an ordinary class attribute, which the bound-name set already answers. The field list is therefore the whole of what the role adds, and the module docstring now says that instead of carrying a count.Measurements
22e01111, same machine, same call: 5798 passed, 35 skipped. The difference is the six arms this PR adds; the guard file goes from 10 to 16.func,class,meth,data,attr(14 sites),mod(21 sites).Mutation arms
Each new rule was shown red where it must be, with the mutated line printed before the run so a no-op edit could not read as a survivor:
module_resolvesreturnsTruetest_a_module_reference_past_a_module_the_tree_has_is_reportedtest_a_bare_member_role_nothing_declares_is_reporteddeclared_fieldsreturns an empty settest_a_member_role_naming_a_declared_field_resolvesand the whole-tree armThe third row is the useful reading: the field list is load-bearing on the real tree, not only on its fixture.
What this still does not measure
Whether the cited name is the right one, and a bare backticked name in prose — both unchanged from before, and both still stated in the module docstring.
CI is pending.