Skip to content

Commit 6004067

Browse files
jfecherasterite
andauthored
feat: Add StructDefinition::add_generic (#5961)
# Description ## Problem\* Resolves #5957 ## Summary\* Adds a generic to an existing struct. ## Additional Context Due to the same reasoning in #5926, existing code referring to these structs won't see the generics on them. For now I've documented to try to avoid this use case. ## 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. --------- Co-authored-by: Ary Borenszweig <asterite@gmail.com>
1 parent 102ebe3 commit 6004067

File tree

5 files changed

+141
-12
lines changed

5 files changed

+141
-12
lines changed

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

+39-2
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,6 @@ pub enum InterpreterError {
194194
candidates: Vec<String>,
195195
location: Location,
196196
},
197-
198197
Unimplemented {
199198
item: String,
200199
location: Location,
@@ -211,6 +210,16 @@ pub enum InterpreterError {
211210
attribute: String,
212211
location: Location,
213212
},
213+
GenericNameShouldBeAnIdent {
214+
name: Rc<String>,
215+
location: Location,
216+
},
217+
DuplicateGeneric {
218+
name: Rc<String>,
219+
struct_name: String,
220+
duplicate_location: Location,
221+
existing_location: Location,
222+
},
214223

215224
// These cases are not errors, they are just used to prevent us from running more code
216225
// until the loop can be resumed properly. These cases will never be displayed to users.
@@ -279,8 +288,10 @@ impl InterpreterError {
279288
| InterpreterError::FunctionAlreadyResolved { location, .. }
280289
| InterpreterError::MultipleMatchingImpls { location, .. }
281290
| InterpreterError::ExpectedIdentForStructField { location, .. }
291+
| InterpreterError::InvalidAttribute { location, .. }
292+
| InterpreterError::GenericNameShouldBeAnIdent { location, .. }
293+
| InterpreterError::DuplicateGeneric { duplicate_location: location, .. }
282294
| InterpreterError::TypeAnnotationsNeededForMethodCall { location } => *location,
283-
InterpreterError::InvalidAttribute { location, .. } => *location,
284295

285296
InterpreterError::FailedToParseMacro { error, file, .. } => {
286297
Location::new(error.span(), *file)
@@ -589,6 +600,32 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic {
589600
let secondary = "Note that this method expects attribute contents, without the leading `#[` or trailing `]`".to_string();
590601
CustomDiagnostic::simple_error(msg, secondary, location.span)
591602
}
603+
InterpreterError::GenericNameShouldBeAnIdent { name, location } => {
604+
let msg =
605+
"Generic name needs to be a valid identifer (one word beginning with a letter)"
606+
.to_string();
607+
let secondary = format!("`{name}` is not a valid identifier");
608+
CustomDiagnostic::simple_error(msg, secondary, location.span)
609+
}
610+
InterpreterError::DuplicateGeneric {
611+
name,
612+
struct_name,
613+
duplicate_location,
614+
existing_location,
615+
} => {
616+
let msg = format!("`{struct_name}` already has a generic named `{name}`");
617+
let secondary = format!("`{name}` added here a second time");
618+
let mut error =
619+
CustomDiagnostic::simple_error(msg, secondary, duplicate_location.span);
620+
621+
let existing_msg = format!("`{name}` was previously defined here");
622+
error.add_secondary_with_file(
623+
existing_msg,
624+
existing_location.span,
625+
existing_location.file,
626+
);
627+
error
628+
}
592629
}
593630
}
594631
}

compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs

+61-5
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ use crate::{
3939
node_interner::{DefinitionKind, TraitImplKind},
4040
parser::{self},
4141
token::{Attribute, SecondaryAttribute, Token},
42-
QuotedType, Shared, Type,
42+
Kind, QuotedType, ResolvedGeneric, Shared, Type, TypeVariable,
4343
};
4444

4545
use self::builtin_helpers::{get_array, get_str, get_u8};
@@ -139,7 +139,8 @@ impl<'local, 'context> Interpreter<'local, 'context> {
139139
"slice_push_front" => slice_push_front(interner, arguments, location),
140140
"slice_remove" => slice_remove(interner, arguments, location, call_stack),
141141
"str_as_bytes" => str_as_bytes(interner, arguments, location),
142-
"struct_def_add_attribute" => struct_def_add_attribute(self, arguments, location),
142+
"struct_def_add_attribute" => struct_def_add_attribute(interner, arguments, location),
143+
"struct_def_add_generic" => struct_def_add_generic(interner, arguments, location),
143144
"struct_def_as_type" => struct_def_as_type(interner, arguments, location),
144145
"struct_def_fields" => struct_def_fields(interner, arguments, location),
145146
"struct_def_generics" => struct_def_generics(interner, arguments, location),
@@ -280,13 +281,13 @@ fn str_as_bytes(
280281

281282
// fn add_attribute<let N: u32>(self, attribute: str<N>)
282283
fn struct_def_add_attribute(
283-
interpreter: &mut Interpreter,
284+
interner: &mut NodeInterner,
284285
arguments: Vec<(Value, Location)>,
285286
location: Location,
286287
) -> IResult<Value> {
287288
let (self_argument, attribute) = check_two_arguments(arguments, location)?;
288289
let attribute_location = attribute.1;
289-
let attribute = get_str(interpreter.elaborator.interner, attribute)?;
290+
let attribute = get_str(interner, attribute)?;
290291

291292
let mut tokens = Lexer::lex(&format!("#[{}]", attribute)).0 .0;
292293
if let Some(Token::EOF) = tokens.last().map(|token| token.token()) {
@@ -315,13 +316,68 @@ fn struct_def_add_attribute(
315316
};
316317

317318
let struct_id = get_struct(self_argument)?;
318-
interpreter.elaborator.interner.update_struct_attributes(struct_id, |attributes| {
319+
interner.update_struct_attributes(struct_id, |attributes| {
319320
attributes.push(attribute.clone());
320321
});
321322

322323
Ok(Value::Unit)
323324
}
324325

326+
// fn add_generic<let N: u32>(self, generic_name: str<N>)
327+
fn struct_def_add_generic(
328+
interner: &NodeInterner,
329+
arguments: Vec<(Value, Location)>,
330+
location: Location,
331+
) -> IResult<Value> {
332+
let (self_argument, generic) = check_two_arguments(arguments, location)?;
333+
let generic_location = generic.1;
334+
let generic = get_str(interner, generic)?;
335+
336+
let mut tokens = Lexer::lex(&generic).0 .0;
337+
if let Some(Token::EOF) = tokens.last().map(|token| token.token()) {
338+
tokens.pop();
339+
}
340+
341+
if tokens.len() != 1 {
342+
return Err(InterpreterError::GenericNameShouldBeAnIdent {
343+
name: generic,
344+
location: generic_location,
345+
});
346+
}
347+
348+
let Token::Ident(generic_name) = tokens.pop().unwrap().into_token() else {
349+
return Err(InterpreterError::GenericNameShouldBeAnIdent {
350+
name: generic,
351+
location: generic_location,
352+
});
353+
};
354+
355+
let struct_id = get_struct(self_argument)?;
356+
let the_struct = interner.get_struct(struct_id);
357+
let mut the_struct = the_struct.borrow_mut();
358+
let name = Rc::new(generic_name);
359+
360+
for generic in &the_struct.generics {
361+
if generic.name == name {
362+
return Err(InterpreterError::DuplicateGeneric {
363+
name,
364+
struct_name: the_struct.name.to_string(),
365+
existing_location: Location::new(generic.span, the_struct.location.file),
366+
duplicate_location: generic_location,
367+
});
368+
}
369+
}
370+
371+
let type_var = TypeVariable::unbound(interner.next_type_variable_id());
372+
let span = generic_location.span;
373+
let kind = Kind::Normal;
374+
let typ = Type::NamedGeneric(type_var.clone(), name.clone(), kind.clone());
375+
let new_generic = ResolvedGeneric { name, type_var, span, kind };
376+
the_struct.generics.push(new_generic);
377+
378+
Ok(Value::Type(typ))
379+
}
380+
325381
/// fn as_type(self) -> Type
326382
fn struct_def_as_type(
327383
interner: &NodeInterner,

docs/docs/noir/standard_library/meta/struct_def.md

+18
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,24 @@ This type corresponds to `struct Name { field1: Type1, ... }` items in the sourc
1313

1414
Adds an attribute to the struct.
1515

16+
### add_generic
17+
18+
#include_code add_generic noir_stdlib/src/meta/struct_def.nr rust
19+
20+
Adds an generic to the struct. Returns the new generic type.
21+
Errors if the given generic name isn't a single identifier or if
22+
the struct already has a generic with the same name.
23+
24+
This method should be used carefully, if there is existing code referring
25+
to the struct type it may be checked before this function is called and
26+
see the struct with the original number of generics. This method should
27+
thus be preferred to use on code generated from other macros and structs
28+
that are not used in function signatures.
29+
30+
Example:
31+
32+
#include_code add-generic-example test_programs/compile_success_empty/comptime_struct_definition/src/main.nr rust
33+
1634
### as_type
1735

1836
#include_code as_type noir_stdlib/src/meta/struct_def.nr rust

noir_stdlib/src/meta/struct_def.nr

+8-3
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@ impl StructDefinition {
44
fn add_attribute<let N: u32>(self, attribute: str<N>) {}
55
// docs:end:add_attribute
66

7+
#[builtin(struct_def_add_generic)]
8+
// docs:start:add_generic
9+
fn add_generic<let N: u32>(self, generic_name: str<N>) -> Type {}
10+
// docs:end:add_generic
11+
712
/// Return a syntactic version of this struct definition as a type.
813
/// For example, `as_type(quote { type Foo<A, B> { ... } })` would return `Foo<A, B>`
914
#[builtin(struct_def_as_type)]
10-
// docs:start:as_type
15+
// docs:start:as_type
1116
fn as_type(self) -> Type {}
1217
// docs:end:as_type
1318

@@ -18,14 +23,14 @@ impl StructDefinition {
1823

1924
/// Return each generic on this struct.
2025
#[builtin(struct_def_generics)]
21-
// docs:start:generics
26+
// docs:start:generics
2227
fn generics(self) -> [Type] {}
2328
// docs:end:generics
2429

2530
/// Returns (name, type) pairs of each field in this struct. Each type is as-is
2631
/// with any generic arguments unchanged.
2732
#[builtin(struct_def_fields)]
28-
// docs:start:fields
33+
// docs:start:fields
2934
fn fields(self) -> [(Quoted, Type)] {}
3035
// docs:end:fields
3136

test_programs/compile_success_empty/comptime_struct_definition/src/main.nr

+15-2
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,23 @@ mod foo {
2828
#[attr]
2929
struct Foo {}
3030

31-
fn attr(s: StructDefinition) {
31+
comptime fn attr(s: StructDefinition) {
3232
assert_eq(s.module().name(), quote { foo });
3333
}
34+
35+
#[add_generic]
36+
struct Bar {}
37+
38+
// docs:start:add-generic-example
39+
comptime fn add_generic(s: StructDefinition) {
40+
assert_eq(s.generics().len(), 0);
41+
let new_generic = s.add_generic("T");
42+
43+
let generics = s.generics();
44+
assert_eq(generics.len(), 1);
45+
assert_eq(generics[0], new_generic);
46+
}
47+
// docs:end:add-generic-example
3448
}
3549

3650
fn main() {}
37-

0 commit comments

Comments
 (0)