Skip to content

Commit 72976e0

Browse files
committed
feat(rust): update cache version to v114 and enhance AST extraction with Drop and Future method overrides
1 parent def55d2 commit 72976e0

6 files changed

Lines changed: 108 additions & 4 deletions

File tree

internal/cachecov/coverage_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ var versionCoverage = map[int][]string{
147147
113: {"TestGolden", "TestOwnsFile", "TestAST_ImplTraitForType_EmitsImplements"}, // Rust extractor added: symbols, implements post-pass, dependency/calls/route facts, cache participation
148148
114: {"TestGolden", "TestAST_CallInsideMacroArgument", "TestAST_MatchesMacroGuardCall", "TestAST_FunctionPassedAsCallbackArgument", "TestAST_OrdinaryArgument_NoPhantomReference", "TestAST_FunctionReferenceInStructField", "TestAST_FunctionReferenceTakenByAddress", "TestAST_SerdeDefaultAttribute_ReferencesFunction", "TestAST_SerdeSkipSerializingIfAttribute_ReferencesFunction", "TestAST_ClapValueParserAttribute_ReferencesFunction", "TestAST_LocalFunctionPassedAsCallbackWithinSameBody", "TestAST_FunctionReferenceNestedInsideMacroArgument"}, // Rust call-reference precision: macro-arg calls, function-as-value references (incl. nested inside a macro arg, e.g. vec![Box::new(f)]), serde/clap attribute refs, stale-owner-pointer fix
149149
115: {"TestAST_MergeStrategyAttribute_ReferencesScopedFunction", "TestExtract_UnprefixedUseOfSiblingFileSubmodule", "TestAST_ThiserrorAttributeCall_ReferencesFunction", "TestAST_MacroRulesBodyCall_EmitsFileRef", "TestAST_ItemLevelMacroInvocationCall_EmitsFileRef"}, // Rust: merge-attribute scoped-path fix, sibling-file submodule use-resolution, calls embedded in any attribute macro, owner-less macro content (macro_rules! bodies, item-level macro invocations) recorded via file_ref
150+
116: {"TestAST_DropImplTagsOverride", "TestAST_FutureImplTagsOverride", "TestAST_InherentDropLikeMethod_NotTaggedOverride", "TestAST_OtherTraitMethodNamedDrop_NotTaggedOverride"}, // Rust: Drop::drop/Future::poll tagged override (compiler/runtime-invoked, never called by name)
150151
}
151152

