syn_solidity/ident/
mod.rs1use crate::Spanned;
2use proc_macro2::{Ident, Span};
3use quote::ToTokens;
4use std::fmt;
5use syn::{
6 ext::IdentExt,
7 parse::{Parse, ParseStream},
8 Result, Token,
9};
10
11mod path;
12pub use path::SolPath;
13
14static KW_DIFFERENCE: &[&str] = &include!("./difference.expr");
19
20#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
22#[repr(transparent)]
23pub struct SolIdent(pub Ident);
24
25impl quote::IdentFragment for SolIdent {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 self.0.fmt(f)
28 }
29
30 fn span(&self) -> Option<Span> {
31 Some(self.0.span())
32 }
33}
34
35impl fmt::Display for SolIdent {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 self.0.fmt(f)
38 }
39}
40
41impl fmt::Debug for SolIdent {
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 f.debug_tuple("SolIdent").field(&self.to_string()).finish()
44 }
45}
46
47impl<T: ?Sized + AsRef<str>> PartialEq<T> for SolIdent {
48 fn eq(&self, other: &T) -> bool {
49 self.0 == other
50 }
51}
52
53impl From<Ident> for SolIdent {
54 fn from(value: Ident) -> Self {
55 Self::new_spanned(&value.to_string(), value.span())
56 }
57}
58
59impl From<SolIdent> for Ident {
60 fn from(value: SolIdent) -> Self {
61 value.0
62 }
63}
64
65impl From<&str> for SolIdent {
66 fn from(value: &str) -> Self {
67 Self::new(value)
68 }
69}
70
71impl Parse for SolIdent {
72 fn parse(input: ParseStream<'_>) -> Result<Self> {
73 check_dollar(input)?;
74 let id = Ident::parse_any(input)?;
75 Ok(Self::from(id))
76 }
77}
78
79impl ToTokens for SolIdent {
80 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
81 self.0.to_tokens(tokens);
82 }
83}
84
85impl Spanned for SolIdent {
86 fn span(&self) -> Span {
87 self.0.span()
88 }
89
90 fn set_span(&mut self, span: Span) {
91 self.0.set_span(span);
92 }
93}
94
95impl SolIdent {
96 pub fn new(s: &str) -> Self {
97 Self::new_spanned(s, Span::call_site())
98 }
99
100 pub fn new_spanned(mut s: &str, span: Span) -> Self {
101 let mut new_raw = KW_DIFFERENCE.contains(&s);
102
103 if s.starts_with("r#") {
104 new_raw = true;
105 s = &s[2..];
106 }
107
108 if matches!(s, "_" | "self" | "Self" | "super" | "crate") {
109 new_raw = false;
110 }
111
112 if new_raw {
113 Self(Ident::new_raw(s, span))
114 } else {
115 Self(Ident::new(s, span))
116 }
117 }
118
119 pub fn as_string(&self) -> String {
121 let mut s = self.0.to_string();
122 if s.starts_with("r#") {
123 s = s[2..].to_string();
124 }
125 s
126 }
127
128 pub fn parse_any(input: ParseStream<'_>) -> Result<Self> {
130 check_dollar(input)?;
131
132 input.call(Ident::parse_any).map(Into::into)
133 }
134
135 pub fn peek_any(input: ParseStream<'_>) -> bool {
137 input.peek(Ident::peek_any)
138 }
139
140 pub fn parse_opt(input: ParseStream<'_>) -> Result<Option<Self>> {
141 if Self::peek_any(input) {
142 input.parse().map(Some)
143 } else {
144 Ok(None)
145 }
146 }
147}
148
149fn check_dollar(input: ParseStream<'_>) -> Result<()> {
150 if input.peek(Token![$]) {
151 Err(input.error("Solidity identifiers starting with `$` are unsupported. This is a known limitation of syn-solidity."))
152 } else {
153 Ok(())
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160 use crate::sol_path;
161
162 #[test]
163 fn ident() {
164 let id: SolIdent = syn::parse_str("a").unwrap();
165 assert_eq!(id, SolIdent::new("a"));
166 }
167
168 #[test]
169 fn keywords() {
170 let difference: &[&str] = &include!("./difference.expr");
173 for &s in difference {
174 let id: SolIdent = syn::parse_str(s).unwrap();
175 assert_eq!(id, SolIdent::new(s));
176 assert_eq!(id.to_string(), format!("r#{s}"));
177 assert_eq!(id.as_string(), s);
178 }
179
180 let intersection: &[&str] = &include!("./intersection.expr");
182 for &s in intersection {
183 let id: SolIdent = syn::parse_str(s).unwrap();
184 assert_eq!(id, SolIdent::new(s));
185 assert_eq!(id.to_string(), s);
186 assert_eq!(id.as_string(), s);
187 }
188 }
189
190 #[test]
191 fn ident_path() {
192 let path: SolPath = syn::parse_str("a.b.c").unwrap();
193 assert_eq!(path, sol_path!["a", "b", "c"]);
194 }
195
196 #[test]
197 fn ident_path_trailing() {
198 let _e = syn::parse_str::<SolPath>("a.b.").unwrap_err();
199 }
200
201 #[test]
202 fn ident_dollar() {
203 assert!(syn::parse_str::<SolIdent>("$hello")
204 .unwrap_err()
205 .to_string()
206 .contains("Solidity identifiers starting with `$` are unsupported."));
207 }
208}