Skip to content

Commit

Permalink
feat: support specifying generics on a struct when calling an associa…
Browse files Browse the repository at this point in the history
…ted function (#6306)
  • Loading branch information
asterite authored Oct 29, 2024
1 parent b985fdf commit eba151e
Show file tree
Hide file tree
Showing 16 changed files with 514 additions and 299 deletions.
5 changes: 1 addition & 4 deletions compiler/noirc_frontend/src/elaborator/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ impl<'context> Elaborator<'context> {
let hir_ident = if let Some((old_value, _)) = variable {
old_value.num_times_used += 1;
old_value.ident.clone()
} else if let Ok(definition_id) =
} else if let Ok((definition_id, _)) =
self.lookup_global(Path::from_single(ident_name.to_string(), call_expr_span))
{
HirIdent::non_trait_method(definition_id, Location::new(call_expr_span, self.file))
Expand Down Expand Up @@ -536,9 +536,6 @@ impl<'context> Elaborator<'context> {
last_segment.generics = Some(generics.ordered_args);
}

let exclude_last_segment = true;
self.check_unsupported_turbofish_usage(&path, exclude_last_segment);

let last_segment = path.last_segment();
let is_self_type = last_segment.ident.is_self_type_name();

Expand Down
17 changes: 9 additions & 8 deletions compiler/noirc_frontend/src/elaborator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use std::{
};

use crate::{
ast::ItemVisibility, hir_def::traits::ResolvedTraitBound, StructField, StructType, TypeBindings,
ast::ItemVisibility, hir::resolution::import::PathResolutionItem,
hir_def::traits::ResolvedTraitBound, StructField, StructType, TypeBindings,
};
use crate::{
ast::{
Expand All @@ -20,7 +21,7 @@ use crate::{
},
def_collector::{dc_crate::CollectedItems, errors::DefCollectorErrorKind},
def_map::{DefMaps, ModuleData},
def_map::{LocalModuleId, ModuleDefId, ModuleId, MAIN_FUNCTION},
def_map::{LocalModuleId, ModuleId, MAIN_FUNCTION},
resolution::errors::ResolverError,
resolution::import::PathResolution,
scope::ScopeForest as GenericScopeForest,
Expand Down Expand Up @@ -667,11 +668,11 @@ impl<'context> Elaborator<'context> {

pub fn resolve_module_by_path(&mut self, path: Path) -> Option<ModuleId> {
match self.resolve_path(path.clone()) {
Ok(PathResolution { module_def_id: ModuleDefId::ModuleId(module_id), error }) => {
if error.is_some() {
None
} else {
Ok(PathResolution { item: PathResolutionItem::Module(module_id), errors }) => {
if errors.is_empty() {
Some(module_id)
} else {
None
}
}
_ => None,
Expand All @@ -680,8 +681,8 @@ impl<'context> Elaborator<'context> {

fn resolve_trait_by_path(&mut self, path: Path) -> Option<TraitId> {
let error = match self.resolve_path(path.clone()) {
Ok(PathResolution { module_def_id: ModuleDefId::TraitId(trait_id), error }) => {
if let Some(error) = error {
Ok(PathResolution { item: PathResolutionItem::Trait(trait_id), errors }) => {
for error in errors {
self.push_err(error);
}
return Some(trait_id);
Expand Down
121 changes: 91 additions & 30 deletions compiler/noirc_frontend/src/elaborator/patterns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
},
hir::{
def_collector::dc_crate::CompilationError,
resolution::errors::ResolverError,
resolution::{errors::ResolverError, import::PathResolutionItem},
type_check::{Source, TypeCheckError},
},
hir_def::{
Expand Down Expand Up @@ -178,9 +178,6 @@ impl<'context> Elaborator<'context> {
mutable: Option<Span>,
new_definitions: &mut Vec<HirIdent>,
) -> HirPattern {
let exclude_last_segment = true;
self.check_unsupported_turbofish_usage(&name, exclude_last_segment);

let last_segment = name.last_segment();
let name_span = last_segment.ident.span();
let is_self_type = last_segment.ident.is_self_type_name();
Expand All @@ -195,7 +192,7 @@ impl<'context> Elaborator<'context> {
};

let (struct_type, generics) = match self.lookup_type_or_error(name) {
Some(Type::Struct(struct_type, generics)) => (struct_type, generics),
Some(Type::Struct(struct_type, struct_generics)) => (struct_type, struct_generics),
None => return error_identifier(self),
Some(typ) => {
let typ = typ.to_string();
Expand Down Expand Up @@ -468,54 +465,102 @@ impl<'context> Elaborator<'context> {
}

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

let unresolved_turbofish = variable.segments.last().unwrap().generics.clone();

let span = variable.span;
let expr = self.resolve_variable(variable);
let (expr, item) = self.resolve_variable(variable);
let definition_id = expr.id;

let type_generics = item.map(|item| self.resolve_item_turbofish(item)).unwrap_or_default();

let definition_kind =
self.interner.try_definition(definition_id).map(|definition| definition.kind.clone());

let mut bindings = TypeBindings::new();

// Resolve any generics if we the variable we have resolved is a function
// and if the turbofish operator was used.
let generics = definition_kind.and_then(|definition_kind| match &definition_kind {
DefinitionKind::Function(function) => {
self.resolve_function_turbofish_generics(function, unresolved_turbofish, span)
let generics = if let Some(DefinitionKind::Function(func_id)) = &definition_kind {
self.resolve_function_turbofish_generics(func_id, unresolved_turbofish, span)
} else {
None
};

// If this is a function call on a type that has generics, we need to bind those generic types.
if !type_generics.is_empty() {
if let Some(DefinitionKind::Function(func_id)) = &definition_kind {
// `all_generics` will always have the enclosing type generics first, so we need to bind those
let func_generics = &self.interner.function_meta(func_id).all_generics;
for (type_generic, func_generic) in type_generics.into_iter().zip(func_generics) {
let type_var = &func_generic.type_var;
bindings
.insert(type_var.id(), (type_var.clone(), type_var.kind(), type_generic));
}
}
_ => None,
});
}

let id = self.interner.push_expr(HirExpression::Ident(expr.clone(), generics.clone()));

self.interner.push_expr_location(id, span, self.file);
let typ = self.type_check_variable(expr, id, generics);
let typ = self.type_check_variable_with_bindings(expr, id, generics, bindings);
self.interner.push_expr_type(id, typ.clone());

(id, typ)
}

fn resolve_variable(&mut self, path: Path) -> HirIdent {
/// Solve any generics that are part of the path before the function, for example:
///
/// foo::Bar::<i32>::baz
/// ^^^^^
/// solve these
fn resolve_item_turbofish(&mut self, item: PathResolutionItem) -> Vec<Type> {
match item {
PathResolutionItem::StructFunction(struct_id, Some(generics), _func_id) => {
let struct_type = self.interner.get_struct(struct_id);
let struct_type = struct_type.borrow();
let struct_generics = struct_type.instantiate(self.interner);
self.resolve_struct_turbofish_generics(
&struct_type,
struct_generics,
Some(generics.generics),
generics.span,
)
}
PathResolutionItem::TypeAliasFunction(_type_alias_id, Some(generics), _func_id) => {
// TODO: https://github.com/noir-lang/noir/issues/6311
self.push_err(TypeCheckError::UnsupportedTurbofishUsage { span: generics.span });
Vec::new()
}
PathResolutionItem::TraitFunction(_trait_id, Some(generics), _func_id) => {
// TODO: https://github.com/noir-lang/noir/issues/6310
self.push_err(TypeCheckError::UnsupportedTurbofishUsage { span: generics.span });
Vec::new()
}
_ => Vec::new(),
}
}

fn resolve_variable(&mut self, path: Path) -> (HirIdent, Option<PathResolutionItem>) {
if let Some(trait_path_resolution) = self.resolve_trait_generic_path(&path) {
if let Some(error) = trait_path_resolution.error {
for error in trait_path_resolution.errors {
self.push_err(error);
}

HirIdent {
location: Location::new(path.span, self.file),
id: self.interner.trait_method_id(trait_path_resolution.method.method_id),
impl_kind: ImplKind::TraitMethod(trait_path_resolution.method),
}
(
HirIdent {
location: Location::new(path.span, self.file),
id: self.interner.trait_method_id(trait_path_resolution.method.method_id),
impl_kind: ImplKind::TraitMethod(trait_path_resolution.method),
},
None,
)
} else {
// If the Path is being used as an Expression, then it is referring to a global from a separate module
// Otherwise, then it is referring to an Identifier
// This lookup allows support of such statements: let x = foo::bar::SOME_GLOBAL + 10;
// If the expression is a singular indent, we search the resolver's current scope as normal.
let span = path.span();
let (hir_ident, var_scope_index) = self.get_ident_from_path(path);
let ((hir_ident, var_scope_index), item) = self.get_ident_from_path(path);

if hir_ident.id != DefinitionId::dummy_id() {
match self.interner.definition(hir_ident.id).kind {
Expand Down Expand Up @@ -557,7 +602,7 @@ impl<'context> Elaborator<'context> {
}
}

hir_ident
(hir_ident, item)
}
}

Expand All @@ -567,8 +612,17 @@ impl<'context> Elaborator<'context> {
expr_id: ExprId,
generics: Option<Vec<Type>>,
) -> Type {
let mut bindings = TypeBindings::new();
let bindings = TypeBindings::new();
self.type_check_variable_with_bindings(ident, expr_id, generics, bindings)
}

pub(super) fn type_check_variable_with_bindings(
&mut self,
ident: HirIdent,
expr_id: ExprId,
generics: Option<Vec<Type>>,
mut bindings: TypeBindings,
) -> Type {
// Add type bindings from any constraints that were used.
// We need to do this first since otherwise instantiating the type below
// will replace each trait generic with a fresh type variable, rather than
Expand Down Expand Up @@ -668,24 +722,31 @@ impl<'context> Elaborator<'context> {
}
}

pub fn get_ident_from_path(&mut self, path: Path) -> (HirIdent, usize) {
pub fn get_ident_from_path(
&mut self,
path: Path,
) -> ((HirIdent, usize), Option<PathResolutionItem>) {
let location = Location::new(path.last_ident().span(), self.file);

let error = match path.as_ident().map(|ident| self.use_variable(ident)) {
Some(Ok(found)) => return found,
Some(Ok(found)) => return (found, None),
// Try to look it up as a global, but still issue the first error if we fail
Some(Err(error)) => match self.lookup_global(path) {
Ok(id) => return (HirIdent::non_trait_method(id, location), 0),
Ok((id, item)) => {
return ((HirIdent::non_trait_method(id, location), 0), Some(item))
}
Err(_) => error,
},
None => match self.lookup_global(path) {
Ok(id) => return (HirIdent::non_trait_method(id, location), 0),
Ok((id, item)) => {
return ((HirIdent::non_trait_method(id, location), 0), Some(item))
}
Err(error) => error,
},
};
self.push_err(error);
let id = DefinitionId::dummy_id();
(HirIdent::non_trait_method(id, location), 0)
((HirIdent::non_trait_method(id, location), 0), None)
}

pub(super) fn elaborate_type_path(&mut self, path: TypePath) -> (ExprId, Type) {
Expand Down
Loading

0 comments on commit eba151e

Please sign in to comment.