From ec349d42703a8a31715cf97b44845ba3dd7a6805 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Tue, 20 Aug 2019 22:20:59 +0200 Subject: Propagate type information in Value::app() --- tests_buffer | 1 + 1 file changed, 1 insertion(+) (limited to 'tests_buffer') diff --git a/tests_buffer b/tests_buffer index c6366ba..15dc9c5 100644 --- a/tests_buffer +++ b/tests_buffer @@ -41,5 +41,6 @@ failure/ merge {x=...,y=...} .x MergeBoolIsNotUnion merge x True MergeOptionalIsNotUnion merge x (Some 1) + SortInLet let x = Sort in 1 equivalence: -- cgit v1.3.1 From d9e3bcca9b4350cbc1db2545d7ed28dde4e12be4 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Fri, 23 Aug 2019 18:34:43 +0200 Subject: Keep type information after RecursiveRecordTypeMerge --- dhall/src/phase/normalize.rs | 75 ++++++++------------------------------------ dhall/src/phase/typecheck.rs | 37 ++++++---------------- tests_buffer | 4 ++- 3 files changed, 26 insertions(+), 90 deletions(-) (limited to 'tests_buffer') diff --git a/dhall/src/phase/normalize.rs b/dhall/src/phase/normalize.rs index 0c4e185..28dc0f6 100644 --- a/dhall/src/phase/normalize.rs +++ b/dhall/src/phase/normalize.rs @@ -452,53 +452,20 @@ pub(crate) fn squash_textlit( ret } -/// Performs an intersection of two HashMaps. -/// -/// # Arguments -/// -/// * `f` - Will compute the final value from the intersecting -/// key and the values from both maps. -/// -/// # Description -/// -/// If the key is present in both maps then the final value for -/// that key is computed via the `f` function. -/// -/// The final map will contain the shared keys from the -/// two input maps with the final computed value from `f`. -pub(crate) fn intersection_with_key( - mut f: impl FnMut(&K, &T, &U) -> V, - map1: &HashMap, - map2: &HashMap, -) -> HashMap -where - K: std::hash::Hash + Eq + Clone, -{ - let mut kvs = HashMap::new(); - - for (k, t) in map1 { - // Only insert in the final map if the key exists in both - if let Some(u) = map2.get(k) { - kvs.insert(k.clone(), f(k, t, u)); - } - } - - kvs -} - -pub(crate) fn merge_maps( +pub(crate) fn merge_maps( map1: &HashMap, map2: &HashMap, - mut f: impl FnMut(&V, &V) -> V, -) -> HashMap + mut f: F, +) -> Result, Err> where + F: FnMut(&V, &V) -> Result, K: std::hash::Hash + Eq + Clone, V: Clone, { let mut kvs = HashMap::new(); for (x, v2) in map2 { let newv = if let Some(v1) = map1.get(x) { - f(v1, v2) + f(v1, v2)? } else { v2.clone() }; @@ -508,7 +475,7 @@ where // Insert only if key not already present kvs.entry(x.clone()).or_insert_with(|| v1.clone()); } - kvs + Ok(kvs) } fn apply_binop<'a>(o: BinOp, x: &'a Value, y: &'a Value) -> Option> { @@ -518,8 +485,7 @@ fn apply_binop<'a>(o: BinOp, x: &'a Value, y: &'a Value) -> Option> { RightBiasedRecordMerge, TextAppend, }; use ValueF::{ - BoolLit, EmptyListLit, NEListLit, NaturalLit, RecordLit, RecordType, - TextLit, + BoolLit, EmptyListLit, NEListLit, NaturalLit, RecordLit, TextLit, }; let x_borrow = x.as_whnf(); let y_borrow = y.as_whnf(); @@ -604,31 +570,16 @@ fn apply_binop<'a>(o: BinOp, x: &'a Value, y: &'a Value) -> Option> { Ret::ValueRef(y) } (RecursiveRecordMerge, RecordLit(kvs1), RecordLit(kvs2)) => { - let kvs = merge_maps(kvs1, kvs2, |v1, v2| { - Value::from_valuef_untyped(ValueF::PartialExpr(ExprF::BinOp( - RecursiveRecordMerge, - v1.clone(), - v2.clone(), + let kvs = merge_maps::<_, _, _, !>(kvs1, kvs2, |v1, v2| { + Ok(Value::from_valuef_untyped(ValueF::PartialExpr( + ExprF::BinOp(RecursiveRecordMerge, v1.clone(), v2.clone()), ))) - }); + })?; Ret::ValueF(RecordLit(kvs)) } - (RecursiveRecordTypeMerge, _, RecordType(kvs)) if kvs.is_empty() => { - Ret::ValueRef(x) - } - (RecursiveRecordTypeMerge, RecordType(kvs), _) if kvs.is_empty() => { - Ret::ValueRef(y) - } - (RecursiveRecordTypeMerge, RecordType(kvs1), RecordType(kvs2)) => { - let kvs = merge_maps(kvs1, kvs2, |v1, v2| { - Value::from_valuef_untyped(ValueF::PartialExpr(ExprF::BinOp( - RecursiveRecordTypeMerge, - v1.clone(), - v2.clone(), - ))) - }); - Ret::ValueF(RecordType(kvs)) + (RecursiveRecordTypeMerge, _, _) => { + unreachable!("This case should have been handled in typecheck") } (Equivalence, _, _) => { diff --git a/dhall/src/phase/typecheck.rs b/dhall/src/phase/typecheck.rs index abe05a3..363d733 100644 --- a/dhall/src/phase/typecheck.rs +++ b/dhall/src/phase/typecheck.rs @@ -567,7 +567,9 @@ fn type_last_layer( // Union the two records, prefering // the values found in the RHS. - let kts = merge_maps(kts_x, kts_y, |_, r_t| r_t.clone()); + let kts = merge_maps::<_, _, _, !>(kts_x, kts_y, |_, r_t| { + Ok(r_t.clone()) + })?; // Construct the final record type from the union RetTypeOnly(tck_record_type( @@ -584,25 +586,7 @@ fn type_last_layer( ), )?), BinOp(RecursiveRecordTypeMerge, l, r) => { - use crate::phase::normalize::intersection_with_key; - - // Extract the Const of the LHS - let k_l = match l.get_type()?.as_const() { - Some(k) => k, - _ => { - return mkerr(RecordTypeMergeRequiresRecordType(l.clone())) - } - }; - - // Extract the Const of the RHS - let k_r = match r.get_type()?.as_const() { - Some(k) => k, - _ => { - return mkerr(RecordTypeMergeRequiresRecordType(r.clone())) - } - }; - - let k = max(k_l, k_r); + use crate::phase::normalize::merge_maps; // Extract the LHS record type let borrow_l = l.as_whnf(); @@ -623,9 +607,11 @@ fn type_last_layer( }; // Ensure that the records combine without a type error - let kts = intersection_with_key( + let kts = merge_maps( + kts_x, + kts_y, // If the Label exists for both records, then we hit the recursive case. - |_: &Label, l: &Value, r: &Value| { + |l: &Value, r: &Value| { type_last_layer( ctx, ExprF::BinOp( @@ -635,12 +621,9 @@ fn type_last_layer( ), ) }, - kts_x, - kts_y, - ); - tck_record_type(ctx, kts.into_iter().map(|(x, v)| Ok((x, v?))))?; + )?; - RetTypeOnly(Value::from_const(k)) + RetWhole(tck_record_type(ctx, kts.into_iter().map(Ok))?) } BinOp(o @ ListAppend, l, r) => { match &*l.get_type()?.as_whnf() { diff --git a/tests_buffer b/tests_buffer index 15dc9c5..511ea49 100644 --- a/tests_buffer +++ b/tests_buffer @@ -32,7 +32,9 @@ variables across import boundaries typecheck: something that involves destructuring a recordtype after merge add some of the more complicated Prelude tests back, like List/enumerate -failure on old-style optional literal +success/ + regression/ + RecursiveRecordTypeMergeTripleCollision { x : { a : Bool } } ⩓ { x : { b : Bool } } ⩓ { x : { c : Bool } } failure/ merge { x = λ(x : Bool) → x } (< x: Bool | y: Natural >.x True) merge { x = λ(_ : Bool) → _, y = 1 } < x = True | y > -- cgit v1.3.1 From 98399997cf289d802fbed674558665547cf73d59 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Sun, 25 Aug 2019 16:09:55 +0200 Subject: Keep type information through normalization --- dhall/src/core/value.rs | 39 ++++++--- dhall/src/phase/normalize.rs | 200 ++++++++++++++++++++++++++++--------------- dhall/src/phase/typecheck.rs | 21 ++--- tests_buffer | 3 + 4 files changed, 173 insertions(+), 90 deletions(-) (limited to 'tests_buffer') diff --git a/dhall/src/core/value.rs b/dhall/src/core/value.rs index 69d372a..6f9f78a 100644 --- a/dhall/src/core/value.rs +++ b/dhall/src/core/value.rs @@ -70,12 +70,25 @@ impl ValueInternal { ty: None, }, |vint| match &vint.form { - Unevaled => ValueInternal { - form: WHNF, - // TODO: thunk chaining - value: normalize_whnf(vint.value).into_whnf(), - ty: vint.ty, - }, + Unevaled => { + let (value, ty) = (vint.value, vint.ty); + let vovf = normalize_whnf(value, ty.as_ref()); + // let was_value = if let VoVF::Value(_) = &vovf { + // true + // } else { + // false + // }; + let (new_val, _new_ty) = + vovf.into_whnf_and_type(ty.as_ref()); + // if was_value { + // debug_assert_eq!(ty, new_ty); + // } + ValueInternal { + form: WHNF, + value: new_val, + ty: ty, + } + } // Already in WHNF WHNF | NF => vint, }, @@ -221,7 +234,8 @@ impl Value { } pub(crate) fn app(&self, v: Value) -> Value { - let vovf = apply_any(self.clone(), v.clone()); + let ty = self.get_type().ok(); + let vovf = apply_any(self.clone(), v.clone(), ty.as_ref()); match self.as_internal().get_type() { Err(_) => vovf.into_value_untyped(), Ok(t) => match &*t.as_whnf() { @@ -240,15 +254,18 @@ impl Value { } impl VoVF { - pub(crate) fn into_whnf(self) -> ValueF { + pub(crate) fn into_whnf_and_type( + self, + ty: Option<&Value>, + ) -> (ValueF, Option) { match self { - VoVF::Value(v) => v.to_whnf(), VoVF::ValueF { val, form: Unevaled, - } => normalize_whnf(val).into_whnf(), + } => normalize_whnf(val, ty).into_whnf_and_type(ty), // Already at lest in WHNF - VoVF::ValueF { val, .. } => val, + VoVF::ValueF { val, .. } => (val, None), + VoVF::Value(v) => (v.to_whnf(), v.get_type().ok()), } } pub(crate) fn into_value_untyped(self) -> Value { diff --git a/dhall/src/phase/normalize.rs b/dhall/src/phase/normalize.rs index c8f5bc2..5743b0d 100644 --- a/dhall/src/phase/normalize.rs +++ b/dhall/src/phase/normalize.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; +use dhall_syntax::Const::Type; use dhall_syntax::{ BinOp, Builtin, ExprF, InterpolatedText, InterpolatedTextContents, Label, NaiveDouble, @@ -7,7 +8,7 @@ use dhall_syntax::{ use crate::core::value::{Value, VoVF}; use crate::core::valuef::ValueF; -use crate::core::var::{Shift, Subst}; +use crate::core::var::{AlphaLabel, Shift, Subst}; use crate::phase::Normalized; // Small helper enum to avoid repetition @@ -32,21 +33,24 @@ impl<'a> Ret<'a> { // Ad-hoc macro to help construct closures macro_rules! make_closure { (#$var:ident) => { $var.clone() }; - (var($var:ident, $n:expr)) => {{ + (var($var:ident, $n:expr, $($ty:tt)*)) => {{ let var = crate::core::var::AlphaVar::from_var_and_alpha( Label::from(stringify!($var)).into(), $n ); - ValueF::Var(var).into_value_untyped() + ValueF::Var(var) + .into_value_with_type(make_closure!($($ty)*)) }}; // Warning: assumes that $ty, as a dhall value, has type `Type` - (λ($var:ident : $($ty:tt)*) -> $($rest:tt)*) => { - ValueF::Lam( - Label::from(stringify!($var)).into(), - make_closure!($($ty)*), - make_closure!($($rest)*), - ).into_value_untyped() - }; + (λ($var:ident : $($ty:tt)*) -> $($body:tt)*) => {{ + let var: AlphaLabel = Label::from(stringify!($var)).into(); + let ty = make_closure!($($ty)*); + let body = make_closure!($($body)*); + let body_ty = body.get_type().expect("Internal type error"); + let lam_ty = ValueF::Pi(var.clone(), ty.clone(), body_ty) + .into_value_with_type(Value::from_const(Type)); + ValueF::Lam(var, ty, body).into_value_with_type(lam_ty) + }}; (Natural) => { Value::from_builtin(Builtin::Natural) }; @@ -54,10 +58,12 @@ macro_rules! make_closure { Value::from_builtin(Builtin::List) .app(make_closure!($($rest)*)) }; - (Some($($rest:tt)*)) => { - ValueF::NEOptionalLit(make_closure!($($rest)*)) - .into_value_untyped() - }; + (Some($($rest:tt)*)) => {{ + let v = make_closure!($($rest)*); + let v_type = v.get_type().expect("Internal type error"); + ValueF::NEOptionalLit(v) + .into_value_with_type(v_type) + }}; (1 + $($rest:tt)*) => { ValueF::PartialExpr(ExprF::BinOp( dhall_syntax::BinOp::NaturalPlus, @@ -70,18 +76,25 @@ macro_rules! make_closure { make_closure!(Natural) ) }; - ([ $($head:tt)* ] # $($tail:tt)*) => { + ([ $($head:tt)* ] # $($tail:tt)*) => {{ + let head = make_closure!($($head)*); + let tail = make_closure!($($tail)*); + let list_type = tail.get_type().expect("Internal type error"); ValueF::PartialExpr(ExprF::BinOp( dhall_syntax::BinOp::ListAppend, - ValueF::NEListLit(vec![make_closure!($($head)*)]) - .into_value_untyped(), - make_closure!($($tail)*), - )).into_value_untyped() - }; + ValueF::NEListLit(vec![head]) + .into_value_with_type(list_type.clone()), + tail, + )).into_value_with_type(list_type) + }}; } #[allow(clippy::cognitive_complexity)] -pub(crate) fn apply_builtin(b: Builtin, args: Vec) -> VoVF { +pub(crate) fn apply_builtin( + b: Builtin, + args: Vec, + _ty: Option<&Value>, +) -> VoVF { use dhall_syntax::Builtin::*; use ValueF::*; @@ -231,37 +244,60 @@ pub(crate) fn apply_builtin(b: Builtin, args: Vec) -> VoVF { )), _ => Err(()), }, - (ListIndexed, [_, l, r..]) => match &*l.as_whnf() { - EmptyListLit(t) => { - let mut kts = HashMap::new(); - kts.insert("index".into(), Value::from_builtin(Natural)); - kts.insert("value".into(), t.clone()); - Ok(( - r, - Ret::ValueF(EmptyListLit(Value::from_valuef_untyped( + (ListIndexed, [_, l, r..]) => { + let l_whnf = l.as_whnf(); + match &*l_whnf { + EmptyListLit(_) | NEListLit(_) => { + // Extract the type of the list elements + let t = match &*l_whnf { + EmptyListLit(t) => t.clone(), + NEListLit(xs) => { + xs[0].get_type().expect("Internal type error") + } + _ => unreachable!(), + }; + + // Construct the returned record type: { index: Natural, value: t } + let mut kts = HashMap::new(); + kts.insert("index".into(), Value::from_builtin(Natural)); + kts.insert("value".into(), t.clone()); + let t = Value::from_valuef_and_type( RecordType(kts), - ))), - )) - } - NEListLit(xs) => { - let xs = xs - .iter() - .enumerate() - .map(|(i, e)| { - let i = NaturalLit(i); - let mut kvs = HashMap::new(); - kvs.insert( - "index".into(), - Value::from_valuef_untyped(i), - ); - kvs.insert("value".into(), e.clone()); - Value::from_valuef_untyped(RecordLit(kvs)) - }) - .collect(); - Ok((r, Ret::ValueF(NEListLit(xs)))) + Value::from_const(Type), + ); + + // Construct the new list, with added indices + let list = match &*l_whnf { + EmptyListLit(_) => EmptyListLit(t), + NEListLit(xs) => NEListLit( + xs.iter() + .enumerate() + .map(|(i, e)| { + let mut kvs = HashMap::new(); + kvs.insert( + "index".into(), + Value::from_valuef_and_type( + NaturalLit(i), + Value::from_builtin( + Builtin::Natural, + ), + ), + ); + kvs.insert("value".into(), e.clone()); + Value::from_valuef_and_type( + RecordLit(kvs), + t.clone(), + ) + }) + .collect(), + ), + _ => unreachable!(), + }; + Ok((r, Ret::ValueF(list))) + } + _ => Err(()), } - _ => Err(()), - }, + } (ListBuild, [t, f, r..]) => match &*f.as_whnf() { // fold/build fusion ValueF::AppliedBuiltin(ListFold, args) => { @@ -279,12 +315,13 @@ pub(crate) fn apply_builtin(b: Builtin, args: Vec) -> VoVF { Ret::Value( f.app(list_t.clone()) .app({ - // Move `t` under new `x` variable + // Move `t` under new variables let t1 = t.under_binder(Label::from("x")); + let t2 = t1.under_binder(Label::from("xs")); make_closure!( λ(x : #t) -> λ(xs : List #t1) -> - [ var(x, 1) ] # var(xs, 0) + [ var(x, 1, #t2) ] # var(xs, 0, List #t2) ) }) .app( @@ -322,7 +359,10 @@ pub(crate) fn apply_builtin(b: Builtin, args: Vec) -> VoVF { r, Ret::Value( f.app(optional_t.clone()) - .app(make_closure!(λ(x: #t) -> Some(var(x, 0)))) + .app({ + let t1 = t.under_binder(Label::from("x")); + make_closure!(λ(x: #t) -> Some(var(x, 0, #t1))) + }) .app( EmptyOptionalLit(t.clone()) .into_value_with_type(optional_t), @@ -350,7 +390,9 @@ pub(crate) fn apply_builtin(b: Builtin, args: Vec) -> VoVF { r, Ret::Value( f.app(Value::from_builtin(Natural)) - .app(make_closure!(λ(x : Natural) -> 1 + var(x, 0))) + .app(make_closure!( + λ(x : Natural) -> 1 + var(x, 0, Natural) + )) .app(NaturalLit(0).into_value_with_type( Value::from_builtin(Natural), )), @@ -387,7 +429,7 @@ pub(crate) fn apply_builtin(b: Builtin, args: Vec) -> VoVF { } } -pub(crate) fn apply_any(f: Value, a: Value) -> VoVF { +pub(crate) fn apply_any(f: Value, a: Value, ty: Option<&Value>) -> VoVF { let fallback = |f: Value, a: Value| { ValueF::PartialExpr(ExprF::App(f, a)).into_vovf_whnf() }; @@ -398,7 +440,7 @@ pub(crate) fn apply_any(f: Value, a: Value) -> VoVF { ValueF::AppliedBuiltin(b, args) => { use std::iter::once; let args = args.iter().cloned().chain(once(a.clone())).collect(); - apply_builtin(*b, args) + apply_builtin(*b, args, ty) } ValueF::UnionConstructor(l, kts) => { ValueF::UnionLit(l.clone(), a, kts.clone()).into_vovf_whnf() @@ -458,14 +500,14 @@ pub(crate) fn merge_maps( mut f: F, ) -> Result, Err> where - F: FnMut(&V, &V) -> Result, + F: FnMut(&K, &V, &V) -> Result, K: std::hash::Hash + Eq + Clone, V: Clone, { let mut kvs = HashMap::new(); for (x, v2) in map2 { let newv = if let Some(v1) = map1.get(x) { - f(v1, v2)? + f(x, v1, v2)? } else { v2.clone() }; @@ -478,14 +520,20 @@ where Ok(kvs) } -fn apply_binop<'a>(o: BinOp, x: &'a Value, y: &'a Value) -> Option> { +fn apply_binop<'a>( + o: BinOp, + x: &'a Value, + y: &'a Value, + ty: Option<&Value>, +) -> Option> { use BinOp::{ BoolAnd, BoolEQ, BoolNE, BoolOr, Equivalence, ListAppend, NaturalPlus, NaturalTimes, RecursiveRecordMerge, RecursiveRecordTypeMerge, RightBiasedRecordMerge, TextAppend, }; use ValueF::{ - BoolLit, EmptyListLit, NEListLit, NaturalLit, RecordLit, TextLit, + BoolLit, EmptyListLit, NEListLit, NaturalLit, RecordLit, RecordType, + TextLit, }; let x_borrow = x.as_whnf(); let y_borrow = y.as_whnf(); @@ -570,10 +618,21 @@ fn apply_binop<'a>(o: BinOp, x: &'a Value, y: &'a Value) -> Option> { Ret::ValueRef(y) } (RecursiveRecordMerge, RecordLit(kvs1), RecordLit(kvs2)) => { - let kvs = merge_maps::<_, _, _, !>(kvs1, kvs2, |v1, v2| { - Ok(Value::from_valuef_untyped(ValueF::PartialExpr( - ExprF::BinOp(RecursiveRecordMerge, v1.clone(), v2.clone()), - ))) + let ty = ty.expect("Internal type error"); + let ty_borrow = ty.as_whnf(); + let kts = match &*ty_borrow { + RecordType(kts) => kts, + _ => unreachable!("Internal type error"), + }; + let kvs = merge_maps::<_, _, _, !>(kvs1, kvs2, |k, v1, v2| { + Ok(Value::from_valuef_and_type( + ValueF::PartialExpr(ExprF::BinOp( + RecursiveRecordMerge, + v1.clone(), + v2.clone(), + )), + kts.get(k).expect("Internal type error").clone(), + )) })?; Ret::ValueF(RecordLit(kvs)) } @@ -586,7 +645,10 @@ fn apply_binop<'a>(o: BinOp, x: &'a Value, y: &'a Value) -> Option> { }) } -pub(crate) fn normalize_one_layer(expr: ExprF) -> VoVF { +pub(crate) fn normalize_one_layer( + expr: ExprF, + ty: Option<&Value>, +) -> VoVF { use ValueF::{ AppliedBuiltin, BoolLit, DoubleLit, EmptyListLit, IntegerLit, NEListLit, NEOptionalLit, NaturalLit, RecordLit, TextLit, @@ -669,7 +731,7 @@ pub(crate) fn normalize_one_layer(expr: ExprF) -> VoVF { } } } - ExprF::BinOp(o, ref x, ref y) => match apply_binop(o, x, y) { + ExprF::BinOp(o, ref x, ref y) => match apply_binop(o, x, y, ty) { Some(ret) => ret, None => Ret::Expr(expr), }, @@ -746,10 +808,10 @@ pub(crate) fn normalize_one_layer(expr: ExprF) -> VoVF { } /// Normalize a ValueF into WHNF -pub(crate) fn normalize_whnf(v: ValueF) -> VoVF { +pub(crate) fn normalize_whnf(v: ValueF, ty: Option<&Value>) -> VoVF { match v { - ValueF::AppliedBuiltin(b, args) => apply_builtin(b, args), - ValueF::PartialExpr(e) => normalize_one_layer(e), + ValueF::AppliedBuiltin(b, args) => apply_builtin(b, args, ty), + ValueF::PartialExpr(e) => normalize_one_layer(e, ty), ValueF::TextLit(elts) => { ValueF::TextLit(squash_textlit(elts.into_iter())).into_vovf_whnf() } diff --git a/dhall/src/phase/typecheck.rs b/dhall/src/phase/typecheck.rs index 0268b80..270191a 100644 --- a/dhall/src/phase/typecheck.rs +++ b/dhall/src/phase/typecheck.rs @@ -307,14 +307,15 @@ fn type_with( use dhall_syntax::ExprF::{Annot, Embed, Lam, Let, Pi, Var}; Ok(match e.as_ref() { - Lam(x, t, b) => { - let tx = type_with(ctx, t.clone())?; - let ctx2 = ctx.insert_type(x, tx.clone()); - let b = type_with(&ctx2, b.clone())?; - let v = ValueF::Lam(x.clone().into(), tx.clone(), b.clone()); - let tb = b.get_type()?; - let t = tck_pi_type(ctx, x.clone(), tx, tb)?; - Value::from_valuef_and_type(v, t) + Lam(var, annot, body) => { + let annot = type_with(ctx, annot.clone())?; + let ctx2 = ctx.insert_type(var, annot.clone()); + let body = type_with(&ctx2, body.clone())?; + let body_type = body.get_type()?; + Value::from_valuef_and_type( + ValueF::Lam(var.clone().into(), annot.clone(), body), + tck_pi_type(ctx, var.clone(), annot, body_type)?, + ) } Pi(x, ta, tb) => { let ta = type_with(ctx, ta.clone())?; @@ -567,7 +568,7 @@ fn type_last_layer( // Union the two records, prefering // the values found in the RHS. - let kts = merge_maps::<_, _, _, !>(kts_x, kts_y, |_, r_t| { + let kts = merge_maps::<_, _, _, !>(kts_x, kts_y, |_, _, r_t| { Ok(r_t.clone()) })?; @@ -611,7 +612,7 @@ fn type_last_layer( kts_x, kts_y, // If the Label exists for both records, then we hit the recursive case. - |l: &Value, r: &Value| { + |_, l: &Value, r: &Value| { type_last_layer( ctx, ExprF::BinOp( diff --git a/tests_buffer b/tests_buffer index 511ea49..93eb626 100644 --- a/tests_buffer +++ b/tests_buffer @@ -35,6 +35,9 @@ add some of the more complicated Prelude tests back, like List/enumerate success/ regression/ RecursiveRecordTypeMergeTripleCollision { x : { a : Bool } } ⩓ { x : { b : Bool } } ⩓ { x : { c : Bool } } + somehow test that ({ x = { z = 1 } } ∧ { x = { y = 2 } }).x has a type + somehow test that the recordtype from List/indexed has a type in both empty and nonempty cases + somehow test types added to the Foo/build closures failure/ merge { x = λ(x : Bool) → x } (< x: Bool | y: Natural >.x True) merge { x = λ(_ : Bool) → _, y = 1 } < x = True | y > -- cgit v1.3.1 From bf37fd9da3782134ca4bca9567c34bbee81784b9 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Sun, 25 Aug 2019 21:16:38 +0200 Subject: Rework apply_builtin to enforce preservation of type information --- dhall/src/core/value.rs | 32 ++-- dhall/src/phase/normalize.rs | 339 ++++++++++++++++++++----------------------- tests_buffer | 2 + 3 files changed, 173 insertions(+), 200 deletions(-) (limited to 'tests_buffer') diff --git a/dhall/src/core/value.rs b/dhall/src/core/value.rs index 4a78b05..2554da1 100644 --- a/dhall/src/core/value.rs +++ b/dhall/src/core/value.rs @@ -116,18 +116,24 @@ impl ValueInternal { } impl Value { - pub(crate) fn new(value: ValueF, form: Form, ty: Option) -> Value { - ValueInternal { form, value, ty }.into_value() - } - // TODO: this is very wrong - pub(crate) fn from_valuef_untyped(v: ValueF) -> Value { - Value::new(v, Unevaled, None) + fn new(value: ValueF, form: Form, ty: Value) -> Value { + ValueInternal { + form, + value, + ty: Some(ty), + } + .into_value() } pub(crate) fn const_sort() -> Value { - Value::new(ValueF::Const(Const::Sort), NF, None) + ValueInternal { + form: NF, + value: ValueF::Const(Const::Sort), + ty: None, + } + .into_value() } pub(crate) fn from_valuef_and_type(v: ValueF, t: Value) -> Value { - Value::new(v, Unevaled, Some(t)) + Value::new(v, Unevaled, t) } pub(crate) fn from_const(c: Const) -> Self { const_to_value(c) @@ -286,17 +292,9 @@ impl VoVF { ); v } - VoVF::ValueF { val, form } => Value::new(val, form, Some(ty)), + VoVF::ValueF { val, form } => Value::new(val, form, ty), } } - - pub(crate) fn app(self, x: Value) -> VoVF { - VoVF::Value(match self { - VoVF::Value(v) => v.app(x), - // TODO: this is very wrong - VoVF::ValueF { val, .. } => Value::from_valuef_untyped(val).app(x), - }) - } } impl Shift for Value { diff --git a/dhall/src/phase/normalize.rs b/dhall/src/phase/normalize.rs index 9837a8b..77f5023 100644 --- a/dhall/src/phase/normalize.rs +++ b/dhall/src/phase/normalize.rs @@ -11,25 +11,6 @@ use crate::core::valuef::ValueF; use crate::core::var::{AlphaLabel, Shift, Subst}; use crate::phase::Normalized; -// Small helper enum to avoid repetition -enum Ret<'a> { - ValueF(ValueF), - Value(Value), - ValueRef(&'a Value), - Expr(ExprF), -} - -impl<'a> Ret<'a> { - fn into_vovf_whnf(self) -> VoVF { - match self { - Ret::ValueF(v) => v.into_vovf_whnf(), - Ret::Value(v) => v.into_vovf(), - Ret::ValueRef(v) => v.clone().into_vovf(), - Ret::Expr(expr) => ValueF::PartialExpr(expr).into_vovf_whnf(), - } - } -} - // Ad-hoc macro to help construct closures macro_rules! make_closure { (#$var:ident) => { $var.clone() }; @@ -94,80 +75,77 @@ pub(crate) fn apply_builtin(b: Builtin, args: Vec, _ty: &Value) -> VoVF { use dhall_syntax::Builtin::*; use ValueF::*; - // Return Ok((unconsumed args, returned value)), or Err(()) if value could not be produced. + // Small helper enum + enum Ret<'a> { + ValueF(ValueF), + Value(Value), + // For applications that can return a function, it's important to keep the remaining + // arguments to apply them to the resulting function. + ValueWithRemainingArgs(&'a [Value], Value), + DoneAsIs, + } + let ret = match (b, args.as_slice()) { - (OptionalNone, [t, r..]) => { - Ok((r, Ret::ValueF(EmptyOptionalLit(t.clone())))) - } - (NaturalIsZero, [n, r..]) => match &*n.as_whnf() { - NaturalLit(n) => Ok((r, Ret::ValueF(BoolLit(*n == 0)))), - _ => Err(()), + (OptionalNone, [t]) => Ret::ValueF(EmptyOptionalLit(t.clone())), + (NaturalIsZero, [n]) => match &*n.as_whnf() { + NaturalLit(n) => Ret::ValueF(BoolLit(*n == 0)), + _ => Ret::DoneAsIs, }, - (NaturalEven, [n, r..]) => match &*n.as_whnf() { - NaturalLit(n) => Ok((r, Ret::ValueF(BoolLit(*n % 2 == 0)))), - _ => Err(()), + (NaturalEven, [n]) => match &*n.as_whnf() { + NaturalLit(n) => Ret::ValueF(BoolLit(*n % 2 == 0)), + _ => Ret::DoneAsIs, }, - (NaturalOdd, [n, r..]) => match &*n.as_whnf() { - NaturalLit(n) => Ok((r, Ret::ValueF(BoolLit(*n % 2 != 0)))), - _ => Err(()), + (NaturalOdd, [n]) => match &*n.as_whnf() { + NaturalLit(n) => Ret::ValueF(BoolLit(*n % 2 != 0)), + _ => Ret::DoneAsIs, }, - (NaturalToInteger, [n, r..]) => match &*n.as_whnf() { - NaturalLit(n) => Ok((r, Ret::ValueF(IntegerLit(*n as isize)))), - _ => Err(()), + (NaturalToInteger, [n]) => match &*n.as_whnf() { + NaturalLit(n) => Ret::ValueF(IntegerLit(*n as isize)), + _ => Ret::DoneAsIs, }, - (NaturalShow, [n, r..]) => match &*n.as_whnf() { - NaturalLit(n) => Ok(( - r, + (NaturalShow, [n]) => match &*n.as_whnf() { + NaturalLit(n) => { Ret::ValueF(TextLit(vec![InterpolatedTextContents::Text( n.to_string(), - )])), - )), - _ => Err(()), + )])) + } + _ => Ret::DoneAsIs, }, - (NaturalSubtract, [a, b, r..]) => { - match (&*a.as_whnf(), &*b.as_whnf()) { - (NaturalLit(a), NaturalLit(b)) => Ok(( - r, - Ret::ValueF(NaturalLit(if b > a { b - a } else { 0 })), - )), - (NaturalLit(0), _) => Ok((r, Ret::ValueRef(b))), - (_, NaturalLit(0)) => Ok((r, Ret::ValueF(NaturalLit(0)))), - _ if a == b => Ok((r, Ret::ValueF(NaturalLit(0)))), - _ => Err(()), + (NaturalSubtract, [a, b]) => match (&*a.as_whnf(), &*b.as_whnf()) { + (NaturalLit(a), NaturalLit(b)) => { + Ret::ValueF(NaturalLit(if b > a { b - a } else { 0 })) } - } - (IntegerShow, [n, r..]) => match &*n.as_whnf() { + (NaturalLit(0), _) => Ret::Value(b.clone()), + (_, NaturalLit(0)) => Ret::ValueF(NaturalLit(0)), + _ if a == b => Ret::ValueF(NaturalLit(0)), + _ => Ret::DoneAsIs, + }, + (IntegerShow, [n]) => match &*n.as_whnf() { IntegerLit(n) => { let s = if *n < 0 { n.to_string() } else { format!("+{}", n) }; - Ok(( - r, - Ret::ValueF(TextLit(vec![InterpolatedTextContents::Text( - s, - )])), - )) + Ret::ValueF(TextLit(vec![InterpolatedTextContents::Text(s)])) } - _ => Err(()), + _ => Ret::DoneAsIs, }, - (IntegerToDouble, [n, r..]) => match &*n.as_whnf() { + (IntegerToDouble, [n]) => match &*n.as_whnf() { IntegerLit(n) => { - Ok((r, Ret::ValueF(DoubleLit(NaiveDouble::from(*n as f64))))) + Ret::ValueF(DoubleLit(NaiveDouble::from(*n as f64))) } - _ => Err(()), + _ => Ret::DoneAsIs, }, - (DoubleShow, [n, r..]) => match &*n.as_whnf() { - DoubleLit(n) => Ok(( - r, + (DoubleShow, [n]) => match &*n.as_whnf() { + DoubleLit(n) => { Ret::ValueF(TextLit(vec![InterpolatedTextContents::Text( n.to_string(), - )])), - )), - _ => Err(()), + )])) + } + _ => Ret::DoneAsIs, }, - (TextShow, [v, r..]) => match &*v.as_whnf() { + (TextShow, [v]) => match &*v.as_whnf() { TextLit(elts) => { match elts.as_slice() { // Empty string literal. @@ -176,12 +154,9 @@ pub(crate) fn apply_builtin(b: Builtin, args: Vec, _ty: &Value) -> VoVF { let txt: InterpolatedText = std::iter::empty().collect(); let s = txt.to_string(); - Ok(( - r, - Ret::ValueF(TextLit(vec![ - InterpolatedTextContents::Text(s), - ])), - )) + Ret::ValueF(TextLit(vec![ + InterpolatedTextContents::Text(s), + ])) } // If there are no interpolations (invariants ensure that when there are no // interpolations, there is a single Text item) in the literal. @@ -193,54 +168,42 @@ pub(crate) fn apply_builtin(b: Builtin, args: Vec, _ty: &Value) -> VoVF { )) .collect(); let s = txt.to_string(); - Ok(( - r, - Ret::ValueF(TextLit(vec![ - InterpolatedTextContents::Text(s), - ])), - )) + Ret::ValueF(TextLit(vec![ + InterpolatedTextContents::Text(s), + ])) } - _ => Err(()), + _ => Ret::DoneAsIs, } } - _ => Err(()), + _ => Ret::DoneAsIs, }, - (ListLength, [_, l, r..]) => match &*l.as_whnf() { - EmptyListLit(_) => Ok((r, Ret::ValueF(NaturalLit(0)))), - NEListLit(xs) => Ok((r, Ret::ValueF(NaturalLit(xs.len())))), - _ => Err(()), + (ListLength, [_, l]) => match &*l.as_whnf() { + EmptyListLit(_) => Ret::ValueF(NaturalLit(0)), + NEListLit(xs) => Ret::ValueF(NaturalLit(xs.len())), + _ => Ret::DoneAsIs, }, - (ListHead, [_, l, r..]) => match &*l.as_whnf() { - EmptyListLit(n) => { - Ok((r, Ret::ValueF(EmptyOptionalLit(n.clone())))) + (ListHead, [_, l]) => match &*l.as_whnf() { + EmptyListLit(n) => Ret::ValueF(EmptyOptionalLit(n.clone())), + NEListLit(xs) => { + Ret::ValueF(NEOptionalLit(xs.iter().next().unwrap().clone())) } - NEListLit(xs) => Ok(( - r, - Ret::ValueF(NEOptionalLit(xs.iter().next().unwrap().clone())), - )), - _ => Err(()), + _ => Ret::DoneAsIs, }, - (ListLast, [_, l, r..]) => match &*l.as_whnf() { - EmptyListLit(n) => { - Ok((r, Ret::ValueF(EmptyOptionalLit(n.clone())))) - } - NEListLit(xs) => Ok(( - r, - Ret::ValueF(NEOptionalLit( - xs.iter().rev().next().unwrap().clone(), - )), + (ListLast, [_, l]) => match &*l.as_whnf() { + EmptyListLit(n) => Ret::ValueF(EmptyOptionalLit(n.clone())), + NEListLit(xs) => Ret::ValueF(NEOptionalLit( + xs.iter().rev().next().unwrap().clone(), )), - _ => Err(()), + _ => Ret::DoneAsIs, }, - (ListReverse, [_, l, r..]) => match &*l.as_whnf() { - EmptyListLit(n) => Ok((r, Ret::ValueF(EmptyListLit(n.clone())))), - NEListLit(xs) => Ok(( - r, - Ret::ValueF(NEListLit(xs.iter().rev().cloned().collect())), - )), - _ => Err(()), + (ListReverse, [_, l]) => match &*l.as_whnf() { + EmptyListLit(n) => Ret::ValueF(EmptyListLit(n.clone())), + NEListLit(xs) => { + Ret::ValueF(NEListLit(xs.iter().rev().cloned().collect())) + } + _ => Ret::DoneAsIs, }, - (ListIndexed, [_, l, r..]) => { + (ListIndexed, [_, l]) => { let l_whnf = l.as_whnf(); match &*l_whnf { EmptyListLit(_) | NEListLit(_) => { @@ -287,16 +250,16 @@ pub(crate) fn apply_builtin(b: Builtin, args: Vec, _ty: &Value) -> VoVF { ), _ => unreachable!(), }; - Ok((r, Ret::ValueF(list))) + Ret::ValueF(list) } - _ => Err(()), + _ => Ret::DoneAsIs, } } - (ListBuild, [t, f, r..]) => match &*f.as_whnf() { + (ListBuild, [t, f]) => match &*f.as_whnf() { // fold/build fusion ValueF::AppliedBuiltin(ListFold, args) => { if args.len() >= 2 { - Ok((r, Ret::Value(args[1].clone()))) + Ret::Value(args[1].clone()) } else { // Do we really need to handle this case ? unimplemented!() @@ -304,44 +267,41 @@ pub(crate) fn apply_builtin(b: Builtin, args: Vec, _ty: &Value) -> VoVF { } _ => { let list_t = Value::from_builtin(List).app(t.clone()); - Ok(( - r, - Ret::Value( - f.app(list_t.clone()) - .app({ - // Move `t` under new variables - let t1 = t.under_binder(Label::from("x")); - let t2 = t1.under_binder(Label::from("xs")); - make_closure!( - λ(x : #t) -> - λ(xs : List #t1) -> - [ var(x, 1, #t2) ] # var(xs, 0, List #t2) - ) - }) - .app( - EmptyListLit(t.clone()) - .into_value_with_type(list_t), - ), - ), - )) + Ret::Value( + f.app(list_t.clone()) + .app({ + // Move `t` under new variables + let t1 = t.under_binder(Label::from("x")); + let t2 = t1.under_binder(Label::from("xs")); + make_closure!( + λ(x : #t) -> + λ(xs : List #t1) -> + [ var(x, 1, #t2) ] # var(xs, 0, List #t2) + ) + }) + .app( + EmptyListLit(t.clone()) + .into_value_with_type(list_t), + ), + ) } }, (ListFold, [_, l, _, cons, nil, r..]) => match &*l.as_whnf() { - EmptyListLit(_) => Ok((r, Ret::ValueRef(nil))), + EmptyListLit(_) => Ret::ValueWithRemainingArgs(r, nil.clone()), NEListLit(xs) => { let mut v = nil.clone(); for x in xs.iter().cloned().rev() { v = cons.app(x).app(v); } - Ok((r, Ret::Value(v))) + Ret::ValueWithRemainingArgs(r, v) } - _ => Err(()), + _ => Ret::DoneAsIs, }, - (OptionalBuild, [t, f, r..]) => match &*f.as_whnf() { + (OptionalBuild, [t, f]) => match &*f.as_whnf() { // fold/build fusion ValueF::AppliedBuiltin(OptionalFold, args) => { if args.len() >= 2 { - Ok((r, Ret::Value(args[1].clone()))) + Ret::Value(args[1].clone()) } else { // Do we really need to handle this case ? unimplemented!() @@ -349,52 +309,51 @@ pub(crate) fn apply_builtin(b: Builtin, args: Vec, _ty: &Value) -> VoVF { } _ => { let optional_t = Value::from_builtin(Optional).app(t.clone()); - Ok(( - r, - Ret::Value( - f.app(optional_t.clone()) - .app({ - let t1 = t.under_binder(Label::from("x")); - make_closure!(λ(x: #t) -> Some(var(x, 0, #t1))) - }) - .app( - EmptyOptionalLit(t.clone()) - .into_value_with_type(optional_t), - ), - ), - )) + Ret::Value( + f.app(optional_t.clone()) + .app({ + let t1 = t.under_binder(Label::from("x")); + make_closure!(λ(x: #t) -> Some(var(x, 0, #t1))) + }) + .app( + EmptyOptionalLit(t.clone()) + .into_value_with_type(optional_t), + ), + ) } }, (OptionalFold, [_, v, _, just, nothing, r..]) => match &*v.as_whnf() { - EmptyOptionalLit(_) => Ok((r, Ret::ValueRef(nothing))), - NEOptionalLit(x) => Ok((r, Ret::Value(just.app(x.clone())))), - _ => Err(()), + EmptyOptionalLit(_) => { + Ret::ValueWithRemainingArgs(r, nothing.clone()) + } + NEOptionalLit(x) => { + Ret::ValueWithRemainingArgs(r, just.app(x.clone())) + } + _ => Ret::DoneAsIs, }, - (NaturalBuild, [f, r..]) => match &*f.as_whnf() { + (NaturalBuild, [f]) => match &*f.as_whnf() { // fold/build fusion ValueF::AppliedBuiltin(NaturalFold, args) => { if !args.is_empty() { - Ok((r, Ret::Value(args[0].clone()))) + Ret::Value(args[0].clone()) } else { // Do we really need to handle this case ? unimplemented!() } } - _ => Ok(( - r, - Ret::Value( - f.app(Value::from_builtin(Natural)) - .app(make_closure!( - λ(x : Natural) -> 1 + var(x, 0, Natural) - )) - .app(NaturalLit(0).into_value_with_type( - Value::from_builtin(Natural), - )), - ), - )), + _ => Ret::Value( + f.app(Value::from_builtin(Natural)) + .app(make_closure!( + λ(x : Natural) -> 1 + var(x, 0, Natural) + )) + .app( + NaturalLit(0) + .into_value_with_type(Value::from_builtin(Natural)), + ), + ), }, (NaturalFold, [n, t, succ, zero, r..]) => match &*n.as_whnf() { - NaturalLit(0) => Ok((r, Ret::ValueRef(zero))), + NaturalLit(0) => Ret::ValueWithRemainingArgs(r, zero.clone()), NaturalLit(n) => { let fold = Value::from_builtin(NaturalFold) .app( @@ -404,22 +363,23 @@ pub(crate) fn apply_builtin(b: Builtin, args: Vec, _ty: &Value) -> VoVF { .app(t.clone()) .app(succ.clone()) .app(zero.clone()); - Ok((r, Ret::Value(succ.app(fold)))) + Ret::ValueWithRemainingArgs(r, succ.app(fold)) } - _ => Err(()), + _ => Ret::DoneAsIs, }, - _ => Err(()), + _ => Ret::DoneAsIs, }; match ret { - Ok((unconsumed_args, ret)) => { - let mut v = ret.into_vovf_whnf(); + Ret::ValueF(v) => v.into_vovf_whnf(), + Ret::Value(v) => v.into_vovf(), + Ret::ValueWithRemainingArgs(unconsumed_args, mut v) => { let n_consumed_args = args.len() - unconsumed_args.len(); for x in args.into_iter().skip(n_consumed_args) { v = v.app(x); } - v + v.into_vovf() } - Err(()) => AppliedBuiltin(b, args).into_vovf_whnf(), + Ret::DoneAsIs => AppliedBuiltin(b, args).into_vovf_whnf(), } } @@ -514,6 +474,14 @@ where Ok(kvs) } +// Small helper enum to avoid repetition +enum Ret<'a> { + ValueF(ValueF), + Value(Value), + ValueRef(&'a Value), + Expr(ExprF), +} + fn apply_binop<'a>( o: BinOp, x: &'a Value, @@ -797,7 +765,12 @@ pub(crate) fn normalize_one_layer( } }; - ret.into_vovf_whnf() + match ret { + Ret::ValueF(v) => v.into_vovf_whnf(), + Ret::Value(v) => v.into_vovf(), + Ret::ValueRef(v) => v.clone().into_vovf(), + Ret::Expr(expr) => ValueF::PartialExpr(expr).into_vovf_whnf(), + } } /// Normalize a ValueF into WHNF diff --git a/tests_buffer b/tests_buffer index 93eb626..1ad880e 100644 --- a/tests_buffer +++ b/tests_buffer @@ -28,6 +28,8 @@ variables across import boundaries TextLitNested1 "${""}${x}" TextLitNested2 "${"${x}"}" TextLitNested3 "${"${""}"}${x}" + regression/ + NaturalFoldExtraArg Natural/fold 0 (Bool -> Bool) (λ(_ : (Bool -> Bool)) → λ(_ : Bool) → True) (λ(_ : Bool) → False) True typecheck: something that involves destructuring a recordtype after merge -- cgit v1.3.1