/*
 * 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.
 */

/// A reusable macro for defining code based on the AST.
/// Takes a `$callback` such as `gen_nodekind_enum`, which is invoked like this:
/// ```ignore
/// $callback! {
/// NodeKind {
///     Node1[parent] {
///       field1: type1[constraint_a, constraint_b],
///       field2: type2,
///     },
///     Node2,
/// }
/// }
/// ```
/// in order to avoid excessive copy/paste boilerplate.
/// Parents and constraints can be any member of `NodeVariant`,
/// which includes any constructible `NodeKind`s` as well as interfaces like
/// `Statement`, `Expression`, etc.
/// If multiple constraints are provided, at least one must be satisfied.
/// The `null` constraint is encoded via `Option`, it need not be listed explicitly.
/// See [`gen_nodekind_enum`] for an example of how to pattern match on the information
/// provided by this macro.
macro_rules! nodekind_defs {
    ($callback:ident) => {
        $callback! {
        NodeKind {
            Empty,
            Metadata,
            Program {
                body: NodeList<'a>[Directive, Statement],
            },
            Module {
                body: NodeList<'a>[Directive, Statement],
            },
            FunctionExpression[Expression] {
                id: Option<&'a Node<'a>>[Identifier],
                params: NodeList<'a>[Pattern],
                body: &'a Node<'a>[BlockStatement],
                type_parameters: Option<&'a Node<'a>>[TypeParameterDeclaration],
                return_type: Option<&'a Node<'a>>[TypeAnnotation],
                predicate: Option<&'a Node<'a>>[FlowPredicate],
                generator: bool,
                is_async: bool,
            },
            ArrowFunctionExpression[Expression] {
                id: Option<&'a Node<'a>>[Identifier],
                params: NodeList<'a>[Pattern],
                body: &'a Node<'a>[Expression, BlockStatement],
                type_parameters: Option<&'a Node<'a>>[TypeParameterDeclaration],
                return_type: Option<&'a Node<'a>>[TypeAnnotation],
                predicate: Option<&'a Node<'a>>[FlowPredicate],
                expression: bool,
                is_async: bool,
            },
            FunctionDeclaration[Declaration] {
                id: Option<&'a Node<'a>>[Identifier],
                params: NodeList<'a>[Pattern],
                body: &'a Node<'a>[BlockStatement],
                type_parameters: Option<&'a Node<'a>>[TypeParameterDeclaration],
                return_type: Option<&'a Node<'a>>[TypeAnnotation],
                predicate: Option<&'a Node<'a>>[InferredPredicate, DeclaredPredicate],
                generator: bool,
                is_async: bool,
            },
            ComponentDeclaration[Declaration] {
                id: &'a Node<'a>[Identifier],
                params: NodeList<'a>[Pattern],
                body: &'a Node<'a>[BlockStatement],
                type_parameters: Option<&'a Node<'a>>[TypeParameterDeclaration],
                renders_type: Option<&'a Node<'a>>[TypeAnnotation],
            },
            WhileStatement[Statement] {
                body: &'a Node<'a>[Statement],
                test: &'a Node<'a>[Expression],
            },
            DoWhileStatement[Statement] {
                body: &'a Node<'a>[Statement],
                test: &'a Node<'a>[Expression],
            },
            ForInStatement[Statement] {
                left: &'a Node<'a>[VariableDeclaration, Pattern],
                right: &'a Node<'a>[Expression],
                body: &'a Node<'a>[Statement],
            },
            ForOfStatement[ForInStatement] {
                left: &'a Node<'a>[VariableDeclaration, Pattern],
                right: &'a Node<'a>[Expression],
                body: &'a Node<'a>[Statement],
                is_await: bool,
            },
            ForStatement[Statement] {
                init: Option<&'a Node<'a>>[VariableDeclaration, Expression],
                test: Option<&'a Node<'a>>[Expression],
                update: Option<&'a Node<'a>>[Expression],
                body: &'a Node<'a>[Statement],
            },
            DebuggerStatement[Statement],
            EmptyStatement[Statement],
            BlockStatement[Statement] {
                body: NodeList<'a>[Statement],
            },
            BreakStatement[Statement] {
                label: Option<&'a Node<'a>>[Identifier],
            },
            ContinueStatement[Statement] {
                label: Option<&'a Node<'a>>[Identifier],
            },
            ThrowStatement[Statement] {
                argument: &'a Node<'a>[Expression],
            },
            ReturnStatement[Statement] {
                argument: Option<&'a Node<'a>>[Expression],
            },
            WithStatement[Statement] {
                object: &'a Node<'a>[Expression],
                body: &'a Node<'a>[Statement],
            },
            SwitchStatement[Statement] {
                discriminant: &'a Node<'a>[Expression],
                cases: NodeList<'a>[SwitchCase],
            },
            LabeledStatement[Statement] {
                label: &'a Node<'a>[Identifier],
                body: &'a Node<'a>[Statement],
            },
            ExpressionStatement[Statement] {
                expression: &'a Node<'a>[Expression],
                directive: Option<NodeString>,
            },
            TryStatement[Statement] {
                block: &'a Node<'a>[BlockStatement],
                handler: Option<&'a Node<'a>>[CatchClause],
                finalizer: Option<&'a Node<'a>>[BlockStatement],
            },
            IfStatement[Statement] {
                test: &'a Node<'a>[Expression],
                consequent: &'a Node<'a>[Statement],
                alternate: Option<&'a Node<'a>>[Statement],
            },
            NullLiteral[Literal],
            BooleanLiteral[Literal] {
                value: bool,
            },
            StringLiteral[Literal] {
                value: NodeString,
            },
            NumericLiteral[Literal] {
                value: f64,
            },
            BigIntLiteral[Literal] {
                bigint: NodeLabel,
            },
            RegExpLiteral[Literal] {
                pattern: NodeLabel,
                flags: NodeLabel,
            },
            ThisExpression[Expression],
            Super,
            SequenceExpression[Expression] {
                expressions: NodeList<'a>[Expression],
            },
            ObjectExpression[Expression] {
                properties: NodeList<'a>[Property, SpreadElement],
            },
            ArrayExpression[Expression] {
                elements: NodeList<'a>[Expression, SpreadElement, Empty],
                trailing_comma: bool,
            },
            SpreadElement {
                argument: &'a Node<'a>[Expression],
            },
            NewExpression[Expression] {
                callee: &'a Node<'a>[Expression],
                type_arguments: Option<&'a Node<'a>>[TypeParameterInstantiation],
                arguments: NodeList<'a>[Expression, SpreadElement],
            },
            YieldExpression[Expression] {
                argument: Option<&'a Node<'a>>[Expression],
                delegate: bool,
            },
            AwaitExpression[Expression] {
                argument: &'a Node<'a>[Expression],
            },
            ImportExpression[Expression] {
                source: &'a Node<'a>[Expression],
                attributes: Option<&'a Node<'a>>[Expression],
            },
            CallExpression[Expression] {
                callee: &'a Node<'a>[Expression, Super],
                type_arguments: Option<&'a Node<'a>>[TypeParameterInstantiation],
                arguments: NodeList<'a>[Expression, SpreadElement],
            },
            OptionalCallExpression[Expression] {
                callee: &'a Node<'a>[Expression, Super],
                type_arguments: Option<&'a Node<'a>>[TypeParameterInstantiation],
                arguments: NodeList<'a>[Expression, SpreadElement],
                optional: bool,
            },
            AssignmentExpression[Expression] {
                operator: AssignmentExpressionOperator,
                left: &'a Node<'a>[LVal],
                right: &'a Node<'a>[Expression],
            },
            UnaryExpression[Expression] {
                operator: UnaryExpressionOperator,
                argument: &'a Node<'a>[Expression],
                prefix: bool,
            },
            UpdateExpression[Expression] {
                operator: UpdateExpressionOperator,
                argument: &'a Node<'a>[Expression],
                prefix: bool,
            },
            MemberExpression[LVal] {
                object: &'a Node<'a>[Super, Expression],
                property: &'a Node<'a>[Expression],
                computed: bool,
            },
            OptionalMemberExpression[Expression] {
                object: &'a Node<'a>[Super, Expression],
                property: &'a Node<'a>[Expression],
                computed: bool,
                optional: bool,
            },
            LogicalExpression[Expression] {
                left: &'a Node<'a>[Expression],
                right: &'a Node<'a>[Expression],
                operator: LogicalExpressionOperator,
            },
            ConditionalExpression[Expression] {
                test: &'a Node<'a>[Expression],
                alternate: &'a Node<'a>[Expression],
                consequent: &'a Node<'a>[Expression],
            },
            BinaryExpression[Expression] {
                left: &'a Node<'a>[Expression],
                right: &'a Node<'a>[Expression],
                operator: BinaryExpressionOperator,
            },
            Directive[Statement] {
                value: &'a Node<'a>[StringLiteral],
            },
            DirectiveLiteral[Literal] {
                value: NodeString,
            },
            Identifier[Pattern] {
                name: NodeLabel,
                type_annotation: Option<&'a Node<'a>>[TypeAnnotation],
                optional: bool,
            },
            PrivateName[LVal] {
                id: &'a Node<'a>[Identifier],
            },
            MetaProperty[LVal] {
                meta: &'a Node<'a>[Identifier],
                property: &'a Node<'a>[Identifier],
            },
            SwitchCase {
                test: Option<&'a Node<'a>>[Expression],
                consequent: NodeList<'a>[Statement],
            },
            CatchClause {
                param: Option<&'a Node<'a>>[Pattern],
                body: &'a Node<'a>[BlockStatement],
            },
            VariableDeclarator[Declaration] {
                init: Option<&'a Node<'a>>[Expression],
                id: &'a Node<'a>[Pattern],
            },
            VariableDeclaration[Declaration] {
                kind: VariableDeclarationKind,
                declarations: NodeList<'a>[VariableDeclarator],
            },
            TemplateLiteral[Expression] {
                quasis: NodeList<'a>[TemplateElement],
                expressions: NodeList<'a>[Expression],
            },
            TaggedTemplateExpression[Expression] {
                tag: &'a Node<'a>[Expression],
                quasi: &'a Node<'a>[TemplateLiteral],
            },
            TemplateElement {
                tail: bool,
                cooked: Option<NodeString>,
                raw: NodeLabel,
            },
            Property {
                key: &'a Node<'a>[Literal, Identifier, Expression],
                value: &'a Node<'a>[Expression],
                kind: PropertyKind,
                computed: bool,
                method: bool,
                shorthand: bool,
            },
            ClassDeclaration[Declaration] {
                id: Option<&'a Node<'a>>[Identifier],
                type_parameters: Option<&'a Node<'a>>[TypeParameterDeclaration],
                super_class: Option<&'a Node<'a>>[Expression],
                super_type_parameters: Option<&'a Node<'a>>[TypeParameterInstantiation],
                implements: NodeList<'a>[ClassImplements],
                decorators: NodeList<'a>,
                body: &'a Node<'a>[ClassBody],
            },
            ClassExpression[Expression] {
                id: Option<&'a Node<'a>>[Identifier],
                type_parameters: Option<&'a Node<'a>>[TypeParameterDeclaration],
                super_class: Option<&'a Node<'a>>[Expression],
                super_type_parameters: Option<&'a Node<'a>>[TypeParameterInstantiation],
                implements: NodeList<'a>[ClassImplements],
                decorators: NodeList<'a>,
                body: &'a Node<'a>[ClassBody],
            },
            ClassBody {
                body: NodeList<'a>[ClassProperty, ClassPrivateProperty, MethodDefinition],
            },
            ClassProperty {
                key: &'a Node<'a>[Expression],
                value: Option<&'a Node<'a>>[Expression],
                computed: bool,
                is_static: bool,
                declare: bool,
                optional: bool,
                variance: Option<&'a Node<'a>>[Variance],
                type_annotation: Option<&'a Node<'a>>[TypeAnnotation],
                ts_modifiers: Option<&'a Node<'a>>[TSModifiers],
            },
            ClassPrivateProperty {
                key: &'a Node<'a>[PrivateName],
                value: Option<&'a Node<'a>>[Expression],
                is_static: bool,
                declare: bool,
                optional: bool,
                variance: Option<&'a Node<'a>>[Variance],
                type_annotation: Option<&'a Node<'a>>[TypeAnnotation],
                ts_modifiers: Option<&'a Node<'a>>[TSModifiers],
            },
            MethodDefinition {
                key: &'a Node<'a>[Expression],
                value: &'a Node<'a>[FunctionExpression],
                kind: MethodDefinitionKind,
                computed: bool,
                is_static: bool,
            },
            ImportDeclaration[Declaration] {
                specifiers: NodeList<'a>[ImportSpecifier],
                source: &'a Node<'a>[StringLiteral],
                assertions: Option<NodeList<'a>>[ImportAttribute],
                import_kind: ImportKind,
            },
            ImportSpecifier {
                imported: &'a Node<'a>[Identifier],
                local: &'a Node<'a>[Identifier],
                import_kind: ImportKind,
            },
            ImportDefaultSpecifier[ImportSpecifier] {
                local: &'a Node<'a>[Identifier],
            },
            ImportNamespaceSpecifier[ImportSpecifier] {
                local: &'a Node<'a>[Identifier],
            },
            ImportAttribute {
                key: &'a Node<'a>[StringLiteral],
                value: &'a Node<'a>[Expression],
            },
            ExportNamedDeclaration[Declaration] {
                declaration: Option<&'a Node<'a>>[Declaration, Identifier],
                specifiers: NodeList<'a>[ExportSpecifier, ExportNamespaceSpecifier],
                source: Option<&'a Node<'a>>[StringLiteral],
                export_kind: ExportKind,
            },
            ExportSpecifier {
                exported: &'a Node<'a>[Identifier],
                local: &'a Node<'a>[Identifier],
            },
            ExportNamespaceSpecifier {
                exported: &'a Node<'a>[Identifier],
            },
            ExportDefaultDeclaration[Declaration] {
                declaration: &'a Node<'a>[Declaration, Expression],
            },
            ExportAllDeclaration[Declaration] {
                source: &'a Node<'a>[StringLiteral],
                export_kind: ExportKind,
            },
            ObjectPattern[Pattern] {
                properties: NodeList<'a>[Property, RestElement],
                type_annotation: Option<&'a Node<'a>>[TypeAnnotation],
            },
            ArrayPattern[Pattern] {
                elements: NodeList<'a>[Empty, Pattern, MemberExpression, RestElement],
                type_annotation: Option<&'a Node<'a>>[TypeAnnotation],
            },
            RestElement[Pattern] {
                argument: &'a Node<'a>[Pattern],
            },
            AssignmentPattern[Pattern] {
                left: &'a Node<'a>[Pattern],
                right: &'a Node<'a>[Expression],
            },

            JSXIdentifier {
                name: NodeLabel,
            },
            JSXMemberExpression {
                object: &'a Node<'a>[JSXMemberExpression, JSXIdentifier],
                property: &'a Node<'a>[JSXIdentifier],
            },
            JSXNamespacedName {
                namespace: &'a Node<'a>[JSXIdentifier],
                name: &'a Node<'a>[JSXIdentifier],
            },
            JSXEmptyExpression[Expression],
            JSXExpressionContainer[JSXChild] {
                expression: &'a Node<'a>[Expression],
            },
            JSXSpreadChild[JSXChild] {
                expression: &'a Node<'a>[Expression],
            },
            JSXOpeningElement {
                name: &'a Node<'a>[JSXIdentifier, JSXMemberExpression, JSXNamespacedName],
                attributes: NodeList<'a>[JSXAttribute, JSXSpreadAttribute],
                self_closing: bool,
                type_arguments: Option<&'a Node<'a>>[TypeParameterInstantiation],
            },
            JSXClosingElement {
                name: &'a Node<'a>[JSXIdentifier, JSXMemberExpression, JSXNamespacedName],
            },
            JSXAttribute {
                name: &'a Node<'a>[JSXIdentifier, JSXMemberExpression, JSXNamespacedName],
                value: Option<&'a Node<'a>>[JSXExpressionContainer, JSXStringLiteral],
            },
            JSXSpreadAttribute {
                argument: &'a Node<'a>[Expression],
            },
            JSXStringLiteral {
                value: NodeString,
                raw: NodeLabel,
            },
            JSXText[JSXChild] {
                value: NodeString,
                raw: NodeLabel,
            },
            JSXElement[Expression] {
                opening_element: &'a Node<'a>[JSXOpeningElement],
                children: NodeList<'a>[JSXElement, JSXFragment, JSXChild],
                closing_element: Option<&'a Node<'a>>[JSXClosingElement],
            },
            JSXFragment[Expression] {
                opening_fragment: &'a Node<'a>[JSXOpeningFragment],
                children: NodeList<'a>[JSXElement, JSXFragment, JSXChild],
                closing_fragment: &'a Node<'a>[JSXClosingFragment],
            },
            JSXOpeningFragment,
            JSXClosingFragment,

            ExistsTypeAnnotation[FlowType],
            EmptyTypeAnnotation[FlowType],
            StringTypeAnnotation[FlowType],
            BigIntTypeAnnotation[FlowType],
            NumberTypeAnnotation[FlowType],
            BigIntLiteralTypeAnnotation[FlowType] {
                raw: NodeLabel,
            },
            StringLiteralTypeAnnotation[FlowType] {
                value: NodeString,
                raw: NodeString,
            },
            NumberLiteralTypeAnnotation[FlowType] {
                value: f64,
                raw: NodeLabel,
            },
            BooleanTypeAnnotation[FlowType],
            BooleanLiteralTypeAnnotation[FlowType] {
                value: bool,
                raw: NodeLabel,
            },
            NullLiteralTypeAnnotation[FlowType],
            SymbolTypeAnnotation[FlowType],
            AnyTypeAnnotation[FlowType],
            MixedTypeAnnotation[FlowType],
            VoidTypeAnnotation[FlowType],
            FunctionTypeAnnotation[FlowType] {
                params: NodeList<'a>[FunctionTypeParam],
                this: Option<&'a Node<'a>>[FunctionTypeParam],
                return_type: &'a Node<'a>[FlowType],
                rest: Option<&'a Node<'a>>[FunctionTypeParam],
                type_parameters: Option<&'a Node<'a>>[TypeParameterDeclaration],
            },
            FunctionTypeParam[FlowType] {
                name: Option<&'a Node<'a>>[Identifier],
                type_annotation: &'a Node<'a>[FlowType],
                optional: bool,
            },
            ComponentTypeAnnotation[FlowType] {
                params: NodeList<'a>[FunctionTypeParam],
                rest: Option<&'a Node<'a>>[FunctionTypeParam],
                type_parameters: Option<&'a Node<'a>>[TypeParameterDeclaration],
                renders_type: Option<&'a Node<'a>>[TypeAnnotation],
            },
            ComponentTypeParameter[FlowType] {
                name: Option<&'a Node<'a>>[Identifier],
                type_annotation: &'a Node<'a>[FlowType],
                optional: bool,
            },
            NullableTypeAnnotation[FlowType] {
                type_annotation: &'a Node<'a>[FlowType],
            },
            QualifiedTypeIdentifier[Flow] {
                qualification: &'a Node<'a>[Identifier, QualifiedTypeIdentifier],
                id: &'a Node<'a>[Identifier],
            },
            TypeofTypeAnnotation[FlowType] {
                argument: &'a Node<'a>[FlowType],
            },
            KeyofTypeAnnotation[FlowType] {
                argument: &'a Node<'a>[FlowType],
            },
            QualifiedTypeofIdentifier[Flow] {
                qualification: &'a Node<'a>[Identifier, QualifiedTypeIdentifier],
                id: &'a Node<'a>[Identifier],
            },
            TupleTypeAnnotation[FlowType] {
                types: NodeList<'a>[FlowType],
            },
            TupleTypeSpreadElement[FlowType] {
                label: Option<&'a Node<'a>>[Identifier],
                type_annotation: &'a Node<'a>[FlowType],
            },
            TupleTypeLabeledElement[FlowType] {
                label: &'a Node<'a>[Identifier],
                element_type: &'a Node<'a>[FlowType],
                optional: bool,
                variance: Option<&'a Node<'a>>[Variance],
            },
            ArrayTypeAnnotation[FlowType] {
                element_type: &'a Node<'a>[FlowType],
            },
            InferTypeAnnotation[FlowType] {
                type_parameter: &'a Node<'a>[FlowType],
            },
            UnionTypeAnnotation[FlowType] {
                types: NodeList<'a>[FlowType],
            },
            IntersectionTypeAnnotation[FlowType] {
                types: NodeList<'a>[FlowType],
            },
            GenericTypeAnnotation[FlowType] {
                id: &'a Node<'a>[Identifier, QualifiedTypeIdentifier],
                type_parameters: Option<&'a Node<'a>>[TypeParameterInstantiation],
            },
            IndexedAccessType[FlowType] {
                object_type: &'a Node<'a>[FlowType],
                index_type: &'a Node<'a>[FlowType],
            },
            OptionalIndexedAccessType[FlowType] {
                object_type: &'a Node<'a>[FlowType],
                index_type: &'a Node<'a>[FlowType],
                optional: bool,
            },
            ConditionalTypeAnnotation[FlowType] {
                check_type: &'a Node<'a>[FlowType],
                extends_type: &'a Node<'a>[FlowType],
                true_type: &'a Node<'a>[FlowType],
                false_type: &'a Node<'a>[FlowType],
            },
            TypePredicate[FlowType] {
                parameter_name: &'a Node<'a>[Identifier],
                type_annotation: Option<&'a Node<'a>>[FlowType],
                asserts: bool,
            },
            InterfaceTypeAnnotation[FlowType] {
                extends: NodeList<'a>[InterfaceExtends],
                body: Option<&'a Node<'a>>[ObjectTypeAnnotation],
            },
            TypeAlias[FlowDeclaration] {
                id: &'a Node<'a>[Identifier],
                type_parameters: Option<&'a Node<'a>>[TypeParameterDeclaration],
                right: &'a Node<'a>[FlowType],
            },
            OpaqueType[FlowDeclaration] {
                id: &'a Node<'a>[Identifier],
                type_parameters: Option<&'a Node<'a>>[TypeParameterDeclaration],
                impltype: &'a Node<'a>[FlowType],
                supertype: Option<&'a Node<'a>>[FlowType],
            },
            InterfaceDeclaration[FlowDeclaration] {
                id: &'a Node<'a>[Identifier],
                type_parameters: Option<&'a Node<'a>>[TypeParameterDeclaration],
                extends: NodeList<'a>[InterfaceExtends],
                body: &'a Node<'a>[ObjectTypeAnnotation],
            },
            DeclareTypeAlias[FlowDeclaration] {
                id: &'a Node<'a>[Identifier],
                type_parameters: Option<&'a Node<'a>>[TypeParameterDeclaration],
                right: &'a Node<'a>[FlowType],
            },
            DeclareOpaqueType[FlowDeclaration] {
                id: &'a Node<'a>[Identifier],
                type_parameters: Option<&'a Node<'a>>[TypeParameterDeclaration],
                impltype: Option<&'a Node<'a>>[FlowType],
                supertype: Option<&'a Node<'a>>[FlowType],
            },
            DeclareInterface[FlowDeclaration] {
                id: &'a Node<'a>[Identifier],
                type_parameters: Option<&'a Node<'a>>[TypeParameterDeclaration],
                extends: NodeList<'a>[InterfaceExtends],
                body: &'a Node<'a>[ObjectTypeAnnotation],
            },
            DeclareClass[FlowDeclaration] {
                id: &'a Node<'a>[Identifier],
                type_parameters: Option<&'a Node<'a>>[TypeParameterDeclaration],
                extends: NodeList<'a>[InterfaceExtends],
                implements: NodeList<'a>[ClassImplements],
                mixins: NodeList<'a>[InterfaceExtends],
                body: &'a Node<'a>[ObjectTypeAnnotation],
            },
            DeclareFunction[FlowDeclaration] {
                id: &'a Node<'a>[Identifier],
                predicate: Option<&'a Node<'a>>[DeclaredPredicate],
            },
            DeclareComponent[Declaration] {
                id: &'a Node<'a>[Identifier],
                params: NodeList<'a>[Pattern],
                rest: Option<&'a Node<'a>>[FunctionTypeParam],
                type_parameters: Option<&'a Node<'a>>[TypeParameterDeclaration],
                renders_type: Option<&'a Node<'a>>[TypeAnnotation],
            },
            DeclareVariable[FlowDeclaration] {
                id: &'a Node<'a>[Identifier],
                kind: VariableDeclarationKind,
            },
            DeclareEnum[FlowDeclaration] {
                id: &'a Node<'a>[Identifier],
                body: &'a Node<'a>[FlowEnumBody],
            },
            DeclareExportDeclaration[FlowDeclaration] {
                declaration: Option<&'a Node<'a>>[Flow, FlowDeclaration],
                specifiers: NodeList<'a>[ExportSpecifier, ExportNamespaceSpecifier],
                source: Option<&'a Node<'a>>[StringLiteral],
                default: bool,
            },
            DeclareExportAllDeclaration[FlowDeclaration] {
                source: &'a Node<'a>[StringLiteral],
            },
            DeclareModule[FlowDeclaration] {
                id: &'a Node<'a>[Identifier, StringLiteral],
                body: &'a Node<'a>[BlockStatement],
                kind: NodeLabel,
            },
            DeclareModuleExports[FlowDeclaration] {
                type_annotation: &'a Node<'a>[TypeAnnotation],
            },
            InterfaceExtends[Flow] {
                id: &'a Node<'a>[Identifier, QualifiedTypeIdentifier],
                type_parameters: Option<&'a Node<'a>>[TypeParameterInstantiation],
            },
            ClassImplements[Flow] {
                id: &'a Node<'a>[Identifier],
                type_parameters: Option<&'a Node<'a>>[TypeParameterInstantiation],
            },
            TypeAnnotation[Flow] {
                type_annotation: &'a Node<'a>[FlowType],
            },
            ObjectTypeAnnotation[FlowType] {
                properties: NodeList<'a>[ObjectTypeProperty, ObjectTypeSpreadProperty],
                indexers: NodeList<'a>[ObjectTypeIndexer],
                call_properties: NodeList<'a>[ObjectTypeCallProperty],
                internal_slots: NodeList<'a>[ObjectTypeInternalSlot],
                inexact: bool,
                exact: bool,
            },
            ObjectTypeProperty[Flow] {
                key: &'a Node<'a>[Identifier, StringLiteral],
                value: &'a Node<'a>[FlowType],
                method: bool,
                optional: bool,
                is_static: bool,
                proto: bool,
                variance: Option<&'a Node<'a>>[Variance],
                kind: NodeLabel,
            },
            ObjectTypeSpreadProperty[Flow] {
                argument: &'a Node<'a>[FlowType],
            },
            ObjectTypeInternalSlot[Flow] {
                id: &'a Node<'a>[Identifier],
                value: &'a Node<'a>[FlowType],
                optional: bool,
                is_static: bool,
                method: bool,
            },
            ObjectTypeCallProperty[Flow] {
                value: &'a Node<'a>[FlowType],
                is_static: bool,
            },
            ObjectTypeIndexer[Flow] {
                id: Option<&'a Node<'a>>[Identifier],
                key: &'a Node<'a>[FlowType],
                value: &'a Node<'a>[FlowType],
                is_static: bool,
                variance: Option<&'a Node<'a>>[Variance],
            },
            ObjectTypeMappedTypeProperty[Flow] {
                key_tparam: &'a Node<'a>[FlowType],
                prop_type: &'a Node<'a>[FlowType],
                source_type: &'a Node<'a>[FlowType],
                variance: Option<&'a Node<'a>>[Variance],
                optional: Option<NodeString>,
            },
            Variance[Flow] {
                kind: NodeLabel,
            },
            TypeParameterDeclaration[Flow] {
                params: NodeList<'a>[TypeParameter],
            },
            TypeParameter[Flow] {
                name: NodeLabel,
                bound: Option<&'a Node<'a>>[TypeAnnotation],
                variance: Option<&'a Node<'a>>[Variance],
                default: Option<&'a Node<'a>>[FlowType],
                uses_extends_bound: bool,
            },
            TypeParameterInstantiation[Flow] {
                params: NodeList<'a>[FlowType],
            },
            TypeCastExpression[FlowExpression] {
                expression: &'a Node<'a>[Expression],
                type_annotation: &'a Node<'a>[TypeAnnotation],
            },
            InferredPredicate[FlowPredicate],
            DeclaredPredicate[FlowPredicate] {
                value: &'a Node<'a>[Flow, Expression],
            },
            EnumDeclaration[Declaration] {
                id: &'a Node<'a>[Identifier],
                body: &'a Node<'a>[FlowEnumBody],
            },
            EnumStringBody[FlowEnumBody] {
                members: NodeList<'a>[EnumStringMember, EnumDefaultedMember],
                explicit_type: bool,
                has_unknown_members: bool,
            },
            EnumNumberBody[FlowEnumBody] {
                members: NodeList<'a>[EnumNumberMember, EnumDefaultedMember],
                explicit_type: bool,
                has_unknown_members: bool,
            },
            EnumBooleanBody[FlowEnumBody] {
                members: NodeList<'a>[EnumBooleanMember, EnumDefaultedMember],
                explicit_type: bool,
                has_unknown_members: bool,
            },
            EnumSymbolBody[FlowEnumBody] {
                members: NodeList<'a>[EnumDefaultedMember],
                has_unknown_members: bool,
            },
            EnumDefaultedMember {
                id: &'a Node<'a>[Identifier],
            },
            EnumStringMember {
                id: &'a Node<'a>[Identifier],
                init: &'a Node<'a>[StringLiteral],
            },
            EnumNumberMember {
                id: &'a Node<'a>[Identifier],
                init: &'a Node<'a>[NumericLiteral],
            },
            EnumBooleanMember {
                id: &'a Node<'a>[Identifier],
                init: &'a Node<'a>[BooleanLiteral],
            },
            ComponentParameter {
                name: &'a Node<'a>[Identifier],
                local: &'a Node<'a>[Identifier],
                shorthand: bool,
            },

            TSTypeAnnotation {
                type_annotation: &'a Node<'a>,
            },
            TSAnyKeyword,
            TSNumberKeyword,
            TSBooleanKeyword,
            TSStringKeyword,
            TSSymbolKeyword,
            TSVoidKeyword,
            TSThisType,
            TSLiteralType {
                literal: &'a Node<'a>,
            },
            TSIndexedAccessType {
                object_type: &'a Node<'a>,
                index_type: &'a Node<'a>,
            },
            TSArrayType {
                element_type: &'a Node<'a>,
            },
            TSTypeReference {
                type_name: &'a Node<'a>,
                type_parameters: Option<&'a Node<'a>>,
            },
            TSQualifiedName {
                left: &'a Node<'a>,
                right: Option<&'a Node<'a>>,
            },
            TSFunctionType {
                params: NodeList<'a>,
                return_type: &'a Node<'a>,
                type_parameters: Option<&'a Node<'a>>,
            },
            TSConstructorType {
                params: NodeList<'a>,
                return_type: &'a Node<'a>,
                type_parameters: Option<&'a Node<'a>>,
            },
            TSTypePredicate {
                parameter_name: &'a Node<'a>,
                type_annotation: &'a Node<'a>,
            },
            TSTupleType {
                element_types: NodeList<'a>,
            },
            TSTypeAssertion {
                type_annotation: &'a Node<'a>,
                expression: &'a Node<'a>,
            },
            TSAsExpression {
                expression: &'a Node<'a>,
                type_annotation: &'a Node<'a>,
            },
            TSParameterProperty {
                parameter: &'a Node<'a>,
                accessibility: Option<NodeLabel>,
                readonly: bool,
                is_static: bool,
                export: bool,
            },
            TSTypeAliasDeclaration {
                id: &'a Node<'a>,
                type_parameters: Option<&'a Node<'a>>,
                type_annotation: &'a Node<'a>,
            },
            TSInterfaceDeclaration {
                id: &'a Node<'a>,
                body: &'a Node<'a>,
                extends: NodeList<'a>,
                type_parameters: Option<&'a Node<'a>>,
            },
            TSInterfaceHeritage {
                expression: &'a Node<'a>,
                type_parameters: Option<&'a Node<'a>>,
            },
            TSInterfaceBody {
                body: NodeList<'a>,
            },
            TSEnumDeclaration {
                id: &'a Node<'a>,
                members: NodeList<'a>,
            },
            TSEnumMember {
                id: &'a Node<'a>,
                initializer: Option<&'a Node<'a>>,
            },
            TSModuleDeclaration {
                id: &'a Node<'a>,
                body: &'a Node<'a>,
            },
            TSModuleBlock {
                body: NodeList<'a>,
            },
            TSModuleMember {
                id: &'a Node<'a>,
                initializer: Option<&'a Node<'a>>,
            },
            TSTypeParameterDeclaration {
                params: NodeList<'a>,
            },
            TSTypeParameter {
                name: &'a Node<'a>,
                constraint: Option<&'a Node<'a>>,
                default: Option<&'a Node<'a>>,
            },
            TSTypeParameterInstantiation {
                params: NodeList<'a>,
            },
            TSUnionType {
                types: NodeList<'a>,
            },
            TSIntersectionType {
                types: NodeList<'a>,
            },
            TSTypeQuery {
                expr_name: &'a Node<'a>,
            },
            TSConditionalType {
                extends_type: &'a Node<'a>,
                check_type: &'a Node<'a>,
                true_type: &'a Node<'a>,
                false_type: &'a Node<'a>,
            },
            TSTypeLiteral {
                members: NodeList<'a>,
            },
            TSPropertySignature {
                key: &'a Node<'a>,
                type_annotation: Option<&'a Node<'a>>,
                initializer: Option<&'a Node<'a>>,
                optional: bool,
                computed: bool,
                readonly: bool,
                is_static: bool,
                export: bool,
            },
            TSMethodSignature {
                key: &'a Node<'a>,
                params: NodeList<'a>,
                return_type: Option<&'a Node<'a>>,
                computed: bool,
            },
            TSIndexSignature {
                parameters: NodeList<'a>,
                type_annotation: Option<&'a Node<'a>>,
            },
            TSCallSignatureDeclaration {
                params: NodeList<'a>,
                return_type: Option<&'a Node<'a>>,
            },
            TSModifiers {
                accessibility: NodeLabel,
                readonly: bool,
            },
        }
        }
    };
}


