-
Notifications
You must be signed in to change notification settings - Fork 83
/
mod.rs
2592 lines (2310 loc) · 89.3 KB
/
mod.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//! AST of a Nickel expression.
//!
//! # Core language
//!
//! At its core, Nickel is a lazy JSON with higher-order functions. It includes:
//! - Basic values: booleans, numerals, string
//! - Data structures: arrays and records
//! - Binders: functions and let bindings
//!
//! It also features types and type annotations, and other typechecking or contracts-related
//! constructs (label, symbols, etc.).
pub mod array;
pub mod record;
pub mod string;
use array::{Array, ArrayAttrs};
use record::{Field, FieldDeps, FieldMetadata, RecordData, RecordDeps};
use string::NickelString;
use crate::{
destructuring::RecordPattern,
error::{EvalError, ParseError},
eval::cache::CacheIndex,
eval::Environment,
identifier::LocIdent,
impl_display_from_pretty,
label::{Label, MergeLabel},
match_sharedterm,
position::TermPos,
typ::{Type, UnboundTypeVariableError},
typecheck::eq::{contract_eq, type_eq_noenv},
};
use codespan::FileId;
pub use malachite::{
num::{
basic::traits::Zero,
conversion::traits::{IsInteger, RoundingFrom, ToSci},
},
rounding_modes::RoundingMode,
Integer, Rational,
};
use serde::{Deserialize, Serialize};
// Because we use `IndexMap` for recors, consumer of Nickel (as a library) might have to
// manipulate values of this type, so we re-export this type.
pub use indexmap::IndexMap;
use std::{
cmp::{Ordering, PartialOrd},
convert::Infallible,
ffi::OsString,
fmt,
ops::Deref,
rc::Rc,
};
/// The AST of a Nickel expression.
///
/// Parsed terms also need to store their position in the source for error reporting. This is why
/// this type is nested with [`RichTerm`].
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Term {
/// The null value.
Null,
/// A boolean value.
Bool(bool),
/// A floating-point value.
#[serde(serialize_with = "crate::serialize::serialize_num")]
#[serde(deserialize_with = "crate::serialize::deserialize_num")]
Num(Number),
/// A literal string.
Str(NickelString),
/// A string containing interpolated expressions, represented as a list of either literals or
/// expressions.
///
/// /|\ CHUNKS ARE STORED IN REVERSE ORDER. As they will be only popped one by one from the
/// head of the list during evaluation, doing so on a `Vec` is costly, and using a more complex
/// data structure is not really necessary, as once created, no other than popping is ever
/// done. In consequence, we just reverse the vector at parsing time, so that we can then pop
/// efficiently from the back of it.
#[serde(skip)]
StrChunks(Vec<StrChunk<RichTerm>>),
/// A standard function.
#[serde(skip)]
Fun(LocIdent, RichTerm),
/// A function able to destruct its arguments.
#[serde(skip)]
FunPattern(Option<LocIdent>, RecordPattern, RichTerm),
/// A blame label.
#[serde(skip)]
Lbl(Label),
/// A let binding.
#[serde(skip)]
Let(LocIdent, RichTerm, RichTerm, LetAttrs),
/// A destructuring let-binding.
#[serde(skip)]
LetPattern(Option<LocIdent>, RecordPattern, RichTerm, RichTerm),
/// An application.
#[serde(skip)]
App(RichTerm, RichTerm),
/// A variable.
#[serde(skip)]
Var(LocIdent),
/// An enum tag, or equivalently, an enum variant without any argument.
Enum(LocIdent),
/// An applied enum variant (an algebraic data type). In Nickel ADTs can have at most one
/// argument: [Self::Enum] is the version with no argument, and [Self::EnumVariant] is the
/// version with one argument. Note that one can just use a record to store multiple named
/// values in the argument.
#[serde(skip)]
EnumVariant {
tag: LocIdent,
arg: RichTerm,
attrs: EnumVariantAttrs,
},
/// A record, mapping identifiers to terms.
#[serde(serialize_with = "crate::serialize::serialize_record")]
#[serde(deserialize_with = "crate::serialize::deserialize_record")]
Record(RecordData),
/// A recursive record, where the fields can reference each others.
#[serde(skip)]
RecRecord(
RecordData,
Vec<(RichTerm, Field)>, /* field whose name is defined by interpolation */
Option<RecordDeps>, /* dependency tracking between fields. None before the free var pass */
),
/// A match construct. Correspond only to the match cases: this expression is still to be
/// applied to an argument to match on. Once applied, the evaluation is done by the
/// corresponding primitive operator. Still, we need this construct for typechecking and being
/// able to handle yet unapplied match expressions.
#[serde(skip)]
Match {
cases: IndexMap<LocIdent, RichTerm>,
default: Option<RichTerm>,
},
/// An array.
#[serde(serialize_with = "crate::serialize::serialize_array")]
#[serde(deserialize_with = "crate::serialize::deserialize_array")]
Array(Array, ArrayAttrs),
/// A primitive unary operator.
#[serde(skip)]
Op1(UnaryOp, RichTerm),
/// A primitive binary operator.
#[serde(skip)]
Op2(BinaryOp, RichTerm, RichTerm),
/// An primitive n-ary operator.
#[serde(skip)]
OpN(NAryOp, Vec<RichTerm>),
/// A key locking a sealed term.
///
/// A unique key corresponding to a type variable. See [`Term::Sealed`] below.
#[serde(skip)]
SealingKey(SealingKey),
/// A sealed term.
///
/// Sealed terms are introduced by contracts on polymorphic types. Take the following example:
///
/// ```text
/// let f | forall a b. a -> b -> a = fun x y => y in
/// f true "a"
/// ```
///
/// This function is ill-typed. To check that, a polymorphic contract will:
///
/// - Assign a unique identifier to each type variable: say `a => 1`, `b => 2`
/// - For each cast on a negative occurrence of a type variable `a` or `b` (corresponding to an
/// argument position), tag the argument with the associated identifier. In our example, `f
/// true "a"` will push `Sealed(1, true)` then `Sealed(2, "a")` on the stack.
/// - For each cast on a positive occurrence of a type variable, this contract check that the
/// term is of the form `Sealed(id, term)` where `id` corresponds to the identifier of the
/// type variable. In our example, the last cast to `a` finds `Sealed(2, "a")`, while it
/// expected `Sealed(1, _)`, hence it raises a positive blame.
#[serde(skip)]
Sealed(SealingKey, RichTerm, Label),
/// A term with a type and/or contract annotation.
#[serde(serialize_with = "crate::serialize::serialize_annotated_value")]
#[serde(skip_deserializing)]
Annotated(TypeAnnotation, RichTerm),
/// An unresolved import.
#[serde(skip)]
Import(OsString),
/// A resolved import (which has already been loaded and parsed).
#[serde(skip)]
ResolvedImport(FileId),
/// A type in term position, such as in `let my_contract = Number -> Number in ...`.
///
/// During evaluation, this will get turned into a contract.
#[serde(skip)]
Type(Type),
/// A term that couldn't be parsed properly. Used by the LSP to handle partially valid
/// programs.
#[serde(skip)]
ParseError(ParseError),
/// A delayed runtime error. Usually, errors are raised and abort the execution right away,
/// without the need to store them in the AST. However, some cases require a term which aborts
/// with a specific error if evaluated, but is fine being stored and passed around.
///
/// The main use-cae is currently missing field definitions: when evaluating a recursive record
/// to a normal record with a recursive environment, we might find fields that aren't defined
/// currently, eg:
///
/// ```nickel
/// let r = {
/// foo = bar + 1,
/// bar | Number,
/// baz = 2,
/// } in
/// r.baz + (r & {bar = 1}).foo
/// ```
///
/// This program is valid, but when evaluating `r` in `r.baz`, `bar` doesn't have a definition
/// yet. This is fine because we don't evaluate `bar` nor `foo`. Still, we have to put
/// something in the recursive environment. And if we wrote `r.foo` instead, we should raise a
/// missing field definition error. Thus, we need to bind `bar` to a term wich, if ever
/// evaluated, will raise a proper missing field definition error. This is precisely the
/// behavior of `RuntimeError` behaves.
#[serde(skip)]
RuntimeError(EvalError),
#[serde(skip)]
/// A "pointer" (cache index, which can see as a kind of generic pointer to the memory managed
/// by the evaluation cache) to a term together with its environment. Unfortunately, this is an
/// evaluation object leaking into the AST: ideally, we would have one concrete syntax tree
/// coming out of the parser, and a different representation for later stages, storing closures
/// and whatnot.
///
/// This is not the case yet, so in the meantime, we have to mix everything together. The
/// ability to store closures directly in the AST without having to generate a variable and bind
/// it in the environment is an important performance boost and we couldn't wait for the AST to
/// be split to implement it.
///
/// For all intent of purpose, you should consider `Closure` as a "inline" variable: before its
/// introduction, it was encoded as a variable bound in the environment.
///
/// This is a temporary solution, and will be removed in the future.
Closure(CacheIndex),
}
// PartialEq is mostly used for tests, when it's handy to compare something to an expected result.
// Most of the instance aren't really meaningful to use outside of very simple cases, and you
// should avoid comparing terms directly.
// We have to implement this instance by hand because of the `Closure` node.
impl PartialEq for Term {
#[track_caller]
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
(Self::Num(l0), Self::Num(r0)) => l0 == r0,
(Self::Str(l0), Self::Str(r0)) => l0 == r0,
(Self::StrChunks(l0), Self::StrChunks(r0)) => l0 == r0,
(Self::Fun(l0, l1), Self::Fun(r0, r1)) => l0 == r0 && l1 == r1,
(Self::FunPattern(l0, l1, l2), Self::FunPattern(r0, r1, r2)) => {
l0 == r0 && l1 == r1 && l2 == r2
}
(Self::Lbl(l0), Self::Lbl(r0)) => l0 == r0,
(Self::Let(l0, l1, l2, l3), Self::Let(r0, r1, r2, r3)) => {
l0 == r0 && l1 == r1 && l2 == r2 && l3 == r3
}
(Self::LetPattern(l0, l1, l2, l3), Self::LetPattern(r0, r1, r2, r3)) => {
l0 == r0 && l1 == r1 && l2 == r2 && l3 == r3
}
(Self::App(l0, l1), Self::App(r0, r1)) => l0 == r0 && l1 == r1,
(Self::Var(l0), Self::Var(r0)) => l0 == r0,
(Self::Enum(l0), Self::Enum(r0)) => l0 == r0,
(Self::Record(l0), Self::Record(r0)) => l0 == r0,
(Self::RecRecord(l0, l1, l2), Self::RecRecord(r0, r1, r2)) => {
l0 == r0 && l1 == r1 && l2 == r2
}
(
Self::Match {
cases: l_cases,
default: l_default,
},
Self::Match {
cases: r_cases,
default: r_default,
},
) => l_cases == r_cases && l_default == r_default,
(Self::Array(l0, l1), Self::Array(r0, r1)) => l0 == r0 && l1 == r1,
(Self::Op1(l0, l1), Self::Op1(r0, r1)) => l0 == r0 && l1 == r1,
(Self::Op2(l0, l1, l2), Self::Op2(r0, r1, r2)) => l0 == r0 && l1 == r1 && l2 == r2,
(Self::OpN(l0, l1), Self::OpN(r0, r1)) => l0 == r0 && l1 == r1,
(Self::SealingKey(l0), Self::SealingKey(r0)) => l0 == r0,
(Self::Sealed(l0, l1, l2), Self::Sealed(r0, r1, r2)) => {
l0 == r0 && l1 == r1 && l2 == r2
}
(Self::Annotated(l0, l1), Self::Annotated(r0, r1)) => l0 == r0 && l1 == r1,
(Self::Import(l0), Self::Import(r0)) => l0 == r0,
(Self::ResolvedImport(l0), Self::ResolvedImport(r0)) => l0 == r0,
(Self::Type(l0), Self::Type(r0)) => l0 == r0,
(Self::ParseError(l0), Self::ParseError(r0)) => l0 == r0,
(Self::RuntimeError(l0), Self::RuntimeError(r0)) => l0 == r0,
// We don't compare closure, because we can't, without the evaluation cache at hand.
// It's ok even if the cache index are the same: we implement PartialEq, so we can have
// `x != x`. In practice, this case shouldn't even be triggered, because tests usually
// compare simple terms without closures in it (or terms where closures have
// been substituted for their value).
(Self::Closure(_l0), Self::Closure(_r0)) => false,
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
}
}
}
/// A unique sealing key, introduced by polymorphic contracts.
pub type SealingKey = i32;
/// The underlying type representing Nickel numbers. Currently, numbers are arbitrary precision
/// rationals.
///
/// Basic arithmetic operations are exact, without loss of precision (within the limits of available
/// memory).
///
/// Raising to a power that doesn't fit in a signed 64bits number will lead to converting both
/// operands to 64-bits floats, performing the floating-point power operation, and converting back
/// to rationals, which can incur a loss of precision.
///
/// [^number-serialization]: Conversion to string and serialization try to first convert the
/// rational as an exact signed or usigned 64-bits integer. If this succeeds, such operations
/// don't lose precision. Otherwise, the number is converted to the nearest 64bit float and then
/// serialized/printed, which can incur a loss of information.
pub type Number = Rational;
/// Type of let-binding. This only affects run-time behavior. Revertible bindings introduce
/// revertible cache elements at evaluation, which are devices used for the implementation of
/// recursive records merging. See the [`crate::eval::merge`] and [`crate::eval`] modules for more
/// details.
#[derive(Debug, Eq, PartialEq, Clone, Default)]
pub enum BindingType {
#[default]
Normal,
/// In the revertible case, we also store an optional set of dependencies. See
/// [`crate::transform::free_vars`] for more details.
Revertible(FieldDeps),
}
/// A runtime representation of a contract, as a term ready to be applied via `AppContract`
/// together with its label.
#[derive(Debug, PartialEq, Clone)]
pub struct RuntimeContract {
/// The pending contract, can be a function or a record.
pub contract: RichTerm,
/// The blame label.
pub label: Label,
}
impl RuntimeContract {
pub fn new(contract: RichTerm, label: Label) -> Self {
RuntimeContract { contract, label }
}
/// Generate a runtime contract from a type used as a static type annotation and a label. Use
/// the guarantees of the static type system to optimize and simplify the contract.
pub fn from_static_type(labeled_typ: LabeledType) -> Result<Self, UnboundTypeVariableError> {
Ok(RuntimeContract {
contract: labeled_typ.typ.contract_static()?,
label: labeled_typ.label,
})
}
/// Map a function over the term representing the underlying contract.
pub fn map_contract<F>(self, f: F) -> Self
where
F: FnOnce(RichTerm) -> RichTerm,
{
RuntimeContract {
contract: f(self.contract),
..self
}
}
/// Apply this contract to a term.
pub fn apply(self, rt: RichTerm, pos: TermPos) -> RichTerm {
use crate::mk_app;
mk_app!(
make::op2(
BinaryOp::ApplyContract(),
self.contract,
Term::Lbl(self.label)
)
.with_pos(pos),
rt
)
.with_pos(pos)
}
/// Apply a series of contracts to a term, in order.
pub fn apply_all<I>(rt: RichTerm, contracts: I, pos: TermPos) -> RichTerm
where
I: Iterator<Item = Self>,
{
contracts.fold(rt, |acc, ctr| ctr.apply(acc, pos))
}
/// Push a pending contract to a vector of contracts if the contract to add isn't already
/// present in the vector, according to the notion of contract equality defined in
/// [crate::typecheck::eq].
pub fn push_dedup(
contracts: &mut Vec<RuntimeContract>,
env1: &Environment,
ctr: Self,
env2: &Environment,
) {
for c in contracts.iter() {
if contract_eq(0, &c.contract, env1, &ctr.contract, env2) {
return;
}
}
contracts.push(ctr);
}
/// Check if this contract might have polymorphic subcontracts. See
/// [crate::label::Label::can_have_poly_ctrs].
pub fn can_have_poly_ctrs(&self) -> bool {
self.label.can_have_poly_ctrs()
}
}
impl Traverse<RichTerm> for RuntimeContract {
fn traverse<F, E>(self, f: &mut F, order: TraverseOrder) -> Result<Self, E>
where
F: FnMut(RichTerm) -> Result<RichTerm, E>,
{
let contract = self.contract.traverse(f, order)?;
Ok(RuntimeContract { contract, ..self })
}
fn traverse_ref<S, U>(
&self,
f: &mut dyn FnMut(&RichTerm, &S) -> TraverseControl<S, U>,
state: &S,
) -> Option<U> {
self.contract.traverse_ref(f, state)
}
}
impl std::convert::TryFrom<LabeledType> for RuntimeContract {
type Error = UnboundTypeVariableError;
fn try_from(labeled_ty: LabeledType) -> Result<Self, Self::Error> {
Ok(RuntimeContract::new(
labeled_ty.typ.contract()?,
labeled_ty.label,
))
}
}
/// The attributes of a enum variant.
#[derive(Debug, Default, Eq, PartialEq, Clone)]
pub struct EnumVariantAttrs {
/// An enum variant is closurized if its argument is a [crate::term::Term::Closure] or a
/// constant.
///
/// When initially produced by the parser, data structures such as enum variants or arrays
/// aren't closurized. At the first evaluation, they will be turned into closurized versions,
/// by allocating cache nodes (think thunks) for non constant elements. Once done, this flag is
/// set to `true`.
///
/// Ideally, we would have a different AST representation for evaluation, where enum variants
/// would always be closurized. In the meantime, while we need to cope with a unique AST across
/// the whole pipeline, we use this flag to remember closurization.
pub closurized: bool,
}
impl EnumVariantAttrs {
/// Create new enum variant attributes. By default, the `closurized` flag is set to `false`.
pub fn new() -> Self {
Default::default()
}
/// Set the `closurized` flag to `true`.
pub fn closurized(mut self) -> Self {
self.closurized = true;
self
}
}
/// The attributes of a let binding.
#[derive(Debug, Default, Eq, PartialEq, Clone)]
pub struct LetAttrs {
/// The type of a let binding. See the documentation of [`BindingType`].
pub binding_type: BindingType,
/// A recursive let binding adds its binding to the environment of the expression.
pub rec: bool,
}
/// The metadata that can be attached to a let.
#[derive(Debug, Default, Clone)]
pub struct LetMetadata {
pub doc: Option<String>,
pub annotation: TypeAnnotation,
}
impl From<LetMetadata> for record::FieldMetadata {
fn from(let_metadata: LetMetadata) -> Self {
record::FieldMetadata {
annotation: let_metadata.annotation,
doc: let_metadata.doc,
..Default::default()
}
}
}
#[derive(Debug, Clone)]
pub enum MergePriority {
/// The priority of default values that are overridden by everything else.
Bottom,
/// The priority by default, when no priority annotation (`default`, `force`, `priority`) is
/// provided.
///
/// Act as the value `MergePriority::Numeral(0)` with respect to ordering and equality
/// testing. The only way to discriminate this variant is to pattern match on it.
Neutral,
/// A numeral priority.
Numeral(Number),
/// The priority of values that override everything else and can't be overridden.
Top,
}
impl PartialOrd for MergePriority {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for MergePriority {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(MergePriority::Bottom, MergePriority::Bottom)
| (MergePriority::Neutral, MergePriority::Neutral)
| (MergePriority::Top, MergePriority::Top) => true,
(MergePriority::Numeral(p1), MergePriority::Numeral(p2)) => p1 == p2,
(MergePriority::Neutral, MergePriority::Numeral(p))
| (MergePriority::Numeral(p), MergePriority::Neutral)
if p == &Number::ZERO =>
{
true
}
_ => false,
}
}
}
impl Eq for MergePriority {}
impl Ord for MergePriority {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
// Equalities
(MergePriority::Bottom, MergePriority::Bottom)
| (MergePriority::Top, MergePriority::Top)
| (MergePriority::Neutral, MergePriority::Neutral) => Ordering::Equal,
(MergePriority::Numeral(p1), MergePriority::Numeral(p2)) => p1.cmp(p2),
// Top and bottom.
(MergePriority::Bottom, _) | (_, MergePriority::Top) => Ordering::Less,
(MergePriority::Top, _) | (_, MergePriority::Bottom) => Ordering::Greater,
// Neutral and numeral.
(MergePriority::Neutral, MergePriority::Numeral(n)) => Number::ZERO.cmp(n),
(MergePriority::Numeral(n), MergePriority::Neutral) => n.cmp(&Number::ZERO),
}
}
}
impl Default for MergePriority {
fn default() -> Self {
Self::Neutral
}
}
impl fmt::Display for MergePriority {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
MergePriority::Bottom => write!(f, "default"),
MergePriority::Neutral => write!(f, "{}", Number::ZERO),
MergePriority::Numeral(p) => write!(f, "{p}"),
MergePriority::Top => write!(f, "force"),
}
}
}
/// A type or a contract together with its corresponding label.
#[derive(Debug, PartialEq, Clone)]
pub struct LabeledType {
pub typ: Type,
pub label: Label,
}
impl LabeledType {
/// Modify the label's `field_name` field.
pub fn with_field_name(self, ident: Option<LocIdent>) -> Self {
LabeledType {
label: self.label.with_field_name(ident),
..self
}
}
}
impl Traverse<RichTerm> for LabeledType {
// Note that this function doesn't traverse the label, which is most often what you want. The
// terms that may hide in a label are mostly types used for error reporting, but are never
// evaluated.
fn traverse<F, E>(self, f: &mut F, order: TraverseOrder) -> Result<LabeledType, E>
where
F: FnMut(RichTerm) -> Result<RichTerm, E>,
{
let LabeledType { typ, label } = self;
typ.traverse(f, order).map(|typ| LabeledType { typ, label })
}
fn traverse_ref<S, U>(
&self,
f: &mut dyn FnMut(&RichTerm, &S) -> TraverseControl<S, U>,
state: &S,
) -> Option<U> {
self.typ.traverse_ref(f, state)
}
}
/// A type and/or contract annotation.
#[derive(Debug, PartialEq, Clone, Default)]
pub struct TypeAnnotation {
/// The type annotation (using `:`).
pub typ: Option<LabeledType>,
/// The contracts annotation (using `|`).
pub contracts: Vec<LabeledType>,
}
impl TypeAnnotation {
/// Return the main annotation, which is either the type annotation if any, or the first
/// contract annotation.
pub fn first(&self) -> Option<&LabeledType> {
self.typ.iter().chain(self.contracts.iter()).next()
}
/// Iterate over the annotations, starting by the type and followed by the contracts.
pub fn iter(&self) -> impl Iterator<Item = &LabeledType> {
self.typ.iter().chain(self.contracts.iter())
}
/// Mutably iterate over the annotations, starting by the type and followed by the contracts.
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut LabeledType> {
self.typ.iter_mut().chain(self.contracts.iter_mut())
}
/// Return a string representation of the contracts (without the static type annotation) as a
/// comma-separated list.
pub fn contracts_to_string(&self) -> Option<String> {
(!self.contracts.is_empty()).then(|| {
self.contracts
.iter()
.map(|contract| format!("{}", contract.label.typ,))
.collect::<Vec<_>>()
.join(",")
})
}
/// Build a list of pending contracts from this annotation, to be stored alongside the metadata
/// of a field. Similar to [Self::all_contracts], but including the contracts from
/// `self.contracts` only, while `types` is excluded. Contracts derived from type annotations
/// aren't treated the same since they don't propagate through merging.
pub fn pending_contracts(&self) -> Result<Vec<RuntimeContract>, UnboundTypeVariableError> {
self.contracts
.iter()
.cloned()
.map(RuntimeContract::try_from)
.collect::<Result<Vec<_>, _>>()
}
/// Build the contract derived from the static type annotation, applying the specific
/// optimizations along the way.
pub fn static_contract(&self) -> Option<Result<RuntimeContract, UnboundTypeVariableError>> {
self.typ
.as_ref()
.cloned()
.map(RuntimeContract::from_static_type)
}
/// Convert all the contracts of this annotation, including the potential type annotation as
/// the first element, to a runtime representation.
pub fn all_contracts(&self) -> Result<Vec<RuntimeContract>, UnboundTypeVariableError> {
self.typ
.iter()
.chain(self.contracts.iter())
.cloned()
.map(RuntimeContract::try_from)
.collect::<Result<Vec<_>, _>>()
}
/// Set the `field_name` attribute of the labels of the type and contracts annotations.
pub fn with_field_name(self, field_name: Option<LocIdent>) -> Self {
TypeAnnotation {
typ: self.typ.map(|t| t.with_field_name(field_name)),
contracts: self
.contracts
.into_iter()
.map(|t| t.with_field_name(field_name))
.collect(),
}
}
/// Return `true` if this annotation is empty, i.e. hold neither a type annotation nor
/// contracts annotations.
pub fn is_empty(&self) -> bool {
self.typ.is_none() && self.contracts.is_empty()
}
/// **Warning**: the contract equality check used in this function behaves like syntactic
/// equality, and doesn't take the environment into account. It's unsound for execution (it
/// could equate contracts that are actually totally distinct), but we use it only to trim
/// accumulated contracts before pretty-printing. Do not use prior to any form of evaluation.
///
/// Same as [`crate::combine::Combine`], but eliminate duplicate contracts. As there's no
/// notion of environment when considering mere annotations, we use an unsound contract
/// equality checking which correspond to compares contracts syntactically.
pub fn combine_dedup(left: Self, right: Self) -> Self {
let mut contracts = left.contracts;
let typ = match (left.typ, right.typ) {
(left_ty @ Some(_), Some(right_ty)) => {
contracts.push(right_ty);
left_ty
}
(left_ty, right_ty) => left_ty.or(right_ty),
};
for ctr in right.contracts.into_iter() {
if !contracts.iter().any(|c| type_eq_noenv(0, &c.typ, &ctr.typ)) {
contracts.push(ctr);
}
}
TypeAnnotation { typ, contracts }
}
}
impl From<TypeAnnotation> for LetMetadata {
fn from(annotation: TypeAnnotation) -> Self {
LetMetadata {
annotation,
..Default::default()
}
}
}
impl Traverse<RichTerm> for TypeAnnotation {
fn traverse<F, E>(self, f: &mut F, order: TraverseOrder) -> Result<Self, E>
where
F: FnMut(RichTerm) -> Result<RichTerm, E>,
{
let TypeAnnotation { typ, contracts } = self;
let contracts = contracts
.into_iter()
.map(|labeled_ty| labeled_ty.traverse(f, order))
.collect::<Result<Vec<_>, _>>()?;
let typ = typ
.map(|labeled_ty| labeled_ty.traverse(f, order))
.transpose()?;
Ok(TypeAnnotation { typ, contracts })
}
fn traverse_ref<S, U>(
&self,
f: &mut dyn FnMut(&RichTerm, &S) -> TraverseControl<S, U>,
state: &S,
) -> Option<U> {
self.contracts
.iter()
.find_map(|c| c.traverse_ref(f, state))
.or_else(|| self.typ.as_ref().and_then(|t| t.traverse_ref(f, state)))
}
}
/// A chunk of a string with interpolated expressions inside. Can be either a string literal or an
/// interpolated expression.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum StrChunk<E> {
/// A string literal.
Literal(String),
/// An interpolated expression.
Expr(
E, /* the expression */
usize, /* the indentation level (see parser::utils::strip_indent) */
),
}
#[cfg(test)]
impl<E> StrChunk<E> {
pub fn expr(e: E) -> Self {
StrChunk::Expr(e, 0)
}
}
impl Term {
/// Return the class of an expression in WHNF.
///
/// The class of an expression is an approximation of its type used in error reporting. Class
/// and type coincide for constants (numbers, strings and booleans) and arrays. Otherwise the
/// class is less precise than the type and indicates the general shape of the term: `"Record"`
/// for records, `"Fun`" for functions, etc. If the term is not a WHNF, `None` is returned.
pub fn type_of(&self) -> Option<String> {
match self {
Term::Null => Some("Null".to_owned()),
Term::Bool(_) => Some("Bool".to_owned()),
Term::Num(_) => Some("Number".to_owned()),
Term::Str(_) => Some("String".to_owned()),
Term::Fun(_, _) | Term::FunPattern(_, _, _) => Some("Function".to_owned()),
Term::Match { .. } => Some("MatchExpression".to_owned()),
Term::Lbl(_) => Some("Label".to_owned()),
Term::Enum(_) => Some("Enum".to_owned()),
Term::EnumVariant { .. } => Some("Enum".to_owned()),
Term::Record(..) | Term::RecRecord(..) => Some("Record".to_owned()),
Term::Array(..) => Some("Array".to_owned()),
Term::SealingKey(_) => Some("SealingKey".to_owned()),
Term::Sealed(..) => Some("Sealed".to_owned()),
Term::Annotated(..) => Some("Annotated".to_owned()),
Term::Let(..)
| Term::LetPattern(..)
| Term::App(_, _)
| Term::Var(_)
| Term::Closure(_)
| Term::Op1(_, _)
| Term::Op2(_, _, _)
| Term::OpN(..)
| Term::Import(_)
| Term::ResolvedImport(_)
| Term::StrChunks(_)
| Term::Type(_)
| Term::ParseError(_)
| Term::RuntimeError(_) => None,
}
}
/// Determine if a term is in evaluated form, called weak head normal form (WHNF). This test is
/// purely syntactic, which has the non-obvious consequence that some terms might be in WHNF
/// according to [Self::is_whnf] but might still be evaluated further.
///
/// This is due to implementation details of the evaluation around closurization. The first
/// time an array or a record is evaluated, it will be closurized - thunks will be allocated to
/// store its elements and make them shareable. Thus, if `self` is `Term::Array(data, attrs)`
/// with `attrs.closurized` set to `false`, evaluation will rewrite it to a different array,
/// although in the surface language of Nickel, arrays are weak head normal forms.
///
/// For everything happening pre-evaluation, you probably shouldn't care about this subtlety
/// and you can use `is_whnf` directly.
///
/// However, at run-time, in particular if the property you care about is "is this term going
/// to be evaluate further", then you should use [Self::is_eff_whnf] instead.
pub fn is_whnf(&self) -> bool {
match self {
Term::Null
| Term::Bool(_)
| Term::Num(_)
| Term::Str(_)
| Term::Fun(..)
// match expressions are function
| Term::Match {..}
| Term::Lbl(_)
| Term::Enum(_)
| Term::EnumVariant {..}
| Term::Record(..)
| Term::Array(..)
| Term::SealingKey(_) => true,
Term::Let(..)
| Term::LetPattern(..)
| Term::FunPattern(..)
| Term::App(..)
| Term::Var(_)
| Term::Closure(_)
| Term::Op1(..)
| Term::Op2(..)
| Term::OpN(..)
| Term::Sealed(..)
| Term::Annotated(..)
| Term::Import(_)
| Term::ResolvedImport(_)
| Term::StrChunks(_)
| Term::RecRecord(..)
| Term::Type(_)
| Term::ParseError(_)
| Term::RuntimeError(_) => false,
}
}
/// Helper used by [Self::is_eff_whnf] to determine if a term is a data structure that hasn't
/// been closurized yet.
fn is_unclosurized_datastructure(&self) -> bool {
match self {
Term::Array(_, attrs) => !attrs.closurized,
Term::Record(data) | Term::RecRecord(data, ..) => !data.attrs.closurized,
Term::EnumVariant { attrs, .. } => !attrs.closurized,
_ => false,
}
}
/// Determine if an expression is an effective weak head normal form, that is a value that
/// won't be evaluated further by the virtual machine. Being an effective WHNF implies being a
/// WHNF, but the converse isn't true. See [Self::is_whnf] for more details.
pub fn is_eff_whnf(&self) -> bool {
self.is_whnf() && !self.is_unclosurized_datastructure()
}
/// Determine if a term is annotated.
pub fn is_annotated(&self) -> bool {
matches!(self, Term::Annotated(..))
}
/// Determine if a term is a constant.
///
/// In this context, a constant is an atomic literal of the language: null, a boolean, a number,
/// a string, a label, an enum tag or a symbol.
pub fn is_constant(&self) -> bool {
match self {
Term::Null
| Term::Bool(_)
| Term::Num(_)
| Term::Str(_)
| Term::Lbl(_)
| Term::Enum(_)
| Term::SealingKey(_) => true,
Term::Let(..)
| Term::LetPattern(..)
| Term::Record(..)
| Term::Array(..)
| Term::Fun(..)
| Term::FunPattern(..)
| Term::App(_, _)
| Term::Match { .. }
| Term::Var(_)
| Term::Closure(_)
| Term::Op1(..)
| Term::Op2(..)
| Term::OpN(..)
| Term::Sealed(..)
| Term::Annotated(..)
| Term::Import(_)
| Term::ResolvedImport(_)
| Term::StrChunks(_)
| Term::RecRecord(..)
| Term::Type(_)
| Term::ParseError(_)
| Term::EnumVariant { .. }
| Term::RuntimeError(_) => false,
}
}
/// Determine if a term is an atom of the surface syntax. Atoms are basic elements of the
/// syntax that can freely substituted without being parenthesized.
pub fn is_atom(&self) -> bool {
match self {
Term::Null
| Term::Bool(..)
| Term::Str(..)
| Term::StrChunks(..)
| Term::Lbl(..)
| Term::Enum(..)