use syn::{ bracketed, parse::{self, ParseStream}, punctuated::Punctuated, spanned::Spanned, Abi, AttrStyle, Attribute, Expr, FnArg, ForeignItemFn, Ident, ItemFn, Pat, PatType, Path, PathArguments, ReturnType, Token, Type, Visibility, }; use crate::{ ast::{Access, Local, LocalResources, SharedResources, TaskLocal}, Map, }; pub fn abi_is_rust(abi: &Abi) -> bool { match &abi.name { None => true, Some(s) => s.value() == "Rust", } } pub fn attr_eq(attr: &Attribute, name: &str) -> bool { attr.style == AttrStyle::Outer && attr.path.segments.len() == 1 && { let segment = attr.path.segments.first().unwrap(); segment.arguments == PathArguments::None && *segment.ident.to_string() == *name } } /// 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 parameters) /// - is not variadic /// - uses the Rust ABI (and not e.g. "C") pub fn check_fn_signature(item: &ItemFn) -> bool { item.vis == Visibility::Inherited && item.sig.constness.is_none() && item.sig.asyncness.is_none() && item.sig.abi.is_none() && item.sig.unsafety.is_none() && item.sig.generics.params.is_empty() && item.sig.generics.where_clause.is_none() && item.sig.variadic.is_none() } #[allow(dead_code)] pub fn check_foreign_fn_signature(item: &ForeignItemFn) -> bool { item.vis == Visibility::Inherited && item.sig.constness.is_none() && item.sig.asyncness.is_none() && item.sig.abi.is_none() && item.sig.unsafety.is_none() && item.sig.generics.params.is_empty() && item.sig.generics.where_clause.is_none() && item.sig.variadic.is_none() } pub struct FilterAttrs { pub cfgs: Vec, pub docs: Vec, pub attrs: Vec, } pub fn filter_attributes(input_attrs: Vec) -> FilterAttrs { let mut cfgs = vec![]; let mut docs = vec![]; let mut attrs = vec![]; for attr in input_attrs { if attr_eq(&attr, "cfg") { cfgs.push(attr); } else if attr_eq(&attr, "doc") { docs.push(attr); } else { attrs.push(attr); } } FilterAttrs { cfgs, docs, attrs } } pub fn extract_lock_free(attrs: &mut Vec) -> parse::Result { if let Some(pos) = attrs.iter().position(|attr| attr_eq(attr, "lock_free")) { attrs.remove(pos); Ok(true) } else { Ok(false) } } pub fn parse_shared_resources(content: ParseStream<'_>) -> parse::Result { let inner; bracketed!(inner in content); let mut resources = Map::new(); for e in inner.call(Punctuated::::parse_terminated)? { let err = Err(parse::Error::new( e.span(), "identifier appears more than once in list", )); let (access, path) = match e { Expr::Path(e) => (Access::Exclusive, e.path), Expr::Reference(ref r) if r.mutability.is_none() => match &*r.expr { Expr::Path(e) => (Access::Shared, e.path.clone()), _ => return err, }, _ => return err, }; let ident = extract_resource_name_ident(path)?; if resources.contains_key(&ident) { return Err(parse::Error::new( ident.span(), "resource appears more than once in list", )); } resources.insert(ident, access); } Ok(resources) } fn extract_resource_name_ident(path: Path) -> parse::Result { if path.leading_colon.is_some() || path.segments.len() != 1 || path.segments[0].arguments != PathArguments::None { Err(parse::Error::new( path.span(), "resource must be an identifier, not a path", )) } else { Ok(path.segments[0].ident.clone()) } } pub fn parse_local_resources(content: ParseStream<'_>) -> parse::Result { let inner; bracketed!(inner in content); let mut resources = Map::new(); for e in inner.call(Punctuated::::parse_terminated)? { let err = Err(parse::Error::new( e.span(), "identifier appears more than once in list", )); let (name, local) = match e { // local = [IDENT], Expr::Path(path) => { if !path.attrs.is_empty() { return Err(parse::Error::new( path.span(), "attributes are not supported here", )); } let ident = extract_resource_name_ident(path.path)?; // let (cfgs, attrs) = extract_cfgs(path.attrs); (ident, TaskLocal::External) } // local = [IDENT: TYPE = EXPR] Expr::Assign(e) => { let (name, ty, cfgs, attrs) = match *e.left { Expr::Type(t) => { // Extract name and attributes let (name, cfgs, attrs) = match *t.expr { Expr::Path(path) => { let name = extract_resource_name_ident(path.path)?; let FilterAttrs { cfgs, attrs, .. } = filter_attributes(path.attrs); (name, cfgs, attrs) } _ => return err, }; let ty = t.ty; // Error check match &*ty { Type::Array(_) => {} Type::Path(_) => {} Type::Ptr(_) => {} Type::Tuple(_) => {} _ => return Err(parse::Error::new( ty.span(), "unsupported type, must be an array, tuple, pointer or type path", )), }; (name, ty, cfgs, attrs) } e => return Err(parse::Error::new(e.span(), "malformed, expected a type")), }; let expr = e.right; // Expr ( name, TaskLocal::Declared(Local { attrs, cfgs, ty, expr, }), ) } expr => { return Err(parse::Error::new( expr.span(), "malformed, expected 'IDENT: TYPE = EXPR'", )) } }; resources.insert(name, local); } Ok(resources) } type ParseInputResult = Option<(Box, Result, FnArg>)>; pub fn parse_inputs(inputs: Punctuated, name: &str) -> ParseInputResult { let mut inputs = inputs.into_iter(); match inputs.next() { Some(FnArg::Typed(first)) => { if type_is_path(&first.ty, &[name, "Context"]) { let rest = inputs .map(|arg| match arg { FnArg::Typed(arg) => Ok(arg), _ => Err(arg), }) .collect::, _>>(); Some((first.pat, rest)) } else { None } } _ => None, } } pub fn type_is_bottom(ty: &ReturnType) -> bool { if let ReturnType::Type(_, ty) = ty { matches!(**ty, Type::Never(_)) } else { false } } fn extract_init_resource_name_ident(ty: Type) -> Result { match ty { Type::Path(path) => { let path = path.path; if path.leading_colon.is_some() || path.segments.len() != 1 || path.segments[0].arguments != PathArguments::None { Err(()) } else { Ok(path.segments[0].ident.clone()) } } _ => Err(()), } } /// Checks Init's return type, return the user provided types for analysis pub fn type_is_init_return(ty: &ReturnType, name: &str) -> Result<(Ident, Ident), ()> { match ty { ReturnType::Default => Err(()), ReturnType::Type(_, ty) => match &**ty { Type::Tuple(t) => { // return should be: // fn -> (User's #[shared] struct, User's #[local] struct, {name}::Monotonics) // // We check the length and the last one here, analysis checks that the user // provided structs are correct. if t.elems.len() == 3 && type_is_path(&t.elems[2], &[name, "Monotonics"]) { return Ok(( extract_init_resource_name_ident(t.elems[0].clone())?, extract_init_resource_name_ident(t.elems[1].clone())?, )); } Err(()) } _ => Err(()), }, } } pub fn type_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, } } pub fn type_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 } }