Skip to content

Commit f8a1fb7

Browse files
authored
chore!: Ban nested slices (#4018)
# Description ## Problem\* Resolves #4017 ## Summary\* After this PR #3979 and an internal discussion we have decided to temporarily block nested slices. This PR adds a check in the frontend and in the SSA against nested slices. The check in the frontend makes sure any struct fields with a nested slice (but without generics) are not blocked as well as any type declarations with nested slices. In order to account for generics in structs we also have a checked array codegen that makes sure we do not have a nested slice. ## Additional Context The actual nested slice code in the ACIR gen is somewhat intertwined so I felt it would be best for a separate PR which will be a followup to this one. ## Documentation\* Check one: - [] No documentation needed. - [X] Documentation included in this PR. - [ ] **[Exceptional Case]** 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.
1 parent d6a16d0 commit f8a1fb7

File tree

21 files changed

+170
-542
lines changed

21 files changed

+170
-542
lines changed

compiler/noirc_evaluator/src/errors.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ pub enum RuntimeError {
4242
UnknownLoopBound { call_stack: CallStack },
4343
#[error("Argument is not constant")]
4444
AssertConstantFailed { call_stack: CallStack },
45+
#[error("Nested slices are not supported")]
46+
NestedSlice { call_stack: CallStack },
4547
}
4648

4749
// We avoid showing the actual lhs and rhs since most of the time they are just 0
@@ -129,7 +131,8 @@ impl RuntimeError {
129131
| RuntimeError::UnknownLoopBound { call_stack }
130132
| RuntimeError::AssertConstantFailed { call_stack }
131133
| RuntimeError::IntegerOutOfBounds { call_stack, .. }
132-
| RuntimeError::UnsupportedIntegerSize { call_stack, .. } => call_stack,
134+
| RuntimeError::UnsupportedIntegerSize { call_stack, .. }
135+
| RuntimeError::NestedSlice { call_stack, .. } => call_stack,
133136
}
134137
}
135138
}

compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -187,12 +187,14 @@ impl<'a> FunctionContext<'a> {
187187

188188
let typ = Self::convert_type(&array.typ).flatten();
189189
Ok(match array.typ {
190-
ast::Type::Array(_, _) => self.codegen_array(elements, typ[0].clone()),
190+
ast::Type::Array(_, _) => {
191+
self.codegen_array_checked(elements, typ[0].clone())?
192+
}
191193
ast::Type::Slice(_) => {
192194
let slice_length =
193195
self.builder.field_constant(array.contents.len() as u128);
194-
195-
let slice_contents = self.codegen_array(elements, typ[1].clone());
196+
let slice_contents =
197+
self.codegen_array_checked(elements, typ[1].clone())?;
196198
Tree::Branch(vec![slice_length.into(), slice_contents])
197199
}
198200
_ => unreachable!(
@@ -231,6 +233,18 @@ impl<'a> FunctionContext<'a> {
231233
self.codegen_array(elements, typ)
232234
}
233235

236+
// Codegen an array but make sure that we do not have a nested slice
237+
fn codegen_array_checked(
238+
&mut self,
239+
elements: Vec<Values>,
240+
typ: Type,
241+
) -> Result<Values, RuntimeError> {
242+
if typ.is_nested_slice() {
243+
return Err(RuntimeError::NestedSlice { call_stack: self.builder.get_call_stack() });
244+
}
245+
Ok(self.codegen_array(elements, typ))
246+
}
247+
234248
/// Codegen an array by allocating enough space for each element and inserting separate
235249
/// store instructions until each element is stored. The store instructions will be separated
236250
/// by add instructions to calculate the new offset address to store to next.

compiler/noirc_frontend/src/hir/resolution/errors.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ pub enum ResolverError {
8282
NonCrateFunctionCalled { name: String, span: Span },
8383
#[error("Only sized types may be used in the entry point to a program")]
8484
InvalidTypeForEntryPoint { span: Span },
85+
#[error("Nested slices are not supported")]
86+
NestedSlices { span: Span },
8587
}
8688

8789
impl ResolverError {
@@ -304,6 +306,11 @@ impl From<ResolverError> for Diagnostic {
304306
ResolverError::InvalidTypeForEntryPoint { span } => Diagnostic::simple_error(
305307
"Only sized types may be used in the entry point to a program".to_string(),
306308
"Slices, references, or any type containing them may not be used in main or a contract function".to_string(), span),
309+
ResolverError::NestedSlices { span } => Diagnostic::simple_error(
310+
"Nested slices are not supported".into(),
311+
"Try to use a constant sized array instead".into(),
312+
span,
313+
),
307314
}
308315
}
309316
}

compiler/noirc_frontend/src/hir/resolution/resolver.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -705,7 +705,12 @@ impl<'a> Resolver<'a> {
705705

706706
/// Translates an UnresolvedType to a Type
707707
pub fn resolve_type(&mut self, typ: UnresolvedType) -> Type {
708-
self.resolve_type_inner(typ, &mut vec![])
708+
let span = typ.span;
709+
let resolved_type = self.resolve_type_inner(typ, &mut vec![]);
710+
if resolved_type.is_nested_slice() {
711+
self.errors.push(ResolverError::NestedSlices { span: span.unwrap() });
712+
}
713+
resolved_type
709714
}
710715

711716
pub fn resolve_type_aliases(

compiler/noirc_frontend/src/hir/resolution/structs.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ pub(crate) fn resolve_structs(
2424
crate_id: CrateId,
2525
) -> Vec<(CompilationError, FileId)> {
2626
let mut errors: Vec<(CompilationError, FileId)> = vec![];
27+
// This is necessary to avoid cloning the entire struct map
28+
// when adding checks after each struct field is resolved.
29+
let struct_ids = structs.keys().copied().collect::<Vec<_>>();
30+
2731
// Resolve each field in each struct.
2832
// Each struct should already be present in the NodeInterner after def collection.
2933
for (type_id, typ) in structs {
@@ -35,6 +39,28 @@ pub(crate) fn resolve_structs(
3539
struct_def.generics = generics;
3640
});
3741
}
42+
43+
// Check whether the struct fields have nested slices
44+
// We need to check after all structs are resolved to
45+
// make sure every struct's fields is accurately set.
46+
for id in struct_ids {
47+
let struct_type = context.def_interner.get_struct(id);
48+
// Only handle structs without generics as any generics args will be checked
49+
// after monomorphization when performing SSA codegen
50+
if struct_type.borrow().generics.is_empty() {
51+
let fields = struct_type.borrow().get_fields(&[]);
52+
for field in fields.iter() {
53+
if field.1.is_nested_slice() {
54+
errors.push((
55+
ResolverError::NestedSlices { span: struct_type.borrow().location.span }
56+
.into(),
57+
struct_type.borrow().location.file,
58+
));
59+
}
60+
}
61+
}
62+
}
63+
3864
errors
3965
}
4066

@@ -49,5 +75,6 @@ fn resolve_struct_fields(
4975
let (generics, fields, errors) =
5076
Resolver::new(&mut context.def_interner, &path_resolver, &context.def_maps, file_id)
5177
.resolve_struct_fields(unresolved.struct_def);
78+
5279
(generics, fields, errors)
5380
}

compiler/noirc_frontend/src/hir_def/types.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,43 @@ impl Type {
143143
| Type::Error => unreachable!("This type cannot exist as a parameter to main"),
144144
}
145145
}
146+
147+
pub(crate) fn is_nested_slice(&self) -> bool {
148+
match self {
149+
Type::Array(size, elem) => {
150+
if let Type::NotConstant = size.as_ref() {
151+
elem.as_ref().contains_slice()
152+
} else {
153+
false
154+
}
155+
}
156+
_ => false,
157+
}
158+
}
159+
160+
fn contains_slice(&self) -> bool {
161+
match self {
162+
Type::Array(size, _) => matches!(size.as_ref(), Type::NotConstant),
163+
Type::Struct(struct_typ, generics) => {
164+
let fields = struct_typ.borrow().get_fields(generics);
165+
for field in fields.iter() {
166+
if field.1.contains_slice() {
167+
return true;
168+
}
169+
}
170+
false
171+
}
172+
Type::Tuple(types) => {
173+
for typ in types.iter() {
174+
if typ.contains_slice() {
175+
return true;
176+
}
177+
}
178+
false
179+
}
180+
_ => false,
181+
}
182+
}
146183
}
147184

148185
/// A list of TypeVariableIds to bind to a type. Storing the

docs/docs/noir/concepts/data_types/arrays.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ You can define multidimensional arrays:
7070
let array : [[Field; 2]; 2];
7171
let element = array[0][0];
7272
```
73+
However, multidimensional slices are not supported. For example, the following code will error at compile time:
74+
```rust
75+
let slice : [[Field]] = [];
76+
```
7377

7478
## Types
7579

test_programs/execution_success/brillig_nested_slices/Nargo.toml renamed to test_programs/compile_failure/brillig_nested_slices/Nargo.toml

File renamed without changes.

test_programs/execution_success/brillig_nested_slices/Prover.toml renamed to test_programs/compile_failure/brillig_nested_slices/Prover.toml

File renamed without changes.

test_programs/execution_success/brillig_nested_slices/src/main.nr renamed to test_programs/compile_failure/brillig_nested_slices/src/main.nr

File renamed without changes.

0 commit comments

Comments
 (0)