Skip to main content

AstNode

Trait AstNode 

Source
pub trait AstNode<Lang> {
    type Syntax: Syntax<Lang = Lang>;
}
Expand description

A trait representing an AST node associated with a syntax definition.

This trait creates a type-level bridge between AST node types and their corresponding Syntax types, enabling generic, type-safe error handling and parser implementation. By associating an AST node with its syntax structure, we can automatically derive error construction logic and write language-polymorphic parsers.

§Design Philosophy

When parsing AST nodes, incomplete syntax errors need to know which syntax element failed to parse. The AstNode trait makes this relationship explicit at the type level:

  • Each AST node type declares its corresponding Syntax type
  • Generic code can use T::Syntax to construct appropriate IncompleteSyntax<T::Syntax> errors
  • The Lang parameter enables the same structural node (e.g., Name<S>) to have different syntax types in different language dialects

§Benefits

  1. Type Safety: Impossible to construct errors with the wrong syntax type
  2. Generic Parsers: Write parsers that work for any T: AstNode<Lang>
  3. Discoverability: Given an AST node, easily find its syntax definition
  4. Language Polymorphism: Same node structure, different syntax per dialect
  5. Reduced Boilerplate: Generic error handling without manual trait implementations

§Type Parameters

  • Lang: The language or dialect this AST node belongs to. This enables:
    • Distinguishing GraphQL from GraphQLx nodes
    • Supporting multiple language dialects in one codebase
    • Language-specific syntax customization

§Examples

§Basic Implementation

use tokora::{SimpleSpan, utils::{GenericArrayDeque, typenum::U2}, syntax::{Syntax, AstNode, Language}, error::IncompleteSyntax};
use core::fmt;

// Define a language
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct MyLanguage;

impl Language for MyLanguage {
    type SyntaxKind = (); // () is a placeholder
}

// Define syntax components
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum VariableComponent {
    Dollar,
    Name,
}

impl fmt::Display for VariableComponent {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Dollar => write!(f, "'$' prefix"),
            Self::Name => write!(f, "variable name"),
        }
    }
}

// Define syntax type
struct VariableSyntax;

impl Syntax for VariableSyntax {
    type Lang = MyLanguage;
    const KIND: () = (); // () is a placeholder
    type Component = VariableComponent;
    type COMPONENTS = U2;
    type REQUIRED = U2;

    fn possible_components() -> &'static GenericArrayDeque<Self::Component, Self::COMPONENTS> {
        const COMPONENTS: &GenericArrayDeque<VariableComponent, U2> = &GenericArrayDeque::from_array([VariableComponent::Dollar, VariableComponent::Name]);
        COMPONENTS
    }

    fn required_components() -> &'static GenericArrayDeque<Self::Component, Self::REQUIRED> {
        const REQUIRED: &GenericArrayDeque<VariableComponent, U2> = &GenericArrayDeque::from_array([VariableComponent::Dollar, VariableComponent::Name]);
        REQUIRED
    }
}

// Define AST node
struct Variable {
    name: String,
}

// Implement AstNode to bridge AST and Syntax
impl AstNode<MyLanguage> for Variable {
    type Syntax = VariableSyntax;
}

// Now generic code can use T::Syntax automatically
fn create_incomplete_error<T>(span: SimpleSpan, component: <T::Syntax as Syntax>::Component) -> IncompleteSyntax<T::Syntax>
where
    T: AstNode<MyLanguage>,
{
    IncompleteSyntax::new(span, component)
}

let error = create_incomplete_error::<Variable>(
    SimpleSpan::new(0, 3),
    VariableComponent::Dollar
);

§Language Polymorphism

// Same structure, different syntax per language
struct Name<S> {
    source: S,
}

// GraphQL implementation
impl<S> AstNode<GraphQL> for Name<S> {
    type Syntax = GraphQLNameSyntax;
}

// GraphQLx implementation (extended dialect)
impl<S> AstNode<GraphQLx> for Name<S> {
    type Syntax = GraphQLxNameSyntax; // Different syntax rules
}

§Common Patterns

§With Generic AST Nodes

For AST nodes with generic parameters, implement AstNode for the generic type:

struct TypeDefinition<Name, Directives> {
    name: Name,
    directives: Option<Directives>,
}

impl<Name, Directives> AstNode<GraphQL> for TypeDefinition<Name, Directives> {
    type Syntax = TypeDefinitionSyntax;
}

§Multiple Language Support

The same node structure can implement AstNode for multiple languages:

impl<S> AstNode<GraphQL> for Variable<S> {
    type Syntax = GraphQLVariableSyntax;
}

impl<S> AstNode<GraphQLx> for Variable<S> {
    type Syntax = GraphQLxVariableSyntax;
}

§See Also

  • Syntax: Defines the structure and components of syntax elements
  • IncompleteSyntax: Error type for tracking missing syntax components

Required Associated Types§

Source

type Syntax: Syntax<Lang = Lang>

The syntax type associated with this AST node.

This type defines the structural components that make up the AST node during parsing. It must implement Syntax<Lang = Lang>, ensuring language consistency.

§Examples
impl AstNode<MyLanguage> for Variable {
    type Syntax = VariableSyntax;
    //             ^^^^^^^^^^^^^^
    // This syntax type defines the components needed to parse a Variable
}

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§