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
24 changes: 16 additions & 8 deletions compiler/noirc_frontend/src/elaborator/patterns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ use crate::{
hir_def::{
expr::{HirExpression, HirIdent, HirLiteral, HirMethodReference, ImplKind, TraitMethod},
stmt::HirPattern,
traits::NamedType,
},
node_interner::{
DefinitionId, DefinitionInfo, DefinitionKind, ExprId, FuncId, GlobalId, TraitImplKind,
Expand Down Expand Up @@ -653,6 +652,7 @@ impl Elaborator<'_> {
return None;
}

let location = variable.location;
let name = variable.segments[1].ident.as_str();

// Check the `Self::AssociatedConstant` case when inside a trait
Expand All @@ -664,7 +664,7 @@ impl Elaborator<'_> {
// produce code (only trait impl methods do)
let numeric_type: Type = *numeric_type.clone();
let value = SignedField::zero();
return Some(self.constant_integer(numeric_type, value, variable.location));
return Some(self.constant_integer(numeric_type, value, location));
}
}
}
Expand All @@ -680,12 +680,17 @@ impl Elaborator<'_> {
// Check the `Self::AssociatedConstant` case when inside a trait impl
let associated_types = self.interner.get_associated_types_for_impl(*trait_impl_id);
let associated_type = associated_types.iter().find(|typ| typ.name.as_str() == name);
if let Some(NamedType { typ: Type::Constant(field, Kind::Numeric(numeric_type)), .. }) =
associated_type
{
let numeric_type: Type = *numeric_type.clone();
let value = SignedField::positive(*field);
return Some(self.constant_integer(numeric_type, value, variable.location));
if let Some(associated_type) = associated_type {
if let Kind::Numeric(numeric_type) = associated_type.typ.kind() {
let definition_id =
self.interner.get_associated_constant_definition_id(*trait_impl_id, name);
let hir_ident = HirIdent::non_trait_method(definition_id, location);
let hir_expr = HirExpression::Ident(hir_ident, None);
let id = self.interner.push_expr(hir_expr);
self.interner.push_expr_location(id, location);
self.interner.push_expr_type(id, *numeric_type.clone());
return Some((id, *numeric_type.clone()));
}
}

// Check the `Self::method_name` case when `Self` is a primitive type
Expand Down Expand Up @@ -964,6 +969,9 @@ impl Elaborator<'_> {

self.interner.add_local_reference(hir_ident.id, location);
}
DefinitionKind::AssociatedConstant(..) => {
// Nothing to do here
}
}
}

