use std::rc::Rc;
use syn::{Expr, ExprIf, Type};
use crate::ir::facts::{IrSpan, Visibility};
use crate::ir::type_introspection::get_type_span_start;
pub(super) fn span_from(lc: proc_macro2::LineColumn) -> IrSpan {
IrSpan {
line: lc.line,
column: lc.column,
}
}
pub(super) fn is_cfg_test_attr(attr: &syn::Attribute) -> bool {
attr.path().is_ident("cfg")
&& attr.meta.require_list().ok().is_some_and(|list| {
let mut iter = list.tokens.clone().into_iter();
let first = iter.next();
matches!(
(&first, iter.next()),
(Some(proc_macro2::TokenTree::Ident(ident)), None) if ident == "test"
)
})
}
pub(super) fn block_line_count(block: &syn::Block) -> usize {
let span = block.brace_token.span.join();
span.end().line.saturating_sub(span.start().line) + 1
}
pub(super) fn is_pure_forwarder(block: &syn::Block) -> bool {
let [syn::Stmt::Expr(expr, _)] = &block.stmts[..] else {
return false;
};
let mut inner = expr;
loop {
match inner {
Expr::Try(try_expr) => inner = &try_expr.expr,
Expr::Await(await_expr) => inner = &await_expr.base,
_ => break,
}
}
let Expr::MethodCall(call) = inner else {
return false;
};
let Expr::Field(field) = call.receiver.as_ref() else {
return false;
};
matches!(field.base.as_ref(), Expr::Path(path) if path.path.is_ident("self"))
}
pub(super) fn normalize_visibility(vis: &syn::Visibility) -> Visibility {
match vis {
syn::Visibility::Inherited => Visibility::Private,
syn::Visibility::Public(_) => Visibility::Public,
syn::Visibility::Restricted(restricted) => normalize_restricted(restricted),
}
}
fn normalize_restricted(restricted: &syn::VisRestricted) -> Visibility {
let path = restricted_path_string(&restricted.path);
match (restricted.in_token.is_some(), path.as_str()) {
(false, "crate") => Visibility::Crate,
(false, "super") => Visibility::Super,
_ => Visibility::Restricted(path.into_boxed_str()),
}
}
fn restricted_path_string(path: &syn::Path) -> String {
path.segments
.iter()
.map(|seg| seg.ident.to_string())
.collect::<Vec<_>>()
.join("::")
}
pub(super) fn cfg_feature_name(attr: &syn::Attribute) -> Option<Rc<str>> {
if !attr.path().is_ident("cfg") {
return None;
}
let name_value = attr.parse_args::<syn::MetaNameValue>().ok()?;
match (name_value.path.is_ident("feature"), &name_value.value) {
(
true,
syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(feature),
..
}),
) => Some(Rc::from(feature.value())),
_ => None,
}
}
pub(super) fn for_each_pat_ident_span(
pat: &syn::Pat,
type_span: Option<IrSpan>,
f: &mut impl FnMut(Box<str>, IrSpan, Option<IrSpan>),
) {
match pat {
syn::Pat::Ident(pi) => {
let name: Box<str> = pi.ident.to_string().into_boxed_str();
let span = span_from(pi.ident.span().start());
f(name, span, type_span);
}
syn::Pat::Type(pt) => {
let ty_span = span_from(get_type_span_start(&pt.ty));
for_each_pat_ident_span(&pt.pat, Some(ty_span), f);
}
syn::Pat::Tuple(pt) => {
for inner_pat in &pt.elems {
for_each_pat_ident_span(inner_pat, None, f);
}
}
syn::Pat::TupleStruct(pts) => {
for field_pat in &pts.elems {
for_each_pat_ident_span(field_pat, None, f);
}
}
syn::Pat::Struct(ps) => {
for field in &ps.fields {
for_each_pat_ident_span(&field.pat, None, f);
}
}
syn::Pat::Slice(psl) => {
for slice_pat in &psl.elems {
for_each_pat_ident_span(slice_pat, None, f);
}
}
syn::Pat::Or(po) => {
for case in &po.cases {
for_each_pat_ident_span(case, None, f);
}
}
syn::Pat::Reference(pr) => for_each_pat_ident_span(&pr.pat, type_span, f),
_ => {}
}
}
pub(super) fn typed_ident_from_pat(pat: &syn::Pat) -> Option<(&syn::Ident, &Type)> {
let syn::Pat::Type(pt) = pat else { return None };
let syn::Pat::Ident(pi) = pt.pat.as_ref() else {
return None;
};
Some((&pi.ident, &pt.ty))
}
pub(super) fn count_else_chain(expr: &ExprIf) -> usize {
let mut count = 1;
let mut current = expr;
while let Some(ref else_branch) = current.else_branch {
match else_branch.1.as_ref() {
Expr::If(next_if) => {
count += 1;
current = next_if;
}
Expr::Block(_) => {
count += 1;
break;
}
_ => break,
}
}
count
}