Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Switch Value::TraitConstraint to a resolved trait constraint #5618

Merged
merged 9 commits into from
Jul 29, 2024
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
6 changes: 5 additions & 1 deletion compiler/noirc_frontend/src/elaborator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,11 @@ impl<'context> Elaborator<'context> {
self.resolve_trait_bound(&constraint.trait_bound, typ)
}

fn resolve_trait_bound(&mut self, bound: &TraitBound, typ: Type) -> Option<TraitConstraint> {
pub fn resolve_trait_bound(
&mut self,
bound: &TraitBound,
typ: Type,
) -> Option<TraitConstraint> {
let the_trait = self.lookup_trait_or_error(bound.trait_path.clone())?;

let resolved_generics = &the_trait.generics.clone();
Expand Down
9 changes: 8 additions & 1 deletion compiler/noirc_frontend/src/hir/comptime/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
use std::rc::Rc;

use crate::{
ast::TraitBound,
hir::{def_collector::dc_crate::CompilationError, type_check::NoMatchingImplFoundError},
parser::ParserError,
token::Tokens,
Expand Down Expand Up @@ -56,6 +57,7 @@
BreakNotInLoop { location: Location },
ContinueNotInLoop { location: Location },
BlackBoxError(BlackBoxResolutionError, Location),
FailedToResolveTraitBound { trait_bound: TraitBound, location: Location },

Unimplemented { item: String, location: Location },

Expand Down Expand Up @@ -119,7 +121,8 @@
| InterpreterError::DebugEvaluateComptime { location, .. }
| InterpreterError::BlackBoxError(_, location)
| InterpreterError::BreakNotInLoop { location, .. }
| InterpreterError::ContinueNotInLoop { location, .. } => *location,
| InterpreterError::ContinueNotInLoop { location, .. }
| InterpreterError::FailedToResolveTraitBound { location, .. } => *location,

InterpreterError::FailedToParseMacro { error, file, .. } => {
Location::new(error.span(), *file)
Expand Down Expand Up @@ -313,7 +316,7 @@
let message = format!("Failed to parse macro's token stream into {rule}");
let tokens = vecmap(&tokens.0, ToString::to_string).join(" ");

// 10 is an aribtrary number of tokens here chosen to fit roughly onto one line

Check warning on line 319 in compiler/noirc_frontend/src/hir/comptime/errors.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (aribtrary)
let token_stream = if tokens.len() > 10 {
format!("The resulting token stream was: {tokens}")
} else {
Expand Down Expand Up @@ -373,6 +376,10 @@
InterpreterError::BlackBoxError(error, location) => {
CustomDiagnostic::simple_error(error.to_string(), String::new(), location.span)
}
InterpreterError::FailedToResolveTraitBound { trait_bound, location } => {
let msg = format!("Failed to resolve trait bound `{trait_bound}`");
CustomDiagnostic::simple_error(msg, String::new(), location.span)
}
InterpreterError::NoMatchingImplFound { error, .. } => error.into(),
InterpreterError::Break => unreachable!("Uncaught InterpreterError::Break"),
InterpreterError::Continue => unreachable!("Uncaught InterpreterError::Continue"),
Expand Down
67 changes: 64 additions & 3 deletions compiler/noirc_frontend/src/hir/comptime/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
undo_instantiation_bindings,
};
use crate::token::Tokens;
use crate::TypeVariable;
use crate::{
hir_def::{
expr::{
Expand Down Expand Up @@ -53,6 +54,12 @@
in_loop: bool,

current_function: Option<FuncId>,

/// Maps each bound generic to each binding it has in the current callstack.
/// Since the interpreter monomorphizes as it interprets, we can bind over the same generic
/// multiple times. Without this map, when one of these inner functions exits we would
/// unbind the generic completely instead of resetting it to its previous binding.
bound_generics: Vec<HashMap<TypeVariable, Type>>,
}

#[allow(unused)]
Expand All @@ -62,26 +69,41 @@
crate_id: CrateId,
current_function: Option<FuncId>,
) -> Self {
Self { elaborator, crate_id, current_function, in_loop: false }
let bound_generics = Vec::new();
Self { elaborator, crate_id, current_function, bound_generics, in_loop: false }
}

pub(crate) fn call_function(
&mut self,
function: FuncId,
arguments: Vec<(Value, Location)>,
instantiation_bindings: TypeBindings,
mut instantiation_bindings: TypeBindings,
location: Location,
) -> IResult<Value> {
let trait_method = self.elaborator.interner.get_trait_method_id(function);

// To match the monomorphizer, we need to call follow_bindings on each of
// the instantiation bindings before we unbind the generics from the previous function.
// This is because the instantiation bindings refer to variables from the call site.
for (_, binding) in instantiation_bindings.values_mut() {
*binding = binding.follow_bindings();
}

self.unbind_generics_from_previous_function();
perform_instantiation_bindings(&instantiation_bindings);
let impl_bindings =
let mut impl_bindings =
perform_impl_bindings(self.elaborator.interner, trait_method, function, location)?;

for (_, binding) in impl_bindings.values_mut() {
*binding = binding.follow_bindings();
}

self.remember_bindings(&instantiation_bindings, &impl_bindings);
let result = self.call_function_inner(function, arguments, location);

undo_instantiation_bindings(impl_bindings);
undo_instantiation_bindings(instantiation_bindings);
self.rebind_generics_from_previous_function();
result
}

Expand Down Expand Up @@ -177,13 +199,16 @@
} else if let Some(oracle) = func_attrs.oracle() {
if oracle == "print" {
self.print_oracle(arguments)
// Ignore debugger functions
} else if oracle.starts_with("__debug") {
Ok(Value::Unit)
} else {
let item = format!("Comptime evaluation for oracle functions like {oracle}");
Err(InterpreterError::Unimplemented { item, location })
}
} else {
let name = self.elaborator.interner.function_name(&function);
unreachable!("Non-builtin, lowlevel or oracle builtin fn '{name}'")

Check warning on line 211 in compiler/noirc_frontend/src/hir/comptime/interpreter.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (lowlevel)
}
}

Expand Down Expand Up @@ -250,6 +275,42 @@
self.elaborator.comptime_scopes.last_mut().unwrap()
}

fn unbind_generics_from_previous_function(&mut self) {
if let Some(bindings) = self.bound_generics.last() {
for var in bindings.keys() {
var.unbind(var.id());
}
}
// Push a new bindings list for the current function
self.bound_generics.push(HashMap::default());
}

fn rebind_generics_from_previous_function(&mut self) {
// Remove the currently bound generics first.
self.bound_generics.pop();

if let Some(bindings) = self.bound_generics.last() {
for (var, binding) in bindings {
var.force_bind(binding.clone());
}
}
}

fn remember_bindings(&mut self, main_bindings: &TypeBindings, impl_bindings: &TypeBindings) {
let bound_generics = self
.bound_generics
.last_mut()
.expect("remember_bindings called with no bound_generics on the stack");

for (var, binding) in main_bindings.values() {
bound_generics.insert(var.clone(), binding.follow_bindings());
}

for (var, binding) in impl_bindings.values() {
bound_generics.insert(var.clone(), binding.follow_bindings());
}
}

pub(super) fn define_pattern(
&mut self,
pattern: &HirPattern,
Expand Down Expand Up @@ -1177,8 +1238,8 @@
let expr = result.into_expression(self.elaborator.interner, location)?;
let expr = self
.elaborator
.elaborate_item_from_comptime(self.current_function, |elab| {

Check warning on line 1241 in compiler/noirc_frontend/src/hir/comptime/interpreter.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (elab)
elab.elaborate_expression(expr).0

Check warning on line 1242 in compiler/noirc_frontend/src/hir/comptime/interpreter.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (elab)
});
result = self.evaluate(expr)?;
}
Expand Down
39 changes: 17 additions & 22 deletions compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
use acvm::{AcirField, FieldElement};
use chumsky::Parser;
use iter_extended::{try_vecmap, vecmap};
use noirc_errors::{Location, Span};
use noirc_errors::Location;
use rustc_hash::FxHashMap as HashMap;

use crate::{
ast::{IntegerBitSize, TraitBound},
ast::IntegerBitSize,
hir::comptime::{errors::IResult, InterpreterError, Value},
macros_api::{NodeInterner, Path, Signedness, UnresolvedTypeData},
macros_api::{NodeInterner, Signedness},
node_interner::TraitId,
parser,
token::{SpannedToken, Token, Tokens},
Expand Down Expand Up @@ -53,9 +53,7 @@
"trait_def_as_trait_constraint" => {
trait_def_as_trait_constraint(interner, arguments, location)
}
"quoted_as_trait_constraint" => {
quoted_as_trait_constraint(interner, arguments, location)
}
"quoted_as_trait_constraint" => quoted_as_trait_constraint(self, arguments, location),
"quoted_as_type" => quoted_as_type(self, arguments, location),
"zeroed" => zeroed(return_type),
_ => {
Expand Down Expand Up @@ -133,9 +131,9 @@
}
}

fn get_trait_constraint(value: Value, location: Location) -> IResult<TraitBound> {
fn get_trait_constraint(value: Value, location: Location) -> IResult<(TraitId, Vec<Type>)> {
match value {
Value::TraitConstraint(bound) => Ok(bound),
Value::TraitConstraint(trait_id, generics) => Ok((trait_id, generics)),
value => {
let expected = Type::Quoted(QuotedType::TraitConstraint);
Err(InterpreterError::TypeMismatch { expected, value, location })
Expand Down Expand Up @@ -387,7 +385,7 @@

// fn as_trait_constraint(quoted: Quoted) -> TraitConstraint
fn quoted_as_trait_constraint(
_interner: &mut NodeInterner,
interpreter: &mut Interpreter,
mut arguments: Vec<(Value, Location)>,
location: Location,
) -> IResult<Value> {
Expand All @@ -402,7 +400,14 @@
InterpreterError::FailedToParseMacro { error, tokens, rule, file: location.file }
})?;

Ok(Value::TraitConstraint(trait_bound))
let bound = interpreter
.elaborator
.elaborate_item_from_comptime(interpreter.current_function, |elaborator| {
elaborator.resolve_trait_bound(&trait_bound, Type::Unit)
})
.ok_or(InterpreterError::FailedToResolveTraitBound { trait_bound, location })?;

Ok(Value::TraitConstraint(bound.trait_id, bound.trait_generics))
}

// fn as_type(quoted: Quoted) -> Type
Expand All @@ -424,7 +429,7 @@

let typ = interpreter
.elaborator
.elaborate_item_from_comptime(interpreter.current_function, |elab| elab.resolve_type(typ));

Check warning on line 432 in compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (elab)

Check warning on line 432 in compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (elab)

Ok(Value::Type(typ))
}
Expand Down Expand Up @@ -529,11 +534,6 @@
let element = zeroed(*element)?;
Ok(Value::Pointer(Shared::new(element), false))
}
Type::Quoted(QuotedType::TraitConstraint) => Ok(Value::TraitConstraint(TraitBound {
trait_path: Path::from_single(String::new(), Span::default()),
trait_id: None,
trait_generics: Vec::new(),
})),
// Optimistically assume we can resolve this type later or that the value is unused
Type::TypeVariable(_, _)
| Type::Forall(_, _)
Expand Down Expand Up @@ -618,14 +618,9 @@

let trait_id = get_trait_def(arguments.pop().unwrap().0, location)?;
let the_trait = interner.get_trait(trait_id);

let trait_path = Path::from_ident(the_trait.name.clone());

let trait_generics = vecmap(&the_trait.generics, |generic| {
let name = Path::from_single(generic.name.as_ref().clone(), generic.span);
UnresolvedTypeData::Named(name, Vec::new(), false).with_span(generic.span)
Type::NamedGeneric(generic.type_var.clone(), generic.name.clone(), generic.kind.clone())
});

let trait_id = Some(trait_id);
Ok(Value::TraitConstraint(TraitBound { trait_path, trait_id, trait_generics }))
Ok(Value::TraitConstraint(trait_id, trait_generics))
}
8 changes: 4 additions & 4 deletions compiler/noirc_frontend/src/hir/comptime/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use iter_extended::{try_vecmap, vecmap};
use noirc_errors::Location;

use crate::{
ast::{ArrayLiteral, ConstructorExpression, Ident, IntegerBitSize, Signedness, TraitBound},
ast::{ArrayLiteral, ConstructorExpression, Ident, IntegerBitSize, Signedness},
hir::def_map::ModuleId,
hir_def::expr::{HirArrayLiteral, HirConstructorExpression, HirIdent, HirLambda, ImplKind},
macros_api::{
Expand Down Expand Up @@ -48,7 +48,7 @@ pub enum Value {
Slice(Vector<Value>, Type),
Code(Rc<Tokens>),
StructDefinition(StructId),
TraitConstraint(TraitBound),
TraitConstraint(TraitId, /* trait generics */ Vec<Type>),
TraitDefinition(TraitId),
FunctionDefinition(FuncId),
ModuleDefinition(ModuleId),
Expand Down Expand Up @@ -222,7 +222,7 @@ impl Value {
}
Value::Pointer(..)
| Value::StructDefinition(_)
| Value::TraitConstraint(_)
| Value::TraitConstraint(..)
| Value::TraitDefinition(_)
| Value::FunctionDefinition(_)
| Value::Zeroed(_)
Expand Down Expand Up @@ -342,7 +342,7 @@ impl Value {
Value::Code(block) => HirExpression::Unquote(unwrap_rc(block)),
Value::Pointer(..)
| Value::StructDefinition(_)
| Value::TraitConstraint(_)
| Value::TraitConstraint(..)
| Value::TraitDefinition(_)
| Value::FunctionDefinition(_)
| Value::Zeroed(_)
Expand Down
7 changes: 7 additions & 0 deletions test_programs/execution_success/derive/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "derive"
type = "bin"
authors = [""]
compiler_version = ">=0.31.0"

[dependencies]
51 changes: 51 additions & 0 deletions test_programs/execution_success/derive/src/main.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use std::collections::umap::UHashMap;
use std::hash::BuildHasherDefault;
use std::hash::poseidon2::Poseidon2Hasher;

#[my_derive(DoNothing)]
struct MyStruct { my_field: u32 }

type DeriveFunction = fn(StructDefinition) -> Quoted;

comptime mut global HANDLERS: UHashMap<TraitConstraint, DeriveFunction, BuildHasherDefault<Poseidon2Hasher>> = UHashMap::default();

comptime fn my_derive(s: StructDefinition, traits: [Quoted]) -> Quoted {
let mut result = quote {};

for trait_to_derive in traits {
let handler = HANDLERS.get(trait_to_derive.as_trait_constraint());
assert(handler.is_some(), f"No derive function registered for `{trait_to_derive}`");

let trait_impl = handler.unwrap()(s);
result = quote { $result $trait_impl };
}

result
}

unconstrained comptime fn my_derive_via(t: TraitDefinition, f: Quoted) {
HANDLERS.insert(t.as_trait_constraint(), std::meta::unquote!(f));
}

#[my_derive_via(derive_do_nothing)]
trait DoNothing {
fn do_nothing(self);
}

comptime fn derive_do_nothing(s: StructDefinition) -> Quoted {
let typ = s.as_type();
let generics = s.generics().join(quote {,});
quote {
impl<$generics> DoNothing for $typ {
fn do_nothing(_self: Self) {
// Traits can't tell us what to do
println("something");
}
}
}
}

fn main() {
let s = MyStruct { my_field: 1 };
s.do_nothing();
}
7 changes: 7 additions & 0 deletions test_programs/execution_success/regression_5615/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "regression_5615"
type = "bin"
authors = [""]
compiler_version = ">=0.32.0"

[dependencies]
12 changes: 12 additions & 0 deletions test_programs/execution_success/regression_5615/src/main.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use std::collections::umap::UHashMap;
use std::hash::BuildHasherDefault;
use std::hash::poseidon2::Poseidon2Hasher;

unconstrained fn main() {
comptime
{
let mut map: UHashMap<u32, Field, BuildHasherDefault<Poseidon2Hasher>> = UHashMap::default();

map.insert(1, 2);
}
}
Loading