From d2f9258f4c6945171d891b199636c2135877e5c6 Mon Sep 17 00:00:00 2001 From: Kostia R Date: Sat, 8 Aug 2026 13:46:09 +0300 Subject: [PATCH 1/2] Fix codegen and macro edge cases (#3158) Co-authored-by: Ziggy K --- sea-orm-macros/src/derives/active_enum.rs | 2 +- .../src/derives/from_query_result.rs | 16 ++++++------ sea-orm-macros/src/derives/model.rs | 10 +++---- .../tests/derive_active_enum_test.rs | 15 +++++++++++ .../derive_entity_model_column_name_test.rs | 26 +++++++++++++++++++ 5 files changed, 55 insertions(+), 14 deletions(-) diff --git a/sea-orm-macros/src/derives/active_enum.rs b/sea-orm-macros/src/derives/active_enum.rs index ceebbd0601..58207d40d5 100644 --- a/sea-orm-macros/src/derives/active_enum.rs +++ b/sea-orm-macros/src/derives/active_enum.rs @@ -556,7 +556,7 @@ impl ActiveEnum { impl std::convert::TryFrom<&str> for #ident { type Error = sea_orm::DbErr; - fn try_from(source: &str) -> std::result::Result { + fn try_from(source: &str) -> std::result::Result { match source { #( #variant_values => Ok(Self::#variant_idents), )* _ => Err(sea_orm::DbErr::Type(format!( diff --git a/sea-orm-macros/src/derives/from_query_result.rs b/sea-orm-macros/src/derives/from_query_result.rs index 80d0f686d9..88e732ecc3 100644 --- a/sea-orm-macros/src/derives/from_query_result.rs +++ b/sea-orm-macros/src/derives/from_query_result.rs @@ -47,7 +47,7 @@ impl ToTokens for TryFromQueryResultCheck<'_> { .to_owned() .unwrap_or_else(|| ident.unraw().to_string()); tokens.extend(quote! { - let #ident = match row.try_get_nullable(pre, #name) { + let #ident = match __sea_orm_row.try_get_nullable(__sea_orm_pre, #name) { Err(v @ sea_orm::TryGetError::DbErr(_)) => { return Err(v); } @@ -62,16 +62,16 @@ impl ToTokens for TryFromQueryResultCheck<'_> { } ItemType::Nested { prefix } => { let prefix = match (self.0, prefix) { - (_, Some(p)) => quote! { &format!("{pre}{}", #p) }, + (_, Some(p)) => quote! { &format!("{}{}", __sea_orm_pre, #p) }, (true, None) => { let name = ident.unraw().to_string(); - quote! { &format!("{pre}{}_", #name) } + quote! { &format!("{}{}_", __sea_orm_pre, #name) } } - (false, None) => quote! { pre }, + (false, None) => quote! { __sea_orm_pre }, }; tokens.extend(quote! { - let #ident = match sea_orm::FromQueryResult::from_query_result_nullable(row, #prefix) { + let #ident = match sea_orm::FromQueryResult::from_query_result_nullable(__sea_orm_row, #prefix) { Err(v @ sea_orm::TryGetError::DbErr(_)) => { return Err(v); } @@ -223,11 +223,11 @@ impl DeriveFromQueryResult { quote!( #[automatically_derived] impl #impl_generics sea_orm::FromQueryResult for #ident #ty_generics #where_clause { - fn from_query_result(row: &sea_orm::QueryResult, pre: &str) -> std::result::Result { - Ok(Self::from_query_result_nullable(row, pre)?) + fn from_query_result(__sea_orm_row: &sea_orm::QueryResult, __sea_orm_pre: &str) -> std::result::Result { + Ok(Self::from_query_result_nullable(__sea_orm_row, __sea_orm_pre)?) } - fn from_query_result_nullable(row: &sea_orm::QueryResult, pre: &str) -> std::result::Result { + fn from_query_result_nullable(__sea_orm_row: &sea_orm::QueryResult, __sea_orm_pre: &str) -> std::result::Result { #(#ident_try_init)* Ok(Self { diff --git a/sea-orm-macros/src/derives/model.rs b/sea-orm-macros/src/derives/model.rs index d29a69b062..f4386e4c37 100644 --- a/sea-orm-macros/src/derives/model.rs +++ b/sea-orm-macros/src/derives/model.rs @@ -125,8 +125,8 @@ impl DeriveModel { } else { let reader = quote! { let #field_ident = - row.try_get_nullable::>( - pre, + __sea_orm_row.try_get_nullable::>( + __sea_orm_pre, sea_orm::IdenStatic::as_str( &<::Entity as sea_orm::entity::EntityTrait>::Column::#column_ident @@ -169,11 +169,11 @@ impl DeriveModel { quote!( #[automatically_derived] impl sea_orm::FromQueryResult for #ident { - fn from_query_result(row: &sea_orm::QueryResult, pre: &str) -> std::result::Result { - Self::from_query_result_nullable(row, pre).map_err(Into::into) + fn from_query_result(__sea_orm_row: &sea_orm::QueryResult, __sea_orm_pre: &str) -> std::result::Result { + Self::from_query_result_nullable(__sea_orm_row, __sea_orm_pre).map_err(Into::into) } - fn from_query_result_nullable(row: &sea_orm::QueryResult, pre: &str) -> std::result::Result { + fn from_query_result_nullable(__sea_orm_row: &sea_orm::QueryResult, __sea_orm_pre: &str) -> std::result::Result { #(#field_readers)* if #all_null_check { diff --git a/sea-orm-macros/tests/derive_active_enum_test.rs b/sea-orm-macros/tests/derive_active_enum_test.rs index cdc5d13583..d650c40f50 100644 --- a/sea-orm-macros/tests/derive_active_enum_test.rs +++ b/sea-orm-macros/tests/derive_active_enum_test.rs @@ -74,6 +74,13 @@ pub enum TestEnum3 { HelloWorld, } +#[derive(Debug, EnumIter, DeriveActiveEnum, Eq, PartialEq)] +#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "error_variant")] +enum ErrorVariantEnum { + #[sea_orm(string_value = "error")] + Error, +} + #[test] fn derive_active_enum_value() { assert_eq!(TestEnum::DefaultVariant.to_value(), "defaultVariant"); @@ -96,6 +103,14 @@ fn derive_active_enum_value() { assert_eq!(TestEnum::CustomStringValue.to_value(), "CuStOmStRiNgVaLuE"); } +#[test] +fn derive_active_enum_with_error_variant() { + assert_eq!( + >::try_from("error"), + Ok(ErrorVariantEnum::Error) + ); +} + #[test] fn derive_active_enum_from_value() { assert_eq!( diff --git a/sea-orm-macros/tests/derive_entity_model_column_name_test.rs b/sea-orm-macros/tests/derive_entity_model_column_name_test.rs index bc821e472c..da2433073e 100644 --- a/sea-orm-macros/tests/derive_entity_model_column_name_test.rs +++ b/sea-orm-macros/tests/derive_entity_model_column_name_test.rs @@ -56,3 +56,29 @@ fn test_column_names() { Column::from_str("lAsTnAmE").expect("column from str should recognize column_name attr"); assert!(matches!(col, Column::LastName)); } + +#[allow(dead_code)] +mod query_parameter_name_collisions { + use sea_orm::entity::prelude::*; + use sea_orm_macros::{DeriveEntityModel, FromQueryResult}; + + #[derive(FromQueryResult)] + struct QueryResultProjection { + row: String, + pre: String, + } + + #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] + #[sea_orm(table_name = "query_parameter_name_collision")] + pub struct Model { + #[sea_orm(primary_key)] + id: i32, + row: String, + pre: String, + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] + pub enum Relation {} + + impl ActiveModelBehavior for ActiveModel {} +} From a22a7cae54317df833aa8a836f2d28e558b20276 Mon Sep 17 00:00:00 2001 From: Huliiiiii Date: Tue, 25 Aug 2026 11:31:49 +0800 Subject: [PATCH 2/2] Improve derive macro hygiene to prevent generated identifier conflicts --- sea-orm-macros/src/derives/active_model_ex.rs | 73 +++++----- sea-orm-macros/src/derives/entity_loader.rs | 135 ++++++++++-------- .../src/derives/from_query_result.rs | 42 ++++-- sea-orm-macros/src/derives/model.rs | 14 +- sea-orm-macros/src/derives/partial_model.rs | 62 ++++---- sea-orm-macros/src/derives/util.rs | 6 + sea-orm-macros/src/derives/value_type.rs | 26 ++-- 7 files changed, 206 insertions(+), 152 deletions(-) diff --git a/sea-orm-macros/src/derives/active_model_ex.rs b/sea-orm-macros/src/derives/active_model_ex.rs index b095a28367..5644498547 100644 --- a/sea-orm-macros/src/derives/active_model_ex.rs +++ b/sea-orm-macros/src/derives/active_model_ex.rs @@ -3,7 +3,8 @@ use super::attributes::compound_attr; use super::model_ex::infer_relation_name_from_entity; use super::util::{ CardinalityKind, CompoundKind, CompoundType, Junction, RelationColumns, async_token, - await_token, consume_meta, escape_rust_keyword, is_self_entity, trim_starting_raw_identifier, + await_token, clone_with_mixed_site_span, consume_meta, escape_rust_keyword, is_self_entity, + trim_starting_raw_identifier, }; use heck::ToUpperCamelCase; use proc_macro2::{Ident, Span, TokenStream}; @@ -1018,6 +1019,7 @@ impl BelongsToField<'_> { cardinality: CardinalityKind, ) -> syn::Result<()> { let ident = self.ident; + let field_binding = clone_with_mixed_site_span(ident); let related_entity = &self.compound_type.entity; let save_model = self.save_related_model(); let from_columns = self.from; @@ -1058,14 +1060,14 @@ impl BelongsToField<'_> { }; output.belongs_to_action.extend(quote! { - let #ident = match self.#ident.take() { + let #field_binding = match self.#ident.take() { ActiveBelongsTo::NotSet => ActiveBelongsTo::NotSet, #action_arms }; }); output.belongs_to_after_action.extend(quote! { - if #ident.is_set() { - model.#ident = #ident; + if #field_binding.is_set() { + model.#ident = #field_binding; } }); Ok(()) @@ -1073,10 +1075,11 @@ impl BelongsToField<'_> { fn expand_active_has_one_into(&self, output: &mut ActiveModelActionTokens) { let ident = self.ident; + let field_binding = clone_with_mixed_site_span(ident); let save_model = self.save_related_model(); output.belongs_to_action.extend(quote! { - let #ident = match std::mem::take(&mut self.#ident) { + let #field_binding = match std::mem::take(&mut self.#ident) { ActiveHasOne::Set(Some(model)) => { #save_model Some(model) @@ -1086,8 +1089,8 @@ impl BelongsToField<'_> { }; }); output.belongs_to_after_action.extend(quote! { - if let Some(#ident) = #ident { - model.#ident = ActiveHasOne::set(Some(#ident)); + if let Some(#field_binding) = #field_binding { + model.#ident = ActiveHasOne::set(Some(#field_binding)); } }); } @@ -1114,8 +1117,9 @@ struct HasOneField<'a> { impl HasOneField<'_> { fn has_one_before_action(&self) -> TokenStream { let ident = self.ident; + let field_binding = clone_with_mixed_site_span(ident); quote! { - let #ident = std::mem::take(&mut self.#ident); + let #field_binding = std::mem::take(&mut self.#ident); } } @@ -1135,6 +1139,7 @@ impl HasOneField<'_> { } }; let ident = self.ident; + let field_binding = clone_with_mixed_site_span(ident); let related_entity = self.entity; let delete_existing_child = quote! { @@ -1158,7 +1163,7 @@ impl HasOneField<'_> { }; quote! { - match #ident { + match #field_binding { ActiveHasOne::NotSet => {} ActiveHasOne::Set(Some(child)) => { let mut child = *child; @@ -1216,6 +1221,7 @@ impl HasManySelfField<'_> { quote!() }; let ident = self.ident; + let field_binding = clone_with_mixed_site_span(ident); let relation_variant = Ident::new(&self.relation_variant.value(), self.relation_variant.span()); let relation_variant = quote!(Relation::#relation_variant); @@ -1231,26 +1237,26 @@ impl HasManySelfField<'_> { }; let has_many_before_action = quote! { - let #ident = self.#ident.take(); + let #field_binding = self.#ident.take(); }; let has_many_action = quote! { - if #ident.is_replace() { + if #field_binding.is_replace() { for item in model.find_belongs_to_self(#relation_variant, db.get_database_backend())?.all(db)#await_? { - if !#ident.find(&item) { + if !#field_binding.find(&item) { #delete_associated_model } } } - model.#ident = #ident.empty_holder(); - for mut #ident in #ident.into_vec() { - #ident.set_parent_key_for_self_rev(&model, #relation_variant)?; - let #ident = if #ident.is_changed() { - #box_pin(#ident.action(action, db))#await_? + model.#ident = #field_binding.empty_holder(); + for mut #field_binding in #field_binding.into_vec() { + #field_binding.set_parent_key_for_self_rev(&model, #relation_variant)?; + let #field_binding = if #field_binding.is_changed() { + #box_pin(#field_binding.action(action, db))#await_? } else { - #ident + #field_binding }; - model.#ident.push(#ident); + model.#ident.push(#field_binding); } }; @@ -1286,6 +1292,7 @@ impl ManyToManyField<'_> { quote!() }; let ident = self.ident; + let field_binding = clone_with_mixed_site_span(ident); let junction_module = self.junction_module; let junction_entity = quote!(super::#junction_module::Entity); let (establish_links, delete_links) = match &self.kind { @@ -1301,13 +1308,13 @@ impl ManyToManyField<'_> { let delete_links = Ident::new(delete_links, ident.span()); let many_to_many_before_action = quote! { - let #ident = self.#ident.take(); + let #field_binding = self.#ident.take(); }; let many_to_many_action = quote! { - model.#ident = #ident.empty_holder(); + model.#ident = #field_binding.empty_holder(); // TODO: Batch save? - for item in #ident.into_vec() { + for item in #field_binding.into_vec() { let item = if item.is_update() && !item.is_changed() { item } else { @@ -1343,8 +1350,9 @@ struct HasManyField<'a> { impl HasManyField<'_> { fn has_many_before_action(&self) -> TokenStream { let ident = self.ident; + let field_binding = clone_with_mixed_site_span(ident); quote! { - let #ident = self.#ident.take(); + let #field_binding = self.#ident.take(); } } @@ -1356,6 +1364,7 @@ impl HasManyField<'_> { quote!() }; let ident = self.ident; + let field_binding = clone_with_mixed_site_span(ident); let related_entity = self.entity; let delete_associated_model = quote! { let mut item = item.into_active_model(); @@ -1366,22 +1375,22 @@ impl HasManyField<'_> { } }; quote! { - if #ident.is_replace() { + if #field_binding.is_replace() { for item in model.find_related(#related_entity).all(db)#await_? { - if !#ident.find(&item) { + if !#field_binding.find(&item) { #delete_associated_model } } } - model.#ident = #ident.empty_holder(); - for mut #ident in #ident.into_vec() { - #ident.set_parent_key(&model)?; - let #ident = if #ident.is_changed() { - #box_pin(#ident.action(action, db))#await_? + model.#ident = #field_binding.empty_holder(); + for mut #field_binding in #field_binding.into_vec() { + #field_binding.set_parent_key(&model)?; + let #field_binding = if #field_binding.is_changed() { + #box_pin(#field_binding.action(action, db))#await_? } else { - #ident + #field_binding }; - model.#ident.push(#ident); + model.#ident.push(#field_binding); } } } diff --git a/sea-orm-macros/src/derives/entity_loader.rs b/sea-orm-macros/src/derives/entity_loader.rs index a9bc84592d..bdcd30a3ef 100644 --- a/sea-orm-macros/src/derives/entity_loader.rs +++ b/sea-orm-macros/src/derives/entity_loader.rs @@ -1,3 +1,4 @@ +use super::util::clone_with_mixed_site_span; use proc_macro2::TokenStream; use quote::quote; use std::collections::{HashMap, HashSet}; @@ -267,9 +268,10 @@ impl EntityLoaderField { fn expand_select_one_into(&self, output: &mut EntityLoaderOutput) { let field = &self.field; + let field_binding = clone_with_mixed_site_span(field); let entity = &self.entity; - output.select_tuple_fields.push(quote!(#field)); + output.select_tuple_fields.push(quote!(#field_binding)); output.fetch_select_impl.extend(quote! { let select = if self.with.#field && self.nest.#field.is_empty() { self.with.#field = false; @@ -281,13 +283,14 @@ impl EntityLoaderField { }); output.assemble_one.extend(quote! { if loaded.#field { - model.#field = #field.map(Into::into).map(Box::new).into(); + model.#field = #field_binding.map(Into::into).map(Box::new).into(); } }); } fn expand_load_one_into(&self, output: &mut EntityLoaderOutput) { let field = &self.field; + let field_binding = clone_with_mixed_site_span(field); let entity = &self.entity; let await_ = if cfg!(feature = "async") { quote!(.await) @@ -300,32 +303,32 @@ impl EntityLoaderField { output.load_one.extend(quote! { if with.#field { - let #field = models.as_slice().load_one_ex(#entity, db)#await_?; - let #field = <#entity_module>::load_nest(#field, &nest.#field, db)#await_?; + let #field_binding = models.as_slice().load_one_ex(#entity, db)#await_?; + let #field_binding = <#entity_module>::load_nest(#field_binding, &nest.#field, db)#await_?; - for (model, #field) in models.iter_mut().zip(#field) { - model.#field = #field.map(Into::into).map(Box::new).into(); + for (model, #field_binding) in models.iter_mut().zip(#field_binding) { + model.#field = #field_binding.map(Into::into).map(Box::new).into(); } } }); output.load_one_nest.extend(quote! { if with.#field { - let #field = models.as_slice().load_one_ex(#entity, db)#await_?; + let #field_binding = models.as_slice().load_one_ex(#entity, db)#await_?; - for (model, #field) in models.iter_mut().zip(#field) { + for (model, #field_binding) in models.iter_mut().zip(#field_binding) { if let Some(model) = model.as_mut() { - model.#field = #field.map(Into::into).map(Box::new).into(); + model.#field = #field_binding.map(Into::into).map(Box::new).into(); } } } }); output.load_one_nest_nest.extend(quote! { if with.#field { - let #field = models.as_slice().load_one_ex(#entity, db)#await_?; + let #field_binding = models.as_slice().load_one_ex(#entity, db)#await_?; - for (models, #field) in models.iter_mut().zip(#field) { - for (model, #field) in models.iter_mut().zip(#field) { - model.#field = #field.map(Into::into).map(Box::new).into(); + for (models, #field_binding) in models.iter_mut().zip(#field_binding) { + for (model, #field_binding) in models.iter_mut().zip(#field_binding) { + model.#field = #field_binding.map(Into::into).map(Box::new).into(); } } } @@ -338,6 +341,7 @@ impl EntityLoaderField { relation_enum: &syn::LitStr, ) { let field = &self.field; + let field_binding = clone_with_mixed_site_span(field); let entity = &self.entity; let relation_enum = Ident::new(&relation_enum.value(), relation_enum.span()); let await_ = if cfg!(feature = "async") { @@ -351,44 +355,44 @@ impl EntityLoaderField { output.load_one.extend(quote! { if with.#field { - let #field = models.as_slice().load_one_ex_with_rel( + let #field_binding = models.as_slice().load_one_ex_with_rel( #entity, sea_orm::RelationTrait::def(&Relation::#relation_enum), db, )#await_?; - let #field = <#entity_module>::load_nest(#field, &nest.#field, db)#await_?; + let #field_binding = <#entity_module>::load_nest(#field_binding, &nest.#field, db)#await_?; - for (model, #field) in models.iter_mut().zip(#field) { - model.#field = #field.map(Into::into).map(Box::new).into(); + for (model, #field_binding) in models.iter_mut().zip(#field_binding) { + model.#field = #field_binding.map(Into::into).map(Box::new).into(); } } }); output.load_one_nest.extend(quote! { if with.#field { - let #field = models.as_slice().load_one_ex_with_rel( + let #field_binding = models.as_slice().load_one_ex_with_rel( #entity, sea_orm::RelationTrait::def(&Relation::#relation_enum), db, )#await_?; - for (model, #field) in models.iter_mut().zip(#field) { + for (model, #field_binding) in models.iter_mut().zip(#field_binding) { if let Some(model) = model.as_mut() { - model.#field = #field.map(Into::into).map(Box::new).into(); + model.#field = #field_binding.map(Into::into).map(Box::new).into(); } } } }); output.load_one_nest_nest.extend(quote! { if with.#field { - let #field = models.as_slice().load_one_ex_with_rel( + let #field_binding = models.as_slice().load_one_ex_with_rel( #entity, sea_orm::RelationTrait::def(&Relation::#relation_enum), db, )#await_?; - for (models, #field) in models.iter_mut().zip(#field) { - for (model, #field) in models.iter_mut().zip(#field) { - model.#field = #field.map(Into::into).map(Box::new).into(); + for (models, #field_binding) in models.iter_mut().zip(#field_binding) { + for (model, #field_binding) in models.iter_mut().zip(#field_binding) { + model.#field = #field_binding.map(Into::into).map(Box::new).into(); } } } @@ -397,6 +401,7 @@ impl EntityLoaderField { fn expand_load_many_into(&self, output: &mut EntityLoaderOutput) { let field = &self.field; + let field_binding = clone_with_mixed_site_span(field); let entity = &self.entity; let await_ = if cfg!(feature = "async") { quote!(.await) @@ -409,32 +414,32 @@ impl EntityLoaderField { output.load_many.extend(quote! { if with.#field { - let #field = models.as_slice().load_many_ex(#entity, db)#await_?; - let #field = <#entity_module>::load_nest_nest(#field, &nest.#field, db)#await_?; + let #field_binding = models.as_slice().load_many_ex(#entity, db)#await_?; + let #field_binding = <#entity_module>::load_nest_nest(#field_binding, &nest.#field, db)#await_?; - for (model, #field) in models.iter_mut().zip(#field) { - model.#field = #field.into(); + for (model, #field_binding) in models.iter_mut().zip(#field_binding) { + model.#field = #field_binding.into(); } } }); output.load_many_nest.extend(quote! { if with.#field { - let #field = models.as_slice().load_many_ex(#entity, db)#await_?; + let #field_binding = models.as_slice().load_many_ex(#entity, db)#await_?; - for (model, #field) in models.iter_mut().zip(#field) { + for (model, #field_binding) in models.iter_mut().zip(#field_binding) { if let Some(model) = model.as_mut() { - model.#field = #field.into(); + model.#field = #field_binding.into(); } } } }); output.load_many_nest_nest.extend(quote! { if with.#field { - let #field = models.as_slice().load_many_ex(#entity, db)#await_?; + let #field_binding = models.as_slice().load_many_ex(#entity, db)#await_?; - for (models, #field) in models.iter_mut().zip(#field) { - for (model, #field) in models.iter_mut().zip(#field) { - model.#field = #field.into(); + for (models, #field_binding) in models.iter_mut().zip(#field_binding) { + for (model, #field_binding) in models.iter_mut().zip(#field_binding) { + model.#field = #field_binding.into(); } } } @@ -447,6 +452,7 @@ impl EntityLoaderField { relation_enum: &syn::LitStr, ) { let field = &self.field; + let field_binding = clone_with_mixed_site_span(field); let entity = &self.entity; let relation_enum = Ident::new(&relation_enum.value(), relation_enum.span()); let await_ = if cfg!(feature = "async") { @@ -460,44 +466,44 @@ impl EntityLoaderField { output.load_many.extend(quote! { if with.#field { - let #field = models.as_slice().load_many_ex_with_rel( + let #field_binding = models.as_slice().load_many_ex_with_rel( #entity, sea_orm::RelationTrait::def(&Relation::#relation_enum), db, )#await_?; - let #field = <#entity_module>::load_nest_nest(#field, &nest.#field, db)#await_?; + let #field_binding = <#entity_module>::load_nest_nest(#field_binding, &nest.#field, db)#await_?; - for (model, #field) in models.iter_mut().zip(#field) { - model.#field = #field.into(); + for (model, #field_binding) in models.iter_mut().zip(#field_binding) { + model.#field = #field_binding.into(); } } }); output.load_many_nest.extend(quote! { if with.#field { - let #field = models.as_slice().load_many_ex_with_rel( + let #field_binding = models.as_slice().load_many_ex_with_rel( #entity, sea_orm::RelationTrait::def(&Relation::#relation_enum), db, )#await_?; - for (model, #field) in models.iter_mut().zip(#field) { + for (model, #field_binding) in models.iter_mut().zip(#field_binding) { if let Some(model) = model.as_mut() { - model.#field = #field.into(); + model.#field = #field_binding.into(); } } } }); output.load_many_nest_nest.extend(quote! { if with.#field { - let #field = models.as_slice().load_many_ex_with_rel( + let #field_binding = models.as_slice().load_many_ex_with_rel( #entity, sea_orm::RelationTrait::def(&Relation::#relation_enum), db, )#await_?; - for (models, #field) in models.iter_mut().zip(#field) { - for (model, #field) in models.iter_mut().zip(#field) { - model.#field = #field.into(); + for (models, #field_binding) in models.iter_mut().zip(#field_binding) { + for (model, #field_binding) in models.iter_mut().zip(#field_binding) { + model.#field = #field_binding.into(); } } } @@ -510,6 +516,7 @@ impl EntityLoaderField { relation_enum: &syn::LitStr, ) { let field = &self.field; + let field_binding = clone_with_mixed_site_span(field); let entity = &self.entity; let relation_enum = Ident::new(&relation_enum.value(), relation_enum.span()); let await_ = if cfg!(feature = "async") { @@ -520,10 +527,10 @@ impl EntityLoaderField { output.load_one.extend(quote! { if with.#field { - let #field = models.as_slice().load_self_ex(#entity, Relation::#relation_enum, db)#await_?; + let #field_binding = models.as_slice().load_self_ex(#entity, Relation::#relation_enum, db)#await_?; - for (model, #field) in models.iter_mut().zip(#field) { - model.#field = #field.map(Into::into).map(Box::new).into(); + for (model, #field_binding) in models.iter_mut().zip(#field_binding) { + model.#field = #field_binding.map(Into::into).map(Box::new).into(); } } }); @@ -535,6 +542,7 @@ impl EntityLoaderField { relation_enum: &syn::LitStr, ) { let field = &self.field; + let field_binding = clone_with_mixed_site_span(field); let entity = &self.entity; let relation_enum = Ident::new(&relation_enum.value(), relation_enum.span()); let await_ = if cfg!(feature = "async") { @@ -545,10 +553,10 @@ impl EntityLoaderField { output.load_many.extend(quote! { if with.#field { - let #field = models.as_slice().load_self_many_ex(#entity, Relation::#relation_enum, db)#await_?; + let #field_binding = models.as_slice().load_self_many_ex(#entity, Relation::#relation_enum, db)#await_?; - for (model, #field) in models.iter_mut().zip(#field) { - model.#field = #field.into(); + for (model, #field_binding) in models.iter_mut().zip(#field_binding) { + model.#field = #field_binding.into(); } } }); @@ -561,6 +569,7 @@ impl EntityLoaderField { reverse: bool, ) { let field = &self.field; + let field_binding = clone_with_mixed_site_span(field); let await_ = if cfg!(feature = "async") { quote!(.await) } else { @@ -569,32 +578,32 @@ impl EntityLoaderField { output.load_many.extend(quote! { if with.#field { - let #field = models.as_slice().load_self_via_ex(super::#junction_module::Entity, #reverse, db)#await_?; - let #field = EntityLoader::load_nest_nest(#field, &nest.#field, db)#await_?; + let #field_binding = models.as_slice().load_self_via_ex(super::#junction_module::Entity, #reverse, db)#await_?; + let #field_binding = EntityLoader::load_nest_nest(#field_binding, &nest.#field, db)#await_?; - for (model, #field) in models.iter_mut().zip(#field) { - model.#field = #field.into(); + for (model, #field_binding) in models.iter_mut().zip(#field_binding) { + model.#field = #field_binding.into(); } } }); output.load_many_nest.extend(quote! { if with.#field { - let #field = models.as_slice().load_self_via_ex(super::#junction_module::Entity, #reverse, db)#await_?; + let #field_binding = models.as_slice().load_self_via_ex(super::#junction_module::Entity, #reverse, db)#await_?; - for (model, #field) in models.iter_mut().zip(#field) { + for (model, #field_binding) in models.iter_mut().zip(#field_binding) { if let Some(model) = model.as_mut() { - model.#field = #field.into(); + model.#field = #field_binding.into(); } } } }); output.load_many_nest_nest.extend(quote! { if with.#field { - let #field = models.as_slice().load_self_via_ex(super::#junction_module::Entity, #reverse, db)#await_?; + let #field_binding = models.as_slice().load_self_via_ex(super::#junction_module::Entity, #reverse, db)#await_?; - for (models, #field) in models.iter_mut().zip(#field) { - for (model, #field) in models.iter_mut().zip(#field) { - model.#field = #field.into(); + for (models, #field_binding) in models.iter_mut().zip(#field_binding) { + for (model, #field_binding) in models.iter_mut().zip(#field_binding) { + model.#field = #field_binding.into(); } } } diff --git a/sea-orm-macros/src/derives/from_query_result.rs b/sea-orm-macros/src/derives/from_query_result.rs index 88e732ecc3..60447f7ace 100644 --- a/sea-orm-macros/src/derives/from_query_result.rs +++ b/sea-orm-macros/src/derives/from_query_result.rs @@ -1,7 +1,7 @@ use std::collections::{HashMap, hash_map::Entry}; use super::util::GetMeta; -use proc_macro2::{Ident, TokenStream}; +use proc_macro2::{Ident, Span, TokenStream}; use quote::{ToTokens, quote}; use syn::{ Data, DataStruct, DeriveInput, Error, Fields, Generics, Meta, ext::IdentExt, @@ -35,11 +35,18 @@ pub(super) struct FromQueryResultItem { /// since structs embedding the current one might have wrapped the current one in an `Option`. /// In this case, we do not want to swallow other errors, which are very likely to actually be /// programming errors that should be noticed (and fixed). -struct TryFromQueryResultCheck<'a>(bool, &'a FromQueryResultItem); +struct TryFromQueryResultCheck<'a> { + use_field_prefix: bool, + item: &'a FromQueryResultItem, + row: &'a Ident, + pre: &'a Ident, +} impl ToTokens for TryFromQueryResultCheck<'_> { fn to_tokens(&self, tokens: &mut TokenStream) { - let FromQueryResultItem { ident, typ, alias } = self.1; + let FromQueryResultItem { ident, typ, alias } = self.item; + let row = self.row; + let pre = self.pre; match typ { ItemType::Flat => { @@ -47,7 +54,7 @@ impl ToTokens for TryFromQueryResultCheck<'_> { .to_owned() .unwrap_or_else(|| ident.unraw().to_string()); tokens.extend(quote! { - let #ident = match __sea_orm_row.try_get_nullable(__sea_orm_pre, #name) { + let #ident = match #row.try_get_nullable(#pre, #name) { Err(v @ sea_orm::TryGetError::DbErr(_)) => { return Err(v); } @@ -61,17 +68,17 @@ impl ToTokens for TryFromQueryResultCheck<'_> { }); } ItemType::Nested { prefix } => { - let prefix = match (self.0, prefix) { - (_, Some(p)) => quote! { &format!("{}{}", __sea_orm_pre, #p) }, + let prefix = match (self.use_field_prefix, prefix) { + (_, Some(p)) => quote! { &format!("{}{}", #pre, #p) }, (true, None) => { let name = ident.unraw().to_string(); - quote! { &format!("{}{}_", __sea_orm_pre, #name) } + quote! { &format!("{}{}_", #pre, #name) } } - (false, None) => quote! { __sea_orm_pre }, + (false, None) => quote! { #pre }, }; tokens.extend(quote! { - let #ident = match sea_orm::FromQueryResult::from_query_result_nullable(__sea_orm_row, #prefix) { + let #ident = match sea_orm::FromQueryResult::from_query_result_nullable(#row, #prefix) { Err(v @ sea_orm::TryGetError::DbErr(_)) => { return Err(v); } @@ -205,7 +212,7 @@ impl DeriveFromQueryResult { Ok(self.impl_from_query_result(false)) } - pub(super) fn impl_from_query_result(&self, prefix: bool) -> TokenStream { + pub(super) fn impl_from_query_result(&self, use_field_prefix: bool) -> TokenStream { let Self { ident, generics, @@ -214,20 +221,27 @@ impl DeriveFromQueryResult { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + let row = Ident::new("row", Span::mixed_site()); + let pre = Ident::new("pre", Span::mixed_site()); let ident_try_init: Vec<_> = fields .iter() - .map(|s| TryFromQueryResultCheck(prefix, s)) + .map(|item| TryFromQueryResultCheck { + use_field_prefix, + item, + row: &row, + pre: &pre, + }) .collect(); let ident_try_assign: Vec<_> = fields.iter().map(TryFromQueryResultAssignment).collect(); quote!( #[automatically_derived] impl #impl_generics sea_orm::FromQueryResult for #ident #ty_generics #where_clause { - fn from_query_result(__sea_orm_row: &sea_orm::QueryResult, __sea_orm_pre: &str) -> std::result::Result { - Ok(Self::from_query_result_nullable(__sea_orm_row, __sea_orm_pre)?) + fn from_query_result(#row: &sea_orm::QueryResult, #pre: &str) -> std::result::Result { + Ok(Self::from_query_result_nullable(#row, #pre)?) } - fn from_query_result_nullable(__sea_orm_row: &sea_orm::QueryResult, __sea_orm_pre: &str) -> std::result::Result { + fn from_query_result_nullable(#row: &sea_orm::QueryResult, #pre: &str) -> std::result::Result { #(#ident_try_init)* Ok(Self { diff --git a/sea-orm-macros/src/derives/model.rs b/sea-orm-macros/src/derives/model.rs index f4386e4c37..42c7151b23 100644 --- a/sea-orm-macros/src/derives/model.rs +++ b/sea-orm-macros/src/derives/model.rs @@ -4,7 +4,7 @@ use super::{ }; use heck::ToUpperCamelCase; use itertools::izip; -use proc_macro2::TokenStream; +use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote}; use std::iter::FromIterator; use syn::{Attribute, Data, Expr, Ident, LitStr, Type}; @@ -106,6 +106,8 @@ impl DeriveModel { let column_idents = &self.column_idents; let field_types = &self.field_types; let ignore_attrs = &self.ignore_attrs; + let row = Ident::new("row", Span::mixed_site()); + let pre = Ident::new("pre", Span::mixed_site()); let (field_readers, field_values): (Vec, Vec) = izip!( field_idents.iter(), @@ -125,8 +127,8 @@ impl DeriveModel { } else { let reader = quote! { let #field_ident = - __sea_orm_row.try_get_nullable::>( - __sea_orm_pre, + #row.try_get_nullable::>( + #pre, sea_orm::IdenStatic::as_str( &<::Entity as sea_orm::entity::EntityTrait>::Column::#column_ident @@ -169,11 +171,11 @@ impl DeriveModel { quote!( #[automatically_derived] impl sea_orm::FromQueryResult for #ident { - fn from_query_result(__sea_orm_row: &sea_orm::QueryResult, __sea_orm_pre: &str) -> std::result::Result { - Self::from_query_result_nullable(__sea_orm_row, __sea_orm_pre).map_err(Into::into) + fn from_query_result(#row: &sea_orm::QueryResult, #pre: &str) -> std::result::Result { + Self::from_query_result_nullable(#row, #pre).map_err(Into::into) } - fn from_query_result_nullable(__sea_orm_row: &sea_orm::QueryResult, __sea_orm_pre: &str) -> std::result::Result { + fn from_query_result_nullable(#row: &sea_orm::QueryResult, #pre: &str) -> std::result::Result { #(#field_readers)* if #all_null_check { diff --git a/sea-orm-macros/src/derives/partial_model.rs b/sea-orm-macros/src/derives/partial_model.rs index c72882de4a..80ce136cc9 100644 --- a/sea-orm-macros/src/derives/partial_model.rs +++ b/sea-orm-macros/src/derives/partial_model.rs @@ -4,7 +4,7 @@ use heck::ToUpperCamelCase; use proc_macro2::{Span, TokenStream}; use quote::{ToTokens, format_ident, quote, quote_spanned}; use syn::{ - Expr, Meta, Type, ext::IdentExt, punctuated::Punctuated, spanned::Spanned, token::Comma, + Expr, Ident, Meta, Type, ext::IdentExt, punctuated::Punctuated, spanned::Spanned, token::Comma, }; use super::from_query_result::{ @@ -295,7 +295,15 @@ impl DerivePartialModel { } fn impl_partial_model(&self) -> TokenStream { - let select_ident = format_ident!("select"); + let select = Ident::new("select", Span::mixed_site()); + let pre = Ident::new("pre", Span::mixed_site()); + let nested_alias = Ident::new("nested_alias", Span::mixed_site()); + let col_alias = Ident::new("col_alias", Span::mixed_site()); + let alias = Ident::new("alias", Span::mixed_site()); + let col_expr = Ident::new("col_expr", Span::mixed_site()); + let casted = Ident::new("casted", Span::mixed_site()); + let prefix_binding = Ident::new("prefix", Span::mixed_site()); + let column_alias = Ident::new("ident", Span::mixed_site()); let DerivePartialModel { entity, model_alias, @@ -323,26 +331,26 @@ impl DerivePartialModel { let non_nested = match model_alias { Some(model_alias) => quote! { - let col_expr = sea_orm::sea_query::Expr::col((#model_alias, #column)); - let casted = sea_orm::ColumnTrait::select_as(&#column, col_expr); - sea_orm::QuerySelect::column_as(#select_ident, casted, col_alias) + let #col_expr = sea_orm::sea_query::Expr::col((#model_alias, #column)); + let #casted = sea_orm::ColumnTrait::select_as(&#column, #col_expr); + sea_orm::QuerySelect::column_as(#select, #casted, #col_alias) }, None => quote! { - sea_orm::QuerySelect::column_as(#select_ident, #column, col_alias) + sea_orm::QuerySelect::column_as(#select, #column, #col_alias) }, }; quote! { - let #select_ident = { - let col_alias = pre.map_or(#field.to_string(), |pre| format!("{pre}{}", #field)); - if let Some(nested_alias) = nested_alias { - let alias = sea_orm::sea_query::SeaRc::new(nested_alias); - let col_expr = sea_orm::sea_query::Expr::col( - (alias, #column) + let #select = { + let #col_alias = #pre.map_or(#field.to_string(), |#pre| format!("{}{}", #pre, #field)); + if let Some(#nested_alias) = #nested_alias { + let #alias = sea_orm::sea_query::SeaRc::new(#nested_alias); + let #col_expr = sea_orm::sea_query::Expr::col( + (#alias, #column) ); - let casted = sea_orm::ColumnTrait::select_as(&#column, col_expr); - sea_orm::QuerySelect::column_as(#select_ident, casted, col_alias) + let #casted = sea_orm::ColumnTrait::select_as(&#column, #col_expr); + sea_orm::QuerySelect::column_as(#select, #casted, #col_alias) } else { #non_nested } @@ -352,12 +360,12 @@ impl DerivePartialModel { ColumnAs::Expr { expr, field } => { let field = field.unraw().to_string(); - quote!(let #select_ident = - if let Some(prefix) = pre { - let ident = format!("{prefix}{}", #field); - sea_orm::QuerySelect::column_as(#select_ident, #expr, ident) + quote!(let #select = + if let Some(#prefix_binding) = #pre { + let #column_alias = format!("{}{}", #prefix_binding, #field); + sea_orm::QuerySelect::column_as(#select, #expr, #column_alias) } else { - sea_orm::QuerySelect::column_as(#select_ident, #expr, #field) + sea_orm::QuerySelect::column_as(#select, #expr, #field) }; ) } @@ -375,23 +383,23 @@ impl DerivePartialModel { }; let prefix_expr = match prefix { Some(p) => quote! { - Some(&if let Some(prefix) = pre { - format!("{prefix}{}", #p) + Some(&if let Some(#prefix_binding) = #pre { + format!("{}{}", #prefix_binding, #p) } else { #p.to_string() }) }, None => quote! { - Some(&if let Some(prefix) = pre { - format!("{prefix}{}_", #field_str) + Some(&if let Some(#prefix_binding) = #pre { + format!("{}{}_", #prefix_binding, #field_str) } else { format!("{}_", #field_str) }) }, }; - quote!(let #select_ident = + quote!(let #select = <#typ as sea_orm::PartialModelTrait>::select_cols_nested( - #select_ident, #prefix_expr, #alias_arg + #select, #prefix_expr, #alias_arg ); ) } @@ -401,9 +409,9 @@ impl DerivePartialModel { quote! { #[automatically_derived] impl sea_orm::PartialModelTrait for #ident { - fn select_cols_nested(#select_ident: S, pre: Option<&str>, nested_alias: Option<&'static str>) -> S { + fn select_cols_nested(#select: S, #pre: Option<&str>, #nested_alias: Option<&'static str>) -> S { #(#select_col_code_gen)* - #select_ident + #select } } } diff --git a/sea-orm-macros/src/derives/util.rs b/sea-orm-macros/src/derives/util.rs index 19a72c5350..cf74483a07 100644 --- a/sea-orm-macros/src/derives/util.rs +++ b/sea-orm-macros/src/derives/util.rs @@ -22,6 +22,12 @@ pub(crate) fn await_token() -> TokenStream { } } +pub(crate) fn clone_with_mixed_site_span(ident: &Ident) -> Ident { + let mut ident = ident.clone(); + ident.set_span(Span::mixed_site()); + ident +} + pub(crate) struct RelationColumns { pub(crate) columns: Vec, pub(crate) span: Span, diff --git a/sea-orm-macros/src/derives/value_type.rs b/sea-orm-macros/src/derives/value_type.rs index 33ae47484b..2e989a1dec 100644 --- a/sea-orm-macros/src/derives/value_type.rs +++ b/sea-orm-macros/src/derives/value_type.rs @@ -1,6 +1,6 @@ use super::attributes::value_type_attr; use super::value_type_match::{array_type_expr, can_try_from_u64, column_type_expr}; -use proc_macro2::TokenStream; +use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{Field, Ident, Type, punctuated::Punctuated, spanned::Spanned, token::Comma}; @@ -299,6 +299,12 @@ impl DeriveValueTypeString { Some(column_type) => column_type, None => "e!(String(sea_orm::sea_query::StringLen::None)), }; + let source = Ident::new("source", Span::mixed_site()); + let res = Ident::new("res", Span::mixed_site()); + let idx = Ident::new("idx", Span::mixed_site()); + let string = Ident::new("string", Span::mixed_site()); + let err = Ident::new("err", Span::mixed_site()); + let value = Ident::new("v", Span::mixed_site()); let impl_not_u8 = if cfg!(feature = "postgres-array") { quote!( @@ -312,21 +318,21 @@ impl DeriveValueTypeString { quote!( #[automatically_derived] impl std::convert::From<#name> for sea_orm::Value { - fn from(source: #name) -> Self { - #to_str(&source).into() + fn from(#source: #name) -> Self { + #to_str(&#source).into() } } #[automatically_derived] impl sea_orm::TryGetable for #name { - fn try_get_by(res: &sea_orm::QueryResult, idx: I) + fn try_get_by(#res: &sea_orm::QueryResult, #idx: I) -> std::result::Result { - let string = String::try_get_by(res, idx)?; - #from_str(&string).map_err(|err| { + let #string = String::try_get_by(#res, #idx)?; + #from_str(&#string).map_err(|#err| { sea_orm::TryGetError::DbErr(sea_orm::DbErr::TryIntoErr { from: "String", into: stringify!(#name), - source: std::sync::Arc::new(err), + source: std::sync::Arc::new(#err), }) }) } @@ -334,9 +340,9 @@ impl DeriveValueTypeString { #[automatically_derived] impl sea_orm::sea_query::ValueType for #name { - fn try_from(v: sea_orm::Value) -> std::result::Result { - let string = ::try_from(v)?; - #from_str(&string).map_err(|_| sea_orm::sea_query::ValueTypeErr) + fn try_from(#value: sea_orm::Value) -> std::result::Result { + let #string = ::try_from(#value)?; + #from_str(&#string).map_err(|_| sea_orm::sea_query::ValueTypeErr) } fn type_name() -> std::string::String {