rune/ast/
attribute.rs

1use crate::ast::prelude::*;
2
3#[test]
4#[cfg(not(miri))]
5fn ast_parse() {
6    rt::<ast::Attribute>("#[foo = \"foo\"]");
7    rt::<ast::Attribute>("#[foo()]");
8    rt::<ast::Attribute>("#![foo]");
9    rt::<ast::Attribute>("#![cfg(all(feature = \"potato\"))]");
10    rt::<ast::Attribute>("#[x+1]");
11
12    const TEST_STRINGS: &[&str] = &[
13        "#[foo]",
14        "#[a::b::c]",
15        "#[foo = \"hello world\"]",
16        "#[foo = 1]",
17        "#[foo = 1.3]",
18        "#[foo = true]",
19        "#[foo = b\"bytes\"]",
20        "#[foo = (1, 2, \"string\")]",
21        "#[foo = #{\"a\": 1} ]",
22        r#"#[foo = Fred {"a": 1} ]"#,
23        r#"#[foo = a::Fred {"a": #{ "b": 2 } } ]"#,
24        "#[bar()]",
25        "#[bar(baz)]",
26        "#[derive(Debug, PartialEq, PartialOrd)]",
27        "#[tracing::instrument(skip(non_debug))]",
28        "#[zanzibar(a = \"z\", both = false, sasquatch::herring)]",
29        r#"#[doc = "multiline \
30                docs are neat"
31        ]"#,
32    ];
33
34    for s in TEST_STRINGS.iter() {
35        rt::<ast::Attribute>(s);
36        let withbang = s.replacen("#[", "#![", 1);
37        rt::<ast::Attribute>(&withbang);
38    }
39}
40
41/// Attributes like:
42///
43/// * `#[derive(Debug)]`.
44/// * `#![doc = "test"]`.
45#[derive(Debug, TryClone, PartialEq, Eq, ToTokens, Spanned)]
46#[non_exhaustive]
47pub struct Attribute {
48    /// The `#` character
49    pub hash: T![#],
50    /// Specify if the attribute is outer `#!` or inner `#`
51    #[rune(option)]
52    pub style: AttrStyle,
53    /// The `[` character
54    pub open: T!['['],
55    /// The path of the attribute
56    pub path: ast::Path,
57    /// The input to the input of the attribute
58    #[rune(iter)]
59    pub input: TokenStream,
60    /// The `]` character
61    pub close: T![']'],
62}
63
64impl Attribute {
65    pub(crate) fn input_span(&self) -> Span {
66        self.input
67            .option_span()
68            .unwrap_or_else(|| self.close.span.head())
69    }
70}
71
72impl Parse for Attribute {
73    fn parse(p: &mut Parser<'_>) -> Result<Self> {
74        let hash = p.parse()?;
75        let style = p.parse()?;
76        let open = p.parse()?;
77        let path = p.parse()?;
78
79        let close;
80
81        let mut level = 1;
82        let mut input = TokenStream::new();
83
84        loop {
85            let token = p.next()?;
86
87            match token.kind {
88                K!['['] => level += 1,
89                K![']'] => {
90                    level -= 1;
91                }
92                _ => (),
93            }
94
95            if level == 0 {
96                close = ast::CloseBracket { span: token.span };
97                break;
98            }
99
100            input.push(token)?;
101        }
102
103        Ok(Attribute {
104            hash,
105            style,
106            open,
107            path,
108            input,
109            close,
110        })
111    }
112}
113
114impl Peek for Attribute {
115    fn peek(p: &mut Peeker<'_>) -> bool {
116        match (p.nth(0), p.nth(1)) {
117            (K![#], K![!]) => true,
118            (K![#], K!['[']) => true,
119            _ => false,
120        }
121    }
122}
123
124impl IntoExpectation for Attribute {
125    fn into_expectation(self) -> Expectation {
126        Expectation::Description(match &self.style {
127            AttrStyle::Inner => "inner attribute",
128            AttrStyle::Outer(_) => "outer attribute",
129        })
130    }
131}
132
133/// Whether or not the attribute is an outer `#!` or inner `#` attribute
134#[derive(Debug, TryClone, Clone, Copy, PartialEq, Eq, OptionSpanned, ToTokens)]
135#[try_clone(copy)]
136#[non_exhaustive]
137pub enum AttrStyle {
138    /// `#`
139    Inner,
140    /// `#!`
141    Outer(T![!]),
142}
143
144impl Parse for AttrStyle {
145    fn parse(p: &mut Parser<'_>) -> Result<Self> {
146        Ok(if p.peek::<T![!]>()? {
147            Self::Outer(p.parse()?)
148        } else {
149            Self::Inner
150        })
151    }
152}
153
154/// Tag struct to assist peeking for an outer `#![...]` attributes at the top of
155/// a module/file
156#[non_exhaustive]
157pub(crate) struct OuterAttribute;
158
159impl Peek for OuterAttribute {
160    fn peek(p: &mut Peeker<'_>) -> bool {
161        match (p.nth(0), p.nth(1)) {
162            (K![#], K![!]) => true,
163            _ => false,
164        }
165    }
166}