rapx/verify/
attr_parser.rs

1//! Parsing utilities for `#[rapx::requires(...)]` outer attributes.
2//!
3//! This module converts a raw `#[rapx::requires(...)]` attribute string into a
4//! structured representation that the verification analysis can consume without
5//! depending on `syn` expression details in later stages.
6//!
7//! The currently supported shape is:
8//!
9//! ```text
10//! #[rapx::requires(property_call, kind = "...")]
11//! ```
12//!
13//! where `kind = "..."` applies to the property in the same attribute.
14
15use syn::{
16    Expr, ExprCall, ExprPath, Lit, Result as SynResult, Token,
17    parse::{Parse, ParseStream},
18};
19
20/// A parsed `requires` property in the form `tag(arg0, arg1, ...)`.
21#[derive(Debug, Clone)]
22pub struct ParsedProperty {
23    /// The property name extracted from the call target.
24    pub tag: String,
25    /// The positional arguments passed to the property call.
26    pub args: Vec<Expr>,
27    /// Optional `kind` metadata associated with this property.
28    pub kind: Option<String>,
29}
30
31/// The parsed result of a `#[rapx::requires(...)]` attribute.
32#[derive(Debug, Clone, Default)]
33pub struct ParsedRapxAttr {
34    /// All parsed properties in source order.
35    pub properties: Vec<ParsedProperty>,
36}
37
38impl Parse for ParsedProperty {
39    /// Parse a single property item from a `requires` attribute argument list.
40    ///
41    /// Supported forms:
42    /// - `nonzero(x)`
43    /// - `nonzero(x), kind = "ptr"`
44    fn parse(input: ParseStream<'_>) -> SynResult<Self> {
45        let expr: Expr = input.parse()?;
46        let mut property = parse_property_expr(expr)?;
47
48        if input.peek(Token![,]) {
49            let fork = input.fork();
50            let _: Token![,] = fork.parse()?;
51            if fork.peek(syn::Ident) && fork.peek2(Token![=]) {
52                let _: Token![,] = input.parse()?;
53                let ident: syn::Ident = input.parse()?;
54                let _: Token![=] = input.parse()?;
55                let value: Expr = input.parse()?;
56
57                if ident == "kind" {
58                    if let Expr::Lit(ref expr_lit) = value
59                        && let Lit::Str(ref kind) = expr_lit.lit
60                    {
61                        property.kind = Some(kind.value());
62                    } else {
63                        return Err(syn::Error::new_spanned(
64                            value,
65                            "RAPx requires attribute kind must be a string literal",
66                        ));
67                    }
68                } else {
69                    return Err(syn::Error::new(
70                        ident.span(),
71                        "unsupported named RAPx requires attribute argument",
72                    ));
73                }
74            }
75        }
76
77        Ok(property)
78    }
79}
80
81/// A thin wrapper that allows parsing exactly one outer attribute from a string.
82struct RequireOuterAttribute {
83    attr: syn::Attribute,
84}
85
86impl Parse for RequireOuterAttribute {
87    /// Parse exactly one outer attribute.
88    fn parse(input: ParseStream<'_>) -> SynResult<Self> {
89        Ok(Self {
90            attr: input
91                .call(syn::Attribute::parse_outer)?
92                .into_iter()
93                .next()
94                .ok_or_else(|| input.error("expected exactly one outer attribute"))?,
95        })
96    }
97}
98
99/// Parse a raw attribute string into a structured `requires` representation.
100///
101/// Returns an empty default value when the attribute does not match
102/// `rapx::requires` or when it is not a list attribute.
103pub fn parse_rapx_attr(attr_str: &str, expected_name: &str) -> SynResult<ParsedRapxAttr> {
104    // Parse the raw string into a single outer attribute node.
105    let attr = syn::parse_str::<RequireOuterAttribute>(attr_str)?.attr;
106    if !is_expected_syn_rapx_attr(&attr, expected_name) {
107        return Ok(ParsedRapxAttr::default());
108    }
109
110    // Only list-style attributes carry an argument list.
111    let syn::Meta::List(meta_list) = &attr.meta else {
112        return Ok(ParsedRapxAttr::default());
113    };
114
115    let property = meta_list.parse_args::<ParsedProperty>()?;
116    Ok(ParsedRapxAttr {
117        properties: vec![property],
118    })
119}
120
121/// Check whether an attribute path is exactly `rapx::<expected_name>`.
122fn is_expected_syn_rapx_attr(attr: &syn::Attribute, expected_name: &str) -> bool {
123    let mut segments = attr.path().segments.iter();
124    matches!(
125        (segments.next(), segments.next(), segments.next()),
126        (Some(first), Some(second), None)
127            if first.ident == "rapx" && second.ident == expected_name
128    )
129}
130
131/// Parse a property call expression into a [`ParsedProperty`].
132fn parse_property_expr(expr: Expr) -> SynResult<ParsedProperty> {
133    match expr {
134        Expr::Call(ExprCall { func, args, .. }) => {
135            // Use the final segment of the callee path as the property tag.
136            let tag = match *func {
137                Expr::Path(ExprPath { path, .. }) => path
138                    .segments
139                    .last()
140                    .map(|seg| seg.ident.to_string())
141                    .ok_or_else(|| syn::Error::new_spanned(path, "missing property name"))?,
142                other => {
143                    return Err(syn::Error::new_spanned(
144                        other,
145                        "unsupported RAPx property callee expression",
146                    ));
147                }
148            };
149
150            Ok(ParsedProperty {
151                tag,
152                args: args.into_iter().collect(),
153                kind: None,
154            })
155        }
156        other => Err(syn::Error::new_spanned(
157            other,
158            "unsupported RAPx property expression",
159        )),
160    }
161}