ronantakizawa/codereview-bench huggingface dataset:
Repository: casey/just
File: tests/working_directory.rs (Rust)

Code before:
      "
set working-directory := 'bar'

file := shell('cat file.txt')

@foo:
  echo {{ file }}
",
    )
    .arg("foo")
    .write("foo/bar/file.txt", "FILE")
    .stdout("FILE\n")
    .run();
}

#[test]
fn attribute() {
  Test::new()
    .justfile(
      r#"
      [working-directory('bar')]
      print1:
        echo "$(basename "$PWD")"

      [working-directory('bar')]
      [working-directory('baz')]
      print2:
        echo "$(basename "$PWD")"

      [working-directory('bar')]
      [no-cd]
      print3:
        echo "$(basename "$PWD")"
    "#,
    )
    .current_dir("foo")
    .tree(tree! {
      foo: {},
      bar: {},
      baz: {},
    })
    .args(["print1", "print2", "print3"])
    .stderr(
      r#"echo "$(basename "$PWD")"
echo "$(basename "$PWD")"
echo "$(basename "$PWD")"
"#,
    )
    .stdout("bar\nbaz\nfoo\n")
    .run();
}

Review comment (refactor):
Duplicate working directory attributes should be an error.

Code after:
      "
set working-directory := 'bar'

file := shell('cat file.txt')

@foo:
  echo {{ file }}
",
    )
    .arg("foo")
    .write("foo/bar/file.txt", "FILE")
    .stdout("FILE\n")
    .run();
}

#[test]
fn attribute_duplicate() {
  Test::new()
    .justfile(
      "
        [working-directory('bar')]
        [working-directory('baz')]
        foo:
      ",
    )
    .stderr(
      "error: Recipe attribute `working-directory` first used on line 1 is duplicated on line 2
 ——▶ justfile:2:2
  │
2 │ [working-directory('baz')]
  │  ^^^^^^^^^^^^^^^^^
",
    )
    .status(EXIT_FAILURE)
    .run();
}

#[test]
fn attribute() {
  Test::new()
    .justfile(
      "
        [working-directory('foo')]
        @qux:
          echo baz > bar
      ",
    )
    .create_dir("foo")
    .expect_file("foo/bar", "baz\n")
    .run();
}
Repository: casey/just
File: tests/json.rs (Rust)

Code before:
use super::*;

fn case<F: Fn(&Path) -> Value>(justfile: &str, value: F) {
  Test::new()
    .justfile(justfile)
    .args(["--dump", "--dump-format", "json"])
    .stdout_with_tempdir(|dir| {
      format!(
        "{}\n",
        serde_json::to_string(&value(&dir.path().canonicalize().unwrap())).unwrap()
      )
    })
    .run();
}

#[test]
fn alias() {
  let test = Test::new().justfile(
    "
      alias f := foo

      foo:
    ",
  );
  let source = test
    .tempdir
    .path()
    .canonicalize()

Review comment (suggestion):
The JSON tests are pretty horrible. I changed the `case` function so that you pass in a struct which should serialize to the output of `--dump`.

You can take advantage of this by putting the relative path in the `source` field of the passed in `Module`, and then, in `case`, prepending the path to the temporary directory, which will make them match.

Code after:
use super::*;

#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
struct Alias<'a> {
  attributes: Vec<&'a str>,
  name: &'a str,
  target: &'a str,
}

#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
struct Assignment<'a> {
  export: bool,
  name: &'a str,
  private: bool,
  value: &'a str,
}

#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
struct Dependency<'a> {
  arguments: Vec<Value>,
  recipe: &'a str,
}

#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
Repository: casey/just
File: src/justfile.rs (Rust)

