Skip to main content

perl_ast/
classification.rs

1//! Static classification metadata for [`NodeKind`] variants.
2//!
3//! This module answers the question **"what kind of node is this?"** at the
4//! variant level, without looking at the node's position in the tree or at
5//! the source text it covers. Consumers that need positional facts (is this
6//! inside a heredoc body? inside POD? after `__DATA__`?) must layer those
7//! checks on top using their own positional knowledge.
8//!
9//! # Design contract
10//!
11//! - **Variant-level only.** Every flag is determined by the `NodeKind`
12//!   discriminant alone. The same `NodeKind::Heredoc { .. }` always returns
13//!   the same flags regardless of where it appears in the AST.
14//!
15//! - **No wildcard arms.** Both `category()` and `flags()` use exhaustive
16//!   `match self { ... }` expressions with no `_ =>` catch-all arm. Adding a
17//!   new `NodeKind` variant is a compile error until the match is extended.
18//!   This is the **drift guard**: consumers that pattern-match `NodeKind` can
19//!   rely on classification staying current.
20//!
21//! - **`safe_for_breakpoint` semantics.** This flag answers the question
22//!   *"can a breakpoint ever be set on this kind of node?"* at the variant
23//!   level. A `true` value must be AND-ed by the DAP/LSP consumer with
24//!   positional checks (e.g., is the cursor inside a heredoc body? Is the
25//!   line inside a POD block?). The flag never means "always stop here" —
26//!   it means "this kind of node is a valid candidate for breakpoint
27//!   placement, pending positional validation."
28//!
29//!   **Compile-time constructs** (`Use`, `No`) are `false` because they run
30//!   inside `BEGIN` blocks and are not stoppable in a runtime debugger session.
31//!   Verified via Perl 5.40.1 debugger probe.
32//!
33//!   **Instance-dependent flags** (variant flag is a conservative prefilter;
34//!   the DAP consumer must verify the AST structure or metadata field):
35//!   - `Eval.introduces_scope`: variant flag is `true`; consumer must check
36//!     whether the `block` child is a `NodeKind::Block` — `eval STRING`/
37//!     `eval EXPR` introduce no static lexical scope.
38//!   - `Package.introduces_scope` / `Package.safe_for_breakpoint`: variant
39//!     flags are both `true`; consumer must check `block.is_some()` —
40//!     `package Foo;` (block absent) creates no lexical scope.
41//!   - `PhaseBlock.safe_for_breakpoint`: variant flag is `true`; DAP consumer
42//!     must check the `phase` field — `BEGIN`/`CHECK`/`UNITCHECK` are
43//!     compile-time phases (not stoppable in a runtime session); `END` and
44//!     `INIT` may be stoppable depending on attach timing.
45//!
46//!   See `docs/reference/PARSER_CONTRACTS.md` §Breakpoint for the full contract
47//!   table with static and instance-dependent rows and consumer guidance.
48//!
49//! - **Invariant.** `recovery_artifact == true` implies
50//!   `safe_for_breakpoint == false`. This is enforced by the table and
51//!   verified by [`NodeKindFlags::validate`].
52//!
53//! # Usage
54//!
55//! ```rust
56//! use perl_ast::NodeKind;
57//! use perl_ast::classification::NodeKindCategory;
58//!
59//! let kind = NodeKind::FunctionCall { name: "print".to_string(), args: vec![] };
60//! assert_eq!(kind.category(), NodeKindCategory::Expression);
61//! assert!(kind.is_executable());
62//! assert!(kind.safe_for_breakpoint());
63//! ```
64
65use crate::ast::NodeKind;
66
67/// High-level semantic category for a [`NodeKind`] variant.
68///
69/// Each variant belongs to exactly one category. The category reflects the
70/// dominant role of the construct in the Perl language, not its syntactic
71/// surface form.
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
73pub enum NodeKindCategory {
74    /// The root program node.
75    Program,
76    /// A statement (flow control, returns, loop control).
77    Statement,
78    /// An expression (has a value; may have side effects).
79    Expression,
80    /// A declaration (introduces a name into some scope or symbol table).
81    Declaration,
82    /// A scoping construct that directly wraps a block (Block only).
83    Scope,
84    /// A pure literal value with no side effects.
85    Literal,
86    /// An operator or punctuation node (Ellipsis).
87    Operator,
88    /// A comment or documentation node (reserved for future use).
89    CommentDoc,
90    /// A synthetic recovery node produced by error recovery.
91    Recovery,
92    /// An uncategorized or unknown node (reserved for future use).
93    Unknown,
94}
95
96/// Boolean flags describing the semantic role of a [`NodeKind`] variant.
97///
98/// All flags are determined statically from the variant discriminant alone.
99/// See the module-level documentation for the precise semantics of each flag
100/// and the invariants between them.
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102pub struct NodeKindFlags {
103    /// Code that can/will execute (methods, ops, side effects).
104    ///
105    /// Opposite of pure literals and declaration-only nodes.
106    /// Conservative default: `false`. If wrong, breakpoints fail silently.
107    pub executable: bool,
108
109    /// Creates a new lexical scope (my vars, blocks, subs, class, package).
110    ///
111    /// Triggers scope analyzer stack push. Conservative default: `false`.
112    /// If wrong, scope-bleeding bugs may result.
113    pub introduces_scope: bool,
114
115    /// Binds a name (`my $x`, `sub foo`, `use Module`, `package Foo`).
116    ///
117    /// Triggers symbol table insertion. Conservative default: `false`.
118    /// Missing declarations lose symbols but do not crash.
119    pub declares_symbol: bool,
120
121    /// References a name (`$x`, `foo()`, `@arr`).
122    ///
123    /// Triggers use-def chain lookups. Conservative default: `false`.
124    /// Missing refs lose cross-references but are safe.
125    pub references_symbol: bool,
126
127    /// Has `Vec` or `Box` children worth walking during AST traversal.
128    ///
129    /// Speeds up traversal filters that can skip leaf nodes. Conservative
130    /// default: `false`. If wrong, traversal misses nodes but does not crash.
131    pub contains_children: bool,
132
133    /// Synthetic node produced by error recovery (`Error`, `Missing*`, `UnknownRest`).
134    ///
135    /// **Critical**: recovery nodes must NEVER be `safe_for_breakpoint`.
136    /// Conservative default: `false`.
137    pub recovery_artifact: bool,
138
139    /// This kind of node can host a debugger breakpoint.
140    ///
141    /// **Static, variant-level.** Must be AND-ed with positional checks by
142    /// the consumer. `true` means "this kind is a valid breakpoint candidate";
143    /// it does not mean "always stop here".
144    ///
145    /// **Invariant**: if `recovery_artifact` is `true`, this must be `false`.
146    /// Conservative default: `false`. Only `true` if proven safe.
147    pub safe_for_breakpoint: bool,
148}
149
150impl NodeKindFlags {
151    /// Verify the invariant: `recovery_artifact && safe_for_breakpoint` is forbidden.
152    ///
153    /// Returns `Ok(())` when the flags are internally consistent, or an error
154    /// string describing the violation.
155    pub fn validate(&self) -> Result<(), String> {
156        if self.recovery_artifact && self.safe_for_breakpoint {
157            Err("recovery_artifact and safe_for_breakpoint must not both be true".into())
158        } else {
159            Ok(())
160        }
161    }
162}
163
164// ────────────────────────────────────────────────────────────────────────────
165// Helper macro for concise flag construction
166// ────────────────────────────────────────────────────────────────────────────
167
168macro_rules! flags {
169    (
170        exec=$exec:expr,
171        scope=$scope:expr,
172        decl=$decl:expr,
173        refs=$refs:expr,
174        children=$children:expr,
175        recovery=$recovery:expr,
176        bp=$bp:expr
177    ) => {
178        NodeKindFlags {
179            executable: $exec,
180            introduces_scope: $scope,
181            declares_symbol: $decl,
182            references_symbol: $refs,
183            contains_children: $children,
184            recovery_artifact: $recovery,
185            safe_for_breakpoint: $bp,
186        }
187    };
188}
189
190impl NodeKind {
191    /// Return the high-level [`NodeKindCategory`] for this variant.
192    ///
193    /// The match is exhaustive with no wildcard arm — adding a new
194    /// `NodeKind` variant is a compile error until this function is updated.
195    pub fn category(&self) -> NodeKindCategory {
196        match self {
197            NodeKind::Program { .. } => NodeKindCategory::Program,
198
199            NodeKind::ExpressionStatement { .. }
200            | NodeKind::Defer { .. }
201            | NodeKind::Try { .. }
202            | NodeKind::If { .. }
203            | NodeKind::LabeledStatement { .. }
204            | NodeKind::While { .. }
205            | NodeKind::For { .. }
206            | NodeKind::Foreach { .. }
207            | NodeKind::Given { .. }
208            | NodeKind::When { .. }
209            | NodeKind::Default { .. }
210            | NodeKind::StatementModifier { .. }
211            | NodeKind::Return { .. }
212            | NodeKind::LoopControl { .. }
213            | NodeKind::Goto { .. } => NodeKindCategory::Statement,
214
215            NodeKind::Variable { .. }
216            | NodeKind::VariableWithAttributes { .. }
217            | NodeKind::Assignment { .. }
218            | NodeKind::Binary { .. }
219            | NodeKind::Ternary { .. }
220            | NodeKind::Unary { .. }
221            | NodeKind::Diamond
222            | NodeKind::Readline { .. }
223            | NodeKind::Glob { .. }
224            | NodeKind::Typeglob { .. }
225            | NodeKind::ArrayLiteral { .. }
226            | NodeKind::HashLiteral { .. }
227            | NodeKind::Eval { .. }
228            | NodeKind::Do { .. }
229            | NodeKind::Tie { .. }
230            | NodeKind::Untie { .. }
231            | NodeKind::MethodCall { .. }
232            | NodeKind::FunctionCall { .. }
233            | NodeKind::IndirectCall { .. }
234            | NodeKind::Match { .. }
235            | NodeKind::Substitution { .. }
236            | NodeKind::Transliteration { .. }
237            | NodeKind::Identifier { .. } => NodeKindCategory::Expression,
238
239            NodeKind::VariableDeclaration { .. }
240            | NodeKind::VariableListDeclaration { .. }
241            | NodeKind::NestedVariableList { .. }
242            | NodeKind::Subroutine { .. }
243            | NodeKind::Prototype { .. }
244            | NodeKind::Signature { .. }
245            | NodeKind::MandatoryParameter { .. }
246            | NodeKind::OptionalParameter { .. }
247            | NodeKind::SlurpyParameter { .. }
248            | NodeKind::NamedParameter { .. }
249            | NodeKind::Method { .. }
250            | NodeKind::Package { .. }
251            | NodeKind::Use { .. }
252            | NodeKind::No { .. }
253            | NodeKind::PhaseBlock { .. }
254            | NodeKind::DataSection { .. }
255            | NodeKind::Class { .. }
256            | NodeKind::Format { .. } => NodeKindCategory::Declaration,
257
258            NodeKind::Block { .. } => NodeKindCategory::Scope,
259
260            NodeKind::Number { .. }
261            | NodeKind::String { .. }
262            | NodeKind::Heredoc { .. }
263            | NodeKind::Regex { .. }
264            | NodeKind::Undef => NodeKindCategory::Literal,
265
266            NodeKind::Ellipsis => NodeKindCategory::Operator,
267
268            NodeKind::Error { .. }
269            | NodeKind::MissingExpression
270            | NodeKind::MissingStatement
271            | NodeKind::MissingIdentifier
272            | NodeKind::MissingBlock
273            | NodeKind::UnknownRest => NodeKindCategory::Recovery,
274        }
275    }
276
277    /// Return the full [`NodeKindFlags`] for this variant.
278    ///
279    /// The match is exhaustive with no wildcard arm — adding a new
280    /// `NodeKind` variant is a compile error until this function is updated.
281    ///
282    /// See [`NodeKindFlags`] for the precise semantics of each flag.
283    ///
284    /// # Invariant
285    ///
286    /// The returned flags always satisfy `flags.validate().is_ok()`.
287    pub fn flags(&self) -> NodeKindFlags {
288        //  Columns:       exec   scope  decl   refs   children  recovery  bp
289        match self {
290            NodeKind::Program { .. } => flags!(
291                exec = false,
292                scope = true,
293                decl = false,
294                refs = false,
295                children = true,
296                recovery = false,
297                bp = false
298            ),
299            NodeKind::ExpressionStatement { .. } => flags!(
300                exec = true,
301                scope = false,
302                decl = false,
303                refs = false,
304                children = true,
305                recovery = false,
306                bp = true
307            ),
308            NodeKind::VariableDeclaration { .. } => flags!(
309                exec = true,
310                scope = true,
311                decl = true,
312                refs = false,
313                children = true,
314                recovery = false,
315                bp = true
316            ),
317            NodeKind::VariableListDeclaration { .. } => flags!(
318                exec = true,
319                scope = true,
320                decl = true,
321                refs = false,
322                children = true,
323                recovery = false,
324                bp = true
325            ),
326            NodeKind::NestedVariableList { .. } => flags!(
327                exec = false,
328                scope = false,
329                decl = true,
330                refs = false,
331                children = true,
332                recovery = false,
333                bp = false
334            ),
335            NodeKind::Variable { .. } => flags!(
336                exec = false,
337                scope = false,
338                decl = false,
339                refs = true,
340                children = false,
341                recovery = false,
342                bp = false
343            ),
344            NodeKind::VariableWithAttributes { .. } => flags!(
345                exec = false,
346                scope = false,
347                decl = false,
348                refs = true,
349                children = true,
350                recovery = false,
351                bp = false
352            ),
353            NodeKind::Assignment { .. } => flags!(
354                exec = true,
355                scope = false,
356                decl = false,
357                refs = true,
358                children = true,
359                recovery = false,
360                bp = true
361            ),
362            NodeKind::Binary { .. } => flags!(
363                exec = true,
364                scope = false,
365                decl = false,
366                refs = true,
367                children = true,
368                recovery = false,
369                bp = true
370            ),
371            NodeKind::Ternary { .. } => flags!(
372                exec = true,
373                scope = false,
374                decl = false,
375                refs = true,
376                children = true,
377                recovery = false,
378                bp = true
379            ),
380            NodeKind::Unary { .. } => flags!(
381                exec = true,
382                scope = false,
383                decl = false,
384                refs = true,
385                children = true,
386                recovery = false,
387                bp = true
388            ),
389            NodeKind::Diamond => flags!(
390                exec = true,
391                scope = false,
392                decl = false,
393                refs = false,
394                children = false,
395                recovery = false,
396                bp = true
397            ),
398            NodeKind::Ellipsis => flags!(
399                exec = false,
400                scope = false,
401                decl = false,
402                refs = false,
403                children = false,
404                recovery = false,
405                bp = false
406            ),
407            NodeKind::Undef => flags!(
408                exec = false,
409                scope = false,
410                decl = false,
411                refs = false,
412                children = false,
413                recovery = false,
414                bp = false
415            ),
416            NodeKind::Readline { .. } => flags!(
417                exec = true,
418                scope = false,
419                decl = false,
420                refs = true,
421                children = false,
422                recovery = false,
423                bp = true
424            ),
425            NodeKind::Glob { .. } => flags!(
426                exec = true,
427                scope = false,
428                decl = false,
429                refs = false,
430                children = false,
431                recovery = false,
432                bp = true
433            ),
434            NodeKind::Typeglob { .. } => flags!(
435                exec = false,
436                scope = false,
437                decl = false,
438                refs = true,
439                children = false,
440                recovery = false,
441                bp = false
442            ),
443            NodeKind::Number { .. } => flags!(
444                exec = false,
445                scope = false,
446                decl = false,
447                refs = false,
448                children = false,
449                recovery = false,
450                bp = false
451            ),
452            NodeKind::String { .. } => flags!(
453                exec = false,
454                scope = false,
455                decl = false,
456                refs = false,
457                children = false,
458                recovery = false,
459                bp = false
460            ),
461            NodeKind::Heredoc { .. } => flags!(
462                exec = true,
463                scope = false,
464                decl = false,
465                refs = false,
466                children = false,
467                recovery = false,
468                bp = true
469            ),
470            NodeKind::ArrayLiteral { .. } => flags!(
471                exec = false,
472                scope = false,
473                decl = false,
474                refs = false,
475                children = true,
476                recovery = false,
477                bp = false
478            ),
479            NodeKind::HashLiteral { .. } => flags!(
480                exec = false,
481                scope = false,
482                decl = false,
483                refs = false,
484                children = true,
485                recovery = false,
486                bp = false
487            ),
488            NodeKind::Block { .. } => flags!(
489                exec = true,
490                scope = true,
491                decl = false,
492                refs = false,
493                children = true,
494                recovery = false,
495                bp = true
496            ),
497            NodeKind::Eval { .. } => flags!(
498                exec = true,
499                scope = true,
500                decl = false,
501                refs = false,
502                children = true,
503                recovery = false,
504                bp = true
505            ),
506            NodeKind::Do { .. } => flags!(
507                exec = true,
508                scope = false,
509                decl = false,
510                refs = false,
511                children = true,
512                recovery = false,
513                bp = true
514            ),
515            NodeKind::Defer { .. } => flags!(
516                exec = true,
517                scope = true,
518                decl = false,
519                refs = false,
520                children = true,
521                recovery = false,
522                bp = true
523            ),
524            NodeKind::Try { .. } => flags!(
525                exec = true,
526                scope = true,
527                decl = false,
528                refs = false,
529                children = true,
530                recovery = false,
531                bp = true
532            ),
533            NodeKind::If { .. } => flags!(
534                exec = true,
535                scope = false,
536                decl = false,
537                refs = true,
538                children = true,
539                recovery = false,
540                bp = true
541            ),
542            NodeKind::LabeledStatement { .. } => flags!(
543                exec = true,
544                scope = false,
545                decl = false,
546                refs = false,
547                children = true,
548                recovery = false,
549                bp = true
550            ),
551            NodeKind::While { .. } => flags!(
552                exec = true,
553                scope = false,
554                decl = false,
555                refs = true,
556                children = true,
557                recovery = false,
558                bp = true
559            ),
560            NodeKind::Tie { .. } => flags!(
561                exec = true,
562                scope = false,
563                decl = false,
564                refs = true,
565                children = true,
566                recovery = false,
567                bp = true
568            ),
569            NodeKind::Untie { .. } => flags!(
570                exec = true,
571                scope = false,
572                decl = false,
573                refs = true,
574                children = true,
575                recovery = false,
576                bp = true
577            ),
578            NodeKind::For { .. } => flags!(
579                exec = true,
580                scope = false,
581                decl = false,
582                refs = true,
583                children = true,
584                recovery = false,
585                bp = true
586            ),
587            NodeKind::Foreach { .. } => flags!(
588                exec = true,
589                scope = false,
590                decl = false,
591                refs = true,
592                children = true,
593                recovery = false,
594                bp = true
595            ),
596            NodeKind::Given { .. } => flags!(
597                exec = true,
598                scope = false,
599                decl = false,
600                refs = true,
601                children = true,
602                recovery = false,
603                bp = true
604            ),
605            NodeKind::When { .. } => flags!(
606                exec = true,
607                scope = false,
608                decl = false,
609                refs = true,
610                children = true,
611                recovery = false,
612                bp = true
613            ),
614            NodeKind::Default { .. } => flags!(
615                exec = true,
616                scope = false,
617                decl = false,
618                refs = false,
619                children = true,
620                recovery = false,
621                bp = true
622            ),
623            NodeKind::StatementModifier { .. } => flags!(
624                exec = true,
625                scope = false,
626                decl = false,
627                refs = true,
628                children = true,
629                recovery = false,
630                bp = true
631            ),
632            NodeKind::Subroutine { .. } => flags!(
633                exec = false,
634                scope = true,
635                decl = true,
636                refs = false,
637                children = true,
638                recovery = false,
639                bp = true
640            ),
641            NodeKind::Prototype { .. } => flags!(
642                exec = false,
643                scope = false,
644                decl = false,
645                refs = false,
646                children = false,
647                recovery = false,
648                bp = false
649            ),
650            NodeKind::Signature { .. } => flags!(
651                exec = false,
652                scope = false,
653                decl = false,
654                refs = false,
655                children = true,
656                recovery = false,
657                bp = false
658            ),
659            NodeKind::MandatoryParameter { .. } => flags!(
660                exec = false,
661                scope = false,
662                decl = true,
663                refs = false,
664                children = true,
665                recovery = false,
666                bp = false
667            ),
668            NodeKind::OptionalParameter { .. } => flags!(
669                exec = false,
670                scope = false,
671                decl = true,
672                refs = true,
673                children = true,
674                recovery = false,
675                bp = false
676            ),
677            NodeKind::SlurpyParameter { .. } => flags!(
678                exec = false,
679                scope = false,
680                decl = true,
681                refs = false,
682                children = true,
683                recovery = false,
684                bp = false
685            ),
686            NodeKind::NamedParameter { .. } => flags!(
687                exec = false,
688                scope = false,
689                decl = true,
690                refs = false,
691                children = true,
692                recovery = false,
693                bp = false
694            ),
695            NodeKind::Method { .. } => flags!(
696                exec = false,
697                scope = true,
698                decl = true,
699                refs = false,
700                children = true,
701                recovery = false,
702                bp = true
703            ),
704            NodeKind::Return { .. } => flags!(
705                exec = true,
706                scope = false,
707                decl = false,
708                refs = true,
709                children = true,
710                recovery = false,
711                bp = true
712            ),
713            NodeKind::LoopControl { .. } => flags!(
714                exec = true,
715                scope = false,
716                decl = false,
717                refs = false,
718                children = false,
719                recovery = false,
720                bp = true
721            ),
722            NodeKind::Goto { .. } => flags!(
723                exec = true,
724                scope = false,
725                decl = false,
726                refs = true,
727                children = true,
728                recovery = false,
729                bp = true
730            ),
731            NodeKind::MethodCall { .. } => flags!(
732                exec = true,
733                scope = false,
734                decl = false,
735                refs = true,
736                children = true,
737                recovery = false,
738                bp = true
739            ),
740            NodeKind::FunctionCall { .. } => flags!(
741                exec = true,
742                scope = false,
743                decl = false,
744                refs = true,
745                children = true,
746                recovery = false,
747                bp = true
748            ),
749            NodeKind::IndirectCall { .. } => flags!(
750                exec = true,
751                scope = false,
752                decl = false,
753                refs = true,
754                children = true,
755                recovery = false,
756                bp = true
757            ),
758            NodeKind::Regex { .. } => flags!(
759                exec = false,
760                scope = false,
761                decl = false,
762                refs = false,
763                children = false,
764                recovery = false,
765                bp = false
766            ),
767            NodeKind::Match { .. } => flags!(
768                exec = true,
769                scope = false,
770                decl = false,
771                refs = true,
772                children = true,
773                recovery = false,
774                bp = true
775            ),
776            NodeKind::Substitution { .. } => flags!(
777                exec = true,
778                scope = false,
779                decl = false,
780                refs = true,
781                children = true,
782                recovery = false,
783                bp = true
784            ),
785            NodeKind::Transliteration { .. } => flags!(
786                exec = true,
787                scope = false,
788                decl = false,
789                refs = true,
790                children = true,
791                recovery = false,
792                bp = true
793            ),
794            NodeKind::Package { .. } => flags!(
795                exec = true,
796                scope = true,
797                decl = true,
798                refs = false,
799                children = true,
800                recovery = false,
801                bp = true
802            ),
803            // `use Module LIST` is BEGIN { require Module; Module->import(@LIST) } —
804            // compile-time pragma. Perl 5.40.1 debugger probe reports "not breakable".
805            // safe_for_breakpoint=false: compile-time pragma; not stoppable in runtime debugger.
806            NodeKind::Use { .. } => flags!(
807                exec = true,
808                scope = false,
809                decl = false,
810                refs = false,
811                children = false,
812                recovery = false,
813                bp = false
814            ),
815            // `no Module LIST` is BEGIN { Module->unimport(@LIST) } —
816            // compile-time unimport. Perl 5.40.1 debugger probe reports "not breakable".
817            // safe_for_breakpoint=false: compile-time unimport; not stoppable in runtime debugger.
818            NodeKind::No { .. } => flags!(
819                exec = true,
820                scope = false,
821                decl = false,
822                refs = false,
823                children = false,
824                recovery = false,
825                bp = false
826            ),
827            NodeKind::PhaseBlock { .. } => flags!(
828                exec = true,
829                scope = true,
830                decl = false,
831                refs = false,
832                children = true,
833                recovery = false,
834                bp = true
835            ),
836            NodeKind::DataSection { .. } => flags!(
837                exec = false,
838                scope = false,
839                decl = false,
840                refs = false,
841                children = false,
842                recovery = false,
843                bp = false
844            ),
845            NodeKind::Class { .. } => flags!(
846                exec = false,
847                scope = true,
848                decl = true,
849                refs = false,
850                children = true,
851                recovery = false,
852                bp = true
853            ),
854            NodeKind::Format { .. } => flags!(
855                exec = false,
856                scope = false,
857                decl = true,
858                refs = false,
859                children = false,
860                recovery = false,
861                bp = false
862            ),
863            NodeKind::Identifier { .. } => flags!(
864                exec = false,
865                scope = false,
866                decl = false,
867                refs = true,
868                children = false,
869                recovery = false,
870                bp = false
871            ),
872            NodeKind::Error { .. } => flags!(
873                exec = false,
874                scope = false,
875                decl = false,
876                refs = false,
877                children = true,
878                recovery = true,
879                bp = false
880            ),
881            NodeKind::MissingExpression => flags!(
882                exec = false,
883                scope = false,
884                decl = false,
885                refs = false,
886                children = false,
887                recovery = true,
888                bp = false
889            ),
890            NodeKind::MissingStatement => flags!(
891                exec = false,
892                scope = false,
893                decl = false,
894                refs = false,
895                children = false,
896                recovery = true,
897                bp = false
898            ),
899            NodeKind::MissingIdentifier => flags!(
900                exec = false,
901                scope = false,
902                decl = false,
903                refs = false,
904                children = false,
905                recovery = true,
906                bp = false
907            ),
908            NodeKind::MissingBlock => flags!(
909                exec = false,
910                scope = false,
911                decl = false,
912                refs = false,
913                children = false,
914                recovery = true,
915                bp = false
916            ),
917            NodeKind::UnknownRest => flags!(
918                exec = false,
919                scope = false,
920                decl = false,
921                refs = false,
922                children = false,
923                recovery = true,
924                bp = false
925            ),
926        }
927    }
928
929    // ────────────────────────────────────────────────────────────────────────
930    // Convenience flag accessors
931    // ────────────────────────────────────────────────────────────────────────
932
933    /// Returns `true` if this node kind represents executable code.
934    ///
935    /// See [`NodeKindFlags::executable`].
936    #[inline]
937    pub fn is_executable(&self) -> bool {
938        self.flags().executable
939    }
940
941    /// Returns `true` if this node kind introduces a new lexical scope.
942    ///
943    /// See [`NodeKindFlags::introduces_scope`].
944    #[inline]
945    pub fn introduces_scope(&self) -> bool {
946        self.flags().introduces_scope
947    }
948
949    /// Returns `true` if this node kind declares a symbol into a scope or
950    /// symbol table.
951    ///
952    /// See [`NodeKindFlags::declares_symbol`].
953    #[inline]
954    pub fn declares_symbol(&self) -> bool {
955        self.flags().declares_symbol
956    }
957
958    /// Returns `true` if this node kind references a symbol.
959    ///
960    /// See [`NodeKindFlags::references_symbol`].
961    #[inline]
962    pub fn references_symbol(&self) -> bool {
963        self.flags().references_symbol
964    }
965
966    /// Returns `true` if this node kind can host a debugger breakpoint.
967    ///
968    /// This is a **variant-level** flag. Consumers must AND it with positional
969    /// checks (heredoc body interior, POD block, `__DATA__` section, etc.)
970    /// before accepting a breakpoint request.
971    ///
972    /// See [`NodeKindFlags::safe_for_breakpoint`].
973    #[inline]
974    pub fn safe_for_breakpoint(&self) -> bool {
975        self.flags().safe_for_breakpoint
976    }
977
978    /// Returns `true` if this node kind is a synthetic recovery artifact.
979    ///
980    /// Recovery nodes should never be offered to editor features such as
981    /// hover, go-to-definition, or breakpoint placement.
982    ///
983    /// See [`NodeKindFlags::recovery_artifact`].
984    #[inline]
985    pub fn is_recovery(&self) -> bool {
986        self.flags().recovery_artifact
987    }
988
989    /// Returns `true` if this node kind can host `Node` children worth walking
990    /// during AST traversal.
991    ///
992    /// This is a **structural** flag: it is `true` for every variant that has
993    /// at least one `Node`-typed field (`Box<Node>`, `Vec<Node>`,
994    /// `Option<Box<Node>>`, …), regardless of whether a particular instance
995    /// populates them. A traversal filter may safely skip nodes for which this
996    /// returns `false` — they are always leaves under
997    /// [`Node::for_each_child`](crate::ast::Node::for_each_child).
998    ///
999    /// See [`NodeKindFlags::contains_children`].
1000    #[inline]
1001    pub fn contains_children(&self) -> bool {
1002        self.flags().contains_children
1003    }
1004}
1005
1006// ─────────────────────────────────────────────────────────────────────────────
1007// Inline lib tests — counted by `--lib` profdata for Codecov patch coverage.
1008//
1009// These assert real behaviour (not padding). The integration contract tests in
1010// `tests/classification_tests.rs` remain the primary specification suite; this
1011// module ensures every production line in `classification.rs` is reachable
1012// under `cargo llvm-cov -p perl-ast --lib`.
1013// ─────────────────────────────────────────────────────────────────────────────
1014#[cfg(test)]
1015mod tests {
1016    use super::NodeKindCategory;
1017    use super::NodeKindFlags;
1018    use crate::ast::{Node, NodeKind};
1019    use perl_position_tracking::SourceLocation;
1020
1021    fn loc() -> SourceLocation {
1022        SourceLocation::new(0, 1)
1023    }
1024
1025    fn leaf() -> Node {
1026        Node::new(NodeKind::Identifier { name: "x".to_string() }, loc())
1027    }
1028
1029    fn block_node() -> Node {
1030        Node::new(NodeKind::Block { statements: vec![] }, loc())
1031    }
1032
1033    /// One representative of every `NodeKind` variant so every match arm in
1034    /// `category()` and `flags()` is exercised under `--lib` profdata.
1035    fn all_variants() -> Vec<NodeKind> {
1036        vec![
1037            NodeKind::Program { statements: vec![] },
1038            NodeKind::ExpressionStatement { expression: Box::new(leaf()) },
1039            NodeKind::VariableDeclaration {
1040                declarator: "my".to_string(),
1041                variable: Box::new(leaf()),
1042                attributes: vec![],
1043                initializer: None,
1044            },
1045            NodeKind::VariableListDeclaration {
1046                declarator: "my".to_string(),
1047                variables: vec![],
1048                attributes: vec![],
1049                initializer: None,
1050            },
1051            NodeKind::NestedVariableList { items: vec![] },
1052            NodeKind::Variable { sigil: "$".to_string(), name: "x".to_string() },
1053            NodeKind::VariableWithAttributes { variable: Box::new(leaf()), attributes: vec![] },
1054            NodeKind::Assignment {
1055                lhs: Box::new(leaf()),
1056                rhs: Box::new(leaf()),
1057                op: "=".to_string(),
1058            },
1059            NodeKind::Binary {
1060                op: "+".to_string(),
1061                left: Box::new(leaf()),
1062                right: Box::new(leaf()),
1063            },
1064            NodeKind::Ternary {
1065                condition: Box::new(leaf()),
1066                then_expr: Box::new(leaf()),
1067                else_expr: Box::new(leaf()),
1068            },
1069            NodeKind::Unary { op: "-".to_string(), operand: Box::new(leaf()) },
1070            NodeKind::Diamond,
1071            NodeKind::Ellipsis,
1072            NodeKind::Undef,
1073            NodeKind::Readline { filehandle: None },
1074            NodeKind::Glob { pattern: "*.pl".to_string() },
1075            NodeKind::Typeglob { name: "foo".to_string() },
1076            NodeKind::Number { value: "42".to_string() },
1077            NodeKind::String { value: "hello".to_string(), interpolated: false },
1078            NodeKind::Heredoc {
1079                delimiter: "EOF".to_string(),
1080                content: "body".to_string(),
1081                interpolated: false,
1082                indented: false,
1083                command: false,
1084                body_span: None,
1085            },
1086            NodeKind::ArrayLiteral { elements: vec![] },
1087            NodeKind::HashLiteral { pairs: vec![] },
1088            NodeKind::Block { statements: vec![] },
1089            NodeKind::Eval { block: Box::new(block_node()) },
1090            NodeKind::Do { block: Box::new(block_node()) },
1091            NodeKind::Defer { block: Box::new(block_node()) },
1092            NodeKind::Try {
1093                body: Box::new(block_node()),
1094                catch_blocks: vec![],
1095                finally_block: None,
1096            },
1097            NodeKind::If {
1098                condition: Box::new(leaf()),
1099                then_branch: Box::new(block_node()),
1100                elsif_branches: vec![],
1101                else_branch: None,
1102                keyword: None,
1103            },
1104            NodeKind::LabeledStatement {
1105                label: "OUTER".to_string(),
1106                statement: Box::new(Node::new(
1107                    NodeKind::LoopControl { op: "next".to_string(), label: None },
1108                    loc(),
1109                )),
1110            },
1111            NodeKind::While {
1112                condition: Box::new(leaf()),
1113                body: Box::new(block_node()),
1114                continue_block: None,
1115                keyword: None,
1116            },
1117            NodeKind::Tie { variable: Box::new(leaf()), package: Box::new(leaf()), args: vec![] },
1118            NodeKind::Untie { variable: Box::new(leaf()) },
1119            NodeKind::For {
1120                init: None,
1121                condition: None,
1122                update: None,
1123                body: Box::new(block_node()),
1124                continue_block: None,
1125            },
1126            NodeKind::Foreach {
1127                variable: Box::new(leaf()),
1128                list: Box::new(leaf()),
1129                body: Box::new(block_node()),
1130                continue_block: None,
1131            },
1132            NodeKind::Given { expr: Box::new(leaf()), body: Box::new(block_node()) },
1133            NodeKind::When { condition: Box::new(leaf()), body: Box::new(block_node()) },
1134            NodeKind::Default { body: Box::new(block_node()) },
1135            NodeKind::StatementModifier {
1136                statement: Box::new(leaf()),
1137                modifier: "if".to_string(),
1138                condition: Box::new(leaf()),
1139            },
1140            NodeKind::Subroutine {
1141                name: Some("foo".to_string()),
1142                name_span: None,
1143                declarator: None,
1144                prototype: None,
1145                signature: None,
1146                attributes: vec![],
1147                body: Box::new(block_node()),
1148            },
1149            NodeKind::Prototype { content: "$@".to_string() },
1150            NodeKind::Signature { parameters: vec![] },
1151            NodeKind::MandatoryParameter { variable: Box::new(leaf()) },
1152            NodeKind::OptionalParameter {
1153                variable: Box::new(leaf()),
1154                default_value: Box::new(leaf()),
1155            },
1156            NodeKind::SlurpyParameter { variable: Box::new(leaf()) },
1157            NodeKind::NamedParameter { variable: Box::new(leaf()) },
1158            NodeKind::Method {
1159                name: "bar".to_string(),
1160                name_span: None,
1161                signature: None,
1162                attributes: vec![],
1163                body: Box::new(block_node()),
1164            },
1165            NodeKind::Return { value: None },
1166            NodeKind::LoopControl { op: "next".to_string(), label: None },
1167            NodeKind::Goto { target: Box::new(leaf()) },
1168            NodeKind::MethodCall {
1169                object: Box::new(leaf()),
1170                method: "foo".to_string(),
1171                args: vec![],
1172            },
1173            NodeKind::FunctionCall { name: "print".to_string(), args: vec![] },
1174            NodeKind::IndirectCall {
1175                method: "new".to_string(),
1176                object: Box::new(leaf()),
1177                args: vec![],
1178            },
1179            NodeKind::Regex {
1180                pattern: "foo".to_string(),
1181                replacement: None,
1182                modifiers: "".to_string(),
1183                has_embedded_code: false,
1184            },
1185            NodeKind::Match {
1186                expr: Box::new(leaf()),
1187                pattern: "foo".to_string(),
1188                modifiers: "".to_string(),
1189                has_embedded_code: false,
1190                negated: false,
1191            },
1192            NodeKind::Substitution {
1193                expr: Box::new(leaf()),
1194                pattern: "foo".to_string(),
1195                replacement: "bar".to_string(),
1196                modifiers: "".to_string(),
1197                has_embedded_code: false,
1198                negated: false,
1199            },
1200            NodeKind::Transliteration {
1201                expr: Box::new(leaf()),
1202                search: "a".to_string(),
1203                replace: "b".to_string(),
1204                modifiers: "".to_string(),
1205                negated: false,
1206            },
1207            NodeKind::Package { name: "Foo".to_string(), name_span: loc(), block: None },
1208            NodeKind::Use { module: "strict".to_string(), args: vec![], has_filter_risk: false },
1209            NodeKind::No { module: "strict".to_string(), args: vec![], has_filter_risk: false },
1210            NodeKind::PhaseBlock {
1211                phase: "BEGIN".to_string(),
1212                phase_span: None,
1213                block: Box::new(block_node()),
1214            },
1215            NodeKind::DataSection { marker: "__DATA__".to_string(), body: None },
1216            NodeKind::Class {
1217                name: "Foo".to_string(),
1218                name_span: None,
1219                parents: vec![],
1220                body: Box::new(block_node()),
1221            },
1222            NodeKind::Format { name: "STDOUT".to_string(), name_span: None, body: "".to_string() },
1223            NodeKind::Identifier { name: "foo".to_string() },
1224            NodeKind::Error {
1225                message: "oops".to_string(),
1226                expected: vec![],
1227                found: None,
1228                partial: None,
1229            },
1230            NodeKind::MissingExpression,
1231            NodeKind::MissingStatement,
1232            NodeKind::MissingIdentifier,
1233            NodeKind::MissingBlock,
1234            NodeKind::UnknownRest,
1235        ]
1236    }
1237
1238    // ── Test 1: every variant produces a category and flags that pass validate()
1239
1240    #[test]
1241    fn all_variants_return_category_and_valid_flags() {
1242        for kind in all_variants() {
1243            let _cat = kind.category();
1244            let flags = kind.flags();
1245            assert!(
1246                flags.validate().is_ok(),
1247                "variant {} failed validate(): {:?}",
1248                kind.kind_name(),
1249                flags.validate()
1250            );
1251        }
1252    }
1253
1254    // ── Test 2: category() spot-checks for every NodeKindCategory value ───────
1255
1256    #[test]
1257    fn category_spot_checks() {
1258        assert_eq!(NodeKind::Program { statements: vec![] }.category(), NodeKindCategory::Program);
1259        assert_eq!(
1260            NodeKind::If {
1261                condition: Box::new(leaf()),
1262                then_branch: Box::new(block_node()),
1263                elsif_branches: vec![],
1264                else_branch: None,
1265                keyword: None,
1266            }
1267            .category(),
1268            NodeKindCategory::Statement
1269        );
1270        assert_eq!(
1271            NodeKind::FunctionCall { name: "print".to_string(), args: vec![] }.category(),
1272            NodeKindCategory::Expression
1273        );
1274        assert_eq!(
1275            NodeKind::Subroutine {
1276                name: Some("foo".to_string()),
1277                name_span: None,
1278                declarator: None,
1279                prototype: None,
1280                signature: None,
1281                attributes: vec![],
1282                body: Box::new(block_node()),
1283            }
1284            .category(),
1285            NodeKindCategory::Declaration
1286        );
1287        assert_eq!(NodeKind::Block { statements: vec![] }.category(), NodeKindCategory::Scope);
1288        assert_eq!(
1289            NodeKind::Number { value: "1".to_string() }.category(),
1290            NodeKindCategory::Literal
1291        );
1292        assert_eq!(NodeKind::Ellipsis.category(), NodeKindCategory::Operator);
1293        assert_eq!(NodeKind::MissingExpression.category(), NodeKindCategory::Recovery);
1294        assert_eq!(NodeKind::UnknownRest.category(), NodeKindCategory::Recovery);
1295    }
1296
1297    // ── Test 3: recovery_artifact implies !safe_for_breakpoint (invariant) ────
1298
1299    #[test]
1300    fn recovery_artifact_implies_not_safe_for_breakpoint() {
1301        for kind in all_variants() {
1302            let flags = kind.flags();
1303            if flags.recovery_artifact {
1304                assert!(
1305                    !flags.safe_for_breakpoint,
1306                    "variant {} has recovery_artifact=true but safe_for_breakpoint=true",
1307                    kind.kind_name()
1308                );
1309            }
1310        }
1311    }
1312
1313    // ── Test 4: convenience accessors are consistent with flags() ─────────────
1314
1315    #[test]
1316    fn convenience_accessors_match_flags() {
1317        for kind in all_variants() {
1318            let flags = kind.flags();
1319            assert_eq!(
1320                kind.is_executable(),
1321                flags.executable,
1322                "{}: is_executable() != flags.executable",
1323                kind.kind_name()
1324            );
1325            assert_eq!(
1326                kind.introduces_scope(),
1327                flags.introduces_scope,
1328                "{}: introduces_scope() != flags.introduces_scope",
1329                kind.kind_name()
1330            );
1331            assert_eq!(
1332                kind.declares_symbol(),
1333                flags.declares_symbol,
1334                "{}: declares_symbol() != flags.declares_symbol",
1335                kind.kind_name()
1336            );
1337            assert_eq!(
1338                kind.references_symbol(),
1339                flags.references_symbol,
1340                "{}: references_symbol() != flags.references_symbol",
1341                kind.kind_name()
1342            );
1343            assert_eq!(
1344                kind.contains_children(),
1345                flags.contains_children,
1346                "{}: contains_children() != flags.contains_children",
1347                kind.kind_name()
1348            );
1349            assert_eq!(
1350                kind.safe_for_breakpoint(),
1351                flags.safe_for_breakpoint,
1352                "{}: safe_for_breakpoint() != flags.safe_for_breakpoint",
1353                kind.kind_name()
1354            );
1355            assert_eq!(
1356                kind.is_recovery(),
1357                flags.recovery_artifact,
1358                "{}: is_recovery() != flags.recovery_artifact",
1359                kind.kind_name()
1360            );
1361        }
1362    }
1363
1364    // ── Test 5: specific expected flag values for representative variants ──────
1365
1366    #[test]
1367    fn flag_values_for_representative_variants() {
1368        // FunctionCall: executable, references symbols, safe for breakpoint
1369        let fc = NodeKind::FunctionCall { name: "say".to_string(), args: vec![] };
1370        assert!(fc.is_executable());
1371        assert!(fc.references_symbol());
1372        assert!(fc.safe_for_breakpoint());
1373        assert!(!fc.introduces_scope());
1374        assert!(!fc.declares_symbol());
1375        assert!(!fc.is_recovery());
1376
1377        // VariableDeclaration: executable, introduces scope, declares symbol
1378        let vd = NodeKind::VariableDeclaration {
1379            declarator: "my".to_string(),
1380            variable: Box::new(leaf()),
1381            attributes: vec![],
1382            initializer: None,
1383        };
1384        assert!(vd.is_executable());
1385        assert!(vd.introduces_scope());
1386        assert!(vd.declares_symbol());
1387        assert!(vd.safe_for_breakpoint());
1388
1389        // Number literal: none of the executable/scope/decl/refs/bp flags set
1390        let num = NodeKind::Number { value: "0".to_string() };
1391        assert!(!num.is_executable());
1392        assert!(!num.introduces_scope());
1393        assert!(!num.declares_symbol());
1394        assert!(!num.references_symbol());
1395        assert!(!num.safe_for_breakpoint());
1396        assert!(!num.is_recovery());
1397
1398        // Error: recovery_artifact, never safe for breakpoint
1399        let err = NodeKind::Error {
1400            message: "bad".to_string(),
1401            expected: vec![],
1402            found: None,
1403            partial: None,
1404        };
1405        assert!(err.is_recovery());
1406        assert!(!err.safe_for_breakpoint());
1407
1408        // All five Missing*/UnknownRest variants are recovery artifacts
1409        for recovery_kind in [
1410            NodeKind::MissingExpression,
1411            NodeKind::MissingStatement,
1412            NodeKind::MissingIdentifier,
1413            NodeKind::MissingBlock,
1414            NodeKind::UnknownRest,
1415        ] {
1416            assert!(
1417                recovery_kind.is_recovery(),
1418                "{} should be recovery",
1419                recovery_kind.kind_name()
1420            );
1421            assert!(
1422                !recovery_kind.safe_for_breakpoint(),
1423                "{} should not be safe_for_breakpoint",
1424                recovery_kind.kind_name()
1425            );
1426        }
1427
1428        // Block: introduces scope, executable, and safe for breakpoint
1429        let blk = NodeKind::Block { statements: vec![] };
1430        assert!(blk.introduces_scope());
1431        assert!(blk.is_executable());
1432        assert!(blk.safe_for_breakpoint());
1433    }
1434
1435    // ── Test 6: NodeKindFlags::validate() accepts valid, rejects invalid ───────
1436
1437    #[test]
1438    fn flags_validate_rejects_recovery_with_breakpoint() {
1439        let good = NodeKindFlags {
1440            executable: false,
1441            introduces_scope: false,
1442            declares_symbol: false,
1443            references_symbol: false,
1444            contains_children: false,
1445            recovery_artifact: true,
1446            safe_for_breakpoint: false,
1447        };
1448        assert!(good.validate().is_ok());
1449
1450        let bad = NodeKindFlags {
1451            executable: false,
1452            introduces_scope: false,
1453            declares_symbol: false,
1454            references_symbol: false,
1455            contains_children: false,
1456            recovery_artifact: true,
1457            safe_for_breakpoint: true, // INVALID: recovery AND breakpoint
1458        };
1459        assert!(bad.validate().is_err());
1460    }
1461
1462    // ── Test 6.5: contains_children() accessor returns correct values ──────────
1463    //
1464    // Direct coverage of the new `contains_children()` public accessor method.
1465    // Tests leaf variants (expect false) and parent variants (expect true).
1466
1467    #[test]
1468    fn contains_children_accessor_leaf_variants() {
1469        // Leaf variants with no Node-typed fields
1470        assert!(
1471            !NodeKind::Number { value: "42".to_string() }.contains_children(),
1472            "Number should not contain children"
1473        );
1474        assert!(
1475            !NodeKind::String { value: "hello".to_string(), interpolated: false }
1476                .contains_children(),
1477            "String should not contain children"
1478        );
1479        assert!(
1480            !NodeKind::Variable { sigil: "$".to_string(), name: "x".to_string() }
1481                .contains_children(),
1482            "Variable should not contain children"
1483        );
1484        assert!(!NodeKind::Diamond.contains_children(), "Diamond should not contain children");
1485        assert!(!NodeKind::Undef.contains_children(), "Undef should not contain children");
1486    }
1487
1488    #[test]
1489    fn contains_children_accessor_parent_variants() {
1490        // Parent variants with Node-typed fields
1491        assert!(
1492            NodeKind::Block { statements: vec![leaf()] }.contains_children(),
1493            "Block with statements should contain children"
1494        );
1495        assert!(
1496            NodeKind::Program { statements: vec![leaf()] }.contains_children(),
1497            "Program should contain children"
1498        );
1499        assert!(
1500            NodeKind::VariableDeclaration {
1501                declarator: "my".to_string(),
1502                variable: Box::new(leaf()),
1503                attributes: vec![],
1504                initializer: None,
1505            }
1506            .contains_children(),
1507            "VariableDeclaration with variable should contain children"
1508        );
1509        assert!(
1510            NodeKind::Binary {
1511                op: "+".to_string(),
1512                left: Box::new(leaf()),
1513                right: Box::new(leaf()),
1514            }
1515            .contains_children(),
1516            "Binary should contain children"
1517        );
1518        assert!(
1519            NodeKind::FunctionCall { name: "print".to_string(), args: vec![leaf()] }
1520                .contains_children(),
1521            "FunctionCall with args should contain children"
1522        );
1523    }
1524
1525    // ── Test 7: contains_children matches the real for_each_child traversal ────
1526    //
1527    // `contains_children` is a structural flag: it must be `true` for exactly
1528    // the variants that have at least one `Node`-typed field. The authoritative
1529    // source of "does this variant have Node children?" is `Node::for_each_child`
1530    // (exercised here via `child_count()`). Building every variant with all of
1531    // its optional/collection child slots populated makes `child_count() > 0`
1532    // equivalent to "this variant can hold children" — so the flag must agree
1533    // exactly. This guards against the drift that produced incorrect flags for
1534    // String/Heredoc/Readline/Glob/Use/No (false positives) and
1535    // VariableDeclaration/Untie/Error (false negatives).
1536
1537    /// One representative of every `NodeKind` variant with *every* `Node`-typed
1538    /// field populated, so `child_count() > 0` iff the variant has Node children.
1539    fn all_variants_maximal() -> Vec<Node> {
1540        let n = |kind| Node::new(kind, loc());
1541        vec![
1542            n(NodeKind::Program { statements: vec![leaf()] }),
1543            n(NodeKind::ExpressionStatement { expression: Box::new(leaf()) }),
1544            n(NodeKind::VariableDeclaration {
1545                declarator: "my".to_string(),
1546                variable: Box::new(leaf()),
1547                attributes: vec![],
1548                initializer: Some(Box::new(leaf())),
1549            }),
1550            n(NodeKind::VariableListDeclaration {
1551                declarator: "my".to_string(),
1552                variables: vec![leaf()],
1553                attributes: vec![],
1554                initializer: Some(Box::new(leaf())),
1555            }),
1556            n(NodeKind::NestedVariableList { items: vec![leaf()] }),
1557            n(NodeKind::Variable { sigil: "$".to_string(), name: "x".to_string() }),
1558            n(NodeKind::VariableWithAttributes { variable: Box::new(leaf()), attributes: vec![] }),
1559            n(NodeKind::Assignment {
1560                lhs: Box::new(leaf()),
1561                rhs: Box::new(leaf()),
1562                op: "=".to_string(),
1563            }),
1564            n(NodeKind::Binary {
1565                op: "+".to_string(),
1566                left: Box::new(leaf()),
1567                right: Box::new(leaf()),
1568            }),
1569            n(NodeKind::Ternary {
1570                condition: Box::new(leaf()),
1571                then_expr: Box::new(leaf()),
1572                else_expr: Box::new(leaf()),
1573            }),
1574            n(NodeKind::Unary { op: "-".to_string(), operand: Box::new(leaf()) }),
1575            n(NodeKind::Diamond),
1576            n(NodeKind::Ellipsis),
1577            n(NodeKind::Undef),
1578            n(NodeKind::Readline { filehandle: Some("STDIN".to_string()) }),
1579            n(NodeKind::Glob { pattern: "*.pl".to_string() }),
1580            n(NodeKind::Typeglob { name: "foo".to_string() }),
1581            n(NodeKind::Number { value: "42".to_string() }),
1582            n(NodeKind::String { value: "hello".to_string(), interpolated: false }),
1583            n(NodeKind::Heredoc {
1584                delimiter: "EOF".to_string(),
1585                content: "body".to_string(),
1586                interpolated: false,
1587                indented: false,
1588                command: false,
1589                body_span: None,
1590            }),
1591            n(NodeKind::ArrayLiteral { elements: vec![leaf()] }),
1592            n(NodeKind::HashLiteral { pairs: vec![(leaf(), leaf())] }),
1593            n(NodeKind::Block { statements: vec![leaf()] }),
1594            n(NodeKind::Eval { block: Box::new(block_node()) }),
1595            n(NodeKind::Do { block: Box::new(block_node()) }),
1596            n(NodeKind::Defer { block: Box::new(block_node()) }),
1597            n(NodeKind::Try {
1598                body: Box::new(block_node()),
1599                catch_blocks: vec![(None, Box::new(block_node()))],
1600                finally_block: Some(Box::new(block_node())),
1601            }),
1602            n(NodeKind::If {
1603                condition: Box::new(leaf()),
1604                then_branch: Box::new(block_node()),
1605                elsif_branches: vec![(Box::new(leaf()), Box::new(block_node()))],
1606                else_branch: Some(Box::new(block_node())),
1607                keyword: None,
1608            }),
1609            n(NodeKind::LabeledStatement {
1610                label: "OUTER".to_string(),
1611                statement: Box::new(leaf()),
1612            }),
1613            n(NodeKind::While {
1614                condition: Box::new(leaf()),
1615                body: Box::new(block_node()),
1616                continue_block: Some(Box::new(block_node())),
1617                keyword: None,
1618            }),
1619            n(NodeKind::Tie {
1620                variable: Box::new(leaf()),
1621                package: Box::new(leaf()),
1622                args: vec![leaf()],
1623            }),
1624            n(NodeKind::Untie { variable: Box::new(leaf()) }),
1625            n(NodeKind::For {
1626                init: Some(Box::new(leaf())),
1627                condition: Some(Box::new(leaf())),
1628                update: Some(Box::new(leaf())),
1629                body: Box::new(block_node()),
1630                continue_block: Some(Box::new(block_node())),
1631            }),
1632            n(NodeKind::Foreach {
1633                variable: Box::new(leaf()),
1634                list: Box::new(leaf()),
1635                body: Box::new(block_node()),
1636                continue_block: Some(Box::new(block_node())),
1637            }),
1638            n(NodeKind::Given { expr: Box::new(leaf()), body: Box::new(block_node()) }),
1639            n(NodeKind::When { condition: Box::new(leaf()), body: Box::new(block_node()) }),
1640            n(NodeKind::Default { body: Box::new(block_node()) }),
1641            n(NodeKind::StatementModifier {
1642                statement: Box::new(leaf()),
1643                modifier: "if".to_string(),
1644                condition: Box::new(leaf()),
1645            }),
1646            n(NodeKind::Subroutine {
1647                name: Some("foo".to_string()),
1648                name_span: None,
1649                declarator: None,
1650                prototype: Some(Box::new(Node::new(
1651                    NodeKind::Prototype { content: "$@".to_string() },
1652                    loc(),
1653                ))),
1654                signature: Some(Box::new(Node::new(
1655                    NodeKind::Signature { parameters: vec![] },
1656                    loc(),
1657                ))),
1658                attributes: vec![],
1659                body: Box::new(block_node()),
1660            }),
1661            n(NodeKind::Prototype { content: "$@".to_string() }),
1662            n(NodeKind::Signature { parameters: vec![leaf()] }),
1663            n(NodeKind::MandatoryParameter { variable: Box::new(leaf()) }),
1664            n(NodeKind::OptionalParameter {
1665                variable: Box::new(leaf()),
1666                default_value: Box::new(leaf()),
1667            }),
1668            n(NodeKind::SlurpyParameter { variable: Box::new(leaf()) }),
1669            n(NodeKind::NamedParameter { variable: Box::new(leaf()) }),
1670            n(NodeKind::Method {
1671                name: "bar".to_string(),
1672                name_span: None,
1673                signature: Some(Box::new(Node::new(
1674                    NodeKind::Signature { parameters: vec![] },
1675                    loc(),
1676                ))),
1677                attributes: vec![],
1678                body: Box::new(block_node()),
1679            }),
1680            n(NodeKind::Return { value: Some(Box::new(leaf())) }),
1681            n(NodeKind::LoopControl { op: "next".to_string(), label: None }),
1682            n(NodeKind::Goto { target: Box::new(leaf()) }),
1683            n(NodeKind::MethodCall {
1684                object: Box::new(leaf()),
1685                method: "foo".to_string(),
1686                args: vec![leaf()],
1687            }),
1688            n(NodeKind::FunctionCall { name: "print".to_string(), args: vec![leaf()] }),
1689            n(NodeKind::IndirectCall {
1690                method: "new".to_string(),
1691                object: Box::new(leaf()),
1692                args: vec![leaf()],
1693            }),
1694            n(NodeKind::Regex {
1695                pattern: "foo".to_string(),
1696                replacement: None,
1697                modifiers: "".to_string(),
1698                has_embedded_code: false,
1699            }),
1700            n(NodeKind::Match {
1701                expr: Box::new(leaf()),
1702                pattern: "foo".to_string(),
1703                modifiers: "".to_string(),
1704                has_embedded_code: false,
1705                negated: false,
1706            }),
1707            n(NodeKind::Substitution {
1708                expr: Box::new(leaf()),
1709                pattern: "foo".to_string(),
1710                replacement: "bar".to_string(),
1711                modifiers: "".to_string(),
1712                has_embedded_code: false,
1713                negated: false,
1714            }),
1715            n(NodeKind::Transliteration {
1716                expr: Box::new(leaf()),
1717                search: "a".to_string(),
1718                replace: "b".to_string(),
1719                modifiers: "".to_string(),
1720                negated: false,
1721            }),
1722            n(NodeKind::Package {
1723                name: "Foo".to_string(),
1724                name_span: loc(),
1725                block: Some(Box::new(block_node())),
1726            }),
1727            n(NodeKind::Use {
1728                module: "strict".to_string(),
1729                args: vec!["foo".to_string()],
1730                has_filter_risk: false,
1731            }),
1732            n(NodeKind::No {
1733                module: "strict".to_string(),
1734                args: vec!["foo".to_string()],
1735                has_filter_risk: false,
1736            }),
1737            n(NodeKind::PhaseBlock {
1738                phase: "BEGIN".to_string(),
1739                phase_span: None,
1740                block: Box::new(block_node()),
1741            }),
1742            n(NodeKind::DataSection { marker: "__DATA__".to_string(), body: None }),
1743            n(NodeKind::Class {
1744                name: "Foo".to_string(),
1745                name_span: None,
1746                parents: vec![],
1747                body: Box::new(block_node()),
1748            }),
1749            n(NodeKind::Format {
1750                name: "STDOUT".to_string(),
1751                name_span: None,
1752                body: "".to_string(),
1753            }),
1754            n(NodeKind::Identifier { name: "foo".to_string() }),
1755            n(NodeKind::Error {
1756                message: "oops".to_string(),
1757                expected: vec![],
1758                found: None,
1759                partial: Some(Box::new(leaf())),
1760            }),
1761            n(NodeKind::MissingExpression),
1762            n(NodeKind::MissingStatement),
1763            n(NodeKind::MissingIdentifier),
1764            n(NodeKind::MissingBlock),
1765            n(NodeKind::UnknownRest),
1766        ]
1767    }
1768
1769    #[test]
1770    fn contains_children_matches_for_each_child() {
1771        for node in all_variants_maximal() {
1772            let has_children = node.child_count() > 0;
1773            assert_eq!(
1774                node.kind.contains_children(),
1775                has_children,
1776                "{}: contains_children() = {} but for_each_child yields {} children",
1777                node.kind.kind_name(),
1778                node.kind.contains_children(),
1779                node.child_count(),
1780            );
1781        }
1782    }
1783}