/*
 * 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 std::io;
use std::io::Write;

use juno_support::case::ascii_snake_to_camel;
pub use juno_support::json::Pretty;
use juno_support::json::*;

use super::AssignmentExpressionOperator;
use super::BinaryExpressionOperator;
use super::Context;
use super::ExportKind;
use super::GCLock;
use super::ImportKind;
use super::LogicalExpressionOperator;
use super::MethodDefinitionKind;
use super::Node;
use super::NodeLabel;
use super::NodeList;
use super::NodeRc;
use super::NodeString;
use super::PropertyKind;
use super::UnaryExpressionOperator;
use super::UpdateExpressionOperator;
use super::VariableDeclarationKind;

/// Generate boilerplate code for the `NodeKind` enum.
macro_rules! gen_dumper {
    ($name:ident {
        $(
            $kind:ident $([ $parent:ident ])? $({
                $(
                    $field:ident : $type:ty
                    $( [ $( $constraint:ident ),* ] )?
                ),*
                $(,)?
            })?
        ),*
        $(,)?
    }) => {
        fn dump_node<'gc, W: Write>(
            ctx: &'gc GCLock,
            node: &'gc Node<'gc>,
            emitter: &mut JSONEmitter<W>,
        ) {
            use crate::*;
            emitter.open_dict();
            emitter.emit_key("type");
            emitter.emit_string(node.name());
            match node {
                $(
                    Node::$kind($kind {$($($field,)*)? .. }) => {
                        $($(
                            emitter.emit_key(&ascii_snake_to_camel(stringify!($field)));
                            $field.dump(ctx, emitter);
                        )*)?
                    }
                ),*
            }
            emitter.close_dict();
        }
    }
}

nodekind_defs! { gen_dumper }

trait DumpChild<'gc> {
    fn dump<W: Write>(&self, ctx: &'gc GCLock, emitter: &mut JSONEmitter<W>);
}

impl<'gc> DumpChild<'gc> for f64 {
    fn dump<W: Write>(&self, _ctx: &'gc GCLock, emitter: &mut JSONEmitter<W>) {
        emitter.emit_number(*self);
    }
}

impl<'gc> DumpChild<'gc> for bool {
    fn dump<W: Write>(&self, _ctx: &'gc GCLock, emitter: &mut JSONEmitter<W>) {
        emitter.emit_bool(*self);
    }
}

impl<'gc> DumpChild<'gc> for NodeLabel {
    fn dump<W: Write>(&self, ctx: &'gc GCLock, emitter: &mut JSONEmitter<W>) {
        emitter.emit_string(ctx.str(*self));
    }
}

impl<'gc> DumpChild<'gc> for UnaryExpressionOperator {
    fn dump<W: Write>(&self, _ctx: &'gc GCLock, emitter: &mut JSONEmitter<W>) {
        emitter.emit_string(self.as_str());
    }
}

impl<'gc> DumpChild<'gc> for BinaryExpressionOperator {
    fn dump<W: Write>(&self, _ctx: &'gc GCLock, emitter: &mut JSONEmitter<W>) {
        emitter.emit_string(self.as_str());
    }
}

impl<'gc> DumpChild<'gc> for LogicalExpressionOperator {
    fn dump<W: Write>(&self, _ctx: &'gc GCLock, emitter: &mut JSONEmitter<W>) {
        emitter.emit_string(self.as_str());
    }
}

impl<'gc> DumpChild<'gc> for UpdateExpressionOperator {
    fn dump<W: Write>(&self, _ctx: &'gc GCLock, emitter: &mut JSONEmitter<W>) {
        emitter.emit_string(self.as_str());
    }
}

impl<'gc> DumpChild<'gc> for AssignmentExpressionOperator {
    fn dump<W: Write>(&self, _ctx: &'gc GCLock, emitter: &mut JSONEmitter<W>) {
        emitter.emit_string(self.as_str());
    }
}

impl<'gc> DumpChild<'gc> for VariableDeclarationKind {
    fn dump<W: Write>(&self, _ctx: &'gc GCLock, emitter: &mut JSONEmitter<W>) {
        emitter.emit_string(self.as_str());
    }
}

impl<'gc> DumpChild<'gc> for PropertyKind {
    fn dump<W: Write>(&self, _ctx: &'gc GCLock, emitter: &mut JSONEmitter<W>) {
        emitter.emit_string(self.as_str());
    }
}

impl<'gc> DumpChild<'gc> for MethodDefinitionKind {
    fn dump<W: Write>(&self, _ctx: &'gc GCLock, emitter: &mut JSONEmitter<W>) {
        emitter.emit_string(self.as_str());
    }
}

impl<'gc> DumpChild<'gc> for ImportKind {
    fn dump<W: Write>(&self, _ctx: &'gc GCLock, emitter: &mut JSONEmitter<W>) {
        emitter.emit_string(self.as_str());
    }
}

impl<'gc> DumpChild<'gc> for ExportKind {
    fn dump<W: Write>(&self, _ctx: &'gc GCLock, emitter: &mut JSONEmitter<W>) {
        emitter.emit_string(self.as_str());
    }
}

impl<'gc> DumpChild<'gc> for NodeString {
    fn dump<W: Write>(&self, ctx: &'gc GCLock, emitter: &mut JSONEmitter<W>) {
        emitter.emit_string(&format!(
            "u{:?}",
            String::from_utf16_lossy(ctx.str_u16(*self))
        ))
    }
}

impl<'gc, T: DumpChild<'gc>> DumpChild<'gc> for Option<T> {
    fn dump<W: Write>(&self, ctx: &'gc GCLock, emitter: &mut JSONEmitter<W>) {
        match self {
            None => emitter.emit_null(),
            Some(t) => t.dump(ctx, emitter),
        };
    }
}

impl<'gc> DumpChild<'gc> for &'gc Node<'gc> {
    fn dump<W: Write>(&self, ctx: &'gc GCLock, emitter: &mut JSONEmitter<W>) {
        dump_node(ctx, self, emitter);
    }
}

impl<'gc> DumpChild<'gc> for NodeList<'gc> {
    fn dump<W: Write>(&self, ctx: &'gc GCLock, emitter: &mut JSONEmitter<W>) {
        emitter.open_array();
        for elem in self.iter() {
            dump_node(ctx, elem, emitter);
        }
        emitter.close_array();
    }
}

pub fn dump_json<W: Write>(
    writer: W,
    ctx: &mut Context,
    root: &NodeRc,
    pretty: Pretty,
) -> io::Result<()> {
    let gc = GCLock::new(ctx);
    let mut emitter = JSONEmitter::new(writer, pretty);
    dump_node(&gc, root.node(&gc), &mut emitter);
    emitter.end()
}


/*
 * 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.
 */

