diff --git a/CHANGELOG.md b/CHANGELOG.md index 70b9bad3..39e9b315 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/elfo-macros-impl/src/msg.rs b/elfo-macros-impl/src/msg.rs index af20d042..8c07cee8 100644 --- a/elfo-macros-impl/src/msg.rs +++ b/elfo-macros-impl/src/msg.rs @@ -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, }; @@ -217,18 +218,15 @@ fn add_groups(groups: &mut Vec, 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::::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 @@ -237,8 +235,8 @@ 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 => .. })`). @@ -246,24 +244,24 @@ pub fn msg_impl(input: proc_macro::TokenStream, path_to_elfo: Path) -> proc_macr trait MustBeRegularNotRequest { fn test(_: &E) {} } impl MustBeRegularNotRequest<(), E> for M {} struct Invalid; - impl + impl MustBeRegularNotRequest 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) => .. })`). @@ -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)* } @@ -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 } } }; @@ -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() } diff --git a/elfo-test/src/proxy.rs b/elfo-test/src/proxy.rs index 35e2817a..938960d6 100644 --- a/elfo-test/src/proxy.rs +++ b/elfo-test/src/proxy.rs @@ -239,7 +239,9 @@ fn testers(tx: shared::OneshotSender) -> 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), }) })) diff --git a/elfo/Cargo.toml b/elfo/Cargo.toml index 1789eab8..4792ace0 100644 --- a/elfo/Cargo.toml +++ b/elfo/Cargo.toml @@ -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 diff --git a/elfo/tests/ui.rs b/elfo/tests/ui.rs new file mode 100644 index 00000000..870c2f95 --- /dev/null +++ b/elfo/tests/ui.rs @@ -0,0 +1,5 @@ +#[test] +fn ui() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/*.rs"); +} diff --git a/elfo/tests/ui/msg_double_wild.rs b/elfo/tests/ui/msg_double_wild.rs new file mode 100644 index 00000000..1d8a9b21 --- /dev/null +++ b/elfo/tests/ui/msg_double_wild.rs @@ -0,0 +1,11 @@ +use elfo::{messages::Terminate, msg, Envelope}; + +fn test(envelope: Envelope) { + msg!(match envelope { + Terminate => {} + a => {} + b => {} + }); +} + +fn main() {} diff --git a/elfo/tests/ui/msg_double_wild.stderr b/elfo/tests/ui/msg_double_wild.stderr new file mode 100644 index 00000000..0ac26e82 --- /dev/null +++ b/elfo/tests/ui/msg_double_wild.stderr @@ -0,0 +1,5 @@ +error: this branch will never be matched + --> tests/ui/msg_double_wild.rs:7:9 + | +7 | b => {} + | ^ diff --git a/elfo/tests/ui/msg_invalid_pattern.rs b/elfo/tests/ui/msg_invalid_pattern.rs new file mode 100644 index 00000000..580787ba --- /dev/null +++ b/elfo/tests/ui/msg_invalid_pattern.rs @@ -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() {} diff --git a/elfo/tests/ui/msg_invalid_pattern.stderr b/elfo/tests/ui/msg_invalid_pattern.stderr new file mode 100644 index 00000000..98f0511d --- /dev/null +++ b/elfo/tests/ui/msg_invalid_pattern.stderr @@ -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) => {} + | ^^^^^^^^^^^^^^^^^ diff --git a/elfo/tests/ui/msg_regular_syntax_for_request.rs b/elfo/tests/ui/msg_regular_syntax_for_request.rs new file mode 100644 index 00000000..3bfe9014 --- /dev/null +++ b/elfo/tests/ui/msg_regular_syntax_for_request.rs @@ -0,0 +1,12 @@ +use elfo::{message, msg, Envelope}; + +#[message(ret = u32)] +struct SomeRequest; + +fn test(envelope: Envelope) { + msg!(match envelope { + SomeRequest => {} + }); +} + +fn main() {} diff --git a/elfo/tests/ui/msg_regular_syntax_for_request.stderr b/elfo/tests/ui/msg_regular_syntax_for_request.stderr new file mode 100644 index 00000000..70cc1c12 --- /dev/null +++ b/elfo/tests/ui/msg_regular_syntax_for_request.stderr @@ -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) diff --git a/elfo/tests/ui/msg_request_syntax_for_regular.rs b/elfo/tests/ui/msg_request_syntax_for_regular.rs new file mode 100644 index 00000000..7d6feff0 --- /dev/null +++ b/elfo/tests/ui/msg_request_syntax_for_regular.rs @@ -0,0 +1,12 @@ +use elfo::{message, msg, Envelope}; + +#[message] +struct SomeEvent; + +fn test(envelope: Envelope) { + msg!(match envelope { + (SomeEvent, token) => {} + }); +} + +fn main() {} diff --git a/elfo/tests/ui/msg_request_syntax_for_regular.stderr b/elfo/tests/ui/msg_request_syntax_for_regular.stderr new file mode 100644 index 00000000..c282cfc2 --- /dev/null +++ b/elfo/tests/ui/msg_request_syntax_for_regular.stderr @@ -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) diff --git a/elfo/tests/ui/msg_unused_token.rs b/elfo/tests/ui/msg_unused_token.rs new file mode 100644 index 00000000..41c8cf53 --- /dev/null +++ b/elfo/tests/ui/msg_unused_token.rs @@ -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() {} diff --git a/elfo/tests/ui/msg_unused_token.stderr b/elfo/tests/ui/msg_unused_token.stderr new file mode 100644 index 00000000..cdc42ecd --- /dev/null +++ b/elfo/tests/ui/msg_unused_token.stderr @@ -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) => {} + | ^^^^^^