diff --git a/aztec_macros/src/transforms/functions.rs b/aztec_macros/src/transforms/functions.rs index 90563c6085c..39d709ef520 100644 --- a/aztec_macros/src/transforms/functions.rs +++ b/aztec_macros/src/transforms/functions.rs @@ -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") diff --git a/aztec_macros/src/utils/ast_utils.rs b/aztec_macros/src/utils/ast_utils.rs index ebb4854f86e..ba51090c2be 100644 --- a/aztec_macros/src/utils/ast_utils.rs +++ b/aztec_macros/src/utils/ast_utils.rs @@ -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( @@ -47,6 +47,7 @@ pub fn method_call( object, method_name: ident(method_name), arguments, + generics: None, }))) } diff --git a/compiler/noirc_frontend/src/ast/expression.rs b/compiler/noirc_frontend/src/ast/expression.rs index 59c04218e2e..0173b17d28f 100644 --- a/compiler/noirc_frontend/src/ast/expression.rs +++ b/compiler/noirc_frontend/src/ast/expression.rs @@ -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), @@ -23,7 +25,9 @@ pub enum ExpressionKind { Cast(Box), Infix(Box), If(Box), - Variable(Path), + // The optional vec here is the optional list of generics + // provided by the turbofish operator, if used + Variable(Path, Option>), Tuple(Vec), Lambda(Box), Parenthesized(Box), @@ -39,7 +43,7 @@ pub type UnresolvedGenerics = Vec; impl ExpressionKind { pub fn into_path(self) -> Option { match self { - ExpressionKind::Variable(path) => Some(path), + ExpressionKind::Variable(path, _) => Some(path), _ => None, } } @@ -164,16 +168,19 @@ impl Expression { pub fn member_access_or_method_call( lhs: Expression, - (rhs, args): (Ident, Option>), + (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) } @@ -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>, pub arguments: Vec, } @@ -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) => { diff --git a/compiler/noirc_frontend/src/ast/mod.rs b/compiler/noirc_frontend/src/ast/mod.rs index 1c5a5c610aa..c3556dac6af 100644 --- a/compiler/noirc_frontend/src/ast/mod.rs +++ b/compiler/noirc_frontend/src/ast/mod.rs @@ -132,6 +132,10 @@ pub struct UnresolvedType { pub span: Option, } +/// Type wrapper for a member access +pub(crate) type UnaryRhsMemberAccess = + (Ident, Option<(Option>, Vec)>); + /// 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. @@ -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)?); diff --git a/compiler/noirc_frontend/src/ast/statement.rs b/compiler/noirc_frontend/src/ast/statement.rs index 94b5841e52c..863615da53d 100644 --- a/compiler/noirc_frontend/src/ast/statement.rs +++ b/compiler/noirc_frontend/src/ast/statement.rs @@ -228,11 +228,10 @@ impl From 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, + ), } } } @@ -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(), @@ -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); @@ -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), diff --git a/compiler/noirc_frontend/src/debug/mod.rs b/compiler/noirc_frontend/src/debug/mod.rs index 3e7d123398b..c222e08e77a 100644 --- a/compiler/noirc_frontend/src/debug/mod.rs +++ b/compiler/noirc_frontend/src/debug/mod.rs @@ -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, @@ -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], @@ -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)], @@ -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: [ @@ -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)], @@ -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(), } } diff --git a/compiler/noirc_frontend/src/elaborator/expressions.rs b/compiler/noirc_frontend/src/elaborator/expressions.rs index ed8ed5305d1..75c95c06d09 100644 --- a/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -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), @@ -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(); @@ -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 diff --git a/compiler/noirc_frontend/src/elaborator/patterns.rs b/compiler/noirc_frontend/src/elaborator/patterns.rs index 195d37878f1..810e1b90743 100644 --- a/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -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>, + ) -> (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()); diff --git a/compiler/noirc_frontend/src/elaborator/types.rs b/compiler/noirc_frontend/src/elaborator/types.rs index 4c8364b6dda..54920d5738b 100644 --- a/compiler/noirc_frontend/src/elaborator/types.rs +++ b/compiler/noirc_frontend/src/elaborator/types.rs @@ -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) => { @@ -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)) = @@ -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) { diff --git a/compiler/noirc_frontend/src/hir/comptime/hir_to_ast.rs b/compiler/noirc_frontend/src/hir/comptime/hir_to_ast.rs index 1ab9c13ea25..e0fdd91adb4 100644 --- a/compiler/noirc_frontend/src/hir/comptime/hir_to_ast.rs +++ b/compiler/noirc_frontend/src/hir/comptime/hir_to_ast.rs @@ -82,9 +82,12 @@ impl ExprId { let span = interner.expr_span(&self); let kind = match expression { - HirExpression::Ident(ident) => { + HirExpression::Ident(ident, generics) => { let path = Path::from_ident(ident.to_ast(interner)); - ExpressionKind::Variable(path) + ExpressionKind::Variable( + path, + generics.map(|option| option.iter().map(|generic| generic.to_ast()).collect()), + ) } HirExpression::Literal(HirLiteral::Array(array)) => { let array = array.into_ast(interner, span); @@ -146,6 +149,9 @@ impl ExprId { object: method_call.object.to_ast(interner), method_name: method_call.method, arguments: vecmap(method_call.arguments, |arg| arg.to_ast(interner)), + generics: method_call + .generics + .map(|option| option.iter().map(|generic| generic.to_ast()).collect()), })) } HirExpression::Cast(cast) => { diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs index 84df3a0a244..5984e454f7a 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs @@ -296,7 +296,7 @@ impl<'a> Interpreter<'a> { /// Evaluate an expression and return the result pub(super) fn evaluate(&mut self, id: ExprId) -> IResult { match self.interner.expression(&id) { - HirExpression::Ident(ident) => self.evaluate_ident(ident, id), + HirExpression::Ident(ident, _) => self.evaluate_ident(ident, id), HirExpression::Literal(literal) => self.evaluate_literal(literal, id), HirExpression::Block(block) => self.evaluate_block(block), HirExpression::Prefix(prefix) => self.evaluate_prefix(prefix, id), diff --git a/compiler/noirc_frontend/src/hir/comptime/scan.rs b/compiler/noirc_frontend/src/hir/comptime/scan.rs index 7101a158ddb..cc6b9aa7e9c 100644 --- a/compiler/noirc_frontend/src/hir/comptime/scan.rs +++ b/compiler/noirc_frontend/src/hir/comptime/scan.rs @@ -65,7 +65,7 @@ impl<'interner> Interpreter<'interner> { fn scan_expression(&mut self, expr: ExprId) -> IResult<()> { match self.interner.expression(&expr) { - HirExpression::Ident(ident) => self.scan_ident(ident, expr), + HirExpression::Ident(ident, _) => self.scan_ident(ident, expr), HirExpression::Literal(literal) => self.scan_literal(literal), HirExpression::Block(block) => self.scan_block(block), HirExpression::Prefix(prefix) => self.scan_expression(prefix.rhs), diff --git a/compiler/noirc_frontend/src/hir/comptime/value.rs b/compiler/noirc_frontend/src/hir/comptime/value.rs index 4e4a260871a..3c8b6e92445 100644 --- a/compiler/noirc_frontend/src/hir/comptime/value.rs +++ b/compiler/noirc_frontend/src/hir/comptime/value.rs @@ -125,7 +125,7 @@ impl Value { Value::Function(id, _typ) => { let id = interner.function_definition_id(id); let impl_kind = ImplKind::NotATraitMethod; - HirExpression::Ident(HirIdent { location, id, impl_kind }) + HirExpression::Ident(HirIdent { location, id, impl_kind }, None) } Value::Closure(_lambda, _env, _typ) => { // TODO: How should a closure's environment be inlined? diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs index e688f192d3d..3d0ffdb0155 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -420,6 +420,7 @@ impl<'a> ModCollector<'a> { // TODO(Maddiaa): Investigate trait implementations with attributes see: https://github.com/noir-lang/noir/issues/2629 attributes: crate::token::Attributes::empty(), is_unconstrained: false, + generic_count: generics.len(), is_comptime: false, }; diff --git a/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/compiler/noirc_frontend/src/hir/resolution/resolver.rs index 7dc307fe716..1f006697359 100644 --- a/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -1475,14 +1475,20 @@ impl<'a> Resolver<'a> { Literal::FmtStr(str) => self.resolve_fmt_str_literal(str, expr.span), Literal::Unit => HirLiteral::Unit, }), - ExpressionKind::Variable(path) => { + ExpressionKind::Variable(path, generics) => { + let generics = + generics.map(|generics| vecmap(generics, |typ| self.resolve_type(typ))); + if let Some((method, constraint, assumed)) = self.resolve_trait_generic_path(&path) { - HirExpression::Ident(HirIdent { - location: Location::new(expr.span, self.file), - id: self.interner.trait_method_id(method), - impl_kind: ImplKind::TraitMethod(method, constraint, assumed), - }) + HirExpression::Ident( + HirIdent { + location: Location::new(expr.span, self.file), + id: self.interner.trait_method_id(method), + impl_kind: ImplKind::TraitMethod(method, constraint, assumed), + }, + generics, + ) } 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 @@ -1519,7 +1525,7 @@ impl<'a> Resolver<'a> { } } - HirExpression::Ident(hir_ident) + HirExpression::Ident(hir_ident, generics) } } ExpressionKind::Prefix(prefix) => { @@ -1557,12 +1563,20 @@ impl<'a> Resolver<'a> { ExpressionKind::MethodCall(call_expr) => { let method = call_expr.method_name; let object = self.resolve_expression(call_expr.object); + + // Cannot verify the generic count here equals the expected count since we don't + // know which definition `method` refers to until it is resolved during type checking. + let generics = call_expr + .generics + .map(|generics| vecmap(generics, |typ| self.resolve_type(typ))); + let arguments = vecmap(call_expr.arguments, |arg| self.resolve_expression(arg)); let location = Location::new(expr.span, self.file); HirExpression::MethodCall(HirMethodCallExpression { - arguments, method, object, + generics, + arguments, location, }) } @@ -2038,7 +2052,7 @@ impl<'a> Resolver<'a> { 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) => { @@ -2092,7 +2106,7 @@ impl<'a> Resolver<'a> { 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); fmt_str_idents.push(expr_id); @@ -2132,7 +2146,7 @@ pub fn verify_mutable_reference(interner: &NodeInterner, rhs: ExprId) -> Result< let span = interner.expr_span(&rhs); Err(ResolverError::MutableReferenceToArrayElement { span }) } - HirExpression::Ident(ident) => { + HirExpression::Ident(ident, _) => { if let Some(definition) = interner.try_definition(ident.id) { if !definition.mutable { return Err(ResolverError::MutableReferenceToImmutableVariable { diff --git a/compiler/noirc_frontend/src/hir/type_check/errors.rs b/compiler/noirc_frontend/src/hir/type_check/errors.rs index b81727a27f5..549d8138f1d 100644 --- a/compiler/noirc_frontend/src/hir/type_check/errors.rs +++ b/compiler/noirc_frontend/src/hir/type_check/errors.rs @@ -115,6 +115,10 @@ pub enum TypeCheckError { NoMatchingImplFound { constraints: Vec<(Type, String)>, span: Span }, #[error("Constraint for `{typ}: {trait_name}` is not needed, another matching impl is already in scope")] UnneededTraitConstraint { trait_name: String, typ: Type, span: Span }, + #[error( + "Expected {expected_count} generic(s) from this function, but {actual_count} were provided" + )] + IncorrectTurbofishGenericCount { expected_count: usize, actual_count: usize, span: Span }, #[error( "Cannot pass a mutable reference from a constrained runtime to an unconstrained runtime" )] @@ -325,6 +329,12 @@ impl<'a> From<&'a TypeCheckError> for Diagnostic { "`{trait_name}::{method_name}` expects {expected_num_parameters} parameter{plural}, but this method has {actual_num_parameters}"); Diagnostic::simple_error(primary_message, "".to_string(), *span) } + TypeCheckError::IncorrectTurbofishGenericCount { expected_count, actual_count, span } => { + let expected_plural = if *expected_count == 1 { "" } else { "s" }; + let actual_plural = if *actual_count == 1 { "was" } else { "were" }; + let msg = format!("Expected {expected_count} generic{expected_plural} from this function, but {actual_count} {actual_plural} provided"); + Diagnostic::simple_error(msg, "".into(), *span) + }, } } } diff --git a/compiler/noirc_frontend/src/hir/type_check/expr.rs b/compiler/noirc_frontend/src/hir/type_check/expr.rs index 48598109829..c4f80591bf8 100644 --- a/compiler/noirc_frontend/src/hir/type_check/expr.rs +++ b/compiler/noirc_frontend/src/hir/type_check/expr.rs @@ -20,7 +20,7 @@ use super::{errors::TypeCheckError, TypeChecker}; impl<'interner> TypeChecker<'interner> { fn check_if_deprecated(&mut self, expr: &ExprId) { - if let HirExpression::Ident(expr::HirIdent { location, id, impl_kind: _ }) = + if let HirExpression::Ident(expr::HirIdent { location, id, impl_kind: _ }, _) = self.interner.expression(expr) { if let Some(DefinitionKind::Function(func_id)) = @@ -39,7 +39,7 @@ impl<'interner> TypeChecker<'interner> { } fn is_unconstrained_call(&self, expr: &ExprId) -> bool { - if let HirExpression::Ident(expr::HirIdent { id, .. }) = self.interner.expression(expr) { + if let HirExpression::Ident(expr::HirIdent { id, .. }, _) = self.interner.expression(expr) { if let Some(DefinitionKind::Function(func_id)) = self.interner.try_definition(id).map(|def| &def.kind) { @@ -103,7 +103,7 @@ impl<'interner> TypeChecker<'interner> { /// function `foo` to refer to. pub(crate) fn check_expression(&mut self, expr_id: &ExprId) -> Type { let typ = match self.interner.expression(expr_id) { - HirExpression::Ident(ident) => self.check_ident(ident, expr_id), + HirExpression::Ident(ident, generics) => self.check_ident(ident, expr_id, generics), HirExpression::Literal(literal) => match literal { HirLiteral::Array(hir_array_literal) => { let (length, elem_type) = self.check_hir_array_literal(hir_array_literal); @@ -349,7 +349,12 @@ impl<'interner> TypeChecker<'interner> { } /// Returns the type of the given identifier - fn check_ident(&mut self, ident: HirIdent, expr_id: &ExprId) -> Type { + fn check_ident( + &mut self, + ident: HirIdent, + expr_id: &ExprId, + generics: Option>, + ) -> Type { let mut bindings = TypeBindings::new(); // Add type bindings from any constraints that were used. @@ -374,10 +379,20 @@ impl<'interner> TypeChecker<'interner> { // variable to handle generic functions. let t = self.interner.id_type_substitute_trait_as_type(ident.id); + let span = self.interner.expr_span(expr_id); + + let definition = self.interner.try_definition(ident.id); + let function_generic_count = definition.map_or(0, |definition| match &definition.kind { + DefinitionKind::Function(function) => { + self.interner.function_modifiers(function).generic_count + } + _ => 0, + }); + // This instantiates a trait's generics as well which need to be set // when the constraint below is later solved for when the function is // finished. How to link the two? - let (typ, bindings) = t.instantiate_with_bindings(bindings, self.interner); + let (typ, bindings) = self.instantiate(t, bindings, generics, function_generic_count, span); // Push any trait constraints required by this definition to the context // to be checked later when the type of this variable is further constrained. @@ -412,6 +427,37 @@ impl<'interner> TypeChecker<'interner> { typ } + fn instantiate( + &mut self, + typ: Type, + bindings: TypeBindings, + turbofish_generics: Option>, + function_generic_count: usize, + span: Span, + ) -> (Type, TypeBindings) { + match turbofish_generics { + Some(turbofish_generics) => { + if turbofish_generics.len() != function_generic_count { + self.errors.push(TypeCheckError::IncorrectTurbofishGenericCount { + expected_count: function_generic_count, + actual_count: turbofish_generics.len(), + span, + }); + typ.instantiate_with_bindings(bindings, self.interner) + } else { + // Fetch the count of any implicit generics on the function, such as + // for a method within a generic impl. + let implicit_generic_count = match &typ { + Type::Forall(generics, _) => generics.len() - function_generic_count, + _ => 0, + }; + typ.instantiate_with(turbofish_generics, self.interner, implicit_generic_count) + } + } + None => typ.instantiate_with_bindings(bindings, self.interner), + } + } + pub fn verify_trait_constraint( &mut self, object_type: &Type, diff --git a/compiler/noirc_frontend/src/hir/type_check/mod.rs b/compiler/noirc_frontend/src/hir/type_check/mod.rs index 2e448858d9e..9335b8297fe 100644 --- a/compiler/noirc_frontend/src/hir/type_check/mod.rs +++ b/compiler/noirc_frontend/src/hir/type_check/mod.rs @@ -89,7 +89,6 @@ pub fn type_check_func(interner: &mut NodeInterner, func_id: FuncId) -> Vec>), Literal(HirLiteral), Block(HirBlockExpression), Prefix(HirPrefixExpression), @@ -181,6 +183,8 @@ pub struct HirCallExpression { pub struct HirMethodCallExpression { pub method: Ident, pub object: ExprId, + /// Method calls have an optional list of generics provided by the turbofish operator + pub generics: Option>, pub arguments: Vec, pub location: Location, } @@ -227,7 +231,7 @@ impl HirMethodCallExpression { } }; let func_var = HirIdent { location, id, impl_kind }; - let func = interner.push_expr(HirExpression::Ident(func_var.clone())); + let func = interner.push_expr(HirExpression::Ident(func_var.clone(), self.generics)); interner.push_expr_location(func, location.span, location.file); let expr = HirCallExpression { func, arguments, location }; ((func, func_var), expr) diff --git a/compiler/noirc_frontend/src/hir_def/types.rs b/compiler/noirc_frontend/src/hir_def/types.rs index f31aeea0552..cf9aafbb308 100644 --- a/compiler/noirc_frontend/src/hir_def/types.rs +++ b/compiler/noirc_frontend/src/hir_def/types.rs @@ -1478,6 +1478,43 @@ impl Type { } } + /// Instantiates a type with the given types. + /// This differs from substitute in that only the quantified type variables + /// are matched against the type list and are eligible for substitution - similar + /// to normal instantiation. This function is used when the turbofish operator + /// is used and generic substitutions are provided manually by users. + /// + /// Expects the given type vector to be the same length as the Forall type variables. + pub fn instantiate_with( + &self, + types: Vec, + interner: &NodeInterner, + implicit_generic_count: usize, + ) -> (Type, TypeBindings) { + match self { + Type::Forall(typevars, typ) => { + assert_eq!(types.len() + implicit_generic_count, typevars.len(), "Turbofish operator used with incorrect generic count which was not caught by name resolution"); + + let replacements = typevars + .iter() + .enumerate() + .map(|(i, var)| { + let binding = if i < implicit_generic_count { + interner.next_type_variable() + } else { + types[i - implicit_generic_count].clone() + }; + (var.id(), (var.clone(), binding)) + }) + .collect(); + + let instantiated = typ.substitute(&replacements); + (instantiated, replacements) + } + other => (other.clone(), HashMap::new()), + } + } + /// Substitute any type variables found within this type with the /// given bindings if found. If a type variable is not found within /// the given TypeBindings, it is unchanged. @@ -1727,7 +1764,7 @@ fn convert_array_expression_to_slice( let as_slice_id = interner.function_definition_id(as_slice_method); let location = interner.expr_location(&expression); - let as_slice = HirExpression::Ident(HirIdent::non_trait_method(as_slice_id, location)); + let as_slice = HirExpression::Ident(HirIdent::non_trait_method(as_slice_id, location), None); let func = interner.push_expr(as_slice); // Copy the expression and give it a new ExprId. The old one diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index f918610af2c..9a20d0dd537 100644 --- a/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -393,7 +393,7 @@ impl<'interner> Monomorphizer<'interner> { use ast::Literal::*; let expr = match self.interner.expression(&expr) { - HirExpression::Ident(ident) => self.ident(ident, expr)?, + HirExpression::Ident(ident, _) => self.ident(ident, expr)?, HirExpression::Literal(HirLiteral::Str(contents)) => Literal(Str(contents)), HirExpression::Literal(HirLiteral::FmtStr(contents, idents)) => { let fields = try_vecmap(idents, |ident| self.expr(ident))?; @@ -1172,7 +1172,7 @@ impl<'interner> Monomorphizer<'interner> { arguments: &mut Vec, ) { match hir_argument { - HirExpression::Ident(ident) => { + HirExpression::Ident(ident, _) => { let typ = self.interner.definition_type(ident.id); let typ: Type = typ.follow_bindings(); let is_fmt_str = match typ { diff --git a/compiler/noirc_frontend/src/node_interner.rs b/compiler/noirc_frontend/src/node_interner.rs index faf89016f96..7f1b67abfbd 100644 --- a/compiler/noirc_frontend/src/node_interner.rs +++ b/compiler/noirc_frontend/src/node_interner.rs @@ -244,6 +244,8 @@ pub struct FunctionModifiers { pub is_unconstrained: bool, + pub generic_count: usize, + pub is_comptime: bool, } @@ -257,6 +259,7 @@ impl FunctionModifiers { visibility: ItemVisibility::Public, attributes: Attributes::empty(), is_unconstrained: false, + generic_count: 0, is_comptime: false, } } @@ -775,6 +778,7 @@ impl NodeInterner { visibility: function.visibility, attributes: function.attributes.clone(), is_unconstrained: function.is_unconstrained, + generic_count: function.generics.len(), is_comptime: function.is_comptime, }; self.push_function_definition(id, modifiers, module, location) diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index b527284d1a9..890ab795e00 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -35,7 +35,7 @@ use super::{spanned, Item, ItemKind}; use crate::ast::{ BinaryOp, BinaryOpKind, BlockExpression, ForLoopStatement, ForRange, Ident, IfExpression, InfixExpression, LValue, Literal, ModuleDeclaration, NoirTypeAlias, Param, Path, Pattern, - Recoverable, Statement, TraitBound, TypeImpl, UnresolvedTraitConstraint, + Recoverable, Statement, TraitBound, TypeImpl, UnaryRhsMemberAccess, UnresolvedTraitConstraint, UnresolvedTypeExpression, UseTree, UseTreeKind, Visibility, }; use crate::ast::{ @@ -676,9 +676,9 @@ fn parse_type<'a>() -> impl NoirParser + 'a { recursive(parse_type_inner) } -fn parse_type_inner( - recursive_type_parser: impl NoirParser, -) -> impl NoirParser { +fn parse_type_inner<'a>( + recursive_type_parser: impl NoirParser + 'a, +) -> impl NoirParser + 'a { choice(( field_type(), int_type(), @@ -751,9 +751,9 @@ fn string_type() -> impl NoirParser { .map_with_span(|expr, span| UnresolvedTypeData::String(expr).with_span(span)) } -fn format_string_type( - type_parser: impl NoirParser, -) -> impl NoirParser { +fn format_string_type<'a>( + type_parser: impl NoirParser + 'a, +) -> impl NoirParser + 'a { keyword(Keyword::FormatString) .ignore_then( type_expression() @@ -783,22 +783,27 @@ fn int_type() -> impl NoirParser { }) } -fn named_type(type_parser: impl NoirParser) -> impl NoirParser { +fn named_type<'a>( + type_parser: impl NoirParser + 'a, +) -> impl NoirParser + 'a { path().then(generic_type_args(type_parser)).map_with_span(|(path, args), span| { UnresolvedTypeData::Named(path, args, false).with_span(span) }) } -fn named_trait(type_parser: impl NoirParser) -> impl NoirParser { +fn named_trait<'a>( + type_parser: impl NoirParser + 'a, +) -> impl NoirParser + 'a { keyword(Keyword::Impl).ignore_then(path()).then(generic_type_args(type_parser)).map_with_span( |(path, args), span| UnresolvedTypeData::TraitAsType(path, args).with_span(span), ) } -fn generic_type_args( - type_parser: impl NoirParser, -) -> impl NoirParser> { +fn generic_type_args<'a>( + type_parser: impl NoirParser + 'a, +) -> impl NoirParser> + 'a { type_parser + .clone() // Without checking for a terminating ',' or '>' here we may incorrectly // parse a generic `N * 2` as just the type `N` then fail when there is no // separator afterward. Failing early here ensures we try the `type_expression` @@ -814,7 +819,9 @@ fn generic_type_args( .map(Option::unwrap_or_default) } -fn array_type(type_parser: impl NoirParser) -> impl NoirParser { +fn array_type<'a>( + type_parser: impl NoirParser + 'a, +) -> impl NoirParser + 'a { just(Token::LeftBracket) .ignore_then(type_parser) .then(just(Token::Semicolon).ignore_then(type_expression())) @@ -1037,6 +1044,7 @@ where expr_no_constructors, statement, allow_constructors, + parse_type(), )) }) } @@ -1057,6 +1065,7 @@ fn atom_or_right_unary<'a, P, P2, S>( expr_no_constructors: P2, statement: S, allow_constructors: bool, + type_parser: impl NoirParser + 'a, ) -> impl NoirParser + 'a where P: ExprParser + 'a, @@ -1067,7 +1076,7 @@ where Call(Vec), ArrayIndex(Expression), Cast(UnresolvedType), - MemberAccess((Ident, Option>)), + MemberAccess(UnaryRhsMemberAccess), } // `(arg1, ..., argN)` in `my_func(arg1, ..., argN)` @@ -1081,14 +1090,17 @@ where // `as Type` in `atom as Type` let cast_rhs = keyword(Keyword::As) - .ignore_then(parse_type()) + .ignore_then(type_parser.clone()) .map(UnaryRhs::Cast) .labelled(ParsingRuleLabel::Cast); + // A turbofish operator is optional in a method call to specify generic types + let turbofish = primitives::turbofish(type_parser); + // `.foo` or `.foo(args)` in `atom.foo` or `atom.foo(args)` let member_rhs = just(Token::Dot) .ignore_then(field_name()) - .then(parenthesized(expression_list(expr_parser.clone())).or_not()) + .then(turbofish.then(parenthesized(expression_list(expr_parser.clone()))).or_not()) .map(UnaryRhs::MemberAccess) .labelled(ParsingRuleLabel::FieldAccess); @@ -1277,7 +1289,7 @@ fn type_expression_atom<'a, P>(expr_parser: P) -> impl NoirParser + where P: ExprParser + 'a, { - variable() + primitives::variable_no_turbofish() .or(literal()) .map_with_span(Expression::new) .or(parenthesized(expr_parser)) @@ -1367,22 +1379,19 @@ mod test { #[test] fn parse_cast() { + let expression_nc = expression_no_constructors(expression()); parse_all( atom_or_right_unary( expression(), expression_no_constructors(expression()), fresh_statement(), true, + parse_type(), ), vec!["x as u8", "x as u16", "0 as Field", "(x + 3) as [Field; 8]"], ); parse_all_failing( - atom_or_right_unary( - expression(), - expression_no_constructors(expression()), - fresh_statement(), - true, - ), + atom_or_right_unary(expression(), expression_nc, fresh_statement(), true, parse_type()), vec!["x as pub u8"], ); } @@ -1402,6 +1411,7 @@ mod test { expression_no_constructors(expression()), fresh_statement(), true, + parse_type(), ), valid, ); diff --git a/compiler/noirc_frontend/src/parser/parser/primitives.rs b/compiler/noirc_frontend/src/parser/parser/primitives.rs index 8413f14ae4d..9da19c0a185 100644 --- a/compiler/noirc_frontend/src/parser/parser/primitives.rs +++ b/compiler/noirc_frontend/src/parser/parser/primitives.rs @@ -1,6 +1,7 @@ use chumsky::prelude::*; use crate::ast::{ExpressionKind, Ident, UnaryOp}; +use crate::macros_api::UnresolvedType; use crate::{ parser::{labels::ParsingRuleLabel, ExprParser, NoirParser, ParserError}, token::{Keyword, Token, TokenKind}, @@ -77,8 +78,20 @@ where .map(|rhs| ExpressionKind::prefix(UnaryOp::Dereference { implicitly_added: false }, rhs)) } +pub(super) fn turbofish<'a>( + type_parser: impl NoirParser + 'a, +) -> impl NoirParser>> + 'a { + just(Token::DoubleColon).ignore_then(super::generic_type_args(type_parser)).or_not() +} + pub(super) fn variable() -> impl NoirParser { - path().map(ExpressionKind::Variable) + path() + .then(turbofish(super::parse_type())) + .map(|(path, generics)| ExpressionKind::Variable(path, generics)) +} + +pub(super) fn variable_no_turbofish() -> impl NoirParser { + path().map(|path| ExpressionKind::Variable(path, None)) } #[cfg(test)] diff --git a/compiler/noirc_frontend/src/resolve_locations.rs b/compiler/noirc_frontend/src/resolve_locations.rs index ac8c96a092e..2fa7c8adbf8 100644 --- a/compiler/noirc_frontend/src/resolve_locations.rs +++ b/compiler/noirc_frontend/src/resolve_locations.rs @@ -97,7 +97,7 @@ impl NodeInterner { return_type_location_instead: bool, ) -> Option { match expression { - HirExpression::Ident(ident) => { + HirExpression::Ident(ident, _) => { let definition_info = self.definition(ident.id); match definition_info.kind { DefinitionKind::Function(func_id) => { diff --git a/compiler/noirc_frontend/src/tests.rs b/compiler/noirc_frontend/src/tests.rs index fb80a7d8018..7bf5655486b 100644 --- a/compiler/noirc_frontend/src/tests.rs +++ b/compiler/noirc_frontend/src/tests.rs @@ -1378,3 +1378,68 @@ fn deny_fold_attribute_on_unconstrained() { CompilationError::ResolverError(ResolverError::FoldAttributeOnUnconstrained { .. }) )); } + +#[test] +fn specify_function_types_with_turbofish() { + let src = r#" + trait Default { + fn default() -> Self; + } + + impl Default for Field { + fn default() -> Self { 0 } + } + + impl Default for u64 { + fn default() -> Self { 0 } + } + + // Need the above as we don't have access to the stdlib here. + // We also need to construct a concrete value of `U` without giving away its type + // as otherwise the unspecified type is ignored. + + fn generic_func() -> (T, U) where T: Default, U: Default { + (T::default(), U::default()) + } + + fn main() { + let _ = generic_func::(); + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 0); +} + +#[test] +fn specify_method_types_with_turbofish() { + let src = r#" + trait Default { + fn default() -> Self; + } + + impl Default for Field { + fn default() -> Self { 0 } + } + + // Need the above as we don't have access to the stdlib here. + // We also need to construct a concrete value of `U` without giving away its type + // as otherwise the unspecified type is ignored. + + struct Foo { + inner: T + } + + impl Foo { + fn generic_method(_self: Self) where U: Default { + U::default() + } + } + + fn main() { + let foo: Foo = Foo { inner: 1 }; + foo.generic_method::(); + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 0); +} diff --git a/cspell.json b/cspell.json index 1fbbe5c428d..eaf3fcd1b00 100644 --- a/cspell.json +++ b/cspell.json @@ -180,6 +180,7 @@ "termcolor", "thiserror", "tslog", + "turbofish", "typecheck", "typechecked", "typevar", diff --git a/test_programs/compile_failure/turbofish_generic_count/Nargo.toml b/test_programs/compile_failure/turbofish_generic_count/Nargo.toml new file mode 100644 index 00000000000..92be7dcb749 --- /dev/null +++ b/test_programs/compile_failure/turbofish_generic_count/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "turbofish_generic_count" +type = "bin" +authors = [""] +compiler_version = ">=0.29.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/turbofish_generic_count/src/main.nr b/test_programs/compile_failure/turbofish_generic_count/src/main.nr new file mode 100644 index 00000000000..a360641fa15 --- /dev/null +++ b/test_programs/compile_failure/turbofish_generic_count/src/main.nr @@ -0,0 +1,22 @@ + +struct Bar { + one: Field, + two: Field, + other: T, +} + +impl Bar { + fn zeroed(_self: Self) -> A { + dep::std::unsafe::zeroed() + } +} + +fn foo(bar: Bar) { + assert(bar.one == bar.two); +} + +fn main(x: Field, y: pub Field) { + let bar1: Bar = Bar { one: x, two: y, other: 0 }; + + assert(bar1.zeroed::() == 0); +} diff --git a/test_programs/execution_success/generics/src/main.nr b/test_programs/execution_success/generics/src/main.nr index 3edce1ed8e7..c8616960559 100644 --- a/test_programs/execution_success/generics/src/main.nr +++ b/test_programs/execution_success/generics/src/main.nr @@ -31,6 +31,13 @@ impl Bar { } } +impl Bar { + // This is to test that we can use turbofish on methods as well + fn zeroed(_self: Self) -> A { + dep::std::unsafe::zeroed() + } +} + fn main(x: Field, y: Field) { let bar1: Bar = Bar { one: x, two: y, other: 0 }; let bar2 = Bar { one: x, two: y, other: [0] }; @@ -51,6 +58,14 @@ fn main(x: Field, y: Field) { let nested_generics: Bar> = Bar { one, two, other: Bar { one, two, other: 0 } }; assert(nested_generics.other.other == bar1.get_other()); + // Test turbofish operator + foo::(bar1); + + // Test that turbofish works on methods and that it uses the generics on the methods + // While still handling the generic on the impl (T in this case) that is implicitly added + // to the method. + assert(bar1.zeroed::() == 0); + let _ = regression_2055([1, 2, 3]); } diff --git a/tooling/nargo_fmt/src/rewrite/expr.rs b/tooling/nargo_fmt/src/rewrite/expr.rs index 6b7dca6c5c7..e5b30f99b7b 100644 --- a/tooling/nargo_fmt/src/rewrite/expr.rs +++ b/tooling/nargo_fmt/src/rewrite/expr.rs @@ -1,8 +1,9 @@ use noirc_frontend::ast::{ - ArrayLiteral, BlockExpression, Expression, ExpressionKind, Literal, UnaryOp, + ArrayLiteral, BlockExpression, Expression, ExpressionKind, Literal, UnaryOp, UnresolvedType, }; use noirc_frontend::{macros_api::Span, token::Token}; +use crate::rewrite; use crate::visitor::{ expr::{format_brackets, format_parens, NewlineMode}, ExpressionType, FmtVisitor, Indent, Shape, @@ -72,6 +73,7 @@ pub(crate) fn rewrite( let object = rewrite_sub_expr(visitor, shape, method_call_expr.object); let method = method_call_expr.method_name.to_string(); + let turbofish = rewrite_turbofish(visitor, shape, method_call_expr.generics); let args = format_parens( visitor.config.fn_call_width.into(), visitor.fork(), @@ -83,7 +85,7 @@ pub(crate) fn rewrite( NewlineMode::IfContainsNewLineAndWidth, ); - format!("{object}.{method}{args}") + format!("{object}.{method}{turbofish}{args}") } ExpressionKind::MemberAccess(member_access_expr) => { let lhs_str = rewrite_sub_expr(visitor, shape, member_access_expr.lhs); @@ -157,7 +159,13 @@ pub(crate) fn rewrite( visitor.format_if(*if_expr) } - ExpressionKind::Lambda(_) | ExpressionKind::Variable(_) => visitor.slice(span).to_string(), + ExpressionKind::Variable(path, generics) => { + let path_string = visitor.slice(path.span); + + let turbofish = rewrite_turbofish(visitor, shape, generics); + format!("{path_string}{turbofish}") + } + ExpressionKind::Lambda(_) => visitor.slice(span).to_string(), ExpressionKind::Quote(block) => format!("quote {}", rewrite_block(visitor, block, span)), ExpressionKind::Comptime(block) => { format!("comptime {}", rewrite_block(visitor, block, span)) @@ -171,3 +179,24 @@ fn rewrite_block(visitor: &FmtVisitor, block: BlockExpression, span: Span) -> St visitor.visit_block(block, span); visitor.finish() } + +fn rewrite_turbofish( + visitor: &FmtVisitor, + shape: Shape, + generics: Option>, +) -> String { + if let Some(generics) = generics { + let mut turbofish = "".to_owned(); + for (i, generic) in generics.into_iter().enumerate() { + let generic = rewrite::typ(visitor, shape, generic); + turbofish = if i == 0 { + format!("::<{}", generic) + } else { + format!("{turbofish}, {}", generic) + }; + } + format!("{turbofish}>") + } else { + "".to_owned() + } +} diff --git a/tooling/nargo_fmt/tests/expected/turbofish_call.nr b/tooling/nargo_fmt/tests/expected/turbofish_call.nr new file mode 100644 index 00000000000..bcf0df9a969 --- /dev/null +++ b/tooling/nargo_fmt/tests/expected/turbofish_call.nr @@ -0,0 +1,8 @@ +fn foo() { + my_function::(10, some_value, another_func(20, 30)); + + outer_function::( + some_function(), // Original inner function call + another_function() // Original inner function call + ); +} diff --git a/tooling/nargo_fmt/tests/expected/turbofish_method_call.nr b/tooling/nargo_fmt/tests/expected/turbofish_method_call.nr new file mode 100644 index 00000000000..52fa3db2ac9 --- /dev/null +++ b/tooling/nargo_fmt/tests/expected/turbofish_method_call.nr @@ -0,0 +1,12 @@ +fn foo() { + my_object.some_method::(10, var_value, inner_method::(20, 30)); + + assert( + p4_affine.eq::( + Gaffine::new::( + 6890855772600357754907169075114257697580319025794532037257385534741338397365, + 4338620300185947561074059802482547481416142213883829469920100239455078257889 + ) + ) + ); +} diff --git a/tooling/nargo_fmt/tests/input/turbofish_call.nr b/tooling/nargo_fmt/tests/input/turbofish_call.nr new file mode 100644 index 00000000000..03abde789fe --- /dev/null +++ b/tooling/nargo_fmt/tests/input/turbofish_call.nr @@ -0,0 +1,7 @@ +fn foo() { + my_function :: ( 10,some_value,another_func( 20 , 30) ); + + outer_function :: (some_function(), // Original inner function call + another_function(), // Original inner function call + ); +} \ No newline at end of file diff --git a/tooling/nargo_fmt/tests/input/turbofish_method_call.nr b/tooling/nargo_fmt/tests/input/turbofish_method_call.nr new file mode 100644 index 00000000000..aa7ae87f23a --- /dev/null +++ b/tooling/nargo_fmt/tests/input/turbofish_method_call.nr @@ -0,0 +1,5 @@ +fn foo() { + my_object . some_method :: ( 10,var_value,inner_method:: ( 20 , 30) ); + + assert(p4_affine.eq::(Gaffine::new::(6890855772600357754907169075114257697580319025794532037257385534741338397365, 4338620300185947561074059802482547481416142213883829469920100239455078257889))); +} \ No newline at end of file