Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 95 additions & 2 deletions crates/csharp/src/world_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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<String> {
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<WorldKey> = 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:?}"
);
}
}
5 changes: 5 additions & 0 deletions tests/codegen/single-version-package/wit/deps/dep/root.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package my:dep@0.1.0;

interface a {
x: func();
}
5 changes: 5 additions & 0 deletions tests/codegen/single-version-package/wit/root.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package foo:bar;

world the-world {
import my:dep/a@0.1.0;
}
Loading