diff --git a/src/ast.rs b/src/ast.rs index e3a908d5a..403e6b639 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1965,7 +1965,7 @@ pub fn dump(&self, orig: &wstr) -> WString { // dot-| padding result += &str::repeat("! ", depth)[..]; - if let Some(n) = node.as_argument() { + if let Kind::Argument(n) = node.kind() { result += "argument"; if let Some(argsrc) = n.try_source(orig) { sprintf!(=> &mut result, ": '%ls'", argsrc); diff --git a/src/builtins/commandline.rs b/src/builtins/commandline.rs index 3c857fe03..7a75d8139 100644 --- a/src/builtins/commandline.rs +++ b/src/builtins/commandline.rs @@ -1,5 +1,5 @@ use super::prelude::*; -use crate::ast::{Ast, Leaf}; +use crate::ast::{Ast, Kind, Leaf}; use crate::common::{unescape_string, UnescapeFlags, UnescapeStringStyle}; use crate::complete::Completion; use crate::expand::{expand_string, ExpandFlags, ExpandResultCode}; @@ -116,7 +116,7 @@ fn strip_dollar_prefixes(insert_mode: AppendMode, prefix: &wstr, insert: &wstr) let mut stripped = WString::new(); let mut have = prefix.len(); for node in ast.walk() { - let Some(ds) = node.as_decorated_statement() else { + let Kind::DecoratedStatement(ds) = node.kind() else { continue; }; let Some(range) = ds.command.range() else { diff --git a/src/builtins/fish_indent.rs b/src/builtins/fish_indent.rs index 13b26b320..03d1fc78c 100644 --- a/src/builtins/fish_indent.rs +++ b/src/builtins/fish_indent.rs @@ -351,7 +351,7 @@ fn compute_multi_line_brace_statement_locations(&self) -> Vec { .collect(); let mut next_newline = 0; for node in Traversal::new(self.ast.top()) { - let Some(brace_statement) = node.as_brace_statement() else { + let Kind::BraceStatement(brace_statement) = node.kind() else { continue; }; while next_newline != newline_offsets.len() @@ -402,15 +402,15 @@ fn gap_text_flags_before_node(&self, node: &dyn Node) -> GapFlags { let p = self.traversal.parent(p); assert_eq!(p.typ(), Type::statement); let p = self.traversal.parent(p); - if let Some(job) = p.as_job_pipeline() { + if let Kind::JobPipeline(job) = p.kind() { if !job.variables.is_empty() { result.allow_escaped_newlines = true; } - } else if let Some(job_cnt) = p.as_job_continuation() { + } else if let Kind::JobContinuation(job_cnt) = p.kind() { if !job_cnt.variables.is_empty() { result.allow_escaped_newlines = true; } - } else if let Some(not_stmt) = p.as_not_statement() { + } else if let Kind::NotStatement(not_stmt) = p.kind() { if !not_stmt.variables.is_empty() { result.allow_escaped_newlines = true; } @@ -727,14 +727,12 @@ fn visit_semi_nl(&mut self, node: &dyn ast::Token) { } fn is_multi_line_brace(&self, node: &dyn ast::Token) -> bool { - self.traversal - .parent(node.as_node()) - .as_brace_statement() - .is_some_and(|brace_statement| { - self.multi_line_brace_statement_locations - .binary_search(&brace_statement.source_range().start()) - .is_ok() - }) + let Kind::BraceStatement(brace) = self.traversal.parent(node.as_node()).kind() else { + return false; + }; + self.multi_line_brace_statement_locations + .binary_search(&brace.source_range().start()) + .is_ok() } fn visit_left_brace(&mut self, node: &dyn ast::Token) { let range = node.source_range(); diff --git a/src/highlight/highlight.rs b/src/highlight/highlight.rs index 36c635acc..a0fa91d9f 100644 --- a/src/highlight/highlight.rs +++ b/src/highlight/highlight.rs @@ -278,7 +278,10 @@ fn autosuggest_parse_command( ); // Find the first statement. - let jc = ast.top().as_job_list().unwrap().get(0)?; + let Kind::JobList(job_list) = ast.top().kind() else { + panic!("Expected job list"); + }; + let jc = job_list.get(0)?; let first_statement = jc.job.statement.as_decorated_statement()?; if let Some(expanded_command) = statement_get_expanded_command(buff, first_statement, ctx) { diff --git a/src/history.rs b/src/history.rs index 72b71ef3b..bd036b876 100644 --- a/src/history.rs +++ b/src/history.rs @@ -47,7 +47,7 @@ use rand::Rng; use crate::{ - ast::{Ast, Node}, + ast::{Ast, Kind, Node}, common::{ str2wcstring, unescape_string, valid_var_name, wcs2zstring, CancelChecker, UnescapeStringStyle, @@ -1585,12 +1585,12 @@ pub fn add_pending_with_file_detection( let mut potential_paths = Vec::new(); for node in ast.walk() { - if let Some(arg) = node.as_argument() { + if let Kind::Argument(arg) = node.kind() { let potential_path = arg.source(s); if string_could_be_path(potential_path) { potential_paths.push(potential_path.to_owned()); } - } else if let Some(stmt) = node.as_decorated_statement() { + } else if let Kind::DecoratedStatement(stmt) = node.kind() { // Hack hack hack - if the command is likely to trigger an exit, then don't do // background file detection, because we won't be able to write it to our history file // before we exit. diff --git a/src/parse_util.rs b/src/parse_util.rs index 726165fda..d87d595d9 100644 --- a/src/parse_util.rs +++ b/src/parse_util.rs @@ -1335,7 +1335,8 @@ pub fn parse_util_detect_errors_in_argument_list( // Get the root argument list and extract arguments from it. // Test each of these. - let args = &ast.top().as_freestanding_argument_list().unwrap().arguments; + let arg_list: &ast::FreestandingArgumentList = ast.top().cast().unwrap(); + let args = &arg_list.arguments; for arg in args.iter() { let arg_src = arg.source(arg_list_src); if parse_util_detect_errors_in_argument(arg, arg_src, &mut Some(&mut errors)).is_err() { @@ -1565,19 +1566,22 @@ fn detect_errors_in_backgrounded_job( // foo & ; or bar // if foo & ; end // while foo & ; end - let Some(job_conj) = traversal.parent(job).as_job_conjunction() else { + let Kind::JobConjunction(job_conj) = traversal.parent(job).kind() else { return false; }; let job_conj_parent = traversal.parent(job_conj); - if job_conj_parent.as_if_clause().is_some() || job_conj_parent.as_while_header().is_some() { + if matches!( + job_conj_parent.kind(), + Kind::IfClause(_) | Kind::WhileHeader(_) + ) { errored = append_syntax_error!( parse_errors, source_range.start(), source_range.length(), BACKGROUND_IN_CONDITIONAL_ERROR_MSG ); - } else if let Some(jlist) = job_conj_parent.as_job_list() { + } else if let Kind::JobList(jlist) = job_conj_parent.kind() { // This isn't very complete, e.g. we don't catch 'foo & ; not and bar'. // Find the index of ourselves in the job list. let index = jlist @@ -1631,13 +1635,18 @@ fn detect_errors_in_decorated_statement( } // Get the statement we are part of. - let st = traversal.parent(dst).as_statement().unwrap(); + let Kind::Statement(st) = traversal.parent(dst).kind() else { + panic!(); + }; // Walk up to the job. let job = traversal .parent_nodes() - .find_map(|n| n.as_job_pipeline()) - .expect("Should have found the job"); + .find_map(|n| match n.kind() { + Kind::JobPipeline(job) => Some(job), + _ => None, + }) + .expect("should have found the job"); // Check our pipeline position. let pipe_pos = if job.continuation.is_empty() { @@ -1748,10 +1757,10 @@ fn detect_errors_in_decorated_statement( // loop from the ancestor alone; we need the header. That is, we hit a // block_statement, and have to check its header. let mut found_loop = false; - for block in traversal - .parent_nodes() - .filter_map(|anc| anc.as_block_statement()) - { + for block in traversal.parent_nodes().filter_map(|anc| match anc.kind() { + Kind::BlockStatement(block) => Some(block), + _ => None, + }) { match block.header { ast::BlockStatementHeader::For(_) | ast::BlockStatementHeader::While(_) => { // This is a loop header, so we can break or continue. @@ -1832,7 +1841,7 @@ fn detect_errors_in_block_redirection_list( return false; }; let r = first_arg.source_range(); - if parent.as_brace_statement().is_some() { + if let Kind::BraceStatement(_) = parent.kind() { append_syntax_error!(out_errors, r.start(), r.length(), RIGHT_BRACE_ARG_ERR_MSG); } else { append_syntax_error!(out_errors, r.start(), r.length(), END_ARG_ERR_MSG); diff --git a/src/parser.rs b/src/parser.rs index 033216ca5..26e3c879b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,6 +1,6 @@ // The fish parser. Contains functions for parsing and evaluating code. -use crate::ast::{self, Ast, Node}; +use crate::ast::{self, Ast, Kind, Node}; use crate::builtins::shared::STATUS_ILLEGAL_CMD; use crate::common::{ escape_string, wcs2string, CancelChecker, EscapeFlags, EscapeStringStyle, FilenameRef, @@ -556,7 +556,9 @@ pub fn eval_parsed_source( block_type: BlockType, ) -> EvalRes { assert!([BlockType::top, BlockType::subst].contains(&block_type)); - let job_list = ps.ast.top().as_job_list().unwrap(); + let Kind::JobList(job_list) = ps.ast.top().kind() else { + panic!("Expected a job list"); + }; if !job_list.is_empty() { // Execute the top job list. self.eval_node(ps, job_list, io, job_group, block_type) @@ -734,7 +736,9 @@ pub fn expand_argument_list( // Get the root argument list and extract arguments from it. let mut result = vec![]; - let list = ast.top().as_freestanding_argument_list().unwrap(); + let Kind::FreestandingArgumentList(list) = ast.top().kind() else { + panic!("Expected a freestanding argument list"); + }; for arg in &list.arguments { let arg_src = arg.source(arg_list_src); if matches!( diff --git a/src/reader.rs b/src/reader.rs index b4d9ab93a..c4e920f68 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -45,7 +45,7 @@ use errno::{errno, Errno}; use crate::abbrs::abbrs_match; -use crate::ast::{is_same_node, Ast}; +use crate::ast::{is_same_node, Ast, Kind}; use crate::builtins::shared::ErrorCode; use crate::builtins::shared::STATUS_CMD_ERROR; use crate::builtins::shared::STATUS_CMD_OK; @@ -5381,10 +5381,10 @@ fn extract_tokens(s: &wstr) -> Vec { if !has_cmd_subs { // Common case of no command substitutions in this leaf node. // Check if a node is the command portion of a decorated statement. - let is_cmd = traversal - .parent(node) - .as_decorated_statement() - .is_some_and(|stmt| is_same_node(node, &stmt.command)); + let mut is_cmd = false; + if let Kind::DecoratedStatement(stmt) = traversal.parent(node).kind() { + is_cmd = is_same_node(node, &stmt.command); + } result.push(PositionedToken { range, is_cmd }) } } diff --git a/src/tests/parser.rs b/src/tests/parser.rs index de047c513..aa10d12c1 100644 --- a/src/tests/parser.rs +++ b/src/tests/parser.rs @@ -1,4 +1,4 @@ -use crate::ast::{self, is_same_node, Ast, JobPipeline, Kind, Node, Traversal}; +use crate::ast::{self, is_same_node, Ast, Castable, JobList, JobPipeline, Kind, Node, Traversal}; use crate::common::ScopeGuard; use crate::env::EnvStack; use crate::expand::ExpandFlags; @@ -35,7 +35,10 @@ fn detect_argument_errors(src: &str) -> Result<(), ParserTestErrorBits> { if ast.errored() { return Err(ParserTestErrorBits::ERROR); } - let args = &ast.top().as_freestanding_argument_list().unwrap().arguments; + let Kind::FreestandingArgumentList(args) = ast.top().kind() else { + panic!("Expected free standing argument list"); + }; + let args = &args.arguments; let first_arg = args.get(0).expect("Failed to parse an argument"); let mut errors = None; parse_util_detect_errors_in_argument(first_arg, first_arg.source(&src), &mut errors) @@ -424,7 +427,7 @@ fn test_1_parse_ll2(src: &wstr) -> Option<(WString, WString, StatementDecoration // Get the statement. Should only have one. let mut statement = None; for n in Traversal::new(ast.top()) { - if let Some(tmp) = n.as_decorated_statement() { + if let Kind::DecoratedStatement(tmp) = n.kind() { assert!( statement.is_none(), "More than one decorated statement found in '{}'", @@ -786,7 +789,7 @@ fn test_line_counter() { assert_eq!(line_offset, expected); } - let pipelines: Vec<_> = ps.ast.walk().filter_map(|n| n.as_job_pipeline()).collect(); + let pipelines: Vec<_> = ps.ast.walk().filter_map(ast::JobPipeline::cast).collect(); assert_eq!(pipelines.len(), 3); let src_offsets = [0, 0, 2]; assert_eq!(line_counter.source_offset_of_node(), None); @@ -832,7 +835,7 @@ struct TrueSemiAstTester<'a> { impl<'a> TrueSemiAstTester<'a> { const TRUE_SEMI: &'static wstr = L!("true;"); fn new(ast: &'a Ast) -> Self { - let job_list = ast.top().as_job_list().expect("Expected job_list"); + let job_list: &JobList = ast.top().cast().unwrap(); let job_conjunction = &job_list[0]; let job_pipeline = &job_conjunction.job; let variable_assignment_list = &job_pipeline.variables; @@ -894,7 +897,7 @@ fn test_ast() { // Light testing of the AST and traversals. let ast = Ast::parse(TrueSemiAstTester::TRUE_SEMI, ParseTreeFlags::empty(), None); let tester = TrueSemiAstTester::new(&ast); - assert!(ast.top().as_job_list().is_some(), "Expected job_list"); + assert!(ast.top().cast::().is_some(), "Expected job_list"); // Walk the AST and collect all nodes. // See is_same_node comments for why we can't use assert_eq! here.