diff options
author | Nadrieril | 2019-05-07 18:12:04 +0200 |
---|---|---|
committer | Nadrieril | 2019-05-07 18:12:04 +0200 |
commit | 3da450aa3fae23214aa982643b9bc4dd0ea4eaa6 (patch) | |
tree | 02e00cd008d2e7dc899b9211379596fe792f41c8 /dhall/src | |
parent | d8a3e831fb67f86269c4baa99f9f0798a73a7247 (diff) | |
parent | 14dfeb8e7d2aa87a361a711a485243449426b144 (diff) |
Merge branch 'reorganize'
Diffstat (limited to 'dhall/src')
-rw-r--r-- | dhall/src/api/mod.rs | 40 | ||||
-rw-r--r-- | dhall/src/api/serde.rs (renamed from dhall/src/serde.rs) | 2 | ||||
-rw-r--r-- | dhall/src/api/traits/deserialize.rs (renamed from dhall/src/traits/deserialize.rs) | 2 | ||||
-rw-r--r-- | dhall/src/api/traits/dynamic_type.rs (renamed from dhall/src/traits/dynamic_type.rs) | 9 | ||||
-rw-r--r-- | dhall/src/api/traits/mod.rs (renamed from dhall/src/traits/mod.rs) | 0 | ||||
-rw-r--r-- | dhall/src/api/traits/static_type.rs (renamed from dhall/src/traits/static_type.rs) | 6 | ||||
-rw-r--r-- | dhall/src/core/context.rs | 136 | ||||
-rw-r--r-- | dhall/src/core/mod.rs | 3 | ||||
-rw-r--r-- | dhall/src/core/thunk.rs | 297 | ||||
-rw-r--r-- | dhall/src/core/value.rs | 627 | ||||
-rw-r--r-- | dhall/src/error.rs | 52 | ||||
-rw-r--r-- | dhall/src/error/mod.rs | 169 | ||||
-rw-r--r-- | dhall/src/error/text/AnnotMismatch.txt (renamed from dhall/src/errors/AnnotMismatch.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/CantTextAppend.txt (renamed from dhall/src/errors/CantTextAppend.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/DuplicateAlternative.txt (renamed from dhall/src/errors/DuplicateAlternative.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/FieldCollision.txt (renamed from dhall/src/errors/FieldCollision.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/HandlerInputTypeMismatch.txt (renamed from dhall/src/errors/HandlerInputTypeMismatch.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/HandlerNotAFunction.txt (renamed from dhall/src/errors/HandlerNotAFunction.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/HandlerOutputTypeMismatch.txt (renamed from dhall/src/errors/HandlerOutputTypeMismatch.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/IfBranchMismatch.txt (renamed from dhall/src/errors/IfBranchMismatch.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/IfBranchMustBeTerm.txt (renamed from dhall/src/errors/IfBranchMustBeTerm.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/InvalidAlterantive.txt (renamed from dhall/src/errors/InvalidAlterantive.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/InvalidAlterantiveType.txt (renamed from dhall/src/errors/InvalidAlterantiveType.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/InvalidField.txt (renamed from dhall/src/errors/InvalidField.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/InvalidFieldType.txt (renamed from dhall/src/errors/InvalidFieldType.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/InvalidInputType.txt (renamed from dhall/src/errors/InvalidInputType.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/InvalidListElement.txt (renamed from dhall/src/errors/InvalidListElement.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/InvalidListType.txt (renamed from dhall/src/errors/InvalidListType.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/InvalidOptionType.txt (renamed from dhall/src/errors/InvalidOptionType.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/InvalidOptionalElement.txt (renamed from dhall/src/errors/InvalidOptionalElement.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/InvalidOptionalLiteral.txt (renamed from dhall/src/errors/InvalidOptionalLiteral.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/InvalidOutputType.txt (renamed from dhall/src/errors/InvalidOutputType.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/InvalidPredicate.txt (renamed from dhall/src/errors/InvalidPredicate.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/MissingField.txt (renamed from dhall/src/errors/MissingField.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/MissingHandler.txt (renamed from dhall/src/errors/MissingHandler.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/MustCombineARecord.txt (renamed from dhall/src/errors/MustCombineARecord.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/MustMergeARecord.txt (renamed from dhall/src/errors/MustMergeARecord.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/MustMergeUnion.txt (renamed from dhall/src/errors/MustMergeUnion.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/NoDependentLet.txt (renamed from dhall/src/errors/NoDependentLet.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/NoDependentTypes.txt (renamed from dhall/src/errors/NoDependentTypes.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/NotAFunction.txt (renamed from dhall/src/errors/NotAFunction.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/NotARecord.txt (renamed from dhall/src/errors/NotARecord.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/TypeMismatch.txt (renamed from dhall/src/errors/TypeMismatch.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/UnboundVariable.txt (renamed from dhall/src/errors/UnboundVariable.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/Untyped.txt (renamed from dhall/src/errors/Untyped.txt) | 0 | ||||
-rw-r--r-- | dhall/src/error/text/UnusedHandler.txt (renamed from dhall/src/errors/UnusedHandler.txt) | 0 | ||||
-rw-r--r-- | dhall/src/expr.rs | 244 | ||||
-rw-r--r-- | dhall/src/lib.rs | 51 | ||||
-rw-r--r-- | dhall/src/main.rs | 93 | ||||
-rw-r--r-- | dhall/src/parser.rs | 6 | ||||
-rw-r--r-- | dhall/src/phase/binary.rs (renamed from dhall/src/binary.rs) | 17 | ||||
-rw-r--r-- | dhall/src/phase/mod.rs | 334 | ||||
-rw-r--r-- | dhall/src/phase/normalize.rs (renamed from dhall/src/normalize.rs) | 1040 | ||||
-rw-r--r-- | dhall/src/phase/parse.rs | 38 | ||||
-rw-r--r-- | dhall/src/phase/resolve.rs (renamed from dhall/src/imports.rs) | 62 | ||||
-rw-r--r-- | dhall/src/phase/typecheck.rs (renamed from dhall/src/typecheck.rs) | 599 | ||||
-rw-r--r-- | dhall/src/tests.rs | 3 |
57 files changed, 1845 insertions, 1985 deletions
diff --git a/dhall/src/api/mod.rs b/dhall/src/api/mod.rs new file mode 100644 index 0000000..0a0ef93 --- /dev/null +++ b/dhall/src/api/mod.rs @@ -0,0 +1,40 @@ +mod serde; +pub(crate) mod traits; + +/// Deserialization of Dhall expressions into Rust +pub mod de { + #[doc(hidden)] + pub use crate::phase::SimpleType; + pub use crate::traits::{Deserialize, SimpleStaticType, StaticType}; + #[doc(hidden)] + pub use dhall_proc_macros::SimpleStaticType; + + /// Deserialize an instance of type T from a string of Dhall text. + /// + /// This will recursively resolve all imports in the expression, and + /// typecheck it before deserialization. Relative imports will be resolved relative to the + /// provided file. More control over this process is not yet available + /// but will be in a coming version of this crate. + /// + /// If a type is provided, this additionally checks that the provided + /// expression has that type. + pub fn from_str<'a, T: Deserialize<'a>>( + s: &'a str, + ty: Option<&crate::phase::Type>, + ) -> crate::error::Result<T> { + T::from_str(s, ty) + } + + /// Deserialize an instance of type T from a string of Dhall text, + /// additionally checking that it matches the type of T. + /// + /// This will recursively resolve all imports in the expression, and + /// typecheck it before deserialization. Relative imports will be resolved relative to the + /// provided file. More control over this process is not yet available + /// but will be in a coming version of this crate. + pub fn from_str_auto_type<'a, T: Deserialize<'a> + StaticType>( + s: &'a str, + ) -> crate::error::Result<T> { + from_str(s, Some(&<T as StaticType>::get_static_type())) + } +} diff --git a/dhall/src/serde.rs b/dhall/src/api/serde.rs index 96bc765..93921ba 100644 --- a/dhall/src/serde.rs +++ b/dhall/src/api/serde.rs @@ -1,5 +1,5 @@ use crate::error::{Error, Result}; -use crate::expr::{Normalized, Type}; +use crate::phase::{Normalized, Type}; use crate::traits::Deserialize; use dhall_syntax::*; use std::borrow::Cow; diff --git a/dhall/src/traits/deserialize.rs b/dhall/src/api/traits/deserialize.rs index e3ff2d5..9673cf9 100644 --- a/dhall/src/traits/deserialize.rs +++ b/dhall/src/api/traits/deserialize.rs @@ -1,5 +1,5 @@ use crate::error::*; -use crate::expr::*; +use crate::phase::*; /// A data structure that can be deserialized from a Dhall expression /// diff --git a/dhall/src/traits/dynamic_type.rs b/dhall/src/api/traits/dynamic_type.rs index 858642e..7763a28 100644 --- a/dhall/src/traits/dynamic_type.rs +++ b/dhall/src/api/traits/dynamic_type.rs @@ -1,9 +1,6 @@ -use crate::expr::*; +use crate::error::TypeError; +use crate::phase::{Normalized, Type, Typed}; use crate::traits::StaticType; -#[allow(unused_imports)] -use crate::typecheck::{TypeError, TypeMessage, TypecheckContext}; -#[allow(unused_imports)] -use dhall_syntax::{Const, ExprF}; use std::borrow::Cow; pub trait DynamicType { @@ -30,6 +27,6 @@ impl DynamicType for Normalized { impl DynamicType for Typed { fn get_type(&self) -> Result<Cow<'_, Type>, TypeError> { - self.0.get_type() + self.get_type() } } diff --git a/dhall/src/traits/mod.rs b/dhall/src/api/traits/mod.rs index 315e17a..315e17a 100644 --- a/dhall/src/traits/mod.rs +++ b/dhall/src/api/traits/mod.rs diff --git a/dhall/src/traits/static_type.rs b/dhall/src/api/traits/static_type.rs index f90b8df..e05dfff 100644 --- a/dhall/src/traits/static_type.rs +++ b/dhall/src/api/traits/static_type.rs @@ -1,4 +1,4 @@ -use crate::expr::*; +use crate::phase::*; use dhall_proc_macros as dhall; use dhall_syntax::*; @@ -38,8 +38,8 @@ fn mktype(x: SubExpr<X, X>) -> SimpleType { impl<T: SimpleStaticType> StaticType for T { fn get_static_type() -> Type { - crate::expr::Normalized::from_thunk_and_type( - crate::normalize::Thunk::from_normalized_expr( + crate::phase::Normalized::from_thunk_and_type( + crate::core::thunk::Thunk::from_normalized_expr( T::get_simple_static_type().into(), ), Type::const_type(), diff --git a/dhall/src/core/context.rs b/dhall/src/core/context.rs new file mode 100644 index 0000000..9b5beed --- /dev/null +++ b/dhall/src/core/context.rs @@ -0,0 +1,136 @@ +use std::borrow::Cow; +use std::rc::Rc; + +use dhall_syntax::context::Context; +use dhall_syntax::{Label, V}; + +use crate::core::thunk::Thunk; +use crate::core::value::{AlphaVar, Value}; +use crate::phase::{Normalized, Type, Typed}; + +#[derive(Debug, Clone)] +enum NzEnvItem { + Thunk(Thunk), + Skip(AlphaVar), +} + +#[derive(Debug, Clone)] +pub(crate) struct NormalizationContext(Rc<Context<Label, NzEnvItem>>); + +#[derive(Debug, Clone)] +pub(crate) enum TyEnvItem { + Type(AlphaVar, Type), + Value(Normalized), +} + +#[derive(Debug, Clone)] +pub(crate) struct TypecheckContext(pub(crate) Context<Label, TyEnvItem>); + +impl NzEnvItem { + pub(crate) fn shift(&self, delta: isize, var: &AlphaVar) -> Self { + use NzEnvItem::*; + match self { + Thunk(e) => Thunk(e.shift(delta, var)), + Skip(v) => Skip(v.shift(delta, var)), + } + } + + pub(crate) fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self { + match self { + NzEnvItem::Thunk(e) => NzEnvItem::Thunk(e.subst_shift(var, val)), + NzEnvItem::Skip(v) if v == var => NzEnvItem::Thunk(val.to_thunk()), + NzEnvItem::Skip(v) => NzEnvItem::Skip(v.shift(-1, var)), + } + } +} + +impl NormalizationContext { + pub(crate) fn new() -> Self { + NormalizationContext(Rc::new(Context::new())) + } + pub(crate) fn skip(&self, x: &Label) -> Self { + NormalizationContext(Rc::new( + self.0 + .map(|_, e| e.shift(1, &x.into())) + .insert(x.clone(), NzEnvItem::Skip(x.into())), + )) + } + pub(crate) fn lookup(&self, var: &V<Label>) -> Value { + let V(x, n) = var; + match self.0.lookup(x, *n) { + Some(NzEnvItem::Thunk(t)) => t.to_value(), + Some(NzEnvItem::Skip(newvar)) => Value::Var(newvar.clone()), + // Free variable + None => Value::Var(AlphaVar::from_var(var.clone())), + } + } + pub(crate) fn from_typecheck_ctx(tc_ctx: &TypecheckContext) -> Self { + use TyEnvItem::*; + let mut ctx = Context::new(); + for (k, vs) in tc_ctx.0.iter_keys() { + for v in vs.iter() { + let new_item = match v { + Type(var, _) => NzEnvItem::Skip(var.clone()), + Value(e) => NzEnvItem::Thunk(e.to_thunk()), + }; + ctx = ctx.insert(k.clone(), new_item); + } + } + NormalizationContext(Rc::new(ctx)) + } + + pub(crate) fn shift(&self, delta: isize, var: &AlphaVar) -> Self { + NormalizationContext(Rc::new(self.0.map(|_, e| e.shift(delta, var)))) + } + + pub(crate) fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self { + NormalizationContext(Rc::new( + self.0.map(|_, e| e.subst_shift(var, val)), + )) + } +} + +impl TyEnvItem { + fn shift(&self, delta: isize, var: &AlphaVar) -> Self { + use TyEnvItem::*; + match self { + Type(v, e) => Type(v.shift(delta, var), e.shift(delta, var)), + Value(e) => Value(e.shift(delta, var)), + } + } +} + +impl TypecheckContext { + pub(crate) fn new() -> Self { + TypecheckContext(Context::new()) + } + pub(crate) fn insert_type(&self, x: &Label, t: Type) -> Self { + TypecheckContext(self.0.map(|_, e| e.shift(1, &x.into())).insert( + x.clone(), + TyEnvItem::Type(x.into(), t.shift(1, &x.into())), + )) + } + pub(crate) fn insert_value(&self, x: &Label, t: Normalized) -> Self { + TypecheckContext(self.0.insert(x.clone(), TyEnvItem::Value(t))) + } + pub(crate) fn lookup(&self, var: &V<Label>) -> Option<Cow<'_, Type>> { + let V(x, n) = var; + match self.0.lookup(x, *n) { + Some(TyEnvItem::Type(_, t)) => Some(Cow::Borrowed(&t)), + Some(TyEnvItem::Value(t)) => Some(t.get_type()?), + None => None, + } + } + pub(crate) fn to_normalization_ctx(&self) -> NormalizationContext { + NormalizationContext::from_typecheck_ctx(self) + } +} + +impl PartialEq for TypecheckContext { + fn eq(&self, _: &Self) -> bool { + // don't count contexts when comparing stuff + // this is dirty but needed for now + true + } +} +impl Eq for TypecheckContext {} diff --git a/dhall/src/core/mod.rs b/dhall/src/core/mod.rs new file mode 100644 index 0000000..8f5c2ca --- /dev/null +++ b/dhall/src/core/mod.rs @@ -0,0 +1,3 @@ +pub(crate) mod context; +pub(crate) mod thunk; +pub(crate) mod value; diff --git a/dhall/src/core/thunk.rs b/dhall/src/core/thunk.rs new file mode 100644 index 0000000..2b013bb --- /dev/null +++ b/dhall/src/core/thunk.rs @@ -0,0 +1,297 @@ +use std::cell::{Ref, RefCell}; +use std::rc::Rc; + +use crate::core::context::NormalizationContext; +use crate::core::context::TypecheckContext; +use crate::core::value::{AlphaVar, Value}; +use crate::error::TypeError; +use crate::phase::normalize::{ + apply_any, normalize_whnf, InputSubExpr, OutputSubExpr, +}; +use crate::phase::typecheck::mktype; +use crate::phase::{Type, Typed}; + +#[derive(Debug, Clone, Copy)] +enum Marker { + /// Weak Head Normal Form, i.e. subexpressions may not be normalized + WHNF, + /// Normal form, i.e. completely normalized + NF, +} +use Marker::{NF, WHNF}; + +#[derive(Debug)] +enum ThunkInternal { + /// Non-normalized value alongside a normalization context + Unnormalized(NormalizationContext, InputSubExpr), + /// Partially normalized value. + /// Invariant: if the marker is `NF`, the value must be fully normalized + Value(Marker, Value), +} + +/// Stores a possibly unevaluated value. Gets (partially) normalized on-demand, +/// sharing computation automatically. +/// Uses a RefCell to share computation. +#[derive(Debug, Clone)] +pub struct Thunk(Rc<RefCell<ThunkInternal>>); + +/// A thunk in type position. Can optionally store a Type from the typechecking phase to preserve +/// type information through the normalization phase. +#[derive(Debug, Clone)] +pub(crate) enum TypeThunk { + Thunk(Thunk), + Type(Type), +} + +impl ThunkInternal { + fn into_thunk(self) -> Thunk { + Thunk(Rc::new(RefCell::new(self))) + } + + fn normalize_whnf(&mut self) { + match self { + ThunkInternal::Unnormalized(ctx, e) => { + *self = ThunkInternal::Value( + WHNF, + normalize_whnf(ctx.clone(), e.clone()), + ) + } + // Already at least in WHNF + ThunkInternal::Value(_, _) => {} + } + } + + fn normalize_nf(&mut self) { + match self { + ThunkInternal::Unnormalized(_, _) => { + self.normalize_whnf(); + self.normalize_nf(); + } + ThunkInternal::Value(m @ WHNF, v) => { + v.normalize_mut(); + *m = NF; + } + // Already in NF + ThunkInternal::Value(NF, _) => {} + } + } + + // Always use normalize_whnf before + fn as_whnf(&self) -> &Value { + match self { + ThunkInternal::Unnormalized(_, _) => unreachable!(), + ThunkInternal::Value(_, v) => v, + } + } + + // Always use normalize_nf before + fn as_nf(&self) -> &Value { + match self { + ThunkInternal::Unnormalized(_, _) => unreachable!(), + ThunkInternal::Value(WHNF, _) => unreachable!(), + ThunkInternal::Value(NF, v) => v, + } + } + + fn shift(&self, delta: isize, var: &AlphaVar) -> Self { + match self { + ThunkInternal::Unnormalized(ctx, e) => { + ThunkInternal::Unnormalized(ctx.shift(delta, var), e.clone()) + } + ThunkInternal::Value(m, v) => { + ThunkInternal::Value(*m, v.shift(delta, var)) + } + } + } + + fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self { + match self { + ThunkInternal::Unnormalized(ctx, e) => ThunkInternal::Unnormalized( + ctx.subst_shift(var, val), + e.clone(), + ), + ThunkInternal::Value(_, v) => { + // The resulting value may not stay in normal form after substitution + ThunkInternal::Value(WHNF, v.subst_shift(var, val)) + } + } + } +} + +impl Thunk { + pub(crate) fn new(ctx: NormalizationContext, e: InputSubExpr) -> Thunk { + ThunkInternal::Unnormalized(ctx, e).into_thunk() + } + + pub(crate) fn from_value(v: Value) -> Thunk { + ThunkInternal::Value(WHNF, v).into_thunk() + } + + pub(crate) fn from_normalized_expr(e: OutputSubExpr) -> Thunk { + Thunk::new(NormalizationContext::new(), e.absurd()) + } + + // Normalizes contents to normal form; faster than `normalize_nf` if + // no one else shares this thunk + pub(crate) fn normalize_mut(&mut self) { + match Rc::get_mut(&mut self.0) { + // Mutate directly if sole owner + Some(refcell) => RefCell::get_mut(refcell).normalize_nf(), + // Otherwise mutate through the refcell + None => self.0.borrow_mut().normalize_nf(), + } + } + + fn do_normalize_whnf(&self) { + let borrow = self.0.borrow(); + match &*borrow { + ThunkInternal::Unnormalized(_, _) => { + drop(borrow); + self.0.borrow_mut().normalize_whnf(); + } + // Already at least in WHNF + ThunkInternal::Value(_, _) => {} + } + } + + fn do_normalize_nf(&self) { + let borrow = self.0.borrow(); + match &*borrow { + ThunkInternal::Unnormalized(_, _) + | ThunkInternal::Value(WHNF, _) => { + drop(borrow); + self.0.borrow_mut().normalize_nf(); + } + // Already in NF + ThunkInternal::Value(NF, _) => {} + } + } + + // WARNING: avoid normalizing any thunk while holding on to this ref + // or you could run into BorrowMut panics + pub(crate) fn normalize_nf(&self) -> Ref<Value> { + self.do_normalize_nf(); + Ref::map(self.0.borrow(), ThunkInternal::as_nf) + } + + // WARNING: avoid normalizing any thunk while holding on to this ref + // or you could run into BorrowMut panics + pub(crate) fn as_value(&self) -> Ref<Value> { + self.do_normalize_whnf(); + Ref::map(self.0.borrow(), ThunkInternal::as_whnf) + } + + pub(crate) fn to_value(&self) -> Value { + self.as_value().clone() + } + + pub(crate) fn normalize_to_expr(&self) -> OutputSubExpr { + self.normalize_to_expr_maybe_alpha(false) + } + + pub(crate) fn normalize_to_expr_maybe_alpha( + &self, + alpha: bool, + ) -> OutputSubExpr { + self.normalize_nf().normalize_to_expr_maybe_alpha(alpha) + } + + pub(crate) fn app_val(&self, val: Value) -> Value { + self.app_thunk(val.into_thunk()) + } + + pub(crate) fn app_thunk(&self, th: Thunk) -> Value { + apply_any(self.clone(), th) + } + + pub(crate) fn shift(&self, delta: isize, var: &AlphaVar) -> Self { + self.0.borrow().shift(delta, var).into_thunk() + } + + pub(crate) fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self { + self.0.borrow().subst_shift(var, val).into_thunk() + } +} + +impl TypeThunk { + pub(crate) fn from_value(v: Value) -> TypeThunk { + TypeThunk::from_thunk(Thunk::from_value(v)) + } + + pub(crate) fn from_thunk(th: Thunk) -> TypeThunk { + TypeThunk::Thunk(th) + } + + pub(crate) fn from_type(t: Type) -> TypeThunk { + TypeThunk::Type(t) + } + + pub(crate) fn normalize_mut(&mut self) { + match self { + TypeThunk::Thunk(th) => th.normalize_mut(), + TypeThunk::Type(_) => {} + } + } + + pub(crate) fn normalize_nf(&self) -> Value { + match self { + TypeThunk::Thunk(th) => th.normalize_nf().clone(), + TypeThunk::Type(t) => t.to_value().normalize(), + } + } + + pub(crate) fn to_value(&self) -> Value { + match self { + TypeThunk::Thunk(th) => th.to_value(), + TypeThunk::Type(t) => t.to_value(), + } + } + + pub(crate) fn to_type( + &self, + ctx: &TypecheckContext, + ) -> Result<Type, TypeError> { + match self { + TypeThunk::Type(t) => Ok(t.clone()), + TypeThunk::Thunk(th) => { + // TODO: rule out statically + mktype(ctx, th.normalize_to_expr().absurd()) + } + } + } + + pub(crate) fn normalize_to_expr_maybe_alpha( + &self, + alpha: bool, + ) -> OutputSubExpr { + self.normalize_nf().normalize_to_expr_maybe_alpha(alpha) + } + + pub(crate) fn shift(&self, delta: isize, var: &AlphaVar) -> Self { + match self { + TypeThunk::Thunk(th) => TypeThunk::Thunk(th.shift(delta, var)), + TypeThunk::Type(t) => TypeThunk::Type(t.shift(delta, var)), + } + } + + pub(crate) fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self { + match self { + TypeThunk::Thunk(th) => TypeThunk::Thunk(th.subst_shift(var, val)), + TypeThunk::Type(t) => TypeThunk::Type(t.subst_shift(var, val)), + } + } +} + +impl std::cmp::PartialEq for Thunk { + fn eq(&self, other: &Self) -> bool { + &*self.as_value() == &*other.as_value() + } +} +impl std::cmp::Eq for Thunk {} + +impl std::cmp::PartialEq for TypeThunk { + fn eq(&self, other: &Self) -> bool { + self.to_value() == other.to_value() + } +} +impl std::cmp::Eq for TypeThunk {} diff --git a/dhall/src/core/value.rs b/dhall/src/core/value.rs new file mode 100644 index 0000000..28fcb3e --- /dev/null +++ b/dhall/src/core/value.rs @@ -0,0 +1,627 @@ +use std::collections::BTreeMap; + +use dhall_proc_macros as dhall; +use dhall_syntax::{ + rc, Builtin, Const, ExprF, Integer, InterpolatedTextContents, Label, + Natural, V, X, +}; + +use crate::core::thunk::{Thunk, TypeThunk}; +use crate::phase::normalize::{ + apply_builtin, normalize_one_layer, squash_textlit, OutputSubExpr, +}; +use crate::phase::Typed; + +/// Stores a pair of variables: a normal one and if relevant one +/// that corresponds to the alpha-normalized version of the first one. +/// Equality is up to alpha-equivalence. +#[derive(Debug, Clone, Eq)] +pub(crate) struct AlphaVar { + normal: V<Label>, + alpha: Option<V<()>>, +} + +// Exactly like a Label, but equality returns always true. +// This is so that Value equality is exactly alpha-equivalence. +#[derive(Debug, Clone, Eq)] +pub(crate) struct AlphaLabel(Label); + +/// A semantic value. The invariants ensure this value represents a Weak-Head +/// Normal Form (WHNF). This means that this first constructor is the first constructor of the +/// final Normal Form (NF). +/// This WHNF must be preserved by operations on `Value`s. In particular, `subst_shift` could break +/// the invariants so need to be careful to reevaluate as needed. +/// Subexpressions are Thunks, which are partially evaluated expressions that are normalized +/// on-demand. When all the Thunks in a Value are at least in WHNF, and recursively so, then the +/// Value is in NF. This is because WHNF ensures that we have the first constructor of the NF; so +/// if we have the first constructor of the NF at all levels, we actually have the NF. +/// Equality is up to alpha-equivalence (renaming of bound variables) and beta-equivalence +/// (normalization). Equality will normalize only as needed. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum Value { + /// Closures + Lam(AlphaLabel, TypeThunk, Thunk), + Pi(AlphaLabel, TypeThunk, TypeThunk), + // Invariant: the evaluation must not be able to progress further. + AppliedBuiltin(Builtin, Vec<Thunk>), + /// `λ(x: a) -> Some x` + OptionalSomeClosure(TypeThunk), + /// `λ(x : a) -> λ(xs : List a) -> [ x ] # xs` + /// `λ(xs : List a) -> [ x ] # xs` + ListConsClosure(TypeThunk, Option<Thunk>), + /// `λ(x : Natural) -> x + 1` + NaturalSuccClosure, + + Var(AlphaVar), + Const(Const), + BoolLit(bool), + NaturalLit(Natural), + IntegerLit(Integer), + EmptyOptionalLit(TypeThunk), + NEOptionalLit(Thunk), + EmptyListLit(TypeThunk), + NEListLit(Vec<Thunk>), + RecordLit(BTreeMap<Label, Thunk>), + RecordType(BTreeMap<Label, TypeThunk>), + UnionType(BTreeMap<Label, Option<TypeThunk>>), + UnionConstructor(Label, BTreeMap<Label, Option<TypeThunk>>), + UnionLit(Label, Thunk, BTreeMap<Label, Option<TypeThunk>>), + // Invariant: this must not contain interpolations that are themselves TextLits, and + // contiguous text values must be merged. + TextLit(Vec<InterpolatedTextContents<Thunk>>), + // Invariant: this must not contain a value captured by one of the variants above. + PartialExpr(ExprF<Thunk, Label, X>), +} + +impl AlphaVar { + pub(crate) fn to_var(&self, alpha: bool) -> V<Label> { + match (alpha, &self.alpha) { + (true, Some(x)) => V("_".into(), x.1), + _ => self.normal.clone(), + } + } + pub(crate) fn from_var(normal: V<Label>) -> Self { + AlphaVar { + normal, + alpha: None, + } + } + pub(crate) fn shift(&self, delta: isize, var: &AlphaVar) -> Self { + AlphaVar { + normal: self.normal.shift(delta, &var.normal), + alpha: match (&self.alpha, &var.alpha) { + (Some(x), Some(v)) => Some(x.shift(delta, v)), + _ => None, + }, + } + } +} + +impl AlphaLabel { + fn to_label_maybe_alpha(&self, alpha: bool) -> Label { + if alpha { + "_".into() + } else { + self.to_label() + } + } + fn to_label(&self) -> Label { + self.clone().into() + } +} + +impl Value { + pub(crate) fn into_thunk(self) -> Thunk { + Thunk::from_value(self) + } + + /// Convert the value to a fully normalized syntactic expression + pub(crate) fn normalize_to_expr(&self) -> OutputSubExpr { + self.normalize_to_expr_maybe_alpha(false) + } + /// Convert the value to a fully normalized syntactic expression. Also alpha-normalize + /// if alpha is `true` + pub(crate) fn normalize_to_expr_maybe_alpha( + &self, + alpha: bool, + ) -> OutputSubExpr { + match self { + Value::Lam(x, t, e) => rc(ExprF::Lam( + x.to_label_maybe_alpha(alpha), + t.normalize_to_expr_maybe_alpha(alpha), + e.normalize_to_expr_maybe_alpha(alpha), + )), + Value::AppliedBuiltin(b, args) => { + let mut e = rc(ExprF::Builtin(*b)); + for v in args { + e = rc(ExprF::App( + e, + v.normalize_to_expr_maybe_alpha(alpha), + )); + } + e + } + Value::OptionalSomeClosure(n) => { + let a = n.normalize_to_expr_maybe_alpha(alpha); + dhall::subexpr!(λ(x: a) -> Some x) + } + Value::ListConsClosure(a, None) => { + // Avoid accidental capture of the new `x` variable + let a1 = a.shift(1, &Label::from("x").into()); + let a1 = a1.normalize_to_expr_maybe_alpha(alpha); + let a = a.normalize_to_expr_maybe_alpha(alpha); + dhall::subexpr!(λ(x : a) -> λ(xs : List a1) -> [ x ] # xs) + } + Value::ListConsClosure(n, Some(v)) => { + // Avoid accidental capture of the new `xs` variable + let v = v.shift(1, &Label::from("xs").into()); + let v = v.normalize_to_expr_maybe_alpha(alpha); + let a = n.normalize_to_expr_maybe_alpha(alpha); + dhall::subexpr!(λ(xs : List a) -> [ v ] # xs) + } + Value::NaturalSuccClosure => { + dhall::subexpr!(λ(x : Natural) -> x + 1) + } + Value::Pi(x, t, e) => rc(ExprF::Pi( + x.to_label_maybe_alpha(alpha), + t.normalize_to_expr_maybe_alpha(alpha), + e.normalize_to_expr_maybe_alpha(alpha), + )), + Value::Var(v) => rc(ExprF::Var(v.to_var(alpha))), + Value::Const(c) => rc(ExprF::Const(*c)), + Value::BoolLit(b) => rc(ExprF::BoolLit(*b)), + Value::NaturalLit(n) => rc(ExprF::NaturalLit(*n)), + Value::IntegerLit(n) => rc(ExprF::IntegerLit(*n)), + Value::EmptyOptionalLit(n) => rc(ExprF::App( + rc(ExprF::Builtin(Builtin::OptionalNone)), + n.normalize_to_expr_maybe_alpha(alpha), + )), + Value::NEOptionalLit(n) => { + rc(ExprF::SomeLit(n.normalize_to_expr_maybe_alpha(alpha))) + } + Value::EmptyListLit(n) => { + rc(ExprF::EmptyListLit(n.normalize_to_expr_maybe_alpha(alpha))) + } + Value::NEListLit(elts) => rc(ExprF::NEListLit( + elts.into_iter() + .map(|n| n.normalize_to_expr_maybe_alpha(alpha)) + .collect(), + )), + Value::RecordLit(kvs) => rc(ExprF::RecordLit( + kvs.iter() + .map(|(k, v)| { + (k.clone(), v.normalize_to_expr_maybe_alpha(alpha)) + }) + .collect(), + )), + Value::RecordType(kts) => rc(ExprF::RecordType( + kts.iter() + .map(|(k, v)| { + (k.clone(), v.normalize_to_expr_maybe_alpha(alpha)) + }) + .collect(), + )), + Value::UnionType(kts) => rc(ExprF::UnionType( + kts.iter() + .map(|(k, v)| { + ( + k.clone(), + v.as_ref().map(|v| { + v.normalize_to_expr_maybe_alpha(alpha) + }), + ) + }) + .collect(), + )), + Value::UnionConstructor(l, kts) => { + let kts = kts + .iter() + .map(|(k, v)| { + ( + k.clone(), + v.as_ref().map(|v| { + v.normalize_to_expr_maybe_alpha(alpha) + }), + ) + }) + .collect(); + rc(ExprF::Field(rc(ExprF::UnionType(kts)), l.clone())) + } + Value::UnionLit(l, v, kts) => rc(ExprF::UnionLit( + l.clone(), + v.normalize_to_expr_maybe_alpha(alpha), + kts.iter() + .map(|(k, v)| { + ( + k.clone(), + v.as_ref().map(|v| { + v.normalize_to_expr_maybe_alpha(alpha) + }), + ) + }) + .collect(), + )), + Value::TextLit(elts) => { + use InterpolatedTextContents::{Expr, Text}; + rc(ExprF::TextLit( + elts.iter() + .map(|contents| match contents { + Expr(e) => { + Expr(e.normalize_to_expr_maybe_alpha(alpha)) + } + Text(s) => Text(s.clone()), + }) + .collect(), + )) + } + Value::PartialExpr(e) => { + rc(e.map_ref_simple(|v| v.normalize_to_expr_maybe_alpha(alpha))) + } + } + } + + // Deprecated + pub(crate) fn normalize(&self) -> Value { + let mut v = self.clone(); + v.normalize_mut(); + v + } + + pub(crate) fn normalize_mut(&mut self) { + match self { + Value::NaturalSuccClosure + | Value::Var(_) + | Value::Const(_) + | Value::BoolLit(_) + | Value::NaturalLit(_) + | Value::IntegerLit(_) => {} + + Value::EmptyOptionalLit(tth) + | Value::OptionalSomeClosure(tth) + | Value::EmptyListLit(tth) => { + tth.normalize_mut(); + } + + Value::NEOptionalLit(th) => { + th.normalize_mut(); + } + Value::Lam(_, t, e) => { + t.normalize_mut(); + e.normalize_mut(); + } + Value::Pi(_, t, e) => { + t.normalize_mut(); + e.normalize_mut(); + } + Value::AppliedBuiltin(_, args) => { + for x in args.iter_mut() { + x.normalize_mut(); + } + } + Value::ListConsClosure(t, v) => { + t.normalize_mut(); + for x in v.iter_mut() { + x.normalize_mut(); + } + } + Value::NEListLit(elts) => { + for x in elts.iter_mut() { + x.normalize_mut(); + } + } + Value::RecordLit(kvs) => { + for x in kvs.values_mut() { + x.normalize_mut(); + } + } + Value::RecordType(kvs) => { + for x in kvs.values_mut() { + x.normalize_mut(); + } + } + Value::UnionType(kts) | Value::UnionConstructor(_, kts) => { + for x in kts.values_mut().flat_map(|opt| opt) { + x.normalize_mut(); + } + } + Value::UnionLit(_, v, kts) => { + v.normalize_mut(); + for x in kts.values_mut().flat_map(|opt| opt) { + x.normalize_mut(); + } + } + Value::TextLit(elts) => { + for x in elts.iter_mut() { + use InterpolatedTextContents::{Expr, Text}; + match x { + Expr(n) => n.normalize_mut(), + Text(_) => {} + } + } + } + Value::PartialExpr(e) => { + // TODO: need map_mut_simple + e.map_ref_simple(|v| { + v.normalize_nf(); + }); + } + } + } + + /// Apply to a value + pub(crate) fn app(self, val: Value) -> Value { + self.app_val(val) + } + + /// Apply to a value + pub(crate) fn app_val(self, val: Value) -> Value { + self.app_thunk(val.into_thunk()) + } + + /// Apply to a thunk + pub(crate) fn app_thunk(self, th: Thunk) -> Value { + Thunk::from_value(self).app_thunk(th) + } + + pub(crate) fn from_builtin(b: Builtin) -> Value { + Value::AppliedBuiltin(b, vec![]) + } + + pub(crate) fn shift(&self, delta: isize, var: &AlphaVar) -> Self { + match self { + Value::Lam(x, t, e) => Value::Lam( + x.clone(), + t.shift(delta, var), + e.shift(delta, &var.shift(1, &x.into())), + ), + Value::AppliedBuiltin(b, args) => Value::AppliedBuiltin( + *b, + args.iter().map(|v| v.shift(delta, var)).collect(), + ), + Value::NaturalSuccClosure => Value::NaturalSuccClosure, + Value::OptionalSomeClosure(tth) => { + Value::OptionalSomeClosure(tth.shift(delta, var)) + } + Value::ListConsClosure(t, v) => Value::ListConsClosure( + t.shift(delta, var), + v.as_ref().map(|v| v.shift(delta, var)), + ), + Value::Pi(x, t, e) => Value::Pi( + x.clone(), + t.shift(delta, var), + e.shift(delta, &var.shift(1, &x.into())), + ), + Value::Var(v) => Value::Var(v.shift(delta, var)), + Value::Const(c) => Value::Const(*c), + Value::BoolLit(b) => Value::BoolLit(*b), + Value::NaturalLit(n) => Value::NaturalLit(*n), + Value::IntegerLit(n) => Value::IntegerLit(*n), + Value::EmptyOptionalLit(tth) => { + Value::EmptyOptionalLit(tth.shift(delta, var)) + } + Value::NEOptionalLit(th) => { + Value::NEOptionalLit(th.shift(delta, var)) + } + Value::EmptyListLit(tth) => { + Value::EmptyListLit(tth.shift(delta, var)) + } + Value::NEListLit(elts) => Value::NEListLit( + elts.iter().map(|v| v.shift(delta, var)).collect(), + ), + Value::RecordLit(kvs) => Value::RecordLit( + kvs.iter() + .map(|(k, v)| (k.clone(), v.shift(delta, var))) + .collect(), + ), + Value::RecordType(kvs) => Value::RecordType( + kvs.iter() + .map(|(k, v)| (k.clone(), v.shift(delta, var))) + .collect(), + ), + Value::UnionType(kts) => Value::UnionType( + kts.iter() + .map(|(k, v)| { + (k.clone(), v.as_ref().map(|v| v.shift(delta, var))) + }) + .collect(), + ), + Value::UnionConstructor(x, kts) => Value::UnionConstructor( + x.clone(), + kts.iter() + .map(|(k, v)| { + (k.clone(), v.as_ref().map(|v| v.shift(delta, var))) + }) + .collect(), + ), + Value::UnionLit(x, v, kts) => Value::UnionLit( + x.clone(), + v.shift(delta, var), + kts.iter() + .map(|(k, v)| { + (k.clone(), v.as_ref().map(|v| v.shift(delta, var))) + }) + .collect(), + ), + Value::TextLit(elts) => Value::TextLit( + elts.iter() + .map(|contents| { + use InterpolatedTextContents::{Expr, Text}; + match contents { + Expr(th) => Expr(th.shift(delta, var)), + Text(s) => Text(s.clone()), + } + }) + .collect(), + ), + Value::PartialExpr(e) => { + Value::PartialExpr(e.map_ref_with_special_handling_of_binders( + |v| v.shift(delta, var), + |x, v| v.shift(delta, &var.shift(1, &x.into())), + X::clone, + Label::clone, + )) + } + } + } + + pub(crate) fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self { + match self { + // Retry normalizing since substituting may allow progress + Value::AppliedBuiltin(b, args) => apply_builtin( + *b, + args.iter().map(|v| v.subst_shift(var, val)).collect(), + ), + // Retry normalizing since substituting may allow progress + Value::PartialExpr(e) => { + normalize_one_layer(e.map_ref_with_special_handling_of_binders( + |v| v.subst_shift(var, val), + |x, v| { + v.subst_shift( + &var.shift(1, &x.into()), + &val.shift(1, &x.into()), + ) + }, + X::clone, + Label::clone, + )) + } + // Retry normalizing since substituting may allow progress + Value::TextLit(elts) => { + Value::TextLit(squash_textlit(elts.iter().map(|contents| { + use InterpolatedTextContents::{Expr, Text}; + match contents { + Expr(th) => Expr(th.subst_shift(var, val)), + Text(s) => Text(s.clone()), + } + }))) + } + Value::Lam(x, t, e) => Value::Lam( + x.clone(), + t.subst_shift(var, val), + e.subst_shift( + &var.shift(1, &x.into()), + &val.shift(1, &x.into()), + ), + ), + Value::NaturalSuccClosure => Value::NaturalSuccClosure, + Value::OptionalSomeClosure(tth) => { + Value::OptionalSomeClosure(tth.subst_shift(var, val)) + } + Value::ListConsClosure(t, v) => Value::ListConsClosure( + t.subst_shift(var, val), + v.as_ref().map(|v| v.subst_shift(var, val)), + ), + Value::Pi(x, t, e) => Value::Pi( + x.clone(), + t.subst_shift(var, val), + e.subst_shift( + &var.shift(1, &x.into()), + &val.shift(1, &x.into()), + ), + ), + Value::Var(v) if v == var => val.to_value().clone(), + Value::Var(v) => Value::Var(v.shift(-1, var)), + Value::Const(c) => Value::Const(*c), + Value::BoolLit(b) => Value::BoolLit(*b), + Value::NaturalLit(n) => Value::NaturalLit(*n), + Value::IntegerLit(n) => Value::IntegerLit(*n), + Value::EmptyOptionalLit(tth) => { + Value::EmptyOptionalLit(tth.subst_shift(var, val)) + } + Value::NEOptionalLit(th) => { + Value::NEOptionalLit(th.subst_shift(var, val)) + } + Value::EmptyListLit(tth) => { + Value::EmptyListLit(tth.subst_shift(var, val)) + } + Value::NEListLit(elts) => Value::NEListLit( + elts.iter().map(|v| v.subst_shift(var, val)).collect(), + ), + Value::RecordLit(kvs) => Value::RecordLit( + kvs.iter() + .map(|(k, v)| (k.clone(), v.subst_shift(var, val))) + .collect(), + ), + Value::RecordType(kvs) => Value::RecordType( + kvs.iter() + .map(|(k, v)| (k.clone(), v.subst_shift(var, val))) + .collect(), + ), + Value::UnionType(kts) => Value::UnionType( + kts.iter() + .map(|(k, v)| { + (k.clone(), v.as_ref().map(|v| v.subst_shift(var, val))) + }) + .collect(), + ), + Value::UnionConstructor(x, kts) => Value::UnionConstructor( + x.clone(), + kts.iter() + .map(|(k, v)| { + (k.clone(), v.as_ref().map(|v| v.subst_shift(var, val))) + }) + .collect(), + ), + Value::UnionLit(x, v, kts) => Value::UnionLit( + x.clone(), + v.subst_shift(var, val), + kts.iter() + .map(|(k, v)| { + (k.clone(), v.as_ref().map(|v| v.subst_shift(var, val))) + }) + .collect(), + ), + } + } +} + +impl std::cmp::PartialEq for AlphaVar { + fn eq(&self, other: &Self) -> bool { + match (&self.alpha, &other.alpha) { + (Some(x), Some(y)) => x == y, + (None, None) => self.normal == other.normal, + _ => false, + } + } +} +impl std::cmp::PartialEq for AlphaLabel { + fn eq(&self, _other: &Self) -> bool { + true + } +} + +impl From<Label> for AlphaVar { + fn from(x: Label) -> AlphaVar { + AlphaVar { + normal: V(x, 0), + alpha: Some(V((), 0)), + } + } +} +impl<'a> From<&'a Label> for AlphaVar { + fn from(x: &'a Label) -> AlphaVar { + x.clone().into() + } +} +impl From<AlphaLabel> for AlphaVar { + fn from(x: AlphaLabel) -> AlphaVar { + let l: Label = x.into(); + l.into() + } +} +impl<'a> From<&'a AlphaLabel> for AlphaVar { + fn from(x: &'a AlphaLabel) -> AlphaVar { + x.clone().into() + } +} + +impl From<Label> for AlphaLabel { + fn from(x: Label) -> AlphaLabel { + AlphaLabel(x) + } +} +impl From<AlphaLabel> for Label { + fn from(x: AlphaLabel) -> Label { + x.0 + } +} diff --git a/dhall/src/error.rs b/dhall/src/error.rs deleted file mode 100644 index 6ed0bfb..0000000 --- a/dhall/src/error.rs +++ /dev/null @@ -1,52 +0,0 @@ -pub type Result<T> = std::result::Result<T, Error>; - -#[derive(Debug)] -#[non_exhaustive] -pub enum Error { - IO(std::io::Error), - Parse(dhall_syntax::ParseError), - Decode(crate::binary::DecodeError), - Resolve(crate::imports::ImportError), - Typecheck(crate::typecheck::TypeError), - Deserialize(String), -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - Error::IO(err) => write!(f, "{}", err), - Error::Parse(err) => write!(f, "{}", err), - Error::Decode(err) => write!(f, "{:?}", err), - Error::Resolve(err) => write!(f, "{:?}", err), - Error::Typecheck(err) => write!(f, "{:?}", err), - Error::Deserialize(err) => write!(f, "{}", err), - } - } -} - -impl std::error::Error for Error {} -impl From<std::io::Error> for Error { - fn from(err: std::io::Error) -> Error { - Error::IO(err) - } -} -impl From<dhall_syntax::ParseError> for Error { - fn from(err: dhall_syntax::ParseError) -> Error { - Error::Parse(err) - } -} -impl From<crate::binary::DecodeError> for Error { - fn from(err: crate::binary::DecodeError) -> Error { - Error::Decode(err) - } -} -impl From<crate::imports::ImportError> for Error { - fn from(err: crate::imports::ImportError) -> Error { - Error::Resolve(err) - } -} -impl From<crate::typecheck::TypeError> for Error { - fn from(err: crate::typecheck::TypeError) -> Error { - Error::Typecheck(err) - } -} diff --git a/dhall/src/error/mod.rs b/dhall/src/error/mod.rs new file mode 100644 index 0000000..f84d078 --- /dev/null +++ b/dhall/src/error/mod.rs @@ -0,0 +1,169 @@ +use std::io::Error as IOError; + +use dhall_syntax::{BinOp, Import, Label, ParseError, V}; + +use crate::core::context::TypecheckContext; +use crate::phase::resolve::ImportStack; +use crate::phase::{Normalized, Type, Typed}; + +pub type Result<T> = std::result::Result<T, Error>; + +#[derive(Debug)] +#[non_exhaustive] +pub enum Error { + IO(IOError), + Parse(ParseError), + Decode(DecodeError), + Resolve(ImportError), + Typecheck(TypeError), + Deserialize(String), +} + +#[derive(Debug)] +pub enum ImportError { + Recursive(Import, Box<Error>), + UnexpectedImport(Import), + ImportCycle(ImportStack, Import), +} + +#[derive(Debug)] +pub enum DecodeError { + CBORError(serde_cbor::error::Error), + WrongFormatError(String), +} + +/// A structured type error that includes context +#[derive(Debug)] +pub struct TypeError { + type_message: TypeMessage, + context: TypecheckContext, +} + +/// The specific type error +#[derive(Debug)] +pub(crate) enum TypeMessage { + UnboundVariable(V<Label>), + InvalidInputType(Normalized), + InvalidOutputType(Normalized), + NotAFunction(Typed), + TypeMismatch(Typed, Normalized, Typed), + AnnotMismatch(Typed, Normalized), + Untyped, + InvalidListElement(usize, Normalized, Typed), + InvalidListType(Normalized), + InvalidOptionalType(Normalized), + InvalidPredicate(Typed), + IfBranchMismatch(Typed, Typed), + IfBranchMustBeTerm(bool, Typed), + InvalidFieldType(Label, Type), + NotARecord(Label, Normalized), + MissingRecordField(Label, Typed), + MissingUnionField(Label, Normalized), + BinOpTypeMismatch(BinOp, Typed), + NoDependentTypes(Normalized, Normalized), + InvalidTextInterpolation(Typed), + Sort, + Unimplemented, +} + +impl TypeError { + pub(crate) fn new( + context: &TypecheckContext, + type_message: TypeMessage, + ) -> Self { + TypeError { + context: context.clone(), + type_message, + } + } +} + +impl From<TypeError> for std::option::NoneError { + fn from(_: TypeError) -> std::option::NoneError { + std::option::NoneError + } +} + +impl std::error::Error for TypeMessage { + fn description(&self) -> &str { + use TypeMessage::*; + match *self { + // UnboundVariable => "Unbound variable", + InvalidInputType(_) => "Invalid function input", + InvalidOutputType(_) => "Invalid function output", + NotAFunction(_) => "Not a function", + TypeMismatch(_, _, _) => "Wrong type of function argument", + _ => "Unhandled error", + } + } +} + +impl std::fmt::Display for TypeMessage { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + // UnboundVariable(_) => { + // f.write_str(include_str!("errors/UnboundVariable.txt")) + // } + // TypeMismatch(e0, e1, e2) => { + // let template = include_str!("errors/TypeMismatch.txt"); + // let s = template + // .replace("$txt0", &format!("{}", e0.as_expr())) + // .replace("$txt1", &format!("{}", e1.as_expr())) + // .replace("$txt2", &format!("{}", e2.as_expr())) + // .replace( + // "$txt3", + // &format!( + // "{}", + // e2.get_type() + // .unwrap() + // .as_normalized() + // .unwrap() + // .as_expr() + // ), + // ); + // f.write_str(&s) + // } + _ => f.write_str("Unhandled error message"), + } + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Error::IO(err) => write!(f, "{}", err), + Error::Parse(err) => write!(f, "{}", err), + Error::Decode(err) => write!(f, "{:?}", err), + Error::Resolve(err) => write!(f, "{:?}", err), + Error::Typecheck(err) => write!(f, "{:?}", err), + Error::Deserialize(err) => write!(f, "{}", err), + } + } +} + +impl std::error::Error for Error {} +impl From<IOError> for Error { + fn from(err: IOError) -> Error { + Error::IO(err) + } +} +impl From<ParseError> for Error { + fn from(err: ParseError) -> Error { + Error::Parse(err) + } +} +impl From<DecodeError> for Error { + fn from(err: DecodeError) -> Error { + Error::Decode(err) + } +} +impl From<ImportError> for Error { + fn from(err: ImportError) -> Error { + Error::Resolve(err) + } +} +impl From<TypeError> for Error { + fn from(err: TypeError) -> Error { + Error::Typecheck(err) + } +} diff --git a/dhall/src/errors/AnnotMismatch.txt b/dhall/src/error/text/AnnotMismatch.txt index 4904bf8..4904bf8 100644 --- a/dhall/src/errors/AnnotMismatch.txt +++ b/dhall/src/error/text/AnnotMismatch.txt diff --git a/dhall/src/errors/CantTextAppend.txt b/dhall/src/error/text/CantTextAppend.txt index 26b9ceb..26b9ceb 100644 --- a/dhall/src/errors/CantTextAppend.txt +++ b/dhall/src/error/text/CantTextAppend.txt diff --git a/dhall/src/errors/DuplicateAlternative.txt b/dhall/src/error/text/DuplicateAlternative.txt index 077f8aa..077f8aa 100644 --- a/dhall/src/errors/DuplicateAlternative.txt +++ b/dhall/src/error/text/DuplicateAlternative.txt diff --git a/dhall/src/errors/FieldCollision.txt b/dhall/src/error/text/FieldCollision.txt index 2b2d260..2b2d260 100644 --- a/dhall/src/errors/FieldCollision.txt +++ b/dhall/src/error/text/FieldCollision.txt diff --git a/dhall/src/errors/HandlerInputTypeMismatch.txt b/dhall/src/error/text/HandlerInputTypeMismatch.txt index 7d3525b..7d3525b 100644 --- a/dhall/src/errors/HandlerInputTypeMismatch.txt +++ b/dhall/src/error/text/HandlerInputTypeMismatch.txt diff --git a/dhall/src/errors/HandlerNotAFunction.txt b/dhall/src/error/text/HandlerNotAFunction.txt index ff87443..ff87443 100644 --- a/dhall/src/errors/HandlerNotAFunction.txt +++ b/dhall/src/error/text/HandlerNotAFunction.txt diff --git a/dhall/src/errors/HandlerOutputTypeMismatch.txt b/dhall/src/error/text/HandlerOutputTypeMismatch.txt index f359459..f359459 100644 --- a/dhall/src/errors/HandlerOutputTypeMismatch.txt +++ b/dhall/src/error/text/HandlerOutputTypeMismatch.txt diff --git a/dhall/src/errors/IfBranchMismatch.txt b/dhall/src/error/text/IfBranchMismatch.txt index a95b130..a95b130 100644 --- a/dhall/src/errors/IfBranchMismatch.txt +++ b/dhall/src/error/text/IfBranchMismatch.txt diff --git a/dhall/src/errors/IfBranchMustBeTerm.txt b/dhall/src/error/text/IfBranchMustBeTerm.txt index 4c15881..4c15881 100644 --- a/dhall/src/errors/IfBranchMustBeTerm.txt +++ b/dhall/src/error/text/IfBranchMustBeTerm.txt diff --git a/dhall/src/errors/InvalidAlterantive.txt b/dhall/src/error/text/InvalidAlterantive.txt index 391fc3a..391fc3a 100644 --- a/dhall/src/errors/InvalidAlterantive.txt +++ b/dhall/src/error/text/InvalidAlterantive.txt diff --git a/dhall/src/errors/InvalidAlterantiveType.txt b/dhall/src/error/text/InvalidAlterantiveType.txt index f5dadef..f5dadef 100644 --- a/dhall/src/errors/InvalidAlterantiveType.txt +++ b/dhall/src/error/text/InvalidAlterantiveType.txt diff --git a/dhall/src/errors/InvalidField.txt b/dhall/src/error/text/InvalidField.txt index bfbf106..bfbf106 100644 --- a/dhall/src/errors/InvalidField.txt +++ b/dhall/src/error/text/InvalidField.txt diff --git a/dhall/src/errors/InvalidFieldType.txt b/dhall/src/error/text/InvalidFieldType.txt index 4f76a64..4f76a64 100644 --- a/dhall/src/errors/InvalidFieldType.txt +++ b/dhall/src/error/text/InvalidFieldType.txt diff --git a/dhall/src/errors/InvalidInputType.txt b/dhall/src/error/text/InvalidInputType.txt index eabafa4..eabafa4 100644 --- a/dhall/src/errors/InvalidInputType.txt +++ b/dhall/src/error/text/InvalidInputType.txt diff --git a/dhall/src/errors/InvalidListElement.txt b/dhall/src/error/text/InvalidListElement.txt index 59db7b7..59db7b7 100644 --- a/dhall/src/errors/InvalidListElement.txt +++ b/dhall/src/error/text/InvalidListElement.txt diff --git a/dhall/src/errors/InvalidListType.txt b/dhall/src/error/text/InvalidListType.txt index 676647e..676647e 100644 --- a/dhall/src/errors/InvalidListType.txt +++ b/dhall/src/error/text/InvalidListType.txt diff --git a/dhall/src/errors/InvalidOptionType.txt b/dhall/src/error/text/InvalidOptionType.txt index 3bc81de..3bc81de 100644 --- a/dhall/src/errors/InvalidOptionType.txt +++ b/dhall/src/error/text/InvalidOptionType.txt diff --git a/dhall/src/errors/InvalidOptionalElement.txt b/dhall/src/error/text/InvalidOptionalElement.txt index 0254220..0254220 100644 --- a/dhall/src/errors/InvalidOptionalElement.txt +++ b/dhall/src/error/text/InvalidOptionalElement.txt diff --git a/dhall/src/errors/InvalidOptionalLiteral.txt b/dhall/src/error/text/InvalidOptionalLiteral.txt index 41c0fdc..41c0fdc 100644 --- a/dhall/src/errors/InvalidOptionalLiteral.txt +++ b/dhall/src/error/text/InvalidOptionalLiteral.txt diff --git a/dhall/src/errors/InvalidOutputType.txt b/dhall/src/error/text/InvalidOutputType.txt index dd2695d..dd2695d 100644 --- a/dhall/src/errors/InvalidOutputType.txt +++ b/dhall/src/error/text/InvalidOutputType.txt diff --git a/dhall/src/errors/InvalidPredicate.txt b/dhall/src/error/text/InvalidPredicate.txt index 4c15881..4c15881 100644 --- a/dhall/src/errors/InvalidPredicate.txt +++ b/dhall/src/error/text/InvalidPredicate.txt diff --git a/dhall/src/errors/MissingField.txt b/dhall/src/error/text/MissingField.txt index de14a33..de14a33 100644 --- a/dhall/src/errors/MissingField.txt +++ b/dhall/src/error/text/MissingField.txt diff --git a/dhall/src/errors/MissingHandler.txt b/dhall/src/error/text/MissingHandler.txt index 433445e..433445e 100644 --- a/dhall/src/errors/MissingHandler.txt +++ b/dhall/src/error/text/MissingHandler.txt diff --git a/dhall/src/errors/MustCombineARecord.txt b/dhall/src/error/text/MustCombineARecord.txt index 141b969..141b969 100644 --- a/dhall/src/errors/MustCombineARecord.txt +++ b/dhall/src/error/text/MustCombineARecord.txt diff --git a/dhall/src/errors/MustMergeARecord.txt b/dhall/src/error/text/MustMergeARecord.txt index 79094bd..79094bd 100644 --- a/dhall/src/errors/MustMergeARecord.txt +++ b/dhall/src/error/text/MustMergeARecord.txt diff --git a/dhall/src/errors/MustMergeUnion.txt b/dhall/src/error/text/MustMergeUnion.txt index 68df70c..68df70c 100644 --- a/dhall/src/errors/MustMergeUnion.txt +++ b/dhall/src/error/text/MustMergeUnion.txt diff --git a/dhall/src/errors/NoDependentLet.txt b/dhall/src/error/text/NoDependentLet.txt index fdc65b4..fdc65b4 100644 --- a/dhall/src/errors/NoDependentLet.txt +++ b/dhall/src/error/text/NoDependentLet.txt diff --git a/dhall/src/errors/NoDependentTypes.txt b/dhall/src/error/text/NoDependentTypes.txt index 435bdcb..435bdcb 100644 --- a/dhall/src/errors/NoDependentTypes.txt +++ b/dhall/src/error/text/NoDependentTypes.txt diff --git a/dhall/src/errors/NotAFunction.txt b/dhall/src/error/text/NotAFunction.txt index dd2695d..dd2695d 100644 --- a/dhall/src/errors/NotAFunction.txt +++ b/dhall/src/error/text/NotAFunction.txt diff --git a/dhall/src/errors/NotARecord.txt b/dhall/src/error/text/NotARecord.txt index e0eebc8..e0eebc8 100644 --- a/dhall/src/errors/NotARecord.txt +++ b/dhall/src/error/text/NotARecord.txt diff --git a/dhall/src/errors/TypeMismatch.txt b/dhall/src/error/text/TypeMismatch.txt index 4904bf8..4904bf8 100644 --- a/dhall/src/errors/TypeMismatch.txt +++ b/dhall/src/error/text/TypeMismatch.txt diff --git a/dhall/src/errors/UnboundVariable.txt b/dhall/src/error/text/UnboundVariable.txt index bd7d483..bd7d483 100644 --- a/dhall/src/errors/UnboundVariable.txt +++ b/dhall/src/error/text/UnboundVariable.txt diff --git a/dhall/src/errors/Untyped.txt b/dhall/src/error/text/Untyped.txt index 4904bf8..4904bf8 100644 --- a/dhall/src/errors/Untyped.txt +++ b/dhall/src/error/text/Untyped.txt diff --git a/dhall/src/errors/UnusedHandler.txt b/dhall/src/error/text/UnusedHandler.txt index 2e46a12..2e46a12 100644 --- a/dhall/src/errors/UnusedHandler.txt +++ b/dhall/src/error/text/UnusedHandler.txt diff --git a/dhall/src/expr.rs b/dhall/src/expr.rs deleted file mode 100644 index 7dbbf1c..0000000 --- a/dhall/src/expr.rs +++ /dev/null @@ -1,244 +0,0 @@ -use crate::imports::ImportRoot; -use crate::normalize::{Thunk, Value}; -use dhall_syntax::*; - -macro_rules! derive_other_traits { - ($ty:ident) => { - impl std::cmp::PartialEq for $ty { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } - } - - impl std::cmp::Eq for $ty {} - - impl std::fmt::Display for $ty { - fn fmt( - &self, - f: &mut std::fmt::Formatter, - ) -> Result<(), std::fmt::Error> { - self.0.fmt(f) - } - } - }; -} - -#[derive(Debug, Clone)] -pub(crate) struct Parsed( - pub(crate) SubExpr<Span, Import>, - pub(crate) ImportRoot, -); -derive_other_traits!(Parsed); - -#[derive(Debug, Clone)] -pub(crate) struct Resolved(pub(crate) SubExpr<Span, Normalized>); -derive_other_traits!(Resolved); - -pub(crate) use self::typed::TypedInternal; - -#[derive(Debug, Clone)] -pub(crate) struct Typed(pub(crate) TypedInternal); - -#[derive(Debug, Clone)] -pub(crate) struct Normalized(pub(crate) TypedInternal); - -impl std::cmp::PartialEq for Normalized { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } -} - -impl std::cmp::Eq for Normalized {} - -impl std::fmt::Display for Normalized { - fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { - self.to_expr().fmt(f) - } -} - -mod typed { - use super::{Type, Typed}; - use crate::normalize::{AlphaVar, Thunk, Value}; - use crate::typecheck::{ - TypeError, TypeInternal, TypeMessage, TypecheckContext, - }; - use dhall_syntax::{Const, SubExpr, X}; - use std::borrow::Cow; - - #[derive(Debug, Clone)] - pub(crate) enum TypedInternal { - // The `Sort` higher-kinded type doesn't have a type - Sort, - // Any other value, along with its type - Value(Thunk, Option<Type>), - } - - impl TypedInternal { - pub(crate) fn from_thunk_and_type(th: Thunk, t: Type) -> Self { - TypedInternal::Value(th, Some(t)) - } - - pub(crate) fn from_thunk_untyped(th: Thunk) -> Self { - TypedInternal::Value(th, None) - } - - // TODO: Avoid cloning if possible - pub(crate) fn to_value(&self) -> Value { - match self { - TypedInternal::Value(th, _) => th.to_value(), - TypedInternal::Sort => Value::Const(Const::Sort), - } - } - - pub(crate) fn to_expr(&self) -> SubExpr<X, X> { - self.to_value().normalize_to_expr() - } - - pub(crate) fn to_expr_alpha(&self) -> SubExpr<X, X> { - self.to_value().normalize_to_expr_maybe_alpha(true) - } - - pub(crate) fn to_thunk(&self) -> Thunk { - match self { - TypedInternal::Value(th, _) => th.clone(), - TypedInternal::Sort => { - Thunk::from_value(Value::Const(Const::Sort)) - } - } - } - - pub(crate) fn to_type(&self) -> Type { - match self { - TypedInternal::Sort => Type(TypeInternal::Const(Const::Sort)), - TypedInternal::Value(th, _) => match &*th.as_value() { - Value::Const(c) => Type(TypeInternal::Const(*c)), - _ => { - Type(TypeInternal::Typed(Box::new(Typed(self.clone())))) - } - }, - } - } - - pub(crate) fn get_type(&self) -> Result<Cow<'_, Type>, TypeError> { - match self { - TypedInternal::Value(_, Some(t)) => Ok(Cow::Borrowed(t)), - TypedInternal::Value(_, None) => Err(TypeError::new( - &TypecheckContext::new(), - TypeMessage::Untyped, - )), - TypedInternal::Sort => Err(TypeError::new( - &TypecheckContext::new(), - TypeMessage::Sort, - )), - } - } - - pub(crate) fn shift(&self, delta: isize, var: &AlphaVar) -> Self { - match self { - TypedInternal::Value(th, t) => TypedInternal::Value( - th.shift(delta, var), - t.as_ref().map(|x| x.shift(delta, var)), - ), - TypedInternal::Sort => TypedInternal::Sort, - } - } - - pub(crate) fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self { - match self { - TypedInternal::Value(th, t) => TypedInternal::Value( - th.subst_shift(var, val), - t.as_ref().map(|x| x.subst_shift(var, val)), - ), - TypedInternal::Sort => TypedInternal::Sort, - } - } - } - - impl std::cmp::PartialEq for TypedInternal { - fn eq(&self, other: &Self) -> bool { - self.to_value() == other.to_value() - } - } - - impl std::cmp::Eq for TypedInternal {} -} - -/// A Dhall expression representing a simple type. -/// -/// This captures what is usually simply called a "type", like -/// `Bool`, `{ x: Integer }` or `Natural -> Text`. -/// -/// For a more general notion of "type", see [Type]. -#[derive(Debug, Clone)] -pub struct SimpleType(pub(crate) SubExpr<X, X>); -derive_other_traits!(SimpleType); - -pub(crate) use crate::typecheck::TypeInternal; - -/// A Dhall expression representing a (possibly higher-kinded) type. -/// -/// This includes [SimpleType]s but also higher-kinded expressions like -/// `Type`, `Kind` and `{ x: Type }`. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Type(pub(crate) TypeInternal); - -impl std::fmt::Display for Type { - fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { - self.to_normalized().fmt(f) - } -} - -// Exposed for the macros -#[doc(hidden)] -impl From<SimpleType> for SubExpr<X, X> { - fn from(x: SimpleType) -> SubExpr<X, X> { - x.0 - } -} - -// Exposed for the macros -#[doc(hidden)] -impl From<SubExpr<X, X>> for SimpleType { - fn from(x: SubExpr<X, X>) -> SimpleType { - SimpleType(x) - } -} - -// Exposed for the macros -#[doc(hidden)] -impl From<Normalized> for Typed { - fn from(x: Normalized) -> Typed { - Typed(x.0) - } -} - -impl Normalized { - pub(crate) fn from_thunk_and_type(th: Thunk, t: Type) -> Self { - Normalized(TypedInternal::from_thunk_and_type(th, t)) - } - pub(crate) fn to_expr(&self) -> SubExpr<X, X> { - self.0.to_expr() - } - #[allow(dead_code)] - pub(crate) fn to_expr_alpha(&self) -> SubExpr<X, X> { - self.0.to_expr_alpha() - } - pub(crate) fn to_value(&self) -> Value { - self.0.to_value() - } - pub(crate) fn to_thunk(&self) -> Thunk { - self.0.to_thunk() - } -} - -impl Typed { - pub(crate) fn from_thunk_and_type(th: Thunk, t: Type) -> Self { - Typed(TypedInternal::from_thunk_and_type(th, t)) - } - pub(crate) fn from_thunk_untyped(th: Thunk) -> Self { - Typed(TypedInternal::from_thunk_untyped(th)) - } - pub(crate) fn const_sort() -> Self { - Typed(TypedInternal::Sort) - } -} diff --git a/dhall/src/lib.rs b/dhall/src/lib.rs index a531f64..8d1496a 100644 --- a/dhall/src/lib.rs +++ b/dhall/src/lib.rs @@ -123,51 +123,10 @@ #[macro_use] mod tests; -#[cfg(test)] -mod parser; - -mod binary; -/// When manipulating Dhall expressions goes wrong. +pub(crate) mod api; +pub(crate) mod core; pub mod error; -pub mod expr; -mod imports; -mod normalize; -mod serde; -mod traits; -mod typecheck; - -/// Deserialization of Dhall expressions into Rust -pub mod de { - pub use crate::traits::{Deserialize, SimpleStaticType, StaticType}; - #[doc(hidden)] - pub use dhall_proc_macros::SimpleStaticType; - - /// Deserialize an instance of type T from a string of Dhall text. - /// - /// This will recursively resolve all imports in the expression, and - /// typecheck it before deserialization. Relative imports will be resolved relative to the - /// provided file. More control over this process is not yet available - /// but will be in a coming version of this crate. - /// - /// If a type is provided, this additionally checks that the provided - /// expression has that type. - pub fn from_str<'a, T: Deserialize<'a>>( - s: &'a str, - ty: Option<&crate::expr::Type>, - ) -> crate::error::Result<T> { - T::from_str(s, ty) - } +pub(crate) mod phase; +pub(crate) use api::traits; - /// Deserialize an instance of type T from a string of Dhall text, - /// additionally checking that it matches the type of T. - /// - /// This will recursively resolve all imports in the expression, and - /// typecheck it before deserialization. Relative imports will be resolved relative to the - /// provided file. More control over this process is not yet available - /// but will be in a coming version of this crate. - pub fn from_str_auto_type<'a, T: Deserialize<'a> + StaticType>( - s: &'a str, - ) -> crate::error::Result<T> { - from_str(s, Some(&<T as StaticType>::get_static_type())) - } -} +pub use api::de; diff --git a/dhall/src/main.rs b/dhall/src/main.rs deleted file mode 100644 index e25a535..0000000 --- a/dhall/src/main.rs +++ /dev/null @@ -1,93 +0,0 @@ -// use std::error::Error; -// use std::io::{self, Read}; -// use term_painter::ToStyle; - -// const ERROR_STYLE: term_painter::Color = term_painter::Color::Red; -// const BOLD: term_painter::Attr = term_painter::Attr::Bold; - -// fn print_error(message: &str, source: &str, start: usize, end: usize) { -// let line_number = bytecount::count(source[..start].as_bytes(), b'\n'); -// let line_start = source[..start].rfind('\n').map(|i| i + 1).unwrap_or(0); -// let line_end = source[end..].find('\n').unwrap_or(0) + end; -// let context_prefix = &source[line_start..start]; -// let context_highlighted = &source[start..end]; -// let context_suffix = &source[end..line_end]; - -// let line_number_str = line_number.to_string(); -// let line_number_width = line_number_str.len(); - -// BOLD.with(|| { -// ERROR_STYLE.with(|| { -// print!("error: "); -// }); -// println!("{}", message); -// }); -// BOLD.with(|| { -// print!(" -->"); -// }); -// println!(" (stdin):{}:0", line_number); -// BOLD.with(|| { -// println!("{:w$} |", "", w = line_number_width); -// print!("{} |", line_number_str); -// }); -// print!(" {}", context_prefix); -// BOLD.with(|| { -// ERROR_STYLE.with(|| { -// print!("{}", context_highlighted); -// }); -// }); -// println!("{}", context_suffix); -// BOLD.with(|| { -// print!("{:w$} |", "", w = line_number_width); -// ERROR_STYLE.with(|| { -// println!( -// " {:so$}{:^>ew$}", -// "", -// "", -// so = source[line_start..start].chars().count(), -// ew = ::std::cmp::max(1, source[start..end].chars().count()) -// ); -// }); -// }); -// } - -fn main() { - // let mut buffer = String::new(); - // io::stdin().read_to_string(&mut buffer).unwrap(); - - // TODO: public API is too restricted for this - // let expr = match dhall::expr::Parsed::parse_str(&buffer) { - // Ok(expr) => expr, - // Err(e) => { - // print_error(&format!("Parse error {}", e), &buffer, 0, 0); - // return; - // } - // }; - - // let expr = expr.resolve().unwrap(); - - // let expr = match expr.typecheck() { - // Ok(expr) => expr, - // Err(e) => { - // // TODO: implement pretty type error printing - // // let explain = ::std::env::args().any(|s| s == "--explain"); - // // if !explain { - // // term_painter::Color::BrightBlack.with(|| { - // // println!("Use \"dhall --explain\" for detailed errors"); - // // }); - // // } - // // ERROR_STYLE.with(|| print!("Error: ")); - // // println!("{}", e.type_message.description()); - // // if explain { - // // println!("{}", e.type_message); - // // } - // // println!("{}", e.current); - // // // FIXME Print source position - // return; - // } - // }; - - // let expr = expr.normalize(); - - // println!("{}", expr); -} diff --git a/dhall/src/parser.rs b/dhall/src/parser.rs deleted file mode 100644 index 3648c4f..0000000 --- a/dhall/src/parser.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[cfg(test)] -mod spec_tests { - #![rustfmt::skip] - // See ../build.rs - include!(concat!(env!("OUT_DIR"), "/parser_tests.rs")); -} diff --git a/dhall/src/binary.rs b/dhall/src/phase/binary.rs index 9c31d4c..30aa7a0 100644 --- a/dhall/src/binary.rs +++ b/dhall/src/phase/binary.rs @@ -1,14 +1,15 @@ -use dhall_syntax::*; -use itertools::*; +use itertools::Itertools; use serde_cbor::value::value as cbor; -type ParsedExpr = SubExpr<X, Import>; +use dhall_syntax::{ + rc, ExprF, FilePrefix, Hash, Import, ImportHashed, ImportLocation, + ImportMode, Integer, InterpolatedText, Label, Natural, Scheme, SubExpr, + URL, V, X, +}; -#[derive(Debug)] -pub enum DecodeError { - CBORError(serde_cbor::error::Error), - WrongFormatError(String), -} +use crate::error::DecodeError; + +type ParsedExpr = SubExpr<X, Import>; pub fn decode(data: &[u8]) -> Result<ParsedExpr, DecodeError> { match serde_cbor::de::from_slice(data) { diff --git a/dhall/src/phase/mod.rs b/dhall/src/phase/mod.rs new file mode 100644 index 0000000..c8a8ffd --- /dev/null +++ b/dhall/src/phase/mod.rs @@ -0,0 +1,334 @@ +use std::borrow::Cow; +use std::fmt::Display; +use std::path::Path; + +use dhall_syntax::{Const, Import, Span, SubExpr, X}; + +use crate::core::context::TypecheckContext; +use crate::core::thunk::Thunk; +use crate::core::value::{AlphaVar, Value}; +use crate::error::{Error, ImportError, TypeError, TypeMessage}; + +use resolve::ImportRoot; +use typecheck::type_of_const; + +pub(crate) mod binary; +pub(crate) mod normalize; +pub(crate) mod parse; +pub(crate) mod resolve; +pub(crate) mod typecheck; + +pub(crate) type ParsedSubExpr = SubExpr<Span, Import>; +pub(crate) type ResolvedSubExpr = SubExpr<Span, Normalized>; +pub(crate) type NormalizedSubExpr = SubExpr<X, X>; + +#[derive(Debug, Clone)] +pub(crate) struct Parsed(pub(crate) ParsedSubExpr, pub(crate) ImportRoot); + +/// An expression where all imports have been resolved +#[derive(Debug, Clone)] +pub(crate) struct Resolved(pub(crate) ResolvedSubExpr); + +/// A typed expression +#[derive(Debug, Clone)] +pub(crate) enum Typed { + // Any value, along with (optionally) its type + Value(Thunk, Option<Type>), + // One of the base higher-kinded typed. + // Used to avoid storing the same tower ot Type->Kind->Sort + // over and over again. Also enables having Sort as a type + // even though it doesn't itself have a type. + Const(Const), +} + +/// A normalized expression. +/// +/// Invariant: the contained Typed expression must be in normal form, +#[derive(Debug, Clone)] +pub(crate) struct Normalized(pub(crate) Typed); + +/// A Dhall expression representing a simple type. +/// +/// This captures what is usually simply called a "type", like +/// `Bool`, `{ x: Integer }` or `Natural -> Text`. +/// +/// For a more general notion of "type", see [Type]. +#[derive(Debug, Clone)] +pub struct SimpleType(pub(crate) NormalizedSubExpr); + +/// A Dhall expression representing a (possibly higher-kinded) type. +/// +/// This includes [SimpleType]s but also higher-kinded expressions like +/// `Type`, `Kind` and `{ x: Type }`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Type(pub(crate) Box<Typed>); + +impl Parsed { + pub fn parse_file(f: &Path) -> Result<Parsed, Error> { + parse::parse_file(f) + } + + pub fn parse_str(s: &str) -> Result<Parsed, Error> { + parse::parse_str(s) + } + + #[allow(dead_code)] + pub fn parse_binary_file(f: &Path) -> Result<Parsed, Error> { + parse::parse_binary_file(f) + } + + pub fn resolve(self) -> Result<Resolved, ImportError> { + resolve::resolve(self) + } + + #[allow(dead_code)] + pub fn skip_resolve(self) -> Result<Resolved, ImportError> { + resolve::skip_resolve_expr(self) + } +} + +impl Resolved { + pub fn typecheck(self) -> Result<Typed, TypeError> { + typecheck::typecheck(self) + } + pub fn typecheck_with(self, ty: &Type) -> Result<Typed, TypeError> { + typecheck::typecheck_with(self, ty) + } + /// Pretends this expression has been typechecked. Use with care. + #[allow(dead_code)] + pub fn skip_typecheck(self) -> Typed { + typecheck::skip_typecheck(self) + } +} + +impl Typed { + /// Reduce an expression to its normal form, performing beta reduction + /// + /// `normalize` does not type-check the expression. You may want to type-check + /// expressions before normalizing them since normalization can convert an + /// ill-typed expression into a well-typed expression. + /// + /// However, `normalize` will not fail if the expression is ill-typed and will + /// leave ill-typed sub-expressions unevaluated. + pub fn normalize(self) -> Normalized { + match &self { + Typed::Const(_) => {} + Typed::Value(thunk, _) => { + thunk.normalize_nf(); + } + } + Normalized(self) + } + + pub(crate) fn from_thunk_and_type(th: Thunk, t: Type) -> Self { + Typed::Value(th, Some(t)) + } + pub(crate) fn from_thunk_untyped(th: Thunk) -> Self { + Typed::Value(th, None) + } + pub(crate) fn from_const(c: Const) -> Self { + Typed::Const(c) + } + + // TODO: Avoid cloning if possible + pub(crate) fn to_value(&self) -> Value { + match self { + Typed::Value(th, _) => th.to_value(), + Typed::Const(c) => Value::Const(*c), + } + } + pub(crate) fn to_expr(&self) -> NormalizedSubExpr { + self.to_value().normalize_to_expr() + } + pub(crate) fn to_expr_alpha(&self) -> NormalizedSubExpr { + self.to_value().normalize_to_expr_maybe_alpha(true) + } + pub(crate) fn to_thunk(&self) -> Thunk { + match self { + Typed::Value(th, _) => th.clone(), + Typed::Const(c) => Thunk::from_value(Value::Const(*c)), + } + } + // Deprecated + pub(crate) fn to_type(&self) -> Type { + self.clone().into_type() + } + pub(crate) fn into_type(self) -> Type { + Type(Box::new(self)) + } + + pub(crate) fn get_type(&self) -> Result<Cow<'_, Type>, TypeError> { + match self { + Typed::Value(_, Some(t)) => Ok(Cow::Borrowed(t)), + Typed::Value(_, None) => Err(TypeError::new( + &TypecheckContext::new(), + TypeMessage::Untyped, + )), + Typed::Const(c) => Ok(Cow::Owned(type_of_const(*c)?)), + } + } + + pub(crate) fn shift(&self, delta: isize, var: &AlphaVar) -> Self { + match self { + Typed::Value(th, t) => Typed::Value( + th.shift(delta, var), + t.as_ref().map(|x| x.shift(delta, var)), + ), + Typed::Const(c) => Typed::Const(*c), + } + } + + pub(crate) fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self { + match self { + Typed::Value(th, t) => Typed::Value( + th.subst_shift(var, val), + t.as_ref().map(|x| x.subst_shift(var, val)), + ), + Typed::Const(c) => Typed::Const(*c), + } + } +} + +impl Type { + pub(crate) fn to_normalized(&self) -> Normalized { + self.0.clone().normalize() + } + pub(crate) fn to_expr(&self) -> NormalizedSubExpr { + self.0.to_expr() + } + pub(crate) fn to_value(&self) -> Value { + self.0.to_value() + } + pub(crate) fn as_const(&self) -> Option<Const> { + // TODO: avoid clone + match &self.to_value() { + Value::Const(c) => Some(*c), + _ => None, + } + } + pub(crate) fn internal_whnf(&self) -> Option<Value> { + Some(self.to_value()) + } + pub(crate) fn get_type(&self) -> Result<Cow<'_, Type>, TypeError> { + self.0.get_type() + } + + pub(crate) fn const_sort() -> Self { + Type::from_const(Const::Sort) + } + pub(crate) fn const_kind() -> Self { + Type::from_const(Const::Kind) + } + pub(crate) fn const_type() -> Self { + Type::from_const(Const::Type) + } + pub(crate) fn from_const(c: Const) -> Self { + Type(Box::new(Typed::from_const(c))) + } + + pub(crate) fn shift(&self, delta: isize, var: &AlphaVar) -> Self { + Type(Box::new(self.0.shift(delta, var))) + } + pub(crate) fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self { + Type(Box::new(self.0.subst_shift(var, val))) + } +} + +impl Normalized { + pub(crate) fn from_thunk_and_type(th: Thunk, t: Type) -> Self { + Normalized(Typed::from_thunk_and_type(th, t)) + } + + pub(crate) fn to_expr(&self) -> NormalizedSubExpr { + self.0.to_expr() + } + #[allow(dead_code)] + pub(crate) fn to_expr_alpha(&self) -> NormalizedSubExpr { + self.0.to_expr_alpha() + } + pub(crate) fn to_value(&self) -> Value { + self.0.to_value() + } + pub(crate) fn to_thunk(&self) -> Thunk { + self.0.to_thunk() + } + pub(crate) fn to_type(self) -> Type { + self.0.to_type() + } + pub(crate) fn get_type(&self) -> Result<Cow<'_, Type>, TypeError> { + self.0.get_type() + } + + pub(crate) fn shift(&self, delta: isize, var: &AlphaVar) -> Self { + Normalized(self.0.shift(delta, var)) + } +} + +macro_rules! derive_traits_for_wrapper_struct { + ($ty:ident) => { + impl std::cmp::PartialEq for $ty { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } + } + + impl std::cmp::Eq for $ty {} + + impl std::fmt::Display for $ty { + fn fmt( + &self, + f: &mut std::fmt::Formatter, + ) -> Result<(), std::fmt::Error> { + self.0.fmt(f) + } + } + }; +} + +derive_traits_for_wrapper_struct!(Parsed); +derive_traits_for_wrapper_struct!(Resolved); +derive_traits_for_wrapper_struct!(Normalized); +derive_traits_for_wrapper_struct!(SimpleType); + +impl Eq for Typed {} +impl PartialEq for Typed { + fn eq(&self, other: &Self) -> bool { + self.to_value() == other.to_value() + } +} + +impl Display for Typed { + fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + self.to_expr().fmt(f) + } +} + +impl Display for Type { + fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + self.to_normalized().fmt(f) + } +} + +// Exposed for the macros +#[doc(hidden)] +impl From<SimpleType> for NormalizedSubExpr { + fn from(x: SimpleType) -> NormalizedSubExpr { + x.0 + } +} + +// Exposed for the macros +#[doc(hidden)] +impl From<NormalizedSubExpr> for SimpleType { + fn from(x: NormalizedSubExpr) -> SimpleType { + SimpleType(x) + } +} + +// Exposed for the macros +#[doc(hidden)] +impl From<Normalized> for Typed { + fn from(x: Normalized) -> Typed { + x.0 + } +} diff --git a/dhall/src/normalize.rs b/dhall/src/phase/normalize.rs index 35ab45d..5dfcfb6 100644 --- a/dhall/src/normalize.rs +++ b/dhall/src/phase/normalize.rs @@ -1,1025 +1,16 @@ -#![allow(non_snake_case)] use std::collections::BTreeMap; -use std::rc::Rc; -use dhall_proc_macros as dhall; -use dhall_syntax::context::Context; -use dhall_syntax::{ - rc, BinOp, Builtin, Const, ExprF, Integer, InterpolatedTextContents, Label, - Natural, Span, SubExpr, V, X, -}; +use dhall_syntax::{BinOp, Builtin, ExprF, InterpolatedTextContents, Label, X}; -use crate::expr::{Normalized, Type, Typed, TypedInternal}; +use crate::core::context::NormalizationContext; +use crate::core::thunk::{Thunk, TypeThunk}; +use crate::core::value::Value; +use crate::phase::{NormalizedSubExpr, ResolvedSubExpr, Typed}; -type InputSubExpr = SubExpr<Span, Normalized>; -type OutputSubExpr = SubExpr<X, X>; +pub(crate) type InputSubExpr = ResolvedSubExpr; +pub(crate) type OutputSubExpr = NormalizedSubExpr; -impl Typed { - /// Reduce an expression to its normal form, performing beta reduction - /// - /// `normalize` does not type-check the expression. You may want to type-check - /// expressions before normalizing them since normalization can convert an - /// ill-typed expression into a well-typed expression. - /// - /// However, `normalize` will not fail if the expression is ill-typed and will - /// leave ill-typed sub-expressions unevaluated. - /// - pub fn normalize(self) -> Normalized { - match &self.0 { - TypedInternal::Sort => {} - TypedInternal::Value(thunk, _) => { - thunk.normalize_nf(); - } - } - Normalized(self.0) - } - - pub(crate) fn shift(&self, delta: isize, var: &AlphaVar) -> Self { - Typed(self.0.shift(delta, var)) - } - - pub(crate) fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self { - Typed(self.0.subst_shift(var, val)) - } - - pub(crate) fn to_value(&self) -> Value { - self.0.to_value() - } - - pub(crate) fn to_thunk(&self) -> Thunk { - self.0.to_thunk() - } -} - -/// Stores a pair of variables: a normal one and if relevant one -/// that corresponds to the alpha-normalized version of the first one. -#[derive(Debug, Clone, Eq)] -pub(crate) struct AlphaVar { - normal: V<Label>, - alpha: Option<V<()>>, -} - -impl AlphaVar { - pub(crate) fn to_var(&self, alpha: bool) -> V<Label> { - match (alpha, &self.alpha) { - (true, Some(x)) => V("_".into(), x.1), - _ => self.normal.clone(), - } - } - pub(crate) fn from_var(normal: V<Label>) -> Self { - AlphaVar { - normal, - alpha: None, - } - } - pub(crate) fn shift(&self, delta: isize, var: &AlphaVar) -> Self { - AlphaVar { - normal: self.normal.shift(delta, &var.normal), - alpha: match (&self.alpha, &var.alpha) { - (Some(x), Some(v)) => Some(x.shift(delta, v)), - _ => None, - }, - } - } -} - -/// Equality is up to alpha-equivalence. -impl std::cmp::PartialEq for AlphaVar { - fn eq(&self, other: &Self) -> bool { - match (&self.alpha, &other.alpha) { - (Some(x), Some(y)) => x == y, - (None, None) => self.normal == other.normal, - _ => false, - } - } -} - -impl From<Label> for AlphaVar { - fn from(x: Label) -> AlphaVar { - AlphaVar { - normal: V(x, 0), - alpha: Some(V((), 0)), - } - } -} -impl<'a> From<&'a Label> for AlphaVar { - fn from(x: &'a Label) -> AlphaVar { - x.clone().into() - } -} -impl From<AlphaLabel> for AlphaVar { - fn from(x: AlphaLabel) -> AlphaVar { - let l: Label = x.into(); - l.into() - } -} -impl<'a> From<&'a AlphaLabel> for AlphaVar { - fn from(x: &'a AlphaLabel) -> AlphaVar { - x.clone().into() - } -} - -// Exactly like a Label, but equality returns always true. -// This is so that Value equality is exactly alpha-equivalence. -#[derive(Debug, Clone, Eq)] -pub(crate) struct AlphaLabel(Label); - -impl AlphaLabel { - fn to_label_maybe_alpha(&self, alpha: bool) -> Label { - if alpha { - "_".into() - } else { - self.to_label() - } - } - fn to_label(&self) -> Label { - self.clone().into() - } -} - -impl std::cmp::PartialEq for AlphaLabel { - fn eq(&self, _other: &Self) -> bool { - true - } -} - -impl From<Label> for AlphaLabel { - fn from(x: Label) -> AlphaLabel { - AlphaLabel(x) - } -} -impl From<AlphaLabel> for Label { - fn from(x: AlphaLabel) -> Label { - x.0 - } -} - -#[derive(Debug, Clone)] -enum EnvItem { - Thunk(Thunk), - Skip(AlphaVar), -} - -impl EnvItem { - fn shift(&self, delta: isize, var: &AlphaVar) -> Self { - use EnvItem::*; - match self { - Thunk(e) => Thunk(e.shift(delta, var)), - Skip(v) => Skip(v.shift(delta, var)), - } - } - - pub(crate) fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self { - match self { - EnvItem::Thunk(e) => EnvItem::Thunk(e.subst_shift(var, val)), - EnvItem::Skip(v) if v == var => EnvItem::Thunk(val.to_thunk()), - EnvItem::Skip(v) => EnvItem::Skip(v.shift(-1, var)), - } - } -} - -#[derive(Debug, Clone)] -pub(crate) struct NormalizationContext(Rc<Context<Label, EnvItem>>); - -impl NormalizationContext { - pub(crate) fn new() -> Self { - NormalizationContext(Rc::new(Context::new())) - } - fn skip(&self, x: &Label) -> Self { - NormalizationContext(Rc::new( - self.0 - .map(|_, e| e.shift(1, &x.into())) - .insert(x.clone(), EnvItem::Skip(x.into())), - )) - } - fn lookup(&self, var: &V<Label>) -> Value { - let V(x, n) = var; - match self.0.lookup(x, *n) { - Some(EnvItem::Thunk(t)) => t.to_value(), - Some(EnvItem::Skip(newvar)) => Value::Var(newvar.clone()), - // Free variable - None => Value::Var(AlphaVar::from_var(var.clone())), - } - } - pub(crate) fn from_typecheck_ctx( - tc_ctx: &crate::typecheck::TypecheckContext, - ) -> Self { - use crate::typecheck::EnvItem::*; - let mut ctx = Context::new(); - for (k, vs) in tc_ctx.0.iter_keys() { - for v in vs.iter() { - let new_item = match v { - Type(var, _) => EnvItem::Skip(var.clone()), - Value(e) => EnvItem::Thunk(e.to_thunk()), - }; - ctx = ctx.insert(k.clone(), new_item); - } - } - NormalizationContext(Rc::new(ctx)) - } - - fn shift(&self, delta: isize, var: &AlphaVar) -> Self { - NormalizationContext(Rc::new(self.0.map(|_, e| e.shift(delta, var)))) - } - - fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self { - NormalizationContext(Rc::new( - self.0.map(|_, e| e.subst_shift(var, val)), - )) - } -} - -/// A semantic value. -/// Equality is up to alpha-equivalence (renaming of bound variables). -#[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) enum Value { - /// Closures - Lam(AlphaLabel, Thunk, Thunk), - AppliedBuiltin(Builtin, Vec<Thunk>), - /// `λ(x: a) -> Some x` - OptionalSomeClosure(TypeThunk), - /// `λ(x : a) -> λ(xs : List a) -> [ x ] # xs` - /// `λ(xs : List a) -> [ x ] # xs` - ListConsClosure(TypeThunk, Option<Thunk>), - /// `λ(x : Natural) -> x + 1` - NaturalSuccClosure, - Pi(AlphaLabel, TypeThunk, TypeThunk), - - Var(AlphaVar), - Const(Const), - BoolLit(bool), - NaturalLit(Natural), - IntegerLit(Integer), - EmptyOptionalLit(TypeThunk), - NEOptionalLit(Thunk), - EmptyListLit(TypeThunk), - NEListLit(Vec<Thunk>), - RecordLit(BTreeMap<Label, Thunk>), - RecordType(BTreeMap<Label, TypeThunk>), - UnionType(BTreeMap<Label, Option<TypeThunk>>), - UnionConstructor(Label, BTreeMap<Label, Option<TypeThunk>>), - UnionLit(Label, Thunk, BTreeMap<Label, Option<TypeThunk>>), - TextLit(Vec<InterpolatedTextContents<Thunk>>), - /// Invariant: This must not contain a value captured by one of the variants above. - PartialExpr(ExprF<Thunk, Label, X>), -} - -impl Value { - pub(crate) fn into_thunk(self) -> Thunk { - Thunk::from_value(self) - } - - /// Convert the value to a fully normalized syntactic expression - pub(crate) fn normalize_to_expr(&self) -> OutputSubExpr { - self.normalize_to_expr_maybe_alpha(false) - } - /// Convert the value to a fully normalized syntactic expression. Also alpha-normalize - /// if alpha is `true` - pub(crate) fn normalize_to_expr_maybe_alpha( - &self, - alpha: bool, - ) -> OutputSubExpr { - match self { - Value::Lam(x, t, e) => rc(ExprF::Lam( - x.to_label_maybe_alpha(alpha), - t.normalize_to_expr_maybe_alpha(alpha), - e.normalize_to_expr_maybe_alpha(alpha), - )), - Value::AppliedBuiltin(b, args) => { - let mut e = rc(ExprF::Builtin(*b)); - for v in args { - e = rc(ExprF::App( - e, - v.normalize_to_expr_maybe_alpha(alpha), - )); - } - e - } - Value::OptionalSomeClosure(n) => { - let a = n.normalize_to_expr_maybe_alpha(alpha); - dhall::subexpr!(λ(x: a) -> Some x) - } - Value::ListConsClosure(a, None) => { - // Avoid accidental capture of the new `x` variable - let a1 = a.shift(1, &Label::from("x").into()); - let a1 = a1.normalize_to_expr_maybe_alpha(alpha); - let a = a.normalize_to_expr_maybe_alpha(alpha); - dhall::subexpr!(λ(x : a) -> λ(xs : List a1) -> [ x ] # xs) - } - Value::ListConsClosure(n, Some(v)) => { - // Avoid accidental capture of the new `xs` variable - let v = v.shift(1, &Label::from("xs").into()); - let v = v.normalize_to_expr_maybe_alpha(alpha); - let a = n.normalize_to_expr_maybe_alpha(alpha); - dhall::subexpr!(λ(xs : List a) -> [ v ] # xs) - } - Value::NaturalSuccClosure => { - dhall::subexpr!(λ(x : Natural) -> x + 1) - } - Value::Pi(x, t, e) => rc(ExprF::Pi( - x.to_label_maybe_alpha(alpha), - t.normalize_to_expr_maybe_alpha(alpha), - e.normalize_to_expr_maybe_alpha(alpha), - )), - Value::Var(v) => rc(ExprF::Var(v.to_var(alpha))), - Value::Const(c) => rc(ExprF::Const(*c)), - Value::BoolLit(b) => rc(ExprF::BoolLit(*b)), - Value::NaturalLit(n) => rc(ExprF::NaturalLit(*n)), - Value::IntegerLit(n) => rc(ExprF::IntegerLit(*n)), - Value::EmptyOptionalLit(n) => rc(ExprF::App( - rc(ExprF::Builtin(Builtin::OptionalNone)), - n.normalize_to_expr_maybe_alpha(alpha), - )), - Value::NEOptionalLit(n) => { - rc(ExprF::SomeLit(n.normalize_to_expr_maybe_alpha(alpha))) - } - Value::EmptyListLit(n) => { - rc(ExprF::EmptyListLit(n.normalize_to_expr_maybe_alpha(alpha))) - } - Value::NEListLit(elts) => rc(ExprF::NEListLit( - elts.into_iter() - .map(|n| n.normalize_to_expr_maybe_alpha(alpha)) - .collect(), - )), - Value::RecordLit(kvs) => rc(ExprF::RecordLit( - kvs.iter() - .map(|(k, v)| { - (k.clone(), v.normalize_to_expr_maybe_alpha(alpha)) - }) - .collect(), - )), - Value::RecordType(kts) => rc(ExprF::RecordType( - kts.iter() - .map(|(k, v)| { - (k.clone(), v.normalize_to_expr_maybe_alpha(alpha)) - }) - .collect(), - )), - Value::UnionType(kts) => rc(ExprF::UnionType( - kts.iter() - .map(|(k, v)| { - ( - k.clone(), - v.as_ref().map(|v| { - v.normalize_to_expr_maybe_alpha(alpha) - }), - ) - }) - .collect(), - )), - Value::UnionConstructor(l, kts) => { - let kts = kts - .iter() - .map(|(k, v)| { - ( - k.clone(), - v.as_ref().map(|v| { - v.normalize_to_expr_maybe_alpha(alpha) - }), - ) - }) - .collect(); - rc(ExprF::Field(rc(ExprF::UnionType(kts)), l.clone())) - } - Value::UnionLit(l, v, kts) => rc(ExprF::UnionLit( - l.clone(), - v.normalize_to_expr_maybe_alpha(alpha), - kts.iter() - .map(|(k, v)| { - ( - k.clone(), - v.as_ref().map(|v| { - v.normalize_to_expr_maybe_alpha(alpha) - }), - ) - }) - .collect(), - )), - Value::TextLit(elts) => { - use InterpolatedTextContents::{Expr, Text}; - rc(ExprF::TextLit( - elts.iter() - .map(|contents| match contents { - Expr(e) => { - Expr(e.normalize_to_expr_maybe_alpha(alpha)) - } - Text(s) => Text(s.clone()), - }) - .collect(), - )) - } - Value::PartialExpr(e) => { - rc(e.map_ref_simple(|v| v.normalize_to_expr_maybe_alpha(alpha))) - } - } - } - - // Deprecated - fn normalize(&self) -> Value { - let mut v = self.clone(); - v.normalize_mut(); - v - } - - pub(crate) fn normalize_mut(&mut self) { - match self { - Value::NaturalSuccClosure - | Value::Var(_) - | Value::Const(_) - | Value::BoolLit(_) - | Value::NaturalLit(_) - | Value::IntegerLit(_) => {} - - Value::EmptyOptionalLit(tth) - | Value::OptionalSomeClosure(tth) - | Value::EmptyListLit(tth) => { - tth.normalize_mut(); - } - - Value::NEOptionalLit(th) => { - th.normalize_mut(); - } - Value::Lam(_, t, e) => { - t.normalize_mut(); - e.normalize_mut(); - } - Value::Pi(_, t, e) => { - t.normalize_mut(); - e.normalize_mut(); - } - Value::AppliedBuiltin(_, args) => { - for x in args.iter_mut() { - x.normalize_mut(); - } - } - Value::ListConsClosure(t, v) => { - t.normalize_mut(); - for x in v.iter_mut() { - x.normalize_mut(); - } - } - Value::NEListLit(elts) => { - for x in elts.iter_mut() { - x.normalize_mut(); - } - } - Value::RecordLit(kvs) => { - for x in kvs.values_mut() { - x.normalize_mut(); - } - } - Value::RecordType(kvs) => { - for x in kvs.values_mut() { - x.normalize_mut(); - } - } - Value::UnionType(kts) | Value::UnionConstructor(_, kts) => { - for x in kts.values_mut().flat_map(|opt| opt) { - x.normalize_mut(); - } - } - Value::UnionLit(_, v, kts) => { - v.normalize_mut(); - for x in kts.values_mut().flat_map(|opt| opt) { - x.normalize_mut(); - } - } - Value::TextLit(elts) => { - for x in elts.iter_mut() { - use InterpolatedTextContents::{Expr, Text}; - match x { - Expr(n) => n.normalize_mut(), - Text(_) => {} - } - } - } - Value::PartialExpr(e) => { - // TODO: need map_mut_simple - e.map_ref_simple(|v| { - v.normalize_nf(); - }); - } - } - } - - /// Apply to a value - pub(crate) fn app(self, val: Value) -> Value { - self.app_val(val) - } - - /// Apply to a value - pub(crate) fn app_val(self, val: Value) -> Value { - self.app_thunk(val.into_thunk()) - } - - /// Apply to a thunk - pub(crate) fn app_thunk(self, th: Thunk) -> Value { - Thunk::from_value(self).app_thunk(th) - } - - pub(crate) fn from_builtin(b: Builtin) -> Value { - Value::AppliedBuiltin(b, vec![]) - } - - fn shift(&self, delta: isize, var: &AlphaVar) -> Self { - match self { - Value::Lam(x, t, e) => Value::Lam( - x.clone(), - t.shift(delta, var), - e.shift(delta, &var.shift(1, &x.into())), - ), - Value::AppliedBuiltin(b, args) => Value::AppliedBuiltin( - *b, - args.iter().map(|v| v.shift(delta, var)).collect(), - ), - Value::NaturalSuccClosure => Value::NaturalSuccClosure, - Value::OptionalSomeClosure(tth) => { - Value::OptionalSomeClosure(tth.shift(delta, var)) - } - Value::ListConsClosure(t, v) => Value::ListConsClosure( - t.shift(delta, var), - v.as_ref().map(|v| v.shift(delta, var)), - ), - Value::Pi(x, t, e) => Value::Pi( - x.clone(), - t.shift(delta, var), - e.shift(delta, &var.shift(1, &x.into())), - ), - Value::Var(v) => Value::Var(v.shift(delta, var)), - Value::Const(c) => Value::Const(*c), - Value::BoolLit(b) => Value::BoolLit(*b), - Value::NaturalLit(n) => Value::NaturalLit(*n), - Value::IntegerLit(n) => Value::IntegerLit(*n), - Value::EmptyOptionalLit(tth) => { - Value::EmptyOptionalLit(tth.shift(delta, var)) - } - Value::NEOptionalLit(th) => { - Value::NEOptionalLit(th.shift(delta, var)) - } - Value::EmptyListLit(tth) => { - Value::EmptyListLit(tth.shift(delta, var)) - } - Value::NEListLit(elts) => Value::NEListLit( - elts.iter().map(|v| v.shift(delta, var)).collect(), - ), - Value::RecordLit(kvs) => Value::RecordLit( - kvs.iter() - .map(|(k, v)| (k.clone(), v.shift(delta, var))) - .collect(), - ), - Value::RecordType(kvs) => Value::RecordType( - kvs.iter() - .map(|(k, v)| (k.clone(), v.shift(delta, var))) - .collect(), - ), - Value::UnionType(kts) => Value::UnionType( - kts.iter() - .map(|(k, v)| { - (k.clone(), v.as_ref().map(|v| v.shift(delta, var))) - }) - .collect(), - ), - Value::UnionConstructor(x, kts) => Value::UnionConstructor( - x.clone(), - kts.iter() - .map(|(k, v)| { - (k.clone(), v.as_ref().map(|v| v.shift(delta, var))) - }) - .collect(), - ), - Value::UnionLit(x, v, kts) => Value::UnionLit( - x.clone(), - v.shift(delta, var), - kts.iter() - .map(|(k, v)| { - (k.clone(), v.as_ref().map(|v| v.shift(delta, var))) - }) - .collect(), - ), - Value::TextLit(elts) => Value::TextLit( - elts.iter() - .map(|contents| { - use InterpolatedTextContents::{Expr, Text}; - match contents { - Expr(th) => Expr(th.shift(delta, var)), - Text(s) => Text(s.clone()), - } - }) - .collect(), - ), - Value::PartialExpr(e) => { - Value::PartialExpr(e.map_ref_with_special_handling_of_binders( - |v| v.shift(delta, var), - |x, v| v.shift(delta, &var.shift(1, &x.into())), - X::clone, - Label::clone, - )) - } - } - } - - pub(crate) fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self { - match self { - // Retry normalizing since substituting may allow progress - Value::AppliedBuiltin(b, args) => apply_builtin( - *b, - args.iter().map(|v| v.subst_shift(var, val)).collect(), - ), - // Retry normalizing since substituting may allow progress - Value::PartialExpr(e) => { - normalize_one_layer(e.map_ref_with_special_handling_of_binders( - |v| v.subst_shift(var, val), - |x, v| { - v.subst_shift( - &var.shift(1, &x.into()), - &val.shift(1, &x.into()), - ) - }, - X::clone, - Label::clone, - )) - } - // Retry normalizing since substituting may allow progress - Value::TextLit(elts) => { - Value::TextLit(squash_textlit(elts.iter().map(|contents| { - use InterpolatedTextContents::{Expr, Text}; - match contents { - Expr(th) => Expr(th.subst_shift(var, val)), - Text(s) => Text(s.clone()), - } - }))) - } - Value::Lam(x, t, e) => Value::Lam( - x.clone(), - t.subst_shift(var, val), - e.subst_shift( - &var.shift(1, &x.into()), - &val.shift(1, &x.into()), - ), - ), - Value::NaturalSuccClosure => Value::NaturalSuccClosure, - Value::OptionalSomeClosure(tth) => { - Value::OptionalSomeClosure(tth.subst_shift(var, val)) - } - Value::ListConsClosure(t, v) => Value::ListConsClosure( - t.subst_shift(var, val), - v.as_ref().map(|v| v.subst_shift(var, val)), - ), - Value::Pi(x, t, e) => Value::Pi( - x.clone(), - t.subst_shift(var, val), - e.subst_shift( - &var.shift(1, &x.into()), - &val.shift(1, &x.into()), - ), - ), - Value::Var(v) if v == var => val.to_value().clone(), - Value::Var(v) => Value::Var(v.shift(-1, var)), - Value::Const(c) => Value::Const(*c), - Value::BoolLit(b) => Value::BoolLit(*b), - Value::NaturalLit(n) => Value::NaturalLit(*n), - Value::IntegerLit(n) => Value::IntegerLit(*n), - Value::EmptyOptionalLit(tth) => { - Value::EmptyOptionalLit(tth.subst_shift(var, val)) - } - Value::NEOptionalLit(th) => { - Value::NEOptionalLit(th.subst_shift(var, val)) - } - Value::EmptyListLit(tth) => { - Value::EmptyListLit(tth.subst_shift(var, val)) - } - Value::NEListLit(elts) => Value::NEListLit( - elts.iter().map(|v| v.subst_shift(var, val)).collect(), - ), - Value::RecordLit(kvs) => Value::RecordLit( - kvs.iter() - .map(|(k, v)| (k.clone(), v.subst_shift(var, val))) - .collect(), - ), - Value::RecordType(kvs) => Value::RecordType( - kvs.iter() - .map(|(k, v)| (k.clone(), v.subst_shift(var, val))) - .collect(), - ), - Value::UnionType(kts) => Value::UnionType( - kts.iter() - .map(|(k, v)| { - (k.clone(), v.as_ref().map(|v| v.subst_shift(var, val))) - }) - .collect(), - ), - Value::UnionConstructor(x, kts) => Value::UnionConstructor( - x.clone(), - kts.iter() - .map(|(k, v)| { - (k.clone(), v.as_ref().map(|v| v.subst_shift(var, val))) - }) - .collect(), - ), - Value::UnionLit(x, v, kts) => Value::UnionLit( - x.clone(), - v.subst_shift(var, val), - kts.iter() - .map(|(k, v)| { - (k.clone(), v.as_ref().map(|v| v.subst_shift(var, val))) - }) - .collect(), - ), - } - } -} - -mod thunk { - use super::{ - apply_any, normalize_whnf, AlphaVar, InputSubExpr, - NormalizationContext, OutputSubExpr, Value, - }; - use crate::expr::Typed; - use std::cell::{Ref, RefCell}; - use std::rc::Rc; - - #[derive(Debug, Clone, Copy)] - enum Marker { - /// Weak Head Normal Form, i.e. subexpressions may not be normalized - WHNF, - /// Normal form, i.e. completely normalized - NF, - } - use Marker::{NF, WHNF}; - - #[derive(Debug)] - enum ThunkInternal { - /// Non-normalized value alongside a normalization context - Unnormalized(NormalizationContext, InputSubExpr), - /// Partially normalized value. - /// Invariant: if the marker is `NF`, the value must be fully normalized - Value(Marker, Value), - } - - /// Stores a possibl unevaluated value. Uses RefCell to ensure that - /// the value gets normalized at most once. - #[derive(Debug, Clone)] - pub struct Thunk(Rc<RefCell<ThunkInternal>>); - - impl ThunkInternal { - fn into_thunk(self) -> Thunk { - Thunk(Rc::new(RefCell::new(self))) - } - - fn normalize_whnf(&mut self) { - match self { - ThunkInternal::Unnormalized(ctx, e) => { - *self = ThunkInternal::Value( - WHNF, - normalize_whnf(ctx.clone(), e.clone()), - ) - } - // Already at least in WHNF - ThunkInternal::Value(_, _) => {} - } - } - - fn normalize_nf(&mut self) { - match self { - ThunkInternal::Unnormalized(_, _) => { - self.normalize_whnf(); - self.normalize_nf(); - } - ThunkInternal::Value(m @ WHNF, v) => { - v.normalize_mut(); - *m = NF; - } - // Already in NF - ThunkInternal::Value(NF, _) => {} - } - } - - // Always use normalize_whnf before - fn as_whnf(&self) -> &Value { - match self { - ThunkInternal::Unnormalized(_, _) => unreachable!(), - ThunkInternal::Value(_, v) => v, - } - } - - // Always use normalize_nf before - fn as_nf(&self) -> &Value { - match self { - ThunkInternal::Unnormalized(_, _) => unreachable!(), - ThunkInternal::Value(WHNF, _) => unreachable!(), - ThunkInternal::Value(NF, v) => v, - } - } - - fn shift(&self, delta: isize, var: &AlphaVar) -> Self { - match self { - ThunkInternal::Unnormalized(ctx, e) => { - ThunkInternal::Unnormalized( - ctx.shift(delta, var), - e.clone(), - ) - } - ThunkInternal::Value(m, v) => { - ThunkInternal::Value(*m, v.shift(delta, var)) - } - } - } - - fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self { - match self { - ThunkInternal::Unnormalized(ctx, e) => { - ThunkInternal::Unnormalized( - ctx.subst_shift(var, val), - e.clone(), - ) - } - ThunkInternal::Value(_, v) => { - // The resulting value may not stay in normal form after substitution - ThunkInternal::Value(WHNF, v.subst_shift(var, val)) - } - } - } - } - - impl Thunk { - pub(crate) fn new(ctx: NormalizationContext, e: InputSubExpr) -> Thunk { - ThunkInternal::Unnormalized(ctx, e).into_thunk() - } - - pub(crate) fn from_value(v: Value) -> Thunk { - ThunkInternal::Value(WHNF, v).into_thunk() - } - - pub(crate) fn from_normalized_expr(e: OutputSubExpr) -> Thunk { - Thunk::new( - NormalizationContext::new(), - e.embed_absurd().note_absurd(), - ) - } - - // Normalizes contents to normal form; faster than `normalize_nf` if - // no one else shares this thunk - pub(crate) fn normalize_mut(&mut self) { - match Rc::get_mut(&mut self.0) { - // Mutate directly if sole owner - Some(refcell) => RefCell::get_mut(refcell).normalize_nf(), - // Otherwise mutate through the refcell - None => self.0.borrow_mut().normalize_nf(), - } - } - - fn do_normalize_whnf(&self) { - let borrow = self.0.borrow(); - match &*borrow { - ThunkInternal::Unnormalized(_, _) => { - drop(borrow); - self.0.borrow_mut().normalize_whnf(); - } - // Already at least in WHNF - ThunkInternal::Value(_, _) => {} - } - } - - fn do_normalize_nf(&self) { - let borrow = self.0.borrow(); - match &*borrow { - ThunkInternal::Unnormalized(_, _) - | ThunkInternal::Value(WHNF, _) => { - drop(borrow); - self.0.borrow_mut().normalize_nf(); - } - // Already in NF - ThunkInternal::Value(NF, _) => {} - } - } - - // WARNING: avoid normalizing any thunk while holding on to this ref - // or you could run into BorrowMut panics - pub(crate) fn normalize_nf(&self) -> Ref<Value> { - self.do_normalize_nf(); - Ref::map(self.0.borrow(), ThunkInternal::as_nf) - } - - // WARNING: avoid normalizing any thunk while holding on to this ref - // or you could run into BorrowMut panics - pub(crate) fn as_value(&self) -> Ref<Value> { - self.do_normalize_whnf(); - Ref::map(self.0.borrow(), ThunkInternal::as_whnf) - } - - pub(crate) fn to_value(&self) -> Value { - self.as_value().clone() - } - - pub(crate) fn normalize_to_expr(&self) -> OutputSubExpr { - self.normalize_to_expr_maybe_alpha(false) - } - - pub(crate) fn normalize_to_expr_maybe_alpha( - &self, - alpha: bool, - ) -> OutputSubExpr { - self.normalize_nf().normalize_to_expr_maybe_alpha(alpha) - } - - pub(crate) fn app_val(&self, val: Value) -> Value { - self.app_thunk(val.into_thunk()) - } - - pub(crate) fn app_thunk(&self, th: Thunk) -> Value { - apply_any(self.clone(), th) - } - - pub(crate) fn shift(&self, delta: isize, var: &AlphaVar) -> Self { - self.0.borrow().shift(delta, var).into_thunk() - } - - pub(crate) fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self { - self.0.borrow().subst_shift(var, val).into_thunk() - } - } - - impl std::cmp::PartialEq for Thunk { - fn eq(&self, other: &Self) -> bool { - &*self.as_value() == &*other.as_value() - } - } - impl std::cmp::Eq for Thunk {} -} - -pub(crate) use thunk::Thunk; - -/// A thunk in type position. -#[derive(Debug, Clone)] -pub(crate) enum TypeThunk { - Thunk(Thunk), - Type(Type), -} - -impl TypeThunk { - fn from_value(v: Value) -> TypeThunk { - TypeThunk::from_thunk(Thunk::from_value(v)) - } - - fn from_thunk(th: Thunk) -> TypeThunk { - TypeThunk::Thunk(th) - } - - pub(crate) fn from_type(t: Type) -> TypeThunk { - TypeThunk::Type(t) - } - - fn normalize_mut(&mut self) { - match self { - TypeThunk::Thunk(th) => th.normalize_mut(), - TypeThunk::Type(_) => {} - } - } - - pub(crate) fn normalize_nf(&self) -> Value { - match self { - TypeThunk::Thunk(th) => th.normalize_nf().clone(), - TypeThunk::Type(t) => t.to_value().normalize(), - } - } - - pub(crate) fn to_value(&self) -> Value { - match self { - TypeThunk::Thunk(th) => th.to_value(), - TypeThunk::Type(t) => t.to_value(), - } - } - - pub(crate) fn normalize_to_expr_maybe_alpha( - &self, - alpha: bool, - ) -> OutputSubExpr { - self.normalize_nf().normalize_to_expr_maybe_alpha(alpha) - } - - fn shift(&self, delta: isize, var: &AlphaVar) -> Self { - match self { - TypeThunk::Thunk(th) => TypeThunk::Thunk(th.shift(delta, var)), - TypeThunk::Type(t) => TypeThunk::Type(t.shift(delta, var)), - } - } - - pub(crate) fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self { - match self { - TypeThunk::Thunk(th) => TypeThunk::Thunk(th.subst_shift(var, val)), - TypeThunk::Type(t) => TypeThunk::Type(t.subst_shift(var, val)), - } - } -} - -impl std::cmp::PartialEq for TypeThunk { - fn eq(&self, other: &Self) -> bool { - self.to_value() == other.to_value() - } -} -impl std::cmp::Eq for TypeThunk {} - -fn apply_builtin(b: Builtin, args: Vec<Thunk>) -> Value { +pub(crate) fn apply_builtin(b: Builtin, args: Vec<Thunk>) -> Value { use dhall_syntax::Builtin::*; use Value::*; @@ -1207,7 +198,7 @@ fn apply_builtin(b: Builtin, args: Vec<Thunk>) -> Value { } } -fn apply_any(f: Thunk, a: Thunk) -> Value { +pub(crate) fn apply_any(f: Thunk, a: Thunk) -> Value { let fallback = |f: Thunk, a: Thunk| Value::PartialExpr(ExprF::App(f, a)); let f_borrow = f.as_value(); @@ -1263,7 +254,7 @@ fn apply_any(f: Thunk, a: Thunk) -> Value { } } -fn squash_textlit( +pub(crate) fn squash_textlit( elts: impl Iterator<Item = InterpolatedTextContents<Thunk>>, ) -> Vec<InterpolatedTextContents<Thunk>> { use std::mem::replace; @@ -1306,7 +297,10 @@ fn squash_textlit( } /// Reduces the imput expression to a Value. Evaluates as little as possible. -fn normalize_whnf(ctx: NormalizationContext, expr: InputSubExpr) -> Value { +pub(crate) fn normalize_whnf( + ctx: NormalizationContext, + expr: InputSubExpr, +) -> Value { match expr.as_ref() { ExprF::Embed(e) => return e.to_value(), ExprF::Var(v) => return ctx.lookup(v), @@ -1325,7 +319,7 @@ fn normalize_whnf(ctx: NormalizationContext, expr: InputSubExpr) -> Value { normalize_one_layer(expr) } -fn normalize_one_layer(expr: ExprF<Thunk, Label, X>) -> Value { +pub(crate) fn normalize_one_layer(expr: ExprF<Thunk, Label, X>) -> Value { use Value::{ BoolLit, EmptyListLit, EmptyOptionalLit, IntegerLit, Lam, NEListLit, NEOptionalLit, NaturalLit, Pi, RecordLit, RecordType, TextLit, @@ -1345,7 +339,9 @@ fn normalize_one_layer(expr: ExprF<Thunk, Label, X>) -> Value { ExprF::Embed(_) => unreachable!(), ExprF::Var(_) => unreachable!(), ExprF::Annot(x, _) => RetThunk(x), - ExprF::Lam(x, t, e) => RetValue(Lam(x.into(), t, e)), + ExprF::Lam(x, t, e) => { + RetValue(Lam(x.into(), TypeThunk::from_thunk(t), e)) + } ExprF::Pi(x, t, e) => RetValue(Pi( x.into(), TypeThunk::from_thunk(t), diff --git a/dhall/src/phase/parse.rs b/dhall/src/phase/parse.rs new file mode 100644 index 0000000..6b426af --- /dev/null +++ b/dhall/src/phase/parse.rs @@ -0,0 +1,38 @@ +use std::fs::File; +use std::io::Read; +use std::path::Path; + +use dhall_syntax::parse_expr; + +use crate::error::Error; +use crate::phase::resolve::ImportRoot; +use crate::phase::Parsed; + +pub(crate) fn parse_file(f: &Path) -> Result<Parsed, Error> { + let mut buffer = String::new(); + File::open(f)?.read_to_string(&mut buffer)?; + let expr = parse_expr(&*buffer)?; + let root = ImportRoot::LocalDir(f.parent().unwrap().to_owned()); + Ok(Parsed(expr, root)) +} + +pub(crate) fn parse_str(s: &str) -> Result<Parsed, Error> { + let expr = parse_expr(s)?; + let root = ImportRoot::LocalDir(std::env::current_dir()?); + Ok(Parsed(expr, root)) +} + +pub(crate) fn parse_binary_file(f: &Path) -> Result<Parsed, Error> { + let mut buffer = Vec::new(); + File::open(f)?.read_to_end(&mut buffer)?; + let expr = crate::phase::binary::decode(&buffer)?; + let root = ImportRoot::LocalDir(f.parent().unwrap().to_owned()); + Ok(Parsed(expr.note_absurd(), root)) +} + +#[cfg(test)] +mod spec_tests { + #![rustfmt::skip] + // See build.rs + include!(concat!(env!("OUT_DIR"), "/parser_tests.rs")); +} diff --git a/dhall/src/imports.rs b/dhall/src/phase/resolve.rs index 87642a2..5ab03ac 100644 --- a/dhall/src/imports.rs +++ b/dhall/src/phase/resolve.rs @@ -1,18 +1,10 @@ -use crate::error::Error; -use crate::expr::*; -use dhall_syntax::*; use std::collections::HashMap; -use std::fs::File; -use std::io::Read; -use std::path::Path; -use std::path::PathBuf; - -#[derive(Debug)] -pub enum ImportError { - Recursive(Import, Box<Error>), - UnexpectedImport(Import), - ImportCycle(ImportStack, Import), -} +use std::path::{Path, PathBuf}; + +use dhall_syntax::Import; + +use crate::error::{Error, ImportError}; +use crate::phase::{Normalized, Parsed, Resolved}; /// A root from which to resolve relative imports. #[derive(Debug, Clone, PartialEq, Eq)] @@ -22,7 +14,7 @@ pub enum ImportRoot { type ImportCache = HashMap<Import, Normalized>; -type ImportStack = Vec<Import>; +pub(crate) type ImportStack = Vec<Import>; fn resolve_import( import: &Import, @@ -97,7 +89,11 @@ fn do_resolve_expr( Ok(Resolved(expr)) } -fn skip_resolve_expr( +pub(crate) fn resolve(e: Parsed) -> Result<Resolved, ImportError> { + do_resolve_expr(e, &mut HashMap::new(), &Vec::new()) +} + +pub(crate) fn skip_resolve_expr( Parsed(expr, _root): Parsed, ) -> Result<Resolved, ImportError> { let resolve = |import: &Import| -> Result<Normalized, ImportError> { @@ -107,40 +103,6 @@ fn skip_resolve_expr( Ok(Resolved(expr)) } -impl Parsed { - pub fn parse_file(f: &Path) -> Result<Parsed, Error> { - let mut buffer = String::new(); - File::open(f)?.read_to_string(&mut buffer)?; - let expr = parse_expr(&*buffer)?; - let root = ImportRoot::LocalDir(f.parent().unwrap().to_owned()); - Ok(Parsed(expr.unnote().note_absurd(), root)) - } - - pub fn parse_str(s: &str) -> Result<Parsed, Error> { - let expr = parse_expr(s)?; - let root = ImportRoot::LocalDir(std::env::current_dir()?); - Ok(Parsed(expr, root)) - } - - #[allow(dead_code)] - pub fn parse_binary_file(f: &Path) -> Result<Parsed, Error> { - let mut buffer = Vec::new(); - File::open(f)?.read_to_end(&mut buffer)?; - let expr = crate::binary::decode(&buffer)?; - let root = ImportRoot::LocalDir(f.parent().unwrap().to_owned()); - Ok(Parsed(expr.note_absurd(), root)) - } - - pub fn resolve(self) -> Result<Resolved, ImportError> { - crate::imports::do_resolve_expr(self, &mut HashMap::new(), &Vec::new()) - } - - #[allow(dead_code)] - pub fn skip_resolve(self) -> Result<Resolved, ImportError> { - crate::imports::skip_resolve_expr(self) - } -} - #[cfg(test)] mod spec_tests { #![rustfmt::skip] diff --git a/dhall/src/typecheck.rs b/dhall/src/phase/typecheck.rs index 8d6b6eb..32c1531 100644 --- a/dhall/src/typecheck.rs +++ b/dhall/src/phase/typecheck.rs @@ -1,361 +1,18 @@ #![allow(non_snake_case)] use std::borrow::Borrow; -use std::borrow::Cow; use std::collections::BTreeMap; -use std::fmt; -use crate::expr::*; -use crate::normalize::{ - AlphaVar, NormalizationContext, Thunk, TypeThunk, Value, -}; -use crate::traits::DynamicType; use dhall_proc_macros as dhall; -use dhall_syntax; -use dhall_syntax::context::Context; -use dhall_syntax::*; - -use self::TypeMessage::*; - -impl Resolved { - pub fn typecheck(self) -> Result<Typed, TypeError> { - type_of(self.0) - } - pub fn typecheck_with(self, ty: &Type) -> Result<Typed, TypeError> { - let expr: SubExpr<_, _> = self.0; - let ty: SubExpr<_, _> = ty.to_expr().embed_absurd().note_absurd(); - type_of(expr.rewrap(ExprF::Annot(expr.clone(), ty))) - } - /// Pretends this expression has been typechecked. Use with care. - #[allow(dead_code)] - pub fn skip_typecheck(self) -> Typed { - Typed::from_thunk_untyped(Thunk::new( - NormalizationContext::new(), - self.0, - )) - } -} - -impl Typed { - fn to_type(&self) -> Type { - match &self.to_value() { - Value::Const(c) => Type(TypeInternal::Const(*c)), - _ => Type(TypeInternal::Typed(Box::new(self.clone()))), - } - } -} - -impl Normalized { - fn shift(&self, delta: isize, var: &AlphaVar) -> Self { - Normalized(self.0.shift(delta, var)) - } - pub(crate) fn to_type(self) -> Type { - self.0.to_type() - } -} - -impl Type { - pub(crate) fn to_normalized(&self) -> Normalized { - self.0.to_normalized() - } - pub(crate) fn to_expr(&self) -> SubExpr<X, X> { - self.0.to_expr() - } - pub(crate) fn to_value(&self) -> Value { - self.0.to_value() - } - pub(crate) fn get_type(&self) -> Result<Cow<'_, Type>, TypeError> { - self.0.get_type() - } - fn as_const(&self) -> Option<Const> { - self.0.as_const() - } - fn internal_whnf(&self) -> Option<Value> { - self.0.whnf() - } - pub(crate) fn shift(&self, delta: isize, var: &AlphaVar) -> Self { - Type(self.0.shift(delta, var)) - } - pub(crate) fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self { - Type(self.0.subst_shift(var, val)) - } - - fn const_sort() -> Self { - Type(TypeInternal::Const(Const::Sort)) - } - fn const_kind() -> Self { - Type(TypeInternal::Const(Const::Kind)) - } - pub(crate) fn const_type() -> Self { - Type(TypeInternal::Const(Const::Type)) - } -} - -impl TypeThunk { - fn to_type(&self, ctx: &TypecheckContext) -> Result<Type, TypeError> { - match self { - TypeThunk::Type(t) => Ok(t.clone()), - TypeThunk::Thunk(th) => { - // TODO: rule out statically - mktype(ctx, th.normalize_to_expr().embed_absurd().note_absurd()) - } - } - } -} - -/// A semantic type. This is partially redundant with `dhall_syntax::Expr`, on purpose. `TypeInternal` should -/// be limited to syntactic expressions: either written by the user or meant to be printed. -/// The rule is the following: we must _not_ construct values of type `Expr` while typechecking, -/// but only construct `TypeInternal`s. -#[derive(Debug, Clone)] -pub(crate) enum TypeInternal { - Const(Const), - /// This must not contain a Const value. - Typed(Box<Typed>), -} - -impl TypeInternal { - fn to_typed(&self) -> Typed { - match self { - TypeInternal::Typed(e) => e.as_ref().clone(), - TypeInternal::Const(c) => const_to_typed(*c), - } - } - fn to_normalized(&self) -> Normalized { - self.to_typed().normalize() - } - fn to_expr(&self) -> SubExpr<X, X> { - self.to_normalized().to_expr() - } - fn to_value(&self) -> Value { - self.to_typed().to_value() - } - pub(crate) fn get_type(&self) -> Result<Cow<'_, Type>, TypeError> { - Ok(match self { - TypeInternal::Typed(e) => e.get_type()?, - TypeInternal::Const(c) => Cow::Owned(type_of_const(*c)?), - }) - } - fn as_const(&self) -> Option<Const> { - match self { - TypeInternal::Const(c) => Some(*c), - _ => None, - } - } - fn whnf(&self) -> Option<Value> { - match self { - TypeInternal::Typed(e) => Some(e.to_value()), - _ => None, - } - } - fn shift(&self, delta: isize, var: &AlphaVar) -> Self { - use TypeInternal::*; - match self { - Typed(e) => Typed(Box::new(e.shift(delta, var))), - Const(c) => Const(*c), - } - } - fn subst_shift(&self, var: &AlphaVar, val: &Typed) -> Self { - use TypeInternal::*; - match self { - Typed(e) => Typed(Box::new(e.subst_shift(var, val))), - Const(c) => Const(*c), - } - } -} - -impl Eq for TypeInternal {} -impl PartialEq for TypeInternal { - fn eq(&self, other: &Self) -> bool { - self.to_normalized() == other.to_normalized() - } -} - -#[derive(Debug, Clone)] -pub(crate) enum EnvItem { - Type(AlphaVar, Type), - Value(Normalized), -} - -impl EnvItem { - fn shift(&self, delta: isize, var: &AlphaVar) -> Self { - use EnvItem::*; - match self { - Type(v, e) => Type(v.shift(delta, var), e.shift(delta, var)), - Value(e) => Value(e.shift(delta, var)), - } - } -} - -#[derive(Debug, Clone)] -pub(crate) struct TypecheckContext(pub(crate) Context<Label, EnvItem>); - -impl TypecheckContext { - pub(crate) fn new() -> Self { - TypecheckContext(Context::new()) - } - pub(crate) fn insert_type(&self, x: &Label, t: Type) -> Self { - TypecheckContext( - self.0.map(|_, e| e.shift(1, &x.into())).insert( - x.clone(), - EnvItem::Type(x.into(), t.shift(1, &x.into())), - ), - ) - } - pub(crate) fn insert_value(&self, x: &Label, t: Normalized) -> Self { - TypecheckContext(self.0.insert(x.clone(), EnvItem::Value(t))) - } - pub(crate) fn lookup(&self, var: &V<Label>) -> Option<Cow<'_, Type>> { - let V(x, n) = var; - match self.0.lookup(x, *n) { - Some(EnvItem::Type(_, t)) => Some(Cow::Borrowed(&t)), - Some(EnvItem::Value(t)) => Some(t.get_type()?), - None => None, - } - } - fn to_normalization_ctx(&self) -> NormalizationContext { - NormalizationContext::from_typecheck_ctx(self) - } -} - -impl PartialEq for TypecheckContext { - fn eq(&self, _: &Self) -> bool { - // don't count contexts when comparing stuff - // this is dirty but needed for now - true - } -} -impl Eq for TypecheckContext {} - -fn function_check(a: Const, b: Const) -> Result<Const, ()> { - use dhall_syntax::Const::*; - match (a, b) { - (_, Type) => Ok(Type), - (Kind, Kind) => Ok(Kind), - (Sort, Sort) => Ok(Sort), - (Sort, Kind) => Ok(Sort), - _ => Err(()), - } -} - -// Equality up to alpha-equivalence (renaming of bound variables) -fn prop_equal<T, U>(eL0: T, eR0: U) -> bool -where - T: Borrow<Type>, - U: Borrow<Type>, -{ - eL0.borrow().to_value() == eR0.borrow().to_value() -} - -fn const_to_typed(c: Const) -> Typed { - match c { - Const::Sort => Typed::const_sort(), - _ => Typed::from_thunk_and_type( - Value::Const(c).into_thunk(), - type_of_const(c).unwrap(), - ), - } -} - -fn const_to_type(c: Const) -> Type { - Type(TypeInternal::Const(c)) -} - -fn type_of_const(c: Const) -> Result<Type, TypeError> { - match c { - Const::Type => Ok(Type::const_kind()), - Const::Kind => Ok(Type::const_sort()), - Const::Sort => { - return Err(TypeError::new( - &TypecheckContext::new(), - TypeMessage::Sort, - )) - } - } -} - -fn type_of_builtin<E>(b: Builtin) -> Expr<X, E> { - use dhall_syntax::Builtin::*; - match b { - Bool | Natural | Integer | Double | Text => dhall::expr!(Type), - List | Optional => dhall::expr!( - Type -> Type - ), - - NaturalFold => dhall::expr!( - Natural -> - forall (natural: Type) -> - forall (succ: natural -> natural) -> - forall (zero: natural) -> - natural - ), - NaturalBuild => dhall::expr!( - (forall (natural: Type) -> - forall (succ: natural -> natural) -> - forall (zero: natural) -> - natural) -> - Natural - ), - NaturalIsZero | NaturalEven | NaturalOdd => dhall::expr!( - Natural -> Bool - ), - NaturalToInteger => dhall::expr!(Natural -> Integer), - NaturalShow => dhall::expr!(Natural -> Text), - - IntegerToDouble => dhall::expr!(Integer -> Double), - IntegerShow => dhall::expr!(Integer -> Text), - DoubleShow => dhall::expr!(Double -> Text), - TextShow => dhall::expr!(Text -> Text), - - ListBuild => dhall::expr!( - forall (a: Type) -> - (forall (list: Type) -> - forall (cons: a -> list -> list) -> - forall (nil: list) -> - list) -> - List a - ), - ListFold => dhall::expr!( - forall (a: Type) -> - List a -> - forall (list: Type) -> - forall (cons: a -> list -> list) -> - forall (nil: list) -> - list - ), - ListLength => dhall::expr!(forall (a: Type) -> List a -> Natural), - ListHead | ListLast => { - dhall::expr!(forall (a: Type) -> List a -> Optional a) - } - ListIndexed => dhall::expr!( - forall (a: Type) -> - List a -> - List { index: Natural, value: a } - ), - ListReverse => dhall::expr!( - forall (a: Type) -> List a -> List a - ), +use dhall_syntax::{ + rc, Builtin, Const, Expr, ExprF, InterpolatedTextContents, Label, Span, + SubExpr, X, +}; - OptionalBuild => dhall::expr!( - forall (a: Type) -> - (forall (optional: Type) -> - forall (just: a -> optional) -> - forall (nothing: optional) -> - optional) -> - Optional a - ), - OptionalFold => dhall::expr!( - forall (a: Type) -> - Optional a -> - forall (optional: Type) -> - forall (just: a -> optional) -> - forall (nothing: optional) -> - optional - ), - OptionalNone => dhall::expr!( - forall (a: Type) -> Optional a - ), - } -} +use crate::core::context::{NormalizationContext, TypecheckContext}; +use crate::core::thunk::{Thunk, TypeThunk}; +use crate::core::value::Value; +use crate::error::{TypeError, TypeMessage}; +use crate::phase::{Normalized, Resolved, Type, Typed}; macro_rules! ensure_equal { ($x:expr, $y:expr, $err:expr $(,)*) => { @@ -386,6 +43,7 @@ pub(crate) enum TypeIntermediate { impl TypeIntermediate { fn typecheck(self) -> Result<Typed, TypeError> { + use crate::error::TypeMessage::*; let mkerr = |ctx, msg| TypeError::new(ctx, msg); Ok(match &self { TypeIntermediate::Pi(ctx, x, ta, tb) => { @@ -439,7 +97,7 @@ impl TypeIntermediate { TypeThunk::from_type(tb.clone()), ) .into_thunk(), - const_to_type(k), + Type::from_const(k), ) } TypeIntermediate::RecordType(ctx, kts) => { @@ -469,7 +127,7 @@ impl TypeIntermediate { .collect(), ) .into_thunk(), - const_to_type(k), + Type::from_const(k), ) } TypeIntermediate::UnionType(ctx, kts) => { @@ -508,7 +166,7 @@ impl TypeIntermediate { .collect(), ) .into_thunk(), - const_to_type(k), + Type::from_const(k), ) } TypeIntermediate::ListType(ctx, t) => { @@ -520,7 +178,7 @@ impl TypeIntermediate { Value::from_builtin(Builtin::List) .app(t.to_value()) .into_thunk(), - const_to_type(Const::Type), + Type::from_const(Const::Type), ) } TypeIntermediate::OptionalType(ctx, t) => { @@ -532,23 +190,140 @@ impl TypeIntermediate { Value::from_builtin(Builtin::Optional) .app(t.to_value()) .into_thunk(), - const_to_type(Const::Type), + Type::from_const(Const::Type), ) } }) } } +fn function_check(a: Const, b: Const) -> Result<Const, ()> { + use dhall_syntax::Const::*; + match (a, b) { + (_, Type) => Ok(Type), + (Kind, Kind) => Ok(Kind), + (Sort, Sort) => Ok(Sort), + (Sort, Kind) => Ok(Sort), + _ => Err(()), + } +} + +// Equality up to alpha-equivalence (renaming of bound variables) +fn prop_equal<T, U>(eL0: T, eR0: U) -> bool +where + T: Borrow<Type>, + U: Borrow<Type>, +{ + eL0.borrow().to_value() == eR0.borrow().to_value() +} + +pub(crate) fn type_of_const(c: Const) -> Result<Type, TypeError> { + match c { + Const::Type => Ok(Type::const_kind()), + Const::Kind => Ok(Type::const_sort()), + Const::Sort => { + return Err(TypeError::new( + &TypecheckContext::new(), + TypeMessage::Sort, + )) + } + } +} + +fn type_of_builtin(b: Builtin) -> Expr<X, X> { + use dhall_syntax::Builtin::*; + match b { + Bool | Natural | Integer | Double | Text => dhall::expr!(Type), + List | Optional => dhall::expr!( + Type -> Type + ), + + NaturalFold => dhall::expr!( + Natural -> + forall (natural: Type) -> + forall (succ: natural -> natural) -> + forall (zero: natural) -> + natural + ), + NaturalBuild => dhall::expr!( + (forall (natural: Type) -> + forall (succ: natural -> natural) -> + forall (zero: natural) -> + natural) -> + Natural + ), + NaturalIsZero | NaturalEven | NaturalOdd => dhall::expr!( + Natural -> Bool + ), + NaturalToInteger => dhall::expr!(Natural -> Integer), + NaturalShow => dhall::expr!(Natural -> Text), + + IntegerToDouble => dhall::expr!(Integer -> Double), + IntegerShow => dhall::expr!(Integer -> Text), + DoubleShow => dhall::expr!(Double -> Text), + TextShow => dhall::expr!(Text -> Text), + + ListBuild => dhall::expr!( + forall (a: Type) -> + (forall (list: Type) -> + forall (cons: a -> list -> list) -> + forall (nil: list) -> + list) -> + List a + ), + ListFold => dhall::expr!( + forall (a: Type) -> + List a -> + forall (list: Type) -> + forall (cons: a -> list -> list) -> + forall (nil: list) -> + list + ), + ListLength => dhall::expr!(forall (a: Type) -> List a -> Natural), + ListHead | ListLast => { + dhall::expr!(forall (a: Type) -> List a -> Optional a) + } + ListIndexed => dhall::expr!( + forall (a: Type) -> + List a -> + List { index: Natural, value: a } + ), + ListReverse => dhall::expr!( + forall (a: Type) -> List a -> List a + ), + + OptionalBuild => dhall::expr!( + forall (a: Type) -> + (forall (optional: Type) -> + forall (just: a -> optional) -> + forall (nothing: optional) -> + optional) -> + Optional a + ), + OptionalFold => dhall::expr!( + forall (a: Type) -> + Optional a -> + forall (optional: Type) -> + forall (just: a -> optional) -> + forall (nothing: optional) -> + optional + ), + OptionalNone => dhall::expr!( + forall (a: Type) -> Optional a + ), + } +} + /// Takes an expression that is meant to contain a Type /// and turn it into a type, typechecking it along the way. -fn mktype( +pub(crate) fn mktype( ctx: &TypecheckContext, e: SubExpr<Span, Normalized>, ) -> Result<Type, TypeError> { Ok(type_with(ctx, e)?.to_type()) } -fn builtin_to_type(b: Builtin) -> Result<Type, TypeError> { +pub(crate) fn builtin_to_type(b: Builtin) -> Result<Type, TypeError> { mktype( &TypecheckContext::new(), SubExpr::from_expr_no_note(ExprF::Builtin(b)), @@ -645,6 +420,7 @@ fn type_last_layer( ctx: &TypecheckContext, e: ExprF<Typed, Label, Normalized>, ) -> Result<Ret, TypeError> { + use crate::error::TypeMessage::*; use dhall_syntax::BinOp::*; use dhall_syntax::Builtin::*; use dhall_syntax::ExprF::*; @@ -857,9 +633,9 @@ fn type_last_layer( // ))), } } - Const(c) => Ok(RetTyped(const_to_typed(c))), + Const(c) => Ok(RetTyped(Typed::from_const(c))), Builtin(b) => { - Ok(RetType(mktype(ctx, rc(type_of_builtin(b)).note_absurd())?)) + Ok(RetType(mktype(ctx, rc(type_of_builtin(b)).absurd())?)) } BoolLit(_) => Ok(RetType(builtin_to_type(Bool)?)), NaturalLit(_) => Ok(RetType(builtin_to_type(Natural)?)), @@ -927,99 +703,20 @@ fn type_of(e: SubExpr<Span, Normalized>) -> Result<Typed, TypeError> { Ok(e) } -/// The specific type error -#[derive(Debug)] -pub(crate) enum TypeMessage { - UnboundVariable(V<Label>), - InvalidInputType(Normalized), - InvalidOutputType(Normalized), - NotAFunction(Typed), - TypeMismatch(Typed, Normalized, Typed), - AnnotMismatch(Typed, Normalized), - Untyped, - InvalidListElement(usize, Normalized, Typed), - InvalidListType(Normalized), - InvalidOptionalType(Normalized), - InvalidPredicate(Typed), - IfBranchMismatch(Typed, Typed), - IfBranchMustBeTerm(bool, Typed), - InvalidFieldType(Label, Type), - NotARecord(Label, Normalized), - MissingRecordField(Label, Typed), - MissingUnionField(Label, Normalized), - BinOpTypeMismatch(BinOp, Typed), - NoDependentTypes(Normalized, Normalized), - InvalidTextInterpolation(Typed), - Sort, - Unimplemented, -} - -/// A structured type error that includes context -#[derive(Debug)] -pub struct TypeError { - type_message: TypeMessage, - context: TypecheckContext, -} - -impl TypeError { - pub(crate) fn new( - context: &TypecheckContext, - type_message: TypeMessage, - ) -> Self { - TypeError { - context: context.clone(), - type_message, - } - } -} - -impl From<TypeError> for std::option::NoneError { - fn from(_: TypeError) -> std::option::NoneError { - std::option::NoneError - } +pub(crate) fn typecheck(e: Resolved) -> Result<Typed, TypeError> { + type_of(e.0) } -impl ::std::error::Error for TypeMessage { - fn description(&self) -> &str { - match *self { - // UnboundVariable => "Unbound variable", - InvalidInputType(_) => "Invalid function input", - InvalidOutputType(_) => "Invalid function output", - NotAFunction(_) => "Not a function", - TypeMismatch(_, _, _) => "Wrong type of function argument", - _ => "Unhandled error", - } - } +pub(crate) fn typecheck_with( + e: Resolved, + ty: &Type, +) -> Result<Typed, TypeError> { + let expr: SubExpr<_, _> = e.0; + let ty: SubExpr<_, _> = ty.to_expr().absurd(); + type_of(expr.rewrap(ExprF::Annot(expr.clone(), ty))) } - -impl fmt::Display for TypeMessage { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match self { - // UnboundVariable(_) => { - // f.write_str(include_str!("errors/UnboundVariable.txt")) - // } - // TypeMismatch(e0, e1, e2) => { - // let template = include_str!("errors/TypeMismatch.txt"); - // let s = template - // .replace("$txt0", &format!("{}", e0.as_expr())) - // .replace("$txt1", &format!("{}", e1.as_expr())) - // .replace("$txt2", &format!("{}", e2.as_expr())) - // .replace( - // "$txt3", - // &format!( - // "{}", - // e2.get_type() - // .unwrap() - // .as_normalized() - // .unwrap() - // .as_expr() - // ), - // ); - // f.write_str(&s) - // } - _ => f.write_str("Unhandled error message"), - } - } +pub(crate) fn skip_typecheck(e: Resolved) -> Typed { + Typed::from_thunk_untyped(Thunk::new(NormalizationContext::new(), e.0)) } #[cfg(test)] diff --git a/dhall/src/tests.rs b/dhall/src/tests.rs index 6b2ab60..4fa1cf5 100644 --- a/dhall/src/tests.rs +++ b/dhall/src/tests.rs @@ -34,8 +34,7 @@ macro_rules! make_spec_test { } use crate::error::{Error, Result}; -use crate::expr::Parsed; -use crate::traits::DynamicType; +use crate::phase::Parsed; use std::path::PathBuf; #[derive(Copy, Clone)] |