mirror of
https://github.com/tweag/nickel.git
synced 2024-09-19 15:38:47 +03:00
Implement contract mode for merging
This commit is contained in:
parent
7199f1831d
commit
4add0815d7
@ -399,7 +399,7 @@ impl Cache {
|
||||
Some(_) => {
|
||||
let (mut t, _) = self.terms.remove(&file_id).unwrap();
|
||||
match t.term.as_mut() {
|
||||
Term::Record(ref mut map) | Term::RecRecord(ref mut map) => {
|
||||
Term::Record(ref mut map, _) | Term::RecRecord(ref mut map, _) => {
|
||||
let map_res: Result<HashMap<Ident, RichTerm>, ImportError> =
|
||||
std::mem::replace(map, HashMap::new())
|
||||
.into_iter()
|
||||
|
18
src/eval.rs
18
src/eval.rs
@ -308,7 +308,7 @@ pub fn env_add_term(env: &mut Environment, rt: RichTerm) -> Result<(), EnvBuildE
|
||||
let RichTerm { term, pos } = rt;
|
||||
|
||||
match *term {
|
||||
Term::Record(bindings) | Term::RecRecord(bindings) => {
|
||||
Term::Record(bindings, _) | Term::RecRecord(bindings, _) => {
|
||||
let ext = bindings.into_iter().map(|(id, t)| {
|
||||
(
|
||||
id,
|
||||
@ -337,7 +337,7 @@ pub fn env_add(env: &mut Environment, id: Ident, rt: RichTerm, local_env: Enviro
|
||||
/// Typically, WHNFs and enriched values will not be evaluated to a simpler expression and are not
|
||||
/// worth updating.
|
||||
fn should_update(t: &Term) -> bool {
|
||||
!t.is_whnf() && !t.is_enriched()
|
||||
!t.is_whnf() && !t.is_metavalue()
|
||||
}
|
||||
|
||||
/// Evaluate a Nickel term. Wrapper around [eval_closure](fn.eval_closure.html) that starts from an
|
||||
@ -508,7 +508,7 @@ where
|
||||
|
||||
stack.push_arg(
|
||||
Closure {
|
||||
body: RichTerm::new(Term::Record(cases), pos),
|
||||
body: RichTerm::new(Term::Record(cases, Default::default()), pos),
|
||||
env: env.clone(),
|
||||
},
|
||||
pos,
|
||||
@ -622,7 +622,7 @@ where
|
||||
env,
|
||||
}
|
||||
}
|
||||
Term::RecRecord(ts) => {
|
||||
Term::RecRecord(ts, attrs) => {
|
||||
// Thanks to the share normal form transformation, the content is either a constant or a
|
||||
// variable.
|
||||
let rec_env = ts.iter().try_fold::<_, _, Result<Environment, EvalError>>(
|
||||
@ -670,7 +670,7 @@ where
|
||||
});
|
||||
Closure {
|
||||
body: RichTerm {
|
||||
term: Box::new(Term::Record(new_ts.collect())),
|
||||
term: Box::new(Term::Record(new_ts.collect(), attrs)),
|
||||
pos,
|
||||
},
|
||||
env,
|
||||
@ -872,7 +872,7 @@ pub fn subst(rt: RichTerm, global_env: &Environment, env: &Environment) -> RichT
|
||||
|
||||
RichTerm::new(Term::Wrapped(i, t), pos)
|
||||
}
|
||||
Term::Record(map) => {
|
||||
Term::Record(map, attrs) => {
|
||||
let map = map
|
||||
.into_iter()
|
||||
.map(|(id, t)| {
|
||||
@ -883,9 +883,9 @@ pub fn subst(rt: RichTerm, global_env: &Environment, env: &Environment) -> RichT
|
||||
})
|
||||
.collect();
|
||||
|
||||
RichTerm::new(Term::Record(map), pos)
|
||||
RichTerm::new(Term::Record(map, attrs), pos)
|
||||
}
|
||||
Term::RecRecord(map) => {
|
||||
Term::RecRecord(map, attrs) => {
|
||||
let map = map
|
||||
.into_iter()
|
||||
.map(|(id, t)| {
|
||||
@ -896,7 +896,7 @@ pub fn subst(rt: RichTerm, global_env: &Environment, env: &Environment) -> RichT
|
||||
})
|
||||
.collect();
|
||||
|
||||
RichTerm::new(Term::RecRecord(map), pos)
|
||||
RichTerm::new(Term::RecRecord(map, attrs), pos)
|
||||
}
|
||||
Term::List(ts) => {
|
||||
let ts = ts
|
||||
|
47
src/merge.rs
47
src/merge.rs
@ -54,21 +54,44 @@
|
||||
use crate::error::EvalError;
|
||||
use crate::eval::{Closure, Environment};
|
||||
use crate::position::TermPos;
|
||||
use crate::term::{make as mk_term, BinaryOp, Contract, MetaValue, RichTerm, Term};
|
||||
use crate::term::{make as mk_term, BinaryOp, Contract, MetaValue, RecordAttrs, RichTerm, Term};
|
||||
use crate::transformations::Closurizable;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Compute the merge of two evaluated operands.
|
||||
/// Merging mode. Merging is used both to combine standard data and to apply contracts defined as
|
||||
/// records.
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
pub enum MergeMode {
|
||||
/// Standard merging for combining data.
|
||||
Standard,
|
||||
/// Merging used to apply a record contract to a value.
|
||||
Contract,
|
||||
}
|
||||
|
||||
impl Default for MergeMode {
|
||||
fn default() -> Self {
|
||||
MergeMode::Standard
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the merge of two evaluated operands. Support both standard merging and record contract
|
||||
/// application.
|
||||
///
|
||||
/// # Mode
|
||||
///
|
||||
/// In `Contract` mode (see [`MergingMode`]()), `t1` must be the value and `t2` must be the
|
||||
/// contract. It is important as `merge` is not commutative in this mode.
|
||||
pub fn merge(
|
||||
t1: RichTerm,
|
||||
env1: Environment,
|
||||
t2: RichTerm,
|
||||
env2: Environment,
|
||||
pos_op: TermPos,
|
||||
mode: MergeMode,
|
||||
) -> Result<Closure, EvalError> {
|
||||
// Merging a simple value and a metavalue is equivalent to first wrapping the simple value in a
|
||||
// new metavalue (with no attribute set excepted the value), and then merging the two
|
||||
let (t1, t2) = match (t1.term.is_enriched(), t2.term.is_enriched()) {
|
||||
let (t1, t2) = match (t1.term.is_metavalue(), t2.term.is_metavalue()) {
|
||||
(true, false) => {
|
||||
let pos = t2.pos;
|
||||
let t = Term::MetaValue(MetaValue::from(t2));
|
||||
@ -297,7 +320,7 @@ pub fn merge(
|
||||
}
|
||||
// Merge put together the fields of records, and recursively merge
|
||||
// fields that are present in both terms
|
||||
(Term::Record(m1), Term::Record(m2)) => {
|
||||
(Term::Record(m1, attrs1), Term::Record(m2, attrs2)) => {
|
||||
/* Terms inside m1 and m2 may capture variables of resp. env1 and env2. Morally, we
|
||||
* need to store closures, or a merge of closures, inside the resulting record. We use
|
||||
* the same trick as in the evaluation of the operator DynExtend, and replace each such
|
||||
@ -307,6 +330,17 @@ pub fn merge(
|
||||
let mut env = HashMap::new();
|
||||
let (mut left, mut center, mut right) = hashmap::split(m1, m2);
|
||||
|
||||
if mode == MergeMode::Contract && !attrs2.open && !left.is_empty() {
|
||||
let fields: Vec<String> = left
|
||||
.into_keys()
|
||||
.map(|field| format!("`{}`", field))
|
||||
.collect();
|
||||
return Err(EvalError::Other(
|
||||
format!("additional field(s) {}", fields.join(", ")),
|
||||
pos1,
|
||||
));
|
||||
}
|
||||
|
||||
for (field, t) in left.drain() {
|
||||
m.insert(field, t.closurize(&mut env, env1.clone()));
|
||||
}
|
||||
@ -323,7 +357,10 @@ pub fn merge(
|
||||
}
|
||||
|
||||
Ok(Closure {
|
||||
body: RichTerm::new(Term::Record(m), pos_op.into_inherited()),
|
||||
body: RichTerm::new(
|
||||
Term::Record(m, RecordAttrs::merge(attrs1, attrs2)),
|
||||
pos_op.into_inherited(),
|
||||
),
|
||||
env,
|
||||
})
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ use crate::eval::{subst, CallStack, Closure, Environment};
|
||||
use crate::identifier::Ident;
|
||||
use crate::label::ty_path;
|
||||
use crate::merge;
|
||||
use crate::merge::merge;
|
||||
use crate::merge::{merge, MergeMode};
|
||||
use crate::mk_record;
|
||||
use crate::position::TermPos;
|
||||
use crate::stack::Stack;
|
||||
@ -315,7 +315,7 @@ fn process_unary_operation(
|
||||
} = cases_closure;
|
||||
|
||||
let mut cases = match *cases_term {
|
||||
Term::Record(map) => map,
|
||||
Term::Record(map, _) => map,
|
||||
_ => panic!("invalid argument for switch"),
|
||||
};
|
||||
|
||||
@ -442,7 +442,7 @@ fn process_unary_operation(
|
||||
}
|
||||
}
|
||||
UnaryOp::StaticAccess(id) => {
|
||||
if let Term::Record(mut static_map) = *t {
|
||||
if let Term::Record(mut static_map, attrs) = *t {
|
||||
match static_map.remove(&id) {
|
||||
Some(e) => Ok(Closure { body: e, env }),
|
||||
|
||||
@ -450,7 +450,7 @@ fn process_unary_operation(
|
||||
id.0,
|
||||
String::from("(.)"),
|
||||
RichTerm {
|
||||
term: Box::new(Term::Record(static_map)),
|
||||
term: Box::new(Term::Record(static_map, attrs)),
|
||||
pos,
|
||||
},
|
||||
pos_op,
|
||||
@ -466,7 +466,7 @@ fn process_unary_operation(
|
||||
}
|
||||
}
|
||||
UnaryOp::FieldsOf() => {
|
||||
if let Term::Record(map) = *t {
|
||||
if let Term::Record(map, _) = *t {
|
||||
let mut fields: Vec<String> = map.keys().map(|Ident(id)| id.clone()).collect();
|
||||
fields.sort();
|
||||
let terms = fields.into_iter().map(mk_term::string).collect();
|
||||
@ -484,7 +484,7 @@ fn process_unary_operation(
|
||||
}
|
||||
}
|
||||
UnaryOp::ValuesOf() => {
|
||||
if let Term::Record(map) = *t {
|
||||
if let Term::Record(map, _) = *t {
|
||||
let mut values: Vec<_> = map.into_iter().collect();
|
||||
// Although it seems that sort_by_key would be easier here, it would actually
|
||||
// require to copy the identifiers because of the lack of HKT. See
|
||||
@ -585,7 +585,7 @@ fn process_unary_operation(
|
||||
.pop_arg()
|
||||
.ok_or_else(|| EvalError::NotEnoughArgs(2, String::from("recordMap"), pos_op))?;
|
||||
|
||||
if let Term::Record(rec) = *t {
|
||||
if let Term::Record(rec, attr) = *t {
|
||||
let mut shared_env = Environment::new();
|
||||
let f_as_var = f.body.closurize(&mut env, f.env);
|
||||
|
||||
@ -605,7 +605,7 @@ fn process_unary_operation(
|
||||
.collect();
|
||||
|
||||
Ok(Closure {
|
||||
body: RichTerm::new(Term::Record(rec), pos_op_inh),
|
||||
body: RichTerm::new(Term::Record(rec, attr), pos_op_inh),
|
||||
env: shared_env,
|
||||
})
|
||||
} else {
|
||||
@ -646,7 +646,7 @@ fn process_unary_operation(
|
||||
}
|
||||
|
||||
match *t {
|
||||
Term::Record(map) if !map.is_empty() => {
|
||||
Term::Record(map, _) if !map.is_empty() => {
|
||||
let terms = map.into_iter().map(|(_, t)| t);
|
||||
Ok(seq_terms(terms, env, pos_op))
|
||||
}
|
||||
@ -1223,11 +1223,12 @@ fn process_binary_operation(
|
||||
}
|
||||
.closurize(&mut new_env, env1);
|
||||
|
||||
// Convert the record to the function `fun l x => contract & x`.
|
||||
// Convert the record to the function `fun l x => x & contract`. The merge
|
||||
// is done in contract mode.
|
||||
let body = mk_fun!(
|
||||
"_l",
|
||||
"x",
|
||||
mk_term::op2(BinaryOp::Merge(), closurized, mk_term::var("x"))
|
||||
mk_term::op2(BinaryOp::MergeContract(), mk_term::var("x"), closurized)
|
||||
)
|
||||
.with_pos(pos1.into_inherited());
|
||||
|
||||
@ -1520,14 +1521,14 @@ fn process_binary_operation(
|
||||
|
||||
BinaryOp::DynAccess() => {
|
||||
if let Term::Str(id) = *t1 {
|
||||
if let Term::Record(mut static_map) = *t2 {
|
||||
if let Term::Record(mut static_map, attrs) = *t2 {
|
||||
match static_map.remove(&Ident(id.clone())) {
|
||||
Some(e) => Ok(Closure { body: e, env: env2 }),
|
||||
None => Err(EvalError::FieldMissing(
|
||||
id,
|
||||
String::from("(.$)"),
|
||||
RichTerm {
|
||||
term: Box::new(Term::Record(static_map)),
|
||||
term: Box::new(Term::Record(static_map, attrs)),
|
||||
pos: pos2,
|
||||
},
|
||||
pos_op,
|
||||
@ -1562,12 +1563,12 @@ fn process_binary_operation(
|
||||
.ok_or_else(|| EvalError::NotEnoughArgs(3, String::from("$[ .. ]"), pos_op))?;
|
||||
|
||||
if let Term::Str(id) = *t1 {
|
||||
if let Term::Record(mut static_map) = *t2 {
|
||||
if let Term::Record(mut static_map, attrs) = *t2 {
|
||||
let as_var = clos.body.closurize(&mut env2, clos.env);
|
||||
match static_map.insert(Ident(id.clone()), as_var) {
|
||||
Some(_) => Err(EvalError::Other(format!("$[ .. ]: tried to extend record with the field {}, but it already exists", id), pos_op)),
|
||||
None => Ok(Closure {
|
||||
body: Term::Record(static_map).into(),
|
||||
body: Term::Record(static_map, attrs).into(),
|
||||
env: env2,
|
||||
}),
|
||||
}
|
||||
@ -1596,19 +1597,19 @@ fn process_binary_operation(
|
||||
}
|
||||
BinaryOp::DynRemove() => {
|
||||
if let Term::Str(id) = *t1 {
|
||||
if let Term::Record(mut static_map) = *t2 {
|
||||
if let Term::Record(mut static_map, attrs) = *t2 {
|
||||
match static_map.remove(&Ident(id.clone())) {
|
||||
None => Err(EvalError::FieldMissing(
|
||||
id,
|
||||
String::from("(-$)"),
|
||||
RichTerm {
|
||||
term: Box::new(Term::Record(static_map)),
|
||||
term: Box::new(Term::Record(static_map, attrs)),
|
||||
pos: pos2,
|
||||
},
|
||||
pos_op,
|
||||
)),
|
||||
Some(_) => Ok(Closure {
|
||||
body: RichTerm::new(Term::Record(static_map), pos_op_inh),
|
||||
body: RichTerm::new(Term::Record(static_map, attrs), pos_op_inh),
|
||||
env: env2,
|
||||
}),
|
||||
}
|
||||
@ -1637,7 +1638,7 @@ fn process_binary_operation(
|
||||
}
|
||||
BinaryOp::HasField() => {
|
||||
if let Term::Str(id) = *t1 {
|
||||
if let Term::Record(static_map) = *t2 {
|
||||
if let Term::Record(static_map, _) = *t2 {
|
||||
Ok(Closure::atomic_closure(RichTerm::new(
|
||||
Term::Bool(static_map.contains_key(&Ident(id))),
|
||||
pos_op_inh,
|
||||
@ -1731,7 +1732,7 @@ fn process_binary_operation(
|
||||
},
|
||||
)),
|
||||
},
|
||||
BinaryOp::Merge() => merge(
|
||||
BinaryOp::Merge() | BinaryOp::MergeContract() => merge(
|
||||
RichTerm {
|
||||
term: t1,
|
||||
pos: pos1,
|
||||
@ -1743,6 +1744,11 @@ fn process_binary_operation(
|
||||
},
|
||||
env2,
|
||||
pos_op,
|
||||
if b_op == BinaryOp::Merge() {
|
||||
MergeMode::Standard
|
||||
} else {
|
||||
MergeMode::Contract
|
||||
},
|
||||
),
|
||||
BinaryOp::Hash() => {
|
||||
let mk_err_fst = |t1| {
|
||||
@ -2214,7 +2220,7 @@ fn eq(env: &mut Environment, c1: Closure, c2: Closure) -> EqResult {
|
||||
(Term::Lbl(l1), Term::Lbl(l2)) => EqResult::Bool(l1 == l2),
|
||||
(Term::Sym(s1), Term::Sym(s2)) => EqResult::Bool(s1 == s2),
|
||||
(Term::Enum(id1), Term::Enum(id2)) => EqResult::Bool(id1 == id2),
|
||||
(Term::Record(m1), Term::Record(m2)) => {
|
||||
(Term::Record(m1, _), Term::Record(m2, _)) => {
|
||||
let (left, center, right) = merge::hashmap::split(m1, m2);
|
||||
|
||||
if !left.is_empty() || !right.is_empty() {
|
||||
|
@ -171,7 +171,8 @@ fn record_terms() {
|
||||
(Ident::from("c"), Num(3.).into())
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
.collect(),
|
||||
Default::default()
|
||||
)
|
||||
.into()
|
||||
);
|
||||
@ -188,7 +189,8 @@ fn record_terms() {
|
||||
(Ident::from("d"), Num(42.).into()),
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
.collect(),
|
||||
Default::default(),
|
||||
)
|
||||
),
|
||||
mk_app!(mk_term::op1(UnaryOp::Ite(), Num(4.)), Num(5.), Num(6.))
|
||||
|
@ -62,10 +62,10 @@ pub fn elaborate_field_path(
|
||||
FieldPathElem::Ident(id) => {
|
||||
let mut map = HashMap::new();
|
||||
map.insert(id, acc);
|
||||
Term::Record(map).into()
|
||||
Term::Record(map, Default::default()).into()
|
||||
}
|
||||
FieldPathElem::Expr(exp) => {
|
||||
let empty = Term::Record(HashMap::new());
|
||||
let empty = Term::Record(HashMap::new(), Default::default());
|
||||
mk_app!(mk_term::op2(BinaryOp::DynExtend(), exp, empty), acc)
|
||||
}
|
||||
});
|
||||
@ -98,12 +98,13 @@ where
|
||||
(FieldPathElem::Expr(e), t) => dynamic_fields.push((e, t)),
|
||||
});
|
||||
|
||||
dynamic_fields
|
||||
.into_iter()
|
||||
.fold(Term::RecRecord(static_map), |rec, field| {
|
||||
dynamic_fields.into_iter().fold(
|
||||
Term::RecRecord(static_map, Default::default()),
|
||||
|rec, field| {
|
||||
let (id_t, t) = field;
|
||||
Term::App(mk_term::op2(BinaryOp::DynExtend(), id_t, rec), t)
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Make a span from parser byte offsets.
|
||||
|
@ -155,7 +155,7 @@ impl REPL for REPLImpl {
|
||||
|
||||
// Check that the entry is a record, which is a precondition of transform_inner
|
||||
match term.as_ref() {
|
||||
Term::Record(_) | Term::RecRecord(_) => (),
|
||||
Term::Record(..) | Term::RecRecord(..) => (),
|
||||
_ => {
|
||||
return Err(Error::EvalError(EvalError::Other(
|
||||
String::from("load: expected a record"),
|
||||
|
@ -190,7 +190,7 @@ fn write_query_result_<R: QueryPrinter>(
|
||||
fields.sort();
|
||||
renderer.write_fields(out, fields.into_iter())
|
||||
}
|
||||
Term::Record(_) | Term::RecRecord(_) => renderer.write_metadata(out, "value", "{}"),
|
||||
Term::Record(..) | Term::RecRecord(..) => renderer.write_metadata(out, "value", "{}"),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
@ -250,7 +250,7 @@ fn write_query_result_<R: QueryPrinter>(
|
||||
.iter()
|
||||
.try_for_each(|rt| write_fields(out, renderer, rt.as_ref()))?;
|
||||
}
|
||||
t @ Term::Record(_) | t @ Term::RecRecord(_) => {
|
||||
t @ Term::Record(..) | t @ Term::RecRecord(..) => {
|
||||
writeln!(out, "No metadata found for this value.")?;
|
||||
write_fields(out, renderer, &t)?;
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ pub fn repl() -> Result<(), InitError> {
|
||||
let cmd = line.chars().skip(1).collect::<String>().parse::<Command>();
|
||||
let result = match cmd {
|
||||
Ok(Command::Load(path)) => repl.load(&path).map(|term| match term.as_ref() {
|
||||
Term::Record(map) | Term::RecRecord(map) => {
|
||||
Term::Record(map, _) | Term::RecRecord(map, _) => {
|
||||
println!("Loaded {} symbol(s) in the environment.", map.len())
|
||||
}
|
||||
_ => (),
|
||||
|
@ -1,7 +1,7 @@
|
||||
//! Serialization of an evaluated program to various data format.
|
||||
use crate::error::SerializationError;
|
||||
use crate::identifier::Ident;
|
||||
use crate::term::{MetaValue, RichTerm, Term};
|
||||
use crate::term::{MetaValue, RecordAttrs, RichTerm, Term};
|
||||
use serde::de::{Deserialize, Deserializer};
|
||||
use serde::ser::{Error, Serialize, SerializeMap, Serializer};
|
||||
use std::collections::HashMap;
|
||||
@ -92,7 +92,11 @@ where
|
||||
|
||||
/// Serializer for a record. Serialize fields in alphabetical order to get a deterministic output
|
||||
/// (by default, `HashMap`'s randomness implies a randomized order of fields in the output).
|
||||
pub fn serialize_record<S>(map: &HashMap<Ident, RichTerm>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
pub fn serialize_record<S>(
|
||||
map: &HashMap<Ident, RichTerm>,
|
||||
_attrs: &RecordAttrs,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
@ -107,6 +111,17 @@ where
|
||||
map_ser.end()
|
||||
}
|
||||
|
||||
/// Deserialize for a record. Required to set the record attributes to default.
|
||||
pub fn deserialize_record<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<(HashMap<Ident, RichTerm>, RecordAttrs), D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let map: HashMap<Ident, RichTerm> = HashMap::deserialize(deserializer)?;
|
||||
Ok((map, Default::default()))
|
||||
}
|
||||
|
||||
impl Serialize for RichTerm {
|
||||
/// Serialize the underlying term.
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
@ -145,7 +160,7 @@ pub fn validate(format: ExportFormat, t: &RichTerm) -> Result<(), SerializationE
|
||||
Null if format == ExportFormat::Json || format == ExportFormat::Yaml => Ok(()),
|
||||
Null => Err(SerializationError::UnsupportedNull(format, t.clone())),
|
||||
Bool(_) | Num(_) | Str(_) | Enum(_) => Ok(()),
|
||||
Record(map) | RecRecord(map) => {
|
||||
Record(map, _) => {
|
||||
map.iter().try_for_each(|(_, t)| validate(format, t))?;
|
||||
Ok(())
|
||||
}
|
||||
|
57
src/term.rs
57
src/term.rs
@ -35,8 +35,6 @@ use std::fmt;
|
||||
#[serde(untagged)]
|
||||
pub enum Term {
|
||||
/// The null value.
|
||||
// #[serde(serialize_with = "crate::serialize::serialize_null")]
|
||||
// #[serde(deserialize_with = "crate::serialize::deserialize_null")]
|
||||
Null,
|
||||
/// A boolean value.
|
||||
Bool(bool),
|
||||
@ -77,10 +75,11 @@ pub enum Term {
|
||||
|
||||
/// A record, mapping identifiers to terms.
|
||||
#[serde(serialize_with = "crate::serialize::serialize_record")]
|
||||
Record(HashMap<Ident, RichTerm>),
|
||||
#[serde(deserialize_with = "crate::serialize::deserialize_record")]
|
||||
Record(HashMap<Ident, RichTerm>, RecordAttrs),
|
||||
/// A recursive record, where the fields can reference each others.
|
||||
#[serde(skip)]
|
||||
RecRecord(HashMap<Ident, RichTerm>),
|
||||
RecRecord(HashMap<Ident, RichTerm>, RecordAttrs),
|
||||
/// A switch construct. The evaluation is done by the corresponding unary operator, but we
|
||||
/// still need this one for typechecking.
|
||||
Switch(
|
||||
@ -147,6 +146,25 @@ pub enum Term {
|
||||
ResolvedImport(FileId),
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
||||
pub struct RecordAttrs {
|
||||
pub open: bool,
|
||||
}
|
||||
|
||||
impl Default for RecordAttrs {
|
||||
fn default() -> Self {
|
||||
RecordAttrs { open: false }
|
||||
}
|
||||
}
|
||||
|
||||
impl RecordAttrs {
|
||||
pub fn merge(attrs1: RecordAttrs, attrs2: RecordAttrs) -> RecordAttrs {
|
||||
RecordAttrs {
|
||||
open: attrs1.open || attrs2.open,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
|
||||
pub enum MergePriority {
|
||||
Default,
|
||||
@ -282,7 +300,7 @@ impl Term {
|
||||
func(def)
|
||||
}
|
||||
}
|
||||
Record(ref mut static_map) | RecRecord(ref mut static_map) => {
|
||||
Record(ref mut static_map, _) | RecRecord(ref mut static_map, _) => {
|
||||
static_map.iter_mut().for_each(|e| {
|
||||
let (_, t) = e;
|
||||
func(t);
|
||||
@ -337,7 +355,7 @@ impl Term {
|
||||
Term::Fun(_, _) => Some("Fun"),
|
||||
Term::Lbl(_) => Some("Label"),
|
||||
Term::Enum(_) => Some("Enum"),
|
||||
Term::Record(_) | Term::RecRecord(_) => Some("Record"),
|
||||
Term::Record(..) | Term::RecRecord(..) => Some("Record"),
|
||||
Term::List(_) => Some("List"),
|
||||
Term::Sym(_) => Some("Sym"),
|
||||
Term::Wrapped(_, _) => Some("Wrapped"),
|
||||
@ -380,7 +398,7 @@ impl Term {
|
||||
Term::Fun(_, _) => String::from("<func>"),
|
||||
Term::Lbl(_) => String::from("<label>"),
|
||||
Term::Enum(Ident(s)) => format!("`{}", s),
|
||||
Term::Record(_) | Term::RecRecord(_) => String::from("{ ... }"),
|
||||
Term::Record(..) | Term::RecRecord(..) => String::from("{ ... }"),
|
||||
Term::List(_) => String::from("[ ... ]"),
|
||||
Term::Sym(_) => String::from("<sym>"),
|
||||
Term::Wrapped(_, _) => String::from("<wrapped>"),
|
||||
@ -451,7 +469,7 @@ impl Term {
|
||||
| Term::Fun(_, _)
|
||||
| Term::Lbl(_)
|
||||
| Term::Enum(_)
|
||||
| Term::Record(_)
|
||||
| Term::Record(..)
|
||||
| Term::List(_)
|
||||
| Term::Sym(_) => true,
|
||||
Term::Let(_, _, _)
|
||||
@ -467,12 +485,12 @@ impl Term {
|
||||
| Term::Import(_)
|
||||
| Term::ResolvedImport(_)
|
||||
| Term::StrChunks(_)
|
||||
| Term::RecRecord(_) => false,
|
||||
| Term::RecRecord(..) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine if a term is an enriched value.
|
||||
pub fn is_enriched(&self) -> bool {
|
||||
/// Determine if a term is a metavalue.
|
||||
pub fn is_metavalue(&self) -> bool {
|
||||
matches!(self, Term::MetaValue(..))
|
||||
}
|
||||
|
||||
@ -490,7 +508,7 @@ impl Term {
|
||||
| Term::Enum(_)
|
||||
| Term::Sym(_) => true,
|
||||
Term::Let(_, _, _)
|
||||
| Term::Record(_)
|
||||
| Term::Record(..)
|
||||
| Term::List(_)
|
||||
| Term::Fun(_, _)
|
||||
| Term::App(_, _)
|
||||
@ -505,7 +523,7 @@ impl Term {
|
||||
| Term::Import(_)
|
||||
| Term::ResolvedImport(_)
|
||||
| Term::StrChunks(_)
|
||||
| Term::RecRecord(_) => false,
|
||||
| Term::RecRecord(..) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -721,6 +739,9 @@ pub enum BinaryOp {
|
||||
ListElemAt(),
|
||||
/// The merge operator (see the [merge module](../merge/index.html)).
|
||||
Merge(),
|
||||
/// The merge operator in contract mode (see the [merge module](../merge/index.html)).
|
||||
MergeContract(),
|
||||
|
||||
/// Hash a string.
|
||||
Hash(),
|
||||
/// Serialize a value to a string.
|
||||
@ -945,7 +966,7 @@ impl RichTerm {
|
||||
state,
|
||||
)
|
||||
}
|
||||
Term::Record(map) => {
|
||||
Term::Record(map, attrs) => {
|
||||
// The annotation on `map_res` uses Result's corresponding trait to convert from
|
||||
// Iterator<Result> to a Result<Iterator>
|
||||
let map_res: Result<HashMap<Ident, RichTerm>, E> = map
|
||||
@ -955,13 +976,13 @@ impl RichTerm {
|
||||
.collect();
|
||||
f(
|
||||
RichTerm {
|
||||
term: Box::new(Term::Record(map_res?)),
|
||||
term: Box::new(Term::Record(map_res?, attrs)),
|
||||
pos,
|
||||
},
|
||||
state,
|
||||
)
|
||||
}
|
||||
Term::RecRecord(map) => {
|
||||
Term::RecRecord(map, attrs) => {
|
||||
// The annotation on `map_res` uses Result's corresponding trait to convert from
|
||||
// Iterator<Result> to a Result<Iterator>
|
||||
let map_res: Result<HashMap<Ident, RichTerm>, E> = map
|
||||
@ -971,7 +992,7 @@ impl RichTerm {
|
||||
.collect();
|
||||
f(
|
||||
RichTerm {
|
||||
term: Box::new(Term::RecRecord(map_res?)),
|
||||
term: Box::new(Term::RecRecord(map_res?, attrs)),
|
||||
pos,
|
||||
},
|
||||
state,
|
||||
@ -1132,7 +1153,7 @@ pub mod make {
|
||||
$(
|
||||
map.insert($id.into(), $body.into());
|
||||
)*
|
||||
$crate::term::RichTerm::from($crate::term::Term::Record(map))
|
||||
$crate::term::RichTerm::from($crate::term::Term::Record(map, Default::default()))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ pub mod share_normal_form {
|
||||
pub fn transform_one(rt: RichTerm) -> RichTerm {
|
||||
let RichTerm { term, pos } = rt;
|
||||
match *term {
|
||||
Term::Record(map) => {
|
||||
Term::Record(map, attrs) => {
|
||||
let mut bindings = Vec::with_capacity(map.len());
|
||||
|
||||
let map = map
|
||||
@ -75,9 +75,9 @@ pub mod share_normal_form {
|
||||
})
|
||||
.collect();
|
||||
|
||||
with_bindings(Term::Record(map), bindings, pos)
|
||||
with_bindings(Term::Record(map, attrs), bindings, pos)
|
||||
}
|
||||
Term::RecRecord(map) => {
|
||||
Term::RecRecord(map, attrs) => {
|
||||
// When a recursive record is evaluated, all fields need to be turned to closures
|
||||
// anyway (see the corresponding case in `eval::eval()`), which is what the share
|
||||
// normal form transformation does. This is why the test is more lax here than for
|
||||
@ -100,7 +100,7 @@ pub mod share_normal_form {
|
||||
})
|
||||
.collect();
|
||||
|
||||
with_bindings(Term::RecRecord(map), bindings, pos)
|
||||
with_bindings(Term::RecRecord(map, attrs), bindings, pos)
|
||||
}
|
||||
Term::List(ts) => {
|
||||
let mut bindings = Vec::with_capacity(ts.len());
|
||||
|
@ -372,7 +372,7 @@ impl<'a> Envs<'a> {
|
||||
let RichTerm { term, pos } = rt;
|
||||
|
||||
match term.as_ref() {
|
||||
Term::Record(bindings) | Term::RecRecord(bindings) => {
|
||||
Term::Record(bindings, _) | Term::RecRecord(bindings, _) => {
|
||||
for (id, t) in bindings {
|
||||
let tyw: TypeWrapper =
|
||||
apparent_type(t.as_ref(), Some(&Envs::from_global(env))).into();
|
||||
@ -605,10 +605,10 @@ fn type_check_(
|
||||
unify(state, strict, ty, mk_tyw_enum!(id.clone(), row))
|
||||
.map_err(|err| err.into_typecheck_err(state, rt.pos))
|
||||
}
|
||||
Term::Record(stat_map) | Term::RecRecord(stat_map) => {
|
||||
Term::Record(stat_map, _) | Term::RecRecord(stat_map, _) => {
|
||||
// For recursive records, we look at the apparent type of each field and bind it in
|
||||
// env before actually typechecking the content of fields
|
||||
if let Term::RecRecord(_) = t.as_ref() {
|
||||
if let Term::RecRecord(..) = t.as_ref() {
|
||||
for (id, rt) in stat_map {
|
||||
let tyw = binding_type(rt.as_ref(), &envs, state.table, strict);
|
||||
envs.insert(id.clone(), tyw);
|
||||
@ -635,7 +635,7 @@ fn type_check_(
|
||||
// In the case of a recursive record, new types (either type variables or
|
||||
// annotations) have already be determined and put in the typing
|
||||
// environment, and we need to use the same.
|
||||
let ty = if let Term::RecRecord(_) = t.as_ref() {
|
||||
let ty = if let Term::RecRecord(..) = t.as_ref() {
|
||||
envs.get(&id).unwrap()
|
||||
} else {
|
||||
TypeWrapper::Ptr(new_var(state.table))
|
||||
@ -1781,7 +1781,7 @@ pub fn get_bop_type(
|
||||
)
|
||||
}
|
||||
// Dyn -> Dyn -> Dyn
|
||||
BinaryOp::Merge() => (
|
||||
BinaryOp::Merge() | BinaryOp::MergeContract() => (
|
||||
mk_typewrapper::dynamic(),
|
||||
mk_typewrapper::dynamic(),
|
||||
mk_typewrapper::dynamic(),
|
||||
|
@ -231,7 +231,7 @@ impl Types {
|
||||
"x",
|
||||
mk_app!(
|
||||
mk_term::op1(UnaryOp::Switch(true), mk_term::var("x")),
|
||||
Term::Record(map),
|
||||
Term::Record(map, Default::default()),
|
||||
Term::Bool(false)
|
||||
)
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user