diff options
Diffstat (limited to '')
-rw-r--r-- | dhall/Cargo.toml | 5 | ||||
-rw-r--r-- | dhall/build.rs | 4 | ||||
-rw-r--r-- | dhall/src/error.rs | 32 | ||||
-rw-r--r-- | dhall/src/expr.rs | 21 | ||||
-rw-r--r-- | dhall/src/imports.rs | 74 | ||||
-rw-r--r-- | dhall/src/lib.rs | 124 | ||||
-rw-r--r-- | dhall/src/main.rs | 2 | ||||
-rw-r--r-- | dhall/src/normalize.rs | 2 | ||||
-rw-r--r-- | dhall/src/serde.rs | 69 | ||||
-rw-r--r-- | dhall/src/tests.rs | 218 | ||||
-rw-r--r-- | dhall/src/traits.rs | 77 | ||||
-rw-r--r-- | dhall/src/traits/deserialize.rs | 46 | ||||
-rw-r--r-- | dhall/src/traits/mod.rs | 4 | ||||
-rw-r--r-- | dhall/src/traits/static_type.rs | 128 | ||||
-rw-r--r-- | dhall/src/typecheck.rs | 34 | ||||
-rw-r--r-- | dhall/tests/traits.rs | 54 | ||||
-rw-r--r-- | dhall_core/src/core.rs | 1 | ||||
-rw-r--r-- | dhall_core/src/label.rs | 3 | ||||
-rw-r--r-- | dhall_generator/src/derive.rs | 35 | ||||
-rw-r--r-- | dhall_generator/src/lib.rs | 6 |
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) } |