Repository: huff-language/huff-rs
Pull request #121 (2022-06-24 12:09:39)

fix(huff_core): Missing File Erroring
## Overview

This PR properly bubbles up file fetching errors instead of gracefully allowing `0` files to compile.

Effect (the first run is the original `huffc` output, second is the output created by this pr):
<img width="814" alt="Screen Shot 2022-06-24 at 7 55 57 AM" src="https://user-images.githubusercontent.com/21288394/175531199-48294a4a-e557-46b7-971c-80b358c8082f.png">



Patch:
diff --git a/huff_core/src/lib.rs b/huff_core/src/lib.rs
index 33dd2bf..6ac535f 100644
--- a/huff_core/src/lib.rs
+++ b/huff_core/src/lib.rs
@@ -103,7 +103,22 @@ impl<'a> Compiler {
         let file_paths: Vec<PathBuf> = Compiler::transform_paths(&self.sources)?;
 
         // Parallel file fetching
-        let files: Vec<Arc<FileSource>> = Compiler::fetch_sources(&file_paths);
+        let files: Vec<Result<Arc<FileSource>, CompilerError>> =
+            Compiler::fetch_sources(file_paths);
+
+        // Unwrap errors
+        let mut errors =
+            files.iter().filter_map(|rfs| rfs.as_ref().err()).collect::<Vec<&CompilerError>>();
+        if !errors.is_empty() {
+            let error = errors.remove(0);
+            return Err(Arc::new(error.clone()))
+        }
+
+        // Unpack files into their file sources
+        let files = files
+            .iter()
+            .filter_map(|fs| fs.as_ref().map(Arc::clone).ok())
+            .collect::<Vec<Arc<FileSource>>>();
 
         // Parallel Dependency Resolution
         let recursed_file_sources: Vec<Result<Arc<FileSource>, Arc<CompilerError<'a>>>> =
@@ -265,13 +280,13 @@ impl<'a> Compiler {
     }
 
     /// Get the file sources for a vec of PathBufs
