diff --git a/crates/go/src/lib.rs b/crates/go/src/lib.rs index 8fa6ee62d..b0c60d6d0 100644 --- a/crates/go/src/lib.rs +++ b/crates/go/src/lib.rs @@ -65,6 +65,20 @@ fn escape_go_keyword(name: String) -> String { } } +/// The bindings generated for variants require helper methods +/// that have a risk of colliding with bindings generated for +/// user-defined WIT types. +fn matches_reserved_variant_function_name(f: &str) -> bool { + matches!(f, "Tag") +} + +/// The bindings generated for resources require helper methods +/// that have a risk of colliding with bindings generated for +/// user-defined WIT types. +fn matches_reserved_resource_function_name(f: &str) -> bool { + matches!(f, "TakeHandle" | "SetHandle" | "Handle" | "Drop" | "OnDrop") +} + #[derive(Default, Debug, Copy, Clone)] pub enum Format { #[default] @@ -143,6 +157,10 @@ pub struct Opts { /// references more than one version of the WIT package. #[cfg_attr(feature = "clap", clap(long))] pub include_versions: bool, + + /// Mutes warnings (if any) + #[cfg_attr(feature = "clap", clap(long))] + pub quiet: bool, } impl Opts { @@ -235,6 +253,7 @@ struct Go { futures_and_streams: HashMap<(TypeId, bool), Option>, // Tracks which `future`/`stream` declarations have already been generated. generated_futures_and_streams: HashSet, + warnings: Vec, } impl Go { @@ -1044,6 +1063,16 @@ import ( } } + if !self.opts.quiet { + let warnings = self + .warnings + .iter() + .map(|w| format!("WARNING: {w}")) + .collect::>() + .join("\n"); + eprintln!("{warnings}"); + } + Ok(()) } } @@ -1068,7 +1097,7 @@ impl Go { let sig = resolve.wasm_signature(variant, func); let import_name = &func.name; let name = func.name.to_snake_case().replace('.', "_"); - let (camel, has_self) = func_declaration(resolve, func); + let (camel, has_self, comment) = self.func_declaration(resolve, func); let module = match interface { Some(name) => resolve.name_world_key(name), @@ -1263,6 +1292,7 @@ defer {PINNER}.Unpin() //go:wasmimport {module} {prefix}{import_name} func {raw_name}({params}) {results} +{comment} func {camel}({go_params}) {go_results} {{ {pinner} {return_area} @@ -1412,7 +1442,12 @@ func wasm_export_post_return_{name}(result {results}) {{ }; if self.opts.generate_stubs { - let (camel, has_self) = func_declaration(resolve, func); + let (camel, has_self, comment) = self.func_declaration(resolve, func); + let comment = if comment.is_empty() { + "// TODO: Implement".to_string() + } else { + format!("// TODO: Implement\n//\n{comment}") + }; let mut imports = BTreeSet::new(); let params = @@ -1424,8 +1459,7 @@ func wasm_export_post_return_{name}(result {results}) {{ .or_default() .extend(InterfaceData { code: format!( - r#" -// TODO: Implement + r#"{comment} func {camel}({params}) {results} {{ panic("not implemented") }} @@ -1638,6 +1672,56 @@ func wasm_export_{name}({params}) {results} {{ format!("{prefix}_{name}") } + + fn func_declaration(&mut self, resolve: &Resolve, func: &Function) -> (String, bool, String) { + match &func.kind { + FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => ( + func.item_name().to_upper_camel_case(), + false, + "".to_string(), + ), + FunctionKind::Constructor(ty) => { + let ty = resolve.types[*ty] + .name + .as_ref() + .unwrap() + .to_upper_camel_case(); + (format!("Make{ty}"), false, "".to_string()) + } + FunctionKind::Method(ty) | FunctionKind::AsyncMethod(ty) => { + let ty = resolve.types[*ty] + .name + .as_ref() + .unwrap() + .to_upper_camel_case(); + let mut camel = func.item_name().to_upper_camel_case(); + let mut comment = "".to_string(); + if matches_reserved_resource_function_name(&camel) { + self.warnings.push(format!( + "The method `{camel}` for resource `{ty}` conflicts with a method reserved by the bindings generator; it will be renamed to `{camel}_()`" + )); + comment = format!( + r#"// This is the user-defined method associated with the `{ty}` resource. + // This is suffixed with `_` because it collides with + // the `{camel}` function, which is reserved by the + // bindings generator. "# + ); + camel.push('_'); + } + + (format!("(self *{ty}) {camel}"), true, comment) + } + FunctionKind::Static(ty) | FunctionKind::AsyncStatic(ty) => { + let ty = resolve.types[*ty] + .name + .as_ref() + .unwrap() + .to_upper_camel_case(); + let camel = func.item_name().to_upper_camel_case(); + (format!("{ty}{camel}"), false, "".to_string()) + } + } + } } struct FunctionGenerator<'a> { @@ -1952,6 +2036,10 @@ for index := 0; index < int({length}); index++ {{ } FunctionKind::Method(_) | FunctionKind::AsyncMethod(_) => { let target = &operands[0]; + let mut name = name.clone(); + if matches_reserved_resource_function_name(&name) { + name.push('_'); + } let args = operands[1..].join(", "); format!("({target}).{name}({args})") } @@ -2464,7 +2552,7 @@ default: }; format!( - "case {ty}{name}: + "case {ty}_{name}: {set_payload} {block} {assignments} @@ -2941,7 +3029,7 @@ func (self *{camel}) OnDrop() {{}} .map(|(i, flag)| { let docs = format_docs(&flag.docs); let flag = flag.name.to_upper_camel_case(); - format!("{docs}{name}{flag} {repr} = 1 << {i}") + format!("{docs}{name}_{flag} {repr} = 1 << {i}") }) .collect::>() .join("\n"); @@ -2990,7 +3078,7 @@ const ( .map(|(i, case)| { let docs = format_docs(&case.docs); let case = case.name.to_upper_camel_case(); - format!("{docs}{name}{case} {repr} = {i}") + format!("{docs}{name}_{case} {repr} = {i}") }) .collect::>() .join("\n"); @@ -3001,16 +3089,32 @@ const ( .filter_map(|case| { case.ty.map(|ty| { let case = case.name.to_upper_camel_case(); + let mut case_func = case.clone(); let ty = self.type_name(self.resolve, ty); - format!( - r#"func (self {name}) {case}() {ty} {{ - if self.tag != {name}{case} {{ + let mangle_comment = if matches_reserved_variant_function_name(&case) { + self.generator.warnings.push(format!("The payload getter for case `{case}` of variant `{name}` conflicts with a method reserved by the bindings generator; it will be renamed to `{case}_()`")); + + case_func.push('_'); + + format!(r#"// This retrieves the payload of the `{case}` case + // of the `{name}` WIT variant. + // + // This is suffixed with `_` because it collides with + // the `{case}` function, which is reserved by the + // bindings generator."#) + + } else {String::new()}; + + format!( + r#"{mangle_comment} + func (self {name}) {case_func}() {ty} {{ + if self.tag != {name}_{case} {{ panic("tag mismatch") }} return self.value.({ty}) }} "# - ) + ) }) }) .collect::>() @@ -3029,7 +3133,7 @@ const ( let case = case.name.to_upper_camel_case(); format!( r#"func Make{name}{case}({param}) {name} {{ - return {name}{{{name}{case}, {value}}} + return {name}{{{name}_{case}, {value}}} }} "# ) @@ -3107,7 +3211,7 @@ func (self {name}) Tag() {repr} {{ .map(|(i, case)| { let docs = format_docs(&case.docs); let case = case.name.to_upper_camel_case(); - format!("{docs}{name}{case} {repr} = {i}") + format!("{docs}{name}_{case} {repr} = {i}") }) .collect::>() .join("\n"); @@ -3325,40 +3429,6 @@ fn any(resolve: &Resolve, ty: Type, fun: &dyn Fn(Type) -> bool) -> bool { } } -fn func_declaration(resolve: &Resolve, func: &Function) -> (String, bool) { - match &func.kind { - FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => { - (func.item_name().to_upper_camel_case(), false) - } - FunctionKind::Constructor(ty) => { - let ty = resolve.types[*ty] - .name - .as_ref() - .unwrap() - .to_upper_camel_case(); - (format!("Make{ty}"), false) - } - FunctionKind::Method(ty) | FunctionKind::AsyncMethod(ty) => { - let ty = resolve.types[*ty] - .name - .as_ref() - .unwrap() - .to_upper_camel_case(); - let camel = func.item_name().to_upper_camel_case(); - (format!("(self *{ty}) {camel}"), true) - } - FunctionKind::Static(ty) | FunctionKind::AsyncStatic(ty) => { - let ty = resolve.types[*ty] - .name - .as_ref() - .unwrap() - .to_upper_camel_case(); - let camel = func.item_name().to_upper_camel_case(); - (format!("{ty}{camel}"), false) - } - } -} - fn maybe_gofmt<'a>(format: Format, code: &'a [u8]) -> Cow<'a, [u8]> { thread::scope(|s| { if let Format::True = format diff --git a/tests/codegen/issue1703.wit b/tests/codegen/issue1703.wit new file mode 100644 index 000000000..e9b6479b5 --- /dev/null +++ b/tests/codegen/issue1703.wit @@ -0,0 +1,217 @@ +package foo:bar; + +world test { + export reserved-function-names; + import resource-collisions; + import record-collisions; + import variant-collisions; + import enum-collisions; + import alias-collisions; + import flags-collisions; +} + +interface reserved-function-names { + variant a { + tag(string), + } + + resource b { + take-handle: func(); + set-handle: func(); + handle: func(); + drop: func(); + on-drop: func(); + } +} + +interface resource-collisions { + // Variant + resource a-b-c { + f: func(); + } + variant a-b { + c, + } + + // Enum + resource z-y-x { + f: func(); + } + enum z-y { + x, + } + + // Flags + resource q-r-s { + f: func(); + } + flags q-r { + s, + } +} + +interface record-collisions { + // Variant + record a-b-c { + foo: string, + } + variant a-b { + c, + } + + // Enum + record z-y-x { + foo: string, + } + enum z-y { + x, + } + + // Flags + record q-r-s { + foo: string, + } + flags q-r { + s, + } +} + +interface variant-collisions { + // Enum + variant a-b-c { + d, + } + enum a-b { + c, + } + + // Flags + variant z-y-x { + w, + } + flags z-y { + x, + } + + // Record + variant q-r { + s, + } + record q-r-s { + foo: string, + } + + // Resource + variant d-e { + f, + } + resource d-e-f { + f: func(); + } + + // Alias + variant h-i { + j, + } + type h-i-j = string; +} + +interface enum-collisions { + // Variant + enum a-b { + c, + } + variant a-b-c { + d, + } + + // Flags + enum x-y-z { + w, + } + flags x-y { + z, + } + + // Record + enum q-r { + s, + } + record q-r-s { + foo: string + } + + // Resource + enum t-u { + v, + } + resource t-u-v { + f: func(); + } + + // Alias + enum h-i { + j, + } + type h-i-j = string; +} + +interface alias-collisions { + // Variant + type a-b-c = string; + variant a-b { + c(a-b-c), + } + + // Enum + type z-y-x = string; + enum z-y { + x, + } + + // Flags + type q-r-s = string; + flags q-r { + s, + } +} + +interface flags-collisions { + // Variant + flags a-b-c { + d, + } + variant a-b { + c, + } + + // Enum + flags d-e-f { + g, + } + enum d-e { + f, + } + + // Record + flags x-y { + z, + } + record x-y-z { + foo: string, + } + + // Resource + flags h-i { + j, + } + resource h-i-j { + f: func(); + } + + // Alias + flags l-m { + n, + } + type l-m-n = string; +} \ No newline at end of file diff --git a/tests/runtime/flavorful/runner.go b/tests/runtime/flavorful/runner.go index d515c49d3..96555aadd 100644 --- a/tests/runtime/flavorful/runner.go +++ b/tests/runtime/flavorful/runner.go @@ -26,7 +26,7 @@ func Run() { assertEqual(test.FListInVariant3(Some[string]("input3")).Some(), "output3") - assertEqual(test.ErrnoResult().Err(), test.MyErrnoB) + assertEqual(test.ErrnoResult().Err(), test.MyErrno_B) test.ErrnoResult().Ok() { @@ -42,7 +42,7 @@ func Run() { Ok[Unit, Unit](Unit{}), Err[Unit, Unit](Unit{}), }, - []test.MyErrno{test.MyErrnoSuccess, test.MyErrnoA}, + []test.MyErrno{test.MyErrno_Success, test.MyErrno_A}, ) assert(slices.Equal(a, []bool{false, true})) assert(slices.Equal(b, []Result[Unit, Unit]{ @@ -50,7 +50,7 @@ func Run() { Ok[Unit, Unit](Unit{}), }, )) - assert(slices.Equal(c, []test.MyErrno{test.MyErrnoA, test.MyErrnoB})) + assert(slices.Equal(c, []test.MyErrno{test.MyErrno_A, test.MyErrno_B})) } } diff --git a/tests/runtime/flavorful/test.go b/tests/runtime/flavorful/test.go index e33748c37..9a828dfba 100644 --- a/tests/runtime/flavorful/test.go +++ b/tests/runtime/flavorful/test.go @@ -56,7 +56,7 @@ var first bool = true func ErrnoResult() Result[Unit, MyErrno] { if first { first = false - return Err[Unit, MyErrno](MyErrnoB) + return Err[Unit, MyErrno](MyErrno_B) } else { return Ok[Unit, MyErrno](Unit{}) } @@ -92,13 +92,13 @@ func ListOfVariants(bools []bool, results []Result[Unit, Unit], enums []MyErrno) if len(enums) != 2 { panic("trouble") } - if enums[0] != MyErrnoSuccess { + if enums[0] != MyErrno_Success { panic("trouble") } - if enums[1] != MyErrnoA { + if enums[1] != MyErrno_A { panic("trouble") } return []bool{false, true}, []Result[Unit, Unit]{Err[Unit, Unit](Unit{}), Ok[Unit, Unit](Unit{})}, - []MyErrno{MyErrnoA, MyErrnoB} + []MyErrno{MyErrno_A, MyErrno_B} } diff --git a/tests/runtime/map/runner.go b/tests/runtime/map/runner.go index b4d5055cf..4055569a7 100644 --- a/tests/runtime/map/runner.go +++ b/tests/runtime/map/runner.go @@ -136,11 +136,11 @@ func testNestedRoundtrip() { func testVariantRoundtrip() { m := test.NamesById{1: "one"} asMap := test.VariantRoundtrip(test.MakeMapOrStringAsMap(m)) - assertEqual(asMap.Tag(), test.MapOrStringAsMap) + assertEqual(asMap.Tag(), test.MapOrString_AsMap) assertEqual(asMap.AsMap()[1], "one") asStr := test.VariantRoundtrip(test.MakeMapOrStringAsString("hello")) - assertEqual(asStr.Tag(), test.MapOrStringAsString) + assertEqual(asStr.Tag(), test.MapOrString_AsString) assertEqual(asStr.AsString(), "hello") } diff --git a/tests/runtime/results/leaf.go b/tests/runtime/results/leaf.go index 5476b0b7a..fee4d62e4 100644 --- a/tests/runtime/results/leaf.go +++ b/tests/runtime/results/leaf.go @@ -16,7 +16,7 @@ func StringError(x float32) Result[float32, string] { func EnumError(x float32) Result[float32, E] { if x == 0.0 { - return Err[float32, E](EA) + return Err[float32, E](E_A) } else { return Ok[float32, E](x) } @@ -36,9 +36,9 @@ func VariantError(x float32) Result[float32, E3] { if x == 0.0 { return Err[float32, E3](MakeE3E2(E2{420, 0})) } else if x == 1.0 { - return Err[float32, E3](MakeE3E1(EB)) + return Err[float32, E3](MakeE3E1(E_B)) } else if x == 2.0 { - return Err[float32, E3](MakeE3E1(EC)) + return Err[float32, E3](MakeE3E1(E_C)) } else { return Ok[float32, E3](x) } diff --git a/tests/runtime/results/runner.go b/tests/runtime/results/runner.go index 2fe10569c..5830fa8c4 100644 --- a/tests/runtime/results/runner.go +++ b/tests/runtime/results/runner.go @@ -16,7 +16,7 @@ func Run() { { val := test.EnumError(0.0) - assertEqual(val.Err(), test.EA) + assertEqual(val.Err(), test.E_A) val = test.EnumError(1.0) assertEqual(val.Ok(), 1.0) @@ -40,11 +40,11 @@ func Run() { a = test.VariantError(1.0) b = a.Err() - assertEqual(b.E1(), test.EB) + assertEqual(b.E1(), test.E_B) a = test.VariantError(2.0) b = a.Err() - assertEqual(b.E1(), test.EC) + assertEqual(b.E1(), test.E_C) } { diff --git a/tests/runtime/variants/runner.go b/tests/runtime/variants/runner.go index 051a2c152..b3fe5539a 100644 --- a/tests/runtime/variants/runner.go +++ b/tests/runtime/variants/runner.go @@ -73,19 +73,19 @@ func Run() { test.MakeZ3B(), test.MakeZ4B(), }) - assertEqual(a.Tag(), test.Z1B) - assertEqual(b.Tag(), test.Z2B) - assertEqual(c.Tag(), test.Z3B) - assertEqual(d.Tag(), test.Z4B) + assertEqual(a.Tag(), test.Z1_B) + assertEqual(b.Tag(), test.Z2_B) + assertEqual(c.Tag(), test.Z3_B) + assertEqual(d.Tag(), test.Z4_B) } test.VariantTypedefs(None[uint32](), false, Err[uint32, Unit](Unit{})) { - a, b, c := test.VariantEnums(true, Ok[Unit, Unit](Unit{}), test.MyErrnoSuccess) + a, b, c := test.VariantEnums(true, Ok[Unit, Unit](Unit{}), test.MyErrno_Success) assertEqual(a, true) b.Ok() - assertEqual(c, test.MyErrnoSuccess) + assertEqual(c, test.MyErrno_Success) } }