152153
func TestCacheVersionCoverage(t *testing.T) {

internal/engine/cache.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,11 @@ import (
448448
// template body, or an item-level macro invocation standing in for a whole function
449449
// like `ffi_fn! { fn foo() { ... } }`) are now recorded on a file_ref fact instead of
450450
// silently dropped for lack of an owner.
451-
const cacheVersion = "v115"
451+
// v116: Rust tags Drop::drop and Future::poll methods with the override prop — they are
452+
// invoked exclusively by the compiler (scope exit) or the async runtime (.await), never
453+
// by their literal method name, so the dead-code detector now excludes them like any
454+
// other framework-dispatched override (mirrors Kotlin/Swift's `override` handling).
455+
const cacheVersion = "v116"
452456

453457
// extractorCache holds per-extractor facts keyed by a content hash of the files
454458
// the extractor depends on. It is loaded from disk at the start of a snapshot and

internal/extractors/rustextractor/rust_ast.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ type astWalker struct {
8787
// emitEdge would otherwise silently drop for lack of an owner.
8888
fileRefIdx int
8989

90+
// implTrait: the trait name of the impl block currently being walked
91+
// ("" outside any impl, or for a plain inherent impl). Lets handleFunction
92+
// recognize compilerOrRuntimeInvokedMethods (Drop::drop, Future::poll).
93+
implTrait string
94+
9095
// modStack/typeStack hold the enclosing inline-`mod { }` and
9196
// impl/trait-block names, so a nested declaration's canonical name is
9297
// "<dir>.<mod1>.<mod2>...<Type>.<name>" — the same qualification scheme
@@ -443,8 +448,9 @@ func (w *astWalker) handleImpl(node *sitter.Node) {
443448
if typeName == "" {
444449
return
445450
}
451+
traitName := ""
446452
if traitNode := node.ChildByFieldName("trait"); traitNode != nil {
447-
if traitName := simpleTypeName(traitNode, w.src); traitName != "" {
453+
if traitName = simpleTypeName(traitNode, w.src); traitName != "" {
448454
w.impls = append(w.impls, implPair{
449455
typeName: w.dir + "." + w.qualify(typeName),
450456
traitName: traitName,
@@ -454,11 +460,14 @@ func (w *astWalker) handleImpl(node *sitter.Node) {
454460

455461
body := node.ChildByFieldName("body")
456462
w.pushType(typeName, collectFnNames(body, w.src))
463+
savedImplTrait := w.implTrait
464+
w.implTrait = traitName
457465
if body != nil {
458466
for i := uint(0); i < uint(body.ChildCount()); i++ {
459467
w.walkChild(body.Child(i))
460468
}
461469
}
470+
w.implTrait = savedImplTrait
462471
w.popType()
463472
}
464473

@@ -492,6 +501,9 @@ func (w *astWalker) handleFunction(node *sitter.Node) {
492501
f.Props["static"] = true
493502
}
494503
}
504+
if compilerInvokedTraitMethods[w.implTrait][name] {
505+
f.Props["override"] = true
506+
}
495507

496508
w.out = append(w.out, f)
497509
ownerIdx := len(w.out) - 1
@@ -1100,6 +1112,17 @@ var rustBuiltins = map[string]bool{
11001112
"format": true, "assert": true, "matches": true,
11011113
}
11021114

1115+
// compilerInvokedTraitMethods: trait methods invoked exclusively by the
1116+
// compiler (Drop::drop, at scope exit — calling it directly is a compile
1117+
// error) or the async runtime (Future::poll, driven by .await), never by
1118+
// their literal method name in ordinary code. Unlike fmt/eq/hash/clone/
1119+
// default — which sometimes genuinely are called by name — these have no
1120+
// legitimate direct-call precedent, so they're always safe to exclude.
1121+
var compilerInvokedTraitMethods = map[string]map[string]bool{
1122+
"Drop": {"drop": true},
1123+
"Future": {"poll": true},
1124+
}
1125+
11031126
// hasSelfParam reports whether a `parameters` node's first parameter is
11041127
// `self`/`&self`/`&mut self`, distinguishing a method from an associated
11051128
// (static) function declared in the same impl/trait block.

internal/extractors/rustextractor/rust_ast_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -899,3 +899,73 @@ ffi_fn! {
899899
t.Errorf("expected a KindFileRef -> pkg.with_cow, got %+v", refs)
900900
}
901901
}
902+
903+
func TestAST_DropImplTagsOverride(t *testing.T) {
904+
ff := extractAST(t, `
905+
pub struct Guard;
906+
impl Drop for Guard {
907+
fn drop(&mut self) {}
908+
}
909+
`)
910+
f, ok := findFact(ff, "pkg.Guard.drop")
911+
if !ok {
912+
t.Fatal("expected fact for pkg.Guard.drop")
913+
}
914+
if f.Props["override"] != true {
915+
t.Errorf("Drop::drop override = %v, want true", f.Props["override"])
916+
}
917+
}
918+
919+
func TestAST_FutureImplTagsOverride(t *testing.T) {
920+
ff := extractAST(t, `
921+
pub struct MyFuture;
922+
impl Future for MyFuture {
923+
type Output = ();
924+
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<()> {
925+
Poll::Ready(())
926+
}
927+
}
928+
`)
929+
f, ok := findFact(ff, "pkg.MyFuture.poll")
930+
if !ok {
931+
t.Fatal("expected fact for pkg.MyFuture.poll")
932+
}
933+
if f.Props["override"] != true {
934+
t.Errorf("Future::poll override = %v, want true", f.Props["override"])
935+
}
936+
}
937+
938+
func TestAST_InherentDropLikeMethod_NotTaggedOverride(t *testing.T) {
939+
ff := extractAST(t, `
940+
pub struct Cache;
941+
impl Cache {
942+
fn drop(&mut self) {}
943+
}
944+
`)
945+
f, ok := findFact(ff, "pkg.Cache.drop")
946+
if !ok {
947+
t.Fatal("expected fact for pkg.Cache.drop")
948+
}
949+
if f.Props["override"] == true {
950+
t.Errorf("inherent (non-trait) drop method should not be tagged override, got %v", f.Props["override"])
951+
}
952+
}
953+
954+
func TestAST_OtherTraitMethodNamedDrop_NotTaggedOverride(t *testing.T) {
955+
ff := extractAST(t, `
956+
trait Cleaner {
957+
fn drop(&mut self);
958+
}
959+
pub struct Sweeper;
960+
impl Cleaner for Sweeper {
961+
fn drop(&mut self) {}
962+
}
963+
`)
964+
f, ok := findFact(ff, "pkg.Sweeper.drop")
965+
if !ok {
966+
t.Fatal("expected fact for pkg.Sweeper.drop")
967+
}
968+
if f.Props["override"] == true {
969+
t.Errorf("a custom trait's drop method (not std::ops::Drop) should not be tagged override, got %v", f.Props["override"])
970+
}
971+
}

pkg/mcputil/generated_path_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ func TestIsGeneratedPath(t *testing.T) {
1717
"airflow/ui/openapi-gen/requests/client/utils.gen.ts",
1818
"a/b/params.gen.tsx", "svc/api.pb.go", "svc/api_pb2.py",
1919
"svc/api_pb2_grpc.py", "web/app.min.js", "web/app.min.css",
20+
// Bare "gen" segment (prost-build and similar Rust/Go protobuf output).
21+
"crates/proto-rust/src/gen/v1.public.core_types.rs",
22+
"crates/dbt-telemetry/src/gen/v1.events.rs",
23+
"pkg/gen/api.pb.rs",
2024
}
2125
for _, p := range generated {
2226
if !IsGeneratedPath(p) {
@@ -28,7 +32,9 @@ func TestIsGeneratedPath(t *testing.T) {
2832
"airflow/models/dagrun.py", "internal/perf/perf.go",
2933
"src/components/Graph/reactflowUtils.ts",
3034
// "generator" is not the exact segment "generated"; ".genesis.ts" is not ".gen.ts".
35+
// "general"/"genetics" are not the exact segment "gen".
3136
"pkg/generator/x.go", "src/genesis.ts", "app/vendored_helpers.py",
37+
"internal/general/config.go", "src/genetics/model.py",
3238
// Bare "env" is too common to treat as a venv; only .venv/venv/site-packages match.
3339
"app/env/settings.py", "src/environments/prod.py",
3440
}

pkg/mcputil/mcputil.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ func CapTokens(s string, maxTokens int, isJSON bool) string {
104104
func IsGeneratedPath(p string) bool {
105105
for _, part := range strings.Split(p, "/") {
106106
switch part {
107-
case "build", "Pods", "node_modules", "kspCaches", "generated",
107+
case "build", "Pods", "node_modules", "kspCaches", "generated", "gen",
108108
"__pycache__", "vendor", "openapi-gen", "__generated__", "third_party",
109109
// Python virtual environments and installed dependencies — never source.
110110
".venv", "venv", "site-packages":
@@ -121,7 +121,7 @@ func IsGeneratedPath(p string) bool {
121121
// protoc, `.min.js` is a minified bundle whose loop nesting is meaningless.
122122
for _, suf := range []string{
123123
".gen.ts", ".gen.tsx", ".gen.js", ".gen.go", ".generated.ts", ".generated.go",
124-
".min.js", ".min.css", ".pb.go", ".pb.gw.go", ".g.dart",
124+
".min.js", ".min.css", ".pb.go", ".pb.gw.go", ".pb.rs", ".g.dart",
125125
"_pb2.py", "_pb2_grpc.py",
126126
} {
127127
if strings.HasSuffix(base, suf) {

0 commit comments

Comments
 (0)