Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
2 changes: 1 addition & 1 deletion stacksafe-macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
38 changes: 22 additions & 16 deletions stacksafe-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -45,24 +44,25 @@ fn stacksafe_impl(args: TokenStream, item: TokenStream) -> syn::Result<TokenStre
let mut crate_path: Option<Path> = 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::<ItemFn>(item.clone()) {
Ok(item) => item,
Err(_) => {
return Err(syn::Error::new(
Span::call_site(),
let mut item_fn = match syn::parse2::<Item>(item)? {
Item::Fn(item_fn) => item_fn,
item => {
return Err(syn::Error::new_spanned(
item,
"#[stacksafe] can only be applied to functions",
));
}
Expand All @@ -75,12 +75,18 @@ fn stacksafe_impl(args: TokenStream, item: TokenStream) -> syn::Result<TokenStre
));
}

let mut item_fn = item_fn;
if item_fn.sig.constness.is_some() {
return Err(syn::Error::new(
item_fn.sig.constness.span(),
"#[stacksafe] does not support const functions",
));
}

let ret = match &item_fn.sig.output {
// impl trait is not supported in closure return type, override with
// default, which is inferring.
ReturnType::Type(_, ty) if matches!(**ty, Type::ImplTrait(_)) => 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));
Expand All @@ -95,6 +101,6 @@ fn stacksafe_impl(args: TokenStream, item: TokenStream) -> syn::Result<TokenStre
}
};

*item_fn.block = syn::parse(wrapped_block.into())?;
*item_fn.block = syn::parse2(wrapped_block)?;
Ok(item_fn.into_token_stream())
}
1 change: 1 addition & 0 deletions stacksafe/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ use std::sync::atomic::Ordering;
/// # Limitations
///
/// - Cannot be applied to `async` functions
/// - Cannot be applied to `const` functions
/// - Functions with `impl Trait` return types may need type annotations
/// - Adds small runtime overhead for stack size checking
pub use stacksafe_macro::stacksafe;
Expand Down
20 changes: 20 additions & 0 deletions stacksafe/tests/ui/const_function.rs
Original file line number Diff line number Diff line change
@@ -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]
const fn unsupported() {}

fn main() {}
5 changes: 5 additions & 0 deletions stacksafe/tests/ui/const_function.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: #[stacksafe] does not support const functions
--> tests/ui/const_function.rs:18:1
|
18 | const fn unsupported() {}
| ^^^^^
20 changes: 20 additions & 0 deletions stacksafe/tests/ui/duplicate_parameter.rs
Original file line number Diff line number Diff line change
@@ -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() {}
5 changes: 5 additions & 0 deletions stacksafe/tests/ui/duplicate_parameter.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: duplicate attribute parameter `crate`
--> tests/ui/duplicate_parameter.rs:17:32
|
17 | #[stacksafe(crate = stacksafe, crate = stacksafe)]
| ^^^^^
20 changes: 20 additions & 0 deletions stacksafe/tests/ui/malformed_function.rs
Original file line number Diff line number Diff line change
@@ -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() {}
11 changes: 11 additions & 0 deletions stacksafe/tests/ui/malformed_function.stderr
Original file line number Diff line number Diff line change
@@ -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(_: ) {}
| ^
8 changes: 3 additions & 5 deletions stacksafe/tests/ui/non_function_item.stderr
Original file line number Diff line number Diff line change
@@ -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;
| ^^^^^^^^^^^^^^^^^^^^