diff --git a/compiler/noirc_frontend/src/hir/resolution/errors.rs b/compiler/noirc_frontend/src/hir/resolution/errors.rs index 70e7a8e40f2..b6bcfe6972e 100644 --- a/compiler/noirc_frontend/src/hir/resolution/errors.rs +++ b/compiler/noirc_frontend/src/hir/resolution/errors.rs @@ -86,6 +86,8 @@ pub enum ResolverError { JumpInConstrainedFn { is_break: bool, span: Span }, #[error("break/continue are only allowed within loops")] JumpOutsideLoop { is_break: bool, span: Span }, + #[error("Self-referential structs are not supported")] + SelfReferentialStruct { span: Span }, #[error("#[inline(tag)] attribute is only allowed on constrained functions")] InlineAttributeOnUnconstrained { ident: Ident }, #[error("#[fold] attribute is only allowed on constrained functions")] @@ -344,6 +346,13 @@ impl From for Diagnostic { span, ) }, + ResolverError::SelfReferentialStruct { span } => { + Diagnostic::simple_error( + "Self-referential structs are not supported".into(), + "".into(), + span, + ) + }, ResolverError::InlineAttributeOnUnconstrained { ident } => { let name = &ident.0.contents; diff --git a/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/compiler/noirc_frontend/src/hir/resolution/resolver.rs index b17cd50564e..e3b1251955c 100644 --- a/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -22,7 +22,7 @@ use crate::hir_def::traits::{Trait, TraitConstraint}; use crate::macros_api::SecondaryAttribute; use crate::token::{Attributes, FunctionAttribute}; use regex::Regex; -use std::collections::{BTreeMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet, HashSet}; use std::rc::Rc; use crate::ast::{ @@ -96,6 +96,21 @@ pub struct Resolver<'a> { /// Used to link items to their dependencies in the dependency graph current_item: Option, + /// In-resolution names + /// + /// This needs to be a set because we can have multiple in-resolution + /// names when resolving structs that are declared in reverse order of their + /// dependencies, such as in the following case: + /// + /// ``` + /// struct Wrapper { + /// value: Wrapped + /// } + /// struct Wrapped { + /// } + /// ``` + resolving_ids: BTreeSet, + /// True if the current module is a contract. /// This is usually determined by self.path_resolver.module_id(), but it can /// be overridden for impls. Impls are an odd case since the methods within resolve @@ -159,6 +174,7 @@ impl<'a> Resolver<'a> { lambda_stack: Vec::new(), current_trait_impl: None, current_item: None, + resolving_ids: BTreeSet::new(), file, in_contract, in_unconstrained_fn: false, @@ -616,6 +632,14 @@ impl<'a> Resolver<'a> { match self.lookup_struct_or_error(path) { Some(struct_type) => { + if self.resolving_ids.contains(&struct_type.borrow().id) { + self.push_err(ResolverError::SelfReferentialStruct { + span: struct_type.borrow().name.span(), + }); + + return Type::Error; + } + let expected_generic_count = struct_type.borrow().generics.len(); if !self.in_contract && self @@ -886,7 +910,10 @@ impl<'a> Resolver<'a> { self.resolve_local_globals(); self.current_item = Some(DependencyId::Struct(struct_id)); + + self.resolving_ids.insert(struct_id); let fields = vecmap(unresolved.fields, |(ident, typ)| (ident, self.resolve_type(typ))); + self.resolving_ids.remove(&struct_id); (generics, fields, self.errors) } diff --git a/compiler/noirc_frontend/src/tests.rs b/compiler/noirc_frontend/src/tests.rs index 5d0f2472a43..fb60047b233 100644 --- a/compiler/noirc_frontend/src/tests.rs +++ b/compiler/noirc_frontend/src/tests.rs @@ -1167,7 +1167,7 @@ fn lambda$f1(mut env$l1: (Field)) -> Field { } #[test] - fn deny_cyclic_structs() { + fn deny_mutually_recursive_structs() { let src = r#" struct Foo { bar: Bar } struct Bar { foo: Foo } diff --git a/test_programs/compile_failure/self_referential_struct/Nargo.toml b/test_programs/compile_failure/self_referential_struct/Nargo.toml new file mode 100644 index 00000000000..a0693f9d4b1 --- /dev/null +++ b/test_programs/compile_failure/self_referential_struct/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "self_referential_struct" +type = "bin" +authors = [""] +compiler_version = ">=0.27.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/self_referential_struct/src/main.nr b/test_programs/compile_failure/self_referential_struct/src/main.nr new file mode 100644 index 00000000000..d70b1a10ded --- /dev/null +++ b/test_programs/compile_failure/self_referential_struct/src/main.nr @@ -0,0 +1,11 @@ +struct Option2 { + _is_some: bool, + _value: T, +} + +struct SelfReferential +{ + prop : Option2 +} + +fn main(x: SelfReferential) { assert(x._is_some); }