summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--dhall/build.rs63
-rw-r--r--dhall/src/lib.rs11
-rw-r--r--dhall/src/semantics/tck/typecheck.rs5
-rw-r--r--dhall/src/tests.rs279
4 files changed, 199 insertions, 159 deletions
diff --git a/dhall/build.rs b/dhall/build.rs
index 7733581..3955b3a 100644
--- a/dhall/build.rs
+++ b/dhall/build.rs
@@ -2,15 +2,19 @@ use std::env;
use std::ffi::OsString;
use std::fs::{read_to_string, File};
use std::io::{BufRead, BufReader, Write};
-use std::path::Path;
+use std::path::{Path, PathBuf};
use walkdir::WalkDir;
use abnf_to_pest::render_rules_to_pest;
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum FileType {
+ /// Dhall source file
Text,
+ /// Dhall binary file
Binary,
+ /// Text file with expected text output
+ UI,
}
impl FileType {
@@ -18,8 +22,23 @@ impl FileType {
match self {
FileType::Text => "dhall",
FileType::Binary => "dhallb",
+ FileType::UI => "txt",
}
}
+ fn constructor(self) -> &'static str {
+ match self {
+ FileType::Text => "TestFile::Source",
+ FileType::Binary => "TestFile::Binary",
+ FileType::UI => "TestFile::UI",
+ }
+ }
+ fn construct(self, path: &str) -> String {
+ // e.g. with
+ // path = "tests/foor/barA"
+ // returns something like:
+ // TestFile::Source("tests/foor/barA.dhall")
+ format!(r#"{}("{}.{}")"#, self.constructor(), path, self.to_ext())
+ }
}
fn dhall_files_in_dir<'a>(
@@ -72,7 +91,8 @@ fn make_test_module(
mut feature: TestFeature,
) -> std::io::Result<()> {
writeln!(w, "mod {} {{", feature.module_name)?;
- let take_ab_suffix = feature.output_type.is_some();
+ let take_ab_suffix = feature.output_type.is_some()
+ && feature.output_type != Some(FileType::UI);
for base_path in base_paths {
let tests_dir = base_path.join(feature.directory);
for (name, path) in
@@ -85,22 +105,26 @@ fn make_test_module(
let path = path.to_string_lossy();
let test = match feature.output_type {
None => {
- let input_file =
- format!("\"{}.{}\"", path, feature.input_type.to_ext());
- format!("{}({})", feature.variant, input_file)
+ let input = feature.input_type.construct(&path);
+ format!("{}({})", feature.variant, input)
+ }
+ Some(output_type @ FileType::UI) => {
+ let input = feature.input_type.construct(&path);
+ let output_file = PathBuf::from(path.as_ref())
+ .strip_prefix(base_path)
+ .unwrap()
+ .strip_prefix(feature.directory)
+ .unwrap()
+ .to_string_lossy()
+ .into_owned();
+ let output = output_type.construct(&output_file);
+ format!("{}({}, {})", feature.variant, input, output)
}
Some(output_type) => {
- let input_file = format!(
- "\"{}A.{}\"",
- path,
- feature.input_type.to_ext()
- );
- let output_file =
- format!("\"{}B.{}\"", path, output_type.to_ext());
- format!(
- "{}({}, {})",
- feature.variant, input_file, output_file
- )
+ let input =
+ feature.input_type.construct(&format!("{}A", path));
+ let output = output_type.construct(&format!("{}B", path));
+ format!("{}({}, {})", feature.variant, input, output)
}
};
writeln!(w, "make_spec_test!({}, {});", test, name)?;
@@ -259,7 +283,7 @@ fn generate_tests() -> std::io::Result<()> {
|| path == "customHeadersUsingBoundVariable"
}),
input_type: FileType::Text,
- output_type: None,
+ output_type: Some(FileType::UI),
},
TestFeature {
module_name: "beta_normalize",
@@ -281,6 +305,7 @@ fn generate_tests() -> std::io::Result<()> {
|| path == "prelude/JSON/number/1"
// TODO: doesn't typecheck
|| path == "unit/RightBiasedRecordMergeWithinRecordProjection"
+ || path == "unit/Sort"
// // TODO: Further record simplifications
|| path == "simplifications/rightBiasedMergeWithinRecordProjectionWithinFieldSelection0"
|| path == "simplifications/rightBiasedMergeWithinRecordProjectionWithinFieldSelection1"
@@ -342,7 +367,7 @@ fn generate_tests() -> std::io::Result<()> {
|| path == "unit/MergeHandlerFreeVar"
}),
input_type: FileType::Text,
- output_type: None,
+ output_type: Some(FileType::UI),
},
];
diff --git a/dhall/src/lib.rs b/dhall/src/lib.rs
index dbf1fc0..7725b28 100644
--- a/dhall/src/lib.rs
+++ b/dhall/src/lib.rs
@@ -234,6 +234,17 @@ impl std::hash::Hash for Normalized {
}
}
+impl From<Parsed> for NormalizedExpr {
+ fn from(other: Parsed) -> Self {
+ other.to_expr()
+ }
+}
+impl From<Normalized> for NormalizedExpr {
+ fn from(other: Normalized) -> Self {
+ other.to_expr()
+ }
+}
+
impl Eq for Typed {}
impl PartialEq for Typed {
fn eq(&self, other: &Self) -> bool {
diff --git a/dhall/src/semantics/tck/typecheck.rs b/dhall/src/semantics/tck/typecheck.rs
index 6817712..dd9a8fa 100644
--- a/dhall/src/semantics/tck/typecheck.rs
+++ b/dhall/src/semantics/tck/typecheck.rs
@@ -847,7 +847,10 @@ pub(crate) fn type_with(
/// Typecheck an expression and return the expression annotated with types if type-checking
/// succeeded, or an error if type-checking failed.
pub(crate) fn typecheck(e: &Expr<Normalized>) -> Result<TyExpr, TypeError> {
- type_with(&TyEnv::new(), e)
+ let res = type_with(&TyEnv::new(), e)?;
+ // Ensure that the inferred type exists (i.e. this is not Sort)
+ res.get_type()?;
+ Ok(res)
}
/// Like `typecheck`, but additionally checks that the expression's type matches the provided type.
diff --git a/dhall/src/tests.rs b/dhall/src/tests.rs
index 449e7ee..659317f 100644
--- a/dhall/src/tests.rs
+++ b/dhall/src/tests.rs
@@ -48,51 +48,124 @@ use std::io::{Read, Write};
use std::path::PathBuf;
use crate::error::{Error, Result};
-use crate::Parsed;
+use crate::syntax::binary;
+use crate::{Normalized, NormalizedExpr, Parsed, Resolved};
#[allow(dead_code)]
-#[derive(Clone)]
-pub enum Test<'a> {
- ParserSuccess(&'a str, &'a str),
- ParserFailure(&'a str),
- Printer(&'a str, &'a str),
- BinaryEncoding(&'a str, &'a str),
- BinaryDecodingSuccess(&'a str, &'a str),
- BinaryDecodingFailure(&'a str),
- ImportSuccess(&'a str, &'a str),
- ImportFailure(&'a str),
- ImportError(&'a str),
- TypeInferenceSuccess(&'a str, &'a str),
- TypeInferenceFailure(&'a str),
- TypeError(&'a str),
- Normalization(&'a str, &'a str),
- AlphaNormalization(&'a str, &'a str),
+enum Test {
+ ParserSuccess(TestFile, TestFile),
+ ParserFailure(TestFile),
+ Printer(TestFile, TestFile),
+ BinaryEncoding(TestFile, TestFile),
+ BinaryDecodingSuccess(TestFile, TestFile),
+ BinaryDecodingFailure(TestFile),
+ ImportSuccess(TestFile, TestFile),
+ ImportFailure(TestFile),
+ ImportError(TestFile, TestFile),
+ TypeInferenceSuccess(TestFile, TestFile),
+ TypeInferenceFailure(TestFile),
+ TypeError(TestFile, TestFile),
+ Normalization(TestFile, TestFile),
+ AlphaNormalization(TestFile, TestFile),
}
-fn parse_file_str(file_path: &str) -> Result<Parsed> {
- Parsed::parse_file(&PathBuf::from(file_path))
+#[allow(dead_code)]
+enum TestFile {
+ Source(&'static str),
+ Binary(&'static str),
+ UI(&'static str),
+}
+
+impl TestFile {
+ pub fn path(&self) -> PathBuf {
+ match self {
+ TestFile::Source(path)
+ | TestFile::Binary(path)
+ | TestFile::UI(path) => PathBuf::from(path),
+ }
+ }
+
+ /// Parse the target file
+ pub fn parse(&self) -> Result<Parsed> {
+ match self {
+ TestFile::Source(_) => Parsed::parse_file(&self.path()),
+ TestFile::Binary(_) => Parsed::parse_binary_file(&self.path()),
+ TestFile::UI(_) => panic!("Can't parse a UI test file"),
+ }
+ }
+ /// Parse and resolve the target file
+ pub fn resolve(&self) -> Result<Resolved> {
+ Ok(self.parse()?.resolve()?)
+ }
+ /// Parse, resolve, tck and normalize the target file
+ pub fn normalize(&self) -> Result<Normalized> {
+ Ok(self.resolve()?.typecheck()?.normalize())
+ }
+
+ /// Check that the provided expression matches the file contents.
+ pub fn compare(&self, expr: impl Into<NormalizedExpr>) -> Result<()> {
+ let expr = expr.into();
+ let expected = self.parse()?.to_expr();
+ assert_eq_display!(expr, expected);
+ Ok(())
+ }
+ /// Check that the provided expression matches the file contents.
+ pub fn compare_debug(&self, expr: impl Into<NormalizedExpr>) -> Result<()> {
+ let expr = expr.into();
+ let expected = self.parse()?.to_expr();
+ assert_eq_pretty!(expr, expected);
+ Ok(())
+ }
+ /// Check that the provided expression matches the file contents.
+ pub fn compare_binary(
+ &self,
+ expr: impl Into<NormalizedExpr>,
+ ) -> Result<()> {
+ match self {
+ TestFile::Binary(_) => {}
+ _ => panic!("This is not a binary file"),
+ }
+ let expr_data = binary::encode(&expr.into())?;
+ let expected_data = {
+ let mut data = Vec::new();
+ File::open(&self.path())?.read_to_end(&mut data)?;
+ data
+ };
+
+ // Compare bit-by-bit
+ if expr_data != expected_data {
+ use serde_cbor::de::from_slice;
+ use serde_cbor::value::Value;
+ // use std::io::Write;
+ // File::create(&expected)?.write_all(&expr_data)?;
+ // Pretty-print difference
+ assert_eq_pretty!(
+ from_slice::<Value>(&expr_data).unwrap(),
+ from_slice::<Value>(&expected_data).unwrap()
+ );
+ // If difference was not visible in the cbor::Value, compare normally.
+ assert_eq!(expr_data, expected_data);
+ }
+ Ok(())
+ }
}
#[allow(dead_code)]
-pub fn run_test_stringy_error(
- test: Test<'_>,
-) -> std::result::Result<(), String> {
- run_test(test).map_err(|e| e.to_string()).map(|_| ())
+fn run_test_stringy_error(test: Test) -> std::result::Result<(), String> {
+ run_test(test).map_err(|e| e.to_string())?;
+ Ok(())
}
-pub fn run_test(test: Test<'_>) -> Result<()> {
+fn run_test(test: Test) -> Result<()> {
use self::Test::*;
match test {
- ParserSuccess(expr_file_path, expected_file_path) => {
- let expr = parse_file_str(&expr_file_path)?;
+ ParserSuccess(expr, expected) => {
+ let expr = expr.parse()?;
// This exercices both parsing and binary decoding
- // Compare parse/decoded
- let expected =
- Parsed::parse_binary_file(&PathBuf::from(expected_file_path))?;
- assert_eq_pretty!(expr, expected);
+ expected.compare_debug(expr)?;
}
- ParserFailure(file_path) => {
- let err = parse_file_str(&file_path).unwrap_err();
+ ParserFailure(expr) => {
+ let err = expr.parse().unwrap_err();
match &err {
Error::Parse(_) => {}
Error::IO(e) if e.kind() == std::io::ErrorKind::InvalidData => {
@@ -100,79 +173,39 @@ pub fn run_test(test: Test<'_>) -> Result<()> {
e => panic!("Expected parse error, got: {:?}", e),
}
}
- BinaryEncoding(expr_file_path, expected_file_path) => {
- let expr = parse_file_str(&expr_file_path)?;
- let mut expected_data = Vec::new();
- {
- File::open(&PathBuf::from(&expected_file_path))?
- .read_to_end(&mut expected_data)?;
- }
- let expr_data = expr.encode()?;
-
- // Compare bit-by-bit
- if expr_data != expected_data {
- // use std::io::Write;
- // File::create(&expected_file_path)?.write_all(&expr_data)?;
- // Pretty-print difference
- assert_eq_pretty!(
- serde_cbor::de::from_slice::<serde_cbor::value::Value>(
- &expr_data
- )
- .unwrap(),
- serde_cbor::de::from_slice::<serde_cbor::value::Value>(
- &expected_data
- )
- .unwrap()
- );
- // If difference was not visible in the cbor::Value
- assert_eq!(expr_data, expected_data);
- }
+ BinaryEncoding(expr, expected) => {
+ let expr = expr.parse()?;
+ expected.compare_binary(expr)?;
}
- BinaryDecodingSuccess(expr_file_path, expected_file_path) => {
- let expr =
- Parsed::parse_binary_file(&PathBuf::from(expr_file_path))?;
- let expected = parse_file_str(&expected_file_path)?;
- assert_eq_pretty!(expr, expected);
+ BinaryDecodingSuccess(expr, expected) => {
+ let expr = expr.parse()?;
+ expected.compare_debug(expr)?;
}
- BinaryDecodingFailure(file_path) => {
- Parsed::parse_binary_file(&PathBuf::from(file_path)).unwrap_err();
+ BinaryDecodingFailure(expr) => {
+ expr.parse().unwrap_err();
}
- Printer(expr_file_path, _) => {
- let expected = parse_file_str(&expr_file_path)?;
+ Printer(expr, _) => {
+ let expected = expr.parse()?;
// Round-trip pretty-printer
let expr: Parsed = Parsed::parse_str(&expected.to_string())?;
assert_eq!(expr, expected);
}
- ImportSuccess(expr_file_path, expected_file_path) => {
- let expr = parse_file_str(&expr_file_path)?
- .resolve()?
- .typecheck()?
- .normalize();
- let expected = parse_file_str(&expected_file_path)?
- .resolve()?
- .typecheck()?
- .normalize();
-
- assert_eq_display!(expr, expected);
+ ImportSuccess(expr, expected) => {
+ let expr = expr.normalize()?;
+ expected.compare(expr)?;
}
- ImportFailure(file_path) => {
- parse_file_str(&file_path)?.resolve().unwrap_err();
+ ImportFailure(expr) => {
+ expr.parse()?.resolve().unwrap_err();
}
// Checks the output of the type error against a text file. If the text file doesn't exist,
// we instead write to it the output we got. This makes it easy to update those files: just
// `rm -r dhall/tests/type-errors` and run the tests again.
- ImportError(file_path) => {
- let err: Error =
- parse_file_str(&file_path)?.resolve().unwrap_err().into();
- let file_path = PathBuf::from(file_path);
- let error_file_path = file_path
- .strip_prefix("../dhall-lang/tests/import/failure/")
- .or_else(|_| file_path.strip_prefix("tests/import/failure/"))
- .unwrap();
+ ImportError(expr, expected) => {
+ let base_path = expected.path();
let error_file_path =
- PathBuf::from("tests/errors/import/").join(error_file_path);
- let error_file_path = error_file_path.with_extension("txt");
+ PathBuf::from("tests/errors/import/").join(base_path);
+ let err: Error = expr.parse()?.resolve().unwrap_err().into();
if error_file_path.is_file() {
let expected_msg = std::fs::read_to_string(error_file_path)?;
let msg = format!("{}\n", err);
@@ -183,42 +216,22 @@ pub fn run_test(test: Test<'_>) -> Result<()> {
writeln!(file, "{}", err)?;
}
}
- TypeInferenceSuccess(expr_file_path, expected_file_path) => {
- let expr =
- parse_file_str(&expr_file_path)?.resolve()?.typecheck()?;
- let ty = expr.get_type()?.to_expr();
- let expected = parse_file_str(&expected_file_path)?.to_expr();
- assert_eq_display!(ty, expected);
+ TypeInferenceSuccess(expr, expected) => {
+ let ty = expr.resolve()?.typecheck()?.get_type()?;
+ expected.compare(ty)?;
}
- TypeInferenceFailure(file_path) => {
- let res = parse_file_str(&file_path)?.skip_resolve()?.typecheck();
- if let Ok(e) = &res {
- // If e did typecheck, check that get_type fails
- e.get_type().unwrap_err();
- }
+ TypeInferenceFailure(expr) => {
+ expr.resolve()?.typecheck().unwrap_err();
}
// Checks the output of the type error against a text file. If the text file doesn't exist,
// we instead write to it the output we got. This makes it easy to update those files: just
// `rm -r dhall/tests/type-errors` and run the tests again.
- TypeError(file_path) => {
- let res = parse_file_str(&file_path)?.skip_resolve()?.typecheck();
- let file_path = PathBuf::from(file_path);
- let error_file_path = file_path
- .strip_prefix("../dhall-lang/tests/type-inference/failure/")
- .or_else(|_| {
- file_path.strip_prefix("tests/type-inference/failure/")
- })
- .unwrap();
+ TypeError(expr, expected) => {
+ let base_path = expected.path();
let error_file_path =
- PathBuf::from("tests/type-errors/").join(error_file_path);
- let error_file_path = error_file_path.with_extension("txt");
- let err: Error = match res {
- Ok(e) => {
- // If e did typecheck, check that get_type fails
- e.get_type().unwrap_err().into()
- }
- Err(e) => e.into(),
- };
+ PathBuf::from("tests/type-errors/").join(base_path);
+
+ let err: Error = expr.resolve()?.typecheck().unwrap_err().into();
if error_file_path.is_file() {
let expected_msg = std::fs::read_to_string(error_file_path)?;
@@ -230,25 +243,13 @@ pub fn run_test(test: Test<'_>) -> Result<()> {
writeln!(file, "{}", err)?;
}
}
- Normalization(expr_file_path, expected_file_path) => {
- let expr = parse_file_str(&expr_file_path)?
- .resolve()?
- .typecheck()?
- .normalize()
- .to_expr();
- let expected = parse_file_str(&expected_file_path)?.to_expr();
-
- assert_eq_display!(expr, expected);
+ Normalization(expr, expected) => {
+ let expr = expr.normalize()?;
+ expected.compare(expr)?;
}
- AlphaNormalization(expr_file_path, expected_file_path) => {
- let expr = parse_file_str(&expr_file_path)?
- .resolve()?
- .typecheck()?
- .normalize()
- .to_expr_alpha();
- let expected = parse_file_str(&expected_file_path)?.to_expr();
-
- assert_eq_display!(expr, expected);
+ AlphaNormalization(expr, expected) => {
+ let expr = expr.normalize()?.to_expr_alpha();
+ expected.compare(expr)?;
}
}
Ok(())