Skip to content

Refuse a lean_lib that is not an array of named tables, and tidy lean_probe - #505

Merged
Deicyde merged 2 commits into
mainfrom
cleanup/lean-probe-tidy
Oct 30, 2026
Merged

Deicyde merged 2 commits into
mainfrom
cleanup/lean-probe-tidy

Conversation

@Deicyde

@Deicyde Deicyde commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

A follow-up to #482: lean_libraries refuses a lean_lib that is not an array of named tables, as Lake refuses it, and lean_probe gets four small tidies that change no behavior.

Change

  • A lean_lib Lake refuses is refused here too. lean_libraries read config.get("lean_lib", []) or [] and skipped any entry that was not a table with a string name. With name = "Solo" in lakefile.toml:

    • lean_lib = true (or any nonzero number) crashed with a TypeError;
    • lean_lib = "Solo", lean_lib = false (or 0), lean_lib = [1], a [[lean_lib]] with no name, and [lean_lib] written for [[lean_lib]] all fell back to the package library without a word. The single-bracket case also lost its configured roots.

    Lake's TOML loader refuses every one of these shapes ("expected array", "expected table", "missing required key: name"), so lake build never built anything for them. They now raise a SkeletonError: "cannot read the lean_lib targets of ...: Lake expects an array of tables, written [[lean_lib]]", or "...: a [[lean_lib]] target has no name". This reaches skeleton, review prepare, work import-impact and library index.

  • What still works. An empty lean_lib = [], or no lean_lib at all, keeps the package fallback. Only a hand-written lakefile.toml can reach the new refusals: for a lakefile.lean project, lean_libraries reads Lake's own translate-config output, which always writes [[lean_lib]].

  • Tidies, same behavior:

    • the two if not libraries: blocks at the end of lean_libraries are one block;
    • _check_build_freshness drops its timeout argument, which its one caller, default_runner, never passed;
    • run_module_probes is typed with a TypeVar, dict[str, _T | SkeletonError], so skeleton.py no longer needs # type: ignore[arg-type] on records.update(result). The union stays because a module's SkeletonError is stored as its result;
    • _snapshot_regular_file is inlined into project_control_snapshot, its one caller.
  • Tests.

    • New test_a_lean_lib_that_is_not_an_array_is_refused_as_lake_refuses_it in tests/test_lean_probe.py, after the TOML 1.1 test: true, false, a string, single brackets and [1] match "array of tables", and the nameless table matches "has no name".
    • test_a_package_without_library_targets_is_its_own_library now also runs with lean_lib = [], which pins the package fallback for an empty array.
    • test_bounded_command_interruption_kills_the_process returns the real clock on every time.monotonic call but the second (calls != 2, was calls == 1). The interrupt fires at the same point, and a later read during cleanup can no longer raise a second KeyboardInterrupt.
  • CHANGELOG. One Fixed entry, "Lake configuration".

Net: +64/−28 across autoform_cli/lean_probe.py, autoform_cli/skeleton.py, tests/test_lean_probe.py and CHANGELOG.md.

Verification

  • lean_libraries called directly on lakefile.toml-only projects (no process starts), with name = "Solo" plus each shape:
    • base branch: true and 3 raise TypeError; "Solo", [lean_lib], false, [1] and the nameless table return the package library Solo;
    • this branch: all seven raise SkeletonError with the messages above, and lean_lib = [] still returns Solo.
  • When called directly on macOS:
    • the six new refusal cases fail on the base branch (one TypeError, five "DID NOT RAISE") and pass here, and both fallback cases pass on both;
    • 29 selected cases from tests/test_lean_probe.py cover the new test, the interruption test, and the project-layout, TOML, snapshot and probe-pool tests: 26 pass and 2 skip because they need real Lake. The last, test_lake_configuration_snapshot_rejects_content_changed_between_reads, misses under the direct runner on the base branch too, because that runner's temporary directory on macOS is not resolved and pytest's is; called with a resolved directory, it passes;
    • 10 cases from tests/test_skeleton.py that patch _check_build_freshness or run extraction through run_module_probes pass, including test_cli_sets_the_probe_timeout.
  • The locked ruff 0.15.17 check passes on the three Python files, and they compile. ruff format --check flags lean_probe.py, skeleton.py and tests/test_lean_probe.py as it flags them on the base branch; the changed lines add no formatter hunks, and CI does not run the formatter.
  • No type checker ran. No CI job type-checks either, so the TypeVar change is a typing claim only; nothing changes at runtime.
  • CI coverage:
    • test (Python 3.10 and 3.13) runs ruff check autoform_cli servers tests and pytest -q, which collects the new test and the interruption test.
    • real-lean-skeleton runs tests/test_skeleton.py and tests/test_lean_probe.py on real Lake, including the two translate-config layout tests that read a lakefile.lean through lean_libraries.
    • real-lean's impact and library index steps run tests/test_impact.py and tests/test_library_generate.py on real Lean; work import-impact and library index call lean_libraries.
    • windows-hardening runs only the node IDs it lists. test_bounded_command_interruption_kills_the_process is one of them; the new lean_lib test is not.

Overlap

