diff --git a/rtic-macros/src/codegen.rs b/rtic-macros/src/codegen.rs
index 060db6d389d..e0cd033834e 100644
--- a/rtic-macros/src/codegen.rs
+++ b/rtic-macros/src/codegen.rs
@@ -44,12 +44,14 @@ pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 {
let user_code = &app.user_code;
let name = &app.name;
let device = &app.args.device;
+ let attribute_metas = &app.attribute_metas;
let rt_err = util::rt_err_ident();
let async_limit = bindings::async_prio_limit(app, analysis);
quote!(
/// The RTIC application module
+ #(#[#attribute_metas])*
pub mod #name {
/// Always include the device crate which contains the vector table
use #device as #rt_err;
diff --git a/rtic-macros/src/syntax/ast.rs b/rtic-macros/src/syntax/ast.rs
index ad73a895cf4..44c1385d24f 100644
--- a/rtic-macros/src/syntax/ast.rs
+++ b/rtic-macros/src/syntax/ast.rs
@@ -1,6 +1,6 @@
//! Abstract Syntax Tree
-use syn::{Attribute, Expr, Ident, Item, ItemUse, Pat, PatType, Path, Stmt, Type};
+use syn::{Attribute, Expr, Ident, Item, ItemUse, Meta, Pat, PatType, Path, Stmt, Type};
use crate::syntax::{backend::BackendArgs, Map};
@@ -11,6 +11,9 @@ pub struct App {
/// The arguments to the `#[app]` attribute
pub args: AppArgs,
+ /// All attributes applied to the `#[app]` module (meta only)
+ pub attribute_metas: Vec,
+
/// The name of the `const` item on which the `#[app]` attribute has been placed
pub name: Ident,
diff --git a/rtic-macros/src/syntax/parse.rs b/rtic-macros/src/syntax/parse.rs
index aae8a503ed8..ea7ff29409a 100644
--- a/rtic-macros/src/syntax/parse.rs
+++ b/rtic-macros/src/syntax/parse.rs
@@ -11,7 +11,7 @@ use syn::{
braced,
parse::{self, Parse, ParseStream, Parser},
token::Brace,
- Ident, Item, LitInt, Token,
+ Attribute, Ident, Item, LitInt, Meta, Token,
};
use crate::syntax::{
@@ -28,6 +28,7 @@ pub fn app(args: TokenStream2, input: TokenStream2) -> parse::Result {
}
pub(crate) struct Input {
+ pub attribute_metas: Vec,
_mod_token: Token![mod],
pub ident: Ident,
_brace_token: Brace,
@@ -48,12 +49,18 @@ impl Parse for Input {
let content;
+ let mut attributes = input.call(Attribute::parse_outer)?;
let _mod_token = input.parse()?;
let ident = input.parse()?;
let _brace_token = braced!(content in input);
+ let inner_attributes = content.call(Attribute::parse_inner)?;
let items = content.call(parse_items)?;
+ attributes.extend(inner_attributes);
+ let attribute_metas = attributes.into_iter().map(|a| a.meta).collect();
+
Ok(Input {
+ attribute_metas,
_mod_token,
ident,
_brace_token,
diff --git a/rtic-macros/src/syntax/parse/app.rs b/rtic-macros/src/syntax/parse/app.rs
index 469bcb880c2..50ef12b9fee 100644
--- a/rtic-macros/src/syntax/parse/app.rs
+++ b/rtic-macros/src/syntax/parse/app.rs
@@ -531,6 +531,7 @@ impl App {
}
Ok(App {
+ attribute_metas: input.attribute_metas,
args,
name: input.ident,
init,
diff --git a/rtic/ui/inner_attribute.rs b/rtic/ui/inner_attribute.rs
new file mode 100644
index 00000000000..86b7f804d7f
--- /dev/null
+++ b/rtic/ui/inner_attribute.rs
@@ -0,0 +1,21 @@
+#![no_main]
+#![deny(unfulfilled_lint_expectations)]
+
+#[rtic::app(device = lm3s6965)]
+mod app {
+ #![expect(while_true)]
+
+ #[shared]
+ struct Shared {
+ #[unsafe(link_section = ".custom_section")]
+ foo: (),
+ }
+
+ #[local]
+ struct Local {}
+
+ #[init]
+ fn init(_cx: init::Context) -> (Shared, Local) {
+ (Shared { foo: () }, Local {})
+ }
+}
diff --git a/rtic/ui/inner_attribute.stderr b/rtic/ui/inner_attribute.stderr
new file mode 100644
index 00000000000..68cb571616d
--- /dev/null
+++ b/rtic/ui/inner_attribute.stderr
@@ -0,0 +1,11 @@
+error: this lint expectation is unfulfilled
+ --> ui/inner_attribute.rs:6:15
+ |
+6 | #![expect(while_true)]
+ | ^^^^^^^^^^
+ |
+note: the lint level is defined here
+ --> ui/inner_attribute.rs:2:9
+ |
+2 | #![deny(unfulfilled_lint_expectations)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/rtic/ui/outer_attribute.rs b/rtic/ui/outer_attribute.rs
new file mode 100644
index 00000000000..f1fbe72976d
--- /dev/null
+++ b/rtic/ui/outer_attribute.rs
@@ -0,0 +1,20 @@
+#![no_main]
+#![deny(unfulfilled_lint_expectations)]
+
+#[rtic::app(device = lm3s6965)]
+#[expect(while_true)]
+mod app {
+ #[shared]
+ struct Shared {
+ #[unsafe(link_section = ".custom_section")]
+ foo: (),
+ }
+
+ #[local]
+ struct Local {}
+
+ #[init]
+ fn init(_cx: init::Context) -> (Shared, Local) {
+ (Shared { foo: () }, Local {})
+ }
+}
diff --git a/rtic/ui/outer_attribute.stderr b/rtic/ui/outer_attribute.stderr
new file mode 100644
index 00000000000..1bce6e745a4
--- /dev/null
+++ b/rtic/ui/outer_attribute.stderr
@@ -0,0 +1,11 @@
+error: this lint expectation is unfulfilled
+ --> ui/outer_attribute.rs:5:10
+ |
+5 | #[expect(while_true)]
+ | ^^^^^^^^^^
+ |
+note: the lint level is defined here
+ --> ui/outer_attribute.rs:2:9
+ |
+2 | #![deny(unfulfilled_lint_expectations)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^