Skip to content

Commit

Permalink
feat: Implement turbofish operator (#3542)
Browse files Browse the repository at this point in the history
# Description

## Problem\*

Resolves #3413 

## Summary\*

Adds the turbofish operator `::<>` to variables and method calls in
Noir.

## Additional Context

I'm publishing this as a draft because I'm shelving this work for now
after implementing the majority of it.

The work that remains to be done is handling the special case of method
calls on generic impls. A generic impl will implicitly add the impl
generics to each method - which means the generic count on a method
itself as seen by the turbofish operator will not match the actual
generics on the function internally. We'll likely need to separate out
these implicit generics internally. Such that `expected_generic_count =
function_generics - impl_generics`. I've added this as a test case to
the `generics` test to ensure it works when this is merged.

## Documentation\*

Check one:
- [ ] No documentation needed.
- [ ] Documentation included in this PR.
- [x] **[Exceptional Case]** Documentation to be submitted in a separate
PR.
- [x] No documentation is in this PR yet since it is still a draft. 

# PR Checklist\*

- [x] I have tested the changes locally.
- [ ] I have formatted the changes with [Prettier](https://prettier.io/)
and/or `cargo fmt` on default settings.

---------

Co-authored-by: Tom French <tom@tomfren.ch>
Co-authored-by: Maxim Vezenov <mvezenov@gmail.com>
  • Loading branch information
3 people committed May 21, 2024
1 parent faf5bd8 commit 226724e
Show file tree
Hide file tree
Showing 35 changed files with 486 additions and 125 deletions.
2 changes: 1 addition & 1 deletion aztec_macros/src/transforms/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -680,7 +680,7 @@ fn add_struct_to_hasher(identifier: &Ident, hasher_name: &str) -> Statement {
fn str_to_bytes(identifier: &Ident) -> (Statement, Ident) {
// let identifier_as_bytes = identifier.as_bytes();
let var = variable_ident(identifier.clone());
let contents = if let ExpressionKind::Variable(p) = &var.kind {
let contents = if let ExpressionKind::Variable(p, _) = &var.kind {
p.segments.first().cloned().unwrap_or_else(|| panic!("No segments")).0.contents
} else {
panic!("Unexpected identifier type")
Expand Down
7 changes: 4 additions & 3 deletions aztec_macros/src/utils/ast_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ pub fn expression(kind: ExpressionKind) -> Expression {
}

pub fn variable(name: &str) -> Expression {
expression(ExpressionKind::Variable(ident_path(name)))
expression(ExpressionKind::Variable(ident_path(name), None))
}

pub fn variable_ident(identifier: Ident) -> Expression {
expression(ExpressionKind::Variable(path(identifier)))
expression(ExpressionKind::Variable(path(identifier), None))
}

pub fn variable_path(path: Path) -> Expression {
expression(ExpressionKind::Variable(path))
expression(ExpressionKind::Variable(path, None))
}

pub fn method_call(
Expand All @@ -47,6 +47,7 @@ pub fn method_call(
object,
method_name: ident(method_name),
arguments,
generics: None,
})))
}

Expand Down
34 changes: 25 additions & 9 deletions compiler/noirc_frontend/src/ast/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use acvm::FieldElement;
use iter_extended::vecmap;
use noirc_errors::{Span, Spanned};

use super::UnaryRhsMemberAccess;

#[derive(Debug, PartialEq, Eq, Clone)]
pub enum ExpressionKind {
Literal(Literal),
Expand All @@ -23,7 +25,9 @@ pub enum ExpressionKind {
Cast(Box<CastExpression>),
Infix(Box<InfixExpression>),
If(Box<IfExpression>),
Variable(Path),
// The optional vec here is the optional list of generics
// provided by the turbofish operator, if used
Variable(Path, Option<Vec<UnresolvedType>>),
Tuple(Vec<Expression>),
Lambda(Box<Lambda>),
Parenthesized(Box<Expression>),
Expand All @@ -39,7 +43,7 @@ pub type UnresolvedGenerics = Vec<Ident>;
impl ExpressionKind {
pub fn into_path(self) -> Option<Path> {
match self {
ExpressionKind::Variable(path) => Some(path),
ExpressionKind::Variable(path, _) => Some(path),
_ => None,
}
}
Expand Down Expand Up @@ -164,16 +168,19 @@ impl Expression {

pub fn member_access_or_method_call(
lhs: Expression,
(rhs, args): (Ident, Option<Vec<Expression>>),
(rhs, args): UnaryRhsMemberAccess,
span: Span,
) -> Expression {
let kind = match args {
None => ExpressionKind::MemberAccess(Box::new(MemberAccessExpression { lhs, rhs })),
Some(arguments) => ExpressionKind::MethodCall(Box::new(MethodCallExpression {
object: lhs,
method_name: rhs,
arguments,
})),
Some((generics, arguments)) => {
ExpressionKind::MethodCall(Box::new(MethodCallExpression {
object: lhs,
method_name: rhs,
generics,
arguments,
}))
}
};
Expression::new(kind, span)
}
Expand Down Expand Up @@ -435,6 +442,8 @@ pub struct CallExpression {
pub struct MethodCallExpression {
pub object: Expression,
pub method_name: Ident,
/// Method calls have an optional list of generics if the turbofish operator was used
pub generics: Option<Vec<UnresolvedType>>,
pub arguments: Vec<Expression>,
}

Expand Down Expand Up @@ -494,7 +503,14 @@ impl Display for ExpressionKind {
Cast(cast) => cast.fmt(f),
Infix(infix) => infix.fmt(f),
If(if_expr) => if_expr.fmt(f),
Variable(path) => path.fmt(f),
Variable(path, generics) => {
if let Some(generics) = generics {
let generics = vecmap(generics, ToString::to_string);
write!(f, "{path}::<{}>", generics.join(", "))
} else {
path.fmt(f)
}
}
Constructor(constructor) => constructor.fmt(f),
MemberAccess(access) => access.fmt(f),
Tuple(elements) => {
Expand Down
6 changes: 5 additions & 1 deletion compiler/noirc_frontend/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ pub struct UnresolvedType {
pub span: Option<Span>,
}

/// Type wrapper for a member access
pub(crate) type UnaryRhsMemberAccess =
(Ident, Option<(Option<Vec<UnresolvedType>>, Vec<Expression>)>);

/// The precursor to TypeExpression, this is the type that the parser allows
/// to be used in the length position of an array type. Only constants, variables,
/// and numeric binary operators are allowed here.
Expand Down Expand Up @@ -310,7 +314,7 @@ impl UnresolvedTypeExpression {
None => Err(expr),
}
}
ExpressionKind::Variable(path) => Ok(UnresolvedTypeExpression::Variable(path)),
ExpressionKind::Variable(path, _) => Ok(UnresolvedTypeExpression::Variable(path)),
ExpressionKind::Prefix(prefix) if prefix.operator == UnaryOp::Minus => {
let lhs = Box::new(UnresolvedTypeExpression::Constant(0, expr.span));
let rhs = Box::new(UnresolvedTypeExpression::from_expr_helper(prefix.rhs)?);
Expand Down
30 changes: 14 additions & 16 deletions compiler/noirc_frontend/src/ast/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,11 +228,10 @@ impl From<Ident> for Expression {
fn from(i: Ident) -> Expression {
Expression {
span: i.0.span(),
kind: ExpressionKind::Variable(Path {
span: i.span(),
segments: vec![i],
kind: PathKind::Plain,
}),
kind: ExpressionKind::Variable(
Path { span: i.span(), segments: vec![i], kind: PathKind::Plain },
None,
),
}
}
}
Expand Down Expand Up @@ -509,7 +508,7 @@ impl Recoverable for Pattern {
impl LValue {
fn as_expression(&self) -> Expression {
let kind = match self {
LValue::Ident(ident) => ExpressionKind::Variable(Path::from_ident(ident.clone())),
LValue::Ident(ident) => ExpressionKind::Variable(Path::from_ident(ident.clone()), None),
LValue::MemberAccess { object, field_name, span: _ } => {
ExpressionKind::MemberAccess(Box::new(MemberAccessExpression {
lhs: object.as_expression(),
Expand Down Expand Up @@ -599,15 +598,15 @@ impl ForRange {

// array.len()
let segments = vec![array_ident];
let array_ident = ExpressionKind::Variable(Path {
segments,
kind: PathKind::Plain,
span: array_span,
});
let array_ident = ExpressionKind::Variable(
Path { segments, kind: PathKind::Plain, span: array_span },
None,
);

let end_range = ExpressionKind::MethodCall(Box::new(MethodCallExpression {
object: Expression::new(array_ident.clone(), array_span),
method_name: Ident::new("len".to_string(), array_span),
generics: None,
arguments: vec![],
}));
let end_range = Expression::new(end_range, array_span);
Expand All @@ -618,11 +617,10 @@ impl ForRange {

// array[i]
let segments = vec![Ident::new(index_name, array_span)];
let index_ident = ExpressionKind::Variable(Path {
segments,
kind: PathKind::Plain,
span: array_span,
});
let index_ident = ExpressionKind::Variable(
Path { segments, kind: PathKind::Plain, span: array_span },
None,
);

let loop_element = ExpressionKind::Index(Box::new(IndexExpression {
collection: Expression::new(array_ident, array_span),
Expand Down
74 changes: 44 additions & 30 deletions compiler/noirc_frontend/src/debug/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,14 @@ impl DebugInstrumenter {
let last_stmt = if has_ret_expr {
ast::Statement {
kind: ast::StatementKind::Expression(ast::Expression {
kind: ast::ExpressionKind::Variable(ast::Path {
segments: vec![ident("__debug_expr", span)],
kind: PathKind::Plain,
span,
}),
kind: ast::ExpressionKind::Variable(
ast::Path {
segments: vec![ident("__debug_expr", span)],
kind: PathKind::Plain,
span,
},
None,
),
span,
}),
span,
Expand Down Expand Up @@ -568,11 +571,14 @@ fn build_assign_var_stmt(var_id: SourceVarId, expr: ast::Expression) -> ast::Sta
let span = expr.span;
let kind = ast::ExpressionKind::Call(Box::new(ast::CallExpression {
func: Box::new(ast::Expression {
kind: ast::ExpressionKind::Variable(ast::Path {
segments: vec![ident("__debug_var_assign", span)],
kind: PathKind::Plain,
span,
}),
kind: ast::ExpressionKind::Variable(
ast::Path {
segments: vec![ident("__debug_var_assign", span)],
kind: PathKind::Plain,
span,
},
None,
),
span,
}),
arguments: vec![uint_expr(var_id.0 as u128, span), expr],
Expand All @@ -583,11 +589,14 @@ fn build_assign_var_stmt(var_id: SourceVarId, expr: ast::Expression) -> ast::Sta
fn build_drop_var_stmt(var_id: SourceVarId, span: Span) -> ast::Statement {
let kind = ast::ExpressionKind::Call(Box::new(ast::CallExpression {
func: Box::new(ast::Expression {
kind: ast::ExpressionKind::Variable(ast::Path {
segments: vec![ident("__debug_var_drop", span)],
kind: PathKind::Plain,
span,
}),
kind: ast::ExpressionKind::Variable(
ast::Path {
segments: vec![ident("__debug_var_drop", span)],
kind: PathKind::Plain,
span,
},
None,
),
span,
}),
arguments: vec![uint_expr(var_id.0 as u128, span)],
Expand All @@ -607,11 +616,14 @@ fn build_assign_member_stmt(
let span = expr.span;
let kind = ast::ExpressionKind::Call(Box::new(ast::CallExpression {
func: Box::new(ast::Expression {
kind: ast::ExpressionKind::Variable(ast::Path {
segments: vec![ident(&format!["__debug_member_assign_{arity}"], span)],
kind: PathKind::Plain,
span,
}),
kind: ast::ExpressionKind::Variable(
ast::Path {
segments: vec![ident(&format!["__debug_member_assign_{arity}"], span)],
kind: PathKind::Plain,
span,
},
None,
),
span,
}),
arguments: [
Expand All @@ -627,11 +639,14 @@ fn build_assign_member_stmt(
fn build_debug_call_stmt(fname: &str, fn_id: DebugFnId, span: Span) -> ast::Statement {
let kind = ast::ExpressionKind::Call(Box::new(ast::CallExpression {
func: Box::new(ast::Expression {
kind: ast::ExpressionKind::Variable(ast::Path {
segments: vec![ident(&format!["__debug_fn_{fname}"], span)],
kind: PathKind::Plain,
span,
}),
kind: ast::ExpressionKind::Variable(
ast::Path {
segments: vec![ident(&format!["__debug_fn_{fname}"], span)],
kind: PathKind::Plain,
span,
},
None,
),
span,
}),
arguments: vec![uint_expr(fn_id.0 as u128, span)],
Expand Down Expand Up @@ -693,11 +708,10 @@ fn ident(s: &str, span: Span) -> ast::Ident {

fn id_expr(id: &ast::Ident) -> ast::Expression {
ast::Expression {
kind: ast::ExpressionKind::Variable(Path {
segments: vec![id.clone()],
kind: PathKind::Plain,
span: id.span(),
}),
kind: ast::ExpressionKind::Variable(
Path { segments: vec![id.clone()], kind: PathKind::Plain, span: id.span() },
None,
),
span: id.span(),
}
}
Expand Down
15 changes: 12 additions & 3 deletions compiler/noirc_frontend/src/elaborator/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,12 @@ impl<'context> Elaborator<'context> {
ExpressionKind::Cast(cast) => self.elaborate_cast(*cast, expr.span),
ExpressionKind::Infix(infix) => return self.elaborate_infix(*infix, expr.span),
ExpressionKind::If(if_) => self.elaborate_if(*if_),
ExpressionKind::Variable(variable) => return self.elaborate_variable(variable),
ExpressionKind::Variable(variable, generics) => {
let generics = generics.map(|option_inner| {
option_inner.into_iter().map(|generic| self.resolve_type(generic)).collect()
});
return self.elaborate_variable(variable, generics);
}
ExpressionKind::Tuple(tuple) => self.elaborate_tuple(tuple),
ExpressionKind::Lambda(lambda) => self.elaborate_lambda(*lambda),
ExpressionKind::Parenthesized(expr) => return self.elaborate_expression(*expr),
Expand Down Expand Up @@ -185,7 +190,7 @@ impl<'context> Elaborator<'context> {
let variable = scope_tree.find(ident_name);
if let Some((old_value, _)) = variable {
old_value.num_times_used += 1;
let ident = HirExpression::Ident(old_value.ident.clone());
let ident = HirExpression::Ident(old_value.ident.clone(), None);
let expr_id = self.interner.push_expr(ident);
self.interner.push_expr_location(expr_id, call_expr_span, self.file);
let ident = old_value.ident.clone();
Expand Down Expand Up @@ -314,7 +319,11 @@ impl<'context> Elaborator<'context> {

let location = Location::new(span, self.file);
let method = method_call.method_name;
let method_call = HirMethodCallExpression { method, object, arguments, location };
let generics = method_call.generics.map(|option_inner| {
option_inner.into_iter().map(|generic| self.resolve_type(generic)).collect()
});
let method_call =
HirMethodCallExpression { method, object, arguments, location, generics };

// Desugar the method call into a normal, resolved function call
// so that the backend doesn't need to worry about methods
Expand Down
8 changes: 6 additions & 2 deletions compiler/noirc_frontend/src/elaborator/patterns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,10 +323,14 @@ impl<'context> Elaborator<'context> {
}
}

pub(super) fn elaborate_variable(&mut self, variable: Path) -> (ExprId, Type) {
pub(super) fn elaborate_variable(
&mut self,
variable: Path,
generics: Option<Vec<Type>>,
) -> (ExprId, Type) {
let span = variable.span;
let expr = self.resolve_variable(variable);
let id = self.interner.push_expr(HirExpression::Ident(expr.clone()));
let id = self.interner.push_expr(HirExpression::Ident(expr.clone(), generics));
self.interner.push_expr_location(id, span, self.file);
let typ = self.type_check_variable(expr, id);
self.interner.push_expr_type(id, typ.clone());
Expand Down
6 changes: 3 additions & 3 deletions compiler/noirc_frontend/src/elaborator/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@ impl<'context> Elaborator<'context> {
HirExpression::Literal(HirLiteral::Integer(int, false)) => {
int.try_into_u128().ok_or(Some(ResolverError::IntegerTooLarge { span }))
}
HirExpression::Ident(ident) => {
HirExpression::Ident(ident, _) => {
let definition = self.interner.definition(ident.id);
match definition.kind {
DefinitionKind::Global(global_id) => {
Expand Down Expand Up @@ -1249,7 +1249,7 @@ impl<'context> Elaborator<'context> {
}

fn check_if_deprecated(&mut self, expr: ExprId) {
if let HirExpression::Ident(HirIdent { location, id, impl_kind: _ }) =
if let HirExpression::Ident(HirIdent { location, id, impl_kind: _ }, _) =
self.interner.expression(&expr)
{
if let Some(DefinitionKind::Function(func_id)) =
Expand All @@ -1268,7 +1268,7 @@ impl<'context> Elaborator<'context> {
}

fn is_unconstrained_call(&self, expr: ExprId) -> bool {
if let HirExpression::Ident(HirIdent { id, .. }) = self.interner.expression(&expr) {
if let HirExpression::Ident(HirIdent { id, .. }, _) = self.interner.expression(&expr) {
if let Some(DefinitionKind::Function(func_id)) =
self.interner.try_definition(id).map(|def| &def.kind)
{
Expand Down
Loading

0 comments on commit 226724e

Please sign in to comment.