summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNadrieril2020-12-06 18:18:05 +0000
committerGitHub2020-12-06 18:18:05 +0000
commit35ed301e8de5a2b1102e370e638564d3c3d204a8 (patch)
tree0f84a9d5b60e00c49168fcf92eb34c903c088dde
parent3c0a850d1522701e136f4fafbcf69d46560fe0ee (diff)
parentee941f668977b66d9d829bc9c359f3a3b64f9dc1 (diff)
Merge pull request #203 from Nadrieril/fix-import-bug
-rw-r--r--dhall/src/semantics/parse.rs13
-rw-r--r--dhall/src/semantics/resolve/resolve.rs235
-rw-r--r--dhall/tests/import/failure/cycle.txt2
-rw-r--r--dhall/tests/import/success/unit/MixImportModesA.dhall1
-rw-r--r--dhall/tests/import/success/unit/MixImportModesB.dhall1
-rw-r--r--dhall/tests/spec.rs1
6 files changed, 145 insertions, 108 deletions
diff --git a/dhall/src/semantics/parse.rs b/dhall/src/semantics/parse.rs
index a770c15..5870d47 100644
--- a/dhall/src/semantics/parse.rs
+++ b/dhall/src/semantics/parse.rs
@@ -3,39 +3,38 @@ use url::Url;
use crate::error::Error;
use crate::semantics::resolve::{download_http_text, ImportLocation};
-use crate::syntax::binary;
-use crate::syntax::parse_expr;
+use crate::syntax::{binary, parse_expr};
use crate::Parsed;
pub fn parse_file(f: &Path) -> Result<Parsed, Error> {
let text = std::fs::read_to_string(f)?;
let expr = parse_expr(&text)?;
- let root = ImportLocation::Local(f.to_owned());
+ let root = ImportLocation::local_dhall_code(f.to_owned());
Ok(Parsed(expr, root))
}
pub fn parse_remote(url: Url) -> Result<Parsed, Error> {
let body = download_http_text(url.clone())?;
let expr = parse_expr(&body)?;
- let root = ImportLocation::Remote(url);
+ let root = ImportLocation::remote_dhall_code(url);
Ok(Parsed(expr, root))
}
pub fn parse_str(s: &str) -> Result<Parsed, Error> {
let expr = parse_expr(s)?;
- let root = ImportLocation::Missing;
+ let root = ImportLocation::dhall_code_of_unknown_origin();
Ok(Parsed(expr, root))
}
pub fn parse_binary(data: &[u8]) -> Result<Parsed, Error> {
let expr = binary::decode(data)?;
- let root = ImportLocation::Missing;
+ let root = ImportLocation::dhall_code_of_unknown_origin();
Ok(Parsed(expr, root))
}
pub fn parse_binary_file(f: &Path) -> Result<Parsed, Error> {
let data = crate::utils::read_binary_file(f)?;
let expr = binary::decode(&data)?;
- let root = ImportLocation::Local(f.to_owned());
+ let root = ImportLocation::local_dhall_code(f.to_owned());
Ok(Parsed(expr, root))
}
diff --git a/dhall/src/semantics/resolve/resolve.rs b/dhall/src/semantics/resolve/resolve.rs
index dc1951e..16987de 100644
--- a/dhall/src/semantics/resolve/resolve.rs
+++ b/dhall/src/semantics/resolve/resolve.rs
@@ -22,7 +22,7 @@ pub type Import = syntax::Import<()>;
/// The location of some data, usually some dhall code.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub enum ImportLocation {
+enum ImportLocationKind {
/// Local file
Local(PathBuf),
/// Remote file
@@ -33,63 +33,29 @@ pub enum ImportLocation {
Missing,
}
-impl ImportLocation {
- /// Given an import pointing to `target` found in the current location, compute the next
- /// location, or error if not allowed.
- /// `sanity_check` indicates whether to check if that location is allowed to be referenced,
- /// for example to prevent a remote file from reading an environment variable.
- fn chain(
- &self,
- target: &ImportTarget<()>,
- sanity_check: bool,
- ) -> Result<ImportLocation, Error> {
- Ok(match target {
- ImportTarget::Local(prefix, path) => {
- self.chain_local(*prefix, path)?
- }
- ImportTarget::Remote(remote) => {
- if sanity_check {
- if let ImportLocation::Remote(..) = self {
- // TODO: allow if CORS check passes
- return Err(ImportError::SanityCheck.into());
- }
- }
- let mut url = Url::parse(&format!(
- "{}://{}",
- remote.scheme, remote.authority
- ))?;
- url.set_path(&remote.path.file_path.iter().join("/"));
- url.set_query(remote.query.as_ref().map(String::as_ref));
- ImportLocation::Remote(url)
- }
- ImportTarget::Env(var_name) => {
- if sanity_check {
- if let ImportLocation::Remote(..) = self {
- return Err(ImportError::SanityCheck.into());
- }
- }
- ImportLocation::Env(var_name.clone())
- }
- ImportTarget::Missing => ImportLocation::Missing,
- })
- }
+/// The location of some data.
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ImportLocation {
+ kind: ImportLocationKind,
+ mode: ImportMode,
+}
+impl ImportLocationKind {
fn chain_local(
&self,
prefix: FilePrefix,
path: &FilePath,
- ) -> Result<ImportLocation, Error> {
+ ) -> Result<Self, Error> {
Ok(match self {
- ImportLocation::Local(..)
- | ImportLocation::Env(..)
- | ImportLocation::Missing => {
+ ImportLocationKind::Local(..)
+ | ImportLocationKind::Env(..)
+ | ImportLocationKind::Missing => {
let dir = match self {
- ImportLocation::Local(path) => {
+ ImportLocationKind::Local(path) => {
path.parent().unwrap().to_owned()
}
- ImportLocation::Env(..) | ImportLocation::Missing => {
- std::env::current_dir()?
- }
+ ImportLocationKind::Env(..)
+ | ImportLocationKind::Missing => std::env::current_dir()?,
_ => unreachable!(),
};
let mut dir: Vec<String> = dir
@@ -120,9 +86,9 @@ impl ImportLocation {
};
let path =
Some(prefix.to_string()).into_iter().chain(path).collect();
- ImportLocation::Local(path)
+ ImportLocationKind::Local(path)
}
- ImportLocation::Remote(url) => {
+ ImportLocationKind::Remote(url) => {
let mut url = url.clone();
match prefix {
FilePrefix::Here => {}
@@ -133,46 +99,56 @@ impl ImportLocation {
FilePrefix::Home => panic!("error"),
}
url = url.join(&path.file_path.join("/"))?;
- ImportLocation::Remote(url)
+ ImportLocationKind::Remote(url)
}
})
}
- fn fetch_dhall(self) -> Result<Parsed, Error> {
+ fn fetch_dhall(&self) -> Result<Parsed, Error> {
Ok(match self {
- ImportLocation::Local(path) => Parsed::parse_file(&path)?,
- ImportLocation::Remote(url) => Parsed::parse_remote(url)?,
- ImportLocation::Env(var_name) => {
+ ImportLocationKind::Local(path) => Parsed::parse_file(path)?,
+ ImportLocationKind::Remote(url) => {
+ Parsed::parse_remote(url.clone())?
+ }
+ ImportLocationKind::Env(var_name) => {
let val = match env::var(var_name) {
Ok(val) => val,
Err(_) => return Err(ImportError::MissingEnvVar.into()),
};
Parsed::parse_str(&val)?
}
- ImportLocation::Missing => return Err(ImportError::Missing.into()),
+ ImportLocationKind::Missing => {
+ return Err(ImportError::Missing.into())
+ }
})
}
- fn fetch_text(self) -> Result<String, Error> {
+ fn fetch_text(&self) -> Result<String, Error> {
Ok(match self {
- ImportLocation::Local(path) => std::fs::read_to_string(&path)?,
- ImportLocation::Remote(url) => download_http_text(url)?,
- ImportLocation::Env(var_name) => match env::var(var_name) {
+ ImportLocationKind::Local(path) => std::fs::read_to_string(path)?,
+ ImportLocationKind::Remote(url) => download_http_text(url.clone())?,
+ ImportLocationKind::Env(var_name) => match env::var(var_name) {
Ok(val) => val,
Err(_) => return Err(ImportError::MissingEnvVar.into()),
},
- ImportLocation::Missing => return Err(ImportError::Missing.into()),
+ ImportLocationKind::Missing => {
+ return Err(ImportError::Missing.into())
+ }
})
}
- fn into_location(self) -> Expr {
+ fn to_location(&self) -> Expr {
let (field_name, arg) = match self {
- ImportLocation::Local(path) => {
+ ImportLocationKind::Local(path) => {
("Local", Some(path.to_string_lossy().into_owned()))
}
- ImportLocation::Remote(url) => ("Remote", Some(url.into_string())),
- ImportLocation::Env(name) => ("Environment", Some(name)),
- ImportLocation::Missing => ("Missing", None),
+ ImportLocationKind::Remote(url) => {
+ ("Remote", Some(url.to_string()))
+ }
+ ImportLocationKind::Env(name) => {
+ ("Environment", Some(name.clone()))
+ }
+ ImportLocationKind::Missing => ("Missing", None),
};
let asloc_ty = make_aslocation_uniontype();
@@ -188,6 +164,96 @@ impl ImportLocation {
}
}
+impl ImportLocation {
+ pub fn dhall_code_of_unknown_origin() -> Self {
+ ImportLocation {
+ kind: ImportLocationKind::Missing,
+ mode: ImportMode::Code,
+ }
+ }
+ pub fn local_dhall_code(path: PathBuf) -> Self {
+ ImportLocation {
+ kind: ImportLocationKind::Local(path),
+ mode: ImportMode::Code,
+ }
+ }
+ pub fn remote_dhall_code(url: Url) -> Self {
+ ImportLocation {
+ kind: ImportLocationKind::Remote(url),
+ mode: ImportMode::Code,
+ }
+ }
+
+ /// Given an import pointing to `target` found in the current location, compute the next
+ /// location, or error if not allowed.
+ /// `sanity_check` indicates whether to check if that location is allowed to be referenced,
+ /// for example to prevent a remote file from reading an environment variable.
+ fn chain(&self, import: &Import) -> Result<ImportLocation, Error> {
+ // Makes no sense to chain an import if the current file is not a dhall file.
+ assert!(matches!(self.mode, ImportMode::Code));
+ let kind = match &import.location {
+ ImportTarget::Local(prefix, path) => {
+ self.kind.chain_local(*prefix, path)?
+ }
+ ImportTarget::Remote(remote) => {
+ if matches!(self.kind, ImportLocationKind::Remote(..))
+ && !matches!(import.mode, ImportMode::Location)
+ {
+ // TODO: allow if CORS check passes
+ return Err(ImportError::SanityCheck.into());
+ }
+ let mut url = Url::parse(&format!(
+ "{}://{}",
+ remote.scheme, remote.authority
+ ))?;
+ url.set_path(&remote.path.file_path.iter().join("/"));
+ url.set_query(remote.query.as_ref().map(String::as_ref));
+ ImportLocationKind::Remote(url)
+ }
+ ImportTarget::Env(var_name) => {
+ if matches!(self.kind, ImportLocationKind::Remote(..))
+ && !matches!(import.mode, ImportMode::Location)
+ {
+ return Err(ImportError::SanityCheck.into());
+ }
+ ImportLocationKind::Env(var_name.clone())
+ }
+ ImportTarget::Missing => ImportLocationKind::Missing,
+ };
+ Ok(ImportLocation {
+ kind,
+ mode: import.mode,
+ })
+ }
+
+ /// Fetches the expression corresponding to this location.
+ fn fetch(&self, env: &mut ImportEnv, span: Span) -> Result<Typed, Error> {
+ let (hir, ty) = match self.mode {
+ ImportMode::Code => {
+ let parsed = self.kind.fetch_dhall()?;
+ let typed = resolve_with_env(env, parsed)?.typecheck()?;
+ let hir = typed.normalize().to_hir();
+ (hir, typed.ty)
+ }
+ ImportMode::RawText => {
+ let text = self.kind.fetch_text()?;
+ let hir = Hir::new(
+ HirKind::Expr(ExprKind::TextLit(text.into())),
+ span,
+ );
+ (hir, Type::from_builtin(Builtin::Text))
+ }
+ ImportMode::Location => {
+ let expr = self.kind.to_location();
+ let hir = skip_resolve_expr(&expr)?;
+ let ty = hir.typecheck_noenv()?.ty().clone();
+ (hir, ty)
+ }
+ };
+ Ok(Typed { hir, ty })
+ }
+}
+
fn mkexpr(kind: UnspannedExpr) -> Expr {
Expr::new(kind, Span::Artificial)
}
@@ -237,35 +303,6 @@ fn check_hash(import: &Import, typed: &Typed, span: Span) -> Result<(), Error> {
Ok(())
}
-fn resolve_one_import(
- env: &mut ImportEnv,
- import: &Import,
- location: ImportLocation,
- span: Span,
-) -> Result<Typed, Error> {
- let (hir, ty) = match import.mode {
- ImportMode::Code => {
- let parsed = location.fetch_dhall()?;
- let typed = resolve_with_env(env, parsed)?.typecheck()?;
- let hir = typed.normalize().to_hir();
- (hir, typed.ty)
- }
- ImportMode::RawText => {
- let text = location.fetch_text()?;
- let hir =
- Hir::new(HirKind::Expr(ExprKind::TextLit(text.into())), span);
- (hir, Type::from_builtin(Builtin::Text))
- }
- ImportMode::Location => {
- let expr = location.into_location();
- let hir = skip_resolve_expr(&expr)?;
- let ty = hir.typecheck_noenv()?.ty().clone();
- (hir, ty)
- }
- };
- Ok(Typed { hir, ty })
-}
-
/// Desugar the first level of the expression.
fn desugar(expr: &Expr) -> Cow<'_, Expr> {
match expr.kind() {
@@ -358,9 +395,7 @@ fn resolve_with_env(
&mut NameEnv::new(),
&expr,
&mut |import, span| {
- let do_sanity_check = import.mode != ImportMode::Location;
- let location =
- base_location.chain(&import.location, do_sanity_check)?;
+ let location = base_location.chain(&import)?;
// If the import is in the in-memory cache, or the hash is in the on-disk cache, return
// the cached contents.
@@ -383,7 +418,7 @@ fn resolve_with_env(
// Resolve this import, making sure that recursive imports don't cycle back to the
// current one.
let res = env.with_cycle_detection(location.clone(), |env| {
- resolve_one_import(env, &import, location.clone(), span.clone())
+ location.fetch(env, span.clone())
});
let typed = match res {
Ok(typed) => typed,
diff --git a/dhall/tests/import/failure/cycle.txt b/dhall/tests/import/failure/cycle.txt
index 899484b..641667f 100644
--- a/dhall/tests/import/failure/cycle.txt
+++ b/dhall/tests/import/failure/cycle.txt
@@ -10,7 +10,7 @@ Type error: error: error
--> <current file>:1:1
|
1 | ../data/cycle.dhall
- | ^^^^^^^^^^^^^^^^^^^ ImportCycle([Local("./dhall-lang/tests/import/data/cycle.dhall"), Local("./dhall-lang/tests/import/failure/cycle.dhall")], Local("./dhall-lang/tests/import/data/cycle.dhall"))
+ | ^^^^^^^^^^^^^^^^^^^ ImportCycle([ImportLocation { kind: Local("./dhall-lang/tests/import/data/cycle.dhall"), mode: Code }, ImportLocation { kind: Local("./dhall-lang/tests/import/failure/cycle.dhall"), mode: Code }], ImportLocation { kind: Local("./dhall-lang/tests/import/data/cycle.dhall"), mode: Code })
|
|
|
diff --git a/dhall/tests/import/success/unit/MixImportModesA.dhall b/dhall/tests/import/success/unit/MixImportModesA.dhall
new file mode 100644
index 0000000..8946ff8
--- /dev/null
+++ b/dhall/tests/import/success/unit/MixImportModesA.dhall
@@ -0,0 +1 @@
+{ n = ../../data/simple.dhall, txt = ../../data/simple.dhall as Text, loc = ../../data/simple.dhall as Location }
diff --git a/dhall/tests/import/success/unit/MixImportModesB.dhall b/dhall/tests/import/success/unit/MixImportModesB.dhall
new file mode 100644
index 0000000..430e0bc
--- /dev/null
+++ b/dhall/tests/import/success/unit/MixImportModesB.dhall
@@ -0,0 +1 @@
+{ loc = < Environment: Text | Local: Text | Missing | Remote: Text >.Local "./dhall/tests/import/data/simple.dhall", n = 3, txt = "3\n" }
diff --git a/dhall/tests/spec.rs b/dhall/tests/spec.rs
index 6b156f6..646083a 100644
--- a/dhall/tests/spec.rs
+++ b/dhall/tests/spec.rs
@@ -519,6 +519,7 @@ fn ignore_test(variant: SpecTestKind, path: &str) -> bool {
// Paths on windows have backslashes; this breaks many things. This is undefined in the
// spec; see https://github.com/dhall-lang/dhall-lang/issues/1032
|| (variant == ImportSuccess && path.contains("asLocation"))
+ || path == "import/success/unit/MixImportModes"
|| variant == ImportFailure;
// Only include in release tests.