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
101 changes: 84 additions & 17 deletions compiler/noirc_frontend/src/elaborator/patterns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ use crate::{
type_check::{Source, TypeCheckError},
},
hir_def::{
expr::{HirExpression, HirIdent, HirMethodReference, ImplKind, TraitMethod},
expr::{HirExpression, HirIdent, HirLiteral, HirMethodReference, ImplKind, TraitMethod},
stmt::HirPattern,
traits::NamedType,
},
node_interner::{
DefinitionId, DefinitionInfo, DefinitionKind, ExprId, FuncId, GlobalId, TraitImplKind,
},
signed_field::SignedField,
};

use super::{
Expand Down Expand Up @@ -554,22 +556,12 @@ impl Elaborator<'_> {
}

pub(super) fn elaborate_variable(&mut self, variable: Path) -> (ExprId, Type) {
let mut variable = self.validate_path(variable);

// Check if this is `Self::method_name` when we are inside a trait impl and `Self`
// resolves to a primitive type. In that case we elaborate this as if it were a TypePath
// (for example, if `Self` is `u32` then we consider this the same as `u32::method_name`).
// A regular path lookup won't work here for the same reason `TypePath` exists.
if let Some(self_type) = &self.self_type {
if self.current_trait_impl.is_some()
&& variable.segments.len() == 2
&& variable.segments[0].ident.is_self_type_name()
&& !matches!(self.self_type, Some(Type::DataType(..)))
{
let ident = variable.segments.remove(1).ident;
let typ_location = variable.segments[0].location;
return self.elaborate_type_path_impl(self_type.clone(), ident, None, typ_location);
}
let variable = self.validate_path(variable);

if let Some((expr_id, typ)) =
self.elaborate_variable_as_self_method_or_associated_constant(&variable)
{
return (expr_id, typ);
}

let resolved_turbofish = variable.segments.last().unwrap().generics.clone();
Expand Down Expand Up @@ -633,6 +625,68 @@ impl Elaborator<'_> {
}
}

/// Checks whether `variable` is `Self::method_name` or `Self::AssociatedConstant` when we are inside a trait impl and `Self`
/// resolves to a primitive type.
///
/// In the first case we elaborate this as if it were a TypePath
/// (for example, if `Self` is `u32` then we consider this the same as `u32::method_name`).
/// A regular path lookup won't work here for the same reason `TypePath` exists.
///
/// In the second case we solve the associated constant by looking up its value, later
/// turning it into a literal.
fn elaborate_variable_as_self_method_or_associated_constant(
&mut self,
variable: &TypedPath,
) -> Option<(ExprId, Type)> {
if !(variable.segments.len() == 2 && variable.segments[0].ident.is_self_type_name()) {
return None;
}

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

// Check the `Self::AssociatedConstant` case when inside a trait
if let Some(trait_id) = &self.current_trait {
let trait_ = self.interner.get_trait(*trait_id);
if let Some(associated_type) = trait_.get_associated_type(name) {
if let Kind::Numeric(numeric_type) = associated_type.kind() {
// We can produce any value here because this trait method is never going to
// 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));
}
}
}

let Some(self_type) = &self.self_type else {
return None;
};

let Some(trait_impl_id) = &self.current_trait_impl else {
return None;
};

// 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));
}

// Check the `Self::method_name` case when `Self` is a primitive type
if matches!(self.self_type, Some(Type::DataType(..))) {
return None;
}

let ident = variable.segments[1].ident.clone();
let typ_location = variable.segments[0].location;
Some(self.elaborate_type_path_impl(self_type.clone(), ident, None, typ_location))
}

pub(crate) fn validate_path(&mut self, path: Path) -> TypedPath {
let mut segments = vecmap(path.segments, |segment| self.validate_path_segment(segment));

Expand Down Expand Up @@ -661,6 +715,19 @@ impl Elaborator<'_> {
TypedPathSegment { ident: segment.ident, generics, location: segment.location }
}

fn constant_integer(
&mut self,
numeric_type: Type,
value: SignedField,
location: Location,
) -> (ExprId, Type) {
let hir_expr = HirExpression::Literal(HirLiteral::Integer(value));
let id = self.interner.push_expr(hir_expr);
self.interner.push_expr_location(id, location);
self.interner.push_expr_type(id, numeric_type.clone());
(id, numeric_type)
}

/// Solve any generics that are part of the path before the function, for example:
///
/// ```noir
Expand Down
48 changes: 48 additions & 0 deletions compiler/noirc_frontend/src/tests/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1580,3 +1580,51 @@ fn errors_if_unconstrained_trait_definition_has_constrained_impl() {
"#;
check_errors!(src);
}

