Skip to content

Commit

Permalink
feat: add support for usage of super in import paths (#5502)
Browse files Browse the repository at this point in the history
# Description

## Problem

Resolves #4835

## Summary

Implements `super` in `use` statements and regular paths. For example:

```rust
fn some_function() {}

mod foo {
  use super::some_function(); // used in a `use`

  fn another_function() {
    super::some_function(); // used in a path
  }

  fn foo() {
    some_function(); // of course this also works because of the `use` above
  }
}
```

Note that, unlike Rust, only one `super` is allowed: `super::super::...`
will give an error.

## Additional Context

None.

## Documentation\*

Check one:
- [ ] No documentation needed.
- [x] Documentation included in this PR.
- [ ] **[For Experimental Features]** Documentation to be submitted in a
separate PR.

# PR Checklist\*

- [x] I have tested the changes locally.
- [x] I have formatted the changes with [Prettier](https://prettier.io/)
and/or `cargo fmt` on default settings.
  • Loading branch information
asterite authored Jul 15, 2024
1 parent b59a29e commit 256509e
Show file tree
Hide file tree
Showing 12 changed files with 186 additions and 66 deletions.
2 changes: 2 additions & 0 deletions compiler/noirc_frontend/src/ast/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ pub enum PathKind {
Crate,
Dep,
Plain,
Super,
}

#[derive(Debug, PartialEq, Eq, Clone)]
Expand Down Expand Up @@ -748,6 +749,7 @@ impl Display for PathKind {
match self {
PathKind::Crate => write!(f, "crate"),
PathKind::Dep => write!(f, "dep"),
PathKind::Super => write!(f, "super"),
PathKind::Plain => write!(f, "plain"),
}
}
Expand Down
6 changes: 1 addition & 5 deletions compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -732,11 +732,7 @@ impl<'a> ModCollector<'a> {

context.def_interner.add_module_attributes(
mod_id,
ModuleAttributes {
name: mod_name.0.contents.clone(),
location: mod_location,
parent: self.module_id,
},
ModuleAttributes { name: mod_name.0.contents.clone(), location: mod_location },
);
}

Expand Down
24 changes: 24 additions & 0 deletions compiler/noirc_frontend/src/hir/resolution/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ pub enum PathResolutionError {
Unresolved(Ident),
#[error("{0} is private and not visible from the current module")]
Private(Ident),
#[error("There is no super module")]
NoSuper(Span),
}

#[derive(Debug)]
Expand Down Expand Up @@ -73,6 +75,9 @@ impl<'a> From<&'a PathResolutionError> for CustomDiagnostic {
format!("{ident} is private"),
ident.span(),
),
PathResolutionError::NoSuper(span) => {
CustomDiagnostic::simple_error(error.to_string(), String::new(), *span)
}
}
}
}
Expand Down Expand Up @@ -187,6 +192,25 @@ fn resolve_path_to_ns(
path_references,
importing_crate,
),

crate::ast::PathKind::Super => {
if let Some(parent_module_id) =
def_maps[&crate_id].modules[import_directive.module_id.0].parent
{
resolve_name_in_module(
crate_id,
importing_crate,
import_path,
parent_module_id,
def_maps,
path_references,
)
} else {
let span_start = import_directive.path.span().start();
let span = Span::from(span_start..span_start + 5); // 5 == "super".len()
Err(PathResolutionError::NoSuper(span))
}
}
}
}

Expand Down
1 change: 0 additions & 1 deletion compiler/noirc_frontend/src/node_interner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ const IMPL_SEARCH_RECURSION_LIMIT: u32 = 10;
pub struct ModuleAttributes {
pub name: String,
pub location: Location,
pub parent: LocalModuleId,
}

type StructAttributes = Vec<SecondaryAttribute>;
Expand Down
2 changes: 2 additions & 0 deletions compiler/noirc_frontend/src/parser/parser/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub(super) fn path() -> impl NoirParser<Path> {
choice((
path_kind(Keyword::Crate, PathKind::Crate),
path_kind(Keyword::Dep, PathKind::Dep),
path_kind(Keyword::Super, PathKind::Super),
idents().map_with_span(make_path(PathKind::Plain)),
))
}
Expand Down Expand Up @@ -64,6 +65,7 @@ mod test {
("std", PathKind::Plain),
("hash::collections", PathKind::Plain),
("crate::std::hash", PathKind::Crate),
("super::foo", PathKind::Super),
];

