brink_syntax/syntax_kind.rs
1/// All syntactic constructs in the Ink language.
2///
3/// Tokens (lexer output) and nodes (parser output) share a single flat enum
4/// so that `rowan` can store them in one `u16` discriminant. Use [`is_token`]
5/// and [`is_node`] to classify at runtime.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
7#[repr(u16)]
8#[expect(non_camel_case_types)]
9pub enum SyntaxKind {
10 // ── Trivia tokens ─────────────────────────────────────────────
11 /// Spaces and tabs (NOT newlines).
12 WHITESPACE = 0,
13 /// `\n` or `\r\n`.
14 NEWLINE,
15 /// `// ...` through end-of-line.
16 LINE_COMMENT,
17 /// `/* ... */` (may span lines).
18 BLOCK_COMMENT,
19
20 // ── Keyword tokens ────────────────────────────────────────────
21 KW_INCLUDE,
22 KW_EXTERNAL,
23 KW_VAR,
24 KW_CONST,
25 KW_LIST,
26 KW_TEMP,
27 KW_RETURN,
28 KW_REF,
29 KW_TRUE,
30 KW_FALSE,
31 KW_NOT,
32 KW_AND,
33 KW_OR,
34 KW_MOD,
35 KW_HAS,
36 KW_HASNT,
37 KW_ELSE,
38 KW_FUNCTION,
39 KW_STOPPING,
40 KW_CYCLE,
41 KW_SHUFFLE,
42 KW_ONCE,
43 KW_DONE,
44 KW_END,
45 KW_TODO,
46 /// `IMPORT` — module import statement (brink extension, M-2,
47 /// docs/modules-spec.md §2). `FROM`/`AS` stay contextual `IDENT`s.
48 KW_IMPORT,
49
50 // ── Punctuation / operator tokens ────────────────────────────
51 /// `=`
52 EQ,
53 /// `+=`
54 PLUS_EQ,
55 /// `-=`
56 MINUS_EQ,
57 /// `==`
58 EQ_EQ,
59 /// `!=`
60 BANG_EQ,
61 /// `<`
62 LT,
63 /// `>`
64 GT,
65 /// `<=`
66 LT_EQ,
67 /// `>=`
68 GT_EQ,
69 /// `&`
70 AMP,
71 /// `&&`
72 AMP_AMP,
73 /// `+`
74 PLUS,
75 /// `-`
76 MINUS,
77 /// `*`
78 STAR,
79 /// `/`
80 SLASH,
81 /// `%`
82 PERCENT,
83 /// `^`
84 CARET,
85 /// `!`
86 BANG,
87 /// `?`
88 QUESTION,
89 /// `!?`
90 BANG_QUESTION,
91 /// `$`
92 DOLLAR,
93 /// `(`
94 L_PAREN,
95 /// `)`
96 R_PAREN,
97 /// `{`
98 L_BRACE,
99 /// `}`
100 R_BRACE,
101 /// `[`
102 L_BRACKET,
103 /// `]`
104 R_BRACKET,
105 /// `|`
106 PIPE,
107 /// `,`
108 COMMA,
109 /// `.`
110 DOT,
111 /// `:`
112 COLON,
113 /// `#`
114 HASH,
115 /// `~`
116 TILDE,
117 /// `\`
118 BACKSLASH,
119
120 // ── Compound tokens ──────────────────────────────────────────
121 /// `@[` — annotation-line opener (brink extension, NS-A2: the
122 /// `@[effects(…)]` assertion final form, `docs/stdlib-spec.md` §9.2).
123 /// Lexed as one compound token so only the *adjacent* pair opens an
124 /// annotation line; a lone `@` in prose stays an `ERROR_TOKEN`
125 /// swallowed into text, exactly as before.
126 AT_L_BRACKET,
127 /// `<>`
128 GLUE,
129 /// `->`
130 DIVERT,
131 /// `<-`
132 THREAD,
133 /// `->->`
134 TUNNEL_ONWARDS,
135
136 // ── Content tokens ───────────────────────────────────────────
137 /// Integer literal (digits only; no leading sign).
138 INTEGER,
139 /// Float literal (`digits.digits`).
140 FLOAT,
141 /// `"` (opening or closing quote).
142 QUOTE,
143 /// Run of non-special characters inside a string literal.
144 STRING_TEXT,
145 /// Escape sequence inside a string (`\n`, `\t`, `\\`, `\"`).
146 STRING_ESCAPE,
147 /// Identifier: `(IDENT_START IDENT_CONTINUE*) | (DIGIT+ IDENT_START IDENT_CONTINUE*)`.
148 IDENT,
149 /// Any byte the lexer could not classify.
150 ERROR_TOKEN,
151 /// End of file (synthetic).
152 EOF,
153
154 // ── Node kinds (parser) ──────────────────────────────────────
155 SOURCE_FILE,
156 INCLUDE_STMT,
157 /// `IMPORT { a, b AS c } FROM mod` or `IMPORT mod` (M-2).
158 IMPORT_STMT,
159 /// The `{ … }` name list of a bare-form import.
160 IMPORT_LIST,
161 /// One `name` or `name AS alias` entry in an import list.
162 IMPORT_ITEM,
163 /// The module name of an import (both forms).
164 IMPORT_MODULE,
165 FILE_PATH,
166 EXTERNAL_DECL,
167 KNOT_DEF,
168 KNOT_HEADER,
169 KNOT_BODY,
170 KNOT_PARAMS,
171 KNOT_PARAM_DECL,
172 STITCH_DEF,
173 STITCH_HEADER,
174 STITCH_BODY,
175 EMPTY_LINE,
176 AUTHOR_WARNING,
177 LOGIC_LINE,
178 CONTENT_LINE,
179 TAG_LINE,
180 /// `@[name(args)]` — a brink annotation line (NS-A2, the
181 /// `@[effects(…)]` assertion surface). Superset-parsed under every
182 /// dialect; `strict-ink` rejects it in `brink-analyzer::dialect_gate`
183 /// (E051), the standard extension posture.
184 ANNOTATION_LINE,
185 STRAY_CLOSING_BRACE,
186 RETURN_STMT,
187 TEMP_DECL,
188 ASSIGNMENT,
189 MIXED_CONTENT,
190 TEXT,
191 ESCAPE,
192 GLUE_NODE,
193 CHOICE,
194 CHOICE_BULLETS,
195 LABEL,
196 CHOICE_CONDITION,
197 CHOICE_START_CONTENT,
198 CHOICE_BRACKET_CONTENT,
199 CHOICE_INNER_CONTENT,
200 GATHER,
201 GATHER_DASHES,
202 TAGS,
203 TAG,
204 INLINE_LOGIC,
205 MULTILINE_BLOCK,
206 SEQUENCE_WITH_ANNOTATION,
207 SEQUENCE_SYMBOL_ANNOTATION,
208 SEQUENCE_WORD_ANNOTATION,
209 INLINE_BRANCHES_SEQ,
210 MULTILINE_BRANCHES_SEQ,
211 MULTILINE_BRANCH_SEQ,
212 BRANCH_CONTENT,
213 CONDITIONAL_WITH_EXPR,
214 BRANCHLESS_COND_BODY,
215 ELSE_BRANCH,
216 INLINE_BRANCHES_COND,
217 MULTILINE_BRANCHES_COND,
218 MULTILINE_CONDITIONAL,
219 MULTILINE_BRANCH_COND,
220 MULTILINE_BRANCH_BODY,
221 IMPLICIT_SEQUENCE,
222 INNER_EXPRESSION,
223 PREFIX_EXPR,
224 POSTFIX_EXPR,
225 INFIX_EXPR,
226 PAREN_EXPR,
227 FUNCTION_CALL,
228 ARG_LIST,
229 DIVERT_TARGET_EXPR,
230 LIST_EXPR,
231 DIVERT_NODE,
232 SIMPLE_DIVERT,
233 DIVERT_TARGET_WITH_ARGS,
234 THREAD_START,
235 TUNNEL_ONWARDS_NODE,
236 TUNNEL_CALL_NODE,
237 IDENTIFIER,
238 PATH,
239 VAR_DECL,
240 CONST_DECL,
241 LIST_DECL,
242 LIST_DEF,
243 LIST_MEMBER,
244 LIST_MEMBER_ON,
245 LIST_MEMBER_OFF,
246 FUNCTION_PARAM_LIST,
247 INTEGER_LIT,
248 FLOAT_LIT,
249 STRING_LIT,
250 BOOLEAN_LIT,
251 ERROR,
252
253 // ── T1b superset grammar (docs/t1b-surface-spec.md) ────────────
254 // Multi-line `~ { … }` logic blocks (§2). Parse-only in T1b-1 — every
255 // node below is dialect-gated at analysis and never reaches LIR.
256 /// `{ stmt* }` — a braced statement list. Used for the top-level
257 /// `~ { … }` block body and every nested `if`/`while`/`for` body.
258 STMT_BLOCK,
259 /// `if cond { … } (else …)?`. `if`/`else if` are contextual keywords
260 /// (plain `IDENT` tokens) — see `parser::logic`.
261 IF_STMT,
262 /// The `else` arm of an `IF_STMT`: either a nested `IF_STMT` (else-if)
263 /// or a bare `STMT_BLOCK` (else).
264 ELSE_CLAUSE,
265 /// `while cond { … }`.
266 WHILE_STMT,
267 /// `for name in expr { … }`.
268 FOR_STMT,
269 /// `break`.
270 BREAK_STMT,
271 /// `continue`.
272 CONTINUE_STMT,
273 /// A bare expression statement inside a block (function/external calls).
274 EXPR_STMT,
275 /// `await <cond>` — `FlowFrame` suspension point (docs/flow-suspension-spec.md
276 /// §3). Statement/logic position only (`~ await …` logic line or inside a
277 /// `~ { … }` block). Brink extension; `await` is a contextual (soft)
278 /// keyword recognized only in this statement position, so it stays an
279 /// ordinary identifier everywhere else. Wraps the condition expression.
280 AWAIT_STMT,
281 /// `#[expr, …]` — array sigil literal (§3). Expression position only.
282 ARRAY_LITERAL,
283 /// `#{key: expr, …}` — map sigil literal (§3). Expression position only.
284 MAP_LITERAL,
285 /// One `key: expr` pair inside a `MAP_LITERAL`.
286 MAP_ENTRY,
287 /// `base[index]` — postfix indexing, chainable (§4).
288 INDEX_EXPR,
289
290 // ── TM-2 inline type annotations (docs/typed-mode-spec.md §3) ──
291 // `name: type` after params/VAR/temp declarations, `): type ===` return
292 // position. Superset grammar — always parses; dialect-gated (E051 under
293 // strict-ink) at analysis, same pattern as T1b.
294 /// `: type_expr` — one annotation, attached after an identifier (param,
295 /// `VAR`/`temp` name) or a knot header's params (return position).
296 TYPE_ANNOTATION,
297 /// A type expression: wraps exactly one of `TYPE_NAME`, `TYPE_GENERIC`,
298 /// or `TYPE_FN`.
299 TYPE_EXPR,
300 /// A bare nominal type name (`int`, `float`, `bool`, `string`, `divert`,
301 /// `void`, or an unrecognized identifier — semantic validity is an
302 /// analyzer concern, not a grammar one).
303 TYPE_NAME,
304 /// `name<type_expr, …>` — `List<L>`, `Array<T>`, `Map<K, V>`.
305 TYPE_GENERIC,
306 /// `fn(type_expr, …): type_expr` — function type. Parses in T1b/TM-2;
307 /// types as reserved until T1c.
308 TYPE_FN,
309
310 // ── TM-4b structs (docs/typed-mode-spec.md §6) ─────────────────
311 // `STRUCT Name = #{ field: type, … }` declaration, `Name#{field: expr, …}`
312 // construction literal, postfix `.field` access. Superset grammar —
313 // always parses (dialect-gated at analysis, same T1b/TM-2 pattern);
314 // `STRUCT` is a contextual (soft) keyword recognized only at top-level
315 // declaration-start position (`STRUCT` `IDENT` `=` `#` `{`), so it never
316 // reserves the word elsewhere. LIR lowering rejects every construct
317 // below — codegen lands with TM-4c (#666).
318 /// `STRUCT Name = #{ field: type, … }`. Top-level only.
319 STRUCT_DECL,
320 /// One `field: type` pair inside a `STRUCT_DECL`'s body.
321 STRUCT_FIELD_DECL,
322 /// `Name#{field: expr, …}` — struct construction literal. Expression
323 /// position only, like `ARRAY_LITERAL`/`MAP_LITERAL`.
324 STRUCT_LITERAL,
325 /// One `field: expr` pair inside a `STRUCT_LITERAL`.
326 STRUCT_FIELD_INIT,
327 /// `base.field` — postfix field access. Only used where the existing
328 /// dotted-`PATH` grammar doesn't already cover the shape (e.g. after a
329 /// `STRUCT_LITERAL`, an `INDEX_EXPR`, or a parenthesized expression); a
330 /// bare `ident.ident` chain still parses as one `PATH` node and the
331 /// static-path-vs-field-access ambiguity is resolved by
332 /// `brink-analyzer`'s resolution fallback (typed-mode-spec §6), not here.
333 FIELD_ACCESS_EXPR,
334
335 // ── T1c function values (docs/t1c-spec.md §2) ──────────────────
336 /// `#fn(target, args…)` — function-value creation (partial application
337 /// over a named function). Joins the `#[…]`/`#{…}`/`Name#{…}` sigil
338 /// family: expression position only — in prose position `#` still opens
339 /// a tag, unchanged. Superset grammar — always parses; dialect-gated
340 /// (E051 under strict-ink) at analysis, same pattern as T1b/TM-4b.
341 FN_LITERAL,
342
343 // ── T1e path projections (docs/t1e-spec.md §2) ──────────────────
344 // `ref lvalue-path` — path-projection creation in ref-argument position
345 // (`heal(ref npc.hp, 5)`, `#fn(heal, ref party[leader].hp)`,
346 // `bind(f, ref inventory[idx])`). Superset grammar — always parses in
347 // expression position (mirrors `FN_LITERAL`'s dialect-gate pattern);
348 // whether the position is legal (ref-argument only, never standalone)
349 // and whether the root is a durable cell is `brink-analyzer`'s job.
350 /// `ref` followed by a single lvalue-shaped operand — a plain path, a
351 /// dotted field chain, `[…]` indexing, or a mix of the two.
352 REF_EXPR,
353
354 // ── Computed-callee call attempt (docs/t1c-spec.md §3/§10, issue #869) ──
355 // `expr(args…)` where `expr` isn't a bare identifier immediately
356 // followed by `(` (that shape is `FUNCTION_CALL`, consumed at `atom()`).
357 // Direct-call syntax is RULED (t1c-spec §3) to a bare variable/temp/param
358 // callee only; "method-call syntax" (dispatch through an indexed/field/
359 // call-result callee via bare-call sugar) is explicitly out of T1c
360 // (§10). Superset grammar — always parses, so the author's `(args…)`
361 // is captured instead of silently reinterpreted as trailing prose text
362 // (the pre-existing behavior, and the exact silent-no-op class #869
363 // reports); `brink-ir`'s HIR lowering always rejects it (E104), pointing
364 // at the ratified `call(f, args…)` form.
365 /// A postfix call applied to a callee that isn't a bare name — always
366 /// rejected at HIR lowering (E104).
367 CALL_EXPR,
368
369 // ── NS-A5 range literals (docs/stdlib-spec.md §7, F7) ───────────
370 // `a..b` (exclusive) / `a..=b` (inclusive) — integer range values,
371 // joining the closed iterable set (`for i in 0..n`) and feeding the
372 // inhabited-range refinement (`int(1..=6)`, `non_empty(a..b)`).
373 // Superset grammar — always parses; dialect-gated (E051 under
374 // strict-ink) at analysis, same pattern as T1b/T1c. The operator is
375 // two adjacent `DOT` tokens (plus an adjacent `EQ` for `..=`) —
376 // detected in the Pratt loop like `||`/`++`, no new lexer token.
377 /// `start .. end` / `start ..= end` — a range literal expression.
378 RANGE_EXPR,
379
380 // Not a real kind — used only for `rowan::Language::kind_to_raw` bounds.
381 #[doc(hidden)]
382 __LAST,
383}
384
385impl SyntaxKind {
386 /// Returns `true` for tokens produced by the lexer (leaf nodes in the CST).
387 #[must_use]
388 pub fn is_token(self) -> bool {
389 matches!(
390 self,
391 Self::WHITESPACE
392 | Self::NEWLINE
393 | Self::LINE_COMMENT
394 | Self::BLOCK_COMMENT
395 | Self::KW_INCLUDE
396 | Self::KW_EXTERNAL
397 | Self::KW_VAR
398 | Self::KW_CONST
399 | Self::KW_LIST
400 | Self::KW_TEMP
401 | Self::KW_RETURN
402 | Self::KW_REF
403 | Self::KW_TRUE
404 | Self::KW_FALSE
405 | Self::KW_NOT
406 | Self::KW_AND
407 | Self::KW_OR
408 | Self::KW_MOD
409 | Self::KW_HAS
410 | Self::KW_HASNT
411 | Self::KW_ELSE
412 | Self::KW_FUNCTION
413 | Self::KW_STOPPING
414 | Self::KW_CYCLE
415 | Self::KW_SHUFFLE
416 | Self::KW_ONCE
417 | Self::KW_DONE
418 | Self::KW_END
419 | Self::KW_TODO
420 | Self::KW_IMPORT
421 | Self::EQ
422 | Self::PLUS_EQ
423 | Self::MINUS_EQ
424 | Self::EQ_EQ
425 | Self::BANG_EQ
426 | Self::LT
427 | Self::GT
428 | Self::LT_EQ
429 | Self::GT_EQ
430 | Self::AMP
431 | Self::AMP_AMP
432 | Self::PLUS
433 | Self::MINUS
434 | Self::STAR
435 | Self::SLASH
436 | Self::PERCENT
437 | Self::CARET
438 | Self::BANG
439 | Self::QUESTION
440 | Self::BANG_QUESTION
441 | Self::DOLLAR
442 | Self::L_PAREN
443 | Self::R_PAREN
444 | Self::L_BRACE
445 | Self::R_BRACE
446 | Self::L_BRACKET
447 | Self::R_BRACKET
448 | Self::PIPE
449 | Self::COMMA
450 | Self::DOT
451 | Self::COLON
452 | Self::HASH
453 | Self::TILDE
454 | Self::BACKSLASH
455 | Self::AT_L_BRACKET
456 | Self::GLUE
457 | Self::DIVERT
458 | Self::THREAD
459 | Self::TUNNEL_ONWARDS
460 | Self::INTEGER
461 | Self::FLOAT
462 | Self::QUOTE
463 | Self::STRING_TEXT
464 | Self::STRING_ESCAPE
465 | Self::IDENT
466 | Self::ERROR_TOKEN
467 | Self::EOF
468 )
469 }
470
471 /// Returns `true` for composite nodes built by the parser.
472 #[must_use]
473 pub fn is_node(self) -> bool {
474 !self.is_token() && self != Self::__LAST
475 }
476
477 /// Returns `true` for trivia — tokens the parser may skip over.
478 /// `NEWLINE` is **not** trivia; it terminates lines and delimits blocks.
479 #[must_use]
480 pub fn is_trivia(self) -> bool {
481 matches!(
482 self,
483 Self::WHITESPACE | Self::LINE_COMMENT | Self::BLOCK_COMMENT
484 )
485 }
486
487 /// Returns `true` for keyword tokens.
488 #[must_use]
489 pub fn is_keyword(self) -> bool {
490 matches!(
491 self,
492 Self::KW_INCLUDE
493 | Self::KW_EXTERNAL
494 | Self::KW_VAR
495 | Self::KW_CONST
496 | Self::KW_LIST
497 | Self::KW_TEMP
498 | Self::KW_RETURN
499 | Self::KW_REF
500 | Self::KW_TRUE
501 | Self::KW_FALSE
502 | Self::KW_NOT
503 | Self::KW_AND
504 | Self::KW_OR
505 | Self::KW_MOD
506 | Self::KW_HAS
507 | Self::KW_HASNT
508 | Self::KW_ELSE
509 | Self::KW_FUNCTION
510 | Self::KW_STOPPING
511 | Self::KW_CYCLE
512 | Self::KW_SHUFFLE
513 | Self::KW_ONCE
514 | Self::KW_DONE
515 | Self::KW_END
516 | Self::KW_TODO
517 | Self::KW_IMPORT
518 )
519 }
520}
521
522/// Rowan language tag for Ink.
523#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
524pub enum InkLanguage {}
525
526impl rowan::Language for InkLanguage {
527 type Kind = SyntaxKind;
528
529 fn kind_from_raw(raw: rowan::SyntaxKind) -> SyntaxKind {
530 assert!(raw.0 < SyntaxKind::__LAST as u16);
531 // SAFETY: `SyntaxKind` is `#[repr(u16)]` with contiguous discriminants,
532 // and we just checked bounds.
533 #[expect(unsafe_code, reason = "repr(u16) transmute with bounds check")]
534 unsafe {
535 std::mem::transmute::<u16, SyntaxKind>(raw.0)
536 }
537 }
538
539 fn kind_to_raw(kind: SyntaxKind) -> rowan::SyntaxKind {
540 rowan::SyntaxKind(kind as u16)
541 }
542}
543
544/// A rowan `SyntaxNode` parameterized by [`InkLanguage`].
545pub type SyntaxNode = rowan::SyntaxNode<InkLanguage>;
546/// A rowan `SyntaxToken` parameterized by [`InkLanguage`].
547pub type SyntaxToken = rowan::SyntaxToken<InkLanguage>;
548/// A rowan `SyntaxElement` parameterized by [`InkLanguage`].
549pub type SyntaxElement = rowan::SyntaxElement<InkLanguage>;
550
551#[cfg(test)]
552mod tests {
553 use super::*;
554 use rowan::Language;
555
556 #[test]
557 fn roundtrip_through_rowan() {
558 // Every SyntaxKind (except __LAST) should survive raw → kind → raw.
559 let mut i = 0u16;
560 loop {
561 if i == SyntaxKind::__LAST as u16 {
562 break;
563 }
564 let raw = rowan::SyntaxKind(i);
565 let kind = InkLanguage::kind_from_raw(raw);
566 let back = InkLanguage::kind_to_raw(kind);
567 assert_eq!(raw, back, "roundtrip failed for discriminant {i}");
568 i += 1;
569 }
570 }
571
572 #[test]
573 fn token_node_partition() {
574 // Every kind (except __LAST) is either a token or a node, never both.
575 let mut i = 0u16;
576 loop {
577 if i == SyntaxKind::__LAST as u16 {
578 break;
579 }
580 let kind = InkLanguage::kind_from_raw(rowan::SyntaxKind(i));
581 assert!(
582 kind.is_token() ^ kind.is_node(),
583 "{kind:?} is neither token nor node (or both)"
584 );
585 i += 1;
586 }
587 }
588
589 #[test]
590 fn trivia_is_subset_of_tokens() {
591 let mut i = 0u16;
592 loop {
593 if i == SyntaxKind::__LAST as u16 {
594 break;
595 }
596 let kind = InkLanguage::kind_from_raw(rowan::SyntaxKind(i));
597 if kind.is_trivia() {
598 assert!(kind.is_token(), "{kind:?} is trivia but not a token");
599 }
600 i += 1;
601 }
602 }
603
604 #[test]
605 fn newline_is_not_trivia() {
606 assert!(!SyntaxKind::NEWLINE.is_trivia());
607 assert!(SyntaxKind::NEWLINE.is_token());
608 }
609
610 #[test]
611 fn keywords_are_tokens() {
612 let keywords = [
613 SyntaxKind::KW_INCLUDE,
614 SyntaxKind::KW_EXTERNAL,
615 SyntaxKind::KW_VAR,
616 SyntaxKind::KW_CONST,
617 SyntaxKind::KW_LIST,
618 SyntaxKind::KW_TEMP,
619 SyntaxKind::KW_RETURN,
620 SyntaxKind::KW_REF,
621 SyntaxKind::KW_TRUE,
622 SyntaxKind::KW_FALSE,
623 SyntaxKind::KW_NOT,
624 SyntaxKind::KW_AND,
625 SyntaxKind::KW_OR,
626 SyntaxKind::KW_MOD,
627 SyntaxKind::KW_HAS,
628 SyntaxKind::KW_HASNT,
629 SyntaxKind::KW_ELSE,
630 SyntaxKind::KW_FUNCTION,
631 SyntaxKind::KW_STOPPING,
632 SyntaxKind::KW_CYCLE,
633 SyntaxKind::KW_SHUFFLE,
634 SyntaxKind::KW_ONCE,
635 SyntaxKind::KW_DONE,
636 SyntaxKind::KW_END,
637 SyntaxKind::KW_TODO,
638 ];
639 for kw in keywords {
640 assert!(kw.is_token(), "{kw:?} should be a token");
641 assert!(kw.is_keyword(), "{kw:?} should be a keyword");
642 }
643 }
644
645 #[test]
646 fn non_keywords_are_not_keywords() {
647 assert!(!SyntaxKind::IDENT.is_keyword());
648 assert!(!SyntaxKind::PLUS.is_keyword());
649 assert!(!SyntaxKind::SOURCE_FILE.is_keyword());
650 }
651}