refactor(tethys): extract Tethys::src_root_for_file helper (rivets-6jxv)#71
Conversation
…6jxv)
Dedupes the per-file `crate_root` derivation that rivets-6aoc introduced
across three call sites:
- resolve.rs::resolve_refs_for_file
- indexing.rs::compute_dependencies
- indexing.rs::compute_dependencies_from_stored
Each site previously open-coded ~12 lines of
`cargo::get_crate_for_file(file, &self.crates).map(CrateInfo::src_root)`
plus a parent-dir sentinel fallback with a per-call-site `debug!` log.
Replaced by a single `pub(crate) fn crate_root_for_file(file, caller)`
on `Tethys`. Each call site collapses to one line.
Side benefit: the three sites previously diverged in their fallback
debug! shape — resolve.rs included a `file_id` field; the other two
prefixed the message string with the caller name ("compute_dependencies:"
/ "compute_dependencies_from_stored:"). The helper unifies them with a
structured `operation = caller` field, matching the project's
"structured fields over string interpolation" convention.
Naming note: the helper's name `crate_root_for_file` reflects the
variable name at the call sites and matches the rivets-6jxv issue's
acceptance text. It's visually close to the pre-existing
`Tethys::get_crate_root_for_file` (returns crate directory) but has
distinct semantics — the helper returns `CrateInfo::src_root`
(lib_path-derived) with a sentinel fallback. The doc-comment on the
new method explicitly flags this differentiation.
Verified: 627 tethys tests pass; clippy -D warnings clean.
Two lines wrapped that rustfmt prefers on a single line. Pure formatting; no semantic change.
Code ReviewClean refactor overall — the deduplication is justified (three identical 12-line blocks → one canonical implementation) and the structured logging alignment matches project conventions. A few observations: Substantive:
|
There was a problem hiding this comment.
Code Review
This pull request centralizes the logic for determining a file's source-root anchor by introducing a new helper method, crate_root_for_file, and refactoring call sites in indexing.rs and resolve.rs. The review feedback suggests renaming this method to entry_point_for_file to avoid confusion with the existing get_crate_root_for_file method and to better align with Rust module resolution rules, which resolve paths to entry point files rather than source directories. Suggestions were also provided to update the associated log messages and call sites to maintain consistency with this renaming.
| /// anchor, NOT the crate's directory. The similarly-named | ||
| /// [`Tethys::get_crate_root_for_file`] returns the crate directory | ||
| /// (Cargo.toml's parent) and has different semantics. | ||
| pub(crate) fn crate_root_for_file(&self, file: &Path, caller: &'static str) -> PathBuf { |
There was a problem hiding this comment.
The name crate_root_for_file is confusingly similar to the existing get_crate_root_for_file method, which returns the crate's directory. Furthermore, project rules for Rust module resolution require that single-segment paths resolve to the crate's entry point file (e.g., lib.rs) rather than the source directory. Renaming this to entry_point_for_file better reflects its purpose as a file-level anchor for resolution and avoids confusion.
| pub(crate) fn crate_root_for_file(&self, file: &Path, caller: &'static str) -> PathBuf { | |
| pub(crate) fn entry_point_for_file( |
References
- In Rust module resolution, ensure single-segment paths resolve to the crate's entry point file (e.g., lib.rs) rather than the source directory to prevent corrupting dependency graphs that expect file-level edges.
| ); | ||
| file.parent() | ||
| .map_or_else(|| self.workspace_root.clone(), Path::to_path_buf) | ||
| } |
| .parent() | ||
| .map_or_else(|| self.workspace_root.clone(), Path::to_path_buf) | ||
| }; | ||
| let crate_root = self.crate_root_for_file(current_file, "compute_dependencies"); |
| .map_or_else(|| self.workspace_root.clone(), Path::to_path_buf) | ||
| }; | ||
| let crate_root = self.crate_root_for_file(current_file, "compute_dependencies_from_stored"); | ||
|
|
| // The sentinel keeps `crate::*` paths semantically inert in | ||
| // import-less workspace-root files while letting `self::`/`super::` | ||
| // and the path-agnostic fallback search continue to work. | ||
| let crate_root = self.crate_root_for_file(¤t_file_path, "resolve_refs_for_file"); |
There was a problem hiding this comment.
Code Review — PR #71:
|
Applies the 3 suggestions from the pr-review-toolkit code-reviewer pass: - S3 (observability audit trail): The resolve.rs call site previously logged a `file_id` field in the orphan-file `debug!`; rivets-6jxv dropped it for log-shape parity with the indexing.rs sites. Document the field drop in a code comment at the call site so future operators triaging an orphan-file warning know to recover the file_id via path lookup. - S4 (doc redundancy): Trimmed the 5-line comment at resolve.rs:104-108 down to 5 lines that explain *why this call site* is OK with the sentinel — without restating the helper's own doc-comment. (The comment is the same length, but its content shifted from contract-restating to call-site-specific rationale and the field-drop note.) - Positive observation lock-in: Added a code comment in the helper body noting that it uses the free `cargo::get_crate_for_file` (not `self.get_crate_for_file`) because the latter canonicalizes, which is wasted I/O on pre-canonicalized paths. Locks the decision in for future maintainers who might refactor toward "use the method, not the free fn". No behavior change; fmt + resolver_routing tests verified locally.
Code Review — PR #71: Extract
|
| Issue | Severity |
|---|---|
Lost file_id field on resolve.rs fallback log |
Minor / nice-to-have to restore |
Naming ambiguity with get_crate_root_for_file |
Minor, but worth fixing before merge given the low cost |
pub(crate) note for future test authors |
Nit |
The logic is correct, CI should stay green, and the existing integration tests cover all three sites. Happy to approve with the naming resolved.
…71 round-2) 3 independent reviewers (claude #1, claude #2, gemini-code-assist) flagged the original name `crate_root_for_file` as visually confusable with the pre-existing `Tethys::get_crate_root_for_file`. claude reviewers suggested `src_root_for_file`; gemini suggested `entry_point_for_file`. Decision: take `src_root_for_file`. Why src_root_for_file: - Matches the underlying accessor name (`CrateInfo::src_root()`) it delegates to — the most discoverable naming. - Accurately describes the return value: the src-DIRECTORY used as a module-resolution anchor (lib_path.parent()-derived). - Two independent claude reviewers landed on this exact name. Why NOT entry_point_for_file (gemini's suggestion): - Semantically wrong. `entry_point_*` implies a file (lib.rs / main.rs), matching `CrateInfo::entry_point_file()` added in PR #70. This helper returns a directory. Confusing two distinct CrateInfo concepts. - Gemini's cited "project rule" is about single-segment workspace-crate path resolution (which `CrateInfo::entry_point_file` does handle) — that's a different code path from this helper's anchor-for-multi- segment-path-resolution role. Scope: - Renamed method declaration (lib.rs:730) - Renamed 3 call sites + the 3 immediate `let crate_root` locals to `let src_root` (resolve.rs:109, indexing.rs:915, indexing.rs:1091) - Updated downstream uses within those same functions (`&src_root` passed to ResolveContext field / resolve_module_path) - Updated 1 inline comment that mentioned the old method name - Updated doc-comment naming-note block to reflect the now-clearer distinction from get_crate_root_for_file NOT touched: - `ResolveContext::crate_root` field name and resolver function parameter names. Those describe the value's ROLE within the resolver pipeline ("the anchor against which `crate::*` paths resolve") rather than its type. Renaming them would balloon the PR scope without addressing the reviewers' actual concern (the method-name collision). Verified: cargo fmt clean, 627 tethys tests pass, clippy -D warnings clean.
Code ReviewOverall this is a clean, well-motivated refactor. Eliminating three near-identical 12-line blocks while improving log-field consistency is a good use of a helper. A few notes below. The good
Issues1. Minor observability regression in
|
…nt trim (PR #71) Applies the two style nits from claude-review on the rename commit: - Nit 2: Rename `ResolveContext::crate_root` field (and the parameter names + ctx.* reads in `resolve_via_explicit_import`, `resolve_symbol_in_module`, `resolve_module_to_file_id`) to `src_root`. Closes the visual mismatch where the helper returned `let src_root` but the struct field that consumed it was named `crate_root` — a reviewer "double-take" trigger. All 12 references in resolve.rs now use the same `src_root` vocabulary, aligned with `CrateInfo::src_root()` and the helper name `Tethys::src_root_for_file`. - Nit 3: Condensed the helper-body inline comment about `cargo::get_crate_for_file` vs `self.get_crate_for_file` from 5 lines to 2. The "why" (canonicalize syscall avoidance) is preserved; the rivets-6jxv backstory is dropped — it's recoverable from `git blame` if anyone ever needs it. Net: -3 / +14 lines across 2 files. 627 tethys tests pass, clippy -D warnings clean, fmt clean. Decision not taken: the function parameter names inside the resolver's internal helper chain (`resolve_via_explicit_import` etc.) were also renamed. They could have stayed as `crate_root` to describe the parameter's ROLE rather than its derivation, but the resolver-wide rename makes the data flow read consistently top-to-bottom and the field-vs-parameter split would have created the same mismatch a layer deeper. One vocabulary wins.
Code Review:
|
…file (PR #71 round-4) claude-review on round-3 flagged that the function-level doc comments on compute_dependencies, compute_dependencies_from_stored, and resolve_refs_for_file still described the inline derivation logic that now lives in Tethys::src_root_for_file. The function bodies were correct after the rename; only the prose was stale. Updates: - indexing.rs::compute_dependencies: trim 6-line derivation explanation to 5 lines, point to [Tethys::src_root_for_file] for the derivation contract; keep the resolver-specific "what happens to crate::* in orphan files" semantic context. - indexing.rs::compute_dependencies_from_stored: replace the see-compute_dependencies-for-the-crate_root-rationale forward reference with a direct link to [Tethys::src_root_for_file] (the forward reference itself was stale because it pointed at content that no longer existed). - resolve.rs::resolve_refs_for_file: trim the duplicate derivation explanation; keep the resolver-specific "why we need this anchor for crate::* paths to work in sub-crate files" rationale that's not in the helper doc. Pure documentation; no code change. fmt clean, clippy -D warnings clean.
Code Review — PR #71:
|
| Correctness | ✅ No semantic changes — pure dedup |
| Logging | ✅ Improved (structured fields, unified shape) |
| Naming | ✅ src_root is more accurate than crate_root |
| PR description accuracy | crate_root_for_file |
| Comment convention | |
| Test coverage | ℹ️ Existing integration tests cover it; a unit test would strengthen it |
The code changes themselves are correct and an improvement. The two items to fix before merge are the PR description mismatch (cosmetic but confusing) and the inline comment anti-pattern (easy one-line trim).
…nit test (PR #71) Applies the two actionable items from claude-review on round-4: - Trim resolve.rs call-site comment to drop the `rivets-6jxv` issue-ID reference. CLAUDE.md explicitly calls this out as an anti-pattern ("Don't reference the current task, fix, or callers ... since those belong in the PR description and rot as the codebase evolves"). The trimmed comment keeps the operationally-useful WHY (file_id not in the helper log, recover via path lookup) without the rot-prone issue reference. - Add a unit test for `src_root_for_file`'s orphan-file branch in lib.rs's tests module. The integration tests primarily exercise the success arm via real workspace fixtures with discovered crates; the orphan-file `cargo::get_crate_for_file -> None` arm with the `file.parent()` sentinel fallback was only indirectly covered. Single test that creates a Tethys against an empty tempdir (no crates discovered), asks for `src_root_for_file` on a synthetic orphan path, and asserts the parent-dir sentinel fires. Also separately: PR title updated via `gh pr edit` from `crate_root_for_file` to `src_root_for_file` to match the renamed helper. The title and PR description had stayed stale through the rename in round-2; round-4 reviewer flagged the mismatch as confusing for skimmers. Verified: 628 tethys tests pass (+1 for the new orphan-file test; was 627 in round-3). clippy -D warnings clean, fmt clean.
Code Review — PR #71:
|
…ound-6) CLAUDE.md's extended guidelines list "Avoid decorative divider lines" as an explicit anti-pattern. claude-review on round-5 flagged the `// === src_root_for_file Tests ===` block I introduced. The same pattern exists elsewhere in the test module (pre-rivets-6jxv), but the reviewer's read — don't extend the debt even if you don't pay it down — is the right call. The orphan-file test's own doc-comment already labels what the test covers, so dropping the header banner loses no information.
Code Review — Current Diff StateThe prior What's good
One remaining item:
|
Summary
crate_rootderivation across three production sites by extracting a singlepub(crate) fn crate_root_for_file(file, caller) -> PathBufonTethys.resolve.rs::resolve_refs_for_fileindexing.rs::compute_dependenciesindexing.rs::compute_dependencies_from_storeddebug!shape — one included afile_idfield, the other two embedded the caller name in the message string. The helper unifies them with a structuredoperation = callerfield, matching the project's "structured fields over string interpolation" convention from CLAUDE.md.Net diff: -46 / +49 lines across 3 files (3 inline derivations × ~12 lines removed; 1 helper ~25 lines incl. doc-comment added).
Naming clarification
The helper's name
crate_root_for_filereflects the variable name at the three call sites and matches the rivets-6jxv acceptance text verbatim. It's visually close to the pre-existingTethys::get_crate_root_for_file(which returns the crate's directory, i.e.Cargo.toml's parent) but has distinct semantics — the new helper returnsCrateInfo::src_root()(lib_path-derived) with a parent-dir sentinel fallback. The doc-comment on the new method has an explicit Naming note block flagging the differentiation for future maintainers.If reviewers prefer a clearer name (e.g.
src_root_for_fileormodule_resolution_anchor_for_file), it's a 4-instance rename — happy to apply in a follow-up commit.Acceptance criteria (from rivets-6jxv)
Tethys::crate_root_for_filehelper extracted with consistent caller-name parameterresolve.rs::resolve_refs_for_fileuses the helperindexing.rs::compute_dependenciesuses the helperindexing.rs::compute_dependencies_from_storeduses the helpercargo clippy --all-targets --all-features -- -D warningscleancargo fmt --checkclean (verified locally pre-commit)ycaq epic status post-merge
If this merges, all 6 acceptance criteria for
rivets-ycaqare met: lcb6 (#65), 0gom (#66), 3d0s (#67), measurement (#68), dn35 (#69), i8qn (#70), 6jxv (this PR). The resolver-correctness epic is closeable.Test plan
resolver_routing.rs,module_path_integration.rs,cargo_discovery.rs,file_deps_corroboration.rs, etc., all passing post-refactor