summaryrefslogtreecommitdiff
path: root/dhall
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dhall/Cargo.toml5
-rw-r--r--dhall/build.rs4
-rw-r--r--dhall/src/error.rs32
-rw-r--r--dhall/src/expr.rs21
-rw-r--r--dhall/src/imports.rs74
-rw-r--r--dhall/src/lib.rs124
-rw-r--r--dhall/src/main.rs2
-rw-r--r--dhall/src/normalize.rs2
-rw-r--r--dhall/src/serde.rs69
-rw-r--r--dhall/src/tests.rs218
-rw-r--r--dhall/src/traits.rs77
-rw-r--r--dhall/src/traits/deserialize.rs46
-rw-r--r--dhall/src/traits/mod.rs4
-rw-r--r--dhall/src/traits/static_type.rs128
-rw-r--r--dhall/src/typecheck.rs34
-rw-r--r--dhall/tests/traits.rs54
-rw-r--r--dhall_core/src/core.rs1
-rw-r--r--dhall_core/src/label.rs3
-rw-r--r--dhall_generator/src/derive.rs35
-rw-r--r--dhall_generator/src/lib.rs6
20 files changed, 638 insertions, 301 deletions
diff --git a/dhall/Cargo.toml b/dhall/Cargo.toml
index e898cde..7f695d4 100644
--- a/dhall/Cargo.toml
+++ b/dhall/Cargo.toml
@@ -5,9 +5,6 @@ authors = ["NanoTech <nanotech@nanotechcorp.net>", "Nadrieril <nadrieril@users.n
edition = "2018"
build = "build.rs"
-[lib]
-doctest = false
-
[features]
nothreads = [] # disable threads for tarpaulin
@@ -16,6 +13,8 @@ bytecount = "0.5.1"
itertools = "0.8.0"
lalrpop-util = "0.16.3"
term-painter = "0.2.3"
+quick-error = "1.2.2"
+serde = { version = "1.0", features = ["derive"] }
serde_cbor = "0.9.0"
dhall_core = { path = "../dhall_core" }
dhall_generator = { path = "../dhall_generator" }
diff --git a/dhall/build.rs b/dhall/build.rs
index 878493d..c258315 100644
--- a/dhall/build.rs
+++ b/dhall/build.rs
@@ -41,7 +41,7 @@ fn main() -> std::io::Result<()> {
}
writeln!(
file,
- r#"make_spec_test!(ParserSuccess, success_{}, "{}");"#,
+ r#"make_spec_test!(Parser, Success, success_{}, "{}");"#,
name, path
)?;
}
@@ -50,7 +50,7 @@ fn main() -> std::io::Result<()> {
let name = path.replace("/", "_");
writeln!(
file,
- r#"make_spec_test!(ParserFailure, failure_{}, "{}");"#,
+ r#"make_spec_test!(Parser, Failure, failure_{}, "{}");"#,
name, path
)?;
}
diff --git a/dhall/src/error.rs b/dhall/src/error.rs
new file mode 100644
index 0000000..cfd6f09
--- /dev/null
+++ b/dhall/src/error.rs
@@ -0,0 +1,32 @@
+use quick_error::quick_error;
+
+pub type Result<T> = std::result::Result<T, Error>;
+
+quick_error! {
+ #[derive(Debug)]
+ pub enum Error {
+ IO(err: std::io::Error) {
+ from()
+ display("{}", err)
+ }
+ Parse(err: dhall_core::ParseError) {
+ from()
+ display("{}", err)
+ }
+ Decode(err: crate::binary::DecodeError) {
+ from()
+ display("{:?}", err)
+ }
+ Resolve(err: crate::imports::ImportError) {
+ from()
+ display("{}", err)
+ }
+ Typecheck(err: crate::typecheck::TypeError<dhall_core::X>) {
+ from()
+ display("{:?}", err)
+ }
+ Deserialize(err: String) {
+ display("{}", err)
+ }
+ }
+}
diff --git a/dhall/src/expr.rs b/dhall/src/expr.rs
index 7baf628..5ff097b 100644
--- a/dhall/src/expr.rs
+++ b/dhall/src/expr.rs
@@ -36,6 +36,11 @@ derive_other_traits!(Typed);
pub struct Normalized(pub(crate) SubExpr<X, X>, pub(crate) Type);
derive_other_traits!(Normalized);
+/// An expression of type `Type` (like `Bool` or `Natural -> Text`, but not `Type`)
+#[derive(Debug, Clone, Eq)]
+pub struct SimpleType(pub(crate) SubExpr<X, X>);
+derive_other_traits!(SimpleType);
+
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Type(pub(crate) TypeInternal);
@@ -44,3 +49,19 @@ pub(crate) enum TypeInternal {
Expr(Box<Normalized>),
Untyped,
}
+
+// 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)
+ }
+}
diff --git a/dhall/src/imports.rs b/dhall/src/imports.rs
index fdde8c3..7810c55 100644
--- a/dhall/src/imports.rs
+++ b/dhall/src/imports.rs
@@ -1,46 +1,17 @@
-// use dhall_core::{Expr, FilePrefix, Import, ImportLocation, ImportMode, X};
-use dhall_core::{Expr, Import, X};
-// use std::path::Path;
-use crate::binary::DecodeError;
+use crate::error::Error;
use crate::expr::*;
use dhall_core::*;
-use std::fmt;
+use quick_error::quick_error;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use std::path::PathBuf;
-#[derive(Debug)]
-pub enum ImportError {
- ParseError(ParseError),
- BinaryDecodeError(DecodeError),
- IOError(std::io::Error),
- UnexpectedImportError(Import),
-}
-impl From<ParseError> for ImportError {
- fn from(e: ParseError) -> Self {
- ImportError::ParseError(e)
- }
-}
-impl From<DecodeError> for ImportError {
- fn from(e: DecodeError) -> Self {
- ImportError::BinaryDecodeError(e)
- }
-}
-impl From<std::io::Error> for ImportError {
- fn from(e: std::io::Error) -> Self {
- ImportError::IOError(e)
- }
-}
-impl fmt::Display for ImportError {
- fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
- use self::ImportError::*;
- match self {
- ParseError(e) => e.fmt(f),
- BinaryDecodeError(_) => unimplemented!(),
- IOError(e) => e.fmt(f),
- UnexpectedImportError(e) => e.fmt(f),
- }
+quick_error! {
+ #[derive(Debug)]
+ pub enum ImportError {
+ Recursive(import: Import, err: Box<Error>) {}
+ UnexpectedImport(import: Import) {}
}
}
@@ -53,7 +24,7 @@ pub enum ImportRoot {
fn resolve_import(
import: &Import,
root: &ImportRoot,
-) -> Result<Expr<X, X>, ImportError> {
+) -> Result<Resolved, ImportError> {
use self::ImportRoot::*;
use dhall_core::FilePrefix::*;
use dhall_core::ImportLocation::*;
@@ -63,16 +34,23 @@ fn resolve_import(
match &import.location_hashed.location {
Local(prefix, path) => {
let path = match prefix {
+ // TODO: fail gracefully
Parent => cwd.parent().unwrap().join(path),
Here => cwd.join(path),
_ => unimplemented!("{:?}", import),
};
- load_dhall_file(&path, true)
+ Ok(load_import(&path).map_err(|e| {
+ ImportError::Recursive(import.clone(), Box::new(e))
+ })?)
}
_ => unimplemented!("{:?}", import),
}
}
+fn load_import(f: &Path) -> Result<Resolved, Error> {
+ Ok(Parsed::parse_file(f)?.resolve()?)
+}
+
fn resolve_expr(
Parsed(expr, root): Parsed,
allow_imports: bool,
@@ -80,9 +58,9 @@ fn resolve_expr(
let resolve = |import: &Import| -> Result<SubExpr<X, X>, ImportError> {
if allow_imports {
let expr = resolve_import(import, &root)?;
- Ok(expr.roll())
+ Ok(expr.0)
} else {
- Err(ImportError::UnexpectedImportError(import.clone()))
+ Err(ImportError::UnexpectedImport(import.clone()))
}
};
let expr = expr.as_ref().traverse_embed(&resolve)?;
@@ -90,7 +68,7 @@ fn resolve_expr(
}
impl Parsed {
- pub fn load_from_file(f: &Path) -> Result<Parsed, ImportError> {
+ 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)?;
@@ -98,13 +76,13 @@ impl Parsed {
Ok(Parsed(expr, root))
}
- pub fn load_from_str(s: &str) -> Result<Parsed, ImportError> {
+ 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))
}
- pub fn load_from_binary_file(f: &Path) -> Result<Parsed, ImportError> {
+ 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)?;
@@ -119,13 +97,3 @@ impl Parsed {
crate::imports::resolve_expr(self, false)
}
}
-
-// Deprecated
-pub fn load_dhall_file(
- f: &Path,
- resolve_imports: bool,
-) -> Result<Expr<X, X>, ImportError> {
- let expr = Parsed::load_from_file(f)?;
- let expr = resolve_expr(expr, resolve_imports)?;
- Ok(expr.0.unroll())
-}
diff --git a/dhall/src/lib.rs b/dhall/src/lib.rs
index 5c5a641..8af5af9 100644
--- a/dhall/src/lib.rs
+++ b/dhall/src/lib.rs
@@ -9,6 +9,111 @@
clippy::many_single_char_names
)]
+//! [Dhall][dhall] is a programmable configuration language that provides a non-repetitive
+//! alternative to JSON and YAML.
+//!
+//! You can think of Dhall as: JSON/YAML + types + imports + functions
+//!
+//! For a description of the dhall language, examples, tutorials, and more, see the [language
+//! website][dhall].
+//!
+//! This crate provides support for consuming dhall files the same way you would consume JSON or
+//! YAML. It uses the [Serde][serde] serialization library to provide drop-in support for dhall
+//! for any datatype that supports serde (and that's a lot of them !).
+//!
+//! This library is limited to deserializing (reading) dhall values; serializing (writing)
+//! values to dhall is not supported for now.
+//!
+//! # Examples
+//!
+//! ### Custom datatype
+//!
+//! If you have a custom datatype for which you derived [serde::Deserialize], chances are
+//! you will be able to derive [SimpleStaticType] for it as well. This gives you access to
+//! a dhall representation of your datatype that can be outputted to users, and
+//! allows easy type-safe deserializing.
+//!
+//! ```edition2018
+//! use serde::Deserialize;
+//! use dhall::SimpleStaticType;
+//!
+//! #[derive(Debug, Deserialize, SimpleStaticType)]
+//! struct Point {
+//! x: u64,
+//! y: u64,
+//! }
+//!
+//! fn main() {
+//! // Some dhall data
+//! let data = "{ x = 1, y = 1 + 1 }";
+//!
+//! // Convert the dhall string to a Point.
+//! let point: Point =
+//! dhall::from_str_auto_type(&data)
+//! .expect("An error ocurred !");
+//!
+//! // Prints "point = Point { x: 1, y: 2 }"
+//! println!("point = {:?}", point);
+//! }
+//! ```
+//!
+//! ### Loosely typed
+//!
+//! If you used to consume JSON or YAML in a loosely typed way, you can continue to do so
+//! with dhall. You only need to replace [serde_json::from_str] or [serde_yaml::from_str]
+//! with [dhall::from_str][from_str].
+//! More generally, if the [SimpleStaticType] derive doesn't suit your needs, you can still
+//! deserialize any valid dhall file that serde can handle.
+//!
+//! [serde_json::from_str]: https://docs.serde.rs/serde_json/de/fn.from_str.html
+//! [serde_yaml::from_str]: https://docs.serde.rs/serde_yaml/fn.from_str.html
+//!
+//! ```edition2018
+//! use std::collections::BTreeMap;
+//!
+//! let mut map = BTreeMap::new();
+//! map.insert("x".to_string(), 1);
+//! map.insert("y".to_string(), 2);
+//!
+//! // Some dhall data
+//! let data = "{ x = 1, y = 1 + 1 } : { x: Natural, y: Natural }";
+//!
+//! // Deserialize it to a Rust type.
+//! let deserialized_map: BTreeMap<String, usize> =
+//! dhall::from_str(&data, None)
+//! .expect("Failed reading the data !");
+//! assert_eq!(map, deserialized_map);
+//! ```
+//!
+//! You can of course specify a dhall type that the input should match.
+//!
+//! ```edition2018
+//! use std::collections::BTreeMap;
+//!
+//! let mut map = BTreeMap::new();
+//! map.insert("x".to_string(), 1);
+//! map.insert("y".to_string(), 2);
+//!
+//! // Some dhall data
+//! let point_data = "{ x = 1, y = 1 + 1 }";
+//! let point_type_data = "{ x: Natural, y: Natural }";
+//!
+//! // Construct a type
+//! let point_type =
+//! dhall::from_str(point_type_data, None)
+//! .expect("Could not parse the Point type");
+//!
+//! // Deserialize it to a Rust type.
+//! let deserialized_map: BTreeMap<String, usize> =
+//! dhall::from_str(&point_data, Some(&point_type))
+//! .expect("Failed reading the data !");
+//! assert_eq!(map, deserialized_map);
+//! ```
+//!
+//! [dhall]: https://dhall-lang.org/
+//! [serde]: https://docs.serde.rs/serde/
+//! [serde::Deserialize]: https://docs.serde.rs/serde/trait.Deserialize.html
+
#[cfg(test)]
#[macro_use]
mod tests;
@@ -21,6 +126,23 @@ mod imports;
mod normalize;
mod traits;
mod typecheck;
+pub use crate::traits::Deserialize;
+pub use crate::traits::SimpleStaticType;
pub use crate::traits::StaticType;
-pub use dhall_generator::StaticType;
+pub use dhall_generator::SimpleStaticType;
+pub mod error;
pub mod expr;
+pub mod serde;
+
+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 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/main.rs b/dhall/src/main.rs
index 2881d5a..3b61a44 100644
--- a/dhall/src/main.rs
+++ b/dhall/src/main.rs
@@ -55,7 +55,7 @@ fn main() {
let mut buffer = String::new();
io::stdin().read_to_string(&mut buffer).unwrap();
- let expr = match dhall::expr::Parsed::load_from_str(&buffer) {
+ let expr = match dhall::expr::Parsed::parse_str(&buffer) {
Ok(expr) => expr,
Err(e) => {
print_error(&format!("Parse error {}", e), &buffer, 0, 0);
diff --git a/dhall/src/normalize.rs b/dhall/src/normalize.rs
index d7f1848..608e6ee 100644
--- a/dhall/src/normalize.rs
+++ b/dhall/src/normalize.rs
@@ -337,7 +337,7 @@ mod spec_tests {
macro_rules! norm {
($name:ident, $path:expr) => {
- make_spec_test!(Normalization, $name, $path);
+ make_spec_test!(Normalization, Success, $name, $path);
};
}
diff --git a/dhall/src/serde.rs b/dhall/src/serde.rs
new file mode 100644
index 0000000..196bda1
--- /dev/null
+++ b/dhall/src/serde.rs
@@ -0,0 +1,69 @@
+use crate::error::{Error, Result};
+use crate::expr::{Normalized, Type};
+use crate::traits::Deserialize;
+use dhall_core::*;
+use std::borrow::Cow;
+
+impl<'a, T: serde::Deserialize<'a>> Deserialize<'a> for T {
+ fn from_str(s: &'a str, ty: Option<&Type>) -> Result<Self> {
+ let expr = Normalized::from_str(s, ty)?;
+ T::deserialize(Deserializer(Cow::Owned(expr.0)))
+ }
+}
+
+struct Deserializer<'a>(Cow<'a, SubExpr<X, X>>);
+
+impl serde::de::Error for Error {
+ fn custom<T>(msg: T) -> Self
+ where
+ T: std::fmt::Display,
+ {
+ Error::Deserialize(msg.to_string())
+ }
+}
+
+impl<'de: 'a, 'a> serde::de::IntoDeserializer<'de, Error> for Deserializer<'a> {
+ type Deserializer = Deserializer<'a>;
+ fn into_deserializer(self) -> Self::Deserializer {
+ self
+ }
+}
+
+impl<'de: 'a, 'a> serde::Deserializer<'de> for Deserializer<'a> {
+ type Error = Error;
+ fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ use std::convert::TryInto;
+ use ExprF::*;
+ match self.0.as_ref().as_ref() {
+ NaturalLit(n) => match (*n).try_into() {
+ Ok(n64) => visitor.visit_u64(n64),
+ Err(_) => match (*n).try_into() {
+ Ok(n32) => visitor.visit_u32(n32),
+ Err(_) => unimplemented!(),
+ },
+ },
+ IntegerLit(n) => match (*n).try_into() {
+ Ok(n64) => visitor.visit_i64(n64),
+ Err(_) => match (*n).try_into() {
+ Ok(n32) => visitor.visit_i32(n32),
+ Err(_) => unimplemented!(),
+ },
+ },
+ RecordLit(m) => visitor.visit_map(
+ serde::de::value::MapDeserializer::new(m.iter().map(
+ |(k, v)| (k.as_ref(), Deserializer(Cow::Borrowed(v))),
+ )),
+ ),
+ _ => unimplemented!(),
+ }
+ }
+
+ serde::forward_to_deserialize_any! {
+ bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
+ bytes byte_buf option unit unit_struct newtype_struct seq tuple
+ tuple_struct map struct enum identifier ignored_any
+ }
+}
diff --git a/dhall/src/tests.rs b/dhall/src/tests.rs
index ebb8855..23ec1f4 100644
--- a/dhall/src/tests.rs
+++ b/dhall/src/tests.rs
@@ -19,160 +19,134 @@ right: `{}`"#,
#[macro_export]
macro_rules! make_spec_test {
- ($type:ident, $name:ident, $path:expr) => {
+ ($type:ident, $status:ident, $name:ident, $path:expr) => {
#[test]
#[allow(non_snake_case)]
fn $name() {
use crate::tests::*;
- run_test($path, Feature::$type);
+ // Many tests stack overflow in debug mode
+ std::thread::Builder::new()
+ .stack_size(4 * 1024 * 1024)
+ .spawn(|| {
+ run_test($path, Feature::$type, Status::$status)
+ .map_err(|e| println!("{}", e))
+ .unwrap();
+ })
+ .unwrap()
+ .join()
+ .unwrap();
}
};
}
-use crate::imports::ImportError;
-use crate::*;
-use dhall_core::*;
-use dhall_generator as dhall;
+use crate::error::{Error, Result};
+use crate::expr::Parsed;
use std::path::PathBuf;
-#[allow(dead_code)]
+#[derive(Copy, Clone)]
pub enum Feature {
- ParserSuccess,
- ParserFailure,
+ Parser,
Normalization,
- TypecheckSuccess,
- TypecheckFailure,
- TypeInferenceSuccess,
- TypeInferenceFailure,
+ Typecheck,
+ TypeInference,
}
-// Deprecated
-fn read_dhall_file<'i>(file_path: &str) -> Result<Expr<X, X>, ImportError> {
- crate::imports::load_dhall_file(&PathBuf::from(file_path), true)
+#[derive(Copy, Clone)]
+pub enum Status {
+ Success,
+ Failure,
}
-fn load_from_file_str<'i>(
- file_path: &str,
-) -> Result<crate::expr::Parsed, ImportError> {
- crate::expr::Parsed::load_from_file(&PathBuf::from(file_path))
+fn parse_file_str<'i>(file_path: &str) -> Result<Parsed> {
+ Parsed::parse_file(&PathBuf::from(file_path))
}
-fn load_from_binary_file_str<'i>(
- file_path: &str,
-) -> Result<crate::expr::Parsed, ImportError> {
- crate::expr::Parsed::load_from_binary_file(&PathBuf::from(file_path))
+fn parse_binary_file_str<'i>(file_path: &str) -> Result<Parsed> {
+ Parsed::parse_binary_file(&PathBuf::from(file_path))
}
-pub fn run_test(base_path: &str, feature: Feature) {
+pub fn run_test(
+ base_path: &str,
+ feature: Feature,
+ status: Status,
+) -> Result<()> {
use self::Feature::*;
- let base_path_prefix = match feature {
- ParserSuccess => "parser/success/",
- ParserFailure => "parser/failure/",
- Normalization => "normalization/success/",
- TypecheckSuccess => "typecheck/success/",
- TypecheckFailure => "typecheck/failure/",
- TypeInferenceSuccess => "type-inference/success/",
- TypeInferenceFailure => "type-inference/failure/",
+ use self::Status::*;
+ let feature_prefix = match feature {
+ Parser => "parser/",
+ Normalization => "normalization/",
+ Typecheck => "typecheck/",
+ TypeInference => "type-inference/",
+ };
+ let status_prefix = match status {
+ Success => "success/",
+ Failure => "failure/",
};
- let base_path =
- "../dhall-lang/tests/".to_owned() + base_path_prefix + base_path;
- match feature {
- ParserSuccess => {
+ let base_path = "../dhall-lang/tests/".to_owned()
+ + feature_prefix
+ + status_prefix
+ + base_path;
+ match status {
+ Success => {
let expr_file_path = base_path.clone() + "A.dhall";
- let expected_file_path = base_path + "B.dhallb";
- let expr = load_from_file_str(&expr_file_path)
- .map_err(|e| println!("{}", e))
- .unwrap();
+ let expr = parse_file_str(&expr_file_path)?;
- let expected = load_from_binary_file_str(&expected_file_path)
- .map_err(|e| println!("{}", e))
- .unwrap();
+ if let Parser = feature {
+ let expected_file_path = base_path + "B.dhallb";
+ let expected = parse_binary_file_str(&expected_file_path)?;
- assert_eq_pretty!(expr, expected);
+ assert_eq_pretty!(expr, expected);
- // Round-trip pretty-printer
- let expr =
- crate::expr::Parsed::load_from_str(&expr.to_string()).unwrap();
- assert_eq!(expr, expected);
- }
- ParserFailure => {
- let file_path = base_path + ".dhall";
- let err = load_from_file_str(&file_path).unwrap_err();
- match err {
- ImportError::ParseError(_) => {}
- e => panic!("Expected parse error, got: {:?}", e),
+ // Round-trip pretty-printer
+ let expr: Parsed = crate::from_str(&expr.to_string(), None)?;
+ assert_eq!(expr, expected);
+
+ return Ok(());
}
- }
- Normalization => {
- let expr_file_path = base_path.clone() + "A.dhall";
+
+ let expr = expr.resolve()?;
+
let expected_file_path = base_path + "B.dhall";
- let expr = load_from_file_str(&expr_file_path)
- .unwrap()
- .resolve()
- .unwrap()
- .skip_typecheck()
- .normalize();
- let expected = load_from_file_str(&expected_file_path)
- .unwrap()
- .resolve()
- .unwrap()
+ let expected = parse_file_str(&expected_file_path)?
+ .resolve()?
.skip_typecheck()
- .normalize();
+ .skip_normalize();
- assert_eq_display!(expr, expected);
- }
- TypecheckFailure => {
- let file_path = base_path + ".dhall";
- load_from_file_str(&file_path)
- .unwrap()
- .skip_resolve()
- .unwrap()
- .typecheck()
- .unwrap_err();
- }
- TypecheckSuccess => {
- // Many tests stack overflow in debug mode
- std::thread::Builder::new()
- .stack_size(4 * 1024 * 1024)
- .spawn(|| {
- let expr_file_path = base_path.clone() + "A.dhall";
- let expected_file_path = base_path + "B.dhall";
- let expr = rc(read_dhall_file(&expr_file_path).unwrap());
- let expected =
- rc(read_dhall_file(&expected_file_path).unwrap());
- typecheck::type_of(dhall::subexpr!(expr: expected))
- .unwrap();
- })
- .unwrap()
- .join()
- .unwrap();
+ match feature {
+ Parser => unreachable!(),
+ Typecheck => {
+ expr.typecheck_with(&expected.into_type())?;
+ }
+ TypeInference => {
+ let expr = expr.typecheck()?;
+ let ty = expr.get_type().as_normalized()?;
+ assert_eq_display!(ty, &expected);
+ }
+ Normalization => {
+ let expr = expr.skip_typecheck().normalize();
+ assert_eq_display!(expr, expected);
+ }
+ }
}
- TypeInferenceFailure => {
+ Failure => {
let file_path = base_path + ".dhall";
- load_from_file_str(&file_path)
- .unwrap()
- .skip_resolve()
- .unwrap()
- .typecheck()
- .unwrap_err();
- }
- TypeInferenceSuccess => {
- let expr_file_path = base_path.clone() + "A.dhall";
- let expected_file_path = base_path + "B.dhall";
- let expr = load_from_file_str(&expr_file_path)
- .unwrap()
- .skip_resolve()
- .unwrap()
- .typecheck()
- .unwrap();
- let ty = expr.get_type().as_normalized().unwrap();
- let expected = load_from_file_str(&expected_file_path)
- .unwrap()
- .skip_resolve()
- .unwrap()
- .skip_typecheck()
- .skip_normalize();
- assert_eq_display!(ty, &expected);
+ match feature {
+ Parser => {
+ let err = parse_file_str(&file_path).unwrap_err();
+ match err {
+ Error::Parse(_) => {}
+ e => panic!("Expected parse error, got: {:?}", e),
+ }
+ }
+ Normalization => unreachable!(),
+ Typecheck | TypeInference => {
+ parse_file_str(&file_path)?
+ .skip_resolve()?
+ .typecheck()
+ .unwrap_err();
+ }
+ }
}
}
+ Ok(())
}
diff --git a/dhall/src/traits.rs b/dhall/src/traits.rs
deleted file mode 100644
index 64e07d9..0000000
--- a/dhall/src/traits.rs
+++ /dev/null
@@ -1,77 +0,0 @@
-use dhall_core::*;
-use dhall_generator::*;
-
-#[derive(Debug, Clone)]
-pub enum ConversionError {}
-
-pub trait StaticType {
- fn get_type() -> DhallExpr;
- // fn as_dhall(&self) -> DhallExpr;
- // fn from_dhall(e: DhallExpr) -> Result<Self, DhallConversionError>;
-}
-
-impl StaticType for bool {
- fn get_type() -> DhallExpr {
- dhall_expr!(Bool)
- }
-}
-
-impl StaticType for Natural {
- fn get_type() -> DhallExpr {
- dhall_expr!(Natural)
- }
-}
-
-impl StaticType for Integer {
- fn get_type() -> DhallExpr {
- dhall_expr!(Integer)
- }
-}
-
-impl StaticType for String {
- fn get_type() -> DhallExpr {
- dhall_expr!(Text)
- }
-}
-
-impl<A: StaticType, B: StaticType> StaticType for (A, B) {
- fn get_type() -> DhallExpr {
- let ta = A::get_type();
- let tb = B::get_type();
- dhall_expr!({ _1: ta, _2: tb })
- }
-}
-
-impl<T: StaticType> StaticType for Option<T> {
- fn get_type() -> DhallExpr {
- let t = T::get_type();
- dhall_expr!(Optional t)
- }
-}
-
-impl<T: StaticType> StaticType for Vec<T> {
- fn get_type() -> DhallExpr {
- let t = T::get_type();
- dhall_expr!(List t)
- }
-}
-
-impl<'a, T: StaticType> StaticType for &'a T {
- fn get_type() -> DhallExpr {
- T::get_type()
- }
-}
-
-impl<T> StaticType for std::marker::PhantomData<T> {
- fn get_type() -> DhallExpr {
- dhall_expr!({})
- }
-}
-
-impl<T: StaticType, E: StaticType> StaticType for Result<T, E> {
- fn get_type() -> DhallExpr {
- let tt = T::get_type();
- let te = E::get_type();
- dhall_expr!(< Ok: tt | Err: te>)
- }
-}
diff --git a/dhall/src/traits/deserialize.rs b/dhall/src/traits/deserialize.rs
new file mode 100644
index 0000000..1fbdfe1
--- /dev/null
+++ b/dhall/src/traits/deserialize.rs
@@ -0,0 +1,46 @@
+use crate::error::*;
+use crate::expr::*;
+
+pub trait Deserialize<'a>: Sized {
+ fn from_str(s: &'a str, ty: Option<&Type>) -> Result<Self>;
+}
+
+impl<'a> Deserialize<'a> for Parsed {
+ /// Simply parses the provided string. Ignores the
+ /// provided type.
+ fn from_str(s: &'a str, _: Option<&Type>) -> Result<Self> {
+ Ok(Parsed::parse_str(s)?)
+ }
+}
+
+impl<'a> Deserialize<'a> for Resolved {
+ /// Parses and resolves the provided string. Ignores the
+ /// provided type.
+ fn from_str(s: &'a str, ty: Option<&Type>) -> Result<Self> {
+ Ok(Parsed::from_str(s, ty)?.resolve()?)
+ }
+}
+
+impl<'a> Deserialize<'a> for Typed {
+ /// Parses, resolves and typechecks the provided string.
+ fn from_str(s: &'a str, ty: Option<&Type>) -> Result<Self> {
+ let resolved = Resolved::from_str(s, ty)?;
+ match ty {
+ None => Ok(resolved.typecheck()?),
+ Some(t) => Ok(resolved.typecheck_with(t)?),
+ }
+ }
+}
+
+impl<'a> Deserialize<'a> for Normalized {
+ /// Parses, resolves, typechecks and normalizes the provided string.
+ fn from_str(s: &'a str, ty: Option<&Type>) -> Result<Self> {
+ Ok(Typed::from_str(s, ty)?.normalize())
+ }
+}
+
+impl<'a> Deserialize<'a> for Type {
+ fn from_str(s: &'a str, ty: Option<&Type>) -> Result<Self> {
+ Ok(Normalized::from_str(s, ty)?.into_type())
+ }
+}
diff --git a/dhall/src/traits/mod.rs b/dhall/src/traits/mod.rs
new file mode 100644
index 0000000..4ce8f97
--- /dev/null
+++ b/dhall/src/traits/mod.rs
@@ -0,0 +1,4 @@
+mod deserialize;
+mod static_type;
+pub use deserialize::Deserialize;
+pub use static_type::{SimpleStaticType, StaticType};
diff --git a/dhall/src/traits/static_type.rs b/dhall/src/traits/static_type.rs
new file mode 100644
index 0000000..6c41e3f
--- /dev/null
+++ b/dhall/src/traits/static_type.rs
@@ -0,0 +1,128 @@
+use crate::expr::*;
+use dhall_core::*;
+use dhall_generator::*;
+
+pub trait StaticType {
+ fn get_static_type() -> Type;
+}
+
+/// Trait for rust types that can be represented in dhall in
+/// a single way, independent of the value. A typical example is `Option<bool>`,
+/// represented by the dhall expression `Optional Bool`. A typical counterexample
+/// is `HashMap<Text, bool>` because dhall cannot represent records with a
+/// variable number of fields.
+pub trait SimpleStaticType {
+ fn get_simple_static_type() -> SimpleType;
+}
+
+fn mktype(x: SubExpr<X, X>) -> SimpleType {
+ SimpleType(x)
+}
+
+impl<T: SimpleStaticType> StaticType for T {
+ fn get_static_type() -> Type {
+ crate::expr::Normalized(
+ T::get_simple_static_type().into(),
+ Type::const_type(),
+ )
+ .into_type()
+ }
+}
+
+impl StaticType for SimpleType {
+ fn get_static_type() -> Type {
+ Type::const_type()
+ }
+}
+
+impl SimpleStaticType for bool {
+ fn get_simple_static_type() -> SimpleType {
+ mktype(dhall_expr!(Bool))
+ }
+}
+
+impl SimpleStaticType for Natural {
+ fn get_simple_static_type() -> SimpleType {
+ mktype(dhall_expr!(Natural))
+ }
+}
+
+impl SimpleStaticType for u32 {
+ fn get_simple_static_type() -> SimpleType {
+ mktype(dhall_expr!(Natural))
+ }
+}
+
+impl SimpleStaticType for u64 {
+ fn get_simple_static_type() -> SimpleType {
+ mktype(dhall_expr!(Natural))
+ }
+}
+
+impl SimpleStaticType for Integer {
+ fn get_simple_static_type() -> SimpleType {
+ mktype(dhall_expr!(Integer))
+ }
+}
+
+impl SimpleStaticType for i32 {
+ fn get_simple_static_type() -> SimpleType {
+ mktype(dhall_expr!(Integer))
+ }
+}
+
+impl SimpleStaticType for i64 {
+ fn get_simple_static_type() -> SimpleType {
+ mktype(dhall_expr!(Integer))
+ }
+}
+
+impl SimpleStaticType for String {
+ fn get_simple_static_type() -> SimpleType {
+ mktype(dhall_expr!(Text))
+ }
+}
+
+impl<A: SimpleStaticType, B: SimpleStaticType> SimpleStaticType for (A, B) {
+ fn get_simple_static_type() -> SimpleType {
+ let ta: SubExpr<_, _> = A::get_simple_static_type().into();
+ let tb: SubExpr<_, _> = B::get_simple_static_type().into();
+ mktype(dhall_expr!({ _1: ta, _2: tb }))
+ }
+}
+
+impl<T: SimpleStaticType> SimpleStaticType for Option<T> {
+ fn get_simple_static_type() -> SimpleType {
+ let t: SubExpr<_, _> = T::get_simple_static_type().into();
+ mktype(dhall_expr!(Optional t))
+ }
+}
+
+impl<T: SimpleStaticType> SimpleStaticType for Vec<T> {
+ fn get_simple_static_type() -> SimpleType {
+ let t: SubExpr<_, _> = T::get_simple_static_type().into();
+ mktype(dhall_expr!(List t))
+ }
+}
+
+impl<'a, T: SimpleStaticType> SimpleStaticType for &'a T {
+ fn get_simple_static_type() -> SimpleType {
+ T::get_simple_static_type()
+ }
+}
+
+impl<T> SimpleStaticType for std::marker::PhantomData<T> {
+ fn get_simple_static_type() -> SimpleType {
+ mktype(dhall_expr!({}))
+ }
+}
+
+impl<T: SimpleStaticType, E: SimpleStaticType> SimpleStaticType
+ for std::result::Result<T, E>
+{
+ fn get_simple_static_type() -> SimpleType {
+ let tt: SubExpr<_, _> = T::get_simple_static_type().into();
+ let te: SubExpr<_, _> = E::get_simple_static_type().into();
+ mktype(dhall_expr!(< Ok: tt | Err: te>))
+ }
+}
diff --git a/dhall/src/typecheck.rs b/dhall/src/typecheck.rs
index 998d3ca..91846bc 100644
--- a/dhall/src/typecheck.rs
+++ b/dhall/src/typecheck.rs
@@ -13,6 +13,11 @@ impl Resolved {
pub fn typecheck(self) -> Result<Typed, TypeError<X>> {
type_of(self.0.clone())
}
+ pub fn typecheck_with(self, ty: &Type) -> Result<Typed, TypeError<X>> {
+ let expr: SubExpr<_, _> = self.0.clone();
+ let ty: SubExpr<_, _> = ty.as_normalized()?.as_expr().clone();
+ type_of(dhall::subexpr!(expr: ty))
+ }
/// Pretends this expression has been typechecked. Use with care.
pub fn skip_typecheck(self) -> Typed {
Typed(self.0, UNTYPE)
@@ -42,7 +47,7 @@ impl Normalized {
&self.0
}
#[inline(always)]
- fn into_expr(self) -> SubExpr<X, X> {
+ pub(crate) fn into_expr(self) -> SubExpr<X, X> {
self.0
}
#[inline(always)]
@@ -50,7 +55,7 @@ impl Normalized {
&self.1
}
#[inline(always)]
- fn into_type(self) -> Type {
+ pub(crate) fn into_type(self) -> Type {
crate::expr::Type(TypeInternal::Expr(Box::new(self)))
}
// Expose the outermost constructor
@@ -78,7 +83,7 @@ impl Type {
}
}
#[inline(always)]
- fn into_normalized(self) -> Result<Normalized, TypeError<X>> {
+ pub(crate) fn into_normalized(self) -> Result<Normalized, TypeError<X>> {
use TypeInternal::*;
match self.0 {
Expr(e) => Ok(*e),
@@ -110,6 +115,21 @@ impl Type {
Untyped => Untyped,
})
}
+
+ #[inline(always)]
+ pub fn const_sort() -> Self {
+ Normalized(rc(ExprF::Const(Const::Sort)), UNTYPE).into_type()
+ }
+ #[inline(always)]
+ pub fn const_kind() -> Self {
+ Normalized(rc(ExprF::Const(Const::Kind)), Type::const_sort())
+ .into_type()
+ }
+ #[inline(always)]
+ pub fn const_type() -> Self {
+ Normalized(rc(ExprF::Const(Const::Type)), Type::const_kind())
+ .into_type()
+ }
}
const UNTYPE: Type = Type(TypeInternal::Untyped);
@@ -706,23 +726,23 @@ mod spec_tests {
macro_rules! tc_success {
($name:ident, $path:expr) => {
- make_spec_test!(TypecheckSuccess, $name, $path);
+ make_spec_test!(Typecheck, Success, $name, $path);
};
}
// macro_rules! tc_failure {
// ($name:ident, $path:expr) => {
- // make_spec_test!(TypecheckFailure, $name, $path);
+ // make_spec_test!(Typecheck, Failure, $name, $path);
// };
// }
macro_rules! ti_success {
($name:ident, $path:expr) => {
- make_spec_test!(TypeInferenceSuccess, $name, $path);
+ make_spec_test!(TypeInference, Success, $name, $path);
};
}
// macro_rules! ti_failure {
// ($name:ident, $path:expr) => {
- // make_spec_test!(TypeInferenceFailure, $name, $path);
+ // make_spec_test!(TypeInference, Failure, $name, $path);
// };
// }
diff --git a/dhall/tests/traits.rs b/dhall/tests/traits.rs
index ac6b5e6..1e9b3c2 100644
--- a/dhall/tests/traits.rs
+++ b/dhall/tests/traits.rs
@@ -1,53 +1,71 @@
#![feature(proc_macro_hygiene)]
-use dhall::StaticType;
+use dhall::SimpleStaticType;
+use dhall_core::{SubExpr, X};
use dhall_generator::dhall_expr;
#[test]
-fn test_dhall_type() {
- assert_eq!(bool::get_type(), dhall_expr!(Bool));
- assert_eq!(String::get_type(), dhall_expr!(Text));
+fn test_static_type() {
+ fn mktype(x: SubExpr<X, X>) -> dhall::expr::SimpleType {
+ x.into()
+ }
+
+ assert_eq!(bool::get_simple_static_type(), mktype(dhall_expr!(Bool)));
+ assert_eq!(String::get_simple_static_type(), mktype(dhall_expr!(Text)));
assert_eq!(
- <(bool, Option<String>)>::get_type(),
- dhall_expr!({ _1: Bool, _2: Optional Text })
+ <Option<bool>>::get_simple_static_type(),
+ mktype(dhall_expr!(Optional Bool))
+ );
+ assert_eq!(
+ <(bool, Option<String>)>::get_simple_static_type(),
+ mktype(dhall_expr!({ _1: Bool, _2: Optional Text }))
);
- #[derive(dhall::StaticType)]
+ #[derive(dhall::SimpleStaticType)]
#[allow(dead_code)]
struct A {
field1: bool,
field2: Option<bool>,
}
assert_eq!(
- <A as dhall::StaticType>::get_type(),
- dhall_expr!({ field1: Bool, field2: Optional Bool })
+ <A as dhall::SimpleStaticType>::get_simple_static_type(),
+ mktype(dhall_expr!({ field1: Bool, field2: Optional Bool }))
);
- #[derive(StaticType)]
+ #[derive(SimpleStaticType)]
#[allow(dead_code)]
struct B<'a, T: 'a> {
field1: &'a T,
field2: Option<T>,
}
- assert_eq!(<B<'static, bool>>::get_type(), A::get_type());
+ assert_eq!(
+ <B<'static, bool>>::get_simple_static_type(),
+ A::get_simple_static_type()
+ );
- #[derive(StaticType)]
+ #[derive(SimpleStaticType)]
#[allow(dead_code)]
struct C<T>(T, Option<String>);
- assert_eq!(<C<bool>>::get_type(), <(bool, Option<String>)>::get_type());
+ assert_eq!(
+ <C<bool>>::get_simple_static_type(),
+ <(bool, Option<String>)>::get_simple_static_type()
+ );
- #[derive(StaticType)]
+ #[derive(SimpleStaticType)]
#[allow(dead_code)]
struct D();
assert_eq!(
- <C<D>>::get_type(),
- dhall_expr!({ _1: {}, _2: Optional Text })
+ <C<D>>::get_simple_static_type(),
+ mktype(dhall_expr!({ _1: {}, _2: Optional Text }))
);
- #[derive(StaticType)]
+ #[derive(SimpleStaticType)]
#[allow(dead_code)]
enum E<T> {
A(T),
B(String),
};
- assert_eq!(<E<bool>>::get_type(), dhall_expr!(< A: Bool | B: Text >));
+ assert_eq!(
+ <E<bool>>::get_simple_static_type(),
+ mktype(dhall_expr!(< A: Bool | B: Text >))
+ );
}
diff --git a/dhall_core/src/core.rs b/dhall_core/src/core.rs
index 39dea37..03a34f1 100644
--- a/dhall_core/src/core.rs
+++ b/dhall_core/src/core.rs
@@ -3,7 +3,6 @@ use crate::*;
use std::collections::BTreeMap;
use std::rc::Rc;
-pub type Int = isize;
pub type Integer = isize;
pub type Natural = usize;
pub type Double = NaiveDouble;
diff --git a/dhall_core/src/label.rs b/dhall_core/src/label.rs
index 9dc2816..43c3f53 100644
--- a/dhall_core/src/label.rs
+++ b/dhall_core/src/label.rs
@@ -28,4 +28,7 @@ impl Label {
pub fn from_str(s: &str) -> Label {
Label(s.into())
}
+ pub fn as_ref(&self) -> &str {
+ self.0.as_ref()
+ }
}
diff --git a/dhall_generator/src/derive.rs b/dhall_generator/src/derive.rs
index 159ff5c..c911be5 100644
--- a/dhall_generator/src/derive.rs
+++ b/dhall_generator/src/derive.rs
@@ -6,14 +6,23 @@ use syn::spanned::Spanned;
use syn::Error;
use syn::{parse_quote, DeriveInput};
-pub fn derive_type(input: TokenStream) -> TokenStream {
- TokenStream::from(match derive_type_inner(input) {
+pub fn derive_simple_static_type(input: TokenStream) -> TokenStream {
+ TokenStream::from(match derive_simple_static_type_inner(input) {
Ok(tokens) => tokens,
Err(err) => err.to_compile_error(),
})
}
-pub fn derive_for_struct(
+fn get_simple_static_type<T>(ty: T) -> proc_macro2::TokenStream
+where
+ T: quote::ToTokens,
+{
+ quote!(
+ <#ty as dhall::SimpleStaticType>::get_simple_static_type()
+ )
+}
+
+fn derive_for_struct(
data: &syn::DataStruct,
constraints: &mut Vec<syn::Type>,
) -> Result<proc_macro2::TokenStream, Error> {
@@ -44,7 +53,8 @@ pub fn derive_for_struct(
.map(|(name, ty)| {
let name = dhall_core::Label::from(name);
constraints.push(ty.clone());
- (name, quote!(<#ty as dhall::StaticType>::get_type()))
+ let ty = get_simple_static_type(ty);
+ (name, quote!(#ty.into()))
})
.collect();
let record =
@@ -52,7 +62,7 @@ pub fn derive_for_struct(
Ok(quote! { dhall_core::rc(#record) })
}
-pub fn derive_for_enum(
+fn derive_for_enum(
data: &syn::DataEnum,
constraints: &mut Vec<syn::Type>,
) -> Result<proc_macro2::TokenStream, Error> {
@@ -88,7 +98,8 @@ pub fn derive_for_enum(
};
let ty = ty?;
constraints.push(ty.clone());
- Ok((name, quote!(<#ty as dhall::StaticType>::get_type())))
+ let ty = get_simple_static_type(ty);
+ Ok((name, quote!(#ty.into())))
})
.collect::<Result<_, Error>>()?;
@@ -97,7 +108,7 @@ pub fn derive_for_enum(
Ok(quote! { dhall_core::rc(#union) })
}
-pub fn derive_type_inner(
+pub fn derive_simple_static_type_inner(
input: TokenStream,
) -> Result<proc_macro2::TokenStream, Error> {
let input: DeriveInput = syn::parse_macro_input::parse(input)?;
@@ -136,7 +147,7 @@ pub fn derive_type_inner(
let mut local_where_clause = orig_where_clause.clone();
local_where_clause
.predicates
- .push(parse_quote!(#ty: dhall::StaticType));
+ .push(parse_quote!(#ty: dhall::SimpleStaticType));
let phantoms = generics.params.iter().map(|param| match param {
syn::GenericParam::Type(syn::TypeParam { ident, .. }) => {
quote!(#ident)
@@ -158,16 +169,16 @@ pub fn derive_type_inner(
for ty in constraints.iter() {
where_clause
.predicates
- .push(parse_quote!(#ty: dhall::StaticType));
+ .push(parse_quote!(#ty: dhall::SimpleStaticType));
}
let ident = &input.ident;
let tokens = quote! {
- impl #impl_generics dhall::StaticType for #ident #ty_generics
+ impl #impl_generics dhall::SimpleStaticType for #ident #ty_generics
#where_clause {
- fn get_type() -> dhall_core::DhallExpr {
+ fn get_simple_static_type() -> dhall::expr::SimpleType {
#(#assertions)*
- #get_type
+ dhall::expr::SimpleType::from(#get_type)
}
}
};
diff --git a/dhall_generator/src/lib.rs b/dhall_generator/src/lib.rs
index b422834..e67f2aa 100644
--- a/dhall_generator/src/lib.rs
+++ b/dhall_generator/src/lib.rs
@@ -21,7 +21,7 @@ pub fn subexpr(input: TokenStream) -> TokenStream {
quote::subexpr(input)
}
-#[proc_macro_derive(StaticType)]
-pub fn derive_type(input: TokenStream) -> TokenStream {
- derive::derive_type(input)
+#[proc_macro_derive(SimpleStaticType)]
+pub fn derive_simple_static_type(input: TokenStream) -> TokenStream {
+ derive::derive_simple_static_type(input)
}