-    pub fn fetch_sources(paths: &Vec<PathBuf>) -> Vec<Arc<FileSource>> {
-        let files: Vec<Arc<FileSource>> = paths
+    pub fn fetch_sources(paths: Vec<PathBuf>) -> Vec<Result<Arc<FileSource>, CompilerError<'a>>> {
+        paths
             .into_par_iter()
             .map(|pb| {
                 let file_loc = String::from(pb.to_string_lossy());
                 match std::fs::read_to_string(&file_loc) {
-                    Ok(source) => Some(Arc::new(FileSource {
+                    Ok(source) => Ok(Arc::new(FileSource {
                         id: Uuid::new_v4(),
                         path: file_loc,
                         source: Some(source),
@@ -280,15 +295,11 @@ impl<'a> Compiler {
                     })),
                     Err(_) => {
                         tracing::error!(target: "core", "FILE READ FAILED: \"{}\"!", file_loc);
-                        None
+                        Err(CompilerError::FileUnpackError(UnpackError::MissingFile(file_loc)))
                     }
                 }
             })
-            .filter(|f| f.is_some())
-            .map(|f| f.unwrap_or_default())
-            .collect();
-        tracing::info!(target: "core", "COMPILER FETCHED {} FILE SOURCES", files.len());
-        files
+            .collect()
     }
 
     /// Recurses file dependencies
@@ -327,7 +338,12 @@ impl<'a> Compiler {
             tracing::info!(target: "core", "LOCALIZED IMPORTS {:?}", localized_imports);
         }
         let import_bufs: Vec<PathBuf> = Compiler::transform_paths(&localized_imports)?;
-        let mut file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(&import_bufs);
+        let potentials: Result<Vec<Arc<FileSource>>, CompilerError> =
+            Compiler::fetch_sources(import_bufs).into_iter().collect();
+        let mut file_sources = match potentials {
+            Ok(p) => p,
+            Err(e) => return Err(Arc::new(e)),
+        };
         if !file_sources.is_empty() {
             tracing::info!(target: "core", "FETCHED {} FILE SOURCES", file_sources.len());
         }
diff --git a/huff_utils/src/error.rs b/huff_utils/src/error.rs
index 277b7e2..4bbbfb9 100644
--- a/huff_utils/src/error.rs
+++ b/huff_utils/src/error.rs
@@ -258,6 +258,9 @@ impl<'a> fmt::Display for CompilerError<'a> {
                         unsupported
                     )
                 }
+                UnpackError::MissingFile(file) => {
+                    write!(f, "\nError: File Not Found \"{}\"\n", file)
+                }
             },
             CompilerError::ParserError(pe) => match &pe.kind {
                 ParserErrorKind::SyntaxError(se) => {
diff --git a/huff_utils/src/io.rs b/huff_utils/src/io.rs
index bdcad60..d6ed321 100644
--- a/huff_utils/src/io.rs
+++ b/huff_utils/src/io.rs
@@ -12,6 +12,8 @@ pub enum UnpackError {
     UnsupportedExtension(String),
     /// Failed to read directory
     InvalidDirectory(String),
+    /// Missing File
+    MissingFile(String),
 }
 
 /// Unpacks huff files into a vec of strings.


Test patch:
diff --git a/huff_core/tests/erc20.rs b/huff_core/tests/erc20.rs
index 78dba6f..e823e3e 100644
--- a/huff_core/tests/erc20.rs
+++ b/huff_core/tests/erc20.rs
@@ -8,9 +8,12 @@ use huff_utils::prelude::*;
 
 #[test]
 fn test_erc20_compile() {
-    let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(&vec![PathBuf::from(
+    let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(vec![PathBuf::from(
         "../huff-examples/erc20/contracts/ERC20.huff".to_string(),
-    )]);
+    )])
+    .iter()
+    .map(|p| p.clone().unwrap())
+    .collect();
 
     // Recurse file deps + generate flattened source
     let file_source = file_sources.get(0).unwrap();
diff --git a/huff_core/tests/erc721.rs b/huff_core/tests/erc721.rs
index 102bd0b..cd647d5 100644
--- a/huff_core/tests/erc721.rs
+++ b/huff_core/tests/erc721.rs
@@ -8,9 +8,12 @@ use huff_utils::prelude::*;
 
 #[test]
 fn test_erc721_compile() {
-    let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(&vec![PathBuf::from(
+    let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(vec![PathBuf::from(
         "../huff-examples/erc721/contracts/ERC721.huff".to_string(),
-    )]);
+    )])
+    .iter()
+    .map(|p| p.clone().unwrap())
+    .collect();
 
     // Recurse file deps + generate flattened source
     let file_source = file_sources.get(0).unwrap();
diff --git a/huff_core/tests/recurse_deps.rs b/huff_core/tests/recurse_deps.rs
index eab44c0..3eee579 100644
--- a/huff_core/tests/recurse_deps.rs
+++ b/huff_core/tests/recurse_deps.rs
@@ -5,9 +5,12 @@ use huff_utils::files::FileSource;
 
 #[test]
 fn test_recursing_fs_dependencies() {
-    let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(&vec![PathBuf::from(
+    let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(vec![PathBuf::from(
         "../huff-examples/erc20/contracts/ERC20.huff".to_string(),
-    )]);
+    )])
+    .iter()
+    .map(|p| p.clone().unwrap())
+    .collect();
     assert_eq!(file_sources.len(), 1);
     let erc20_file_source = file_sources[0].clone();
     let res = Compiler::recurse_deps(Arc::clone(&erc20_file_source));
@@ -22,9 +25,12 @@ fn test_recursing_fs_dependencies() {
 
 #[test]
 fn test_recursing_external_dependencies() {
-    let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(&vec![PathBuf::from(
+    let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(vec![PathBuf::from(
         "../huff-examples/erc20/contracts/ERC20.huff".to_string(),
-    )]);
+    )])
+    .iter()
+    .map(|p| p.clone().unwrap())
+    .collect();
     assert_eq!(file_sources.len(), 1);
     let erc20_file_source = file_sources[0].clone();
     let res = Compiler::recurse_deps(Arc::clone(&erc20_file_source));
diff --git a/huff_core/tests/sources.rs b/huff_core/tests/sources.rs
index fe2bf78..de2b7e9 100644
--- a/huff_core/tests/sources.rs
+++ b/huff_core/tests/sources.rs
@@ -1,15 +1,18 @@
 use std::{path::PathBuf, sync::Arc};
 
 use huff_core::Compiler;
-use huff_utils::files::FileSource;
+use huff_utils::prelude::*;
 
 #[test]
 fn test_fetch_sources() {
-    let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(&vec![
+    let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(vec![
         PathBuf::from("../huff-examples/erc20/contracts/ERC20.huff".to_string()),
         PathBuf::from("../huff-examples/erc20/contracts/utils/Address.huff".to_string()),
         PathBuf::from("../huff-examples/erc20/contracts/utils/HashMap.huff".to_string()),
-    ]);
+    ])
+    .iter()
+    .map(|p| p.clone().unwrap())
+    .collect();
     assert_eq!(file_sources.len(), 3);
     assert_eq!(file_sources[0].path, "../huff-examples/erc20/contracts/ERC20.huff".to_string());
     assert_eq!(
@@ -24,12 +27,20 @@ fn test_fetch_sources() {
 
 #[test]
 fn test_fetch_invalid_sources() {
-    let file_sources: Vec<Arc<FileSource>> = Compiler::fetch_sources(&vec![
+    let paths = vec![
         PathBuf::from("../huff-examples/erc20/contracts/non_existant.huff".to_string()),
         PathBuf::from("../huff-examples/erc20/contracts/non_huff.txt".to_string()),
         PathBuf::from("../huff-examples/erc20/contracts/random/Address.huff".to_string()),
         PathBuf::from("../huff-examples/erc20/contracts/random/".to_string()),
         PathBuf::from("../huff-examples/erc20/contracts/utils/".to_string()),
-    ]);
-    assert_eq!(file_sources.len(), 0);
+    ];
+    let file_sources: Vec<Result<Arc<FileSource>, CompilerError>> =
+        Compiler::fetch_sources(paths.clone());
+    for (i, e) in file_sources.iter().enumerate() {
+        let file_loc = String::from(paths[i].to_string_lossy());
+        assert_eq!(
+            e.clone().err().unwrap(),
+            CompilerError::FileUnpackError(UnpackError::MissingFile(file_loc))
+        )
+    }
 }