forked from transact-rs/sqlx
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathargs.rs
More file actions
157 lines (136 loc) · 5.6 KB
/
Copy pathargs.rs
File metadata and controls
157 lines (136 loc) · 5.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
use crate::database::DatabaseExt;
use crate::query::QueryMacroInput;
use either::Either;
use proc_macro2::TokenStream;
use quote::{format_ident, quote, quote_spanned};
use sqlx_core::describe::Describe;
use syn::spanned::Spanned;
use syn::{Expr, ExprCast, ExprGroup, ExprType, Type};
/// Returns a tokenstream which typechecks the arguments passed to the macro
/// and binds them to `DB::Arguments` with the ident `query_args`.
pub fn quote_args<DB: DatabaseExt>(
input: &QueryMacroInput,
info: &Describe<DB>,
) -> crate::Result<TokenStream> {
let db_path = DB::db_path();
if input.arg_exprs.is_empty() {
return Ok(quote! {
let query_args = <#db_path as sqlx::database::HasArguments>::Arguments::default();
});
}
let arg_names = (0..input.arg_exprs.len())
.map(|i| format_ident!("arg{}", i))
.collect::<Vec<_>>();
let arg_name = &arg_names;
let arg_expr = input.arg_exprs.iter().cloned().map(strip_wildcard);
let arg_bindings = quote! {
#(let #arg_name = &(#arg_expr);)*
};
let args_check = match info.parameters() {
None | Some(Either::Right(_)) => {
// all we can do is check arity which we did
TokenStream::new()
}
Some(Either::Left(_)) if !input.checked => {
// this is an `*_unchecked!()` macro invocation
TokenStream::new()
}
Some(Either::Left(params)) => {
params
.iter()
.zip(arg_names.iter().zip(&input.arg_exprs))
.enumerate()
.map(|(i, (param_ty, (name, expr)))| -> crate::Result<_> {
let param_ty = match get_type_override(expr) {
// cast or type ascription will fail to compile if the type does not match
// and we strip casts to wildcard
Some(_) => return Ok(quote!()),
None => {
DB::param_type_for_id(¶m_ty)
.ok_or_else(|| {
if let Some(feature_gate) = <DB as DatabaseExt>::get_feature_gate(¶m_ty) {
format!(
"optional feature `{}` required for type {} of param #{}",
feature_gate,
param_ty,
i + 1,
)
} else {
format!("unsupported type {} for param #{}", param_ty, i + 1)
}
})?
.parse::<proc_macro2::TokenStream>()
.map_err(|_| format!("Rust type mapping for {} not parsable", param_ty))?
}
};
Ok(quote_spanned!(expr.span() =>
// this shouldn't actually run
if false {
use sqlx::ty_match::{WrapSameExt as _, MatchBorrowExt as _};
// evaluate the expression only once in case it contains moves
let _expr = sqlx::ty_match::dupe_value(#name);
// if `_expr` is `Option<T>`, get `Option<$ty>`, otherwise `$ty`
let ty_check = sqlx::ty_match::WrapSame::<#param_ty, _>::new(&_expr).wrap_same();
// if `_expr` is `&str`, convert `String` to `&str`
let (mut _ty_check, match_borrow) = sqlx::ty_match::MatchBorrow::new(ty_check, &_expr);
_ty_check = match_borrow.match_borrow();
// this causes move-analysis to effectively ignore this block
panic!();
}
))
})
.collect::<crate::Result<TokenStream>>()?
}
};
let args_count = input.arg_exprs.len();
Ok(quote! {
#arg_bindings
#args_check
let mut query_args = <#db_path as sqlx::database::HasArguments>::Arguments::default();
query_args.reserve(
#args_count,
0 #(+ sqlx::encode::Encode::<#db_path>::size_hint(#arg_name))*
);
#(query_args.add(#arg_name);)*
})
}
fn get_type_override(expr: &Expr) -> Option<&Type> {
match expr {
Expr::Group(group) => get_type_override(&group.expr),
Expr::Cast(cast) => Some(&cast.ty),
Expr::Type(ascription) => Some(&ascription.ty),
_ => None,
}
}
fn strip_wildcard(expr: Expr) -> Expr {
match expr {
Expr::Group(ExprGroup {
attrs,
group_token,
expr,
}) => Expr::Group(ExprGroup {
attrs,
group_token,
expr: Box::new(strip_wildcard(*expr)),
}),
// type ascription syntax is experimental so we always strip it
Expr::Type(ExprType { expr, .. }) => *expr,
// we want to retain casts if they semantically matter
Expr::Cast(ExprCast {
attrs,
expr,
as_token,
ty,
}) => match *ty {
// cast to wildcard `_` will produce weird errors; we interpret it as taking the value as-is
Type::Infer(_) => *expr,
_ => Expr::Cast(ExprCast {
attrs,
expr,
as_token,
ty,
}),
},
_ => expr,
}
}