Skip to content

Commit

Permalink
feat: Implement type paths (#6093)
Browse files Browse the repository at this point in the history
# Description

## Problem\*

Resolves #6078

## Summary\*

Adds the ability to do `Type::method` where `Type` is a primitive type
or interned type. Other named types should already be supported by
existing paths.

## Additional Context

I also made a small update to `lookup_method` to support looking up
methods without a `self` parameter to allow for us to possibly have
something like `Field::static_method()` in the future, although we have
no functions like this in the stdlib currently.

## 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 Sep 18, 2024
1 parent 26d275b commit 2174ffb
Show file tree
Hide file tree
Showing 22 changed files with 268 additions and 52 deletions.
1 change: 1 addition & 0 deletions aztec_macros/src/transforms/note_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,7 @@ pub fn inject_note_exports(
note.borrow().id,
"get_note_type_id",
false,
true,
)
.ok_or((
AztecMacroError::CouldNotExportStorageLayout {
Expand Down
1 change: 1 addition & 0 deletions aztec_macros/src/transforms/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ pub fn assign_storage_slots(
storage_struct.borrow().id,
"init",
false,
true,
)
.ok_or((
AztecMacroError::CouldNotAssignStorageSlots {
Expand Down
6 changes: 6 additions & 0 deletions aztec_macros/src/utils/parse_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,12 @@ fn empty_expression(expression: &mut Expression) {
empty_unresolved_type(&mut path.typ);
empty_path(&mut path.trait_path);
empty_ident(&mut path.impl_item);
empty_type_args(&mut path.trait_generics);
}
ExpressionKind::TypePath(path) => {
empty_unresolved_type(&mut path.typ);
empty_ident(&mut path.item);
empty_type_args(&mut path.turbofish);
}
ExpressionKind::Quote(..)
| ExpressionKind::Resolved(_)
Expand Down
14 changes: 13 additions & 1 deletion compiler/noirc_frontend/src/ast/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use acvm::{acir::AcirField, FieldElement};
use iter_extended::vecmap;
use noirc_errors::{Span, Spanned};

use super::{AsTraitPath, UnaryRhsMemberAccess};
use super::{AsTraitPath, TypePath, UnaryRhsMemberAccess};

#[derive(Debug, PartialEq, Eq, Clone)]
pub enum ExpressionKind {
Expand All @@ -38,6 +38,7 @@ pub enum ExpressionKind {
Comptime(BlockExpression, Span),
Unsafe(BlockExpression, Span),
AsTraitPath(AsTraitPath),
TypePath(TypePath),

// This variant is only emitted when inlining the result of comptime
// code. It is used to translate function values back into the AST while
Expand Down Expand Up @@ -621,6 +622,7 @@ impl Display for ExpressionKind {
write!(f, "quote {{ {} }}", tokens.join(" "))
}
AsTraitPath(path) => write!(f, "{path}"),
TypePath(path) => write!(f, "{path}"),
InternedStatement(_) => write!(f, "?InternedStatement"),
}
}
Expand Down Expand Up @@ -787,6 +789,16 @@ impl Display for AsTraitPath {
}
}

impl Display for TypePath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}::{}", self.typ, self.item)?;
if !self.turbofish.is_empty() {
write!(f, "::{}", self.turbofish)?;
}
Ok(())
}
}

impl FunctionDefinition {
pub fn normal(
name: &Ident,
Expand Down
10 changes: 10 additions & 0 deletions compiler/noirc_frontend/src/ast/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,16 @@ pub struct AsTraitPath {
pub impl_item: Ident,
}

/// A special kind of path in the form `Type::ident::<turbofish>`
/// Unlike normal paths, the type here can be a primitive type or
/// interned type.
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub struct TypePath {
pub typ: UnresolvedType,
pub item: Ident,
pub turbofish: GenericTypeArgs,
}

// Note: Path deliberately doesn't implement Recoverable.
// No matter which default value we could give in Recoverable::error,
// it would most likely cause further errors during name resolution
Expand Down
20 changes: 19 additions & 1 deletion compiler/noirc_frontend/src/ast/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use crate::{

use super::{
FunctionReturnType, GenericTypeArgs, IntegerBitSize, ItemVisibility, Pattern, Signedness,
TraitImplItemKind, UnresolvedGenerics, UnresolvedTraitConstraint, UnresolvedType,
TraitImplItemKind, TypePath, UnresolvedGenerics, UnresolvedTraitConstraint, UnresolvedType,
UnresolvedTypeData, UnresolvedTypeExpression,
};

Expand Down Expand Up @@ -340,6 +340,10 @@ pub trait Visitor {
true
}

fn visit_type_path(&mut self, _: &TypePath, _: Span) -> bool {
true
}

fn visit_unresolved_type(&mut self, _: &UnresolvedType) -> bool {
true
}
Expand Down Expand Up @@ -838,6 +842,7 @@ impl Expression {
ExpressionKind::AsTraitPath(as_trait_path) => {
as_trait_path.accept(self.span, visitor);
}
ExpressionKind::TypePath(path) => path.accept(self.span, visitor),
ExpressionKind::Quote(tokens) => visitor.visit_quote(tokens),
ExpressionKind::Resolved(expr_id) => visitor.visit_resolved_expression(*expr_id),
ExpressionKind::Interned(id) => visitor.visit_interned_expression(*id),
Expand Down Expand Up @@ -1208,6 +1213,19 @@ impl AsTraitPath {
}
}

impl TypePath {
pub fn accept(&self, span: Span, visitor: &mut impl Visitor) {
if visitor.visit_type_path(self, span) {
self.accept_children(visitor);
}
}

pub fn accept_children(&self, visitor: &mut impl Visitor) {
self.typ.accept(visitor);
self.turbofish.accept(visitor);
}
}

impl UnresolvedType {
pub fn accept(&self, visitor: &mut impl Visitor) {
if visitor.visit_unresolved_type(self) {
Expand Down
19 changes: 6 additions & 13 deletions compiler/noirc_frontend/src/elaborator/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::{
HirArrayLiteral, HirBinaryOp, HirBlockExpression, HirCallExpression, HirCastExpression,
HirConstructorExpression, HirExpression, HirIfExpression, HirIndexExpression,
HirInfixExpression, HirLambda, HirMemberAccess, HirMethodCallExpression,
HirMethodReference, HirPrefixExpression,
HirPrefixExpression,
},
traits::TraitConstraint,
},
Expand Down Expand Up @@ -76,6 +76,7 @@ impl<'context> Elaborator<'context> {
(HirExpression::Error, Type::Error)
}
ExpressionKind::AsTraitPath(_) => todo!("Implement AsTraitPath"),
ExpressionKind::TypePath(path) => return self.elaborate_type_path(path),
};
let id = self.interner.push_expr(hir_expr);
self.interner.push_expr_location(id, expr.span, self.file);
Expand Down Expand Up @@ -406,21 +407,13 @@ impl<'context> Elaborator<'context> {

let method_name_span = method_call.method_name.span();
let method_name = method_call.method_name.0.contents.as_str();
match self.lookup_method(&object_type, method_name, span) {
match self.lookup_method(&object_type, method_name, span, true) {
Some(method_ref) => {
// Automatically add `&mut` if the method expects a mutable reference and
// the object is not already one.
let func_id = match &method_ref {
HirMethodReference::FuncId(func_id) => *func_id,
HirMethodReference::TraitMethodId(method_id, _) => {
let id = self.interner.trait_method_id(*method_id);
let definition = self.interner.definition(id);
let DefinitionKind::Function(func_id) = definition.kind else {
unreachable!("Expected trait function to be a DefinitionKind::Function")
};
func_id
}
};
let func_id = method_ref
.func_id(self.interner)
.expect("Expected trait function to be a DefinitionKind::Function");

let generics = if func_id != FuncId::dummy_id() {
let function_type = self.interner.function_meta(&func_id).typ.clone();
Expand Down
45 changes: 42 additions & 3 deletions compiler/noirc_frontend/src/elaborator/patterns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ use noirc_errors::{Location, Span};
use rustc_hash::FxHashSet as HashSet;

use crate::{
ast::{UnresolvedType, ERROR_IDENT},
ast::{TypePath, UnresolvedType, ERROR_IDENT},
hir::{
def_collector::dc_crate::CompilationError,
resolution::errors::ResolverError,
type_check::{Source, TypeCheckError},
},
hir_def::{
expr::{HirIdent, ImplKind},
expr::{HirIdent, HirMethodReference, ImplKind},
stmt::HirPattern,
},
macros_api::{HirExpression, Ident, Path, Pattern},
macros_api::{Expression, ExpressionKind, HirExpression, Ident, Path, Pattern},
node_interner::{DefinitionId, DefinitionKind, ExprId, FuncId, GlobalId, TraitImplKind},
ResolvedGeneric, Shared, StructType, Type, TypeBindings,
};
Expand Down Expand Up @@ -697,4 +697,43 @@ impl<'context> Elaborator<'context> {
let id = DefinitionId::dummy_id();
(HirIdent::non_trait_method(id, location), 0)
}

pub(super) fn elaborate_type_path(&mut self, path: TypePath) -> (ExprId, Type) {
let span = path.item.span();
let typ = self.resolve_type(path.typ);

let Some(method) = self.lookup_method(&typ, &path.item.0.contents, span, false) else {
let error = Expression::new(ExpressionKind::Error, span);
return self.elaborate_expression(error);
};

let func_id = method
.func_id(self.interner)
.expect("Expected trait function to be a DefinitionKind::Function");

let generics = self.resolve_type_args(path.turbofish, func_id, span).0;
let generics = (!generics.is_empty()).then_some(generics);

let location = Location::new(span, self.file);
let id = self.interner.function_definition_id(func_id);

let impl_kind = match method {
HirMethodReference::FuncId(_) => ImplKind::NotATraitMethod,
HirMethodReference::TraitMethodId(method_id, generics) => {
let mut constraint =
self.interner.get_trait(method_id.trait_id).as_constraint(span);
constraint.trait_generics = generics;
ImplKind::TraitMethod(method_id, constraint, false)
}
};

let ident = HirIdent { location, id, impl_kind };
let id = self.interner.push_expr(HirExpression::Ident(ident.clone(), generics.clone()));
self.interner.push_expr_location(id, location.span, location.file);

let typ = self.type_check_variable(ident, id, generics);
self.interner.push_expr_type(id, typ.clone());

(id, typ)
}
}
11 changes: 7 additions & 4 deletions compiler/noirc_frontend/src/elaborator/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1306,11 +1306,13 @@ impl<'context> Elaborator<'context> {
object_type: &Type,
method_name: &str,
span: Span,
has_self_arg: bool,
) -> Option<HirMethodReference> {
match object_type.follow_bindings() {
Type::Struct(typ, _args) => {
let id = typ.borrow().id;
match self.interner.lookup_method(object_type, id, method_name, false) {
match self.interner.lookup_method(object_type, id, method_name, false, has_self_arg)
{
Some(method_id) => Some(HirMethodReference::FuncId(method_id)),
None => {
self.push_err(TypeCheckError::UnresolvedMethodCall {
Expand Down Expand Up @@ -1339,9 +1341,9 @@ impl<'context> Elaborator<'context> {
// This may be a struct or a primitive type.
Type::MutableReference(element) => self
.interner
.lookup_primitive_trait_method_mut(element.as_ref(), method_name)
.lookup_primitive_trait_method_mut(element.as_ref(), method_name, has_self_arg)
.map(HirMethodReference::FuncId)
.or_else(|| self.lookup_method(&element, method_name, span)),
.or_else(|| self.lookup_method(&element, method_name, span, has_self_arg)),

// If we fail to resolve the object to a struct type, we have no way of type
// checking its arguments as we can't even resolve the name of the function
Expand All @@ -1353,7 +1355,8 @@ impl<'context> Elaborator<'context> {
None
}

other => match self.interner.lookup_primitive_method(&other, method_name) {
other => match self.interner.lookup_primitive_method(&other, method_name, has_self_arg)
{
Some(method_id) => Some(HirMethodReference::FuncId(method_id)),
None => {
// It could be that this type is a composite type that is bound to a trait,
Expand Down
3 changes: 2 additions & 1 deletion compiler/noirc_frontend/src/hir/comptime/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1352,8 +1352,9 @@ impl<'local, 'interner> Interpreter<'local, 'interner> {
struct_def.borrow().id,
method_name,
false,
true,
),
_ => self.elaborator.interner.lookup_primitive_method(&typ, method_name),
_ => self.elaborator.interner.lookup_primitive_method(&typ, method_name, true),
};

if let Some(method) = method {
Expand Down
12 changes: 11 additions & 1 deletion compiler/noirc_frontend/src/hir/comptime/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -862,7 +862,17 @@ fn remove_interned_in_expression_kind(
vecmap(block.statements, |stmt| remove_interned_in_statement(interner, stmt));
ExpressionKind::Unsafe(BlockExpression { statements }, span)
}
ExpressionKind::AsTraitPath(_) => expr,
ExpressionKind::AsTraitPath(mut path) => {
path.typ = remove_interned_in_unresolved_type(interner, path.typ);
path.trait_generics =
remove_interned_in_generic_type_args(interner, path.trait_generics);
ExpressionKind::AsTraitPath(path)
}
ExpressionKind::TypePath(mut path) => {
path.typ = remove_interned_in_unresolved_type(interner, path.typ);
path.turbofish = remove_interned_in_generic_type_args(interner, path.turbofish);
ExpressionKind::TypePath(path)
}
ExpressionKind::Resolved(id) => {
let expr = interner.expression(&id);
expr.to_display_ast(interner, Span::default()).kind
Expand Down
24 changes: 23 additions & 1 deletion compiler/noirc_frontend/src/hir/type_check/generics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use iter_extended::vecmap;
use crate::{
hir_def::traits::NamedType,
macros_api::NodeInterner,
node_interner::{TraitId, TypeAliasId},
node_interner::{FuncId, TraitId, TypeAliasId},
ResolvedGeneric, StructType, Type,
};

Expand Down Expand Up @@ -97,6 +97,28 @@ impl Generic for Ref<'_, StructType> {
}
}

impl Generic for FuncId {
fn item_kind(&self) -> &'static str {
"function"
}

fn item_name(&self, interner: &NodeInterner) -> String {
interner.function_name(self).to_string()
}

fn generics(&self, interner: &NodeInterner) -> Vec<ResolvedGeneric> {
interner.function_meta(self).direct_generics.clone()
}

fn accepts_named_type_args(&self) -> bool {
false
}

fn named_generics(&self, _interner: &NodeInterner) -> Vec<ResolvedGeneric> {
Vec::new()
}
}

/// TraitGenerics are different from regular generics in that they can
/// also contain associated type arguments.
#[derive(Default, PartialEq, Eq, Clone, Hash, Ord, PartialOrd)]
Expand Down
19 changes: 18 additions & 1 deletion compiler/noirc_frontend/src/hir_def/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ use noirc_errors::Location;

use crate::ast::{BinaryOp, BinaryOpKind, Ident, UnaryOp};
use crate::hir::type_check::generics::TraitGenerics;
use crate::node_interner::{DefinitionId, ExprId, FuncId, NodeInterner, StmtId, TraitMethodId};
use crate::node_interner::{
DefinitionId, DefinitionKind, ExprId, FuncId, NodeInterner, StmtId, TraitMethodId,
};
use crate::token::Tokens;
use crate::Shared;

Expand Down Expand Up @@ -203,6 +205,21 @@ pub enum HirMethodReference {
TraitMethodId(TraitMethodId, TraitGenerics),
}

impl HirMethodReference {
pub fn func_id(&self, interner: &NodeInterner) -> Option<FuncId> {
match self {
HirMethodReference::FuncId(func_id) => Some(*func_id),
HirMethodReference::TraitMethodId(method_id, _) => {
let id = interner.trait_method_id(*method_id);
match &interner.try_definition(id)?.kind {
DefinitionKind::Function(func_id) => Some(*func_id),
_ => None,
}
}
}
}
}

impl HirMethodCallExpression {
/// Converts a method call into a function call
///
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 @@ -1680,7 +1680,7 @@ impl Type {
if let (Type::Array(_size, element1), Type::Slice(element2)) = (&this, &target) {
// We can only do the coercion if the `as_slice` method exists.
// This is usually true, but some tests don't have access to the standard library.
if let Some(as_slice) = interner.lookup_primitive_method(&this, "as_slice") {
if let Some(as_slice) = interner.lookup_primitive_method(&this, "as_slice", true) {
// Still have to ensure the element types match.
// Don't need to issue an error here if not, it will be done in unify_with_coercions
let mut bindings = TypeBindings::new();
Expand Down
Loading

0 comments on commit 2174ffb

Please sign in to comment.