Code before:
  run_error! {
    name: export_failure,
    src: r#"
      export foo := "a"
      baz := "c"
      export bar := "b"
      export abc := foo + bar + baz

      wut:
        echo $foo $bar $baz
    "#,
    args: ["--quiet", "wut"],
    error: Code {
      recipe,
      line_number,
      print_message,
      ..
    },
    check: {
      assert_eq!(recipe, "wut");
      assert_eq!(line_number, Some(7));
      assert!(print_message);
    }
  }

  run_error! {
    name: exit_message_error,
    src: "
      set no-exit-message
      
      [exit-message]
      fail:
        @exit 100
    ",
    args: ["fail"],
    error: Code {
      recipe,
      line_number,
      code,
      print_message,
    },
    check: {
      assert_eq!(recipe, "fail");
      assert_eq!(code, 100);
      assert_eq!(line_number, Some(5));
      assert!(print_message);
    }
  }

  run_error! {
    name: exit_message_overrides_error,

Review comment (suggestion):
I'm trying to move away from the test generation macros. New tests should be integration tests, and use `Test::new()`, see examples in the `tests` directory.

Code after:
  run_error! {
    name: export_failure,
    src: r#"
      export foo := "a"
      baz := "c"
      export bar := "b"
      export abc := foo + bar + baz

      wut:
        echo $foo $bar $baz
    "#,
    args: ["--quiet", "wut"],
    error: Code {
      recipe,
      line_number,
      print_message,
      ..
    },
    check: {
      assert_eq!(recipe, "wut");
      assert_eq!(line_number, Some(7));
      assert!(print_message);
    }
  }

  fn case(input: &str, expected: &str) {
    let justfile = compile(input);
    let actual = format!("{}", justfile.color_display(Color::never()));
    assert_eq!(actual, expected);
    println!("Re-parsing...");
    let reparsed = compile(&actual);
    let redumped = format!("{}", reparsed.color_display(Color::never()));
    assert_eq!(redumped, actual);
  }

  #[test]
  fn parse_empty() {
    case(
      "

# hello


    ",
      "",
    );
  }

  #[test]
  fn parse_string_default() {
    case(
Repository: casey/just
File: tests/no_exit_message.rs (Rust)

Code before:
",
  stdout: "Hello, World!\n",
  stderr: "",
  status: 100,
}

test! {
  name: exit_message_attribute_and_setting,
  justfile: r"
set no-exit-message

[no-exit-message]
hello:
  echo 'Hello, World!'
  exit 100
",
  stdout: "Hello, World!\n",
  stderr: r#"
    echo 'Hello, World!'
    exit 100
  "#,
  status: 100,
}


test! {
  name: silent_recipe_exit_message_setting_suppressed,
  justfile: r#"
set no-exit-message

@hello:
  echo "Hello, World!"
  exit 100
"#,
  stdout:   "Hello, World!\n",
  stderr:   "",
  status:   100,
}

Review comment (suggestion):
I think this test can be removed, since whether or not a recipe is silent doesn't interact with exit messages.

Code after:
      "
      [no-exit-message]
      hello:
        #!/usr/bin/env bash
        echo 'Hello, World!'
        exit 100
    ",
    )
    .stdout("Hello, World!\n")
    .status(100)
    .run();
}

#[test]
fn no_exit_message() {
  Test::new()
    .justfile(
      "
      [no-exit-message]
      @hello:
        echo 'Hello, World!'
        exit 100
    ",
    )
    .stdout("Hello, World!\n")
    .status(100)
    .run();
}

#[test]
fn exit_message() {
  Test::new()
    .justfile(
      "
      [exit-message]
      @hello:
        echo 'Hello, World!'
        exit 100
    ",
    )
    .stdout("Hello, World!\n")
    .status(100)
    .stderr("error: Recipe `hello` failed on line 4 with exit code 100\n")
    .run();
}

#[test]
fn recipe_exit_message_setting_suppressed() {
  Test::new()
    .justfile(
      "
Repository: casey/just
File: tests/no_exit_message.rs (Rust)

Code before:
hello:
  @echo "Hello, World!"
  @exit 100
"#,
  stdout:   "Hello, World!\n",
  stderr:   "",
  status:   100,
}


test! {
  name: shebang_exit_message_setting_suppressed,
  justfile: r"
set no-exit-message

hello:
  #!/usr/bin/env bash
  echo 'Hello, World!'
  exit 100
",
  stdout: "Hello, World!\n",
  stderr: "",
  status: 100,
}

test! {
  name: exit_message_attribute_and_setting,
  justfile: r"
set no-exit-message

[no-exit-message]
hello:
  echo 'Hello, World!'
  exit 100
",
  stdout: "Hello, World!\n",
  stderr: r#"
    echo 'Hello, World!'
    exit 100
  "#,
  status: 100,
}


test! {
  name: silent_recipe_exit_message_setting_suppressed,
  justfile: r#"
set no-exit-message

@hello:
  echo "Hello, World!"

Review comment (bug):
I think this test can be removed. It would be a quite weird interaction in this code if this failed.

Code after:
    ",
    )
    .stderr(
      "
      error: Extraneous attribute
       ——▶ justfile:1:1
        │
      1 │ [no-exit-message]
        │ ^
    ",
    )
    .status(EXIT_FAILURE)
    .run();
}

