ast: remove parent pointers

This removes parent back-pointers from ast nodes. Nodes no longer store
references to their parents, resulting in memory size reductions for the ast
(__fish_complete_gpg.fish goes from 508 KB to 198 KB in memory usage).
This commit is contained in:
Peter Ammon
2025-04-05 14:46:41 -07:00
parent a528567d5c
commit ff87e2cf0a

View File

@@ -98,9 +98,6 @@ fn accept_mut(&mut self, visitor: &mut dyn NodeVisitorMut, reversed: bool) {
/// Node is the base trait of all AST nodes.
pub trait Node: Acceptor + ConcreteNode + std::fmt::Debug {
/// The parent node, or None if this is root.
fn parent(&self) -> Option<&dyn Node>;
/// The type of this node.
fn typ(&self) -> Type;
@@ -509,9 +506,6 @@ impl Node for $name {
fn typ(&self) -> Type {
Type::$type
}
fn parent(&self) -> Option<&dyn Node> {
self.parent.map(|p| unsafe { &*p })
}
fn category(&self) -> Category {
Category::$category
}
@@ -557,10 +551,6 @@ fn accept_mut(&mut self, visitor: &mut dyn NodeVisitorMut, reversed: bool) {
visitor.did_visit_fields_of(self, VisitResult::Continue(()));
}
}
impl $name {
/// Set the parent fields of all nodes in the tree rooted at `self`.
fn set_parents(&mut self) {}
}
};
}
@@ -569,7 +559,6 @@ macro_rules! define_keyword_node {
( $name:ident, $($allowed:ident),* $(,)? ) => {
#[derive(Default, Debug)]
pub struct $name {
parent: Option<*const dyn Node>,
range: Option<SourceRange>,
keyword: ParseKeyword,
}
@@ -610,7 +599,6 @@ macro_rules! define_token_node {
( $name:ident, $($allowed:ident),* $(,)? ) => {
#[derive(Default, Debug)]
pub struct $name {
parent: Option<*const dyn Node>,
range: Option<SourceRange>,
parse_token_type: ParseTokenType,
}
@@ -664,7 +652,6 @@ macro_rules! define_list_node {
) => {
#[derive(Default, Debug)]
pub struct $name {
parent: Option<*const dyn Node>,
list_contents: Box<[$contents]>,
}
implement_node!($name, list, $type);
@@ -712,15 +699,6 @@ fn accept_mut(&mut self, visitor: &mut dyn NodeVisitorMut, reversed: bool) {
visitor.did_visit_fields_of(self, flow);
}
}
impl $name {
/// Set the parent fields of all nodes in the tree rooted at `self`.
fn set_parents(&mut self) {
for i in 0..self.count() {
self[i].parent = Some(self);
self[i].set_parents();
}
}
}
};
}
@@ -811,14 +789,6 @@ fn accept_mut(&mut self, visitor: &mut dyn NodeVisitorMut, reversed: bool) {
visitor.did_visit_fields_of(self, flow);
}
}
impl $name {
/// Set the parent fields of all nodes in the tree rooted at `self`.
fn set_parents(&mut self) {
$(
set_parent_of_field!(self, $field_name, $field_type);
)*
}
}
}
}
@@ -1017,108 +987,10 @@ macro_rules! visit_result {
};
}
macro_rules! set_parent_of_field {
(
$self:ident,
$field_name:ident,
(variant<$field_type:ident>)
) => {
set_parent_of_union_field!($self, $field_name, $field_type);
};
(
$self:ident,
$field_name:ident,
(Option<$field_type:ident>)
) => {
if $self.$field_name.is_some() {
$self.$field_name.as_mut().unwrap().parent = Some($self);
$self.$field_name.as_mut().unwrap().set_parents();
}
};
(
$self:ident,
$field_name:ident,
$field_type:tt
) => {
$self.$field_name.parent = Some($self);
$self.$field_name.set_parents();
};
}
macro_rules! set_parent_of_union_field {
(
$self:ident,
$field_name:ident,
ArgumentOrRedirectionVariant
) => {
if matches!($self.$field_name, ArgumentOrRedirectionVariant::Argument(_)) {
$self.$field_name.as_mut_argument().parent = Some($self);
$self.$field_name.as_mut_argument().set_parents();
} else {
$self.$field_name.as_mut_redirection().parent = Some($self);
$self.$field_name.as_mut_redirection().set_parents();
}
};
(
$self:ident,
$field_name:ident,
StatementVariant
) => {
if matches!($self.$field_name, StatementVariant::NotStatement(_)) {
$self.$field_name.as_mut_not_statement().parent = Some($self);
$self.$field_name.as_mut_not_statement().set_parents();
} else if matches!($self.$field_name, StatementVariant::BlockStatement(_)) {
$self.$field_name.as_mut_block_statement().parent = Some($self);
$self.$field_name.as_mut_block_statement().set_parents();
} else if matches!($self.$field_name, StatementVariant::BraceStatement(_)) {
$self.$field_name.as_mut_brace_statement().parent = Some($self);
$self.$field_name.as_mut_brace_statement().set_parents();
} else if matches!($self.$field_name, StatementVariant::IfStatement(_)) {
$self.$field_name.as_mut_if_statement().parent = Some($self);
$self.$field_name.as_mut_if_statement().set_parents();
} else if matches!($self.$field_name, StatementVariant::SwitchStatement(_)) {
$self.$field_name.as_mut_switch_statement().parent = Some($self);
$self.$field_name.as_mut_switch_statement().set_parents();
} else if matches!($self.$field_name, StatementVariant::DecoratedStatement(_)) {
$self.$field_name.as_mut_decorated_statement().parent = Some($self);
$self.$field_name.as_mut_decorated_statement().set_parents();
}
};
(
$self:ident,
$field_name:ident,
BlockStatementHeaderVariant
) => {
if matches!($self.$field_name, BlockStatementHeaderVariant::ForHeader(_)) {
$self.$field_name.as_mut_for_header().parent = Some($self);
$self.$field_name.as_mut_for_header().set_parents();
} else if matches!(
$self.$field_name,
BlockStatementHeaderVariant::WhileHeader(_)
) {
$self.$field_name.as_mut_while_header().parent = Some($self);
$self.$field_name.as_mut_while_header().set_parents();
} else if matches!(
$self.$field_name,
BlockStatementHeaderVariant::FunctionHeader(_)
) {
$self.$field_name.as_mut_function_header().parent = Some($self);
$self.$field_name.as_mut_function_header().set_parents();
} else if matches!(
$self.$field_name,
BlockStatementHeaderVariant::BeginHeader(_)
) {
$self.$field_name.as_mut_begin_header().parent = Some($self);
$self.$field_name.as_mut_begin_header().set_parents();
}
};
}
/// A redirection has an operator like > or 2>, and a target like /dev/null or &1.
/// Note that pipes are not redirections.
#[derive(Default, Debug)]
pub struct Redirection {
parent: Option<*const dyn Node>,
pub oper: TokenRedirection,
pub target: String_,
}
@@ -1159,7 +1031,6 @@ fn as_mut_variable_assignment_list(&mut self) -> Option<&mut VariableAssignmentL
/// An argument or redirection holds either an argument or redirection.
#[derive(Default, Debug)]
pub struct ArgumentOrRedirection {
parent: Option<*const dyn Node>,
pub contents: ArgumentOrRedirectionVariant,
}
implement_node!(ArgumentOrRedirection, branch, argument_or_redirection);
@@ -1203,7 +1074,6 @@ fn as_mut_argument_or_redirection_list(&mut self) -> Option<&mut ArgumentOrRedir
/// A statement is a normal command, or an if / while / etc
#[derive(Default, Debug)]
pub struct Statement {
parent: Option<*const dyn Node>,
pub contents: StatementVariant,
}
implement_node!(Statement, branch, statement);
@@ -1223,7 +1093,6 @@ fn as_mut_statement(&mut self) -> Option<&mut Statement> {
/// like if statements, where we require a command).
#[derive(Default, Debug)]
pub struct JobPipeline {
parent: Option<*const dyn Node>,
/// Maybe the time keyword.
pub time: Option<KeywordTime>,
/// A (possibly empty) list of variable assignments.
@@ -1258,7 +1127,6 @@ fn as_mut_job_pipeline(&mut self) -> Option<&mut JobPipeline> {
/// A job_conjunction is a job followed by a && or || continuations.
#[derive(Default, Debug)]
pub struct JobConjunction {
parent: Option<*const dyn Node>,
/// The job conjunction decorator.
pub decorator: Option<JobConjunctionDecorator>,
/// The job itself.
@@ -1303,7 +1171,6 @@ fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
#[derive(Default, Debug)]
pub struct ForHeader {
parent: Option<*const dyn Node>,
/// 'for'
pub kw_for: KeywordFor,
/// var_name
@@ -1337,7 +1204,6 @@ fn as_mut_for_header(&mut self) -> Option<&mut ForHeader> {
#[derive(Default, Debug)]
pub struct WhileHeader {
parent: Option<*const dyn Node>,
/// 'while'
pub kw_while: KeywordWhile,
pub condition: JobConjunction,
@@ -1363,7 +1229,6 @@ fn as_mut_while_header(&mut self) -> Option<&mut WhileHeader> {
#[derive(Default, Debug)]
pub struct FunctionHeader {
parent: Option<*const dyn Node>,
pub kw_function: KeywordFunction,
/// functions require at least one argument.
pub first_arg: Argument,
@@ -1391,7 +1256,6 @@ fn as_mut_function_header(&mut self) -> Option<&mut FunctionHeader> {
#[derive(Default, Debug)]
pub struct BeginHeader {
parent: Option<*const dyn Node>,
pub kw_begin: KeywordBegin,
/// Note that 'begin' does NOT require a semi or nl afterwards.
/// This is valid: begin echo hi; end
@@ -1416,7 +1280,6 @@ fn as_mut_begin_header(&mut self) -> Option<&mut BeginHeader> {
#[derive(Default, Debug)]
pub struct BlockStatement {
parent: Option<*const dyn Node>,
/// A header like for, while, etc.
pub header: BlockStatementHeaderVariant,
/// List of jobs in this block.
@@ -1447,7 +1310,6 @@ fn as_mut_block_statement(&mut self) -> Option<&mut BlockStatement> {
#[derive(Default, Debug)]
pub struct BraceStatement {
parent: Option<*const dyn Node>,
/// The opening brace, in command position.
pub left_brace: TokenLeftBrace,
/// List of jobs in this block.
@@ -1478,7 +1340,6 @@ fn as_mut_brace_statement(&mut self) -> Option<&mut BraceStatement> {
#[derive(Default, Debug)]
pub struct IfClause {
parent: Option<*const dyn Node>,
/// The 'if' keyword.
pub kw_if: KeywordIf,
/// The 'if' condition.
@@ -1509,7 +1370,6 @@ fn as_mut_if_clause(&mut self) -> Option<&mut IfClause> {
#[derive(Default, Debug)]
pub struct ElseifClause {
parent: Option<*const dyn Node>,
/// The 'else' keyword.
pub kw_else: KeywordElse,
/// The 'if' clause following it.
@@ -1552,7 +1412,6 @@ fn as_mut_elseif_clause_list(&mut self) -> Option<&mut ElseifClauseList> {
#[derive(Default, Debug)]
pub struct ElseClause {
parent: Option<*const dyn Node>,
/// else ; body
pub kw_else: KeywordElse,
pub semi_nl: Option<SemiNl>,
@@ -1583,7 +1442,6 @@ fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
#[derive(Default, Debug)]
pub struct IfStatement {
parent: Option<*const dyn Node>,
/// if part
pub if_clause: IfClause,
/// else if list
@@ -1617,7 +1475,6 @@ fn as_mut_if_statement(&mut self) -> Option<&mut IfStatement> {
#[derive(Default, Debug)]
pub struct CaseItem {
parent: Option<*const dyn Node>,
/// case \<arguments\> ; body
pub kw_case: KeywordCase,
pub arguments: ArgumentList,
@@ -1650,7 +1507,6 @@ fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
#[derive(Default, Debug)]
pub struct SwitchStatement {
parent: Option<*const dyn Node>,
/// switch \<argument\> ; body ; end args_redirs
pub kw_switch: KeywordSwitch,
pub argument: Argument,
@@ -1684,7 +1540,6 @@ fn as_mut_switch_statement(&mut self) -> Option<&mut SwitchStatement> {
/// "builtin" or "command" or "exec"
#[derive(Default, Debug)]
pub struct DecoratedStatement {
parent: Option<*const dyn Node>,
/// An optional decoration (command, builtin, exec, etc).
pub opt_decoration: Option<DecoratedStatementDecorator>,
/// Command to run.
@@ -1713,7 +1568,6 @@ fn as_mut_decorated_statement(&mut self) -> Option<&mut DecoratedStatement> {
/// A not statement like `not true` or `! true`
#[derive(Default, Debug)]
pub struct NotStatement {
parent: Option<*const dyn Node>,
/// Keyword, either not or exclam.
pub kw: KeywordNot,
pub time: Option<KeywordTime>,
@@ -1741,7 +1595,6 @@ fn as_mut_not_statement(&mut self) -> Option<&mut NotStatement> {
#[derive(Default, Debug)]
pub struct JobContinuation {
parent: Option<*const dyn Node>,
pub pipe: TokenPipe,
pub newlines: MaybeNewlines,
pub variables: VariableAssignmentList,
@@ -1785,7 +1638,6 @@ fn as_mut_job_continuation_list(&mut self) -> Option<&mut JobContinuationList> {
#[derive(Default, Debug)]
pub struct JobConjunctionContinuation {
parent: Option<*const dyn Node>,
/// The && or || token.
pub conjunction: TokenConjunction,
pub newlines: MaybeNewlines,
@@ -1825,7 +1677,6 @@ fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
/// instances of this.
#[derive(Default, Debug)]
pub struct AndorJob {
parent: Option<*const dyn Node>,
pub job: JobConjunction,
}
implement_node!(AndorJob, branch, andor_job);
@@ -1873,7 +1724,6 @@ fn as_mut_andor_job_list(&mut self) -> Option<&mut AndorJobList> {
/// In practice the tok_ends are ignored by fish code so we do not bother to store them.
#[derive(Default, Debug)]
pub struct FreestandingArgumentList {
parent: Option<*const dyn Node>,
pub arguments: ArgumentList,
}
implement_node!(FreestandingArgumentList, branch, freestanding_argument_list);
@@ -1947,7 +1797,6 @@ fn as_mut_case_item_list(&mut self) -> Option<&mut CaseItemList> {
/// A variable_assignment contains a source range like FOO=bar.
#[derive(Default, Debug)]
pub struct VariableAssignment {
parent: Option<*const dyn Node>,
range: Option<SourceRange>,
}
implement_node!(VariableAssignment, leaf, variable_assignment);
@@ -1988,7 +1837,6 @@ fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
/// Zero or more newlines.
#[derive(Default, Debug)]
pub struct MaybeNewlines {
parent: Option<*const dyn Node>,
range: Option<SourceRange>,
}
implement_node!(MaybeNewlines, leaf, maybe_newlines);
@@ -2014,7 +1862,6 @@ fn as_mut_maybe_newlines(&mut self) -> Option<&mut MaybeNewlines> {
/// This is a separate type because it is sometimes useful to find all arguments.
#[derive(Default, Debug)]
pub struct Argument {
parent: Option<*const dyn Node>,
range: Option<SourceRange>,
}
implement_node!(Argument, leaf, argument);
@@ -2659,9 +2506,6 @@ pub fn walk(&'_ self) -> Traversal<'_> {
pub fn top(&self) -> &dyn Node {
self.top.as_node()
}
fn top_mut(&mut self) -> &mut dyn NodeMut {
&mut *self.top
}
/// Return whether any errors were encountered during parsing.
pub fn errored(&self) -> bool {
self.any_error
@@ -4104,25 +3948,6 @@ fn parse_from_top(
semis: pops.semis,
errors: pops.errors,
};
if top_type == Type::job_list {
// Set all parent nodes.
// It turns out to be more convenient to do this after the parse phase.
// Note: parent nodes are implemented as raw pointers! This means that the contents Ast must not
// change or move after construction.
ast.top_mut()
.as_mut_job_list()
.as_mut()
.unwrap()
.set_parents();
} else {
ast.top_mut()
.as_mut_freestanding_argument_list()
.as_mut()
.unwrap()
.set_parents();
}
ast
}