@@ -15,7 +15,7 @@ limitations under the License.
1515 */
1616
1717//! A bunch of utilities used by the actual code emit functions
18- use std:: collections:: { BTreeMap , BTreeSet , HashSet , VecDeque } ;
18+ use std:: collections:: { BTreeMap , BTreeSet , HashMap , HashSet , VecDeque } ;
1919use std:: vec:: Vec ;
2020
2121use proc_macro2:: TokenStream ;
@@ -26,35 +26,95 @@ use crate::etypes::{
2626 BoundedTyvar , Defined , ExternDecl , ExternDesc , Handleable , ImportExport , TypeBound , Tyvar ,
2727} ;
2828
29+ /// Import interface names that need generated trait member disambiguation.
30+ #[ derive( Clone , Debug , Default ) ]
31+ pub struct ImportNameCollisions {
32+ short_names : HashSet < String > ,
33+ qualified_names : HashSet < ( Vec < String > , String ) > ,
34+ }
35+ impl ImportNameCollisions {
36+ pub fn is_empty ( & self ) -> bool {
37+ self . short_names . is_empty ( ) && self . qualified_names . is_empty ( )
38+ }
39+ pub fn len ( & self ) -> usize {
40+ self . short_names . len ( )
41+ }
42+ pub fn contains ( & self , name : & str ) -> bool {
43+ self . short_names . contains ( name)
44+ }
45+ fn qualified_contains ( & self , wn : & WitName ) -> bool {
46+ self . qualified_names . contains ( & qualified_import_key ( wn) )
47+ }
48+ }
49+
50+ fn qualified_import_key ( wn : & WitName ) -> ( Vec < String > , String ) {
51+ (
52+ wn. namespaces . iter ( ) . map ( |ns| ns. to_string ( ) ) . collect ( ) ,
53+ wn. name . to_string ( ) ,
54+ )
55+ }
56+
57+ fn version_to_kebab ( version : & [ & str ] ) -> String {
58+ version
59+ . join ( "-" )
60+ . chars ( )
61+ . map ( |c| {
62+ if c. is_ascii_alphanumeric ( ) {
63+ c. to_ascii_lowercase ( )
64+ } else {
65+ '-'
66+ }
67+ } )
68+ . collect ( )
69+ }
70+
2971/// Scan a list of import extern decls for interface name collisions.
30- /// Returns the set of interface names that appear more than once.
31- pub fn find_colliding_import_names ( imports : & [ ExternDecl ] ) -> HashSet < String > {
32- let mut counts = std:: collections:: HashMap :: < String , usize > :: new ( ) ;
72+ /// Returns import names that need disambiguating, tracking when the
73+ /// namespace-qualified name still collides and must include the version.
74+ pub fn find_colliding_import_names ( imports : & [ ExternDecl ] ) -> ImportNameCollisions {
75+ let mut short_counts = HashMap :: < String , usize > :: new ( ) ;
76+ let mut qualified_counts = HashMap :: < ( Vec < String > , String ) , usize > :: new ( ) ;
3377 for ed in imports {
3478 if let ExternDesc :: Instance ( _) = & ed. desc {
3579 let wn = split_wit_name ( ed. kebab_name ) ;
36- * counts. entry ( wn. name . to_string ( ) ) . or_default ( ) += 1 ;
80+ * short_counts. entry ( wn. name . to_string ( ) ) . or_default ( ) += 1 ;
81+ * qualified_counts
82+ . entry ( qualified_import_key ( & wn) )
83+ . or_default ( ) += 1 ;
3784 }
3885 }
39- counts
86+ let short_names = short_counts
4087 . into_iter ( )
4188 . filter ( |( _, c) | * c > 1 )
4289 . map ( |( n, _) | n)
43- . collect ( )
90+ . collect ( ) ;
91+ let qualified_names = qualified_counts
92+ . into_iter ( )
93+ . filter ( |( _, c) | * c > 1 )
94+ . map ( |( n, _) | n)
95+ . collect ( ) ;
96+ ImportNameCollisions {
97+ short_names,
98+ qualified_names,
99+ }
44100}
45101
46102/// Get the disambiguated type and getter names for an import instance.
47103/// If the interface name collides with another import, prepend the full
48104/// kebab-joined namespace path to disambiguate
49105/// (e.g. "types" from "wasi:http" becomes "WasiHttpTypes"/"wasi_http_types").
50- pub fn import_member_names ( wn : & WitName , collisions : & HashSet < String > ) -> ( Ident , Ident ) {
106+ pub fn import_member_names ( wn : & WitName , collisions : & ImportNameCollisions ) -> ( Ident , Ident ) {
51107 if collisions. contains ( wn. name ) {
52108 let prefix = if wn. namespaces . is_empty ( ) {
53109 wn. name . to_string ( )
54110 } else {
55111 wn. namespaces . join ( "-" )
56112 } ;
57- let qualified = format ! ( "{}-{}" , prefix, wn. name) ;
113+ let mut qualified = format ! ( "{}-{}" , prefix, wn. name) ;
114+ if collisions. qualified_contains ( wn) && !wn. _version . is_empty ( ) {
115+ qualified. push_str ( "-v" ) ;
116+ qualified. push_str ( & version_to_kebab ( & wn. _version ) ) ;
117+ }
58118 ( kebab_to_type ( & qualified) , kebab_to_getter ( & qualified) )
59119 } else {
60120 ( kebab_to_type ( wn. name ) , kebab_to_getter ( wn. name ) )
@@ -325,7 +385,7 @@ pub struct State<'a, 'b> {
325385 /// (e.g. "types" appears in both wasi:filesystem/types and wasi:http/types).
326386 /// When a name is in this set, the parent namespace is prepended to
327387 /// disambiguate the trait member name.
328- pub colliding_import_names : HashSet < String > ,
388+ pub colliding_import_names : ImportNameCollisions ,
329389}
330390
331391/// Create a State with all of its &mut references pointing to
@@ -378,7 +438,7 @@ impl<'a, 'b> State<'a, 'b> {
378438 is_guest,
379439 is_wasmtime_guest,
380440 is_export : false ,
381- colliding_import_names : HashSet :: new ( ) ,
441+ colliding_import_names : ImportNameCollisions :: default ( ) ,
382442 }
383443 }
384444 pub fn clone < ' c > ( & ' c mut self ) -> State < ' c , ' b > {
@@ -968,7 +1028,7 @@ mod tests {
9681028 #[ test]
9691029 fn no_collision_uses_short_name ( ) {
9701030 let wn = split_wit_name ( "wasi:http/types" ) ;
971- let collisions = HashSet :: new ( ) ;
1031+ let collisions = ImportNameCollisions :: default ( ) ;
9721032 let ( ty, getter) = import_member_names ( & wn, & collisions) ;
9731033 assert_eq ! ( ty. to_string( ) , "Types" ) ;
9741034 assert_eq ! ( getter. to_string( ) , "r#types" ) ;
@@ -977,17 +1037,21 @@ mod tests {
9771037 #[ test]
9781038 fn collision_prepends_parent_namespace ( ) {
9791039 let wn = split_wit_name ( "wasi:http/types" ) ;
980- let mut collisions = HashSet :: new ( ) ;
981- collisions. insert ( "types" . to_string ( ) ) ;
1040+ let collisions = find_colliding_import_names ( & [
1041+ instance_decl ( "wasi:http/types" ) ,
1042+ instance_decl ( "wasi:filesystem/types" ) ,
1043+ ] ) ;
9821044 let ( ty, getter) = import_member_names ( & wn, & collisions) ;
9831045 assert_eq ! ( ty. to_string( ) , "WasiHttpTypes" ) ;
9841046 assert_eq ! ( getter. to_string( ) , "r#wasi_http_types" ) ;
9851047 }
9861048
9871049 #[ test]
9881050 fn collision_different_parents_produce_different_names ( ) {
989- let mut collisions = HashSet :: new ( ) ;
990- collisions. insert ( "types" . to_string ( ) ) ;
1051+ let collisions = find_colliding_import_names ( & [
1052+ instance_decl ( "wasi:http/types" ) ,
1053+ instance_decl ( "wasi:filesystem/types" ) ,
1054+ ] ) ;
9911055
9921056 let wn_http = split_wit_name ( "wasi:http/types" ) ;
9931057 let ( ty_http, getter_http) = import_member_names ( & wn_http, & collisions) ;
@@ -1003,8 +1067,10 @@ mod tests {
10031067
10041068 #[ test]
10051069 fn collision_same_parent_different_package_produces_different_names ( ) {
1006- let mut collisions = HashSet :: new ( ) ;
1007- collisions. insert ( "types" . to_string ( ) ) ;
1070+ let collisions = find_colliding_import_names ( & [
1071+ instance_decl ( "a:pkg/types" ) ,
1072+ instance_decl ( "b:pkg/types" ) ,
1073+ ] ) ;
10081074
10091075 let wn_a = split_wit_name ( "a:pkg/types" ) ;
10101076 let ( ty_a, _) = import_member_names ( & wn_a, & collisions) ;
@@ -1020,11 +1086,44 @@ mod tests {
10201086 #[ test]
10211087 fn collision_simple_name_uses_name_as_parent ( ) {
10221088 let wn = split_wit_name ( "types" ) ;
1023- let mut collisions = HashSet :: new ( ) ;
1024- collisions . insert ( "types" . to_string ( ) ) ;
1089+ let collisions =
1090+ find_colliding_import_names ( & [ instance_decl ( "types" ) , instance_decl ( "pkg:types" ) ] ) ;
10251091 let ( ty, getter) = import_member_names ( & wn, & collisions) ;
10261092 // When there are no namespaces, the name itself is used as prefix
10271093 assert_eq ! ( ty. to_string( ) , "TypesTypes" ) ;
10281094 assert_eq ! ( getter. to_string( ) , "r#types_types" ) ;
10291095 }
1096+
1097+ #[ test]
1098+ fn versioned_collision_adds_version_after_namespace ( ) {
1099+ let collisions = find_colliding_import_names ( & [
1100+ instance_decl ( "a:pkg/types@1.0.0" ) ,
1101+ instance_decl ( "a:pkg/types@2.0.0" ) ,
1102+ ] ) ;
1103+
1104+ let wn_v1 = split_wit_name ( "a:pkg/types@1.0.0" ) ;
1105+ let ( ty_v1, getter_v1) = import_member_names ( & wn_v1, & collisions) ;
1106+
1107+ let wn_v2 = split_wit_name ( "a:pkg/types@2.0.0" ) ;
1108+ let ( ty_v2, getter_v2) = import_member_names ( & wn_v2, & collisions) ;
1109+
1110+ assert_eq ! ( ty_v1. to_string( ) , "APkgTypesV100" ) ;
1111+ assert_eq ! ( ty_v2. to_string( ) , "APkgTypesV200" ) ;
1112+ assert_eq ! ( getter_v1. to_string( ) , "r#a_pkg_types_v1_0_0" ) ;
1113+ assert_eq ! ( getter_v2. to_string( ) , "r#a_pkg_types_v2_0_0" ) ;
1114+ }
1115+
1116+ #[ test]
1117+ fn version_is_not_added_when_namespace_disambiguates ( ) {
1118+ let collisions = find_colliding_import_names ( & [
1119+ instance_decl ( "a:pkg/types@1.0.0" ) ,
1120+ instance_decl ( "b:pkg/types@1.0.0" ) ,
1121+ ] ) ;
1122+
1123+ let wn = split_wit_name ( "a:pkg/types@1.0.0" ) ;
1124+ let ( ty, getter) = import_member_names ( & wn, & collisions) ;
1125+
1126+ assert_eq ! ( ty. to_string( ) , "APkgTypes" ) ;
1127+ assert_eq ! ( getter. to_string( ) , "r#a_pkg_types" ) ;
1128+ }
10301129}
0 commit comments