Skip to content

Commit 86d568a

Browse files
authored
C#: use exceptions for functions returning result (#968)
* C#: use exceptions for functions returning `result` This addresses part of #964. For WIT functions returning `result<T, E>`, we now generate methods which return `T` and throw `WitException` such that the `E` value may be retrieved using `WitException.Value`. I considered making `WitException` generic, but I couldn't find a precident for generic exception classes when searching online, so I'm not sure how idiomatic that would look like. So, for now at least, you need to cast the value obtained using `WitException.Value` to the expected type. Happy to revisit this if desired. For WIT functions returning nested results, e.g. `result<result<T, E1>, E2>`, we generate methods which return `T` and throw `WitException`, using `WitException.NestingLevel` to disambiguate when `E1` and `E2` are the same. Signed-off-by: Joel Dice <joel.dice@fermyon.com> * use 32-bit values for `results` test This is a workaround for the `return pointer not aligned` issue, which is still causing problems for Windows builds. Signed-off-by: Joel Dice <joel.dice@fermyon.com> * update C# results test Signed-off-by: Joel Dice <joel.dice@fermyon.com> --------- Signed-off-by: Joel Dice <joel.dice@fermyon.com>
1 parent 0fdc730 commit 86d568a

File tree

7 files changed

+467
-42
lines changed

7 files changed

+467
-42
lines changed

crates/csharp/src/lib.rs

Lines changed: 206 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ pub struct CSharp {
135135
needs_interop_string: bool,
136136
needs_export_return_area: bool,
137137
needs_rep_table: bool,
138+
needs_wit_exception: bool,
138139
interface_fragments: HashMap<String, InterfaceTypeAndFragments>,
139140
world_fragments: Vec<InterfaceFragment>,
140141
sizes: SizeAlign,
@@ -515,6 +516,23 @@ impl WorldGenerator for CSharp {
515516
)
516517
}
517518

519+
if self.needs_wit_exception {
520+
src.push_str(
521+
r#"
522+
public class WitException: Exception {
523+
public object Value { get; }
524+
public uint NestingLevel { get; }
525+
526+
public WitException(object v, uint level)
527+
{
528+
Value = v;
529+
NestingLevel = level;
530+
}
531+
}
532+
"#,
533+
)
534+
}
535+
518536
// Declare a statically-allocated return area, if needed. We only do
519537
// this for export bindings, because import bindings allocate their
520538
// return-area on the stack.
@@ -929,14 +947,25 @@ impl InterfaceGenerator<'_> {
929947
_ => unreachable!(),
930948
};
931949

932-
let result_type = if let FunctionKind::Constructor(_) = &func.kind {
933-
String::new()
950+
let (result_type, results) = if let FunctionKind::Constructor(_) = &func.kind {
951+
(String::new(), Vec::new())
934952
} else {
935953
match func.results.len() {
936-
0 => "void".to_string(),
954+
0 => ("void".to_string(), Vec::new()),
937955
1 => {
938-
let ty = func.results.iter_types().next().unwrap();
939-
self.type_name_with_qualifier(ty, true)
956+
let (payload, results) = payload_and_results(
957+
self.resolve,
958+
*func.results.iter_types().next().unwrap(),
959+
);
960+
(
961+
if let Some(ty) = payload {
962+
self.gen.needs_result = true;
963+
self.type_name_with_qualifier(&ty, true)
964+
} else {
965+
"void".to_string()
966+
},
967+
results,
968+
)
940969
}
941970
_ => {
942971
let types = func
@@ -945,7 +974,7 @@ impl InterfaceGenerator<'_> {
945974
.map(|ty| self.type_name_with_qualifier(ty, true))
946975
.collect::<Vec<_>>()
947976
.join(", ");
948-
format!("({})", types)
977+
(format!("({})", types), Vec::new())
949978
}
950979
}
951980
};
@@ -976,6 +1005,7 @@ impl InterfaceGenerator<'_> {
9761005
}
9771006
})
9781007
.collect(),
1008+
results,
9791009
);
9801010

