Skip to main content

perl_ast/
ast.rs

1//! Abstract Syntax Tree definitions for Perl within the parsing and LSP workflow.
2//!
3//! This module defines the comprehensive AST node types that represent parsed Perl code
4//! during the Parse → Index → Navigate → Complete → Analyze stages. The design is optimized
5//! for both direct use in Rust analysis and for generating tree-sitter compatible
6//! S-expressions during large workspace processing operations.
7//!
8//! # LSP Workflow Integration
9//!
10//! The AST structures support Perl tooling workflows by:
11//! - **Parse**: Produced by the parser as the canonical syntax tree
12//! - **Index**: Traversed to build symbol and reference tables
13//! - **Navigate**: Provides locations for definition and reference lookups
14//! - **Complete**: Supplies context for completion, hover, and signature help
15//! - **Analyze**: Feeds semantic analysis, diagnostics, and refactoring
16//!
17//! # Performance Characteristics
18//!
19//! AST structures are optimized for large codebases with:
20//! - Memory-efficient node representation using `Box<Node>` for recursive structures
21//! - Fast pattern matching via enum variants for common Perl constructs
22//! - Location tracking for precise error reporting in large files
23//! - Cheap cloning for parallel analysis tasks
24//!
25//! # Usage Examples
26//!
27//! ## Basic AST Construction
28//!
29//! ```rust
30//! use perl_ast::{Node, NodeKind, SourceLocation};
31//!
32//! // Create a simple variable declaration node
33//! let location = SourceLocation { start: 0, end: 10 };
34//! let node = Node::new(
35//!     NodeKind::VariableDeclaration {
36//!         declarator: "my".to_string(),
37//!         variable: Box::new(Node::new(
38//!             NodeKind::Variable { sigil: "$".to_string(), name: "x".to_string() },
39//!             location,
40//!         )),
41//!         attributes: vec![],
42//!         initializer: None,
43//!     },
44//!     location,
45//! );
46//! assert_eq!(node.kind.kind_name(), "VariableDeclaration");
47//! ```
48//!
49//! ## Tree-sitter S-expression Generation
50//!
51//! ```rust
52//! use perl_ast::{Node, NodeKind, SourceLocation};
53//!
54//! let loc = SourceLocation { start: 0, end: 2 };
55//! let num = Node::new(NodeKind::Number { value: "42".to_string() }, loc);
56//! let program = Node::new(NodeKind::Program { statements: vec![num] }, loc);
57//!
58//! let sexp = program.to_sexp();
59//! assert!(sexp.starts_with("(source_file"));
60//! ```
61//!
62//! ## AST Traversal and Analysis
63//!
64//! ```rust
65//! use perl_ast::{Node, NodeKind, SourceLocation};
66//!
67//! fn count_variables(node: &Node) -> usize {
68//!     let mut count = 0;
69//!     match &node.kind {
70//!         NodeKind::Variable { .. } => count += 1,
71//!         NodeKind::Program { statements } => {
72//!             for stmt in statements {
73//!                 count += count_variables(stmt);
74//!             }
75//!         }
76//!         _ => {} // Handle other node types as needed
77//!     }
78//!     count
79//! }
80//!
81//! let loc = SourceLocation { start: 0, end: 5 };
82//! let var = Node::new(
83//!     NodeKind::Variable { sigil: "$".to_string(), name: "x".to_string() },
84//!     loc,
85//! );
86//! let program = Node::new(NodeKind::Program { statements: vec![var] }, loc);
87//! assert_eq!(count_variables(&program), 1);
88//! ```
89//!
90//! ## Parsing Integration
91//!
92//! In practice the AST is produced by the parser rather than built by hand
93//! (requires `perl-parser-core`):
94//!
95//! ```rust,ignore
96//! use perl_parser_core::Parser;
97//! use perl_ast::NodeKind;
98//!
99//! let mut parser = Parser::new("my $x = 42;");
100//! let ast = parser.parse().expect("should parse");
101//! assert!(matches!(ast.kind, NodeKind::Program { .. }));
102//! ```
103
104// Re-export SourceLocation from perl-position-tracking for unified span handling
105pub use perl_position_tracking::SourceLocation;
106// Re-export Token and TokenKind from perl-token for AST error nodes
107pub use perl_token::{Token, TokenKind};
108use std::fmt;
109use strum::VariantNames as _;
110
111/// Core AST node representing any Perl language construct within parsing workflows.
112///
113/// This is the fundamental building block for representing parsed Perl code. Each node
114/// contains both the semantic information (kind) and positional information (location)
115/// necessary for comprehensive script analysis.
116///
117/// # LSP Workflow Role
118///
119/// Nodes flow through tooling stages:
120/// - **Parse**: Created by the parser as it builds the syntax tree
121/// - **Index**: Visited to build symbol and reference tables
122/// - **Navigate**: Used to resolve definitions, references, and call hierarchy
123/// - **Complete**: Provides contextual information for completion and hover
124/// - **Analyze**: Drives semantic analysis and diagnostics
125///
126/// # Memory Optimization
127///
128/// The structure is designed for efficient memory usage during large-scale parsing:
129/// - `SourceLocation` uses compact position encoding for large files
130/// - `NodeKind` enum variants minimize memory overhead for common constructs
131/// - Clone operations are optimized for shared analysis workflows
132///
133/// # Examples
134///
135/// Construct a variable declaration node manually:
136///
137/// ```
138/// use perl_ast::{Node, NodeKind, SourceLocation};
139///
140/// let loc = SourceLocation { start: 0, end: 11 };
141/// let var = Node::new(
142///     NodeKind::Variable { sigil: "$".to_string(), name: "x".to_string() },
143///     loc,
144/// );
145/// let decl = Node::new(
146///     NodeKind::VariableDeclaration {
147///         declarator: "my".to_string(),
148///         variable: Box::new(var),
149///         attributes: vec![],
150///         initializer: None,
151///     },
152///     loc,
153/// );
154/// assert_eq!(decl.kind.kind_name(), "VariableDeclaration");
155/// ```
156///
157/// Typically you obtain nodes from the parser rather than constructing them by hand:
158///
159/// ```ignore
160/// use perl_parser::Parser;
161///
162/// let mut parser = Parser::new("my $x = 42;");
163/// let ast = parser.parse()?;
164/// println!("AST: {}", ast.to_sexp());
165/// ```
166#[derive(Debug, Clone, PartialEq)]
167pub struct Node {
168    /// The specific type and semantic content of this AST node
169    pub kind: NodeKind,
170    /// Source position information for error reporting and code navigation
171    pub location: SourceLocation,
172}
173
174impl Node {
175    /// Create a new AST node with the given kind and source location.
176    ///
177    /// # Examples
178    ///
179    /// ```
180    /// use perl_ast::{Node, NodeKind, SourceLocation};
181    ///
182    /// let node = Node::new(
183    ///     NodeKind::Number { value: "42".to_string() },
184    ///     SourceLocation { start: 0, end: 2 },
185    /// );
186    /// assert_eq!(node.kind.kind_name(), "Number");
187    /// assert_eq!(node.location.start, 0);
188    /// ```
189    pub fn new(kind: NodeKind, location: SourceLocation) -> Self {
190        Node { kind, location }
191    }
192
193    /// Convert the AST to a tree-sitter compatible S-expression.
194    ///
195    /// Produces a parenthesized representation compatible with tree-sitter's
196    /// S-expression format, useful for debugging and snapshot testing.
197    ///
198    /// # Examples
199    ///
200    /// ```
201    /// use perl_ast::{Node, NodeKind, SourceLocation};
202    ///
203    /// let loc = SourceLocation { start: 0, end: 2 };
204    /// let num = Node::new(NodeKind::Number { value: "42".to_string() }, loc);
205    /// let program = Node::new(
206    ///     NodeKind::Program { statements: vec![num] },
207    ///     loc,
208    /// );
209    /// let sexp = program.to_sexp();
210    /// assert!(sexp.starts_with("(source_file"));
211    /// ```
212    pub fn to_sexp(&self) -> String {
213        match &self.kind {
214            NodeKind::Program { statements } => {
215                let stmts =
216                    statements.iter().map(|s| s.to_sexp_inner()).collect::<Vec<_>>().join(" ");
217                format!("(source_file {})", stmts)
218            }
219
220            NodeKind::ExpressionStatement { expression } => {
221                format!("(expression_statement {})", expression.to_sexp())
222            }
223
224            NodeKind::VariableDeclaration { declarator, variable, attributes, initializer } => {
225                let attrs_str = if attributes.is_empty() {
226                    String::new()
227                } else {
228                    format!(" (attributes {})", attributes.join(" "))
229                };
230                if let Some(init) = initializer {
231                    format!(
232                        "({}_declaration {}{}{})",
233                        declarator,
234                        variable.to_sexp(),
235                        attrs_str,
236                        init.to_sexp()
237                    )
238                } else {
239                    format!("({}_declaration {}{})", declarator, variable.to_sexp(), attrs_str)
240                }
241            }
242
243            NodeKind::VariableListDeclaration {
244                declarator,
245                variables,
246                attributes,
247                initializer,
248            } => {
249                let vars = variables.iter().map(|v| v.to_sexp()).collect::<Vec<_>>().join(" ");
250                let attrs_str = if attributes.is_empty() {
251                    String::new()
252                } else {
253                    format!(" (attributes {})", attributes.join(" "))
254                };
255                if let Some(init) = initializer {
256                    format!(
257                        "({}_declaration ({}){}{})",
258                        declarator,
259                        vars,
260                        attrs_str,
261                        init.to_sexp()
262                    )
263                } else {
264                    format!("({}_declaration ({}){})", declarator, vars, attrs_str)
265                }
266            }
267
268            NodeKind::NestedVariableList { items } => {
269                let item_sexps = items.iter().map(|i| i.to_sexp()).collect::<Vec<_>>().join(" ");
270                format!("(nested_variable_list {})", item_sexps)
271            }
272
273            NodeKind::Variable { sigil, name } => {
274                // Format expected by bless parsing tests: (variable $ name)
275                format!("(variable {} {})", sigil, name)
276            }
277
278            NodeKind::VariableWithAttributes { variable, attributes } => {
279                let attrs = attributes.join(" ");
280                format!("({} (attributes {}))", variable.to_sexp(), attrs)
281            }
282
283            NodeKind::Assignment { lhs, rhs, op } => {
284                format!(
285                    "(assignment_{} {} {})",
286                    op.replace("=", "assign"),
287                    lhs.to_sexp(),
288                    rhs.to_sexp()
289                )
290            }
291
292            NodeKind::Binary { op, left, right } => {
293                // Tree-sitter format: (binary_op left right)
294                let op_name = format_binary_operator(op);
295                format!("({} {} {})", op_name, left.to_sexp(), right.to_sexp())
296            }
297
298            NodeKind::Ternary { condition, then_expr, else_expr } => {
299                format!(
300                    "(ternary {} {} {})",
301                    condition.to_sexp(),
302                    then_expr.to_sexp(),
303                    else_expr.to_sexp()
304                )
305            }
306
307            NodeKind::Unary { op, operand } => {
308                // Tree-sitter format: (unary_op operand)
309                let op_name = format_unary_operator(op);
310                format!("({} {})", op_name, operand.to_sexp())
311            }
312
313            NodeKind::Diamond => "(diamond)".to_string(),
314
315            NodeKind::Ellipsis => "(ellipsis)".to_string(),
316
317            NodeKind::Undef => "(undef)".to_string(),
318
319            NodeKind::Readline { filehandle } => {
320                if let Some(fh) = filehandle {
321                    format!("(readline {})", fh)
322                } else {
323                    "(readline)".to_string()
324                }
325            }
326
327            NodeKind::Glob { pattern } => {
328                format!("(glob {})", pattern)
329            }
330            NodeKind::Typeglob { name } => {
331                format!("(typeglob {})", name)
332            }
333
334            NodeKind::Number { value } => {
335                // Format expected by bless parsing tests: (number value)
336                format!("(number {})", value)
337            }
338
339            NodeKind::String { value, interpolated } => {
340                // Escape quotes in string value to prevent S-expression parsing issues
341                let escaped_value = value.replace('\\', "\\\\").replace('"', "\\\"");
342
343                // Format based on interpolation status
344                if *interpolated {
345                    format!("(string_interpolated \"{}\")", escaped_value)
346                } else {
347                    format!("(string \"{}\")", escaped_value)
348                }
349            }
350
351            NodeKind::Heredoc { delimiter, content, interpolated, indented, command, .. } => {
352                let type_str = if *command {
353                    "heredoc_command"
354                } else if *indented {
355                    if *interpolated { "heredoc_indented_interpolated" } else { "heredoc_indented" }
356                } else if *interpolated {
357                    "heredoc_interpolated"
358                } else {
359                    "heredoc"
360                };
361                format!("({} {:?} {:?})", type_str, delimiter, content)
362            }
363
364            NodeKind::ArrayLiteral { elements } => {
365                let elems = elements.iter().map(|e| e.to_sexp()).collect::<Vec<_>>().join(" ");
366                format!("(array {})", elems)
367            }
368
369            NodeKind::HashLiteral { pairs } => {
370                let kvs = pairs
371                    .iter()
372                    .map(|(k, v)| format!("({} {})", k.to_sexp(), v.to_sexp()))
373                    .collect::<Vec<_>>()
374                    .join(" ");
375                format!("(hash {})", kvs)
376            }
377
378            NodeKind::Block { statements } => {
379                let stmts = statements.iter().map(|s| s.to_sexp()).collect::<Vec<_>>().join(" ");
380                format!("(block {})", stmts)
381            }
382
383            NodeKind::Eval { block } => {
384                format!("(eval {})", block.to_sexp())
385            }
386
387            NodeKind::Do { block } => {
388                format!("(do {})", block.to_sexp())
389            }
390
391            NodeKind::Defer { block } => {
392                format!("(defer {})", block.to_sexp())
393            }
394
395            NodeKind::Try { body, catch_blocks, finally_block } => {
396                let mut parts = vec![format!("(try {})", body.to_sexp())];
397
398                for (var, block) in catch_blocks {
399                    if let Some(v) = var {
400                        parts.push(format!("(catch {} {})", v, block.to_sexp()));
401                    } else {
402                        parts.push(format!("(catch {})", block.to_sexp()));
403                    }
404                }
405
406                if let Some(finally) = finally_block {
407                    parts.push(format!("(finally {})", finally.to_sexp()));
408                }
409
410                parts.join(" ")
411            }
412
413            NodeKind::If { condition, then_branch, elsif_branches, else_branch, keyword } => {
414                let kw = keyword.as_deref().unwrap_or("if");
415                let mut parts =
416                    vec![format!("({} {} {})", kw, condition.to_sexp(), then_branch.to_sexp())];
417
418                for (cond, block) in elsif_branches {
419                    parts.push(format!("(elsif {} {})", cond.to_sexp(), block.to_sexp()));
420                }
421
422                if let Some(else_block) = else_branch {
423                    parts.push(format!("(else {})", else_block.to_sexp()));
424                }
425
426                parts.join(" ")
427            }
428
429            NodeKind::LabeledStatement { label, statement } => {
430                format!("(labeled_statement {} {})", label, statement.to_sexp())
431            }
432
433            NodeKind::While { condition, body, continue_block, keyword } => {
434                let kw = keyword.as_deref().unwrap_or("while");
435                let mut s = format!("({} {} {})", kw, condition.to_sexp(), body.to_sexp());
436                if let Some(cont) = continue_block {
437                    s.push_str(&format!(" (continue {})", cont.to_sexp()));
438                }
439                s
440            }
441            NodeKind::Tie { variable, package, args } => {
442                let mut s = format!("(tie {} {}", variable.to_sexp(), package.to_sexp());
443                for arg in args {
444                    s.push_str(&format!(" {}", arg.to_sexp()));
445                }
446                s.push(')');
447                s
448            }
449            NodeKind::Untie { variable } => {
450                format!("(untie {})", variable.to_sexp())
451            }
452            NodeKind::For { init, condition, update, body, continue_block } => {
453                let init_str =
454                    init.as_ref().map(|i| i.to_sexp()).unwrap_or_else(|| "()".to_string());
455                let cond_str =
456                    condition.as_ref().map(|c| c.to_sexp()).unwrap_or_else(|| "()".to_string());
457                let update_str =
458                    update.as_ref().map(|u| u.to_sexp()).unwrap_or_else(|| "()".to_string());
459                let mut result =
460                    format!("(for {} {} {} {})", init_str, cond_str, update_str, body.to_sexp());
461                if let Some(cont) = continue_block {
462                    result.push_str(&format!(" (continue {})", cont.to_sexp()));
463                }
464                result
465            }
466
467            NodeKind::Foreach { variable, list, body, continue_block } => {
468                let cont = if let Some(cb) = continue_block {
469                    format!(" {}", cb.to_sexp())
470                } else {
471                    String::new()
472                };
473                format!(
474                    "(foreach {} {} {}{})",
475                    variable.to_sexp(),
476                    list.to_sexp(),
477                    body.to_sexp(),
478                    cont
479                )
480            }
481
482            NodeKind::Given { expr, body } => {
483                format!("(given {} {})", expr.to_sexp(), body.to_sexp())
484            }
485
486            NodeKind::When { condition, body } => {
487                format!("(when {} {})", condition.to_sexp(), body.to_sexp())
488            }
489
490            NodeKind::Default { body } => {
491                format!("(default {})", body.to_sexp())
492            }
493
494            NodeKind::StatementModifier { statement, modifier, condition } => {
495                format!(
496                    "(statement_modifier_{} {} {})",
497                    modifier,
498                    statement.to_sexp(),
499                    condition.to_sexp()
500                )
501            }
502
503            NodeKind::Subroutine {
504                name,
505                prototype,
506                signature,
507                attributes,
508                body,
509                name_span: _,
510                declarator: _,
511            } => {
512                if let Some(sub_name) = name {
513                    // Named subroutine - bless test expected format: (sub name () block)
514                    let mut parts = vec![sub_name.clone()];
515
516                    // Add attributes if present (before prototype/signature)
517                    if !attributes.is_empty() {
518                        for attr in attributes {
519                            parts.push(format!(":{}", attr));
520                        }
521                    }
522
523                    // Add prototype/signature - use () for empty prototype
524                    if let Some(proto) = prototype {
525                        parts.push(format!("({})", proto.to_sexp()));
526                    } else if signature.is_some() {
527                        // If there's a signature but no prototype, still show ()
528                        parts.push("()".to_string());
529                    } else {
530                        parts.push("()".to_string());
531                    }
532
533                    // Add body
534                    parts.push(body.to_sexp());
535
536                    // Format: (sub name [attrs...] ()(block ...)) - space between name and (), no space between () and block
537                    if parts.len() >= 3 && parts[parts.len() - 2] == "()" {
538                        let name_and_attrs = parts[0..parts.len() - 2].join(" ");
539                        let proto = &parts[parts.len() - 2];
540                        let body = &parts[parts.len() - 1];
541                        format!("(sub {} {}{})", name_and_attrs, proto, body)
542                    } else {
543                        format!("(sub {})", parts.join(" "))
544                    }
545                } else {
546                    // Anonymous subroutine - tree-sitter format
547                    let mut parts = Vec::new();
548
549                    // Add attributes if present
550                    if !attributes.is_empty() {
551                        let attrs: Vec<String> = attributes
552                            .iter()
553                            .map(|_attr| "(attribute (attribute_name))".to_string())
554                            .collect();
555                        parts.push(format!("(attrlist {})", attrs.join("")));
556                    }
557
558                    // Add prototype if present
559                    if let Some(proto) = prototype {
560                        parts.push(proto.to_sexp());
561                    }
562
563                    // Add signature if present
564                    if let Some(sig) = signature {
565                        parts.push(sig.to_sexp());
566                    }
567
568                    // Add body
569                    parts.push(body.to_sexp());
570
571                    format!("(anonymous_subroutine_expression {})", parts.join(""))
572                }
573            }
574
575            NodeKind::Prototype { content: _ } => "(prototype)".to_string(),
576
577            NodeKind::Signature { parameters } => {
578                let params = parameters.iter().map(|p| p.to_sexp()).collect::<Vec<_>>().join(" ");
579                format!("(signature {})", params)
580            }
581
582            NodeKind::MandatoryParameter { variable } => {
583                format!("(mandatory_parameter {})", variable.to_sexp())
584            }
585
586            NodeKind::OptionalParameter { variable, default_value } => {
587                format!("(optional_parameter {} {})", variable.to_sexp(), default_value.to_sexp())
588            }
589
590            NodeKind::SlurpyParameter { variable } => {
591                format!("(slurpy_parameter {})", variable.to_sexp())
592            }
593
594            NodeKind::NamedParameter { variable } => {
595                format!("(named_parameter {})", variable.to_sexp())
596            }
597
598            NodeKind::Method { name: _, name_span: _, signature, attributes, body } => {
599                let block_contents = match &body.kind {
600                    NodeKind::Block { statements } => {
601                        statements.iter().map(|s| s.to_sexp()).collect::<Vec<_>>().join(" ")
602                    }
603                    _ => body.to_sexp(),
604                };
605
606                let mut parts = vec!["(bareword)".to_string()];
607
608                // Add signature if present
609                if let Some(sig) = signature {
610                    parts.push(sig.to_sexp());
611                }
612
613                // Add attributes if present
614                if !attributes.is_empty() {
615                    let attrs: Vec<String> = attributes
616                        .iter()
617                        .map(|_attr| "(attribute (attribute_name))".to_string())
618                        .collect();
619                    parts.push(format!("(attrlist {})", attrs.join("")));
620                }
621
622                parts.push(format!("(block {})", block_contents));
623                format!("(method_declaration_statement {})", parts.join(" "))
624            }
625
626            NodeKind::Return { value } => {
627                if let Some(val) = value {
628                    format!("(return {})", val.to_sexp())
629                } else {
630                    "(return)".to_string()
631                }
632            }
633
634            NodeKind::LoopControl { op, label } => {
635                if let Some(l) = label {
636                    format!("({} {})", op, l)
637                } else {
638                    format!("({})", op)
639                }
640            }
641
642            NodeKind::Goto { target } => {
643                format!("(goto {})", target.to_sexp())
644            }
645
646            NodeKind::MethodCall { object, method, args } => {
647                let args_str = args.iter().map(|a| a.to_sexp()).collect::<Vec<_>>().join(" ");
648                format!("(method_call {} {} ({}))", object.to_sexp(), method, args_str)
649            }
650
651            NodeKind::FunctionCall { name, args } => {
652                // Special handling for functions that should use call format in tree-sitter tests
653                if matches!(
654                    name.as_str(),
655                    "bless"
656                        | "shift"
657                        | "unshift"
658                        | "open"
659                        | "die"
660                        | "warn"
661                        | "print"
662                        | "printf"
663                        | "say"
664                        | "push"
665                        | "pop"
666                        | "map"
667                        | "sort"
668                        | "grep"
669                        | "keys"
670                        | "values"
671                        | "each"
672                        | "defined"
673                        | "scalar"
674                        | "ref"
675                ) {
676                    let args_str = args.iter().map(|a| a.to_sexp()).collect::<Vec<_>>().join(" ");
677                    if args.is_empty() {
678                        format!("(call {} ())", name)
679                    } else {
680                        format!("(call {} ({}))", name, args_str)
681                    }
682                } else {
683                    // Tree-sitter format varies by context
684                    let args_str = args.iter().map(|a| a.to_sexp()).collect::<Vec<_>>().join(" ");
685                    if args.is_empty() {
686                        "(function_call_expression (function))".to_string()
687                    } else {
688                        format!("(ambiguous_function_call_expression (function) {})", args_str)
689                    }
690                }
691            }
692
693            NodeKind::IndirectCall { method, object, args } => {
694                let args_str = args.iter().map(|a| a.to_sexp()).collect::<Vec<_>>().join(" ");
695                format!("(indirect_call {} {} ({}))", method, object.to_sexp(), args_str)
696            }
697
698            NodeKind::Regex { pattern, replacement, modifiers, has_embedded_code } => {
699                let risk_marker = if *has_embedded_code { " (risk:code)" } else { "" };
700                format!("(regex {:?} {:?} {:?}{})", pattern, replacement, modifiers, risk_marker)
701            }
702
703            NodeKind::Match { expr, pattern, modifiers, has_embedded_code, negated } => {
704                let risk_marker = if *has_embedded_code { " (risk:code)" } else { "" };
705                let op = if *negated { "not_match" } else { "match" };
706                format!(
707                    "({} {} (regex {:?} {:?}{}))",
708                    op,
709                    expr.to_sexp(),
710                    pattern,
711                    modifiers,
712                    risk_marker
713                )
714            }
715
716            NodeKind::Substitution {
717                expr,
718                pattern,
719                replacement,
720                modifiers,
721                has_embedded_code,
722                negated,
723            } => {
724                let risk_marker = if *has_embedded_code { " (risk:code)" } else { "" };
725                let neg_marker = if *negated { " (negated)" } else { "" };
726                format!(
727                    "(substitution {} {:?} {:?} {:?}{}{})",
728                    expr.to_sexp(),
729                    pattern,
730                    replacement,
731                    modifiers,
732                    risk_marker,
733                    neg_marker
734                )
735            }
736
737            NodeKind::Transliteration { expr, search, replace, modifiers, negated } => {
738                let neg_marker = if *negated { " (negated)" } else { "" };
739                format!(
740                    "(transliteration {} {:?} {:?} {:?}{})",
741                    expr.to_sexp(),
742                    search,
743                    replace,
744                    modifiers,
745                    neg_marker
746                )
747            }
748
749            NodeKind::Package { name, block, name_span: _ } => {
750                if let Some(blk) = block {
751                    format!("(package {} {})", name, blk.to_sexp())
752                } else {
753                    format!("(package {})", name)
754                }
755            }
756
757            NodeKind::Use { module, args, has_filter_risk } => {
758                let risk_marker = if *has_filter_risk { " (risk:filter)" } else { "" };
759                if args.is_empty() {
760                    format!("(use {}{})", module, risk_marker)
761                } else {
762                    let args_str = args.join(" ");
763                    format!("(use {} ({}){})", module, args_str, risk_marker)
764                }
765            }
766
767            NodeKind::No { module, args, has_filter_risk } => {
768                let risk_marker = if *has_filter_risk { " (risk:filter)" } else { "" };
769                if args.is_empty() {
770                    format!("(no {}{})", module, risk_marker)
771                } else {
772                    let args_str = args.join(" ");
773                    format!("(no {} ({}){})", module, args_str, risk_marker)
774                }
775            }
776
777            NodeKind::PhaseBlock { phase, phase_span: _, block } => {
778                format!("({} {})", phase, block.to_sexp())
779            }
780
781            NodeKind::DataSection { marker, body } => {
782                if let Some(body_text) = body {
783                    format!("(data_section {} \"{}\")", marker, body_text.escape_default())
784                } else {
785                    format!("(data_section {})", marker)
786                }
787            }
788
789            NodeKind::Class { name, name_span: _, parents, body } => {
790                if parents.is_empty() {
791                    format!("(class {} {})", name, body.to_sexp())
792                } else {
793                    format!("(class {} :isa({}) {})", name, parents.join(","), body.to_sexp())
794                }
795            }
796
797            NodeKind::Format { name, name_span: _, body } => {
798                format!("(format {} {:?})", name, body)
799            }
800
801            NodeKind::Identifier { name } => {
802                // Format expected by tests: (identifier name)
803                format!("(identifier {})", name)
804            }
805
806            NodeKind::Error { message, partial, .. } => {
807                if let Some(node) = partial {
808                    format!("(ERROR \"{}\" {})", message.escape_default(), node.to_sexp())
809                } else {
810                    format!("(ERROR \"{}\")", message.escape_default())
811                }
812            }
813            NodeKind::MissingExpression => "(missing_expression)".to_string(),
814            NodeKind::MissingStatement => "(missing_statement)".to_string(),
815            NodeKind::MissingIdentifier => "(missing_identifier)".to_string(),
816            NodeKind::MissingBlock => "(missing_block)".to_string(),
817            NodeKind::UnknownRest => "(UNKNOWN_REST)".to_string(),
818        }
819    }
820
821    /// Convert the AST to S-expression format that unwraps expression statements in programs
822    pub fn to_sexp_inner(&self) -> String {
823        match &self.kind {
824            NodeKind::ExpressionStatement { expression } => {
825                // Check if this is an anonymous subroutine - if so, keep it wrapped
826                match &expression.kind {
827                    NodeKind::Subroutine { name, .. } if name.is_none() => {
828                        // Anonymous subroutine should remain wrapped in expression statement
829                        self.to_sexp()
830                    }
831                    _ => {
832                        // In the inner format, other expression statements are unwrapped
833                        expression.to_sexp()
834                    }
835                }
836            }
837            _ => {
838                // For all other node types, use regular to_sexp
839                self.to_sexp()
840            }
841        }
842    }
843
844    /// Call a function on every direct child node of this node.
845    ///
846    /// This enables depth-first traversal for operations like heredoc content attachment.
847    /// The closure receives a mutable reference to each child node.
848    #[inline]
849    pub fn for_each_child_mut<F: FnMut(&mut Node)>(&mut self, mut f: F) {
850        match &mut self.kind {
851            NodeKind::Tie { variable, package, args } => {
852                f(variable);
853                f(package);
854                for arg in args {
855                    f(arg);
856                }
857            }
858            NodeKind::Untie { variable } => f(variable),
859
860            // Root program node
861            NodeKind::Program { statements } => {
862                for stmt in statements {
863                    f(stmt);
864                }
865            }
866
867            // Statement wrappers
868            NodeKind::ExpressionStatement { expression } => f(expression),
869
870            // Variable declarations
871            NodeKind::VariableDeclaration { variable, initializer, .. } => {
872                f(variable);
873                if let Some(init) = initializer {
874                    f(init);
875                }
876            }
877            NodeKind::VariableListDeclaration { variables, initializer, .. } => {
878                for var in variables {
879                    f(var);
880                }
881                if let Some(init) = initializer {
882                    f(init);
883                }
884            }
885            NodeKind::NestedVariableList { items } => {
886                for item in items {
887                    f(item);
888                }
889            }
890            NodeKind::VariableWithAttributes { variable, .. } => f(variable),
891
892            // Binary operations
893            NodeKind::Binary { left, right, .. } => {
894                f(left);
895                f(right);
896            }
897            NodeKind::Ternary { condition, then_expr, else_expr } => {
898                f(condition);
899                f(then_expr);
900                f(else_expr);
901            }
902            NodeKind::Unary { operand, .. } => f(operand),
903            NodeKind::Assignment { lhs, rhs, .. } => {
904                f(lhs);
905                f(rhs);
906            }
907
908            // Control flow
909            NodeKind::Block { statements } => {
910                for stmt in statements {
911                    f(stmt);
912                }
913            }
914            NodeKind::If { condition, then_branch, elsif_branches, else_branch, .. } => {
915                f(condition);
916                f(then_branch);
917                for (elsif_cond, elsif_body) in elsif_branches {
918                    f(elsif_cond);
919                    f(elsif_body);
920                }
921                if let Some(else_body) = else_branch {
922                    f(else_body);
923                }
924            }
925            NodeKind::While { condition, body, continue_block, .. } => {
926                f(condition);
927                f(body);
928                if let Some(cont) = continue_block {
929                    f(cont);
930                }
931            }
932            NodeKind::For { init, condition, update, body, continue_block, .. } => {
933                if let Some(i) = init {
934                    f(i);
935                }
936                if let Some(c) = condition {
937                    f(c);
938                }
939                if let Some(u) = update {
940                    f(u);
941                }
942                f(body);
943                if let Some(cont) = continue_block {
944                    f(cont);
945                }
946            }
947            NodeKind::Foreach { variable, list, body, continue_block } => {
948                f(variable);
949                f(list);
950                f(body);
951                if let Some(cb) = continue_block {
952                    f(cb);
953                }
954            }
955            NodeKind::Given { expr, body } => {
956                f(expr);
957                f(body);
958            }
959            NodeKind::When { condition, body } => {
960                f(condition);
961                f(body);
962            }
963            NodeKind::Default { body } => f(body),
964            NodeKind::StatementModifier { statement, condition, .. } => {
965                f(statement);
966                f(condition);
967            }
968            NodeKind::LabeledStatement { statement, .. } => f(statement),
969
970            // Eval and Do blocks
971            NodeKind::Eval { block } => f(block),
972            NodeKind::Do { block } => f(block),
973            NodeKind::Defer { block } => f(block),
974            NodeKind::Try { body, catch_blocks, finally_block } => {
975                f(body);
976                for (_, catch_body) in catch_blocks {
977                    f(catch_body);
978                }
979                if let Some(finally) = finally_block {
980                    f(finally);
981                }
982            }
983
984            // Function calls
985            NodeKind::FunctionCall { args, .. } => {
986                for arg in args {
987                    f(arg);
988                }
989            }
990            NodeKind::MethodCall { object, args, .. } => {
991                f(object);
992                for arg in args {
993                    f(arg);
994                }
995            }
996            NodeKind::IndirectCall { object, args, .. } => {
997                f(object);
998                for arg in args {
999                    f(arg);
1000                }
1001            }
1002
1003            // Functions
1004            NodeKind::Subroutine { prototype, signature, body, .. } => {
1005                if let Some(proto) = prototype {
1006                    f(proto);
1007                }
1008                if let Some(sig) = signature {
1009                    f(sig);
1010                }
1011                f(body);
1012            }
1013            NodeKind::Method { signature, body, .. } => {
1014                if let Some(sig) = signature {
1015                    f(sig);
1016                }
1017                f(body);
1018            }
1019            NodeKind::Return { value } => {
1020                if let Some(v) = value {
1021                    f(v);
1022                }
1023            }
1024            NodeKind::Goto { target } => f(target),
1025            NodeKind::Signature { parameters } => {
1026                for param in parameters {
1027                    f(param);
1028                }
1029            }
1030            NodeKind::MandatoryParameter { variable } => f(variable),
1031            NodeKind::OptionalParameter { variable, default_value } => {
1032                f(variable);
1033                f(default_value);
1034            }
1035            NodeKind::SlurpyParameter { variable } => f(variable),
1036            NodeKind::NamedParameter { variable } => f(variable),
1037
1038            // Pattern matching
1039            NodeKind::Match { expr, .. } => f(expr),
1040            NodeKind::Substitution { expr, .. } => f(expr),
1041            NodeKind::Transliteration { expr, .. } => f(expr),
1042
1043            // Containers
1044            NodeKind::ArrayLiteral { elements } => {
1045                for elem in elements {
1046                    f(elem);
1047                }
1048            }
1049            NodeKind::HashLiteral { pairs } => {
1050                for (key, value) in pairs {
1051                    f(key);
1052                    f(value);
1053                }
1054            }
1055
1056            // Package system
1057            NodeKind::Package { block, .. } => {
1058                if let Some(b) = block {
1059                    f(b);
1060                }
1061            }
1062            NodeKind::PhaseBlock { block, .. } => f(block),
1063            NodeKind::Class { body, .. } => f(body),
1064
1065            // Error node might have a partial valid tree
1066            NodeKind::Error { partial, .. } => {
1067                if let Some(node) = partial {
1068                    f(node);
1069                }
1070            }
1071
1072            // Leaf nodes (no children to traverse)
1073            NodeKind::Variable { .. }
1074            | NodeKind::Identifier { .. }
1075            | NodeKind::Number { .. }
1076            | NodeKind::String { .. }
1077            | NodeKind::Heredoc { .. }
1078            | NodeKind::Regex { .. }
1079            | NodeKind::Readline { .. }
1080            | NodeKind::Glob { .. }
1081            | NodeKind::Typeglob { .. }
1082            | NodeKind::Diamond
1083            | NodeKind::Ellipsis
1084            | NodeKind::Undef
1085            | NodeKind::Use { .. }
1086            | NodeKind::No { .. }
1087            | NodeKind::Prototype { .. }
1088            | NodeKind::DataSection { .. }
1089            | NodeKind::Format { .. }
1090            | NodeKind::LoopControl { .. }
1091            | NodeKind::MissingExpression
1092            | NodeKind::MissingStatement
1093            | NodeKind::MissingIdentifier
1094            | NodeKind::MissingBlock
1095            | NodeKind::UnknownRest => {}
1096        }
1097    }
1098
1099    /// Call a function on every direct child node of this node (immutable version).
1100    ///
1101    /// This enables depth-first traversal for read-only operations like AST analysis.
1102    /// The closure receives an immutable reference to each child node.
1103    #[inline]
1104    pub fn for_each_child<'a, F: FnMut(&'a Node)>(&'a self, mut f: F) {
1105        match &self.kind {
1106            NodeKind::Tie { variable, package, args } => {
1107                f(variable);
1108                f(package);
1109                for arg in args {
1110                    f(arg);
1111                }
1112            }
1113            NodeKind::Untie { variable } => f(variable),
1114
1115            // Root program node
1116            NodeKind::Program { statements } => {
1117                for stmt in statements {
1118                    f(stmt);
1119                }
1120            }
1121
1122            // Statement wrappers
1123            NodeKind::ExpressionStatement { expression } => f(expression),
1124
1125            // Variable declarations
1126            NodeKind::VariableDeclaration { variable, initializer, .. } => {
1127                f(variable);
1128                if let Some(init) = initializer {
1129                    f(init);
1130                }
1131            }
1132            NodeKind::VariableListDeclaration { variables, initializer, .. } => {
1133                for var in variables {
1134                    f(var);
1135                }
1136                if let Some(init) = initializer {
1137                    f(init);
1138                }
1139            }
1140            NodeKind::NestedVariableList { items } => {
1141                for item in items {
1142                    f(item);
1143                }
1144            }
1145            NodeKind::VariableWithAttributes { variable, .. } => f(variable),
1146
1147            // Binary operations
1148            NodeKind::Binary { left, right, .. } => {
1149                f(left);
1150                f(right);
1151            }
1152            NodeKind::Ternary { condition, then_expr, else_expr } => {
1153                f(condition);
1154                f(then_expr);
1155                f(else_expr);
1156            }
1157            NodeKind::Unary { operand, .. } => f(operand),
1158            NodeKind::Assignment { lhs, rhs, .. } => {
1159                f(lhs);
1160                f(rhs);
1161            }
1162
1163            // Control flow
1164            NodeKind::Block { statements } => {
1165                for stmt in statements {
1166                    f(stmt);
1167                }
1168            }
1169            NodeKind::If { condition, then_branch, elsif_branches, else_branch, .. } => {
1170                f(condition);
1171                f(then_branch);
1172                for (elsif_cond, elsif_body) in elsif_branches {
1173                    f(elsif_cond);
1174                    f(elsif_body);
1175                }
1176                if let Some(else_body) = else_branch {
1177                    f(else_body);
1178                }
1179            }
1180            NodeKind::While { condition, body, continue_block, .. } => {
1181                f(condition);
1182                f(body);
1183                if let Some(cont) = continue_block {
1184                    f(cont);
1185                }
1186            }
1187            NodeKind::For { init, condition, update, body, continue_block, .. } => {
1188                if let Some(i) = init {
1189                    f(i);
1190                }
1191                if let Some(c) = condition {
1192                    f(c);
1193                }
1194                if let Some(u) = update {
1195                    f(u);
1196                }
1197                f(body);
1198                if let Some(cont) = continue_block {
1199                    f(cont);
1200                }
1201            }
1202            NodeKind::Foreach { variable, list, body, continue_block } => {
1203                f(variable);
1204                f(list);
1205                f(body);
1206                if let Some(cb) = continue_block {
1207                    f(cb);
1208                }
1209            }
1210            NodeKind::Given { expr, body } => {
1211                f(expr);
1212                f(body);
1213            }
1214            NodeKind::When { condition, body } => {
1215                f(condition);
1216                f(body);
1217            }
1218            NodeKind::Default { body } => f(body),
1219            NodeKind::StatementModifier { statement, condition, .. } => {
1220                f(statement);
1221                f(condition);
1222            }
1223            NodeKind::LabeledStatement { statement, .. } => f(statement),
1224
1225            // Eval and Do blocks
1226            NodeKind::Eval { block } => f(block),
1227            NodeKind::Do { block } => f(block),
1228            NodeKind::Defer { block } => f(block),
1229            NodeKind::Try { body, catch_blocks, finally_block } => {
1230                f(body);
1231                for (_, catch_body) in catch_blocks {
1232                    f(catch_body);
1233                }
1234                if let Some(finally) = finally_block {
1235                    f(finally);
1236                }
1237            }
1238
1239            // Function calls
1240            NodeKind::FunctionCall { args, .. } => {
1241                for arg in args {
1242                    f(arg);
1243                }
1244            }
1245            NodeKind::MethodCall { object, args, .. } => {
1246                f(object);
1247                for arg in args {
1248                    f(arg);
1249                }
1250            }
1251            NodeKind::IndirectCall { object, args, .. } => {
1252                f(object);
1253                for arg in args {
1254                    f(arg);
1255                }
1256            }
1257
1258            // Functions
1259            NodeKind::Subroutine { prototype, signature, body, .. } => {
1260                if let Some(proto) = prototype {
1261                    f(proto);
1262                }
1263                if let Some(sig) = signature {
1264                    f(sig);
1265                }
1266                f(body);
1267            }
1268            NodeKind::Method { signature, body, .. } => {
1269                if let Some(sig) = signature {
1270                    f(sig);
1271                }
1272                f(body);
1273            }
1274            NodeKind::Return { value } => {
1275                if let Some(v) = value {
1276                    f(v);
1277                }
1278            }
1279            NodeKind::Goto { target } => f(target),
1280            NodeKind::Signature { parameters } => {
1281                for param in parameters {
1282                    f(param);
1283                }
1284            }
1285            NodeKind::MandatoryParameter { variable } => f(variable),
1286            NodeKind::OptionalParameter { variable, default_value } => {
1287                f(variable);
1288                f(default_value);
1289            }
1290            NodeKind::SlurpyParameter { variable } => f(variable),
1291            NodeKind::NamedParameter { variable } => f(variable),
1292
1293            // Pattern matching
1294            NodeKind::Match { expr, .. } => f(expr),
1295            NodeKind::Substitution { expr, .. } => f(expr),
1296            NodeKind::Transliteration { expr, .. } => f(expr),
1297
1298            // Containers
1299            NodeKind::ArrayLiteral { elements } => {
1300                for elem in elements {
1301                    f(elem);
1302                }
1303            }
1304            NodeKind::HashLiteral { pairs } => {
1305                for (key, value) in pairs {
1306                    f(key);
1307                    f(value);
1308                }
1309            }
1310
1311            // Package system
1312            NodeKind::Package { block, .. } => {
1313                if let Some(b) = block {
1314                    f(b);
1315                }
1316            }
1317            NodeKind::PhaseBlock { block, .. } => f(block),
1318            NodeKind::Class { body, .. } => f(body),
1319
1320            // Error node might have a partial valid tree
1321            NodeKind::Error { partial, .. } => {
1322                if let Some(node) = partial {
1323                    f(node);
1324                }
1325            }
1326
1327            // Leaf nodes (no children to traverse)
1328            NodeKind::Variable { .. }
1329            | NodeKind::Identifier { .. }
1330            | NodeKind::Number { .. }
1331            | NodeKind::String { .. }
1332            | NodeKind::Heredoc { .. }
1333            | NodeKind::Regex { .. }
1334            | NodeKind::Readline { .. }
1335            | NodeKind::Glob { .. }
1336            | NodeKind::Typeglob { .. }
1337            | NodeKind::Diamond
1338            | NodeKind::Ellipsis
1339            | NodeKind::Undef
1340            | NodeKind::Use { .. }
1341            | NodeKind::No { .. }
1342            | NodeKind::Prototype { .. }
1343            | NodeKind::DataSection { .. }
1344            | NodeKind::Format { .. }
1345            | NodeKind::LoopControl { .. }
1346            | NodeKind::MissingExpression
1347            | NodeKind::MissingStatement
1348            | NodeKind::MissingIdentifier
1349            | NodeKind::MissingBlock
1350            | NodeKind::UnknownRest => {}
1351        }
1352    }
1353
1354    /// Count the total number of nodes in this subtree (inclusive).
1355    ///
1356    /// # Examples
1357    ///
1358    /// ```
1359    /// use perl_ast::{Node, NodeKind, SourceLocation};
1360    ///
1361    /// let loc = SourceLocation { start: 0, end: 1 };
1362    /// let leaf = Node::new(NodeKind::Number { value: "1".to_string() }, loc);
1363    /// assert_eq!(leaf.count_nodes(), 1);
1364    ///
1365    /// let program = Node::new(
1366    ///     NodeKind::Program { statements: vec![leaf] },
1367    ///     loc,
1368    /// );
1369    /// assert_eq!(program.count_nodes(), 2);
1370    /// ```
1371    pub fn count_nodes(&self) -> usize {
1372        let mut count = 1;
1373        self.for_each_child(|child| {
1374            count += child.count_nodes();
1375        });
1376        count
1377    }
1378
1379    /// Collect direct child nodes into a vector for convenience APIs.
1380    ///
1381    /// # Examples
1382    ///
1383    /// ```
1384    /// use perl_ast::{Node, NodeKind, SourceLocation};
1385    ///
1386    /// let loc = SourceLocation { start: 0, end: 1 };
1387    /// let stmt = Node::new(NodeKind::Number { value: "1".to_string() }, loc);
1388    /// let program = Node::new(
1389    ///     NodeKind::Program { statements: vec![stmt] },
1390    ///     loc,
1391    /// );
1392    /// assert_eq!(program.children().len(), 1);
1393    /// ```
1394    #[inline]
1395    pub fn children(&self) -> Vec<&Node> {
1396        let mut children = Vec::new();
1397        self.for_each_child(|child| children.push(child));
1398        children
1399    }
1400
1401    /// Count direct child nodes without allocating an intermediate vector.
1402    ///
1403    /// This is more efficient than `children().len()` when callers only need
1404    /// cardinality.
1405    #[inline]
1406    pub fn child_count(&self) -> usize {
1407        let mut count = 0;
1408        self.for_each_child(|_| count += 1);
1409        count
1410    }
1411
1412    /// Get the first direct child node, if any.
1413    ///
1414    /// Optimized to avoid allocating the children vector.
1415    #[inline]
1416    pub fn first_child(&self) -> Option<&Node> {
1417        let mut result = None;
1418        self.for_each_child(|child| {
1419            if result.is_none() {
1420                result = Some(child);
1421            }
1422        });
1423        result
1424    }
1425
1426    /// Returns `true` when this node's source span contains `offset`.
1427    ///
1428    /// The start position is inclusive and the end position is exclusive.
1429    #[inline]
1430    pub fn contains_offset(&self, offset: usize) -> bool {
1431        self.location.start <= offset && offset < self.location.end
1432    }
1433
1434    /// Find the most specific node whose source span contains `offset`.
1435    ///
1436    /// Returns `None` when `offset` is outside this node. Otherwise, returns this
1437    /// node or the deepest descendant whose span contains the offset. This is useful
1438    /// for LSP features that need to map a cursor byte offset to the smallest AST
1439    /// construct at that position.
1440    ///
1441    /// The same half-open span semantics as [`Node::contains_offset`] apply: start
1442    /// positions are inclusive and end positions are exclusive.
1443    ///
1444    /// # Examples
1445    ///
1446    /// ```
1447    /// use perl_ast::{Node, NodeKind, SourceLocation};
1448    ///
1449    /// let left = Node::new(
1450    ///     NodeKind::Identifier { name: "left".to_string() },
1451    ///     SourceLocation { start: 0, end: 4 },
1452    /// );
1453    /// let right = Node::new(
1454    ///     NodeKind::Number { value: "1".to_string() },
1455    ///     SourceLocation { start: 7, end: 8 },
1456    /// );
1457    /// let expr = Node::new(
1458    ///     NodeKind::Binary {
1459    ///         op: "+".to_string(),
1460    ///         left: Box::new(left),
1461    ///         right: Box::new(right),
1462    ///     },
1463    ///     SourceLocation { start: 0, end: 8 },
1464    /// );
1465    ///
1466    /// assert_eq!(
1467    ///     expr.find_deepest_containing_offset(7).map(|node| node.kind.kind_name()),
1468    ///     Some("Number"),
1469    /// );
1470    /// assert_eq!(expr.find_deepest_containing_offset(8), None);
1471    /// ```
1472    #[inline]
1473    pub fn find_deepest_containing_offset(&self, offset: usize) -> Option<&Node> {
1474        if !self.contains_offset(offset) {
1475            return None;
1476        }
1477
1478        let mut result = self;
1479        self.for_each_child(|child| {
1480            if let Some(descendant) = child.find_deepest_containing_offset(offset) {
1481                result = descendant;
1482            }
1483        });
1484        Some(result)
1485    }
1486
1487    /// Returns the byte length of this node's source span.
1488    ///
1489    /// Uses saturating subtraction so malformed spans never underflow.
1490    #[inline]
1491    pub fn span_len(&self) -> usize {
1492        self.location.end.saturating_sub(self.location.start)
1493    }
1494
1495    /// Get the last direct child node, if any.
1496    ///
1497    /// Optimized to avoid allocating the children vector.
1498    ///
1499    /// # Examples
1500    ///
1501    /// ```
1502    /// use perl_ast::{Node, NodeKind, SourceLocation};
1503    ///
1504    /// let loc = SourceLocation { start: 0, end: 1 };
1505    /// let first = Node::new(NodeKind::Number { value: "1".to_string() }, loc);
1506    /// let second = Node::new(NodeKind::Number { value: "2".to_string() }, loc);
1507    /// let program = Node::new(
1508    ///     NodeKind::Program { statements: vec![first, second] },
1509    ///     loc,
1510    /// );
1511    ///
1512    /// assert_eq!(program.last_child().map(|n| n.kind.kind_name()), Some("Number"));
1513    /// assert_eq!(Node::new(NodeKind::Block { statements: vec![] }, loc).last_child(), None);
1514    /// ```
1515    #[inline]
1516    pub fn last_child(&self) -> Option<&Node> {
1517        let mut result = None;
1518        self.for_each_child(|child| {
1519            result = Some(child);
1520        });
1521        result
1522    }
1523}
1524
1525/// Comprehensive enumeration of all Perl language constructs supported by the parser.
1526///
1527/// This enum represents every possible AST node type that can be parsed from Perl code
1528/// during the Parse → Index → Navigate → Complete → Analyze workflow. Each variant captures
1529/// the semantic meaning and structural relationships needed for complete script analysis
1530/// and transformation.
1531///
1532/// # LSP Workflow Integration
1533///
1534/// Node kinds are processed differently across workflow stages:
1535/// - **Parse**: All variants are produced by the parser
1536/// - **Index**: Symbol-bearing variants feed workspace indexing
1537/// - **Navigate**: Call and reference variants support navigation features
1538/// - **Complete**: Expression variants provide completion context
1539/// - **Analyze**: Semantic variants drive diagnostics and refactoring
1540///
1541/// # Examples
1542///
1543/// Pattern-match on node kinds to extract semantic information:
1544///
1545/// ```
1546/// use perl_ast::{Node, NodeKind, SourceLocation};
1547///
1548/// let loc = SourceLocation { start: 0, end: 5 };
1549/// let node = Node::new(
1550///     NodeKind::Variable { sigil: "$".to_string(), name: "foo".to_string() },
1551///     loc,
1552/// );
1553///
1554/// assert!(matches!(
1555///     &node.kind,
1556///     NodeKind::Variable { sigil, name } if sigil == "$" && name == "foo"
1557/// ));
1558/// ```
1559///
1560/// Use [`kind_name()`](NodeKind::kind_name) for debugging and diagnostics:
1561///
1562/// ```
1563/// use perl_ast::NodeKind;
1564///
1565/// let kind = NodeKind::Number { value: "99".to_string() };
1566/// assert_eq!(kind.kind_name(), "Number");
1567///
1568/// let kind = NodeKind::Variable { sigil: "@".to_string(), name: "list".to_string() };
1569/// assert_eq!(kind.kind_name(), "Variable");
1570/// ```
1571///
1572/// # Performance Considerations
1573///
1574/// The enum design optimizes for large codebases:
1575/// - Box pointers minimize stack usage for recursive structures
1576/// - Vector storage enables efficient bulk operations on child nodes
1577/// - Clone operations optimized for concurrent analysis workflows
1578/// - Pattern matching performance tuned for common Perl constructs
1579#[derive(Debug, Clone, PartialEq, strum::VariantNames)]
1580pub enum NodeKind {
1581    /// Top-level program containing all statements in an Perl script
1582    ///
1583    /// This is the root node for any parsed Perl script content, containing all
1584    /// top-level statements found during the Parse stage of LSP workflow.
1585    Program {
1586        /// All top-level statements in the Perl script
1587        statements: Vec<Node>,
1588    },
1589
1590    /// Statement wrapper for expressions that appear at statement level
1591    ///
1592    /// Used during Analyze stage to distinguish between expressions used as
1593    /// statements versus expressions within other contexts during Perl parsing.
1594    ExpressionStatement {
1595        /// The expression being used as a statement
1596        expression: Box<Node>,
1597    },
1598
1599    /// Variable declaration with scope declarator in Perl script processing
1600    ///
1601    /// Represents declarations like `my $var`, `our $global`, `local $dynamic`, etc.
1602    /// Critical for Analyze stage symbol table construction during Perl parsing.
1603    VariableDeclaration {
1604        /// Scope declarator: "my", "our", "local", "state"
1605        declarator: String,
1606        /// The variable being declared
1607        variable: Box<Node>,
1608        /// Variable attributes (e.g., ":shared", ":locked")
1609        attributes: Vec<String>,
1610        /// Optional initializer expression
1611        initializer: Option<Box<Node>>,
1612    },
1613
1614    /// Multiple variable declaration in a single statement
1615    ///
1616    /// Handles constructs like `my ($x, $y) = @values` common in Perl script processing.
1617    /// Supports efficient bulk variable analysis during Navigate stage operations.
1618    VariableListDeclaration {
1619        /// Scope declarator for all variables in the list
1620        declarator: String,
1621        /// All variables being declared in the list
1622        variables: Vec<Node>,
1623        /// Attributes applied to the variable list
1624        attributes: Vec<String>,
1625        /// Optional initializer for the entire variable list
1626        initializer: Option<Box<Node>>,
1627    },
1628
1629    /// Nested variable list within a lexical list declaration.
1630    ///
1631    /// Represents a parenthesised group of variables inside a `my`/`our`/`state`
1632    /// list declaration, such as the `($b, $c)` in `my ($a, ($b, $c)) = ...`.
1633    /// A nested group with exactly one item is returned unwrapped (as the item
1634    /// itself), so this variant only appears for two-or-more-item groups.
1635    NestedVariableList {
1636        /// The variables or nested lists inside the inner parentheses.
1637        items: Vec<Node>,
1638    },
1639
1640    /// Perl variable reference (scalar, array, hash, etc.) in Perl parsing workflow
1641    Variable {
1642        /// Variable sigil indicating type: $, @, %, &, *
1643        sigil: String, // $, @, %, &, *
1644        /// Variable name without sigil
1645        name: String,
1646    },
1647
1648    /// Variable with additional attributes for enhanced LSP workflow
1649    VariableWithAttributes {
1650        /// The base variable node
1651        variable: Box<Node>,
1652        /// List of attribute names applied to the variable
1653        attributes: Vec<String>,
1654    },
1655
1656    /// Assignment operation for LSP data processing workflows
1657    Assignment {
1658        /// Left-hand side of assignment
1659        lhs: Box<Node>,
1660        /// Right-hand side of assignment
1661        rhs: Box<Node>,
1662        /// Assignment operator: =, +=, -=, etc.
1663        op: String, // =, +=, -=, etc.
1664    },
1665
1666    // Expressions
1667    /// Binary operation for Perl parsing workflow calculations
1668    Binary {
1669        /// Binary operator
1670        op: String,
1671        /// Left operand
1672        left: Box<Node>,
1673        /// Right operand
1674        right: Box<Node>,
1675    },
1676
1677    /// Ternary conditional expression for Perl parsing workflow logic
1678    Ternary {
1679        /// Condition to evaluate
1680        condition: Box<Node>,
1681        /// Expression when condition is true
1682        then_expr: Box<Node>,
1683        /// Expression when condition is false
1684        else_expr: Box<Node>,
1685    },
1686
1687    /// Unary operation for Perl parsing workflow
1688    Unary {
1689        /// Unary operator
1690        op: String,
1691        /// Operand to apply operator to
1692        operand: Box<Node>,
1693    },
1694
1695    // I/O operations
1696    /// Diamond operator for file input in Perl parsing workflow
1697    Diamond, // <>
1698
1699    /// Ellipsis operator for Perl parsing workflow
1700    Ellipsis, // ...
1701
1702    /// Undef value for Perl parsing workflow
1703    Undef, // undef
1704
1705    /// Readline operation for LSP file processing
1706    Readline {
1707        /// Optional filehandle: `<STDIN>`, `<$fh>`, etc.
1708        filehandle: Option<String>, // <STDIN>, <$fh>, etc.
1709    },
1710
1711    /// Glob pattern for LSP workspace file matching
1712    Glob {
1713        /// Pattern string for file matching
1714        pattern: String, // <*.txt>
1715    },
1716
1717    /// Typeglob expression: `*foo` or `*main::bar`
1718    ///
1719    /// Provides access to all symbol table entries for a given name.
1720    Typeglob {
1721        /// Name of the symbol (including package qualification)
1722        name: String,
1723    },
1724
1725    /// Numeric literal in Perl code (integer, float, hex, octal, binary)
1726    ///
1727    /// Represents all numeric literal forms: `42`, `3.14`, `0x1A`, `0o755`, `0b1010`.
1728    Number {
1729        /// String representation preserving original format
1730        value: String,
1731    },
1732
1733    /// String literal with optional interpolation
1734    ///
1735    /// Handles both single-quoted (`'literal'`) and double-quoted (`"$interpolated"`) strings.
1736    String {
1737        /// String content (after quote processing)
1738        value: String,
1739        /// Whether the string supports variable interpolation
1740        interpolated: bool,
1741    },
1742
1743    /// Heredoc string literal for multi-line content
1744    ///
1745    /// Supports all heredoc forms: `<<EOF`, `<<'EOF'`, `<<"EOF"`, `<<~EOF` (indented).
1746    Heredoc {
1747        /// Delimiter marking heredoc boundaries
1748        delimiter: String,
1749        /// Content between delimiters
1750        content: String,
1751        /// Whether content supports variable interpolation
1752        interpolated: bool,
1753        /// Whether leading whitespace is stripped (<<~ form)
1754        indented: bool,
1755        /// Whether this is a command execution heredoc (<<`EOF`)
1756        command: bool,
1757        /// Body span for breakpoint detection (populated by drain_pending_heredocs)
1758        body_span: Option<SourceLocation>,
1759    },
1760
1761    /// Array literal expression: `(1, 2, 3)` or `[1, 2, 3]`
1762    ArrayLiteral {
1763        /// Elements in the array
1764        elements: Vec<Node>,
1765    },
1766
1767    /// Hash literal expression: `(key => 'value')` or `{key => 'value'}`
1768    HashLiteral {
1769        /// Key-value pairs in the hash
1770        pairs: Vec<(Node, Node)>,
1771    },
1772
1773    /// Block of statements: `{ ... }`
1774    ///
1775    /// Used for control structures, subroutine bodies, and bare blocks.
1776    Block {
1777        /// Statements within the block
1778        statements: Vec<Node>,
1779    },
1780
1781    /// Eval block for exception handling: `eval { ... }`
1782    Eval {
1783        /// Block to evaluate with exception trapping
1784        block: Box<Node>,
1785    },
1786
1787    /// Do block for file inclusion or expression evaluation: `do { ... }` or `do "file"`
1788    Do {
1789        /// Block to execute or file expression
1790        block: Box<Node>,
1791    },
1792
1793    /// Defer block for deferred cleanup on scope exit (Perl 5.36+ experimental, stable in 5.40)
1794    Defer {
1795        /// Block to execute on scope exit
1796        block: Box<Node>,
1797    },
1798
1799    /// Try-catch-finally for modern exception handling (Syntax::Keyword::Try style)
1800    Try {
1801        /// Try block body
1802        body: Box<Node>,
1803        /// Catch blocks: (optional exception variable, handler block)
1804        catch_blocks: Vec<(Option<String>, Box<Node>)>,
1805        /// Optional finally block
1806        finally_block: Option<Box<Node>>,
1807    },
1808
1809    /// If-elsif-else conditional statement
1810    If {
1811        /// Condition expression
1812        condition: Box<Node>,
1813        /// Then branch block
1814        then_branch: Box<Node>,
1815        /// Elsif branches: (condition, block) pairs
1816        elsif_branches: Vec<(Box<Node>, Box<Node>)>,
1817        /// Optional else branch
1818        else_branch: Option<Box<Node>>,
1819        /// Original keyword: None for 'if', Some("unless") for 'unless' block form.
1820        keyword: Option<String>,
1821    },
1822
1823    /// Statement with a label for loop control: `LABEL: while (...)`
1824    LabeledStatement {
1825        /// Label name (e.g., "OUTER", "LINE")
1826        label: String,
1827        /// Labeled statement (typically a loop)
1828        statement: Box<Node>,
1829    },
1830
1831    /// While loop: `while (condition) { ... }`
1832    While {
1833        /// Loop condition
1834        condition: Box<Node>,
1835        /// Loop body
1836        body: Box<Node>,
1837        /// Optional continue block
1838        continue_block: Option<Box<Node>>,
1839        /// Original keyword: None for 'while', Some("until") for 'until' block form.
1840        keyword: Option<String>,
1841    },
1842
1843    /// Tie operation for binding variables to objects: `tie %hash, 'Package', @args`
1844    Tie {
1845        /// Variable being tied
1846        variable: Box<Node>,
1847        /// Class/package name to tie to
1848        package: Box<Node>,
1849        /// Arguments passed to TIE* method
1850        args: Vec<Node>,
1851    },
1852
1853    /// Untie operation for unbinding variables: `untie %hash`
1854    Untie {
1855        /// Variable being untied
1856        variable: Box<Node>,
1857    },
1858
1859    /// C-style for loop: `for (init; cond; update) { ... }`
1860    For {
1861        /// Initialization expression
1862        init: Option<Box<Node>>,
1863        /// Loop condition
1864        condition: Option<Box<Node>>,
1865        /// Update expression
1866        update: Option<Box<Node>>,
1867        /// Loop body
1868        body: Box<Node>,
1869        /// Optional continue block
1870        continue_block: Option<Box<Node>>,
1871    },
1872
1873    /// Foreach loop: `foreach my $item (@list) { ... }`
1874    Foreach {
1875        /// Iterator variable
1876        variable: Box<Node>,
1877        /// List to iterate
1878        list: Box<Node>,
1879        /// Loop body
1880        body: Box<Node>,
1881        /// Optional continue block
1882        continue_block: Option<Box<Node>>,
1883    },
1884
1885    /// Given statement for switch-like matching (Perl 5.10+)
1886    Given {
1887        /// Expression to match against
1888        expr: Box<Node>,
1889        /// Body containing when/default blocks
1890        body: Box<Node>,
1891    },
1892
1893    /// When clause in given/switch: `when ($pattern) { ... }`
1894    When {
1895        /// Pattern to match
1896        condition: Box<Node>,
1897        /// Handler block
1898        body: Box<Node>,
1899    },
1900
1901    /// Default clause in given/switch: `default { ... }`
1902    Default {
1903        /// Handler block for unmatched cases
1904        body: Box<Node>,
1905    },
1906
1907    /// Statement modifier syntax: `print "ok" if $condition`
1908    StatementModifier {
1909        /// Statement to conditionally execute
1910        statement: Box<Node>,
1911        /// Modifier keyword: if, unless, while, until, for, foreach
1912        modifier: String,
1913        /// Modifier condition
1914        condition: Box<Node>,
1915    },
1916
1917    // Functions
1918    /// Subroutine declaration (function) including name, prototype, signature and body.
1919    Subroutine {
1920        /// Name of the subroutine
1921        ///
1922        /// # Precise Navigation Support
1923        /// - Added name_span for exact LSP navigation
1924        /// - Enables precise go-to-definition and hover behavior
1925        /// - O(1) span lookup in workspace symbols
1926        ///
1927        /// ## Integration Points
1928        /// - Semantic token providers
1929        /// - Cross-reference generation
1930        /// - Symbol renaming
1931        name: Option<String>,
1932
1933        /// Source location span of the subroutine name
1934        ///
1935        /// ## Usage Notes
1936        /// - Always corresponds to the name field
1937        /// - Provides constant-time position information
1938        /// - Essential for precise editor interactions
1939        name_span: Option<SourceLocation>,
1940
1941        /// Optional scope declarator: "my", "our", or "state" for lexical/package-scoped subs
1942        ///
1943        /// # Lexical Subroutines
1944        /// - Perl 5.18+ feature: `my sub helper { ... }`, `our sub global { ... }`, `state sub memo { ... }`
1945        /// - Distinguishes lexical scope binding from package-scoped subroutines
1946        /// - Essential for scope tracking, renaming, and dead code detection
1947        ///
1948        /// # Values
1949        /// - `None` — package-scoped subroutine (no declarator)
1950        /// - `Some("my")` — lexical subroutine with lexical binding
1951        /// - `Some("our")` — package-scoped subroutine with explicit package declaration
1952        /// - `Some("state")` — persistent lexical subroutine (persistent across invocations)
1953        declarator: Option<String>,
1954
1955        /// Optional prototype node (e.g. `($;@)`).
1956        prototype: Option<Box<Node>>,
1957        /// Optional signature node (Perl 5.20+ feature).
1958        signature: Option<Box<Node>>,
1959        /// Attributes attached to the subroutine (`:lvalue`, etc.).
1960        attributes: Vec<String>,
1961        /// The body block of the subroutine.
1962        body: Box<Node>,
1963    },
1964
1965    /// Subroutine prototype specification: `sub foo ($;@) { ... }`
1966    Prototype {
1967        /// Prototype string defining argument behavior
1968        content: String,
1969    },
1970
1971    /// Subroutine signature (Perl 5.20+): `sub foo ($x, $y = 0) { ... }`
1972    Signature {
1973        /// List of signature parameters
1974        parameters: Vec<Node>,
1975    },
1976
1977    /// Mandatory signature parameter: `$x` in `sub foo ($x) { }`
1978    MandatoryParameter {
1979        /// Variable being bound
1980        variable: Box<Node>,
1981    },
1982
1983    /// Optional signature parameter with default: `$y = 0` in `sub foo ($y = 0) { }`
1984    OptionalParameter {
1985        /// Variable being bound
1986        variable: Box<Node>,
1987        /// Default value expression
1988        default_value: Box<Node>,
1989    },
1990
1991    /// Slurpy parameter collecting remaining args: `@rest` or `%opts` in signature
1992    SlurpyParameter {
1993        /// Array or hash variable to receive remaining arguments
1994        variable: Box<Node>,
1995    },
1996
1997    /// Named parameter placeholder in signature (future Perl feature)
1998    NamedParameter {
1999        /// Variable for named parameter binding
2000        variable: Box<Node>,
2001    },
2002
2003    /// Method declaration (Perl 5.38+ with `use feature 'class'`)
2004    Method {
2005        /// Method name
2006        name: String,
2007        /// Source location span of the method name
2008        name_span: Option<SourceLocation>,
2009        /// Optional signature
2010        signature: Option<Box<Node>>,
2011        /// Method attributes (e.g., `:lvalue`)
2012        attributes: Vec<String>,
2013        /// Method body
2014        body: Box<Node>,
2015    },
2016
2017    /// Return statement: `return;` or `return $value;`
2018    Return {
2019        /// Optional return value
2020        value: Option<Box<Node>>,
2021    },
2022
2023    /// Loop control statement: `next`, `last`, or `redo`
2024    LoopControl {
2025        /// Control keyword: "next", "last", or "redo"
2026        op: String,
2027        /// Optional label: `next LABEL`
2028        label: Option<String>,
2029    },
2030
2031    /// Goto statement: `goto LABEL`, `goto &sub`, or `goto $expr`
2032    Goto {
2033        /// The target of the goto (label identifier, sub reference, or expression)
2034        target: Box<Node>,
2035    },
2036
2037    /// Method call: `$obj->method(@args)` or `$obj->method`
2038    MethodCall {
2039        /// Object or class expression
2040        object: Box<Node>,
2041        /// Method name being called
2042        method: String,
2043        /// Method arguments
2044        args: Vec<Node>,
2045    },
2046
2047    /// Function call: `foo(@args)` or `foo()`
2048    FunctionCall {
2049        /// Function name (may be qualified: `Package::func`)
2050        name: String,
2051        /// Function arguments
2052        args: Vec<Node>,
2053    },
2054
2055    /// Indirect object call (legacy syntax): `new Class @args`
2056    IndirectCall {
2057        /// Method name
2058        method: String,
2059        /// Object or class
2060        object: Box<Node>,
2061        /// Arguments
2062        args: Vec<Node>,
2063    },
2064
2065    /// Regex literal: `/pattern/modifiers` or `qr/pattern/modifiers`
2066    Regex {
2067        /// Regular expression pattern
2068        pattern: String,
2069        /// Replacement string (for s/// when parsed as regex)
2070        replacement: Option<String>,
2071        /// Regex modifiers (i, m, s, x, g, etc.)
2072        modifiers: String,
2073        /// Whether the regex contains embedded code `(?{...})`
2074        has_embedded_code: bool,
2075    },
2076
2077    /// Match operation: `$str =~ /pattern/modifiers` or `$str !~ /pattern/modifiers`
2078    Match {
2079        /// Expression to match against
2080        expr: Box<Node>,
2081        /// Pattern to match
2082        pattern: String,
2083        /// Match modifiers
2084        modifiers: String,
2085        /// Whether the regex contains embedded code `(?{...})`
2086        has_embedded_code: bool,
2087        /// Whether the binding operator was `!~` (negated match)
2088        negated: bool,
2089    },
2090
2091    /// Substitution operation: `$str =~ s/pattern/replacement/modifiers`
2092    Substitution {
2093        /// Expression to substitute in
2094        expr: Box<Node>,
2095        /// Pattern to find
2096        pattern: String,
2097        /// Replacement string
2098        replacement: String,
2099        /// Substitution modifiers (g, e, r, etc.)
2100        modifiers: String,
2101        /// Whether the substitution contains embedded code — either a `(?{...})` inline
2102        /// code block in the pattern, or the `e`/`ee` modifier which evaluates the
2103        /// replacement string as Perl code (equivalent to `eval`).
2104        has_embedded_code: bool,
2105        /// Whether the binding operator was `!~` (negated match)
2106        negated: bool,
2107    },
2108
2109    /// Transliteration operation: `$str =~ tr/search/replace/` or `y///`
2110    Transliteration {
2111        /// Expression to transliterate
2112        expr: Box<Node>,
2113        /// Characters to search for
2114        search: String,
2115        /// Replacement characters
2116        replace: String,
2117        /// Transliteration modifiers (c, d, s, r)
2118        modifiers: String,
2119        /// Whether the binding operator was `!~` (negated match)
2120        negated: bool,
2121    },
2122
2123    // Package system
2124    /// Package declaration (e.g. `package Foo;`) and optional inline block form.
2125    Package {
2126        /// Name of the package
2127        ///
2128        /// # Precise Navigation Support
2129        /// - Added name_span for exact LSP navigation
2130        /// - Enables precise go-to-definition and hover behavior
2131        /// - O(1) span lookup in workspace symbols
2132        ///
2133        /// ## Integration Points
2134        /// - Workspace indexing
2135        /// - Cross-module symbol resolution
2136        /// - Code action providers
2137        name: String,
2138
2139        /// Source location span of the package name
2140        ///
2141        /// ## Usage Notes
2142        /// - Always corresponds to the name field
2143        /// - Provides constant-time position information
2144        /// - Essential for precise editor interactions
2145        name_span: SourceLocation,
2146
2147        /// Optional inline block for `package Foo { ... }` declarations.
2148        block: Option<Box<Node>>,
2149    },
2150
2151    /// Use statement for module loading: `use Module qw(imports);`
2152    Use {
2153        /// Module name to load
2154        module: String,
2155        /// Import arguments (symbols to import)
2156        args: Vec<String>,
2157        /// Whether this module is a known source filter (security risk)
2158        has_filter_risk: bool,
2159    },
2160
2161    /// No statement for disabling features: `no strict;`
2162    No {
2163        /// Module/pragma name to disable
2164        module: String,
2165        /// Arguments for the no statement
2166        args: Vec<String>,
2167        /// Whether this module is a known source filter (security risk)
2168        has_filter_risk: bool,
2169    },
2170
2171    /// Phase block for compile/runtime hooks: `BEGIN`, `END`, `CHECK`, `INIT`, `UNITCHECK`
2172    PhaseBlock {
2173        /// Phase name: BEGIN, END, CHECK, INIT, UNITCHECK
2174        phase: String,
2175        /// Source location span of the phase block name for precise navigation
2176        phase_span: Option<SourceLocation>,
2177        /// Block to execute during the specified phase
2178        block: Box<Node>,
2179    },
2180
2181    /// Data section marker: `__DATA__` or `__END__`
2182    DataSection {
2183        /// Section marker (__DATA__ or __END__)
2184        marker: String,
2185        /// Content following the marker (if any)
2186        body: Option<String>,
2187    },
2188
2189    /// Class declaration (Perl 5.38+ with `use feature 'class'`)
2190    Class {
2191        /// Class name
2192        name: String,
2193        /// Source location span of the class name
2194        name_span: Option<SourceLocation>,
2195        /// Parent class names from `:isa(Parent)` attributes
2196        parents: Vec<String>,
2197        /// Class body containing methods and attributes
2198        body: Box<Node>,
2199    },
2200
2201    /// Format declaration for legacy report generation
2202    Format {
2203        /// Format name (defaults to filehandle name)
2204        name: String,
2205        /// Source location span of the format name
2206        name_span: Option<SourceLocation>,
2207        /// Format specification body
2208        body: String,
2209    },
2210
2211    /// Bare identifier (bareword or package-qualified name)
2212    Identifier {
2213        /// Identifier string
2214        name: String,
2215    },
2216
2217    /// Parse error placeholder with error message and recovery context
2218    Error {
2219        /// Error description
2220        message: String,
2221        /// Expected token types (if any)
2222        expected: Vec<TokenKind>,
2223        /// The token actually found (if any)
2224        found: Option<Token>,
2225        /// Partial AST node parsed before error (if any)
2226        partial: Option<Box<Node>>,
2227    },
2228
2229    /// Missing expression where one was expected.
2230    ///
2231    /// Emitted by `recover_missing_infix_rhs` when a binary operator has no
2232    /// right-hand-side (e.g. `1 +` at end of input). This is the **only**
2233    /// `Missing*` variant currently emitted by the production parser.
2234    MissingExpression,
2235
2236    /// RESERVED — not currently emitted by the parser.
2237    ///
2238    /// Retained for API symmetry and future error-recovery work. If recovery
2239    /// starts emitting this variant, add real parser fixture tests before
2240    /// shipping. Do not pattern-match on this variant expecting it to appear
2241    /// in normal parse output.
2242    MissingStatement,
2243
2244    /// RESERVED — not currently emitted by the parser.
2245    ///
2246    /// Retained for API symmetry and future error-recovery work. If recovery
2247    /// starts emitting this variant, add real parser fixture tests before
2248    /// shipping. Do not pattern-match on this variant expecting it to appear
2249    /// in normal parse output.
2250    MissingIdentifier,
2251
2252    /// RESERVED — not currently emitted by the parser.
2253    ///
2254    /// Retained for API symmetry and future error-recovery work. If recovery
2255    /// starts emitting this variant, add real parser fixture tests before
2256    /// shipping. Do not pattern-match on this variant expecting it to appear
2257    /// in normal parse output.
2258    MissingBlock,
2259
2260    /// Lexer budget exceeded marker preserving partial parse results
2261    ///
2262    /// Used when recursion or token limits are hit to preserve already-parsed content.
2263    UnknownRest,
2264}
2265
2266impl NodeKind {
2267    /// Get the name of this `NodeKind` as a static string.
2268    ///
2269    /// Useful for diagnostics, logging, and human-readable AST dumps.
2270    ///
2271    /// # Examples
2272    ///
2273    /// ```
2274    /// use perl_ast::NodeKind;
2275    ///
2276    /// let kind = NodeKind::Variable { sigil: "$".to_string(), name: "x".to_string() };
2277    /// assert_eq!(kind.kind_name(), "Variable");
2278    ///
2279    /// let kind = NodeKind::Program { statements: vec![] };
2280    /// assert_eq!(kind.kind_name(), "Program");
2281    /// ```
2282    pub fn kind_name(&self) -> &'static str {
2283        match self {
2284            NodeKind::Program { .. } => "Program",
2285            NodeKind::ExpressionStatement { .. } => "ExpressionStatement",
2286            NodeKind::VariableDeclaration { .. } => "VariableDeclaration",
2287            NodeKind::VariableListDeclaration { .. } => "VariableListDeclaration",
2288            NodeKind::NestedVariableList { .. } => "NestedVariableList",
2289            NodeKind::Variable { .. } => "Variable",
2290            NodeKind::VariableWithAttributes { .. } => "VariableWithAttributes",
2291            NodeKind::Assignment { .. } => "Assignment",
2292            NodeKind::Binary { .. } => "Binary",
2293            NodeKind::Ternary { .. } => "Ternary",
2294            NodeKind::Unary { .. } => "Unary",
2295            NodeKind::Diamond => "Diamond",
2296            NodeKind::Ellipsis => "Ellipsis",
2297            NodeKind::Undef => "Undef",
2298            NodeKind::Readline { .. } => "Readline",
2299            NodeKind::Glob { .. } => "Glob",
2300            NodeKind::Typeglob { .. } => "Typeglob",
2301            NodeKind::Number { .. } => "Number",
2302            NodeKind::String { .. } => "String",
2303            NodeKind::Heredoc { .. } => "Heredoc",
2304            NodeKind::ArrayLiteral { .. } => "ArrayLiteral",
2305            NodeKind::HashLiteral { .. } => "HashLiteral",
2306            NodeKind::Block { .. } => "Block",
2307            NodeKind::Eval { .. } => "Eval",
2308            NodeKind::Do { .. } => "Do",
2309            NodeKind::Defer { .. } => "Defer",
2310            NodeKind::Try { .. } => "Try",
2311            NodeKind::If { .. } => "If",
2312            NodeKind::LabeledStatement { .. } => "LabeledStatement",
2313            NodeKind::While { .. } => "While",
2314            NodeKind::Tie { .. } => "Tie",
2315            NodeKind::Untie { .. } => "Untie",
2316            NodeKind::For { .. } => "For",
2317            NodeKind::Foreach { .. } => "Foreach",
2318            NodeKind::Given { .. } => "Given",
2319            NodeKind::When { .. } => "When",
2320            NodeKind::Default { .. } => "Default",
2321            NodeKind::StatementModifier { .. } => "StatementModifier",
2322            NodeKind::Subroutine { .. } => "Subroutine",
2323            NodeKind::Prototype { .. } => "Prototype",
2324            NodeKind::Signature { .. } => "Signature",
2325            NodeKind::MandatoryParameter { .. } => "MandatoryParameter",
2326            NodeKind::OptionalParameter { .. } => "OptionalParameter",
2327            NodeKind::SlurpyParameter { .. } => "SlurpyParameter",
2328            NodeKind::NamedParameter { .. } => "NamedParameter",
2329            NodeKind::Method { .. } => "Method",
2330            NodeKind::Return { .. } => "Return",
2331            NodeKind::LoopControl { .. } => "LoopControl",
2332            NodeKind::Goto { .. } => "Goto",
2333            NodeKind::MethodCall { .. } => "MethodCall",
2334            NodeKind::FunctionCall { .. } => "FunctionCall",
2335            NodeKind::IndirectCall { .. } => "IndirectCall",
2336            NodeKind::Regex { .. } => "Regex",
2337            NodeKind::Match { .. } => "Match",
2338            NodeKind::Substitution { .. } => "Substitution",
2339            NodeKind::Transliteration { .. } => "Transliteration",
2340            NodeKind::Package { .. } => "Package",
2341            NodeKind::Use { .. } => "Use",
2342            NodeKind::No { .. } => "No",
2343            NodeKind::PhaseBlock { .. } => "PhaseBlock",
2344            NodeKind::DataSection { .. } => "DataSection",
2345            NodeKind::Class { .. } => "Class",
2346            NodeKind::Format { .. } => "Format",
2347            NodeKind::Identifier { .. } => "Identifier",
2348            NodeKind::Error { .. } => "Error",
2349            NodeKind::MissingExpression => "MissingExpression",
2350            NodeKind::MissingStatement => "MissingStatement",
2351            NodeKind::MissingIdentifier => "MissingIdentifier",
2352            NodeKind::MissingBlock => "MissingBlock",
2353            NodeKind::UnknownRest => "UnknownRest",
2354        }
2355    }
2356
2357    /// Canonical list of **all** `kind_name()` strings, in declaration order.
2358    ///
2359    /// Auto-derived from the `NodeKind` enum via `strum::VariantNames` — adding a new
2360    /// variant automatically updates this list. No manual maintenance required.
2361    ///
2362    /// Every consumer that needs the full set of NodeKind names should reference
2363    /// this constant instead of maintaining a hand-written copy.
2364    pub const ALL_KIND_NAMES: &[&'static str] = NodeKind::VARIANTS;
2365
2366    /// Subset of `ALL_KIND_NAMES` that represent synthetic/recovery nodes.
2367    ///
2368    /// These kinds are only produced by `parse_with_recovery()` on malformed
2369    /// input and should not be expected in clean parses.
2370    pub const RECOVERY_KIND_NAMES: &[&'static str] = &[
2371        "Error",
2372        "MissingBlock",
2373        "MissingExpression",
2374        "MissingIdentifier",
2375        "MissingStatement",
2376        "UnknownRest",
2377    ];
2378}
2379
2380impl fmt::Display for NodeKind {
2381    /// Formats as the canonical `kind_name()` string.
2382    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2383        f.write_str(self.kind_name())
2384    }
2385}
2386
2387impl fmt::Display for Node {
2388    /// Formats as the tree-sitter compatible S-expression.
2389    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2390        f.write_str(&self.to_sexp())
2391    }
2392}
2393
2394/// Format unary operator for S-expression output
2395fn format_unary_operator(op: &str) -> String {
2396    match op {
2397        // Arithmetic unary operators
2398        "+" => "unary_+".to_string(),
2399        "-" => "unary_-".to_string(),
2400
2401        // Logical unary operators
2402        "!" => "unary_not".to_string(),
2403        "not" => "unary_not".to_string(),
2404
2405        // Bitwise complement
2406        "~" => "unary_complement".to_string(),
2407
2408        // Reference operator
2409        "\\" => "unary_ref".to_string(),
2410
2411        // Postfix operators
2412        "++" => "unary_++".to_string(),
2413        "--" => "unary_--".to_string(),
2414
2415        // File test operators
2416        "-f" => "unary_-f".to_string(),
2417        "-d" => "unary_-d".to_string(),
2418        "-e" => "unary_-e".to_string(),
2419        "-r" => "unary_-r".to_string(),
2420        "-w" => "unary_-w".to_string(),
2421        "-x" => "unary_-x".to_string(),
2422        "-o" => "unary_-o".to_string(),
2423        "-R" => "unary_-R".to_string(),
2424        "-W" => "unary_-W".to_string(),
2425        "-X" => "unary_-X".to_string(),
2426        "-O" => "unary_-O".to_string(),
2427        "-s" => "unary_-s".to_string(),
2428        "-p" => "unary_-p".to_string(),
2429        "-S" => "unary_-S".to_string(),
2430        "-b" => "unary_-b".to_string(),
2431        "-c" => "unary_-c".to_string(),
2432        "-t" => "unary_-t".to_string(),
2433        "-u" => "unary_-u".to_string(),
2434        "-g" => "unary_-g".to_string(),
2435        "-k" => "unary_-k".to_string(),
2436        "-T" => "unary_-T".to_string(),
2437        "-B" => "unary_-B".to_string(),
2438        "-M" => "unary_-M".to_string(),
2439        "-A" => "unary_-A".to_string(),
2440        "-C" => "unary_-C".to_string(),
2441        "-l" => "unary_-l".to_string(),
2442        "-z" => "unary_-z".to_string(),
2443
2444        // Postfix dereferencing
2445        "->@*" => "unary_->@*".to_string(),
2446        "->%*" => "unary_->%*".to_string(),
2447        "->$*" => "unary_->$*".to_string(),
2448        "->&*" => "unary_->&*".to_string(),
2449        "->**" => "unary_->**".to_string(),
2450
2451        // Defined operator
2452        "defined" => "unary_defined".to_string(),
2453
2454        // Default case for unknown operators
2455        _ => format!("unary_{}", op.replace(' ', "_")),
2456    }
2457}
2458
2459/// Format binary operator for S-expression output
2460fn format_binary_operator(op: &str) -> String {
2461    match op {
2462        // Arithmetic operators
2463        "+" => "binary_+".to_string(),
2464        "-" => "binary_-".to_string(),
2465        "*" => "binary_*".to_string(),
2466        "/" => "binary_/".to_string(),
2467        "%" => "binary_%".to_string(),
2468        "**" => "binary_**".to_string(),
2469
2470        // Comparison operators
2471        "==" => "binary_==".to_string(),
2472        "!=" => "binary_!=".to_string(),
2473        "<" => "binary_<".to_string(),
2474        ">" => "binary_>".to_string(),
2475        "<=" => "binary_<=".to_string(),
2476        ">=" => "binary_>=".to_string(),
2477        "<=>" => "binary_<=>".to_string(),
2478
2479        // String comparison
2480        "eq" => "binary_eq".to_string(),
2481        "ne" => "binary_ne".to_string(),
2482        "lt" => "binary_lt".to_string(),
2483        "le" => "binary_le".to_string(),
2484        "gt" => "binary_gt".to_string(),
2485        "ge" => "binary_ge".to_string(),
2486        "cmp" => "binary_cmp".to_string(),
2487
2488        // Logical operators
2489        "&&" => "binary_&&".to_string(),
2490        "||" => "binary_||".to_string(),
2491        "and" => "binary_and".to_string(),
2492        "or" => "binary_or".to_string(),
2493        "xor" => "binary_xor".to_string(),
2494
2495        // Bitwise operators
2496        "&" => "binary_&".to_string(),
2497        "|" => "binary_|".to_string(),
2498        "^" => "binary_^".to_string(),
2499        "<<" => "binary_<<".to_string(),
2500        ">>" => "binary_>>".to_string(),
2501
2502        // Pattern matching
2503        "=~" => "binary_=~".to_string(),
2504        "!~" => "binary_!~".to_string(),
2505
2506        // Smart match
2507        "~~" => "binary_~~".to_string(),
2508
2509        // String repetition
2510        "x" => "binary_x".to_string(),
2511
2512        // Concatenation
2513        "." => "binary_.".to_string(),
2514
2515        // Range operators
2516        ".." => "binary_..".to_string(),
2517        "..." => "binary_...".to_string(),
2518
2519        // Type checking
2520        "isa" => "binary_isa".to_string(),
2521
2522        // Assignment operators
2523        "=" => "binary_=".to_string(),
2524        "+=" => "binary_+=".to_string(),
2525        "-=" => "binary_-=".to_string(),
2526        "*=" => "binary_*=".to_string(),
2527        "/=" => "binary_/=".to_string(),
2528        "%=" => "binary_%=".to_string(),
2529        "**=" => "binary_**=".to_string(),
2530        ".=" => "binary_.=".to_string(),
2531        "&=" => "binary_&=".to_string(),
2532        "|=" => "binary_|=".to_string(),
2533        "^=" => "binary_^=".to_string(),
2534        "<<=" => "binary_<<=".to_string(),
2535        ">>=" => "binary_>>=".to_string(),
2536        "&&=" => "binary_&&=".to_string(),
2537        "||=" => "binary_||=".to_string(),
2538        "//=" => "binary_//=".to_string(),
2539
2540        // Defined-or operator
2541        "//" => "binary_//".to_string(),
2542
2543        // Method calls and dereferencing
2544        "->" => "binary_->".to_string(),
2545
2546        // Hash/array access
2547        "{}" => "binary_{}".to_string(),
2548        "[]" => "binary_[]".to_string(),
2549
2550        // Arrow hash/array dereference
2551        "->{}" => "arrow_hash_deref".to_string(),
2552        "->[]" => "arrow_array_deref".to_string(),
2553
2554        // Default case for unknown operators
2555        _ => format!("binary_{}", op.replace(' ', "_")),
2556    }
2557}
2558
2559// SourceLocation is now provided by perl-position-tracking crate
2560// See the re-export at the top of this file
2561
2562#[cfg(test)]
2563mod tests {
2564    use super::*;
2565    use std::collections::BTreeSet;
2566
2567    /// Build a dummy instance for every `NodeKind` variant and return its
2568    /// `kind_name()`.  This ensures the compiler forces us to update here
2569    /// whenever a variant is added/removed.
2570    fn all_kind_names_from_variants() -> BTreeSet<&'static str> {
2571        let loc = SourceLocation { start: 0, end: 0 };
2572        let dummy_node = || Node::new(NodeKind::Undef, loc);
2573
2574        let variants: Vec<NodeKind> = vec![
2575            NodeKind::Program { statements: vec![] },
2576            NodeKind::ExpressionStatement { expression: Box::new(dummy_node()) },
2577            NodeKind::VariableDeclaration {
2578                declarator: String::new(),
2579                variable: Box::new(dummy_node()),
2580                attributes: vec![],
2581                initializer: None,
2582            },
2583            NodeKind::VariableListDeclaration {
2584                declarator: String::new(),
2585                variables: vec![],
2586                attributes: vec![],
2587                initializer: None,
2588            },
2589            NodeKind::NestedVariableList { items: vec![] },
2590            NodeKind::Variable { sigil: String::new(), name: String::new() },
2591            NodeKind::VariableWithAttributes {
2592                variable: Box::new(dummy_node()),
2593                attributes: vec![],
2594            },
2595            NodeKind::Assignment {
2596                lhs: Box::new(dummy_node()),
2597                rhs: Box::new(dummy_node()),
2598                op: String::new(),
2599            },
2600            NodeKind::Binary {
2601                op: String::new(),
2602                left: Box::new(dummy_node()),
2603                right: Box::new(dummy_node()),
2604            },
2605            NodeKind::Ternary {
2606                condition: Box::new(dummy_node()),
2607                then_expr: Box::new(dummy_node()),
2608                else_expr: Box::new(dummy_node()),
2609            },
2610            NodeKind::Unary { op: String::new(), operand: Box::new(dummy_node()) },
2611            NodeKind::Diamond,
2612            NodeKind::Ellipsis,
2613            NodeKind::Undef,
2614            NodeKind::Readline { filehandle: None },
2615            NodeKind::Glob { pattern: String::new() },
2616            NodeKind::Typeglob { name: String::new() },
2617            NodeKind::Number { value: String::new() },
2618            NodeKind::String { value: String::new(), interpolated: false },
2619            NodeKind::Heredoc {
2620                delimiter: String::new(),
2621                content: String::new(),
2622                interpolated: false,
2623                indented: false,
2624                command: false,
2625                body_span: None,
2626            },
2627            NodeKind::ArrayLiteral { elements: vec![] },
2628            NodeKind::HashLiteral { pairs: vec![] },
2629            NodeKind::Block { statements: vec![] },
2630            NodeKind::Eval { block: Box::new(dummy_node()) },
2631            NodeKind::Do { block: Box::new(dummy_node()) },
2632            NodeKind::Defer { block: Box::new(dummy_node()) },
2633            NodeKind::Try {
2634                body: Box::new(dummy_node()),
2635                catch_blocks: vec![],
2636                finally_block: None,
2637            },
2638            NodeKind::If {
2639                condition: Box::new(dummy_node()),
2640                then_branch: Box::new(dummy_node()),
2641                elsif_branches: vec![],
2642                else_branch: None,
2643                keyword: None,
2644            },
2645            NodeKind::LabeledStatement { label: String::new(), statement: Box::new(dummy_node()) },
2646            NodeKind::While {
2647                condition: Box::new(dummy_node()),
2648                body: Box::new(dummy_node()),
2649                continue_block: None,
2650                keyword: None,
2651            },
2652            NodeKind::Tie {
2653                variable: Box::new(dummy_node()),
2654                package: Box::new(dummy_node()),
2655                args: vec![],
2656            },
2657            NodeKind::Untie { variable: Box::new(dummy_node()) },
2658            NodeKind::For {
2659                init: None,
2660                condition: None,
2661                update: None,
2662                body: Box::new(dummy_node()),
2663                continue_block: None,
2664            },
2665            NodeKind::Foreach {
2666                variable: Box::new(dummy_node()),
2667                list: Box::new(dummy_node()),
2668                body: Box::new(dummy_node()),
2669                continue_block: None,
2670            },
2671            NodeKind::Given { expr: Box::new(dummy_node()), body: Box::new(dummy_node()) },
2672            NodeKind::When { condition: Box::new(dummy_node()), body: Box::new(dummy_node()) },
2673            NodeKind::Default { body: Box::new(dummy_node()) },
2674            NodeKind::StatementModifier {
2675                statement: Box::new(dummy_node()),
2676                modifier: String::new(),
2677                condition: Box::new(dummy_node()),
2678            },
2679            NodeKind::Subroutine {
2680                name: None,
2681                name_span: None,
2682                declarator: None,
2683                prototype: None,
2684                signature: None,
2685                attributes: vec![],
2686                body: Box::new(dummy_node()),
2687            },
2688            NodeKind::Prototype { content: String::new() },
2689            NodeKind::Signature { parameters: vec![] },
2690            NodeKind::MandatoryParameter { variable: Box::new(dummy_node()) },
2691            NodeKind::OptionalParameter {
2692                variable: Box::new(dummy_node()),
2693                default_value: Box::new(dummy_node()),
2694            },
2695            NodeKind::SlurpyParameter { variable: Box::new(dummy_node()) },
2696            NodeKind::NamedParameter { variable: Box::new(dummy_node()) },
2697            NodeKind::Method {
2698                name: String::new(),
2699                name_span: None,
2700                signature: None,
2701                attributes: vec![],
2702                body: Box::new(dummy_node()),
2703            },
2704            NodeKind::Return { value: None },
2705            NodeKind::LoopControl { op: String::new(), label: None },
2706            NodeKind::Goto { target: Box::new(dummy_node()) },
2707            NodeKind::MethodCall {
2708                object: Box::new(dummy_node()),
2709                method: String::new(),
2710                args: vec![],
2711            },
2712            NodeKind::FunctionCall { name: String::new(), args: vec![] },
2713            NodeKind::IndirectCall {
2714                method: String::new(),
2715                object: Box::new(dummy_node()),
2716                args: vec![],
2717            },
2718            NodeKind::Regex {
2719                pattern: String::new(),
2720                replacement: None,
2721                modifiers: String::new(),
2722                has_embedded_code: false,
2723            },
2724            NodeKind::Match {
2725                expr: Box::new(dummy_node()),
2726                pattern: String::new(),
2727                modifiers: String::new(),
2728                has_embedded_code: false,
2729                negated: false,
2730            },
2731            NodeKind::Substitution {
2732                expr: Box::new(dummy_node()),
2733                pattern: String::new(),
2734                replacement: String::new(),
2735                modifiers: String::new(),
2736                has_embedded_code: false,
2737                negated: false,
2738            },
2739            NodeKind::Transliteration {
2740                expr: Box::new(dummy_node()),
2741                search: String::new(),
2742                replace: String::new(),
2743                modifiers: String::new(),
2744                negated: false,
2745            },
2746            NodeKind::Package { name: String::new(), name_span: loc, block: None },
2747            NodeKind::Use { module: String::new(), args: vec![], has_filter_risk: false },
2748            NodeKind::No { module: String::new(), args: vec![], has_filter_risk: false },
2749            NodeKind::PhaseBlock {
2750                phase: String::new(),
2751                phase_span: None,
2752                block: Box::new(dummy_node()),
2753            },
2754            NodeKind::DataSection { marker: String::new(), body: None },
2755            NodeKind::Class {
2756                name: String::new(),
2757                name_span: None,
2758                parents: vec![],
2759                body: Box::new(dummy_node()),
2760            },
2761            NodeKind::Format { name: String::new(), name_span: None, body: String::new() },
2762            NodeKind::Identifier { name: String::new() },
2763            NodeKind::Error {
2764                message: String::new(),
2765                expected: vec![],
2766                found: None,
2767                partial: None,
2768            },
2769            NodeKind::MissingExpression,
2770            NodeKind::MissingStatement,
2771            NodeKind::MissingIdentifier,
2772            NodeKind::MissingBlock,
2773            NodeKind::UnknownRest,
2774        ];
2775
2776        variants.iter().map(|v| v.kind_name()).collect()
2777    }
2778
2779    #[test]
2780    fn for_each_child_mut_nested_variable_list() {
2781        // Covers lines 876-879: NestedVariableList arm in for_each_child_mut.
2782        let loc = SourceLocation { start: 0, end: 10 };
2783        let item_a =
2784            Node::new(NodeKind::Variable { sigil: "$".to_string(), name: "a".to_string() }, loc);
2785        let item_b =
2786            Node::new(NodeKind::Variable { sigil: "$".to_string(), name: "b".to_string() }, loc);
2787        let mut node = Node::new(NodeKind::NestedVariableList { items: vec![item_a, item_b] }, loc);
2788        let mut count = 0;
2789        node.for_each_child_mut(|_child| count += 1);
2790        assert_eq!(count, 2, "for_each_child_mut should visit both items in NestedVariableList");
2791    }
2792
2793    #[test]
2794    fn for_each_child_nested_variable_list() {
2795        // Covers lines 1131-1134: NestedVariableList arm in for_each_child.
2796        let loc = SourceLocation { start: 0, end: 10 };
2797        let item_a =
2798            Node::new(NodeKind::Variable { sigil: "$".to_string(), name: "x".to_string() }, loc);
2799        let item_b =
2800            Node::new(NodeKind::Variable { sigil: "$".to_string(), name: "y".to_string() }, loc);
2801        let node = Node::new(NodeKind::NestedVariableList { items: vec![item_a, item_b] }, loc);
2802        let mut names = Vec::new();
2803        node.for_each_child(|child| {
2804            if let NodeKind::Variable { name, .. } = &child.kind {
2805                names.push(name.clone());
2806            }
2807        });
2808        assert_eq!(
2809            names,
2810            vec!["x", "y"],
2811            "for_each_child should visit all items in NestedVariableList"
2812        );
2813    }
2814
2815    #[test]
2816    fn all_kind_names_is_consistent_with_kind_name() {
2817        let from_enum = all_kind_names_from_variants();
2818        let from_const: BTreeSet<&str> = NodeKind::ALL_KIND_NAMES.iter().copied().collect();
2819
2820        // Check for duplicates in the const array
2821        assert_eq!(
2822            NodeKind::ALL_KIND_NAMES.len(),
2823            from_const.len(),
2824            "ALL_KIND_NAMES contains duplicates"
2825        );
2826
2827        let only_in_enum: Vec<_> = from_enum.difference(&from_const).collect();
2828        let only_in_const: Vec<_> = from_const.difference(&from_enum).collect();
2829
2830        assert!(
2831            only_in_enum.is_empty() && only_in_const.is_empty(),
2832            "ALL_KIND_NAMES is out of sync with NodeKind variants:\n  \
2833             in enum but not in ALL_KIND_NAMES: {only_in_enum:?}\n  \
2834             in ALL_KIND_NAMES but not in enum: {only_in_const:?}"
2835        );
2836    }
2837
2838    /// Construct recovery variants and return their `kind_name()` strings.
2839    ///
2840    /// Adding a recovery variant to `NodeKind` without updating `RECOVERY_KIND_NAMES`
2841    /// will cause `recovery_kind_names_is_consistent_with_kind_name` to fail.
2842    fn recovery_kind_names_from_variants() -> BTreeSet<&'static str> {
2843        vec![
2844            NodeKind::Error {
2845                message: String::new(),
2846                expected: vec![],
2847                found: None,
2848                partial: None,
2849            },
2850            NodeKind::MissingExpression,
2851            NodeKind::MissingStatement,
2852            NodeKind::MissingIdentifier,
2853            NodeKind::MissingBlock,
2854            NodeKind::UnknownRest,
2855        ]
2856        .iter()
2857        .map(|v| v.kind_name())
2858        .collect()
2859    }
2860
2861    #[test]
2862    fn recovery_kind_names_is_consistent_with_kind_name() {
2863        let from_enum = recovery_kind_names_from_variants();
2864        let from_const: BTreeSet<&str> = NodeKind::RECOVERY_KIND_NAMES.iter().copied().collect();
2865        let only_in_enum: Vec<_> = from_enum.difference(&from_const).collect();
2866        let only_in_const: Vec<_> = from_const.difference(&from_enum).collect();
2867        assert!(
2868            only_in_enum.is_empty() && only_in_const.is_empty(),
2869            "RECOVERY_KIND_NAMES is out of sync with recovery variants:\n  \
2870             in enum but not in RECOVERY_KIND_NAMES: {only_in_enum:?}\n  \
2871             in RECOVERY_KIND_NAMES but not in enum: {only_in_const:?}"
2872        );
2873    }
2874
2875    #[test]
2876    fn recovery_kind_names_is_subset_of_all() {
2877        let all: BTreeSet<&str> = NodeKind::ALL_KIND_NAMES.iter().copied().collect();
2878        let recovery: BTreeSet<&str> = NodeKind::RECOVERY_KIND_NAMES.iter().copied().collect();
2879
2880        // No duplicates
2881        assert_eq!(
2882            NodeKind::RECOVERY_KIND_NAMES.len(),
2883            recovery.len(),
2884            "RECOVERY_KIND_NAMES contains duplicates"
2885        );
2886
2887        let not_in_all: Vec<_> = recovery.difference(&all).collect();
2888        assert!(
2889            not_in_all.is_empty(),
2890            "RECOVERY_KIND_NAMES contains entries not in ALL_KIND_NAMES: {not_in_all:?}"
2891        );
2892    }
2893
2894    #[test]
2895    fn all_kind_names_not_empty() {
2896        // Regression guard: ALL_KIND_NAMES should always be populated
2897        assert!(
2898            !NodeKind::ALL_KIND_NAMES.is_empty(),
2899            "ALL_KIND_NAMES should not be empty; strum derivation failed"
2900        );
2901    }
2902
2903    #[test]
2904    fn all_kind_names_no_empty_strings() {
2905        // Boundary condition: no entry should be an empty string
2906        for (i, name) in NodeKind::ALL_KIND_NAMES.iter().enumerate() {
2907            assert!(!name.is_empty(), "ALL_KIND_NAMES[{}] is empty string", i);
2908        }
2909    }
2910
2911    #[test]
2912    fn all_kind_names_starts_with_program() {
2913        // Regression guard: first variant is Program (declaration order invariant)
2914        assert_eq!(
2915            NodeKind::ALL_KIND_NAMES.first(),
2916            Some(&"Program"),
2917            "First variant in ALL_KIND_NAMES should be 'Program' (declaration order)"
2918        );
2919    }
2920
2921    #[test]
2922    fn all_kind_names_ends_with_unknown_rest() {
2923        // Regression guard: last variant is UnknownRest (declaration order invariant)
2924        assert_eq!(
2925            NodeKind::ALL_KIND_NAMES.last(),
2926            Some(&"UnknownRest"),
2927            "Last variant in ALL_KIND_NAMES should be 'UnknownRest' (declaration order)"
2928        );
2929    }
2930
2931    #[test]
2932    fn all_kind_names_valid_kind_names() {
2933        // Regression guard: every string in ALL_KIND_NAMES is a valid kind_name() output
2934        for (i, name) in NodeKind::ALL_KIND_NAMES.iter().enumerate() {
2935            let found = all_kind_names_from_variants().contains(name);
2936            assert!(
2937                found,
2938                "ALL_KIND_NAMES[{}] = '{}' is not a valid kind_name() return value",
2939                i, name
2940            );
2941        }
2942    }
2943
2944    #[test]
2945    fn all_kind_names_exact_match_with_variants_set() {
2946        // Regression guard: ALL_KIND_NAMES contains exactly the same names as all variants
2947        let from_enum = all_kind_names_from_variants();
2948        let from_const: BTreeSet<&str> = NodeKind::ALL_KIND_NAMES.iter().copied().collect();
2949
2950        assert_eq!(from_enum, from_const, "ALL_KIND_NAMES set does not match variant kind_names");
2951    }
2952
2953    #[test]
2954    fn all_kind_names_no_whitespace_padding() {
2955        // Boundary condition: no leading/trailing whitespace in variant names
2956        for (i, name) in NodeKind::ALL_KIND_NAMES.iter().enumerate() {
2957            assert_eq!(
2958                *name,
2959                name.trim(),
2960                "ALL_KIND_NAMES[{}] = '{}' has leading/trailing whitespace",
2961                i,
2962                name
2963            );
2964        }
2965    }
2966
2967    #[test]
2968    fn all_kind_names_count_regression_guard() {
2969        // Regression guard: ALL_KIND_NAMES must have at least 70 entries.
2970        // The previous hand-maintained list had 70 variants (including NestedVariableList
2971        // added in #1457). Failing below that count means a variant was deleted or the
2972        // strum derivation silently stopped working.
2973        //
2974        // Note: the previous test `all_kind_names_strum_derived_stability` asserted
2975        // `NodeKind::VARIANTS == NodeKind::ALL_KIND_NAMES`, which is trivially true by
2976        // definition (ALL_KIND_NAMES = NodeKind::VARIANTS). That assertion was vacuous
2977        // and has been replaced with this count guard.
2978        assert!(
2979            NodeKind::ALL_KIND_NAMES.len() >= 70,
2980            "ALL_KIND_NAMES has only {} entries; expected >= 70. \
2981             A variant may have been accidentally removed, or strum::VariantNames \
2982             is not being applied correctly.",
2983            NodeKind::ALL_KIND_NAMES.len()
2984        );
2985    }
2986}