nickel_lang_core/typ.rs
1//! Nickel static types.
2//!
3//! The type system of Nickel is comprised of primitive types, arrays, records, functions, and
4//! opaque types (contracts). This is a structural type system with row polymorphism for both
5//! records and enums.
6//!
7//! ## Record types (rows)
8//!
9//! A row type for a record is represented as a linked list of pairs `(id, type)` indicating the
10//! name and the type of each field. Row-polymorphism means that the tail of this list can be a
11//! type variable which can be abstracted over, leaving the row open for future extension. A simple
12//! illustration is record field access:
13//!
14//! ```nickel
15//! let f : forall a. { some_field : Number; a} -> Number =
16//! fun record => record.some_field
17//! ```
18//!
19//! The type `{ some_field: Number; a }` indicates that an argument to this function must have at
20//! least the field `some_field` of type `Number`, but may contain other fields (or not).
21//!
22//! ## Dictionaries
23//!
24//! A dictionary type `{ _ : Type }` represents a record whose fields all have the type `Type`. The
25//! count and the name of the fields aren't constrained. Dictionaries can be mapped over, extended,
26//! shrinked and accessed in a type-safe manner.
27//!
28//! # Enum types
29//!
30//! An enum type is also a row type where each element is a tag, such as `[| 'foo, 'bar, 'baz |]`.
31//! This type represent values that can be either `'foo`, `'bar` or `'baz`. Enums support row
32//! polymorphism as well.
33//!
34//! # Contracts
35//!
36//! To each type corresponds a contract, which is equivalent to a Nickel function which checks at
37//! runtime that its argument is of the given type. Contract checks are introduced by a contract
38//! annotation or propagated via merging. They ensure sane interaction between typed and untyped
39//! parts.
40//!
41//! Conversely, any Nickel term seen as a contract corresponds to a type, which is opaque and can
42//! only be equated with itself.
43use crate::{
44 environment::Environment,
45 error::{EvalError, ParseError, ParseErrors, TypecheckError},
46 identifier::{Ident, LocIdent},
47 impl_display_from_pretty,
48 label::Polarity,
49 metrics::increment,
50 mk_app, mk_fun,
51 position::TermPos,
52 pretty::PrettyPrintCap,
53 stdlib::internals,
54 term::pattern::compile::Compile,
55 term::{
56 array::Array, make as mk_term, record::RecordData, string::NickelString, IndexMap,
57 MatchBranch, MatchData, RichTerm, Term,
58 },
59 traverse::*,
60};
61
62use std::{collections::HashSet, convert::Infallible};
63
64/// A record row, mapping an identifier to a type. A record type is a dictionary mapping
65/// identifiers to Nickel type. Record types are represented as sequences of `RecordRowF`, ending
66/// potentially with a type variable or `Dyn` in tail position.
67///
68/// # Type parameters
69///
70/// As other types with the `F` suffix, this type is parametrized by one or more recursive
71/// unfoldings (here, `Ty` for `TypeF`). See [`TypeF`] for more details.
72#[derive(Clone, PartialEq, Eq, Debug)]
73pub struct RecordRowF<Ty> {
74 pub id: LocIdent,
75 pub typ: Ty,
76}
77
78/// An enum row, mapping an identifier to an optional type. If the associated type is `None`, this
79/// represent a bare (unapplied) enum tag such as `'foo`. If the enum is applied (a variant), the
80/// type is store in `typ`. An enum type is a set of enum rows, represented as a sequence of
81/// `EnumRow`s, ending potentially with a type variable tail position.
82///
83/// # Type parameters
84///
85/// As other types with the `F` suffix, this type is parametrized by one or more recursive
86/// unfoldings (here, `Ty` for `TypeF`). See [`TypeF`] for more details.
87#[derive(Clone, PartialEq, Eq, Debug)]
88pub struct EnumRowF<Ty> {
89 pub id: LocIdent,
90 pub typ: Option<Ty>,
91}
92
93/// Generic sequence of record rows potentially with a type variable or `Dyn` in tail position.
94///
95/// As other types with the `F` suffix, this type is parametrized by one or more recursive
96/// unfoldings. See [`TypeF`] for more details.
97///
98/// # Type parameters
99///
100/// - `Ty` is the recursive unfolding of a Nickel type stored inside one row. In practice, a
101/// wrapper around an instantiation of `TypeF`.
102/// - `RRows` is the recursive unfolding of record rows (the tail of this row sequence). In
103/// practice, a wrapper around an instantiation of `RecordRowsF`.
104#[derive(Clone, PartialEq, Eq, Debug)]
105pub enum RecordRowsF<Ty, RRows> {
106 Empty,
107 Extend { row: RecordRowF<Ty>, tail: RRows },
108 TailVar(LocIdent),
109 TailDyn,
110}
111
112/// Generic sequence of enum rows potentially with a type variable in tail position.
113///
114/// As other types with the `F` suffix, this type is parametrized by one or more recursive
115/// unfoldings. See [`TypeF`] for more details.
116///
117/// # Type parameters
118///
119/// - `ERows` is the recursive unfolding of enum rows (the tail of this row sequence). In practice,
120/// a wrapper around `EnumRowsF`.
121#[derive(Clone, PartialEq, Eq, Debug)]
122pub enum EnumRowsF<Ty, ERows> {
123 Empty,
124 Extend { row: EnumRowF<Ty>, tail: ERows },
125 TailVar(LocIdent),
126}
127
128/// The kind of a quantified type variable.
129///
130/// Nickel uses several forms of polymorphism. A type variable can be substituted for a type, as in
131/// `id : forall a. a -> a`, for record rows as in `access_foo : forall a . {foo : Number; a} ->
132/// Number}`, or for enum rows. This information is implicit in the source syntax: we don't require
133/// users to write e.g. `forall a :: Type` or `forall a :: Rows`. But the kind of a variable is
134/// required for the typechecker. It is thus determined during parsing and stored as `VarKind` where
135/// type variables are introduced, that is, on forall quantifiers.
136#[derive(Clone, PartialEq, Eq, Debug, Default)]
137pub enum VarKind {
138 #[default]
139 Type,
140 /// `excluded` keeps track of which rows appear somewhere alongside the tail, and therefore
141 /// cannot appear in the tail. For instance `forall r. { ; r } -> { x : Number ; r }` assumes
142 EnumRows { excluded: HashSet<Ident> },
143 /// Same as for [Self::EnumRows].
144 RecordRows { excluded: HashSet<Ident> },
145}
146
147/// Equivalent to `std::mem::Discriminant<VarKind>`, but we can do things like match on it
148// TODO: this seems overly complicated, and it's anyways more space-efficient to store the
149// `excluded` information separately like we do in the `State` field constr. Probably we can store
150// it that way during parsing too.
151#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
152pub enum VarKindDiscriminant {
153 Type,
154 EnumRows,
155 RecordRows,
156}
157
158impl From<&VarKind> for VarKindDiscriminant {
159 fn from(vk: &VarKind) -> Self {
160 match vk {
161 VarKind::Type => VarKindDiscriminant::Type,
162 VarKind::EnumRows { .. } => VarKindDiscriminant::EnumRows,
163 VarKind::RecordRows { .. } => VarKindDiscriminant::RecordRows,
164 }
165 }
166}
167
168/// Flavour of a dictionary type. There are currently two way of writing a dictionary type-ish
169/// object: as a dictionary contract `{_ | T}` or as a dictionary type `{_ : T}`. Ideally, the
170/// former wouldn't even be a type but mostly syntactic sugar for a builtin contract application,
171/// or maybe a proper AST node.
172///
173/// However, the LSP needs to handle both dictionary types and contracts specifically in order to
174/// provide good completion. As we added dictionary contract just before 1.0 to fix a non trivial
175/// issue with respect to polymorphic contracts ([GitHub
176/// issue](https://github.com/tweag/nickel/issues/1228)), the solution to just tweak dictionary
177/// types to be able to hold both kinds - generating a different contract - seemed to be the
178/// simplest to preserve the user experience (LSP, handling of dictionary when reporting a contract
179/// blame, etc.).
180///
181/// Dictionary contracts might get a proper AST node later on.
182#[derive(Clone, Debug, Copy, Eq, PartialEq)]
183pub enum DictTypeFlavour {
184 /// Dictionary type (`{_ : T}`)
185 Type,
186 /// Dictionary contract (`{_ | T}`)
187 Contract,
188}
189
190/// A Nickel type.
191///
192/// # Generic representation (functor)
193///
194/// A Nickel type is represented by a tree and is naturally defined in a recursive manner: for
195/// example, one would expect the constructor for function types (`Arrow`) to look like:
196///
197/// ```rust
198/// pub enum Type {
199/// Arrow(Box<Type>, Box<Type>),
200/// // ...
201/// }
202/// ```
203///
204/// However, `TypeF` is slightly different, in that it is parametrized by a generic type instead of
205/// using a concrete definition like `Box<Types>` (forget about rows for now):
206///
207/// ```rust
208/// pub enum TypeF<Ty /* , .. */> {
209/// Arrow(Ty, Ty),
210/// // ...
211/// }
212/// ```
213///
214/// `Ty` is also called a recursive unfolding throughout the documentation. By defining `struct
215/// Type(TypeF<Box<Types>>)`, we get back the original, natural definition.
216///
217/// ## Motivation 1: variation on `Types`
218///
219/// Having a generic definition makes it possible to easily create other types with the _same shape_
220/// as `Type` (seen as trees), but with enriched nodes. The typical use-case in Nickel is the
221/// variation on types used by the typechecker. During type inference, the typechecker operates on
222/// trees where each node can either be a concrete type, or a unification variable (a unknown type
223/// to be inferred). Instead of duplicating the whole definition of `Type` as well as all basic
224/// methods, we can simply have a different recursive definition:
225///
226/// ```
227/// # // phony declarations to make this example pass the tests
228/// # type VarId = ();
229/// # type TypeF<T> = T;
230///
231/// pub enum UnifType {
232/// UnifVar(VarId),
233/// Concrete(TypeF<Box<UnifType> /*, .. */>),
234/// // ..
235/// }
236/// ```
237///
238/// We get something that looks like normal Nickel types, except that each node can also be a
239/// unification variable as well.
240///
241/// ## Motivation 2: recursion schemes
242///
243/// This definition is actually in the style of recursion schemes. Pedantically, `TypeF` (hence the
244/// `F` suffix) is a functor, but the formal details aren't so important: keep in mind that the `F`
245/// suffix means that the recursive occurrences of subtrees (and enum rows and record rows as well)
246/// are replaced by generic parameters.
247///
248/// The usual motivation for recursion schemes is that they allow for elegant and simple definitions
249/// of recursive transformation over trees (here, `TypeF`, and more generally anything with an `F`
250/// suffix) in terms of simple appropriate chaining of `map` and folding/unfolding operations. A
251/// good example is the definition of [Type::traverse]. Although [crate::term::Term] isn't currently
252/// defined using functors per se, the way program transformations are written is in the same style
253/// as recursion schemes: we simply define the action of a transformation as a mapping on the
254/// current node, and let the traversal take care of the plumbing of recursion and reconstruction.
255///
256/// ## Type parameters
257///
258/// - `Ty`: the recursive unfolding of Nickel types
259/// - `RRows`: the recursive unfolding of record rows
260/// - `ERows`: the recursive unfolding of enum rows
261/// - `Te`: the type of a term (used to store contracts)
262#[derive(Clone, PartialEq, Eq, Debug)]
263pub enum TypeF<Ty, RRows, ERows, Te> {
264 /// The dynamic type, or unitype. Assigned to values whose actual type is not statically known
265 /// or checked.
266 Dyn,
267 /// A floating point number.
268 Number,
269 /// A boolean.
270 Bool,
271 /// A string literal.
272 String,
273 /// A symbol.
274 ///
275 /// See [`crate::term::Term::Sealed`].
276 Symbol,
277 /// The type of `Term::ForeignId`.
278 ForeignId,
279 /// A type created from a user-defined contract.
280 Contract(Te),
281 /// A function.
282 Arrow(Ty, Ty),
283 /// A type variable.
284 Var(Ident),
285 /// A forall binder.
286 Forall {
287 var: LocIdent,
288 var_kind: VarKind,
289 body: Ty,
290 },
291
292 /// An enum type, composed of a sequence of enum rows.
293 Enum(ERows),
294 /// A record type, composed of a sequence of record rows.
295 Record(RRows),
296 /// A dictionary type.
297 Dict {
298 type_fields: Ty,
299 flavour: DictTypeFlavour,
300 },
301 /// A parametrized array.
302 Array(Ty),
303 /// A type wildcard, wrapping an ID unique within a given file.
304 Wildcard(usize),
305}
306
307// Concrete, recursive definition of Nickel types from the generic `XxxF` definitions. This is
308// "tying" the knot. We have to put `Box` in the appropriate positions (otherwise, Rust will
309// complain that the type has an infinite size), but also avoid putting in more than necessary.
310//
311// For example, `RecordRows` contains a `RecordRow`. The latter doesn't need to be boxed, because a
312// `RecordRow` itself potentially contains occurrences of `Type` and `RecordRows`, which need to
313// be boxed. Hence, we don't need to additionally box `RecordRow`.
314
315/// Concrete, recursive definition for an enum row.
316pub type EnumRow = EnumRowF<Box<Type>>;
317/// Concrete, recursive definition for enum rows.
318#[derive(Clone, PartialEq, Debug)]
319pub struct EnumRows(pub EnumRowsF<Box<Type>, Box<EnumRows>>);
320/// Concrete, recursive definition for a record row.
321pub type RecordRow = RecordRowF<Box<Type>>;
322#[derive(Clone, PartialEq, Debug)]
323/// Concrete, recursive definition for record rows.
324pub struct RecordRows(pub RecordRowsF<Box<Type>, Box<RecordRows>>);
325
326/// Concrete, recursive type for a Nickel type.
327#[derive(Clone, PartialEq, Debug)]
328pub struct Type {
329 pub typ: TypeF<Box<Type>, RecordRows, EnumRows, RichTerm>,
330 pub pos: TermPos,
331}
332
333impl<Ty, RRows> RecordRowsF<Ty, RRows> {
334 /// Map functions over the children nodes of record rows, when seen as a tree. The mutable
335 /// state ( `S`) is threaded through the calls to the mapped functions. Functions are fallible
336 /// and may return an error `E`, which causes `try_map_state` to return early with the same
337 /// error.
338 ///
339 /// If we put aside the state and the error (see [RecordRowsF::map), this function makes
340 /// `RecordRowsF` a functor (of arity 2). As hinted by the type signature, this function just
341 /// maps on "one-level" of recursion, so to speak. Take the instantiated version `RecordRows`,
342 /// and record rows of the form `{foo : T, bar: U, baz: V}`. Then, calling `try_map_state(f_ty,
343 /// f_rrows, state)` on these rows will map `f_ty` onto `T` and `f_rrows` onto `{bar : U, baz:
344 /// V}`.
345 ///
346 /// Note that `f_ty` isn't mapped onto `U` and `V` recursively: map isn't a recursive
347 /// operation. It's however a building block to express recursive operations: as an example,
348 /// see [RecordRows::traverse].
349 pub fn try_map_state<TyO, RRowsO, FTy, FRRows, S, E>(
350 self,
351 mut f_ty: FTy,
352 mut f_rrows: FRRows,
353 state: &mut S,
354 ) -> Result<RecordRowsF<TyO, RRowsO>, E>
355 where
356 FTy: FnMut(Ty, &mut S) -> Result<TyO, E>,
357 FRRows: FnMut(RRows, &mut S) -> Result<RRowsO, E>,
358 {
359 match self {
360 RecordRowsF::Empty => Ok(RecordRowsF::Empty),
361 RecordRowsF::Extend {
362 row: RecordRowF { id, typ },
363 tail,
364 } => Ok(RecordRowsF::Extend {
365 row: RecordRowF {
366 id,
367 typ: f_ty(typ, state)?,
368 },
369 tail: f_rrows(tail, state)?,
370 }),
371 RecordRowsF::TailDyn => Ok(RecordRowsF::TailDyn),
372 RecordRowsF::TailVar(id) => Ok(RecordRowsF::TailVar(id)),
373 }
374 }
375
376 /// Variant of `try_map_state` without threaded state.
377 pub fn try_map<TyO, RRowsO, FTy, FRRows, E>(
378 self,
379 mut f_ty: FTy,
380 mut f_rrows: FRRows,
381 ) -> Result<RecordRowsF<TyO, RRowsO>, E>
382 where
383 FTy: FnMut(Ty) -> Result<TyO, E>,
384 FRRows: FnMut(RRows) -> Result<RRowsO, E>,
385 {
386 let f_ty_lifted = |rrow: Ty, _: &mut ()| -> Result<TyO, E> { f_ty(rrow) };
387 let f_rrows_lifted = |rrows: RRows, _: &mut ()| -> Result<RRowsO, E> { f_rrows(rrows) };
388
389 self.try_map_state(f_ty_lifted, f_rrows_lifted, &mut ())
390 }
391
392 /// Variant of `try_map_state` with infallible functions.
393 pub fn map_state<TyO, RRowsO, FTy, FRRows, S>(
394 self,
395 mut f_ty: FTy,
396 mut f_rrows: FRRows,
397 state: &mut S,
398 ) -> RecordRowsF<TyO, RRowsO>
399 where
400 FTy: FnMut(Ty, &mut S) -> TyO,
401 FRRows: FnMut(RRows, &mut S) -> RRowsO,
402 {
403 let f_ty_lifted = |rrow: Ty, state: &mut S| -> Result<TyO, ()> { Ok(f_ty(rrow, state)) };
404 let f_rrows_lifted =
405 |rrows: RRows, state: &mut S| -> Result<RRowsO, ()> { Ok(f_rrows(rrows, state)) };
406
407 self.try_map_state(f_ty_lifted, f_rrows_lifted, state)
408 .unwrap()
409 }
410
411 /// Variant of `try_map_state` without threaded state and with infallible functions.
412 pub fn map<TyO, RRowsO, FTy, FRRows>(
413 self,
414 mut f_ty: FTy,
415 mut f_rrows: FRRows,
416 ) -> RecordRowsF<TyO, RRowsO>
417 where
418 FTy: FnMut(Ty) -> TyO,
419 FRRows: FnMut(RRows) -> RRowsO,
420 {
421 let f_ty_lifted = |rrow: Ty| -> Result<TyO, Infallible> { Ok(f_ty(rrow)) };
422 let f_rrows_lifted = |rrows: RRows| -> Result<RRowsO, Infallible> { Ok(f_rrows(rrows)) };
423 self.try_map(f_ty_lifted, f_rrows_lifted).unwrap()
424 }
425}
426
427impl<Ty, ERows> EnumRowsF<Ty, ERows> {
428 /// Map functions over the tail of enum rows. The mutable state ( `S`) is threaded through the
429 /// calls to the mapped function. The function is fallible and may return an error `E`, which
430 /// causes `try_map_state` to return early with the same error.
431 ///
432 /// If we put aside the state and the error (see [EnumRowsF::map), this function makes
433 /// `EnumRowsF` a functor. As hinted by the type signature, this function just maps on
434 /// "one-level" of recursion, so to speak. Take the instantiated version `EnumRows`, and
435 /// enum rows of the form ``[| 'foo, 'bar, 'baz |]``. Then, calling `try_map_state(f_erows,
436 /// state)` on these rows will map `f_erows` onto ``[| 'bar, 'baz |]``.
437 ///
438 /// Note that `f_erows` is just mapped once. Map isn't a recursive operation. It's however a
439 /// building block to express recursive operations: as an example, see [RecordRows::traverse].
440 pub fn try_map_state<TyO, ERowsO, FTy, FERows, S, E>(
441 self,
442 mut f_ty: FTy,
443 f_erows: FERows,
444 state: &mut S,
445 ) -> Result<EnumRowsF<TyO, ERowsO>, E>
446 where
447 FTy: FnMut(Ty, &mut S) -> Result<TyO, E>,
448 FERows: FnOnce(ERows, &mut S) -> Result<ERowsO, E>,
449 {
450 match self {
451 EnumRowsF::Empty => Ok(EnumRowsF::Empty),
452 EnumRowsF::Extend {
453 row: EnumRowF { id, typ },
454 tail,
455 } => Ok(EnumRowsF::Extend {
456 row: EnumRowF {
457 id,
458 typ: typ.map(|ty| f_ty(ty, state)).transpose()?,
459 },
460 tail: f_erows(tail, state)?,
461 }),
462 EnumRowsF::TailVar(id) => Ok(EnumRowsF::TailVar(id)),
463 }
464 }
465
466 /// Variant of `try_map_state` without threaded state.
467 pub fn try_map<TyO, ERowsO, FTy, FERows, E>(
468 self,
469 mut f_ty: FTy,
470 mut f_erows: FERows,
471 ) -> Result<EnumRowsF<TyO, ERowsO>, E>
472 where
473 FTy: FnMut(Ty) -> Result<TyO, E>,
474 FERows: FnMut(ERows) -> Result<ERowsO, E>,
475 {
476 let f_ty_lifted = |erow: Ty, _: &mut ()| -> Result<TyO, E> { f_ty(erow) };
477 let f_erows_lifted = |erows: ERows, _: &mut ()| -> Result<ERowsO, E> { f_erows(erows) };
478
479 self.try_map_state(f_ty_lifted, f_erows_lifted, &mut ())
480 }
481
482 /// Variant of `try_map_state` with infallible functions.
483 pub fn map_state<TyO, ERowsO, FTy, FERows, S>(
484 self,
485 mut f_ty: FTy,
486 mut f_erows: FERows,
487 state: &mut S,
488 ) -> EnumRowsF<TyO, ERowsO>
489 where
490 FTy: FnMut(Ty, &mut S) -> TyO,
491 FERows: FnMut(ERows, &mut S) -> ERowsO,
492 {
493 let f_ty_lifted = |erow: Ty, state: &mut S| -> Result<TyO, ()> { Ok(f_ty(erow, state)) };
494 let f_erows_lifted =
495 |erows: ERows, state: &mut S| -> Result<ERowsO, ()> { Ok(f_erows(erows, state)) };
496 self.try_map_state(f_ty_lifted, f_erows_lifted, state)
497 .unwrap()
498 }
499
500 /// Variant of `try_map_state` without threaded state and with infallible functions.
501 pub fn map<TyO, ERowsO, FTy, FERows>(
502 self,
503 mut f_ty: FTy,
504 mut f_erows: FERows,
505 ) -> EnumRowsF<TyO, ERowsO>
506 where
507 FTy: FnMut(Ty) -> TyO,
508 FERows: FnMut(ERows) -> ERowsO,
509 {
510 let f_ty_lifted = |erow: Ty| -> Result<TyO, Infallible> { Ok(f_ty(erow)) };
511 let f_erows_lifted = |erows: ERows| -> Result<ERowsO, Infallible> { Ok(f_erows(erows)) };
512
513 self.try_map(f_ty_lifted, f_erows_lifted).unwrap()
514 }
515}
516
517impl<Ty, RRows, ERows, Te> TypeF<Ty, RRows, ERows, Te> {
518 /// Map functions over the children nodes of a type, when seen as a tree. The mutable state (
519 /// `S`) is threaded through the calls to the mapped functions. Functions are fallible and may
520 /// return an error `E`, which causes `try_map_state` to return early with the same error.
521 ///
522 /// If we put aside the state and the error (see [RecordRowsF::map), this function makes
523 /// `TypeF` a functor (of arity 3). As hinted by the type signature, this function just
524 /// maps on "one-level" of recursion, so to speak.
525 ///
526 /// Take the instantiated version `Type`, and a type of the form `(Dyn -> Dyn) -> (Number ->
527 /// Dyn)`. Then, calling `try_map_state(f_ty, ..)` on this type rows will map `f_ty` onto `(Dyn
528 /// -> Dyn)` and `Number -> Dyn` because they are direct children of the root `Arrow` node.
529 ///
530 /// Note that `f_ty` isn't mapped onto `Dyn` and `Number` recursively: map isn't a recursive
531 /// operation. It's however a building block to express recursive operations: as an example,
532 /// see [RecordRows::traverse].
533 ///
534 /// Since `TypeF` may contain record rows and enum rows as well, `f_rrows` and `f_erows` are
535 /// required to know how to map on record and enum types respectively.
536 pub fn try_map_state<TyO, RRowsO, ERowsO, TeO, FTy, FRRows, FERows, FTe, S, E>(
537 self,
538 mut f: FTy,
539 mut f_rrows: FRRows,
540 mut f_erows: FERows,
541 mut f_ctr: FTe,
542 state: &mut S,
543 ) -> Result<TypeF<TyO, RRowsO, ERowsO, TeO>, E>
544 where
545 FTy: FnMut(Ty, &mut S) -> Result<TyO, E>,
546 FRRows: FnMut(RRows, &mut S) -> Result<RRowsO, E>,
547 FERows: FnMut(ERows, &mut S) -> Result<ERowsO, E>,
548 FTe: FnMut(Te, &mut S) -> Result<TeO, E>,
549 {
550 match self {
551 TypeF::Dyn => Ok(TypeF::Dyn),
552 TypeF::Number => Ok(TypeF::Number),
553 TypeF::Bool => Ok(TypeF::Bool),
554 TypeF::String => Ok(TypeF::String),
555 TypeF::ForeignId => Ok(TypeF::ForeignId),
556 TypeF::Symbol => Ok(TypeF::Symbol),
557 TypeF::Contract(t) => Ok(TypeF::Contract(f_ctr(t, state)?)),
558 TypeF::Arrow(dom, codom) => Ok(TypeF::Arrow(f(dom, state)?, f(codom, state)?)),
559 TypeF::Var(i) => Ok(TypeF::Var(i)),
560 TypeF::Forall {
561 var,
562 var_kind,
563 body,
564 } => Ok(TypeF::Forall {
565 var,
566 var_kind,
567 body: f(body, state)?,
568 }),
569 TypeF::Enum(erows) => Ok(TypeF::Enum(f_erows(erows, state)?)),
570 TypeF::Record(rrows) => Ok(TypeF::Record(f_rrows(rrows, state)?)),
571 TypeF::Dict {
572 type_fields,
573 flavour: attrs,
574 } => Ok(TypeF::Dict {
575 type_fields: f(type_fields, state)?,
576 flavour: attrs,
577 }),
578 TypeF::Array(t) => Ok(TypeF::Array(f(t, state)?)),
579 TypeF::Wildcard(i) => Ok(TypeF::Wildcard(i)),
580 }
581 }
582
583 /// Variant of `try_map_state` without threaded state.
584 pub fn try_map<TyO, RRowsO, ERowsO, TeO, FTy, FRRows, FERows, FTe, E>(
585 self,
586 mut f: FTy,
587 mut f_rrows: FRRows,
588 mut f_erows: FERows,
589 mut f_ctr: FTe,
590 ) -> Result<TypeF<TyO, RRowsO, ERowsO, TeO>, E>
591 where
592 FTy: FnMut(Ty) -> Result<TyO, E>,
593 FRRows: FnMut(RRows) -> Result<RRowsO, E>,
594 FERows: FnMut(ERows) -> Result<ERowsO, E>,
595 FTe: FnMut(Te) -> Result<TeO, E>,
596 {
597 let f_lifted = |ty: Ty, _: &mut ()| -> Result<TyO, E> { f(ty) };
598 let f_rrows_lifted = |rrows: RRows, _: &mut ()| -> Result<RRowsO, E> { f_rrows(rrows) };
599 let f_erows_lifted = |erows: ERows, _: &mut ()| -> Result<ERowsO, E> { f_erows(erows) };
600 let f_ctr_lifted = |ctr: Te, _: &mut ()| -> Result<TeO, E> { f_ctr(ctr) };
601
602 self.try_map_state(
603 f_lifted,
604 f_rrows_lifted,
605 f_erows_lifted,
606 f_ctr_lifted,
607 &mut (),
608 )
609 }
610
611 /// Variant of `try_map_state` with infallible functions.
612 pub fn map_state<TyO, RRowsO, ERowsO, TeO, FTy, FRRows, FERows, FTe, S>(
613 self,
614 mut f: FTy,
615 mut f_rrows: FRRows,
616 mut f_erows: FERows,
617 mut f_ctr: FTe,
618 state: &mut S,
619 ) -> TypeF<TyO, RRowsO, ERowsO, TeO>
620 where
621 FTy: FnMut(Ty, &mut S) -> TyO,
622 FRRows: FnMut(RRows, &mut S) -> RRowsO,
623 FERows: FnMut(ERows, &mut S) -> ERowsO,
624 FTe: FnMut(Te, &mut S) -> TeO,
625 {
626 let f_lifted = |ty: Ty, state: &mut S| -> Result<TyO, Infallible> { Ok(f(ty, state)) };
627 let f_rrows_lifted = |rrows: RRows, state: &mut S| -> Result<RRowsO, Infallible> {
628 Ok(f_rrows(rrows, state))
629 };
630 let f_erows_lifted = |erows: ERows, state: &mut S| -> Result<ERowsO, Infallible> {
631 Ok(f_erows(erows, state))
632 };
633 let f_ctr_lifted =
634 |ctr: Te, state: &mut S| -> Result<TeO, Infallible> { Ok(f_ctr(ctr, state)) };
635
636 self.try_map_state(
637 f_lifted,
638 f_rrows_lifted,
639 f_erows_lifted,
640 f_ctr_lifted,
641 state,
642 )
643 .unwrap()
644 }
645
646 /// Variant of `try_map_state` without threaded state and with infallible functions.
647 pub fn map<TyO, RRowsO, ERowsO, TeO, FTy, FRRows, FERows, FTe>(
648 self,
649 mut f: FTy,
650 mut f_rrows: FRRows,
651 mut f_erows: FERows,
652 mut f_ctr: FTe,
653 ) -> TypeF<TyO, RRowsO, ERowsO, TeO>
654 where
655 FTy: FnMut(Ty) -> TyO,
656 FRRows: FnMut(RRows) -> RRowsO,
657 FERows: FnMut(ERows) -> ERowsO,
658 FTe: FnMut(Te) -> TeO,
659 {
660 let f_lifted = |ty: Ty, _: &mut ()| -> TyO { f(ty) };
661 let f_rrows_lifted = |rrows: RRows, _: &mut ()| -> RRowsO { f_rrows(rrows) };
662 let f_erows_lifted = |erows: ERows, _: &mut ()| -> ERowsO { f_erows(erows) };
663 let f_ctr_lifted = |ctr: Te, _: &mut ()| -> TeO { f_ctr(ctr) };
664
665 self.map_state(
666 f_lifted,
667 f_rrows_lifted,
668 f_erows_lifted,
669 f_ctr_lifted,
670 &mut (),
671 )
672 }
673
674 pub fn is_wildcard(&self) -> bool {
675 matches!(self, TypeF::Wildcard(_))
676 }
677
678 pub fn is_contract(&self) -> bool {
679 matches!(self, TypeF::Contract(_))
680 }
681}
682
683impl Traverse<Type> for RecordRows {
684 fn traverse<F, E>(self, f: &mut F, order: TraverseOrder) -> Result<RecordRows, E>
685 where
686 F: FnMut(Type) -> Result<Type, E>,
687 {
688 // traverse keeps track of state in the FnMut function. try_map_state
689 // keeps track of it in a separate state variable. we can pass the
690 // former into the latter by treating the function itself as the state
691 let rows = self.0.try_map_state(
692 |ty, f| Ok(Box::new(ty.traverse(f, order)?)),
693 |rrows, f| Ok(Box::new(rrows.traverse(f, order)?)),
694 f,
695 )?;
696
697 Ok(RecordRows(rows))
698 }
699
700 fn traverse_ref<S, U>(
701 &self,
702 f: &mut dyn FnMut(&Type, &S) -> TraverseControl<S, U>,
703 state: &S,
704 ) -> Option<U> {
705 match &self.0 {
706 RecordRowsF::Extend { row, tail } => row
707 .typ
708 .traverse_ref(f, state)
709 .or_else(|| tail.traverse_ref(f, state)),
710 _ => None,
711 }
712 }
713}
714
715impl Traverse<Type> for EnumRows {
716 fn traverse<F, E>(self, f: &mut F, order: TraverseOrder) -> Result<EnumRows, E>
717 where
718 F: FnMut(Type) -> Result<Type, E>,
719 {
720 // traverse keeps track of state in the FnMut function. try_map_state
721 // keeps track of it in a separate state variable. we can pass the
722 // former into the latter by treating the function itself as the state
723 let rows = self.0.try_map_state(
724 |ty, f| Ok(Box::new(ty.traverse(f, order)?)),
725 |rrows, f| Ok(Box::new(rrows.traverse(f, order)?)),
726 f,
727 )?;
728
729 Ok(EnumRows(rows))
730 }
731
732 fn traverse_ref<S, U>(
733 &self,
734 f: &mut dyn FnMut(&Type, &S) -> TraverseControl<S, U>,
735 state: &S,
736 ) -> Option<U> {
737 match &self.0 {
738 EnumRowsF::Extend { row, tail } => row
739 .typ
740 .as_ref()
741 .and_then(|ty| ty.traverse_ref(f, state))
742 .or_else(|| tail.traverse_ref(f, state)),
743 _ => None,
744 }
745 }
746}
747
748#[derive(Clone, Debug)]
749pub struct UnboundTypeVariableError(pub LocIdent);
750
751impl From<UnboundTypeVariableError> for EvalError {
752 fn from(err: UnboundTypeVariableError) -> Self {
753 let UnboundTypeVariableError(id) = err;
754 let pos = id.pos;
755 EvalError::UnboundIdentifier(id, pos)
756 }
757}
758
759impl From<UnboundTypeVariableError> for TypecheckError {
760 fn from(err: UnboundTypeVariableError) -> Self {
761 use crate::{bytecode::ast::alloc::AstAlloc, error::TypecheckErrorData};
762
763 TypecheckError::new(AstAlloc::new(), |_alloc| {
764 TypecheckErrorData::UnboundTypeVariable(err.0)
765 })
766 }
767}
768
769impl From<UnboundTypeVariableError> for ParseError {
770 fn from(err: UnboundTypeVariableError) -> Self {
771 ParseError::UnboundTypeVariables(vec![err.0])
772 }
773}
774
775impl From<UnboundTypeVariableError> for ParseErrors {
776 fn from(err: UnboundTypeVariableError) -> Self {
777 ParseErrors::from(ParseError::from(err))
778 }
779}
780
781pub struct RecordRowsIterator<'a, Ty, RRows> {
782 pub(crate) rrows: Option<&'a RRows>,
783 pub(crate) ty: std::marker::PhantomData<Ty>,
784}
785
786pub enum RecordRowsIteratorItem<'a, Ty> {
787 TailDyn,
788 TailVar(&'a LocIdent),
789 Row(RecordRowF<&'a Ty>),
790}
791
792impl<'a> Iterator for RecordRowsIterator<'a, Type, RecordRows> {
793 type Item = RecordRowsIteratorItem<'a, Type>;
794
795 fn next(&mut self) -> Option<Self::Item> {
796 self.rrows.and_then(|next| match next.0 {
797 RecordRowsF::Empty => {
798 self.rrows = None;
799 None
800 }
801 RecordRowsF::TailDyn => {
802 self.rrows = None;
803 Some(RecordRowsIteratorItem::TailDyn)
804 }
805 RecordRowsF::TailVar(ref id) => {
806 self.rrows = None;
807 Some(RecordRowsIteratorItem::TailVar(id))
808 }
809 RecordRowsF::Extend { ref row, ref tail } => {
810 self.rrows = Some(tail);
811 Some(RecordRowsIteratorItem::Row(RecordRowF {
812 id: row.id,
813 typ: row.typ.as_ref(),
814 }))
815 }
816 })
817 }
818}
819
820pub struct EnumRowsIterator<'a, Ty, ERows> {
821 pub(crate) erows: Option<&'a ERows>,
822 pub(crate) ty: std::marker::PhantomData<Ty>,
823}
824
825pub enum EnumRowsIteratorItem<'a, Ty> {
826 TailVar(&'a LocIdent),
827 Row(EnumRowF<&'a Ty>),
828}
829
830impl<'a> Iterator for EnumRowsIterator<'a, Type, EnumRows> {
831 type Item = EnumRowsIteratorItem<'a, Type>;
832
833 fn next(&mut self) -> Option<Self::Item> {
834 self.erows.and_then(|next| match next.0 {
835 EnumRowsF::Empty => {
836 self.erows = None;
837 None
838 }
839 EnumRowsF::TailVar(ref id) => {
840 self.erows = None;
841 Some(EnumRowsIteratorItem::TailVar(id))
842 }
843 EnumRowsF::Extend { ref row, ref tail } => {
844 self.erows = Some(tail);
845 Some(EnumRowsIteratorItem::Row(EnumRowF {
846 id: row.id,
847 typ: row.typ.as_ref().map(AsRef::as_ref),
848 }))
849 }
850 })
851 }
852}
853
854trait Subcontract {
855 /// Return the contract corresponding to a type component of a larger type.
856 ///
857 /// # Arguments
858 ///
859 /// - `vars` is an environment mapping type variables to contracts. Type variables are
860 /// introduced locally when opening a `forall`. Note that we don't need to keep separate
861 /// environments for different kind of type variables, as by shadowing, one name can only
862 /// refer to one type of variable at any given time.
863 /// - `pol` is the current polarity, which is toggled when generating a contract for the
864 /// argument of an arrow type (see [`crate::label::Label`]).
865 /// - `sy` is a counter used to generate fresh symbols for `forall` contracts (see
866 /// [`crate::term::Term::Sealed`]).
867 fn subcontract(
868 &self,
869 vars: Environment<Ident, RichTerm>,
870 pol: Polarity,
871 sy: &mut i32,
872 ) -> Result<RichTerm, UnboundTypeVariableError>;
873}
874
875/// Retrieve the contract corresponding to a type variable occurrence in a type as a `RichTerm`.
876/// Helper used by the `subcontract` functions.
877fn get_var_contract(
878 vars: &Environment<Ident, RichTerm>,
879 sym: Ident,
880 pos: TermPos,
881) -> Result<RichTerm, UnboundTypeVariableError> {
882 Ok(vars
883 .get(&sym)
884 .ok_or(UnboundTypeVariableError(LocIdent::from(sym).with_pos(pos)))?
885 .clone())
886}
887
888impl Subcontract for Type {
889 fn subcontract(
890 &self,
891 mut vars: Environment<Ident, RichTerm>,
892 pol: Polarity,
893 sy: &mut i32,
894 ) -> Result<RichTerm, UnboundTypeVariableError> {
895 let ctr = match self.typ {
896 TypeF::Dyn => internals::dynamic(),
897 TypeF::Number => internals::num(),
898 TypeF::Bool => internals::bool(),
899 TypeF::String => internals::string(),
900 TypeF::ForeignId => internals::foreign_id(),
901 // Array Dyn is specialized to array_dyn, which is constant time
902 TypeF::Array(ref ty) if matches!(ty.typ, TypeF::Dyn) => internals::array_dyn(),
903 TypeF::Array(ref ty) => mk_app!(internals::array(), ty.subcontract(vars, pol, sy)?),
904 TypeF::Symbol => panic!("unexpected Symbol type during contract elaboration"),
905 // Similarly, any variant of `A -> B` where either `A` or `B` is `Dyn` get specialized
906 // to the corresponding builtin contract.
907 TypeF::Arrow(ref s, ref t) if matches!((&s.typ, &t.typ), (TypeF::Dyn, TypeF::Dyn)) => {
908 internals::func_dyn()
909 }
910 TypeF::Arrow(ref s, ref t) if matches!(s.typ, TypeF::Dyn) => {
911 mk_app!(internals::func_codom(), t.subcontract(vars, pol, sy)?)
912 }
913 TypeF::Arrow(ref s, ref t) if matches!(t.typ, TypeF::Dyn) => {
914 mk_app!(
915 internals::func_dom(),
916 s.subcontract(vars.clone(), pol.flip(), sy)?
917 )
918 }
919 TypeF::Arrow(ref s, ref t) => mk_app!(
920 internals::func(),
921 s.subcontract(vars.clone(), pol.flip(), sy)?,
922 t.subcontract(vars, pol, sy)?
923 ),
924 // Note that we do an early return here.
925 //
926 // All builtin contracts needs an additional wrapping as a `CustomContract`: they're
927 // written as a custom contract but they miss the `%contract/custom%` (mostly because
928 // it's nicer to do it just once at the end than littering the internals module with
929 // `%contract/custom%` applications).
930 //
931 // However, in the case of a contract embedded in a type, we don't want to do the
932 // additional wrapping, as `t` should already be a fully constructed contract.
933 TypeF::Contract(ref t) => return Ok(t.clone()),
934 TypeF::Var(id) => get_var_contract(&vars, id, self.pos)?,
935 TypeF::Forall {
936 ref var,
937 ref body,
938 ref var_kind,
939 } => {
940 let sealing_key = Term::SealingKey(*sy);
941 let contract = match var_kind {
942 VarKind::Type => mk_app!(internals::forall_var(), sealing_key.clone()),
943 // For now, the enum contract doesn't enforce parametricity: see the
944 // implementation of `forall_enum_tail` inside the internal module for more
945 // details.
946 VarKind::EnumRows { .. } => internals::forall_enum_tail(),
947 VarKind::RecordRows { excluded } => {
948 let excluded_ncl: RichTerm = Term::Array(
949 Array::from_iter(
950 excluded
951 .iter()
952 .map(|id| Term::Str(NickelString::from(*id)).into()),
953 ),
954 Default::default(),
955 )
956 .into();
957
958 mk_app!(
959 internals::forall_record_tail(),
960 sealing_key.clone(),
961 excluded_ncl
962 )
963 }
964 };
965 vars.insert(var.ident(), contract);
966
967 *sy += 1;
968 mk_app!(
969 internals::forall(),
970 sealing_key,
971 Term::from(pol),
972 body.subcontract(vars, pol, sy)?
973 )
974 }
975 TypeF::Enum(ref erows) => erows.subcontract(vars, pol, sy)?,
976 TypeF::Record(ref rrows) => rrows.subcontract(vars, pol, sy)?,
977 // `{_: Dyn}` and `{_ | Dyn}` are equivalent, and both specialied to the constant-time
978 // `dict_dyn`.
979 TypeF::Dict {
980 ref type_fields,
981 flavour: _,
982 } if matches!(type_fields.typ, TypeF::Dyn) => internals::dict_dyn(),
983 TypeF::Dict {
984 ref type_fields,
985 flavour: DictTypeFlavour::Contract,
986 } => {
987 mk_app!(
988 internals::dict_contract(),
989 type_fields.subcontract(vars, pol, sy)?
990 )
991 }
992 TypeF::Dict {
993 ref type_fields,
994 flavour: DictTypeFlavour::Type,
995 } => {
996 mk_app!(
997 internals::dict_type(),
998 type_fields.subcontract(vars, pol, sy)?
999 )
1000 }
1001 TypeF::Wildcard(_) => internals::dynamic(),
1002 };
1003
1004 Ok(mk_term::custom_contract(ctr))
1005 }
1006}
1007
1008impl EnumRows {
1009 /// Find the row with the given identifier in the enum type. Return `None` if there is no such
1010 /// row.
1011 pub fn find_row(&self, id: Ident) -> Option<EnumRow> {
1012 self.iter().find_map(|row_item| match row_item {
1013 EnumRowsIteratorItem::Row(row) if row.id.ident() == id => Some(EnumRow {
1014 id: row.id,
1015 typ: row.typ.cloned().map(Box::new),
1016 }),
1017 _ => None,
1018 })
1019 }
1020
1021 pub fn iter(&self) -> EnumRowsIterator<Type, EnumRows> {
1022 EnumRowsIterator {
1023 erows: Some(self),
1024 ty: std::marker::PhantomData,
1025 }
1026 }
1027
1028 /// Simplify enum rows for contract generation when the rows are part of a static type
1029 /// annotation. See [Type::contract_static].
1030 ///
1031 /// The following simplification are applied:
1032 ///
1033 /// - the type of the argument of each enum variant is simplified as well
1034 /// - if the polarity is positive and the rows are composed entirely of enum tags and enum
1035 /// variants whose argument's simplified type is `Dyn`, the entire rows are elided by returning
1036 /// `None`
1037 /// - a tail variable in tail position is currently left unchanged, because it doesn't give
1038 /// rise to any sealing at runtime currently (see documentation of `$forall_enum_tail` in the
1039 /// internals module of the stdlib)
1040 fn simplify(
1041 self,
1042 contract_env: &mut Environment<Ident, RichTerm>,
1043 simplify_vars: SimplifyVars,
1044 polarity: Polarity,
1045 ) -> Option<Self> {
1046 // Actual simplification logic. We can elide the initial enum row type if **each**
1047 // individual row can be elided and this enum has an empty tail. Thus, when making
1048 // recursive calls, we can't just return `None` when some subrows can be elided, as we
1049 // might still need them. Instead, `do_simplify` returns a tuple of the simplified rows and
1050 // a boolean indicating if this part can be elided.
1051 fn do_simplify(
1052 rows: EnumRows,
1053 contract_env: &mut Environment<Ident, RichTerm>,
1054 simplify_vars: SimplifyVars,
1055 polarity: Polarity,
1056 ) -> (EnumRows, bool) {
1057 match rows {
1058 EnumRows(EnumRowsF::Empty) => (EnumRows(EnumRowsF::Empty), true),
1059 // Currently, we could actually return `true` here, because the tail contract for
1060 // enums doesn't do anything (see documentation of `$forall_enum_tail` in the
1061 // `internals` module of the stdlib). However, if we ever fix this shortcoming of
1062 // enum tail contracts and actually enforce parametricity, returning `true` would
1063 // become incorrect. So let's be future proof.
1064 EnumRows(EnumRowsF::TailVar(id)) => (EnumRows(EnumRowsF::TailVar(id)), false),
1065 EnumRows(EnumRowsF::Extend { row, tail }) => {
1066 let (tail, mut elide) =
1067 do_simplify(*tail, contract_env, simplify_vars.clone(), polarity);
1068
1069 let typ = row.typ.map(|mut typ| {
1070 *typ = typ.simplify(contract_env, simplify_vars, polarity);
1071 elide = elide && matches!(typ.typ, TypeF::Dyn);
1072 typ
1073 });
1074
1075 let row = EnumRowF { id: row.id, typ };
1076
1077 (
1078 EnumRows(EnumRowsF::Extend {
1079 row,
1080 tail: Box::new(tail),
1081 }),
1082 elide,
1083 )
1084 }
1085 }
1086 }
1087
1088 let (result, elide) = do_simplify(self, contract_env, simplify_vars, polarity);
1089
1090 if matches!((elide, polarity), (true, Polarity::Positive)) {
1091 None
1092 } else {
1093 Some(result)
1094 }
1095 }
1096}
1097
1098impl Subcontract for EnumRows {
1099 fn subcontract(
1100 &self,
1101 vars: Environment<Ident, RichTerm>,
1102 pol: Polarity,
1103 sy: &mut i32,
1104 ) -> Result<RichTerm, UnboundTypeVariableError> {
1105 use crate::term::{
1106 pattern::{EnumPattern, Pattern, PatternData},
1107 BinaryOp,
1108 };
1109
1110 let mut branches = Vec::new();
1111 let mut tail_var = None;
1112
1113 let value_arg = LocIdent::fresh();
1114 let label_arg = LocIdent::fresh();
1115 // We don't need to generate a different fresh variable for each match branch, as they have
1116 // their own scope, so we use the same name instead.
1117 let variant_arg = LocIdent::fresh();
1118
1119 // We build a match where each row corresponds to a branch, such that:
1120 //
1121 // - if the row is a simple enum tag, we just return the original contract argument
1122 // - if the row is an enum variant, we extract the argument and apply the corresponding
1123 // contract to it
1124 //
1125 // For the default branch, depending on the tail:
1126 //
1127 // - if the tail is an enum type variable, we perform the required sealing/unsealing
1128 // - otherwise, if the enum type is closed, we add a default case which blames
1129 //
1130 // For example, for an enum type [| 'foo, 'bar, 'Baz T |], the function looks like:
1131 //
1132 // ```
1133 // fun label value =>
1134 // value |> match {
1135 // 'foo => 'Ok x,
1136 // 'bar => 'Ok x,
1137 // 'Baz variant_arg => 'Ok ('Baz (%apply_contract% T label_arg variant_arg)),
1138 // _ => $enum_fail l
1139 // }
1140 // ```
1141 for row in self.iter() {
1142 match row {
1143 EnumRowsIteratorItem::Row(row) => {
1144 let arg_pattern = row.typ.as_ref().map(|_| {
1145 Box::new(Pattern {
1146 data: PatternData::Any(variant_arg),
1147 alias: None,
1148 pos: TermPos::None,
1149 })
1150 });
1151
1152 let body = if let Some(ty) = row.typ.as_ref() {
1153 // 'Tag (%apply_contract% T label_arg variant_arg)
1154 let arg = mk_app!(
1155 mk_term::op2(
1156 BinaryOp::ContractApply,
1157 ty.subcontract(vars.clone(), pol, sy)?,
1158 mk_term::var(label_arg)
1159 ),
1160 mk_term::var(variant_arg)
1161 );
1162
1163 mk_term::enum_variant(row.id, arg)
1164 } else {
1165 mk_term::var(value_arg)
1166 };
1167
1168 let body = mk_term::enum_variant("Ok", body);
1169
1170 let pattern = Pattern {
1171 data: PatternData::Enum(EnumPattern {
1172 tag: row.id,
1173 pattern: arg_pattern,
1174 pos: row.id.pos,
1175 }),
1176 alias: None,
1177 pos: row.id.pos,
1178 };
1179
1180 branches.push(MatchBranch {
1181 pattern,
1182 guard: None,
1183 body,
1184 });
1185 }
1186 EnumRowsIteratorItem::TailVar(var) => {
1187 tail_var = Some(var);
1188 }
1189 }
1190 }
1191
1192 let (default, default_pos) = if let Some(var) = tail_var {
1193 (
1194 mk_app!(
1195 mk_term::op2(
1196 BinaryOp::ContractApply,
1197 get_var_contract(&vars, var.ident(), var.pos)?,
1198 mk_term::var(label_arg)
1199 ),
1200 mk_term::var(value_arg)
1201 ),
1202 var.pos,
1203 )
1204 } else {
1205 (
1206 mk_app!(internals::enum_fail(), mk_term::var(label_arg)),
1207 TermPos::None,
1208 )
1209 };
1210
1211 branches.push(MatchBranch {
1212 pattern: Pattern {
1213 data: PatternData::Wildcard,
1214 alias: None,
1215 pos: default_pos,
1216 },
1217 guard: None,
1218 body: default,
1219 });
1220
1221 // We pre-compile the match expression, so that it's not compiled again and again at each
1222 // application of the contract.
1223 let match_expr = MatchData { branches }.compile(mk_term::var(value_arg), TermPos::None);
1224
1225 let case = mk_fun!(label_arg, value_arg, match_expr);
1226 Ok(mk_app!(internals::enumeration(), case))
1227 }
1228}
1229
1230impl RecordRows {
1231 /// Find a nested binding in a record row type. The nested field is given as a list of
1232 /// successive fields, that is, as a path. Return `None` if there is no such binding.
1233 ///
1234 /// # Example
1235 ///
1236 /// - self: ` {a : {b : Number }}`
1237 /// - path: `["a", "b"]`
1238 /// - result: `Some(Number)`
1239 pub fn find_path(&self, path: &[Ident]) -> Option<RecordRow> {
1240 if path.is_empty() {
1241 return None;
1242 }
1243
1244 // While going through the record rows, we use this helper for recursion instead of
1245 // `find_path`, to avoid cloning a lot of intermediate rows, and rather only clone the
1246 // final one to return.
1247 fn find_path_ref<'a>(
1248 rrows: &'a RecordRows,
1249 path: &[Ident],
1250 ) -> Option<RecordRowF<&'a Type>> {
1251 let next = rrows.iter().find_map(|item| match item {
1252 RecordRowsIteratorItem::Row(row) if row.id.ident() == path[0] => Some(row.clone()),
1253 _ => None,
1254 });
1255
1256 if path.len() == 1 {
1257 next
1258 } else {
1259 match next.map(|row| &row.typ.typ) {
1260 Some(TypeF::Record(rrows)) => find_path_ref(rrows, &path[1..]),
1261 _ => None,
1262 }
1263 }
1264 }
1265
1266 find_path_ref(self, path).map(|row| RecordRow {
1267 id: row.id,
1268 typ: Box::new(row.typ.clone()),
1269 })
1270 }
1271
1272 /// Find the row with the given identifier in the record type. Return `None` if there is no such
1273 /// row.
1274 ///
1275 /// Equivalent to `find_path(&[id])`.
1276 pub fn find_row(&self, id: Ident) -> Option<RecordRow> {
1277 self.find_path(&[id])
1278 }
1279
1280 pub fn iter(&self) -> RecordRowsIterator<Type, RecordRows> {
1281 RecordRowsIterator {
1282 rrows: Some(self),
1283 ty: std::marker::PhantomData,
1284 }
1285 }
1286
1287 /// Simplify record rows for contract generation when the rows are part of a static type
1288 /// annotation. See [Type::contract_static].
1289 ///
1290 /// The following simplifications are applied:
1291 ///
1292 /// - the type of each field is simplified
1293 /// - if the polarity is positive and the tail is known to be simplified to `Dyn`, any field
1294 /// whose simplified type is `Dyn` is entirely elided from the final result. That is, `{foo :
1295 /// Number, bar : Number -> Number}` in positive position is simplified to `{bar : Number ->
1296 /// Dyn; Dyn}`. `{foo : Number, bar : Dyn}` is simplified to `{; Dyn}` which is simplified
1297 /// further to `Dyn` by [Type::simplify].
1298 ///
1299 /// We can't do that when the tail can't be simplified to `Dyn` (this is the case when the
1300 /// tail variable has been introduced by a forall in negative position).
1301 /// - The case of a tail variable is a bit more complex and is detailed below.
1302 ///
1303 /// # Simplification of tail variable
1304 ///
1305 /// The following paragraphs distinguish between a tail variable (and thus an enclosing record
1306 /// type) in positive position and a tail variable in negative position (the polarity of the
1307 /// introducing forall is yet another dimension, covered in each paragraph).
1308 ///
1309 /// ## Negative polarity
1310 ///
1311 /// The simplification of record row variables is made harder by the presence of excluded
1312 /// fields. It's tempting to replace all `r` in tail position by `Dyn` if the introducing
1313 /// `forall r` was in positive position, as we do for regular type variables, but it's
1314 /// unfortunately not sound. For example:
1315 ///
1316 /// ```nickel
1317 /// inject_meta: forall r. {; r} -> {meta : String; r}
1318 /// ```
1319 ///
1320 /// In this case, the `forall r` is in positive position, but the `r` in `{; r}` in negative
1321 /// position can't be entirely elided, because it checks that `meta` isn't already present in
1322 /// the original argument. In particular, the original contract rejects `inject_meta {meta =
1323 /// "hello"}`, but blindly simplifying the type of `inject_meta` to `{; Dyn} -> {; Dyn}`
1324 /// would allow this argument.
1325 ///
1326 /// We can simplify it to something like `{; $forall_record_tail_excluded_only ["meta"]} -> {;
1327 /// Dyn}` (further simplified to `{; $forall_record_tail_excluded_only ["meta"]} -> Dyn` by
1328 /// `Type::simplify`). `$forall_record_tail_excluded_only` is a specialized version of the
1329 /// record tail contract `$forall_record_tail$ which doesn't seal but still check for the
1330 /// absence of excluded fields.
1331 ///
1332 /// Note that we can't actually put an arbitrary contract in the tail of a record type. This is
1333 /// why the `simplify()` methods take a mutable reference to an environment `contract_env`
1334 /// which will be passed to the contract generation methods. What we do in practice is to bind
1335 /// a fresh record row variable to `$forall_record_tail_excluded_only ["meta"]` in this
1336 /// environment and we substitute the tail var for this fresh variable.
1337 ///
1338 /// On the other hand, in
1339 ///
1340 /// ```nickel
1341 /// update_meta: forall r. {meta : String; r} -> {meta : String; r}
1342 /// ```
1343 ///
1344 /// The contract can be simplified to `{meta : String; Dyn} -> Dyn`. To detect this case, we
1345 /// record the set of fields that are present in the current rows and check if this set is
1346 /// equal to the excluded fields for this row variable (it's always a subset of the excluded
1347 /// fields by definition of row constraints), or if there are some excluded fields left to
1348 /// check.
1349 ///
1350 /// ## Positive polarity
1351 ///
1352 /// In a positive position, we can replace the tail with `Dyn` if the tail isn't a variable
1353 /// introduced in negative position, because a typed term can never violate row constraints.
1354 ///
1355 /// For a variable introduced by a forall in negative position, we need to keep the unsealing
1356 /// operation to make sure the corresponding forall type doesn't blame. But the unsealing
1357 /// operation needs prior sealing to work out, so we need to keep the tail even for record
1358 /// types in positive position. What's more, _we can't elide any field in this case_. Indeed,
1359 /// eliding fields can change what goes in the tail, and can only be done when the tail is
1360 /// ensured to be `Dyn`.
1361 fn simplify(
1362 self,
1363 contract_env: &mut Environment<Ident, RichTerm>,
1364 simplify_vars: SimplifyVars,
1365 polarity: Polarity,
1366 ) -> Self {
1367 // This helper does a first traversal of record rows in order to peek the tail and check if
1368 // it's a variable introduced by a forall in negative position. In this case, the second
1369 // component of the result will be `false` and we can't elide any field during the actual
1370 // simplification.
1371 //
1372 // As we will need the set of all fields later, we build during this first traversal as well.
1373 fn peek_tail(rrows: &RecordRows, simplify_vars: &SimplifyVars) -> (HashSet<Ident>, bool) {
1374 let mut fields = HashSet::new();
1375 let mut can_elide = true;
1376
1377 for row in rrows.iter() {
1378 match row {
1379 RecordRowsIteratorItem::Row(row) => {
1380 fields.insert(row.id.ident());
1381 }
1382 RecordRowsIteratorItem::TailVar(id)
1383 if simplify_vars.rrows_vars_elide.get(&id.ident()).is_none() =>
1384 {
1385 can_elide = false;
1386 }
1387 _ => {}
1388 }
1389 }
1390
1391 (fields, can_elide)
1392 }
1393
1394 // Actual simplification logic, which additionally remembers if a field has been elided and
1395 // if we thus need to change the tail to `Dyn` to account for the elision.
1396 fn do_simplify(
1397 rrows: RecordRows,
1398 contract_env: &mut Environment<Ident, RichTerm>,
1399 simplify_vars: SimplifyVars,
1400 polarity: Polarity,
1401 fields: &HashSet<Ident>,
1402 can_elide: bool,
1403 ) -> RecordRows {
1404 let rrows = match rrows.0 {
1405 // Because we may have elided some fields, we always make this record type open
1406 // when in positive position, which never hurts
1407 RecordRowsF::Empty if matches!(polarity, Polarity::Positive) => {
1408 RecordRowsF::TailDyn
1409 }
1410 RecordRowsF::Empty => RecordRowsF::Empty,
1411 RecordRowsF::Extend { row, tail } => {
1412 let typ_simplified =
1413 row.typ
1414 .simplify(contract_env, simplify_vars.clone(), polarity);
1415 // If we are in a positive position, the simplified type of the field is `Dyn`,
1416 // and we can elide fields given the current tail of these rows, we get rid of
1417 // the whole field.
1418 let elide_field = matches!(
1419 (&typ_simplified.typ, polarity),
1420 (TypeF::Dyn, Polarity::Positive)
1421 ) && can_elide;
1422
1423 let row = RecordRow {
1424 id: row.id,
1425 typ: Box::new(typ_simplified),
1426 };
1427
1428 let tail = Box::new(do_simplify(
1429 *tail,
1430 contract_env,
1431 simplify_vars,
1432 polarity,
1433 fields,
1434 can_elide,
1435 ));
1436
1437 if elide_field {
1438 tail.0
1439 } else {
1440 RecordRowsF::Extend { row, tail }
1441 }
1442 }
1443 RecordRowsF::TailDyn => RecordRowsF::TailDyn,
1444 RecordRowsF::TailVar(id) => {
1445 let excluded = simplify_vars.rrows_vars_elide.get(&id.ident());
1446
1447 match (excluded, polarity) {
1448 // If the variable was introduced in positive position, it won't cause any
1449 // (un)sealing. So if we are in positive position as well, we can just get
1450 // rid of the tail
1451 (Some(_), Polarity::Positive) => RecordRowsF::TailDyn,
1452 // If the variable was introduced in negative position, we can't elide the
1453 // unsealing contracts which are in negative position as well, and thus
1454 // neither elide the corresponding sealing contract.
1455 (None, Polarity::Positive | Polarity::Negative) => RecordRowsF::TailVar(id),
1456 // If the variable was introduced by a forall in positive position and we
1457 // are in a negative position, we can do some simplifying and at least get
1458 // rid of the sealing operation, and sometimes of the whole tail contract.
1459 (Some(excluded), Polarity::Negative) => {
1460 // We don't have to check for the absence of fields that appear in this row
1461 // type
1462 let excluded = excluded - fields;
1463
1464 // If all the excluded fields are listed in the record type, the
1465 // contract will never blame on the presence of an excluded field, so
1466 // we can get rid of the tail altogether
1467 if excluded.is_empty() {
1468 RecordRowsF::TailDyn
1469 }
1470 // Otherwise, we need to check for the absence of the remaining excluded
1471 // fields with a specialized tail contract which doesn't seal the tail.
1472 else {
1473 let fresh_var = LocIdent::fresh();
1474
1475 let excluded_ncl: RichTerm = Term::Array(
1476 Array::from_iter(
1477 excluded
1478 .iter()
1479 .map(|id| Term::Str(NickelString::from(*id)).into()),
1480 ),
1481 Default::default(),
1482 )
1483 .into();
1484
1485 contract_env.insert(
1486 fresh_var.ident(),
1487 mk_app!(
1488 internals::forall_record_tail_excluded_only(),
1489 excluded_ncl
1490 ),
1491 );
1492
1493 RecordRowsF::TailVar(fresh_var)
1494 }
1495 }
1496 }
1497 }
1498 };
1499
1500 RecordRows(rrows)
1501 }
1502
1503 let (fields, can_elide) = peek_tail(&self, &simplify_vars);
1504 do_simplify(
1505 self,
1506 contract_env,
1507 simplify_vars,
1508 polarity,
1509 &fields,
1510 can_elide,
1511 )
1512 }
1513}
1514
1515impl Subcontract for RecordRows {
1516 fn subcontract(
1517 &self,
1518 vars: Environment<Ident, RichTerm>,
1519 pol: Polarity,
1520 sy: &mut i32,
1521 ) -> Result<RichTerm, UnboundTypeVariableError> {
1522 // We begin by building a record whose arguments are contracts
1523 // derived from the types of the statically known fields.
1524 let mut rrows = self;
1525 let mut fcs = IndexMap::new();
1526
1527 while let RecordRowsF::Extend {
1528 row: RecordRowF { id, typ: ty },
1529 tail,
1530 } = &rrows.0
1531 {
1532 fcs.insert(*id, ty.subcontract(vars.clone(), pol, sy)?);
1533 rrows = tail
1534 }
1535
1536 let has_tail = !matches!(&rrows.0, RecordRowsF::Empty);
1537 // Now that we've dealt with the row extends, we just need to
1538 // work out the tail.
1539 let tail = match &rrows.0 {
1540 RecordRowsF::Empty => internals::empty_tail(),
1541 RecordRowsF::TailDyn => internals::dyn_tail(),
1542 RecordRowsF::TailVar(id) => get_var_contract(&vars, id.ident(), id.pos)?,
1543 // Safety: the while above excludes that `tail` can have the form `Extend`.
1544 RecordRowsF::Extend { .. } => unreachable!(),
1545 };
1546
1547 let rec = RichTerm::from(Term::Record(RecordData::with_field_values(fcs)));
1548
1549 Ok(mk_app!(
1550 internals::record_type(),
1551 rec,
1552 tail,
1553 Term::Bool(has_tail)
1554 ))
1555 }
1556}
1557
1558impl From<TypeF<Box<Type>, RecordRows, EnumRows, RichTerm>> for Type {
1559 fn from(typ: TypeF<Box<Type>, RecordRows, EnumRows, RichTerm>) -> Self {
1560 Type {
1561 typ,
1562 pos: TermPos::None,
1563 }
1564 }
1565}
1566
1567impl Type {
1568 /// Creates a `Type` with the specified position
1569 pub fn with_pos(self, pos: TermPos) -> Type {
1570 Type { pos, ..self }
1571 }
1572
1573 /// Returns the same type with the position cleared (set to `None`).
1574 ///
1575 /// This is currently only used in test code, but because it's used from integration
1576 /// tests we cannot hide it behind cfg(test).
1577 pub fn without_pos(self) -> Type {
1578 self.traverse(
1579 &mut |t: Type| {
1580 Ok::<_, Infallible>(Type {
1581 pos: TermPos::None,
1582 ..t
1583 })
1584 },
1585 TraverseOrder::BottomUp,
1586 )
1587 .unwrap()
1588 .traverse(
1589 &mut |t: RichTerm| {
1590 Ok::<_, Infallible>(RichTerm {
1591 pos: TermPos::None,
1592 ..t
1593 })
1594 },
1595 TraverseOrder::BottomUp,
1596 )
1597 .unwrap()
1598 }
1599
1600 /// Variant of [Self::contract] that returns the contract corresponding to a type which appears
1601 /// in a static type annotation. [Self::contract_static] uses the fact that the checked term
1602 /// has been typechecked to optimize the generated contract thanks to the guarantee of static
1603 /// typing.
1604 pub fn contract_static(self) -> Result<RichTerm, UnboundTypeVariableError> {
1605 let mut sy = 0;
1606 let mut contract_env = Environment::new();
1607
1608 self.simplify(&mut contract_env, SimplifyVars::new(), Polarity::Positive)
1609 .subcontract(contract_env, Polarity::Positive, &mut sy)
1610 }
1611
1612 /// Return the contract corresponding to a type. Said contract must then be applied using the
1613 /// `ApplyContract` primitive operation.
1614 pub fn contract(&self) -> Result<RichTerm, UnboundTypeVariableError> {
1615 increment!(format!("gen_contract:{}", self.pretty_print_cap(40)));
1616
1617 let mut sy = 0;
1618
1619 self.subcontract(Environment::new(), Polarity::Positive, &mut sy)
1620 }
1621
1622 /// Returns true if this type is a function type (including a polymorphic one), false
1623 /// otherwise.
1624 pub fn is_function_type(&self) -> bool {
1625 match &self.typ {
1626 TypeF::Forall { body, .. } => body.is_function_type(),
1627 TypeF::Arrow(..) => true,
1628 _ => false,
1629 }
1630 }
1631
1632 /// Determine if a type is an atom, that is a either a primitive type (`Dyn`, `Number`, etc.) or
1633 /// a type delimited by specific markers (such as a row type). Used in formatting to decide if
1634 /// parentheses need to be inserted during pretty pretting.
1635 pub fn fmt_is_atom(&self) -> bool {
1636 match &self.typ {
1637 TypeF::Dyn
1638 | TypeF::Number
1639 | TypeF::Bool
1640 | TypeF::String
1641 | TypeF::Var(_)
1642 | TypeF::Record(_)
1643 | TypeF::Enum(_) => true,
1644 TypeF::Contract(rt) if rt.as_ref().is_atom() => true,
1645 _ => false,
1646 }
1647 }
1648
1649 /// Searches for a `TypeF::Contract`. If one is found, returns the term it contains.
1650 pub fn find_contract(&self) -> Option<RichTerm> {
1651 self.find_map(|ty: &Type| match &ty.typ {
1652 TypeF::Contract(f) => Some(f.clone()),
1653 _ => None,
1654 })
1655 }
1656
1657 /// Static typing guarantees make some of the contract checks useless, assuming that blame
1658 /// safety holds. This function simplifies `self` for contract generation, assuming it is part
1659 /// of a static type annotation, by eliding some of these useless subcontracts.
1660 ///
1661 /// # Simplifications
1662 ///
1663 /// - `forall`s of a type variable in positive positions are removed, and the corresponding type
1664 /// variable is substituted for a `Dyn` contract. In consequence, [Self::contract()] will generate
1665 /// simplified contracts as well. For example, `forall a. Array a -> a` becomes `Array Dyn -> Dyn`,
1666 /// and `Array Dyn` will then be specialized to `$array_dyn` which has a constant-time overhead
1667 /// (while `Array a` is linear in the size of the array). The final contract will just check that
1668 /// the argument is an array.
1669 /// - `forall`s of a row variable (either record or enum) are removed as well. The substitution of
1670 /// the corresponding row variable is a bit more complex than in the type variable case and depends
1671 /// on the situation. See [RecordRows::simplify] and [EnumRows::simplify] for more details.
1672 /// - `forall`s in negative position are left unchanged.
1673 /// - All positive occurrences of type constructors that aren't a function type are recursively
1674 /// simplified. If they are simplified to a trivial type, such as `{; Dyn}` for a record or `Array
1675 /// Dyn` for an array, they are further simplified to `Dyn`.
1676 /// are turned to `Dyn` contracts. However, we must be careful here: type constructors might
1677 /// have function types somewhere inside, as in `{foo : Number, bar : Array (String ->
1678 /// String) }`. In this case, we can elide `foo`, but not `bar`.
1679 fn simplify(
1680 self,
1681 contract_env: &mut Environment<Ident, RichTerm>,
1682 mut simplify_vars: SimplifyVars,
1683 polarity: Polarity,
1684 ) -> Self {
1685 let mut pos = self.pos;
1686
1687 let simplified = match self.typ {
1688 TypeF::Arrow(dom, codom) => TypeF::Arrow(
1689 Box::new(dom.simplify(contract_env, simplify_vars.clone(), polarity.flip())),
1690 Box::new(codom.simplify(contract_env, simplify_vars, polarity)),
1691 ),
1692 TypeF::Forall {
1693 var,
1694 var_kind: VarKind::Type,
1695 body,
1696 } if polarity == Polarity::Positive => {
1697 // The case of type variables is the simplest: we just replace all of them by
1698 // `Dyn`. We don't bother messing with the contract environment and only register
1699 // them in `simplify_vars`. Subsequent calls to `simplify` will check the register
1700 // and replace the variable by `Dyn` when appropriate.
1701 simplify_vars.type_vars_elide.insert(var.ident(), ());
1702
1703 let result = body.simplify(contract_env, simplify_vars, polarity);
1704 // we keep the position of the body, not the one of the forall
1705 pos = result.pos;
1706 result.typ
1707 }
1708 TypeF::Forall {
1709 var,
1710 var_kind: VarKind::RecordRows { excluded },
1711 body,
1712 } if polarity == Polarity::Positive => {
1713 simplify_vars.rrows_vars_elide.insert(var.ident(), excluded);
1714
1715 let result = body.simplify(contract_env, simplify_vars, polarity);
1716 // we keep the position of the body, not the one of the forall
1717 pos = result.pos;
1718 result.typ
1719 }
1720 // For enum rows in negative position, we don't any simplification for now because
1721 // parametricity isn't enforced. Polymorphic enum row contracts are mostly doing
1722 // nothing, and it's fine to keep them as they are. See the documentation of
1723 // `$forall_enum_tail` for more details. Even if we do enforce parametricity in the
1724 // future, we will be missing an optimization opportunity here but we won't introduce
1725 // any unsoundness.
1726 //
1727 // We don't elide foralls in negative position either.
1728 //
1729 // In both cases, we still simplify the body of the forall.
1730 TypeF::Forall {
1731 var,
1732 var_kind,
1733 body,
1734 } => TypeF::Forall {
1735 var,
1736 var_kind,
1737 body: Box::new(body.simplify(contract_env, simplify_vars, polarity)),
1738 },
1739 TypeF::Var(id) if simplify_vars.type_vars_elide.get(&id).is_some() => TypeF::Dyn,
1740 // Any ground type in positive position can be entirely elided
1741 TypeF::Number | TypeF::String | TypeF::Bool | TypeF::Symbol | TypeF::ForeignId
1742 if matches!(polarity, Polarity::Positive) =>
1743 {
1744 TypeF::Dyn
1745 }
1746 // We need to recurse into type constructors, which could contain arrow types inside.
1747 TypeF::Record(rrows) => {
1748 let rrows_simplified = rrows.simplify(contract_env, simplify_vars, polarity);
1749
1750 // [^simplify-type-constructor-positive]: if we are in positive position, and the
1751 // record type is entirely elided (which means simplified to `{; Dyn}`) or is
1752 // empty, we can simplify it further to `Dyn`. This works for other type
1753 // constructors as well (arrays, dictionaries, etc.): if there's no negative type
1754 // or polymorphic tail somewhere inside, we can get rid of the whole thing.
1755 if matches!(
1756 (&rrows_simplified, polarity),
1757 (
1758 RecordRows(RecordRowsF::TailDyn) | RecordRows(RecordRowsF::Empty),
1759 Polarity::Positive
1760 )
1761 ) {
1762 TypeF::Dyn
1763 } else {
1764 TypeF::Record(rrows_simplified)
1765 }
1766 }
1767 TypeF::Enum(erows) => {
1768 let erows = erows.simplify(contract_env, simplify_vars, polarity);
1769
1770 // See [^simplify-type-constructor-positive]
1771 erows.map(TypeF::Enum).unwrap_or(TypeF::Dyn)
1772 }
1773 TypeF::Dict {
1774 type_fields,
1775 flavour,
1776 } => {
1777 let type_fields =
1778 Box::new(type_fields.simplify(contract_env, simplify_vars, polarity));
1779
1780 // See [^simplify-type-constructor-positive]
1781 if matches!(
1782 (&type_fields.typ, polarity),
1783 (TypeF::Dyn, Polarity::Positive)
1784 ) {
1785 TypeF::Dyn
1786 } else {
1787 TypeF::Dict {
1788 type_fields,
1789 flavour,
1790 }
1791 }
1792 }
1793 TypeF::Array(t) => {
1794 let type_elts = t.simplify(contract_env, simplify_vars, polarity);
1795
1796 // See [^simplify-type-constructor-positive]
1797 if matches!((&type_elts.typ, polarity), (TypeF::Dyn, Polarity::Positive)) {
1798 TypeF::Dyn
1799 } else {
1800 TypeF::Array(Box::new(type_elts))
1801 }
1802 }
1803 // All the remaining cases are ground types that don't contain subtypes, or they are
1804 // type variables that can't be elided. We leave them unchanged
1805 t => t,
1806 };
1807
1808 Type {
1809 typ: simplified,
1810 pos,
1811 }
1812 }
1813}
1814
1815impl Traverse<Type> for Type {
1816 fn traverse<F, E>(self, f: &mut F, order: TraverseOrder) -> Result<Self, E>
1817 where
1818 F: FnMut(Type) -> Result<Type, E>,
1819 {
1820 let pre_map = match order {
1821 TraverseOrder::TopDown => f(self)?,
1822 TraverseOrder::BottomUp => self,
1823 };
1824
1825 // traverse keeps track of state in the FnMut function. try_map_state
1826 // keeps track of it in a separate state variable. we can pass the
1827 // former into the latter by treating the function itself as the state
1828 let typ = pre_map.typ.try_map_state(
1829 |ty, f| Ok(Box::new(ty.traverse(f, order)?)),
1830 |rrows, f| rrows.traverse(f, order),
1831 |erows, f| erows.traverse(f, order),
1832 |ctr, _| Ok(ctr),
1833 f,
1834 )?;
1835
1836 let post_map = Type { typ, ..pre_map };
1837
1838 match order {
1839 TraverseOrder::TopDown => Ok(post_map),
1840 TraverseOrder::BottomUp => f(post_map),
1841 }
1842 }
1843
1844 fn traverse_ref<S, U>(
1845 &self,
1846 f: &mut dyn FnMut(&Type, &S) -> TraverseControl<S, U>,
1847 state: &S,
1848 ) -> Option<U> {
1849 let child_state = match f(self, state) {
1850 TraverseControl::Continue => None,
1851 TraverseControl::ContinueWithScope(s) => Some(s),
1852 TraverseControl::SkipBranch => {
1853 return None;
1854 }
1855 TraverseControl::Return(ret) => {
1856 return Some(ret);
1857 }
1858 };
1859 let state = child_state.as_ref().unwrap_or(state);
1860
1861 match &self.typ {
1862 TypeF::Dyn
1863 | TypeF::Number
1864 | TypeF::Bool
1865 | TypeF::String
1866 | TypeF::ForeignId
1867 | TypeF::Symbol
1868 | TypeF::Var(_)
1869 | TypeF::Wildcard(_) => None,
1870 TypeF::Contract(rt) => rt.traverse_ref(f, state),
1871 TypeF::Arrow(t1, t2) => t1
1872 .traverse_ref(f, state)
1873 .or_else(|| t2.traverse_ref(f, state)),
1874 TypeF::Forall { body: t, .. }
1875 | TypeF::Dict { type_fields: t, .. }
1876 | TypeF::Array(t) => t.traverse_ref(f, state),
1877 TypeF::Record(rrows) => rrows.traverse_ref(f, state),
1878 TypeF::Enum(erows) => erows.traverse_ref(f, state),
1879 }
1880 }
1881}
1882
1883impl Traverse<RichTerm> for Type {
1884 fn traverse<F, E>(self, f: &mut F, order: TraverseOrder) -> Result<Self, E>
1885 where
1886 F: FnMut(RichTerm) -> Result<RichTerm, E>,
1887 {
1888 self.traverse(
1889 &mut |ty: Type| match ty.typ {
1890 TypeF::Contract(t) => t
1891 .traverse(f, order)
1892 .map(|t| Type::from(TypeF::Contract(t)).with_pos(ty.pos)),
1893 _ => Ok(ty),
1894 },
1895 order,
1896 )
1897 }
1898
1899 fn traverse_ref<S, U>(
1900 &self,
1901 f: &mut dyn FnMut(&RichTerm, &S) -> TraverseControl<S, U>,
1902 state: &S,
1903 ) -> Option<U> {
1904 self.traverse_ref(
1905 &mut |ty: &Type, s: &S| match &ty.typ {
1906 TypeF::Contract(t) => {
1907 if let Some(ret) = t.traverse_ref(f, s) {
1908 TraverseControl::Return(ret)
1909 } else {
1910 TraverseControl::SkipBranch
1911 }
1912 }
1913 _ => TraverseControl::Continue,
1914 },
1915 state,
1916 )
1917 }
1918}
1919
1920/// Some context storing various type variables that can be simplified during static contract
1921/// simplification (see the various `simplify()` methods).
1922#[derive(Clone, Debug, Default)]
1923struct SimplifyVars {
1924 /// Environment used as a persistent HashSet to record type variables that can be elided
1925 /// (replaced by `Dyn`) because they were introduced by a forall in positive position.
1926 type_vars_elide: Environment<Ident, ()>,
1927 /// Record record rows variables that were introduced by a forall in positive position and
1928 /// their corresponding `excluded` field. They will be substituted for different simplified
1929 /// contracts depending on where the variable appears and the surrounding record.
1930 rrows_vars_elide: Environment<Ident, HashSet<Ident>>,
1931}
1932
1933impl SimplifyVars {
1934 fn new() -> Self {
1935 Self::default()
1936 }
1937}
1938
1939impl_display_from_pretty!(Type);
1940impl_display_from_pretty!(EnumRow);
1941impl_display_from_pretty!(EnumRows);
1942impl_display_from_pretty!(RecordRow);
1943impl_display_from_pretty!(RecordRows);
1944
1945impl PrettyPrintCap for Type {}
1946
1947#[cfg(test)]
1948mod tests {
1949 use super::*;
1950 use crate::parser::{grammar::FixedTypeParser, lexer::Lexer, ErrorTolerantParserCompat};
1951
1952 /// Parse a type represented as a string.
1953 fn parse_type(s: &str) -> Type {
1954 use crate::files::Files;
1955 let id = Files::new().add("<test>", s);
1956
1957 FixedTypeParser::new()
1958 .parse_strict_compat(id, Lexer::new(s))
1959 .unwrap()
1960 }
1961
1962 /// Parse a type, simplify it and assert that the result corresponds to the second argument
1963 /// (which is parsed and printed again to eliminate formatting differences). This function also
1964 /// checks that the contract generation from the simplified version works without an unbound
1965 /// type variable error.
1966 #[track_caller]
1967 fn assert_simplifies_to(orig: &str, target: &str) {
1968 let parsed = parse_type(orig);
1969
1970 parsed.clone().contract_static().unwrap();
1971
1972 let simplified = parsed.simplify(
1973 &mut Environment::new(),
1974 SimplifyVars::new(),
1975 Polarity::Positive,
1976 );
1977 let target_typ = parse_type(target);
1978
1979 assert_eq!(format!("{simplified}"), format!("{target_typ}"));
1980 }
1981
1982 #[test]
1983 fn simplify() {
1984 assert_simplifies_to("forall a. a -> (a -> a) -> a", "Dyn -> (Dyn -> Dyn) -> Dyn");
1985 assert_simplifies_to(
1986 "forall b. (forall a. a -> a) -> b",
1987 "(forall a. a -> a) -> Dyn",
1988 );
1989
1990 // Big but entirely positive type
1991 assert_simplifies_to(
1992 "{foo : Array {bar : String, baz : Number}, qux: [| 'Foo, 'Bar, 'Baz Dyn |], pweep: {single : Array Bool}}",
1993 "Dyn"
1994 );
1995 // Mixed type with arrows inside the return value
1996 assert_simplifies_to(
1997 "Array Number -> Array {foo : Number, bar : String -> String}",
1998 "Array Number -> Array {bar: String -> Dyn; Dyn}",
1999 );
2000 // Enum with arrows inside
2001 assert_simplifies_to(
2002 "{single : Array [| 'Foo, 'Bar, 'Baz (Dyn -> Dyn) |] }",
2003 "{single : Array [| 'Foo, 'Bar, 'Baz (Dyn -> Dyn) |]; Dyn}",
2004 );
2005
2006 // Polymorphic rows, case without excluded fields to check
2007 assert_simplifies_to(
2008 "forall r. {meta : String, doc : String; r} -> {meta : String; r}",
2009 "{meta : String, doc : String; Dyn} -> Dyn",
2010 );
2011 // Polymorphic rows in negative position should prevent row elision in positive position
2012 assert_simplifies_to(
2013 "(forall r. {meta : String, count: Number; r} -> {; r}) -> {meta : String, count: Number}",
2014 "(forall r. {meta : Dyn, count : Dyn; r} -> {; r}) -> Dyn",
2015 );
2016 }
2017
2018 #[test]
2019 fn simplify_dont_elide_record() {
2020 use regex::Regex;
2021 // The simplified version of the type should contain a generated variable here. We can't
2022 // represent a generated variable in normal Nickel syntax, so instead of parsing the
2023 // expected result and print it again to eliminate formatting details as in
2024 // `assert_simplifies_to`, we will print the simplified version and do a direct string
2025 // comparison on the result.
2026 let orig = "forall r. {x: Number, y: String; r} -> {z: Array Bool; r}";
2027
2028 let mut contract_env = Environment::new();
2029 let simplified = parse_type(orig)
2030 .simplify(&mut contract_env, SimplifyVars::new(), Polarity::Positive)
2031 .to_string();
2032
2033 // Note: because we match on strings directly, we must get the formatting right.
2034 let expected = Regex::new(r"\{ x : Number, y : String; (%\d+) \} -> Dyn").unwrap();
2035 let caps = expected.captures(&simplified).unwrap();
2036
2037 // We should have the generated variable in the contract environment bound to
2038 // `$forall_enum_tail_excluded_only ["z"]`. Indeed, `x`, `y` and `z` are excluded fields
2039 // for the row variable `r`. Because `forall r` is in positive position, we remove the
2040 // forall. `{x: Number, y: String; r}` is in negative position, so we still need to check
2041 // for excluded fields, but `x` and `y` are already taken care of in the body of the record
2042 // type. It only remains to check that `z` isn't present in the input record.
2043 let expected_var = Ident::from(caps.get(1).unwrap().as_str());
2044
2045 assert_eq!(
2046 contract_env.get(&expected_var).unwrap().to_string(),
2047 "$forall_record_tail_excluded_only [ \"z\" ]"
2048 );
2049 }
2050}