1
1
mirror of https://github.com/tweag/nickel.git synced 2024-10-06 08:07:37 +03:00

Make rustfmt work in match_sharedterm! (#1677)

* Make rustfmt work in match_sharedterm!

macros can have syntax that rustfmt doens't know how to deal with, so it
only triggers on macros that
1. are called with `()` or `[]` delimiters (not `{}`)
2. contain only valid rust syntax

our match_sharedterm! failed on both those counts, so this commit fixes
that. we move the `else` clause back inside the match statement as a
wildcard arm, and use the `match` keyword so that rustfmt understands
how we want the code formatted

* Update core/src/term/mod.rs

Co-authored-by: Yann Hamdaoui <yann.hamdaoui@tweag.io>

---------

Co-authored-by: Yann Hamdaoui <yann.hamdaoui@tweag.io>
This commit is contained in:
Taeer Bar-Yam 2023-10-13 12:46:12 -04:00 committed by GitHub
parent 7e4ecd908c
commit 859293a3e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 1008 additions and 989 deletions

View File

@ -407,7 +407,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
// a definition, we complete the error with the additional information of where
// it was accessed:
let Closure { body, env } = self.cache.get(idx);
let body = match_sharedterm! {body.term, with {
let body = match_sharedterm!(match (body.term) {
Term::RuntimeError(EvalError::MissingFieldDef {
id,
metadata,
@ -422,10 +422,8 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
}),
pos,
),
} else {
body
}
};
_ => body,
});
Closure { body, env }
}
@ -910,24 +908,28 @@ pub fn env_add_record<C: Cache>(
env: &mut Environment,
closure: Closure,
) -> Result<(), EnvBuildError> {
match_sharedterm! {closure.body.term, with {
match_sharedterm!(match (closure.body.term) {
Term::Record(record) | Term::RecRecord(record, ..) => {
let ext = record.fields.into_iter().filter_map(|(id, field)| {
field.value.map(|value|
field.value.map(|value| {
(
id.ident(),
cache.add(
Closure { body: value, env: closure.env.clone() },
BindingType::Normal
Closure {
body: value,
env: closure.env.clone(),
},
BindingType::Normal,
),
))
)
})
});
env.extend(ext);
Ok(())
},
} else Err(EnvBuildError::NotARecord(closure.body))
}
_ => Err(EnvBuildError::NotARecord(closure.body)),
})
}
/// Bind a closure in an environment.

View File