/// The name of the field of an AST node.
#[derive(Debug, Copy, Clone, PartialEq)]
#[allow(non_camel_case_types)]
pub enum NodeField {
    accessibility,
    alternate,
    argument,
    arguments,
    assertions,
    asserts,
    attributes,
    bigint,
    block,
    body,
    bound,
    call_properties,
    callee,
    cases,
    check_type,
    children,
    closing_element,
    closing_fragment,
    computed,
    consequent,
    constraint,
    cooked,
    declaration,
    declarations,
    declare,
    decorators,
    default,
    delegate,
    directive,
    discriminant,
    element_type,
    element_types,
    elements,
    exact,
    explicit_type,
    export,
    export_kind,
    exported,
    expr_name,
    expression,
    expressions,
    extends,
    extends_type,
    false_type,
    finalizer,
    flags,
    generator,
    handler,
    has_unknown_members,
    id,
    implements,
    impltype,
    import_kind,
    imported,
    index_type,
    indexers,
    inexact,
    init,
    initializer,
    internal_slots,
    is_async,
    is_await,
    is_static,
    key,
    key_tparam,
    kind,
    label,
    left,
    literal,
    local,
    members,
    meta,
    method,
    mixins,
    name,
    namespace,
    object,
    object_type,
    opening_element,
    opening_fragment,
    operator,
    optional,
    param,
    parameter,
    parameter_name,
    parameters,
    params,
    pattern,
    predicate,
    prefix,
    prop_type,
    properties,
    property,
    proto,
    qualification,
    quasi,
    quasis,
    raw,
    readonly,
    renders_type,
    rest,
    return_type,
    right,
    self_closing,
    shorthand,
    source,
    source_type,
    specifiers,
    super_class,
    super_type_parameters,
    supertype,
    tag,
    tail,
    test,
    this,
    trailing_comma,
    true_type,
    ts_modifiers,
    type_annotation,
    type_arguments,
    type_name,
    type_parameter,
    type_parameters,
    types,
    update,
    uses_extends_bound,
    value,
    variance,
}