for (src, expected_path_kind) in cases {
Expand Down
118 changes: 74 additions & 44 deletions compiler/noirc_frontend/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ pub(crate) fn get_program_errors(src: &str) -> Vec<(CompilationError, FileId)> {
get_program(src, false).2
}

fn assert_no_errors(src: &str) {
let errors = get_program_errors(src);
if !errors.is_empty() {
panic!("Expected no errors, got: {:?}", errors);
}
}

#[test]
fn check_trait_implemented_for_all_t() {
let src = "
Expand Down Expand Up @@ -141,10 +148,7 @@ fn check_trait_implemented_for_all_t() {
fn main(a: Foo) -> pub bool {
a.is_default()
}";

let errors = get_program_errors(src);
errors.iter().for_each(|err| println!("{:?}", err));
assert!(errors.is_empty());
assert_no_errors(src);
}

#[test]
Expand Down Expand Up @@ -767,9 +771,7 @@ fn test_impl_self_within_default_def() {
self
}
}";
let errors = get_program_errors(src);
errors.iter().for_each(|err| println!("{:?}", err));
assert!(errors.is_empty());
assert_no_errors(src);
}

#[test]
Expand All @@ -794,10 +796,7 @@ fn check_trait_as_type_as_fn_parameter() {
fn main(a: Foo) -> pub bool {
test_eq(a)
}";

let errors = get_program_errors(src);
errors.iter().for_each(|err| println!("{:?}", err));
assert!(errors.is_empty());
assert_no_errors(src);
}

#[test]
Expand Down Expand Up @@ -830,10 +829,7 @@ fn check_trait_as_type_as_two_fn_parameters() {
fn main(a: Foo, b: u64) -> pub bool {
test_eq(a, b)
}";

let errors = get_program_errors(src);
errors.iter().for_each(|err| println!("{:?}", err));
assert!(errors.is_empty());
assert_no_errors(src);
}

fn get_program_captures(src: &str) -> Vec<Vec<String>> {
Expand Down Expand Up @@ -898,7 +894,7 @@ fn resolve_empty_function() {
}
";
assert!(get_program_errors(src).is_empty());
assert_no_errors(src);
}
#[test]
fn resolve_basic_function() {
Expand All @@ -908,7 +904,7 @@ fn resolve_basic_function() {
assert(y == x);
}
"#;
assert!(get_program_errors(src).is_empty());
assert_no_errors(src);
}
#[test]
fn resolve_unused_var() {
Expand Down Expand Up @@ -981,7 +977,7 @@ fn resolve_literal_expr() {
assert(y == x);
}
"#;
assert!(get_program_errors(src).is_empty());
assert_no_errors(src);
}

#[test]
Expand Down Expand Up @@ -1028,7 +1024,7 @@ fn resolve_prefix_expr() {
let _y = -x;
}
"#;
assert!(get_program_errors(src).is_empty());
assert_no_errors(src);
}

#[test]
Expand All @@ -1040,7 +1036,7 @@ fn resolve_for_expr() {
};
}
"#;
assert!(get_program_errors(src).is_empty());
assert_no_errors(src);
}

#[test]
Expand All @@ -1054,7 +1050,7 @@ fn resolve_call_expr() {
x
}
"#;
assert!(get_program_errors(src).is_empty());
assert_no_errors(src);
}

#[test]
Expand All @@ -1071,7 +1067,7 @@ fn resolve_shadowing() {
x
}
"#;
assert!(get_program_errors(src).is_empty());
assert_no_errors(src);
}

#[test]
Expand All @@ -1082,7 +1078,7 @@ fn resolve_basic_closure() {
closure(x)
}
"#;
assert!(get_program_errors(src).is_empty());
assert_no_errors(src);
}

