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(backtick_escaped_name)` if `name` is a Swift reserved keyword,
676/// else `None`.
677///
678/// Use this for identifiers that appear in *emitted Swift source code* — enum
679/// cases, struct field names, function parameter labels — where the idiomatic
680/// escape for a keyword collision is `` `keyword` `` (backticks) rather than a
681/// trailing underscore. For identifiers on the Rust side of the swift-bridge
682/// boundary use [`swift_safe_name`] / [`swift_ident`] instead.
683pub fn swift_case_safe_name(name: &str) -> Option<String> {
684    if SWIFT_KEYWORDS.contains(&name) {
685        Some(format!("`{name}`"))
686    } else {
687        None
688    }
689}
690
691/// Convenience: always returns a usable Swift identifier for emitted Swift
692/// code, wrapping reserved keywords in backticks (`` `default` ``).
693///
694/// This is the Swift-idiomatic escape for keyword-collision identifiers in
695/// Swift source — distinct from [`swift_ident`], which appends a trailing
696/// underscore for use on the Rust side of the bridge.
697pub fn swift_case_ident(name: &str) -> String {
698    swift_case_safe_name(name).unwrap_or_else(|| name.to_string())
699}
700
701/// Returns `Some(escaped_name)` if `name` is a Dart reserved keyword, else `None`.
702pub fn dart_safe_name(name: &str) -> Option<String> {
703    if DART_KEYWORDS.contains(&name) {
704        Some(format!("{name}_"))
705    } else {
706        None
707    }
708}
709
710/// Convenience: always returns a usable Dart identifier.
711pub fn dart_ident(name: &str) -> String {
712    dart_safe_name(name).unwrap_or_else(|| name.to_string())
713}
714
715/// Returns `Some(escaped_name)` if `name` is a Gleam reserved keyword, else `None`.
716pub fn gleam_safe_name(name: &str) -> Option<String> {
717    if GLEAM_KEYWORDS.contains(&name) {
718        Some(format!("{name}_"))
719    } else {
720        None
721    }
722}
723
724/// Convenience: always returns a usable Gleam identifier.
725pub fn gleam_ident(name: &str) -> String {
726    gleam_safe_name(name).unwrap_or_else(|| name.to_string())
727}
728
729/// Returns `Some(escaped_name)` if `name` is a Zig reserved keyword, else `None`.
730pub fn zig_safe_name(name: &str) -> Option<String> {
731    if ZIG_KEYWORDS.contains(&name) {
732        Some(format!("{name}_"))
733    } else {
734        None
735    }
736}
737
738/// Convenience: always returns a usable Zig identifier.
739pub fn zig_ident(name: &str) -> String {
740    zig_safe_name(name).unwrap_or_else(|| name.to_string())
741}
742
743#[cfg(test)]
744mod tests {
745    use super::*;
746
747    #[test]
748    fn python_class_is_reserved() {
749        assert_eq!(python_safe_name("class"), Some("class_".to_string()));
750    }
751
752    #[test]
753    fn python_ordinary_name_is_none() {
754        assert_eq!(python_safe_name("layout_class"), None);
755    }
756
757    #[test]
758    fn python_ident_reserved() {
759        assert_eq!(python_ident("class"), "class_");
760    }
761
762    #[test]
763    fn python_ident_ordinary() {
764        assert_eq!(python_ident("layout_class"), "layout_class");
765    }
766
767    #[test]
768    fn kotlin_class_is_reserved() {
769        assert_eq!(kotlin_safe_name("class"), Some("class_".to_string()));
770        assert_eq!(kotlin_safe_name("fun"), Some("fun_".to_string()));
771        assert_eq!(kotlin_safe_name("ordinary"), None);
772        assert_eq!(kotlin_ident("typealias"), "typealias_");
773    }
774
775    #[test]
776    fn swift_init_is_reserved() {
777        assert_eq!(swift_safe_name("init"), Some("init_".to_string()));
778        assert_eq!(swift_safe_name("Self"), Some("Self_".to_string()));
779        assert_eq!(swift_safe_name("normal"), None);
780        assert_eq!(swift_ident("protocol"), "protocol_");
781    }
782
783    #[test]
784    fn swift_case_ident_backtick_escapes_reserved_keywords() {
785        // Backtick escape is the Swift-idiomatic form for keyword-collision
786        // identifiers in *emitted Swift code* (enum cases, struct fields,
787        // function parameter labels). Distinct from `swift_ident`, which
788        // emits a trailing-underscore form suitable for the Rust side of the
789        // bridge.
790        assert_eq!(swift_case_ident("default"), "`default`");
791        assert_eq!(swift_case_ident("protocol"), "`protocol`");
792        assert_eq!(swift_case_ident("init"), "`init`");
793        assert_eq!(swift_case_ident("Self"), "`Self`");
794        assert_eq!(swift_case_ident("Any"), "`Any`");
795        assert_eq!(swift_case_ident("class"), "`class`");
796        assert_eq!(swift_case_ident("inout"), "`inout`");
797        assert_eq!(swift_case_ident("rethrows"), "`rethrows`");
798        // Non-reserved identifiers pass through unchanged.
799        assert_eq!(swift_case_ident("gitHub"), "gitHub");
800        assert_eq!(swift_case_ident("normal"), "normal");
801        assert_eq!(swift_case_ident("dracula"), "dracula");
802    }
803
804    #[test]
805    fn swift_case_safe_name_returns_some_for_reserved() {
806        assert_eq!(swift_case_safe_name("default"), Some("`default`".to_string()));
807        assert_eq!(swift_case_safe_name("normal"), None);
808    }
809
810    #[test]
811    fn dart_async_is_reserved() {
812        assert_eq!(dart_safe_name("async"), Some("async_".to_string()));
813        assert_eq!(dart_safe_name("late"), Some("late_".to_string()));
814        assert_eq!(dart_safe_name("normal"), None);
815        assert_eq!(dart_ident("required"), "required_");
816    }
817
818    #[test]
819    fn gleam_pub_is_reserved() {
820        assert_eq!(gleam_safe_name("pub"), Some("pub_".to_string()));
821        assert_eq!(gleam_safe_name("opaque"), Some("opaque_".to_string()));
822        assert_eq!(gleam_safe_name("normal"), None);
823        assert_eq!(gleam_ident("type"), "type_");
824    }
825
826    #[test]
827    fn zig_comptime_is_reserved() {
828        assert_eq!(zig_safe_name("comptime"), Some("comptime_".to_string()));
829        assert_eq!(zig_safe_name("errdefer"), Some("errdefer_".to_string()));
830        assert_eq!(zig_safe_name("normal"), None);
831        assert_eq!(zig_ident("usingnamespace"), "usingnamespace_");
832    }
833
834    #[test]
835    fn python_keywords_covers_common_cases() {
836        for kw in &[
837            "def", "return", "yield", "pass", "import", "from", "type", "None", "True", "False",
838        ] {
839            assert!(
840                python_safe_name(kw).is_some(),
841                "expected {kw:?} to be a Python reserved keyword"
842            );
843        }
844    }
845}