/*
 * 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 ),* ] )?
                ),*
                $(,)?
            })?
        ),*
        $(,)?
    }) => {
        use crate::context::NodeListElement;
        use crate::node_child::NodeChild;

        // 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 list fields of `self` and call `cb` with each `NodeListElement`
            /// in this AST node only (non-recursive).
            pub(crate) fn mark_lists<'ast: 'gc, CB: Fn(&NodeListElement)>(
                &'gc self,
                ctx: &'gc GCLock<'ast, '_>,
                cb: CB,
            ) {
                match self {
                    $(
                        Node::$kind($kind {
                            $($($field,)*)?
                            ..
                        }) => {
                            $($(
                                $field.mark_list(ctx, &cb);
                            )*)?
                        }
                    ),*
                }
            }

            /// 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< V: VisitorMut<'gc>>(
                &'gc self,
                ctx: &'gc GCLock,
                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,)*)?
            }

            impl<'a> $kind<'a> {
                /// Build this template.
                /// This function just calls `build_template` for the corresponding builder
                /// in `builder`, but allows type inference to avoid repeating the name.
                pub fn build(self, lock: &'a super::GCLock) -> &'a Node<'a> {
                    super::builder::$kind::build_template(lock, self)
                }
            }
            )*
        }

        pub mod builder {
            use super::{
                AssignmentExpressionOperator,
                BinaryExpressionOperator,
                ExportKind,
                GCLock,
                ImportKind,
                LogicalExpressionOperator,
                MethodDefinitionKind,
                Node,
                NodeLabel,
                NodeList,
                node_child::NodeChild,
                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> From<$kind<'a>> for Builder<'a> {
                /// Simple conversion which goes from the individual builder structs
                /// to the actual `Builder` enum.
                fn from(kind: $kind<'a>) -> Self {
                    Builder::$kind(kind)
                }
            }

            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: &'gc Node<'gc>) -> 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<&'gc Node<'gc>> {
        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<'gc> {
        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) -> &'gc Node<'gc> {
        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(_)
        )
    }
    pub fn is_pattern(&self) -> bool {
        matches!(
            self,
            Node::ObjectPattern(..)
                | Node::ArrayPattern(..)
                | Node::AssignmentPattern(..)
                | Node::RestElement(..)
        )
    }
}

#[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,
        }
    };
}


/*
 * 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.
 */

