Implement record pattern for parameters

This commit is contained in:
Shunsuke Shibayama 2022-11-09 18:14:02 +09:00
parent 388010cc27
commit a4c6009ec6
9 changed files with 170 additions and 66 deletions

View file

@ -29,6 +29,8 @@ use erg_parser::ast::DefKind;
use CommonOpcode::*; use CommonOpcode::*;
use erg_parser::ast::{NonDefaultParamSignature, ParamPattern, VarName}; use erg_parser::ast::{NonDefaultParamSignature, ParamPattern, VarName};
use erg_parser::token::DOT;
use erg_parser::token::EQUAL;
use erg_parser::token::{Token, TokenKind}; use erg_parser::token::{Token, TokenKind};
use crate::compile::{AccessKind, Name, StoreLoadKind}; use crate::compile::{AccessKind, Name, StoreLoadKind};
@ -53,7 +55,6 @@ fn fake_method_to_func(class: &str, name: &str) -> Option<&'static str> {
} }
} }
/// This method obviously does not scale, so in the future all Python APIs will be replaced by declarations in d.er, and renaming will be done in `HIRDesugarer`.
fn escape_name(ident: Identifier) -> Str { fn escape_name(ident: Identifier) -> Str {
let vis = ident.vis(); let vis = ident.vis();
if let Some(py_name) = ident.vi.py_name { if let Some(py_name) = ident.vi.py_name {
@ -801,7 +802,13 @@ impl PyCodeGenerator {
} }
self.emit_load_name_instr(ident); self.emit_load_name_instr(ident);
} }
Accessor::Attr(a) => { Accessor::Attr(mut a) => {
// Python's namedtuple, a representation of Record, does not allow attribute names such as `::x`.
// Since Erg does not allow the coexistence of private and public variables with the same name, there is no problem in this trick.
let is_record = a.obj.ref_t().is_record();
if is_record {
a.ident.dot = Some(DOT);
}
self.emit_expr(*a.obj); self.emit_expr(*a.obj);
self.emit_load_attr_instr(a.ident); self.emit_load_attr_instr(a.ident);
} }
@ -2245,10 +2252,7 @@ impl PyCodeGenerator {
log!(info "entered {}", fn_name!()); log!(info "entered {}", fn_name!());
let line = block.ln_begin().unwrap_or(0); let line = block.ln_begin().unwrap_or(0);
for param in params { for param in params {
self.emit_store_instr( self.emit_store_instr(Identifier::public_with_line(DOT, param, line), Name);
Identifier::public_with_line(Token::dummy(), param, line),
Name,
);
} }
let init_stack_len = self.stack_len(); let init_stack_len = self.stack_len();
for expr in block.into_iter() { for expr in block.into_iter() {
@ -2264,10 +2268,7 @@ impl PyCodeGenerator {
log!(info "entered {}", fn_name!()); log!(info "entered {}", fn_name!());
let line = block.ln_begin().unwrap_or(0); let line = block.ln_begin().unwrap_or(0);
for param in params { for param in params {
self.emit_store_instr( self.emit_store_instr(Identifier::public_with_line(DOT, param, line), Name);
Identifier::public_with_line(Token::dummy(), param, line),
Name,
);
} }
let init_stack_len = self.stack_len(); let init_stack_len = self.stack_len();
for expr in block.into_iter() { for expr in block.into_iter() {
@ -2349,7 +2350,7 @@ impl PyCodeGenerator {
log!(info "entered {}", fn_name!()); log!(info "entered {}", fn_name!());
let line = sig.ln_begin().unwrap(); let line = sig.ln_begin().unwrap();
let class_name = sig.ident().inspect(); let class_name = sig.ident().inspect();
let ident = Identifier::public_with_line(Token::dummy(), Str::ever("__init__"), line); let ident = Identifier::public_with_line(DOT, Str::ever("__init__"), line);
let param_name = fresh_varname(); let param_name = fresh_varname();
let param = VarName::from_str_and_line(Str::from(param_name.clone()), line); let param = VarName::from_str_and_line(Str::from(param_name.clone()), line);
let param = NonDefaultParamSignature::new(ParamPattern::VarName(param), None); let param = NonDefaultParamSignature::new(ParamPattern::VarName(param), None);
@ -2369,14 +2370,14 @@ impl PyCodeGenerator {
let obj = let obj =
Expr::Accessor(Accessor::private_with_line(Str::from(&param_name), line)); Expr::Accessor(Accessor::private_with_line(Str::from(&param_name), line));
let expr = obj.attr_expr(Identifier::bare( let expr = obj.attr_expr(Identifier::bare(
Some(Token::dummy()), Some(DOT),
VarName::from_str(field.symbol.clone()), VarName::from_str(field.symbol.clone()),
)); ));
let obj = Expr::Accessor(Accessor::private_with_line(Str::ever("self"), line)); let obj = Expr::Accessor(Accessor::private_with_line(Str::ever("self"), line));
let dot = if field.vis.is_private() { let dot = if field.vis.is_private() {
None None
} else { } else {
Some(Token::dummy()) Some(DOT)
}; };
let attr = obj.attr(Identifier::bare( let attr = obj.attr(Identifier::bare(
dot, dot,
@ -2391,7 +2392,7 @@ impl PyCodeGenerator {
other => todo!("{other}"), other => todo!("{other}"),
} }
let block = Block::new(attrs); let block = Block::new(attrs);
let body = DefBody::new(Token::dummy(), block, DefId(0)); let body = DefBody::new(EQUAL, block, DefId(0));
self.emit_subr_def(Some(class_name), subr_sig, body); self.emit_subr_def(Some(class_name), subr_sig, body);
} }
@ -2404,7 +2405,7 @@ impl PyCodeGenerator {
log!(info "entered {}", fn_name!()); log!(info "entered {}", fn_name!());
let class_ident = sig.ident(); let class_ident = sig.ident();
let line = sig.ln_begin().unwrap(); let line = sig.ln_begin().unwrap();
let ident = Identifier::public_with_line(Token::dummy(), Str::ever("new"), line); let ident = Identifier::public_with_line(DOT, Str::ever("new"), line);
let param_name = fresh_varname(); let param_name = fresh_varname();
let param = VarName::from_str_and_line(Str::from(param_name.clone()), line); let param = VarName::from_str_and_line(Str::from(param_name.clone()), line);
let param = NonDefaultParamSignature::new(ParamPattern::VarName(param), None); let param = NonDefaultParamSignature::new(ParamPattern::VarName(param), None);
@ -2420,7 +2421,7 @@ impl PyCodeGenerator {
let class_new = class.attr_expr(new_ident); let class_new = class.attr_expr(new_ident);
let call = class_new.call_expr(Args::new(vec![arg], None, vec![], None)); let call = class_new.call_expr(Args::new(vec![arg], None, vec![], None));
let block = Block::new(vec![call]); let block = Block::new(vec![call]);
let body = DefBody::new(Token::dummy(), block, DefId(0)); let body = DefBody::new(EQUAL, block, DefId(0));
self.emit_subr_def(Some(class_ident.inspect()), sig, body); self.emit_subr_def(Some(class_ident.inspect()), sig, body);
} }

View file

@ -47,6 +47,26 @@ impl Context {
fv.generalize(); fv.generalize();
TyParam::FreeVar(fv) TyParam::FreeVar(fv)
} }
TyParam::Array(tps) => TyParam::Array(
tps.into_iter()
.map(|tp| self.generalize_tp(tp, variance))
.collect(),
),
TyParam::Tuple(tps) => TyParam::Tuple(
tps.into_iter()
.map(|tp| self.generalize_tp(tp, variance))
.collect(),
),
TyParam::Dict(tps) => TyParam::Dict(
tps.into_iter()
.map(|(k, v)| {
(
self.generalize_tp(k, variance),
self.generalize_tp(v, variance),
)
})
.collect(),
),
TyParam::FreeVar(_) => free, TyParam::FreeVar(_) => free,
other if other.has_no_unbound_var() => other, other if other.has_no_unbound_var() => other,
other => todo!("{other}"), other => todo!("{other}"),
@ -980,6 +1000,27 @@ impl Context {
self.sub_unify(l, &r, loc, None)?; self.sub_unify(l, &r, loc, None)?;
Ok(()) Ok(())
} }
(TyParam::Array(ls), TyParam::Array(rs)) | (TyParam::Tuple(ls), TyParam::Tuple(rs)) => {
for (l, r) in ls.iter().zip(rs.iter()) {
self.sub_unify_tp(l, r, _variance, loc, allow_divergence)?;
}
Ok(())
}
(TyParam::Dict(ls), TyParam::Dict(rs)) => {
for (lk, lv) in ls.iter() {
if let Some(rv) = rs.get(lk) {
self.sub_unify_tp(lv, rv, _variance, loc, allow_divergence)?;
} else {
// TODO:
return Err(TyCheckErrors::from(TyCheckError::unreachable(
self.cfg.input.clone(),
fn_name!(),
line!(),
)));
}
}
Ok(())
}
(l, r) => panic!("type-parameter unification failed:\nl:{l}\nr: {r}"), (l, r) => panic!("type-parameter unification failed:\nl:{l}\nr: {r}"),
} }
} }

View file

@ -17,7 +17,7 @@ use erg_common::{
use erg_parser::ast::{ use erg_parser::ast::{
fmt_lines, DefId, DefKind, NonDefaultParamSignature, OperationKind, TypeSpec, VarName, fmt_lines, DefId, DefKind, NonDefaultParamSignature, OperationKind, TypeSpec, VarName,
}; };
use erg_parser::token::{Token, TokenKind}; use erg_parser::token::{Token, TokenKind, DOT};
use crate::ty::constructors::{array_t, dict_t, set_t, tuple_t}; use crate::ty::constructors::{array_t, dict_t, set_t, tuple_t};
use crate::ty::typaram::TyParam; use crate::ty::typaram::TyParam;
@ -506,7 +506,7 @@ impl Accessor {
} }
pub fn public_with_line(name: Str, line: usize) -> Self { pub fn public_with_line(name: Str, line: usize) -> Self {
Self::Ident(Identifier::public_with_line(Token::dummy(), name, line)) Self::Ident(Identifier::public_with_line(DOT, name, line))
} }
pub const fn private(name: Token, vi: VarInfo) -> Self { pub const fn private(name: Token, vi: VarInfo) -> Self {
@ -514,12 +514,7 @@ impl Accessor {
} }
pub fn public(name: Token, vi: VarInfo) -> Self { pub fn public(name: Token, vi: VarInfo) -> Self {
Self::Ident(Identifier::new( Self::Ident(Identifier::new(Some(DOT), VarName::new(name), None, vi))
Some(Token::dummy()),
VarName::new(name),
None,
vi,
))
} }
pub fn attr(obj: Expr, ident: Identifier) -> Self { pub fn attr(obj: Expr, ident: Identifier) -> Self {

View file

@ -8,7 +8,7 @@ use erg_common::Str;
use erg_common::{enum_unwrap, log}; use erg_common::{enum_unwrap, log};
use erg_parser::ast::{DefId, OperationKind}; use erg_parser::ast::{DefId, OperationKind};
use erg_parser::token::{Token, TokenKind}; use erg_parser::token::{Token, TokenKind, DOT, EQUAL};
use crate::ty::free::fresh_varname; use crate::ty::free::fresh_varname;
use crate::ty::typaram::TyParam; use crate::ty::typaram::TyParam;
@ -314,7 +314,7 @@ impl<'a> Linker<'a> {
Identifier::private_with_line(Str::from(fresh_varname()), expr.ln_begin().unwrap()); Identifier::private_with_line(Str::from(fresh_varname()), expr.ln_begin().unwrap());
let mod_def = Expr::Def(Def::new( let mod_def = Expr::Def(Def::new(
Signature::Var(VarSignature::new(tmp.clone())), Signature::Var(VarSignature::new(tmp.clone())),
DefBody::new(Token::dummy(), block, DefId(0)), DefBody::new(EQUAL, block, DefId(0)),
)); ));
let module = Expr::Accessor(Accessor::Ident(tmp)); let module = Expr::Accessor(Accessor::Ident(tmp));
let __dict__ = Identifier::public("__dict__"); let __dict__ = Identifier::public("__dict__");
@ -382,7 +382,7 @@ impl<'a> Linker<'a> {
for attr in comps { for attr in comps {
*expr = mem::replace(expr, Expr::Code(Block::empty())).attr_expr( *expr = mem::replace(expr, Expr::Code(Block::empty())).attr_expr(
Identifier::public_with_line( Identifier::public_with_line(
Token::dummy(), DOT,
Str::rc(attr.as_os_str().to_str().unwrap()), Str::rc(attr.as_os_str().to_str().unwrap()),
line, line,
), ),

View file

@ -1600,6 +1600,15 @@ impl Type {
} }
} }
pub fn is_record(&self) -> bool {
match self {
Self::FreeVar(fv) if fv.is_linked() => fv.crack().is_record(),
Self::Record(_) => true,
Self::Refinement(refine) => refine.t.is_record(),
_ => false,
}
}
pub fn is_module(&self) -> bool { pub fn is_module(&self) -> bool {
match self { match self {
Self::FreeVar(fv) if fv.is_linked() => fv.crack().is_module(), Self::FreeVar(fv) if fv.is_linked() => fv.crack().is_module(),

View file

@ -14,12 +14,12 @@ use crate::ast::{
Accessor, Args, Array, ArrayComprehension, ArrayTypeSpec, ArrayWithLength, BinOp, Block, Call, Accessor, Args, Array, ArrayComprehension, ArrayTypeSpec, ArrayWithLength, BinOp, Block, Call,
ClassAttr, ClassAttrs, ConstExpr, DataPack, Def, DefBody, DefId, Dict, Expr, Identifier, ClassAttr, ClassAttrs, ConstExpr, DataPack, Def, DefBody, DefId, Dict, Expr, Identifier,
KeyValue, KwArg, Lambda, LambdaSignature, Literal, Methods, Module, NonDefaultParamSignature, KeyValue, KwArg, Lambda, LambdaSignature, Literal, Methods, Module, NonDefaultParamSignature,
NormalArray, NormalDict, NormalRecord, NormalSet, NormalTuple, ParamPattern, Params, PosArg, NormalArray, NormalDict, NormalRecord, NormalSet, NormalTuple, ParamPattern, ParamRecordAttr,
Record, RecordAttrs, Set as astSet, SetWithLength, ShortenedRecord, Signature, SubrSignature, Params, PosArg, Record, RecordAttrs, Set as astSet, SetWithLength, ShortenedRecord, Signature,
Tuple, TypeBoundSpecs, TypeSpec, TypeSpecWithOp, UnaryOp, VarName, VarPattern, VarRecordAttr, SubrSignature, Tuple, TypeBoundSpecs, TypeSpec, TypeSpecWithOp, UnaryOp, VarName, VarPattern,
VarSignature, VarRecordAttr, VarSignature,
}; };
use crate::token::{Token, TokenKind}; use crate::token::{Token, TokenKind, COLON, DOT};
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
enum BufIndex<'i> { enum BufIndex<'i> {
@ -635,10 +635,7 @@ impl Desugarer {
l.ln_begin().unwrap(), l.ln_begin().unwrap(),
l.col_begin().unwrap(), l.col_begin().unwrap(),
)); ));
param.t_spec = Some(TypeSpecWithOp::new( param.t_spec = Some(TypeSpecWithOp::new(COLON, TypeSpec::enum_t_spec(vec![lit])));
Token::dummy(),
TypeSpec::enum_t_spec(vec![lit]),
));
} }
ParamPattern::Tuple(tup) => { ParamPattern::Tuple(tup) => {
let (buf_name, buf_param) = self.gen_buf_nd_param(line); let (buf_name, buf_param) = self.gen_buf_nd_param(line);
@ -661,7 +658,7 @@ impl Desugarer {
); );
} }
if param.t_spec.is_none() { if param.t_spec.is_none() {
param.t_spec = Some(TypeSpecWithOp::new(Token::dummy(), TypeSpec::Tuple(tys))); param.t_spec = Some(TypeSpecWithOp::new(COLON, TypeSpec::Tuple(tys)));
} }
param.pat = buf_param; param.pat = buf_param;
} }
@ -681,27 +678,42 @@ impl Desugarer {
let len = Literal::new(Token::new(TokenKind::NatLit, len.to_string(), line, 0)); let len = Literal::new(Token::new(TokenKind::NatLit, len.to_string(), line, 0));
let infer = Token::new(TokenKind::Try, "?", line, 0); let infer = Token::new(TokenKind::Try, "?", line, 0);
let t_spec = ArrayTypeSpec::new(TypeSpec::Infer(infer), ConstExpr::Lit(len)); let t_spec = ArrayTypeSpec::new(TypeSpec::Infer(infer), ConstExpr::Lit(len));
param.t_spec = param.t_spec = Some(TypeSpecWithOp::new(
Some(TypeSpecWithOp::new(Token::dummy(), TypeSpec::Array(t_spec))); Token::dummy(TokenKind::Colon, ":"),
TypeSpec::Array(t_spec),
));
} }
param.pat = buf_param; param.pat = buf_param;
} }
/* ParamPattern::Record(rec) => {
VarPattern::Record(rec) => { let (buf_name, buf_param) = self.gen_buf_nd_param(line);
let (buf_name, buf_sig) = for ParamRecordAttr { lhs, rhs } in rec.elems.iter_mut() {
self.gen_buf_name_and_sig(v.ln_begin().unwrap(), v.t_spec); insertion_idx = self.desugar_nested_param_pattern(
let buf_def = Def::new(buf_sig, body); body,
new.push(Expr::Def(buf_def));
for VarRecordAttr { lhs, rhs } in rec.attrs.iter() {
self.desugar_nested_var_pattern(
&mut new,
rhs, rhs,
&buf_name, &buf_name,
BufIndex::Record(lhs), BufIndex::Record(lhs),
insertion_idx,
); );
} }
if param.t_spec.is_none() {
let mut tys = vec![];
for ParamRecordAttr { lhs, rhs } in rec.elems.iter() {
let infer = Token::new(TokenKind::Try, "?", line, 0);
tys.push((
lhs.clone(),
rhs.t_spec
.as_ref()
.map(|ts| ts.t_spec.clone())
.unwrap_or(TypeSpec::Infer(infer))
.clone(),
));
}
param.t_spec = Some(TypeSpecWithOp::new(COLON, TypeSpec::Record(tys)));
}
param.pat = buf_param;
} }
VarPattern::DataPack(pack) => { /*ParamPattern::DataPack(pack) => {
let (buf_name, buf_sig) = self.gen_buf_name_and_sig( let (buf_name, buf_sig) = self.gen_buf_name_and_sig(
v.ln_begin().unwrap(), v.ln_begin().unwrap(),
Some(pack.class.clone()), // TODO: これだとvの型指定の意味がなくなる Some(pack.class.clone()), // TODO: これだとvの型指定の意味がなくなる
@ -781,7 +793,7 @@ impl Desugarer {
); );
} }
if sig.t_spec.is_none() { if sig.t_spec.is_none() {
sig.t_spec = Some(TypeSpecWithOp::new(Token::dummy(), TypeSpec::Tuple(tys))); sig.t_spec = Some(TypeSpecWithOp::new(COLON, TypeSpec::Tuple(tys)));
} }
sig.pat = buf_sig; sig.pat = buf_sig;
insertion_idx insertion_idx
@ -813,24 +825,50 @@ impl Desugarer {
let len = Literal::new(Token::new(TokenKind::NatLit, len.to_string(), line, 0)); let len = Literal::new(Token::new(TokenKind::NatLit, len.to_string(), line, 0));
let infer = Token::new(TokenKind::Try, "?", line, 0); let infer = Token::new(TokenKind::Try, "?", line, 0);
let t_spec = ArrayTypeSpec::new(TypeSpec::Infer(infer), ConstExpr::Lit(len)); let t_spec = ArrayTypeSpec::new(TypeSpec::Infer(infer), ConstExpr::Lit(len));
sig.t_spec = Some(TypeSpecWithOp::new(Token::dummy(), TypeSpec::Array(t_spec))); sig.t_spec = Some(TypeSpecWithOp::new(COLON, TypeSpec::Array(t_spec)));
} }
sig.pat = buf_sig; sig.pat = buf_sig;
insertion_idx insertion_idx
} }
/*VarPattern::Record(rec) => { ParamPattern::Record(rec) => {
let (buf_name, buf_sig) = self.gen_buf_name_and_sig(sig.ln_begin().unwrap(), None); let (buf_name, buf_sig) = self.gen_buf_nd_param(line);
let buf_def = Def::new(buf_sig, body); new_sub_body.block.insert(
new_module.push(Expr::Def(buf_def)); insertion_idx,
for VarRecordAttr { lhs, rhs } in rec.attrs.iter() { Expr::Def(Def::new(
self.desugar_nested_var_pattern( Signature::Var(VarSignature::new(
new_module, VarPattern::Ident(Identifier::private(Str::from(&buf_name))),
sig.t_spec.as_ref().map(|ts| ts.t_spec.clone()),
)),
body,
)),
);
insertion_idx += 1;
let mut tys = vec![];
for ParamRecordAttr { lhs, rhs } in rec.elems.iter_mut() {
insertion_idx = self.desugar_nested_param_pattern(
new_sub_body,
rhs, rhs,
&buf_name, &buf_name,
BufIndex::Record(lhs), BufIndex::Record(lhs),
insertion_idx,
); );
let infer = Token::new(TokenKind::Try, "?", line, 0);
tys.push((
lhs.clone(),
rhs.t_spec
.as_ref()
.map(|ts| ts.t_spec.clone())
.unwrap_or(TypeSpec::Infer(infer))
.clone(),
));
} }
if sig.t_spec.is_none() {
sig.t_spec = Some(TypeSpecWithOp::new(COLON, TypeSpec::Record(tys)));
}
sig.pat = buf_sig;
insertion_idx
} }
/*
VarPattern::DataPack(pack) => { VarPattern::DataPack(pack) => {
let (buf_name, buf_sig) = let (buf_name, buf_sig) =
self.gen_buf_name_and_sig(sig.ln_begin().unwrap(), Some(pack.class.clone())); self.gen_buf_name_and_sig(sig.ln_begin().unwrap(), Some(pack.class.clone()));
@ -844,7 +882,8 @@ impl Desugarer {
BufIndex::Record(lhs), BufIndex::Record(lhs),
); );
} }
}*/ }
*/
ParamPattern::VarName(name) => { ParamPattern::VarName(name) => {
let v = VarSignature::new( let v = VarSignature::new(
VarPattern::Ident(Identifier::new(None, name.clone())), VarPattern::Ident(Identifier::new(None, name.clone())),
@ -894,7 +933,7 @@ impl Desugarer {
let call = Call::new( let call = Call::new(
Self::rec_desugar_acc(*subscr.obj), Self::rec_desugar_acc(*subscr.obj),
Some(Identifier::public_with_line( Some(Identifier::public_with_line(
Token::dummy(), DOT,
Str::ever("__getitem__"), Str::ever("__getitem__"),
line, line,
)), )),
@ -909,7 +948,7 @@ impl Desugarer {
let call = Call::new( let call = Call::new(
Self::rec_desugar_acc(*tattr.obj), Self::rec_desugar_acc(*tattr.obj),
Some(Identifier::public_with_line( Some(Identifier::public_with_line(
Token::dummy(), DOT,
Str::ever("__Tuple_getitem__"), Str::ever("__Tuple_getitem__"),
line, line,
)), )),

View file

@ -311,6 +311,10 @@ pub struct Token {
pub col_begin: usize, pub col_begin: usize,
} }
pub const COLON: Token = Token::dummy(TokenKind::Colon, ":");
pub const DOT: Token = Token::dummy(TokenKind::Dot, ".");
pub const EQUAL: Token = Token::dummy(TokenKind::Equal, "=");
impl fmt::Debug for Token { impl fmt::Debug for Token {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Token") f.debug_struct("Token")
@ -364,11 +368,17 @@ impl Locational for Token {
} }
impl Token { impl Token {
#[inline] pub const DUMMY: Token = Token {
pub fn dummy() -> Self { kind: TokenKind::Illegal,
Token { content: Str::ever("DUMMY"),
kind: TokenKind::Illegal, lineno: 1,
content: "DUMMY".into(), col_begin: 0,
};
pub const fn dummy(kind: TokenKind, content: &'static str) -> Self {
Self {
kind,
content: Str::ever(content),
lineno: 1, lineno: 1,
col_begin: 0, col_begin: 0,
} }

4
tests/pattern.er Normal file
View file

@ -0,0 +1,4 @@
f {x; y}, [_], (_, _) = x + y
x = f {x = 1; y = 2}, [3], (4, 5)
assert x == 3

View file

@ -74,6 +74,11 @@ fn exec_move_check() -> Result<(), ()> {
expect_failure("examples/move_check.er", 1) expect_failure("examples/move_check.er", 1)
} }
#[test]
fn exec_pattern() -> Result<(), ()> {
expect_success("tests/pattern.er")
}
#[test] #[test]
fn exec_pyimport() -> Result<(), ()> { fn exec_pyimport() -> Result<(), ()> {
if cfg!(unix) { if cfg!(unix) {