#[test]
Expand Down Expand Up @@ -1133,7 +1129,7 @@ fn resolve_complex_closures() {
a + b + c + closure_with_transitive_captures(6)
}
"#;
assert!(get_program_errors(src).is_empty(), "there should be no errors");
assert_no_errors(src);

let expected_captures = vec![
vec![],
Expand Down Expand Up @@ -1636,8 +1632,7 @@ fn numeric_generic_in_function_signature() {
let src = r#"
fn foo<let N: u8>(arr: [Field; N]) -> [Field; N] { arr }
"#;
let errors = get_program_errors(src);
assert!(errors.is_empty());
assert_no_errors(src);
}

#[test]
Expand Down Expand Up @@ -1749,12 +1744,14 @@ fn numeric_generic_used_in_nested_type_pass() {
inner: [u64; N],
}
"#;
let errors = get_program_errors(src);
assert!(errors.is_empty());
assert_no_errors(src);
}

#[test]
fn numeric_generic_used_in_trait() {
// We want to make sure that `N` in `impl<let N: u64, T> Deserialize<N, T>` does
// not trigger `expected type, found numeric generic parameter N` as the trait
// does in fact expect a numeric generic.
let src = r#"
struct MyType<T> {
a: Field,
Expand All @@ -1773,11 +1770,7 @@ fn numeric_generic_used_in_trait() {
fn deserialize(fields: [Field; N], other: T) -> Self;
}
"#;
let errors = get_program_errors(src);
// We want to make sure that `N` in `impl<let N: u64, T> Deserialize<N, T>` does
// not trigger `expected type, found numeric generic parameter N` as the trait
// does in fact expect a numeric generic.
assert!(errors.is_empty());
assert_no_errors(src);
}

#[test]
Expand Down Expand Up @@ -1808,8 +1801,7 @@ fn numeric_generic_in_trait_impl_with_extra_impl_generics() {
fn deserialize(fields: [Field; N]) -> Self;
}
"#;
let errors = get_program_errors(src);
assert!(errors.is_empty());
assert_no_errors(src);
}

#[test]
Expand All @@ -1827,8 +1819,7 @@ fn numeric_generic_used_in_where_clause() {
T::deserialize(fields)
}
"#;
let errors = get_program_errors(src);
assert!(errors.is_empty());
assert_no_errors(src);
}

#[test]
Expand All @@ -1845,8 +1836,7 @@ fn numeric_generic_used_in_turbofish() {
assert(double::<7 + 8>() == 30);
}
"#;
let errors = get_program_errors(src);
assert!(errors.is_empty());
assert_no_errors(src);
}

#[test]
Expand All @@ -1866,8 +1856,7 @@ fn constant_used_with_numeric_generic() {
}
}
"#;
let errors = get_program_errors(src);
assert!(errors.is_empty());
assert_no_errors(src);
}

#[test]
Expand Down Expand Up @@ -2076,8 +2065,7 @@ fn turbofish_numeric_generic_nested_call() {
let _ = bar::<M>();
}
"#;
let errors = get_program_errors(src);
assert!(errors.is_empty());
assert_no_errors(src);

// Check for turbofish numeric generics used with method calls
let src = r#"
Expand Down Expand Up @@ -2107,6 +2095,48 @@ fn turbofish_numeric_generic_nested_call() {
let _ = bar::<M>();
}
"#;
assert_no_errors(src);
}

#[test]
fn use_super() {
let src = r#"
fn some_func() {}
mod foo {
use super::some_func;
}
"#;
assert_no_errors(src);
}

#[test]
fn use_super_in_path() {
let src = r#"
fn some_func() {}
mod foo {
fn func() {
super::some_func();
}
}
"#;
assert_no_errors(src);
}

#[test]
fn no_super() {
let src = "use super::some_func;";
let errors = get_program_errors(src);
assert!(errors.is_empty());
assert_eq!(errors.len(), 1);

let CompilationError::DefinitionError(DefCollectorErrorKind::PathResolutionError(
PathResolutionError::NoSuper(span),
)) = &errors[0].0
else {
panic!("Expected a 'no super' error, got {:?}", errors[0].0);
};

assert_eq!(span.start(), 4);
assert_eq!(span.end(), 9);
}
Loading

0 comments on commit 256509e

Please sign in to comment.