//! Juno abstract syntax tree.
//!
//! Provides a transformable AST which is stored in a garbage-collected heap.
//! All nodes are stored in [`Context`], which handles memory management of the nodes
//! and exposes a safe API.
//!
//! Allocation and viewing of nodes must be done via the use of a [`GCLock`],
//! **of which there must be only one active per thread at any time**,
//! to avoid accidentally mixing `Node`s between `Context`.
//! The `GCLock` will provide `&'gc Node<'gc>`,
//! i.e. a `Node` that does not outlive the `GCLock` and which references other `Node`s which
//! also do not outlive the `GCLock`.
//!
//! Nodes are allocated and cloned/modified by using the various `Builder` structs,
//! for example [`NumericLiteralBuilder`].
//! Builder structs have `build_template` functions that take "template" structs,
//! which have the same general structure as the various node kinds, but are only used
//! for building/allocating nodes in the `Context`.
//!
//! Visitor patterns are provided by [`Visitor`] and [`VisitorMut`].

#[macro_use]
mod def;

mod context;
mod dump;
mod field;
mod kind;
mod node_child;
mod node_enums;
mod validate;
mod visitor;

pub use context::Context;
pub use context::GCLock;
pub use context::NodePtr;
pub use context::NodeRc;
pub use dump::dump_json;
pub use dump::Pretty;
pub use field::NodeField;
pub use juno_support::source_manager::SourceId;
pub use juno_support::source_manager::SourceLoc;
pub use juno_support::source_manager::SourceManager;
pub use juno_support::source_manager::SourceRange;
pub use kind::NodeVariant;
pub use kind::*;
pub use node_child::NodeLabel;
pub use node_child::NodeList;
pub use node_child::NodeMetadata;
pub use node_child::NodeString;
pub use node_child::TemplateMetadata;
pub use node_enums::*;
pub use validate::validate_tree;
pub use validate::validate_tree_pure;
pub use validate::TreeValidationError;
pub use validate::ValidationError;
pub use visitor::Path;
pub use visitor::TransformResult;
pub use visitor::Visitor;
pub use visitor::VisitorMut;


/*
 * 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.
 */

//! Structures used to represent children in the AST.
//! `NodeChild` must be implemented by all node fields in the AST.

use std::marker::PhantomData;

