Repository: sxyazi/yazi
File: yazi-codegen/src/lib.rs (Rust)

Code before:
							self.#field_ident = self.#field_ident.deserialize_over_with(toml::Spanned::new(span, table))?;
						}
					});
				}

				assignments
			}
			_ => panic!("DeserializeOver1 only supports structs with named fields"),
		},
		_ => panic!("DeserializeOver1 only supports structs"),
	};

	quote! {
		impl #ident {
			#[inline]
			pub(crate) fn deserialize_over_with<'de>(mut self, table: toml::Spanned<toml::de::DeTable<'de>>) -> Result<Self, toml::de::Error> {
				let mut table = table.into_inner();
				#(#assignments)*
				Ok(self)
			}
		}
	}
	.into()
}

#[proc_macro_derive(DeserializeOver2)]
pub fn deserialize_over2(input: TokenStream) -> TokenStream {
	let DeriveInput { ident, data, .. } = parse_macro_input!(input as DeriveInput);

	let assignments = match data {
		Data::Struct(struct_) => match struct_.fields {
			Fields::Named(fields) => {
				let mut assignments = Vec::with_capacity(fields.named.len());

				for field in fields.named {
					let (ty, field_ident) = (field.ty, field.ident);
					let field_name = field_ident.as_ref().unwrap().to_string();

					assignments.push(quote! {
						if let Some(value) = table.remove(#field_name) {
							let de = serde::de::IntoDeserializer::into_deserializer(value);
							self.#field_ident = <#ty as serde::Deserialize>::deserialize(de)?;
						}
					});
				}

				assignments
			}
			_ => panic!("DeserializeOver2 only supports structs with named fields"),
		},
		_ => panic!("DeserializeOver2 only supports structs"),

Review comment (suggestion):
The new `DeserializeOver1/2` implementations remove consumed keys from `table` but never error if unknown keys remain. The previous shadow-struct approach would reject unknown fields during deserialization, so this is a behavior change that can hide typos in config keys. Consider validating that `table` is empty at the end and returning a deserialization error listing the unexpected keys.

Code after:
							let span = value.span();
							if let toml::de::DeValue::Table(table) = value.into_inner() {
								self.#field_ident = self.#field_ident.deserialize_over_with(toml::Spanned::new(span, table))?;
							}
						}
					});
				}

				assignments
			}
			_ => panic!("DeserializeOver1 only supports structs with named fields"),
		},
		_ => panic!("DeserializeOver1 only supports structs"),
	};

	quote! {
		impl #ident {
			#[inline]
			pub(crate) fn deserialize_over_with<'de>(mut self, table: toml::Spanned<toml::de::DeTable<'de>>) -> Result<Self, toml::de::Error> {
				use serde::{Deserialize, de::IntoDeserializer};

				let mut table = table.into_inner();
				#(#assignments)*

				Ok(self)
			}
		}
	}
	.into()
}

#[proc_macro_derive(DeserializeOver2)]
pub fn deserialize_over2(input: TokenStream) -> TokenStream {
	let DeriveInput { ident, data, .. } = parse_macro_input!(input as DeriveInput);

	let assignments = match data {
		Data::Struct(struct_) => match struct_.fields {
			Fields::Named(fields) => {
				let mut assignments = Vec::with_capacity(fields.named.len());

				for field in fields.named {
					let field_ident = field.ident;
					let field_name = field_ident.as_ref().unwrap().to_string();

					assignments.push(quote! {
						if let Some(value) = table.remove(#field_name) {
							self.#field_ident = <_>::deserialize(value.into_deserializer())?;
						}
					});
				}