Skip to main content

alef_core/
keywords.rs

1//! Reserved keyword lists and field-name escaping for all supported language backends.
2//!
3//! Each language backend may encounter Rust field names that are reserved keywords
4//! in the target language. This module provides a central registry of those keywords
5//! and a function to compute the safe name to use in the generated binding.
6//!
7//! # Escape strategy
8//!
9//! When a field name is reserved in the target language it is escaped by appending
10//! a trailing underscore (e.g. `class` → `class_`).  The original name is preserved
11//! in language-level attribute annotations so the user-visible API still exposes the
12//! original name (e.g. `#[pyo3(get, name = "class")]`, `#[serde(rename = "class")]`).
13
14/// Python reserved keywords and soft-keywords that cannot be used as identifiers.
15///
16/// Includes the `type` soft-keyword (Python 3.12+) and the built-in constants
17/// `None`, `True`, `False` which are also reserved in identifier position.
18pub const PYTHON_KEYWORDS: &[&str] = &[
19    "False", "None", "True", "and", "as", "assert", "async", "await", "break", "class", "continue", "def", "del",
20    "elif", "else", "except", "finally", "for", "from", "global", "if", "import", "in", "is", "lambda", "nonlocal",
21    "not", "or", "pass", "raise", "return", "try", "type", "while", "with", "yield",
22];
23
24/// Java reserved keywords (including all contextual/reserved identifiers).
25pub const JAVA_KEYWORDS: &[&str] = &[
26    "abstract",
27    "assert",
28    "boolean",
29    "break",
30    "byte",
31    "case",
32    "catch",
33    "char",
34    "class",
35    "const",
36    "continue",
37    "default",
38    "do",
39    "double",
40    "else",
41    "enum",
42    "extends",
43    "final",
44    "finally",
45    "float",
46    "for",
47    "goto",
48    "if",
49    "implements",
50    "import",
51    "instanceof",
52    "int",
53    "interface",
54    "long",
55    "native",
56    "new",
57    "package",
58    "private",
59    "protected",
60    "public",
61    "return",
62    "short",
63    "static",
64    "strictfp",
65    "super",
66    "switch",
67    "synchronized",
68    "this",
69    "throw",
70    "throws",
71    "transient",
72    "try",
73    "void",
74    "volatile",
75    "while",
76];
77
78/// C# reserved keywords.
79pub const CSHARP_KEYWORDS: &[&str] = &[
80    "abstract",
81    "as",
82    "base",
83    "bool",
84    "break",
85    "byte",
86    "case",
87    "catch",
88    "char",
89    "checked",
90    "class",
91    "const",
92    "continue",
93    "decimal",
94    "default",
95    "delegate",
96    "do",
97    "double",
98    "else",
99    "enum",
100    "event",
101    "explicit",
102    "extern",
103    "false",
104    "finally",
105    "fixed",
106    "float",
107    "for",
108    "foreach",
109    "goto",
110    "if",
111    "implicit",
112    "in",
113    "int",
114    "interface",
115    "internal",
116    "is",
117    "lock",
118    "long",
119    "namespace",
120    "new",
121    "null",
122    "object",
123    "operator",
124    "out",
125    "override",
126    "params",
127    "private",
128    "protected",
129    "public",
130    "readonly",
131    "ref",
132    "return",
133    "sbyte",
134    "sealed",
135    "short",
136    "sizeof",
137    "stackalloc",
138    "static",
139    "string",
140    "struct",
141    "switch",
142    "this",
143    "throw",
144    "true",
145    "try",
146    "typeof",
147    "uint",
148    "ulong",
149    "unchecked",
150    "unsafe",
151    "ushort",
152    "using",
153    "virtual",
154    "void",
155    "volatile",
156    "while",
157];
158
159/// PHP reserved keywords.
160pub const PHP_KEYWORDS: &[&str] = &[
161    "abstract",
162    "and",
163    "as",
164    "break",
165    "callable",
166    "case",
167    "catch",
168    "class",
169    "clone",
170    "const",
171    "continue",
172    "declare",
173    "default",
174    "die",
175    "do",
176    "echo",
177    "else",
178    "elseif",
179    "empty",
180    "enddeclare",
181    "endfor",
182    "endforeach",
183    "endif",
184    "endswitch",
185    "endwhile",
186    "eval",
187    "exit",
188    "extends",
189    "final",
190    "finally",
191    "fn",
192    "for",
193    "foreach",
194    "function",
195    "global",
196    "goto",
197    "if",
198    "implements",
199    "include",
200    "instanceof",
201    "insteadof",
202    "interface",
203    "isset",
204    "list",
205    "match",
206    "namespace",
207    "new",
208    "or",
209    "print",
210    "private",
211    "protected",
212    "public",
213    "readonly",
214    "require",
215    "return",
216    "static",
217    "switch",
218    "throw",
219    "trait",
220    "try",
221    "unset",
222    "use",
223    "var",
224    "while",
225    "xor",
226    "yield",
227];
228
229/// Ruby reserved keywords.
230pub const RUBY_KEYWORDS: &[&str] = &[
231    "__ENCODING__",
232    "__FILE__",
233    "__LINE__",
234    "BEGIN",
235    "END",
236    "alias",
237    "and",
238    "begin",
239    "break",
240    "case",
241    "class",
242    "def",
243    "defined?",
244    "do",
245    "else",
246    "elsif",
247    "end",
248    "ensure",
249    "false",
250    "for",
251    "if",
252    "in",
253    "module",
254    "next",
255    "nil",
256    "not",
257    "or",
258    "redo",
259    "rescue",
260    "retry",
261    "return",
262    "self",
263    "super",
264    "then",
265    "true",
266    "undef",
267    "unless",
268    "until",
269    "when",
270    "while",
271    "yield",
272];
273
274/// Elixir reserved keywords (including sigil names and special atoms).
275pub const ELIXIR_KEYWORDS: &[&str] = &[
276    "after", "and", "catch", "do", "else", "end", "false", "fn", "in", "nil", "not", "or", "rescue", "true", "when",
277];
278
279/// Go reserved keywords.
280pub const GO_KEYWORDS: &[&str] = &[
281    "break",
282    "case",
283    "chan",
284    "const",
285    "continue",
286    "default",
287    "defer",
288    "else",
289    "fallthrough",
290    "for",
291    "func",
292    "go",
293    "goto",
294    "if",
295    "import",
296    "interface",
297    "map",
298    "package",
299    "range",
300    "return",
301    "select",
302    "struct",
303    "switch",
304    "type",
305    "var",
306];
307
308/// JavaScript / TypeScript reserved keywords (union of both).
309pub const JS_KEYWORDS: &[&str] = &[
310    "abstract",
311    "arguments",
312    "await",
313    "boolean",
314    "break",
315    "byte",
316    "case",
317    "catch",
318    "char",
319    "class",
320    "const",
321    "continue",
322    "debugger",
323    "default",
324    "delete",
325    "do",
326    "double",
327    "else",
328    "enum",
329    "eval",
330    "export",
331    "extends",
332    "false",
333    "final",
334    "finally",
335    "float",
336    "for",
337    "function",
338    "goto",
339    "if",
340    "implements",
341    "import",
342    "in",
343    "instanceof",
344    "int",
345    "interface",
346    "let",
347    "long",
348    "native",
349    "new",
350    "null",
351    "package",
352    "private",
353    "protected",
354    "public",
355    "return",
356    "short",
357    "static",
358    "super",
359    "switch",
360    "synchronized",
361    "this",
362    "throw",
363    "throws",
364    "transient",
365    "true",
366    "try",
367    "typeof",
368    "var",
369    "void",
370    "volatile",
371    "while",
372    "with",
373    "yield",
374];
375
376/// R reserved keywords.
377pub const R_KEYWORDS: &[&str] = &[
378    "FALSE", "Inf", "NA", "NaN", "NULL", "TRUE", "break", "else", "for", "function", "if", "in", "next", "repeat",
379    "return", "while",
380];
381
382/// Return the escaped field name for use in the generated binding of the given language,
383/// or `None` if the name is not reserved and no escaping is needed.
384///
385/// The escape strategy appends `_` to the name (e.g. `class` → `class_`).
386/// Call sites should use the returned value as the Rust field name in the binding struct
387/// and add language-appropriate attribute annotations to preserve the original name in
388/// the user-facing API.
389pub fn python_safe_name(name: &str) -> Option<String> {
390    if PYTHON_KEYWORDS.contains(&name) {
391        Some(format!("{name}_"))
392    } else {
393        None
394    }
395}
396
397/// Like `python_safe_name` but always returns a `String`, using the original when no
398/// escaping is needed. Convenience wrapper for call sites that always need a `String`.
399pub fn python_ident(name: &str) -> String {
400    python_safe_name(name).unwrap_or_else(|| name.to_string())
401}
402
403#[cfg(test)]
404mod tests {
405    use super::*;
406
407    #[test]
408    fn python_class_is_reserved() {
409        assert_eq!(python_safe_name("class"), Some("class_".to_string()));
410    }
411
412    #[test]
413    fn python_ordinary_name_is_none() {
414        assert_eq!(python_safe_name("layout_class"), None);
415    }
416
417    #[test]
418    fn python_ident_reserved() {
419        assert_eq!(python_ident("class"), "class_");
420    }
421
422    #[test]
423    fn python_ident_ordinary() {
424        assert_eq!(python_ident("layout_class"), "layout_class");
425    }
426
427    #[test]
428    fn python_keywords_covers_common_cases() {
429        for kw in &[
430            "def", "return", "yield", "pass", "import", "from", "type", "None", "True", "False",
431        ] {
432            assert!(
433                python_safe_name(kw).is_some(),
434                "expected {kw:?} to be a Python reserved keyword"
435            );
436        }
437    }
438}