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
Syntaxtype - Generic code can use
T::Syntaxto construct appropriateIncompleteSyntax<T::Syntax>errors - The
Langparameter enables the same structural node (e.g.,Name<S>) to have different syntax types in different language dialects
§Benefits
- Type Safety: Impossible to construct errors with the wrong syntax type
- Generic Parsers: Write parsers that work for any
T: AstNode<Lang> - Discoverability: Given an AST node, easily find its syntax definition
- Language Polymorphism: Same node structure, different syntax per dialect
- 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 elementsIncompleteSyntax: Error type for tracking missing syntax components
Required Associated Types§
Sourcetype Syntax: Syntax<Lang = Lang>
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".