#[test]
fn shebang_exit_message_suppressed() {
  Test::new()
    .justfile(
      "
      [no-exit-message]
      hello:
        #!/usr/bin/env bash
        echo 'Hello, World!'
        exit 100
    ",
    )
    .stdout("Hello, World!\n")
    .status(100)
    .run();
}

#[test]
fn no_exit_message() {
  Test::new()
    .justfile(
      "
      [no-exit-message]
      @hello:
        echo 'Hello, World!'
        exit 100
    ",
    )
    .stdout("Hello, World!\n")
    .status(100)
    .run();
}

#[test]
fn exit_message() {
  Test::new()
Repository: casey/just
File: src/compile_error_kind.rs (Rust)

Code before:
    close: Delimiter,
  },
  UnexpectedEndOfToken {
    expected: Vec<char>,
  },
  UnexpectedToken {
    expected: Vec<TokenKind>,
    found: TokenKind,
  },
  UnicodeEscapeCharacter {
    character: char,
  },
  UnicodeEscapeDelimiter {
    character: char,
  },
  UnicodeEscapeEmpty,
  UnicodeEscapeLength {
    hex: String,
  },
  UnicodeEscapeRange {
    hex: String,
  },
  UnicodeEscapeUnterminated,
  UnknownAliasTarget {
    alias: &'src str,
    target: String,
  },
  UnknownAttribute {
    attribute: &'src str,
  },
  UnknownDependency {
    recipe: &'src str,
    unknown: &'src str,
  },
  UnknownFunction {
    function: &'src str,
  },
  UnknownSetting {
    setting: &'src str,
  },
  UnknownStartOfToken,
  UnpairedCarriageReturn,
  UnterminatedBacktick,
  UnterminatedInterpolation,
  UnterminatedString,
}


Review comment (suggestion):
Can this be a namepath?

```suggestion
    target: Namepath<'src>,
```

Code after:
    close: Delimiter,
  },
  UnexpectedEndOfToken {
    expected: Vec<char>,
  },
  UnexpectedToken {
    expected: Vec<TokenKind>,
    found: TokenKind,
  },
  UnicodeEscapeCharacter {
    character: char,
  },
  UnicodeEscapeDelimiter {
    character: char,
  },
  UnicodeEscapeEmpty,
  UnicodeEscapeLength {
    hex: String,
  },
  UnicodeEscapeRange {
    hex: String,
  },
  UnicodeEscapeUnterminated,
  UnknownAliasTarget {
    alias: &'src str,
    target: Namepath<'src>,
  },
  UnknownAttribute {
    attribute: &'src str,
  },
  UnknownDependency {
    recipe: &'src str,
    unknown: &'src str,
  },
  UnknownFunction {
    function: &'src str,
  },
  UnknownSetting {
    setting: &'src str,
  },
  UnknownStartOfToken {
    start: char,
  },
  UnpairedCarriageReturn,
  UnterminatedBacktick,
  UnterminatedInterpolation,
  UnterminatedString,
}
Repository: casey/just
File: src/analyzer.rs (Rust)

Code before:
      .pop()
      .expect("Internal error: Alias target path can't be empty");

    let alias = Alias {
      attributes,
      name,
      target,
    };

    let mut maybe_recipes = Some(recipes);
    if !path.is_empty() {
      maybe_recipes =
        Self::traverse_nonempty_module_name_path(&path, modules).map(|justfile| &justfile.recipes);
    }

    // Make sure the target recipe exists
    match maybe_recipes.and_then(|recipes| recipes.get(target.lexeme())) {
      Some(target) => Ok(alias.resolve(Rc::clone(target))),
      None => Err(alias.name.token.error(UnknownAliasTarget {
        alias: alias.name.lexeme(),
        target: target_str,
      })),
    }
  }

  fn traverse_nonempty_module_name_path<'modules>(
    path: &[Name],
    mut modules: &'modules Table<'src, Justfile<'src>>,
  ) -> Option<&'modules Justfile<'src>> {
    assert!(!path.is_empty());
  
    let mut iter = path.iter();
    let mut result = modules.get(iter.next().unwrap().lexeme())?;
    modules = &result.modules;

    for mod_name in iter {
      result = modules.get(mod_name.lexeme())?;
      modules = &result.modules;
    }

    Some(result)
  }
}

#[cfg(test)]
mod tests {
  use super::*;