use juno_support::atom_table::Atom;
use juno_support::atom_table::AtomU16;
use juno_support::source_manager::SourceId;
use juno_support::source_manager::SourceLoc;
use juno_support::source_manager::SourceRange;

use crate::builder;
use crate::context::NodeListElement;
use crate::template;
use crate::GCLock;
use crate::Node;
use crate::Path;
use crate::TransformResult;
use crate::Visitor;
use crate::VisitorMut;

/// Metadata common to all AST nodes.
///
/// Stored inside [`Node`] and must not be constructed directly by users.
/// Instead it should be allocated in the GC by using the `Builder`.
/// Must not derive `Clone` or `Copy` in order to avoid `Node` being allocated
/// by callers outside this module.
#[derive(Debug)]
pub struct NodeMetadata<'a> {
    pub(crate) phantom: PhantomData<&'a Node<'a>>,
    pub range: SourceRange,
}

impl<'a> NodeMetadata<'a> {
    pub(crate) fn build_template(template: TemplateMetadata<'a>) -> NodeMetadata<'a> {
        NodeMetadata {
            phantom: template.phantom,
            range: template.range,
        }
    }
}

/// Metadata common to all AST nodes used in templates.
/// Stored inside template notes and may be constructed directly by users.
#[derive(Debug, Clone)]
pub struct TemplateMetadata<'a> {
    pub phantom: PhantomData<&'a Node<'a>>,
    pub range: SourceRange,
}

impl Default for TemplateMetadata<'_> {
    fn default() -> Self {
        Self {
            phantom: Default::default(),
            range: SourceRange {
                file: SourceId::INVALID,
                start: SourceLoc::invalid(),
                end: SourceLoc::invalid(),
            },
        }
    }
}

impl<'a> From<&NodeMetadata<'a>> for TemplateMetadata<'a> {
    fn from(metadata: &NodeMetadata<'a>) -> Self {
        Self {
            phantom: Default::default(),
            range: metadata.range,
        }
    }
}

impl<'a> From<SourceRange> for TemplateMetadata<'a> {
    fn from(range: SourceRange) -> Self {
        Self {
            phantom: Default::default(),
            range,
        }
    }
}

/// JS identifier represented as valid UTF-8.
pub type NodeLabel = Atom;

/// An ordered list of nodes used as a property in the AST.
///
/// Implemented as a linked list internally to avoid extra overhead that would exist if it were
/// to allocate a `Vec` or some other structure that required allocating on the native heap.
///
/// Because this is just a pointer to the head of the list, it implements `Copy` much like any other
/// pointer/reference, allowing to user to handle it much like `&Node` in many cases.
#[derive(Debug, Copy, Clone)]
pub struct NodeList<'a> {
    /// If non-null, pointer to the first element of the list.
    /// If null, the list is empty.
    head: *const NodeListElement<'a>,
}

impl<'a> NodeList<'a> {
    /// Create a new empty list.
    /// Guaranteed to be fast, performs no allocations.
    pub fn new<'gc>(_: &'gc GCLock) -> NodeList<'gc> {
        NodeList {
            head: std::ptr::null(),
        }
    }

    /// Connect the provided pre-existing nodes into a `NodeList` via iteration.
    /// `NodeList` doesn't implement `FromIterator` directly due to the `GCLock` requirement.
    pub fn from_iter<'gc, I: IntoIterator<Item = &'gc Node<'gc>>>(
        lock: &'gc GCLock<'_, '_>,
        nodes: I,
    ) -> NodeList<'gc> {
        let mut it = nodes.into_iter();
        match it.next() {
            Some(first) => {
                // At least one element in the list.
                // Allocate the `NodeListElement`s in the context.
                let head_elem: &'gc NodeListElement<'gc> = lock.append_list_element(None, first);
                let mut prev_elem = head_elem;
                // Exhaust the rest of the iterator.
                for next in it {
                    let next_elem = lock.append_list_element(Some(prev_elem), next);
                    prev_elem = next_elem;
                }
                NodeList { head: head_elem }
            }
            _ => {
                // No elements, return the empty `NodeList`.
                Self::new(lock)
            }
        }
    }

    pub fn iter(self) -> NodeListIterator<'a> {
        NodeListIterator { ptr: self.head }
    }

    /// Whether this `NodeList` has no elements.
    /// Cost: `O(1)`
    pub fn is_empty(&self) -> bool {
        self.head.is_null()
    }

    /// Return the first element of the list if it exists, else `None`.
    pub fn head(&self) -> Option<&'a Node<'a>> {
        self.iter().next()
    }

    /// Return the length of the list.
    /// Cost: `O(n)` where `n` is the length of the list.
    /// Use [`Self::is_empty`] instead if you just want to check whether it's empty.
    pub fn len(&self) -> usize {
        self.iter().count()
    }
}

impl<'a> IntoIterator for NodeList<'a> {
    type Item = &'a Node<'a>;

    type IntoIter = NodeListIterator<'a>;

    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}

/// Iterator for `Node`s in the `NodeList`.
pub struct NodeListIterator<'a> {
    /// The upcoming element in the iteration order.
    /// `null` if the iteration is complete (`next` will return `None`).
    ptr: *const NodeListElement<'a>,
}

impl<'a> Iterator for NodeListIterator<'a> {
    type Item = &'a Node<'a>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.ptr.is_null() {
            None
        } else {
            unsafe {
                let result = &*self.ptr;
                self.ptr = result.next.get();
                debug_assert!(!result.inner.is_null(), "NodeList node must not be null");
                Some(&*result.inner)
            }
        }
    }
}

/// JS string literals don't have to contain valid UTF-8,
/// so we wrap a `Vec<u16>`, which allows us to represent UTF-16 characters
/// without being subject to Rust's restrictions on [`String`].
pub type NodeString = AtomU16;

impl<'gc> Node<'gc> {
    pub fn visit<V: Visitor<'gc>>(
        &'gc self,
        ctx: &'gc GCLock,
        visitor: &mut V,
        path: Option<Path<'gc>>,
    ) {
        visitor.call(ctx, self, path);
    }

    /// Visit this node with `visitor` and return the modified root node.
    /// If the root node is to be removed, return `None`.
    pub fn visit_mut<V: VisitorMut<'gc>>(
        &'gc self,
        ctx: &'gc GCLock,
        visitor: &mut V,
        path: Option<Path<'gc>>,
    ) -> Option<&'gc Node<'gc>> {
        match visitor.call(ctx, self, path) {
            TransformResult::Unchanged => Some(self),
            TransformResult::Removed => None,
            TransformResult::Changed(new_node) => Some(new_node),
            TransformResult::Expanded(_) => {
                panic!("Attempt to replace a single node with multiple");
            }
        }
    }
}

/// Trait implemented by possible child types of `NodeKind`.
pub(crate) trait NodeChild<'gc>
where
    Self: std::marker::Sized,
{
    type Out;

    /// Visit this child of the given `node`.
    /// Should be no-op for any type that doesn't contain pointers to other
    /// `Node`s.
    fn visit_child<V: Visitor<'gc>>(self, _ctx: &'gc GCLock, _visitor: &mut V, _path: Path<'gc>) {}

    /// Visit this child of the given `node`.
    /// Should be no-op for any type that doesn't contain pointers to other
    /// `Node`s.
    fn visit_child_mut<V: VisitorMut<'gc>>(
        self,
        _ctx: &'gc GCLock,
        _visitor: &mut V,
        _path: Path<'gc>,
    ) -> TransformResult<Self::Out> {
        TransformResult::Unchanged
    }

    /// A way to "clone" children without actually implementing Clone.
    /// Not implementing Clone prevents accidental creation of `Node` references
    /// on the stack.
    /// Can't provide a default implementation here because associated type default definitions
    /// (for `Out`) are unstable.
    fn duplicate(self) -> Self::Out;

    /// If this NodeChild is a list, visit the elements and call `cb` with each `NodeListElement`
    /// in this AST node only (non-recursive).
    fn mark_list<CB: Fn(&NodeListElement)>(self, _lock: &'gc GCLock, _cb: CB) {}
}

impl NodeChild<'_> for f64 {
    type Out = Self;
    fn duplicate(self) -> Self::Out {
        self
    }
}
impl NodeChild<'_> for bool {
    type Out = Self;
    fn duplicate(self) -> Self::Out {
        self
    }
}
impl NodeChild<'_> for NodeLabel {
    type Out = Self;
    fn duplicate(self) -> Self::Out {
        self
    }
}
impl NodeChild<'_> for NodeString {
    type Out = Self;
    fn duplicate(self) -> Self::Out {
        self
    }
}
impl<'gc> NodeChild<'gc> for &NodeString {
    type Out = NodeString;
    fn duplicate(self) -> Self::Out {
        *self
    }
}
impl NodeChild<'_> for &Option<NodeString> {
    type Out = Option<NodeString>;
    fn duplicate(self) -> Self::Out {
        *self
    }
}

impl<'gc, T: NodeChild<'gc> + NodeChild<'gc, Out = T>> NodeChild<'gc> for Option<T> {
    type Out = Self;

    fn visit_child<V: Visitor<'gc>>(self, ctx: &'gc GCLock, visitor: &mut V, path: Path<'gc>) {
        if let Some(t) = self {
            t.visit_child(ctx, visitor, path);
        }
    }

    /// Visit this child of the given `node`.
    /// Should be no-op for any type that doesn't contain pointers to other
    /// `Node`s.
    fn visit_child_mut<V: VisitorMut<'gc>>(
        self,
        ctx: &'gc GCLock,
        visitor: &mut V,
        path: Path<'gc>,
    ) -> TransformResult<Self::Out> {
        use TransformResult::*;
        match self {
            None => Unchanged,
            Some(inner) => match inner.visit_child_mut(ctx, visitor, path) {
                Unchanged => Unchanged,
                Removed => Changed(None),
                Changed(new_node) => Changed(Some(new_node)),
                Expanded(_) => {
                    panic!("Attempt to replace a single optional node with multiple");
                }
            },
        }
    }

    fn duplicate(self) -> Self::Out {
        self.map(|inner| inner.duplicate())
    }
}

