diff --git a/crates/csharp/src/world_generator.rs b/crates/csharp/src/world_generator.rs index 685c69076..fb7d2450b 100644 --- a/crates/csharp/src/world_generator.rs +++ b/crates/csharp/src/world_generator.rs @@ -1049,8 +1049,18 @@ fn interface_name( ); if let Some(version) = &name.version { - let v = version.to_string().replace(['.', '-', '+'], "_"); - ns = format!("{}v{}.", ns, &v); + // Only include the version segment when the same package name exists at + // multiple versions in this Resolve; omit it when unambiguous. + // Mirrors the equivalent guard in the C generator (crates/c/src/lib.rs). + let pkg_has_multiple_versions = resolve.packages.iter().any(|(_, p)| { + p.name.namespace == name.namespace + && p.name.name == name.name + && p.name.version != name.version + }); + if pkg_has_multiple_versions { + let v = version.to_string().replace(['.', '-', '+'], "_"); + ns = format!("{}v{}.", ns, &v); + } } ns } @@ -1106,3 +1116,86 @@ fn by_resource<'a>( } by_resource } + +#[cfg(test)] +mod tests { + use super::*; + + /// Build a `Resolve` from inline WIT and return the generated C# namespace + /// for every imported interface in `world`. + fn imported_interface_namespaces(wit: &[(&str, &str)], world: &str) -> Vec { + let mut resolve = Resolve::default(); + for (path, contents) in wit { + resolve.push_str(path, contents).unwrap(); + } + let (world_id, _) = resolve + .worlds + .iter() + .find(|(_, w)| w.name == world) + .unwrap(); + let keys: Vec = resolve.worlds[world_id] + .imports + .keys() + .filter(|k| matches!(k, WorldKey::Interface(_))) + .cloned() + .collect(); + let mut csharp = CSharp::default(); + keys.iter() + .map(|k| interface_name(&mut csharp, &resolve, k, Direction::Import)) + .collect() + } + + #[test] + fn version_segment_omitted_when_package_is_unambiguous() { + let names = imported_interface_namespaces( + &[ + ( + "dep.wit", + "package my:dep@0.1.0;\ninterface a { x: func(); }", + ), + ( + "root.wit", + "package foo:bar;\nworld the-world { import my:dep/a@0.1.0; }", + ), + ], + "the-world", + ); + assert!(!names.is_empty()); + for n in &names { + assert!(n.contains("my.dep"), "expected package segment: {n}"); + assert!( + !n.contains("v0_1_0"), + "version segment should be omitted: {n}" + ); + } + } + + #[test] + fn version_segment_kept_when_multiple_versions_coexist() { + let names = imported_interface_namespaces( + &[ + ( + "v1.wit", + "package my:dep@0.1.0;\ninterface a { x: func(); }", + ), + ( + "v2.wit", + "package my:dep@0.2.0;\ninterface a { x: func(); }", + ), + ( + "root.wit", + "package foo:bar;\nworld the-world { import my:dep/a@0.1.0; import my:dep/a@0.2.0; }", + ), + ], + "the-world", + ); + assert!( + names.iter().any(|n| n.contains("v0_1_0")), + "expected a v0_1_0 segment: {names:?}" + ); + assert!( + names.iter().any(|n| n.contains("v0_2_0")), + "expected a v0_2_0 segment: {names:?}" + ); + } +} diff --git a/tests/codegen/single-version-package/wit/deps/dep/root.wit b/tests/codegen/single-version-package/wit/deps/dep/root.wit new file mode 100644 index 000000000..cdda0a38b --- /dev/null +++ b/tests/codegen/single-version-package/wit/deps/dep/root.wit @@ -0,0 +1,5 @@ +package my:dep@0.1.0; + +interface a { + x: func(); +} diff --git a/tests/codegen/single-version-package/wit/root.wit b/tests/codegen/single-version-package/wit/root.wit new file mode 100644 index 000000000..82df7fd45 --- /dev/null +++ b/tests/codegen/single-version-package/wit/root.wit @@ -0,0 +1,5 @@ +package foo:bar; + +world the-world { + import my:dep/a@0.1.0; +}