  analysis_error! {
    name: duplicate_alias,
    input: "alias foo := bar\nalias foo := baz",

Review comment (bug):
I think this can be made simpler if it's recursive. Maybe with this signature:

```
fn foo(
  path: &[Name],
  modules: &Table<Justfile>,
  recipes: &Table<Rc<Recipe>>,
) -> Result<Option<Rc<Recipe>>, ()>;
```

If the path is empty, then resolution has failed, and you can return `Err(())`.

If path is length one, then look in `recipes` for the target recipe.

Otherwise, try to find the next module to look in using the first item in `path`. If not found, then return an error. If found, then make the recursive call with `path[1..]`, and the submodules recipes and modules.

Code after:
  }

  fn alias_target<'a>(
    path: &Namepath<'src>,
    mut modules: &'a Table<'src, Justfile<'src>>,
    mut recipes: &'a Table<'src, Rc<Recipe<'src>>>,
  ) -> Option<Rc<Recipe<'src>>> {
    let (name, path) = path.split_last();

    for name in path {
      let module = modules.get(name.lexeme())?;
      modules = &module.modules;
      recipes = &module.recipes;
    }

    recipes.get(name.lexeme()).cloned()
  }
}

#[cfg(test)]
mod tests {
  use super::*;

  analysis_error! {
    name: duplicate_alias,
    input: "alias foo := bar\nalias foo := baz",
    offset: 23,
    line: 1,
    column: 6,
    width: 3,
    kind: Redefinition { first_type: "alias", second_type: "alias", name: "foo", first: 0 },
  }

  analysis_error! {
    name: unknown_alias_target,
    input: "alias foo := bar\n",
    offset: 6,
    line: 0,
    column: 6,
    width: 3,
    kind: UnknownAliasTarget {
      alias: "foo",
      target: Namepath::from(Name::from_identifier(
        Token{
          column: 13,
          kind: TokenKind::Identifier,
          length: 3,
          line: 0,
          offset: 13,
          path: Path::new("justfile"),
          src: "alias foo := bar\n",
Repository: casey/just
File: src/analyzer.rs (Rust)

Code before:
      return Err(set.name.error(DuplicateSet {
        setting: original.name.lexeme(),
        first: original.name.line,
      }));
    }

    Ok(())
  }

  fn resolve_alias<'a>(
    modules: &'a Table<'src, Justfile<'src>>,
    recipes: &'a Table<'src, Rc<Recipe<'src>>>,
    alias: Alias<'src, Namepath<'src>>,
  ) -> CompileResult<'src, Alias<'src>> {
    let Alias {
      attributes,
      name,
      target,
    } = alias;
    let target_str = format!("{target}");
    let mut path = target.into_inner();
    let target = path
      .pop()
      .expect("Internal error: Alias target path can't be empty");

    let alias = Alias {
      attributes,
      name,
      target,
    };

    let mut maybe_recipes = Some(recipes);
    if !path.is_empty() {
      maybe_recipes =
        Self::traverse_nonempty_module_name_path(&path, modules).map(|justfile| &justfile.recipes);
    }

    // Make sure the target recipe exists
    match maybe_recipes.and_then(|recipes| recipes.get(target.lexeme())) {
      Some(target) => Ok(alias.resolve(Rc::clone(target))),
      None => Err(alias.name.token.error(UnknownAliasTarget {
        alias: alias.name.lexeme(),
        target: target_str,
      })),
    }
  }

  fn traverse_nonempty_module_name_path<'modules>(
    path: &[Name],
    mut modules: &'modules Table<'src, Justfile<'src>>,
  ) -> Option<&'modules Justfile<'src>> {

Review comment (suggestion):
I think that `resolve` can be changed so that it's implemented for the correct type, so we don't need to take apart and put the alias back together again.

Code after:
    if let Some(original) = self.sets.get(set.name.lexeme()) {
      return Err(set.name.error(DuplicateSet {
        setting: original.name.lexeme(),
        first: original.name.line,
      }));
    }

    Ok(())
  }

  fn resolve_alias<'a>(
    modules: &'a Table<'src, Justfile<'src>>,
    recipes: &'a Table<'src, Rc<Recipe<'src>>>,
    alias: Alias<'src, Namepath<'src>>,
  ) -> CompileResult<'src, Alias<'src>> {
    match Self::alias_target(&alias.target, modules, recipes) {
      Some(target) => Ok(alias.resolve(target)),
      None => Err(alias.name.token.error(UnknownAliasTarget {
        alias: alias.name.lexeme(),
        target: alias.target,
      })),
    }
  }

