diff --git a/crates/csharp/src/csproj.rs b/crates/csharp/src/csproj.rs
index 76b8c5d57..9be60a0f1 100644
--- a/crates/csharp/src/csproj.rs
+++ b/crates/csharp/src/csproj.rs
@@ -12,6 +12,7 @@ pub struct CSProjectLLVMBuilder {
clean_targets: bool,
world_name: String,
binary: bool,
+ skip_wit_component: bool,
}
pub struct CSProjectMonoBuilder {
@@ -31,6 +32,7 @@ impl CSProject {
clean_targets: false,
world_name: world_name.to_string(),
binary: false,
+ skip_wit_component: false,
}
}
@@ -69,6 +71,14 @@ impl CSProjectLLVMBuilder {
"Library"
};
+ let component_type_linker_arg = if self.skip_wit_component {
+ "".to_string()
+ } else {
+ format!(
+ ""
+ )
+ };
+
let mut csproj = format!(
"
@@ -94,7 +104,7 @@ impl CSProjectLLVMBuilder {
-
+ {component_type_linker_arg}
"
);
@@ -178,6 +188,10 @@ impl CSProjectLLVMBuilder {
self.binary = true;
}
+ pub fn skip_wit_component(&mut self) {
+ self.skip_wit_component = true;
+ }
+
pub fn clean(&mut self) -> &mut Self {
self.clean_targets = true;
diff --git a/crates/test/src/csharp.rs b/crates/test/src/csharp.rs
index 8e11ca7f7..53032296e 100644
--- a/crates/test/src/csharp.rs
+++ b/crates/test/src/csharp.rs
@@ -1,6 +1,7 @@
use crate::{Compile, LanguageMethods, Runner, Verify};
use anyhow::Result;
use heck::*;
+use serde::Deserialize;
use std::env;
use std::fs;
use std::path::Path;
@@ -8,6 +9,12 @@ use std::process::Command;
pub struct Csharp;
+#[derive(Default, Deserialize)]
+#[serde(deny_unknown_fields, rename_all = "kebab-case")]
+struct LangConfig {
+ skip_wit_component: bool,
+}
+
fn dotnet() -> Command {
let dotnet_cmd = match env::var("DOTNET_ROOT") {
Ok(val) => Path::new(&val).join("dotnet"),
@@ -59,6 +66,7 @@ impl LanguageMethods for Csharp {
}
fn compile(&self, runner: &Runner, compile: &Compile<'_>) -> Result<()> {
+ let config = compile.component.deserialize_lang_config::()?;
let world_name = &compile.component.bindgen.world;
let path = &compile.component.path;
let test_dir = &compile.bindings_dir;
@@ -75,6 +83,9 @@ impl LanguageMethods for Csharp {
let mut csproj =
wit_bindgen_csharp::CSProject::new(test_dir.to_path_buf(), &assembly_name, world_name);
csproj.aot();
+ if config.skip_wit_component {
+ csproj.skip_wit_component();
+ }
csproj.generate()?;
let mut cmd = dotnet();
@@ -109,7 +120,11 @@ impl LanguageMethods for Csharp {
runner.run_command(&mut cmd)?;
- fs::copy(&wasm_filename, &compile.output)?;
+ if config.skip_wit_component {
+ runner.convert_p1_to_component_appending_type_metadata(&wasm_filename, compile)?;
+ } else {
+ fs::copy(&wasm_filename, &compile.output)?;
+ }
Ok(())
}
diff --git a/crates/test/src/lib.rs b/crates/test/src/lib.rs
index 615fb9ce0..e65f91606 100644
--- a/crates/test/src/lib.rs
+++ b/crates/test/src/lib.rs
@@ -1081,6 +1081,30 @@ status: {}",
///
/// Stores the output at `compile.output`.
fn convert_p1_to_component(&self, p1: &Path, compile: &Compile<'_>) -> Result<()> {
+ self.convert_p1_to_component_impl(p1, compile, false)
+ }
+
+ /// Converts the WASIp1 module at `p1` to a component, appending component
+ /// type metadata from the test WIT even if existing metadata is present.
+ ///
+ /// Some toolchains emit incomplete or stale component type metadata even
+ /// when asked to skip componentization, while still needing their own
+ /// metadata for WASI imports. Use this when the test harness must add
+ /// guest-world metadata without discarding toolchain metadata.
+ fn convert_p1_to_component_appending_type_metadata(
+ &self,
+ p1: &Path,
+ compile: &Compile<'_>,
+ ) -> Result<()> {
+ self.convert_p1_to_component_impl(p1, compile, true)
+ }
+
+ fn convert_p1_to_component_impl(
+ &self,
+ p1: &Path,
+ compile: &Compile<'_>,
+ append_component_type: bool,
+ ) -> Result<()> {
let mut resolve = wit_parser::Resolve::default();
let (pkg, _) = resolve
.push_path(&compile.component.bindgen.wit_path)
@@ -1088,7 +1112,7 @@ status: {}",
let world = resolve.select_world(&[pkg], Some(&compile.component.bindgen.world))?;
let mut module = fs::read(&p1).context("failed to read wasm file")?;
- if !has_component_type_sections(&module) {
+ if append_component_type || !has_component_type_sections(&module) {
let encoded =
wit_component::metadata::encode(&resolve, world, StringEncoding::UTF8, None)?;
let section = wasm_encoder::CustomSection {
diff --git a/tests/runtime/map/test.cs b/tests/runtime/map/test.cs
new file mode 100644
index 000000000..e4e63bf5a
--- /dev/null
+++ b/tests/runtime/map/test.cs
@@ -0,0 +1,123 @@
+//@ wasmtime-flags = '-Wcomponent-model-map'
+//@ [lang]
+//@ skip-wit-component = true
+
+using System.Diagnostics;
+using System.Text;
+
+namespace TestWorld.wit.Exports.test.maps
+{
+ public class ToTestExportsImpl : IToTestExports
+ {
+ public static Dictionary NamedRoundtrip(Dictionary a)
+ {
+ Debug.Assert(a.Count == 2);
+ Debug.Assert(a[1] == "uno");
+ Debug.Assert(a[2] == "two");
+
+ return a.ToDictionary(entry => entry.Value, entry => entry.Key);
+ }
+
+ public static Dictionary BytesRoundtrip(Dictionary a)
+ {
+ Debug.Assert(a.Count == 2);
+ Debug.Assert(a["hello"].SequenceEqual(Encoding.UTF8.GetBytes("world")));
+ Debug.Assert(a["bin"].SequenceEqual(new byte[] { 0, 1, 2 }));
+
+ return a;
+ }
+
+ public static Dictionary EmptyRoundtrip(Dictionary a)
+ {
+ Debug.Assert(a.Count == 0);
+ return a;
+ }
+
+ public static Dictionary OptionRoundtrip(Dictionary a)
+ {
+ Debug.Assert(a.Count == 2);
+ Debug.Assert(a["some"] == 42);
+ Debug.Assert(a["none"] == null);
+
+ return a;
+ }
+
+ public static IToTestExports.LabeledEntry RecordRoundtrip(IToTestExports.LabeledEntry a)
+ {
+ Debug.Assert(a.label == "test-label");
+ Debug.Assert(a.values.Count == 2);
+ Debug.Assert(a.values[10] == "ten");
+ Debug.Assert(a.values[20] == "twenty");
+
+ return a;
+ }
+
+ public static Dictionary InlineRoundtrip(Dictionary a)
+ {
+ return a.ToDictionary(entry => entry.Value, entry => entry.Key);
+ }
+
+ public static Dictionary LargeRoundtrip(Dictionary a)
+ {
+ Debug.Assert(a.Count == 100);
+ return a;
+ }
+
+ public static (Dictionary, Dictionary) MultiParamRoundtrip(
+ Dictionary a,
+ Dictionary b)
+ {
+ Debug.Assert(a.Count == 2);
+ Debug.Assert(b.Count == 1);
+
+ return (a.ToDictionary(entry => entry.Value, entry => entry.Key), b);
+ }
+
+ public static Dictionary> NestedRoundtrip(
+ Dictionary> a)
+ {
+ Debug.Assert(a.Count == 2);
+ Debug.Assert(a["group-a"].Count == 2);
+ Debug.Assert(a["group-a"][1] == "one");
+ Debug.Assert(a["group-a"][2] == "two");
+ Debug.Assert(a["group-b"].Count == 1);
+ Debug.Assert(a["group-b"][10] == "ten");
+
+ return a;
+ }
+
+ public static IToTestExports.MapOrString VariantRoundtrip(IToTestExports.MapOrString a)
+ {
+ return a;
+ }
+
+ public static Dictionary ResultRoundtrip(
+ Result, string> a)
+ {
+ if (a.IsErr)
+ {
+ throw new WitException(a.AsErr, 0);
+ }
+
+ return a.AsOk;
+ }
+
+ public static (Dictionary, ulong) TupleRoundtrip(
+ (Dictionary, ulong) a)
+ {
+ Debug.Assert(a.Item1.Count == 1);
+ Debug.Assert(a.Item1[7] == "seven");
+ Debug.Assert(a.Item2 == 42);
+
+ return a;
+ }
+
+ public static Dictionary SingleEntryRoundtrip(Dictionary a)
+ {
+ Debug.Assert(a.Count == 1);
+ Debug.Assert(a[99] == "ninety-nine");
+
+ return a;
+ }
+ }
+}