syn_solidity/ident/
mod.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
use crate::Spanned;
use proc_macro2::{Ident, Span};
use quote::ToTokens;
use std::fmt;
use syn::{
    ext::IdentExt,
    parse::{Parse, ParseStream},
    Result, Token,
};

mod path;
pub use path::SolPath;

// See `./kw.c`.

/// The set difference of the Rust and Solidity keyword sets. We need this so that we can emit raw
/// identifiers for Solidity keywords.
static KW_DIFFERENCE: &[&str] = &include!("./difference.expr");

/// A Solidity identifier.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct SolIdent(pub Ident);

impl quote::IdentFragment for SolIdent {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.fmt(f)
    }

    fn span(&self) -> Option<Span> {
        Some(self.0.span())
    }
}

impl fmt::Display for SolIdent {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.fmt(f)
    }
}

impl fmt::Debug for SolIdent {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_tuple("SolIdent").field(&self.to_string()).finish()
    }
}

impl<T: ?Sized + AsRef<str>> PartialEq<T> for SolIdent {
    fn eq(&self, other: &T) -> bool {
        self.0 == other
    }
}

impl From<Ident> for SolIdent {
    fn from(value: Ident) -> Self {
        Self::new_spanned(&value.to_string(), value.span())
    }
}

impl From<SolIdent> for Ident {
    fn from(value: SolIdent) -> Self {
        value.0
    }
}

impl From<&str> for SolIdent {
    fn from(value: &str) -> Self {
        Self::new(value)
    }
}

impl Parse for SolIdent {
    fn parse(input: ParseStream<'_>) -> Result<Self> {
        check_dollar(input)?;
        let id = Ident::parse_any(input)?;
        Ok(Self::from(id))
    }
}

impl ToTokens for SolIdent {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        self.0.to_tokens(tokens);
    }
}

impl Spanned for SolIdent {
    fn span(&self) -> Span {
        self.0.span()
    }

    fn set_span(&mut self, span: Span) {
        self.0.set_span(span);
    }
}

impl SolIdent {
    pub fn new(s: &str) -> Self {
        Self::new_spanned(s, Span::call_site())
    }

    pub fn new_spanned(mut s: &str, span: Span) -> Self {
        let mut new_raw = KW_DIFFERENCE.contains(&s);

        if s.starts_with("r#") {
            new_raw = true;
            s = &s[2..];
        }

        if matches!(s, "_" | "self" | "Self" | "super" | "crate") {
            new_raw = false;
        }

        if new_raw {
            Self(Ident::new_raw(s, span))
        } else {
            Self(Ident::new(s, span))
        }
    }

    /// Returns the identifier as a string, without the `r#` prefix if present.
    pub fn as_string(&self) -> String {
        let mut s = self.0.to_string();
        if s.starts_with("r#") {
            s = s[2..].to_string();
        }
        s
    }

    /// Parses any identifier including keywords.
    pub fn parse_any(input: ParseStream<'_>) -> Result<Self> {
        check_dollar(input)?;

        input.call(Ident::parse_any).map(Into::into)
    }

    /// Peeks any identifier including keywords.
    pub fn peek_any(input: ParseStream<'_>) -> bool {
        input.peek(Ident::peek_any)
    }

    pub fn parse_opt(input: ParseStream<'_>) -> Result<Option<Self>> {
        if Self::peek_any(input) {
            input.parse().map(Some)
        } else {
            Ok(None)
        }
    }
}

fn check_dollar(input: ParseStream<'_>) -> Result<()> {
    if input.peek(Token![$]) {
        Err(input.error("Solidity identifiers starting with `$` are unsupported. This is a known limitation of syn-solidity."))
    } else {
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::sol_path;

    #[test]
    fn ident() {
        let id: SolIdent = syn::parse_str("a").unwrap();
        assert_eq!(id, SolIdent::new("a"));
    }

    #[test]
    fn keywords() {
        // keywords in Rust, but not Solidity; we try to make them "raw", although some, like
        // `crate` can never be made identifiers. See ./kw.c`.
        let difference: &[&str] = &include!("./difference.expr");
        for &s in difference {
            let id: SolIdent = syn::parse_str(s).unwrap();
            assert_eq!(id, SolIdent::new(s));
            assert_eq!(id.to_string(), format!("r#{s}"));
            assert_eq!(id.as_string(), s);
        }

        // keywords in both languages; we don't make them "raw" because they are always invalid.
        let intersection: &[&str] = &include!("./intersection.expr");
        for &s in intersection {
            let id: SolIdent = syn::parse_str(s).unwrap();
            assert_eq!(id, SolIdent::new(s));
            assert_eq!(id.to_string(), s);
            assert_eq!(id.as_string(), s);
        }
    }

    #[test]
    fn ident_path() {
        let path: SolPath = syn::parse_str("a.b.c").unwrap();
        assert_eq!(path, sol_path!["a", "b", "c"]);
    }

    #[test]
    fn ident_path_trailing() {
        let _e = syn::parse_str::<SolPath>("a.b.").unwrap_err();
    }

    #[test]
    fn ident_dollar() {
        assert!(syn::parse_str::<SolIdent>("$hello")
            .unwrap_err()
            .to_string()
            .contains("Solidity identifiers starting with `$` are unsupported."));
    }
}