@ -292,16 +292,14 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
Err(mk_type_error!("unary negation", "Bool"))
}
}
UnaryOp::Blame() => match_sharedterm! { t, with {
Term::Lbl(label) => Err(
EvalError::BlameError {
UnaryOp::Blame() => match_sharedterm!(match (t) {
Term::Lbl(label) => Err(EvalError::BlameError {
evaluated_arg: label.get_evaluated_arg(&self.cache),
label,
call_stack: std::mem::take(&mut self.call_stack),
}),
} else
Err(mk_type_error!("blame", "Label"))
},
_ => Err(mk_type_error!("blame", "Label")),
}),
UnaryOp::Embed(_id) => {
if let Term::Enum(_) = &*t {
Ok(Closure::atomic_closure(RichTerm {
@ -362,7 +360,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
Err(mk_type_error!("match", "Enum", 2))
}
}
UnaryOp::ChangePolarity() => match_sharedterm! {t, with {
UnaryOp::ChangePolarity() => match_sharedterm!(match (t) {
Term::Lbl(l) => {
let mut l = l;
l.polarity = l.polarity.flip();
@ -371,10 +369,8 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
pos_op_inh,
)))
}
} else {
Err(mk_type_error!("chng_pol", "Label"))
}
},
_ => Err(mk_type_error!("chng_pol", "Label")),
}),
UnaryOp::Pol() => {
if let Term::Lbl(l) = &*t {
Ok(Closure::atomic_closure(RichTerm::new(
@ -385,7 +381,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
Err(mk_type_error!("polarity", "Label"))
}
}
UnaryOp::GoDom() => match_sharedterm! {t, with {
UnaryOp::GoDom() => match_sharedterm!(match (t) {
Term::Lbl(l) => {
let mut l = l;
l.path.push(ty_path::Elem::Domain);
@ -394,11 +390,9 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
pos_op_inh,
)))
}
} else {
Err(mk_type_error!("go_dom", "Label"))
}
},
UnaryOp::GoCodom() => match_sharedterm! {t, with {
_ => Err(mk_type_error!("go_dom", "Label")),
}),
UnaryOp::GoCodom() => match_sharedterm!(match (t) {
Term::Lbl(l) => {
let mut l = l;
l.path.push(ty_path::Elem::Codomain);
@ -407,11 +401,9 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
pos_op_inh,
)))
}
} else {
Err(mk_type_error!("go_codom", "Label"))
}
},
UnaryOp::GoArray() => match_sharedterm! {t, with {
_ => Err(mk_type_error!("go_codom", "Label")),
}),
UnaryOp::GoArray() => match_sharedterm!(match (t) {
Term::Lbl(l) => {
let mut l = l;
l.path.push(ty_path::Elem::Array);
@ -420,11 +412,9 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
pos_op_inh,
)))
}
} else {
Err(mk_type_error!("go_array", "Label"))
}
},
UnaryOp::GoDict() => match_sharedterm! {t, with {
_ => Err(mk_type_error!("go_array", "Label")),
}),
UnaryOp::GoDict() => match_sharedterm!(match (t) {
Term::Lbl(l) => {
let mut l = l;
l.path.push(ty_path::Elem::Dict);
@ -433,10 +423,8 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
pos_op_inh,
)))
}
} else {
Err(mk_type_error!("go_dict", "Label"))
}
},
_ => Err(mk_type_error!("go_dict", "Label")),
}),
UnaryOp::StaticAccess(id) => {
if let Term::Record(record) = &*t {
// We have to apply potentially pending contracts. Right now, this
@ -481,14 +469,15 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
))
}
}
UnaryOp::FieldsOf() => match_sharedterm! {t, with {
UnaryOp::FieldsOf() => match_sharedterm!(match (t) {
Term::Record(record) => {
let mut fields: Vec<String> = record
.fields
.into_iter()
// Ignore optional fields without definitions.
.filter_map(|(id, field)|
(!field.is_empty_optional()).then(|| id.to_string()))
.filter_map(|(id, field)| {
(!field.is_empty_optional()).then(|| id.to_string())
})
.collect();
fields.sort();
let terms = fields.into_iter().map(mk_term::string).collect();
@ -498,11 +487,9 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
pos_op_inh,
)))
}
} else {
Err(mk_type_error!("fields", "Record"))
}
},
UnaryOp::ValuesOf() => match_sharedterm! {t, with {
_ => Err(mk_type_error!("fields", "Record")),
}),
UnaryOp::ValuesOf() => match_sharedterm!(match (t) {
Term::Record(record) => {
let mut values = record
.into_iter_without_opts()
@ -515,23 +502,18 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
Ok(Closure {
// TODO: once sure that the Record is properly closurized, we can safely
// assume that the extracted array here is, in turn, also closuried.
body: RichTerm::new(
Term::Array(terms, ArrayAttrs::default()),
pos_op_inh
),
body: RichTerm::new(Term::Array(terms, ArrayAttrs::default()), pos_op_inh),
env,
})
}
} else {
Err(mk_type_error!("values", "Record"))
}
},
_ => Err(mk_type_error!("values", "Record")),
}),
UnaryOp::ArrayMap() => {
let (f, ..) = self
.stack
.pop_arg(&self.cache)
.ok_or_else(|| EvalError::NotEnoughArgs(2, String::from("map"), pos_op))?;
match_sharedterm! {t, with {
match_sharedterm!(match (t) {
Term::Array(ts, attrs) => {
let mut shared_env = Environment::new();
let f_as_var = f.body.closurize(&mut self.cache, &mut env, f.env);
@ -548,10 +530,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
pos.into_inherited(),
);
RichTerm::new(
Term::App(f_as_var.clone(), t_with_ctrs),
pos_op_inh
)
RichTerm::new(Term::App(f_as_var.clone(), t_with_ctrs), pos_op_inh)
.closurize(&mut self.cache, &mut shared_env, env.clone())
})
.collect();
@ -559,15 +538,13 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
Ok(Closure {
body: RichTerm::new(
Term::Array(ts, attrs.contracts_cleared().closurized()),
pos_op_inh
pos_op_inh,
),
env: shared_env,
})
}
} else {
Err(mk_type_error!("map", "Array"))
}
}
_ => Err(mk_type_error!("map", "Array")),
})
}
UnaryOp::ArrayGen() => {
let (f, _) = self
@ -628,7 +605,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
EvalError::NotEnoughArgs(2, String::from("record_map"), pos_op)
})?;
match_sharedterm! {t, with {
match_sharedterm!(match (t) {
Term::Record(record) => {
// While it's certainly possible to allow mapping over
// a record with a sealed tail, it's not entirely obvious
@ -642,7 +619,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
evaluated_arg: label.get_evaluated_arg(&self.cache),
label,
call_stack: std::mem::take(&mut self.call_stack),
})
});
}
let mut shared_env = Environment::new();
@ -654,32 +631,31 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
.fields
.into_iter()
.filter(|(_, field)| !field.is_empty_optional())
.map_values_closurize(&mut self.cache, &mut shared_env, &env,
.map_values_closurize(
&mut self.cache,
&mut shared_env,
&env,
|id, t| {
let pos = t.pos.into_inherited();
mk_app!(
f_as_var.clone(),
mk_term::string(id.label()), t
)
mk_app!(f_as_var.clone(), mk_term::string(id.label()), t)
.with_pos(pos)
}
},
)
.map_err(|missing_field_err|
missing_field_err.into_eval_err(pos, pos_op))?;
.map_err(|missing_field_err| {
missing_field_err.into_eval_err(pos, pos_op)
})?;
Ok(Closure {
body: RichTerm::new(
Term::Record(RecordData { fields, ..record }),
pos_op_inh
pos_op_inh,
),
env: shared_env,
})
}
} else {
Err(mk_type_error!("record_map", "Record", 1))
}
}
_ => Err(mk_type_error!("record_map", "Record", 1)),
})
}
UnaryOp::Seq() => self
.stack
@ -881,21 +857,20 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
}
}
UnaryOp::ToStr() => {
let result = match_sharedterm! {t, with {
let result = match_sharedterm!(match (t) {
Term::Num(n) => Ok(Term::Str(format!("{}", n.to_sci()).into())),
Term::Str(s) => Ok(Term::Str(s)),
Term::Bool(b) => Ok(Term::Str(b.to_string().into())),
Term::Enum(id) => Ok(Term::Str(id.into())),
Term::Null => Ok(Term::Str("null".into())),
} else {
Err(EvalError::Other(
_ => Err(EvalError::Other(
format!(
"to_string: can't convert an argument of type {} to string",
t.type_of().unwrap()
),
pos,
))
}}?;
)),
})?;
Ok(Closure::atomic_closure(RichTerm::new(result, pos_op_inh)))
}
@ -1036,33 +1011,36 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
.with_pos(pos)
}
match_sharedterm! {t,
with {
match_sharedterm!(match (t) {
Term::Record(record) if !record.fields.is_empty() => {
let mut shared_env = Environment::new();
let fields = record.fields
let fields = record
.fields
.into_iter()
.filter(|(_, field)| {
!(field.is_empty_optional()
|| (ignore_not_exported && field.metadata.not_exported))
})
.map_values_closurize(&mut self.cache, &mut shared_env, &env,
.map_values_closurize(
&mut self.cache,
&mut shared_env,
&env,
|_, value| {
mk_term::op1(UnaryOp::Force { ignore_not_exported }, value)
}
mk_term::op1(
UnaryOp::Force {
ignore_not_exported,
},
value,
)
},
)
.map_err(|e| e.into_eval_err(pos, pos_op))?;
let terms = fields
.clone()
.into_values()
.map(|field| {
field
.value
.expect(
let terms = fields.clone().into_values().map(|field| {
field.value.expect(
"map_values_closurize ensures that values without a \
definition throw a MissingFieldDefError"
definition throw a MissingFieldDefError",
)
});
@ -1075,21 +1053,27 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
body: seq_terms(terms, pos_op, cont),
env: shared_env,
})
},
}
Term::Array(ts, attrs) if !ts.is_empty() => {
let mut shared_env = Environment::new();
let ts = ts
.into_iter()
.map(|t| {
mk_term::op1(
UnaryOp::Force { ignore_not_exported },
UnaryOp::Force {
ignore_not_exported,
},
RuntimeContract::apply_all(
t,
attrs.pending_contracts.iter().cloned(),
pos.into_inherited(),
),
)
.closurize(&mut self.cache, &mut shared_env, env.clone())
.closurize(
&mut self.cache,
&mut shared_env,
env.clone(),
)
})
// It's important to collect here, otherwise the two usages below
// will each do their own .closurize(...) calls and end up with
@ -1105,32 +1089,29 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
env: shared_env,
})
}
} else Ok(Closure {
body: RichTerm { term : t, pos},
_ => Ok(Closure {
body: RichTerm { term: t, pos },
env
}),
})
}
}
UnaryOp::RecDefault() => {
Ok(RecPriority::Bottom.propagate_in_term(&mut self.cache, t, env, pos))
}
UnaryOp::RecForce() => {
Ok(RecPriority::Top.propagate_in_term(&mut self.cache, t, env, pos))
}
UnaryOp::RecordEmptyWithTail() => match_sharedterm! { t,
with {
UnaryOp::RecordEmptyWithTail() => match_sharedterm!(match (t) {
Term::Record(r) => {
let mut empty = RecordData::empty();
empty.sealed_tail = r.sealed_tail;
Ok(Closure {
body: RichTerm::new(Term::Record(empty), pos_op.into_inherited()),
env
env,
})
},
} else {
Err(mk_type_error!("record_empty_with_tail", "Record"))
}
},
_ => Err(mk_type_error!("record_empty_with_tail", "Record")),
}),
UnaryOp::Trace() => {
if let Term::Str(s) = &*t {
let _ = writeln!(self.trace, "std.trace: {s}");
@ -1145,18 +1126,17 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
.ok_or_else(|| EvalError::NotEnoughArgs(2, String::from("trace"), pos_op))
}
UnaryOp::LabelPushDiag() => {
match_sharedterm! {t, with {
match_sharedterm!(match (t) {
Term::Lbl(label) => {
let mut label = label;
label.push_diagnostic();
Ok(Closure {
body: RichTerm::new(Term::Lbl(label), pos),
env
env,
})
}
} else {
Err(mk_type_error!("label_push_diag", "Label"))
}}
_ => Err(mk_type_error!("label_push_diag", "Label")),
})
}
#[cfg(feature = "nix-experimental")]
UnaryOp::EvalNix() => {
@ -1574,8 +1554,8 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
Err(mk_type_error!("(>=)", "Number", 1, t1, pos1))
}
}
BinaryOp::GoField() => match_sharedterm! {t1, with {
Term::Str(field) => match_sharedterm! {t2, with {
BinaryOp::GoField() => match_sharedterm!(match (t1) {
Term::Str(field) => match_sharedterm!(match (t2) {
Term::Lbl(l) => {
let mut l = l;
l.path.push(ty_path::Elem::Field(field.into_inner().into()));
@ -1584,15 +1564,12 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
pos_op_inh,
)))
}
} else {
Err(mk_type_error!("go_field", "Label", 2, t2, pos2))
}
},
} else {
Err(mk_type_error!("go_field", "String", 1, t1, pos1))
}
},
BinaryOp::DynAccess() => match_sharedterm! {t1, with {
_ => Err(mk_type_error!("go_field", "Label", 2, t2, pos2)),
}),
_ => Err(mk_type_error!("go_field", "String", 1, t1, pos1)),
}),
BinaryOp::DynAccess() => {
match_sharedterm!(match (t1) {
Term::Str(id) => {
if let Term::Record(record) = &*t2 {
// We have to apply potential pending contracts. Right now, this
@ -1601,12 +1578,9 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
// contracts. There are several way to improve this, but this is left
// as future work.
let ident = LocIdent::from(&id);
match record
.get_value_with_ctrs(&ident)
.map_err(|missing_field_err| {
missing_field_err.into_eval_err(pos2, pos_op)
})?
{
match record.get_value_with_ctrs(&ident).map_err(
|missing_field_err| missing_field_err.into_eval_err(pos2, pos_op),
)? {
Some(value) => {
self.call_stack.enter_field(ident, pos2, value.pos, pos_op);
Ok(Closure {
@ -1615,16 +1589,16 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
})
}
None => match record.sealed_tail.as_ref() {
Some(t) if t.has_dyn_field(&id) =>
Some(t) if t.has_dyn_field(&id) => {
Err(EvalError::IllegalPolymorphicTailAccess {
action: IllegalPolymorphicTailAction::FieldAccess {
field: id.to_string()
field: id.to_string(),
},
evaluated_arg: t.label.get_evaluated_arg(&self.cache),
label: t.label.clone(),
call_stack: std::mem::take(&mut self.call_stack)
call_stack: std::mem::take(&mut self.call_stack),
})
}
),
_ => Err(EvalError::FieldMissing(
id.into_inner(),
String::from("(.$)"),
@ -1634,8 +1608,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
},
pos_op,
)),
}
},
}
} else {
// Not using mk_type_error! because of a non-uniform message
@ -1650,13 +1623,12 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
))
}
}
} else {
// This error should be impossible to trigger. The parser
// prevents a dynamic field access where the field name is not syntactically
// a string.
Err(mk_type_error!(".$", "String", 1, t1, pos1))
_ => Err(mk_type_error!(".$", "String", 1, t1, pos1)),
})
}
},
BinaryOp::DynExtend {
metadata,
pending_contracts,
@ -1664,78 +1636,90 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
op_kind,
} => {
if let Term::Str(id) = &*t1 {
match_sharedterm! {t2, with {
match_sharedterm!(match (t2) {
Term::Record(record) => {
let mut fields = record.fields;
// If a defined value is expected for this field, it must be
// provided as an additional argument, so we pop it from the stack
let value = if let RecordExtKind::WithValue = ext_kind {
let (value_closure, _) = self
.stack
.pop_arg(&self.cache)
.ok_or_else(|| EvalError::NotEnoughArgs(
3,
String::from("insert"),
pos_op
))?;
let (value_closure, _) =
self.stack.pop_arg(&self.cache).ok_or_else(|| {
EvalError::NotEnoughArgs(3, String::from("insert"), pos_op)
})?;
let as_var = value_closure.body
.closurize(&mut self.cache, &mut env2, value_closure.env);
let as_var = value_closure.body.closurize(
&mut self.cache,
&mut env2,
value_closure.env,
);
Some(as_var)
}
else {
} else {
None
};
match fields.insert(
LocIdent::from(id),
Field { value, metadata, pending_contracts }
Field {
value,
metadata,
pending_contracts,
},
) {
Some(t) if matches!(op_kind, RecordOpKind::ConsiderAllFields)
Some(t)
if matches!(op_kind, RecordOpKind::ConsiderAllFields)
|| !t.is_empty_optional() =>
Err(EvalError::Other(format!(
{
Err(EvalError::Other(
format!(
"record_insert: \
tried to extend a record with the field {id}, \
but it already exists"
), pos_op)),
),
pos_op,
))
}
_ => Ok(Closure {
body: Term::Record(RecordData { fields, ..record }).into(),
env: env2,
}),
}
}
} else {
Err(mk_type_error!("record_insert", "Record", 2, t2, pos2))
}
}
_ => Err(mk_type_error!("record_insert", "Record", 2, t2, pos2)),
})
} else {
Err(mk_type_error!("record_insert", "String", 1, t1, pos1))
}
}
BinaryOp::DynRemove(op_kind) => match_sharedterm! {t1, with {
Term::Str(id) => match_sharedterm! {t2, with {
BinaryOp::DynRemove(op_kind) => match_sharedterm!(match (t1) {
Term::Str(id) => match_sharedterm!(match (t2) {
Term::Record(record) => {
let mut fields = record.fields;
let fetched = fields.remove(&LocIdent::from(&id));
if fetched.is_none()
|| matches!((op_kind, fetched), (RecordOpKind::IgnoreEmptyOpt, Some(Field {
|| matches!(
(op_kind, fetched),
(
RecordOpKind::IgnoreEmptyOpt,
Some(Field {
value: None,
metadata: FieldMetadata { opt: true, ..},
metadata: FieldMetadata { opt: true, .. },
..
}))) {
})
)
)
{
match record.sealed_tail.as_ref() {
Some(t) if t.has_dyn_field(&id) =>
Some(t) if t.has_dyn_field(&id) => {
Err(EvalError::IllegalPolymorphicTailAccess {
action: IllegalPolymorphicTailAction::RecordRemove {
field: id.to_string()
field: id.to_string(),
},
evaluated_arg: t.label
.get_evaluated_arg(&self.cache),
evaluated_arg: t.label.get_evaluated_arg(&self.cache),
label: t.label.clone(),
call_stack: std::mem::take(&mut self.call_stack)
call_stack: std::mem::take(&mut self.call_stack),
})
}
),
_ => Err(EvalError::FieldMissing(
id.into_inner(),
String::from("record_remove"),
@ -1746,26 +1730,21 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
pos_op,
)),
}
}
else {
} else {
Ok(Closure {
body: RichTerm::new(
Term::Record(RecordData { fields, ..record }),
pos_op_inh
pos_op_inh,
),
env: env2,
})
}
}
} else {
Err(mk_type_error!("record_remove", "Record", 2, t2, pos2))
}
}
} else {
Err(mk_type_error!("record_remove", "String", 1, t1, pos1))
}
},
BinaryOp::HasField(op_kind) => match_sharedterm! {t1, with {
_ => Err(mk_type_error!("record_remove", "Record", 2, t2, pos2)),
}),
_ => Err(mk_type_error!("record_remove", "String", 1, t1, pos1)),
}),
BinaryOp::HasField(op_kind) => match_sharedterm!(match (t1) {
Term::Str(id) => {
if let Term::Record(record) = &*t2 {
Ok(Closure::atomic_closure(RichTerm::new(
@ -1779,23 +1758,23 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
Err(mk_type_error!("has_field", "Record", 2, t2, pos2))
}
}
} else {
Err(mk_type_error!("has_field", "String", 1, t1, pos1))
}
},
BinaryOp::ArrayConcat() => match_sharedterm! {t1,
with {
Term::Array(ts1, attrs1) => match_sharedterm! {t2,
with {
_ => Err(mk_type_error!("has_field", "String", 1, t1, pos1)),
}),
BinaryOp::ArrayConcat() => match_sharedterm!(match (t1) {
Term::Array(ts1, attrs1) => match_sharedterm!(match (t2) {
Term::Array(ts2, attrs2) => {
// NOTE: the [eval_closure] function in [eval] should've made sure
// that the array is closurized. We leave a debug_assert! here just
// in case something goes wrong in the future. If the assert failed,
// you may need to map closurize over `ts1` and `ts2`.
debug_assert!(attrs1.closurized,
"the left-hand side of ArrayConcat (@) is not closurized.");
debug_assert!(attrs2.closurized,
"the right-hand side of ArrayConcat (@) is not closurized.");
debug_assert!(
attrs1.closurized,
"the left-hand side of ArrayConcat (@) is not closurized."
);
debug_assert!(
attrs2.closurized,
"the right-hand side of ArrayConcat (@) is not closurized."
);
// NOTE: To avoid the extra Vec allocation, we could use
// Rc<[T]>::new_uninit_slice() and fill up the slice manually, but
@ -1808,9 +1787,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
// - Rc<[T]>::from_iter docs:
// https://doc.rust-lang.org/std/rc/struct.Rc.html#impl-FromIterator%3CT%3E
// - chain issue: https://github.com/rust-lang/rust/issues/63340
let mut ts: Vec<RichTerm> = Vec::with_capacity(
ts1.len() + ts2.len()
);
let mut ts: Vec<RichTerm> = Vec::with_capacity(ts1.len() + ts2.len());
let mut env = env1.clone();
// TODO: Is there a cheaper way to "merge" two environements?
@ -1831,13 +1808,13 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
// We use a vector of `Option` so that we can set the elements to
// remove to `None` and make a single pass at the end
// to retain the remaining ones.
let mut ctrs_right_sieve : Vec<_> = attrs2.pending_contracts.into_iter().map(Some).collect();
let mut ctrs_right_sieve: Vec<_> =
attrs2.pending_contracts.into_iter().map(Some).collect();
let mut ctrs_common = Vec::new();
// We basically compute the intersection (`ctr_common`),
// `ctrs_left - ctr_common`, and `ctrs_right - ctr_common`.
let ctrs_left_dedup : Vec<_> =
ctrs_left
let ctrs_left: Vec<_> = ctrs_left
.into_iter()
.filter(|ctr| {
// We don't deduplicate polymorphic contracts, because
@ -1851,15 +1828,10 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
initial_env: &self.initial_env,
};
// We check if there is a remaining contract in
// `ctrs_right_sieve` which matches `ctr`: in this case,
// `twin_index` will hold its index.
let twin_index = ctrs_right_sieve
.iter()
.filter_map(|ctr| ctr.as_ref())
.position(|other_ctr| {
other_ctr
.as_ref()
.map_or(false, |other_ctr| {
let envs_right = EvalEnvsRef {
eval_env: &env2,
initial_env: &self.initial_env,
@ -1872,36 +1844,31 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
&other_ctr.contract,
envs_right,
)
})
});
if let Some(index) = twin_index {
// unwrap(): we know that the contract at this index is
// `Some`, because all elements are initially some when
// creating `ctrs_right_sieve` and then we don't
// consider `None` values when computing a new `index`
// in the `position` above.
let common = ctrs_right_sieve[index].take().unwrap();
ctrs_common.push(common);
ctrs_right_sieve[index] = None;
false
}
else {
} else {
true
}
})
.collect();
let ctrs_right_dedup = ctrs_right_sieve.into_iter().flatten();
let ctrs_right = ctrs_right_sieve.into_iter().flatten();
ts.extend(ts1.into_iter().map(|t|
RuntimeContract::apply_all(t, ctrs_left_dedup.iter().cloned(), pos1)
ts.extend(ts1.into_iter().map(|t| {
RuntimeContract::apply_all(t, ctrs_left.iter().cloned(), pos1)
.closurize(&mut self.cache, &mut env, env1.clone())
));
}));
ts.extend(ts2.into_iter().map(|t|
RuntimeContract::apply_all(t, ctrs_right_dedup.clone(), pos2)
.closurize(&mut self.cache, &mut env, env2.clone())
));
ts.extend(ts2.into_iter().map(|t| {
RuntimeContract::apply_all(t, ctrs_right.clone(), pos2).closurize(
&mut self.cache,
&mut env,
env2.clone(),
)
}));
let attrs = ArrayAttrs {
closurized: true,
@ -1911,20 +1878,15 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
Ok(Closure {
body: RichTerm::new(
Term::Array(Array::new(Rc::from(ts)), attrs),
pos_op_inh
pos_op_inh,
),
env,
})
}
} else {
Err(mk_type_error!("(@)", "Array", 2, t2, pos2))
}
},
} else {
Err(mk_type_error!("(@)", "Array", 1, t1, pos1))
}
},
_ => Err(mk_type_error!("(@)", "Array", 2, t2, pos2)),
}),
_ => Err(mk_type_error!("(@)", "Array", 1, t1, pos1)),
}),
BinaryOp::ArrayElemAt() => match (&*t1, &*t2) {
(Term::Array(ts, attrs), Term::Num(n)) => {
let Ok(n_as_usize) = usize::try_from(n) else {
@ -2148,13 +2110,12 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
} = ctr;
// FIXME: use match?
let lbl = match_sharedterm! {t1, with {
Term::Lbl(lbl) => lbl
} else return Err(mk_type_error!("array_lazy_app_ctr", "Label", 1, t1, pos1))
};
let lbl = match_sharedterm!(match (t1) {
Term::Lbl(lbl) => lbl,
_ => return Err(mk_type_error!("array_lazy_app_ctr", "Label", 1, t1, pos1)),
});
match_sharedterm! {t2,
with {
match_sharedterm!(match (t2) {
Term::Array(ts, attrs) => {
let mut attrs = attrs;
let mut final_env = env2;
@ -2166,24 +2127,18 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
&mut attrs.pending_contracts,
&final_env,
RuntimeContract::new(contract, lbl),
&final_env
&final_env,
);
let array_with_ctr = Closure {
body: RichTerm::new(
Term::Array(
ts,
attrs,
),
pos2,
),
body: RichTerm::new(Term::Array(ts, attrs), pos2),
env: final_env,
};
Ok(array_with_ctr)
}
} else Err(mk_type_error!("array_lazy_app_ctr", "Array", 2, t2, pos2))
}
_ => Err(mk_type_error!("array_lazy_app_ctr", "Array", 2, t2, pos2)),
})
}
BinaryOp::RecordLazyAppCtr() => {
// The contract is expected to be of type `String -> Contract`: it takes the name
@ -2198,13 +2153,12 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
EvalError::NotEnoughArgs(3, String::from("record_lazy_app_ctr"), pos_op)
})?;
let label = match_sharedterm! {t1, with {
Term::Lbl(label) => label
} else return Err(mk_type_error!("record_lazy_app_ctr", "Label", 1, t1, pos1))
};
let label = match_sharedterm!(match (t1) {
Term::Lbl(label) => label,
_ => return Err(mk_type_error!("record_lazy_app_ctr", "Label", 1, t1, pos1)),
});
match_sharedterm! {t2,
with {
match_sharedterm!(match (t2) {
Term::Record(record_data) => {
// due to a limitation of `match_sharedterm`: see the macro's
// documentation
@ -2215,9 +2169,13 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
let pos = contract_term.pos;
mk_app!(
contract_term.clone(),
RichTerm::new(Term::Str(id.into()), id.pos))
RichTerm::new(Term::Str(id.into()), id.pos)
)
.with_pos(pos)
.closurize(&mut self.cache, &mut env2, contract_env.clone(),
.closurize(
&mut self.cache,
&mut env2,
contract_env.clone(),
)
};
@ -2245,20 +2203,16 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
// well: hence, we need to recompute the fixpoint, which is done by
// `fixpoint::revert`.
let mut env = Environment::new();
let reverted = super::fixpoint::revert(
&mut self.cache,
record_data,
&mut env,
&env2
);
let reverted =
super::fixpoint::revert(&mut self.cache, record_data, &mut env, &env2);
Ok(Closure {
body: RichTerm::new(reverted, pos2),
env,
})
}
} else Err(mk_type_error!("record_lazy_app_ctr", "Record", 2, t2, pos2))
}
_ => Err(mk_type_error!("record_lazy_app_ctr", "Record", 2, t2, pos2)),
})
}
BinaryOp::LabelWithMessage() => {
let t1 = t1.into_owned();
@ -2564,7 +2518,7 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
) = args_iter.next().unwrap();
debug_assert!(args_iter.next().is_none());
match_sharedterm! {t1, with {
match_sharedterm!(match (t1) {
Term::Lbl(lbl) => {
merge::merge(
&mut self.cache,
@ -2581,20 +2535,19 @@ impl<R: ImportResolver, C: Cache> VirtualMachine<R, C> {
env3,
pos_op,
MergeMode::Contract(lbl),
&mut self.call_stack
&mut self.call_stack,
)
}
} else {
Err(EvalError::InternalError(
_ => Err(EvalError::InternalError(
format!(
"The MergeContract() operator was expecting \
a first argument of type Label, got {}",
t1.type_of().unwrap_or_else(|| String::from("<unevaluated>"))
t1.type_of()
.unwrap_or_else(|| String::from("<unevaluated>"))
),
pos_op
))
}
}
)),
})
}
NAryOp::RecordSealTail() => {
let mut args = args.into_iter();

View File

@ -474,7 +474,7 @@ impl<EC: EvalCache> Program<EC> {
_ => result,
}?;
match_sharedterm! {rt.term, with {
match_sharedterm!(match (rt.term) {
Term::Record(data) => {
let fields = data
.fields
@ -502,8 +502,8 @@ impl<EC: EvalCache> Program<EC> {
rt.pos,
))
}
} else Ok(rt)
}
_ => Ok(rt),
})
}
do_eval(&mut self.vm, t, Environment::new())