#[named]
#[test]
fn accesses_associated_type_inside_trait_impl_using_self() {
let src = r#"
pub trait Trait {
let N: u32;

fn foo() -> u32;
}

impl Trait for i32 {
let N: u32 = 10;

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

fn main() {
let _ = i32::foo();
}
"#;
assert_no_errors!(src);
}

#[named]
#[test]
fn accesses_associated_type_inside_trait_using_self() {
let src = r#"
pub trait Trait {
let N: u32;

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

impl Trait for i32 {
let N: u32 = 10;
}

fn main() {
let _ = i32::foo();
}
"#;
assert_no_errors!(src);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

[package]
name = "noirc_frontend_tests_traits_accesses_associated_type_inside_trait_impl_using_self"
type = "bin"
authors = [""]

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

pub trait Trait {
let N: u32;

fn foo() -> u32;
}

impl Trait for i32 {
let N: u32 = 10;

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

fn main() {
let _ = i32::foo();
}

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
16784421911760213987
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

[package]
name = "noirc_frontend_tests_traits_accesses_associated_type_inside_trait_using_self"
type = "bin"
authors = [""]

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

pub trait Trait {
let N: u32;

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

impl Trait for i32 {
let N: u32 = 10;
}

fn main() {
let _ = i32::foo();
}

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
13003701006269760980
7 changes: 3 additions & 4 deletions tooling/nargo_cli/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ const IGNORED_NARGO_EXPAND_COMPILE_SUCCESS_EMPTY_TESTS: [&str; 16] = [

/// These tests 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_COMPILE_SUCCESS_NO_BUG_TESTS: [&str; 17] = [
const IGNORED_NARGO_EXPAND_COMPILE_SUCCESS_NO_BUG_TESTS: [&str; 16] = [
"noirc_frontend_tests_arithmetic_generics_checked_casts_do_not_prevent_canonicalization",
"noirc_frontend_tests_check_trait_as_type_as_fn_parameter",
"noirc_frontend_tests_check_trait_as_type_as_two_fn_parameters",
Expand All @@ -189,13 +189,12 @@ const IGNORED_NARGO_EXPAND_COMPILE_SUCCESS_NO_BUG_TESTS: [&str; 17] = [
"noirc_frontend_tests_traits_calls_trait_function_if_it_is_in_scope",
"noirc_frontend_tests_traits_calls_trait_function_if_it_is_only_candidate_in_scope",
"noirc_frontend_tests_traits_calls_trait_function_if_it_is_only_candidate_in_scope_in_nested_module_using_super",
"noirc_frontend_tests_traits_passes_trait_with_associated_number_to_generic_function",
"noirc_frontend_tests_traits_passes_trait_with_associated_number_to_generic_function_inside_struct_impl",
"noirc_frontend_tests_traits_trait_alias_polymorphic_inheritance",
"noirc_frontend_tests_traits_trait_alias_single_member",
"noirc_frontend_tests_traits_trait_alias_two_members",
"noirc_frontend_tests_traits_trait_impl_with_where_clause_with_trait_with_associated_numeric",
"noirc_frontend_tests_traits_trait_impl_with_where_clause_with_trait_with_associated_type",
"noirc_frontend_tests_traits_accesses_associated_type_inside_trait_impl_using_self",
"noirc_frontend_tests_traits_accesses_associated_type_inside_trait_using_self",
"noirc_frontend_tests_u32_globals_as_sizes_in_types",
"noirc_frontend_tests_unused_items_considers_struct_as_constructed_if_trait_method_is_called",
];
Expand Down
28 changes: 23 additions & 5 deletions tooling/nargo_cli/src/cli/expand_cmd/printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,8 +353,17 @@ impl<'interner, 'def_map, 'string> ItemPrinter<'interner, 'def_map, 'string> {
}

self.write_indent();
self.push_str("type ");
self.push_str(&associated_type.name);

if let Kind::Numeric(numeric_type) = associated_type.kind() {
self.push_str("let ");
self.push_str(&associated_type.name);
self.push_str(": ");
self.show_type(&numeric_type);
} else {
self.push_str("type ");
self.push_str(&associated_type.name);
}

self.push_str(";");
printed_type_or_function = true;
}
Expand Down Expand Up @@ -414,9 +423,18 @@ impl<'interner, 'def_map, 'string> ItemPrinter<'interner, 'def_map, 'string> {
}

self.write_indent();
self.push_str("type ");
self.push_str(&named_type.name.to_string());
self.push_str(" = ");

if let Type::Constant(_, Kind::Numeric(numeric_type)) = &named_type.typ {
self.push_str("let ");
self.push_str(&named_type.name.to_string());
self.push_str(": ");
self.show_type(numeric_type);
self.push_str(" = ");
} else {
self.push_str("type ");
self.push_str(&named_type.name.to_string());
self.push_str(" = ");
}
self.show_type(&named_type.typ);
self.push_str(";");

Expand Down

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

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

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

Loading