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/// Kotlin reserved keywords (hard + soft + modifier keywords that conflict with identifiers).
383pub const KOTLIN_KEYWORDS: &[&str] = &[
384    "as",
385    "break",
386    "class",
387    "continue",
388    "do",
389    "else",
390    "false",
391    "for",
392    "fun",
393    "if",
394    "in",
395    "interface",
396    "is",
397    "null",
398    "object",
399    "package",
400    "return",
401    "super",
402    "this",
403    "throw",
404    "true",
405    "try",
406    "typealias",
407    "typeof",
408    "val",
409    "var",
410    "when",
411    "while",
412    // Soft keywords commonly mistaken as identifiers
413    "by",
414    "init",
415    "constructor",
416    "field",
417    "value",
418    "where",
419];
420
421/// Swift reserved keywords (declarations + statements + expressions/types + patterns).
422pub const SWIFT_KEYWORDS: &[&str] = &[
423    "associatedtype",
424    "class",
425    "deinit",
426    "enum",
427    "extension",
428    "fileprivate",
429    "func",
430    "import",
431    "init",
432    "inout",
433    "internal",
434    "let",
435    "open",
436    "operator",
437    "private",
438    "protocol",
439    "public",
440    "rethrows",
441    "static",
442    "struct",
443    "subscript",
444    "typealias",
445    "var",
446    "break",
447    "case",
448    "continue",
449    "default",
450    "defer",
451    "do",
452    "else",
453    "fallthrough",
454    "for",
455    "guard",
456    "if",
457    "in",
458    "repeat",
459    "return",
460    "switch",
461    "where",
462    "while",
463    "as",
464    "Any",
465    "catch",
466    "false",
467    "is",
468    "nil",
469    "super",
470    "self",
471    "Self",
472    "throw",
473    "throws",
474    "true",
475    "try",
476    "_",
477];
478
479/// Dart reserved + built-in identifiers that cannot be used as plain identifiers.
480pub const DART_KEYWORDS: &[&str] = &[
481    "abstract",
482    "as",
483    "assert",
484    "async",
485    "await",
486    "break",
487    "case",
488    "catch",
489    "class",
490    "const",
491    "continue",
492    "covariant",
493    "default",
494    "deferred",
495    "do",
496    "dynamic",
497    "else",
498    "enum",
499    "export",
500    "extends",
501    "extension",
502    "external",
503    "factory",
504    "false",
505    "final",
506    "finally",
507    "for",
508    "Function",
509    "get",
510    "hide",
511    "if",
512    "implements",
513    "import",
514    "in",
515    "interface",
516    "is",
517    "late",
518    "library",
519    "mixin",
520    "new",
521    "null",
522    "of",
523    "on",
524    "operator",
525    "part",
526    "required",
527    "rethrow",
528    "return",
529    "set",
530    "show",
531    "static",
532    "super",
533    "switch",
534    "sync",
535    "this",
536    "throw",
537    "true",
538    "try",
539    "typedef",
540    "var",
541    "void",
542    "when",
543    "while",
544    "with",
545    "yield",
546];
547
548/// Gleam reserved keywords.
549pub const GLEAM_KEYWORDS: &[&str] = &[
550    "as",
551    "assert",
552    "auto",
553    "case",
554    "const",
555    "delegate",
556    "derive",
557    "echo",
558    "else",
559    "fn",
560    "if",
561    "implement",
562    "import",
563    "let",
564    "macro",
565    "opaque",
566    "panic",
567    "pub",
568    "test",
569    "todo",
570    "type",
571    "use",
572];
573
574/// Zig reserved keywords.
575pub const ZIG_KEYWORDS: &[&str] = &[
576    "addrspace",
577    "align",
578    "allowzero",
579    "and",
580    "anyframe",
581    "anytype",
582    "asm",
583    "async",
584    "await",
585    "break",
586    "callconv",
587    "catch",
588    "comptime",
589    "const",
590    "continue",
591    "defer",
592    "else",
593    "enum",
594    "errdefer",
595    "error",
596    "export",
597    "extern",
598    "fn",
599    "for",
600    "if",
601    "inline",
602    "linksection",
603    "noalias",
604    "noinline",
605    "nosuspend",
606    "or",
607    "orelse",
608    "packed",
609    "pub",
610    "resume",
611    "return",
612    "struct",
613    "suspend",
614    "switch",
615    "test",
616    "threadlocal",
617    "try",
618    "union",
619    "unreachable",
620    "usingnamespace",
621    "var",
622    "volatile",
623    "while",
624];
625
626/// Return the escaped field name for use in the generated binding of the given language,
627/// or `None` if the name is not reserved and no escaping is needed.
628///
629/// The escape strategy appends `_` to the name (e.g. `class` → `class_`).
630/// Call sites should use the returned value as the Rust field name in the binding struct
631/// and add language-appropriate attribute annotations to preserve the original name in
632/// the user-facing API.
633pub fn python_safe_name(name: &str) -> Option<String> {
634    if PYTHON_KEYWORDS.contains(&name) {
635        Some(format!("{name}_"))
636    } else {
637        None
638    }
639}
640
641/// Like `python_safe_name` but always returns a `String`, using the original when no
642/// escaping is needed. Convenience wrapper for call sites that always need a `String`.
643pub fn python_ident(name: &str) -> String {
644    python_safe_name(name).unwrap_or_else(|| name.to_string())
645}
646
647/// Returns `Some(escaped_name)` if `name` is a Kotlin reserved keyword, else `None`.
648pub fn kotlin_safe_name(name: &str) -> Option<String> {
649    if KOTLIN_KEYWORDS.contains(&name) {
650        Some(format!("{name}_"))
651    } else {
652        None
653    }
654}
655
656/// Convenience: always returns a usable Kotlin identifier.
657pub fn kotlin_ident(name: &str) -> String {
658    kotlin_safe_name(name).unwrap_or_else(|| name.to_string())
659}
660
661/// Returns `Some(escaped_name)` if `name` is a Swift reserved keyword, else `None`.
662pub fn swift_safe_name(name: &str) -> Option<String> {
663    if SWIFT_KEYWORDS.contains(&name) {
664        Some(format!("{name}_"))
665    } else {
666        None
667    }
668}
669
670/// Convenience: always returns a usable Swift identifier.
671pub fn swift_ident(name: &str) -> String {
672    swift_safe_name(name).unwrap_or_else(|| name.to_string())
673}
674
675/// Returns `Some(escaped_name)` if `name` is a Dart reserved keyword, else `None`.
676pub fn dart_safe_name(name: &str) -> Option<String> {
677    if DART_KEYWORDS.contains(&name) {
678        Some(format!("{name}_"))
679    } else {
680        None
681    }
682}
683
684/// Convenience: always returns a usable Dart identifier.
685pub fn dart_ident(name: &str) -> String {
686    dart_safe_name(name).unwrap_or_else(|| name.to_string())
687}
688
689/// Returns `Some(escaped_name)` if `name` is a Gleam reserved keyword, else `None`.
690pub fn gleam_safe_name(name: &str) -> Option<String> {
691    if GLEAM_KEYWORDS.contains(&name) {
692        Some(format!("{name}_"))
693    } else {
694        None
695    }
696}
697
698/// Convenience: always returns a usable Gleam identifier.
699pub fn gleam_ident(name: &str) -> String {
700    gleam_safe_name(name).unwrap_or_else(|| name.to_string())
701}
702
703/// Returns `Some(escaped_name)` if `name` is a Zig reserved keyword, else `None`.
704pub fn zig_safe_name(name: &str) -> Option<String> {
705    if ZIG_KEYWORDS.contains(&name) {
706        Some(format!("{name}_"))
707    } else {
708        None
709    }
710}
711
712/// Convenience: always returns a usable Zig identifier.
713pub fn zig_ident(name: &str) -> String {
714    zig_safe_name(name).unwrap_or_else(|| name.to_string())
715}
716
717#[cfg(test)]
718mod tests {
719    use super::*;
720
721    #[test]
722    fn python_class_is_reserved() {
723        assert_eq!(python_safe_name("class"), Some("class_".to_string()));
724    }
725
726    #[test]
727    fn python_ordinary_name_is_none() {
728        assert_eq!(python_safe_name("layout_class"), None);
729    }
730
731    #[test]
732    fn python_ident_reserved() {
733        assert_eq!(python_ident("class"), "class_");
734    }
735
736    #[test]
737    fn python_ident_ordinary() {
738        assert_eq!(python_ident("layout_class"), "layout_class");
739    }
740
741    #[test]
742    fn kotlin_class_is_reserved() {
743        assert_eq!(kotlin_safe_name("class"), Some("class_".to_string()));
744        assert_eq!(kotlin_safe_name("fun"), Some("fun_".to_string()));
745        assert_eq!(kotlin_safe_name("ordinary"), None);
746        assert_eq!(kotlin_ident("typealias"), "typealias_");
747    }
748
749    #[test]
750    fn swift_init_is_reserved() {
751        assert_eq!(swift_safe_name("init"), Some("init_".to_string()));
752        assert_eq!(swift_safe_name("Self"), Some("Self_".to_string()));
753        assert_eq!(swift_safe_name("normal"), None);
754        assert_eq!(swift_ident("protocol"), "protocol_");
755    }
756
757    #[test]
758    fn dart_async_is_reserved() {
759        assert_eq!(dart_safe_name("async"), Some("async_".to_string()));
760        assert_eq!(dart_safe_name("late"), Some("late_".to_string()));
761        assert_eq!(dart_safe_name("normal"), None);
762        assert_eq!(dart_ident("required"), "required_");
763    }
764
765    #[test]
766    fn gleam_pub_is_reserved() {
767        assert_eq!(gleam_safe_name("pub"), Some("pub_".to_string()));
768        assert_eq!(gleam_safe_name("opaque"), Some("opaque_".to_string()));
769        assert_eq!(gleam_safe_name("normal"), None);
770        assert_eq!(gleam_ident("type"), "type_");
771    }
772
773    #[test]
774    fn zig_comptime_is_reserved() {
775        assert_eq!(zig_safe_name("comptime"), Some("comptime_".to_string()));
776        assert_eq!(zig_safe_name("errdefer"), Some("errdefer_".to_string()));
777        assert_eq!(zig_safe_name("normal"), None);
778        assert_eq!(zig_ident("usingnamespace"), "usingnamespace_");
779    }
780
781    #[test]
782    fn python_keywords_covers_common_cases() {
783        for kw in &[
784            "def", "return", "yield", "pass", "import", "from", "type", "None", "True", "False",
785        ] {
786            assert!(
787                python_safe_name(kw).is_some(),
788                "expected {kw:?} to be a Python reserved keyword"
789            );
790        }
791    }
792}