Repository navigation
fix(rust): resolve bare calls through exact use bindings - #2308
danusha2345 wants to merge 1 commit into
Conversation
fa3f3ac to
826c811
Compare
4bbca0c to
c99a26b
Compare
c99a26b to
b23125d
Compare
b23125d to
3365c14
Compare
3365c14 to
c928581
Compare
c928581 to
c7688f4
Compare
…ymchenry#1921, colbymchenry#1935, colbymchenry#1955, colbymchenry#1957, colbymchenry#1965, colbymchenry#2575, colbymchenry#2595, colbymchenry#2597, colbymchenry#2603–colbymchenry#2608, colbymchenry#2611 landed upstream) Conflicts: files owned by the foreign PRs now in main (colbymchenry#1921's Python import resolver and tests, colbymchenry#1935's class-strategy method) take main's version; our colbymchenry#1954/colbymchenry#1942/colbymchenry#1945/colbymchenry#1939/colbymchenry#1715/colbymchenry#1951/colbymchenry#2308 hunks sit beside main's new Rust and Python blocks as in the rebased PR branches; java.rs keeps the integration's `constant_declaration` fields under main's initializer walk; three CHANGELOG lines the merged PRs had already contributed are deduplicated, and the method blocks the merge duplicated in queries.ts and extraction/index.ts (the resolution-edge queries and the sync resurrection helpers, present once on main) are kept once. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…olbymchenry#2308 follow-up for colbymchenry#2711's test) The same change went into the rebased colbymchenry#2308 branch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1d2a8e8 to
5ca190d
Compare
A bare call bound by a `use` (`use crate::util::take; take(3)`, also through `as` aliases, nested groups, `self`/`super`/`crate` paths and block-local imports) now resolves to the item that path names before name matching runs. A parameter, `let` or nested fn of that name still shadows the import. A binding the resolver cannot follow (another workspace crate, a `pub use` re-export, a relative path inside an inline module) keeps the previous resolution. A module path (`crate::util::take(3)`) names the module's own item: never a method, an associated item of an impl or trait body, or a fn-local item. A module-level item wins over an inline module's item reached through a re-export. Includes and supersedes colbymchenry#2259. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5ca190d to
9b6f7ef
Compare
A Rust path into a module, `crate::util::take(1)`, `super::util::take(1)`,
`util::take(1)` or a `use crate::util::take;`, went to the first node of
that name in the module's file, methods included, so an `impl Buf { fn
take }` above `pub fn take` caught every such call. A function written
inside another function, or in a `mod tests { ... }` above the module's
own item, could take it too, a path into the module's own file linked
nothing, and a path through a module a `use` takes from the standard
library (`fs::read` under `use std::fs;`) went to the project's own `fs`
module.
The path's item is now the module's own: never a method, an impl's or a
trait's associated item, or an item of a function body; one written at
the file's top level before one in a module the file declares inline,
which the path reaches through the module's re-export (`#[cfg(unix)] mod
imp { ... }` + `pub use imp::*;`). A path whose head a `use` binds from
the standard library is left to it (main's isBoundToOutOfRepoImport rule
for bare names), and a path into the module's own file resolves.
Validation:
- rust-module-path-call.test.ts: 8/8; on main 7 fail (method Buf::take,
Deep::get, Only::grab, no import edge, the inline-module namesake, the
re-export from its own file, std `fs::read` -> src/fs/mod.rs).
- A/B on main 7cceba5, 6 Rust repos: 162 changed sites, all inspected and
right. gleam: 133 calls like `fs::write(...)` / `crate::fs::mkdir(...)` move
from `impl ... for ProjectIO`'s methods to compiler-cli's free functions,
4 wrong `type_::list(...)` -> `Type::list` edges are gone, 15 `use`s link;
tokio: `crate::io::uring::open::open(...)` moves from `Op::open` to the free
`open`, +4; dioxus +7, 1 more; ripgrep +1; clap and serde unchanged.
express and flask byte-identical.
- Cost (medians of 3 interleaved runs): tokio CPU 36.6 -> 36.0 s, gleam
34.3 -> 35.2 s, both within the runs' spread; peak RSS unchanged. In a
CPU profile of tokio the changed functions take under 0.1 s. The
standard-library check reads the file's `use`s, so it runs only when the
path would otherwise link a project item (asked for every path, it cost
0.3 s on tokio).
This is the separable part of PR #2308. Its bare-call bindings (`as`
aliases, block-local `use`, shadowing by locals, enum variants through a
`use`) and the second `use` parser it adds are left out: main's `use` walk
(rustUsesOf, rustWalkModules, rustModuleItems) and scope reader
(rust-scopes.ts) cover the same ground, and folding those cases into them
is proposed separately.
Original PR #2308 by @danusha2345.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thank you for this careful work, @danusha2345! The module-path part has landed in #2955 with you as the commit author: calls like |
…mchenry#2972; 10 landed PRs yield to main; EXTRACTION_VERSION 56) Main landed ten more of our PRs as our own commits with the maintainer's additions (colbymchenry#2923 ← colbymchenry#1936, colbymchenry#2924 ← colbymchenry#1951, colbymchenry#2925 ← colbymchenry#1939, colbymchenry#2930 ← colbymchenry#2921, colbymchenry#2940 ← colbymchenry#1736, colbymchenry#2942 ← colbymchenry#1947, colbymchenry#2950 ← colbymchenry#1952, colbymchenry#2961 ← colbymchenry#1954, colbymchenry#2965 ← colbymchenry#1942, colbymchenry#2972 ← colbymchenry#1933) and the module-path half of colbymchenry#2308 (colbymchenry#2955). Where the integration carried our version, main's wins: the enclosing-scope member pick and its tests, the Go and Java receiver typers, the delegating-call self-loop rule, the duplicated-module copy confidence, the C #if collapse, the module-path resolution of Rust paths, the trail/path hardening in utils.ts. Kept beside main: colbymchenry#1875's Verilog hooks and status fields, colbymchenry#2839's implementations command, colbymchenry#1923, colbymchenry#2922, colbymchenry#2258, colbymchenry#1761, colbymchenry#1583, the bare-call half of colbymchenry#2308 (rust-use-bindings.ts). CHANGELOG is main's plus the open PRs' bullets. EXTRACTION_VERSION 56: the C #if group handling (colbymchenry#2940) and the kernel change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…he crate's modules
A bare Rust name took the file's `use`s all at once: a `use` inside one
function (`fn first() { use crate::util::take; take() }`) bound `take` for
every other function too, and for the file's own `fn take`; a renamed one
(`use crate::util::take as consume; consume()`) linked nothing, since no
item carries the alias; a parameter or closure of the function's name
(`fn run(take: fn()) { take() }`, `let take = || {};`) linked to the
imported function; and `use crate::models::E::Good; Good(1)` took any
`E::Good`. Paths through modules (`resolveRustPathReference`) walked the
directories instead of the modules: `super::helper()` and `crate::config()`
with no module between linked nothing, `super` inside an inline
`mod tests { }` went to the parent directory's module, and an item a module
passes on (`pub use`, a glob) or another workspace crate's was not found.
The fix keeps one `use` reader, rustUsesOf:
- each `use` keeps the block it is written in (a function's body or a
block; an inline `mod` and a macro's braces, `cfg_if!`'s nested ones
included, are the module's), and std `use`s are kept by position;
- a reference reads the binding of its innermost scope (rustVisibleUse);
isRustNameInScope uses it, so a block's `use` shadows the file's own item
and a variant `use` names its enum's variant (rustUsePathItems' enum tail);
- matchRustUseBoundName, ahead of the pre-filter, resolves a rename to the
item its path names, and a call to a name a local binds (rust-scopes'
bindings, nearer than any `use`) to nothing, unless the local is a `let`
holding a function by its path (serde's `let test = test::<i8>;`);
- resolveRustPathReference is rustWalkModules + rustModuleItems from the
module the path is written in (rustPathTarget), with today's crate-root
fallback; resolveRustModuleFile, collectRustUseBindings and
rustItemPlacement are gone, and isBoundToOutOfRepoImport reads
rustUsesOf's std `use`s;
- `use`s are read with strings blanked (blankRustCode), so a string's
"use crate::x;" binds nothing;
- the crate graph reads a package's name by its section (axum-core writes
`categories = [..]` first) and a `[lib]`/`[[bin]]` path of its own
(ripgrep's `crates/core/main.rs`), which `crate::` then starts from; a
file no manifest covers starts it from the nearest lib.rs or main.rs
above it, as the directory walk did.
Validation:
- rust-use-call.test.ts (PR 2308's 15 cases), rust-use-scopes.test.ts,
rust-crate-roots.test.ts and 5 new rust-module-path-call cases: on main's
source 18 of 32 fail; all pass. Every rust-* suite passes.
- A/B on main 01e0ac3, 7 repos, the same sites on the kernel and on wasm:
tokio +2,434 sites (1,354 calls, 1,053 imports), 159 removed (152 locals
called or passed as values, 6 `Result`s std's, 1 import through a path
attribute module); ripgrep +248 / -149; axum +710 / -60; clap +730 / -112;
serde +155 / -207 (192 calls to a closure named `test`); dioxus
+2,950 / -272; gleam +1,573 / -359. Groups and samples inspected, every
changed site's crate checked against the manifests' dependencies;
express and flask byte-identical.
- Cost (kernel, medians of 3 interleaved): tokio CPU 34.5 -> 33.0 s, peak
RSS 991 -> 1,007 MB; gleam 32.5 -> 30.6 s, 1,022 -> 1,012 MB; dioxus
45.4 -> 45.4 s, 1,101 -> 1,107 MB.
- Original PR #2308 by @danusha2345; its module-path part landed as PR #2955.
Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he crate's modules
A bare Rust name took the file's `use`s all at once: a `use` inside one
function (`fn first() { use crate::util::take; take() }`) bound `take` for
every other function too, and for the file's own `fn take`; a renamed one
(`use crate::util::take as consume; consume()`) linked nothing, since no
item carries the alias; a parameter or closure of the function's name
(`fn run(take: fn()) { take() }`, `let take = || {};`) linked to the
imported function; and `use crate::models::E::Good; Good(1)` took any
`E::Good`. Paths through modules (`resolveRustPathReference`) walked the
directories instead of the modules: `super::helper()` and `crate::config()`
with no module between linked nothing, `super` inside an inline
`mod tests { }` went to the parent directory's module, and an item a module
passes on (`pub use`, a glob) or another workspace crate's was not found.
The fix keeps one `use` reader, rustUsesOf:
- each `use` keeps the block it is written in (a function's body or a
block; an inline `mod` and a macro's braces, `cfg_if!`'s nested ones
included, are the module's), and std `use`s are kept by position;
- a reference reads the binding of its innermost scope (rustVisibleUse);
isRustNameInScope uses it, so a block's `use` shadows the file's own item
and a variant `use` names its enum's variant (rustUsePathItems' enum tail);
- matchRustUseBoundName, ahead of the pre-filter, resolves a rename to the
item its path names, and a call to a name a local binds (rust-scopes'
bindings, nearer than any `use`) to nothing, unless the local is a `let`
holding a function by its path (serde's `let test = test::<i8>;`);
- resolveRustPathReference is rustWalkModules + rustModuleItems from the
module the path is written in (rustPathTarget), with today's crate-root
fallback; resolveRustModuleFile, collectRustUseBindings and
rustItemPlacement are gone, and isBoundToOutOfRepoImport reads
rustUsesOf's std `use`s;
- `use`s are read with strings blanked (blankRustCode), so a string's
"use crate::x;" binds nothing;
- the crate graph reads a package's name by its section (axum-core writes
`categories = [..]` first) and a `[lib]`/`[[bin]]` path of its own
(ripgrep's `crates/core/main.rs`), which `crate::` then starts from; a
file no manifest covers starts it from the nearest lib.rs or main.rs
above it, as the directory walk did.
Validation:
- rust-use-call.test.ts (PR 2308's 15 cases), rust-use-scopes.test.ts,
rust-crate-roots.test.ts and 5 new rust-module-path-call cases: on main's
source 18 of 32 fail; all pass. Every rust-* suite passes.
- A/B on main 01e0ac3, 7 repos, the same sites on the kernel and on wasm:
tokio +2,434 sites (1,354 calls, 1,053 imports), 159 removed (152 locals
called or passed as values, 6 `Result`s std's, 1 import through a path
attribute module); ripgrep +248 / -149; axum +710 / -60; clap +730 / -112;
serde +155 / -207 (192 calls to a closure named `test`); dioxus
+2,950 / -272; gleam +1,573 / -359. Groups and samples inspected, every
changed site's crate checked against the manifests' dependencies;
express and flask byte-identical.
- Cost (kernel, medians of 3 interleaved): tokio CPU 34.5 -> 33.0 s, peak
RSS 991 -> 1,007 MB; gleam 32.5 -> 30.6 s, 1,022 -> 1,012 MB; dioxus
45.4 -> 45.4 s, 1,101 -> 1,107 MB.
- Original PR #2308 by @danusha2345; its module-path part landed as PR #2955.
Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he crate's modules
A bare Rust name took the file's `use`s all at once: a `use` inside one
function (`fn first() { use crate::util::take; take() }`) bound `take` for
every other function too, and for the file's own `fn take`; a renamed one
(`use crate::util::take as consume; consume()`) linked nothing, since no
item carries the alias; a parameter or closure of the function's name
(`fn run(take: fn()) { take() }`, `let take = || {};`) linked to the
imported function; and `use crate::models::E::Good; Good(1)` took any
`E::Good`. Paths through modules (`resolveRustPathReference`) walked the
directories instead of the modules: `super::helper()` and `crate::config()`
with no module between linked nothing, `super` inside an inline
`mod tests { }` went to the parent directory's module, and an item a module
passes on (`pub use`, a glob) or another workspace crate's was not found.
The fix keeps one `use` reader, rustUsesOf:
- each `use` keeps the block it is written in (a function's body or a
block; an inline `mod` and a macro's braces, `cfg_if!`'s nested ones
included, are the module's), and std `use`s are kept by position;
- a reference reads the binding of its innermost scope (rustVisibleUse);
isRustNameInScope uses it, so a block's `use` shadows the file's own item
and a variant `use` names its enum's variant (rustUsePathItems' enum tail);
- matchRustUseBoundName, ahead of the pre-filter, resolves a rename to the
item its path names, and a call to a name a local binds (rust-scopes'
bindings, nearer than any `use`) to nothing, unless the local is a `let`
holding a function by its path (serde's `let test = test::<i8>;`);
- resolveRustPathReference is rustWalkModules + rustModuleItems from the
module the path is written in (rustPathTarget), with today's crate-root
fallback; resolveRustModuleFile, collectRustUseBindings and
rustItemPlacement are gone, and isBoundToOutOfRepoImport reads
rustUsesOf's std `use`s;
- `use`s are read with strings blanked (blankRustCode), so a string's
"use crate::x;" binds nothing;
- the crate graph reads a package's name by its section (axum-core writes
`categories = [..]` first) and a `[lib]`/`[[bin]]` path of its own
(ripgrep's `crates/core/main.rs`), which `crate::` then starts from; a
file no manifest covers starts it from the nearest lib.rs or main.rs
above it, as the directory walk did.
Validation:
- rust-use-call.test.ts (PR 2308's 15 cases), rust-use-scopes.test.ts,
rust-crate-roots.test.ts and 5 new rust-module-path-call cases: on main's
source 18 of 32 fail; all pass. Every rust-* suite passes.
- A/B on main 01e0ac3, 7 repos, the same sites on the kernel and on wasm:
tokio +2,434 sites (1,354 calls, 1,053 imports), 159 removed (152 locals
called or passed as values, 6 `Result`s std's, 1 import through a path
attribute module); ripgrep +248 / -149; axum +710 / -60; clap +730 / -112;
serde +155 / -207 (192 calls to a closure named `test`); dioxus
+2,950 / -272; gleam +1,573 / -359. Groups and samples inspected, every
changed site's crate checked against the manifests' dependencies;
express and flask byte-identical.
- Cost (kernel, medians of 3 interleaved): tokio CPU 34.5 -> 33.0 s, peak
RSS 991 -> 1,007 MB; gleam 32.5 -> 30.6 s, 1,022 -> 1,012 MB; dioxus
45.4 -> 45.4 s, 1,101 -> 1,107 MB.
- Original PR #2308 by @danusha2345; its module-path part landed as PR #2955.
Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he crate's modules (#3014) A bare Rust name took the file's `use`s all at once: a `use` inside one function (`fn first() { use crate::util::take; take() }`) bound `take` for every other function too, and for the file's own `fn take`; a renamed one (`use crate::util::take as consume; consume()`) linked nothing, since no item carries the alias; a parameter or closure of the function's name (`fn run(take: fn()) { take() }`, `let take = || {};`) linked to the imported function; and `use crate::models::E::Good; Good(1)` took any `E::Good`. Paths through modules (`resolveRustPathReference`) walked the directories instead of the modules: `super::helper()` and `crate::config()` with no module between linked nothing, `super` inside an inline `mod tests { }` went to the parent directory's module, and an item a module passes on (`pub use`, a glob) or another workspace crate's was not found. The fix keeps one `use` reader, rustUsesOf: - each `use` keeps the block it is written in (a function's body or a block; an inline `mod` and a macro's braces, `cfg_if!`'s nested ones included, are the module's), and std `use`s are kept by position; - a reference reads the binding of its innermost scope (rustVisibleUse); isRustNameInScope uses it, so a block's `use` shadows the file's own item and a variant `use` names its enum's variant (rustUsePathItems' enum tail); - matchRustUseBoundName, ahead of the pre-filter, resolves a rename to the item its path names, and a call to a name a local binds (rust-scopes' bindings, nearer than any `use`) to nothing, unless the local is a `let` holding a function by its path (serde's `let test = test::<i8>;`); - resolveRustPathReference is rustWalkModules + rustModuleItems from the module the path is written in (rustPathTarget), with today's crate-root fallback; resolveRustModuleFile, collectRustUseBindings and rustItemPlacement are gone, and isBoundToOutOfRepoImport reads rustUsesOf's std `use`s; - `use`s are read with strings blanked (blankRustCode), so a string's "use crate::x;" binds nothing; - the crate graph reads a package's name by its section (axum-core writes `categories = [..]` first) and a `[lib]`/`[[bin]]` path of its own (ripgrep's `crates/core/main.rs`), which `crate::` then starts from; a file no manifest covers starts it from the nearest lib.rs or main.rs above it, as the directory walk did. Validation: - rust-use-call.test.ts (PR 2308's 15 cases), rust-use-scopes.test.ts, rust-crate-roots.test.ts and 5 new rust-module-path-call cases: on main's source 18 of 32 fail; all pass. Every rust-* suite passes. - A/B on main 01e0ac3, 7 repos, the same sites on the kernel and on wasm: tokio +2,434 sites (1,354 calls, 1,053 imports), 159 removed (152 locals called or passed as values, 6 `Result`s std's, 1 import through a path attribute module); ripgrep +248 / -149; axum +710 / -60; clap +730 / -112; serde +155 / -207 (192 calls to a closure named `test`); dioxus +2,950 / -272; gleam +1,573 / -359. Groups and samples inspected, every changed site's crate checked against the manifests' dependencies; express and flask byte-identical. - Cost (kernel, medians of 3 interleaved): tokio CPU 34.5 -> 33.0 s, peak RSS 991 -> 1,007 MB; gleam 32.5 -> 30.6 s, 1,022 -> 1,012 MB; dioxus 45.4 -> 45.4 s, 1,101 -> 1,107 MB. - Original PR #2308 by @danusha2345; its module-path part landed as PR #2955. Co-authored-by: danusha2345 <ewidusoc498@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thank you for this careful work, @danusha2345! The rest of your PR has landed in #3014, built on main's own |
Problem
A bare Rust call never looked at the
usethat brought its name in. Withimported()was linked toa::take, andaliased()to the unrelateda::consume, because name matching only sees the local name. The same happened with nested groups (use crate::{util::{take as selected}, a::take as other}),self::/super::imports anduseinside a function body.A call written as a module path had a related problem (this is #2259):
crate::util::take(3),super::util::take(3)orutil::take(3)mapped the module prefix to the right file, then took the first node namedtakein it. Methods were an accepted kind, soimpl Buf { fn take }above the freepub fn takecaught every such call. Paths that end in the caller's own file (self::take(),crate::this_module::f()from inside it) andsuper::Itemfrom a submodule file were not resolved by the module walk at all.Fix
usebindings (rust-use-bindings.ts), parsed once per file per resolution pass and dropped with the other import-resolver memos. It knows whichuseis nearest to a source position (block and module scopes, nested groups,as,self, raw identifiers), and hides comments, strings and char literals. Globs bind no name and are skipped.resolveRustImportedCallruns before name matching for a bare Rust call. When ausebinds the name, the call goes to the item that path names: the module's free function, a crate-root item, an enum variant through its own enum, or a tuple-struct constructor. A parameter, aletalready in effect, or a nested fn of the same name still shadows the import.implortraitbody, never a fn-local item. A module-level item wins; with none, an item of an inline module reached through the module's re-export (#[cfg(…)] mod imp { … }pluspub use imp::*;, a common platform pattern). Two module-level items of one name are#[cfg]variants, and the first one stands for both.super::to the parent module's file, ends in the caller's own file forself::, and goes through an inline module (mod support { pub mod panic; }) to a file-backed submodule, including in Cargo's standalonetests/,examples/andbenches/files.EXTRACTION_VERSIONbump: main already raised it to 28 for this release.This PR includes #2259 (its source change and its whole test file) and supersedes it.
Rebased onto main after #2354 and #2359. Neither overlaps: #2354 adds a
referencesedge from a writtenEnum::Variantpath to its enum, while this PR resolves barecallsthrough theusethat binds the name (includinguse crate::models::E::Good as Build; Build(1)), and #2359 only touches Axum/Actix route linking.Deliberately left as before
A binding this resolver cannot follow keeps today's resolution, so nothing that resolves now goes unresolved:
use my_core::partition::read_table;),pub usere-export in the module the path names,useinside an inline module (mod tests { use super::parse; }),The exceptions are an alias whose path cannot be followed (
use external::take as consume;) and an enum without that variant: name matching on the local name could only find an unrelated item, so those calls stay unresolved. Glob imports (use super::*) and#[path]modules are left alone; #1892 covers that case and should compose with this.Tests
__tests__/rust-use-bindings.test.ts(17 cases): the binding parser by itself. Groups, aliases,self, raw identifiers, nearest-scope lookup, inline modules, comments and string kinds,letshadowing, UTF-16 columns and CRLF, and inline-module items told apart fromimpl/trait/macro bodies. It also coversimpl Trait + use<'a>(precise capturing), which must not read as an import.__tests__/rust-use-call.test.ts(15 cases) and__tests__/rust-module-path-call.test.ts(5 cases, fix(rust): a module path names the module's free function, not a method #2259's 4 plus an associated-type decoy). Each one indexes a real temporary Cargo project into SQLite: imported vs same-named decoys, aliases, groups,self/super, block-local imports, shadowing, enum variants, crate-root and same-file imports, a workspace crate by package name,mod tests { use super::…; },#[cfg]variants, an inline module's re-export, Unicode columns, and refresh after edit + sync. 17 of the 20 call-level cases fail on current main.rust-*suite (including main'srust-enum-variant-referenceandrust-route-handler-line),resolution.test.tsandextraction.test.ts(13 files, 1023 tests) pass with the native kernel and withCODEGRAPH_KERNEL=0.Real repositories (Rust edges, main → this branch)
The tokio and GNSS numbers were measured before the rebase.
super::unowned(…)reaching the test helper through its inline module's re-export, twocrate::sync::watch::channel(…)from insidewatch.rs, and twosupport::mpsc_stream::…from an integration test. 4SendError(value)constructions inmpscmoved frombroadcast::SendErrorto thempsc::error::SendErrortheirusenames. There are also 42 newuseimport edges, all checked, and 0 lost edges.crate::gnsstime::utc_to_gps_time(…)-style calls moved from the same-namedGnssTimeConvertermethods to the module's free functions, plus 7 import edges. All correct.use super::…import edges in the larger one, all correct.🤖 Generated with Claude Code