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

File diff suppressed because it is too large Load Diff

View File

@ -474,36 +474,36 @@ impl<EC: EvalCache> Program<EC> {
_ => result, _ => result,
}?; }?;
match_sharedterm! {rt.term, with { match_sharedterm!(match (rt.term) {
Term::Record(data) => { Term::Record(data) => {
let fields = data let fields = data
.fields .fields
.into_iter() .into_iter()
.map(|(id, field)| -> Result<_, Error> { .map(|(id, field)| -> Result<_, Error> {
Ok(( Ok((
id, id,
Field { Field {
value: field value: field
.value .value
.map(|rt| do_eval(vm, rt, env.clone())) .map(|rt| do_eval(vm, rt, env.clone()))
.transpose()?, .transpose()?,
pending_contracts: eval_contracts( pending_contracts: eval_contracts(
vm, vm,
field.pending_contracts, field.pending_contracts,
env.clone(), env.clone(),
)?, )?,
..field ..field
}, },
)) ))
}) })
.collect::<Result<_, Error>>()?; .collect::<Result<_, Error>>()?;
Ok(RichTerm::new( Ok(RichTerm::new(
Term::Record(RecordData { fields, ..data }), Term::Record(RecordData { fields, ..data }),
rt.pos, rt.pos,
)) ))
} }
} else Ok(rt) _ => Ok(rt),
} })
} }
do_eval(&mut self.vm, t, Environment::new()) do_eval(&mut self.vm, t, Environment::new())

View File

