feat(parser): add type_spec_to_expr

This commit is contained in:
Shunsuke Shibayama 2023-02-06 12:04:15 +09:00
parent a2a55b0645
commit d8f4d14abd
10 changed files with 148 additions and 49 deletions

View file

@ -139,7 +139,7 @@ impl ASTSemanticState {
fn gen_from_expr(&mut self, expr: Expr) -> Vec<SemanticToken> {
match expr {
Expr::Lit(lit) => {
Expr::Literal(lit) => {
let typ = match lit.token.kind {
TokenKind::StrLit => SemanticTokenType::STRING,
TokenKind::NatLit | TokenKind::IntLit | TokenKind::RatioLit => {

View file

@ -810,6 +810,19 @@ macro_rules! impl_nested_display_for_chunk_enum {
}
}
#[macro_export]
macro_rules! impl_from_trait_for_enum {
($Enum: ident; $($Variant: ident $(,)?)*) => {
$(
impl From<$Variant> for $Enum {
fn from(v: $Variant) -> Self {
$Enum::$Variant(v)
}
}
)*
}
}
#[macro_export]
macro_rules! impl_nested_display_for_enum {
($Enum: ident; $($Variant: ident $(,)?)*) => {

View file

@ -523,7 +523,7 @@ impl Context {
pub(crate) fn eval_const_expr(&self, expr: &Expr) -> EvalResult<ValueObj> {
match expr {
Expr::Lit(lit) => self.eval_lit(lit),
Expr::Literal(lit) => self.eval_lit(lit),
Expr::Accessor(acc) => self.eval_const_acc(acc),
Expr::BinOp(bin) => self.eval_const_bin(bin),
Expr::UnaryOp(unary) => self.eval_const_unary(unary),
@ -535,7 +535,7 @@ impl Context {
Expr::Record(rec) => self.eval_const_record(rec),
Expr::Lambda(lambda) => self.eval_const_lambda(lambda),
// FIXME: type check
Expr::TypeAsc(tasc) => self.eval_const_expr(&tasc.expr),
Expr::TypeAscription(tasc) => self.eval_const_expr(&tasc.expr),
other => Err(EvalErrors::from(EvalError::not_const_expr(
self.cfg.input.clone(),
line!() as usize,
@ -553,7 +553,7 @@ impl Context {
match expr {
// TODO: ClassDef, PatchDef
Expr::Def(def) => self.eval_const_def(def),
Expr::Lit(lit) => self.eval_lit(lit),
Expr::Literal(lit) => self.eval_lit(lit),
Expr::Accessor(acc) => self.eval_const_acc(acc),
Expr::BinOp(bin) => self.eval_const_bin(bin),
Expr::UnaryOp(unary) => self.eval_const_unary(unary),
@ -564,7 +564,7 @@ impl Context {
Expr::Tuple(tuple) => self.eval_const_tuple(tuple),
Expr::Record(rec) => self.eval_const_record(rec),
Expr::Lambda(lambda) => self.eval_const_lambda(lambda),
Expr::TypeAsc(tasc) => self.eval_const_expr(&tasc.expr),
Expr::TypeAscription(tasc) => self.eval_const_expr(&tasc.expr),
other => Err(EvalErrors::from(EvalError::not_const_expr(
self.cfg.input.clone(),
line!() as usize,

View file

@ -302,11 +302,11 @@ impl ASTLowerer {
fn declare_chunk(&mut self, expr: ast::Expr) -> LowerResult<hir::Expr> {
log!(info "entered {}", fn_name!());
match expr {
ast::Expr::Lit(lit) if lit.is_doc_comment() => {
ast::Expr::Literal(lit) if lit.is_doc_comment() => {
Ok(hir::Expr::Lit(self.lower_literal(lit)?))
}
ast::Expr::Def(def) => Ok(hir::Expr::Def(self.declare_def(def)?)),
ast::Expr::TypeAsc(tasc) => Ok(hir::Expr::TypeAsc(self.declare_ident(tasc)?)),
ast::Expr::TypeAscription(tasc) => Ok(hir::Expr::TypeAsc(self.declare_ident(tasc)?)),
ast::Expr::Call(call)
if call
.additional_operation()

View file

@ -1404,7 +1404,7 @@ impl ASTLowerer {
ast::TypeSpec::TypeApp { spec, args } => {
let (impl_trait, loc) = match &args.args.pos_args().first().unwrap().expr {
// TODO: check `tasc.op`
ast::Expr::TypeAsc(tasc) => (
ast::Expr::TypeAscription(tasc) => (
self.module.context.instantiate_typespec(
&tasc.t_spec,
None,
@ -2143,7 +2143,7 @@ impl ASTLowerer {
fn lower_expr(&mut self, expr: ast::Expr) -> LowerResult<hir::Expr> {
log!(info "entered {}", fn_name!());
match expr {
ast::Expr::Lit(lit) => Ok(hir::Expr::Lit(self.lower_literal(lit)?)),
ast::Expr::Literal(lit) => Ok(hir::Expr::Lit(self.lower_literal(lit)?)),
ast::Expr::Array(arr) => Ok(hir::Expr::Array(self.lower_array(arr)?)),
ast::Expr::Tuple(tup) => Ok(hir::Expr::Tuple(self.lower_tuple(tup)?)),
ast::Expr::Record(rec) => Ok(hir::Expr::Record(self.lower_record(rec)?)),
@ -2155,7 +2155,7 @@ impl ASTLowerer {
ast::Expr::Call(call) => Ok(hir::Expr::Call(self.lower_call(call)?)),
ast::Expr::DataPack(pack) => Ok(hir::Expr::Call(self.lower_pack(pack)?)),
ast::Expr::Lambda(lambda) => Ok(hir::Expr::Lambda(self.lower_lambda(lambda)?)),
ast::Expr::TypeAsc(tasc) => Ok(hir::Expr::TypeAsc(self.lower_type_asc(tasc)?)),
ast::Expr::TypeAscription(tasc) => Ok(hir::Expr::TypeAsc(self.lower_type_asc(tasc)?)),
// Checking is also performed for expressions in Dummy. However, it has no meaning in code generation
ast::Expr::Dummy(dummy) => Ok(hir::Expr::Dummy(self.lower_dummy(dummy)?)),
other => {
@ -2175,7 +2175,7 @@ impl ASTLowerer {
ast::Expr::ClassDef(defs) => Ok(hir::Expr::ClassDef(self.lower_class_def(defs)?)),
ast::Expr::PatchDef(defs) => Ok(hir::Expr::PatchDef(self.lower_patch_def(defs)?)),
ast::Expr::ReDef(redef) => Ok(hir::Expr::ReDef(self.lower_redef(redef)?)),
ast::Expr::TypeAsc(tasc) => Ok(hir::Expr::TypeAsc(self.lower_decl(tasc)?)),
ast::Expr::TypeAscription(tasc) => Ok(hir::Expr::TypeAsc(self.lower_decl(tasc)?)),
other => self.lower_expr(other),
}
}

View file

@ -10,9 +10,9 @@ use erg_common::traits::{Locational, NestedDisplay, Stream};
use erg_common::vis::{Field, Visibility};
use erg_common::{
fmt_option, fmt_vec, impl_display_for_enum, impl_display_for_single_struct,
impl_display_from_nested, impl_displayable_stream_for_wrapper, impl_locational,
impl_locational_for_enum, impl_nested_display_for_chunk_enum, impl_nested_display_for_enum,
impl_stream, option_enum_unwrap,
impl_display_from_nested, impl_displayable_stream_for_wrapper, impl_from_trait_for_enum,
impl_locational, impl_locational_for_enum, impl_nested_display_for_chunk_enum,
impl_nested_display_for_enum, impl_stream, option_enum_unwrap,
};
use erg_common::{fmt_vec_split_with, Str};
@ -1629,7 +1629,7 @@ impl ConstExpr {
pub fn downcast(self) -> Expr {
match self {
Self::Lit(lit) => Expr::Lit(lit),
Self::Lit(lit) => Expr::Literal(lit),
Self::Accessor(acc) => Expr::Accessor(acc.downcast()),
Self::App(app) => Expr::Call(app.downcast()),
Self::Array(arr) => Expr::Array(arr.downcast()),
@ -2450,6 +2450,12 @@ impl From<&Identifier> for Field {
}
}
impl From<Identifier> for Expr {
fn from(ident: Identifier) -> Self {
Self::Accessor(Accessor::Ident(ident))
}
}
impl Identifier {
pub const fn new(dot: Option<Token>, name: VarName) -> Self {
Self { dot, name }
@ -3703,7 +3709,7 @@ impl PatchDef {
/// Expression(式)
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Expr {
Lit(Literal),
Literal(Literal),
Accessor(Accessor),
Array(Array),
Tuple(Tuple),
@ -3715,7 +3721,7 @@ pub enum Expr {
Call(Call),
DataPack(DataPack),
Lambda(Lambda),
TypeAsc(TypeAscription),
TypeAscription(TypeAscription),
Def(Def),
Methods(Methods),
ClassDef(ClassDef),
@ -3725,9 +3731,10 @@ pub enum Expr {
Dummy(Dummy),
}
impl_nested_display_for_chunk_enum!(Expr; Lit, Accessor, Array, Tuple, Dict, Set, Record, BinOp, UnaryOp, Call, DataPack, Lambda, TypeAsc, Def, Methods, ClassDef, PatchDef, ReDef, Dummy);
impl_nested_display_for_chunk_enum!(Expr; Literal, Accessor, Array, Tuple, Dict, Set, Record, BinOp, UnaryOp, Call, DataPack, Lambda, TypeAscription, Def, Methods, ClassDef, PatchDef, ReDef, Dummy);
impl_from_trait_for_enum!(Expr; Literal, Accessor, Array, Tuple, Dict, Set, Record, BinOp, UnaryOp, Call, DataPack, Lambda, TypeAscription, Def, Methods, ClassDef, PatchDef, ReDef, Dummy);
impl_display_from_nested!(Expr);
impl_locational_for_enum!(Expr; Lit, Accessor, Array, Tuple, Dict, Set, Record, BinOp, UnaryOp, Call, DataPack, Lambda, TypeAsc, Def, Methods, ClassDef, PatchDef, ReDef, Dummy);
impl_locational_for_enum!(Expr; Literal, Accessor, Array, Tuple, Dict, Set, Record, BinOp, UnaryOp, Call, DataPack, Lambda, TypeAscription, Def, Methods, ClassDef, PatchDef, ReDef, Dummy);
impl Expr {
pub fn is_match_call(&self) -> bool {
@ -3752,7 +3759,7 @@ impl Expr {
pub fn need_to_be_closed(&self) -> bool {
matches!(
self,
Expr::BinOp(_) | Expr::UnaryOp(_) | Expr::Lambda(_) | Expr::TypeAsc(_)
Expr::BinOp(_) | Expr::UnaryOp(_) | Expr::Lambda(_) | Expr::TypeAscription(_)
)
}
@ -3824,7 +3831,7 @@ impl Expr {
}
pub fn type_asc_expr(self, op: Token, t_spec: TypeSpec) -> Self {
Self::TypeAsc(self.type_asc(op, t_spec))
Self::TypeAscription(self.type_asc(op, t_spec))
}
}

View file

@ -59,7 +59,7 @@ impl Parser {
debug_exit_info!(self);
Ok(Signature::Var(var))
}
Expr::TypeAsc(tasc) => {
Expr::TypeAscription(tasc) => {
let sig = self
.convert_type_asc_to_sig(tasc)
.map_err(|_| self.stack_dec(fn_name!()))?;
@ -326,7 +326,7 @@ impl Parser {
fn convert_type_arg_to_bound(&mut self, arg: PosArg) -> ParseResult<TypeBoundSpec> {
match arg.expr {
Expr::TypeAsc(tasc) => {
Expr::TypeAscription(tasc) => {
let lhs = self
.convert_rhs_to_sig(*tasc.expr)
.map_err(|_| self.stack_dec(fn_name!()))?;
@ -406,7 +406,7 @@ impl Parser {
debug_exit_info!(self);
Ok(param)
}
Expr::Lit(lit) => {
Expr::Literal(lit) => {
let pat = ParamPattern::Lit(lit);
let param = NonDefaultParamSignature::new(pat, None);
debug_exit_info!(self);
@ -439,7 +439,7 @@ impl Parser {
debug_exit_info!(self);
Ok(param)
}
Expr::TypeAsc(tasc) => {
Expr::TypeAscription(tasc) => {
let param = self
.convert_type_asc_to_param_pattern(tasc, allow_self)
.map_err(|_| self.stack_dec(fn_name!()))?;
@ -607,7 +607,7 @@ impl Parser {
pub(crate) fn convert_rhs_to_lambda_sig(&mut self, rhs: Expr) -> ParseResult<LambdaSignature> {
debug_call_info!(self);
match rhs {
Expr::Lit(lit) => {
Expr::Literal(lit) => {
let param = NonDefaultParamSignature::new(ParamPattern::Lit(lit), None);
let params = Params::new(vec![param], None, vec![], None);
Ok(LambdaSignature::new(params, None, TypeBoundSpecs::empty()))
@ -645,7 +645,7 @@ impl Parser {
debug_exit_info!(self);
Ok(LambdaSignature::new(params, None, TypeBoundSpecs::empty()))
}
Expr::TypeAsc(tasc) => {
Expr::TypeAscription(tasc) => {
let sig = self
.convert_type_asc_to_lambda_sig(tasc)
.map_err(|_| self.stack_dec(fn_name!()))?;
@ -738,7 +738,7 @@ impl Parser {
) -> ParseResult<LambdaSignature> {
debug_call_info!(self);
let sig = self
.convert_rhs_to_param(Expr::TypeAsc(tasc), true)
.convert_rhs_to_param(Expr::TypeAscription(tasc), true)
.map_err(|_| self.stack_dec(fn_name!()))?;
debug_exit_info!(self);
Ok(LambdaSignature::new(

View file

@ -96,7 +96,7 @@ impl Desugarer {
fn perform_desugar(mut desugar: impl FnMut(Expr) -> Expr, expr: Expr) -> Expr {
match expr {
Expr::Lit(_) => expr,
Expr::Literal(_) => expr,
Expr::Record(record) => match record {
Record::Normal(rec) => {
let mut new_attrs = vec![];
@ -268,7 +268,7 @@ impl Desugarer {
let body = Block::new(chunks);
Expr::Lambda(Lambda::new(lambda.sig, lambda.op, body, lambda.id))
}
Expr::TypeAsc(tasc) => {
Expr::TypeAscription(tasc) => {
let expr = desugar(*tasc.expr);
expr.type_asc_expr(tasc.op, tasc.t_spec)
}
@ -605,7 +605,10 @@ impl Desugarer {
sig.ln_begin().unwrap(),
sig.col_begin().unwrap(),
);
obj.subscr(Expr::Lit(Literal::nat(n, sig.ln_begin().unwrap())), r_brace)
obj.subscr(
Expr::Literal(Literal::nat(n, sig.ln_begin().unwrap())),
r_brace,
)
}
BufIndex::Record(attr) => obj.attr(attr.clone()),
};
@ -887,7 +890,10 @@ impl Desugarer {
sig.ln_begin().unwrap(),
sig.col_begin().unwrap(),
);
obj.subscr(Expr::Lit(Literal::nat(n, sig.ln_begin().unwrap())), r_brace)
obj.subscr(
Expr::Literal(Literal::nat(n, sig.ln_begin().unwrap())),
r_brace,
)
}
BufIndex::Record(attr) => obj.attr(attr.clone()),
};
@ -1096,7 +1102,7 @@ impl Desugarer {
}
// x.0 => x.__Tuple_getitem__(0)
Accessor::TupleAttr(tattr) => {
let args = Args::pos_only(vec![PosArg::new(Expr::Lit(tattr.index))], None);
let args = Args::pos_only(vec![PosArg::new(Expr::Literal(tattr.index))], None);
let line = tattr.obj.ln_begin().unwrap();
let call = Call::new(
Self::rec_desugar_acc(*tattr.obj),

View file

@ -719,7 +719,7 @@ impl Parser {
Args::new(vec![], Some(pos_args), vec![], None)
}
PosOrKwArg::Pos(PosArg {
expr: Expr::TypeAsc(TypeAscription { expr, op, t_spec }),
expr: Expr::TypeAscription(TypeAscription { expr, op, t_spec }),
}) if matches!(expr.as_ref(), Expr::UnaryOp(unary) if unary.op.is(PreStar)) => {
let Expr::UnaryOp(unary) = *expr else { unreachable!() };
let var_args = PosArg::new(unary.deconstruct().1.type_asc_expr(op, t_spec));
@ -789,7 +789,7 @@ impl Parser {
args.set_var_args(PosArg::new(unary.deconstruct().1));
}
PosOrKwArg::Pos(PosArg {
expr: Expr::TypeAsc(TypeAscription { expr, op, t_spec }),
expr: Expr::TypeAscription(TypeAscription { expr, op, t_spec }),
}) if matches!(expr.as_ref(), Expr::UnaryOp(unary) if unary.op.is(PreStar)) =>
{
let Expr::UnaryOp(unary) = *expr else { unreachable!() };
@ -909,7 +909,7 @@ impl Parser {
self.skip();
let (kw, t_spec) = match expr {
Expr::Accessor(Accessor::Ident(n)) => (n.name.into_token(), None),
Expr::TypeAsc(tasc) => {
Expr::TypeAscription(tasc) => {
if let Expr::Accessor(Accessor::Ident(n)) = *tasc.expr {
let t_spec = TypeSpecWithOp::new(tasc.op, tasc.t_spec);
(n.name.into_token(), Some(t_spec))
@ -1010,8 +1010,8 @@ impl Parser {
.map_err(|_| self.stack_dec(fn_name!()))?;
let first = match first {
Expr::Def(def) => ClassAttr::Def(def),
Expr::TypeAsc(tasc) => ClassAttr::Decl(tasc),
Expr::Lit(lit) if lit.is_doc_comment() => ClassAttr::Doc(lit),
Expr::TypeAscription(tasc) => ClassAttr::Decl(tasc),
Expr::Literal(lit) if lit.is_doc_comment() => ClassAttr::Doc(lit),
_ => {
// self.restore();
let err = self.skip_and_throw_syntax_err(caused_by!());
@ -1040,7 +1040,7 @@ impl Parser {
Expr::Def(def) => {
attrs.push(ClassAttr::Def(def));
}
Expr::TypeAsc(tasc) => {
Expr::TypeAscription(tasc) => {
attrs.push(ClassAttr::Decl(tasc));
}
other => {
@ -1597,7 +1597,7 @@ impl Parser {
let first_elem = enum_unwrap!(stack.pop(), Some:(ExprOrOp::Expr:(_)));
let (keyword, t_spec) = match first_elem {
Expr::Accessor(Accessor::Ident(ident)) => (ident.name.into_token(), None),
Expr::TypeAsc(tasc) => {
Expr::TypeAscription(tasc) => {
if let Expr::Accessor(Accessor::Ident(ident)) = *tasc.expr {
(
ident.name.into_token(),
@ -1658,7 +1658,7 @@ impl Parser {
}
}
debug_exit_info!(self);
Ok(Expr::Lit(lit))
Ok(Expr::Literal(lit))
}
Some(t) if t.is(StrInterpLeft) => {
let str_interp = self
@ -2370,7 +2370,7 @@ impl Parser {
Args::new(vec![], var_args, vec![], None)
}
PosOrKwArg::Pos(PosArg {
expr: Expr::TypeAsc(TypeAscription { expr, op, t_spec }),
expr: Expr::TypeAscription(TypeAscription { expr, op, t_spec }),
}) if matches!(expr.as_ref(), Expr::UnaryOp(unary) if unary.op.is(PreStar)) => {
let Expr::UnaryOp(unary) = *expr else { unreachable!() };
let expr = unary.deconstruct().1;
@ -2405,7 +2405,7 @@ impl Parser {
Expr::UnaryOp(unary) if unary.op.is(PreStar) => {
args.set_var_args(PosArg::new(unary.deconstruct().1));
}
Expr::TypeAsc(TypeAscription { expr, op, t_spec }) if matches!(expr.as_ref(), Expr::UnaryOp(unary) if unary.op.is(PreStar)) =>
Expr::TypeAscription(TypeAscription { expr, op, t_spec }) if matches!(expr.as_ref(), Expr::UnaryOp(unary) if unary.op.is(PreStar)) =>
{
let Expr::UnaryOp(unary) = *expr else { unreachable!() };
let expr = unary.deconstruct().1;
@ -2473,7 +2473,7 @@ impl Parser {
let mut left = self.lpop();
left.content = Str::from(left.content.trim_end_matches("\\{").to_string() + "\"");
left.kind = StrLit;
let mut expr = Expr::Lit(Literal::from(left));
let mut expr = Expr::Literal(Literal::from(left));
loop {
match self.peek() {
Some(l) if l.is(StrInterpRight) => {
@ -2481,7 +2481,7 @@ impl Parser {
right.content =
Str::from(format!("\"{}", right.content.trim_start_matches('}')));
right.kind = StrLit;
let right = Expr::Lit(Literal::from(right));
let right = Expr::Literal(Literal::from(right));
let op = Token::new(
Plus,
"+",
@ -2519,7 +2519,7 @@ impl Parser {
mid.content.trim_start_matches('}').trim_end_matches("\\{")
));
mid.kind = StrLit;
let mid = Expr::Lit(Literal::from(mid));
let mid = Expr::Literal(Literal::from(mid));
let op = Token::new(
Plus,
"+",

View file

@ -3,14 +3,14 @@ use erg_common::traits::{Locational, Stream};
use crate::ast::*;
use crate::error::ParseError;
use crate::token::TokenKind;
use crate::token::{Token, TokenKind};
use crate::Parser;
// The APIs defined below are also used by `ASTLowerer` to interpret expressions as types.
impl Parser {
pub fn validate_const_expr(expr: Expr) -> Result<ConstExpr, ParseError> {
match expr {
Expr::Lit(l) => Ok(ConstExpr::Lit(l)),
Expr::Literal(l) => Ok(ConstExpr::Lit(l)),
Expr::Accessor(Accessor::Ident(local)) => {
Ok(ConstExpr::Accessor(ConstAccessor::Local(local)))
}
@ -399,7 +399,7 @@ impl Parser {
Err(err)
}
}
Expr::Lit(lit) => {
Expr::Literal(lit) => {
let mut err = ParseError::simple_syntax_error(line!() as usize, lit.loc());
if lit.is(TokenKind::NoneLit) {
err.set_hint("you mean: `NoneType`?");
@ -412,4 +412,77 @@ impl Parser {
}
}
}
fn simple_type_spec_to_ident(simple: SimpleTypeSpec) -> Result<Identifier, ParseError> {
Ok(simple.ident)
}
fn simple_type_spec_to_call(simple: SimpleTypeSpec) -> Result<Call, ParseError> {
let (pos_args_, var_args_, kw_args_, paren) = simple.args.deconstruct();
let pos_args = pos_args_
.into_iter()
.map(|arg| PosArg::new(arg.expr.downcast()))
.collect::<Vec<_>>();
let var_args = var_args_.map(|arg| PosArg::new(arg.expr.downcast()));
let kw_args = kw_args_
.into_iter()
.map(|arg| KwArg::new(arg.keyword, None, arg.expr.downcast()))
.collect::<Vec<_>>();
let args = Args::new(pos_args, var_args, kw_args, paren);
let call = Call::new(simple.ident.into(), None, args);
Ok(call)
}
fn predecl_type_spec_to_expr(predecl: PreDeclTypeSpec) -> Result<Expr, ParseError> {
match predecl {
PreDeclTypeSpec::Simple(simple) if simple.args.is_empty() => {
Ok(Self::simple_type_spec_to_ident(simple)?.into())
}
PreDeclTypeSpec::Simple(simple) => Ok(Self::simple_type_spec_to_call(simple)?.into()),
PreDeclTypeSpec::Attr { namespace, t } => {
let ident = Self::simple_type_spec_to_ident(t)?;
Ok(namespace.attr_expr(ident))
}
other => Err(ParseError::feature_error(
line!() as usize,
other.loc(),
"compound predecl type spec to call conversion",
)),
}
}
pub fn type_spec_to_expr(t_spec: TypeSpec) -> Result<Expr, ParseError> {
match t_spec {
TypeSpec::PreDeclTy(predecl) => Self::predecl_type_spec_to_expr(predecl),
TypeSpec::Or(lhs, rhs) => {
let lhs = Self::type_spec_to_expr(*lhs)?;
let rhs = Self::type_spec_to_expr(*rhs)?;
let op = Token::new(
TokenKind::OrOp,
"or",
lhs.ln_begin().unwrap(),
lhs.col_end().unwrap(),
);
let bin = BinOp::new(op, lhs, rhs);
Ok(Expr::BinOp(bin))
}
TypeSpec::And(lhs, rhs) => {
let lhs = Self::type_spec_to_expr(*lhs)?;
let rhs = Self::type_spec_to_expr(*rhs)?;
let op = Token::new(
TokenKind::AndOp,
"and",
lhs.ln_begin().unwrap(),
lhs.col_end().unwrap(),
);
let bin = BinOp::new(op, lhs, rhs);
Ok(Expr::BinOp(bin))
}
other => Err(ParseError::feature_error(
line!() as usize,
other.loc(),
"compound type spec to expr conversion",
)),
}
}
}