Fix walrus-binding, venv-pruning, fix-scope, scope-notice, and tuple-context bugs; seed the bundled typeshed pin#347
Merged
Merged
Conversation
`literal_collection_assignable_to` ([TYPEINF-SPECIAL-LITERAL-CONTEXT]) had arms for Union/Optional/List/Set/Dict but none for Tuple, so a declared `tuple[list[int], str]` fell through to `_ => None`. The caller then fell back to the invariant `is_assignable_to`, where the bottom-up `list[Never]` synthesised for `[]` fails invariance against `list[int]` — a false `returns_compatibility` on `return [], "lit"`. Add the Tuple arm plus a `tuple_literal_assignable_to` helper that carries the declared element type inward per position, reusing the existing `homogeneous_tuple_elem` / `is_unpacked_tuple_elem` shape decomposition rather than re-deriving it. PEP 646 unpacked segments and arity mismatches still return `None`, leaving the invariant fallback untouched. This closes the asymmetry with the assignment path, whose gate already included `RhsKind::Tuple` — `x: tuple[list[int], str] = ([], "a")` was accepted while the identical `return` was rejected. Conformance is unchanged at 141/141, 0 missed, 0 false positives. Refs #337
`basilisk check` filters every analyze-scope diagnostic at the CLI edge ([CHKARCH-COMMANDS]), so a project that grades non-`pep` rule tags `error` could see "All checked. No issues found." while none of those rules were ever evaluated. A silent clean run was indistinguishable from a clean project — the same class of failure as a skipped CI gate reporting success. One reported repo hid 66 configured errors this way for the life of its pipeline. A text-mode `check` now closes with one line naming how many rules the configuration selected that this command does not run, and pointing at `basilisk analyze` ([CHKARCH-CLI-SCOPE-NOTICE]). - `analyze_selected_rules` is the counterpart to the adjacent `pep_disable_violations`: the non-`pep` rules a config resolves to a non-disabled severity. It keeps the bare-tree fast path used by `EffectiveRuleConfig::severity`, so conformance and benchmark runs never walk the rule catalog. - The pipeline counts them across the base config and every per-directory config, so one file's config never speaks for the whole run. The union walk it shares with `pep_disable_config_error` is now a single `codes_across_configs` helper rather than two copies. - The notice is a fact about the project, not boilerplate: a tree that selects no analyze-scope rule prints nothing extra, and `analyze` — which just ran them — never prints it. Exit codes and the JSON contract are unchanged; machine consumers see no new field. The issue's other two parts: `basilisk --help` already lists `analyze` on this branch, and the check/analyze split is now documented next to `rule-tags` in the configuration reference. Conformance is unchanged at 141/141, 0 missed, 0 false positives. Refs #334
`basilisk fix` declared `PATHS` with `#[arg(default_value = ".")]`, so `paths` was never empty and `collect_and_fix` skipped `effective_check_paths` — the [CHKARCH-CONFIG-INCLUDE] fallback that `check`/`analyze` run. A no-args `fix` therefore walked the entire working directory instead of `[tool.basilisk] include`. `fix` mutates files, so that default is materially riskier than the same default on a read-only command: in a project with a checked-out virtualenv it rewrote third-party sources under `venv/site-packages` (and walked ~10k files, appearing to hang). Drop the clap default and route the paths through `effective_check_paths`, so explicit CLI paths still win and a bare `basilisk fix` touches exactly what `basilisk check` reads. Refs #333
Virtualenvs were skipped only because `venv`/`.venv`/`env`/`site-packages` are literal entries in `DEFAULT_EXCLUDES`. Per [CHKARCH-CONFIG-EXCLUDE], configuring `exclude` *replaces* that list wholesale, so any project that sets its own patterns silently lost all venv protection — and `fix` mutates files, so it rewrote installed third-party packages. Add `basilisk_config::is_virtualenv_dir`, keyed on PEP 405's `pyvenv.cfg` rather than on directory names, so `env/` and `.direnv/python-3.13/` are recognised alongside `.venv/`. Both walkers call it at the exact point they already skip hidden directories — the CLI walk behind `check`/`fix`/`format`/`adopt`/`stubs`, and the LSP workspace scan — so the editor and CLI still agree file-for-file. The skip prunes traversal *into* a venv and never overrides an explicit request: `basilisk check venv/lib/.../dep` still reports, mirroring the walk's existing depth-0 root exemption. Refs #341
`names_undefined` decided a return-name was defined by consulting `FunctionInfo::all_local_assigns`, built by matching statement-shaped binding forms — `Assign`, `AnnAssign`, `For`, `With`, imports, nested `def`/`class`. A PEP 572 assignment expression binds from inside an *expression* (`if item := prices.get(asset):` binds `item` from the `if` test), so no statement match ever saw it and the rule reported valid code as undefined. A new `visitor::walrus` collects `Expr::Named` targets at two reaches. `Any` feeds `all_local_assigns`: the target is bound somewhere in the body. `Definite` feeds `unconditional_assigns`: only what Python must evaluate once control reaches a statement, so branch bodies and short-circuiting operands are pruned while the first operand of `and`/`or`, a ternary's test, and a comprehension's outermost iterable are kept. Both skip nested `def`/`class`/`lambda` scopes and both descend into comprehensions, which PEP 572 exempts from the comprehension's own scope. Without the `Definite` half the fix would merely trade `names_undefined` for a fresh `names_unbound` false positive on `if hit := d.get(k): ...` followed by a top-level `return hit`. Conformance stays 141/141; `scope_tree` picks up walrus locals for rename and scope-aware reference finding for free. Refs #339
# Conflicts: # crates/basilisk-config/src/lib.rs
Opening an unconfigured workspace seeded only the strict-by-default rule tag, so a freshly-opened repo sat permanently UNPINNED ([STUBRES-TYPESHED-WARN]) until the user separately ran `basilisk typeshed download` — not reproducible out of the box. `write_seed` (`crates/basilisk-lsp/src/config_seed.rs`) now builds a full `ConfigurationUpdate` through the same validated, structure-preserving editor transaction, stamping `typeshed-commit = bundled_commit_sha()` alongside `[tool.basilisk.rule-tags] basilisk = "error"`. The bundled commit is complete inside the binary, so the pin needs no network access and cannot produce a NO SOURCE state; it suppresses UNPINNED without changing which stubs are resolved. The seed only runs when the ancestor walk found no `[tool.basilisk]` table, so no `typeshed-path` can exist for the pin to conflict with. One-time and never-resurrect guarantees are untouched: any existing table still blocks the seed entirely, and a deleted pin is never resurrected. The CLI still never seeds. [LSPARCH-CONFIG-SEEDING] is updated to describe the stamped pin. Refs #343
Regenerated by scripts/gen_conformance_reference.py during the local scorer run: 141/141 (100%), 0 false positives, unchanged — only the graded python/typing commit advanced.
…0% kill rate, unchanged)
Any patch that had to create the `tool` path rendered an explicit empty `[tool]` header (and a rule-only seed also a bare `[tool.basilisk]`), because `child_table_mut` inserted `Table::new()`, which toml_edit renders explicitly. Tables the parser creates for dotted headers are implicit, so a hand-written file never shows them — the writer now marks the tables it creates the same way ([CONFIGEDITOR-SOURCES]). toml_edit renders an implicit table the moment it holds direct entries, so mutation targets are unaffected, and tables already present in the document keep their explicit headers — the never-prune guarantee is untouched (`removing_last_entry_keeps_empty_table` still passes). The seed now renders exactly the shape #343 specified: [tool.basilisk] typeshed-commit = "<sha>" [tool.basilisk.rule-tags] basilisk = "error" Refs #343
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.
TLDR
Fixes six reported bugs — four checker/CLI false-positive and file-safety defects, one silent-clean-run reporting gap, and the LSP config seed leaving fresh workspaces
UNPINNED— with no conformance or benchmark movement (141/141, 0 FP).Refs #333, Refs #334, Refs #337, Refs #339, Refs #341, Refs #343
What Was Added?
crates/basilisk-resolver/src/visitor/walrus.rs— collects PEP 572 assignment-expression (:=) targets at two reaches:Any(bound somewhere in the body) andDefinite(must-evaluate positions only — first operand ofand/or, ternary tests, outermost comprehension iterables; branch bodies and short-circuited operands pruned). Feedsall_local_assigns/unconditional_assignssonames_undefinedandnames_unboundboth recognise walrus bindings;scope_treepicks up walrus locals for rename/references (Refs Walrus operator not correctly supported #339).basilisk_config::is_virtualenv_dir(crates/basilisk-config/src/paths.rs) — structural venv detection keyed on PEP 405'spyvenv.cfginstead of directory names. Both walkers (CLI walk behindcheck/fix/format/adopt/stubs, and the LSP workspace scan) prune venvs unconditionally, so a project-setexcludelist no longer strips venv protection andfixcan never rewritesite-packages(Refs fix/check still traverse into virtualenvs when a custom exclude replaces the defaults (no pyvenv.cfg guard) #341). An explicitly-passed path inside a venv is still checked.[CHKARCH-CLI-SCOPE-NOTICE]— a text-modechecknow closes with one line naming how many configured rules this command never runs (analyze-scope rules), pointing atbasilisk analyze. Counted across the base config and every per-directory config; prints nothing when no analyze-scope rule is selected, andanalyzenever prints it. JSON output and exit codes unchanged (Refs analyze missing from --help: tag-configured rules silently never run under check (66 hidden errors in one repo) #334).tuple_literal_assignable_toin the checker carries a declaredtuple[...]element type inward per position, closing the asymmetry wherex: tuple[list[int], str] = ([], "a")was accepted butreturn [], "lit"was rejected (Refs Empty container literal inside a returned tuple ignores the declared return type — tuple[list[Never], ...] false positive #337).What Was Changed or Deleted?
basilisk fixdefault paths (crates/basilisk-cli/src/main.rs) — dropped the clapdefault_value = "."so a barefixroutes througheffective_check_pathsand touches exactly the configured[tool.basilisk] includeroots thatcheckreads, instead of walking (and rewriting) the entire working directory (Refs basilisk fix defaults to '.' and ignores include/exclude — traverses venv/ and hangs, while mutating files #333). Explicit CLI paths still win.crates/basilisk-lsp/src/config_seed.rs) — the one-time strict-by-default seed now also stampstypeshed-commit = "<bundled sha>"(viabundled_commit_sha()) through the same validatedbuild_configuration_patchtransaction, so a freshly-opened workspace is pinned and reproducible — neverUNPINNED— with no network access (Refs LSP config seeding never stamps typeshed-commit, leaving freshly-opened workspaces UNPINNED #343). One-time and never-resurrect guarantees preserved; the CLI still never seeds.python/typing@39164cd(score unchanged); mutation baseline date refreshed after a green 144-mutant, 0-missed run.How Do The Automated Tests Prove It Works?
walrus_in_if_test_binds_the_name_for_a_return_inside_the_branch,..._for_a_later_top_level_return,..._while_test_...(names_undefined_tests) fail withoutvisitor::walrusand pass with it — including theDefinitehalf that would otherwise trade the FP for anames_unboundFP.fix_never_rewrites_inside_a_virtualenv_when_custom_exclude_replaces_defaultsandcheck_does_not_scan_inside_a_virtualenv_when_custom_exclude_replaces_defaults(e2e_exclude_config) build a project with a customexcludeplus a fake venv and assert the vendored file is untouched/unreported;an_explicit_path_inside_a_virtualenv_is_still_checkedguards the root exemption;virtualenvs_are_pruned_by_pyvenv_cfg_regardless_of_configured_excludescovers the LSP scan.fix_no_args_honors_include_and_never_rewrites_vendored_files(e2e_include_config) runs a barebasilisk fixin a project withincludeset and asserts only included roots are rewritten.check_reports_configured_rules_its_scope_did_not_run,check_stays_quiet_when_configuration_selects_no_analyze_rule(e2e_scope) andanalyze_selected_rules_reports_what_check_will_not_runpin the notice's presence, absence, and count.empty_list_inside_returned_tuple_uses_declared_element_context,empty_dict_...,tuple_literal_nested_in_returned_list_uses_declared_context, plustuple_literal_with_wrong_element_still_errors/homogeneous_...proving required errors are still emitted (returns_compatibility_tests).seed_stamps_the_bundled_typeshed_commit(unit, written first and verified failing) and the strengthenedinitialization_seeds_an_unconfigured_root_exactly_oncee2e assert the pin lands via the realinitializepath and is never resurrected after deletion.python/typingclone, mutation gate 144 mutants / 0 missed / 100% kill rate, VSIX (93% ≥ 92%), Neovim (45% ≥ 44%), Zed (build + clippy + 97 tests + standalone mirror), website build + drift guards + Playwright smoke tests, lint/fmt/deslop.Spec / Doc Changes
[LSPARCH-CONFIG-SEEDING](LSP-ARCHITECTURE-SPEC) — seed now documented as rule tag + bundled typeshed pin, with the no-conflict and never-resurrect reasoning.[CHKARCH-CLI-SCOPE-NOTICE]added and[CHKARCH-CONFIG-EXCLUDE]extended (CHECKER-ARCHITECTURE-SPEC) for the scope notice and structural venv pruning.[TYPEINF-SPECIAL-LITERAL-CONTEXT](CHECKER-TYPE-INFERENCE-SPEC) — tuple element positions added to literal-context propagation.website/src/docs/configuration.md— documents the check/analyze rule-scope split next torule-tags.python/typing@39164cd.Breaking Changes
basilisk fix's bare-invocation default narrows from.to the configured include roots (the documented,check-consistent behaviour), and text-modecheckmay print one additional advisory line; JSON output and exit codes are unchanged.