diff --git a/Cargo.toml b/Cargo.toml index 36a7439..f9270a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,5 +33,5 @@ proc-macro2 = { version = "1.0.106" } quote = { version = "1.0.45" } serde = { version = "1.0.228" } stacker = { version = "0.1.24" } -syn = { version = "3.0.3" } +syn = { version = "3.0.3", default-features = false } trybuild = { version = "1.0.116" } diff --git a/stacksafe-macro/Cargo.toml b/stacksafe-macro/Cargo.toml index 5d56c5a..51f4ddd 100644 --- a/stacksafe-macro/Cargo.toml +++ b/stacksafe-macro/Cargo.toml @@ -33,4 +33,4 @@ proc-macro = true [dependencies] proc-macro2 = { workspace = true } quote = { workspace = true } -syn = { workspace = true, features = ["full"] } +syn = { workspace = true, features = ["full", "parsing", "printing"] } diff --git a/stacksafe-macro/src/lib.rs b/stacksafe-macro/src/lib.rs index 0443a7d..430dce6 100644 --- a/stacksafe-macro/src/lib.rs +++ b/stacksafe-macro/src/lib.rs @@ -17,11 +17,10 @@ //! This crate provides the `#[stacksafe]` attribute macro that transforms functions //! to use automatic stack growth, preventing stack overflow in deeply recursive scenarios. -use proc_macro2::Span; use proc_macro2::TokenStream; use quote::ToTokens; use quote::quote; -use syn::ItemFn; +use syn::Item; use syn::Path; use syn::ReturnType; use syn::Type; @@ -45,24 +44,25 @@ fn stacksafe_impl(args: TokenStream, item: TokenStream) -> syn::Result = None; let arg_parser = syn::meta::parser(|meta| { if meta.path.is_ident("crate") { + if crate_path.is_some() { + return Err(meta.error("duplicate attribute parameter `crate`")); + } crate_path = Some(meta.value()?.parse()?); Ok(()) } else { Err(meta.error(format!( "unknown attribute parameter `{}`", - meta.path - .get_ident() - .map_or("unknown".to_string(), |i| i.to_string()) + meta.path.to_token_stream() ))) } }); syn::parse::Parser::parse2(arg_parser, args)?; - let item_fn = match syn::parse2::(item.clone()) { - Ok(item) => item, - Err(_) => { - return Err(syn::Error::new( - Span::call_site(), + let mut item_fn = match syn::parse2::(item)? { + Item::Fn(item_fn) => item_fn, + item => { + return Err(syn::Error::new_spanned( + item, "#[stacksafe] can only be applied to functions", )); } @@ -75,12 +75,18 @@ fn stacksafe_impl(args: TokenStream, item: TokenStream) -> syn::Result ReturnType::Default, - _ => item_fn.sig.output.clone(), + // Closures cannot use `impl Trait` return types, so omit the return + // type and let the compiler infer it. + ReturnType::Type(_, ty) if matches!(**ty, Type::ImplTrait(_)) => None, + ret => Some(ret), }; let stacksafe_crate = crate_path.unwrap_or_else(|| parse_quote!(::stacksafe)); @@ -95,6 +101,6 @@ fn stacksafe_impl(args: TokenStream, item: TokenStream) -> syn::Result tests/ui/const_function.rs:18:1 + | +18 | const fn unsupported() {} + | ^^^^^ diff --git a/stacksafe/tests/ui/duplicate_parameter.rs b/stacksafe/tests/ui/duplicate_parameter.rs new file mode 100644 index 0000000..2ce8582 --- /dev/null +++ b/stacksafe/tests/ui/duplicate_parameter.rs @@ -0,0 +1,20 @@ +// Copyright 2025 FastLabs Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use stacksafe::stacksafe; + +#[stacksafe(crate = stacksafe, crate = stacksafe)] +fn unsupported() {} + +fn main() {} diff --git a/stacksafe/tests/ui/duplicate_parameter.stderr b/stacksafe/tests/ui/duplicate_parameter.stderr new file mode 100644 index 0000000..113e35f --- /dev/null +++ b/stacksafe/tests/ui/duplicate_parameter.stderr @@ -0,0 +1,5 @@ +error: duplicate attribute parameter `crate` + --> tests/ui/duplicate_parameter.rs:17:32 + | +17 | #[stacksafe(crate = stacksafe, crate = stacksafe)] + | ^^^^^ diff --git a/stacksafe/tests/ui/malformed_function.rs b/stacksafe/tests/ui/malformed_function.rs new file mode 100644 index 0000000..65bf04d --- /dev/null +++ b/stacksafe/tests/ui/malformed_function.rs @@ -0,0 +1,20 @@ +// Copyright 2025 FastLabs Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use stacksafe::stacksafe; + +#[stacksafe] +fn malformed(_: ) {} + +fn main() {} diff --git a/stacksafe/tests/ui/malformed_function.stderr b/stacksafe/tests/ui/malformed_function.stderr new file mode 100644 index 0000000..a28dabd --- /dev/null +++ b/stacksafe/tests/ui/malformed_function.stderr @@ -0,0 +1,11 @@ +error: expected type, found `)` + --> tests/ui/malformed_function.rs:18:17 + | +18 | fn malformed(_: ) {} + | ^ expected type + +error: unexpected end of input, expected one of: `for`, parentheses, `unsafe`, `fn`, `extern`, identifier, `::`, `<`, `dyn`, square brackets, `*`, `&`, `!`, `impl`, `_`, lifetime + --> tests/ui/malformed_function.rs:18:17 + | +18 | fn malformed(_: ) {} + | ^ diff --git a/stacksafe/tests/ui/non_function_item.stderr b/stacksafe/tests/ui/non_function_item.stderr index 2d024fe..40794ae 100644 --- a/stacksafe/tests/ui/non_function_item.stderr +++ b/stacksafe/tests/ui/non_function_item.stderr @@ -1,7 +1,5 @@ error: #[stacksafe] can only be applied to functions - --> tests/ui/non_function_item.rs:17:1 + --> tests/ui/non_function_item.rs:18:1 | -17 | #[stacksafe] - | ^^^^^^^^^^^^ - | - = note: this error originates in the attribute macro `stacksafe` (in Nightly builds, run with -Z macro-backtrace for more info) +18 | struct NotAFunction; + | ^^^^^^^^^^^^^^^^^^^^