diff --git a/src/ast.rs b/src/ast.rs index 4c1493b04265..06fad986c4d0 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -76,7 +76,7 @@ impl AcceptorMut for Option { /// Node is the base trait of all AST nodes. #[enum_dispatch] -pub trait Node: Acceptor + ConcreteNode + std::fmt::Debug { +pub trait Node: Acceptor + std::fmt::Debug { /// The parent node, or null if this is root. fn parent(&self) -> Option<&dyn Node>; @@ -88,15 +88,15 @@ pub trait Node: Acceptor + ConcreteNode + std::fmt::Debug { /// \return a helpful string description of this node. fn describe(&self) -> WString { - let mut res = ast_type_to_string(self.typ()).to_owned(); - if let Some(n) = self.as_token() { - let token_type = n.token_type().to_wstr(); - sprintf!(=> &mut res, " '%ls'", token_type); - } else if let Some(n) = self.as_keyword() { - let keyword = n.keyword().to_wstr(); - sprintf!(=> &mut res, " '%ls'", keyword); + match self.as_node() { + NodeEnumRef::Leaf(LeafRef::Token(n)) => { + sprintf!(" '%ls'", n.token_type().to_wstr()) + } + NodeEnumRef::Leaf(LeafRef::Keyword(n)) => { + sprintf!(" '%ls'", n.keyword().to_wstr()) + } + _ => ast_type_to_string(self.typ()).to_owned(), } - res } /// \return the source range for this node, or none if unsourced. @@ -196,9 +196,6 @@ macro_rules! define_node { $(#[$node_attr:meta])* pub enum $node:ident; - $(#[$downcast_attr:meta])* - $downcast_vis:vis trait $downcast:ident; - $(#[$downcast_mut_attr:meta])* $downcast_mut_vis:vis trait $downcast_mut:ident; @@ -390,29 +387,6 @@ macro_rules! define_node { )* )* - $(#[$downcast_attr])* - $downcast_vis trait $downcast { - // as_branch, as_leaf, as_list - $( - fn [< as_ $category:snake >] (&self) -> Option< [< $category Ref >] <'_> > { - None - } - )* - - // as_keyword, as_token - $($( - fn [< as_ $subcat:snake >] (&self) -> Option< [< $subcat Ref >] <'_> > { - None - } - )*)* - - $($( - fn [< as_ $variant:snake >] (&self) -> Option<&$variant> { - None - } - )*)* - } - $(#[$downcast_mut_attr])* $downcast_mut_vis trait $downcast_mut { // as_mut_branch, as_mut_leaf, as_mut_list @@ -436,40 +410,6 @@ macro_rules! define_node { )*)* } - impl $downcast for &T { - $( - fn [< as_ $category:snake >] (&self) -> Option< [< $category Ref >] <'_> > { - T::[< as_ $category:snake >](self) - } - )* - $($( - fn [< as_ $subcat:snake >] (&self) -> Option< [< $subcat Ref >] <'_> > { - T::[< as_ $subcat:snake >](self) - } - )*)* - $($( - fn [< as_ $variant:snake >] (&self) -> Option<&$variant> { - T::[< as_ $variant:snake >](self) - } - )*)* - } - impl $downcast for &mut T { - $( - fn [< as_ $category:snake >] (&self) -> Option< [< $category Ref >] <'_> > { - T::[< as_ $category:snake >](self) - } - )* - $($( - fn [< as_ $subcat:snake >] (&self) -> Option< [< $subcat Ref >] <'_> > { - T::[< as_ $subcat:snake >](self) - } - )*)* - $($( - fn [< as_ $variant:snake >] (&self) -> Option<&$variant> { - T::[< as_ $variant:snake >](self) - } - )*)* - } impl $downcast_mut for &mut T { $( fn [< as_mut_ $category:snake >] (&mut self) -> Option< [< $category RefMut >] <'_> > { @@ -489,16 +429,6 @@ macro_rules! define_node { } $($( - impl $downcast for $variant { - fn [< as_ $category:snake >] (&self) -> Option< [< $category Ref >] <'_> > { - Some([< $category Ref >]::$variant(self)) - } - - fn [< as_ $variant:snake >] (&self) -> Option<&Self> { - Some(self) - } - } - impl $downcast_mut for $variant { fn [< as_mut_ $category:snake >] (&mut self) -> Option< [< $category RefMut >] <'_> > { Some([< $category RefMut >]::$variant(self)) @@ -511,16 +441,6 @@ macro_rules! define_node { )*)* $($($( - impl $downcast for $subvariant { - fn [< as_ $category:snake >] (&self) -> Option< [< $category Ref >] <'_> > { - Some([< $category Ref >]::$subcat([< $subcat Ref >]::$subvariant(self))) - } - - fn [< as_ $subcat:snake >] (&self) -> Option< [< $subcat Ref >] <'_> > { - Some([< $subcat Ref >]::$subvariant(self)) - } - } - impl $downcast_mut for $subvariant { fn [< as_mut_ $category:snake >] (&mut self) -> Option< [< $category RefMut >] <'_> > { Some([< $category RefMut >]::$subcat([< $subcat RefMut >]::$subvariant(self))) @@ -549,18 +469,15 @@ macro_rules! define_node { } define_node! { - #[enum_dispatch(Acceptor, ConcreteNode, Node)] + #[enum_dispatch(Acceptor, Node)] #[derive(Debug)] pub enum NodeEnum; - #[enum_dispatch] - pub trait ConcreteNode; - #[allow(dead_code)] #[enum_dispatch] trait ConcreteNodeMut; - #[enum_dispatch(Acceptor, ConcreteNode, Node)] + #[enum_dispatch(Acceptor, Node)] #[derive(Debug)] pub enum Branch { Redirection, @@ -587,14 +504,14 @@ define_node! { FreestandingArgumentList, } - #[enum_dispatch(Acceptor, ConcreteNode, Node, Leaf)] + #[enum_dispatch(Acceptor, Node, Leaf)] #[derive(Debug)] pub enum Leaf { VariableAssignment, MaybeNewlines, Argument, @ - #[enum_dispatch(Acceptor, ConcreteNode, Node, Leaf, Keyword)] + #[enum_dispatch(Acceptor, Node, Leaf, Keyword)] #[derive(Debug)] pub enum Keyword { DecoratedStatementDecorator, @@ -612,7 +529,7 @@ define_node! { KeywordTime, KeywordWhile, } - #[enum_dispatch(Acceptor, ConcreteNode, Node, Leaf, Token)] + #[enum_dispatch(Acceptor, Node, Leaf, Token)] #[derive(Debug)] pub enum Token { SemiNl, @@ -624,7 +541,7 @@ define_node! { } } - #[enum_dispatch(Acceptor, ConcreteNode, Node)] + #[enum_dispatch(Acceptor, Node)] #[derive(Debug)] pub enum List { VariableAssignmentList, @@ -2493,14 +2410,14 @@ impl Ast { // dot-| padding result += &str::repeat("! ", depth)[..]; - if let Some(n) = node.as_argument() { + if let NodeEnumRef::Leaf(LeafRef::Argument(n)) = node { result += "argument"; if let Some(argsrc) = n.try_source(orig) { sprintf!(=> &mut result, ": '%ls'", argsrc); } - } else if let Some(n) = node.as_keyword() { + } else if let NodeEnumRef::Leaf(LeafRef::Keyword(n)) = node { sprintf!(=> &mut result, "keyword: %ls", n.keyword().to_wstr()); - } else if let Some(n) = node.as_token() { + } else if let NodeEnumRef::Leaf(LeafRef::Token(n)) = node { let desc = match n.token_type() { ParseTokenType::string => { let mut desc = WString::from_str("string"); @@ -2556,8 +2473,8 @@ struct SourceRangeVisitor { impl<'a> NodeVisitor<'a> for SourceRangeVisitor { fn visit(&mut self, node: NodeEnumRef<'a>) { - match node.category() { - Category::leaf => match node.as_leaf().unwrap().range() { + match node { + NodeEnumRef::Leaf(leaf) => match leaf.range() { None => self.any_unsourced = true, // Union with our range. Some(range) if range.length > 0 => { @@ -2920,39 +2837,26 @@ impl Populator<'_> { // incomplete; in particular parent nodes are not set. let mut cursor = node.as_node(); let header = loop { - match cursor.typ() { - Type::block_statement => { - let NodeEnumRef::Branch(BranchRef::BlockStatement(block_stmt)) = cursor else { - panic!(); - }; - cursor = block_stmt.header.embedded_node(); - } - Type::for_header => { - let n = cursor.as_for_header().unwrap(); - break Some((n.kw_for.range.unwrap(), L!("for loop"))); - } - Type::while_header => { - let n = cursor.as_while_header().unwrap(); - break Some((n.kw_while.range.unwrap(), L!("while loop"))); - } - Type::function_header => { - let n = cursor.as_function_header().unwrap(); - break Some((n.kw_function.range.unwrap(), L!("function definition"))); - } - Type::begin_header => { - let n = cursor.as_begin_header().unwrap(); - break Some((n.kw_begin.range.unwrap(), L!("begin"))); - } - Type::if_statement => { - let n = cursor.as_if_statement().unwrap(); - break Some((n.if_clause.kw_if.range.unwrap(), L!("if statement"))); - } - Type::switch_statement => { - let n = cursor.as_switch_statement().unwrap(); - break Some((n.kw_switch.range.unwrap(), L!("switch statement"))); - } - _ => break None, + let NodeEnumRef::Branch(branch) = cursor else { + break None; + }; + + if let BranchRef::BlockStatement(n) = branch { + cursor = n.header.embedded_node(); + continue; } + + let (range, stmt) = match branch { + BranchRef::ForHeader(n) => (n.kw_for.range, L!("for loop")), + BranchRef::WhileHeader(n) => (n.kw_while.range, L!("while loop")), + BranchRef::FunctionHeader(n) => (n.kw_function.range, L!("function definition")), + BranchRef::BeginHeader(n) => (n.kw_begin.range, L!("begin")), + BranchRef::IfStatement(n) => (n.if_clause.kw_if.range, L!("if statement")), + BranchRef::SwitchStatement(n) => (n.kw_switch.range, L!("switch statement")), + _ => break None, + }; + + break Some((range.unwrap(), stmt)); }; if let Some((header_kw_range, enclosing_stmt)) = header { diff --git a/src/bin/fish_indent.rs b/src/bin/fish_indent.rs index 060421a36be1..d948f99bd4f1 100644 --- a/src/bin/fish_indent.rs +++ b/src/bin/fish_indent.rs @@ -14,8 +14,8 @@ use std::sync::atomic::Ordering; use libc::{LC_ALL, STDOUT_FILENO}; use fish::ast::{ - self, Ast, Category, ConcreteNode, Leaf, List, Node, NodeEnumRef, NodeVisitor, SourceRangeList, - Token, Traversal, Type, + self, Acceptor, Ast, BranchRef, Category, Leaf, LeafRef, List, ListRef, Node, NodeEnumRef, + NodeVisitor, SourceRangeList, Token, TokenRef, Traversal, Type, }; use fish::builtins::shared::{STATUS_CMD_ERROR, STATUS_CMD_OK}; use fish::common::{ @@ -215,17 +215,15 @@ impl<'source, 'ast> PrettyPrinter<'source, 'ast> { // andor_job_lists get semis if the input uses semis. for node in Traversal::new(self.ast.top()) { // See if we have a condition and an andor_job_list. - let condition; - let andors; - if let Some(ifc) = node.as_if_clause() { - condition = ifc.condition.semi_nl.as_ref(); - andors = &ifc.andor_tail; - } else if let Some(wc) = node.as_while_header() { - condition = wc.condition.semi_nl.as_ref(); - andors = &wc.andor_tail; - } else { - continue; - } + let (condition, andors) = match node { + NodeEnumRef::Branch(BranchRef::IfClause(ifc)) => { + (ifc.condition.semi_nl.as_ref(), &ifc.andor_tail) + } + NodeEnumRef::Branch(BranchRef::WhileHeader(wc)) => { + (wc.condition.semi_nl.as_ref(), &wc.andor_tail) + } + _ => continue, + }; // If there is no and-or tail then we always use a newline. if andors.count() > 0 { @@ -239,7 +237,7 @@ impl<'source, 'ast> PrettyPrinter<'source, 'ast> { // `x ; and y` gets semis if it has them already, and they are on the same line. for node in Traversal::new(self.ast.top()) { - let Some(job_list) = node.as_job_list() else { + let NodeEnumRef::List(ListRef::JobList(job_list)) = node else { continue; }; let mut prev_job_semi_nl = None; @@ -285,14 +283,16 @@ impl<'source, 'ast> PrettyPrinterState<'source, 'ast> { // \return gap text flags for the gap text that comes *before* a given node type. fn gap_text_flags_before_node(&self, node: impl Node) -> GapFlags { let mut result = GapFlags::default(); - match node.typ() { + match node.as_node() { // Allow escaped newlines before leaf nodes that can be part of a long command. - Type::argument | Type::redirection | Type::variable_assignment => { - result.allow_escaped_newlines = true + NodeEnumRef::Leaf(LeafRef::Argument(_)) + | NodeEnumRef::Leaf(LeafRef::VariableAssignment(_)) + | NodeEnumRef::Branch(BranchRef::Redirection(_)) => { + result.allow_escaped_newlines = true; } - Type::token_base => { + NodeEnumRef::Leaf(LeafRef::Token(token)) => { // Allow escaped newlines before && and ||, and also pipes. - match node.as_token().unwrap().token_type() { + match token.token_type() { ParseTokenType::andand | ParseTokenType::oror | ParseTokenType::pipe => { result.allow_escaped_newlines = true; } @@ -305,16 +305,17 @@ impl<'source, 'ast> PrettyPrinterState<'source, 'ast> { } let p = p.parent().unwrap(); assert_eq!(p.typ(), Type::statement); - let p = p.parent().unwrap(); - if let Some(job) = p.as_job_pipeline() { + let p = p.parent().unwrap().as_node(); + + if let NodeEnumRef::Branch(BranchRef::JobPipeline(job)) = p { if !job.variables.is_empty() { result.allow_escaped_newlines = true; } - } else if let Some(job_cnt) = p.as_job_continuation() { + } else if let NodeEnumRef::Branch(BranchRef::JobContinuation(job_cnt)) = p { if !job_cnt.variables.is_empty() { result.allow_escaped_newlines = true; } - } else if let Some(not_stmt) = p.as_not_statement() { + } else if let NodeEnumRef::Branch(BranchRef::NotStatement(not_stmt)) = p { if !not_stmt.variables.is_empty() { result.allow_escaped_newlines = true; } @@ -691,42 +692,24 @@ fn parse_flags() -> ParseTreeFlags { impl<'source, 'ast> NodeVisitor<'_> for PrettyPrinterState<'source, 'ast> { // Default implementation is to just visit children. fn visit(&mut self, node: NodeEnumRef<'_>) { - // Leaf nodes we just visit their text. - if node.as_keyword().is_some() { - self.emit_node_text(node); - return; - } - if let Some(token) = node.as_token() { - if token.token_type() == ParseTokenType::end { - self.visit_semi_nl(token); - return; - } - self.emit_node_text(node); - return; - } - match node.typ() { - Type::argument | Type::variable_assignment => { - self.emit_node_text(node); - } - Type::redirection => { - self.visit_redirection(node.as_redirection().unwrap()); - } - Type::maybe_newlines => { - self.visit_maybe_newlines(node.as_maybe_newlines().unwrap()); - } - Type::begin_header => { + match node { + // Leaf nodes we just visit their text. + NodeEnumRef::Leaf(l) => match l { + LeafRef::Token(TokenRef::SemiNl(n)) => self.visit_semi_nl(n), + LeafRef::MaybeNewlines(n) => self.visit_maybe_newlines(n), + _ => self.emit_node_text(l), + }, + + NodeEnumRef::Branch(BranchRef::Redirection(n)) => self.visit_redirection(n), + NodeEnumRef::Branch(BranchRef::BeginHeader(n)) => { // 'begin' does not require a newline after it, but we insert one. - node.enum_accept(self, false); + n.accept(self, false); self.visit_begin_header(); } - _ => { - // For branch and list nodes, default is to visit their children. - if [Category::branch, Category::list].contains(&node.category()) { - node.enum_accept(self, false); - return; - } - panic!("unexpected node type"); - } + + // For branch and list nodes, default is to visit their children. + NodeEnumRef::Branch(n) => n.accept(self, false), + NodeEnumRef::List(n) => n.accept(self, false), } } } diff --git a/src/highlight.rs b/src/highlight.rs index 4ab97f3b6594..4fc99b47fdf9 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -1,9 +1,9 @@ //! Functions for syntax highlighting. use crate::abbrs::{self, with_abbrs}; use crate::ast::{ - self, Argument, Ast, BlockStatement, BlockStatementHeaderVariant, ConcreteNode, - DecoratedStatement, Keyword, Leaf, List, Node, NodeEnumRef, NodeVisitor, Redirection, Token, - Type, VariableAssignment, + self, Argument, Ast, BlockStatement, BlockStatementHeaderVariant, BranchRef, + DecoratedStatement, Keyword, Leaf, LeafRef, List, Node, NodeEnumRef, NodeVisitor, Redirection, + Token, TokenRef, VariableAssignment, }; use crate::builtins::shared::builtin_exists; use crate::color::RgbColor; @@ -297,8 +297,10 @@ fn autosuggest_parse_command( ); // Find the first statement. - let top = ast.top(); - let jc = top.as_job_list().unwrap().get(0)?; + let NodeEnumRef::List(ast::ListRef::JobList(job_list)) = ast.top() else { + panic!() + }; + let jc = job_list.get(0)?; let first_statement = jc.job.statement.contents.as_decorated_statement()?; if let Some(expanded_command) = statement_get_expanded_command(buff, first_statement, ctx) { @@ -1448,27 +1450,23 @@ fn contains_pending_variable(pending_variables: &[&wstr], haystack: &wstr) -> bo impl<'s, 'a> NodeVisitor<'a> for Highlighter<'s> { fn visit(&mut self, node: NodeEnumRef<'a>) { - if let Some(keyword) = node.as_keyword() { - return self.visit_keyword(keyword); - } - if let Some(token) = node.as_token() { - if token.token_type() == ParseTokenType::end { - self.visit_semi_nl(node); - return; - } - self.visit_token(token); - return; - } - match node.typ() { - Type::argument => self.visit_argument(node.as_argument().unwrap(), false, true), - Type::redirection => self.visit_redirection(node.as_redirection().unwrap()), - Type::variable_assignment => { - self.visit_variable_assignment(node.as_variable_assignment().unwrap()) - } - Type::decorated_statement => { - self.visit_decorated_statement(node.as_decorated_statement().unwrap()) - } - Type::block_statement => self.visit_block_statement(node.as_block_statement().unwrap()), + match node { + NodeEnumRef::Leaf(l) => match l { + LeafRef::Keyword(n) => self.visit_keyword(n), + LeafRef::Token(TokenRef::SemiNl(n)) => self.visit_semi_nl(n.as_node()), + LeafRef::Token(token) => self.visit_token(token), + LeafRef::Argument(n) => self.visit_argument(n, false, true), + LeafRef::VariableAssignment(n) => self.visit_variable_assignment(n), + _ => self.visit_children(l), + }, + + NodeEnumRef::Branch(b) => match b { + BranchRef::Redirection(n) => self.visit_redirection(n), + BranchRef::DecoratedStatement(n) => self.visit_decorated_statement(n), + BranchRef::BlockStatement(n) => self.visit_block_statement(n), + _ => self.visit_children(b), + }, + // Default implementation is to just visit children. _ => self.visit_children(node), } diff --git a/src/history.rs b/src/history.rs index 13056fc15f1c..1dbea9dbab7a 100644 --- a/src/history.rs +++ b/src/history.rs @@ -16,7 +16,12 @@ //! 5. The chaos_mode boolean can be set to true to do things like lower buffer sizes which can //! trigger race conditions. This is useful for testing. -use crate::{ast::ConcreteNode, common::cstr2wcstring, env::EnvVar, wcstringutil::trim}; +use crate::{ + ast::{BranchRef, LeafRef, NodeEnumRef}, + common::cstr2wcstring, + env::EnvVar, + wcstringutil::trim, +}; use std::{ borrow::Cow, collections::{BTreeMap, HashMap, HashSet, VecDeque}, @@ -1573,12 +1578,12 @@ impl History { let mut potential_paths = Vec::new(); for node in ast.walk() { - if let Some(arg) = node.as_argument() { + if let NodeEnumRef::Leaf(LeafRef::Argument(arg)) = node { 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 NodeEnumRef::Branch(BranchRef::DecoratedStatement(stmt)) = node { // 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_execution.rs b/src/parse_execution.rs index d93f70bd1b76..77d37ea9fe53 100644 --- a/src/parse_execution.rs +++ b/src/parse_execution.rs @@ -178,12 +178,12 @@ impl<'a> ParseExecutionContext { node: &dyn Node, associated_block: Option, ) -> EndExecutionReason { - match node.typ() { - ast::Type::statement => { - self.eval_statement(ctx, node.as_statement().unwrap(), associated_block) + match node.as_node() { + ast::NodeEnumRef::Branch(ast::BranchRef::Statement(n)) => { + self.eval_statement(ctx, n, associated_block) } - ast::Type::job_list => { - self.eval_job_list(ctx, node.as_job_list().unwrap(), associated_block.unwrap()) + ast::NodeEnumRef::List(ast::ListRef::JobList(n)) => { + self.eval_job_list(ctx, n, associated_block.unwrap()) } _ => unreachable!(), } diff --git a/src/parse_util.rs b/src/parse_util.rs index c027b2ed804b..622b6f4e30a4 100644 --- a/src/parse_util.rs +++ b/src/parse_util.rs @@ -1,6 +1,7 @@ //! Various mostly unrelated utility functions related to parsing, loading and evaluating fish code. use crate::ast::{ - self, Ast, ConcreteNode, Keyword, Leaf, List, Node, NodeEnumRef, NodeVisitor, Token, + self, Ast, BranchRef, Keyword, Leaf, LeafRef, List, ListRef, Node, NodeEnumRef, NodeVisitor, + Token, TokenRef, }; use crate::builtins::shared::builtin_exists; use crate::common::{ @@ -913,15 +914,15 @@ impl<'a> NodeVisitor<'a> for IndentVisitor<'a> { let mut inc = 0; let mut dec = 0; use ast::{Category, Type}; - match node.typ() { - Type::job_list | Type::andor_job_list => { + match node { + NodeEnumRef::List(ListRef::JobList(_) | ListRef::AndorJobList(_)) => { // Job lists are never unwound. inc = 1; dec = 1; } // Increment indents for conditions in headers (#1665). - Type::job_conjunction => { + NodeEnumRef::Branch(BranchRef::JobConjunction(_)) => { if [Type::while_header, Type::if_clause].contains(&node.parent().unwrap().typ()) { inc = 1; dec = 1; @@ -937,22 +938,22 @@ impl<'a> NodeVisitor<'a> for IndentVisitor<'a> { // ....cmd3 // end // See #7252. - Type::job_continuation => { - if self.has_newline(&node.as_job_continuation().unwrap().newlines) { + NodeEnumRef::Branch(BranchRef::JobContinuation(n)) => { + if self.has_newline(&n.newlines) { inc = 1; dec = 1; } } // Likewise for && and ||. - Type::job_conjunction_continuation => { - if self.has_newline(&node.as_job_conjunction_continuation().unwrap().newlines) { + NodeEnumRef::Branch(BranchRef::JobConjunctionContinuation(n)) => { + if self.has_newline(&n.newlines) { inc = 1; dec = 1; } } - Type::case_item_list => { + NodeEnumRef::List(ListRef::CaseItemList(_)) => { // Here's a hack. Consider: // switch abc // cas @@ -971,16 +972,19 @@ impl<'a> NodeVisitor<'a> for IndentVisitor<'a> { // To address this, if we see that the switch statement was not closed, do not // decrement the indent afterwards. inc = 1; - let switchs = node.parent().unwrap().as_switch_statement().unwrap(); + let NodeEnumRef::Branch(BranchRef::SwitchStatement(switchs)) = + node.parent().unwrap().as_node() + else { + panic!() + }; dec = if switchs.end.has_source() { 1 } else { 0 }; } - Type::token_base => { - if node.parent().unwrap().typ() == Type::begin_header - && node.as_token().unwrap().token_type() == ParseTokenType::end - { + + NodeEnumRef::Leaf(LeafRef::Token(TokenRef::SemiNl(n))) => { + if n.parent().unwrap().typ() == Type::begin_header { // The newline after "begin" is optional, so it is part of the header. // The header is not in the indented block, so indent the newline here. - if node.source(self.src) == "\n" { + if n.source(self.src) == "\n" { inc = 1; dec = 1; } @@ -1110,59 +1114,80 @@ pub fn parse_util_detect_errors_in_ast( // Verify no variable expansions. for node in ast::Traversal::new(ast.top()) { - if let Some(jc) = node.as_job_continuation() { - // Somewhat clumsy way of checking for a statement without source in a pipeline. - // See if our pipe has source but our statement does not. - if jc.pipe.has_source() && jc.statement.try_source_range().is_none() { - has_unclosed_pipe = true; - } - } else if let Some(job_conjunction) = node.as_job_conjunction() { - errored |= detect_errors_in_job_conjunction(job_conjunction, &mut out_errors); - } else if let Some(jcc) = node.as_job_conjunction_continuation() { - // Somewhat clumsy way of checking for a job without source in a conjunction. - // See if our conjunction operator (&& or ||) has source but our job does not. - if jcc.conjunction.has_source() && jcc.job.try_source_range().is_none() { - has_unclosed_conjunction = true; - } - } else if let Some(arg) = node.as_argument() { - let arg_src = arg.source(buff_src); - res |= parse_util_detect_errors_in_argument(arg, arg_src, &mut out_errors) - .err() - .unwrap_or_default(); - } else if let Some(job) = node.as_job_pipeline() { - // Disallow background in the following cases: - // - // foo & ; and bar - // foo & ; or bar - // if foo & ; end - // while foo & ; end - // If it's not a background job, nothing to do. - if job.bg.is_some() { - errored |= detect_errors_in_backgrounded_job(job, &mut out_errors); - } - } else if let Some(stmt) = node.as_decorated_statement() { - errored |= detect_errors_in_decorated_statement(buff_src, stmt, &mut out_errors); - } else if let Some(block) = node.as_block_statement() { - // If our 'end' had no source, we are unsourced. - if !block.end.has_source() { - has_unclosed_block = true; - } - errored |= - detect_errors_in_block_redirection_list(&block.args_or_redirs, &mut out_errors); - } else if let Some(ifs) = node.as_if_statement() { - // If our 'end' had no source, we are unsourced. - if !ifs.end.has_source() { - has_unclosed_block = true; - } - errored |= - detect_errors_in_block_redirection_list(&ifs.args_or_redirs, &mut out_errors); - } else if let Some(switchs) = node.as_switch_statement() { - // If our 'end' had no source, we are unsourced. - if !switchs.end.has_source() { - has_unclosed_block = true; + match node { + NodeEnumRef::Branch(b) => match b { + BranchRef::JobContinuation(jc) => { + // Somewhat clumsy way of checking for a statement without source in a pipeline. + // See if our pipe has source but our statement does not. + if jc.pipe.has_source() && jc.statement.try_source_range().is_none() { + has_unclosed_pipe = true; + } + } + BranchRef::JobConjunction(job_conjunction) => { + errored |= detect_errors_in_job_conjunction(job_conjunction, &mut out_errors); + } + BranchRef::JobConjunctionContinuation(jcc) => { + // Somewhat clumsy way of checking for a job without source in a conjunction. + // See if our conjunction operator (&& or ||) has source but our job does not. + if jcc.conjunction.has_source() && jcc.job.try_source_range().is_none() { + has_unclosed_conjunction = true; + } + } + BranchRef::JobPipeline(job) => { + // Disallow background in the following cases: + // + // foo & ; and bar + // foo & ; or bar + // if foo & ; end + // while foo & ; end + // If it's not a background job, nothing to do. + if job.bg.is_some() { + errored |= detect_errors_in_backgrounded_job(job, &mut out_errors); + } + } + BranchRef::DecoratedStatement(stmt) => { + errored |= + detect_errors_in_decorated_statement(buff_src, stmt, &mut out_errors); + } + BranchRef::BlockStatement(block) => { + // If our 'end' had no source, we are unsourced. + if !block.end.has_source() { + has_unclosed_block = true; + } + errored |= detect_errors_in_block_redirection_list( + &block.args_or_redirs, + &mut out_errors, + ); + } + BranchRef::IfStatement(ifs) => { + // If our 'end' had no source, we are unsourced. + if !ifs.end.has_source() { + has_unclosed_block = true; + } + errored |= detect_errors_in_block_redirection_list( + &ifs.args_or_redirs, + &mut out_errors, + ); + } + BranchRef::SwitchStatement(switchs) => { + // If our 'end' had no source, we are unsourced. + if !switchs.end.has_source() { + has_unclosed_block = true; + } + errored |= detect_errors_in_block_redirection_list( + &switchs.args_or_redirs, + &mut out_errors, + ); + } + _ => {} + }, + NodeEnumRef::Leaf(ast::LeafRef::Argument(arg)) => { + let arg_src = arg.source(buff_src); + res |= parse_util_detect_errors_in_argument(arg, arg_src, &mut out_errors) + .err() + .unwrap_or_default(); } - errored |= - detect_errors_in_block_redirection_list(&switchs.args_or_redirs, &mut out_errors); + _ => {} } } @@ -1206,9 +1231,10 @@ pub fn parse_util_detect_errors_in_argument_list( // Get the root argument list and extract arguments from it. // Test each of these. - let top = ast.top(); - let args = &top.as_freestanding_argument_list().unwrap().arguments; - for arg in args.iter() { + let NodeEnumRef::Branch(BranchRef::FreestandingArgumentList(arg_list)) = ast.top() else { + panic!() + }; + for arg in &arg_list.arguments { let arg_src = arg.source(arg_list_src); if parse_util_detect_errors_in_argument(arg, arg_src, &mut Some(&mut errors)).is_err() { return get_error_text(&errors); @@ -1436,18 +1462,20 @@ fn detect_errors_in_backgrounded_job( return false; }; - let mut errored = false; // Disallow background in the following cases: // foo & ; and bar // foo & ; or bar // if foo & ; end // while foo & ; end - let Some(job_conj) = job.parent().unwrap().as_job_conjunction() else { + let NodeEnumRef::Branch(BranchRef::JobConjunction(job_conj)) = job.parent().unwrap().as_node() + else { return false; }; - if job_conj.parent().unwrap().as_if_clause().is_some() - || job_conj.parent().unwrap().as_while_header().is_some() + let mut errored = false; + + if let NodeEnumRef::Branch(BranchRef::IfClause(_) | BranchRef::WhileHeader(_)) = + job_conj.parent().unwrap().as_node() { errored = append_syntax_error!( parse_errors, @@ -1455,7 +1483,8 @@ fn detect_errors_in_backgrounded_job( source_range.length(), BACKGROUND_IN_CONDITIONAL_ERROR_MSG ); - } else if let Some(jlist) = job_conj.parent().unwrap().as_job_list() { + } else if let NodeEnumRef::List(ListRef::JobList(jlist)) = job_conj.parent().unwrap().as_node() + { // 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 @@ -1508,14 +1537,18 @@ fn detect_errors_in_decorated_statement( } // Get the statement we are part of. - let st = dst.parent().unwrap().as_statement().unwrap(); + let NodeEnumRef::Branch(BranchRef::Statement(st)) = dst.parent().unwrap().as_node() else { + panic!() + }; // Walk up to the job. let mut job = None; let mut cursor = dst.parent(); while job.is_none() { let c = cursor.expect("Reached root without finding a job"); - job = c.as_job_pipeline(); + if let NodeEnumRef::Branch(BranchRef::JobPipeline(j)) = c.as_node() { + job = Some(j); + } cursor = c.parent(); } let job = job.expect("Should have found the job"); @@ -1628,7 +1661,7 @@ fn detect_errors_in_decorated_statement( let mut found_loop = false; let mut ancestor: Option<&dyn Node> = Some(dst); while let Some(anc) = ancestor { - if let Some(block) = anc.as_block_statement() { + if let NodeEnumRef::Branch(BranchRef::BlockStatement(block)) = anc.as_node() { if [ast::Type::for_header, ast::Type::while_header] .contains(&block.header.typ()) { diff --git a/src/parser.rs b/src/parser.rs index 9e269e5d899f..0000537ce3fa 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::{Ast, ConcreteNode, List, Node}; +use crate::ast::{Ast, BranchRef, List, ListRef, Node, NodeEnumRef}; use crate::builtins::shared::STATUS_ILLEGAL_CMD; use crate::common::{ escape_string, scoped_push_replacer, CancelChecker, EscapeFlags, EscapeStringStyle, @@ -474,8 +474,9 @@ impl Parser { block_type: BlockType, ) -> EvalRes { assert!([BlockType::top, BlockType::subst].contains(&block_type)); - let top = ps.ast.top(); - let job_list = top.as_job_list().unwrap(); + let NodeEnumRef::List(ListRef::JobList(job_list)) = ps.ast.top() else { + panic!() + }; if !job_list.is_empty() { // Execute the top job list. self.eval_node(ps, job_list, io, job_group, block_type) @@ -617,8 +618,9 @@ impl Parser { // Get the root argument list and extract arguments from it. let mut result = vec![]; - let top = ast.top(); - let list = top.as_freestanding_argument_list().unwrap(); + let NodeEnumRef::Branch(BranchRef::FreestandingArgumentList(list)) = ast.top() else { + panic!() + }; for arg in &list.arguments { let arg_src = arg.source(arg_list_src); if expand_string(arg_src.to_owned(), &mut result, flags, ctx, None) diff --git a/src/reader.rs b/src/reader.rs index d30f2519fd3a..729158bcd762 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -4307,7 +4307,9 @@ fn extract_tokens(s: &wstr) -> Vec { let is_command = |node: &dyn ast::Node| -> bool { let mut cursor = Some(node); while let Some(cur) = cursor { - if let Some(stmt) = cur.as_decorated_statement() { + if let ast::NodeEnumRef::Branch(ast::BranchRef::DecoratedStatement(stmt)) = + cur.as_node() + { if stmt.opt_decoration.is_none() && node.pointer_eq(&stmt.command) { return true; } diff --git a/src/tests/parser.rs b/src/tests/parser.rs index 17481aa5ab66..470f578de677 100644 --- a/src/tests/parser.rs +++ b/src/tests/parser.rs @@ -1,4 +1,4 @@ -use crate::ast::{self, Ast, ConcreteNode, List, Node, Traversal}; +use crate::ast::{self, Ast, List, Node, Traversal}; use crate::common::ScopeGuard; use crate::expand::ExpandFlags; use crate::io::{IoBufferfill, IoChain}; @@ -34,8 +34,12 @@ fn test_parser() { if ast.errored() { return Err(ParserTestErrorBits::ERROR); } - let top = ast.top(); - let args = &top.as_freestanding_argument_list().unwrap().arguments; + let ast::NodeEnumRef::Branch(ast::BranchRef::FreestandingArgumentList(arg_list)) = + ast.top() + else { + panic!() + }; + let args = &arg_list.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)