diff --git a/README.md b/README.md index c47a734f4..d05ea9b01 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,9 @@ Neon! See our [Neon fundamentals docs](https://neon-bindings.com/docs/intro) and our [API docs](https://docs.rs/neon/latest/neon). +Compiler diagnostics may include error codes like `N0001`. Refer to +[the error code reference](doc/error-codes.md) for details. + ## Neon 1.0.0 Migration Guide The latest version of Neon, 1.0.0, includes several breaking changes in order to fix unsoundness, improve consistency, and add features. diff --git a/crates/neon-macros/src/error.rs b/crates/neon-macros/src/error.rs new file mode 100644 index 000000000..9516f899e --- /dev/null +++ b/crates/neon-macros/src/error.rs @@ -0,0 +1,55 @@ +#[derive(Copy, Clone)] +pub(crate) enum ErrorCode { + UnsupportedExportItem, + WrongContextChannelRef, + ContextMustBeMut, + WrongContextChannel, + MustBeMutRef, + ChannelByRef, + ChannelContextRef, + ContextNotAvailable, + MissingContext, + SelfReceiver, + UnsupportedProperty, + AsyncAttrAsyncFn, +} + +impl ErrorCode { + pub(crate) fn code(self) -> &'static str { + match self { + ErrorCode::UnsupportedExportItem => "N0001", + ErrorCode::WrongContextChannelRef => "N0002", + ErrorCode::ContextMustBeMut => "N0003", + ErrorCode::WrongContextChannel => "N0004", + ErrorCode::MustBeMutRef => "N0005", + ErrorCode::ChannelByRef => "N0006", + ErrorCode::ChannelContextRef => "N0007", + ErrorCode::ContextNotAvailable => "N0008", + ErrorCode::MissingContext => "N0009", + ErrorCode::SelfReceiver => "N0010", + ErrorCode::UnsupportedProperty => "N0011", + ErrorCode::AsyncAttrAsyncFn => "N0012", + } + } + + pub(crate) fn message(self) -> &'static str { + match self { + ErrorCode::UnsupportedExportItem => "`neon::export` can only be applied to functions, consts, and statics.", + ErrorCode::WrongContextChannelRef => "Expected `&mut Cx` instead of a `Channel` reference.", + ErrorCode::ContextMustBeMut => "Context must be a `&mut` reference.", + ErrorCode::WrongContextChannel => "Expected `&mut Cx` instead of `Channel`.", + ErrorCode::MustBeMutRef => "Must be a `&mut` reference.", + ErrorCode::ChannelByRef => "Expected an owned `Channel` instead of a reference.", + ErrorCode::ChannelContextRef => "Expected an owned `Channel` instead of a context reference.", + ErrorCode::ContextNotAvailable => "Context is not available in async functions. Try a `Channel` instead.", + ErrorCode::MissingContext => "Expected a context argument. Try removing the `context` attribute.", + ErrorCode::SelfReceiver => "Exported functions cannot receive `self`.", + ErrorCode::UnsupportedProperty => "unsupported property", + ErrorCode::AsyncAttrAsyncFn => "`async` attribute should not be used with an `async fn`", + } + } +} + +pub(crate) fn error(span: proc_macro2::Span, code: ErrorCode) -> syn::Error { + syn::Error::new(span, format!("{} [{}]", code.message(), code.code())) +} diff --git a/crates/neon-macros/src/export/function/meta.rs b/crates/neon-macros/src/export/function/meta.rs index cfe2bd776..02267ef1d 100644 --- a/crates/neon-macros/src/export/function/meta.rs +++ b/crates/neon-macros/src/export/function/meta.rs @@ -16,6 +16,8 @@ pub(super) enum Kind { Task, } +use crate::error::ErrorCode; + impl Meta { fn set_name(&mut self, meta: syn::meta::ParseNestedMeta) -> syn::Result<()> { self.name = Some(meta.value()?.parse::()?); @@ -43,7 +45,13 @@ impl Meta { fn make_async(&mut self, meta: syn::meta::ParseNestedMeta) -> syn::Result<()> { if matches!(self.kind, Kind::AsyncFn) { - return Err(meta.error("`async` attribute should not be used with an `async fn`")); + return Err(meta.error( + format!( + "{} [{}]", + ErrorCode::AsyncAttrAsyncFn.message(), + ErrorCode::AsyncAttrAsyncFn.code() + ), + )); } self.kind = Kind::Async; @@ -102,7 +110,13 @@ impl syn::parse::Parser for Parser { return attr.make_task(meta); } - Err(meta.error("unsupported property")) + Err(meta.error( + format!( + "{} [{}]", + ErrorCode::UnsupportedProperty.message(), + ErrorCode::UnsupportedProperty.code() + ), + )) }); parser.parse2(tokens)?; diff --git a/crates/neon-macros/src/export/function/mod.rs b/crates/neon-macros/src/export/function/mod.rs index 651650f83..74f9348ee 100644 --- a/crates/neon-macros/src/export/function/mod.rs +++ b/crates/neon-macros/src/export/function/mod.rs @@ -189,6 +189,8 @@ fn context_parse( // * Context argument must be a `&mut` reference // * First argument must not be `Channel` // * Must not be a `self` receiver +use crate::error::{self, ErrorCode}; + fn check_context(opts: &meta::Meta, sig: &syn::Signature) -> syn::Result { // Extract the first argument let ty = match first_arg(opts, sig)? { @@ -200,28 +202,19 @@ fn check_context(opts: &meta::Meta, sig: &syn::Signature) -> syn::Result { let ty = match &*ty.ty { // Tried to use a borrowed Channel syn::Type::Reference(ty) if !opts.context && is_channel_type(&ty.elem) => { - return Err(syn::Error::new( - ty.elem.span(), - "Expected `&mut Cx` instead of a `Channel` reference.", - )) + return Err(error::error(ty.elem.span(), ErrorCode::WrongContextChannelRef)) } syn::Type::Reference(ty) => ty, // Context needs to be a reference _ if opts.context || is_context_type(&ty.ty) => { - return Err(syn::Error::new( - ty.ty.span(), - "Context must be a `&mut` reference.", - )) + return Err(error::error(ty.ty.span(), ErrorCode::ContextMustBeMut)) } // Hint that `Channel` should be swapped for `&mut Cx` _ if is_channel_type(&ty.ty) => { - return Err(syn::Error::new( - ty.ty.span(), - "Expected `&mut Cx` instead of `Channel`.", - )) + return Err(error::error(ty.ty.span(), ErrorCode::WrongContextChannel)) } _ => return Ok(false), @@ -234,7 +227,7 @@ fn check_context(opts: &meta::Meta, sig: &syn::Signature) -> syn::Result { // Context argument must be mutable if ty.mutability.is_none() { - return Err(syn::Error::new(ty.span(), "Must be a `&mut` reference.")); + return Err(error::error(ty.span(), ErrorCode::MustBeMutRef)); } // All tests passed! @@ -258,25 +251,22 @@ fn check_channel(opts: &meta::Meta, sig: &syn::Signature) -> syn::Result { match &*ty.ty { // Provided `&mut Channel` instead of `Channel` syn::Type::Reference(ty) if opts.context || is_channel_type(&ty.elem) => { - Err(syn::Error::new( - ty.span(), - "Expected an owned `Channel` instead of a reference.", - )) + Err(error::error(ty.span(), ErrorCode::ChannelByRef)) } // Provided a `&mut Cx` instead of a `Channel` - syn::Type::Reference(ty) if is_context_type(&ty.elem) => Err(syn::Error::new( + syn::Type::Reference(ty) if is_context_type(&ty.elem) => Err(error::error( ty.elem.span(), - "Expected an owned `Channel` instead of a context reference.", + ErrorCode::ChannelContextRef, )), // Found a `Channel` _ if opts.context || is_channel_type(&ty.ty) => Ok(true), // Tried to use an owned `Cx` - _ if is_context_type(&ty.ty) => Err(syn::Error::new( + _ if is_context_type(&ty.ty) => Err(error::error( ty.ty.span(), - "Context is not available in async functions. Try a `Channel` instead.", + ErrorCode::ContextNotAvailable, )), _ => Ok(false), @@ -294,9 +284,9 @@ fn first_arg<'a>( // If context was forced, error to let the user know the mistake None if opts.context => { - return Err(syn::Error::new( + return Err(error::error( sig.inputs.span(), - "Expected a context argument. Try removing the `context` attribute.", + ErrorCode::MissingContext, )) } @@ -306,9 +296,9 @@ fn first_arg<'a>( // Expect a typed pattern; self receivers are not supported match arg { syn::FnArg::Typed(ty) => Ok(Some(ty)), - syn::FnArg::Receiver(arg) => Err(syn::Error::new( + syn::FnArg::Receiver(arg) => Err(error::error( arg.span(), - "Exported functions cannot receive `self`.", + ErrorCode::SelfReceiver, )), } } diff --git a/crates/neon-macros/src/export/global/meta.rs b/crates/neon-macros/src/export/global/meta.rs index 91b392727..9d2097b00 100644 --- a/crates/neon-macros/src/export/global/meta.rs +++ b/crates/neon-macros/src/export/global/meta.rs @@ -5,6 +5,7 @@ pub(crate) struct Meta { } pub(crate) struct Parser; +use crate::error::ErrorCode; impl syn::parse::Parser for Parser { type Output = Meta; @@ -24,7 +25,13 @@ impl syn::parse::Parser for Parser { return Ok(()); } - Err(meta.error("unsupported property")) + Err(meta.error( + format!( + "{} [{}]", + ErrorCode::UnsupportedProperty.message(), + ErrorCode::UnsupportedProperty.code() + ), + )) }); parser.parse2(tokens)?; diff --git a/crates/neon-macros/src/export/mod.rs b/crates/neon-macros/src/export/mod.rs index cccbb2ccf..8bcbab357 100644 --- a/crates/neon-macros/src/export/mod.rs +++ b/crates/neon-macros/src/export/mod.rs @@ -1,5 +1,6 @@ mod function; mod global; +use crate::error::{self, ErrorCode}; // N.B.: Meta attribute parsing happens in this function because `syn::parse_macro_input!` // must be called from a function that returns `proc_macro::TokenStream`. @@ -45,8 +46,7 @@ pub(crate) fn export( // Generate an error for unsupported item types fn unsupported(item: syn::Item) -> proc_macro::TokenStream { let span = syn::spanned::Spanned::span(&item); - let msg = "`neon::export` can only be applied to functions, consts, and statics."; - let err = syn::Error::new(span, msg); + let err = error::error(span, ErrorCode::UnsupportedExportItem); err.into_compile_error().into() } diff --git a/crates/neon-macros/src/lib.rs b/crates/neon-macros/src/lib.rs index fe68c2111..5788fbba5 100644 --- a/crates/neon-macros/src/lib.rs +++ b/crates/neon-macros/src/lib.rs @@ -1,6 +1,7 @@ //! Procedural macros supporting [Neon](https://docs.rs/neon/latest/neon/) mod export; +mod error; #[proc_macro_attribute] pub fn main( diff --git a/doc/error-codes.md b/doc/error-codes.md new file mode 100644 index 000000000..98cded38a --- /dev/null +++ b/doc/error-codes.md @@ -0,0 +1,18 @@ +# Neon Error Codes + +Neon compiler errors include a short code in brackets. The table below lists each code and the associated message. + +| Code | Message | +|-------|---------| +| N0001 | `neon::export` can only be applied to functions, consts, and statics. | +| N0002 | Expected `&mut Cx` instead of a `Channel` reference. | +| N0003 | Context must be a `&mut` reference. | +| N0004 | Expected `&mut Cx` instead of `Channel`. | +| N0005 | Must be a `&mut` reference. | +| N0006 | Expected an owned `Channel` instead of a reference. | +| N0007 | Expected an owned `Channel` instead of a context reference. | +| N0008 | Context is not available in async functions. Try a `Channel` instead. | +| N0009 | Expected a context argument. Try removing the `context` attribute. | +| N0010 | Exported functions cannot receive `self`. | +| N0011 | Unsupported property. | +| N0012 | `async` attribute should not be used with an `async fn`. | diff --git a/test/ui/tests/fail/missing-context.stderr b/test/ui/tests/fail/missing-context.stderr index b00ee44a2..431446076 100644 --- a/test/ui/tests/fail/missing-context.stderr +++ b/test/ui/tests/fail/missing-context.stderr @@ -1,4 +1,4 @@ -error: Expected a context argument. Try removing the `context` attribute. +error: Expected a context argument. Try removing the `context` attribute. [N0009] --> tests/fail/missing-context.rs:1:1 | 1 | #[neon::export(context)] diff --git a/test/ui/tests/fail/need-borrowed-context.stderr b/test/ui/tests/fail/need-borrowed-context.stderr index 1a807a3a6..4ad8cbd13 100644 --- a/test/ui/tests/fail/need-borrowed-context.stderr +++ b/test/ui/tests/fail/need-borrowed-context.stderr @@ -1,34 +1,34 @@ -error: Context must be a `&mut` reference. +error: Context must be a `&mut` reference. [N0003] --> tests/fail/need-borrowed-context.rs:2:18 | 2 | fn owned_cx(_cx: Cx) {} | ^^ -error: Context must be a `&mut` reference. +error: Context must be a `&mut` reference. [N0003] --> tests/fail/need-borrowed-context.rs:5:27 | 5 | fn owned_function_cx(_cx: FunctionContext) {} | ^^^^^^^^^^^^^^^ -error: Must be a `&mut` reference. +error: Must be a `&mut` reference. [N0005] --> tests/fail/need-borrowed-context.rs:8:16 | 8 | fn ref_cx(_cx: &Cx) {} | ^ -error: Must be a `&mut` reference. +error: Must be a `&mut` reference. [N0005] --> tests/fail/need-borrowed-context.rs:11:25 | 11 | fn ref_function_cx(_cx: &FunctionContext) {} | ^ -error: Context must be a `&mut` reference. +error: Context must be a `&mut` reference. [N0003] --> tests/fail/need-borrowed-context.rs:14:19 | 14 | fn forced_cx(_cx: String) {} | ^^^^^^ -error: Must be a `&mut` reference. +error: Must be a `&mut` reference. [N0005] --> tests/fail/need-borrowed-context.rs:17:23 | 17 | fn forced_ref_cx(_cx: &String) {} diff --git a/test/ui/tests/fail/unexpected-self.stderr b/test/ui/tests/fail/unexpected-self.stderr index e60386084..4b1f0f01f 100644 --- a/test/ui/tests/fail/unexpected-self.stderr +++ b/test/ui/tests/fail/unexpected-self.stderr @@ -1,16 +1,16 @@ -error: Exported functions cannot receive `self`. +error: Exported functions cannot receive `self`. [N0010] --> tests/fail/unexpected-self.rs:5:15 | 5 | fn borrow(&self) {} | ^ -error: Exported functions cannot receive `self`. +error: Exported functions cannot receive `self`. [N0010] --> tests/fail/unexpected-self.rs:8:19 | 8 | fn borrow_mut(&mut self) {} | ^ -error: Exported functions cannot receive `self`. +error: Exported functions cannot receive `self`. [N0010] --> tests/fail/unexpected-self.rs:11:14 | 11 | fn owned(self) {} diff --git a/test/ui/tests/fail/unnecessary-attribute.stderr b/test/ui/tests/fail/unnecessary-attribute.stderr index 9b931ade6..a04a313b4 100644 --- a/test/ui/tests/fail/unnecessary-attribute.stderr +++ b/test/ui/tests/fail/unnecessary-attribute.stderr @@ -1,4 +1,4 @@ -error: `async` attribute should not be used with an `async fn` +error: `async` attribute should not be used with an `async fn` [N0012] --> tests/fail/unnecessary-attribute.rs:1:16 | 1 | #[neon::export(async)] diff --git a/test/ui/tests/fail/unsupported-property.stderr b/test/ui/tests/fail/unsupported-property.stderr index d2a3a1f45..1897a8d68 100644 --- a/test/ui/tests/fail/unsupported-property.stderr +++ b/test/ui/tests/fail/unsupported-property.stderr @@ -1,10 +1,10 @@ -error: unsupported property +error: unsupported property [N0011] --> tests/fail/unsupported-property.rs:1:16 | 1 | #[neon::export(foo)] | ^^^ -error: unsupported property +error: unsupported property [N0011] --> tests/fail/unsupported-property.rs:4:16 | 4 | #[neon::export(foo)] diff --git a/test/ui/tests/fail/wrong-context.stderr b/test/ui/tests/fail/wrong-context.stderr index 293873bf5..773c859a9 100644 --- a/test/ui/tests/fail/wrong-context.stderr +++ b/test/ui/tests/fail/wrong-context.stderr @@ -1,76 +1,76 @@ -error: Expected `&mut Cx` instead of `Channel`. +error: Expected `&mut Cx` instead of `Channel`. [N0004] --> tests/fail/wrong-context.rs:2:22 | 2 | fn sync_channel(_ch: Channel) {} | ^^^^^^^ -error: Expected `&mut Cx` instead of a `Channel` reference. +error: Expected `&mut Cx` instead of a `Channel` reference. [N0002] --> tests/fail/wrong-context.rs:5:34 | 5 | fn sync_borrow_channel(_ch: &mut Channel) {} | ^^^^^^^ -error: Expected `&mut Cx` instead of `Channel`. +error: Expected `&mut Cx` instead of `Channel`. [N0004] --> tests/fail/wrong-context.rs:8:23 | 8 | fn async_channel(_ch: Channel) {} | ^^^^^^^ -error: Expected `&mut Cx` instead of a `Channel` reference. +error: Expected `&mut Cx` instead of a `Channel` reference. [N0002] --> tests/fail/wrong-context.rs:11:35 | 11 | fn async_borrow_channel(_ch: &mut Channel) {} | ^^^^^^^ -error: Context is not available in async functions. Try a `Channel` instead. +error: Context is not available in async functions. Try a `Channel` instead. [N0008] --> tests/fail/wrong-context.rs:14:24 | 14 | async fn async_cx(_cx: Cx) {} | ^^ -error: Context is not available in async functions. Try a `Channel` instead. +error: Context is not available in async functions. Try a `Channel` instead. [N0008] --> tests/fail/wrong-context.rs:17:38 | 17 | async fn async_function_context(_cx: FunctionContext) {} | ^^^^^^^^^^^^^^^ -error: Expected an owned `Channel` instead of a context reference. +error: Expected an owned `Channel` instead of a context reference. [N0007] --> tests/fail/wrong-context.rs:20:29 | 20 | async fn async_cx_ref(_cx: &Cx) {} | ^^ -error: Expected an owned `Channel` instead of a reference. +error: Expected an owned `Channel` instead of a reference. [N0006] --> tests/fail/wrong-context.rs:23:36 | 23 | async fn async_borrow_channel(_cx: &Channel) {} | ^ -error: Expected an owned `Channel` instead of a reference. +error: Expected an owned `Channel` instead of a reference. [N0006] --> tests/fail/wrong-context.rs:26:43 | 26 | async fn async_borrow_forced_channel(_cx: &String) {} | ^ -error: Expected an owned `Channel` instead of a context reference. +error: Expected an owned `Channel` instead of a context reference. [N0007] --> tests/fail/wrong-context.rs:29:43 | 29 | async fn async_function_context_ref(_cx: &FunctionContext) {} | ^^^^^^^^^^^^^^^ -error: Context is not available in async functions. Try a `Channel` instead. +error: Context is not available in async functions. Try a `Channel` instead. [N0008] --> tests/fail/wrong-context.rs:32:31 | 32 | fn task_function_context(_cx: FunctionContext) {} | ^^^^^^^^^^^^^^^ -error: Expected an owned `Channel` instead of a context reference. +error: Expected an owned `Channel` instead of a context reference. [N0007] --> tests/fail/wrong-context.rs:35:22 | 35 | fn task_cx_ref(_cx: &Cx) {} | ^^ -error: Expected an owned `Channel` instead of a context reference. +error: Expected an owned `Channel` instead of a context reference. [N0007] --> tests/fail/wrong-context.rs:38:36 | 38 | fn task_function_context_ref(_cx: &FunctionContext) {}