  fn alias_target<'a>(
    path: &Namepath<'src>,
    mut modules: &'a Table<'src, Justfile<'src>>,
    mut recipes: &'a Table<'src, Rc<Recipe<'src>>>,
  ) -> Option<Rc<Recipe<'src>>> {
    let (name, path) = path.split_last();

    for name in path {
      let module = modules.get(name.lexeme())?;
      modules = &module.modules;
      recipes = &module.recipes;
    }

    recipes.get(name.lexeme()).cloned()
  }
}

#[cfg(test)]
mod tests {
  use super::*;

  analysis_error! {
    name: duplicate_alias,
    input: "alias foo := bar\nalias foo := baz",
    offset: 23,
    line: 1,
    column: 6,
Repository: casey/just
File: src/parser.rs (Rust)

Code before:
            return Err(token.error(CompileErrorKind::UnicodeEscapeCharacter { character: c }));
          }
        },
      }
    }

    if state != State::Initial {
      return Err(token.error(CompileErrorKind::UnicodeEscapeUnterminated));
    }

    Ok(cooked)
  }

  /// Parse a string literal, e.g. `"FOO"`
  fn parse_string_literal(&mut self) -> CompileResult<'src, StringLiteral<'src>> {
    let (_token, string_literal) = self.parse_string_literal_token()?;
    Ok(string_literal)
  }

  /// Parse a name from an identifier token
  fn parse_name(&mut self) -> CompileResult<'src, Name<'src>> {
    self.expect(Identifier).map(Name::from_identifier)
  }

  /// Parse a path of the forms `a` or `a::b` or `a::b::c` etc.
  fn parse_double_colon_path(&mut self) -> CompileResult<'src, Namepath<'src>> {
    let first = self.parse_name()?;
    let mut result = vec![first];

    while self.accepted(ColonColon)? {
      let name = self.parse_name()?;
      result.push(name);
    }

    Ok(Namepath::new(result))
  }

  /// Parse sequence of comma-separated expressions
  fn parse_sequence(&mut self) -> CompileResult<'src, Vec<Expression<'src>>> {
    self.presume(ParenL)?;

    let mut elements = Vec::new();

    while !self.next_is(ParenR) {
      elements.push(self.parse_expression()?);

      if !self.accepted(Comma)? {
        break;
      }
    }


Review comment (suggestion):
```suggestion
  fn parse_namepath(&mut self) -> CompileResult<'src, Namepath<'src>> {
```

Code after:
            return Err(token.error(CompileErrorKind::UnicodeEscapeCharacter { character: c }));
          }
        },
      }
    }

    if state != State::Initial {
      return Err(token.error(CompileErrorKind::UnicodeEscapeUnterminated));
    }

    Ok(cooked)
  }

  /// Parse a string literal, e.g. `"FOO"`
  fn parse_string_literal(&mut self) -> CompileResult<'src, StringLiteral<'src>> {
    let (_token, string_literal) = self.parse_string_literal_token()?;
    Ok(string_literal)
  }

  /// Parse a name from an identifier token
  fn parse_name(&mut self) -> CompileResult<'src, Name<'src>> {
    self.expect(Identifier).map(Name::from_identifier)
  }

  /// Parse a path of `::` separated names
  fn parse_namepath(&mut self) -> CompileResult<'src, Namepath<'src>> {
    let first = self.parse_name()?;
    let mut path = Namepath::from(first);

    while self.accepted(ColonColon)? {
      let name = self.parse_name()?;
      path.push(name);
    }

    Ok(path)
  }

  /// Parse sequence of comma-separated expressions
  fn parse_sequence(&mut self) -> CompileResult<'src, Vec<Expression<'src>>> {
    self.presume(ParenL)?;

    let mut elements = Vec::new();

    while !self.next_is(ParenR) {
      elements.push(self.parse_expression()?);

      if !self.accepted(Comma)? {
        break;
      }
    }
Repository: casey/just
File: src/analyzer.rs (Rust)

Code before:
  }
}

#[cfg(test)]
mod tests {
  use super::*;

  analysis_error! {
    name: duplicate_alias,
    input: "alias foo := bar\nalias foo := baz",
    offset: 23,
    line: 1,
    column: 6,
    width: 3,
    kind: Redefinition { first_type: "alias", second_type: "alias", name: "foo", first: 0 },
  }

