Skip to content

Commit

Permalink
chore(experimental): Add types and traits to the elaborator (#5066)
Browse files Browse the repository at this point in the history
# Description

## Problem\*

Working towards #4594 

## Summary\*

Adds support for resolving top-level traits and type definitions to the
elaborator.

## Additional Context

After this PR only globals will need to be resolved in the elaborator.

## Documentation\*

Check one:
- [x] No documentation needed.
- [ ] Documentation included in this PR.
- [ ] **[For Experimental Features]** Documentation to be submitted in a
separate PR.

# PR Checklist\*

- [x] I have tested the changes locally.
- [x] I have formatted the changes with [Prettier](https://prettier.io/)
and/or `cargo fmt` on default settings.
  • Loading branch information
jfecher authored May 22, 2024
1 parent 9420d7c commit 904f5eb
Show file tree
Hide file tree
Showing 3 changed files with 307 additions and 9 deletions.
87 changes: 80 additions & 7 deletions compiler/noirc_frontend/src/elaborator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ use crate::{
UnresolvedTraitConstraint, UnresolvedTypeExpression,
},
hir::{
def_collector::{dc_crate::CompilationError, errors::DuplicateType},
def_collector::{
dc_crate::{CompilationError, UnresolvedStruct, UnresolvedTrait, UnresolvedTypeAlias},
errors::DuplicateType,
},
resolution::{errors::ResolverError, path_resolver::PathResolver, resolver::LambdaContext},
scope::ScopeForest as GenericScopeForest,
type_check::TypeCheckError,
Expand All @@ -27,10 +30,10 @@ use crate::{
macros_api::{
BlockExpression, CallExpression, CastExpression, Expression, ExpressionKind, HirExpression,
HirLiteral, HirStatement, Ident, IndexExpression, Literal, MemberAccessExpression,
MethodCallExpression, NodeInterner, NoirFunction, PrefixExpression, Statement,
MethodCallExpression, NodeInterner, NoirFunction, NoirStruct, PrefixExpression, Statement,
StatementKind, StructId,
},
node_interner::{DefinitionKind, DependencyId, ExprId, FuncId, StmtId, TraitId},
node_interner::{DefinitionKind, DependencyId, ExprId, FuncId, StmtId, TraitId, TypeAliasId},
Shared, StructType, Type, TypeVariable,
};
use crate::{
Expand Down Expand Up @@ -68,6 +71,7 @@ mod expressions;
mod patterns;
mod scope;
mod statements;
mod traits;
mod types;

use fm::FileId;
Expand Down Expand Up @@ -206,11 +210,12 @@ impl<'context> Elaborator<'context> {
// the resolver filters literal globals first
for global in items.globals {}

for alias in items.type_aliases {}

for trait_ in items.traits {}
for (alias_id, alias) in items.type_aliases {
this.define_type_alias(alias_id, alias);
}

for struct_ in items.types {}
this.collect_traits(items.traits);
this.collect_struct_definitions(items.types);

for trait_impl in &mut items.trait_impls {
this.collect_trait_impl(trait_impl);
Expand Down Expand Up @@ -1100,4 +1105,72 @@ impl<'context> Elaborator<'context> {
});
}
}

fn define_type_alias(&mut self, alias_id: TypeAliasId, alias: UnresolvedTypeAlias) {
self.file = alias.file_id;
self.local_module = alias.module_id;

let generics = self.add_generics(&alias.type_alias_def.generics);
self.resolve_local_globals();
self.current_item = Some(DependencyId::Alias(alias_id));
let typ = self.resolve_type(alias.type_alias_def.typ);
self.interner.set_type_alias(alias_id, typ, generics);
}

fn collect_struct_definitions(&mut self, structs: BTreeMap<StructId, UnresolvedStruct>) {
// This is necessary to avoid cloning the entire struct map
// when adding checks after each struct field is resolved.
let struct_ids = structs.keys().copied().collect::<Vec<_>>();

// Resolve each field in each struct.
// Each struct should already be present in the NodeInterner after def collection.
for (type_id, typ) in structs {
self.file = typ.file_id;
self.local_module = typ.module_id;
let (generics, fields) = self.resolve_struct_fields(typ.struct_def, type_id);

self.interner.update_struct(type_id, |struct_def| {
struct_def.set_fields(fields);
struct_def.generics = generics;
});
}

// Check whether the struct fields have nested slices
// We need to check after all structs are resolved to
// make sure every struct's fields is accurately set.
for id in struct_ids {
let struct_type = self.interner.get_struct(id);
// Only handle structs without generics as any generics args will be checked
// after monomorphization when performing SSA codegen
if struct_type.borrow().generics.is_empty() {
let fields = struct_type.borrow().get_fields(&[]);
for (_, field_type) in fields.iter() {
if field_type.is_nested_slice() {
let location = struct_type.borrow().location;
self.file = location.file;
self.push_err(ResolverError::NestedSlices { span: location.span });
}
}
}
}
}

pub fn resolve_struct_fields(
&mut self,
unresolved: NoirStruct,
struct_id: StructId,
) -> (Generics, Vec<(Ident, Type)>) {
let generics = self.add_generics(&unresolved.generics);

// Check whether the struct definition has globals in the local module and add them to the scope
self.resolve_local_globals();

self.current_item = Some(DependencyId::Struct(struct_id));

self.resolving_ids.insert(struct_id);
let fields = vecmap(unresolved.fields, |(ident, typ)| (ident, self.resolve_type(typ)));
self.resolving_ids.remove(&struct_id);

(generics, fields)
}
}
196 changes: 196 additions & 0 deletions compiler/noirc_frontend/src/elaborator/traits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
use std::collections::BTreeMap;

use iter_extended::vecmap;
use noirc_errors::Location;

use crate::{
ast::{FunctionKind, TraitItem, UnresolvedGenerics, UnresolvedTraitConstraint},
hir::{
def_collector::dc_crate::UnresolvedTrait, def_map::ModuleId,
resolution::path_resolver::StandardPathResolver,
},
hir_def::{
function::{FuncMeta, HirFunction},
traits::{TraitConstant, TraitFunction, TraitType},
},
macros_api::{
BlockExpression, FunctionDefinition, FunctionReturnType, Ident, ItemVisibility,
NoirFunction, Param, Pattern, UnresolvedType, Visibility,
},
node_interner::{FuncId, TraitId},
token::Attributes,
Generics, Type, TypeVariable, TypeVariableKind,
};

use super::Elaborator;

impl<'context> Elaborator<'context> {
pub fn collect_traits(&mut self, traits: BTreeMap<TraitId, UnresolvedTrait>) {
for (trait_id, unresolved_trait) in &traits {
self.interner.push_empty_trait(*trait_id, unresolved_trait);
}

for (trait_id, unresolved_trait) in traits {
let generics = vecmap(&unresolved_trait.trait_def.generics, |_| {
TypeVariable::unbound(self.interner.next_type_variable_id())
});

// Resolve order
// 1. Trait Types ( Trait constants can have a trait type, therefore types before constants)
let _ = self.resolve_trait_types(&unresolved_trait);
// 2. Trait Constants ( Trait's methods can use trait types & constants, therefore they should be after)
let _ = self.resolve_trait_constants(&unresolved_trait);
// 3. Trait Methods
let methods = self.resolve_trait_methods(trait_id, &unresolved_trait, &generics);

self.interner.update_trait(trait_id, |trait_def| {
trait_def.set_methods(methods);
trait_def.generics = generics;
});

// This check needs to be after the trait's methods are set since
// the interner may set `interner.ordering_type` based on the result type
// of the Cmp trait, if this is it.
if self.crate_id.is_stdlib() {
self.interner.try_add_operator_trait(trait_id);
}
}
}

fn resolve_trait_types(&mut self, _unresolved_trait: &UnresolvedTrait) -> Vec<TraitType> {
// TODO
vec![]
}

fn resolve_trait_constants(
&mut self,
_unresolved_trait: &UnresolvedTrait,
) -> Vec<TraitConstant> {
// TODO
vec![]
}

fn resolve_trait_methods(
&mut self,
trait_id: TraitId,
unresolved_trait: &UnresolvedTrait,
trait_generics: &Generics,
) -> Vec<TraitFunction> {
self.local_module = unresolved_trait.module_id;
self.file = self.def_maps[&self.crate_id].file_id(unresolved_trait.module_id);

let mut functions = vec![];

for item in &unresolved_trait.trait_def.items {
if let TraitItem::Function {
name,
generics,
parameters,
return_type,
where_clause,
body: _,
} = item
{
let old_generic_count = self.generics.len();

let the_trait = self.interner.get_trait(trait_id);
let self_typevar = the_trait.self_type_typevar.clone();
let self_type = Type::TypeVariable(self_typevar.clone(), TypeVariableKind::Normal);
let name_span = the_trait.name.span();

self.add_generics(generics);
self.add_existing_generics(&unresolved_trait.trait_def.generics, trait_generics);
self.add_existing_generic("Self", name_span, self_typevar);
self.self_type = Some(self_type.clone());

let func_id = unresolved_trait.method_ids[&name.0.contents];
self.resolve_trait_function(
name,
generics,
parameters,
return_type,
where_clause,
func_id,
);

let arguments = vecmap(parameters, |param| self.resolve_type(param.1.clone()));
let return_type = self.resolve_type(return_type.get_type().into_owned());

let generics = vecmap(&self.generics, |(_, type_var, _)| type_var.clone());

let default_impl_list: Vec<_> = unresolved_trait
.fns_with_default_impl
.functions
.iter()
.filter(|(_, _, q)| q.name() == name.0.contents)
.collect();

let default_impl = if default_impl_list.len() == 1 {
Some(Box::new(default_impl_list[0].2.clone()))
} else {
None
};

let no_environment = Box::new(Type::Unit);
let function_type =
Type::Function(arguments, Box::new(return_type), no_environment);

functions.push(TraitFunction {
name: name.clone(),
typ: Type::Forall(generics, Box::new(function_type)),
location: Location::new(name.span(), unresolved_trait.file_id),
default_impl,
default_impl_module_id: unresolved_trait.module_id,
});

self.generics.truncate(old_generic_count);
}
}
functions
}

pub fn resolve_trait_function(
&mut self,
name: &Ident,
generics: &UnresolvedGenerics,
parameters: &[(Ident, UnresolvedType)],
return_type: &FunctionReturnType,
where_clause: &[UnresolvedTraitConstraint],
func_id: FuncId,
) {
let old_generic_count = self.generics.len();
self.scopes.start_function();

// Check whether the function has globals in the local module and add them to the scope
self.resolve_local_globals();

self.trait_bounds = where_clause.to_vec();

let kind = FunctionKind::Normal;
let def = FunctionDefinition {
name: name.clone(),
attributes: Attributes::empty(),
is_unconstrained: false,
is_comptime: false,
visibility: ItemVisibility::Public, // Trait functions are always public
generics: generics.clone(),
parameters: vecmap(parameters, |(name, typ)| Param {
visibility: Visibility::Private,
pattern: Pattern::Identifier(name.clone()),
typ: typ.clone(),
span: name.span(),
}),
body: BlockExpression { statements: Vec::new() },
span: name.span(),
where_clause: where_clause.to_vec(),
return_type: return_type.clone(),
return_visibility: Visibility::Private,
};

self.elaborate_function(NoirFunction { kind, def }, func_id);
let _ = self.scopes.end_function();
// Don't check the scope tree for unused variables, they can't be used in a declaration anyway.
self.trait_bounds.clear();
self.generics.truncate(old_generic_count);
}
}
33 changes: 31 additions & 2 deletions compiler/noirc_frontend/src/elaborator/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ use iter_extended::vecmap;
use noirc_errors::{Location, Span};

use crate::{
ast::{BinaryOpKind, IntegerBitSize, UnresolvedTraitConstraint, UnresolvedTypeExpression},
ast::{
BinaryOpKind, IntegerBitSize, NoirTypeAlias, UnresolvedGenerics, UnresolvedTraitConstraint,
UnresolvedTypeExpression,
},
hir::{
def_map::ModuleDefId,
resolution::{
Expand All @@ -26,7 +29,10 @@ use crate::{
HirExpression, HirLiteral, HirStatement, Path, PathKind, SecondaryAttribute, Signedness,
UnaryOp, UnresolvedType, UnresolvedTypeData,
},
node_interner::{DefinitionKind, ExprId, GlobalId, TraitId, TraitImplKind, TraitMethodId},
node_interner::{
DefinitionKind, DependencyId, ExprId, GlobalId, TraitId, TraitImplKind, TraitMethodId,
TypeAliasId,
},
Generics, Shared, StructType, Type, TypeAlias, TypeBinding, TypeVariable, TypeVariableKind,
};

Expand Down Expand Up @@ -1435,4 +1441,27 @@ impl<'context> Elaborator<'context> {
}
}
}

pub fn add_existing_generics(&mut self, names: &UnresolvedGenerics, generics: &Generics) {
assert_eq!(names.len(), generics.len());

for (name, typevar) in names.iter().zip(generics) {
self.add_existing_generic(&name.0.contents, name.0.span(), typevar.clone());
}
}

pub fn add_existing_generic(&mut self, name: &str, span: Span, typevar: TypeVariable) {
// Check for name collisions of this generic
let rc_name = Rc::new(name.to_owned());

if let Some((_, _, first_span)) = self.find_generic(&rc_name) {
self.push_err(ResolverError::DuplicateDefinition {
name: name.to_owned(),
first_span: *first_span,
second_span: span,
});
} else {
self.generics.push((rc_name, typevar, span));
}
}
}

0 comments on commit 904f5eb

Please sign in to comment.