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