@@ -45,6 +45,8 @@ impl std::fmt::Display for SkipReason {
4545pub struct SkippedFunction {
4646 pub name : String ,
4747 pub reason : SkipReason ,
48+ /// File path relative to the project root (e.g. "src/ffi.rs").
49+ pub path : PathBuf ,
4850}
4951
5052/// Result of target resolution: resolved targets plus any skipped functions.
@@ -68,6 +70,14 @@ pub fn resolve_targets(
6870) -> Result < ResolveResult , Error > {
6971 let rs_files = walk_rs_files ( src_dir) ?;
7072
73+ // Compute a relative path like "src/main.rs" from an absolute file path.
74+ let project_root = src_dir. parent ( ) . unwrap_or ( src_dir) ;
75+ let rel_path = |file : & Path | -> PathBuf {
76+ file. strip_prefix ( project_root)
77+ . unwrap_or ( file)
78+ . to_path_buf ( )
79+ } ;
80+
7181 let mut results: Vec < ResolvedTarget > = Vec :: new ( ) ;
7282 let mut skipped: Vec < SkippedFunction > = Vec :: new ( ) ;
7383
@@ -78,7 +88,7 @@ pub fn resolve_targets(
7888 path : file. clone ( ) ,
7989 source,
8090 } ) ?;
81- let ( all_fns, file_skipped) = extract_functions ( & source, file) ;
91+ let ( all_fns, file_skipped) = extract_functions ( & source, file, rel_path ( file ) ) ;
8292 if !all_fns. is_empty ( ) {
8393 merge_into ( & mut results, file, all_fns) ;
8494 }
@@ -95,7 +105,8 @@ pub fn resolve_targets(
95105 source,
96106 }
97107 } ) ?;
98- let ( all_fns, file_skipped) = extract_functions ( & source, file) ;
108+ let ( all_fns, file_skipped) =
109+ extract_functions ( & source, file, rel_path ( file) ) ;
99110 let matched: Vec < String > = all_fns
100111 . into_iter ( )
101112 . filter ( |name| {
@@ -138,7 +149,8 @@ pub fn resolve_targets(
138149 source,
139150 }
140151 } ) ?;
141- let ( all_fns, file_skipped) = extract_functions ( & source, file) ;
152+ let ( all_fns, file_skipped) =
153+ extract_functions ( & source, file, rel_path ( file) ) ;
142154 if !all_fns. is_empty ( ) {
143155 merge_into ( & mut results, file, all_fns) ;
144156 }
@@ -167,7 +179,8 @@ pub fn resolve_targets(
167179 source,
168180 }
169181 } ) ?;
170- let ( all_fns, file_skipped) = extract_functions ( & source, file) ;
182+ let ( all_fns, file_skipped) =
183+ extract_functions ( & source, file, rel_path ( file) ) ;
171184 if !all_fns. is_empty ( ) {
172185 merge_into ( & mut results, file, all_fns) ;
173186 }
@@ -251,7 +264,11 @@ fn walk_rs_files_inner(dir: &Path, out: &mut Vec<PathBuf>) -> Result<(), Error>
251264///
252265/// Functions annotated with `#[test]` and items inside `#[cfg(test)]` modules
253266/// are excluded -- they are not useful instrumentation targets.
254- fn extract_functions ( source : & str , path : & Path ) -> ( Vec < String > , Vec < SkippedFunction > ) {
267+ fn extract_functions (
268+ source : & str ,
269+ path : & Path ,
270+ rel_path : PathBuf ,
271+ ) -> ( Vec < String > , Vec < SkippedFunction > ) {
255272 let syntax = match syn:: parse_file ( source) {
256273 Ok ( f) => f,
257274 Err ( e) => {
@@ -260,7 +277,13 @@ fn extract_functions(source: &str, path: &Path) -> (Vec<String>, Vec<SkippedFunc
260277 }
261278 } ;
262279
263- let mut collector = FnCollector :: default ( ) ;
280+ let mut collector = FnCollector {
281+ functions : Vec :: new ( ) ,
282+ skipped : Vec :: new ( ) ,
283+ path : rel_path,
284+ current_impl : None ,
285+ current_trait : None ,
286+ } ;
264287 collector. visit_file ( & syntax) ;
265288 ( collector. functions , collector. skipped )
266289}
@@ -307,10 +330,11 @@ pub(crate) fn classify_skip(sig: &syn::Signature) -> Option<SkipReason> {
307330}
308331
309332/// AST visitor that collects function names from a parsed Rust file.
310- #[ derive( Default ) ]
311333struct FnCollector {
312334 functions : Vec < String > ,
313335 skipped : Vec < SkippedFunction > ,
336+ /// File path relative to the project root (e.g. "src/core.rs").
337+ path : PathBuf ,
314338 /// When inside an `impl` block, holds the type name (e.g. "Resolver").
315339 current_impl : Option < String > ,
316340 /// When inside a `trait` block, holds the trait name.
@@ -331,6 +355,7 @@ impl<'ast> Visit<'ast> for FnCollector {
331355 self . skipped . push ( SkippedFunction {
332356 name : node. sig . ident . to_string ( ) ,
333357 reason,
358+ path : self . path . clone ( ) ,
334359 } ) ;
335360 } else {
336361 self . functions . push ( node. sig . ident . to_string ( ) ) ;
@@ -358,6 +383,7 @@ impl<'ast> Visit<'ast> for FnCollector {
358383 self . skipped . push ( SkippedFunction {
359384 name : qualified,
360385 reason,
386+ path : self . path . clone ( ) ,
361387 } ) ;
362388 } else {
363389 self . functions . push ( qualified) ;
@@ -385,6 +411,7 @@ impl<'ast> Visit<'ast> for FnCollector {
385411 self . skipped . push ( SkippedFunction {
386412 name : qualified,
387413 reason,
414+ path : self . path . clone ( ) ,
388415 } ) ;
389416 } else {
390417 self . functions . push ( qualified) ;
@@ -449,7 +476,8 @@ fn build_suggestion_hint(specs: &[TargetSpec], rs_files: &[PathBuf]) -> String {
449476 let Ok ( source) = std:: fs:: read_to_string ( file) else {
450477 continue ;
451478 } ;
452- let ( fns, _skipped) = extract_functions ( & source, file) ;
479+ // path unused — only collecting function names for suggestions
480+ let ( fns, _skipped) = extract_functions ( & source, file, PathBuf :: new ( ) ) ;
453481 all_names. extend ( fns) ;
454482 }
455483 all_names. sort ( ) ;
@@ -993,6 +1021,11 @@ mod tests {
9931021 ) ;
9941022 assert_eq ! ( result. skipped[ 0 ] . name, "dangerous" ) ;
9951023 assert_eq ! ( result. skipped[ 0 ] . reason, SkipReason :: Unsafe ) ;
1024+ assert_eq ! (
1025+ result. skipped[ 0 ] . path,
1026+ Path :: new( "src/special_fns.rs" ) ,
1027+ "skipped function should have relative file path"
1028+ ) ;
9961029 }
9971030
9981031 #[ test]
@@ -1030,6 +1063,16 @@ mod tests {
10301063 "should report unsafe impl method as skipped: {skipped_names:?}"
10311064 ) ;
10321065
1066+ // All skipped functions from this file should have the correct relative path.
1067+ for s in & result. skipped {
1068+ assert_eq ! (
1069+ s. path,
1070+ Path :: new( "src/special_fns.rs" ) ,
1071+ "skipped fn '{}' should have relative path src/special_fns.rs" ,
1072+ s. name
1073+ ) ;
1074+ }
1075+
10331076 let all_fns: Vec < & str > = result
10341077 . targets
10351078 . iter ( )
0 commit comments