@@ -64,36 +64,36 @@ pub fn find_colliding_import_names(imports: &[ExternDecl]) -> HashSet<String> {
6464/// `wasi:http/types` -> `WasiHttpTypes` / `wasi_http_types`.
6565pub fn import_member_names ( wn : & WitName , collisions : & HashSet < String > ) -> ( Ident , Ident ) {
6666 if collisions. contains ( wn. name ) {
67- // Preserve namespace component boundaries so `a:b-c /types` and
68- // `a-b: c/types` do not both flatten to `a_b_c_types `.
67+ // Preserve namespace component and hyphen boundaries so `a:bc /types`
68+ // and `a:b- c/types` do not both flatten to `a_bc_types `.
6969 let getter_prefix = if wn. namespaces . is_empty ( ) {
7070 wn. name . to_string ( )
7171 } else {
7272 wn. namespaces
7373 . iter ( )
74- . map ( |ns| ns . replace ( '-' , "" ) )
74+ . map ( |ns| namespace_component_to_getter_part ( ns ) )
7575 . collect :: < Vec < _ > > ( )
7676 . join ( "_" )
7777 } ;
7878 let mut qualified_getter = format ! ( "{}-{}" , getter_prefix, wn. name) ;
7979
8080 // Use the same boundary-preserving approach for type names.
8181 let type_prefix: String = if wn. namespaces . is_empty ( ) {
82- component_first_camel ( wn. name )
82+ collapsed_kebab_to_camel ( wn. name )
8383 } else {
8484 wn. namespaces
8585 . iter ( )
86- . map ( |ns| component_first_camel ( ns) )
86+ . map ( |ns| namespace_component_to_type_part ( ns) )
8787 . collect ( )
8888 } ;
89- let mut type_name = format ! ( "{}{}" , type_prefix, component_first_camel ( wn. name) ) ;
89+ let mut type_name = format ! ( "{}{}" , type_prefix, collapsed_kebab_to_camel ( wn. name) ) ;
9090
9191 if !wn. _version . is_empty ( ) {
9292 let v = version_to_kebab ( & wn. _version ) ;
9393 qualified_getter. push_str ( "-v" ) ;
9494 qualified_getter. push_str ( & v) ;
9595 type_name. push ( 'V' ) ;
96- type_name. push_str ( & component_first_camel ( & v) ) ;
96+ type_name. push_str ( & collapsed_kebab_to_camel ( & v) ) ;
9797 }
9898 (
9999 format_ident ! ( "{}" , type_name) ,
@@ -104,12 +104,28 @@ pub fn import_member_names(wn: &WitName, collisions: &HashSet<String>) -> (Ident
104104 }
105105}
106106
107- /// Capitalize only the first letter of a kebab component, removing hyphens
108- /// without capitalizing subsequent sub-words. This preserves namespace-boundary
109- /// information when concatenating multiple components into a type identifier:
110- /// "b-c" → "Bc" and "a-b" → "Ab" remain distinguishable when prefixed,
111- /// whereas full camel-casing gives "BC" and "AB" which collapse on concatenation.
112- fn component_first_camel ( s : & str ) -> String {
107+ fn namespace_component_to_getter_part ( s : & str ) -> String {
108+ s. replace ( '-' , "_dash_" )
109+ }
110+
111+ fn namespace_component_to_type_part ( s : & str ) -> String {
112+ let mut result = String :: new ( ) ;
113+ let mut capitalize_next = true ;
114+ for c in s. chars ( ) {
115+ if c == '-' {
116+ result. push_str ( "Dash" ) ;
117+ capitalize_next = true ;
118+ } else if capitalize_next {
119+ result. extend ( c. to_uppercase ( ) ) ;
120+ capitalize_next = false ;
121+ } else {
122+ result. push ( c) ;
123+ }
124+ }
125+ result
126+ }
127+
128+ fn collapsed_kebab_to_camel ( s : & str ) -> String {
113129 let mut result = String :: new ( ) ;
114130 let mut chars = s. chars ( ) ;
115131 if let Some ( first) = chars. next ( ) {
@@ -1139,9 +1155,27 @@ mod tests {
11391155 let ( ty1, getter1) = import_member_names ( & wn1, & collisions) ;
11401156 let ( ty2, getter2) = import_member_names ( & wn2, & collisions) ;
11411157
1142- assert_eq ! ( ty1. to_string( ) , "ABcTypes" ) ;
1143- assert_eq ! ( getter1. to_string( ) , "r#a_bc_types" ) ;
1144- assert_eq ! ( ty2. to_string( ) , "AbCTypes" ) ;
1145- assert_eq ! ( getter2. to_string( ) , "r#ab_c_types" ) ;
1158+ assert_eq ! ( ty1. to_string( ) , "ABDashCTypes" ) ;
1159+ assert_eq ! ( getter1. to_string( ) , "r#a_b_dash_c_types" ) ;
1160+ assert_eq ! ( ty2. to_string( ) , "ADashBCTypes" ) ;
1161+ assert_eq ! ( getter2. to_string( ) , "r#a_dash_b_c_types" ) ;
1162+ }
1163+
1164+ #[ test]
1165+ fn plain_and_hyphenated_namespace_components_produce_distinct_type_names ( ) {
1166+ let collisions = find_colliding_import_names ( & [
1167+ instance_decl ( "a:bc/types" ) ,
1168+ instance_decl ( "a:b-c/types" ) ,
1169+ ] ) ;
1170+
1171+ let wn_plain = split_wit_name ( "a:bc/types" ) ;
1172+ let wn_hyphenated = split_wit_name ( "a:b-c/types" ) ;
1173+ let ( ty_plain, getter_plain) = import_member_names ( & wn_plain, & collisions) ;
1174+ let ( ty_hyphenated, getter_hyphenated) = import_member_names ( & wn_hyphenated, & collisions) ;
1175+
1176+ assert_eq ! ( ty_plain. to_string( ) , "ABcTypes" ) ;
1177+ assert_eq ! ( getter_plain. to_string( ) , "r#a_bc_types" ) ;
1178+ assert_eq ! ( ty_hyphenated. to_string( ) , "ABDashCTypes" ) ;
1179+ assert_eq ! ( getter_hyphenated. to_string( ) , "r#a_b_dash_c_types" ) ;
11461180 }
11471181}
0 commit comments