ast: Add Fold, Folder traits, reorganize visit
parser: fix precedence of `BindOp::Let`
This commit is contained in:
@@ -4,6 +4,8 @@ pub mod macro_matcher;
|
|||||||
|
|
||||||
pub mod visit;
|
pub mod visit;
|
||||||
|
|
||||||
|
pub mod fold;
|
||||||
|
|
||||||
/// An annotation: extra data added on to important AST nodes.
|
/// An annotation: extra data added on to important AST nodes.
|
||||||
pub trait Annotation: Clone + std::fmt::Display + std::fmt::Debug + PartialEq + Eq {}
|
pub trait Annotation: Clone + std::fmt::Display + std::fmt::Debug + PartialEq + Eq {}
|
||||||
impl<T: Clone + std::fmt::Debug + std::fmt::Display + PartialEq + Eq> Annotation for T {}
|
impl<T: Clone + std::fmt::Debug + std::fmt::Display + PartialEq + Eq> Annotation for T {}
|
||||||
@@ -130,6 +132,7 @@ pub enum Op {
|
|||||||
XorSet, // Expr ^= Expr
|
XorSet, // Expr ^= Expr
|
||||||
OrSet, // Expr |= Expr
|
OrSet, // Expr |= Expr
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A qualified identifier
|
/// A qualified identifier
|
||||||
///
|
///
|
||||||
/// TODO: qualify identifier
|
/// TODO: qualify identifier
|
||||||
|
|||||||
271
src/ast/fold.rs
Normal file
271
src/ast/fold.rs
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
//! A folder (implementer of the [`Fold`] trait) maps ASTs to ASTs
|
||||||
|
#![warn(clippy::all, clippy::pedantic)]
|
||||||
|
#![allow(clippy::wildcard_imports, clippy::missing_errors_doc)]
|
||||||
|
|
||||||
|
use std::mem::MaybeUninit;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Deconstructs an entire AST, and reconstructs it from parts.
|
||||||
|
///
|
||||||
|
/// Each function acts as a customization point.
|
||||||
|
///
|
||||||
|
/// Aside from [Annotation]s, each node in the AST implements the [`Foldable`] trait,
|
||||||
|
/// which provides double dispatch.
|
||||||
|
pub trait Fold<A: Annotation> {
|
||||||
|
type Error;
|
||||||
|
|
||||||
|
fn fold_annotation(&mut self, anno: A) -> Result<A, Self::Error> {
|
||||||
|
Ok(anno)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes an [`Expr`], possibly transforms it, and produces a replacement [`Expr`]
|
||||||
|
fn fold_expr(&mut self, expr: Expr<A>) -> Result<Expr<A>, Self::Error> {
|
||||||
|
expr.children(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes a Symbol, possibly transforms it, and produces a replacement Symbol
|
||||||
|
fn fold_ident(&mut self, name: String) -> Result<String, Self::Error> {
|
||||||
|
name.children(self) // TODO: ^ this should be a symbol
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes a [`Path`], possibly transforms it, and produces a replacement [`Path`]
|
||||||
|
fn fold_path(&mut self, path: Path) -> Result<Path, Self::Error> {
|
||||||
|
path.children(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes a [`Literal`], possibly transforms it, and produces a replacement [`Literal`]
|
||||||
|
fn fold_literal(&mut self, lit: Literal) -> Result<Literal, Self::Error> {
|
||||||
|
lit.children(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes a [`Use`], possibly transforms it, and produces a replacement [`Use`]
|
||||||
|
fn fold_use(&mut self, item: Use) -> Result<Use, Self::Error> {
|
||||||
|
item.children(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes a [`Pat`], possibly transforms it, and produces a replacement [`Pat`]
|
||||||
|
fn fold_pat(&mut self, pat: Pat) -> Result<Pat, Self::Error> {
|
||||||
|
pat.children(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes a [`Bind`], possibly transforms it, and produces a replacement [`Bind`]
|
||||||
|
fn fold_bind(&mut self, bind: Bind<A>) -> Result<Bind<A>, Self::Error> {
|
||||||
|
bind.children(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes a [`Make`], possibly transforms it, and produces a replacement [`Make`]
|
||||||
|
fn fold_make(&mut self, make: Make<A>) -> Result<Make<A>, Self::Error> {
|
||||||
|
make.children(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes a [`MakeArm`], possibly transforms it, and produces a replacement [`MakeArm`]
|
||||||
|
fn fold_makearm(&mut self, arm: MakeArm<A>) -> Result<MakeArm<A>, Self::Error> {
|
||||||
|
arm.children(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Foldable<A: Annotation>: Sized {
|
||||||
|
/// Calls `Self`'s appropriate [Folder] function(s)
|
||||||
|
fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error>;
|
||||||
|
|
||||||
|
/// Destructures `self`, calling [`Foldable::fold_in`] on all foldable members,
|
||||||
|
/// and rebuilds a `Self` out of the results.
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn children<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Annotation> Foldable<A> for Expr<A> {
|
||||||
|
fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
|
||||||
|
folder.fold_expr(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn children<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
|
||||||
|
Ok(match self {
|
||||||
|
Self::Omitted => Self::Omitted,
|
||||||
|
Self::Id(path) => Self::Id(path.fold_in(folder)?),
|
||||||
|
Self::MetId(id) => Self::MetId(id.fold_in(folder)?),
|
||||||
|
Self::Lit(literal) => Self::Lit(literal.fold_in(folder)?),
|
||||||
|
Self::Use(item) => Self::Use(item.fold_in(folder)?),
|
||||||
|
Self::Bind(bind) => Self::Bind(bind.fold_in(folder)?),
|
||||||
|
Self::Make(make) => Self::Make(make.fold_in(folder)?),
|
||||||
|
Self::Op(op, annos) => Self::Op(op, annos.fold_in(folder)?),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Annotation> Foldable<A> for String {
|
||||||
|
fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
|
||||||
|
folder.fold_ident(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Annotation> Foldable<A> for Path {
|
||||||
|
fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
|
||||||
|
folder.fold_path(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn children<F: Fold<A> + ?Sized>(self, _folder: &mut F) -> Result<Self, F::Error> {
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Annotation> Foldable<A> for Literal {
|
||||||
|
fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
|
||||||
|
folder.fold_literal(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn children<F: Fold<A> + ?Sized>(self, _folder: &mut F) -> Result<Self, F::Error> {
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Annotation> Foldable<A> for Use {
|
||||||
|
fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
|
||||||
|
folder.fold_use(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn children<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
|
||||||
|
Ok(match self {
|
||||||
|
Self::Glob => Self::Glob,
|
||||||
|
Self::Name(name) => Self::Name(name),
|
||||||
|
Self::Path(name, rest) => Self::Path(name, rest.fold_in(folder)?),
|
||||||
|
Self::Tree(items) => Self::Tree(items.fold_in(folder)?),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Annotation> Foldable<A> for Pat {
|
||||||
|
fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
|
||||||
|
folder.fold_pat(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn children<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
|
||||||
|
Ok(match self {
|
||||||
|
Self::Ignore => Self::Ignore,
|
||||||
|
Self::Never => Self::Never,
|
||||||
|
Self::MetId(name) => Self::MetId(name.fold_in(folder)?),
|
||||||
|
Self::Name(name) => Self::Name(name.fold_in(folder)?),
|
||||||
|
Self::Path(path) => Self::Path(path.fold_in(folder)?),
|
||||||
|
Self::NamedStruct(path, pat) => {
|
||||||
|
Self::NamedStruct(path.fold_in(folder)?, pat.fold_in(folder)?)
|
||||||
|
}
|
||||||
|
Self::NamedTuple(path, pat) => {
|
||||||
|
Self::NamedTuple(path.fold_in(folder)?, pat.fold_in(folder)?)
|
||||||
|
}
|
||||||
|
Self::Lit(literal) => Self::Lit(literal.fold_in(folder)?),
|
||||||
|
Self::Op(op, pats) => Self::Op(op, pats.fold_in(folder)?),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Annotation> Foldable<A> for Bind<A> {
|
||||||
|
fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
|
||||||
|
folder.fold_bind(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn children<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
|
||||||
|
let Self(op, gens, pat, exprs) = self;
|
||||||
|
Ok(Self(
|
||||||
|
op,
|
||||||
|
gens.fold_in(folder)?,
|
||||||
|
pat.fold_in(folder)?,
|
||||||
|
exprs.fold_in(folder)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Annotation> Foldable<A> for Make<A> {
|
||||||
|
fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
|
||||||
|
folder.fold_make(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn children<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
|
||||||
|
let Self(expr, arms) = self;
|
||||||
|
Ok(Self(expr.fold_in(folder)?, arms.fold_in(folder)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Annotation> Foldable<A> for MakeArm<A> {
|
||||||
|
fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
|
||||||
|
folder.fold_makearm(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn children<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
|
||||||
|
let Self(name, expr) = self;
|
||||||
|
Ok(Self(name, expr.fold_in(folder)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Annotation + Foldable<A>, A: Annotation> Foldable<A> for Anno<T, A> {
|
||||||
|
fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
|
||||||
|
let Self(expr, anno) = self;
|
||||||
|
let anno = folder.fold_annotation(anno)?;
|
||||||
|
Ok(Self(expr.fold_in(folder)?, anno))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn children<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
|
||||||
|
let Self(expr, anno) = self;
|
||||||
|
Ok(Self(expr.children(folder)?, anno))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
// GENERIC IMPLEMENTATIONS ON COLLECTIONS //
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Maps the value in the box across `f()` without deallocating
|
||||||
|
fn box_try_map<T, E>(boxed: Box<T>, f: impl FnOnce(T) -> Result<T, E>) -> Result<Box<T>, E> {
|
||||||
|
// TODO: replace with Box::take when it stabilizes.
|
||||||
|
let rawbox = Box::into_raw(boxed);
|
||||||
|
|
||||||
|
// Safety: `rawbox` came from a Box, so it is aligned and initialized.
|
||||||
|
// To prevent further reuse and deallocate on failure, rawbox is
|
||||||
|
// shadowed by a Box<MaybeUninit<T>>.
|
||||||
|
// Safety: MaybeUninit<T> has the same size and alignment as T.
|
||||||
|
let (value, rawbox) = (unsafe { rawbox.read() }, unsafe {
|
||||||
|
Box::from_raw(rawbox.cast::<MaybeUninit<T>>())
|
||||||
|
});
|
||||||
|
|
||||||
|
// rawbox is reinitialized with f(value)
|
||||||
|
Ok(Box::write(rawbox, f(value)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Foldable<A>, A: Annotation> Foldable<A> for Box<T> {
|
||||||
|
fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
|
||||||
|
box_try_map(self, |t| t.fold_in(folder))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn children<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
|
||||||
|
box_try_map(self, |t| t.children(folder))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Foldable<A>, A: Annotation> Foldable<A> for Vec<T> {
|
||||||
|
fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
|
||||||
|
self.into_iter().map(|e| e.fold_in(folder)).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn children<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
|
||||||
|
// TODO: is this correct for generic data structures?
|
||||||
|
self.into_iter().map(|e| e.fold_in(folder)).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Foldable<A>, A: Annotation> Foldable<A> for Option<T> {
|
||||||
|
fn fold_in<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
|
||||||
|
Ok(match self {
|
||||||
|
Self::Some(value) => Some(value.fold_in(folder)?),
|
||||||
|
Self::None => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn children<F: Fold<A> + ?Sized>(self, folder: &mut F) -> Result<Self, F::Error> {
|
||||||
|
Ok(match self {
|
||||||
|
Self::Some(value) => Some(value.children(folder)?),
|
||||||
|
Self::None => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,9 @@ pub trait Visit<'a> {
|
|||||||
fn visit(&mut self, walk: &'a impl Walk<'a>) -> Result<(), Self::Error> {
|
fn visit(&mut self, walk: &'a impl Walk<'a>) -> Result<(), Self::Error> {
|
||||||
walk.visit_in(self)
|
walk.visit_in(self)
|
||||||
}
|
}
|
||||||
|
fn visit_expr<A: Annotation>(&mut self, expr: &'a Expr<A>) -> Result<(), Self::Error> {
|
||||||
|
expr.children(self)
|
||||||
|
}
|
||||||
fn visit_ident(&mut self, name: &'a str) -> Result<(), Self::Error> {
|
fn visit_ident(&mut self, name: &'a str) -> Result<(), Self::Error> {
|
||||||
name.children(self)
|
name.children(self)
|
||||||
}
|
}
|
||||||
@@ -33,9 +36,6 @@ pub trait Visit<'a> {
|
|||||||
fn visit_makearm<A: Annotation>(&mut self, item: &'a MakeArm<A>) -> Result<(), Self::Error> {
|
fn visit_makearm<A: Annotation>(&mut self, item: &'a MakeArm<A>) -> Result<(), Self::Error> {
|
||||||
item.children(self)
|
item.children(self)
|
||||||
}
|
}
|
||||||
fn visit_expr<A: Annotation>(&mut self, expr: &'a Expr<A>) -> Result<(), Self::Error> {
|
|
||||||
expr.children(self)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Walk<'a> {
|
pub trait Walk<'a> {
|
||||||
@@ -46,6 +46,26 @@ pub trait Walk<'a> {
|
|||||||
fn visit_in<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error>;
|
fn visit_in<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, A: Annotation> Walk<'a> for Expr<A> {
|
||||||
|
fn children<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
|
||||||
|
match self {
|
||||||
|
Self::Omitted => Ok(()),
|
||||||
|
Self::Id(path) => path.visit_in(v),
|
||||||
|
Self::MetId(id) => id.visit_in(v),
|
||||||
|
Self::Lit(literal) => literal.visit_in(v),
|
||||||
|
Self::Use(u) => u.visit_in(v),
|
||||||
|
Self::Bind(bind) => bind.visit_in(v),
|
||||||
|
Self::Make(make) => make.visit_in(v),
|
||||||
|
Self::Op(_op, annos) => annos.visit_in(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn visit_in<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
|
||||||
|
v.visit_expr(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> Walk<'a> for str {
|
impl<'a> Walk<'a> for str {
|
||||||
fn visit_in<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
|
fn visit_in<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
|
||||||
v.visit_ident(self)
|
v.visit_ident(self)
|
||||||
@@ -145,26 +165,6 @@ impl<'a, A: Annotation> Walk<'a> for MakeArm<A> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, A: Annotation> Walk<'a> for Expr<A> {
|
|
||||||
fn children<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
|
|
||||||
match self {
|
|
||||||
Self::Omitted => Ok(()),
|
|
||||||
Self::Id(path) => path.visit_in(v),
|
|
||||||
Self::MetId(id) => id.visit_in(v),
|
|
||||||
Self::Lit(literal) => literal.visit_in(v),
|
|
||||||
Self::Use(u) => u.visit_in(v),
|
|
||||||
Self::Bind(bind) => bind.visit_in(v),
|
|
||||||
Self::Make(make) => make.visit_in(v),
|
|
||||||
Self::Op(_op, annos) => annos.visit_in(v),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn visit_in<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
|
|
||||||
v.visit_expr(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T: Annotation + Walk<'a>, A: Annotation> Walk<'a> for Anno<T, A> {
|
impl<'a, T: Annotation + Walk<'a>, A: Annotation> Walk<'a> for Anno<T, A> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn children<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
|
fn children<V: Visit<'a> + ?Sized>(&'a self, v: &mut V) -> Result<(), V::Error> {
|
||||||
|
|||||||
@@ -507,7 +507,7 @@ fn parse_for(p: &mut Parser<'_>, _level: ()) -> PResult<Expr> {
|
|||||||
fn from_bind(p: &mut Parser<'_>) -> PResult<(BindOp, PPrec, Option<TKind>, Option<Prec>, Option<Prec>)> {
|
fn from_bind(p: &mut Parser<'_>) -> PResult<(BindOp, PPrec, Option<TKind>, Option<Prec>, Option<Prec>)> {
|
||||||
let bk = match p.peek()?.kind {
|
let bk = match p.peek()?.kind {
|
||||||
// Token Operator Pat prec Body Token Body prec Else prec
|
// Token Operator Pat prec Body Token Body prec Else prec
|
||||||
TKind::Let => (BindOp::Let, PPrec::Tuple, Some(TKind::Eq), Some(Prec::Compare), Some(Prec::Body)),
|
TKind::Let => (BindOp::Let, PPrec::Tuple, Some(TKind::Eq), Some(Prec::Tuple), Some(Prec::Body)),
|
||||||
TKind::Const => (BindOp::Const, PPrec::Tuple, Some(TKind::Eq), Some(Prec::Assign), None),
|
TKind::Const => (BindOp::Const, PPrec::Tuple, Some(TKind::Eq), Some(Prec::Assign), None),
|
||||||
TKind::Static => (BindOp::Static, PPrec::Tuple, Some(TKind::Eq), Some(Prec::Assign), None),
|
TKind::Static => (BindOp::Static, PPrec::Tuple, Some(TKind::Eq), Some(Prec::Assign), None),
|
||||||
TKind::Type => (BindOp::Type, PPrec::Tuple, Some(TKind::Eq), Some(Prec::Project), None),
|
TKind::Type => (BindOp::Type, PPrec::Tuple, Some(TKind::Eq), Some(Prec::Project), None),
|
||||||
|
|||||||
Reference in New Issue
Block a user