9811011
abi::call(
@@ -1055,11 +1085,44 @@ impl InterfaceGenerator<'_> {
10551085

10561086
let sig = self.resolve.wasm_signature(AbiVariant::GuestExport, func);
10571087

1088+
let (result_type, results) = if let FunctionKind::Constructor(_) = &func.kind {
1089+
(String::new(), Vec::new())
1090+
} else {
1091+
match func.results.len() {
1092+
0 => ("void".to_owned(), Vec::new()),
1093+
1 => {
1094+
let (payload, results) = payload_and_results(
1095+
self.resolve,
1096+
*func.results.iter_types().next().unwrap(),
1097+
);
1098+
(
1099+
if let Some(ty) = payload {
1100+
self.gen.needs_result = true;
1101+
self.type_name(&ty)
1102+
} else {
1103+
"void".to_string()
1104+
},
1105+
results,
1106+
)
1107+
}
1108+
_ => {
1109+
let types = func
1110+
.results
1111+
.iter_types()
1112+
.map(|ty| self.type_name(ty))
1113+
.collect::<Vec<String>>()
1114+
.join(", ");
1115+
(format!("({}) ", types), Vec::new())
1116+
}
1117+
}
1118+
};
1119+
10581120
let mut bindgen = FunctionBindgen::new(
10591121
self,
10601122
&func.item_name(),
10611123
&func.kind,
10621124
(0..sig.params.len()).map(|i| format!("p{i}")).collect(),
1125+
results,
10631126
);
10641127

10651128
abi::call(
@@ -1087,24 +1150,6 @@ impl InterfaceGenerator<'_> {
10871150
_ => unreachable!(),
10881151
};
10891152

1090-
let result_type = if let FunctionKind::Constructor(_) = &func.kind {
1091-
String::new()
1092-
} else {
1093-
match func.results.len() {
1094-
0 => "void".to_owned(),
1095-
1 => self.type_name(func.results.iter_types().next().unwrap()),
1096-
_ => {
1097-
let types = func
1098-
.results
1099-
.iter_types()
1100-
.map(|ty| self.type_name(ty))
1101-
.collect::<Vec<String>>()
1102-
.join(", ");
1103-
format!("({}) ", types)
1104-
}
1105-
}
1106-
};
1107-
11081153
let wasm_params = sig
11091154
.params
11101155
.iter()
@@ -1507,8 +1552,18 @@ impl InterfaceGenerator<'_> {
15071552
} else {
15081553
match func.results.len() {
15091554
0 => "void".into(),
1510-
1 => self
1511-
.type_name_with_qualifier(func.results.iter_types().next().unwrap(), qualifier),
1555+
1 => {
1556+
let (payload, _) = payload_and_results(
1557+
self.resolve,
1558+
*func.results.iter_types().next().unwrap(),
1559+
);
1560+
if let Some(ty) = payload {
1561+
self.gen.needs_result = true;
1562+
self.type_name_with_qualifier(&ty, qualifier)
1563+
} else {
1564+
"void".to_string()
1565+
}
1566+
}
15121567
count => {
15131568
self.gen.tuple_counts.insert(count);
15141569
format!(
@@ -1834,6 +1889,7 @@ struct FunctionBindgen<'a, 'b> {
18341889
func_name: &'b str,
18351890
kind: &'b FunctionKind,
18361891
params: Box<[String]>,
1892+
results: Vec<TypeId>,
18371893
src: String,
18381894
locals: Ns,
18391895
block_storage: Vec<BlockStorage>,
@@ -1853,6 +1909,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> {
18531909
func_name: &'b str,
18541910
kind: &'b FunctionKind,
18551911
params: Box<[String]>,
1912+
results: Vec<TypeId>,
18561913
) -> FunctionBindgen<'a, 'b> {
18571914
let mut locals = Ns::default();
18581915
// Ensure temporary variable names don't clash with parameter names:
@@ -1865,6 +1922,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> {
18651922
func_name,
18661923
kind,
18671924
params,
1925+
results,
18681926
src: String::new(),
18691927
locals,
18701928
block_storage: Vec::new(),
@@ -2585,10 +2643,69 @@ impl Bindgen for FunctionBindgen<'_, '_> {
25852643
0 => uwriteln!(self.src, "{target}.{func_name}({oper});"),
25862644
1 => {
25872645
let ret = self.locals.tmp("ret");
2646+
let ty = self.gen.type_name_with_qualifier(
2647+
func.results.iter_types().next().unwrap(),
2648+
true
2649+
);
2650+
uwriteln!(self.src, "{ty} {ret};");
2651+
let mut cases = Vec::with_capacity(self.results.len());
2652+
let mut oks = Vec::with_capacity(self.results.len());
2653+
let mut payload_is_void = false;
2654+
for (index, ty) in self.results.iter().enumerate() {
2655+
let TypeDefKind::Result(result) = &self.gen.resolve.types[*ty].kind else {
2656+
unreachable!();
2657+
};
2658+
let err_ty = if let Some(ty) = result.err {
2659+
self.gen.type_name_with_qualifier(&ty, true)
2660+
} else {
2661+
"None".to_owned()
2662+
};
2663+
let ty = self.gen.type_name_with_qualifier(&Type::Id(*ty), true);
2664+
let head = oks.concat();
2665+
let tail = oks.iter().map(|_| ")").collect::<Vec<_>>().concat();
2666+
cases.push(
2667+
format!(
2668+
"\
2669+
case {index}: {{
2670+
ret = {head}{ty}.err(({err_ty}) e.Value){tail};
2671+
break;
2672+
}}
2673+
"
2674+
)
2675+
);
2676+
oks.push(format!("{ty}.ok("));
2677+
payload_is_void = result.ok.is_none();
2678+
}
2679+
if !self.results.is_empty() {
2680+
self.src.push_str("try {\n");
2681+
}
2682+
let head = oks.concat();
2683+
let tail = oks.iter().map(|_| ")").collect::<Vec<_>>().concat();
2684+
let val = if payload_is_void {
2685+
uwriteln!(self.src, "{target}.{func_name}({oper});");
2686+
"new None()".to_owned()
2687+
} else {
2688+
format!("{target}.{func_name}({oper})")
2689+
};
25882690
uwriteln!(
25892691
self.src,
2590-
"var {ret} = {target}.{func_name}({oper});"
2692+
"{ret} = {head}{val}{tail};"
25912693
);
2694+
if !self.results.is_empty() {
2695+
self.gen.gen.needs_wit_exception = true;
2696+
let cases = cases.join("\n");
2697+
uwriteln!(
2698+
self.src,
2699+
r#"}} catch (WitException e) {{
2700+
switch (e.NestingLevel) {{
2701+
{cases}
2702+
2703+
default: throw new ArgumentException($"invalid nesting level: {{e.NestingLevel}}");
2704+
}}
2705+
}}
2706+
"#
2707+
);
2708+
}
25922709
results.push(ret);
25932710
}
25942711
_ => {
@@ -2626,17 +2743,51 @@ impl Bindgen for FunctionBindgen<'_, '_> {
26262743
if !matches!((self.gen.direction, self.kind), (Direction::Import, FunctionKind::Constructor(_))) {
26272744
match func.results.len() {
26282745
0 => (),
2629-
1 => uwriteln!(self.src, "return {};", operands[0]),
2746+
1 => {
2747+
let mut payload_is_void = false;
2748+
let mut previous = operands[0].clone();
2749+
let mut vars = Vec::with_capacity(self.results.len());
2750+
if let Direction::Import = self.gen.direction {
2751+
for ty in &self.results {
2752+
vars.push(previous.clone());
2753+
let tmp = self.locals.tmp("tmp");
2754+
uwrite!(
2755+
self.src,
2756+
"\
2757+
if ({previous}.IsOk) {{
2758+
var {tmp} = {previous}.AsOk;
2759+
"
2760+
);
2761+
previous = tmp;
2762+
let TypeDefKind::Result(result) = &self.gen.resolve.types[*ty].kind else {
2763+
unreachable!();
2764+
};
2765+
payload_is_void = result.ok.is_none();
2766+
}
2767+
}
2768+
uwriteln!(self.src, "return {};", if payload_is_void { "" } else { &previous });
2769+
for (level, var) in vars.iter().enumerate().rev() {
2770+
self.gen.gen.needs_wit_exception = true;
2771+
uwrite!(
2772+
self.src,
2773+
"\
2774+
}} else {{
2775+
throw new WitException({var}.AsErr, {level});
2776+
}}
2777+
"
2778+
);
2779+
}
2780+
}
26302781
_ => {
26312782
let results = operands.join(", ");
26322783
uwriteln!(self.src, "return ({results});")
26332784
}
26342785
}
26352786

2636-
// Close all the fixed blocks.
2637-
for _ in 0..self.fixed {
2638-
uwriteln!(self.src, "}}");
2639-
}
2787+
// Close all the fixed blocks.
2788+
for _ in 0..self.fixed {
2789+
uwriteln!(self.src, "}}");
2790+
}
26402791
}
26412792
}
26422793

@@ -3102,3 +3253,26 @@ fn dealias(resolve: &Resolve, mut id: TypeId) -> TypeId {
31023253
}
31033254
}
31043255
}
3256+
3257+
fn payload_and_results(resolve: &Resolve, ty: Type) -> (Option<Type>, Vec<TypeId>) {
3258+
fn recurse(resolve: &Resolve, ty: Type, results: &mut Vec<TypeId>) -> Option<Type> {
3259+
if let Type::Id(id) = ty {
3260+
if let TypeDefKind::Result(result) = &resolve.types[id].kind {
3261+
results.push(id);
3262+
if let Some(ty) = result.ok {
3263+
recurse(resolve, ty, results)
3264+
} else {
3265+
None
3266+
}
3267+
} else {
3268+
Some(ty)
3269+
}
3270+
} else {
3271+
Some(ty)
3272+
}
3273+
}
3274+
3275+
let mut results = Vec::new();
3276+
let payload = recurse(resolve, ty, &mut results);
3277+
(payload, results)
3278+
}

tests/runtime/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ mod resource_import_and_export;
3333
mod resource_into_inner;
3434
mod resource_with_lists;
3535
mod resources;
36+
mod results;
3637
mod rust_xcrate;
3738
mod smoke;
3839
mod strings;

tests/runtime/resources/wasm.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public static void Consume(IExports.X x)
1616
x.Dispose();
1717
}
1818

19-
public static Result<None, string> TestImports()
19+
public static void TestImports()
2020
{
2121
var y1 = new IImports.Y(10);
2222
Debug.Assert(y1.GetA() == 10);
@@ -36,9 +36,7 @@ public static Result<None, string> TestImports()
3636
var y5 = IImports.Y.Add(y3, 20);
3737
var y6 = IImports.Y.Add(y4, 30);
3838
Debug.Assert(y5.GetA() == 30);
39-
Debug.Assert(y6.GetA() == 50);
40-
41-
return Result<None, string>.ok(new None());
39+
Debug.Assert(y6.GetA() == 50);
4240
}
4341

4442
public class X : IExports.X, IExports.IX {

0 commit comments

Comments
 (0)