ast: adopt Kind in yet more places

This commit is contained in:
Peter Ammon
2025-04-28 10:57:01 -07:00
parent 9ae01ae00d
commit dfac66082a
9 changed files with 62 additions and 45 deletions

View File

@@ -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);

View File

@@ -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 {

View File

@@ -351,7 +351,7 @@ fn compute_multi_line_brace_statement_locations(&self) -> Vec<usize> {
.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();

View File

@@ -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) {

View File

@@ -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.

View File

@@ -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);

View File

@@ -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!(

View File

@@ -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<PositionedToken> {
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 })
}
}

View File

@@ -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::<JobList>().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.