Repository: casey/just (rust)

Issue:
Multiple use of the same import is detected as circular
Currently circular imports are defined as including each other. This is the test verifying that behavior:
```rust
fn circular_import() {
  Test::new()
    .justfile("import 'a'")
    .tree(tree! {
      a: "import 'b'",
      b: "import 'a'",
    })
    .status(EXIT_FAILURE)
    .stderr_regex(path_for_regex(
      "error: Import `.*/a` in `.*/b` is circular\n",
    ))
    .run();
}
```

However, multiple use of the same import is also detected as circular and I don't think that is correct (test passes, but shouldn't IMHO):

```rust
fn reused_import() {
  Test::new()
    .justfile(
      "
      import 'a'
      import 'b'
    ",
    )
    .tree(tree! {
      a: "import 'c'",
      b: "import 'c'",
      c: "",
    })
    .status(EXIT_FAILURE)
    .stderr_regex(path_for_regex(
      "error: Import `.*/c` in `.*/a` is circular\n",
    ))
    .run();
}
```


Pull request description:
Allow multiple imports of the same file in different modules
Fixes #1848.

Patch:
diff --git a/src/compiler.rs b/src/compiler.rs
index a1e987a1..ac3dfc75 100644
--- a/src/compiler.rs
+++ b/src/compiler.rs
@@ -61,7 +61,7 @@ impl Compiler {
             };
 
             if let Some(import) = import {
-              if srcs.contains_key(&import) {
+              if current.file_path.contains(&import) {
                 return Err(Error::CircularImport {
                   current: current.path,
                   import,
@@ -87,7 +87,7 @@ impl Compiler {
               .lexiclean();
 
             if import.is_file() {
-              if srcs.contains_key(&import) {
+              if current.file_path.contains(&import) {
                 return Err(Error::CircularImport {
                   current: current.path,
                   import,
@@ -229,26 +229,11 @@ recipe_b: recipe_c
 
   #[test]
   fn recursive_includes_fail() {
-    let justfile_a = r#"
-# A comment at the top of the file
-import "./subdir/justfile_b"
-
-some_recipe: recipe_b
-    echo "some recipe"
-
-"#;
-
-    let justfile_b = r#"
-import "../justfile"
-
-recipe_b:
-    echo "recipe b"
-"#;
     let tmp = temptree! {
-        justfile: justfile_a,
-        subdir: {
-            justfile_b: justfile_b
-        }
+      justfile: "import './subdir/b'\na: b",
+      subdir: {
+        b: "import '../justfile'\nb:"
+      }
     };
 
     let loader = Loader::new();
@@ -257,8 +242,8 @@ recipe_b:
     let loader_output = Compiler::compile(false, &loader, &justfile_a_path).unwrap_err();
 
     assert_matches!(loader_output, Error::CircularImport { current, import }
-        if current == tmp.path().join("subdir").join("justfile_b").lexiclean() &&
-        import == tmp.path().join("justfile").lexiclean()
+      if current == tmp.path().join("subdir").join("b").lexiclean() &&
+      import == tmp.path().join("justfile").lexiclean()
     );
   }
 }
diff --git a/src/source.rs b/src/source.rs
index 1da7a5f7..00754215 100644
--- a/src/source.rs
+++ b/src/source.rs
@@ -3,6 +3,7 @@ use super::*;
 #[derive(Debug)]
 pub(crate) struct Source<'src> {
   pub(crate) file_depth: u32,
+  pub(crate) file_path: Vec<PathBuf>,
   pub(crate) namepath: Namepath<'src>,
   pub(crate) path: PathBuf,
   pub(crate) submodule_depth: u32,
@@ -13,6 +14,7 @@ impl<'src> Source<'src> {
   pub(crate) fn root(path: &Path) -> Self {
     Self {
       file_depth: 0,
+      file_path: vec![path.into()],
       namepath: Namepath::default(),
       path: path.into(),
       submodule_depth: 0,
@@ -23,6 +25,12 @@ impl<'src> Source<'src> {
   pub(crate) fn import(&self, path: PathBuf) -> Self {
     Self {
       file_depth: self.file_depth + 1,
+      file_path: self
+        .file_path
+        .clone()
+        .into_iter()
+        .chain(iter::once(path.clone()))
+        .collect(),
       namepath: self.namepath.clone(),
       path,
       submodule_depth: self.submodule_depth,
@@ -33,10 +41,16 @@ impl<'src> Source<'src> {
   pub(crate) fn module(&self, name: Name<'src>, path: PathBuf) -> Self {
     Self {
       file_depth: self.file_depth + 1,
+      file_path: self
+        .file_path
+        .clone()
+        .into_iter()
+        .chain(iter::once(path.clone()))
+        .collect(),
       namepath: self.namepath.join(name),
+      path: path.clone(),
       submodule_depth: self.submodule_depth + 1,
       working_directory: path.parent().unwrap().into(),
-      path,
     }
   }
 }


Test patch:
diff --git a/tests/imports.rs b/tests/imports.rs
index ce6dcf94..448af29d 100644
--- a/tests/imports.rs
+++ b/tests/imports.rs
@@ -361,3 +361,22 @@ fn recipes_imported_in_root_run_in_command_line_provided_working_directory() {
     .stdout("BAZBAZ")
     .run();
 }
+
+#[test]
+fn reused_import_are_allowed() {
+  Test::new()
+    .justfile(
+      "
+      import 'a'
+      import 'b'
+
+      bar:
+    ",
+    )
+    .tree(tree! {
+      a: "import 'c'",
+      b: "import 'c'",
+      c: "",
+    })
+    .run();
+}