aboutsummaryrefslogtreecommitdiff
path: root/macros/src/syntax/parse/util.rs
diff options
context:
space:
mode:
Diffstat (limited to 'macros/src/syntax/parse/util.rs')
-rw-r--r--macros/src/syntax/parse/util.rs338
1 files changed, 338 insertions, 0 deletions
diff --git a/macros/src/syntax/parse/util.rs b/macros/src/syntax/parse/util.rs
new file mode 100644
index 00000000..3fa51ef8
--- /dev/null
+++ b/macros/src/syntax/parse/util.rs
@@ -0,0 +1,338 @@
+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::syntax::{
+ 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, allow_async: bool) -> bool {
+ item.vis == Visibility::Inherited
+ && item.sig.constness.is_none()
+ && (item.sig.asyncness.is_none() || allow_async)
+ && 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, allow_async: bool) -> bool {
+ item.vis == Visibility::Inherited
+ && item.sig.constness.is_none()
+ && (item.sig.asyncness.is_none() || allow_async)
+ && 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<Attribute>,
+ pub docs: Vec<Attribute>,
+ pub attrs: Vec<Attribute>,
+}
+
+pub fn filter_attributes(input_attrs: Vec<Attribute>) -> 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<Attribute>) -> parse::Result<bool> {
+ 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<SharedResources> {
+ let inner;
+ bracketed!(inner in content);
+
+ let mut resources = Map::new();
+ for e in inner.call(Punctuated::<Expr, Token![,]>::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<Ident> {
+ 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<LocalResources> {
+ let inner;
+ bracketed!(inner in content);
+
+ let mut resources = Map::new();
+
+ for e in inner.call(Punctuated::<Expr, Token![,]>::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<Pat>, Result<Vec<PatType>, FnArg>)>;
+
+pub fn parse_inputs(inputs: Punctuated<FnArg, Token![,]>, 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::<Result<Vec<_>, _>>();
+
+ 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<Ident, ()> {
+ 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
+ }
+}