impl<'gc> NodeChild<'gc> for &Option<&'gc Node<'gc>> {
    type Out = Option<&'gc Node<'gc>>;

    fn visit_child<V: Visitor<'gc>>(self, ctx: &'gc GCLock, visitor: &mut V, path: Path<'gc>) {
        if let Some(t) = *self {
            t.visit_child(ctx, visitor, path);
        }
    }

    /// Visit this child of the given `node`.
    /// Should be no-op for any type that doesn't contain pointers to other
    /// `Node`s.
    fn visit_child_mut<V: VisitorMut<'gc>>(
        self,
        ctx: &'gc GCLock,
        visitor: &mut V,
        path: Path<'gc>,
    ) -> TransformResult<Self::Out> {
        use TransformResult::*;
        match self {
            None => Unchanged,
            Some(inner) => match inner.visit_child_mut(ctx, visitor, path) {
                Unchanged => Unchanged,
                Removed => Changed(None),
                Changed(new_node) => Changed(Some(new_node)),
                Expanded(_) => {
                    panic!("Attempt to replace a single optional node with multiple");
                }
            },
        }
    }

    fn duplicate(self) -> Self::Out {
        *self
    }
}

impl<'gc> NodeChild<'gc> for &'gc Node<'gc> {
    type Out = Self;

    fn visit_child<V: Visitor<'gc>>(self, ctx: &'gc GCLock, visitor: &mut V, path: Path<'gc>) {
        visitor.call(ctx, self, Some(path));
    }

    /// Visit this child of the given `node`.
    /// Should be no-op for any type that doesn't contain pointers to other
    /// `Node`s.
    fn visit_child_mut<V: VisitorMut<'gc>>(
        self,
        ctx: &'gc GCLock,
        visitor: &mut V,
        path: Path<'gc>,
    ) -> TransformResult<Self::Out> {
        match visitor.call(ctx, self, Some(path)) {
            TransformResult::Removed => {
                TransformResult::Changed(builder::EmptyStatement::build_template(
                    ctx,
                    template::EmptyStatement {
                        metadata: TemplateMetadata {
                            phantom: Default::default(),
                            range: SourceRange {
                                file: self.range().file,
                                start: self.range().start,
                                end: self.range().start,
                            },
                        },
                    },
                ))
            }
            result => result,
        }
    }

    fn duplicate(self) -> Self::Out {
        self
    }
}

impl<'gc> NodeChild<'gc> for NodeList<'gc> {
    type Out = NodeList<'gc>;

    fn visit_child<V: Visitor<'gc>>(self, ctx: &'gc GCLock, visitor: &mut V, path: Path<'gc>) {
        for child in self.iter() {
            visitor.call(ctx, child, Some(path));
        }
    }

    fn visit_child_mut<'ast, 'ctx, V: VisitorMut<'gc>>(
        self,
        ctx: &'gc GCLock<'ast, 'ctx>,
        visitor: &mut V,
        path: Path<'gc>,
    ) -> TransformResult<Self::Out> {
        use TransformResult::*;
        let mut index = 0;
        let mut it: NodeListIterator<'gc> = self.iter();
        // Assume no copies to start.
        while let Some(elem) = it.next() {
            let node = visitor.call(ctx, elem, Some(path));
            if let Unchanged = node {
                index += 1;
                continue;
            }
            // First change found, either removed or changed node.
            // Fill in the elements we skippped.
            let mut result = self.iter().take(index).collect::<Vec<&Node>>();
            // If the node was changed or expanded, push it.
            match node {
                Changed(new_node) => result.push(new_node),
                Expanded(new_nodes) => {
                    result.extend(new_nodes);
                }
                Removed => {}
                Unchanged => {
                    unreachable!("checked for unchanged above")
                }
            };
            index += 1;
            // Fill the rest of the elements.
            for elem in it.by_ref() {
                match visitor.call(ctx, elem, Some(path)) {
                    Unchanged => result.push(elem),
                    Removed => {}
                    Changed(new_node) => result.push(new_node),
                    Expanded(new_nodes) => {
                        result.extend(new_nodes);
                    }
                }
                index += 1;
            }
            return Changed(NodeList::from_iter(ctx, result));
        }
        Unchanged
    }

    fn duplicate(self) -> Self::Out {
        NodeList { head: self.head }
    }

    fn mark_list<CB: Fn(&NodeListElement)>(self, _lock: &'gc GCLock, cb: CB) {
        let mut cur = self.head;
        while !cur.is_null() {
            let elem = unsafe { &*cur };
            cb(elem);
            cur = elem.next.get();
        }
    }
}

#[cfg(test)]
mod tests {
    use std::collections::HashMap;

    use crate::*;

    #[test]
    fn test_node_ref() {
        let mut ctx = Context::new();
        let lock = GCLock::new(&mut ctx);

        let mut m = HashMap::new();

        let n1 = NodePtr::from(builder::NumericLiteral::build_template(
            &lock,
            template::NumericLiteral {
                metadata: Default::default(),
                value: 10.0,
            },
        ));
        let n2 = NodePtr::from(builder::NumericLiteral::build_template(
            &lock,
            template::NumericLiteral {
                metadata: Default::default(),
                value: 20.0,
            },
        ));
        let n25 = Box::new(n2);
        assert_ne!(n1, n2);
        assert_eq!(n2, *n25);
        m.insert(n1, 10);
        m.insert(n2, 20);

        assert_eq!(10, *m.get(&n1).unwrap());
        assert_eq!(20, *m.get(&n2).unwrap());
        assert_eq!(20, *m.get(&*n25).unwrap());
    }
}


/*
 * 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.
 */

//! Simple enums used as children in AST nodes.

use juno_support::define_str_enum;
use thiserror::Error;

use crate::node_child::NodeChild;

#[derive(Debug, Copy, Clone, Error)]
#[error("Invalid string property for AST node")]
pub struct TryFromStringError;

define_str_enum!(
    UnaryExpressionOperator,
    TryFromStringError,
    (Delete, "delete"),
    (Void, "void"),
    (Typeof, "typeof"),
    (Plus, "+"),
    (Minus, "-"),
    (BitNot, "~"),
    (Not, "!"),
);

define_str_enum!(
    BinaryExpressionOperator,
    TryFromStringError,
    (LooseEquals, "=="),
    (LooseNotEquals, "!="),
    (StrictEquals, "==="),
    (StrictNotEquals, "!=="),
    (Less, "<"),
    (LessEquals, "<="),
    (Greater, ">"),
    (GreaterEquals, ">="),
    (LShift, "<<"),
    (RShift, ">>"),
    (RShift3, ">>>"),
    (Plus, "+"),
    (Minus, "-"),
    (Mult, "*"),
    (Div, "/"),
    (Mod, "%"),
    (BitOr, "|"),
    (BitXor, "^"),
    (BitAnd, "&"),
    (Exp, "**"),
    (In, "in"),
    (Instanceof, "instanceof"),
);

define_str_enum!(
    LogicalExpressionOperator,
    TryFromStringError,
    (And, "&&"),
    (Or, "||"),
    (NullishCoalesce, "??"),
);

define_str_enum!(
    UpdateExpressionOperator,
    TryFromStringError,
    (Increment, "++"),
    (Decrement, "--"),
);

define_str_enum!(
    AssignmentExpressionOperator,
    TryFromStringError,
    (Assign, "="),
    (LShiftAssign, "<<="),
    (RShiftAssign, ">>="),
    (RShift3Assign, ">>>="),
    (PlusAssign, "+="),
    (MinusAssign, "-="),
    (MultAssign, "*="),
    (DivAssign, "/="),
    (ModAssign, "%="),
    (BitOrAssign, "|="),
    (BitXorAssign, "^="),
    (BitAndAssign, "&="),
    (ExpAssign, "**="),
    (LogicalOrAssign, "||="),
    (LogicalAndAssign, "&&="),
    (NullishCoalesceAssign, "??="),
);

define_str_enum!(
    VariableDeclarationKind,
    TryFromStringError,
    (Var, "var"),
    (Let, "let"),
    (Const, "const"),
);

define_str_enum!(
    PropertyKind,
    TryFromStringError,
    (Init, "init"),
    (Get, "get"),
    (Set, "set"),
);

define_str_enum!(
    MethodDefinitionKind,
    TryFromStringError,
    (Method, "method"),
    (Constructor, "constructor"),
    (Get, "get"),
    (Set, "set"),
);

define_str_enum!(
    ImportKind,
    TryFromStringError,
    (Value, "value"),
    (Type, "type"),
    (Typeof, "typeof"),
);

define_str_enum!(
    ExportKind,
    TryFromStringError,
    (Value, "value"),
    (Type, "type"),
);

