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#[derive(Debug, TryClone, PartialEq, Eq, ToTokens, Spanned)]
46#[non_exhaustive]
47pub struct Attribute {
48 pub hash: T![#],
50 #[rune(option)]
52 pub style: AttrStyle,
53 pub open: T!['['],
55 pub path: ast::Path,
57 #[rune(iter)]
59 pub input: TokenStream,
60 pub close: T![']'],
62}
63
64impl Attribute {}
65
66impl Parse for Attribute {
67 fn parse(p: &mut Parser<'_>) -> Result<Self> {
68 let hash = p.parse()?;
69 let style = p.parse()?;
70 let open = p.parse()?;
71 let path = p.parse()?;
72
73 let close;
74
75 let mut level = 1;
76 let mut input = TokenStream::new();
77
78 loop {
79 let token = p.next()?;
80
81 match token.kind {
82 K!['['] => level += 1,
83 K![']'] => {
84 level -= 1;
85 }
86 _ => (),
87 }
88
89 if level == 0 {
90 close = ast::CloseBracket { span: token.span };
91 break;
92 }
93
94 input.push(token)?;
95 }
96
97 Ok(Attribute {
98 hash,
99 style,
100 open,
101 path,
102 input,
103 close,
104 })
105 }
106}
107
108impl Peek for Attribute {
109 fn peek(p: &mut Peeker<'_>) -> bool {
110 match (p.nth(0), p.nth(1)) {
111 (K![#], K![!]) => true,
112 (K![#], K!['[']) => true,
113 _ => false,
114 }
115 }
116}
117
118impl IntoExpectation for Attribute {
119 fn into_expectation(self) -> Expectation {
120 Expectation::Description(match &self.style {
121 AttrStyle::Inner => "inner attribute",
122 AttrStyle::Outer(_) => "outer attribute",
123 })
124 }
125}
126
127#[derive(Debug, TryClone, Clone, Copy, PartialEq, Eq, OptionSpanned, ToTokens)]
129#[try_clone(copy)]
130#[non_exhaustive]
131pub enum AttrStyle {
132 Inner,
134 Outer(T![!]),
136}
137
138impl Parse for AttrStyle {
139 fn parse(p: &mut Parser<'_>) -> Result<Self> {
140 Ok(if p.peek::<T![!]>()? {
141 Self::Outer(p.parse()?)
142 } else {
143 Self::Inner
144 })
145 }
146}
147
148#[non_exhaustive]
151pub(crate) struct OuterAttribute;
152
153impl Peek for OuterAttribute {
154 fn peek(p: &mut Peeker<'_>) -> bool {
155 match (p.nth(0), p.nth(1)) {
156 (K![#], K![!]) => true,
157 _ => false,
158 }
159 }
160}