mirror of
https://github.com/rtic-rs/rtic.git
synced 2025-12-20 06:45:36 +01:00
RTIC v2: Initial commit
rtic-syntax is now part of RTIC repository
This commit is contained in:
parent
d43c2b64cc
commit
4c2c05a801
167 changed files with 5219 additions and 602 deletions
338
macros/src/syntax/parse/util.rs
Normal file
338
macros/src/syntax/parse/util.rs
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue