strip out proc-macro-hack

This commit is contained in:
Austin Bonander 2020-01-14 12:57:55 -08:00 committed by Austin Bonander
parent 28ed854b03
commit da5c538d22
11 changed files with 190 additions and 107 deletions

2
Cargo.lock generated
View File

@ -1308,7 +1308,6 @@ dependencies = [
"dotenv 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "dotenv 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
"sqlx-core 0.1.4", "sqlx-core 0.1.4",
"sqlx-macros 0.1.1", "sqlx-macros 0.1.1",
] ]
@ -1368,7 +1367,6 @@ dependencies = [
"async-std 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "async-std 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"dotenv 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "dotenv 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"sqlx-core 0.1.4", "sqlx-core 0.1.4",

View File

@ -29,7 +29,7 @@ all-features = true
[features] [features]
default = [ "macros" ] default = [ "macros" ]
macros = [ "sqlx-macros", "proc-macro-hack" ] macros = [ "sqlx-macros" ]
tls = ["sqlx-core/tls"] tls = ["sqlx-core/tls"]
# database # database
@ -43,7 +43,6 @@ uuid = [ "sqlx-core/uuid", "sqlx-macros/uuid" ]
[dependencies] [dependencies]
sqlx-core = { version = "=0.1.4", path = "sqlx-core" } sqlx-core = { version = "=0.1.4", path = "sqlx-core" }
sqlx-macros = { version = "0.1.1", path = "sqlx-macros", optional = true } sqlx-macros = { version = "0.1.1", path = "sqlx-macros", optional = true }
proc-macro-hack = { version = "0.5.11", optional = true }
[dev-dependencies] [dev-dependencies]
anyhow = "1.0.26" anyhow = "1.0.26"

View File

@ -33,7 +33,6 @@ uuid = [ "sqlx/uuid" ]
async-std = { version = "1.4.0", default-features = false } async-std = { version = "1.4.0", default-features = false }
dotenv = { version = "0.15.0", default-features = false } dotenv = { version = "0.15.0", default-features = false }
futures = { version = "0.3.1", default-features = false } futures = { version = "0.3.1", default-features = false }
proc-macro-hack = { version = "0.5.11", default-features = false }
proc-macro2 = { version = "1.0.6", default-features = false } proc-macro2 = { version = "1.0.6", default-features = false }
sqlx = { version = "0.1.1", default-features = false, path = "../sqlx-core", package = "sqlx-core" } sqlx = { version = "0.1.1", default-features = false, path = "../sqlx-core", package = "sqlx-core" }
syn = { version = "1.0.11", default-features = false, features = [ "full" ] } syn = { version = "1.0.11", default-features = false, features = [ "full" ] }

View File

@ -6,8 +6,6 @@ extern crate proc_macro;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro_hack::proc_macro_hack;
use quote::quote; use quote::quote;
use syn::parse_macro_input; use syn::parse_macro_input;
@ -26,8 +24,22 @@ mod query_macros;
use query_macros::*; use query_macros::*;
fn macro_result(tokens: proc_macro2::TokenStream) -> TokenStream {
quote!(
macro_rules! macro_result {
($($args:tt)*) => (#tokens)
}
)
.into()
}
macro_rules! async_macro ( macro_rules! async_macro (
($db:ident => $expr:expr) => {{ ($db:ident, $input:ident: $ty:ty => $expr:expr) => {{
let $input = match syn::parse::<$ty>($input) {
Ok(input) => input,
Err(e) => return macro_result(e.to_compile_error()),
};
let res: Result<proc_macro2::TokenStream> = task::block_on(async { let res: Result<proc_macro2::TokenStream> = task::block_on(async {
use sqlx::Connection; use sqlx::Connection;
@ -70,40 +82,36 @@ macro_rules! async_macro (
Ok(ts) => ts.into(), Ok(ts) => ts.into(),
Err(e) => { Err(e) => {
if let Some(parse_err) = e.downcast_ref::<syn::Error>() { if let Some(parse_err) = e.downcast_ref::<syn::Error>() {
return dbg!(parse_err).to_compile_error().into(); macro_result(parse_err.to_compile_error())
} else {
let msg = format!("{:?}", e);
macro_result(quote!(compile_error(#msg)))
} }
let msg = format!("{:?}", e);
quote!(compile_error!(#msg);).into()
} }
} }
}} }}
); );
#[proc_macro_hack] #[proc_macro]
pub fn query(input: TokenStream) -> TokenStream { pub fn query(input: TokenStream) -> TokenStream {
#[allow(unused_variables)] #[allow(unused_variables)]
let input = parse_macro_input!(input as QueryMacroInput); async_macro!(db, input: QueryMacroInput => expand_query(input, db))
async_macro!(db => expand_query(input, db))
} }
#[proc_macro_hack] #[proc_macro]
pub fn query_file(input: TokenStream) -> TokenStream { pub fn query_file(input: TokenStream) -> TokenStream {
#[allow(unused_variables)] #[allow(unused_variables)]
let input = parse_macro_input!(input as QueryMacroInput); async_macro!(db, input: QueryMacroInput => expand_query_file(input, db))
async_macro!(db => expand_query_file(input, db))
} }
#[proc_macro_hack] #[proc_macro]
pub fn query_as(input: TokenStream) -> TokenStream { pub fn query_as(input: TokenStream) -> TokenStream {
#[allow(unused_variables)] #[allow(unused_variables)]
let input = parse_macro_input!(input as QueryAsMacroInput); async_macro!(db, input: QueryAsMacroInput => expand_query_as(input, db))
async_macro!(db => expand_query_as(input, db))
} }
#[proc_macro_hack] #[proc_macro]
pub fn query_file_as(input: TokenStream) -> TokenStream { pub fn query_file_as(input: TokenStream) -> TokenStream {
#[allow(unused_variables)] #[allow(unused_variables)]
let input = parse_macro_input!(input as QueryAsMacroInput); async_macro!(db, input: QueryAsMacroInput => expand_query_file_as(input, db))
async_macro!(db => expand_query_file_as(input, db))
} }

View File

@ -12,7 +12,7 @@ pub fn quote_args<DB: DatabaseExt>(
input: &QueryMacroInput, input: &QueryMacroInput,
describe: &Describe<DB>, describe: &Describe<DB>,
) -> crate::Result<TokenStream> { ) -> crate::Result<TokenStream> {
if input.args.is_empty() { if input.arg_names.is_empty() {
return Ok(quote! { return Ok(quote! {
let args = (); let args = ();
}); });
@ -22,7 +22,7 @@ pub fn quote_args<DB: DatabaseExt>(
let param_types = describe let param_types = describe
.param_types .param_types
.iter() .iter()
.zip(&*input.args) .zip(&*input.arg_exprs)
.map(|(type_, expr)| { .map(|(type_, expr)| {
get_type_override(expr) get_type_override(expr)
.or_else(|| { .or_else(|| {
@ -36,7 +36,7 @@ pub fn quote_args<DB: DatabaseExt>(
}) })
.collect::<crate::Result<Vec<_>>>()?; .collect::<crate::Result<Vec<_>>>()?;
let args_ty_cons = input.args.iter().enumerate().map(|(i, expr)| { let args_ty_cons = input.arg_names.iter().enumerate().map(|(i, expr)| {
// required or `quote!()` emits it as `Nusize` // required or `quote!()` emits it as `Nusize`
let i = syn::Index::from(i); let i = syn::Index::from(i);
quote_spanned!( expr.span() => { quote_spanned!( expr.span() => {
@ -56,10 +56,10 @@ pub fn quote_args<DB: DatabaseExt>(
TokenStream::new() TokenStream::new()
}; };
let args = input.args.iter(); let args = input.arg_names.iter();
Ok(quote! { Ok(quote! {
let args = (#(&#args),*,); let args = (#(&$#args),*,);
#args_check #args_check
}) })
} }

View File

@ -1,11 +1,15 @@
use std::env; use std::env;
use async_std::fs; use async_std::fs;
use proc_macro2::Span; use proc_macro2::{Ident, Span, TokenStream};
use syn::parse::{Parse, ParseStream}; use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated; use syn::punctuated::Punctuated;
use syn::Token; use syn::spanned::Spanned;
use syn::token::Group;
use syn::{Expr, ExprLit, ExprPath, Lit}; use syn::{Expr, ExprLit, ExprPath, Lit};
use syn::{ExprGroup, Token};
use quote::{format_ident, quote, ToTokens};
use sqlx::describe::Describe; use sqlx::describe::Describe;
use sqlx::Connection; use sqlx::Connection;
@ -14,28 +18,55 @@ use sqlx::Connection;
pub struct QueryMacroInput { pub struct QueryMacroInput {
pub(super) source: String, pub(super) source: String,
pub(super) source_span: Span, pub(super) source_span: Span,
pub(super) args: Vec<Expr>, // `arg0 .. argN` for N arguments
pub(super) arg_names: Vec<Ident>,
pub(super) arg_exprs: Vec<Expr>,
} }
impl QueryMacroInput { impl QueryMacroInput {
fn from_exprs(input: ParseStream, mut args: impl Iterator<Item = Expr>) -> syn::Result<Self> { fn from_exprs(input: ParseStream, mut args: impl Iterator<Item = Expr>) -> syn::Result<Self> {
let sql = match args.next() { fn lit_err<T>(span: Span, unexpected: Expr) -> syn::Result<T> {
Err(syn::Error::new(
span,
format!(
"expected string literal, got {}",
unexpected.to_token_stream()
),
))
}
let (source, source_span) = match args.next() {
Some(Expr::Lit(ExprLit { Some(Expr::Lit(ExprLit {
lit: Lit::Str(sql), .. lit: Lit::Str(sql), ..
})) => sql, })) => (sql.value(), sql.span()),
Some(other_expr) => { Some(Expr::Group(ExprGroup {
return Err(syn::Error::new_spanned( expr,
other_expr, group_token: Group { span },
"expected string literal", ..
)); })) => {
// this duplication with the above is necessary because `expr` is `Box<Expr>` here
// which we can't directly pattern-match without `box_patterns`
match *expr {
Expr::Lit(ExprLit {
lit: Lit::Str(sql), ..
}) => (sql.value(), span),
other_expr => return lit_err(span, other_expr),
}
} }
Some(other_expr) => return lit_err(other_expr.span(), other_expr),
None => return Err(input.error("expected SQL string literal")), None => return Err(input.error("expected SQL string literal")),
}; };
let arg_exprs: Vec<_> = args.collect();
let arg_names = (0..arg_exprs.len())
.map(|i| format_ident!("arg{}", i))
.collect();
Ok(Self { Ok(Self {
source: sql.value(), source,
source_span: sql.span(), source_span,
args: args.collect(), arg_exprs,
arg_names,
}) })
} }
@ -56,13 +87,13 @@ impl QueryMacroInput {
.await .await
.map_err(|e| syn::Error::new(self.source_span, e))?; .map_err(|e| syn::Error::new(self.source_span, e))?;
if self.args.len() != describe.param_types.len() { if self.arg_names.len() != describe.param_types.len() {
return Err(syn::Error::new( return Err(syn::Error::new(
Span::call_site(), Span::call_site(),
format!( format!(
"expected {} parameters, got {}", "expected {} parameters, got {}",
describe.param_types.len(), describe.param_types.len(),
self.args.len() self.arg_names.len()
), ),
) )
.into()); .into());
@ -97,16 +128,33 @@ impl QueryAsMacroInput {
impl Parse for QueryAsMacroInput { impl Parse for QueryAsMacroInput {
fn parse(input: ParseStream) -> syn::Result<Self> { fn parse(input: ParseStream) -> syn::Result<Self> {
fn path_err<T>(span: Span, unexpected: Expr) -> syn::Result<T> {
Err(syn::Error::new(
span,
format!(
"expected path to a type, got {}",
unexpected.to_token_stream()
),
))
}
let mut args = Punctuated::<Expr, Token![,]>::parse_terminated(input)?.into_iter(); let mut args = Punctuated::<Expr, Token![,]>::parse_terminated(input)?.into_iter();
let as_ty = match args.next() { let as_ty = match args.next() {
Some(Expr::Path(path)) => path, Some(Expr::Path(path)) => path,
Some(other_expr) => { Some(Expr::Group(ExprGroup {
return Err(syn::Error::new_spanned( expr,
other_expr, group_token: Group { span },
"expected path to a type", ..
)); })) => {
// this duplication with the above is necessary because `expr` is `Box<Expr>` here
// which we can't directly pattern-match without `box_patterns`
match *expr {
Expr::Path(path) => path,
other_expr => return path_err(span, other_expr),
}
} }
Some(other_expr) => return path_err(other_expr.span(), other_expr),
None => return Err(input.error("expected path to SQL file")), None => return Err(input.error("expected path to SQL file")),
}; };

View File

@ -46,6 +46,7 @@ where
} }
let args_tokens = args::quote_args(&input.query_input, &describe)?; let args_tokens = args::quote_args(&input.query_input, &describe)?;
let arg_names = &input.query_input.arg_names;
let columns = output::columns_to_rust(&describe)?; let columns = output::columns_to_rust(&describe)?;
let output = output::quote_query_as::<C::Database>( let output = output::quote_query_as::<C::Database>(
@ -54,10 +55,14 @@ where
&columns, &columns,
); );
Ok(quote! {{ Ok(quote! {
#args_tokens macro_rules! macro_result {
#output.bind_all(args) (#($#arg_names:expr),*) => {{
}}) #args_tokens
#output.bind_all(args)
}}
}
})
} }
pub async fn expand_query_file_as<C: Connection>( pub async fn expand_query_file_as<C: Connection>(

View File

@ -50,15 +50,20 @@ where
.collect::<TokenStream>(); .collect::<TokenStream>();
let output = output::quote_query_as::<C::Database>(sql, &record_type, &columns); let output = output::quote_query_as::<C::Database>(sql, &record_type, &columns);
let arg_names = &input.arg_names;
Ok(quote! {{ Ok(quote! {
#[derive(Debug)] macro_rules! macro_result {
struct #record_type { (#($#arg_names:expr),*) => {{
#record_fields #[derive(Debug)]
} struct #record_type {
#record_fields
}
#args #args
#output.bind_all(args) #output.bind_all(args)
}}) }
}}
})
} }

View File

@ -20,34 +20,14 @@ pub use sqlx_core::mysql::{self, MySql, MySqlConnection, MySqlPool};
#[cfg(feature = "postgres")] #[cfg(feature = "postgres")]
pub use sqlx_core::postgres::{self, PgConnection, PgPool, Postgres}; pub use sqlx_core::postgres::{self, PgConnection, PgPool, Postgres};
#[allow(unused_attributes)] #[cfg(feature = "macros")]
#[doc(hidden)]
pub extern crate sqlx_macros;
#[cfg(feature = "macros")]
#[macro_export] #[macro_export]
mod macros; mod macros;
#[cfg(feature = "macros")]
#[doc(hidden)]
#[proc_macro_hack::proc_macro_hack(fake_call_site)]
#[allow(dead_code)]
pub use sqlx_macros::query as query_;
#[cfg(feature = "macros")]
#[doc(hidden)]
#[proc_macro_hack::proc_macro_hack(fake_call_site)]
#[allow(dead_code)]
pub use sqlx_macros::query_as as query_as_;
#[cfg(feature = "macros")]
#[doc(hidden)]
#[proc_macro_hack::proc_macro_hack(fake_call_site)]
#[allow(dead_code)]
pub use sqlx_macros::query_file as query_file_;
#[cfg(feature = "macros")]
#[doc(hidden)]
#[proc_macro_hack::proc_macro_hack(fake_call_site)]
#[allow(dead_code)]
pub use sqlx_macros::query_file_as as query_file_as_;
// macro support // macro support
#[cfg(feature = "macros")] #[cfg(feature = "macros")]
#[doc(hidden)] #[doc(hidden)]

View File

@ -82,18 +82,23 @@
/// * [query_as!] if you want to use a struct you can name, /// * [query_as!] if you want to use a struct you can name,
/// * [query_file!] if you want to define the SQL query out-of-line, /// * [query_file!] if you want to define the SQL query out-of-line,
/// * [query_file_as!] if you want both of the above. /// * [query_file_as!] if you want both of the above.
#[cfg(feature = "macros")]
#[macro_export] #[macro_export]
macro_rules! query ( macro_rules! query (
// the emitted item for `#[proc_macro_hack]` doesn't look great in docs // by emitting a macro definition from our proc-macro containing the result tokens,
// plus this might let IDEs hint at the syntax // we no longer have a need for `proc-macro-hack`
// `#[allow(dead_code)]` to silence the `enum ProcMacroHack` error ($query:literal) => ({
($query:literal) => (#[allow(dead_code)] { #[macro_use]
$crate::query_!($query) mod _macro_result {
$crate::sqlx_macros::query!($query);
}
macro_result!()
}); });
($query:literal, $($args:tt)*) => (#[allow(dead_code)]{ ($query:literal, $($args:expr),*) => ({
#![allow(dead_code)] #[macro_use]
$crate::query_!($query, $($args)*) mod _macro_result {
$crate::sqlx_macros::query!($query, $($args),*);
}
macro_result!($($args),*)
}) })
); );
@ -139,14 +144,21 @@ macro_rules! query (
/// # #[cfg(not(feature = "mysql"))] /// # #[cfg(not(feature = "mysql"))]
/// # fn main() {} /// # fn main() {}
/// ``` /// ```
#[cfg(feature = "macros")]
#[macro_export] #[macro_export]
macro_rules! query_file ( macro_rules! query_file (
($query:literal) => (#[allow(dead_code)]{ ($query:literal) => (#[allow(dead_code)]{
$crate::query_file_!($query) #[macro_use]
mod _macro_result {
$crate::sqlx_macros::query_file!($query);
}
macro_result!()
}); });
($query:literal, $($args:tt)*) => (#[allow(dead_code)]{ ($query:literal, $($args:expr),*) => (#[allow(dead_code)]{
$crate::query_file_!($query, $($args)*) #[macro_use]
mod _macro_result {
$crate::sqlx_macros::query_file!($query, $($args),*);
}
macro_result!($($args),*)
}) })
); );
@ -197,14 +209,21 @@ macro_rules! query_file (
/// # #[cfg(not(feature = "mysql"))] /// # #[cfg(not(feature = "mysql"))]
/// # fn main() {} /// # fn main() {}
/// ``` /// ```
#[cfg(feature = "macros")]
#[macro_export] #[macro_export]
macro_rules! query_as ( macro_rules! query_as (
($out_struct:path, $query:literal) => (#[allow(dead_code)] { ($out_struct:path, $query:literal) => (#[allow(dead_code)] {
$crate::query_as_!($out_struct, $query) #[macro_use]
mod _macro_result {
$crate::sqlx_macros::query_as!($out_struct, $query);
}
macro_result!()
}); });
($out_struct:path, $query:literal, $($args:tt)*) => (#[allow(dead_code)] { ($out_struct:path, $query:literal, $($args:expr),*) => (#[allow(dead_code)] {
$crate::query_as_!($out_struct, $query, $($args)*) #[macro_use]
mod _macro_result {
$crate::sqlx_macros::query_as!($out_struct, $query, $($args),*);
}
macro_result!($($args),*)
}) })
); );
@ -240,13 +259,20 @@ macro_rules! query_as (
/// # #[cfg(not(feature = "mysql"))] /// # #[cfg(not(feature = "mysql"))]
/// # fn main() {} /// # fn main() {}
/// ``` /// ```
#[cfg(feature = "macros")]
#[macro_export] #[macro_export]
macro_rules! query_file_as ( macro_rules! query_file_as (
($out_struct:path, $query:literal) => (#[allow(dead_code)] { ($out_struct:path, $query:literal) => (#[allow(dead_code)] {
$crate::query_file_as_!($out_struct, $query) #[macro_use]
mod _macro_result {
$crate::sqlx_macros::query_file_as!($out_struct, $query);
}
macro_result!()
}); });
($out_struct:path, $query:literal, $($args:tt)*) => (#[allow(dead_code)] { ($out_struct:path, $query:literal, $($args:expr),*) => (#[allow(dead_code)] {
$crate::query_file_as_!($out_struct, $query, $($args)*) #[macro_use]
mod _macro_result {
$crate::sqlx_macros::query_file_as!($out_struct, $query, $($args),*);
}
macro_result!($($args),*)
}) })
); );

View File

@ -73,6 +73,21 @@ async fn pool_immediately_fails_with_db_error() -> anyhow::Result<()> {
#[cfg(feature = "macros")] #[cfg(feature = "macros")]
#[async_std::test] #[async_std::test]
async fn macro_select_from_cte() -> anyhow::Result<()> { async fn macro_select_from_cte() -> anyhow::Result<()> {
let mut conn = connect().await?;
let account =
sqlx::query!("select * from (select (1) as id, 'Herp Derpinson' as name) accounts")
.fetch_one(&mut conn)
.await?;
println!("{:?}", account);
println!("{}: {}", account.id, account.name);
Ok(())
}
#[cfg(feature = "macros")]
#[async_std::test]
async fn macro_select_from_cte_bind() -> anyhow::Result<()> {
let mut conn = connect().await?; let mut conn = connect().await?;
let account = sqlx::query!( let account = sqlx::query!(
"select * from (select (1) as id, 'Herp Derpinson' as name) accounts where id = ?", "select * from (select (1) as id, 'Herp Derpinson' as name) accounts where id = ?",