summaryrefslogtreecommitdiff
path: root/dhall/src/semantics/resolve
diff options
context:
space:
mode:
authorNadrieril2020-12-06 18:18:05 +0000
committerGitHub2020-12-06 18:18:05 +0000
commit35ed301e8de5a2b1102e370e638564d3c3d204a8 (patch)
tree0f84a9d5b60e00c49168fcf92eb34c903c088dde /dhall/src/semantics/resolve
parent3c0a850d1522701e136f4fafbcf69d46560fe0ee (diff)
parentee941f668977b66d9d829bc9c359f3a3b64f9dc1 (diff)
Merge pull request #203 from Nadrieril/fix-import-bug
Diffstat (limited to 'dhall/src/semantics/resolve')
-rw-r--r--dhall/src/semantics/resolve/resolve.rs235
1 files changed, 135 insertions, 100 deletions
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,