diff --git a/.travis.yml b/.travis.yml index 459cc15147d4..740c69db2570 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,6 @@ script: (cd diesel && travis-cargo test -- --no-default-features --features "extras $BACKEND") fi && (cd diesel && travis-cargo test -- --no-default-features --features "extras with-deprecated $BACKEND") && - (cd diesel_derives && travis-cargo test -- --features "diesel/$BACKEND") && (cd diesel_derives2 && travis-cargo test -- --features "$BACKEND") && if [[ "$TRAVIS_RUST_VERSION" == nightly* ]]; then (cd diesel_derives2 && travis-cargo test -- --features "nightly $BACKEND") @@ -48,7 +47,6 @@ matrix: script: - (cd diesel && cargo rustc --no-default-features --features "lint unstable sqlite postgres mysql extras" -- -Zno-trans) - (cd diesel_cli && cargo rustc --no-default-features --features "lint sqlite postgres mysql" -- -Zno-trans) - - (cd diesel_derives && cargo rustc --no-default-features --features "lint dotenv sqlite postgres mysql" -- -Zno-trans) - (cd diesel_derives2 && cargo rustc --no-default-features --features "lint" -- -Zno-trans) - (cd diesel_migrations && cargo rustc --no-default-features --features "lint dotenv sqlite postgres mysql" -- -Zno-trans) - (cd diesel_infer_schema && cargo rustc --no-default-features --features "lint dotenv sqlite postgres mysql" -- -Zno-trans) diff --git a/bin/check b/bin/check index 69d308251031..0b43bfa7f1bf 100755 --- a/bin/check +++ b/bin/check @@ -7,10 +7,6 @@ check() { cargo rustc --quiet --features "lint $BACKENDS $1" -- -Zno-trans } -(cd diesel_derives && - check && - echo "✔ derives crate looks good!") - (cd diesel_derives2 && check && echo "✔ derives2 crate looks good!") diff --git a/bin/test b/bin/test index ffc8106beed1..563a98147360 100755 --- a/bin/test +++ b/bin/test @@ -26,7 +26,6 @@ else (cd diesel_infer_schema/infer_schema_macros && cargo test --features "$CLIPPY sqlite" $*) (cd diesel_infer_schema && cargo test --features "$CLIPPY sqlite" $*) (cd diesel_migrations && cargo test --features "$CLIPPY sqlite" $*) - (cd diesel_derives && cargo test --features "$CLIPPY diesel/sqlite" $*) (cd diesel_derives2 && cargo test --features "$CLIPPY sqlite" $*) (cd diesel_tests && cargo test --features "$CLIPPY sqlite" --no-default-features $*) @@ -34,7 +33,6 @@ else (cd diesel_infer_schema/infer_schema_macros && cargo test --features "$CLIPPY postgres" $*) (cd diesel_infer_schema && cargo test --features "$CLIPPY postgres" $*) (cd diesel_migrations && cargo test --features "$CLIPPY postgres" $*) - (cd diesel_derives && cargo test --features "$CLIPPY diesel/postgres" $*) (cd diesel_derives2 && cargo test --features "$CLIPPY postgres" $*) (cd diesel_cli && cargo test --features "$CLIPPY postgres" --no-default-features $*) (cd diesel_tests && cargo test --features "$CLIPPY postgres" --no-default-features $*) @@ -44,7 +42,6 @@ else (cd diesel_infer_schema/infer_schema_macros && cargo test --features "$CLIPPY mysql" $*) (cd diesel_infer_schema && cargo test --features "$CLIPPY mysql" $*) (cd diesel_migrations && cargo test --features "$CLIPPY mysql" $*) - (cd diesel_derives && cargo test --features "$CLIPPY diesel/mysql" $*) (cd diesel_derives2 && cargo test --features "$CLIPPY mysql" $*) (cd diesel_cli && cargo test --features "$CLIPPY mysql" --no-default-features $*) (cd diesel_tests && cargo test --features "$CLIPPY mysql" --no-default-features $*) diff --git a/diesel_compile_tests/tests/compile-fail/queryable_by_name_requires_table_name_or_sql_type_annotation.rs b/diesel_compile_tests/tests/compile-fail/queryable_by_name_requires_table_name_or_sql_type_annotation.rs deleted file mode 100644 index d1187c3a3bfd..000000000000 --- a/diesel_compile_tests/tests/compile-fail/queryable_by_name_requires_table_name_or_sql_type_annotation.rs +++ /dev/null @@ -1,9 +0,0 @@ -#[macro_use] extern crate diesel; - -#[derive(QueryableByName)] -//~^ ERROR Your struct must either be annotated with `#[table_name = "foo"]` or have all of its fields annotated with `#[sql_type = "Integer"]` -struct Foo { - a: i32, -} - -fn main() {} diff --git a/diesel_compile_tests/tests/ui/queryable_by_name_requires_table_name_or_sql_type_annotation.rs b/diesel_compile_tests/tests/ui/queryable_by_name_requires_table_name_or_sql_type_annotation.rs new file mode 100644 index 000000000000..935000545f30 --- /dev/null +++ b/diesel_compile_tests/tests/ui/queryable_by_name_requires_table_name_or_sql_type_annotation.rs @@ -0,0 +1,12 @@ +#[macro_use] extern crate diesel; + +#[derive(QueryableByName)] +struct Foo { + foo: i32, + bar: String, +} + +#[derive(QueryableByName)] +struct Bar(i32, String); + +fn main() {} diff --git a/diesel_compile_tests/tests/ui/queryable_by_name_requires_table_name_or_sql_type_annotation.stderr b/diesel_compile_tests/tests/ui/queryable_by_name_requires_table_name_or_sql_type_annotation.stderr new file mode 100644 index 000000000000..9197218fdd09 --- /dev/null +++ b/diesel_compile_tests/tests/ui/queryable_by_name_requires_table_name_or_sql_type_annotation.stderr @@ -0,0 +1,46 @@ +error: Cannot determine the SQL type of foo + --> $DIR/queryable_by_name_requires_table_name_or_sql_type_annotation.rs:5:5 + | +5 | foo: i32, + | ^^^ + | + = help: Your struct must either be annotated with `#[table_name = "foo"]` or have all of its fields annotated with `#[sql_type = "Integer"]` + +error: Cannot determine the SQL type of bar + --> $DIR/queryable_by_name_requires_table_name_or_sql_type_annotation.rs:6:5 + | +6 | bar: String, + | ^^^ + | + = help: Your struct must either be annotated with `#[table_name = "foo"]` or have all of its fields annotated with `#[sql_type = "Integer"]` + +error: All fields of tuple structs must be annotated with `#[column_name]` + --> $DIR/queryable_by_name_requires_table_name_or_sql_type_annotation.rs:10:12 + | +10 | struct Bar(i32, String); + | ^^^ + +error: Cannot determine the SQL type of field + --> $DIR/queryable_by_name_requires_table_name_or_sql_type_annotation.rs:10:12 + | +10 | struct Bar(i32, String); + | ^^^ + | + = help: Your struct must either be annotated with `#[table_name = "foo"]` or have all of its fields annotated with `#[sql_type = "Integer"]` + +error: All fields of tuple structs must be annotated with `#[column_name]` + --> $DIR/queryable_by_name_requires_table_name_or_sql_type_annotation.rs:10:17 + | +10 | struct Bar(i32, String); + | ^^^^^^ + +error: Cannot determine the SQL type of field + --> $DIR/queryable_by_name_requires_table_name_or_sql_type_annotation.rs:10:17 + | +10 | struct Bar(i32, String); + | ^^^^^^ + | + = help: Your struct must either be annotated with `#[table_name = "foo"]` or have all of its fields annotated with `#[sql_type = "Integer"]` + +error: aborting due to 6 previous errors + diff --git a/diesel_derives/Cargo.toml b/diesel_derives/Cargo.toml index 9db4766dd851..8b3a70ee9480 100644 --- a/diesel_derives/Cargo.toml +++ b/diesel_derives/Cargo.toml @@ -29,9 +29,6 @@ postgres = [] sqlite = [] mysql = [] -[[test]] -name = "tests" - [badges] travis-ci = { repository = "diesel-rs/diesel" } appveyor = { repository = "diesel-rs/diesel" } diff --git a/diesel_derives/src/attr.rs b/diesel_derives/src/attr.rs index de3cc43e9efa..37ced44bc843 100644 --- a/diesel_derives/src/attr.rs +++ b/diesel_derives/src/attr.rs @@ -36,31 +36,6 @@ impl Attr { } } - pub fn field_name(&self) -> &syn::Ident { - self.field_name.as_ref().unwrap_or(&self.field_position) - } - - pub fn column_name(&self) -> &syn::Ident { - self.column_name - .as_ref() - .or_else(|| self.field_name.as_ref()) - .expect( - "All fields of tuple structs must be annotated with `#[column_name=\"something\"]`", - ) - } - - pub fn sql_type(&self) -> Option<&syn::Ty> { - self.sql_type.as_ref() - } - - pub fn has_flag(&self, flag: &T) -> bool - where - T: ?Sized, - syn::Ident: PartialEq, - { - self.flags.iter().any(|f| f == flag) - } - fn field_kind(&self) -> &str { if is_option_ty(&self.ty) { "option" diff --git a/diesel_derives/src/lib.rs b/diesel_derives/src/lib.rs index 6c962898d067..22e69f38679b 100644 --- a/diesel_derives/src/lib.rs +++ b/diesel_derives/src/lib.rs @@ -30,18 +30,12 @@ mod attr; mod from_sql_row; mod insertable; mod model; -mod queryable_by_name; mod sql_type; mod util; use proc_macro::TokenStream; use syn::parse_derive_input; -#[proc_macro_derive(QueryableByName, attributes(table_name, column_name, sql_type, diesel))] -pub fn derive_queryable_by_name(input: TokenStream) -> TokenStream { - expand_derive(input, queryable_by_name::derive) -} - #[proc_macro_derive(Insertable, attributes(table_name, column_name))] pub fn derive_insertable(input: TokenStream) -> TokenStream { expand_derive(input, insertable::derive_insertable) diff --git a/diesel_derives/src/model.rs b/diesel_derives/src/model.rs index 328216a4b131..152400e1779b 100644 --- a/diesel_derives/src/model.rs +++ b/diesel_derives/src/model.rs @@ -50,11 +50,6 @@ impl Model { pub fn has_table_name_annotation(&self) -> bool { self.table_name_from_annotation.is_some() } - - pub fn dummy_const_name(&self, trait_name: &str) -> syn::Ident { - let model_name = self.name.as_ref().to_uppercase(); - format!("_IMPL_{}_FOR_{}", trait_name, model_name).into() - } } pub fn infer_association_name(name: &str) -> String { diff --git a/diesel_derives/src/queryable_by_name.rs b/diesel_derives/src/queryable_by_name.rs deleted file mode 100644 index 6f757aaaabd8..000000000000 --- a/diesel_derives/src/queryable_by_name.rs +++ /dev/null @@ -1,76 +0,0 @@ -use quote::Tokens; -use syn; - -use attr::Attr; -use model::Model; -use util::wrap_item_in_const; - -pub fn derive(item: syn::DeriveInput) -> Tokens { - let model = t!(Model::from_item(&item, "QueryableByName")); - - let generics = syn::aster::from_generics(model.generics.clone()) - .ty_param_id("__DB") - .build(); - let struct_ty = &model.ty; - - let attr_where_clause = model.attrs.iter().map(|attr| { - let attr_ty = &attr.ty; - if attr.has_flag("embed") { - quote!(#attr_ty: diesel::deserialize::QueryableByName<__DB>,) - } else { - let st = sql_type(attr, &model); - quote!( - #attr_ty: diesel::deserialize::FromSql<#st, __DB>, - ) - } - }); - - let build_expr = build_expr_for_model(&model); - - wrap_item_in_const( - model.dummy_const_name("QUERYABLE_BY_NAME"), - quote!( - impl#generics diesel::deserialize::QueryableByName<__DB> for #struct_ty where - __DB: diesel::backend::Backend, - #(#attr_where_clause)* - { - fn build<__R: diesel::row::NamedRow<__DB>>(row: &__R) -> diesel::deserialize::Result { - Ok(#build_expr) - } - } - ), - ) -} - -fn build_expr_for_model(model: &Model) -> Tokens { - let attr_exprs = model.attrs.iter().map(|attr| { - let name = attr.field_name(); - if attr.has_flag("embed") { - quote!(#name: diesel::deserialize::QueryableByName::build(row)?) - } else { - let column_name = attr.column_name(); - let st = sql_type(attr, model); - quote!(#name: diesel::row::NamedRow::get::<#st, _>(row, stringify!(#column_name))?) - } - }); - - quote!(Self { - #(#attr_exprs,)* - }) -} - -fn sql_type(attr: &Attr, model: &Model) -> Tokens { - let table_name = model.table_name(); - let column_name = attr.column_name(); - - match attr.sql_type() { - Some(st) => quote!(#st), - None => if model.has_table_name_annotation() { - quote!(diesel::dsl::SqlTypeOf<#table_name::#column_name>) - } else { - quote!(compile_error!( - "Your struct must either be annotated with `#[table_name = \"foo\"]` or have all of its fields annotated with `#[sql_type = \"Integer\"]`" - )) - }, - } -} diff --git a/diesel_derives/tests/test_helpers.rs b/diesel_derives/tests/test_helpers.rs deleted file mode 100644 index 9c5e96327923..000000000000 --- a/diesel_derives/tests/test_helpers.rs +++ /dev/null @@ -1,56 +0,0 @@ -use diesel::prelude::*; - -cfg_if! { - if #[cfg(feature = "sqlite")] { - pub type TestConnection = SqliteConnection; - - pub fn connection() -> TestConnection { - SqliteConnection::establish(":memory:").unwrap() - } - } else if #[cfg(feature = "postgres")] { - extern crate dotenv; - - use self::dotenv::dotenv; - use std::env; - - pub type TestConnection = PgConnection; - - pub fn connection() -> TestConnection { - dotenv().ok(); - let database_url = env::var("PG_DATABASE_URL") - .or_else(|_| env::var("DATABASE_URL")) - .expect("DATABASE_URL must be set in order to run tests"); - let conn = PgConnection::establish(&database_url).unwrap(); - conn.begin_test_transaction().unwrap(); - conn - } - } else if #[cfg(feature = "mysql")] { - extern crate dotenv; - - use self::dotenv::dotenv; - use std::env; - - pub type TestConnection = MysqlConnection; - - pub fn connection() -> TestConnection { - let conn = connection_no_transaction(); - conn.begin_test_transaction().unwrap(); - conn - } - - pub fn connection_no_transaction() -> TestConnection { - dotenv().ok(); - let database_url = env::var("MYSQL_UNIT_TEST_DATABASE_URL") - .or_else(|_| env::var("DATABASE_URL")) - .expect("DATABASE_URL must be set in order to run tests"); - MysqlConnection::establish(&database_url).unwrap() - } - } else { - compile_error!( - "At least one backend must be used to test this crate.\n \ - Pass argument `--features \"\"` with one or more of the following backends, \ - 'mysql', 'postgres', or 'sqlite'. \n\n \ - ex. cargo test --features \"mysql postgres sqlite\"\n" - ); - } -} diff --git a/diesel_derives/tests/tests.rs b/diesel_derives/tests/tests.rs deleted file mode 100644 index 3a33c3957966..000000000000 --- a/diesel_derives/tests/tests.rs +++ /dev/null @@ -1,9 +0,0 @@ -#[macro_use] -extern crate cfg_if; -#[macro_use] -extern crate diesel; -#[macro_use] -extern crate diesel_derives; - -mod queryable_by_name; -mod test_helpers; diff --git a/diesel_derives2/src/as_changeset.rs b/diesel_derives2/src/as_changeset.rs index 277dd60dfa78..5c13f9b1de9a 100644 --- a/diesel_derives2/src/as_changeset.rs +++ b/diesel_derives2/src/as_changeset.rs @@ -86,7 +86,7 @@ fn field_changeset_expr( table_name: syn::Ident, treat_none_as_null: bool, ) -> syn::Expr { - let field_access = &field.name; + let field_access = field.name.access(); let column_name = field.column_name(); if !treat_none_as_null && is_option_ty(&field.ty) { parse_quote!(self#field_access.as_ref().map(|x| #table_name::#column_name.eq(x))) diff --git a/diesel_derives2/src/associations.rs b/diesel_derives2/src/associations.rs index 2a22b1234eaf..23d8efc880da 100644 --- a/diesel_derives2/src/associations.rs +++ b/diesel_derives2/src/associations.rs @@ -39,7 +39,7 @@ fn derive_belongs_to( let foreign_key_field = model.find_column(foreign_key)?; let struct_name = model.name; - let foreign_key_access = &foreign_key_field.name; + let foreign_key_access = foreign_key_field.name.access(); let foreign_key_ty = inner_of_option_ty(&foreign_key_field.ty); let table_name = model.table_name(); diff --git a/diesel_derives2/src/field.rs b/diesel_derives2/src/field.rs index cc021e375884..801aa6853710 100644 --- a/diesel_derives2/src/field.rs +++ b/diesel_derives2/src/field.rs @@ -1,15 +1,18 @@ use proc_macro2::Span; use quote; -use syn; use syn::spanned::Spanned; +use syn; -use diagnostic_shim::*; use meta::*; +use util::*; pub struct Field { pub ty: syn::Type, pub name: FieldName, + pub span: Span, + pub sql_type: Option, column_name_from_attribute: Option, + flags: MetaItem, } impl Field { @@ -17,18 +20,30 @@ impl Field { let column_name_from_attribute = MetaItem::with_name(&field.attrs, "column_name").map(|m| m.expect_ident_value()); let name = match field.ident { - Some(x) => FieldName::Named(x), + Some(mut x) => { + // https://github.com/rust-lang/rust/issues/47983#issuecomment-362817105 + x.span = fix_span(x.span, Span::call_site()); + FieldName::Named(x) + } None => FieldName::Unnamed(syn::Index { index: index as u32, // https://github.com/rust-lang/rust/issues/47312 span: Span::call_site(), }), }; + let sql_type = MetaItem::with_name(&field.attrs, "sql_type") + .and_then(|m| m.ty_value().map_err(Diagnostic::emit).ok()); + let flags = MetaItem::with_name(&field.attrs, "diesel") + .unwrap_or_else(|| MetaItem::empty("diesel")); + let span = field.span(); Self { ty: field.ty.clone(), column_name_from_attribute, name, + sql_type, + flags, + span, } } @@ -37,8 +52,7 @@ impl Field { .unwrap_or_else(|| match self.name { FieldName::Named(x) => x, _ => { - self.ty - .span() + self.span .error( "All fields of tuple structs must be annotated with `#[column_name]`", ) @@ -47,6 +61,10 @@ impl Field { } }) } + + pub fn has_flag(&self, flag: &str) -> bool { + self.flags.has_flag(flag) + } } pub enum FieldName { @@ -55,27 +73,30 @@ pub enum FieldName { } impl FieldName { - pub fn for_assignment(&self) -> quote::Tokens { + pub fn assign(&self, expr: syn::Expr) -> syn::FieldValue { + let span = self.span(); + // Parens are to work around https://github.com/rust-lang/rust/issues/47311 + let tokens = quote_spanned!(span=> #self: (#expr)); + parse_quote!(#tokens) + } + + pub fn access(&self) -> quote::Tokens { + let span = self.span(); + // Span of the dot is important due to + // https://github.com/rust-lang/rust/issues/47312 + quote_spanned!(span=> .#self) + } + + pub fn span(&self) -> Span { match *self { - FieldName::Named(mut x) => { - // https://github.com/rust-lang/rust/issues/47311 - x.span = Span::call_site(); - quote!(#x) - } - FieldName::Unnamed(ref x) => quote!(#x), + FieldName::Named(x) => x.span, + FieldName::Unnamed(ref x) => x.span, } } } impl quote::ToTokens for FieldName { fn to_tokens(&self, tokens: &mut quote::Tokens) { - use proc_macro2::{Spacing, TokenNode, TokenTree}; - - // https://github.com/rust-lang/rust/issues/47312 - tokens.append(TokenTree { - span: Span::call_site(), - kind: TokenNode::Op('.', Spacing::Alone), - }); match *self { FieldName::Named(x) => x.to_tokens(tokens), FieldName::Unnamed(ref x) => x.to_tokens(tokens), diff --git a/diesel_derives2/src/identifiable.rs b/diesel_derives2/src/identifiable.rs index e86492d71f9a..4602ab6a3413 100644 --- a/diesel_derives2/src/identifiable.rs +++ b/diesel_derives2/src/identifiable.rs @@ -18,7 +18,7 @@ pub fn derive(item: syn::DeriveInput) -> Result { .primary_key_names .iter() .filter_map(|&pk| model.find_column(pk).map_err(Diagnostic::emit).ok()) - .map(|f| (&f.ty, &f.name)) + .map(|f| (&f.ty, f.name.access())) .unzip(); Ok(wrap_in_dummy_mod( diff --git a/diesel_derives2/src/lib.rs b/diesel_derives2/src/lib.rs index 4e5bacfc91e2..6c38ed6716ee 100644 --- a/diesel_derives2/src/lib.rs +++ b/diesel_derives2/src/lib.rs @@ -31,6 +31,7 @@ mod associations; mod identifiable; mod query_id; mod queryable; +mod queryable_by_name; use diagnostic_shim::*; @@ -60,6 +61,11 @@ pub fn derive_queryable(input: TokenStream) -> TokenStream { expand_derive(input, queryable::derive) } +#[proc_macro_derive(QueryableByName, attributes(table_name, column_name, sql_type, diesel))] +pub fn derive_queryable_by_name(input: TokenStream) -> TokenStream { + expand_derive(input, queryable_by_name::derive) +} + fn expand_derive( input: TokenStream, f: fn(syn::DeriveInput) -> Result, diff --git a/diesel_derives2/src/meta.rs b/diesel_derives2/src/meta.rs index e96875ecf0f1..abcb61f9bc93 100644 --- a/diesel_derives2/src/meta.rs +++ b/diesel_derives2/src/meta.rs @@ -2,7 +2,7 @@ use proc_macro2::Span; use syn; use syn::spanned::Spanned; -use diagnostic_shim::*; +use util::*; pub struct MetaItem { // Due to https://github.com/rust-lang/rust/issues/47941 @@ -25,6 +25,17 @@ impl MetaItem { Self::all_with_name(attrs, name).pop() } + pub fn empty(name: &str) -> Self { + Self { + pound_span: Span::call_site(), + meta: syn::Meta::List(syn::MetaList { + ident: name.into(), + paren_token: Default::default(), + nested: Default::default(), + }), + } + } + pub fn nested_item<'a>(&self, name: &'a str) -> Result { self.nested().and_then(|mut i| { i.find(|n| n.name() == name).ok_or_else(|| { @@ -117,7 +128,26 @@ impl MetaItem { self.meta.name() } + pub fn has_flag(&self, flag: &str) -> bool { + self.nested() + .map(|mut n| n.any(|m| m.expect_word() == flag)) + .unwrap_or_else(|e| { + e.emit(); + false + }) + } + + pub fn ty_value(&self) -> Result { + let mut str = self.lit_str_value()?.clone(); + str.span = self.span_or_pound_token(str.span); + str.parse().map_err(|_| str.span.error("Invalid Rust type")) + } + fn str_value(&self) -> Result { + self.lit_str_value().map(syn::LitStr::value) + } + + fn lit_str_value(&self) -> Result<&syn::LitStr, Diagnostic> { use syn::Meta::*; use syn::MetaNameValue; use syn::Lit::*; @@ -125,7 +155,7 @@ impl MetaItem { match self.meta { NameValue(MetaNameValue { lit: Str(ref s), .. - }) => Ok(s.value()), + }) => Ok(s), _ => Err(self.span().error(format!( "`{0}` must be in the form `{0} = \"value\"`", self.name() @@ -152,12 +182,7 @@ impl MetaItem { /// https://github.com/rust-lang/rust/issues/47941, /// returns the span of the pound token fn span_or_pound_token(&self, span: Span) -> Span { - let bad_span_debug = "Span(Span { lo: BytePos(0), hi: BytePos(0), ctxt: #0 })"; - if format!("{:?}", span) == bad_span_debug { - self.pound_span - } else { - span - } + fix_span(span, self.pound_span) } } diff --git a/diesel_derives2/src/model.rs b/diesel_derives2/src/model.rs index 9d79f22d9a6a..8a7dc60f5159 100644 --- a/diesel_derives2/src/model.rs +++ b/diesel_derives2/src/model.rs @@ -56,6 +56,10 @@ impl Model { .error(format!("No field with column name {}", column_name)) }) } + + pub fn has_table_name_attribute(&self) -> bool { + self.table_name_from_attribute.is_some() + } } pub fn camel_to_snake(name: &str) -> String { diff --git a/diesel_derives2/src/queryable.rs b/diesel_derives2/src/queryable.rs index f6c2601d486e..99ab2a7b3301 100644 --- a/diesel_derives2/src/queryable.rs +++ b/diesel_derives2/src/queryable.rs @@ -1,4 +1,3 @@ -use proc_macro2::Span; use quote; use syn; @@ -12,13 +11,8 @@ pub fn derive(item: syn::DeriveInput) -> Result { let field_ty = model.fields().iter().map(|f| &f.ty).collect::>(); let field_ty = &field_ty; let build_expr = model.fields().iter().enumerate().map(|(i, f)| { - let field_name = &f.name.for_assignment(); - let i: syn::Index = i.into(); - // Make sure `row` has a `def_site` span - let row = quote!(row); - // https://github.com/rust-lang/rust/issues/47311 - let span = Span::call_site(); - quote_spanned!(span=> #field_name: (#row.#i)) + let i = syn::Index::from(i); + f.name.assign(parse_quote!(row.#i)) }); let (_, ty_generics, _) = item.generics.split_for_impl(); diff --git a/diesel_derives2/src/queryable_by_name.rs b/diesel_derives2/src/queryable_by_name.rs new file mode 100644 index 000000000000..f319368c0b6e --- /dev/null +++ b/diesel_derives2/src/queryable_by_name.rs @@ -0,0 +1,95 @@ +use syn; +use quote; + +use field::*; +use model::*; +use util::*; + +pub fn derive(item: syn::DeriveInput) -> Result { + let model = Model::from_item(&item)?; + + let struct_name = item.ident; + let field_expr = model.fields().iter().map(|f| field_expr(f, &model)); + + let (_, ty_generics, ..) = item.generics.split_for_impl(); + let mut generics = item.generics.clone(); + generics + .params + .push(parse_quote!(__DB: diesel::backend::Backend)); + + for field in model.fields() { + let where_clause = generics.where_clause.get_or_insert(parse_quote!(where)); + let field_ty = &field.ty; + if field.has_flag("embed") { + where_clause + .predicates + .push(parse_quote!(#field_ty: QueryableByName<__DB>)); + } else { + let st = sql_type(field, &model); + where_clause + .predicates + .push(parse_quote!(#field_ty: diesel::deserialize::FromSql<#st, __DB>)); + } + } + + let (impl_generics, _, where_clause) = generics.split_for_impl(); + + Ok(wrap_in_dummy_mod( + model.dummy_mod_name("queryable_by_name"), + quote! { + use self::diesel::deserialize::{self, QueryableByName}; + use self::diesel::row::NamedRow; + + impl #impl_generics QueryableByName<__DB> + for #struct_name #ty_generics + #where_clause + { + fn build<__R: NamedRow<__DB>>(row: &__R) -> deserialize::Result { + std::result::Result::Ok(Self { + #(#field_expr,)* + }) + } + } + }, + )) +} + +fn field_expr(field: &Field, model: &Model) -> syn::FieldValue { + if field.has_flag("embed") { + field + .name + .assign(parse_quote!(QueryableByName::build(row)?)) + } else { + let column_name = field.column_name(); + let st = sql_type(field, model); + field + .name + .assign(parse_quote!(row.get::<#st, _>(stringify!(#column_name))?)) + } +} + +fn sql_type(field: &Field, model: &Model) -> syn::Type { + let table_name = model.table_name(); + let column_name = field.column_name(); + + match field.sql_type { + Some(ref st) => st.clone(), + None => if model.has_table_name_attribute() { + parse_quote!(diesel::dsl::SqlTypeOf<#table_name::#column_name>) + } else { + let field_name = match field.name { + FieldName::Named(ref x) => x.as_ref(), + _ => "field", + }; + field + .span + .error(format!("Cannot determine the SQL type of {}", field_name)) + .help( + "Your struct must either be annotated with `#[table_name = \"foo\"]` \ + or have all of its fields annotated with `#[sql_type = \"Integer\"]`", + ) + .emit(); + parse_quote!(()) + }, + } +} diff --git a/diesel_derives2/src/util.rs b/diesel_derives2/src/util.rs index 1772d9d0b3e9..e27949ecaa13 100644 --- a/diesel_derives2/src/util.rs +++ b/diesel_derives2/src/util.rs @@ -49,6 +49,15 @@ fn option_ty_arg(ty: &Type) -> Option<&Type> { } } +pub fn fix_span(maybe_bad_span: Span, fallback: Span) -> Span { + let bad_span_debug = "Span(Span { lo: BytePos(0), hi: BytePos(0), ctxt: #0 })"; + if format!("{:?}", maybe_bad_span) == bad_span_debug { + fallback + } else { + maybe_bad_span + } +} + #[cfg(not(feature = "nightly"))] fn root_span(span: Span) -> Span { span diff --git a/diesel_derives/tests/queryable_by_name.rs b/diesel_derives2/tests/queryable_by_name.rs similarity index 95% rename from diesel_derives/tests/queryable_by_name.rs rename to diesel_derives2/tests/queryable_by_name.rs index fb8cd06907b2..0b53e5188ade 100644 --- a/diesel_derives/tests/queryable_by_name.rs +++ b/diesel_derives2/tests/queryable_by_name.rs @@ -1,6 +1,6 @@ use diesel::*; -use test_helpers::connection; +use helpers::connection; #[cfg(feature = "mysql")] type IntSql = ::diesel::sql_types::BigInt; @@ -38,7 +38,10 @@ fn named_struct_definition() { fn tuple_struct() { #[derive(Debug, Clone, Copy, PartialEq, Eq, QueryableByName)] #[table_name = "my_structs"] - struct MyStruct(#[column_name(foo)] IntRust, #[column_name(bar)] IntRust); + struct MyStruct( + #[column_name = "foo"] IntRust, + #[column_name = "bar"] IntRust, + ); let conn = connection(); let data = sql_query("SELECT 1 AS foo, 2 AS bar").get_result(&conn); diff --git a/diesel_derives2/tests/tests.rs b/diesel_derives2/tests/tests.rs index 11bdd5d734f5..63d1b5b6769c 100644 --- a/diesel_derives2/tests/tests.rs +++ b/diesel_derives2/tests/tests.rs @@ -14,3 +14,4 @@ mod as_changeset; mod associations; mod identifiable; mod queryable; +mod queryable_by_name;