syn_solidity/ident/
mod.rs1use crate::Spanned;
2use proc_macro2::{Ident, Span};
3use quote::ToTokens;
4use std::fmt;
5use syn::{
6 Result, Token,
7 ext::IdentExt,
8 parse::{Parse, ParseStream},
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 if matches!(s, "self") {
114 s = "this";
115 }
116 if matches!(s, "Self") {
117 s = "This";
118 }
119 }
120
121 if new_raw { Self(Ident::new_raw(s, span)) } else { Self(Ident::new(s, span)) }
122 }
123
124 pub fn as_string(&self) -> String {
126 let mut s = self.0.to_string();
127 if s.starts_with("r#") {
128 s = s[2..].to_string();
129 }
130 s
131 }
132
133 pub fn parse_any(input: ParseStream<'_>) -> Result<Self> {
135 check_dollar(input)?;
136
137 input.call(Ident::parse_any).map(Into::into)
138 }
139
140 pub fn peek_any(input: ParseStream<'_>) -> bool {
142 input.peek(Ident::peek_any)
143 }
144
145 pub fn parse_opt(input: ParseStream<'_>) -> Result<Option<Self>> {
146 if Self::peek_any(input) { input.parse().map(Some) } else { Ok(None) }
147 }
148}
149
150fn check_dollar(input: ParseStream<'_>) -> Result<()> {
151 if input.peek(Token![$]) {
152 Err(input.error("Solidity identifiers starting with `$` are unsupported. This is a known limitation of syn-solidity."))
153 } else {
154 Ok(())
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use crate::sol_path;
162
163 #[test]
164 fn ident() {
165 let id: SolIdent = syn::parse_str("a").unwrap();
166 assert_eq!(id, SolIdent::new("a"));
167 }
168
169 #[test]
170 fn keywords() {
171 let difference: &[&str] = &include!("./difference.expr");
174 for &s in difference {
175 let id: SolIdent = syn::parse_str(s).unwrap();
176 assert_eq!(id, SolIdent::new(s));
177 assert_eq!(id.to_string(), format!("r#{s}"));
178 assert_eq!(id.as_string(), s);
179 }
180
181 let intersection: &[&str] = &include!("./intersection.expr");
183 for &s in intersection {
184 let id: SolIdent = syn::parse_str(s).unwrap();
185 assert_eq!(id, SolIdent::new(s));
186 assert_eq!(id.to_string(), s);
187 assert_eq!(id.as_string(), s);
188 }
189 }
190
191 #[test]
193 fn self_keywords() {
194 let id: SolIdent = syn::parse_str("self").unwrap();
195 assert_eq!(id, SolIdent::new("this"));
196 assert_eq!(id.to_string(), "this");
197 assert_eq!(id.as_string(), "this");
198
199 let id: SolIdent = syn::parse_str("Self").unwrap();
200 assert_eq!(id, SolIdent::new("This"));
201 assert_eq!(id.to_string(), "This");
202 assert_eq!(id.as_string(), "This");
203 }
204
205 #[test]
206 fn ident_path() {
207 let path: SolPath = syn::parse_str("a.b.c").unwrap();
208 assert_eq!(path, sol_path!["a", "b", "c"]);
209 }
210
211 #[test]
212 fn ident_path_trailing() {
213 let _e = syn::parse_str::<SolPath>("a.b.").unwrap_err();
214 }
215
216 #[test]
217 fn ident_dollar() {
218 assert!(
219 syn::parse_str::<SolIdent>("$hello")
220 .unwrap_err()
221 .to_string()
222 .contains("Solidity identifiers starting with `$` are unsupported.")
223 );
224 }
225}