From efe4b340bebaa7ef8bce6e69194959b126c5fade Mon Sep 17 00:00:00 2001
From: Nadrieril
Date: Fri, 10 Apr 2020 12:10:53 +0100
Subject: Deserialize `Prelude.Map` and `toMap` to a map instead of a list

---
 README.md                             |  4 +++
 dhall/src/operations/normalization.rs |  4 +--
 dhall/src/operations/typecheck.rs     |  6 ++--
 dhall/src/syntax/ast/label.rs         |  6 ++++
 serde_dhall/src/static_type.rs        |  1 +
 serde_dhall/src/value.rs              | 58 ++++++++++++++++++++++++++++++++---
 serde_dhall/tests/de.rs               |  4 +++
 tests_buffer                          |  1 +
 8 files changed, 74 insertions(+), 10 deletions(-)

diff --git a/README.md b/README.md
index 6d9372b..e4411d3 100644
--- a/README.md
+++ b/README.md
@@ -169,8 +169,12 @@ same name as the corresponding test.
 
 #### [Unreleased]
 
+- Deserialize `Prelude.Map` and `toMap` to a map instead of a list.
+
 #### [0.5.1] - 2020-04-09
 
+- Small fixes
+
 #### [0.5.0] - 2020-04-05
 
 - Add `serde_dhall::from_file` to read a Dhall file directly.
diff --git a/dhall/src/operations/normalization.rs b/dhall/src/operations/normalization.rs
index e3a9415..86fed13 100644
--- a/dhall/src/operations/normalization.rs
+++ b/dhall/src/operations/normalization.rs
@@ -226,11 +226,11 @@ pub fn normalize_operation(opkind: &OpKind<Nir>) -> Ret {
                     Some(h) => ret_kind(h.app_to_kind(v.clone())),
                     None => nothing_to_do(),
                 },