lean_libraries read `lean_lib` with `config.get("lean_lib", []) or []`
and skipped every entry that was not a table with a string name. So
`lean_lib = true` crashed with a TypeError, while `lean_lib = "Solo"`,
`lean_lib = false`, `lean_lib = [1]`, a nameless `[[lean_lib]]` and
`[lean_lib]` written for `[[lean_lib]]` all fell back to the package
library without a word; the single-bracket case dropped its configured
roots. Lake's TOML loader refuses every one of these shapes ("expected
array", "expected table", "missing required key: name"), so
`lake build` never built anything for them. lean_libraries now raises
a SkeletonError naming the problem. An empty array keeps the package
fallback. Only a hand-written lakefile.toml reaches the check: for a
lakefile.lean project, Lake's own translate-config output always writes
`[[lean_lib]]`.

The rest is tidying with no change in behavior:
- merge the two `if not libraries:` blocks in lean_libraries;
- drop `_check_build_freshness`'s `timeout` argument, which its one
  caller never passed;
- type run_module_probes with a TypeVar, so a probe's result type
  reaches the caller and skeleton.py's `type: ignore[arg-type]` on
  `records.update(result)` goes away;
- inline `_snapshot_regular_file` into project_control_snapshot, its
  one caller.

The bounded-command interruption test now returns the real clock on
every call but the second, so a later read of time.monotonic during
cleanup cannot raise a second KeyboardInterrupt. A new test covers the
six refused lean_lib shapes, and the package-fallback test now also runs
with `lean_lib = []`.
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 30, 2026
@Deicyde

Deicyde commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

Review at af9826f: merge.

Nothing blocks merge. This covers #505's own commits (eaa0cca, af9826f) on top of #504. lean_libraries now refuses exactly the shapes Lake's TOML loader refuses (decodeTargetDecls in Lake/Load/Toml.lean, v4.35.0): true, false, numbers, a string, [1], a nameless target and the single-bracket form. An empty lean_lib = [], or none, keeps the package fallback. The four tidies change no behaviour.

Notes

  1. Untested refusals. Main read the valid entries and silently dropped the bad one for lean_lib = [{name = "A"}, 1] and for a nameless [[lean_lib]] after a named one. This PR refuses both, which is right, since Lake refuses them, but no test covers either. lean_lib = 3, which the body names as a former TypeError, is not a parameter either. Adding the three cases to test_a_lean_lib_that_is_not_an_array_is_refused_as_lake_refuses_it would pin them.
  2. name = 1 is reported as "has no name". The target does have a name, just not a string one. Wording such as "has no string name" would be exact. Also, the test's regexes stop before the advice, so ", written [[lean_lib]]" can change or disappear without a failure. Matching array of tables, written \[\[lean_lib\]\] would pin it.
  3. Description. work impact reaches the refusal too: revision_impact goes through _load_impact_evidence, which calls lean_libraries (impact.py:1517). The body's list leaves it out. The CHANGELOG says such files were refused "instead of crashing or silently reading the package's default library"; in the mixed case main skipped the bad entries instead.
  4. After Move the lean_probe runner tests into tests/test_lean_probe.py #504 merges. The repository does not delete merged branches, and tests.yml does not run on a base-branch edit. So retarget this PR to main (gh pr edit 505 --base main) and close and reopen it, so that CI runs against main before merge.

Checks

Every lean_lib shape in the body, plus mixed arrays and non-string names, was run on #504's head and on this head, and compared with Lake's loader source. Of 23 mutants, 14 were killed; the survivors are the untested cases in notes 1 and 2, plus two gaps that already existed on main. Merged with current main, the probe, skeleton, impact, library, review, doc-budget, release and skill-example suites gave 970 passed, 77 skipped (real Lean) and 1 failed. The failure is a Python 3.14 quirk that also fails on plain main. The 8 new collected IDs are the six refusal cases and the now-parametrized fallback test. CI passed 9 of 9.

Posted by Claude Code (session jackmccarthy-63).

Base automatically changed from cleanup/lean-probe-tests to main October 30, 2026 06:05
@Deicyde
Deicyde merged commit 8be7089 into main Oct 30, 2026
9 checks passed
@Deicyde
Deicyde deleted the cleanup/lean-probe-tidy branch October 30, 2026 06:07
Deicyde added a commit to zibo-yang/autoform-bot that referenced this pull request Oct 31, 2026
Lake replaces a lean_lib's default of one glob per root with the globs
array as written, so `globs = []` builds no module. lean_libraries stored
it as () like a missing key, and builds, project_modules and library
generate then fell back to the roots. LeanLibrary.globs is now None for a
missing key and () for an empty array.

When the impact probe loads a module whose source is captured but that no
library builds, the refusal names that source instead of saying there is
none.

Review notes on facebookresearch#500: pin the strict module map for a nested library, the
import-closure listing of a name declared in a file no library builds,
the glob refusals under strict ownership, and builds against Lake's
isBuildableModule rule.

Review notes on facebookresearch#505: refuse-test a number, a mixed array, a nameless
target after a named one and a non-string name; say "has no string name";
pin the ", written [[lean_lib]]" advice; and correct the CHANGELOG, since
main skipped the bad entries of a mixed array.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant