mirror of
https://github.com/tweag/nickel.git
synced 2024-09-11 11:47:03 +03:00
Change switch to match
This commit changes the keyword of the switch branching construct to a match, to make room for future more advanced features like pattern matching. Also, the match is now curried, meaning it doesn't have to be applied to an argument and `match {..cases..}` can be used directly as a function.
This commit is contained in:
parent
760a611fb8
commit
2518b2ce3e
@ -211,11 +211,11 @@ The following type constructors are available:
|
||||
|
||||
```nickel
|
||||
let protocol : [| `http, `ftp, `sftp |] = `http in
|
||||
(switch {
|
||||
(protocol |> match {
|
||||
`http => 1,
|
||||
`ftp => 2,
|
||||
`sftp => 3
|
||||
} protocol) : Num
|
||||
}) : Num
|
||||
```
|
||||
|
||||
- **Arrow (function)**: `S -> T`. A function taking arguments of type `S` and
|
||||
|
@ -79,7 +79,7 @@ rather than data. Take the following example:
|
||||
```nickel
|
||||
let record = {
|
||||
protocol | default = `Http,
|
||||
port | default = switch protocol {
|
||||
port | default = protocol |> match {
|
||||
`Http => 80,
|
||||
`Ftp => 21,
|
||||
_ => 8181,
|
||||
|
@ -216,8 +216,8 @@ look-ahead to distinguish).
|
||||
|
||||
With the first release in mind, we propose to just disable support for enum
|
||||
altogether. This is a handy but hardly fundamental feature, and this lets us
|
||||
more time to find a good syntax replacement. We can also disable `switch`
|
||||
temporarily, as currently its only usage is for enums.
|
||||
more time to find a good syntax replacement. We can also disable `switch` (edit
|
||||
from 05.12.22: now `match`) temporarily, as currently its only usage is for enums.
|
||||
|
||||
### Translation
|
||||
|
||||
|
@ -386,40 +386,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
|
||||
env,
|
||||
}
|
||||
}
|
||||
Term::Switch(exp, cases, default) => {
|
||||
self.set_mode(EvalMode::UnwrapMeta);
|
||||
|
||||
let has_default = default.is_some();
|
||||
|
||||
if let Some(t) = default {
|
||||
self.stack.push_arg(
|
||||
Closure {
|
||||
body: t.clone(),
|
||||
env: env.clone(),
|
||||
},
|
||||
pos,
|
||||
);
|
||||
}
|
||||
|
||||
self.stack.push_arg(
|
||||
Closure {
|
||||
body: RichTerm::new(
|
||||
Term::Record(RecordData::with_fields(cases.clone())),
|
||||
pos,
|
||||
),
|
||||
env: env.clone(),
|
||||
},
|
||||
pos,
|
||||
);
|
||||
|
||||
Closure {
|
||||
body: RichTerm::new(
|
||||
Term::Op1(UnaryOp::Switch(has_default), exp.clone()),
|
||||
pos,
|
||||
),
|
||||
env,
|
||||
}
|
||||
}
|
||||
Term::Op1(op, t) => {
|
||||
self.set_mode(op.eval_mode());
|
||||
|
||||
@ -686,6 +653,64 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
|
||||
return Ok((RichTerm::new(Term::Fun(*x, t.clone()), pos), env));
|
||||
}
|
||||
}
|
||||
// A match expression acts as a function (in Nickel, a match expression corresponds
|
||||
// to the cases, and doesn't include the examined value).
|
||||
//
|
||||
// The behavior is the same as for a function: we look for an argument on the
|
||||
// stack, and proceed to the evaluation of the match, or stop here otherwise. If
|
||||
// found (let's call it `arg`), we evaluate `%match% arg cases default`, where
|
||||
// `%match%` is the primitive operation `UnaryOp::Match` taking care of forcing the
|
||||
// argument `arg` and doing the actual matching operation.
|
||||
Term::Match { cases, default } => {
|
||||
if let Some((arg, pos_app)) = self.stack.pop_arg(&self.cache) {
|
||||
// Setting the stack to be as if we would have evaluated an application
|
||||
// `_ cases default`, where `default` is optional, and `_` is not relevant.
|
||||
|
||||
let has_default = default.is_some();
|
||||
|
||||
if let Some(t) = default {
|
||||
self.stack.push_arg(
|
||||
Closure {
|
||||
body: t.clone(),
|
||||
env: env.clone(),
|
||||
},
|
||||
pos,
|
||||
);
|
||||
}
|
||||
|
||||
self.stack.push_arg(
|
||||
Closure {
|
||||
body: RichTerm::new(
|
||||
Term::Record(RecordData::with_fields(cases.clone())),
|
||||
pos,
|
||||
),
|
||||
env: env.clone(),
|
||||
},
|
||||
pos,
|
||||
);
|
||||
|
||||
// Now evaluating `%match% arg`, the left-most part of the application `%match%
|
||||
// arg cases default`, which is in fact a primop application.
|
||||
self.stack.push_op_cont(
|
||||
OperationCont::Op1(UnaryOp::Match { has_default }, pos_app),
|
||||
self.call_stack.len(),
|
||||
pos,
|
||||
);
|
||||
|
||||
arg
|
||||
} else {
|
||||
return Ok((
|
||||
RichTerm::new(
|
||||
Term::Match {
|
||||
cases: cases.clone(),
|
||||
default: default.clone(),
|
||||
},
|
||||
pos,
|
||||
),
|
||||
env,
|
||||
));
|
||||
}
|
||||
}
|
||||
// Otherwise, this is either an ill-formed application, or we are done
|
||||
t => {
|
||||
if let Some((arg, pos_app)) = self.stack.pop_arg(&self.cache) {
|
||||
@ -850,7 +875,7 @@ pub fn subst<C: Cache>(
|
||||
|
||||
RichTerm::new(Term::App(t1, t2), pos)
|
||||
}
|
||||
Term::Switch(t, cases, default) => {
|
||||
Term::Match {cases, default} => {
|
||||
let default =
|
||||
default.map(|d| subst(cache, d, initial_env, env));
|
||||
let cases = cases
|
||||
@ -862,9 +887,8 @@ pub fn subst<C: Cache>(
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let t = subst(cache, t, initial_env, env);
|
||||
|
||||
RichTerm::new(Term::Switch(t, cases, default), pos)
|
||||
RichTerm::new(Term::Match {cases, default}, pos)
|
||||
}
|
||||
Term::Op1(op, t) => {
|
||||
let t = subst(cache, t, initial_env, env);
|
||||
|
@ -199,7 +199,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
|
||||
Term::Bool(_) => "Bool",
|
||||
Term::Str(_) => "Str",
|
||||
Term::Enum(_) => "Enum",
|
||||
Term::Fun(..) => "Fun",
|
||||
Term::Fun(..) | Term::Match { .. } => "Fun",
|
||||
Term::Array(..) => "Array",
|
||||
Term::Record(..) | Term::RecRecord(..) => "Record",
|
||||
Term::Lbl(..) => "Lbl",
|
||||
@ -304,17 +304,17 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
|
||||
))
|
||||
}
|
||||
}
|
||||
UnaryOp::Switch(has_default) => {
|
||||
UnaryOp::Match { has_default } => {
|
||||
let (cases_closure, ..) = self
|
||||
.stack
|
||||
.pop_arg(&self.cache)
|
||||
.expect("missing arg for switch");
|
||||
.expect("missing arg for match");
|
||||
let default = if has_default {
|
||||
Some(
|
||||
self.stack
|
||||
.pop_arg(&self.cache)
|
||||
.map(|(clos, ..)| clos)
|
||||
.expect("missing default case for switch"),
|
||||
.expect("missing default case for match"),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
@ -331,7 +331,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
|
||||
|
||||
let mut cases = match cases_term.into_owned() {
|
||||
Term::Record(r) => r.fields,
|
||||
_ => panic!("invalid argument for switch"),
|
||||
_ => panic!("invalid argument for match"),
|
||||
};
|
||||
|
||||
cases
|
||||
@ -345,7 +345,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
|
||||
// ? We should have a dedicated error for unmatched pattern
|
||||
EvalError::TypeError(
|
||||
String::from("Enum"),
|
||||
String::from("switch"),
|
||||
String::from("match"),
|
||||
arg_pos,
|
||||
RichTerm {
|
||||
term: t,
|
||||
@ -357,7 +357,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
|
||||
} else {
|
||||
Err(EvalError::TypeError(
|
||||
String::from("Enum"),
|
||||
String::from("switch"),
|
||||
String::from("match"),
|
||||
arg_pos,
|
||||
RichTerm { term: t, pos },
|
||||
))
|
||||
@ -1077,7 +1077,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
|
||||
))
|
||||
}
|
||||
}
|
||||
UnaryOp::StrMatch() => {
|
||||
UnaryOp::StrFind() => {
|
||||
if let Term::Str(s) = &*t {
|
||||
let re = regex::Regex::new(s)
|
||||
.map_err(|err| EvalError::Other(err.to_string(), pos_op))?;
|
||||
@ -1087,7 +1087,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
|
||||
param,
|
||||
RichTerm::new(
|
||||
Term::Op1(
|
||||
UnaryOp::StrMatchCompiled(re.into()),
|
||||
UnaryOp::StrFindCompiled(re.into()),
|
||||
RichTerm::new(Term::Var(param), pos_op_inh),
|
||||
),
|
||||
pos_op_inh,
|
||||
@ -1119,7 +1119,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
|
||||
))
|
||||
}
|
||||
}
|
||||
UnaryOp::StrMatchCompiled(regex) => {
|
||||
UnaryOp::StrFindCompiled(regex) => {
|
||||
if let Term::Str(s) = &*t {
|
||||
let capt = regex.captures(s);
|
||||
let result = if let Some(capt) = capt {
|
||||
@ -1551,7 +1551,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
|
||||
);
|
||||
|
||||
match *t1 {
|
||||
Term::Fun(..) => Ok(Closure {
|
||||
Term::Fun(..) | Term::Match { .. } => Ok(Closure {
|
||||
body: RichTerm {
|
||||
term: t1,
|
||||
pos: pos1,
|
||||
|
@ -364,10 +364,10 @@ fn substitution() {
|
||||
parse("let x = 1 in if true then 1 + (if false then 1 else \"Glob2\") else false").unwrap()
|
||||
);
|
||||
|
||||
let t = parse("switch {`x => [1, glob1], `y => loc2, `z => {id = true, other = glob3}} loc1")
|
||||
let t = parse("match{`x => [1, glob1], `y => loc2, `z => {id = true, other = glob3}} loc1")
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
subst(&eval_cache, t, &initial_env, &env),
|
||||
parse("switch {`x => [1, 1], `y => (if false then 1 else \"Glob2\"), `z => {id = true, other = false}} true").unwrap()
|
||||
parse("match {`x => [1, 1], `y => (if false then 1 else \"Glob2\"), `z => {id = true, other = false}} true").unwrap()
|
||||
);
|
||||
}
|
||||
|
@ -236,28 +236,6 @@ UniTerm: UniTerm = {
|
||||
|
||||
UniTerm::from(rt)
|
||||
},
|
||||
"switch" "{" <cases: (SwitchCase ",")*> <last: SwitchCase?> "}"
|
||||
<exp: Term> => {
|
||||
let mut acc = HashMap::with_capacity(cases.len());
|
||||
let mut default = None;
|
||||
|
||||
for case in cases.into_iter().map(|x| x.0).chain(last.into_iter()) {
|
||||
match case {
|
||||
SwitchCase::Normal(id, t) => acc.insert(id, t),
|
||||
// If there are multiple default cases, the last one silently
|
||||
// erases the others. We should have a dedicated error for that
|
||||
SwitchCase::Default(t) => default.replace(t),
|
||||
};
|
||||
}
|
||||
|
||||
UniTerm::from(
|
||||
Term::Switch(
|
||||
exp,
|
||||
acc,
|
||||
default,
|
||||
)
|
||||
)
|
||||
},
|
||||
"if" <cond: Term> "then" <t1: Term> "else" <t2: Term> =>
|
||||
UniTerm::from(mk_app!(Term::Op1(UnaryOp::Ite(), cond), t1, t2)),
|
||||
<l: @L> <t: !> <r: @R> => {
|
||||
@ -300,6 +278,26 @@ Applicative: UniTerm = {
|
||||
=> UniTerm::from(mk_term::op2(op, t1, t2)),
|
||||
NOpPre<AsTerm<RecordOperand>>,
|
||||
RecordOperand,
|
||||
"match" "{" <cases: (MatchCase ",")*> <last: MatchCase?> "}" => {
|
||||
let mut acc = HashMap::with_capacity(cases.len());
|
||||
let mut default = None;
|
||||
|
||||
for case in cases.into_iter().map(|x| x.0).chain(last.into_iter()) {
|
||||
match case {
|
||||
MatchCase::Normal(id, t) => acc.insert(id, t),
|
||||
// If there are multiple default cases, the last one silently
|
||||
// erases the others. We should have a dedicated error for that
|
||||
MatchCase::Default(t) => default.replace(t),
|
||||
};
|
||||
}
|
||||
|
||||
UniTerm::from(
|
||||
Term::Match {
|
||||
cases: acc,
|
||||
default,
|
||||
}
|
||||
)
|
||||
},
|
||||
};
|
||||
|
||||
// The parametrized array type.
|
||||
@ -580,15 +578,15 @@ UOp: UnaryOp = {
|
||||
"num_from" => UnaryOp::NumFromStr(),
|
||||
"enum_from" => UnaryOp::EnumFromStr(),
|
||||
"str_is_match" => UnaryOp::StrIsMatch(),
|
||||
"str_match" => UnaryOp::StrMatch(),
|
||||
"str_find" => UnaryOp::StrFind(),
|
||||
"rec_force_op" => UnaryOp::RecForce(),
|
||||
"rec_default_op" => UnaryOp::RecDefault(),
|
||||
"record_empty_with_tail" => UnaryOp::RecordEmptyWithTail(),
|
||||
};
|
||||
|
||||
SwitchCase: SwitchCase = {
|
||||
<id: EnumTag> "=>" <t: Term> => SwitchCase::Normal(id, t),
|
||||
"_" "=>" <t: Term> => SwitchCase::Default(<>),
|
||||
MatchCase: MatchCase = {
|
||||
<id: EnumTag> "=>" <t: Term> => MatchCase::Normal(id, t),
|
||||
"_" "=>" <t: Term> => MatchCase::Default(<>),
|
||||
}
|
||||
|
||||
// Infix operators by precedence levels. Lowest levels take precedence over
|
||||
@ -845,7 +843,7 @@ extern {
|
||||
"in" => Token::Normal(NormalToken::In),
|
||||
"let" => Token::Normal(NormalToken::Let),
|
||||
"rec" => Token::Normal(NormalToken::Rec),
|
||||
"switch" => Token::Normal(NormalToken::Switch),
|
||||
"match" => Token::Normal(NormalToken::Match),
|
||||
|
||||
"null" => Token::Normal(NormalToken::Null),
|
||||
"true" => Token::Normal(NormalToken::True),
|
||||
@ -951,7 +949,7 @@ extern {
|
||||
"str_replace" => Token::Normal(NormalToken::StrReplace),
|
||||
"str_replace_regex" => Token::Normal(NormalToken::StrReplaceRegex),
|
||||
"str_is_match" => Token::Normal(NormalToken::StrIsMatch),
|
||||
"str_match" => Token::Normal(NormalToken::StrMatch),
|
||||
"str_find" => Token::Normal(NormalToken::StrFind),
|
||||
"str_length" => Token::Normal(NormalToken::StrLength),
|
||||
"str_substr" => Token::Normal(NormalToken::StrSubstr),
|
||||
"str_from" => Token::Normal(NormalToken::ToStr),
|
||||
|
@ -88,8 +88,8 @@ pub enum NormalToken<'input> {
|
||||
Let,
|
||||
#[token("rec")]
|
||||
Rec,
|
||||
#[token("switch")]
|
||||
Switch,
|
||||
#[token("match")]
|
||||
Match,
|
||||
|
||||
#[token("null")]
|
||||
Null,
|
||||
@ -276,8 +276,8 @@ pub enum NormalToken<'input> {
|
||||
StrReplaceRegex,
|
||||
#[token("%str_is_match%")]
|
||||
StrIsMatch,
|
||||
#[token("%str_match%")]
|
||||
StrMatch,
|
||||
#[token("%str_find%")]
|
||||
StrFind,
|
||||
#[token("%str_length%")]
|
||||
StrLength,
|
||||
#[token("%str_substr%")]
|
||||
@ -319,7 +319,7 @@ pub enum NormalToken<'input> {
|
||||
|
||||
pub const KEYWORDS: &[&str] = &[
|
||||
"Dyn", "Num", "Bool", "Str", "Array", "if", "then", "else", "forall", "in", "let", "rec",
|
||||
"switch", "null", "true", "false", "fun", "import", "merge", "default", "doc", "optional",
|
||||
"match", "null", "true", "false", "fun", "import", "merge", "default", "doc", "optional",
|
||||
"priority", "force",
|
||||
];
|
||||
|
||||
|
@ -5,7 +5,7 @@ use crate::parser::error::ParseError as InternalParseError;
|
||||
use crate::term::make as mk_term;
|
||||
use crate::term::Term::*;
|
||||
use crate::term::{record, BinaryOp, RichTerm, StrChunk, UnaryOp};
|
||||
use crate::{mk_app, mk_switch};
|
||||
use crate::{mk_app, mk_match};
|
||||
use assert_matches::assert_matches;
|
||||
use codespan::Files;
|
||||
|
||||
@ -177,14 +177,20 @@ fn enum_terms() {
|
||||
Enum(Ident::from("this works!")).into(),
|
||||
),
|
||||
(
|
||||
"switch with raw tags",
|
||||
"switch { `foo => true, `bar => false, _ => 456, } 123",
|
||||
mk_switch!(Num(123.), ("foo", Bool(true)), ("bar", Bool(false)) ; Num(456.)),
|
||||
"match with raw tags",
|
||||
"match { `foo => true, `bar => false, _ => 456, } 123",
|
||||
mk_app!(
|
||||
mk_match!(("foo", Bool(true)), ("bar", Bool(false)) ; Num(456.)),
|
||||
Num(123.)
|
||||
),
|
||||
),
|
||||
(
|
||||
"switch with string tags",
|
||||
"switch { `\"one:two\" => true, `\"three four\" => false, _ => 13 } 1",
|
||||
mk_switch!(Num(1.), ("one:two", Bool(true)), ("three four", Bool(false)) ; Num(13.)),
|
||||
"match with string tags",
|
||||
"match { `\"one:two\" => true, `\"three four\" => false, _ => 13 } 1",
|
||||
mk_app!(
|
||||
mk_match!(("one:two", Bool(true)), ("three four", Bool(false)) ; Num(13.)),
|
||||
Num(1.)
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
|
@ -33,7 +33,7 @@ pub enum StringKind {
|
||||
|
||||
/// Distinguish between a normal case `id => exp` and a default case `_ => exp`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum SwitchCase {
|
||||
pub enum MatchCase {
|
||||
Normal(Ident, RichTerm),
|
||||
Default(RichTerm),
|
||||
}
|
||||
|
@ -581,40 +581,35 @@ where
|
||||
.append(allocator.line())
|
||||
.group()
|
||||
.braces(),
|
||||
Switch(tst, cases, def) => allocator
|
||||
.text("switch")
|
||||
.append(allocator.space())
|
||||
.append(
|
||||
allocator
|
||||
.intersperse(
|
||||
sorted_map(cases).iter().map(|&(id, t)| {
|
||||
allocator
|
||||
.text("`")
|
||||
.append(allocator.quote_if_needed(id))
|
||||
.append(allocator.space())
|
||||
.append(allocator.text("=>"))
|
||||
.append(allocator.space())
|
||||
.append(t.to_owned().pretty(allocator))
|
||||
.append(allocator.text(","))
|
||||
}),
|
||||
allocator.line(),
|
||||
)
|
||||
.append(def.clone().map_or(allocator.nil(), |d| {
|
||||
Match { cases, default } => allocator.text("match").append(allocator.space()).append(
|
||||
allocator
|
||||
.intersperse(
|
||||
sorted_map(cases).iter().map(|&(id, t)| {
|
||||
allocator
|
||||
.line()
|
||||
.append(allocator.text("_"))
|
||||
.text("`")
|
||||
.append(allocator.quote_if_needed(id))
|
||||
.append(allocator.space())
|
||||
.append(allocator.text("=>"))
|
||||
.append(allocator.space())
|
||||
.append(d.pretty(allocator))
|
||||
}))
|
||||
.nest(2)
|
||||
.append(allocator.line_())
|
||||
.braces()
|
||||
.group(),
|
||||
)
|
||||
.append(allocator.space())
|
||||
.append(allocator.atom(tst)),
|
||||
.append(t.to_owned().pretty(allocator))
|
||||
.append(allocator.text(","))
|
||||
}),
|
||||
allocator.line(),
|
||||
)
|
||||
.append(default.clone().map_or(allocator.nil(), |d| {
|
||||
allocator
|
||||
.line()
|
||||
.append(allocator.text("_"))
|
||||
.append(allocator.space())
|
||||
.append(allocator.text("=>"))
|
||||
.append(allocator.space())
|
||||
.append(d.pretty(allocator))
|
||||
}))
|
||||
.nest(2)
|
||||
.append(allocator.line_())
|
||||
.braces()
|
||||
.group(),
|
||||
),
|
||||
Array(fields, _) => allocator
|
||||
// NOTE: the Array attributes are ignored here.
|
||||
.line()
|
||||
|
92
src/term.rs
92
src/term.rs
@ -108,14 +108,15 @@ pub enum Term {
|
||||
Vec<(RichTerm, RichTerm)>, /* field whose name is defined by interpolation */
|
||||
Option<RecordDeps>, /* dependency tracking between fields. None before the free var pass */
|
||||
),
|
||||
/// A switch construct. The evaluation is done by the corresponding unary operator, but we
|
||||
/// still need this one for typechecking.
|
||||
/// A match construct. Correspond only to the match cases: this expression is still to be
|
||||
/// applied to an argument to match on. Once applied, the evaluation is done by the
|
||||
/// corresponding primitive operator. Still, we need this construct for typechecking and being
|
||||
/// able to handle yet unapplied match expressions.
|
||||
#[serde(skip)]
|
||||
Switch(
|
||||
RichTerm, /* tested expression */
|
||||
HashMap<Ident, RichTerm>, /* cases */
|
||||
Option<RichTerm>, /* default */
|
||||
),
|
||||
Match {
|
||||
cases: HashMap<Ident, RichTerm>,
|
||||
default: Option<RichTerm>,
|
||||
},
|
||||
|
||||
/// An array.
|
||||
#[serde(serialize_with = "crate::serialize::serialize_array")]
|
||||
@ -830,7 +831,6 @@ impl<E> StrChunk<E> {
|
||||
}
|
||||
|
||||
impl Term {
|
||||
//#[cfg(test)]
|
||||
/// Recursively apply a function to all `Term`s contained in a `RichTerm`.
|
||||
pub fn apply_to_rich_terms<F>(&mut self, func: F)
|
||||
where
|
||||
@ -839,14 +839,16 @@ impl Term {
|
||||
use self::Term::*;
|
||||
match self {
|
||||
Null | ParseError(_) => (),
|
||||
Switch(ref mut t, ref mut cases, ref mut def) => {
|
||||
Match {
|
||||
ref mut cases,
|
||||
ref mut default,
|
||||
} => {
|
||||
cases.iter_mut().for_each(|c| {
|
||||
let (_, t) = c;
|
||||
func(t);
|
||||
});
|
||||
func(t);
|
||||
if let Some(def) = def {
|
||||
func(def)
|
||||
if let Some(default) = default {
|
||||
func(default)
|
||||
}
|
||||
}
|
||||
Record(ref mut r) => {
|
||||
@ -908,6 +910,7 @@ impl Term {
|
||||
Term::Num(_) => Some("Num"),
|
||||
Term::Str(_) => Some("Str"),
|
||||
Term::Fun(_, _) | Term::FunPattern(_, _, _) => Some("Fun"),
|
||||
Term::Match { .. } => Some("MatchExpression"),
|
||||
Term::Lbl(_) => Some("Label"),
|
||||
Term::Enum(_) => Some("Enum"),
|
||||
Term::Record(..) | Term::RecRecord(..) => Some("Record"),
|
||||
@ -919,7 +922,6 @@ impl Term {
|
||||
| Term::LetPattern(..)
|
||||
| Term::App(_, _)
|
||||
| Term::Var(_)
|
||||
| Term::Switch(..)
|
||||
| Term::Op1(_, _)
|
||||
| Term::Op2(_, _, _)
|
||||
| Term::OpN(..)
|
||||
@ -952,6 +954,7 @@ impl Term {
|
||||
format!("\"{}\"", chunks_str.join(""))
|
||||
}
|
||||
Term::Fun(_, _) | Term::FunPattern(_, _, _) => String::from("<func>"),
|
||||
Term::Match { .. } => String::from("<func (match expr)>"),
|
||||
Term::Lbl(_) => String::from("<label>"),
|
||||
Term::Enum(id) => {
|
||||
let re = regex::Regex::new("_?[a-zA-Z][_a-zA-Z0-9]*").unwrap();
|
||||
@ -994,7 +997,6 @@ impl Term {
|
||||
Term::Let(..)
|
||||
| Term::LetPattern(..)
|
||||
| Term::App(_, _)
|
||||
| Term::Switch(..)
|
||||
| Term::Op1(_, _)
|
||||
| Term::Op2(_, _, _)
|
||||
| Term::OpN(..)
|
||||
@ -1039,6 +1041,8 @@ impl Term {
|
||||
| Term::Num(_)
|
||||
| Term::Str(_)
|
||||
| Term::Fun(_, _)
|
||||
// match expressions are function
|
||||
| Term::Match {..}
|
||||
| Term::Lbl(_)
|
||||
| Term::Enum(_)
|
||||
| Term::Record(..)
|
||||
@ -1049,7 +1053,6 @@ impl Term {
|
||||
| Term::FunPattern(..)
|
||||
| Term::App(_, _)
|
||||
| Term::Var(_)
|
||||
| Term::Switch(..)
|
||||
| Term::Op1(_, _)
|
||||
| Term::Op2(_, _, _)
|
||||
| Term::OpN(..)
|
||||
@ -1088,7 +1091,7 @@ impl Term {
|
||||
| Term::Fun(_, _)
|
||||
| Term::FunPattern(_, _, _)
|
||||
| Term::App(_, _)
|
||||
| Term::Switch(..)
|
||||
| Term::Match { .. }
|
||||
| Term::Var(_)
|
||||
| Term::Op1(_, _)
|
||||
| Term::Op2(_, _, _)
|
||||
@ -1117,14 +1120,25 @@ impl Term {
|
||||
| Term::RecRecord(..)
|
||||
| Term::Array(..)
|
||||
| Term::Var(..)
|
||||
| Term::Op1(..)
|
||||
| Term::SealingKey(..) => true,
|
||||
| Term::SealingKey(..)
|
||||
// Those special cases aren't really atoms, but mustn't be parenthesized because they
|
||||
// are really functions taking additional non-strict arguments and printed as "partial"
|
||||
// infix operators.
|
||||
//
|
||||
// For example, `Op1(BoolOr, Var("x"))` is currently printed as `x ||`. Such operators
|
||||
// must never parenthesized, such as in `(x ||)`.
|
||||
//
|
||||
// We might want a more robust mechanism for pretty printing such operators.
|
||||
| Term::Op1(UnaryOp::BoolAnd(), _)
|
||||
| Term::Op1(UnaryOp::BoolOr(), _)
|
||||
=> true,
|
||||
Term::Let(..)
|
||||
| Term::Switch(..)
|
||||
| Term::Match { .. }
|
||||
| Term::LetPattern(..)
|
||||
| Term::Fun(..)
|
||||
| Term::FunPattern(..)
|
||||
| Term::App(..)
|
||||
| Term::Op1(..)
|
||||
| Term::Op2(..)
|
||||
| Term::OpN(..)
|
||||
| Term::Sealed(..)
|
||||
@ -1215,8 +1229,8 @@ pub enum UnaryOp {
|
||||
/// `embed c x` will have enum type `a | b | c`. It only affects typechecking as at runtime
|
||||
/// `embed someId` act like the identity.
|
||||
Embed(Ident),
|
||||
/// A switch block. Used to match on a enumeration.
|
||||
Switch(bool /* presence of a default case */), //HashMap<Ident, CapturedTerm>, Option<CapturedTerm>),
|
||||
/// Evaluate a match block applied to an argument.
|
||||
Match { has_default: bool },
|
||||
|
||||
/// Static access to a record field.
|
||||
///
|
||||
@ -1318,17 +1332,17 @@ pub enum UnaryOp {
|
||||
/// Transform a string to an enum.
|
||||
EnumFromStr(),
|
||||
/// Test if a regex matches a string.
|
||||
/// Like [`UnaryOp::StrMatch`], this is a unary operator because we would like a way to share the
|
||||
/// Like [`UnaryOp::StrFind`], this is a unary operator because we would like a way to share the
|
||||
/// same "compiled regex" for many matching calls. This is done by returning functions
|
||||
/// wrapping [`UnaryOp::StrIsMatchCompiled`] and [`UnaryOp::StrMatchCompiled`]
|
||||
/// wrapping [`UnaryOp::StrIsMatchCompiled`] and [`UnaryOp::StrFindCompiled`]
|
||||
StrIsMatch(),
|
||||
/// Match a regex on a string, and returns the captured groups together, the index of the
|
||||
/// match, etc.
|
||||
StrMatch(),
|
||||
StrFind(),
|
||||
/// Version of [`UnaryOp::StrIsMatch`] which remembers the compiled regex.
|
||||
StrIsMatchCompiled(CompiledRegex),
|
||||
/// Version of [`UnaryOp::StrMatch`] which remembers the compiled regex.
|
||||
StrMatchCompiled(CompiledRegex),
|
||||
/// Version of [`UnaryOp::StrFind`] which remembers the compiled regex.
|
||||
StrFindCompiled(CompiledRegex),
|
||||
/// Force full evaluation of a term and return it.
|
||||
///
|
||||
/// This was added in the context of [`BinaryOp::ArrayLazyAssume`],
|
||||
@ -1707,10 +1721,10 @@ impl RichTerm {
|
||||
pos,
|
||||
)
|
||||
},
|
||||
Term::Switch(t, cases, default) => {
|
||||
Term::Match { cases, default } => {
|
||||
// The annotation on `map_res` use Result's corresponding trait to convert from
|
||||
// Iterator<Result> to a Result<Iterator>
|
||||
let cases_res: Result<HashMap<Ident, RichTerm>, E> = cases
|
||||
let cases_result : Result<HashMap<Ident, RichTerm>, E> = cases
|
||||
.into_iter()
|
||||
// For the conversion to work, note that we need a Result<(Ident,RichTerm), E>
|
||||
.map(|(id, t)| t.traverse(f, state, order).map(|t_ok| (id, t_ok)))
|
||||
@ -1718,10 +1732,8 @@ impl RichTerm {
|
||||
|
||||
let default = default.map(|t| t.traverse(f, state, order)).transpose()?;
|
||||
|
||||
let t = t.traverse(f, state, order)?;
|
||||
|
||||
RichTerm::new(
|
||||
Term::Switch(t, cases_res?, default),
|
||||
Term::Match {cases: cases_result?, default },
|
||||
pos,
|
||||
)
|
||||
},
|
||||
@ -2033,22 +2045,22 @@ pub mod make {
|
||||
/// `mk_switch!(format, ("Json", json_case), ("Yaml", yaml_case) ; def)` corresponds to
|
||||
/// ``switch { `Json => json_case, `Yaml => yaml_case, _ => def} format``.
|
||||
#[macro_export]
|
||||
macro_rules! mk_switch {
|
||||
( $exp:expr, $( ($id:expr, $body:expr) ),* ; $default:expr ) => {
|
||||
macro_rules! mk_match {
|
||||
( $( ($id:expr, $body:expr) ),* ; $default:expr ) => {
|
||||
{
|
||||
let mut map = std::collections::HashMap::new();
|
||||
let mut cases = std::collections::HashMap::new();
|
||||
$(
|
||||
map.insert($id.into(), $body.into());
|
||||
cases.insert($id.into(), $body.into());
|
||||
)*
|
||||
$crate::term::RichTerm::from($crate::term::Term::Switch($crate::term::RichTerm::from($exp), map, Some($crate::term::RichTerm::from($default))))
|
||||
$crate::term::RichTerm::from($crate::term::Term::Match {cases, default: Some($crate::term::RichTerm::from($default)) })
|
||||
}
|
||||
};
|
||||
( $exp:expr, $( ($id:expr, $body:expr) ),*) => {
|
||||
let mut map = std::collections::HashMap::new();
|
||||
( $( ($id:expr, $body:expr) ),*) => {
|
||||
let mut cases = std::collections::HashMap::new();
|
||||
$(
|
||||
map.insert($id.into(), $body.into());
|
||||
cases.insert($id.into(), $body.into());
|
||||
)*
|
||||
$crate::term::RichTerm::from($crate::term::Term::Switch($crate::term::RichTerm::from($exp), map, None))
|
||||
$crate::term::RichTerm::from($crate::term::Term::Match {cases, default: None})
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -88,8 +88,7 @@ impl CollectFreeVars for RichTerm {
|
||||
t1.collect_free_vars(free_vars);
|
||||
t2.collect_free_vars(free_vars);
|
||||
}
|
||||
Term::Switch(t, cases, default) => {
|
||||
t.collect_free_vars(free_vars);
|
||||
Term::Match { cases, default } => {
|
||||
for t in cases.values_mut().chain(default.iter_mut()) {
|
||||
t.collect_free_vars(free_vars);
|
||||
}
|
||||
|
@ -181,7 +181,9 @@ pub fn should_share(t: &Term) -> bool {
|
||||
| Term::SealingKey(_)
|
||||
| Term::Var(_)
|
||||
| Term::Enum(_)
|
||||
| Term::Fun(_, _) => false,
|
||||
| Term::Fun(_, _)
|
||||
// match acts like a function, and is a WHNF
|
||||
| Term::Match {..} => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@
|
||||
//!
|
||||
//! ## Equality on terms
|
||||
//!
|
||||
//! The terms inside a type may be arbitrarily complex. Primops applications, `switch`, and the
|
||||
//! The terms inside a type may be arbitrarily complex. Primops applications, `match`, and the
|
||||
//! like are quite unlikely to appear inside an annotation (they surely appear inside contract
|
||||
//! definitions). We don't want to compare functions syntactically either. The spirit of this
|
||||
//! implementation is to equate aliases or simple constructs that may appear inlined inside an
|
||||
|
@ -869,12 +869,10 @@ fn walk<L: Linearizer>(
|
||||
walk(state, ctxt.clone(), lin, linearizer.scope(), e)?;
|
||||
walk(state, ctxt, lin, linearizer, t)
|
||||
}
|
||||
Term::Switch(exp, cases, default) => {
|
||||
Term::Match {cases, default} => {
|
||||
cases.values().chain(default.iter()).try_for_each(|case| {
|
||||
walk(state, ctxt.clone(), lin, linearizer.scope(), case)
|
||||
})?;
|
||||
|
||||
walk(state, ctxt, lin, linearizer, exp)
|
||||
})
|
||||
}
|
||||
Term::RecRecord(record, dynamic, ..) => {
|
||||
for (id, field) in record.fields.iter() {
|
||||
@ -1182,10 +1180,14 @@ fn type_check_<L: Linearizer>(
|
||||
type_check_(state, ctxt.clone(), lin, linearizer.scope(), e, arr)?;
|
||||
type_check_(state, ctxt, lin, linearizer, t, src)
|
||||
}
|
||||
Term::Switch(exp, cases, default) => {
|
||||
Term::Match { cases, default } => {
|
||||
// Currently, if it has a default value, we typecheck the whole thing as
|
||||
// taking ANY enum, since it's more permissive and there's no loss of information
|
||||
let res = state.table.fresh_type_uvar();
|
||||
// taking ANY enum, since it's more permissive and there's no loss of information.
|
||||
|
||||
// A match expression is a special kind of function. Thus it's typed as `a -> b`, where
|
||||
// `a` is a enum type determined by the matched tags and `b` is the type of each match
|
||||
// arm.
|
||||
let return_type = state.table.fresh_type_uvar();
|
||||
|
||||
for case in cases.values() {
|
||||
type_check_(
|
||||
@ -1194,13 +1196,20 @@ fn type_check_<L: Linearizer>(
|
||||
lin,
|
||||
linearizer.scope(),
|
||||
case,
|
||||
res.clone(),
|
||||
return_type.clone(),
|
||||
)?;
|
||||
}
|
||||
|
||||
let erows = match default {
|
||||
Some(t) => {
|
||||
type_check_(state, ctxt.clone(), lin, linearizer.scope(), t, res.clone())?;
|
||||
type_check_(
|
||||
state,
|
||||
ctxt.clone(),
|
||||
lin,
|
||||
linearizer.scope(),
|
||||
t,
|
||||
return_type.clone(),
|
||||
)?;
|
||||
state.table.fresh_erows_uvar()
|
||||
}
|
||||
None => cases.iter().try_fold(
|
||||
@ -1211,8 +1220,13 @@ fn type_check_<L: Linearizer>(
|
||||
)?,
|
||||
};
|
||||
|
||||
unify(state, &ctxt, ty, res).map_err(|err| err.into_typecheck_err(state, rt.pos))?;
|
||||
type_check_(state, ctxt, lin, linearizer, exp, mk_uty_enum!(; erows))
|
||||
// `arg_type` represents the type of arguments that the match expression can be applied
|
||||
// to.
|
||||
let arg_type = mk_uty_enum!(; erows);
|
||||
|
||||
// we unify the expected type of the match expression with `arg_type -> return_type`
|
||||
unify(state, &ctxt, ty, mk_uty_arrow!(arg_type, return_type))
|
||||
.map_err(|err| err.into_typecheck_err(state, rt.pos))
|
||||
}
|
||||
Term::Var(x) => {
|
||||
let x_ty = ctxt
|
||||
|
@ -54,8 +54,8 @@ pub fn get_uop_type(
|
||||
codomain.constrain_fresh_erows_var(state, row_var_id);
|
||||
(domain, codomain)
|
||||
}
|
||||
// This should not happen, as Switch() is only produced during evaluation.
|
||||
UnaryOp::Switch(_) => panic!("cannot typecheck Switch()"),
|
||||
// This should not happen, as a match primop is only produced during evaluation.
|
||||
UnaryOp::Match { .. } => panic!("cannot typecheck match primop"),
|
||||
// Dyn -> Dyn
|
||||
UnaryOp::ChangePolarity() | UnaryOp::GoDom() | UnaryOp::GoCodom() | UnaryOp::GoArray() => {
|
||||
(mk_uniftype::dynamic(), mk_uniftype::dynamic())
|
||||
@ -166,13 +166,13 @@ pub fn get_uop_type(
|
||||
mk_uniftype::str(),
|
||||
mk_uty_arrow!(mk_uniftype::str(), mk_uniftype::bool()),
|
||||
),
|
||||
// Str -> Str -> {match: Str, index: Num, groups: Array Str}
|
||||
UnaryOp::StrMatch() => (
|
||||
// Str -> Str -> {matched: Str, index: Num, groups: Array Str}
|
||||
UnaryOp::StrFind() => (
|
||||
mk_uniftype::str(),
|
||||
mk_uty_arrow!(
|
||||
mk_uniftype::str(),
|
||||
mk_uty_record!(
|
||||
("match", TypeF::Str),
|
||||
("matched", TypeF::Str),
|
||||
("index", TypeF::Num),
|
||||
("groups", mk_uniftype::array(TypeF::Str))
|
||||
)
|
||||
@ -180,8 +180,8 @@ pub fn get_uop_type(
|
||||
),
|
||||
// Str -> Bool
|
||||
UnaryOp::StrIsMatchCompiled(_) => (mk_uniftype::str(), mk_uniftype::bool()),
|
||||
// Str -> {match: Str, index: Num, groups: Array Str}
|
||||
UnaryOp::StrMatchCompiled(_) => (
|
||||
// Str -> {matched: Str, index: Num, groups: Array Str}
|
||||
UnaryOp::StrFindCompiled(_) => (
|
||||
mk_uniftype::str(),
|
||||
mk_uty_record!(
|
||||
("match", TypeF::Str),
|
||||
@ -398,7 +398,7 @@ pub fn get_nop_type(
|
||||
],
|
||||
mk_uniftype::dynamic(),
|
||||
),
|
||||
// This should not happen, as Switch() is only produced during evaluation.
|
||||
// This should not happen, as MergeContract() is only produced during evaluation.
|
||||
NAryOp::MergeContract() => panic!("cannot typecheck MergeContract()"),
|
||||
})
|
||||
}
|
||||
|
16
src/types.rs
16
src/types.rs
@ -703,7 +703,7 @@ impl EnumRows {
|
||||
let case_body = if has_tail {
|
||||
mk_term::var(value_arg)
|
||||
}
|
||||
// Otherwise, we build a switch with all the tags as cases, which just returns the
|
||||
// Otherwise, we build a match with all the tags as cases, which just returns the
|
||||
// original argument, and a default case that blames.
|
||||
//
|
||||
// For example, for an enum type [| `foo, `bar, `baz |], the `case` function looks
|
||||
@ -711,7 +711,7 @@ impl EnumRows {
|
||||
//
|
||||
// ```
|
||||
// fun l x =>
|
||||
// switch {
|
||||
// match {
|
||||
// `foo => x,
|
||||
// `bar => x,
|
||||
// `baz => x,
|
||||
@ -719,11 +719,13 @@ impl EnumRows {
|
||||
// } x
|
||||
// ```
|
||||
else {
|
||||
RichTerm::from(Term::Switch(
|
||||
mk_term::var(value_arg),
|
||||
cases,
|
||||
Some(mk_app!(contract::enum_fail(), mk_term::var(label_arg))),
|
||||
))
|
||||
mk_app!(
|
||||
Term::Match {
|
||||
cases,
|
||||
default: Some(mk_app!(contract::enum_fail(), mk_term::var(label_arg))),
|
||||
},
|
||||
mk_term::var(value_arg)
|
||||
)
|
||||
};
|
||||
let case = mk_fun!(label_arg, value_arg, case_body);
|
||||
|
||||
|
@ -349,24 +349,24 @@
|
||||
"%
|
||||
= fun regex => %str_is_match% regex,
|
||||
|
||||
match : Str -> Str -> {match: Str, index: Num, groups: Array Str}
|
||||
find : Str -> Str -> {matched: Str, index: Num, groups: Array Str}
|
||||
| doc m%"
|
||||
`match regex str` matches `str` given `regex`. Results in the part of `str` that matched, the index of the
|
||||
`find regex str` matches `str` given `regex`. Results in the part of `str` that matched, the index of the
|
||||
first character that was part of the match in `str`, and a arrays of all capture groups if any.
|
||||
|
||||
For example:
|
||||
```nickel
|
||||
match "^(\\d).*(\\d).*(\\d).*$" "5 apples, 6 pears and 0 grapes" =>
|
||||
{ match = "5 apples, 6 pears and 0 grapes", index = 0, groups = [ "5", "6", "0" ] }
|
||||
match "3" "01234" =>
|
||||
{ match = "3", index = 3, groups = [ ] }
|
||||
find "^(\\d).*(\\d).*(\\d).*$" "5 apples, 6 pears and 0 grapes" =>
|
||||
{ matched = "5 apples, 6 pears and 0 grapes", index = 0, groups = [ "5", "6", "0" ] }
|
||||
find "3" "01234" =>
|
||||
{ matched = "3", index = 3, groups = [ ] }
|
||||
```
|
||||
|
||||
Note that this function may perform better by sharing its partial application between multiple calls,
|
||||
because in this case the underlying regular expression will only be compiled once (See the documentation
|
||||
of `string.is_match` for more details).
|
||||
"%
|
||||
= fun regex => %str_match% regex,
|
||||
= fun regex => %str_find% regex,
|
||||
|
||||
length : Str -> Num
|
||||
| doc m%"
|
||||
|
@ -277,12 +277,11 @@ fn records_contracts_closed() {
|
||||
assert_raise_blame!("let Contract = {a | Num} & {b | Num} in ({a=1, b=2, c=3} | Contract)");
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn enum_complex() {
|
||||
// eval(
|
||||
// "let f : <foo, bar> -> Num =
|
||||
// fun x => switch { `foo => 1, `bar => 2, } x in
|
||||
// f `boo",
|
||||
// )
|
||||
// .unwrap_err();
|
||||
// }
|
||||
#[test]
|
||||
fn enum_complex() {
|
||||
eval(
|
||||
"let f : [| `foo, `bar |] -> Num = match { `foo => 1, `bar => 2, } in
|
||||
f `boo",
|
||||
)
|
||||
.unwrap_err();
|
||||
}
|
||||
|
@ -21,7 +21,5 @@ let {check, Assert, ..} = import "testlib.ncl" in
|
||||
|
||||
# others_precedence
|
||||
((fun x => x | Assert) true),
|
||||
(let AssertOk = fun l t => if t == `Ok then t else %blame% l in
|
||||
switch {`Ok => true, `Err => false} `Ok | AssertOk),
|
||||
]
|
||||
|> check
|
||||
|
@ -31,7 +31,7 @@ let {check, ..} = import "testlib.ncl" in
|
||||
1/4 + 1/4 - 1/4 + 1/4 >= 1/2 == true,
|
||||
1/4 + 1/4 - 1/4 + 1/4 < 1/2 == false,
|
||||
|
||||
# This test checks that the terms of a switch are closured
|
||||
let x = 3 in (switch { `foo => 1, _ => x} (3 + 2)) == 3,
|
||||
# This test checks that the terms of a match are closured
|
||||
let x = 3 in ((3 + 2) |> match { `foo => 1, _ => x}) == 3,
|
||||
]
|
||||
|> check
|
||||
|
@ -37,19 +37,19 @@ let {check, Assert, ..} = import "testlib.ncl" in
|
||||
|
||||
# enums_complex
|
||||
let f : forall r. [| `foo, `bar ; r |] -> Num =
|
||||
fun x => switch { `foo => 1, `bar => 2, _ => 3, } x in
|
||||
match { `foo => 1, `bar => 2, _ => 3, } in
|
||||
f `bar == 2,
|
||||
|
||||
let f : forall r. [| `foo, `bar ; r |] -> Num =
|
||||
fun x => switch { `foo => 1, `bar => 2, _ => 3, } x in
|
||||
match { `foo => 1, `bar => 2, _ => 3, } in
|
||||
f `boo == 3,
|
||||
|
||||
let f : forall r. [| `foo, `"bar:baz" ; r |] -> Num =
|
||||
fun x => switch { `foo => 1, `"bar:baz" => 2, _ => 3, } x in
|
||||
fun x => match { `foo => 1, `"bar:baz" => 2, _ => 3, } x in
|
||||
f `"bar:baz" == 2,
|
||||
|
||||
let f : forall r. [| `foo, `"bar:baz" ; r |] -> Num =
|
||||
fun x => switch { `foo => 1, `"bar:baz" => 2, _ => 3, } x in
|
||||
fun x => match { `foo => 1, `"bar:baz" => 2, _ => 3, } x in
|
||||
f `"boo,grr" == 3,
|
||||
|
||||
# enums_applied
|
||||
|
@ -21,9 +21,9 @@ let assertDeserInv = fun x =>
|
||||
assertSerInv {val = ["a", 3, []]},
|
||||
assertSerInv {a.foo.bar = "2", b = false, c = [{d = "e"}, {d = "f"}]},
|
||||
|
||||
# regression test for a previously missing `#[serde(skip)]` on the `Switch`
|
||||
# regression test for a previously missing `#[serde(skip)]` on the `Match`
|
||||
# variant of `term::Term`. That was causing a list with the right size to be
|
||||
# serialized as a `Switch` instead of an array. This test checks that lists of
|
||||
# serialized as a `Match` instead of an array. This test checks that lists of
|
||||
# various sizes don't get misinterpreted again as a term construct that misses a
|
||||
# `#[serde(skip)]` annotations
|
||||
{
|
||||
|
@ -54,25 +54,25 @@ let typecheck = [
|
||||
(`blo : [|`bla, `blo |]),
|
||||
(`bla : forall r. [|`bla ; r |]),
|
||||
(`bla : forall r. [|`bla, `blo ; r |]),
|
||||
((switch {`bla => 3} `bla) : Num),
|
||||
((switch {`bla => 3, _ => 2} `blo) : Num),
|
||||
((`bla |> match {`bla => 3}) : Num),
|
||||
((`blo |> match {`bla => 3, _ => 2}) : Num),
|
||||
|
||||
# enums_complex
|
||||
((fun x => switch {`bla => 1, `ble => 2} x) : [|`bla, `ble |] -> Num),
|
||||
((fun x => switch {`bla => 1, `ble => 2, `bli => 4} (%embed% bli x))
|
||||
((fun x => x |> match {`bla => 1, `ble => 2}) : [|`bla, `ble |] -> Num),
|
||||
((fun x => %embed% bli x |> match {`bla => 1, `ble => 2, `bli => 4})
|
||||
: [|`bla, `ble |] -> Num),
|
||||
((fun x =>
|
||||
(switch {`bla => 3, `bli => 2} x)
|
||||
+ (switch {`bli => 6, `bla => 20} x))
|
||||
(x |> match {`bla => 3, `bli => 2})
|
||||
+ (x |> match {`bli => 6, `bla => 20}))
|
||||
`bla
|
||||
: Num),
|
||||
|
||||
let f : forall r. [|`blo, `ble ; r |] -> Num = fun x =>
|
||||
switch {`blo => 1, `ble => 2, _ => 3} x in
|
||||
(f `bli : Num),
|
||||
let f : forall r. [|`blo, `ble ; r |] -> Num =
|
||||
match {`blo => 1, `ble => 2, _ => 3} in
|
||||
(f `bli : Num),
|
||||
|
||||
let f : forall r. (forall p. [|`blo, `ble ; r |] -> [|`bla, `bli ; p |]) =
|
||||
fun x => switch {`blo => `bla, `ble => `bli, _ => `bla} x in
|
||||
match {`blo => `bla, `ble => `bli, _ => `bla} in
|
||||
f `bli,
|
||||
|
||||
# recursive let bindings
|
||||
|
@ -79,30 +79,28 @@ fn simple_forall() {
|
||||
#[test]
|
||||
fn enum_simple() {
|
||||
assert_typecheck_fails!("`foo : [| `bar |]");
|
||||
assert_typecheck_fails!("switch { `foo => 3} `bar : Num");
|
||||
assert_typecheck_fails!("switch { `foo => 3, `bar => true} `bar : Num");
|
||||
assert_typecheck_fails!("match { `foo => 3} `bar : Num");
|
||||
assert_typecheck_fails!("match { `foo => 3, `bar => true} `bar : Num");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enum_complex() {
|
||||
assert_typecheck_fails!(
|
||||
"(fun x => switch {`bla => 1, `ble => 2, `bli => 4} x) : [| `bla, `ble |] -> Num"
|
||||
);
|
||||
assert_typecheck_fails!("(match {`bla => 1, `ble => 2, `bli => 4}) : [| `bla, `ble |] -> Num");
|
||||
// TODO typecheck this, I'm not sure how to do it with row variables
|
||||
// LATER NOTE: this requires row subtyping, not easy
|
||||
assert_typecheck_fails!(
|
||||
"(fun x =>
|
||||
(switch {`bla => 3, `bli => 2} x) +
|
||||
(switch {`bla => 6, `blo => 20} x)) `bla : Num"
|
||||
(x |> match {`bla => 3, `bli => 2}) +
|
||||
(x |> match {`bla => 6, `blo => 20})) `bla : Num"
|
||||
);
|
||||
assert_typecheck_fails!(
|
||||
"let f : forall r. [| `blo, `ble ; r |] -> Num =
|
||||
fun x => (switch {`blo => 1, `ble => 2, `bli => 3} x) in
|
||||
match {`blo => 1, `ble => 2, `bli => 3} in
|
||||
f"
|
||||
);
|
||||
assert_typecheck_fails!(
|
||||
"let f : forall r. (forall p. [| `blo, `ble ; r |] -> [| `bla, `bli ; p |]) =
|
||||
fun x => (switch {`blo => `bla, `ble => `bli, _ => `blo} x) in
|
||||
match {`blo => `bla, `ble => `bli, _ => `blo} in
|
||||
f `bli"
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user