Reputation: 3261
I have an procedural attribute macro which given a function operates on each binary expression e.g. let a = b + c;
and returns another expression depending on it. With the +
operation depending on the types, it needs to know the types of a
, b
and c
.
Is there a way to get the inferred types of variables at compile time?
(like rust-analyser might display inferred types, can I get these inside a macro?)
To illustrate my approach a bit more succinctly in a Rust playground we can use a declarative macro which calls a function on a given variable, the specifics of this function being based on the type of the given variable.
The closest I can get to my desired functionality in a Rust playground:
macro_rules! SomeMacro {
($x:expr) => {{
$x.some_function(3.)
}};
}
trait SomeTrait {
fn some_function(&self,x:f32) -> f32;
}
impl SomeTrait for u32 {
fn some_function(&self,x:f32) -> f32 {
x * 3.
}
}
fn main() {
let a = 3u32;
let b = SomeMacro!(a);
assert_eq!(b,9.);
}
How would I make something work like:
fn some_function<u32>(x:f32) -> f32 {
3. * x
}
fn some_function<u32,i8,f32>(x:f32) -> f32 {
3. * x
}
fn main() {
let a = 3u32;
let b = <type_of<a>()>::some_function(3.);
assert_eq!(b,9.);
let c = 5i8;
let d = <type_of<a>(),type_of<b>(),type_of<c>()>::some_function(2.);
assert_eq!(c,6.);
}
lib.rs
extern crate proc_macro;
use proc_macro::TokenStream;
#[proc_macro_attribute]
pub fn my_attribute_macro(_attr: TokenStream, item: TokenStream) -> TokenStream {
let ast = syn::parse_macro_input!(item as syn::Item);
// eprintln!("{:#?}",ast);
// Checks item is function.
let mut function = match ast {
syn::Item::Fn(func) => func,
_ => panic!("Only `fn` items are supported."),
};
let block = &mut function.block;
// Updates statements
let statements = block.stmts
.iter()
.filter_map(|statement| update_statements(statement))
.collect::<Vec<_>>();
block.stmts = statements;
let new = quote::quote! { #function };
TokenStream::from(new)
}
fn update_statements(stmt: &syn::Stmt) -> Option<syn::Stmt> {
let local = match stmt {
syn::Stmt::Local(local) => local,
_ => return Some(stmt.clone())
};
let init = &local.init;
let bin_expr = match *init.as_ref().unwrap().1 {
syn::Expr::Binary(ref bin) => bin,
_ => return Some(stmt.clone())
};
eprintln!("looking at: {:#?}",stmt);
//
None
}
main.rs
use macro_test::*;
// Goals:
// - Map from `x` being equal to `a+b` to `x` being equal to `a*b` based off `x` being `f32`.
// - Map from `y` being equal to `c+d` to `y` being equal to `c/d` based off `y` being `u32`.
#[my_attribute_macro]
fn my_function(a: f32, b: f32, c: u32, d: u32) {
let x = a + b;
let y = c + d;
}
fn main() {
}
With one of the prints looking like (from cargo expand --bin macro-test
):
looking at: Local(
Local {
attrs: [],
let_token: Let,
pat: Ident(
PatIdent {
attrs: [],
by_ref: None,
mutability: None,
ident: Ident {
ident: "y",
span: #0 bytes(316..317),
},
subpat: None,
},
),
init: Some(
(
Eq,
Binary(
ExprBinary {
attrs: [],
left: Path(
ExprPath {
attrs: [],
qself: None,
path: Path {
leading_colon: None,
segments: [
PathSegment {
ident: Ident {
ident: "c",
span: #0 bytes(320..321),
},
arguments: None,
},
],
},
},
),
op: Add(
Add,
),
right: Path(
ExprPath {
attrs: [],
qself: None,
path: Path {
leading_colon: None,
segments: [
PathSegment {
ident: Ident {
ident: "d",
span: #0 bytes(324..325),
},
arguments: None,
},
],
},
},
),
},
),
),
),
semi_token: Semi,
},
)
Upvotes: 5
Views: 4386
Reputation: 299565
The compiler doesn't decide the types until the macros have been processed. In many cases, a macro can change the inferred types. This is why it's possible to write things like:
let mut xs = vec![];
xs.push(1);
Type inference can go back and assign the right type to xs
. This wouldn't be possible if vec!
had to know the type prior to expansion. You will need to approach this problem another way. See @PossiblyAShrub's answer for one approach.
As The Rust Programming Language:Macros notes (emphasis added):
Also, macros are expanded before the compiler interprets the meaning of the code, so a macro can, for example, implement a trait on a given type.
You may want to explore the Native Differential Programming Support for Rust discussion from the forums, and particularly the follow-up threads linked.
Upvotes: 5
Reputation: 587
OP has updated their post clarifying their issue, this addition is in response to that.
It appears that you may want to implement the new type pattern. In your comment you mention that you want to change the behavior of standard operators (+
, -
, *
, /
, ...) on functions decorated with a specific procedural macro.
The newtype pattern looks like this:
struct Differentiable(f32);
Note: we could even use generics here such that our new type can be over
f32
,u32
,u8
...
We can now implement our desired operators on top of our new type.
impl Add for Differentiable {
type Output = Self;
fn add(self, other: Self) -> Self {
Self(self.0 * other.0)
}
}
This gives us the behavior:
fn main() {
let x = Differentiable(5.0);
let y = Differentiable(2.0);
assert_eq!(x + y, 10);
}
Furthermore we could implement Into
and From
on the type to make it's use more ergonomic. We can also implement the other operators via implementing the traits in std::ops
.
Now, you mentioned wanting to implement this as a procedural macro on a function. I would strongly suggest otherwise. I would argue that such a feature would:
See:
Personally I do not see the value in providing such a macro, nor how it could even be implemented in a way which "feels" right. Nevertheless it is possible:
Simply convert from this ast:
fn my_function(a: f32, b: f32, c: u32, d: u32) {
let x = a + b;
let y = c + d;
}
to:
fn my_function(a: f32, b: f32, c: u32, d: u32) {
fn inner(
a: Differentiable,
b: Differentiable,
c: Differentiable,
d: Differentiable,
) {
let x = a + b;
let y = c + d;
}
inner(
Differentiable(a),
Differentiable(b),
Differentiable(c),
Differentiable(d),
)
}
Further reading:
I am not sure what use knowing the type of an expression inside of a macro would be for you. Rust's type inference system should be powerful enough to handle most cases of type resolution.
For example, I would implement the above snippet using generics and trait bounds:
use std::ops::Add;
fn add<A: Into<B>, B: Add>(a: A, b: B) -> <B as Add>::Output {
a.into() + b
}
fn main() {
let a: u8 = 4;
let b: f32 = 3.5;
assert_eq!(add(a, b), 7.5);
}
Rust will automatically find the right values for A
and B
. In the case that it cannot, due to our trait restrictions, rustc will output helpful error message:
fn main() {
let a = "Hello, world!";
let b = 56;
add(a, b);
}
Results in:
error[E0277]: the trait bound `{integer}: From<&str>` is not satisfied
--> src/main.rs:10:12
|
10 | add(a, b);
| --- ^ the trait `From<&str>` is not implemented for `{integer}`
| |
| required by a bound introduced by this call
|
= help: the following implementations were found:
<Arc<B> as From<Cow<'a, B>>>
<Arc<CStr> as From<&CStr>>
<Arc<CStr> as From<CString>>
<Arc<OsStr> as From<&OsStr>>
and 329 others
= note: required because of the requirements on the impl of `Into<{integer}>` for `&str`
note: required by a bound in `add`
--> src/main.rs:3:11
|
3 | fn add<A: Into<B>, B: Add>(a: A, b: B) -> <B as Add>::Output {
| ^^^^^^^ required by this bound in `add`
For more information about this error, try `rustc --explain E0277`.
Here rust could not resolve the type of a
to fit our restrictions in the add
function.
Now this works through macros, with no extra work necessary:
macro_rules! some_macro {
($x:expr) => (add($x, 3.0))
}
fn main() {
let x = 42u32;
let y = some_macro!(x);
assert_eq!(y, 45.0);
}
Further reading:
Upvotes: 2