@ -1780,49 +1780,34 @@ impl Traverse<RichTerm> for RichTerm {
}; };
let pos = rt.pos; let pos = rt.pos;
let result = match_sharedterm! {rt.term, with { let result = match_sharedterm!(match (rt.term) {
Term::Fun(id, t) => { Term::Fun(id, t) => {
let t = t.traverse(f, order)?; let t = t.traverse(f, order)?;
RichTerm::new( RichTerm::new(Term::Fun(id, t), pos)
Term::Fun(id, t), }
pos,
)
},
Term::FunPattern(id, d, t) => { Term::FunPattern(id, d, t) => {
let t = t.traverse(f, order)?; let t = t.traverse(f, order)?;
RichTerm::new( RichTerm::new(Term::FunPattern(id, d, t), pos)
Term::FunPattern(id, d, t), }
pos,
)
},
Term::Let(id, t1, t2, attrs) => { Term::Let(id, t1, t2, attrs) => {
let t1 = t1.traverse(f, order)?; let t1 = t1.traverse(f, order)?;
let t2 = t2.traverse(f, order)?; let t2 = t2.traverse(f, order)?;
RichTerm::new( RichTerm::new(Term::Let(id, t1, t2, attrs), pos)
Term::Let(id, t1, t2, attrs), }
pos,
)
},
Term::LetPattern(id, pat, t1, t2) => { Term::LetPattern(id, pat, t1, t2) => {
let t1 = t1.traverse(f, order)?; let t1 = t1.traverse(f, order)?;
let t2 = t2.traverse(f, order)?; let t2 = t2.traverse(f, order)?;
RichTerm::new( RichTerm::new(Term::LetPattern(id, pat, t1, t2), pos)
Term::LetPattern(id, pat, t1, t2), }
pos,
)
},
Term::App(t1, t2) => { Term::App(t1, t2) => {
let t1 = t1.traverse(f, order)?; let t1 = t1.traverse(f, order)?;
let t2 = t2.traverse(f, order)?; let t2 = t2.traverse(f, order)?;
RichTerm::new( RichTerm::new(Term::App(t1, t2), pos)
Term::App(t1, t2), }
pos,
)
},
Term::Match { cases, default } => { Term::Match { cases, default } => {
// The annotation on `map_res` use Result's corresponding trait to convert from // The annotation on `map_res` use Result's corresponding trait to convert from
// Iterator<Result> to a Result<Iterator> // 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() .into_iter()
// For the conversion to work, note that we need a Result<(Ident,RichTerm), E> // 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))) .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()?; let default = default.map(|t| t.traverse(f, order)).transpose()?;
RichTerm::new( RichTerm::new(
Term::Match {cases: cases_result?, default }, Term::Match {
cases: cases_result?,
default,
},
pos, pos,
) )
}, }
Term::Op1(op, t) => { Term::Op1(op, t) => {
let t = t.traverse(f, order)?; let t = t.traverse(f, order)?;
RichTerm::new( RichTerm::new(Term::Op1(op, t), pos)
Term::Op1(op, t), }
pos,
)
},
Term::Op2(op, t1, t2) => { Term::Op2(op, t1, t2) => {
let t1 = t1.traverse(f, order)?; let t1 = t1.traverse(f, order)?;
let t2 = t2.traverse(f, order)?; let t2 = t2.traverse(f, order)?;
RichTerm::new(Term::Op2(op, t1, t2), RichTerm::new(Term::Op2(op, t1, t2), pos)
pos, }
)
},
Term::OpN(op, ts) => { Term::OpN(op, ts) => {
let ts_res: Result<Vec<RichTerm>, E> = ts let ts_res: Result<Vec<RichTerm>, E> =
.into_iter() ts.into_iter().map(|t| t.traverse(f, order)).collect();
.map(|t| t.traverse(f, order)) RichTerm::new(Term::OpN(op, ts_res?), pos)
.collect(); }
RichTerm::new(
Term::OpN(op, ts_res?),
pos,
)
},
Term::Sealed(i, t1, lbl) => { Term::Sealed(i, t1, lbl) => {
let t1 = t1.traverse(f, order)?; let t1 = t1.traverse(f, order)?;
RichTerm::new( RichTerm::new(Term::Sealed(i, t1, lbl), pos)
Term::Sealed(i, t1, lbl), }
pos,
)
},
Term::Record(record) => { Term::Record(record) => {
// The annotation on `fields_res` uses Result's corresponding trait to convert from // The annotation on `fields_res` uses Result's corresponding trait to convert from
// Iterator<Result> to a Result<Iterator> // 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() .into_iter()
// For the conversion to work, note that we need a Result<(Ident,RichTerm), E> // For the conversion to work, note that we need a Result<(Ident,RichTerm), E>
.map(|(id, field)| { .map(|(id, field)| {
@ -1878,14 +1854,19 @@ impl Traverse<RichTerm> for RichTerm {
}) })
.collect(); .collect();
RichTerm::new( RichTerm::new(
Term::Record(RecordData::new(fields_res?, record.attrs, record.sealed_tail)), Term::Record(RecordData::new(
pos fields_res?,
record.attrs,
record.sealed_tail,
)),
pos,
) )
}, }
Term::RecRecord(record, dyn_fields, deps) => { Term::RecRecord(record, dyn_fields, deps) => {
// The annotation on `map_res` uses Result's corresponding trait to convert from // The annotation on `map_res` uses Result's corresponding trait to convert from
// Iterator<Result> to a Result<Iterator> // 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() .into_iter()
// For the conversion to work, note that we need a Result<(Ident,Field), E> // For the conversion to work, note that we need a Result<(Ident,Field), E>
.map(|(id, field)| { .map(|(id, field)| {
@ -1899,33 +1880,27 @@ impl Traverse<RichTerm> for RichTerm {
let id_t = id_t.traverse(f, order)?; let id_t = id_t.traverse(f, order)?;
let field = field.traverse(f, order)?; let field = field.traverse(f, order)?;
Ok((id_t, field,)) Ok((id_t, field))
}) })
.collect(); .collect();
RichTerm::new( RichTerm::new(
Term::RecRecord( Term::RecRecord(
RecordData::new( RecordData::new(static_fields_res?, record.attrs, record.sealed_tail),
static_fields_res?,
record.attrs,
record.sealed_tail
),
dyn_fields_res?, dyn_fields_res?,
deps deps,
), ),
pos, pos,
) )
}, }
Term::Array(ts, attrs) => { Term::Array(ts, attrs) => {
let ts_res = Array::new(ts let ts_res = Array::new(
.into_iter() ts.into_iter()
.map(|t| t.traverse(f, order)) .map(|t| t.traverse(f, order))
.collect::<Result<Rc<[_]>, _>>()?); .collect::<Result<Rc<[_]>, _>>()?,
);
RichTerm::new( RichTerm::new(Term::Array(ts_res, attrs), pos)
Term::Array(ts_res, attrs), }
pos,
)
},
Term::StrChunks(chunks) => { Term::StrChunks(chunks) => {
let chunks_res: Result<Vec<StrChunk<RichTerm>>, E> = chunks let chunks_res: Result<Vec<StrChunk<RichTerm>>, E> = chunks
.into_iter() .into_iter()
@ -1937,23 +1912,18 @@ impl Traverse<RichTerm> for RichTerm {
}) })
.collect(); .collect();
RichTerm::new( RichTerm::new(Term::StrChunks(chunks_res?), pos)
Term::StrChunks(chunks_res?), }
pos,
)
},
Term::Annotated(annot, term) => { Term::Annotated(annot, term) => {
let annot = annot.traverse(f, order)?; let annot = annot.traverse(f, order)?;
let term = term.traverse(f, order)?; let term = term.traverse(f, order)?;
RichTerm::new( RichTerm::new(Term::Annotated(annot, term), pos)
Term::Annotated(annot, term), }
pos,
)
},
Term::Type(ty) => { Term::Type(ty) => {
RichTerm::new(Term::Type(ty.traverse(f, order)?), pos) RichTerm::new(Term::Type(ty.traverse(f, order)?), pos)
} }
} else rt}; _ => rt,
});
match order { match order {
TraverseOrder::TopDown => Ok(result), TraverseOrder::TopDown => Ok(result),
@ -2043,10 +2013,12 @@ impl Traverse<Type> for RichTerm {
{ {
self.traverse( self.traverse(
&mut |rt: RichTerm| { &mut |rt: RichTerm| {
match_sharedterm! {rt.term, with { match_sharedterm!(match (rt.term) {
Term::Type(ty) => Term::Type(ty) => ty
ty.traverse(f, order).map(|ty| RichTerm::new(Term::Type(ty), rt.pos)) .traverse(f, order)
} else Ok(rt)} .map(|ty| RichTerm::new(Term::Type(ty), rt.pos)),
_ => Ok(rt),
})
}, },
order, 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 /// Allows to match on SharedTerm without taking ownership of the matched part
/// the `else` clause, we haven't taken ownership yet, so we can still use the richterm at that /// until the match. In the wildcard pattern, we don't take ownership, so we can
/// point. /// still use the richterm at that point.
/// ///
/// It is used somehow as a match statement, going from /// 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; /// # use nickel_lang_core::match_sharedterm;
/// let rt = RichTerm::from(Term::Bool(true)); /// let rt = RichTerm::from(Term::Bool(true));
/// ///
/// match_sharedterm!{rt.term, with { /// match_sharedterm!(match (rt.term) {
/// Term::Bool(x) => usize::from(x), /// Term::Bool(x) => usize::from(x),
/// Term::Str(s) => s.len(), /// 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. /// Known limitation: cannot use a `mut` inside the patterns.
#[macro_export] #[macro_export]
macro_rules! match_sharedterm { macro_rules! match_sharedterm {
( (
$st: expr, with { match ($st: expr) {
$($($pat: pat_param)|+ $(if $if_expr: expr)? => $expr: expr),+ $(,)? $(%PROCESSED% $($pat: pat_param)|+ $(if $if_expr: expr)? => $expr: expr,)+
} else $else_clause: expr _ => $else_expr: expr $(,)?
}
) => { ) => {
match $st.as_ref() { match $st.as_ref() {
$( $(
@ -2145,9 +2122,56 @@ macro_rules! match_sharedterm {
_ => unsafe {::core::hint::unreachable_unchecked()} _ => 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] #[macro_use]

View File

@ -58,27 +58,23 @@ pub fn transform_one(rt: RichTerm) -> RichTerm {
/// down traversal. This means that the return value is a normal `Term::Fun` but it can contain /// down traversal. This means that the return value is a normal `Term::Fun` but it can contain
/// `Term::FunPattern` and `Term::LetPattern` inside. /// `Term::FunPattern` and `Term::LetPattern` inside.
pub fn desugar_fun(rt: RichTerm) -> RichTerm { pub fn desugar_fun(rt: RichTerm) -> RichTerm {
match_sharedterm! { rt.term, match_sharedterm!(match (rt.term) {
with { Term::FunPattern(x, pat, t_) => {
Term::FunPattern(x, pat, t_) => { let x = x.unwrap_or_else(LocIdent::fresh);
let x = x.unwrap_or_else(LocIdent::fresh); let t_pos = t_.pos;
let t_pos = t_.pos; RichTerm::new(
RichTerm::new( Term::Fun(
Term::Fun( x,
x, RichTerm::new(
RichTerm::new( Term::LetPattern(None, pat, Term::Var(x).into(), t_),
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, ),
) rt.pos,
} )
} }
else { _ => rt,
rt })
}
}
} }
/// Wrap the desugar `LetPattern` in a meta value containing the "Record contract" needed to check /// Wrap the desugar `LetPattern` in a meta value containing the "Record contract" needed to check
@ -86,48 +82,52 @@ pub fn desugar_fun(rt: RichTerm) -> RichTerm {
/// record. This function should be, in the general case, considered as the entry point of the let /// record. This function should be, in the general case, considered as the entry point of the let
/// patterns transformation. /// patterns transformation.
pub fn desugar_with_contract(rt: RichTerm) -> RichTerm { pub fn desugar_with_contract(rt: RichTerm) -> RichTerm {
match_sharedterm!(rt.term, match_sharedterm!(match (rt.term) {
with { Term::LetPattern(x, pat, bound, body) => {
Term::LetPattern(x, pat, bound, body) => { let pos = body.pos;
let pos = body.pos; let contract = pat.clone().into_contract();
let contract = pat.clone().into_contract(); let annotated = {
let annotated = { let t_pos = bound.pos;
let t_pos = bound.pos; RichTerm::new(
RichTerm::new( Term::Annotated(
Term::Annotated( TypeAnnotation {
TypeAnnotation { contracts: vec![contract], ..Default::default() }, contracts: vec![contract],
bound ..Default::default()
), },
t_pos, bound,
) ),
}; t_pos,
desugar(RichTerm::new(Term::LetPattern(x, pat, annotated, body), pos)) )
} };
} else rt desugar(RichTerm::new(
) Term::LetPattern(x, pat, annotated, body),
pos,
))
}
_ => rt,
})
} }
/// Main transformation function to desugar let patterns. WARNING: In a real usage case, you will /// 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 /// 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`. /// this function as the entry point of the transformation. For that, use `desugar_with_contract`.
pub fn desugar(rt: RichTerm) -> RichTerm { pub fn desugar(rt: RichTerm) -> RichTerm {
match_sharedterm!(rt.term, match_sharedterm!(match (rt.term) {
with { Term::LetPattern(x, pat, t_, body) => {
Term::LetPattern(x, pat, t_, body) => { let pos = body.pos;
let pos = body.pos; let x = x.unwrap_or_else(LocIdent::fresh);
let x = x.unwrap_or_else(LocIdent::fresh); RichTerm::new(
RichTerm::new( Term::Let(
Term::Let( x,
x, t_,
t_, destruct_term(x, &pat, bind_open_field(x, &pat, body)),
destruct_term(x, &pat, bind_open_field(x, &pat, body)), Default::default(),
Default::default(), ),
), pos,
pos, )
) }
} _ => rt,
} else rt })
)
} }
/// Wrap `body` in a let construct binding the open part of the pattern to the required value. /// 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 pos = rt.pos;
let result = match_sharedterm! {rt.term, let result = match_sharedterm!(match (rt.term) {
with { Term::RecRecord(record_data, dyn_fields, deps) => {
Term::RecRecord(record_data, dyn_fields, deps) => { let RecordData {
let RecordData {fields, attrs, sealed_tail} = record_data; fields,
attrs,
sealed_tail,
} = record_data;
let fields = attach_to_fields(fields)?; let fields = attach_to_fields(fields)?;
let dyn_fields = dyn_fields.into_iter().map(|(id_term, field)| { let dyn_fields = dyn_fields
Ok((id_term, attach_to_field(field)?)) .into_iter()
}).collect::<Result<_, _>>()?; .map(|(id_term, field)| Ok((id_term, attach_to_field(field)?)))
.collect::<Result<_, _>>()?;
RichTerm::new( RichTerm::new(
Term::RecRecord(RecordData {fields, attrs, sealed_tail}, dyn_fields, deps), Term::RecRecord(
pos RecordData {
) fields,
}, attrs,
Term::Record(record_data) => { sealed_tail,
let RecordData {fields, attrs, sealed_tail} = record_data; },
dyn_fields,
deps,
),
pos,
)
}
Term::Record(record_data) => {
let RecordData {
fields,
attrs,
sealed_tail,
} = record_data;
let fields = attach_to_fields(fields)?; let fields = attach_to_fields(fields)?;
RichTerm::new(Term::Record(RecordData {fields, attrs, sealed_tail}), pos) RichTerm::new(
} Term::Record(RecordData {
} else rt fields,
}; attrs,
sealed_tail,
}),
pos,
)
}
_ => rt,
});
Ok(result) Ok(result)
} }

View File

@ -53,88 +53,112 @@ struct Binding {
/// traversal to obtain a full transformation. /// traversal to obtain a full transformation.
pub fn transform_one(rt: RichTerm) -> RichTerm { pub fn transform_one(rt: RichTerm) -> RichTerm {
let pos = rt.pos; let pos = rt.pos;
match_sharedterm! {rt.term, match_sharedterm!(match (rt.term) {
with { // CAUTION: currently, if the record literals written by a user are always recursive
// 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
// records, other phases of program transformation (such as desugaring of field paths // like `{foo.bar.baz = val}` or desugaring of destructuring) do introduce non
// like `{foo.bar.baz = val}` or desugaring of destructuring) do introduce non // recursive records.
// recursive records. //
// // I've scratched my head more that once on a transformation seemingly not happening
// I've scratched my head more that once on a transformation seemingly not happening // only for record elaborated by the destructuring desguaring phase, to find that the
// only for record elaborated by the destructuring desguaring phase, to find that the // `Term::Record` case below has been forgotten, and only the case `Term::RecRecord`
// `Term::Record` case below has been forgotten, and only the case `Term::RecRecord` // was updated.
// was updated. Term::Record(record_data) => {
Term::Record(record_data) => { let mut bindings = Vec::with_capacity(record_data.fields.len());
let mut bindings = Vec::with_capacity(record_data.fields.len()); let empty_deps = FieldDeps::empty();
let empty_deps = FieldDeps::empty();
let fields = record_data.fields.into_iter().map(|(id, field)| { let fields = record_data
(id, transform_rec_field(field, Some(empty_deps.clone()), &mut bindings)) .fields
}).collect(); .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 {
Term::RecRecord(record_data, dyn_fields, deps) => { fields,
let mut bindings = Vec::with_capacity(record_data.fields.len()); ..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 let field_deps = deps
.as_ref() .as_ref()
.and_then(|deps| deps.stat_fields.get(&id.ident())) .and_then(|deps| deps.stat_fields.get(&id.ident()))
.cloned(); .cloned();
(id, transform_rec_field(field, field_deps, &mut bindings)) (id, transform_rec_field(field, field_deps, &mut bindings))
}).collect(); })
.collect();
let dyn_fields = dyn_fields let dyn_fields = dyn_fields
.into_iter() .into_iter()
.enumerate() .enumerate()
.map(|(index, (id_t, field))| { .map(|(index, (id_t, field))| {
let field_deps = deps let field_deps = deps
.as_ref() .as_ref()
.and_then(|deps| deps.dyn_fields.get(index)) .and_then(|deps| deps.dyn_fields.get(index))
.cloned(); .cloned();
(id_t, transform_rec_field(field, field_deps, &mut bindings)) (id_t, transform_rec_field(field, field_deps, &mut bindings))
}) })
.collect(); .collect();
with_bindings( with_bindings(
Term::RecRecord(RecordData { fields, ..record_data}, dyn_fields, deps), Term::RecRecord(
bindings, RecordData {
pos fields,
) ..record_data
}, },
Term::Array(ts, attrs) => { dyn_fields,
let mut bindings = Vec::with_capacity(ts.len()); deps,
),
bindings,
pos,
)
}
Term::Array(ts, attrs) => {
let mut bindings = Vec::with_capacity(ts.len());
let ts = ts let ts = ts
.into_iter() .into_iter()
.map(|t| { .map(|t| {
if should_share(&t.term) { if should_share(&t.term) {
let fresh_var = LocIdent::fresh(); let fresh_var = LocIdent::fresh();
let pos_t = t.pos; let pos_t = t.pos;
bindings.push(Binding { bindings.push(Binding {
fresh_var, fresh_var,
term: t, term: t,
binding_type: BindingType::Normal binding_type: BindingType::Normal,
}); });
RichTerm::new(Term::Var(fresh_var), pos_t) RichTerm::new(Term::Var(fresh_var), pos_t)
} else { } else {
t t
} }
}) })
.collect(); .collect();
with_bindings(Term::Array(ts, attrs), bindings, pos) with_bindings(Term::Array(ts, attrs), bindings, pos)
}, }
Term::Annotated(annot, t) if should_share(&t.term) => { Term::Annotated(annot, t) if should_share(&t.term) => {
let fresh_var = LocIdent::fresh(); let fresh_var = LocIdent::fresh();
let shared = RichTerm::new(Term::Var(fresh_var), t.pos); let shared = RichTerm::new(Term::Var(fresh_var), t.pos);
let inner = RichTerm::new(Term::Annotated(annot, shared), pos); let inner = RichTerm::new(Term::Annotated(annot, shared), pos);
RichTerm::new(Term::Let(fresh_var, t, inner, LetAttrs::default()), 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 /// Transform the field of a recursive record. Take care of transforming the pending contracts

View File

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