loess_rust/lexical_structure/
identifiers.rs

1use loess::{Error, ErrorPriority, Errors, Input, IntoTokens, PeekFrom, PopFrom, SimpleSpanned};
2use proc_macro2::{Ident, Span, TokenStream, TokenTree};
3
4/// [IDENTIFIER](https://doc.rust-lang.org/stable/reference/identifiers.html#r-ident.syntax)
5#[derive(Clone)]
6pub struct Identifier(pub Ident);
7
8impl PeekFrom for Identifier {
9	fn peek_from(input: &Input) -> bool {
10		matches!(
11			input.front(),
12			Some(TokenTree::Ident(ident))
13				if !(["r#crate", "r#self", "r#super", "r#Self"]
14					.into_iter()
15					.any(|s| ident == s)
16					|| is_strict_keyword(&ident)
17					|| is_reserved_keyword(&ident)),
18		)
19	}
20}
21
22/// See <https://doc.rust-lang.org/stable/reference/identifiers.html?highlight=IDENTIFIER#identifiers> as of 2025-04-13.
23impl PopFrom for Identifier {
24	fn pop_from(input: &mut Input, errors: &mut Errors) -> Result<Self, ()> {
25		let ident = Ident::peek_pop_from(input, errors)?;
26
27		match ident {
28			Some(ident)
29				if !(["r#crate", "r#self", "r#super", "r#Self"]
30					.into_iter()
31					.any(|s| ident == s)
32					|| is_strict_keyword(&ident)
33					|| is_reserved_keyword(&ident)) =>
34			{
35				Ok(Self(ident))
36			}
37			ident => Err(if let Some(ident) = ident {
38				errors.push(Error::new(
39					ErrorPriority::GRAMMAR,
40					if ident.to_string().starts_with("r#") {
41						format!(
42							"Expected Identifier. (`{}` cannot be a raw identifier.)",
43							&ident.to_string()[2..]
44						)
45					} else {
46						format!("Expected Identifier. (`{ident}` is a keyword.)")
47					},
48					[ident.span()],
49				));
50
51				input.push_front(TokenTree::Ident(ident));
52			} else {
53				errors.push(Error::new(
54					ErrorPriority::GRAMMAR,
55					"Expected Identifier.",
56					[input.front_span()],
57				));
58			}),
59		}
60	}
61}
62
63impl IntoTokens for Identifier {
64	fn into_tokens(self, root: &TokenStream, tokens: &mut impl Extend<TokenTree>) {
65		self.0.into_tokens(root, tokens)
66	}
67}
68
69impl SimpleSpanned for Identifier {
70	fn span(&self) -> Span {
71		self.0.span()
72	}
73
74	fn set_span(&mut self, span: Span) {
75		self.0.set_span(span)
76	}
77}
78
79/// See <https://doc.rust-lang.org/stable/reference/keywords.html#strict-keywords> as of 2025-04-13.
80pub fn is_strict_keyword(ident: &Ident) -> bool {
81	[
82		"as", "break", "const", "continue", "crate", "else", "enum", "extern", "false", "fn",
83		"for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref",
84		"return", "self", "Self", "static", "struct", "super", "trait", "true", "type", "unsafe",
85		"use", "where", "while", //
86		// 2018 edition
87		"async", "await", "dyn",
88	]
89	.iter()
90	.any(|s| ident == s)
91}
92
93/// See <https://doc.rust-lang.org/stable/reference/keywords.html#reserved-keywords> as of 2025-04-13.
94pub fn is_reserved_keyword(ident: &Ident) -> bool {
95	[
96		"abstract", "become", "box", "do", "final", "macro", "override", "priv", "typeof",
97		"unsized", "virtual", "yield", //
98		// 2018+
99		"try", //
100		// 2024+
101		"gen",
102	]
103	.iter()
104	.any(|s| ident == s)
105}