-
-
Notifications
You must be signed in to change notification settings - Fork 34
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
Expose derive macro (for better Rust-Analyzer compatibility) #320
Conversation
let allowed_lints = global_allowed_lints(); | ||
quote! { | ||
#allowed_lints |
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.
The global lint allowlist is normally only applied to items in the private scope.
@@ -910,6 +912,7 @@ fn make_drop_impl(cx: &Context<'_>) -> TokenStream { | |||
/// On enums, only methods that the returned projected type is named will be generated. | |||
fn make_proj_impl( | |||
cx: &Context<'_>, | |||
expose: bool, |
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.
It's a bit hacky to have to run make_proj_twice
, might be better to make a single-use struct and just return both cases at once?
quote! { | ||
#allowed_lints |
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 does not generate a #[allow(dead_code)]
, which is also applied globally to the private scope. IMO, renaming the projection is a pretty clear signal that you're planning to use it, and silencing the warning is a false negative in this case.
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.
The CI failures are about these lints. Leaving them until a consensus has been reached on where to go about that.
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.
Basically, the warning is not issued to the method itself even if no #[allow(dead_code)]
because fn project()
has call-site span. The problem is the macro_rules macro changes the span (rust-lang/rust#77973).
I would prefer to use #[allow(dead_code)]
because it's preferable to work the same as when it's not called inside macros.
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.
Yeah makes sense.
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.
Alright, tried to get the spans to make sense but couldn't get it to cooperate, so I added the #[allow]
.
Thanks for the PR! First, under what situation and what improvements does this bring? Second, I agree with it's preferable to be compatible with RA, but AFAIK few people actually names the projection type and the API generated by pin-project is very simple, so I'm currently feeling this doesn't bring an advantage beyond the user confusion of having two same API. |
You also need to set After that it should look something like this (with LSP inlays and completions enabled): This PR also allows RA's go-to-definition and (to a degree) refactoring features to cross projection boundaries. For example, go-to-definition at the
The API generated by pin-project itself, sure, the important thing IMO is completion for the stuff contained inside those structs. As someone who spends a fair amount of time writing custom #[pin_project]
struct Map<T, F> {
#[pin]
inner: T,
map: F,
}
impl<T: Future, U, F: FnMut(T::Output) -> U> Future for Map<T, F> {
type Output = U;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<U> {
let this = self.project();
let inner: Pin<&mut T> = this.inner;
let map: &mut F = this.map;
Poll::Ready(map(ready!(inner.poll(cx))))
}
}
|
I don't feel very comfortable with the idea of modifying the public API solely to support rust-analyzer. Additionally, wouldn't every single crate that defines an attribute macro need to do something like this? I'm sympathetic to the issue of missing completions, but entirely avoiding a Rust feature doesn't seem like the best suction. Can you elaborate on what's blocking RA from expanding attribute macros? |
Not sure here. For example, Snafu was a derive macro since way before Rust-Analyzer supported proc macros.
The only libraries that I use regularly that use attribute macros are Tokio and pin-project. Tokio's attribute macros don't add any new API surface (items or impls), so they aren't affected by this problem anyway.
I'm not too familiar with RA's internals, but as far as I understand it, derive macros are easier to implement because they are guaranteed to be purely additive (can't change the annotated item itself, only generate new items), and are independent from each other (they can't see or interact with each other during the expansion process). IMO it makes sense to commit to (and implicitly document) those constraints, even if RA wasn't a factor (although admittedly I probably wouldn't have bothered to submit a PR in that case.. :P). |
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.
The rust-analyzer team seems to be starting to work on support for attribute proc-macros (rust-lang/rust-analyzer#8486), so I would prefer to wait for it instead of adding the API on the pin-project side for now.
Fixes #319
With this patch Rust-Analyzer will give proper suggestions iff you use
#[derive(PinProject)]
and you rename the projections that you use (#[pin(project = FooProj, project_ref = FooProjRef)
). The existing#[pin_project]
macro still exists unchanged.#[pin]
attribute optional (not an issue before, since this was always generated when using#[pin_project]
)