diff options
Diffstat (limited to 'macros/src')
-rw-r--r-- | macros/src/check.rs | 280 | ||||
-rw-r--r-- | macros/src/codegen.rs | 107 | ||||
-rw-r--r-- | macros/src/syntax.rs | 64 |
3 files changed, 418 insertions, 33 deletions
diff --git a/macros/src/check.rs b/macros/src/check.rs index ae2262a8..045d152f 100644 --- a/macros/src/check.rs +++ b/macros/src/check.rs @@ -1,7 +1,7 @@ use std::{collections::HashSet, iter}; use proc_macro2::Span; -use syn::parse; +use syn::{parse, spanned::Spanned, Block, Expr, Stmt}; use crate::syntax::App; @@ -35,17 +35,20 @@ pub fn app(app: &App) -> parse::Result<()> { } } - // Check that all late resources have been initialized in `#[init]` - for res in app - .resources - .iter() - .filter_map(|(name, res)| if res.expr.is_none() { Some(name) } else { None }) - { - if app.init.assigns.iter().all(|assign| assign.left != *res) { - return Err(parse::Error::new( - res.span(), - "late resources MUST be initialized at the end of `init`", - )); + // Check that all late resources have been initialized in `#[init]` if `init` has signature + // `fn()` + if !app.init.returns_late_resources { + for res in app + .resources + .iter() + .filter_map(|(name, res)| if res.expr.is_none() { Some(name) } else { None }) + { + if app.init.assigns.iter().all(|assign| assign.left != *res) { + return Err(parse::Error::new( + res.span(), + "late resources MUST be initialized at the end of `init`", + )); + } } } @@ -112,5 +115,258 @@ pub fn app(app: &App) -> parse::Result<()> { } } + // Check that `init` contains no early returns *if* late resources exist and `init` signature is + // `fn()` + if app.resources.values().any(|res| res.expr.is_none()) { + if !app.init.returns_late_resources { + for stmt in &app.init.stmts { + noreturn_stmt(stmt)?; + } + } + } else if app.init.returns_late_resources { + return Err(parse::Error::new( + Span::call_site(), + "`init` signature must be `[unsafe] fn()` if there are no late resources", + )); + } + + Ok(()) +} + +// checks that the given block contains no instance of `return` +fn noreturn_block(block: &Block) -> Result<(), parse::Error> { + for stmt in &block.stmts { + noreturn_stmt(stmt)?; + } + + Ok(()) +} + +// checks that the given statement contains no instance of `return` +fn noreturn_stmt(stmt: &Stmt) -> Result<(), parse::Error> { + match stmt { + // `let x = ..` -- this may contain a return in the RHS + Stmt::Local(local) => { + if let Some(ref init) = local.init { + noreturn_expr(&init.1)? + } + } + + // items have no effect on control flow + Stmt::Item(..) => {} + + Stmt::Expr(expr) => noreturn_expr(expr)?, + + Stmt::Semi(expr, ..) => noreturn_expr(expr)?, + } + + Ok(()) +} + +// checks that the given expression contains no `return` +fn noreturn_expr(expr: &Expr) -> Result<(), parse::Error> { + match expr { + Expr::Box(b) => noreturn_expr(&b.expr)?, + + Expr::InPlace(ip) => { + noreturn_expr(&ip.place)?; + noreturn_expr(&ip.value)?; + } + + Expr::Array(a) => { + for elem in &a.elems { + noreturn_expr(elem)?; + } + } + + Expr::Call(c) => { + noreturn_expr(&c.func)?; + + for arg in &c.args { + noreturn_expr(arg)?; + } + } + + Expr::MethodCall(mc) => { + noreturn_expr(&mc.receiver)?; + + for arg in &mc.args { + noreturn_expr(arg)?; + } + } + + Expr::Tuple(t) => { + for elem in &t.elems { + noreturn_expr(elem)?; + } + } + + Expr::Binary(b) => { + noreturn_expr(&b.left)?; + noreturn_expr(&b.right)?; + } + + Expr::Unary(u) => { + noreturn_expr(&u.expr)?; + } + + Expr::Lit(..) => {} + + Expr::Cast(c) => { + noreturn_expr(&c.expr)?; + } + + Expr::Type(t) => { + noreturn_expr(&t.expr)?; + } + + Expr::Let(l) => { + noreturn_expr(&l.expr)?; + } + + Expr::If(i) => { + noreturn_expr(&i.cond)?; + + noreturn_block(&i.then_branch)?; + + if let Some(ref e) = i.else_branch { + noreturn_expr(&e.1)?; + } + } + + Expr::While(w) => { + noreturn_expr(&w.cond)?; + noreturn_block(&w.body)?; + } + + Expr::ForLoop(fl) => { + noreturn_expr(&fl.expr)?; + noreturn_block(&fl.body)?; + } + + Expr::Loop(l) => { + noreturn_block(&l.body)?; + } + + Expr::Match(m) => { + noreturn_expr(&m.expr)?; + + for arm in &m.arms { + if let Some(g) = &arm.guard { + noreturn_expr(&g.1)?; + } + + noreturn_expr(&arm.body)?; + } + } + + // we don't care about `return`s inside closures + Expr::Closure(..) => {} + + Expr::Unsafe(u) => { + noreturn_block(&u.block)?; + } + + Expr::Block(b) => { + noreturn_block(&b.block)?; + } + + Expr::Assign(a) => { + noreturn_expr(&a.left)?; + noreturn_expr(&a.right)?; + } + + Expr::AssignOp(ao) => { + noreturn_expr(&ao.left)?; + noreturn_expr(&ao.right)?; + } + + Expr::Field(f) => { + noreturn_expr(&f.base)?; + } + + Expr::Index(i) => { + noreturn_expr(&i.expr)?; + noreturn_expr(&i.index)?; + } + + Expr::Range(r) => { + if let Some(ref f) = r.from { + noreturn_expr(f)?; + } + + if let Some(ref t) = r.to { + noreturn_expr(t)?; + } + } + + Expr::Path(..) => {} + + Expr::Reference(r) => { + noreturn_expr(&r.expr)?; + } + + Expr::Break(b) => { + if let Some(ref e) = b.expr { + noreturn_expr(e)?; + } + } + + Expr::Continue(..) => {} + + Expr::Return(r) => { + return Err(parse::Error::new( + r.span(), + "`init` is *not* allowed to early return", + )); + } + + // we can not analyze this + Expr::Macro(..) => {} + + Expr::Struct(s) => { + for field in &s.fields { + noreturn_expr(&field.expr)?; + } + + if let Some(ref rest) = s.rest { + noreturn_expr(rest)?; + } + } + + Expr::Repeat(r) => { + noreturn_expr(&r.expr)?; + noreturn_expr(&r.len)?; + } + + Expr::Paren(p) => { + noreturn_expr(&p.expr)?; + } + + Expr::Group(g) => { + noreturn_expr(&g.expr)?; + } + + Expr::Try(t) => { + noreturn_expr(&t.expr)?; + } + + // we don't care about `return`s inside async blocks + Expr::Async(..) => {} + + Expr::TryBlock(tb) => { + noreturn_block(&tb.block)?; + } + + Expr::Yield(y) => { + if let Some(expr) = &y.expr { + noreturn_expr(expr)?; + } + } + + // we can not analyze this + Expr::Verbatim(..) => {} + } + Ok(()) } diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index 6c1baeca..c7adbd67 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -94,7 +94,7 @@ pub fn app(app: &App, analysis: &Analysis) -> TokenStream { let (dispatchers_data, dispatchers) = dispatchers(&mut ctxt, &app, analysis); - let init_fn = init(&mut ctxt, &app, analysis); + let (init_fn, has_late_resources) = init(&mut ctxt, &app, analysis); let init_arg = if cfg!(feature = "timer-queue") { quote!(rtfm::Peripherals { CBP: p.CBP, @@ -123,6 +123,30 @@ pub fn app(app: &App, analysis: &Analysis) -> TokenStream { }) }; + let init = &ctxt.init; + let init_phase = if has_late_resources { + let assigns = app + .resources + .iter() + .filter_map(|(name, res)| { + if res.expr.is_none() { + let alias = &ctxt.statics[name]; + + Some(quote!(#alias.set(res.#name);)) + } else { + None + } + }) + .collect::<Vec<_>>(); + + quote!( + let res = #init(#init_arg); + #(#assigns)* + ) + } else { + quote!(#init(#init_arg);) + }; + let post_init = post_init(&ctxt, &app, analysis); let (idle_fn, idle_expr) = idle(&mut ctxt, &app, analysis); @@ -147,7 +171,6 @@ pub fn app(app: &App, analysis: &Analysis) -> TokenStream { let assertions = assertions(app, analysis); let main = mk_ident(None); - let init = &ctxt.init; quote!( #resources @@ -185,7 +208,7 @@ pub fn app(app: &App, analysis: &Analysis) -> TokenStream { #pre_init - #init(#init_arg); + #init_phase #post_init @@ -290,10 +313,11 @@ fn resources(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2: quote!(#(#items)*) } -fn init(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { +fn init(ctxt: &mut Context, app: &App, analysis: &Analysis) -> (proc_macro2::TokenStream, bool) { let attrs = &app.init.attrs; let locals = mk_locals(&app.init.statics, true); let stmts = &app.init.stmts; + // TODO remove in v0.5.x let assigns = app .init .assigns @@ -334,12 +358,47 @@ fn init(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::Toke analysis, ); + let (late_resources, late_resources_ident, ret) = if app.init.returns_late_resources { + // create `LateResources` struct in the root of the crate + let ident = mk_ident(None); + + let fields = app + .resources + .iter() + .filter_map(|(name, res)| { + if res.expr.is_none() { + let ty = &res.ty; + Some(quote!(pub #name: #ty)) + } else { + None + } + }) + .collect::<Vec<_>>(); + + let late_resources = quote!( + #[allow(non_snake_case)] + pub struct #ident { + #(#fields),* + } + ); + + ( + Some(late_resources), + Some(ident), + Some(quote!(-> init::LateResources)), + ) + } else { + (None, None, None) + }; + let has_late_resources = late_resources.is_some(); + let module = module( ctxt, Kind::Init, !app.init.args.schedule.is_empty(), !app.init.args.spawn.is_empty(), app, + late_resources_ident, ); #[cfg(feature = "timer-queue")] @@ -365,25 +424,30 @@ fn init(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::Toke let unsafety = &app.init.unsafety; let device = &app.args.device; let init = &ctxt.init; - quote!( - #module + ( + quote!( + #late_resources - #(#attrs)* - #unsafety fn #init(mut core: rtfm::Peripherals) { - #(#locals)* + #module - #baseline_let + #(#attrs)* + #unsafety fn #init(mut core: rtfm::Peripherals) #ret { + #(#locals)* - #prelude + #baseline_let - let mut device = unsafe { #device::Peripherals::steal() }; + #prelude - #start_let + let mut device = unsafe { #device::Peripherals::steal() }; - #(#stmts)* + #start_let - #(#assigns)* - } + #(#stmts)* + + #(#assigns)* + } + ), + has_late_resources, ) } @@ -440,6 +504,7 @@ fn module( schedule: bool, spawn: bool, app: &App, + late_resources: Option<Ident>, ) -> proc_macro2::TokenStream { let mut items = vec![]; let mut fields = vec![]; @@ -572,6 +637,12 @@ fn module( Kind::Task(_) => "Software task", }; + if let Some(late_resources) = late_resources { + items.push(quote!( + pub use super::#late_resources as LateResources; + )); + } + quote!( #root @@ -950,6 +1021,7 @@ fn idle( !idle.args.schedule.is_empty(), !idle.args.spawn.is_empty(), app, + None, ); let unsafety = &idle.unsafety; @@ -1004,6 +1076,7 @@ fn exceptions(ctxt: &mut Context, app: &App, analysis: &Analysis) -> Vec<proc_ma !exception.args.schedule.is_empty(), !exception.args.spawn.is_empty(), app, + None, ); #[cfg(feature = "timer-queue")] @@ -1083,6 +1156,7 @@ fn interrupts( !interrupt.args.schedule.is_empty(), !interrupt.args.spawn.is_empty(), app, + None, )); #[cfg(feature = "timer-queue")] @@ -1245,6 +1319,7 @@ fn tasks(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::Tok !task.args.schedule.is_empty(), !task.args.spawn.is_empty(), app, + None, )); let attrs = &task.attrs; diff --git a/macros/src/syntax.rs b/macros/src/syntax.rs index 85f3caaa..ad7d8bde 100644 --- a/macros/src/syntax.rs +++ b/macros/src/syntax.rs @@ -596,6 +596,7 @@ impl Parse for InitArgs { } } +// TODO remove in v0.5.x pub struct Assign { pub attrs: Vec<Attribute>, pub left: Ident, @@ -608,32 +609,83 @@ pub struct Init { pub unsafety: Option<Token![unsafe]>, pub statics: HashMap<Ident, Static>, pub stmts: Vec<Stmt>, + // TODO remove in v0.5.x pub assigns: Vec<Assign>, + pub returns_late_resources: bool, } impl Init { fn check(args: InitArgs, item: ItemFn) -> parse::Result<Self> { - let valid_signature = item.vis == Visibility::Inherited + let mut valid_signature = item.vis == Visibility::Inherited && item.constness.is_none() && item.asyncness.is_none() && item.abi.is_none() && item.decl.generics.params.is_empty() && item.decl.generics.where_clause.is_none() && item.decl.inputs.is_empty() - && item.decl.variadic.is_none() - && is_unit(&item.decl.output); + && item.decl.variadic.is_none(); + + let returns_late_resources = match &item.decl.output { + ReturnType::Default => false, + ReturnType::Type(_, ty) => { + match &**ty { + Type::Tuple(t) => { + if t.elems.is_empty() { + // -> () + true + } else { + valid_signature = false; + + false // don't care + } + } + + Type::Path(p) => { + let mut segments = p.path.segments.iter(); + if p.qself.is_none() + && p.path.leading_colon.is_none() + && p.path.segments.len() == 2 + && segments.next().map(|s| { + s.arguments == PathArguments::None && s.ident.to_string() == "init" + }) == Some(true) + && segments.next().map(|s| { + s.arguments == PathArguments::None + && s.ident.to_string() == "LateResources" + }) == Some(true) + { + // -> init::LateResources + true + } else { + valid_signature = false; + + false // don't care + } + } + + _ => { + valid_signature = false; + + false // don't care + } + } + } + }; let span = item.span(); if !valid_signature { return Err(parse::Error::new( span, - "`init` must have type signature `[unsafe] fn()`", + "`init` must have type signature `[unsafe] fn() [-> init::LateResources]`", )); } let (statics, stmts) = extract_statics(item.block.stmts); - let (stmts, assigns) = extract_assignments(stmts); + let (stmts, assigns) = if returns_late_resources { + (stmts, vec![]) + } else { + extract_assignments(stmts) + }; Ok(Init { args, @@ -642,6 +694,7 @@ impl Init { statics: Static::parse(statics)?, stmts, assigns, + returns_late_resources, }) } } @@ -1207,6 +1260,7 @@ fn extract_statics(stmts: Vec<Stmt>) -> (Statics, Vec<Stmt>) { (statics, stmts) } +// TODO remove in v0.5.x fn extract_assignments(stmts: Vec<Stmt>) -> (Vec<Stmt>, Vec<Assign>) { let mut istmts = stmts.into_iter().rev(); |