use std::{ collections::{BTreeMap, BTreeSet}, iter, u8, }; use proc_macro2::Span; use syn::{ braced, bracketed, parenthesized, parse::{self, Parse, ParseStream}, punctuated::Punctuated, spanned::Spanned, token::Brace, ArgCaptured, AttrStyle, Attribute, Expr, FnArg, ForeignItem, Ident, IntSuffix, Item, ItemFn, ItemForeignMod, ItemStatic, LitInt, Pat, Path, PathArguments, ReturnType, Stmt, Token, Type, TypeTuple, Visibility, }; pub struct AppArgs { pub device: Path, } impl Parse for AppArgs { fn parse(input: ParseStream<'_>) -> parse::Result { let mut device = None; loop { if input.is_empty() { break; } // #ident = .. let ident: Ident = input.parse()?; let _eq_token: Token![=] = input.parse()?; let ident_s = ident.to_string(); match &*ident_s { "device" => { if device.is_some() { return Err(parse::Error::new( ident.span(), "argument appears more than once", )); } device = Some(input.parse()?); } _ => { return Err(parse::Error::new( ident.span(), "expected `device`; other keys are not accepted", )); } } if input.is_empty() { break; } // , let _: Token![,] = input.parse()?; } Ok(AppArgs { device: device.ok_or(parse::Error::new( Span::call_site(), "`device` argument is required", ))?, }) } } pub struct Input { _const_token: Token![const], pub ident: Ident, _colon_token: Token![:], _ty: TypeTuple, _eq_token: Token![=], _brace_token: Brace, pub items: Vec, _semi_token: Token![;], } impl Parse for Input { fn parse(input: ParseStream<'_>) -> parse::Result { fn parse_items(input: ParseStream<'_>) -> parse::Result> { let mut items = vec![]; while !input.is_empty() { items.push(input.parse()?); } Ok(items) } let content; Ok(Input { _const_token: input.parse()?, ident: input.parse()?, _colon_token: input.parse()?, _ty: input.parse()?, _eq_token: input.parse()?, _brace_token: braced!(content in input), items: content.call(parse_items)?, _semi_token: input.parse()?, }) } } pub struct App { pub args: AppArgs, pub idle: Option, pub init: Init, pub exceptions: Exceptions, pub interrupts: Interrupts, pub resources: Resources, pub tasks: Tasks, pub free_interrupts: FreeInterrupts, } impl App { pub fn parse(items: Vec, args: AppArgs) -> parse::Result { let mut idle = None; let mut init = None; let mut exceptions = BTreeMap::new(); let mut interrupts = BTreeMap::new(); let mut resources = BTreeMap::new(); let mut tasks = BTreeMap::new(); let mut free_interrupts = None; for item in items { match item { Item::Fn(mut item) => { if let Some(pos) = item.attrs.iter().position(|attr| eq(attr, "idle")) { if idle.is_some() { return Err(parse::Error::new( item.span(), "`#[idle]` function must appear at most once", )); } let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; idle = Some(Idle::check(args, item)?); } else if let Some(pos) = item.attrs.iter().position(|attr| eq(attr, "init")) { if init.is_some() { return Err(parse::Error::new( item.span(), "`#[init]` function must appear exactly once", )); } let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; init = Some(Init::check(args, item)?); } else if let Some(pos) = item.attrs.iter().position(|attr| eq(attr, "exception")) { if exceptions.contains_key(&item.ident) || interrupts.contains_key(&item.ident) || tasks.contains_key(&item.ident) { return Err(parse::Error::new( item.ident.span(), "this task is defined multiple times", )); } let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; exceptions.insert(item.ident.clone(), Exception::check(args, item)?); } else if let Some(pos) = item.attrs.iter().position(|attr| eq(attr, "interrupt")) { if exceptions.contains_key(&item.ident) || interrupts.contains_key(&item.ident) || tasks.contains_key(&item.ident) { return Err(parse::Error::new( item.ident.span(), "this task is defined multiple times", )); } let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; interrupts.insert(item.ident.clone(), Interrupt::check(args, item)?); } else if let Some(pos) = item.attrs.iter().position(|attr| eq(attr, "task")) { if exceptions.contains_key(&item.ident) || interrupts.contains_key(&item.ident) || tasks.contains_key(&item.ident) { return Err(parse::Error::new( item.ident.span(), "this task is defined multiple times", )); } let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; tasks.insert(item.ident.clone(), Task::check(args, item)?); } else { return Err(parse::Error::new( item.span(), "this item must live outside the `#[app]` module", )); } } Item::Static(item) => { if resources.contains_key(&item.ident) { return Err(parse::Error::new( item.ident.span(), "this resource is listed twice", )); } resources.insert(item.ident.clone(), Resource::check(item)?); } Item::ForeignMod(item) => { if free_interrupts.is_some() { return Err(parse::Error::new( item.abi.extern_token.span(), "`extern` block can only appear at most once", )); } free_interrupts = Some(FreeInterrupt::parse(item)?); } _ => { return Err(parse::Error::new( item.span(), "this item must live outside the `#[app]` module", )); } } } Ok(App { args, idle, init: init.ok_or_else(|| { parse::Error::new(Span::call_site(), "`#[init]` function is missing") })?, exceptions, interrupts, resources, tasks, free_interrupts: free_interrupts.unwrap_or_else(|| FreeInterrupts::new()), }) } /// Returns an iterator over all resource accesses. /// /// Each resource access include the priority it's accessed at (`u8`) and the name of the /// resource (`Ident`). A resource may appear more than once in this iterator pub fn resource_accesses(&self) -> impl Iterator { self.idle .as_ref() .map(|idle| -> Box> { Box::new(idle.args.resources.iter().map(|res| (0, res))) }) .unwrap_or_else(|| Box::new(iter::empty())) .chain(self.exceptions.values().flat_map(|e| { e.args .resources .iter() .map(move |res| (e.args.priority, res)) })) .chain(self.interrupts.values().flat_map(|i| { i.args .resources .iter() .map(move |res| (i.args.priority, res)) })) .chain(self.tasks.values().flat_map(|t| { t.args .resources .iter() .map(move |res| (t.args.priority, res)) })) } /// Returns an iterator over all `spawn` calls /// /// Each spawn call includes the priority of the task from which it's issued and the name of the /// task that's spawned. A task may appear more that once in this iterator. /// /// A priority of `None` means that this being called from `init` pub fn spawn_calls(&self) -> impl Iterator, &Ident)> { self.init .args .spawn .iter() .map(|s| (None, s)) .chain( self.idle .as_ref() .map(|idle| -> Box> { Box::new(idle.args.spawn.iter().map(|s| (Some(0), s))) }) .unwrap_or_else(|| Box::new(iter::empty())), ) .chain( self.exceptions .values() .flat_map(|e| e.args.spawn.iter().map(move |s| (Some(e.args.priority), s))), ) .chain( self.interrupts .values() .flat_map(|i| i.args.spawn.iter().map(move |s| (Some(i.args.priority), s))), ) .chain( self.tasks .values() .flat_map(|t| t.args.spawn.iter().map(move |s| (Some(t.args.priority), s))), ) } /// Returns an iterator over all `schedule` calls /// /// Each spawn call includes the priority of the task from which it's issued and the name of the /// task that's spawned. A task may appear more that once in this iterator. #[allow(dead_code)] pub fn schedule_calls(&self) -> impl Iterator, &Ident)> { self.init .args .schedule .iter() .map(|s| (None, s)) .chain( self.idle .as_ref() .map(|idle| -> Box> { Box::new(idle.args.schedule.iter().map(|s| (Some(0), s))) }) .unwrap_or_else(|| Box::new(iter::empty())), ) .chain(self.exceptions.values().flat_map(|e| { e.args .schedule .iter() .map(move |s| (Some(e.args.priority), s)) })) .chain(self.interrupts.values().flat_map(|i| { i.args .schedule .iter() .map(move |s| (Some(i.args.priority), s)) })) .chain(self.tasks.values().flat_map(|t| { t.args .schedule .iter() .map(move |s| (Some(t.args.priority), s)) })) } #[allow(dead_code)] pub fn schedule_callers(&self) -> impl Iterator { self.idle .as_ref() .map(|idle| -> Box> { Box::new(iter::once(( Ident::new("idle", Span::call_site()), &idle.args.schedule, ))) }) .unwrap_or_else(|| Box::new(iter::empty())) .chain(iter::once(( Ident::new("init", Span::call_site()), &self.init.args.schedule, ))) .chain( self.exceptions .iter() .map(|(name, exception)| (name.clone(), &exception.args.schedule)), ) .chain( self.interrupts .iter() .map(|(name, interrupt)| (name.clone(), &interrupt.args.schedule)), ) .chain( self.tasks .iter() .map(|(name, task)| (name.clone(), &task.args.schedule)), ) } pub fn spawn_callers(&self) -> impl Iterator { self.idle .as_ref() .map(|idle| -> Box> { Box::new(iter::once(( Ident::new("idle", Span::call_site()), &idle.args.spawn, ))) }) .unwrap_or_else(|| Box::new(iter::empty())) .chain(iter::once(( Ident::new("init", Span::call_site()), &self.init.args.spawn, ))) .chain( self.exceptions .iter() .map(|(name, exception)| (name.clone(), &exception.args.spawn)), ) .chain( self.interrupts .iter() .map(|(name, interrupt)| (name.clone(), &interrupt.args.spawn)), ) .chain( self.tasks .iter() .map(|(name, task)| (name.clone(), &task.args.spawn)), ) } } pub type Idents = BTreeSet; pub type Exceptions = BTreeMap; pub type Interrupts = BTreeMap; pub type Resources = BTreeMap; pub type Statics = Vec; pub type Tasks = BTreeMap; pub type FreeInterrupts = BTreeMap; pub struct Idle { pub args: IdleArgs, pub attrs: Vec, pub context: Pat, pub statics: BTreeMap, pub stmts: Vec, } pub type IdleArgs = InitArgs; impl Idle { fn check(args: IdleArgs, item: ItemFn) -> parse::Result { let valid_signature = check_signature(&item) && item.decl.inputs.len() == 1 && is_bottom(&item.decl.output); let span = item.span(); if valid_signature { if let Some((context, _)) = check_inputs(item.decl.inputs, "idle") { let (statics, stmts) = extract_statics(item.block.stmts); return Ok(Idle { args, attrs: item.attrs, context, statics: Static::parse(statics)?, stmts, }); } } Err(parse::Error::new( span, "`idle` must have type signature `fn(idle::Context) -> !`", )) } } pub struct InitArgs { pub resources: Idents, pub schedule: Idents, pub spawn: Idents, } impl Default for InitArgs { fn default() -> Self { InitArgs { resources: Idents::new(), schedule: Idents::new(), spawn: Idents::new(), } } } impl Parse for InitArgs { fn parse(input: ParseStream<'_>) -> parse::Result { if input.is_empty() { return Ok(InitArgs::default()); } let mut resources = None; let mut schedule = None; let mut spawn = None; let content; parenthesized!(content in input); loop { if content.is_empty() { break; } // #ident = .. let ident: Ident = content.parse()?; let _: Token![=] = content.parse()?; let ident_s = ident.to_string(); match &*ident_s { "schedule" if cfg!(not(feature = "timer-queue")) => { return Err(parse::Error::new( ident.span(), "The `schedule` API requires that the `timer-queue` feature is \ enabled in the `cortex-m-rtfm` crate", )); } "resources" | "schedule" | "spawn" => {} // OK _ => { return Err(parse::Error::new( ident.span(), "expected one of: resources, schedule or spawn", )); } } // .. [#(#idents)*] let inner; bracketed!(inner in content); let mut idents = Idents::new(); for ident in inner.call(Punctuated::<_, Token![,]>::parse_terminated)? { if idents.contains(&ident) { return Err(parse::Error::new( ident.span(), "element appears more than once in list", )); } idents.insert(ident); } let ident_s = ident.to_string(); match &*ident_s { "resources" => { if resources.is_some() { return Err(parse::Error::new( ident.span(), "argument appears more than once", )); } resources = Some(idents); } "schedule" => { if schedule.is_some() { return Err(parse::Error::new( ident.span(), "argument appears more than once", )); } schedule = Some(idents); } "spawn" => { if spawn.is_some() { return Err(parse::Error::new( ident.span(), "argument appears more than once", )); } spawn = Some(idents); } _ => unreachable!(), } if content.is_empty() { break; } // , let _: Token![,] = content.parse()?; } Ok(InitArgs { resources: resources.unwrap_or(Idents::new()), schedule: schedule.unwrap_or(Idents::new()), spawn: spawn.unwrap_or(Idents::new()), }) } } pub struct Init { pub args: InitArgs, pub attrs: Vec, pub statics: BTreeMap, pub context: Pat, pub stmts: Vec, pub returns_late_resources: bool, pub span: Span, } impl Init { fn check(args: InitArgs, item: ItemFn) -> parse::Result { let mut valid_signature = check_signature(&item) && item.decl.inputs.len() == 1; const DONT_CARE: bool = false; 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; DONT_CARE } } Type::Path(_) => { if is_path(ty, &["init", "LateResources"]) { // -> init::LateResources true } else { valid_signature = false; DONT_CARE } } _ => { valid_signature = false; DONT_CARE } } } }; let span = item.span(); if valid_signature { if let Some((context, _)) = check_inputs(item.decl.inputs, "init") { let (statics, stmts) = extract_statics(item.block.stmts); return Ok(Init { args, attrs: item.attrs, statics: Static::parse(statics)?, context, stmts, returns_late_resources, span, }); } } Err(parse::Error::new( span, "`init` must have type signature `fn(init::Context) [-> init::LateResources]`", )) } } /// Union of `TaskArgs`, `ExceptionArgs` and `InterruptArgs` pub struct Args { pub binds: Option, pub capacity: Option, pub priority: u8, pub resources: Idents, pub schedule: Idents, pub spawn: Idents, } impl Default for Args { fn default() -> Self { Args { binds: None, capacity: None, priority: 1, resources: Idents::new(), schedule: Idents::new(), spawn: Idents::new(), } } } pub struct Exception { pub args: ExceptionArgs, pub attrs: Vec, pub statics: BTreeMap, pub context: Pat, pub stmts: Vec, } pub struct ExceptionArgs { binds: Option, pub priority: u8, pub resources: Idents, pub schedule: Idents, pub spawn: Idents, } impl ExceptionArgs { /// Returns the name of the exception / interrupt this handler binds to pub fn binds<'a>(&'a self, handler: &'a Ident) -> &'a Ident { self.binds.as_ref().unwrap_or(handler) } } impl Parse for ExceptionArgs { fn parse(input: ParseStream<'_>) -> parse::Result { parse_args(input, /* binds */ true, /* capacity */ false).map( |Args { binds, priority, resources, schedule, spawn, .. }| { ExceptionArgs { binds, priority, resources, schedule, spawn, } }, ) } } impl Exception { fn check(args: ExceptionArgs, item: ItemFn) -> parse::Result { let valid_signature = check_signature(&item) && item.decl.inputs.len() == 1 && is_unit(&item.decl.output); let span = item.span(); let name = item.ident.to_string(); if valid_signature { if let Some((context, _)) = check_inputs(item.decl.inputs, &name) { let span = item.ident.span(); match &*args .binds .as_ref() .map(|ident| ident.to_string()) .unwrap_or(name) { "MemoryManagement" | "BusFault" | "UsageFault" | "SecureFault" | "SVCall" | "DebugMonitor" | "PendSV" => {} // OK "SysTick" => { if cfg!(feature = "timer-queue") { return Err(parse::Error::new( span, "the `SysTick` exception can't be used because it's used by \ the runtime when the `timer-queue` feature is enabled", )); } } _ => { return Err(parse::Error::new( span, "only exceptions with configurable priority can be used as hardware tasks", )); } } let (statics, stmts) = extract_statics(item.block.stmts); return Ok(Exception { args, attrs: item.attrs, statics: Static::parse(statics)?, context, stmts, }); } } Err(parse::Error::new( span, &format!( "this `exception` handler must have type signature `fn({}::Context)`", name ), )) } } pub struct Interrupt { pub args: InterruptArgs, pub attrs: Vec, pub statics: BTreeMap, pub context: Pat, pub stmts: Vec, } pub type InterruptArgs = ExceptionArgs; impl Interrupt { fn check(args: InterruptArgs, item: ItemFn) -> parse::Result { let valid_signature = check_signature(&item) && item.decl.inputs.len() == 1 && is_unit(&item.decl.output); let span = item.span(); let name = item.ident.to_string(); if valid_signature { if let Some((context, _)) = check_inputs(item.decl.inputs, &name) { match &*name { "init" | "idle" | "resources" => { return Err(parse::Error::new( span, "`interrupt` handlers can NOT be named `idle`, `init` or `resources`", )); } _ => {} } let (statics, stmts) = extract_statics(item.block.stmts); return Ok(Interrupt { args, attrs: item.attrs, statics: Static::parse(statics)?, context, stmts, }); } } Err(parse::Error::new( span, format!( "this `interrupt` handler must have type signature `fn({}::Context)`", name ), )) } } pub struct Resource { pub cfgs: Vec, pub attrs: Vec, pub mutability: Option, pub ty: Box, pub expr: Option>, } impl Resource { fn check(item: ItemStatic) -> parse::Result { if item.vis != Visibility::Inherited { return Err(parse::Error::new( item.span(), "resources must have inherited / private visibility", )); } let uninitialized = match *item.expr { Expr::Tuple(ref tuple) => tuple.elems.is_empty(), _ => false, }; let (cfgs, attrs) = extract_cfgs(item.attrs); Ok(Resource { cfgs, attrs, mutability: item.mutability, ty: item.ty, expr: if uninitialized { None } else { Some(item.expr) }, }) } } pub struct TaskArgs { pub capacity: Option, pub priority: u8, pub resources: Idents, pub spawn: Idents, pub schedule: Idents, } impl Parse for TaskArgs { fn parse(input: ParseStream<'_>) -> parse::Result { parse_args(input, /* binds */ false, /* capacity */ true).map( |Args { capacity, priority, resources, schedule, spawn, .. }| { TaskArgs { capacity, priority, resources, schedule, spawn, } }, ) } } // Parser shared by ExceptionArgs, InterruptArgs and TaskArgs fn parse_args( input: ParseStream<'_>, accepts_binds: bool, accepts_capacity: bool, ) -> parse::Result { if input.is_empty() { return Ok(Args::default()); } let mut binds = None; let mut capacity = None; let mut priority = None; let mut resources = None; let mut schedule = None; let mut spawn = None; let content; parenthesized!(content in input); loop { if content.is_empty() { break; } // #ident = .. let ident: Ident = content.parse()?; let _: Token![=] = content.parse()?; let ident_s = ident.to_string(); match &*ident_s { "binds" if accepts_binds => { if binds.is_some() { return Err(parse::Error::new( ident.span(), "argument appears more than once", )); } // #ident let ident = content.parse()?; binds = Some(ident); } "capacity" if accepts_capacity => { if capacity.is_some() { return Err(parse::Error::new( ident.span(), "argument appears more than once", )); } // #lit let lit: LitInt = content.parse()?; if lit.suffix() != IntSuffix::None { return Err(parse::Error::new( lit.span(), "this literal must be unsuffixed", )); } let value = lit.value(); if value > u64::from(u8::MAX) || value == 0 { return Err(parse::Error::new( lit.span(), "this literal must be in the range 1...255", )); } capacity = Some(value as u8); } "priority" => { if priority.is_some() { return Err(parse::Error::new( ident.span(), "argument appears more than once", )); } // #lit let lit: LitInt = content.parse()?; if lit.suffix() != IntSuffix::None { return Err(parse::Error::new( lit.span(), "this literal must be unsuffixed", )); } let value = lit.value(); if value > u64::from(u8::MAX) || value == 0 { return Err(parse::Error::new( lit.span(), "this literal must be in the range 1...255", )); } priority = Some(value as u8); } "schedule" if cfg!(not(feature = "timer-queue")) => { return Err(parse::Error::new( ident.span(), "The `schedule` API requires that the `timer-queue` feature is \ enabled in the `cortex-m-rtfm` crate", )); } "resources" | "schedule" | "spawn" => { // .. [#(#idents)*] let inner; bracketed!(inner in content); let mut idents = Idents::new(); for ident in inner.call(Punctuated::<_, Token![,]>::parse_terminated)? { if idents.contains(&ident) { return Err(parse::Error::new( ident.span(), "element appears more than once in list", )); } idents.insert(ident); } match &*ident_s { "resources" => { if resources.is_some() { return Err(parse::Error::new( ident.span(), "argument appears more than once", )); } resources = Some(idents); } "schedule" => { if schedule.is_some() { return Err(parse::Error::new( ident.span(), "argument appears more than once", )); } schedule = Some(idents); } "spawn" => { if spawn.is_some() { return Err(parse::Error::new( ident.span(), "argument appears more than once", )); } spawn = Some(idents); } _ => unreachable!(), } } _ => { return Err(parse::Error::new( ident.span(), format!( "expected one of: {}{}priority, resources, schedule or spawn", if accepts_binds { "binds, " } else { "" }, if accepts_capacity { "capacity, " } else { "" }, ), )); } } if content.is_empty() { break; } // , let _: Token![,] = content.parse()?; } Ok(Args { binds, capacity, priority: priority.unwrap_or(1), resources: resources.unwrap_or(Idents::new()), schedule: schedule.unwrap_or(Idents::new()), spawn: spawn.unwrap_or(Idents::new()), }) } pub struct Static { /// `#[cfg]` attributes pub cfgs: Vec, /// Attributes that are not `#[cfg]` pub attrs: Vec, pub ty: Box, pub expr: Box, } impl Static { fn parse(items: Vec) -> parse::Result> { let mut statics = BTreeMap::new(); for item in items { if statics.contains_key(&item.ident) { return Err(parse::Error::new( item.ident.span(), "this `static` is listed twice", )); } let (cfgs, attrs) = extract_cfgs(item.attrs); statics.insert( item.ident, Static { cfgs, attrs, ty: item.ty, expr: item.expr, }, ); } Ok(statics) } } pub struct Task { pub args: TaskArgs, pub cfgs: Vec, pub attrs: Vec, pub inputs: Vec, pub context: Pat, pub statics: BTreeMap, pub stmts: Vec, } impl Task { fn check(args: TaskArgs, item: ItemFn) -> parse::Result { let valid_signature = check_signature(&item) && !item.decl.inputs.is_empty() && is_unit(&item.decl.output); let span = item.span(); let name = item.ident.to_string(); if valid_signature { if let Some((context, rest)) = check_inputs(item.decl.inputs, &name) { let (statics, stmts) = extract_statics(item.block.stmts); let inputs = rest.map_err(|arg| { parse::Error::new( arg.span(), "inputs must be named arguments (e.f. `foo: u32`) and not include `self`", ) })?; match &*name { "init" | "idle" | "resources" => { return Err(parse::Error::new( span, "`task` handlers can NOT be named `idle`, `init` or `resources`", )); } _ => {} } let (cfgs, attrs) = extract_cfgs(item.attrs); return Ok(Task { args, cfgs, attrs, inputs, context, statics: Static::parse(statics)?, stmts, }); } } Err(parse::Error::new( span, &format!( "this `task` handler must have type signature `fn({}::Context, ..)`", name ), )) } } pub struct FreeInterrupt { pub attrs: Vec, } impl FreeInterrupt { fn parse(mod_: ItemForeignMod) -> parse::Result { let mut free_interrupts = FreeInterrupts::new(); for item in mod_.items { if let ForeignItem::Fn(f) = item { let valid_signature = f.vis == Visibility::Inherited && f.decl.generics.params.is_empty() && f.decl.generics.where_clause.is_none() && f.decl.inputs.is_empty() && f.decl.variadic.is_none() && is_unit(&f.decl.output); if !valid_signature { return Err(parse::Error::new( f.span(), "free interrupts must have type signature `fn()`", )); } if free_interrupts.contains_key(&f.ident) { return Err(parse::Error::new( f.ident.span(), "this interrupt appears twice", )); } free_interrupts.insert(f.ident, FreeInterrupt { attrs: f.attrs }); } else { return Err(parse::Error::new( mod_.abi.extern_token.span(), "`extern` block should only contains functions", )); } } Ok(free_interrupts) } } fn eq(attr: &Attribute, name: &str) -> bool { attr.style == AttrStyle::Outer && attr.path.segments.len() == 1 && { let pair = attr.path.segments.first().unwrap(); let segment = pair.value(); segment.arguments == PathArguments::None && segment.ident.to_string() == name } } fn extract_cfgs(attrs: Vec) -> (Vec, Vec) { let mut cfgs = vec![]; let mut not_cfgs = vec![]; for attr in attrs { if eq(&attr, "cfg") { cfgs.push(attr); } else { not_cfgs.push(attr); } } (cfgs, not_cfgs) } /// Extracts `static mut` vars from the beginning of the given statements fn extract_statics(stmts: Vec) -> (Statics, Vec) { let mut istmts = stmts.into_iter(); let mut statics = Statics::new(); let mut stmts = vec![]; while let Some(stmt) = istmts.next() { match stmt { Stmt::Item(Item::Static(var)) => { if var.mutability.is_some() { statics.push(var); } else { stmts.push(Stmt::Item(Item::Static(var))); break; } } _ => { stmts.push(stmt); break; } } } stmts.extend(istmts); (statics, stmts) } // checks that the list of arguments has the form `#pat: #name::Context, (..)` // // if the check succeeds it returns `#pat` plus the remaining arguments fn check_inputs( inputs: Punctuated, name: &str, ) -> Option<(Pat, Result, FnArg>)> { let mut inputs = inputs.into_iter(); match inputs.next() { Some(FnArg::Captured(first)) => { if is_path(&first.ty, &[name, "Context"]) { let rest = inputs .map(|arg| match arg { FnArg::Captured(arg) => Ok(arg), _ => Err(arg), }) .collect::, _>>(); Some((first.pat, rest)) } else { None } } _ => None, } } /// checks that a function signature /// /// - has no bounds (like where clauses) /// - is not `async` /// - is not `const` /// - is not `unsafe` /// - is not generic (has no type parametrs) /// - is not variadic /// - uses the Rust ABI (and not e.g. "C") fn check_signature(item: &ItemFn) -> bool { item.vis == Visibility::Inherited && item.constness.is_none() && item.asyncness.is_none() && item.abi.is_none() && item.unsafety.is_none() && item.decl.generics.params.is_empty() && item.decl.generics.where_clause.is_none() && item.decl.variadic.is_none() } fn is_path(ty: &Type, segments: &[&str]) -> bool { match ty { Type::Path(tpath) if tpath.qself.is_none() => { tpath.path.segments.len() == segments.len() && tpath .path .segments .iter() .zip(segments) .all(|(lhs, rhs)| lhs.ident == **rhs) } _ => false, } } fn is_bottom(ty: &ReturnType) -> bool { if let ReturnType::Type(_, ty) = ty { if let Type::Never(_) = **ty { true } else { false } } else { false } } fn is_unit(ty: &ReturnType) -> bool { if let ReturnType::Type(_, ty) = ty { if let Type::Tuple(ref tuple) = **ty { tuple.elems.is_empty() } else { false } } else { true } }