impl NodeChild<'_> for UnaryExpressionOperator {
    type Out = Self;
    fn duplicate(self) -> Self::Out {
        self
    }
}
impl NodeChild<'_> for BinaryExpressionOperator {
    type Out = Self;
    fn duplicate(self) -> Self::Out {
        self
    }
}
impl NodeChild<'_> for LogicalExpressionOperator {
    type Out = Self;
    fn duplicate(self) -> Self::Out {
        self
    }
}
impl NodeChild<'_> for UpdateExpressionOperator {
    type Out = Self;
    fn duplicate(self) -> Self::Out {
        self
    }
}
impl NodeChild<'_> for AssignmentExpressionOperator {
    type Out = Self;
    fn duplicate(self) -> Self::Out {
        self
    }
}
impl NodeChild<'_> for VariableDeclarationKind {
    type Out = Self;
    fn duplicate(self) -> Self::Out {
        self
    }
}
impl NodeChild<'_> for PropertyKind {
    type Out = Self;
    fn duplicate(self) -> Self::Out {
        self
    }
}
impl NodeChild<'_> for MethodDefinitionKind {
    type Out = Self;
    fn duplicate(self) -> Self::Out {
        self
    }
}
impl NodeChild<'_> for ImportKind {
    type Out = Self;
    fn duplicate(self) -> Self::Out {
        self
    }
}
impl NodeChild<'_> for ExportKind {
    type Out = Self;
    fn duplicate(self) -> Self::Out {
        self
    }
}


/*
 * 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 thiserror::Error;

use super::kind::*;
use super::AssignmentExpressionOperator;
use super::BinaryExpressionOperator;
use super::Context;
use super::ExportKind;
use super::GCLock;
use super::ImportKind;
use super::LogicalExpressionOperator;
use super::MethodDefinitionKind;
use super::Node;
use super::NodeLabel;
use super::NodeList;
use super::NodeRc;
use super::NodeString;
use super::NodeVariant;
use super::Path;
use super::PropertyKind;
use super::UnaryExpressionOperator;
use super::UpdateExpressionOperator;
use super::VariableDeclarationKind;
use super::Visitor;

macro_rules! gen_validate_fn {
    ($name:ident {
        $(
            $kind:ident $([ $parent:ident ])? $({
                $(
                    $field:ident : $type:ty
                    $( [ $( $constraint:ident ),* ] )?
                ),*
                $(,)?
            })?
        ),*
        $(,)?
    }) => {
            /// Check whether this is a valid kind for `node`.
            fn validate_node<'gc>(
                ctx: &'gc GCLock,
                node: &'gc Node<'gc>,
            ) -> Result<(), ValidationError> {
                match node {
                    $(
                        Node::$kind($kind {$($($field,)*)? .. }) => {
                            // Run the validation for each child.
                            // Use `true &&` to make it work when there's no children.
                            $($(
                                $field.validate_child(ctx, node, &[$($(NodeVariant::$constraint),*)?])?;
                            )*)?
                        }
                    ),*
                };
                validate_custom(ctx, node)?;
                Ok(())
            }
    }
}

nodekind_defs! { gen_validate_fn }

trait ValidChild<'gc> {
    /// Check whether this is a valid child of `node` given the constraints.
    fn validate_child(
        &self,
        _ctx: &'gc GCLock,
        _node: &'gc Node<'gc>,
        _constraints: &[NodeVariant],
    ) -> Result<(), ValidationError> {
        Ok(())
    }
}

impl ValidChild<'_> for f64 {}
impl ValidChild<'_> for bool {}
impl ValidChild<'_> for NodeLabel {}
impl ValidChild<'_> for UnaryExpressionOperator {}
impl ValidChild<'_> for BinaryExpressionOperator {}
impl ValidChild<'_> for LogicalExpressionOperator {}
impl ValidChild<'_> for UpdateExpressionOperator {}
impl ValidChild<'_> for AssignmentExpressionOperator {}
impl ValidChild<'_> for VariableDeclarationKind {}
impl ValidChild<'_> for PropertyKind {}
impl ValidChild<'_> for MethodDefinitionKind {}
impl ValidChild<'_> for ImportKind {}
impl ValidChild<'_> for ExportKind {}
impl ValidChild<'_> for NodeString {}

impl<'gc, T: ValidChild<'gc>> ValidChild<'gc> for Option<T> {
    fn validate_child(
        &self,
        ctx: &'gc GCLock,
        node: &'gc Node<'gc>,
        constraints: &[NodeVariant],
    ) -> Result<(), ValidationError> {
        match self {
            None => Ok(()),
            Some(t) => t.validate_child(ctx, node, constraints),
        }
    }
}

impl<'gc> ValidChild<'gc> for &Node<'gc> {
    fn validate_child(
        &self,
        ctx: &'gc GCLock,
        node: &'gc Node<'gc>,
        constraints: &[NodeVariant],
    ) -> Result<(), ValidationError> {
        for &constraint in constraints {
            if instanceof(self.variant(), constraint) {
                return Ok(());
            }
        }
        Err(ValidationError::new(
            ctx,
            node,
            format!("Unexpected {:?} in {:?}", self.variant(), node.variant()),
        ))
    }
}

impl<'gc> ValidChild<'gc> for NodeList<'gc> {
    fn validate_child(
        &self,
        ctx: &'gc GCLock,
        node: &'gc Node<'gc>,
        constraints: &[NodeVariant],
    ) -> Result<(), ValidationError> {
        'elems: for elem in self.iter() {
            for &constraint in constraints {
                if instanceof(elem.variant(), constraint) {
                    // Found a valid constraint for this element,
                    // move on to the next element.
                    continue 'elems;
                }
            }
            // Failed to find a constraint that matched, early return.
            return Err(ValidationError::new(
                ctx,
                node,
                format!("Unexpected {:?} in {:?}", elem.variant(), node.variant()),
            ));
        }
        Ok(())
    }
}

/// Return whether `subtype` contains `supertype` in its parent chain.
fn instanceof(subtype: NodeVariant, supertype: NodeVariant) -> bool {
    let mut cur = subtype;
    loop {
        if cur == supertype {
            return true;
        }
        match cur.parent() {
            None => return false,
            Some(next) => {
                cur = next;
            }
        }
    }
}

/// Custom validation function for constraints which can't be expressed
/// using just the inheritance structure in Node.
fn validate_custom<'gc>(ctx: &'gc GCLock, node: &'gc Node<'gc>) -> Result<(), ValidationError> {
    match node {
        Node::MemberExpression(MemberExpression {
            metadata: _,
            property,
            object: _,
            computed,
        })
        | Node::OptionalMemberExpression(OptionalMemberExpression {
            metadata: _,
            property,
            object: _,
            computed,
            optional: _,
        }) => {
            if *computed {
                property.validate_child(ctx, node, &[NodeVariant::Expression])?;
            } else {
                property.validate_child(ctx, node, &[NodeVariant::Identifier])?;
            }
        }

        Node::Property(Property {
            metadata: _,
            key,
            value,
            kind,
            computed,
            method,
            shorthand,
        }) => {
            if *computed && *shorthand {
                return Err(ValidationError::new(
                    ctx,
                    node,
                    "Property cannot be computed and shorthand".to_string(),
                ));
            }
            if !*computed {
                key.validate_child(ctx, node, &[NodeVariant::Identifier, NodeVariant::Literal])?;
            }
            if *method {
                value.validate_child(ctx, node, &[NodeVariant::FunctionExpression])?;
            }
            if *kind == PropertyKind::Get || *kind == PropertyKind::Set {
                value.validate_child(ctx, node, &[NodeVariant::FunctionExpression])?;
            }
        }

        _ => {}
    }
    Ok(())
}

/// An AST validation error.
pub struct ValidationError {
    /// The AST node which failed to validate.
    pub node: NodeRc,

    /// A description of the invalid state encountered.
    pub message: String,
}

impl ValidationError {
    pub fn new<'gc>(gc: &'gc GCLock, node: &'gc Node<'gc>, message: String) -> ValidationError {
        ValidationError {
            node: NodeRc::from_node(gc, node),
            message,
        }
    }
}

/// Runs validation on the AST and stores errors.
struct Validator {
    /// Every error encountered so far.
    /// If empty after validation, the AST is valid.
    pub errors: Vec<ValidationError>,
}

impl Validator {
    pub fn new() -> Self {
        Validator { errors: Vec::new() }
    }

    /// Run validation recursively starting at the `root`.
    pub fn validate_root<'gc>(&mut self, ctx: &'gc GCLock, root: &'gc Node<'gc>) {
        self.validate_node(ctx, root);
    }

    /// Validate `node` and recursively validate its children.
    fn validate_node<'gc>(&mut self, ctx: &'gc GCLock, node: &'gc Node<'gc>) {
        if let Err(e) = validate_node(ctx, node) {
            self.errors.push(e);
        }
        node.visit_children(ctx, self);
    }
}

impl<'gc> Visitor<'gc> for Validator {
    fn call(&mut self, ctx: &'gc GCLock, node: &'gc Node<'gc>, _parent: Option<Path<'gc>>) {
        self.validate_node(ctx, node);
    }
}

/// Validate the full AST tree.
/// If it fails, return all the errors encountered along the way.
pub fn validate_tree_pure(ctx: &mut Context, root: &NodeRc) -> Result<(), Vec<ValidationError>> {
    let mut validator = Validator::new();
    let gc = GCLock::new(ctx);
    validator.validate_root(&gc, root.node(&gc));
    if validator.errors.is_empty() {
        Ok(())
    } else {
        Err(validator.errors)
    }
}

#[derive(Debug, Error)]
#[error("{0} AST validation errors")]
pub struct TreeValidationError(usize);

/// Validate the full AST tree.
/// If it fails, reports all errors to the source manager.
pub fn validate_tree(ctx: &mut Context, root: &NodeRc) -> Result<(), TreeValidationError> {
    match validate_tree_pure(ctx, root) {
        Ok(_) => Ok(()),
        Err(errors) => {
            let lock = GCLock::new(ctx);
            for e in &errors {
                lock.sm()
                    .error(*e.node.node(&lock).range(), e.message.as_str());
            }
            Err(TreeValidationError(errors.len()))
        }
    }
}