1
1
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:
Yann Hamdaoui 2021-08-04 16:30:08 +02:00
parent 7199f1831d
commit 4add0815d7
14 changed files with 161 additions and 79 deletions

View File

@ -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()

View File

@ -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

View File

@ -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,
})
}

View File

@ -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() {

View File

@ -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.))

View File

@ -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.

View File

@ -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"),

View File

@ -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)?;
}

View File

@ -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())
}
_ => (),

View File

@ -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(())
}

View File

@ -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()))
}
};
}

View File

@ -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());

View File

@ -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(),

View File

@ -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)
)
)