Repository navigation
Index alias declarations and resolve them as Lean does - #308
Conversation
The declaration index skipped Batteries' `alias`, so an article whose `lean:` named an alias got `lean-target-not-found` from `autoform audit --lean-root`, and `render` built no link for it. ATLAS v1 has six such targets, the last ones the index cannot resolve. `alias b := a` now records `b`, and `alias ⟨mp, mpr⟩ := a_iff` records each of its names that is not `_`. The `:=` must be on the alias's line; the target may be on that line or the next. A declaration also records whether it is `private` or `protected`, and a name written `_root_.X` is recorded as `X`, for every declaration. The audit gives an alias its target's kind, size and elaboration limits. It resolves the target as Lean resolves a name: in the alias's namespace, then each enclosing one, then the root, passing over a declaration below the alias in its file, another file's `private` one, and, for a one-component target, a `protected` one. It stops at the first name the index does not hold, since Mathlib or another library may declare it; such an alias matches any intent and is measured by its own line. Each name of an iff alias is a theorem whatever its target is. An alias of a wanted command is reported like one. `autoform unused-hypotheses` refuses an alias: its own proof only names its target, so the probe would find every hypothesis used. Closes #266.
Review of the first version found that resolution treated every other project file as visible. Lean sees only what a file imports, so a file that imports the alias's file, or an unrelated one, could supply the target: a wrong kind, and another declaration's limits and span charged to the article. Batteries' wanted placeholders and module-system declarations are private without saying so, which the index could not see either. An alias is now followed only to an earlier declaration of its own file. A candidate in another file, one the index lacks, or a `protected` one for a one-component target, which the module system may have made private too, leaves the target unknown: any intent matches, and the alias is measured by its own span. Every step goes to an earlier line, so a chain always ends. Declarations record only `protected` now. Each name of an iff alias, and an alias of one, is a theorem even when the index reaches the iff lemma. A target written `«a.b»` is one component. The unused-hypotheses refusal no longer suggests naming the target, which may be outside the index. The tests now pin a target on the very next line, a same-file private target, a dotted target that reaches a protected declaration, a local iff target, a cross-file alias, and a target named beside its alias.
A second review found that `protected` written on its own line above the keyword, as Mathlib does 29 times, was not recorded, so an alias was followed to a declaration Lean skips. The scanner now reads modifiers from the lines above the keyword that hold only attribute lists and modifiers. The tests now pin the rules that end resolution: a candidate in another file ends it even when the alias's own file holds an outer one, a declaration on the alias's own line is passed over (an alias named like its target), and a chain whose end the index cannot place leads nowhere. They also check an iff alias's limits, the own limits of an alias the index cannot place, and that unused-hypotheses refuses an alias it could follow. The README says an explicitly private protected name is found too, and that an unplaced alias keeps its own limits.
|
Verdict: ready to merge once it is out of draft. This review covers head f3f8be2. The PR went through three review rounds; each round's should-fix findings are fixed in the commit that followed it, and nothing above a nit remains. Review rounds
What was checked
Known limits. None of these blocks merge.
Overlap. #90 and the #212-#219/#284 stack each conflict mechanically. The description gives the resolution for whichever lands second. Posted by PR swarm: PR Swarm Lead |
Resolves the conflicts with #308, which indexes alias declarations and resolves them as Lean does. #288 touches only the REPL server and merges as is. - lean: keep the universe-binder stripping in _scan and build the name with #308's _qualified, recording whether the declaration is protected. - lean: let the name prefilter read an alias line as _aliases does. It took `alias ⟨mp, mpr⟩ := t` to declare "⟨mp", so a file whose only requested name came from an iff alias was skipped: audit reported lean-target-not-found, check --lean-root reported the declaration as not found, and render built no link. - audit: take #308's lean-target-wanted check, which reads each target's kind through aliases, and keep it before the early exit for catalogs, so a catalog that records proof: formalized is checked through its aliases too. kinds is now computed before that check. bodies stays after the exit, next to the limit loop and the size line that use it, since catalogs use neither; the size line keeps the catalog guard and sums the bodies' spans. - tests: a targeted index finds both names of an iff alias, and the proved-catalog wanted test also names an alias of its theorem_wanted.
Closes #266. I took this on 2026-10-14 when the user asked for new work; the board lists #266 under this session.
The declaration index skipped Batteries'
alias. So an article whoselean:named an alias gotlean-target-not-foundfromautoform audit --lean-root, andrenderbuilt no link for it. ATLAS v1 has six such targets, the last ones the index cannot resolve.Indexing (
autoform_cli/lean.py)alias b := arecordsb, andalias ⟨mp, mpr⟩ := a_iffrecords each of its names that is not_. Neither is recorded as⟨mp.:=must be on the alias's line, which keeps lines wherealiasis an ordinary identifier out of the index. The target may be on that line or the next nonblank one.protected, written on its line or on the lines above that hold only attribute lists and modifiers._root_.Xis recorded asX, whatever namespace it is declared in. This applies to every declaration, not only aliases:theorem _root_.Xinside a namespace was indexed under that namespace before.SourceIndex.alias_targetresolves one alias, andSourceIndex.reachedfollows a chain of them.How the audit treats an alias (
autoform_cli/audit.py)An alias has its target's kind, size and elaboration limits when the index can be sure what the target is.
Resolving the target.
protectedone for a one-component target is skipped by Lean unless the module system has also made it private.Kind.
lean-target-kind-mismatchnames an alias's kind asalias of def.thmDecl.defin Batteries. The audit still checks it against its target's kind, because it asks what object an article describes, and an alias names its target's.Other checks.
lean-target-wantedreports an alias of a wanted command.node-too-largespan of the declaration above it. Its own@[deprecated]makes it a deprecated target.autoform unused-hypothesesIt now refuses an alias as an unread target ("an alias, whose own proof only names its target"), and the run exits 1. The probe would otherwise report every hypothesis as used.
Not covered
openstays unresolved.⟨…⟩list split across lines, or with its:=on the next line, is not indexed. Mathlib has six.Tests
test_aliases_are_indexed_with_where_their_target_can_be:_slots, modifiers and attributes;«x.y»target that counts as one component;_root_in names and targets;aliasis an ordinary identifier.test_root_qualified_names_leave_their_namespace_out.test_an_alias_is_followed_only_to_an_earlier_declaration_of_its_file:protecteddeclaration;These come out unknown:
protectedone-component target, withprotectedon the same line or on the line above;test_audit_resolves_an_alias_s_target_as_lean_does: every resolved alias shows as a finding, covering:Five aliases that cannot be placed must give no finding:
protectedtarget;Natcase;test_audit_reads_an_alias_s_limits_and_wanted_command_from_its_target:test_audit_measures_an_alias_by_its_target_s_span: the target named beside its alias counts once.test_an_alias_is_refused_without_running_lean, intest_unused_hypotheses.py: a cross-file alias and one the index could follow are both refused.test_an_alias_ends_the_source_span_of_the_declaration_above_it, plus anold-aliascase in the deprecated test.Twenty-four mutants of the change were each caught by these tests:
protectedalways visible, or always skipped;unused-hypothesesrefusal;_root_kept in names;.;>for>=in the line check, which loops on an alias named like its target;reachedstopping at an iff alias;unused-hypothesesrefusing only unplaced aliases;protectedread only from the keyword's line.The unit suite on Python 3.10 passed on the second commit: https://github.com/facebookresearch/autoform-bot/actions/runs/38540151992. CI on this PR covers the third.
Review history
An adversarial review of the first commit found that it treated every project file as visible. That could reach a declaration Lean cannot see: a downstream or unrelated file, Batteries' implicitly private wanted placeholders, or module-system private names. It also found that an iff alias could take its local target's kind. The second commit narrows resolution to the alias's own file, makes iff aliases theorems, and adds the tests above.
A review of the second commit found that
protectedwritten on its own line above the keyword, as Mathlib does 29 times, was not recorded. It also found that several rules that end resolution were unpinned. The third commit reads modifiers from those lines and adds the tests for them.Overlap
Trial merges are clean against #307, #296, #300 and #301. The rest resolve mechanically.
#90, in
lean.pyandaudit.py:_qualified(namespaces, name)and passprotected;lean-target-wantedblock above the kind check, so computebodiesandkindsbefore it and keep this PR'skindandwhatin it;if resolved and node.catalog is None: sizes[node_id] = sum(spans[body.name] for body in bodies).The #212-#219 and #284 stack, in
lean.py, the README andtests/test_audit.py:_scanreturnsfound, len(lines), so keep that return, with this PR'sDeclaration(...)line;unused-hypothesesexit sentence becomes "it is not found, several source files declare it, or it is an alias, whose proof only names its target";tests/test_audit.py.This machine is over the agreed load limit for local pytest runs. The tests were called directly in Python, and CI on this PR is the full run.