Skip to content

Commit

Permalink
fix(macros/msg): better go-to-definition via RA
Browse files Browse the repository at this point in the history
Closes #141.
  • Loading branch information
loyd committed Nov 30, 2024
1 parent 5385428 commit 8284305
Show file tree
Hide file tree
Showing 15 changed files with 174 additions and 24 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- core: update the `idr-ebr` crate to v0.3 to fix possible crash in `Context::finished()`.
- logger: replace the `atty` crate with `IsTerminal` to fix cargo-audit warnings.
- macros/msg: better go-to-definition via RA ([#141]).

[#74]: https://github.com/elfo-rs/elfo/issues/74
[#135]: https://github.com/elfo-rs/elfo/pull/135
[#136]: https://github.com/elfo-rs/elfo/pull/136
[#137]: https://github.com/elfo-rs/elfo/pull/137
[#141]: https://github.com/elfo-rs/elfo/issues/141

## [0.2.0-alpha.16] - 2024-07-24
### Added
Expand Down
45 changes: 22 additions & 23 deletions elfo-macros-impl/src/msg.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{char, collections::HashMap};

use quote::{quote, quote_spanned};
use proc_macro2::Span;
use quote::quote_spanned;
use syn::{
parse_macro_input, spanned::Spanned, Arm, ExprMatch, Ident, Pat, PatIdent, PatWild, Path, Token,
};
Expand Down Expand Up @@ -217,18 +218,15 @@ fn add_groups(groups: &mut Vec<MessageGroup>, mut arm: Arm) {

/// Implements the `msg!` macro.
pub fn msg_impl(input: proc_macro::TokenStream, path_to_elfo: Path) -> proc_macro::TokenStream {
let crate_ = path_to_elfo;
let mixed_site = Span::mixed_site();
let input = parse_macro_input!(input as ExprMatch);
let mut groups = Vec::<MessageGroup>::with_capacity(input.arms.len());
let crate_ = path_to_elfo;
let internal = quote![#crate_::_priv];

for arm in input.arms.into_iter() {
add_groups(&mut groups, arm);
}

let type_id_ident = quote! { _elfo_type_id };
let envelope_ident = quote! { _elfo_envelope };

// println!(">>> HERE {:#?}", groups);

let groups = groups
Expand All @@ -237,33 +235,33 @@ pub fn msg_impl(input: proc_macro::TokenStream, path_to_elfo: Path) -> proc_macr
// Specify the span for better error localization:
// - used the regular syntax while the request one is expected
// - unexhaustive match
(GroupKind::Regular(path), arms) => quote_spanned! { path.span()=>
else if #type_id_ident == <#path as #crate_::Message>::_type_id() {
(GroupKind::Regular(path), arms) => quote_spanned! {mixed_site=>
else if type_id == <#path as #crate_::Message>::_type_id() {
// Ensure it's not a request, or a request but only in a borrowed context.
// We cannot use `static_assertions` here because it wraps the check into
// a closure that forbids us to use generic `msg!`: (`msg!(match e { M => .. })`).
{
trait MustBeRegularNotRequest<A, E> { fn test(_: &E) {} }
impl<E, M> MustBeRegularNotRequest<(), E> for M {}
struct Invalid;
impl<E: #internal::EnvelopeOwned, M: #crate_::Request>
impl<E: internal::EnvelopeOwned, M: #crate_::Request>
MustBeRegularNotRequest<Invalid, E> for M {}
<#path as MustBeRegularNotRequest<_, _>>::test(&#envelope_ident)
<#path as MustBeRegularNotRequest<_, _>>::test(&envelope)
}

#[allow(unknown_lints, clippy::blocks_in_conditions)]
match {
// Support both owned and borrowed contexts, relying on the type inference.
#[allow(unused_imports)]
use #internal::{EnvelopeOwned as _, EnvelopeBorrowed as _};
unsafe { #envelope_ident.unpack_regular_unchecked::<#path>() }
use internal::{EnvelopeOwned as _, EnvelopeBorrowed as _};
unsafe { envelope.unpack_regular_unchecked::<#path>() }
} {
#(#arms)*
}
}
},
(GroupKind::Request(path), arms) => quote_spanned! { path.span()=>
else if #type_id_ident == <#path as #crate_::Message>::_type_id() {
(GroupKind::Request(path), arms) => quote_spanned! {mixed_site=>
else if type_id == <#path as #crate_::Message>::_type_id() {
// Ensure it's a request. We cannot use `static_assertions` here
// because it wraps the check into a closure that forbids us to
// use generic `msg!`: (`msg!(match e { (R, token) => .. })`).
Expand All @@ -276,8 +274,8 @@ pub fn msg_impl(input: proc_macro::TokenStream, path_to_elfo: Path) -> proc_macr
match {
// Only the owned context is supported.
#[allow(unused_imports)]
use #internal::EnvelopeOwned as _;
unsafe { #envelope_ident.unpack_request_unchecked::<#path>() }
use internal::EnvelopeOwned as _;
unsafe { envelope.unpack_request_unchecked::<#path>() }
} {
#(#arms)*
}
Expand All @@ -287,9 +285,9 @@ pub fn msg_impl(input: proc_macro::TokenStream, path_to_elfo: Path) -> proc_macr
let mut arms_iter = arms.iter();
let arm = arms_iter.next().unwrap();

let expanded = quote! {
let expanded = quote_spanned! {mixed_site=>
else {
match #envelope_ident { #arm }
match envelope { #arm }
}
};

Expand All @@ -304,17 +302,18 @@ pub fn msg_impl(input: proc_macro::TokenStream, path_to_elfo: Path) -> proc_macr
let match_expr = input.expr;

// TODO: propagate `input.attrs`?
let expanded = quote! {{
let #envelope_ident = #match_expr;
let #type_id_ident = #envelope_ident.type_id();
let expanded = quote_spanned!(mixed_site=> {
use #crate_::_priv as internal;
let envelope = #match_expr;
let type_id = envelope.type_id();
#[allow(clippy::suspicious_else_formatting)]
if false { unreachable!(); }
#(#groups)*
}};
});

// Errors must be checked after expansion, otherwise some errors can be lost.
if let Some(errors) = crate::errors::into_tokens() {
quote! {{ #errors #expanded }}.into()
quote_spanned!(mixed_site=> { #errors #expanded }).into()
} else {
expanded.into()
}
Expand Down
4 changes: 3 additions & 1 deletion elfo-test/src/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,9 @@ fn testers(tx: shared::OneshotSender<ProxyContext>) -> Blueprint {
ActorGroup::new()
.router(MapRouter::new(move |envelope| {
msg!(match envelope {
StealContext => Outcome::Unicast(next_tester_key.fetch_add(1, Ordering::SeqCst)),
StealContext => {
Outcome::Unicast(next_tester_key.fetch_add(1, Ordering::SeqCst))
}
_ => Outcome::Unicast(0),
})
}))
Expand Down
1 change: 1 addition & 0 deletions elfo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ parking_lot = "0.12"
libc = "0.2.97"
futures-intrusive = "0.5"
turmoil = "0.6"
trybuild = "1.0"

[package.metadata.docs.rs]
all-features = true
Expand Down
5 changes: 5 additions & 0 deletions elfo/tests/ui.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#[test]
fn ui() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/*.rs");
}
11 changes: 11 additions & 0 deletions elfo/tests/ui/msg_double_wild.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use elfo::{messages::Terminate, msg, Envelope};

fn test(envelope: Envelope) {
msg!(match envelope {
Terminate => {}
a => {}
b => {}
});
}

fn main() {}
5 changes: 5 additions & 0 deletions elfo/tests/ui/msg_double_wild.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: this branch will never be matched
--> tests/ui/msg_double_wild.rs:7:9
|
7 | b => {}
| ^
15 changes: 15 additions & 0 deletions elfo/tests/ui/msg_invalid_pattern.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use elfo::{msg, Envelope};

// TODO: check more invalid patterns.
fn test(envelope: Envelope) {
msg!(match envelope {
foo!() => {}
"liternal" => {}
10..20 => {}
(A | B) => {}
(SomeRequest, token, extra) => {}
(SomeRequest, 20) => {}
});
}

fn main() {}
35 changes: 35 additions & 0 deletions elfo/tests/ui/msg_invalid_pattern.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
error: macros in pattern position are forbidden
--> tests/ui/msg_invalid_pattern.rs:6:9
|
6 | foo!() => {}
| ^^^

error: literal patterns are forbidden
--> tests/ui/msg_invalid_pattern.rs:7:9
|
7 | "liternal" => {}
| ^^^^^^^^^^

error: range patterns are forbidden
--> tests/ui/msg_invalid_pattern.rs:8:9
|
8 | 10..20 => {}
| ^^

error: parenthesized patterns are forbidden
--> tests/ui/msg_invalid_pattern.rs:9:9
|
9 | (A | B) => {}
| ^^^^^^^

error: invalid request pattern
--> tests/ui/msg_invalid_pattern.rs:10:9
|
10 | (SomeRequest, token, extra) => {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: token must be identifier
--> tests/ui/msg_invalid_pattern.rs:11:9
|
11 | (SomeRequest, 20) => {}
| ^^^^^^^^^^^^^^^^^
12 changes: 12 additions & 0 deletions elfo/tests/ui/msg_regular_syntax_for_request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use elfo::{message, msg, Envelope};

#[message(ret = u32)]
struct SomeRequest;

fn test(envelope: Envelope) {
msg!(match envelope {
SomeRequest => {}
});
}

fn main() {}
14 changes: 14 additions & 0 deletions elfo/tests/ui/msg_regular_syntax_for_request.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error[E0283]: type annotations needed
--> tests/ui/msg_regular_syntax_for_request.rs:8:9
|
8 | SomeRequest => {}
| ^^^^^^^^^^^ cannot infer type
|
note: multiple `impl`s satisfying `SomeRequest: MustBeRegularNotRequest<_, Envelope>` found
--> tests/ui/msg_regular_syntax_for_request.rs:7:5
|
7 | / msg!(match envelope {
8 | | SomeRequest => {}
9 | | });
| |______^
= note: this error originates in the macro `msg` (in Nightly builds, run with -Z macro-backtrace for more info)
12 changes: 12 additions & 0 deletions elfo/tests/ui/msg_request_syntax_for_regular.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use elfo::{message, msg, Envelope};

#[message]
struct SomeEvent;

fn test(envelope: Envelope) {
msg!(match envelope {
(SomeEvent, token) => {}
});
}

fn main() {}
20 changes: 20 additions & 0 deletions elfo/tests/ui/msg_request_syntax_for_regular.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
error[E0277]: the trait bound `SomeEvent: elfo::Request` is not satisfied
--> tests/ui/msg_request_syntax_for_regular.rs:8:10
|
8 | (SomeEvent, token) => {}
| ^^^^^^^^^ the trait `elfo::Request` is not implemented for `SomeEvent`
|
= help: the following other types implement trait `elfo::Request`:
Ping
ReloadConfigs
StartEntrypoint
UpdateConfig
ValidateConfig
note: required by a bound in `must_be_request`
--> tests/ui/msg_request_syntax_for_regular.rs:7:5
|
7 | / msg!(match envelope {
8 | | (SomeEvent, token) => {}
9 | | });
| |______^ required by this bound in `must_be_request`
= note: this error originates in the macro `msg` (in Nightly builds, run with -Z macro-backtrace for more info)
12 changes: 12 additions & 0 deletions elfo/tests/ui/msg_unused_token.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use elfo::{message, msg, Envelope};

#[message(ret = u32)]
struct SomeRequest;

fn test(envelope: Envelope) {
msg!(match envelope {
(SomeRequest, _token) => {}
});
}

fn main() {}
5 changes: 5 additions & 0 deletions elfo/tests/ui/msg_unused_token.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: the token must be used, or call `drop(_)` explicitly
--> tests/ui/msg_unused_token.rs:8:23
|
8 | (SomeRequest, _token) => {}
| ^^^^^^

0 comments on commit 8284305

Please sign in to comment.