Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions compiler/noirc_frontend/src/hir/comptime/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,7 @@ impl<'a> Interpreter<'a> {
.expect("all builtin functions must contain a function attribute which contains the opcode which it links to");

if let Some(builtin) = func_attrs.builtin() {
match builtin.as_str() {
"array_len" => builtin::array_len(&arguments),
"as_slice" => builtin::as_slice(arguments),
_ => {
let item = format!("Comptime evaluation for builtin function {builtin}");
Err(InterpreterError::Unimplemented { item, location })
}
}
builtin::call_builtin(self.interner, builtin, arguments, location)
} else if let Some(foreign) = func_attrs.foreign() {
let item = format!("Comptime evaluation for foreign functions like {foreign}");
Err(InterpreterError::Unimplemented { item, location })
Expand Down Expand Up @@ -934,6 +927,18 @@ impl<'a> Interpreter<'a> {
fn evaluate_access(&mut self, access: HirMemberAccess, id: ExprId) -> IResult<Value> {
let (fields, struct_type) = match self.evaluate(access.lhs)? {
Value::Struct(fields, typ) => (fields, typ),
Value::Tuple(fields) => {
let (fields, field_types): (HashMap<Rc<String>, Value>, Vec<Type>) = fields
.into_iter()
.enumerate()
.map(|(i, field)| {
let field_type = field.get_type().into_owned();
let key_val_pair = (Rc::new(i.to_string()), field);
(key_val_pair, field_type)
})
.unzip();
(fields, Type::Tuple(field_types))
}
value => {
let location = self.interner.expr_location(&id);
return Err(InterpreterError::NonTupleOrStructInMemberAccess { value, location });
Expand Down
159 changes: 155 additions & 4 deletions compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,36 @@
use std::rc::Rc;

use noirc_errors::Location;

use crate::{
hir::comptime::{errors::IResult, Value},
Type,
hir::comptime::{errors::IResult, InterpreterError, Value},
lexer::Lexer,
macros_api::NodeInterner,
token::{SpannedToken, Token, Tokens},
QuotedType, Type,
};

pub(super) fn array_len(arguments: &[(Value, Location)]) -> IResult<Value> {
pub(super) fn call_builtin(
interner: &NodeInterner,
name: &str,
arguments: Vec<(Value, Location)>,
location: Location,
) -> IResult<Value> {
match name {
"array_len" => array_len(&arguments),
"as_slice" => as_slice(arguments),
"slice_push_back" => slice_push_back(arguments),
"type_def_as_type" => type_def_as_type(interner, arguments),
"type_def_generics" => type_def_generics(interner, arguments),
"type_def_fields" => type_def_fields(interner, arguments),
_ => {
let item = format!("Comptime evaluation for builtin function {name}");
Err(InterpreterError::Unimplemented { item, location })
}
}
}

fn array_len(arguments: &[(Value, Location)]) -> IResult<Value> {
assert_eq!(arguments.len(), 1, "ICE: `array_len` should only receive a single argument");
match &arguments[0].0 {
Value::Array(values, _) | Value::Slice(values, _) => Ok(Value::U32(values.len() as u32)),
Expand All @@ -14,7 +39,7 @@ pub(super) fn array_len(arguments: &[(Value, Location)]) -> IResult<Value> {
}
}

pub(super) fn as_slice(mut arguments: Vec<(Value, Location)>) -> IResult<Value> {
fn as_slice(mut arguments: Vec<(Value, Location)>) -> IResult<Value> {
assert_eq!(arguments.len(), 1, "ICE: `as_slice` should only receive a single argument");
let (array, _) = arguments.pop().unwrap();
match array {
Expand All @@ -23,3 +48,129 @@ pub(super) fn as_slice(mut arguments: Vec<(Value, Location)>) -> IResult<Value>
_ => unreachable!("ICE: Cannot convert types other than arrays into slices"),
}
}

fn slice_push_back(mut arguments: Vec<(Value, Location)>) -> IResult<Value> {
assert_eq!(arguments.len(), 2, "ICE: `slice_push_back` should only receive two arguments");
let (element, _) = arguments.pop().unwrap();
let (slice, _) = arguments.pop().unwrap();
match slice {
Value::Slice(mut values, typ) => {
values.push_back(element);
Ok(Value::Slice(values, typ))
}
// Type checking should prevent this branch being taken.
_ => unreachable!("ICE: `slice_push_back` expects a slice as its first argument"),
}
}

/// fn as_type(self) -> Quoted
fn type_def_as_type(
interner: &NodeInterner,
mut arguments: Vec<(Value, Location)>,
) -> IResult<Value> {
assert_eq!(arguments.len(), 1, "ICE: `generics` should only receive a single argument");
let (type_def, span) = match arguments.pop() {
Some((Value::TypeDefinition(id), location)) => (id, location.span),
other => {
unreachable!("ICE: `as_type` expected a `TypeDefinition` argument, found {other:?}")
}
};

let struct_def = interner.get_struct(type_def);
let struct_def = struct_def.borrow();
let make_token = |name| SpannedToken::new(Token::Str(name), span);

let mut tokens = vec![make_token(struct_def.name.to_string())];

for (i, generic) in struct_def.generics.iter().enumerate() {
if i != 0 {
tokens.push(SpannedToken::new(Token::Comma, span));
}
tokens.push(make_token(generic.borrow().to_string()));
}

Ok(Value::Code(Rc::new(Tokens(tokens))))
}

/// fn generics(self) -> [Quoted]
fn type_def_generics(
interner: &NodeInterner,
mut arguments: Vec<(Value, Location)>,
) -> IResult<Value> {
assert_eq!(arguments.len(), 1, "ICE: `generics` should only receive a single argument");
let (type_def, span) = match arguments.pop() {
Some((Value::TypeDefinition(id), location)) => (id, location.span),
other => {
unreachable!("ICE: `as_type` expected a `TypeDefinition` argument, found {other:?}")
}
};

let struct_def = interner.get_struct(type_def);

let generics = struct_def
.borrow()
.generics
.iter()
.map(|generic| {
let name = SpannedToken::new(Token::Str(generic.borrow().to_string()), span);
Value::Code(Rc::new(Tokens(vec![name])))
})
.collect();

let typ = Type::Slice(Box::new(Type::Quoted(QuotedType::Quoted)));
Ok(Value::Slice(generics, typ))
}

/// fn fields(self) -> [(Quoted, Quoted)]
/// Returns (name, type) pairs of each field of this TypeDefinition
fn type_def_fields(
interner: &NodeInterner,
mut arguments: Vec<(Value, Location)>,
) -> IResult<Value> {
assert_eq!(arguments.len(), 1, "ICE: `generics` should only receive a single argument");
let (type_def, span) = match arguments.pop() {
Some((Value::TypeDefinition(id), location)) => (id, location.span),
other => {
unreachable!("ICE: `as_type` expected a `TypeDefinition` argument, found {other:?}")
}
};

let struct_def = interner.get_struct(type_def);
let struct_def = struct_def.borrow();

let make_token = |name| SpannedToken::new(Token::Str(name), span);
let make_quoted = |tokens| Value::Code(Rc::new(Tokens(tokens)));

let mut fields = im::Vector::new();

for (name, typ) in struct_def.get_fields_as_written() {
let name = make_quoted(vec![make_token(name)]);
let typ = Value::Code(Rc::new(type_to_tokens(&typ)?));
fields.push_back(Value::Tuple(vec![name, typ]));
}

let typ = Type::Slice(Box::new(Type::Tuple(vec![
Type::Quoted(QuotedType::Quoted),
Type::Quoted(QuotedType::Quoted),
])));
Ok(Value::Slice(fields, typ))
}

/// FIXME(https://github.com/noir-lang/noir/issues/5309): This code is temporary.
/// It will produce poor results for type variables and will result in incorrect
/// spans on the returned tokens.
fn type_to_tokens(typ: &Type) -> IResult<Tokens> {
let (mut tokens, mut errors) = Lexer::lex(&typ.to_string());

if let Some(last) = tokens.0.last() {
if matches!(last.token(), Token::EOF) {
tokens.0.pop();
}
}

if !errors.is_empty() {
let error = errors.swap_remove(0);
todo!("Got lexer error: {error}")
}
Ok(tokens)
}
19 changes: 15 additions & 4 deletions compiler/noirc_frontend/src/hir_def/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,16 @@ impl StructType {
})
}

/// Returns the name and raw types of each field of this type.
/// This will not substitute any generic arguments so a generic field like `x`
/// in `struct Foo<T> { x: T }` will return a `("x", T)` pair.
///
/// This method is almost never what is wanted for type checking or monomorphization,
/// prefer to use `get_fields` whenever possible.
pub fn get_fields_as_written(&self) -> Vec<(String, Type)> {
vecmap(&self.fields, |(name, typ)| (name.0.contents.clone(), typ.clone()))
}

pub fn field_names(&self) -> BTreeSet<Ident> {
self.fields.iter().map(|(name, _)| name.clone()).collect()
}
Expand Down Expand Up @@ -805,10 +815,11 @@ impl Type {
| Type::FmtString(_, _)
| Type::Error => true,

Type::MutableReference(_)
| Type::Forall(_, _)
| Type::Quoted(_)
| Type::TraitAsType(..) => false,
// Quoted objects only exist at compile-time where the only execution
// environment is the interpreter. In this environment, they are valid.
Type::Quoted(_) => true,

Type::MutableReference(_) | Type::Forall(_, _) | Type::TraitAsType(..) => false,

Type::Alias(alias, generics) => {
let alias = alias.borrow();
Expand Down
1 change: 1 addition & 0 deletions noir_stdlib/src/lib.nr
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ mod prelude;
mod uint128;
mod bigint;
mod runtime;
mod meta;

// Oracle calls are required to be wrapped in an unconstrained function
// Thus, the only argument to the `println` oracle is expected to always be an ident
Expand Down
1 change: 1 addition & 0 deletions noir_stdlib/src/meta.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod type_def;
16 changes: 16 additions & 0 deletions noir_stdlib/src/meta/type_def.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
impl TypeDefinition {
/// Return a syntactic version of this type definition as a type.
/// For example, `as_type(quote { type Foo<A, B> { ... } })` would return `Foo<A, B>`
#[builtin(type_def_as_type)]
fn as_type(self) -> Quoted {}

/// Return each generic on this type. The names of these generics are unchanged
/// so users may need to keep name collisions in mind if this is used directly in a macro.
#[builtin(type_def_generics)]
fn generics(self) -> [Quoted] {}

/// Returns (name, type) pairs of each field in this type. Each type is as-is
/// with any generic arguments unchanged.
#[builtin(type_def_fields)]
fn fields(self) -> [(Quoted, Quoted)] {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "comptime_type_definition"
type = "bin"
authors = [""]
compiler_version = ">=0.31.0"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
fn main() {}

#[my_comptime_fn]
struct MyType<A, B, C> {
field1: [A; 10],
field2: (B, C),
}

comptime fn my_comptime_fn(typ: TypeDefinition) {
let _ = typ.as_type();
assert_eq(typ.generics().len(), 3);
assert_eq(typ.fields().len(), 2);
}
7 changes: 7 additions & 0 deletions test_programs/compile_success_empty/derive_impl/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "derive_impl"
type = "bin"
authors = [""]
compiler_version = ">=0.30.0"

[dependencies]
44 changes: 44 additions & 0 deletions test_programs/compile_success_empty/derive_impl/src/main.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
comptime fn derive_default(typ: TypeDefinition) -> Quoted {
let generics: [Quoted] = typ.generics();
assert_eq(
generics.len(), 0, "derive_default: Deriving Default on generic types is currently unimplemented"
);

let type_name = typ.as_type();
let fields = typ.fields();

let fields = join(make_field_exprs(fields));

quote {
impl Default for $type_name {
fn default() -> Self {
Self { $fields }
}
}
}
}

#[derive_default]
struct Foo {
x: Field,
y: u32,
}

comptime fn make_field_exprs(fields: [(Quoted, Quoted)]) -> [Quoted] {
let mut result = &[];
for my_field in fields {
let name = my_field.0;
result = result.push_back(quote { $name: Default::default(), });
}
result
}

comptime fn join(slice: [Quoted]) -> Quoted {
let mut result = quote {};
for elem in slice {
result = quote { $result $elem };
}
result
}

fn main() {}