View File

@ -1780,49 +1780,34 @@ impl Traverse<RichTerm> for RichTerm {
};
let pos = rt.pos;
let result = match_sharedterm! {rt.term, with {
let result = match_sharedterm!(match (rt.term) {
Term::Fun(id, t) => {
let t = t.traverse(f, order)?;
RichTerm::new(
Term::Fun(id, t),
pos,
)
},
RichTerm::new(Term::Fun(id, t), pos)
}
Term::FunPattern(id, d, t) => {
let t = t.traverse(f, order)?;
RichTerm::new(
Term::FunPattern(id, d, t),
pos,
)
},
RichTerm::new(Term::FunPattern(id, d, t), pos)
}
Term::Let(id, t1, t2, attrs) => {
let t1 = t1.traverse(f, order)?;
let t2 = t2.traverse(f, order)?;
RichTerm::new(
Term::Let(id, t1, t2, attrs),
pos,
)
},
RichTerm::new(Term::Let(id, t1, t2, attrs), pos)
}
Term::LetPattern(id, pat, t1, t2) => {
let t1 = t1.traverse(f, order)?;
let t2 = t2.traverse(f, order)?;
RichTerm::new(
Term::LetPattern(id, pat, t1, t2),
pos,
)
},
RichTerm::new(Term::LetPattern(id, pat, t1, t2), pos)
}
Term::App(t1, t2) => {
let t1 = t1.traverse(f, order)?;
let t2 = t2.traverse(f, order)?;
RichTerm::new(
Term::App(t1, t2),
pos,
)
},
RichTerm::new(Term::App(t1, t2), pos)
}
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_result : Result<IndexMap<LocIdent, RichTerm>, E> = cases
let cases_result: Result<IndexMap<LocIdent, 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, order).map(|t_ok| (id, t_ok)))
@ -1831,45 +1816,36 @@ impl Traverse<RichTerm> for RichTerm {
let default = default.map(|t| t.traverse(f, order)).transpose()?;
RichTerm::new(
Term::Match {cases: cases_result?, default },
Term::Match {
cases: cases_result?,
default,
},
pos,
)
},
}
Term::Op1(op, t) => {
let t = t.traverse(f, order)?;
RichTerm::new(
Term::Op1(op, t),
pos,
)
},
RichTerm::new(Term::Op1(op, t), pos)
}
Term::Op2(op, t1, t2) => {
let t1 = t1.traverse(f, order)?;
let t2 = t2.traverse(f, order)?;
RichTerm::new(Term::Op2(op, t1, t2),
pos,
)
},
RichTerm::new(Term::Op2(op, t1, t2), pos)
}
Term::OpN(op, ts) => {
let ts_res: Result<Vec<RichTerm>, E> = ts
.into_iter()
.map(|t| t.traverse(f, order))
.collect();
RichTerm::new(
Term::OpN(op, ts_res?),
pos,
)
},
let ts_res: Result<Vec<RichTerm>, E> =
ts.into_iter().map(|t| t.traverse(f, order)).collect();
RichTerm::new(Term::OpN(op, ts_res?), pos)
}
Term::Sealed(i, t1, lbl) => {
let t1 = t1.traverse(f, order)?;
RichTerm::new(
Term::Sealed(i, t1, lbl),
pos,
)
},
RichTerm::new(Term::Sealed(i, t1, lbl), pos)
}
Term::Record(record) => {
// The annotation on `fields_res` uses Result's corresponding trait to convert from
// Iterator<Result> to a Result<Iterator>
let fields_res: Result<IndexMap<LocIdent, Field>, E> = record.fields
let fields_res: Result<IndexMap<LocIdent, Field>, E> = record
.fields
.into_iter()
// For the conversion to work, note that we need a Result<(Ident,RichTerm), E>
.map(|(id, field)| {
@ -1878,14 +1854,19 @@ impl Traverse<RichTerm> for RichTerm {
})
.collect();
RichTerm::new(
Term::Record(RecordData::new(fields_res?, record.attrs, record.sealed_tail)),
pos
Term::Record(RecordData::new(
fields_res?,
record.attrs,
record.sealed_tail,
)),
pos,
)
},
}
Term::RecRecord(record, dyn_fields, deps) => {
// The annotation on `map_res` uses Result's corresponding trait to convert from
// Iterator<Result> to a Result<Iterator>
let static_fields_res: Result<IndexMap<LocIdent, Field>, E> = record.fields
let static_fields_res: Result<IndexMap<LocIdent, Field>, E> = record
.fields
.into_iter()
// For the conversion to work, note that we need a Result<(Ident,Field), E>
.map(|(id, field)| {
@ -1899,33 +1880,27 @@ impl Traverse<RichTerm> for RichTerm {
let id_t = id_t.traverse(f, order)?;
let field = field.traverse(f, order)?;
Ok((id_t, field,))
Ok((id_t, field))
})
.collect();
RichTerm::new(
Term::RecRecord(
RecordData::new(
static_fields_res?,
record.attrs,
record.sealed_tail
),
RecordData::new(static_fields_res?, record.attrs, record.sealed_tail),
dyn_fields_res?,
deps
deps,
),
pos,
)
},
}
Term::Array(ts, attrs) => {
let ts_res = Array::new(ts
.into_iter()
let ts_res = Array::new(
ts.into_iter()
.map(|t| t.traverse(f, order))
.collect::<Result<Rc<[_]>, _>>()?);
.collect::<Result<Rc<[_]>, _>>()?,
);
RichTerm::new(
Term::Array(ts_res, attrs),
pos,
)
},
RichTerm::new(Term::Array(ts_res, attrs), pos)
}
Term::StrChunks(chunks) => {
let chunks_res: Result<Vec<StrChunk<RichTerm>>, E> = chunks
.into_iter()
@ -1937,23 +1912,18 @@ impl Traverse<RichTerm> for RichTerm {
})
.collect();
RichTerm::new(
Term::StrChunks(chunks_res?),
pos,
)
},
RichTerm::new(Term::StrChunks(chunks_res?), pos)
}
Term::Annotated(annot, term) => {
let annot = annot.traverse(f, order)?;
let term = term.traverse(f, order)?;
RichTerm::new(
Term::Annotated(annot, term),
pos,
)
},
RichTerm::new(Term::Annotated(annot, term), pos)
}
Term::Type(ty) => {
RichTerm::new(Term::Type(ty.traverse(f, order)?), pos)
}
} else rt};
_ => rt,
});
match order {
TraverseOrder::TopDown => Ok(result),
@ -2043,10 +2013,12 @@ impl Traverse<Type> for RichTerm {
{
self.traverse(
&mut |rt: RichTerm| {
match_sharedterm! {rt.term, with {
Term::Type(ty) =>
ty.traverse(f, order).map(|ty| RichTerm::new(Term::Type(ty), rt.pos))
} else Ok(rt)}
match_sharedterm!(match (rt.term) {
Term::Type(ty) => ty
.traverse(f, order)
.map(|ty| RichTerm::new(Term::Type(ty), rt.pos)),
_ => Ok(rt),
})
},
order,
)
@ -2100,9 +2072,9 @@ impl fmt::Display for Term {
}
}
/// Allows to match on SharedTerm without taking ownership of the matched part until the match. In
/// the `else` clause, we haven't taken ownership yet, so we can still use the richterm at that
/// point.
/// Allows to match on SharedTerm without taking ownership of the matched part
/// until the match. In the wildcard pattern, we don't take ownership, so we can
/// still use the richterm at that point.
///
/// It is used somehow as a match statement, going from
/// ```
@ -2121,20 +2093,25 @@ impl fmt::Display for Term {
/// # use nickel_lang_core::match_sharedterm;
/// let rt = RichTerm::from(Term::Bool(true));
///
/// match_sharedterm!{rt.term, with {
/// match_sharedterm!(match (rt.term) {
/// Term::Bool(x) => usize::from(x),
/// Term::Str(s) => s.len(),
/// } else 42
/// };
/// _ => 42,
/// }
/// );
/// ```
///
/// Unlike a regular match statement, the expression being matched on must be
/// surrounded in parentheses
///
/// Known limitation: cannot use a `mut` inside the patterns.
#[macro_export]
macro_rules! match_sharedterm {
(
$st: expr, with {
$($($pat: pat_param)|+ $(if $if_expr: expr)? => $expr: expr),+ $(,)?
} else $else_clause: expr
match ($st: expr) {
$(%PROCESSED% $($pat: pat_param)|+ $(if $if_expr: expr)? => $expr: expr,)+
_ => $else_expr: expr $(,)?
}
) => {
match $st.as_ref() {
$(
@ -2145,9 +2122,56 @@ macro_rules! match_sharedterm {
_ => unsafe {::core::hint::unreachable_unchecked()}
},
)+
_ => $else_clause
_ => $else_expr
}
};
// recurse through the match arms prepending %PROCESSED% for two reasons:
// 1. to standardize match arms with trailing commas on <pattern> => { <body> }
// 2. so there's no ambiguity between a normal match arm and the final _ => <body>
(
match ($st: expr) {
$(%PROCESSED% $($pat1: pat_param)|+ $(if $if_expr1: expr)? => $expr1: expr,)*
$($pat2: pat_param)|+ $(if $if_expr2: expr)? => $expr2: expr,
$($rest:tt)*
}
) => {
match_sharedterm!(match ($st) {
$(%PROCESSED% $($pat1)|+ $(if $if_expr1)? => $expr1,)*
%PROCESSED% $($pat2)|+ $(if $if_expr2)? => $expr2,
$($rest)*
})
};
(
match ($st: expr) {
$(%PROCESSED% $($pat1: pat_param)|+ $(if $if_expr1: expr)? => $expr1: expr,)*
$($pat2: pat_param)|+ $(if $if_expr2: expr)? => $expr2: block
$($rest:tt)*
}
) => {
match_sharedterm!(match ($st) {
$(%PROCESSED% $($pat1)|+ $(if $if_expr1)? => $expr1,)*
%PROCESSED% $($pat2)|+ $(if $if_expr2)? => $expr2,
$($rest)*
})
};
// throw nice error messages for common mistakes
(
match ($st: expr) {
$(%PROCESSED% $($pat: pat_param)|+ $(if $if_expr: expr)? => $expr: expr,)+
}
) => {
compile_error!("`match_sharedterm!` used without a final wildcard match arm. You can just match on `shared_term.into_owned()`")
};
(
match ($st: expr) {
_ => $else_expr: expr $(,)?
}
) => {
compile_error!("`match_sharedterm!` used with only a wildcard match arm. You can just unconditionally execute that arm")
};
}
#[macro_use]

View File

@ -58,8 +58,7 @@ pub fn transform_one(rt: RichTerm) -> RichTerm {
/// down traversal. This means that the return value is a normal `Term::Fun` but it can contain
/// `Term::FunPattern` and `Term::LetPattern` inside.
pub fn desugar_fun(rt: RichTerm) -> RichTerm {
match_sharedterm! { rt.term,
with {
match_sharedterm!(match (rt.term) {
Term::FunPattern(x, pat, t_) => {
let x = x.unwrap_or_else(LocIdent::fresh);
let t_pos = t_.pos;
@ -68,17 +67,14 @@ pub fn desugar_fun(rt: RichTerm) -> RichTerm {
x,
RichTerm::new(
Term::LetPattern(None, pat, Term::Var(x).into(), t_),
t_pos /* TODO: should we use rt.pos? */
t_pos, /* TODO: should we use rt.pos? */
),
),
rt.pos,
)
}
}
else {
rt
}
}
_ => rt,
})
}
/// Wrap the desugar `LetPattern` in a meta value containing the "Record contract" needed to check
@ -86,8 +82,7 @@ pub fn desugar_fun(rt: RichTerm) -> RichTerm {
/// record. This function should be, in the general case, considered as the entry point of the let
/// patterns transformation.
pub fn desugar_with_contract(rt: RichTerm) -> RichTerm {
match_sharedterm!(rt.term,
with {
match_sharedterm!(match (rt.term) {
Term::LetPattern(x, pat, bound, body) => {
let pos = body.pos;
let contract = pat.clone().into_contract();
@ -95,24 +90,29 @@ pub fn desugar_with_contract(rt: RichTerm) -> RichTerm {
let t_pos = bound.pos;
RichTerm::new(
Term::Annotated(
TypeAnnotation { contracts: vec![contract], ..Default::default() },
bound
TypeAnnotation {
contracts: vec![contract],
..Default::default()
},
bound,
),
t_pos,
)
};
desugar(RichTerm::new(Term::LetPattern(x, pat, annotated, body), pos))
desugar(RichTerm::new(
Term::LetPattern(x, pat, annotated, body),
pos,
))
}
} else rt
)
_ => rt,
})
}
/// Main transformation function to desugar let patterns. WARNING: In a real usage case, you will
/// want to generate also the contract associated to this pattern destructuring. Do not consider
/// this function as the entry point of the transformation. For that, use `desugar_with_contract`.
pub fn desugar(rt: RichTerm) -> RichTerm {
match_sharedterm!(rt.term,
with {
match_sharedterm!(match (rt.term) {
Term::LetPattern(x, pat, t_, body) => {
let pos = body.pos;
let x = x.unwrap_or_else(LocIdent::fresh);
@ -126,8 +126,8 @@ pub fn desugar(rt: RichTerm) -> RichTerm {
pos,
)
}
} else rt
)
_ => rt,
})
}
/// Wrap `body` in a let construct binding the open part of the pattern to the required value.

View File

@ -57,29 +57,52 @@ pub fn transform_one(rt: RichTerm) -> Result<RichTerm, UnboundTypeVariableError>
}
let pos = rt.pos;
let result = match_sharedterm! {rt.term,
with {
let result = match_sharedterm!(match (rt.term) {
Term::RecRecord(record_data, dyn_fields, deps) => {
let RecordData {fields, attrs, sealed_tail} = record_data;
let RecordData {
fields,
attrs,
sealed_tail,
} = record_data;
let fields = attach_to_fields(fields)?;
let dyn_fields = dyn_fields.into_iter().map(|(id_term, field)| {
Ok((id_term, attach_to_field(field)?))
}).collect::<Result<_, _>>()?;
let dyn_fields = dyn_fields
.into_iter()
.map(|(id_term, field)| Ok((id_term, attach_to_field(field)?)))
.collect::<Result<_, _>>()?;
RichTerm::new(
Term::RecRecord(RecordData {fields, attrs, sealed_tail}, dyn_fields, deps),
pos
)
Term::RecRecord(
RecordData {
fields,
attrs,
sealed_tail,
},
dyn_fields,
deps,
),
pos,
)
}
Term::Record(record_data) => {
let RecordData {fields, attrs, sealed_tail} = record_data;
let RecordData {
fields,
attrs,
sealed_tail,
} = record_data;
let fields = attach_to_fields(fields)?;
RichTerm::new(Term::Record(RecordData {fields, attrs, sealed_tail}), pos)
RichTerm::new(
Term::Record(RecordData {
fields,
attrs,
sealed_tail,
}),
pos,
)
}
} else rt
};
_ => rt,
});
Ok(result)
}

View File

@ -53,8 +53,7 @@ struct Binding {
/// traversal to obtain a full transformation.
pub fn transform_one(rt: RichTerm) -> RichTerm {
let pos = rt.pos;
match_sharedterm! {rt.term,
with {
match_sharedterm!(match (rt.term) {
// CAUTION: currently, if the record literals written by a user are always recursive
// records, other phases of program transformation (such as desugaring of field paths
// like `{foo.bar.baz = val}` or desugaring of destructuring) do introduce non
@ -68,23 +67,41 @@ pub fn transform_one(rt: RichTerm) -> RichTerm {
let mut bindings = Vec::with_capacity(record_data.fields.len());
let empty_deps = FieldDeps::empty();
let fields = record_data.fields.into_iter().map(|(id, field)| {
(id, transform_rec_field(field, Some(empty_deps.clone()), &mut bindings))
}).collect();
let fields = record_data
.fields
.into_iter()
.map(|(id, field)| {
(
id,
transform_rec_field(field, Some(empty_deps.clone()), &mut bindings),
)
})
.collect();
with_bindings(Term::Record(RecordData { fields, ..record_data }), bindings, pos)
},
with_bindings(
Term::Record(RecordData {
fields,
..record_data
}),
bindings,
pos,
)
}
Term::RecRecord(record_data, dyn_fields, deps) => {
let mut bindings = Vec::with_capacity(record_data.fields.len());
let fields = record_data.fields.into_iter().map(|(id, field)| {
let fields = record_data
.fields
.into_iter()
.map(|(id, field)| {
let field_deps = deps
.as_ref()
.and_then(|deps| deps.stat_fields.get(&id.ident()))
.cloned();
(id, transform_rec_field(field, field_deps, &mut bindings))
}).collect();
})
.collect();
let dyn_fields = dyn_fields
.into_iter()
@ -99,11 +116,18 @@ pub fn transform_one(rt: RichTerm) -> RichTerm {
.collect();
with_bindings(
Term::RecRecord(RecordData { fields, ..record_data}, dyn_fields, deps),
bindings,
pos
)
Term::RecRecord(
RecordData {
fields,
..record_data
},
dyn_fields,
deps,
),
bindings,
pos,
)
}
Term::Array(ts, attrs) => {
let mut bindings = Vec::with_capacity(ts.len());
@ -116,7 +140,7 @@ pub fn transform_one(rt: RichTerm) -> RichTerm {
bindings.push(Binding {
fresh_var,
term: t,
binding_type: BindingType::Normal
binding_type: BindingType::Normal,
});
RichTerm::new(Term::Var(fresh_var), pos_t)
} else {
@ -126,15 +150,15 @@ pub fn transform_one(rt: RichTerm) -> RichTerm {
.collect();
with_bindings(Term::Array(ts, attrs), bindings, pos)
},
}
Term::Annotated(annot, t) if should_share(&t.term) => {
let fresh_var = LocIdent::fresh();
let shared = RichTerm::new(Term::Var(fresh_var), t.pos);
let inner = RichTerm::new(Term::Annotated(annot, shared), pos);
RichTerm::new(Term::Let(fresh_var, t, inner, LetAttrs::default()), pos)
},
} else rt
}
_ => rt,
})
}
/// Transform the field of a recursive record. Take care of transforming the pending contracts

View File

@ -21,17 +21,13 @@ use crate::{
/// both the type annotation and the label's type with the inferred type.
pub fn transform_one(rt: RichTerm, wildcards: &Wildcards) -> RichTerm {
let pos = rt.pos;
match_sharedterm! {rt.term,
with {
Term::Annotated(
annot @ TypeAnnotation {
typ: Some(_),
..
},
inner
) => {
RichTerm::new(Term::Annotated(annot.subst_wildcards(wildcards), inner), pos)
},
match_sharedterm!(match (rt.term) {
Term::Annotated(annot @ TypeAnnotation { typ: Some(_), .. }, inner) => {
RichTerm::new(
Term::Annotated(annot.subst_wildcards(wildcards), inner),
pos,
)
}
Term::RecRecord(record_data, dyn_fields, deps) => {
let record_data = record_data.subst_wildcards(wildcards);
let dyn_fields = dyn_fields
@ -40,15 +36,12 @@ pub fn transform_one(rt: RichTerm, wildcards: &Wildcards) -> RichTerm {
.collect();
RichTerm::new(Term::RecRecord(record_data, dyn_fields, deps), pos)
},
Term::Record(record_data) => {
RichTerm::new(
Term::Record(record_data.subst_wildcards(wildcards)),
pos,
)
},
} else rt
}
Term::Record(record_data) => {
RichTerm::new(Term::Record(record_data.subst_wildcards(wildcards)), pos)
}
_ => rt,
})
}
/// Get the inferred type for a wildcard, or `Dyn` if no type was inferred.