@@ -958,14 +958,14 @@ pub fn extract_simple_parameters(
958958
959959// ── Type-map helpers ───────────────────────────────────────────────────────
960960
961- /// Merge a type-map entry into the sink with highest-confidence-wins semantics,
962- /// matching `setTypeMapEntry` in `src/extractors/helpers.ts`.
961+ /// Append a raw type-map entry (name → type_name at the given confidence)
962+ /// without deduplication. Call [`dedup_type_map`] once after all entries
963+ /// have been pushed to collapse duplicates with highest-confidence-wins
964+ /// semantics.
963965///
964- /// If the key already exists with equal-or-higher confidence the new entry is
965- /// silently dropped; otherwise the old entry is replaced. This prevents
966- /// duplicate entries from accumulating when the same bare key is written
967- /// multiple times (e.g. two class expressions in one file that both define a
968- /// field with the same name).
966+ /// This is the write half of a two-phase approach: accumulate all entries
967+ /// cheaply (O(1) per write), then deduplicate once in O(n) at the end of
968+ /// extraction — avoiding the previous O(n²) scan-per-write pattern.
969969///
970970/// Mirrors `setTypeMapEntry` in `src/extractors/helpers.ts`.
971971pub fn set_type_map_entry (
@@ -978,14 +978,6 @@ pub fn set_type_map_entry(
978978 if name. is_empty ( ) {
979979 return ;
980980 }
981- // Scan for an existing entry with the same key.
982- if let Some ( existing) = symbols. type_map . iter_mut ( ) . find ( |e| e. name == name) {
983- if confidence > existing. confidence {
984- existing. type_name = type_name. into ( ) ;
985- existing. confidence = confidence;
986- }
987- return ;
988- }
989981 symbols. type_map . push ( TypeMapEntry {
990982 name,
991983 type_name : type_name. into ( ) ,
@@ -996,9 +988,8 @@ pub fn set_type_map_entry(
996988/// Record a parameter name → type binding in the type-map sink, using
997989/// the default confidence of `0.9` shared by every Rust extractor.
998990///
999- /// Delegates to [`set_type_map_entry`] so duplicate keys are deduplicated with
1000- /// highest-confidence-wins semantics (first-write-wins at the uniform 0.9
1001- /// confidence), matching `setTypeMapEntry` in `src/extractors/helpers.ts`.
991+ /// Delegates to [`set_type_map_entry`]. Call [`dedup_type_map`] once after
992+ /// all entries have been pushed to collapse duplicates.
1002993pub fn push_type_map_entry (
1003994 symbols : & mut FileSymbols ,
1004995 name : impl Into < String > ,
@@ -1007,6 +998,38 @@ pub fn push_type_map_entry(
1007998 set_type_map_entry ( symbols, name, type_name, 0.9 ) ;
1008999}
10091000
1001+ /// Deduplicate a type-map `Vec` in-place, keeping the highest-confidence
1002+ /// entry per key (first-write-wins on ties, matching `setTypeMapEntry` in
1003+ /// `src/extractors/helpers.ts`).
1004+ ///
1005+ /// This is the read half of the two-phase write-then-dedup approach used by
1006+ /// all extractors. Call it once at the end of each `extract()` implementation
1007+ /// after all tree-walk passes have completed, for both `symbols.type_map` and
1008+ /// `symbols.return_type_map` when applicable.
1009+ ///
1010+ /// Complexity: O(n) where n = number of entries (including duplicates).
1011+ pub fn dedup_type_map ( entries : & mut Vec < TypeMapEntry > ) {
1012+ if entries. len ( ) <= 1 {
1013+ return ;
1014+ }
1015+ use std:: collections:: HashMap ;
1016+ // Drain all entries into a HashMap, keeping the highest-confidence value
1017+ // per key. On ties the first entry seen wins (insertion-order preserved
1018+ // by the `if conf > prev` guard), matching the previous first-write-wins
1019+ // behaviour of the old per-entry linear scan.
1020+ let mut map: HashMap < String , TypeMapEntry > = HashMap :: with_capacity ( entries. len ( ) ) ;
1021+ for e in entries. drain ( ..) {
1022+ match map. get ( & e. name ) {
1023+ None => { map. insert ( e. name . clone ( ) , e) ; }
1024+ Some ( prev) if e. confidence > prev. confidence => {
1025+ map. insert ( e. name . clone ( ) , e) ;
1026+ }
1027+ _ => { }
1028+ }
1029+ }
1030+ entries. extend ( map. into_values ( ) ) ;
1031+ }
1032+
10101033/// C-family `declaration` / `parameter_declaration` type-map matcher.
10111034///
10121035/// The cpp / cuda / c extractors all emit verbatim copies of the same
@@ -1061,3 +1084,76 @@ where
10611084 _ => { }
10621085 }
10631086}
1087+
1088+ #[ cfg( test) ]
1089+ mod tests {
1090+ use super :: * ;
1091+ use crate :: types:: TypeMapEntry ;
1092+
1093+ fn entry ( name : & str , type_name : & str , confidence : f64 ) -> TypeMapEntry {
1094+ TypeMapEntry { name : name. to_string ( ) , type_name : type_name. to_string ( ) , confidence }
1095+ }
1096+
1097+ #[ test]
1098+ fn dedup_empty ( ) {
1099+ let mut v: Vec < TypeMapEntry > = vec ! [ ] ;
1100+ dedup_type_map ( & mut v) ;
1101+ assert ! ( v. is_empty( ) ) ;
1102+ }
1103+
1104+ #[ test]
1105+ fn dedup_single ( ) {
1106+ let mut v = vec ! [ entry( "x" , "Foo" , 0.9 ) ] ;
1107+ dedup_type_map ( & mut v) ;
1108+ assert_eq ! ( v. len( ) , 1 ) ;
1109+ assert_eq ! ( v[ 0 ] . name, "x" ) ;
1110+ }
1111+
1112+ #[ test]
1113+ fn dedup_no_duplicates ( ) {
1114+ let mut v = vec ! [ entry( "x" , "Foo" , 0.9 ) , entry( "y" , "Bar" , 0.9 ) ] ;
1115+ dedup_type_map ( & mut v) ;
1116+ assert_eq ! ( v. len( ) , 2 ) ;
1117+ }
1118+
1119+ #[ test]
1120+ fn dedup_keeps_highest_confidence ( ) {
1121+ // Lower confidence written first, higher written second — higher wins.
1122+ let mut v = vec ! [
1123+ entry( "x" , "Low" , 0.6 ) ,
1124+ entry( "x" , "High" , 0.9 ) ,
1125+ ] ;
1126+ dedup_type_map ( & mut v) ;
1127+ assert_eq ! ( v. len( ) , 1 ) ;
1128+ assert_eq ! ( v[ 0 ] . type_name, "High" ) ;
1129+ assert_eq ! ( v[ 0 ] . confidence, 0.9 ) ;
1130+ }
1131+
1132+ #[ test]
1133+ fn dedup_first_write_wins_on_equal_confidence ( ) {
1134+ // Two entries with equal confidence — first one wins.
1135+ let mut v = vec ! [
1136+ entry( "x" , "First" , 0.9 ) ,
1137+ entry( "x" , "Second" , 0.9 ) ,
1138+ ] ;
1139+ dedup_type_map ( & mut v) ;
1140+ assert_eq ! ( v. len( ) , 1 ) ;
1141+ assert_eq ! ( v[ 0 ] . type_name, "First" ) ;
1142+ }
1143+
1144+ #[ test]
1145+ fn dedup_mixed_keys ( ) {
1146+ let mut v = vec ! [
1147+ entry( "a" , "A1" , 0.6 ) ,
1148+ entry( "b" , "B1" , 0.9 ) ,
1149+ entry( "a" , "A2" , 0.9 ) , // higher — wins for "a"
1150+ entry( "b" , "B2" , 0.7 ) , // lower — loses for "b"
1151+ ] ;
1152+ dedup_type_map ( & mut v) ;
1153+ assert_eq ! ( v. len( ) , 2 ) ;
1154+ let a = v. iter ( ) . find ( |e| e. name == "a" ) . expect ( "a present" ) ;
1155+ let b = v. iter ( ) . find ( |e| e. name == "b" ) . expect ( "b present" ) ;
1156+ assert_eq ! ( a. type_name, "A2" ) ;
1157+ assert_eq ! ( b. type_name, "B1" ) ;
1158+ }
1159+ }
0 commit comments