  const UNKNOWN_ALIAS_TARGET: &str = "alias foo := bar\n";
  analysis_error! {
    name: unknown_alias_target,
    input: UNKNOWN_ALIAS_TARGET,
    offset: 6,
    line: 0,
    column: 6,
    width: 3,
    kind: UnknownAliasTarget {alias: "foo", target: Namepath::from(Name::from_identifier(Token{
    column: 13,kind: TokenKind::Identifier,length: 3,line: 0,offset: 13,path: Path::new("justfile"),src: UNKNOWN_ALIAS_TARGET,}))},
  }

  analysis_error! {
    name: alias_shadows_recipe_before,
    input: "bar: \n  echo bar\nalias foo := bar\nfoo:\n  echo foo",
    offset: 34,
    line: 3,
    column: 0,
    width: 3,
    kind: Redefinition { first_type: "alias", second_type: "recipe", name: "foo", first: 2 },
  }

  analysis_error! {
    name: alias_shadows_recipe_after,
    input: "foo:\n  echo foo\nalias foo := bar\nbar:\n  echo bar",
    offset: 22,
    line: 2,
    column: 6,
    width: 3,
    kind: Redefinition { first_type: "recipe", second_type: "alias", name: "foo", first: 0 },
  }

  analysis_error! {
    name:   required_after_default,

Review comment (suggestion):
This should be formatted to be on multiple lines:

```suggestion
    kind: UnknownAliasTarget {
      alias: "foo",
      target: Namepath::from(Name::from_identifier(Token {
        column: 13,
        …
```

Code after:

#[cfg(test)]
mod tests {
  use super::*;

  analysis_error! {
    name: duplicate_alias,
    input: "alias foo := bar\nalias foo := baz",
    offset: 23,
    line: 1,
    column: 6,
    width: 3,
    kind: Redefinition { first_type: "alias", second_type: "alias", name: "foo", first: 0 },
  }

  analysis_error! {
    name: unknown_alias_target,
    input: "alias foo := bar\n",
    offset: 6,
    line: 0,
    column: 6,
    width: 3,
    kind: UnknownAliasTarget {
      alias: "foo",
      target: Namepath::from(Name::from_identifier(
        Token{
          column: 13,
          kind: TokenKind::Identifier,
          length: 3,
          line: 0,
          offset: 13,
          path: Path::new("justfile"),
          src: "alias foo := bar\n",
        }
      ))
    },
  }

  analysis_error! {
    name: alias_shadows_recipe_before,
    input: "bar: \n  echo bar\nalias foo := bar\nfoo:\n  echo foo",
    offset: 34,
    line: 3,
    column: 0,
    width: 3,
    kind: Redefinition { first_type: "alias", second_type: "recipe", name: "foo", first: 2 },
  }

  analysis_error! {
    name: alias_shadows_recipe_after,
    input: "foo:\n  echo foo\nalias foo := bar\nbar:\n  echo bar",
Repository: casey/just
File: src/namepath.rs (Rust)

Code before:
    for (i, name) in self.0.iter().enumerate() {
      if i > 0 {
        write!(f, "::")?;
      }
      write!(f, "{name}")?;
    }
    Ok(())
  }
}

impl<'src> From<Name<'src>> for Namepath<'src> {
  fn from(name: Name<'src>) -> Self {
    Self(vec![name])
  }
}

impl Serialize for Namepath<'_> {
  fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
  where
    S: Serializer,
  {
    serializer.serialize_str(&format!("{self}"))
  }
}

impl<'src> Deref for Namepath<'src> {
  type Target = [Name<'src>];

  fn deref(&self) -> &Self::Target {
    &self.0
  }
}


Review comment (suggestion):
We shouldn't add `deref` to `Namepath`. It means that any function on `Vec` (I actually changed it to slice before I decided we shouldn't provide it at all.) is valid for namepath, which I think is not ideal. See comment about `last` later on.

Code after:
  fn fmt(&self, f: &mut Formatter) -> fmt::Result {
    for (i, name) in self.0.iter().enumerate() {
      if i > 0 {
        write!(f, "::")?;
      }
      write!(f, "{name}")?;
    }
    Ok(())
  }
}

impl<'src> From<Name<'src>> for Namepath<'src> {
  fn from(name: Name<'src>) -> Self {
    Self(vec![name])
  }
}

impl Serialize for Namepath<'_> {
  fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
  where
    S: Serializer,
  {
    serializer.serialize_str(&format!("{self}"))
  }
}