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)",
"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)",
"proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
"sqlx-core 0.1.4",
"sqlx-macros 0.1.1",
]
@ -1368,7 +1367,6 @@ dependencies = [
"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)",
"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)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"sqlx-core 0.1.4",

View File

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

View File

@ -33,7 +33,6 @@ uuid = [ "sqlx/uuid" ]
async-std = { version = "1.4.0", default-features = false }
dotenv = { version = "0.15.0", 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 }
sqlx = { version = "0.1.1", default-features = false, path = "../sqlx-core", package = "sqlx-core" }
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_hack::proc_macro_hack;
use quote::quote;
use syn::parse_macro_input;
@ -26,8 +24,22 @@ mod 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 (
($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 {
use sqlx::Connection;
@ -70,40 +82,36 @@ macro_rules! async_macro (
Ok(ts) => ts.into(),
Err(e) => {
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 {
#[allow(unused_variables)]
let input = parse_macro_input!(input as QueryMacroInput);
async_macro!(db => expand_query(input, db))
async_macro!(db, input: QueryMacroInput => expand_query(input, db))
}
#[proc_macro_hack]
#[proc_macro]
pub fn query_file(input: TokenStream) -> TokenStream {
#[allow(unused_variables)]
let input = parse_macro_input!(input as QueryMacroInput);
async_macro!(db => expand_query_file(input, db))
async_macro!(db, input: QueryMacroInput => expand_query_file(input, db))
}
#[proc_macro_hack]
#[proc_macro]
pub fn query_as(input: TokenStream) -> TokenStream {
#[allow(unused_variables)]
let input = parse_macro_input!(input as QueryAsMacroInput);
async_macro!(db => expand_query_as(input, db))
async_macro!(db, input: QueryAsMacroInput => expand_query_as(input, db))
}
#[proc_macro_hack]
#[proc_macro]
pub fn query_file_as(input: TokenStream) -> TokenStream {
#[allow(unused_variables)]
let input = parse_macro_input!(input as QueryAsMacroInput);
async_macro!(db => expand_query_file_as(input, db))
async_macro!(db, input: QueryAsMacroInput => expand_query_file_as(input, db))
}

View File

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

View File

@ -1,11 +1,15 @@
use std::env;
use async_std::fs;
use proc_macro2::Span;
use proc_macro2::{Ident, Span, TokenStream};
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::Token;
use syn::spanned::Spanned;
use syn::token::Group;
use syn::{Expr, ExprLit, ExprPath, Lit};
use syn::{ExprGroup, Token};
use quote::{format_ident, quote, ToTokens};
use sqlx::describe::Describe;
use sqlx::Connection;
@ -14,28 +18,55 @@ use sqlx::Connection;
pub struct QueryMacroInput {
pub(super) source: String,
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 {
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 {
lit: Lit::Str(sql), ..
})) => sql,
Some(other_expr) => {
return Err(syn::Error::new_spanned(
other_expr,
"expected string literal",
));
})) => (sql.value(), sql.span()),
Some(Expr::Group(ExprGroup {
expr,
group_token: Group { span },
..
})) => {
// 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")),
};
let arg_exprs: Vec<_> = args.collect();
let arg_names = (0..arg_exprs.len())
.map(|i| format_ident!("arg{}", i))
.collect();
Ok(Self {
source: sql.value(),
source_span: sql.span(),
args: args.collect(),
source,
source_span,
arg_exprs,
arg_names,
})
}
@ -56,13 +87,13 @@ impl QueryMacroInput {
.await
.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(
Span::call_site(),
format!(
"expected {} parameters, got {}",
describe.param_types.len(),
self.args.len()
self.arg_names.len()
),
)
.into());
@ -97,16 +128,33 @@ impl QueryAsMacroInput {
impl Parse for QueryAsMacroInput {
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 as_ty = match args.next() {
Some(Expr::Path(path)) => path,
Some(other_expr) => {
return Err(syn::Error::new_spanned(
other_expr,
"expected path to a type",
));
Some(Expr::Group(ExprGroup {
expr,
group_token: Group { span },
..
})) => {
// 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")),
};

View File

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

View File

@ -50,15 +50,20 @@ where
.collect::<TokenStream>();
let output = output::quote_query_as::<C::Database>(sql, &record_type, &columns);
let arg_names = &input.arg_names;
Ok(quote! {{
#[derive(Debug)]
struct #record_type {
#record_fields
}
Ok(quote! {
macro_rules! macro_result {
(#($#arg_names:expr),*) => {{
#[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")]
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]
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
#[cfg(feature = "macros")]
#[doc(hidden)]

View File

@ -82,18 +82,23 @@
/// * [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_as!] if you want both of the above.
#[cfg(feature = "macros")]
#[macro_export]
macro_rules! query (
// the emitted item for `#[proc_macro_hack]` doesn't look great in docs
// plus this might let IDEs hint at the syntax
// `#[allow(dead_code)]` to silence the `enum ProcMacroHack` error
($query:literal) => (#[allow(dead_code)] {
$crate::query_!($query)
// by emitting a macro definition from our proc-macro containing the result tokens,
// we no longer have a need for `proc-macro-hack`
($query:literal) => ({
#[macro_use]
mod _macro_result {
$crate::sqlx_macros::query!($query);
}
macro_result!()
});
($query:literal, $($args:tt)*) => (#[allow(dead_code)]{
#![allow(dead_code)]
$crate::query_!($query, $($args)*)
($query:literal, $($args:expr),*) => ({
#[macro_use]
mod _macro_result {
$crate::sqlx_macros::query!($query, $($args),*);
}
macro_result!($($args),*)
})
);
@ -139,14 +144,21 @@ macro_rules! query (
/// # #[cfg(not(feature = "mysql"))]
/// # fn main() {}
/// ```
#[cfg(feature = "macros")]
#[macro_export]
macro_rules! query_file (
($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)]{
$crate::query_file_!($query, $($args)*)
($query:literal, $($args:expr),*) => (#[allow(dead_code)]{
#[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"))]
/// # fn main() {}
/// ```
#[cfg(feature = "macros")]
#[macro_export]
macro_rules! query_as (
($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)] {
$crate::query_as_!($out_struct, $query, $($args)*)
($out_struct:path, $query:literal, $($args:expr),*) => (#[allow(dead_code)] {
#[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"))]
/// # fn main() {}
/// ```
#[cfg(feature = "macros")]
#[macro_export]
macro_rules! query_file_as (
($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)] {
$crate::query_file_as_!($out_struct, $query, $($args)*)
($out_struct:path, $query:literal, $($args:expr),*) => (#[allow(dead_code)] {
#[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")]
#[async_std::test]
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 account = sqlx::query!(
"select * from (select (1) as id, 'Herp Derpinson' as name) accounts where id = ?",