-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for defaulted methods #20
base: main
Are you sure you want to change the base?
Conversation
d1db272
to
4ab0022
Compare
trait-variant/src/variant.rs
Outdated
fn_item | ||
.default | ||
.as_ref() | ||
.map(|b| syn::parse2(quote! { { async move #b } }).unwrap()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just noting that the correct desugaring would involve moving every argument into the block as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mendess I believe what @compiler-errors means here is that every argument of the trait fn should also be moved into the block. eg
async fn defaulted_mut(&mut self, x: u32) -> i32 {
self.make(x, "10").await
}
becomes
fn defaulted_mut<'the_self_lt>(&'the_self_lt mut self, x: u32) -> impl ::core::future::Future<Output = i32> + Send
where
&'the_self_lt mut Self: Send,
{
async move {
let self = self;
let x = x;
self.make(x, "10").await
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a commit that does this. Sorry for misunderstanding the original comment
@compiler-errors so I gave your suggestions a try with these modifications to the example trait #[trait_variant::make(IntFactory: Send)]
pub trait LocalIntFactory {
const NAME: &'static str;
type MyFut<'a>: Future
where
Self: 'a;
async fn make(&self, x: u32, y: &str) -> i32;
async fn make_mut(&mut self);
fn stream(&self) -> impl Iterator<Item = i32>;
fn call(&self) -> u32;
fn another_async(&self, input: Result<(), &str>) -> Self::MyFut<'_>;
async fn defaulted(&self) -> i32 {
self.make(10, "10").await
}
async fn defaulted_mut(&mut self) -> i32 {
self.make(10, "10").await
}
async fn defaulted_mut_2(&mut self) {
self.make_mut().await
}
async fn defaulted_move(self) -> i32
where
Self: Sized,
{
self.make(10, "10").await
}
} And I generated something like this pub trait IntFactory: Send {
const NAME: &'static str;
type MyFut<'a>: Future
where
Self: 'a;
fn make(&self, x: u32, y: &str) -> impl ::core::future::Future<Output = i32> + Send;
fn make_mut(&mut self) -> impl ::core::future::Future<Output = ()> + Send;
fn stream(&self) -> impl Iterator<Item = i32> + Send;
fn call(&self) -> u32;
fn another_async(&self, input: Result<(), &str>) -> Self::MyFut<'_>;
fn defaulted<'the_self_lt>(&'the_self_lt self) -> impl ::core::future::Future<Output = i32> + Send
where
&'the_self_lt Self: Send,
{
async { self.make(10, "10").await }
}
fn defaulted_mut<'the_self_lt>(&'the_self_lt mut self) -> impl ::core::future::Future<Output = i32> + Send
where
&'the_self_lt mut Self: Send, // error[E0277]: `Self` cannot be shared between threads safely
{
async { self.make(10, "10").await }
}
fn defaulted_mut_2<'the_self_lt>(&'the_self_lt mut self) -> impl ::core::future::Future<Output = ()> + Send
where
&'the_self_lt mut Self: Send,
{
async { self.make_mut().await }
}
fn defaulted_move(self) -> impl ::core::future::Future<Output = i32> + Send
where
Self: Sized,
Self: Send, // error[E0277]: `Self` cannot be shared between threads safely
{
async { self.make(10, "10").await }
}
} The errors are because the Do you still think it's worth trying to generate very precise bounds like I tried to do here? |
@mendess I believe in your generated example you are missing the Your explanation of the error makes sense, hence why it is required to |
@andrewtoth I remove the |
I didn't mention removing the move keyword--that is definitely needed. |
b3706eb
to
d279a3c
Compare
Alright, edited the codegen to have more precise bounds for Self This is what it looks like now use std::future::Future;
#[allow(async_fn_in_trait)]
pub trait LocalIntFactory {
const NAME: &'static str;
type MyFut<'a>: Future
where
Self: 'a;
async fn make(&self, x: u32, y: &str) -> i32;
async fn make_mut(&mut self);
fn stream(&self) -> impl Iterator<Item = i32>;
fn call(&self) -> u32;
fn another_async(&self, input: Result<(), &str>) -> Self::MyFut<'_>;
async fn defaulted(&self) -> i32 {
self.make(10, "10").await
}
async fn defaulted_mut(&mut self) -> i32 {
self.make(10, "10").await
}
async fn defaulted_mut_2(&mut self) {
self.make_mut().await
}
async fn defaulted_move(self) -> i32
where
Self: Sized,
{
self.make(10, "10").await
}
}
pub trait IntFactory: Send {
const NAME: &'static str;
type MyFut<'a>: Future
where
Self: 'a;
fn make(&self, x: u32, y: &str) -> impl ::core::future::Future<Output = i32> + Send;
fn make_mut(&mut self) -> impl ::core::future::Future<Output = ()> + Send;
fn stream(&self) -> impl Iterator<Item = i32> + Send;
fn call(&self) -> u32;
fn another_async(&self, input: Result<(), &str>) -> Self::MyFut<'_>;
fn defaulted<'the_self_lt>(&'the_self_lt self) -> impl ::core::future::Future<Output = i32> + Send
where
&'the_self_lt Self: Send,
{
async move { self.make(10, "10").await }
}
fn defaulted_mut<'the_self_lt>(&'the_self_lt mut self) -> impl ::core::future::Future<Output = i32> + Send
where
&'the_self_lt mut Self: Send,
{
async move { self.make(10, "10").await }
}
fn defaulted_mut_2<'the_self_lt>(&'the_self_lt mut self) -> impl ::core::future::Future<Output = ()> + Send
where
&'the_self_lt mut Self: Send,
{
async move { self.make_mut().await }
}
fn defaulted_move(self) -> impl ::core::future::Future<Output = i32> + Send
where
Self: Sized,
Self: Send,
{
async move { self.make(10, "10").await }
}
}
impl<T> LocalIntFactory for T
where
T: IntFactory,
Self: Sync // this is only added if there are defaulted methods
{
const NAME: &'static str = <Self as IntFactory>::NAME;
type MyFut<'a> = <Self as IntFactory>::MyFut<'a> where Self: 'a;
async fn make(&self, x: u32, y: &str) -> i32 {
<Self as IntFactory>::make(self, x, y).await
}
async fn make_mut(&mut self) {
<Self as IntFactory>::make_mut(self).await
}
fn stream(&self) -> impl Iterator<Item = i32> {
<Self as IntFactory>::stream(self)
}
fn call(&self) -> u32 {
<Self as IntFactory>::call(self)
}
fn another_async(&self, input: Result<(), &str>) -> Self::MyFut<'_> {
<Self as IntFactory>::another_async(self, input)
}
async fn defaulted(&self) -> i32 {
<Self as IntFactory>::defaulted(self).await
}
async fn defaulted_mut(&mut self) -> i32 {
<Self as IntFactory>::defaulted_mut(self).await
}
async fn defaulted_mut_2(&mut self) {
<Self as IntFactory>::defaulted_mut_2(self).await
}
async fn defaulted_move(self) -> i32
where
Self: Sized,
{
<Self as IntFactory>::defaulted_move(self).await
}
} |
trait-variant/src/variant.rs
Outdated
@@ -164,8 +182,26 @@ fn mk_blanket_impl(attrs: &Attrs, tr: &ItemTrait) -> TokenStream { | |||
let orig = &tr.ident; | |||
let variant = &attrs.variant.name; | |||
let items = tr.items.iter().map(|item| blanket_impl_item(item, variant)); | |||
let self_is_sync = tr |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is no longer necessary if we are adding the bounds on each function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, but would it make more sense to add to add the bound for<'a> &'a Self: Send
, which is what the compiler is complaining about?
Also, not sure why it complains about only about the defaulted(&self)
function and not the default_mut(&mut self)
and defaulted_move(self)
functions? What is different about the mut
and move
ones?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what the difference is, maybe there is some implied bound? 🤔
trait-variant/src/variant.rs
Outdated
fn_item | ||
.default | ||
.as_ref() | ||
.map(|b| syn::parse2(quote! { { async move #b } }).unwrap()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mendess I believe what @compiler-errors means here is that every argument of the trait fn should also be moved into the block. eg
async fn defaulted_mut(&mut self, x: u32) -> i32 {
self.make(x, "10").await
}
becomes
fn defaulted_mut<'the_self_lt>(&'the_self_lt mut self, x: u32) -> impl ::core::future::Future<Output = i32> + Send
where
&'the_self_lt mut Self: Send,
{
async move {
let self = self;
let x = x;
self.make(x, "10").await
}
}
trait-variant/src/variant.rs
Outdated
syn::parse2(quote! { { async move { #(#items)* #block} } }) | ||
.expect("valid async block") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Though I'm not confident, using parse_quote
instead seems idiomatic in this case.
parse_quote! { { async move { #(#items)* #block} } }
58351ab
to
68944f5
Compare
Edited the codegen again and this is what it looks like now impl<TraitVariantBlanketType> LocalIntFactory for TraitVariantBlanketType
where
TraitVariantBlanketType: IntFactory,
for<'s> &'s Self: Send, // no longer bounds `Self: Sync`
{
const NAME: &'static str = <Self as IntFactory>::NAME;
type MyFut<'a> = <Self as IntFactory<>>::MyFut<'a> where Self: 'a;
async fn make(&self, x: u32, y: &str) -> i32 {
<Self as IntFactory>::make(self, x, y).await
}
async fn make_mut(&mut self) {
<Self as IntFactory>::make_mut(self).await
}
fn stream(&self) -> impl Iterator<Item = i32> {
<Self as IntFactory>::stream(self)
}
fn call(&self) -> u32 {
<Self as IntFactory>::call(self)
}
fn another_async(&self, input: Result<(), &str>) -> Self::MyFut<'_> {
<Self as IntFactory>::another_async(self, input)
}
async fn defaulted(&self, x: u32) -> i32 {
<Self as IntFactory>::defaulted(self, x).await
}
async fn defaulted_mut(&mut self) -> i32 {
<Self as IntFactory>::defaulted_mut(self).await
}
async fn defaulted_mut_2(&mut self) {
<Self as IntFactory>::defaulted_mut_2(self).await
}
async fn defaulted_move(self) -> i32
where
Self: Sized,
{
<Self as IntFactory>::defaulted_move(self).await
}
} |
@mendess can you please rebase it onto new main. |
68944f5
to
5fb6904
Compare
@sargarass Done! |
@compiler-errors could I get a re-review? Maybe get this merged? 👀 |
@tmandry could you also take a look? We are using the Mendess branch in our closed-source project and would like to have it merged. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I looked it over. I think I'd rather require that the needed bounds are already listed on the trait itself than silently add them to individual methods. My goal is for uses/expansions of this macro to eventually be replaced with trait aliases. It isn't possible to have a trait alias that adds where clauses to individual methods.
For the common case of Send
bounds, we can require that Sync
is also listed. In the general case, we unfortunately can't check for the user and will have to let the compiler give an error on the generated code. (Once #[diagnostic::on_unimplemented]
is stabilized in the next release I think we can do something clever with that to improve the user experience, in the general case.)
} | ||
FnArg::Typed(PatType { pat, .. }) => match pat.as_ref() { | ||
Pat::Ident(PatIdent { ident, .. }) => quote! { let #ident = #ident; }, | ||
_ => todo!(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Report an error
..sig.clone() | ||
}, | ||
fn_item.default.as_ref().map(|b| { | ||
let items = sig.inputs.iter().map(|i| match i { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let items = sig.inputs.iter().map(|i| match i { | |
let move_args = sig.inputs.iter().map(|i| match i { |
fn stream(&self) -> impl Iterator<Item = i32>; | ||
fn call(&self) -> u32; | ||
fn another_async(&self, input: Result<(), &str>) -> Self::MyFut<'_>; | ||
async fn defaulted(&self, x: u32) -> i32 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we're outgrowing using this example as a test. Feel free to keep one as an actual example, but can you move the remaining cases to a new test case under trait-variant/tests
? See #30 for an example.
.unwrap_or_default(); | ||
|
||
if self_is_sync { | ||
blanket_where_clause.push(parse_quote! { for<'s> &'s Self: Send }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here and below: This assumes the user is using Send
as a bound, but the macro is designed to be more general than that. I don't have a problem with special casing Send
to provide a better user experience, but we cannot just assume that's what the bound is.
I tested this PR and found that calling an async function that returns a static reference in the defaulted method leads to a compilation error: #[trait_variant::make(MyTrait: Send)]
// 1. lifetime may not live long enough
// returning this value requires that `'the_self_lt` must outlive `'static`
// 2. implementation of `std::marker::Send` is not general enough
// `&'0 Self` must implement `std::marker::Send`, for any lifetime `'0`...
// ...but `std::marker::Send` is actually implemented for the type `&'the_self_lt Self`
// 3. to declare that `impl std::future::Future<Output = i32> + std::marker::Send` captures data from argument `self`, you can add an explicit `'the_self_lt` lifetime bound: ` + 'the_self_lt`
// 4. lifetime `'the_self_lt` defined here
pub trait LocalMyTrait {
async fn make(&self, x: u32, y: &str) -> i32;
async fn defaulted_with_static(&self, x: u32, y: &str) -> i32 {
let _t = get_global_var().await;
self.make(x, y).await
}
}
async fn get_global_var() -> &'static i32 {
todo!()
} The expanded code: #[allow(async_fn_in_trait)]
pub trait LocalMyTrait {
async fn make(&self, x: u32, y: &str) -> i32;
async fn defaulted_with_static(&self, x: u32, y: &str) -> i32 {
let _t = get_global_var().await;
self.make(x, y).await
}
}
pub trait MyTrait: Send {
fn make(&self, x: u32, y: &str) -> impl ::core::future::Future<Output = i32> + Send;
fn defaulted_with_static<'the_self_lt>(
&'the_self_lt self,
x: u32,
y: &str,
) -> impl ::core::future::Future<Output = i32> + Send // Add 'the_self_lt bound here, it compiles
where
&'the_self_lt Self: Send,
{
async move {
let __self = self;
let x = x;
let y = y;
{
let _t = get_global_var().await;
__self.make(x, y).await
}
}
}
}
impl<TraitVariantBlanketType: MyTrait> LocalMyTrait for TraitVariantBlanketType
where
for<'s> &'s Self: Send,
{
async fn make(&self, x: u32, y: &str) -> i32 {
<Self as MyTrait>::make(self, x, y).await
}
async fn defaulted_with_static(&self, x: u32, y: &str) -> i32 {
<Self as MyTrait>::defaulted_with_static(self, x, y).await
}
} |
It also results in a lifetime mismatch error when overriding the default method: #[trait_variant::make(MyTrait: Send)]
pub trait LocalMyTrait {
async fn defaulted(&self, _x: u32, _y: &str) -> i32 {
todo!()
}
}
struct MyImpl;
impl MyTrait for MyImpl {
// 1. lifetime parameters or bounds on method `defaulted` do not match the trait declaration
// lifetimes do not match method in trait [E0195]
async fn defaulted(&self, _x: u32, _y: &str) -> i32 {
todo!()
}
} |
A possible solution for the two issues is to replace the |
Any movement? |
That is one way, and might lead to better error messages in the In the general case we can replace |
Resolves #17