-                EmptyOptionalLit(_) => match kvs.get(&"None".into()) {
+                EmptyOptionalLit(_) => match kvs.get("None") {
                     Some(h) => ret_ref(h),
                     None => nothing_to_do(),
                 },
-                NEOptionalLit(v) => match kvs.get(&"Some".into()) {
+                NEOptionalLit(v) => match kvs.get("Some") {
                     Some(h) => ret_kind(h.app_to_kind(v.clone())),
                     None => nothing_to_do(),
                 },
diff --git a/dhall/src/operations/typecheck.rs b/dhall/src/operations/typecheck.rs
index 91d5059..314c587 100644
--- a/dhall/src/operations/typecheck.rs
+++ b/dhall/src/operations/typecheck.rs
@@ -398,11 +398,11 @@ pub fn typecheck_operation(
                 if kts.len() != 2 {
                     return span_err(err_msg);
                 }
-                match kts.get(&"mapKey".into()) {
+                match kts.get("mapKey") {
                     Some(t) if *t == Nir::from_builtin(Builtin::Text) => {}
                     _ => return span_err(err_msg),
                 }
-                match kts.get(&"mapValue".into()) {
+                match kts.get("mapValue") {
                     Some(_) => {}
                     None => return span_err(err_msg),
                 }
@@ -434,7 +434,7 @@ pub fn typecheck_operation(
         }
         Field(scrut, x) => {
             match scrut.ty().kind() {
-                RecordType(kts) => match kts.get(&x) {
+                RecordType(kts) => match kts.get(x) {
                     Some(val) => Type::new_infer_universe(env, val.clone())?,
                     None => return span_err("MissingRecordField"),
                 },
diff --git a/dhall/src/syntax/ast/label.rs b/dhall/src/syntax/ast/label.rs
index 43c3f53..2abde80 100644
--- a/dhall/src/syntax/ast/label.rs
+++ b/dhall/src/syntax/ast/label.rs
@@ -24,6 +24,12 @@ impl From<&Label> for String {
     }
 }
 
+impl std::borrow::Borrow<str> for Label {
+    fn borrow(&self) -> &str {
+        self.0.as_ref()
+    }
+}
+
 impl Label {
     pub fn from_str(s: &str) -> Label {
         Label(s.into())
diff --git a/serde_dhall/src/static_type.rs b/serde_dhall/src/static_type.rs
index 26c70cd..3c5da18 100644
--- a/serde_dhall/src/static_type.rs
+++ b/serde_dhall/src/static_type.rs
@@ -47,6 +47,7 @@ use crate::SimpleType;
 /// `{ _1: T, _2: U }`  | `(T, U)`, structs
 /// `{ x: T, y: T }`  | `HashMap<String, T>`, structs
 /// `< x: T \| y: U >`  | enums
+/// `Prelude.Map.Type Text T`  | `HashMap<String, T>`, structs
 /// `T -> U`  | unsupported
 /// `Prelude.JSON.Type`  | unsupported
 /// `Prelude.Map.Type T U`  | unsupported
diff --git a/serde_dhall/src/value.rs b/serde_dhall/src/value.rs
index 03cfdba..0f0b256 100644
--- a/serde_dhall/src/value.rs
+++ b/serde_dhall/src/value.rs
@@ -144,13 +144,61 @@ impl SimpleValue {
             NirKind::NEOptionalLit(x) => {
                 SimpleValue::Optional(Some(Box::new(Self::from_nir(x)?)))
             }
-            NirKind::EmptyListLit(_) => SimpleValue::List(vec![]),
-            NirKind::NEListLit(xs) => SimpleValue::List(
-                xs.iter().map(Self::from_nir).collect::<Option<_>>()?,
-            ),
+            NirKind::EmptyListLit(t) => {
+                // Detect and handle the special records that make assoc maps
+                if let NirKind::RecordType(kts) = t.kind() {
+                    if kts.len() == 2
+                        && kts.contains_key("mapKey")
+                        && kts.contains_key("mapValue")
+                    {
+                        return Some(SimpleValue::Record(Default::default()));
+                    }
+                }
+                SimpleValue::List(vec![])
+            }
+            NirKind::NEListLit(xs) => {
+                // Detect and handle the special records that make assoc maps
+                if let NirKind::RecordLit(kvs) = xs[0].kind() {
+                    if kvs.len() == 2
+                        && kvs.contains_key("mapKey")
+                        && kvs.contains_key("mapValue")
+                    {
+                        let convert_entry = |x: &Nir| match x.kind() {
+                            NirKind::RecordLit(kvs) => {
+                                let k = match kvs.get("mapKey").unwrap().kind()
+                                {
+                                    NirKind::TextLit(t)
+                                        if t.as_text().is_some() =>
+                                    {
+                                        t.as_text().unwrap()
+                                    }
+                                    // TODO
+                                    _ => panic!(
+                                        "Expected `mapKey` to be a text \
+                                         literal"
+                                    ),
+                                };
+                                let v = Self::from_nir(
+                                    kvs.get("mapValue").unwrap(),
+                                )?;
+                                Some((k, v))
+                            }
+                            _ => unreachable!("Internal type error"),
+                        };
+                        return Some(SimpleValue::Record(
+                            xs.iter()
+                                .map(convert_entry)
+                                .collect::<Option<_>>()?,
+                        ));
+                    }
+                }
+                SimpleValue::List(
+                    xs.iter().map(Self::from_nir).collect::<Option<_>>()?,
+                )
+            }
             NirKind::RecordLit(kvs) => SimpleValue::Record(
                 kvs.iter()
-                    .map(|(k, v)| Some((k.into(), Self::from_nir(v)?)))
+                    .map(|(k, v)| Some((k.to_string(), Self::from_nir(v)?)))
                     .collect::<Option<_>>()?,
             ),
             NirKind::UnionLit(field, x, _) => SimpleValue::Union(
diff --git a/serde_dhall/tests/de.rs b/serde_dhall/tests/de.rs
index 41b4080..1932e26 100644
--- a/serde_dhall/tests/de.rs
+++ b/serde_dhall/tests/de.rs
@@ -80,6 +80,10 @@ fn test_de_untyped() {
         parse::<HashMap<String, usize>>("{ x = 1, y = 2 }"),
         expected_map
     );
+    assert_eq!(
+        parse::<HashMap<String, usize>>("toMap { x = 1, y = 2 }"),
+        expected_map
+    );
 
     let mut expected_map = HashMap::new();
     expected_map.insert("if".to_string(), 1);
diff --git a/tests_buffer b/tests_buffer
index bd76d36..3240b41 100644
--- a/tests_buffer
+++ b/tests_buffer
@@ -24,6 +24,7 @@ normalization:
 move builtins and operators in their own folder ?
 RecordSortFields { b = 1, a = 0 }
 RecordTypeSortFields { b : Bool, a : Natural }
+\(foo: { x: Bool, y: Bool }) -> \(bar: { x: Bool }) -> (foo.{x, y} // bar).{ x }
 
 type-inference:
 something that involves destructuring a recordtype after merge
-- 
cgit v1.2.3