From 02784109b9f99c1cb075083037fd9dc7d7ebfb93 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Sun, 9 Aug 2026 20:44:42 +0200 Subject: [PATCH] fix: avoid duplicate anonymous error types --- examples/async_more/src/org_example_more.rs | 19 +++++++++++-------- examples/async_ping/src/org_example_ping.rs | 6 +++--- examples/more/src/org_example_more.rs | 19 +++++++++++-------- examples/ping/src/org_example_ping.rs | 10 +++++----- varlink_generator/src/lib.rs | 10 +++++++--- varlink_generator/tests/generator.rs | 19 +++++++++++++++++++ .../tests/org.example.complex.rs_out | 17 ----------------- 7 files changed, 56 insertions(+), 44 deletions(-) diff --git a/examples/async_more/src/org_example_more.rs b/examples/async_more/src/org_example_more.rs index 324967e..fe14fbe 100644 --- a/examples/async_more/src/org_example_more.rs +++ b/examples/async_more/src/org_example_more.rs @@ -134,22 +134,25 @@ pub trait VarlinkCallError: varlink::CallTrait { } } impl VarlinkCallError for varlink::Call<'_> {} -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct r#State { + #[serde(skip_serializing_if = "Option::is_none")] pub r#start: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub r#progress: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub r#end: Option, } -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct TestMoreError_Args { pub r#reason: String, } -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct Ping_Reply { pub r#pong: String, } impl varlink::VarlinkReply for Ping_Reply {} -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct Ping_Args { pub r#ping: String, } @@ -159,10 +162,10 @@ pub trait Call_Ping: VarlinkCallError + Send { self.reply_struct(Ping_Reply { r#pong }.into()) } } -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct StopServing_Reply {} impl varlink::VarlinkReply for StopServing_Reply {} -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct StopServing_Args {} #[allow(dead_code)] pub trait Call_StopServing: VarlinkCallError + Send { @@ -170,12 +173,12 @@ pub trait Call_StopServing: VarlinkCallError + Send { self.reply_struct(varlink::Reply::parameters(None)) } } -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct TestMore_Reply { pub r#state: State, } impl varlink::VarlinkReply for TestMore_Reply {} -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct TestMore_Args { pub r#n: i64, } diff --git a/examples/async_ping/src/org_example_ping.rs b/examples/async_ping/src/org_example_ping.rs index cdb7dfa..2595dc7 100644 --- a/examples/async_ping/src/org_example_ping.rs +++ b/examples/async_ping/src/org_example_ping.rs @@ -132,16 +132,16 @@ pub trait VarlinkCallError: varlink::CallTrait { } } impl VarlinkCallError for varlink::Call<'_> {} -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct PingError_Args { pub r#parameter: i64, } -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct Ping_Reply { pub r#pong: String, } impl varlink::VarlinkReply for Ping_Reply {} -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct Ping_Args { pub r#ping: String, } diff --git a/examples/more/src/org_example_more.rs b/examples/more/src/org_example_more.rs index d6167e3..95e96e5 100644 --- a/examples/more/src/org_example_more.rs +++ b/examples/more/src/org_example_more.rs @@ -133,22 +133,25 @@ pub trait VarlinkCallError: varlink::CallTrait { } } impl VarlinkCallError for varlink::Call<'_> {} -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct r#State { + #[serde(skip_serializing_if = "Option::is_none")] pub r#start: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub r#progress: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub r#end: Option, } -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct TestMoreError_Args { pub r#reason: String, } -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct Ping_Reply { pub r#pong: String, } impl varlink::VarlinkReply for Ping_Reply {} -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct Ping_Args { pub r#ping: String, } @@ -159,10 +162,10 @@ pub trait Call_Ping: VarlinkCallError { } } impl Call_Ping for varlink::Call<'_> {} -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct StopServing_Reply {} impl varlink::VarlinkReply for StopServing_Reply {} -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct StopServing_Args {} #[allow(dead_code)] pub trait Call_StopServing: VarlinkCallError { @@ -171,12 +174,12 @@ pub trait Call_StopServing: VarlinkCallError { } } impl Call_StopServing for varlink::Call<'_> {} -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct TestMore_Reply { pub r#state: State, } impl varlink::VarlinkReply for TestMore_Reply {} -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct TestMore_Args { pub r#n: i64, } diff --git a/examples/ping/src/org_example_ping.rs b/examples/ping/src/org_example_ping.rs index dff802f..b69b4c6 100644 --- a/examples/ping/src/org_example_ping.rs +++ b/examples/ping/src/org_example_ping.rs @@ -131,16 +131,16 @@ pub trait VarlinkCallError: varlink::CallTrait { } } impl VarlinkCallError for varlink::Call<'_> {} -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct PingError_Args { pub r#parameter: i64, } -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct Ping_Reply { pub r#pong: String, } impl varlink::VarlinkReply for Ping_Reply {} -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct Ping_Args { pub r#ping: String, } @@ -151,10 +151,10 @@ pub trait Call_Ping: VarlinkCallError { } } impl Call_Ping for varlink::Call<'_> {} -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct Upgrade_Reply {} impl varlink::VarlinkReply for Upgrade_Reply {} -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] pub struct Upgrade_Args {} #[allow(dead_code)] pub trait Call_Upgrade: VarlinkCallError { diff --git a/varlink_generator/src/lib.rs b/varlink_generator/src/lib.rs index b552164..d4acef9 100644 --- a/varlink_generator/src/lib.rs +++ b/varlink_generator/src/lib.rs @@ -917,7 +917,12 @@ fn generate_error_code( ) { // Errors traits { - let mut error_structs_and_enums = TokenStream::new(); + // Scratch sink for the anonymous helper types produced as a side effect + // of `to_rust_string` below. We only need the type *names* here (for the + // reply_* signatures); the definitions themselves are emitted by + // `VError::to_tokenstream` alongside each error's `_Args` struct. Emitting + // them here too would duplicate every anonymous error helper type. + let mut discard = TokenStream::new(); let mut funcs = TokenStream::new(); { let mut errors = Vec::new(); @@ -1093,7 +1098,7 @@ fn generate_error_code( e.vtype .to_rust_string( format!("{}_Args_{}", t.name, e.name).as_ref(), - &mut error_structs_and_enums, + &mut discard, options, ) .as_ref(), @@ -1122,7 +1127,6 @@ fn generate_error_code( )); } ts.extend(quote!( - #error_structs_and_enums #[allow(dead_code)] pub trait VarlinkCallError: varlink::CallTrait { #funcs diff --git a/varlink_generator/tests/generator.rs b/varlink_generator/tests/generator.rs index d8a209a..dda4de4 100644 --- a/varlink_generator/tests/generator.rs +++ b/varlink_generator/tests/generator.rs @@ -77,3 +77,22 @@ fn test_generate() { let _ = std::fs::remove_file(path); let _ = std::fs::remove_file(path2); } + +// Regression: an `error` whose parameters contain anonymous nested types must +// emit each generated helper type exactly once. The error definitions are +// visited by two passes (the error-code/reply_* generation and the per-error +// `_Args` struct generation); a bug had both passes emit the anonymous helper +// types, producing duplicate `struct`/`enum` definitions that fail to compile. +#[test] +fn error_anonymous_types_emitted_once() { + let src = "interface org.example.test\n\ + error Foo (bar: (a: int, b: string), baz: (x: bool))\n"; + let code = varlink_generator::compile(src.to_string()) + .unwrap() + .to_string(); + + for def in ["struct r#Foo_Args_bar", "struct r#Foo_Args_baz"] { + let count = code.matches(def).count(); + assert_eq!(count, 1, "`{def}` was emitted {count} times, expected once"); + } +} diff --git a/varlink_generator/tests/org.example.complex.rs_out b/varlink_generator/tests/org.example.complex.rs_out index d9c1d99..1f464dc 100644 --- a/varlink_generator/tests/org.example.complex.rs_out +++ b/varlink_generator/tests/org.example.complex.rs_out @@ -129,23 +129,6 @@ impl From<&varlink::Reply> for ErrorKind { } } } -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] -pub struct r#ErrorFoo_Args_enum { - pub r#b: bool, - pub r#c: i64, - pub r#interface: Interface, -} -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] -pub enum r#ErrorFoo_Args_bar { - #[default] - r#type, - r#enum, - r#int, - r#bool, - r#string, - r#if, - r#let, -} #[allow(dead_code)] pub trait VarlinkCallError: varlink::CallTrait { fn reply_error_bar(&mut self) -> varlink::Result<()> {