unsupported/juno/crates/juno_ast/src/kind.rs (451 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ use super::*; /// Generate boilerplate code for the `Node` enum. /// /// The `Node` enum has variants for each of the different types of AST nodes /// listed in [`nodekind_defs`]. /// Each variant contains a struct of the same name, which is where the data is actually /// stored. These member structs' first fields contain data which is common to all kinds of nodes /// (e.g. `range`, which represents location info). /// Force each of the member structs as well as the `Node` enum to have `repr(C)` /// to ensure that the shared fields are in the same place /// in all the structs. This means that the identical match arms will be optimized /// away into some fast pointer arithmetic that's easy to inline. macro_rules! gen_nodekind_enum { ($name:ident { $( $kind:ident $([ $parent:ident ])? $({ $( $field:ident : $type:ty $( [ $( $constraint:ident ),* ] )? ),* $(,)? })? ),* $(,)? }) => { // The kind of an AST node. // Matching against this enum allows traversal of the AST. // Each variant of the enum must only have fields of the following types: // * `NodeRc` // * `Option<NodeRc>` // * `NodeList` // * `StringLiteral` // * `NodeLabel` // * `bool` // * `f64` #[derive(Debug)] #[repr(C)] pub enum Node<'a> { // Create each field in the enum. $($kind($kind<'a>),)* } $( #[derive(Debug)] #[repr(C)] pub struct $kind<'a> { // Common fields to all node kinds. pub metadata: NodeMetadata<'a>, // Create each field that's meant for just this node kind. $($(pub $field : $type,)*)? } )* impl<'gc> Node<'gc> { pub fn variant(&self) -> NodeVariant { match self { $( Self::$kind { .. } => NodeVariant::$kind ),* } } /// Visit the child fields of `self`. pub fn visit_children<'ast: 'gc, V: Visitor<'gc>>( &'gc self, ctx: &'gc GCLock<'ast, '_>, visitor: &mut V, ) { match self { $( Node::$kind($kind { $($($field,)*)? .. }) => { $($( $field.visit_child( ctx, visitor, Path::new(self, NodeField::$field), ); )*)? } ),* } } /// Visit the child fields of this node. /// `self` is the *original* parent of the children to visit. /// Will only allocate a new node if one of the children was changed. pub fn visit_children_mut<'ast: 'gc, V: VisitorMut<'gc>>( &'gc self, ctx: &'gc GCLock<'ast, '_>, visitor: &mut V, ) -> TransformResult<&'gc Node<'gc>> { let builder = builder::Builder::from_node(self); #[allow(unused_mut)] match builder { $( builder::Builder::$kind(mut builder) => { $($( if let TransformResult::Changed($field) = (&builder.inner.$field) .visit_child_mut( ctx, visitor, Path::new(self, NodeField::$field)) { builder.$field($field); } )*)? builder.build(ctx) } ),* } } /// Replace `self` in the AST with the result of the `builder`. /// Always allocates a new node. /// `self` is the *original* parent of the children to visit, /// but the children which will be visited are determined via `builder`. pub fn replace_with_new<'ast: 'gc, V: VisitorMut<'gc>>( &'gc self, builder: builder::Builder<'gc>, ctx: &'gc GCLock<'ast, '_>, visitor: &mut V, ) -> TransformResult<&'gc Node<'gc>> { #[allow(unused_mut)] match builder { $( builder::Builder::$kind(mut builder) => { $($( if let TransformResult::Changed($field) = (&builder.inner.$field) .visit_child_mut( ctx, visitor, Path::new(self, NodeField::$field)) { builder.$field($field); } )*)? builder.build(ctx) } ),* } } /// Replace `self` in the AST with the result of the `builders`. /// Always allocates new nodes. /// `self` is the *original* parent of the children to visit, /// but the children which will be visited are determined via `builders`. pub fn replace_with_multiple<V: VisitorMut<'gc>>( &'gc self, builders: Vec<builder::Builder<'gc>>, ctx: &'gc GCLock, visitor: &mut V, ) -> TransformResult<&'gc Node<'gc>> { let mut expanded = Vec::new(); for builder in builders { #[allow(unused_mut)] match builder { $( builder::Builder::$kind(mut builder) => { $($( if let TransformResult::Changed($field) = (&builder.inner.$field) .visit_child_mut( ctx, visitor, Path::new(self, NodeField::$field)) { builder.$field($field); } )*)? expanded.push(builder.build_forced(ctx)); } ),* } } TransformResult::Expanded(expanded) } /// Replace `self` in the AST with the `existing` node. /// `self` is the *original* parent of the children to visit, /// but the children which will be visited are determined via `existing`. /// Will only allocate a new node if the `existing` node also must be modified. pub fn replace_with_existing<'ast: 'gc, V: VisitorMut<'gc>>( &'gc self, existing: &'gc Node<'gc>, ctx: &'gc GCLock<'ast, '_>, visitor: &mut V, ) -> TransformResult<&'gc Node<'gc>> { let builder = builder::Builder::from_node(existing); #[allow(unused_mut)] match builder { $( builder::Builder::$kind(mut builder) => { $($( if let TransformResult::Changed($field) = (&builder.inner.$field) .visit_child_mut( ctx, visitor, Path::new(self, NodeField::$field)) { builder.$field($field); } )*)? match builder.build(ctx) { TransformResult::Unchanged => TransformResult::Changed(existing), TransformResult::Removed | TransformResult::Expanded(_)=> { unreachable!("Builder can't remove or expand a node"); } TransformResult::Changed(n) => TransformResult::Changed(n), } } ),* } } #[inline] pub fn range(&self) -> &SourceRange { match self { $( Self::$kind($kind { metadata, .. }) => &metadata.range ),* } } #[inline] pub fn range_mut(&mut self) -> &mut SourceRange { match self { $( Self::$kind($kind { metadata, .. }) => &mut metadata.range ),* } } pub fn name(&self) -> &'static str { match self { $( Self::$kind { .. } => { stringify!($kind) } ),* } } } /// Just type information on the node without any of the children. /// Used for performing tasks based only on the type of the AST node /// without having to know more about it. /// Includes "abstract" nodes which cannot be truly constructed. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum NodeVariant { Expression, Statement, Declaration, Literal, Pattern, LVal, JSXChild, Flow, FlowType, FlowDeclaration, FlowExpression, FlowPredicate, FlowEnumBody, $($kind),* } impl NodeVariant { /// The `parent` of the variant in ESTree, used for validation. /// Return `None` if there is no parent. pub fn parent(&self) -> Option<NodeVariant> { match self { Self::Expression => None, Self::Statement => None, Self::Declaration => Some(Self::Statement), Self::Literal => Some(Self::Expression), Self::Pattern => Some(Self::LVal), Self::LVal => Some(Self::Expression), Self::JSXChild => None, Self::Flow => None, Self::FlowType => Some(Self::Flow), Self::FlowDeclaration => Some(Self::Declaration), Self::FlowExpression => Some(Self::Expression), Self::FlowPredicate => Some(Self::Flow), Self::FlowEnumBody => None, $( Self::$kind => { None$(.or(Some(Self::$parent)))? } ),* } } } pub mod template { use super::{ AssignmentExpressionOperator, BinaryExpressionOperator, ExportKind, ImportKind, LogicalExpressionOperator, MethodDefinitionKind, Node, NodeLabel, NodeList, NodeString, PropertyKind, UnaryExpressionOperator, UpdateExpressionOperator, VariableDeclarationKind, }; $( #[derive(Debug, Clone)] #[repr(C)] pub struct $kind<'a> { // Common fields to all node kinds. pub metadata: super::TemplateMetadata<'a>, // Create each field that's meant for just this node kind. $($(pub $field : $type,)*)? } )* } pub mod builder { use super::{ AssignmentExpressionOperator, BinaryExpressionOperator, ExportKind, GCLock, ImportKind, LogicalExpressionOperator, MethodDefinitionKind, Node, NodeChild, NodeLabel, NodeList, NodeMetadata, NodeString, PropertyKind, TransformResult, UnaryExpressionOperator, UpdateExpressionOperator, VariableDeclarationKind, }; /// Used for building various kinds of AST nodes. #[derive(Debug)] pub enum Builder<'a> { // Create each field in the enum. $($kind(self::$kind<'a>),)* } impl<'a> Builder<'a> { pub fn from_node(node: &'a Node<'a>) -> Self { match node { $( Node::$kind(original) => Self::$kind(self::$kind::from_node(original)), )* } } } $( /// Used for building nodes. #[derive(Debug)] pub struct $kind<'a> { is_changed: bool, pub(super) inner: super::$kind<'a>, } impl<'a> $kind<'a> { /// Initialize the builder from `node`. pub fn from_node(node: &'a super::$kind<'a>) -> Self { Self { is_changed: false, inner: super::$kind { metadata: NodeMetadata { phantom: node.metadata.phantom, range: node.metadata.range, }, $($( $field: (&node.$field).duplicate(), )*)? } } } /// Return Unchanged if the node the builder started with was never changed. /// Return Changed(node) with a new node if it was changed. pub fn build(self, gc: &'a GCLock) -> TransformResult<&'a Node<'a>> { if self.is_changed { TransformResult::Changed(self.build_forced(gc)) } else { TransformResult::Unchanged } } /// Return the new node. pub fn build_forced(self, gc: &'a GCLock) -> &'a Node<'a> { gc.alloc(super::Node::$kind(self.inner)) } /// Make a builder from a template. pub fn from_template( node: super::template::$kind<'a>, ) -> Self { Self { is_changed: true, inner: super::$kind { metadata: NodeMetadata { phantom: node.metadata.phantom, range: node.metadata.range, }, $($( $field: (&node.$field).duplicate(), )*)? } } } /// Build from a template. pub fn build_template( gc: &'a GCLock, node: super::template::$kind<'a>, ) -> &'a Node<'a> { gc.alloc(super::Node::$kind(super::$kind { metadata: NodeMetadata::build_template(node.metadata), $($( $field: node.$field, )*)? })) } // Setters for the fields. $($( pub fn $field(&mut self, $field: $type) { self.is_changed = true; self.inner.$field = $field; } )*)? } )* } }; } nodekind_defs! { gen_nodekind_enum } impl<'gc> Node<'gc> { /// Shallow equality comparison. pub fn ptr_eq(&self, other: &'_ Node<'_>) -> bool { std::ptr::eq(self, other) } fn panic(&self, msg: &str) -> ! { panic!("{} {}", self.name(), msg) } fn function_like_panic(&self) -> ! { self.panic("is not function-like") } pub fn is_function_like(&self) -> bool { matches!( self, Node::FunctionExpression(..) | Node::ArrowFunctionExpression(..) | Node::FunctionDeclaration(..) ) } pub fn function_like_id(&self) -> Option<&Node> { match self { Node::FunctionExpression(FunctionExpression { id, .. }) | Node::ArrowFunctionExpression(ArrowFunctionExpression { id, .. }) | Node::FunctionDeclaration(FunctionDeclaration { id, .. }) => *id, _ => self.function_like_panic(), } } pub fn function_like_params(&self) -> &NodeList { match self { Node::FunctionExpression(FunctionExpression { params, .. }) | Node::ArrowFunctionExpression(ArrowFunctionExpression { params, .. }) | Node::FunctionDeclaration(FunctionDeclaration { params, .. }) => params, _ => self.function_like_panic(), } } pub fn function_like_body(&self) -> &Node { match self { Node::FunctionExpression(FunctionExpression { body, .. }) | Node::ArrowFunctionExpression(ArrowFunctionExpression { body, .. }) | Node::FunctionDeclaration(FunctionDeclaration { body, .. }) => body, _ => self.function_like_panic(), } } pub fn is_loop_statement(&self) -> bool { matches!( self, Node::WhileStatement(_) | Node::DoWhileStatement(_) | Node::ForInStatement(_) | Node::ForOfStatement(_) | Node::ForStatement(_) ) } } #[macro_export] macro_rules! node_cast { ($kind:path, $node:expr) => { match $node { $kind(v) => v, _ => panic!( "invalid cast to {} from {}", stringify!($kind), $node.name() ), } }; } #[macro_export] macro_rules! node_isa { ($kind:path, $node:expr) => { match $node { $kind(_) => true, _ => false, } }; }