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:
parent
7e4ecd908c
commit
859293a3e5
@ -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
@ -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())
|
||||||
|
@ -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]
|
||||||
|
@ -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.
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
Loading…
Reference in New Issue
Block a user