use crate::csharp_ident::ToCSharpIdent;
use crate::function::FunctionBindgen;
use crate::function::ResourceInfo;
use crate::world_generator::CSharp;
use heck::ToLowerCamelCase;
use heck::{ToShoutySnakeCase, ToUpperCamelCase};
use std::collections::HashMap;
use std::collections::HashSet;
use std::fmt::Write;
use std::mem;
use std::ops::Deref;
use wit_bindgen_core::abi::LiftLower;
use wit_bindgen_core::{
Direction, InterfaceGenerator as CoreInterfaceGenerator, abi, uwrite, uwriteln,
};
use wit_parser::Param;
use wit_parser::abi::AbiVariant;
use wit_parser::{
Docs, Enum, Flags, FlagsRepr, Function, FunctionKind, Handle, Int, InterfaceId, LiveTypes,
Record, Resolve, Result_, Tuple, Type, TypeDefKind, TypeId, TypeOwner, Variant, WorldKey,
};
pub(crate) struct InterfaceFragment {
pub(crate) csharp_src: String,
pub(crate) csharp_interop_src: String,
pub(crate) stub: String,
pub(crate) direction: Option<Direction>, }
pub(crate) struct InterfaceTypeAndFragments {
pub(crate) is_export: bool,
pub(crate) interface_fragments: Vec<InterfaceFragment>,
}
impl InterfaceTypeAndFragments {
pub(crate) fn new(is_export: bool) -> Self {
InterfaceTypeAndFragments {
is_export,
interface_fragments: Vec::<InterfaceFragment>::new(),
}
}
}
pub(crate) struct FutureInfo {
pub name: String,
pub generic_type_name: String,
pub ty: Option<Type>,
}
pub(crate) struct InterfaceGenerator<'a> {
pub(crate) src: String,
pub(crate) csharp_interop_src: String,
pub(crate) stub: String,
pub(crate) csharp_gen: &'a mut CSharp,
pub(crate) resolve: &'a Resolve,
pub(crate) name: &'a str,
pub(crate) direction: Direction,
pub(crate) futures: Vec<FutureInfo>,
pub(crate) streams: Vec<FutureInfo>,
pub(crate) is_world: bool,
}
impl InterfaceGenerator<'_> {
pub fn is_async(kind: &FunctionKind) -> bool {
matches!(
kind,
FunctionKind::AsyncFreestanding
| FunctionKind::AsyncStatic(_)
| FunctionKind::AsyncMethod(_)
)
}
pub(crate) fn define_interface_types(&mut self, id: InterfaceId) {
let mut live = LiveTypes::default();
live.add_interface(self.resolve, id);
self.define_live_types(live, id);
}
fn define_live_types(&mut self, live: LiveTypes, id: InterfaceId) {
let mut type_names = HashMap::new();
for ty in live.iter() {
let type_def = &self.resolve.types[ty];
if type_names.contains_key(&ty) || type_def.name.is_some() {
continue;
}
let typedef_name = self.type_name(&Type::Id(ty));
let prev = type_names.insert(ty, typedef_name.clone());
assert!(prev.is_none());
self.csharp_gen
.anonymous_type_owners
.insert(ty, TypeOwner::Interface(id));
self.define_anonymous_type(ty, &typedef_name)
}
}
fn define_anonymous_type(&mut self, type_id: TypeId, typedef_name: &str) {
let type_def = &self.resolve().types[type_id];
let kind = &type_def.kind;
match kind {
TypeDefKind::Tuple(t) => self.type_tuple(type_id, typedef_name, t, &type_def.docs),
TypeDefKind::Option(t) => self.type_option(type_id, typedef_name, t, &type_def.docs),
TypeDefKind::Record(t) => self.type_record(type_id, typedef_name, t, &type_def.docs),
TypeDefKind::List(t) => self.type_list(type_id, typedef_name, t, &type_def.docs),
TypeDefKind::Variant(t) => self.type_variant(type_id, typedef_name, t, &type_def.docs),
TypeDefKind::Result(t) => self.type_result(type_id, typedef_name, t, &type_def.docs),
TypeDefKind::Handle(_) => {
}
TypeDefKind::Future(t) => self.type_future(type_id, typedef_name, t, &type_def.docs),
TypeDefKind::Stream(t) => self.type_stream(type_id, typedef_name, t, &type_def.docs),
_ => unreachable!(),
}
}
pub(crate) fn qualifier(&self, when: bool, ty: &TypeId) -> String {
let type_def = &self.resolve.types[*ty];
let owner = if let Some(owner_type) = self.csharp_gen.anonymous_type_owners.get(ty) {
*owner_type
} else {
type_def.owner
};
let global_prefix = self.global_if_user_type(&Type::Id(*ty));
if let TypeOwner::Interface(id) = owner {
if let Some(name) = self.csharp_gen.interface_names.get(&id) {
if name != self.name {
return format!("{global_prefix}{name}.");
}
}
}
if when {
let mut name = self.name;
let tmp_name: String;
if self.is_world {
if CSharp::type_is_bidirectional(self.resolve(), ty) {
name = name.rsplitn(2, ".").nth(1).unwrap();
}
else if name.ends_with("Exports") && type_def.kind != TypeDefKind::Resource {
tmp_name = Self::replace_last(&name, "Exports", "Imports");
name = &tmp_name;
}
}
format!("{global_prefix}{name}.")
} else {
String::new()
}
}
fn replace_last(haystack: &str, needle: &str, replacement: &str) -> String {
if let Some(pos) = haystack.rfind(needle) {
let mut result =
String::with_capacity(haystack.len() - needle.len() + replacement.len());
result.push_str(&haystack[..pos]);
result.push_str(replacement);
result.push_str(&haystack[pos + needle.len()..]);
result
} else {
haystack.to_string()
}
}
pub(crate) fn add_interface_fragment(self, is_export: bool) {
self.csharp_gen
.interface_fragments
.entry(self.name.to_string())
.or_insert_with(|| InterfaceTypeAndFragments::new(is_export))
.interface_fragments
.push(InterfaceFragment {
csharp_src: self.src,
csharp_interop_src: self.csharp_interop_src,
stub: self.stub,
direction: Some(self.direction),
});
}
pub(crate) fn add_futures_or_streams(
&mut self,
import_module_name: &str,
is_export: bool,
is_future: bool,
) {
if (is_future && self.futures.is_empty()) || (!is_future && self.streams.is_empty()) {
return;
}
let future_stream_name = if is_future { "Future" } else { "Stream" };
let future_stream_name_lower = future_stream_name.to_lowercase();
let mut bool_non_generic_new_added = false;
let mut bool_generic_new_added = false;
let mut generated_future_types: HashSet<Option<Type>> = HashSet::new();
let (_namespace, interface_name) = &CSharp::get_class_name_from_qualified_name(self.name);
let interop_name = format!("{}Interop", interface_name.strip_prefix("I").unwrap());
let (futures_or_streams, stream_length_param) = if is_future {
(&self.futures, "")
} else {
(&self.streams, ", uint length")
};
let mut index = 0;
for future in futures_or_streams {
let canonical_payload = match future.ty {
Some(Type::Id(id)) => {
let id = self.csharp_gen.types.get_representative_type(id);
match self.resolve.types[id].kind {
TypeDefKind::Type(t) => Some(t),
_ => Some(Type::Id(id)),
}
}
other => other,
};
{
if generated_future_types.contains(&canonical_payload) {
continue;
}
}
let future_name = &future.name;
let generic_type_name = &future.generic_type_name;
let upper_camel_future_type = generic_type_name.to_upper_camel_case();
uwrite!(
self.csharp_interop_src,
r#"
internal static {future_stream_name}VTable {future_stream_name}VTable{upper_camel_future_type} = new {future_stream_name}VTable()
{{
New = {future_stream_name}New{upper_camel_future_type},
Read = {future_stream_name}Read{upper_camel_future_type},
Write = {future_stream_name}Write{upper_camel_future_type},
DropReader = {future_stream_name}DropReader{upper_camel_future_type},
DropWriter = {future_stream_name}DropWriter{upper_camel_future_type},
CancelReadDelegate = {future_stream_name}CancelRead{upper_camel_future_type},
CancelWriteDelegate = {future_stream_name}CancelWrite{upper_camel_future_type},
}};
"#
);
uwrite!(
self.csharp_interop_src,
r#"
[global::System.Runtime.InteropServices.DllImportAttribute("{import_module_name}", EntryPoint = "[async-lower][{future_stream_name_lower}-read-{index}]{future_name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute]
internal static unsafe extern uint {future_stream_name}Read{upper_camel_future_type}(int readable, IntPtr ptr{stream_length_param});
[global::System.Runtime.InteropServices.DllImportAttribute("{import_module_name}", EntryPoint = "[async-lower][{future_stream_name_lower}-write-{index}]{future_name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute]
internal static unsafe extern uint {future_stream_name}Write{upper_camel_future_type}(int writeable, IntPtr buffer{stream_length_param});
[global::System.Runtime.InteropServices.DllImportAttribute("{import_module_name}", EntryPoint = "[{future_stream_name_lower}-drop-readable-{index}]{future_name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute]
internal static extern void {future_stream_name}DropReader{upper_camel_future_type}(int readable);
[global::System.Runtime.InteropServices.DllImportAttribute("{import_module_name}", EntryPoint = "[{future_stream_name_lower}-drop-writable-{index}]{future_name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute]
internal static extern void {future_stream_name}DropWriter{upper_camel_future_type}(int readable);
"#
);
uwrite!(
self.csharp_interop_src,
r#"
[global::System.Runtime.InteropServices.DllImportAttribute("{import_module_name}", EntryPoint = "[{future_stream_name_lower}-new-{index}]{future_name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute]
internal static extern ulong {future_stream_name}New{upper_camel_future_type}();
"#
);
uwrite!(
self.csharp_interop_src,
r#"
[global::System.Runtime.InteropServices.DllImportAttribute("{import_module_name}", EntryPoint = "[{future_stream_name_lower}-cancel-read-{index}]{future_name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute]
internal static extern uint {future_stream_name}CancelRead{upper_camel_future_type}(int readable);
"#
);
uwrite!(
self.csharp_interop_src,
r#"
[global::System.Runtime.InteropServices.DllImportAttribute("{import_module_name}", EntryPoint = "[{future_stream_name_lower}-cancel-write-{index}]{future_name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute]
internal static extern uint {future_stream_name}CancelWrite{upper_camel_future_type}(int writeable);
"#
);
uwrite!(
self.csharp_interop_src,
r#"
[global::System.Runtime.InteropServices.DllImportAttribute("{import_module_name}", EntryPoint = "[{future_stream_name_lower}-drop-writeable-{index}]{future_name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute]
internal static extern void {future_stream_name}DropWriteable{upper_camel_future_type}(int writeable);
"#
);
match future.generic_type_name.as_str() {
"" => {
if !bool_non_generic_new_added {
self.csharp_gen
.interface_fragments
.entry(self.name.to_string())
.or_insert_with(|| InterfaceTypeAndFragments::new(is_export))
.interface_fragments
.push(InterfaceFragment {
csharp_src: format!(r#"
internal static ({future_stream_name}Reader, {future_stream_name}Writer) {future_stream_name}New()
{{
return FutureHelpers.Raw{future_stream_name}New({interop_name}.{future_stream_name}VTable);
}}
"#).to_string(),
csharp_interop_src: "".to_string(),
stub: "".to_string(),
direction: Some(self.direction),
});
bool_non_generic_new_added = true;
}
}
_ => {
self.csharp_gen
.interface_fragments
.entry(self.name.to_string())
.or_insert_with(|| InterfaceTypeAndFragments::new(is_export))
.interface_fragments
.push(InterfaceFragment {
csharp_src: format!(r#"
public static ({future_stream_name}Reader<{generic_type_name}>, {future_stream_name}Writer<{generic_type_name}>) {future_stream_name}New{upper_camel_future_type}()
{{
return FutureHelpers.Raw{future_stream_name}New<{generic_type_name}>({interop_name}.{future_stream_name}VTable{upper_camel_future_type});
}}
"#).to_string(),
csharp_interop_src: "".to_string(),
stub: "".to_string(),
direction: Some(self.direction),
});
if !bool_generic_new_added {
self.csharp_gen
.interface_fragments
.entry(self.name.to_string())
.or_insert_with(|| InterfaceTypeAndFragments::new(is_export))
.interface_fragments
.push(InterfaceFragment {
csharp_src: format!(r#"
public static ({future_stream_name}Reader<T>, {future_stream_name}Writer<T>) {future_stream_name}New<T>({future_stream_name}VTable vtable)
{{
return FutureHelpers.Raw{future_stream_name}New<T>(vtable);
}}
"#).to_string(),
csharp_interop_src: "".to_string(),
stub: "".to_string(),
direction: Some(self.direction),
});
bool_generic_new_added = true;
}
}
}
generated_future_types.insert(canonical_payload);
index = index + 1;
}
self.csharp_gen.needs_async_support = true;
self.csharp_gen
.generated_future_types
.extend(generated_future_types.iter());
}
pub(crate) fn add_world_fragment(self, direction: Option<Direction>) {
self.csharp_gen.world_fragments.push(InterfaceFragment {
csharp_src: self.src,
csharp_interop_src: self.csharp_interop_src,
stub: self.stub,
direction,
});
}
pub(crate) fn import(&mut self, import_module_name: &str, func: &Function) {
let camel_name = match &func.kind {
FunctionKind::Freestanding
| FunctionKind::Static(_)
| FunctionKind::AsyncFreestanding
| FunctionKind::AsyncStatic(_) => func.item_name().to_upper_camel_case(),
FunctionKind::Method(_) | FunctionKind::AsyncMethod(_) => {
func.item_name().to_upper_camel_case()
}
FunctionKind::Constructor(id) => {
self.csharp_gen.all_resources[id].name.to_upper_camel_case()
}
};
let access = self.csharp_gen.access_modifier();
let modifiers = modifiers(func, &camel_name, Direction::Import);
let interop_camel_name = func.item_name().to_upper_camel_case();
let sig = self.resolve.wasm_signature(AbiVariant::GuestImport, func);
let is_async = matches!(
func.kind,
FunctionKind::AsyncFreestanding
| FunctionKind::AsyncStatic(_)
| FunctionKind::AsyncMethod(_)
);
let mut wasm_result_type = match &sig.results[..] {
[] => {
if is_async {
"uint"
} else {
"void"
}
}
[result] => crate::world_generator::wasm_type(*result),
_ => unreachable!(),
};
let (result_type, results) = self.func_payload_and_return_type(func);
let is_async = InterfaceGenerator::is_async(&func.kind);
let requires_async_return_buffer_param = is_async && !sig.results.is_empty();
let sig_unsafe = if requires_async_return_buffer_param {
"unsafe "
} else {
""
};
let wasm_params: String = {
let param_list = sig
.params
.iter()
.enumerate()
.map(|(i, param)| {
let ty = crate::world_generator::wasm_type(*param);
format!("{ty} p{i}")
})
.collect::<Vec<_>>()
.join(", ");
if requires_async_return_buffer_param {
if param_list.is_empty() {
"void *taskResultBuffer".to_string()
} else {
format!("{param_list}, void *taskResultBuffer")
}
} else {
param_list
}
};
let mut funcs: Vec<(String, String)> = Vec::new();
funcs.push(self.gen_import_src(func, &results, ParameterType::ABI));
let include_additional_functions = func
.params
.iter()
.skip(if let FunctionKind::Method(_) = &func.kind {
1
} else {
0
})
.any(|param| self.is_primative_list(¶m.ty));
if include_additional_functions {
funcs.push(self.gen_import_src(func, &results, ParameterType::Span));
funcs.push(self.gen_import_src(func, &results, ParameterType::Memory));
}
let import_name = if is_async {
wasm_result_type = "uint";
format!("[async-lower]{}", func.name)
} else {
func.name.to_string()
};
uwrite!(
self.csharp_interop_src,
r#"
public static class {interop_camel_name}WasmInterop
{{
[global::System.Runtime.InteropServices.DllImportAttribute("{import_module_name}", EntryPoint = "{import_name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute]
public static extern {sig_unsafe}{wasm_result_type} wasmImport{interop_camel_name}({wasm_params});
}}
"#
);
for (src, params) in funcs {
uwrite!(
self.src,
r#"
{access} {modifiers} unsafe {result_type} {camel_name}({params})
{{
{src}
}}
"#
);
}
}
fn func_payload_and_return_type(&mut self, func: &Function) -> (String, Vec<TypeId>) {
let return_type = self.func_return_type(func, true);
let results = if let FunctionKind::Constructor(_) = &func.kind {
Vec::new()
} else {
let payload = match func.result {
None => Vec::new(),
Some(ty) => {
let (_payload, results) = payload_and_results(
self.resolve,
ty,
self.csharp_gen.opts.with_wit_results,
);
results
}
};
payload
};
(return_type, results)
}
fn func_return_type(&mut self, func: &Function, qualifier: bool) -> String {
let result_type = if let FunctionKind::Constructor(_) = &func.kind {
String::new()
} else {
let base_type = match func.result {
None => "void".to_string(),
Some(_ty) => {
let result_type = match func.result {
None => "void".to_string(),
Some(ty) => {
let (payload, _results) = payload_and_results(
self.resolve,
ty,
self.csharp_gen.opts.with_wit_results,
);
if let Some(ty) = payload {
self.csharp_gen.needs_result = true;
self.type_name_with_qualifier(&ty, qualifier)
} else {
"void".to_string()
}
}
};
result_type
}
};
let asyncified_type = match &func.kind {
FunctionKind::AsyncFreestanding
| FunctionKind::AsyncStatic(_)
| FunctionKind::AsyncMethod(_) => match func.result {
None => "Task".to_string(),
Some(_ty) => format!("Task<{base_type}>"),
},
_ => base_type,
};
asyncified_type
};
result_type
}
fn gen_import_src(
&mut self,
func: &Function,
results: &[TypeId],
parameter_type: ParameterType,
) -> (String, String) {
let mut bindgen = FunctionBindgen::new(
self,
func.item_name(),
&func.kind,
func.params
.iter()
.enumerate()
.map(|(i, Param { name, .. })| {
if i == 0 && matches!(&func.kind, FunctionKind::Method(_)) {
"this".to_owned()
} else {
name.to_csharp_ident()
}
})
.collect(),
results.to_vec(),
parameter_type,
func.result,
);
let async_ = InterfaceGenerator::is_async(&func.kind);
let mut src: String;
if async_ {
let sig = bindgen
.interface_gen
.resolve
.wasm_signature(AbiVariant::GuestImportAsync, func);
let async_status_var = "result"; let requires_async_return_buffer_param = func.result.is_some();
let async_return_buffer = if requires_async_return_buffer_param {
let buffer = bindgen.emit_allocation_for_type(&sig.results);
uwriteln!(
bindgen.src,
"//TODO: store somewhere with the TaskCompletionSource, possibly in the state, using Task.AsyncState to retrieve it later."
);
Some(buffer)
} else {
None
};
let csharp_param_names = func
.params
.iter()
.map(|param| param.name.to_lower_camel_case())
.collect::<Vec<_>>();
let (lower, wasm_params) = if sig.indirect_params {
todo!("indirect params not supported for async imports yet");
} else {
let wasm_params: Vec<String> = csharp_param_names
.iter()
.zip(&func.params)
.flat_map(|(name, param)| {
abi::lower_flat(
bindgen.interface_gen.resolve,
&mut bindgen,
name.clone(),
¶m.ty,
)
})
.collect();
(mem::take(&mut bindgen.src), wasm_params)
};
let name = func.name.to_upper_camel_case();
let raw_name = format!("IImportsInterop.{name}WasmInterop.wasmImport{name}");
let wasm_params = wasm_params
.iter()
.map(|v| v.as_str())
.chain(func.result.map(|_| "address"))
.collect::<Vec<_>>()
.join(", ");
let code = format!(
"{lower}
var {async_status_var} = {raw_name}({wasm_params});
"
);
src = format!("{code}{}", bindgen.src);
if let Some(buffer) = async_return_buffer {
let ty = bindgen.result_type.expect("expected a result type");
let lift_expr = abi::lift_from_memory(
bindgen.interface_gen.resolve,
&mut bindgen,
buffer.clone(),
&ty,
);
let return_type = self.type_name_with_qualifier(&ty, true);
let lift_func = format!("() => {lift_expr}");
uwriteln!(
src,
"
var task = AsyncSupport.TaskFromStatus<{return_type}>({async_status_var}, {});",
lift_func
);
uwriteln!(
src,
"global::System.Runtime.InteropServices.NativeMemory.Free({});",
buffer
);
uwriteln!(src, "return task;");
} else {
uwriteln!(
src,
"
return AsyncSupport.TaskFromStatus({async_status_var});"
);
}
} else {
abi::call(
bindgen.interface_gen.resolve,
AbiVariant::GuestImport,
LiftLower::LowerArgsLiftResults,
func,
&mut bindgen,
false,
);
src = bindgen.src;
}
let params = func
.params
.iter()
.skip(if let FunctionKind::Method(_) = &func.kind {
1
} else {
0
})
.map(|param| {
let ty = self.name_with_qualifier(¶m.ty, true, parameter_type);
let param_name = ¶m.name;
let param_name = param_name.to_csharp_ident();
format!("{ty} {param_name}")
})
.collect::<Vec<_>>()
.join(", ");
(src, params)
}
pub(crate) fn export(&mut self, func: &Function, interface_key: Option<&WorldKey>) {
let camel_name = match &func.kind {
FunctionKind::Freestanding
| FunctionKind::Static(_)
| FunctionKind::AsyncFreestanding
| FunctionKind::AsyncStatic(_) => func.item_name().to_upper_camel_case(),
FunctionKind::Method(_) | FunctionKind::AsyncMethod(_) => {
func.item_name().to_upper_camel_case()
}
FunctionKind::Constructor(id) => {
self.csharp_gen.all_resources[id].name.to_upper_camel_case()
}
};
let modifiers = modifiers(func, &camel_name, Direction::Export);
let sig = self.resolve.wasm_signature(AbiVariant::GuestExport, func);
let (result_type, results) = self.func_payload_and_return_type(func);
let mut bindgen = FunctionBindgen::new(
self,
func.item_name(),
&func.kind,
(0..sig.params.len()).map(|i| format!("p{i}")).collect(),
results,
ParameterType::ABI,
func.result,
);
let async_ = matches!(
func.kind,
FunctionKind::AsyncFreestanding
| FunctionKind::AsyncStatic(_)
| FunctionKind::AsyncMethod(_)
);
abi::call(
bindgen.interface_gen.resolve,
AbiVariant::GuestExport,
LiftLower::LiftArgsLowerResults,
func,
&mut bindgen,
async_,
);
let src = bindgen.src;
let resource_type_name = bindgen.resource_type_name;
let vars = bindgen
.resource_drops
.iter()
.map(|(t, v)| format!("{t}? {v} = null;"))
.collect::<Vec<_>>()
.join(";\n");
let wasm_result_type = if async_ {
"uint"
} else {
match &sig.results[..] {
[] => "void",
[result] => crate::world_generator::wasm_type(*result),
_ => unreachable!(),
}
};
let wasm_params = sig
.params
.iter()
.enumerate()
.map(|(i, param)| {
let ty = crate::world_generator::wasm_type(*param);
format!("{ty} p{i}")
})
.collect::<Vec<_>>()
.join(", ");
let (_namespace, _interface_name) = &CSharp::get_class_name_from_qualified_name(self.name);
let params = func
.params
.iter()
.skip(if let FunctionKind::Method(_) = &func.kind {
1
} else {
0
})
.map(|Param { name, ty, .. }| {
let ty = self.type_name_with_qualifier(ty, true);
let name = name.to_csharp_ident();
format!("{ty} {name}")
})
.collect::<Vec<String>>()
.join(", ");
let wasm_func_name = func.name.clone();
let interop_name = format!("wasmExport{}", wasm_func_name.to_upper_camel_case());
let core_module_name = interface_key.map(|s| self.resolve.name_world_key(s));
let export_name = func.legacy_core_export_name(core_module_name.as_deref());
let export_name = if async_ {
format!("[async-lift]{export_name}")
} else {
export_name.to_string()
};
let access = self.csharp_gen.access_modifier();
uwrite!(
self.csharp_interop_src,
r#"
[global::System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute(EntryPoint = "{export_name}")]
{access} static unsafe {wasm_result_type} {interop_name}({wasm_params}) {{
{vars}
{src}
}}
"#
);
if async_ {
uwriteln!(
self.csharp_interop_src,
r#"
[global::System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute(EntryPoint = "[callback]{export_name}")]
public static unsafe uint {camel_name}Callback(int eventRaw, uint waitable, uint code)
{{
EventWaitable e = new EventWaitable((EventCode)eventRaw, waitable, code);
"#
);
if sig.results.len() > 0 {
uwriteln!(
self.csharp_interop_src,
r#"
throw new NotImplementedException("callbacks with parameters are not yet implemented.");
}}
"#
);
} else {
uwriteln!(
self.csharp_interop_src,
r#"
return (uint)AsyncSupport.Callback(e, (ContextTask *)IntPtr.Zero);
}}
"#
);
}
}
if abi::guest_export_needs_post_return(self.resolve, func) {
let params = sig
.results
.iter()
.enumerate()
.map(|(i, param)| {
let ty = crate::world_generator::wasm_type(*param);
format!("{ty} p{i}")
})
.collect::<Vec<_>>()
.join(", ");
let mut bindgen = FunctionBindgen::new(
self,
"INVALID",
&func.kind,
(0..sig.results.len()).map(|i| format!("p{i}")).collect(),
Vec::new(),
ParameterType::ABI,
func.result,
);
abi::post_return(bindgen.interface_gen.resolve, func, &mut bindgen);
let src = bindgen.src;
uwrite!(
self.csharp_interop_src,
r#"
[global::System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute(EntryPoint = "cabi_post_{export_name}")]
{access} static unsafe void cabi_post_{interop_name}({params}) {{
{src}
}}
"#
);
}
if async_ {
let (_namespace, interface_name) =
&CSharp::get_class_name_from_qualified_name(self.name);
let impl_name = if resource_type_name.is_some() {
resource_type_name.unwrap()
} else {
format!("{}Impl", interface_name.strip_prefix("I").unwrap())
};
let (_import_module_prefix, import_module) = match interface_key {
Some(world_key) => {
let interface_world = self.resolve.name_world_key(world_key);
(format!("{interface_world}#"), interface_world)
}
None => (String::new(), "$root".to_string()),
};
let mut interop_class_name = impl_name.replace("Impl", "Interop");
if self.is_world {
interop_class_name = interop_class_name.replace("ExportsInterop", "Interop");
interop_class_name = format!("Exports.{interop_class_name}");
}
let (task_return_param_sig, task_return_param) = match &sig.results[..] {
[] => (String::new(), String::new()),
[_result] => (format!("{wasm_result_type} result"), "result".to_string()),
_ => unreachable!(),
};
uwriteln!(
self.src,
r#"
public static void {camel_name}TaskReturn({task_return_param_sig} )
{{
{interop_class_name}.{camel_name}TaskReturn({task_return_param});
}}
"#
);
uwriteln!(
self.csharp_interop_src,
r#"
// TODO: The task return function can take up to 16 core parameters.
[global::System.Runtime.InteropServices.DllImportAttribute("[export]{import_module}", EntryPoint = "[task-return]{wasm_func_name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute]
public static extern void {camel_name}TaskReturn({task_return_param_sig});
"#
);
}
if !matches!(&func.kind, FunctionKind::Constructor(_)) {
uwrite!(
self.src,
r#"{modifiers} {result_type} {camel_name}({params});
"#
);
}
if self.csharp_gen.opts.generate_stub {
let sig: String = self.sig_string(func, true);
let func_name = func.item_name().to_upper_camel_case();
uwrite!(
self.stub,
r#"
{sig}
{{
throw new global::System.NotImplementedException();
}}
public static int {func_name}Callback(/* TODO: event arg */)
{{
throw new global::System.NotImplementedException();
}}
"#
);
}
}
fn type_name(&mut self, ty: &Type) -> String {
self.type_name_with_qualifier(ty, false)
}
fn global_if_user_type(&self, ty: &Type) -> String {
match ty {
Type::Id(id) => {
let ty = &self.resolve.types[*id];
match &ty.kind {
TypeDefKind::Option(_ty) => "".to_owned(),
TypeDefKind::Result(_result) => "".to_owned(),
TypeDefKind::List(_list) => "".to_owned(),
TypeDefKind::Tuple(_tuple) => "".to_owned(),
TypeDefKind::Type(inner_type) => self.global_if_user_type(inner_type),
_ => "global::".to_owned(),
}
}
_ => "".to_owned(),
}
}
pub(crate) fn type_name_with_qualifier(&mut self, ty: &Type, qualifier: bool) -> String {
self.name_with_qualifier(ty, qualifier, ParameterType::ABI)
}
fn is_primative_list(&mut self, ty: &Type) -> bool {
match ty {
Type::Id(id) => {
let ty = &self.resolve.types[*id];
match &ty.kind {
TypeDefKind::Type(ty) => self.is_primative_list(ty),
TypeDefKind::List(ty) if crate::world_generator::is_primitive(ty) => {
return true;
}
_ => false,
}
}
_ => false,
}
}
pub(crate) fn name_with_qualifier(
&mut self,
ty: &Type,
qualifier: bool,
parameter_type: ParameterType,
) -> String {
match ty {
Type::Bool => "bool".to_owned(),
Type::U8 => "byte".to_owned(),
Type::U16 => "ushort".to_owned(),
Type::U32 => "uint".to_owned(),
Type::U64 => "ulong".to_owned(),
Type::S8 => "sbyte".to_owned(),
Type::S16 => "short".to_owned(),
Type::S32 => "int".to_owned(),
Type::S64 => "long".to_owned(),
Type::F32 => "float".to_owned(),
Type::F64 => "double".to_owned(),
Type::Char => "uint".to_owned(),
Type::String => "string".to_owned(),
Type::ErrorContext => todo!("error context name with qualifier"),
Type::Id(id) => {
let ty = &self.resolve.types[*id];
match &ty.kind {
TypeDefKind::Type(ty) => {
self.name_with_qualifier(ty, qualifier, parameter_type)
}
TypeDefKind::List(ty) => {
if crate::world_generator::is_primitive(ty)
&& self.direction == Direction::Import
&& parameter_type == ParameterType::Span
{
format!("global::System.Span<{}>", self.type_name(ty))
} else if crate::world_generator::is_primitive(ty)
&& self.direction == Direction::Import
&& parameter_type == ParameterType::Memory
{
format!("global::System.Memory<{}>", self.type_name(ty))
} else if crate::world_generator::is_primitive(ty) {
format!("{}[]", self.type_name(ty))
} else {
format!(
"global::System.Collections.Generic.List<{}>",
self.type_name_with_qualifier(ty, qualifier)
)
}
}
TypeDefKind::Tuple(tuple) => {
let count = tuple.types.len();
self.csharp_gen.tuple_counts.insert(count);
let params = match count {
0 => String::new(),
1 => self
.type_name_with_qualifier(tuple.types.first().unwrap(), qualifier),
_ => format!(
"({})",
tuple
.types
.iter()
.map(|ty| self.type_name_with_qualifier(ty, qualifier))
.collect::<Vec<_>>()
.join(", ")
),
};
params
}
TypeDefKind::Option(base_ty) => {
self.csharp_gen.needs_option = true;
let nesting = if let Type::Id(id) = base_ty {
matches!(&self.resolve.types[*id].kind, TypeDefKind::Option(_))
} else {
false
};
let base_ty = self.type_name_with_qualifier(base_ty, qualifier);
if nesting {
format!("Option<{base_ty}>")
} else {
format!("{base_ty}?")
}
}
TypeDefKind::Result(result) => {
self.csharp_gen.needs_result = true;
let mut name = |ty: &Option<Type>| {
ty.as_ref()
.map(|ty| self.type_name_with_qualifier(ty, qualifier))
.unwrap_or_else(|| "None".to_owned())
};
let ok = name(&result.ok);
let err = name(&result.err);
format!("Result<{ok}, {err}>")
}
TypeDefKind::Handle(handle) => {
let (Handle::Own(id) | Handle::Borrow(id)) = handle;
self.type_name_with_qualifier(&Type::Id(*id), qualifier)
}
TypeDefKind::Future(ty) => {
let generic_type = if let Some(typ) = ty {
format!("<{}>", self.type_name_with_qualifier(typ, qualifier))
} else {
String::new()
};
return format!("FutureReader{generic_type}").to_owned();
}
TypeDefKind::Stream(ty) => {
let generic_type = if let Some(typ) = ty {
format!("<{}>", self.type_name_with_qualifier(typ, qualifier))
} else {
String::new()
};
return format!("StreamReader{generic_type}").to_owned();
}
_ => {
if let Some(name) = &ty.name {
format!(
"{}{}",
self.qualifier(qualifier, id),
name.to_upper_camel_case()
)
} else {
unreachable!("todo: {ty:?}")
}
}
}
}
}
}
fn print_docs(&mut self, docs: &Docs) {
if let Some(docs) = &docs.contents {
let lines = docs
.trim()
.replace("<", "<")
.replace(">", ">")
.replace("*/", "*") .lines()
.map(|line| format!("* {line}"))
.collect::<Vec<_>>()
.join("\n");
uwrite!(
self.src,
"
/**
{lines}
*/
"
)
}
}
pub(crate) fn non_empty_type<'a>(&self, ty: Option<&'a Type>) -> Option<&'a Type> {
if let Some(ty) = ty {
let id = match ty {
Type::Id(id) => *id,
_ => return Some(ty),
};
match &self.resolve.types[id].kind {
TypeDefKind::Type(t) => self.non_empty_type(Some(t)).map(|_| ty),
TypeDefKind::Record(r) => (!r.fields.is_empty()).then_some(ty),
TypeDefKind::Tuple(t) => (!t.types.is_empty()).then_some(ty),
_ => Some(ty),
}
} else {
None
}
}
pub(crate) fn start_resource(&mut self, id: TypeId, key: Option<&WorldKey>) {
let access = self.csharp_gen.access_modifier();
let qualified = self.type_name_with_qualifier(&Type::Id(id), true);
let info = &self.csharp_gen.all_resources[&id];
let name = info.name.clone();
let upper_camel = name.to_upper_camel_case();
let docs = info.docs.clone();
self.print_docs(&docs);
match self.direction {
Direction::Import => {
let module_name = key
.map(|key| self.resolve.name_world_key(key))
.unwrap_or_else(|| "$root".into());
uwriteln!(
self.src,
r#"
{access} class {upper_camel}: global::System.IDisposable {{
internal int Handle {{ get; set; }}
{access} readonly record struct THandle(int Handle);
{access} {upper_camel}(THandle handle) {{
Handle = handle.Handle;
}}
public void Dispose() {{
Dispose(true);
}}
[global::System.Runtime.InteropServices.DllImportAttribute("{module_name}", EntryPoint = "[resource-drop]{name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute]
private static extern void wasmImportResourceDrop(int p0);
protected virtual void Dispose(bool disposing) {{
if (disposing && Handle != 0) {{
wasmImportResourceDrop(Handle);
Handle = 0;
}}
}}
"#
);
}
Direction::Export => {
let prefix = key
.map(|s| format!("{}#", self.resolve.name_world_key(s)))
.unwrap_or_default();
uwrite!(
self.csharp_interop_src,
r#"
[global::System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute(EntryPoint = "{prefix}[dtor]{name}")]
{access} static unsafe void wasmExportResourceDtor{upper_camel}(int rep) {{
var val = ({qualified}) {qualified}.repTable.Remove(rep);
val.Handle = 0;
// Note we call `Dispose` here even though the handle has already been disposed in case
// the implementation has overridden `Dispose(bool)`.
val.Dispose();
}}
"#
);
let module_name = key
.map(|key| format!("[export]{}", self.resolve.name_world_key(key)))
.unwrap_or_else(|| "[export]$root".into());
uwriteln!(
self.src,
r#"
{access} abstract class {upper_camel}: global::System.IDisposable {{
internal static RepTable<{upper_camel}> repTable = new ();
internal int Handle {{ get; set; }}
public void Dispose() {{
Dispose(true);
GC.SuppressFinalize(this);
}}
internal static class WasmInterop {{
[global::System.Runtime.InteropServices.DllImportAttribute("{module_name}", EntryPoint = "[resource-drop]{name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute]
internal static extern void wasmImportResourceDrop(int p0);
[global::System.Runtime.InteropServices.DllImportAttribute("{module_name}", EntryPoint = "[resource-new]{name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute]
internal static extern int wasmImportResourceNew(int p0);
[global::System.Runtime.InteropServices.DllImportAttribute("{module_name}", EntryPoint = "[resource-rep]{name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute]
internal static extern int wasmImportResourceRep(int p0);
}}
protected virtual void Dispose(bool disposing) {{
if (Handle != 0) {{
var handle = Handle;
Handle = 0;
WasmInterop.wasmImportResourceDrop(handle);
}}
}}
~{upper_camel}() {{
Dispose(false);
}}
}}
{access} interface I{upper_camel} {{
"#
);
self.csharp_gen.needs_rep_table = true;
if self.csharp_gen.opts.generate_stub {
let super_ = self.type_name_with_qualifier(&Type::Id(id), true);
let interface = {
let split = super_.split('.').collect::<Vec<_>>();
split
.iter()
.map(|&v| v.to_owned())
.take(split.len() - 1)
.chain(split.last().map(|v| format!("I{v}")))
.collect::<Vec<_>>()
.join(".")
};
uwriteln!(
self.stub,
r#"
{access} class {upper_camel}: {super_}, {interface} {{
"#
);
}
}
};
uwrite!(
self.csharp_interop_src,
r#"
internal static class {upper_camel}
{{
"#
);
}
pub(crate) fn end_resource(&mut self) {
if self.direction == Direction::Export && self.csharp_gen.opts.generate_stub {
uwriteln!(
self.stub,
"
}}
"
);
}
uwriteln!(
self.src,
"
}}
"
);
uwrite!(
self.csharp_interop_src,
"
}}
"
);
}
fn sig_string(&mut self, func: &Function, qualifier: bool) -> String {
let result_type = self.func_return_type(&func, qualifier);
let params = func
.params
.iter()
.skip(if let FunctionKind::Method(_) = &func.kind {
1
} else {
0
})
.map(|Param { name, ty, .. }| {
let ty = self.type_name_with_qualifier(ty, qualifier);
let name = name.to_csharp_ident();
format!("{ty} {name}")
})
.collect::<Vec<_>>()
.join(", ");
let (camel_name, modifiers) = match &func.kind {
FunctionKind::Freestanding
| FunctionKind::AsyncFreestanding
| FunctionKind::Static(_)
| FunctionKind::AsyncStatic(_) => (func.item_name().to_upper_camel_case(), "static"),
FunctionKind::Method(_) | FunctionKind::AsyncMethod(_) => {
(func.item_name().to_upper_camel_case(), "")
}
FunctionKind::Constructor(id) => (
self.csharp_gen.all_resources[id].name.to_upper_camel_case(),
"",
),
};
let access = self.csharp_gen.access_modifier();
format!("{access} {modifiers} {result_type} {camel_name}({params})")
}
pub(crate) fn add_future(
&mut self,
func_name: &str,
generic_type_name: &str,
ty: Option<Type>,
) {
self.futures.push(FutureInfo {
name: func_name.to_string(),
generic_type_name: generic_type_name.to_string(),
ty: ty,
});
}
pub(crate) fn add_stream(
&mut self,
func_name: &str,
generic_type_name: &str,
ty: Option<Type>,
) {
self.streams.push(FutureInfo {
name: func_name.to_string(),
generic_type_name: generic_type_name.to_string(),
ty: ty,
});
}
}
impl<'a> CoreInterfaceGenerator<'a> for InterfaceGenerator<'a> {
fn resolve(&self) -> &'a Resolve {
self.resolve
}
fn type_record(&mut self, _id: TypeId, name: &str, record: &Record, docs: &Docs) {
let access = self.csharp_gen.access_modifier();
self.print_docs(docs);
let name = name.to_upper_camel_case();
let parameters = record
.fields
.iter()
.map(|field| {
format!(
"{} {}",
self.type_name(&field.ty),
field.name.to_csharp_ident()
)
})
.collect::<Vec<_>>()
.join(", ");
let assignments = record
.fields
.iter()
.map(|field| {
let name = field.name.to_csharp_ident();
format!("this.{name} = {name};")
})
.collect::<Vec<_>>()
.join("\n");
let fields = if record.fields.is_empty() {
format!("{access} const {name} INSTANCE = new {name}();")
} else {
record
.fields
.iter()
.map(|field| {
format!(
"{access} {} {};",
self.type_name(&field.ty),
field.name.to_csharp_ident()
)
})
.collect::<Vec<_>>()
.join("\n")
};
uwrite!(
self.src,
"
{access} struct {name} {{
{fields}
{access} {name}({parameters}) {{
{assignments}
}}
}}
"
);
}
fn type_flags(&mut self, _id: TypeId, name: &str, flags: &Flags, docs: &Docs) {
self.print_docs(docs);
let name = name.to_upper_camel_case();
let enum_elements = flags
.flags
.iter()
.enumerate()
.map(|(i, flag)| {
let flag_name = flag.name.to_shouty_snake_case();
let suffix = if matches!(flags.repr(), FlagsRepr::U32(2)) {
"UL"
} else {
""
};
format!("{flag_name} = 1{suffix} << {i},")
})
.collect::<Vec<_>>()
.join("\n");
let enum_type = match flags.repr() {
FlagsRepr::U32(2) => ": ulong",
FlagsRepr::U16 => ": ushort",
FlagsRepr::U8 => ": byte",
_ => "",
};
let access = self.csharp_gen.access_modifier();
uwrite!(
self.src,
"
{access} enum {name} {enum_type} {{
{enum_elements}
}}
"
);
}
fn type_tuple(&mut self, id: TypeId, _name: &str, _tuple: &Tuple, _docs: &Docs) {
self.type_name(&Type::Id(id));
}
fn type_variant(&mut self, _id: TypeId, name: &str, variant: &Variant, docs: &Docs) {
self.print_docs(docs);
let name = name.to_upper_camel_case();
let tag_type = int_type(variant.tag());
let access = self.csharp_gen.access_modifier();
let constructors = variant
.cases
.iter()
.map(|case| {
let case_name = case.name.to_csharp_ident();
let tag = case.name.to_csharp_ident_upper();
let method_name = variant_new_func_name(&name, &tag);
let (parameter, argument) = if let Some(ty) = self.non_empty_type(case.ty.as_ref())
{
(
format!("{} {case_name}", self.type_name(ty)),
case_name.deref(),
)
} else {
(String::new(), "null")
};
format!(
"{access} static {name} {method_name}({parameter}) {{
return new {name}(Tags.{tag}, {argument});
}}
"
)
})
.collect::<Vec<_>>()
.join("\n");
let accessors = variant
.cases
.iter()
.filter_map(|case| {
self.non_empty_type(case.ty.as_ref()).map(|ty| {
let case_name = case.name.to_upper_camel_case();
let tag = case.name.to_csharp_ident_upper();
let ty = self.type_name(ty);
format!(
r#"{access} {ty} As{case_name}
{{
get
{{
if (Tag == Tags.{tag})
return ({ty})value!;
else
throw new global::System.ArgumentException("expected {tag}, got " + Tag);
}}
}}
"#
)
})
})
.collect::<Vec<_>>()
.join("\n");
let tags = variant
.cases
.iter()
.enumerate()
.map(|(i, case)| {
let tag = case.name.to_csharp_ident_upper();
format!("{access} const {tag_type} {tag} = {i};")
})
.collect::<Vec<_>>()
.join("\n");
uwrite!(
self.src,
"
{access} class {name} {{
{access} readonly {tag_type} Tag;
private readonly object? value;
private {name}({tag_type} tag, object? value) {{
this.Tag = tag;
this.value = value;
}}
{constructors}
{accessors}
{access} class Tags {{
{tags}
}}
}}
"
);
}
fn type_option(&mut self, id: TypeId, _name: &str, _payload: &Type, _docs: &Docs) {
self.type_name(&Type::Id(id));
}
fn type_result(&mut self, id: TypeId, _name: &str, _result: &Result_, _docs: &Docs) {
self.type_name(&Type::Id(id));
}
fn type_enum(&mut self, ty: TypeId, name: &str, enum_: &Enum, docs: &Docs) {
self.print_docs(docs);
let name = name.to_upper_camel_case();
let cases = enum_
.cases
.iter()
.map(|case| case.name.to_shouty_snake_case())
.collect::<Vec<_>>()
.join(", ");
let access = self.csharp_gen.access_modifier();
match &self.resolve.types[ty].owner {
TypeOwner::World(_id) => {
self.csharp_gen.bidirectional_types_src.insert(format!(
"
{access} enum {name} {{
{cases}
}}
"
));
}
_ => {
uwrite!(
self.src,
"
{access} enum {name} {{
{cases}
}}
"
);
}
};
}
fn type_alias(&mut self, id: TypeId, _name: &str, _ty: &Type, _docs: &Docs) {
self.type_name(&Type::Id(id));
}
fn type_list(&mut self, id: TypeId, _name: &str, _ty: &Type, _docs: &Docs) {
self.type_name(&Type::Id(id));
}
fn type_fixed_length_list(
&mut self,
_id: TypeId,
_name: &str,
_ty: &Type,
_size: u32,
_docs: &Docs,
) {
todo!("named fixed-length list types are not yet supported in the C# backend")
}
fn type_map(&mut self, _id: TypeId, _name: &str, _key: &Type, _value: &Type, _docs: &Docs) {
todo!("map types are not yet supported in the C# backend")
}
fn type_builtin(&mut self, _id: TypeId, _name: &str, _ty: &Type, _docs: &Docs) {
unimplemented!();
}
fn type_resource(&mut self, id: TypeId, name: &str, docs: &Docs) {
self.csharp_gen
.all_resources
.entry(id)
.or_insert_with(|| ResourceInfo {
module: self.name.to_owned(),
name: name.to_owned(),
docs: docs.clone(),
direction: Direction::Import,
})
.direction = self.direction;
}
fn type_future(&mut self, id: TypeId, _name: &str, _ty: &Option<Type>, _docs: &Docs) {
self.type_name(&Type::Id(id));
}
fn type_stream(&mut self, id: TypeId, _name: &str, _ty: &Option<Type>, _docs: &Docs) {
self.type_name(&Type::Id(id));
}
}
pub fn variant_new_func_name(variant_name: &String, tag: &String) -> String {
if *tag == *variant_name {
format!("{tag}_") } else {
tag.clone()
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub(crate) enum ParameterType {
ABI,
Span,
Memory,
}
fn payload_and_results(
resolve: &Resolve,
ty: Type,
with_wit_results: bool,
) -> (Option<Type>, Vec<TypeId>) {
if with_wit_results {
return (Some(ty), Vec::new());
}
fn recurse(resolve: &Resolve, ty: Type, results: &mut Vec<TypeId>) -> Option<Type> {
if let Type::Id(id) = ty {
if let TypeDefKind::Result(result) = &resolve.types[id].kind {
results.push(id);
if let Some(ty) = result.ok {
recurse(resolve, ty, results)
} else {
None
}
} else {
Some(ty)
}
} else {
Some(ty)
}
}
let mut results = Vec::new();
let payload = recurse(resolve, ty, &mut results);
(payload, results)
}
fn modifiers(func: &Function, name: &str, direction: Direction) -> String {
let new_modifier = match &func.kind {
FunctionKind::Method(_) if name == "GetType" => " new",
_ => "",
};
let static_modifiers = match &func.kind {
FunctionKind::Freestanding
| FunctionKind::Static(_)
| FunctionKind::AsyncFreestanding
| FunctionKind::AsyncStatic(_) => "static",
_ => "",
};
let abstract_modifier = match (direction, &func.kind) {
(Direction::Export, _) => " abstract",
_ => "",
};
let async_modifier = match &func.kind {
FunctionKind::AsyncMethod(_) | FunctionKind::AsyncStatic(_) if abstract_modifier == "" => {
" async"
}
_ => "",
};
format!("{static_modifiers}{abstract_modifier}{async_modifier}{new_modifier}")
}
fn int_type(int: Int) -> &'static str {
match int {
Int::U8 => "byte",
Int::U16 => "ushort",
Int::U32 => "uint",
Int::U64 => "ulong",
}
}