Expand Down
17 changes: 17 additions & 0 deletions compiler/noirc_frontend/src/hir/comptime/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ pub enum InterpreterError {
err: Option<Box<TypeCheckError>>,
location: Location,
},
NonIntegerAssociatedConstant {
typ: Type,
err: Option<Box<TypeCheckError>>,
location: Location,
},
NonNumericCasted {
typ: Type,
location: Location,
Expand Down Expand Up @@ -289,6 +294,7 @@ impl InterpreterError {
| InterpreterError::NonIntegerUsedAsIndex { location, .. }
| InterpreterError::NonIntegerIntegerLiteral { location, .. }
| InterpreterError::NonIntegerArrayLength { location, .. }
| InterpreterError::NonIntegerAssociatedConstant { location, .. }
| InterpreterError::NonNumericCasted { location, .. }
| InterpreterError::IndexOutOfBounds { location, .. }
| InterpreterError::ExpectedStructToHaveField { location, .. }
Expand Down Expand Up @@ -461,6 +467,17 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic {
};
CustomDiagnostic::simple_error(msg, secondary, *location)
}
InterpreterError::NonIntegerAssociatedConstant { typ, err, location } => {
let msg = format!("Non-integer associated constant: `{typ}`");
let secondary = if let Some(err) = err {
format!(
"Associated constants must be integers, but evaluating `{typ}` resulted in `{err}`"
)
} else {
"Associated constants must be integers".to_string()
};
CustomDiagnostic::simple_error(msg, secondary, *location)
}
InterpreterError::NonNumericCasted { typ, location } => {
let msg = "Only numeric types may be casted".into();
let secondary = format!("`{typ}` is non-numeric");
Expand Down
23 changes: 23 additions & 0 deletions compiler/noirc_frontend/src/hir/comptime/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,29 @@ impl<'local, 'interner> Interpreter<'local, 'interner> {

self.evaluate_integer(value.into(), id)
}
DefinitionKind::AssociatedConstant(trait_impl_id, name) => {
let associated_types =
self.elaborator.interner.get_associated_types_for_impl(*trait_impl_id);
let associated_type = associated_types
.iter()
.find(|typ| typ.name.as_str() == name)
.expect("Expected to find associated type");
let Kind::Numeric(numeric_type) = associated_type.typ.kind() else {
unreachable!("Expected associated type to be numeric");
};
let location = self.elaborator.interner.expr_location(&id);
match associated_type
.typ
.evaluate_to_field_element(&associated_type.typ.kind(), location)
{
Ok(value) => self.evaluate_integer(value.into(), id),
Err(err) => Err(InterpreterError::NonIntegerArrayLength {
typ: associated_type.typ.clone(),
err: Some(Box::new(err)),
location,
}),
}
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_frontend/src/hir_def/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1512,7 +1512,7 @@ impl Type {
}
}

pub(crate) fn kind(&self) -> Kind {
pub fn kind(&self) -> Kind {
match self {
Type::CheckedCast { to, .. } => to.kind(),
Type::NamedGeneric(NamedGeneric { type_var, .. }) => type_var.kind(),
Expand Down
13 changes: 12 additions & 1 deletion compiler/noirc_frontend/src/monomorphization/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ pub enum MonomorphizationError {
typ: Type,
location: Location,
},
CannotComputeAssociatedConstant {
name: String,
err: TypeCheckError,
location: Location,
},
}

impl MonomorphizationError {
Expand All @@ -66,7 +71,8 @@ impl MonomorphizationError {
| MonomorphizationError::CheckedCastFailed { location, .. }
| MonomorphizationError::RecursiveType { location, .. }
| MonomorphizationError::NoDefaultType { location, .. }
| MonomorphizationError::NoDefaultTypeInItem { location, .. } => *location,
| MonomorphizationError::NoDefaultTypeInItem { location, .. }
| MonomorphizationError::CannotComputeAssociatedConstant { location, .. } => *location,
MonomorphizationError::InterpreterError(error) => error.location(),
}
}
Expand Down Expand Up @@ -124,6 +130,11 @@ impl From<MonomorphizationError> for CustomDiagnostic {
let secondary = "All types in Noir must have a known size at compile-time".into();
return CustomDiagnostic::simple_error(message, secondary, *location);
}
MonomorphizationError::CannotComputeAssociatedConstant { name, err, .. } => {
format!(
"Could not determine the value of associated constant `{name}`, encountered error: `{err}`"
)
}
};

let location = error.location();
Expand Down
28 changes: 28 additions & 0 deletions compiler/noirc_frontend/src/monomorphization/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,34 @@ impl<'interner> Monomorphizer<'interner> {
let value = SignedField::positive(value);
ast::Expression::Literal(ast::Literal::Integer(value, typ, location))
}
DefinitionKind::AssociatedConstant(trait_impl_id, name) => {
let location = ident.location;
let associated_types = self.interner.get_associated_types_for_impl(*trait_impl_id);
let associated_type = associated_types
.iter()
.find(|typ| typ.name.as_str() == name)
.expect("Expected to find associated type");
let Kind::Numeric(numeric_type) = associated_type.typ.kind() else {
unreachable!("Expected associated type to be numeric");
};
match associated_type
.typ
.evaluate_to_field_element(&associated_type.typ.kind(), location)
{
Ok(value) => {
let typ = Self::convert_type(&numeric_type, location)?;
let value = SignedField::positive(value);
ast::Expression::Literal(ast::Literal::Integer(value, typ, location))
}
Err(err) => {
return Err(MonomorphizationError::CannotComputeAssociatedConstant {
name: name.clone(),
err,
location,
});
}
}
}
};

Ok(ident)
Expand Down
42 changes: 41 additions & 1 deletion compiler/noirc_frontend/src/node_interner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ pub struct NodeInterner {
/// created, when resolving the type signature of each method in the impl.
trait_impl_associated_types: HashMap<TraitImplId, Vec<NamedType>>,

trait_impl_associated_constant_definition_ids:
HashMap<TraitImplId, HashMap<String, DefinitionId>>,

/// Trait implementations on each type. This is expected to always have the same length as
/// `self.trait_implementations`.
///
Expand Down Expand Up @@ -597,6 +600,8 @@ pub enum DefinitionKind {
/// Generic types in functions (T, U in `fn foo<T, U>(...)` are declared as variables
/// in scope in case they resolve to numeric generics later.
NumericGeneric(TypeVariable, Box<Type>),

AssociatedConstant(TraitImplId, String),
}

impl DefinitionKind {
Expand All @@ -612,6 +617,7 @@ impl DefinitionKind {
DefinitionKind::Global(_) => None,
DefinitionKind::Local(id) => *id,
DefinitionKind::NumericGeneric(_, _) => None,
DefinitionKind::AssociatedConstant(_, _) => None,
}
}
}
Expand Down Expand Up @@ -718,6 +724,7 @@ impl Default for NodeInterner {
auto_import_names: HashMap::default(),
comptime_scopes: vec![HashMap::default()],
trait_impl_associated_types: HashMap::default(),
trait_impl_associated_constant_definition_ids: HashMap::default(),
doc_comments: HashMap::default(),
reexports: HashMap::default(),
}
Expand Down Expand Up @@ -2279,14 +2286,38 @@ impl NodeInterner {
self.lsp_mode
}

pub fn set_associated_types_for_impl(
/// Sets the associated types for the given trait impl.
/// Each type in [`NamedType`] will be wrapped in a [`Type::TypeVariable`] if it's of kind [`Kind::Numeric`].
pub(crate) fn set_associated_types_for_impl(
&mut self,
impl_id: TraitImplId,
associated_types: Vec<NamedType>,
) {
// Wrap the named generics in type variables to be able to refer them as type variables
for associated_type in &associated_types {
let Kind::Numeric(..) = associated_type.typ.kind() else {
continue;
};

let name = associated_type.name.to_string();
let definition_id = self.push_definition(
associated_type.name.to_string(),
false,
false,
DefinitionKind::AssociatedConstant(impl_id, name.clone()),
associated_type.name.location(),
);
self.trait_impl_associated_constant_definition_ids
.entry(impl_id)
.or_default()
.insert(name, definition_id);
}

self.trait_impl_associated_types.insert(impl_id, associated_types);
}

/// Returns the associated types for the given trait impl.
/// The Type of each [`NamedType`] that is an associated constant is guaranteed to be a [`Type::TypeVariable`].
pub fn get_associated_types_for_impl(&self, impl_id: TraitImplId) -> &[NamedType] {
&self.trait_impl_associated_types[&impl_id]
}
Expand All @@ -2300,6 +2331,15 @@ impl NodeInterner {
types.iter().find(|typ| typ.name.as_str() == type_name).map(|typ| &typ.typ)
}

/// Returns the definition id for the associated constant of the given type variable.
pub fn get_associated_constant_definition_id(
&self,
impl_id: TraitImplId,
name: &str,
) -> DefinitionId {
self.trait_impl_associated_constant_definition_ids[&impl_id][name]
}

/// Return a set of TypeBindings to bind types from the parent trait to those from the trait impl.
pub fn trait_to_impl_bindings(
&self,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[package]
name = "trait_associated_constant"
type = "bin"
authors = [""]
[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
pub trait Trait {
let N: u32;

fn foo() -> u32;
}

struct Foo<let A: u32, let B: u32> {}

impl<let A: u32, let B: u32> Trait for Foo<A, B> {
let N: u32 = A + B;

fn foo() -> u32 {
Self::N
}
}

fn main() {
let x = Foo::<10, 20>::foo();
assert_eq(x, 30);

comptime {
let x = Foo::<10, 20>::foo();
assert_eq(x, 30);
}
}
4 changes: 3 additions & 1 deletion tooling/nargo_cli/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ const TESTS_WITH_EXPECTED_WARNINGS: [&str; 5] = [
/// might not be worth it.
/// Others are ignored because of existing bugs in `nargo expand`.
/// As the bugs are fixed these tests should be removed from this list.
const IGNORED_NARGO_EXPAND_EXECUTION_TESTS: [&str; 8] = [
const IGNORED_NARGO_EXPAND_EXECUTION_TESTS: [&str; 9] = [
// There's nothing special about this program but making it work with a custom entry would involve
// having to parse the Nargo.toml file, etc., which is not worth it
"custom_entry",
Expand All @@ -133,6 +133,8 @@ const IGNORED_NARGO_EXPAND_EXECUTION_TESTS: [&str; 8] = [
"regression_5045",
// bug
"regression_7744",
// bug
"trait_associated_constant",
// There's no "src/main.nr" here so it's trickier to make this work
"workspace",
// There's no "src/main.nr" here so it's trickier to make this work
Expand Down
4 changes: 2 additions & 2 deletions tooling/nargo_cli/src/cli/expand_cmd/printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -432,11 +432,11 @@ impl<'context, 'string> ItemPrinter<'context, 'string> {

self.write_indent();

if let Type::Constant(_, Kind::Numeric(numeric_type)) = &named_type.typ {
if let Kind::Numeric(numeric_type) = named_type.typ.kind() {
self.push_str("let ");
self.push_str(&named_type.name.to_string());
self.push_str(": ");
self.show_type(numeric_type);
self.show_type(&numeric_type);
self.push_str(" = ");
} else {
self.push_str("type ");
Expand Down
4 changes: 3 additions & 1 deletion tooling/nargo_cli/src/cli/expand_cmd/printer/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -727,7 +727,9 @@ impl ItemPrinter<'_, '_> {
use_import,
);
}
DefinitionKind::Local(..) | DefinitionKind::NumericGeneric(..) => {
DefinitionKind::Local(..)
| DefinitionKind::NumericGeneric(..)
| DefinitionKind::AssociatedConstant(..) => {
let name = self.interner.definition_name(ident.id);

// The compiler uses '$' for some internal identifiers.
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading