From 0bb8372e118036a34709da37c26d11a539a86bb3 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Thu, 25 Jul 2024 16:31:39 -0300 Subject: [PATCH] feat: turbofish operator on path segments (#5603) # Description ## Problem Part of #5584 ## Summary This PR adds support for parsing turbofishes in any path segments. Turbofish is only still checked when elaborating variables, but only for the last turbofish (like before). For now any other placement of a turbofish will give a type error, but at least they can be parsed now. In later PRs we can make them work in the middle of variables, in constructors, etc. ## Additional Context Right now turbofishes aren't parsed in named typed (for example in a struct pattern). The reason is that if I use `path()` instead of `path_no_turbofish()` then the parser gives a stack overflow and I'm not sure why. Maybe it's because `parse_type()` is used recursively, I'm not sure... but these stack overflow errors are kind of hard to diagnose. In any case we can parse those later on once we decide to support turbofish in struct patterns. Also, because a `Path`'s segments changed from `Vec` to `Vec` some functions, like `Path::last_segment()` made more sense if they returned `PathSegment` instead of `Ident`. Then I introduced a bunch of helper functions to reduce some call chains, but also introduced helpers like `last_name()` which is nice because it returns a `&str` where in some cases an unnecessary clone was made (so some refactors/cleanups are included in this PR as part of this feature). ## Documentation\* Check one: - [ ] No documentation needed. - [ ] Documentation included in this PR. - [x] **[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. --- aztec_macros/src/transforms/functions.rs | 7 +- aztec_macros/src/transforms/note_interface.rs | 15 ++- aztec_macros/src/transforms/storage.rs | 12 +-- aztec_macros/src/utils/ast_utils.rs | 18 ++-- compiler/noirc_frontend/src/ast/expression.rs | 15 +-- compiler/noirc_frontend/src/ast/mod.rs | 2 +- compiler/noirc_frontend/src/ast/statement.rs | 97 ++++++++++++++----- compiler/noirc_frontend/src/debug/mod.rs | 76 +++++++-------- .../src/elaborator/expressions.rs | 9 +- compiler/noirc_frontend/src/elaborator/mod.rs | 4 +- .../noirc_frontend/src/elaborator/patterns.rs | 17 ++-- .../noirc_frontend/src/elaborator/scope.rs | 8 +- .../noirc_frontend/src/elaborator/types.rs | 34 ++++--- .../noirc_frontend/src/elaborator/unquote.rs | 2 +- .../src/hir/comptime/hir_to_display_ast.rs | 20 ++-- .../src/hir/def_collector/dc_crate.rs | 19 ++-- .../src/hir/resolution/import.rs | 18 ++-- .../src/hir/type_check/errors.rs | 6 ++ .../noirc_frontend/src/noir_parser.lalrpop | 18 +++- compiler/noirc_frontend/src/parser/parser.rs | 6 +- .../noirc_frontend/src/parser/parser/path.rs | 23 +++-- .../src/parser/parser/primitives.rs | 23 +++-- .../src/parser/parser/traits.rs | 11 +-- .../noirc_frontend/src/parser/parser/types.rs | 24 +++-- compiler/noirc_frontend/src/tests.rs | 46 +++++++++ tooling/lsp/src/requests/document_symbol.rs | 2 +- tooling/lsp/src/requests/inlay_hint.rs | 2 +- tooling/nargo_fmt/src/rewrite/expr.rs | 29 ++++-- 28 files changed, 362 insertions(+), 201 deletions(-) diff --git a/aztec_macros/src/transforms/functions.rs b/aztec_macros/src/transforms/functions.rs index 4d8b6ef7cdf..1f6bc2dfc62 100644 --- a/aztec_macros/src/transforms/functions.rs +++ b/aztec_macros/src/transforms/functions.rs @@ -17,7 +17,8 @@ use crate::{ ast_utils::{ assignment, assignment_with_type, call, cast, expression, ident, ident_path, index_array, make_eq, make_statement, make_type, method_call, mutable_assignment, - mutable_reference, path, return_type, variable, variable_ident, variable_path, + mutable_reference, path, path_segment, return_type, variable, variable_ident, + variable_path, }, errors::AztecMacroError, }, @@ -722,8 +723,8 @@ 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 { - p.segments.first().cloned().unwrap_or_else(|| panic!("No segments")).0.contents + let contents = if let ExpressionKind::Variable(p) = &var.kind { + p.first_name() } else { panic!("Unexpected identifier type") }; diff --git a/aztec_macros/src/transforms/note_interface.rs b/aztec_macros/src/transforms/note_interface.rs index 1a25950e6c8..d3af76f09d9 100644 --- a/aztec_macros/src/transforms/note_interface.rs +++ b/aztec_macros/src/transforms/note_interface.rs @@ -21,6 +21,7 @@ use crate::{ utils::{ ast_utils::{ check_trait_method_implemented, ident, ident_path, is_custom_attribute, make_type, + path_segment, }, errors::AztecMacroError, hir_utils::{fetch_notes, get_contract_module_data, inject_global}, @@ -45,8 +46,8 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt .iter_mut() .find(|trait_impl| { if let UnresolvedTypeData::Named(struct_path, _, _) = &trait_impl.object_type.typ { - struct_path.last_segment() == note_struct.name - && trait_impl.trait_name.last_segment().0.contents == "NoteInterface" + struct_path.last_ident() == note_struct.name + && trait_impl.trait_name.last_name() == "NoteInterface" } else { false } @@ -61,7 +62,7 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt let note_interface_impl_span: Option = trait_impl.object_type.span; // Look for the note struct implementation, generate a default one if it doesn't exist (in order to append methods to it) let existing_impl = module.impls.iter_mut().find(|r#impl| match &r#impl.object_type.typ { - UnresolvedTypeData::Named(path, _, _) => path.last_segment().eq(¬e_struct.name), + UnresolvedTypeData::Named(path, _, _) => path.last_ident().eq(¬e_struct.name), _ => false, }); let note_impl = if let Some(note_impl) = existing_impl { @@ -85,9 +86,7 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt .trait_generics .iter() .map(|gen| match gen.typ.clone() { - UnresolvedTypeData::Named(path, _, _) => { - Ok(path.last_segment().0.contents.to_string()) - } + UnresolvedTypeData::Named(path, _, _) => Ok(path.last_name().to_string()), UnresolvedTypeData::Expression(UnresolvedTypeExpression::Constant(val, _)) => { Ok(val.to_string()) } @@ -106,9 +105,7 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt // Automatically inject the header field if it's not present let (header_field_name, _) = if let Some(existing_header) = note_struct.fields.iter().find(|(_, field_type)| match &field_type.typ { - UnresolvedTypeData::Named(path, _, _) => { - path.last_segment().0.contents == "NoteHeader" - } + UnresolvedTypeData::Named(path, _, _) => path.last_name() == "NoteHeader", _ => false, }) { existing_header.clone() diff --git a/aztec_macros/src/transforms/storage.rs b/aztec_macros/src/transforms/storage.rs index 1c6ef634070..073152a297b 100644 --- a/aztec_macros/src/transforms/storage.rs +++ b/aztec_macros/src/transforms/storage.rs @@ -21,7 +21,7 @@ use crate::{ utils::{ ast_utils::{ call, expression, ident, ident_path, is_custom_attribute, lambda, make_statement, - make_type, pattern, return_type, variable, variable_path, + make_type, path_segment, pattern, return_type, variable, variable_path, }, errors::AztecMacroError, hir_utils::{ @@ -59,7 +59,7 @@ fn inject_context_in_storage_field(field: &mut UnresolvedType) -> Result<(), Azt vec![], false, ))); - match path.segments.last().unwrap().0.contents.as_str() { + match path.last_name() { "Map" => inject_context_in_storage_field(&mut generics[1]), _ => Ok(()), } @@ -106,9 +106,7 @@ pub fn check_for_storage_implementation( storage_struct_name: &String, ) -> bool { module.impls.iter().any(|r#impl| match &r#impl.object_type.typ { - UnresolvedTypeData::Named(path, _, _) => { - path.segments.last().is_some_and(|segment| segment.0.contents == *storage_struct_name) - } + UnresolvedTypeData::Named(path, _, _) => path.last_name() == *storage_struct_name, _ => false, }) } @@ -123,8 +121,8 @@ pub fn generate_storage_field_constructor( match typ { UnresolvedTypeData::Named(path, generics, _) => { let mut new_path = path.clone().to_owned(); - new_path.segments.push(ident("new")); - match path.segments.last().unwrap().0.contents.as_str() { + new_path.segments.push(path_segment("new")); + match path.last_name() { "Map" => Ok(call( variable_path(new_path), vec![ diff --git a/aztec_macros/src/utils/ast_utils.rs b/aztec_macros/src/utils/ast_utils.rs index 4467c4bca4b..a74ec5b777a 100644 --- a/aztec_macros/src/utils/ast_utils.rs +++ b/aztec_macros/src/utils/ast_utils.rs @@ -2,8 +2,8 @@ use noirc_errors::{Span, Spanned}; use noirc_frontend::ast::{ BinaryOpKind, CallExpression, CastExpression, Expression, ExpressionKind, FunctionReturnType, Ident, IndexExpression, InfixExpression, Lambda, LetStatement, MemberAccessExpression, - MethodCallExpression, NoirTraitImpl, Path, Pattern, PrefixExpression, Statement, StatementKind, - TraitImplItem, UnaryOp, UnresolvedType, UnresolvedTypeData, + MethodCallExpression, NoirTraitImpl, Path, PathSegment, Pattern, PrefixExpression, Statement, + StatementKind, TraitImplItem, UnaryOp, UnresolvedType, UnresolvedTypeData, }; use noirc_frontend::token::SecondaryAttribute; @@ -18,6 +18,10 @@ pub fn ident_path(name: &str) -> Path { Path::from_ident(ident(name)) } +pub fn path_segment(name: &str) -> PathSegment { + PathSegment::from(ident(name)) +} + pub fn path(ident: Ident) -> Path { Path::from_ident(ident) } @@ -27,15 +31,15 @@ pub fn expression(kind: ExpressionKind) -> Expression { } pub fn variable(name: &str) -> Expression { - expression(ExpressionKind::Variable(ident_path(name), None)) + expression(ExpressionKind::Variable(ident_path(name))) } pub fn variable_ident(identifier: Ident) -> Expression { - expression(ExpressionKind::Variable(path(identifier), None)) + expression(ExpressionKind::Variable(path(identifier))) } pub fn variable_path(path: Path) -> Expression { - expression(ExpressionKind::Variable(path, None)) + expression(ExpressionKind::Variable(path)) } pub fn method_call( @@ -149,7 +153,7 @@ macro_rules! chained_path { { let mut base_path = ident_path($base); $( - base_path.segments.push(ident($tail)); + base_path.segments.push(path_segment($tail)); )* base_path } @@ -163,7 +167,7 @@ macro_rules! chained_dep { let mut base_path = ident_path($base); base_path.kind = PathKind::Plain; $( - base_path.segments.push(ident($tail)); + base_path.segments.push(path_segment($tail)); )* base_path } diff --git a/compiler/noirc_frontend/src/ast/expression.rs b/compiler/noirc_frontend/src/ast/expression.rs index 057daa2bdde..633e38a0e0a 100644 --- a/compiler/noirc_frontend/src/ast/expression.rs +++ b/compiler/noirc_frontend/src/ast/expression.rs @@ -29,9 +29,7 @@ pub enum ExpressionKind { Cast(Box), Infix(Box), If(Box), - // The optional vec here is the optional list of generics - // provided by the turbofish operator, if used - Variable(Path, Option>), + Variable(Path), Tuple(Vec), Lambda(Box), Parenthesized(Box), @@ -118,7 +116,7 @@ impl From for UnresolvedGeneric { impl ExpressionKind { pub fn into_path(self) -> Option { match self { - ExpressionKind::Variable(path, _) => Some(path), + ExpressionKind::Variable(path) => Some(path), _ => None, } } @@ -583,14 +581,7 @@ impl Display for ExpressionKind { Cast(cast) => cast.fmt(f), Infix(infix) => infix.fmt(f), If(if_expr) => if_expr.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) - } - } + Variable(path) => 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 038a13529d7..df8edeab7e0 100644 --- a/compiler/noirc_frontend/src/ast/mod.rs +++ b/compiler/noirc_frontend/src/ast/mod.rs @@ -324,7 +324,7 @@ impl UnresolvedTypeExpression { Some(int) => Ok(UnresolvedTypeExpression::Constant(int, expr.span)), 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 b41efebc905..ac4da2892fb 100644 --- a/compiler/noirc_frontend/src/ast/statement.rs +++ b/compiler/noirc_frontend/src/ast/statement.rs @@ -236,10 +236,11 @@ 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 }, - None, - ), + kind: ExpressionKind::Variable(Path { + span: i.span(), + segments: vec![PathSegment::from(i)], + kind: PathKind::Plain, + }), } } } @@ -362,18 +363,18 @@ impl UseTree { // it would most likely cause further errors during name resolution #[derive(Debug, PartialEq, Eq, Clone, Hash)] pub struct Path { - pub segments: Vec, + pub segments: Vec, pub kind: PathKind, pub span: Span, } impl Path { - pub fn pop(&mut self) -> Ident { + pub fn pop(&mut self) -> PathSegment { self.segments.pop().unwrap() } fn join(mut self, ident: Ident) -> Path { - self.segments.push(ident); + self.segments.push(PathSegment::from(ident)); self } @@ -384,18 +385,37 @@ impl Path { } pub fn from_ident(name: Ident) -> Path { - Path { span: name.span(), segments: vec![name], kind: PathKind::Plain } + Path { span: name.span(), segments: vec![PathSegment::from(name)], kind: PathKind::Plain } } pub fn span(&self) -> Span { self.span } - pub fn last_segment(&self) -> Ident { + pub fn first_segment(&self) -> PathSegment { + assert!(!self.segments.is_empty()); + self.segments.first().unwrap().clone() + } + + pub fn last_segment(&self) -> PathSegment { assert!(!self.segments.is_empty()); self.segments.last().unwrap().clone() } + pub fn last_ident(&self) -> Ident { + self.last_segment().ident + } + + pub fn first_name(&self) -> &str { + assert!(!self.segments.is_empty()); + &self.segments.first().unwrap().ident.0.contents + } + + pub fn last_name(&self) -> &str { + assert!(!self.segments.is_empty()); + &self.segments.last().unwrap().ident.0.contents + } + pub fn is_ident(&self) -> bool { self.segments.len() == 1 && self.kind == PathKind::Plain } @@ -404,14 +424,14 @@ impl Path { if !self.is_ident() { return None; } - self.segments.first() + self.segments.first().map(|segment| &segment.ident) } pub fn to_ident(&self) -> Option { if !self.is_ident() { return None; } - self.segments.first().cloned() + self.segments.first().cloned().map(|segment| segment.ident) } pub fn as_string(&self) -> String { @@ -421,19 +441,46 @@ impl Path { match segments.next() { None => panic!("empty segment"), Some(seg) => { - string.push_str(&seg.0.contents); + string.push_str(&seg.ident.0.contents); } } for segment in segments { string.push_str("::"); - string.push_str(&segment.0.contents); + string.push_str(&segment.ident.0.contents); } string } } +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub struct PathSegment { + pub ident: Ident, + pub generics: Option>, + pub span: Span, +} + +impl From for PathSegment { + fn from(ident: Ident) -> PathSegment { + let span = ident.span(); + PathSegment { ident, generics: None, span } + } +} + +impl Display for PathSegment { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.ident.fmt(f)?; + + if let Some(generics) = &self.generics { + let generics = vecmap(generics, ToString::to_string); + write!(f, "::<{}>", generics.join(", "))?; + } + + Ok(()) + } +} + #[derive(Debug, PartialEq, Eq, Clone)] pub struct LetStatement { pub pattern: Pattern, @@ -517,7 +564,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()), None), + LValue::Ident(ident) => ExpressionKind::Variable(Path::from_ident(ident.clone())), LValue::MemberAccess { object, field_name, span: _ } => { ExpressionKind::MemberAccess(Box::new(MemberAccessExpression { lhs: object.as_expression(), @@ -606,11 +653,12 @@ impl ForRange { }; // array.len() - let segments = vec![array_ident]; - let array_ident = ExpressionKind::Variable( - Path { segments, kind: PathKind::Plain, span: array_span }, - None, - ); + let segments = vec![PathSegment::from(array_ident)]; + let array_ident = ExpressionKind::Variable(Path { + segments, + kind: PathKind::Plain, + span: array_span, + }); let end_range = ExpressionKind::MethodCall(Box::new(MethodCallExpression { object: Expression::new(array_ident.clone(), array_span), @@ -626,11 +674,12 @@ impl ForRange { let fresh_identifier = Ident::new(index_name.clone(), array_span); // array[i] - let segments = vec![Ident::new(index_name, array_span)]; - let index_ident = ExpressionKind::Variable( - Path { segments, kind: PathKind::Plain, span: array_span }, - None, - ); + let segments = vec![PathSegment::from(Ident::new(index_name, array_span))]; + let index_ident = ExpressionKind::Variable(Path { + segments, + kind: PathKind::Plain, + span: array_span, + }); 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 443267380b5..598ffed1433 100644 --- a/compiler/noirc_frontend/src/debug/mod.rs +++ b/compiler/noirc_frontend/src/debug/mod.rs @@ -1,3 +1,4 @@ +use crate::ast::PathSegment; use crate::parser::{parse_program, ParsedModule}; use crate::{ ast, @@ -171,14 +172,11 @@ 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, - }, - None, - ), + kind: ast::ExpressionKind::Variable(ast::Path { + segments: vec![PathSegment::from(ident("__debug_expr", span))], + kind: PathKind::Plain, + span, + }), span, }), span, @@ -571,14 +569,11 @@ 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, - }, - None, - ), + kind: ast::ExpressionKind::Variable(ast::Path { + segments: vec![PathSegment::from(ident("__debug_var_assign", span))], + kind: PathKind::Plain, + span, + }), span, }), is_macro_call: false, @@ -590,14 +585,11 @@ 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, - }, - None, - ), + kind: ast::ExpressionKind::Variable(ast::Path { + segments: vec![PathSegment::from(ident("__debug_var_drop", span))], + kind: PathKind::Plain, + span, + }), span, }), is_macro_call: false, @@ -618,14 +610,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, + kind: ast::ExpressionKind::Variable(ast::Path { + segments: vec![PathSegment::from(ident( + &format!["__debug_member_assign_{arity}"], span, - }, - None, - ), + ))], + kind: PathKind::Plain, + span, + }), span, }), is_macro_call: false, @@ -642,14 +634,11 @@ 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, - }, - None, - ), + kind: ast::ExpressionKind::Variable(ast::Path { + segments: vec![PathSegment::from(ident(&format!["__debug_fn_{fname}"], span))], + kind: PathKind::Plain, + span, + }), span, }), is_macro_call: false, @@ -712,10 +701,11 @@ 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() }, - None, - ), + kind: ast::ExpressionKind::Variable(Path { + segments: vec![PathSegment::from(id.clone())], + kind: PathKind::Plain, + span: id.span(), + }), span: id.span(), } } diff --git a/compiler/noirc_frontend/src/elaborator/expressions.rs b/compiler/noirc_frontend/src/elaborator/expressions.rs index 3b8fab5360d..60e400e9e07 100644 --- a/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -50,9 +50,7 @@ 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, generics) => { - return self.elaborate_variable(variable, generics) - } + ExpressionKind::Variable(variable) => return self.elaborate_variable(variable), ExpressionKind::Tuple(tuple) => self.elaborate_tuple(tuple), ExpressionKind::Lambda(lambda) => self.elaborate_lambda(*lambda), ExpressionKind::Parenthesized(expr) => return self.elaborate_expression(*expr), @@ -410,8 +408,11 @@ impl<'context> Elaborator<'context> { &mut self, constructor: ConstructorExpression, ) -> (HirExpression, Type) { + let exclude_last_segment = false; + self.check_unsupported_turbofish_usage(&constructor.type_name, exclude_last_segment); + let span = constructor.type_name.span(); - let last_segment = constructor.type_name.last_segment(); + let last_segment = constructor.type_name.last_ident(); let is_self_type = last_segment.is_self_type_name(); let (r#type, struct_generics) = if let Some(struct_id) = constructor.struct_type { diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 5cfbf483eb6..f103e3a7954 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -984,7 +984,7 @@ impl<'context> Elaborator<'context> { let trait_generics = trait_impl.resolved_trait_generics.clone(); let resolved_trait_impl = Shared::new(TraitImpl { - ident: trait_impl.trait_path.last_segment().clone(), + ident: trait_impl.trait_path.last_ident(), typ: self_type.clone(), trait_id, trait_generics: trait_generics.clone(), @@ -1459,7 +1459,7 @@ impl<'context> Elaborator<'context> { self.generics.clear(); if let Some(trait_id) = trait_id { - let trait_name = trait_impl.trait_path.last_segment(); + let trait_name = trait_impl.trait_path.last_ident(); self.interner.add_trait_reference( trait_id, Location::new(trait_name.span(), trait_impl.file_id), diff --git a/compiler/noirc_frontend/src/elaborator/patterns.rs b/compiler/noirc_frontend/src/elaborator/patterns.rs index e24b6a3a067..7e75eae7a16 100644 --- a/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -157,8 +157,8 @@ impl<'context> Elaborator<'context> { mutable: Option, new_definitions: &mut Vec, ) -> HirPattern { - let name_span = name.last_segment().span(); - let is_self_type = name.last_segment().is_self_type_name(); + let name_span = name.last_ident().span(); + let is_self_type = name.last_ident().is_self_type_name(); let error_identifier = |this: &mut Self| { // Must create a name here to return a HirPattern::Identifier. Allowing @@ -429,11 +429,12 @@ impl<'context> Elaborator<'context> { }) } - pub(super) fn elaborate_variable( - &mut self, - variable: Path, - unresolved_turbofish: Option>, - ) -> (ExprId, Type) { + pub(super) fn elaborate_variable(&mut self, variable: Path) -> (ExprId, Type) { + let exclude_last_segment = true; + self.check_unsupported_turbofish_usage(&variable, exclude_last_segment); + + let unresolved_turbofish = variable.segments.last().unwrap().generics.clone(); + let span = variable.span; let expr = self.resolve_variable(variable); let definition_id = expr.id; @@ -648,7 +649,7 @@ impl<'context> Elaborator<'context> { } pub fn get_ident_from_path(&mut self, path: Path) -> (HirIdent, usize) { - let location = Location::new(path.last_segment().span(), self.file); + let location = Location::new(path.last_ident().span(), self.file); let error = match path.as_ident().map(|ident| self.use_variable(ident)) { Some(Ok(found)) => return found, diff --git a/compiler/noirc_frontend/src/elaborator/scope.rs b/compiler/noirc_frontend/src/elaborator/scope.rs index 23638b03cf5..73aed9bf06c 100644 --- a/compiler/noirc_frontend/src/elaborator/scope.rs +++ b/compiler/noirc_frontend/src/elaborator/scope.rs @@ -47,7 +47,7 @@ impl<'context> Elaborator<'context> { let path_resolution; if self.interner.track_references { - let last_segment = path.last_segment(); + let last_segment = path.last_ident(); let location = Location::new(last_segment.span(), self.file); let is_self_type_name = last_segment.is_self_type_name(); @@ -55,14 +55,14 @@ impl<'context> Elaborator<'context> { path_resolution = resolver.resolve(self.def_maps, path.clone(), &mut Some(&mut references))?; - for (referenced, ident) in references.iter().zip(path.segments) { + for (referenced, segment) in references.iter().zip(path.segments) { let Some(referenced) = referenced else { continue; }; self.interner.add_reference( *referenced, - Location::new(ident.span(), self.file), - ident.is_self_type_name(), + Location::new(segment.ident.span(), self.file), + segment.ident.is_self_type_name(), ); } diff --git a/compiler/noirc_frontend/src/elaborator/types.rs b/compiler/noirc_frontend/src/elaborator/types.rs index e6cf91594ad..430967d8a51 100644 --- a/compiler/noirc_frontend/src/elaborator/types.rs +++ b/compiler/noirc_frontend/src/elaborator/types.rs @@ -61,8 +61,8 @@ impl<'context> Elaborator<'context> { let (named_path_span, is_self_type_name, is_synthetic) = if let Named(ref named_path, _, synthetic) = typ.typ { ( - Some(named_path.last_segment().span()), - named_path.last_segment().is_self_type_name(), + Some(named_path.last_ident().span()), + named_path.last_ident().is_self_type_name(), synthetic, ) } else { @@ -221,7 +221,7 @@ impl<'context> Elaborator<'context> { // Check if the path is a type variable first. We currently disallow generics on type // variables since we do not support higher-kinded types. if path.segments.len() == 1 { - let name = &path.last_segment().0.contents; + let name = path.last_name(); if name == SELF_TYPE_NAME { if let Some(self_type) = self.self_type.clone() { @@ -352,7 +352,7 @@ impl<'context> Elaborator<'context> { pub fn lookup_generic_or_global_type(&mut self, path: &Path) -> Option { if path.segments.len() == 1 { - let name = &path.last_segment().0.contents; + let name = path.last_name(); if let Some(generic) = self.find_generic(name) { let generic = generic.clone(); return Some(Type::NamedGeneric(generic.type_var, generic.name, generic.kind)); @@ -416,8 +416,8 @@ impl<'context> Elaborator<'context> { let trait_id = self.interner.try_get_trait_implementation(trait_impl)?.borrow().trait_id; if path.kind == PathKind::Plain && path.segments.len() == 2 { - let name = &path.segments[0].0.contents; - let method = &path.segments[1]; + let name = &path.segments[0].ident.0.contents; + let method = &path.segments[1].ident; if name == SELF_TYPE_NAME { let the_trait = self.interner.get_trait(trait_id); @@ -450,7 +450,7 @@ impl<'context> Elaborator<'context> { let meta = self.interner.function_meta(&func_id); let trait_id = meta.trait_id?; let the_trait = self.interner.get_trait(trait_id); - let method = the_trait.find_method(&path.last_segment().0.contents)?; + let method = the_trait.find_method(path.last_name())?; let constraint = TraitConstraint { typ: Type::TypeVariable(the_trait.self_type_typevar.clone(), TypeVariableKind::Normal), trait_generics: Type::from_generics(&vecmap(&the_trait.generics, |generic| { @@ -478,14 +478,12 @@ impl<'context> Elaborator<'context> { for constraint in self.trait_bounds.clone() { if let Type::NamedGeneric(_, name, _) = &constraint.typ { // if `path` is `T::method_name`, we're looking for constraint of the form `T: SomeTrait` - if path.segments[0].0.contents != name.as_str() { + if path.segments[0].ident.0.contents != name.as_str() { continue; } let the_trait = self.interner.get_trait(constraint.trait_id); - if let Some(method) = - the_trait.find_method(path.segments.last().unwrap().0.contents.as_str()) - { + if let Some(method) = the_trait.find_method(path.last_name()) { return Some((method, constraint, true)); } } @@ -1617,6 +1615,20 @@ impl<'context> Elaborator<'context> { let context = context.expect("The function_context stack should always be non-empty"); context.trait_constraints.push((constraint, expr_id)); } + + pub fn check_unsupported_turbofish_usage(&mut self, path: &Path, exclude_last_segment: bool) { + for (index, segment) in path.segments.iter().enumerate() { + if exclude_last_segment && index == path.segments.len() - 1 { + continue; + } + + if segment.generics.is_some() { + // From "foo::", create a span for just "::" + let span = Span::from(segment.ident.span().end()..segment.span.end()); + self.push_err(TypeCheckError::UnsupportedTurbofishUsage { span }); + } + } + } } /// Gives an error if a user tries to create a mutable reference diff --git a/compiler/noirc_frontend/src/elaborator/unquote.rs b/compiler/noirc_frontend/src/elaborator/unquote.rs index ed12ba21398..fd7e02df905 100644 --- a/compiler/noirc_frontend/src/elaborator/unquote.rs +++ b/compiler/noirc_frontend/src/elaborator/unquote.rs @@ -27,7 +27,7 @@ impl<'a> Elaborator<'a> { // Don't want the leading `$` anymore new_tokens.pop(); let path = Path::from_single(name, span); - let (expr_id, _) = self.elaborate_variable(path, None); + let (expr_id, _) = self.elaborate_variable(path); new_tokens.push(SpannedToken::new(Token::UnquoteMarker(expr_id), span)); } other_next => new_tokens.push(SpannedToken::new(other_next, span)), diff --git a/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs b/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs index 22763c9cb64..6328a164a2a 100644 --- a/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs +++ b/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs @@ -5,8 +5,8 @@ use crate::ast::{ ArrayLiteral, AssignStatement, BlockExpression, CallExpression, CastExpression, ConstrainKind, ConstructorExpression, ExpressionKind, ForLoopStatement, ForRange, Ident, IfExpression, IndexExpression, InfixExpression, LValue, Lambda, LetStatement, Literal, - MemberAccessExpression, MethodCallExpression, Path, Pattern, PrefixExpression, UnresolvedType, - UnresolvedTypeData, UnresolvedTypeExpression, + MemberAccessExpression, MethodCallExpression, Path, PathSegment, Pattern, PrefixExpression, + UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression, }; use crate::ast::{ConstrainStatement, Expression, Statement, StatementKind}; use crate::hir_def::expr::{HirArrayLiteral, HirBlockExpression, HirExpression, HirIdent}; @@ -88,13 +88,19 @@ impl HirExpression { pub fn to_display_ast(&self, interner: &NodeInterner, span: Span) -> Expression { let kind = match self { HirExpression::Ident(ident, generics) => { - let path = Path::from_ident(ident.to_display_ast(interner)); - ExpressionKind::Variable( - path, - generics.as_ref().map(|option| { + let ident = ident.to_display_ast(interner); + let segment = PathSegment { + ident, + generics: generics.as_ref().map(|option| { option.iter().map(|generic| generic.to_display_ast()).collect() }), - ) + span, + }; + + let path = + Path { segments: vec![segment], kind: crate::ast::PathKind::Plain, span }; + + ExpressionKind::Variable(path) } HirExpression::Literal(HirLiteral::Array(array)) => { let array = array.to_display_ast(interner, span); diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index 80186c19c76..4aa61b50e55 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -19,7 +19,8 @@ use crate::node_interner::{ use crate::ast::{ ExpressionKind, Ident, LetStatement, Literal, NoirFunction, NoirStruct, NoirTrait, - NoirTypeAlias, Path, PathKind, UnresolvedGenerics, UnresolvedTraitConstraint, UnresolvedType, + NoirTypeAlias, Path, PathKind, PathSegment, UnresolvedGenerics, UnresolvedTraitConstraint, + UnresolvedType, }; use crate::parser::{ParserError, SortedModule}; @@ -318,13 +319,14 @@ impl DefCollector { let current_def_map = context.def_maps.get(&crate_id).unwrap(); let file_id = current_def_map.file_id(module_id); - for (referenced, ident) in references.iter().zip(&collected_import.path.segments) { + for (referenced, segment) in references.iter().zip(&collected_import.path.segments) + { let Some(referenced) = referenced else { continue; }; context.def_interner.add_reference( *referenced, - Location::new(ident.span(), file_id), + Location::new(segment.ident.span(), file_id), false, ); } @@ -351,7 +353,7 @@ impl DefCollector { .import(name.clone(), ns, resolved_import.is_prelude); let file_id = current_def_map.file_id(module_id); - let last_segment = collected_import.path.last_segment(); + let last_segment = collected_import.path.last_ident(); add_import_reference(ns, &last_segment, &mut context.def_interner, file_id); if let Some(ref alias) = collected_import.alias { @@ -425,7 +427,12 @@ fn inject_prelude( if !crate_id.is_stdlib() { let segments: Vec<_> = "std::prelude" .split("::") - .map(|segment| crate::ast::Ident::new(segment.into(), Span::default())) + .map(|segment| { + crate::ast::PathSegment::from(crate::ast::Ident::new( + segment.into(), + Span::default(), + )) + }) .collect(); let path = Path { @@ -446,7 +453,7 @@ fn inject_prelude( for path in prelude { let mut segments = segments.clone(); - segments.push(Ident::new(path.to_string(), Span::default())); + segments.push(PathSegment::from(Ident::new(path.to_string(), Span::default()))); collected_imports.insert( 0, diff --git a/compiler/noirc_frontend/src/hir/resolution/import.rs b/compiler/noirc_frontend/src/hir/resolution/import.rs index 10e18248dec..4693d3826a8 100644 --- a/compiler/noirc_frontend/src/hir/resolution/import.rs +++ b/compiler/noirc_frontend/src/hir/resolution/import.rs @@ -6,7 +6,7 @@ use crate::hir::def_collector::dc_crate::CompilationError; use crate::node_interner::ReferenceId; use std::collections::BTreeMap; -use crate::ast::{Ident, ItemVisibility, Path, PathKind}; +use crate::ast::{Ident, ItemVisibility, Path, PathKind, PathSegment}; use crate::hir::def_map::{CrateDefMap, LocalModuleId, ModuleDefId, ModuleId, PerNs}; use super::errors::ResolverError; @@ -163,7 +163,8 @@ fn resolve_path_to_ns( let current_mod_id = ModuleId { krate: crate_id, local_id: import_directive.module_id }; let current_mod = &def_map.modules[current_mod_id.local_id.0]; - let first_segment = import_path.first().expect("ice: could not fetch first segment"); + let first_segment = + &import_path.first().expect("ice: could not fetch first segment").ident; if current_mod.find_name(first_segment).is_none() { // Resolve externally when first segment is unresolved return resolve_external_dep( @@ -218,7 +219,7 @@ fn resolve_path_from_crate_root( crate_id: CrateId, importing_crate: CrateId, - import_path: &[Ident], + import_path: &[PathSegment], def_maps: &BTreeMap, path_references: &mut Option<&mut Vec>>, ) -> NamespaceResolutionResult { @@ -235,7 +236,7 @@ fn resolve_path_from_crate_root( fn resolve_name_in_module( krate: CrateId, importing_crate: CrateId, - import_path: &[Ident], + import_path: &[PathSegment], starting_mod: LocalModuleId, def_maps: &BTreeMap, path_references: &mut Option<&mut Vec>>, @@ -254,7 +255,7 @@ fn resolve_name_in_module( }); } - let first_segment = import_path.first().expect("ice: could not fetch first segment"); + let first_segment = &import_path.first().expect("ice: could not fetch first segment").ident; let mut current_ns = current_mod.find_name(first_segment); if current_ns.is_none() { return Err(PathResolutionError::Unresolved(first_segment.clone())); @@ -262,6 +263,9 @@ fn resolve_name_in_module( let mut warning: Option = None; for (last_segment, current_segment) in import_path.iter().zip(import_path.iter().skip(1)) { + let last_segment = &last_segment.ident; + let current_segment = ¤t_segment.ident; + let (typ, visibility) = match current_ns.types { None => return Err(PathResolutionError::Unresolved(last_segment.clone())), Some((typ, visibility, _)) => (typ, visibility), @@ -324,7 +328,7 @@ fn resolve_name_in_module( fn resolve_path_name(import_directive: &ImportDirective) -> Ident { match &import_directive.alias { - None => import_directive.path.segments.last().unwrap().clone(), + None => import_directive.path.last_ident(), Some(ident) => ident.clone(), } } @@ -340,7 +344,7 @@ fn resolve_external_dep( let path = &directive.path.segments; // Fetch the root module from the prelude - let crate_name = path.first().unwrap(); + let crate_name = &path.first().unwrap().ident; let dep_module = current_def_map .extern_prelude .get(&crate_name.0.contents) diff --git a/compiler/noirc_frontend/src/hir/type_check/errors.rs b/compiler/noirc_frontend/src/hir/type_check/errors.rs index af168a10df9..8eba8215f84 100644 --- a/compiler/noirc_frontend/src/hir/type_check/errors.rs +++ b/compiler/noirc_frontend/src/hir/type_check/errors.rs @@ -152,6 +152,8 @@ pub enum TypeCheckError { StringIndexAssign { span: Span }, #[error("Macro calls may only return `Quoted` values")] MacroReturningNonExpr { typ: Type, span: Span }, + #[error("turbofish (`::<_>`) usage at this position isn't supported yet")] + UnsupportedTurbofishUsage { span: Span }, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -350,6 +352,10 @@ impl<'a> From<&'a TypeCheckError> for Diagnostic { "Macro calls must return quoted values, otherwise there is no code to insert".into(), *span, ), + TypeCheckError::UnsupportedTurbofishUsage { span } => { + let msg = "turbofish (`::<_>`) usage at this position isn't supported yet"; + Diagnostic::simple_error(msg.to_string(), "".to_string(), *span) + }, } } } diff --git a/compiler/noirc_frontend/src/noir_parser.lalrpop b/compiler/noirc_frontend/src/noir_parser.lalrpop index 5bf48a764d6..1488a53183e 100644 --- a/compiler/noirc_frontend/src/noir_parser.lalrpop +++ b/compiler/noirc_frontend/src/noir_parser.lalrpop @@ -4,7 +4,7 @@ use crate::lexer::token::BorrowedToken; use crate::lexer::token as noir_token; use crate::lexer::errors::LexerErrorKind; use crate::parser::TopLevelStatement; -use crate::ast::{Ident, Path, PathKind, UseTree, UseTreeKind}; +use crate::ast::{Ident, Path, PathKind, PathSegment, UseTree, UseTreeKind}; use lalrpop_util::ErrorRecovery; @@ -110,7 +110,7 @@ pub(crate) TopLevelStatement: TopLevelStatement = { UseTree: UseTree = { // path::to::ident as SomeAlias => { - let ident = prefix.pop(); + let ident = prefix.pop().ident; let kind = UseTreeKind::Path(ident, alias); UseTree { prefix, kind } }, @@ -129,7 +129,7 @@ pub(crate) Path: Path = { Path { segments, kind, span } }, - => { + => { segments.insert(0, id); let kind = PathKind::Plain; let span = Span::from(lo as u32..hi as u32); @@ -137,12 +137,20 @@ pub(crate) Path: Path = { }, } -PathSegments: Vec = { - )*> => { +PathSegments: Vec = { + )*> => { segments } } +PathSegment: PathSegment = { + => { + let token = noir_token::Token::Ident(i.to_string()); + let span = Span::from(lo as u32..hi as u32); + PathSegment::from(Ident::from_token(token, span)) + }, +} + Alias: Ident = { r"[\t\r\n ]+" "as" r"[\t\r\n ]+" => <>, } diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index 3879f628eae..0f7377044f6 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -70,7 +70,7 @@ lalrpop_mod!(pub noir_parser); mod test_helpers; use literals::literal; -use path::{maybe_empty_path, path}; +use path::{maybe_empty_path, path, path_no_turbofish}; use primitives::{dereference, ident, negation, not, nothing, right_shift_operator, token_kind}; use traits::where_clause; @@ -432,8 +432,8 @@ fn rename() -> impl NoirParser> { fn use_tree() -> impl NoirParser { recursive(|use_tree| { - let simple = path().then(rename()).map(|(mut prefix, alias)| { - let ident = prefix.pop(); + let simple = path_no_turbofish().then(rename()).map(|(mut prefix, alias)| { + let ident = prefix.pop().ident; UseTree { prefix, kind: UseTreeKind::Path(ident, alias) } }); diff --git a/compiler/noirc_frontend/src/parser/parser/path.rs b/compiler/noirc_frontend/src/parser/parser/path.rs index 8957fb7c40b..5565c392d59 100644 --- a/compiler/noirc_frontend/src/parser/parser/path.rs +++ b/compiler/noirc_frontend/src/parser/parser/path.rs @@ -1,24 +1,34 @@ -use crate::ast::{Path, PathKind}; +use crate::ast::{Path, PathKind, PathSegment}; use crate::parser::NoirParser; use crate::token::{Keyword, Token}; use chumsky::prelude::*; -use super::{ident, keyword}; +use super::keyword; +use super::primitives::{path_segment, path_segment_no_turbofish}; pub(super) fn path() -> impl NoirParser { - let idents = || ident().separated_by(just(Token::DoubleColon)).at_least(1); + path_inner(path_segment()) +} + +pub(super) fn path_no_turbofish() -> impl NoirParser { + path_inner(path_segment_no_turbofish()) +} + +fn path_inner<'a>(segment: impl NoirParser + 'a) -> impl NoirParser + 'a { + let segments = segment.separated_by(just(Token::DoubleColon)).at_least(1); let make_path = |kind| move |segments, span| Path { segments, kind, span }; let prefix = |key| keyword(key).ignore_then(just(Token::DoubleColon)); - let path_kind = |key, kind| prefix(key).ignore_then(idents()).map_with_span(make_path(kind)); + let path_kind = + |key, kind| prefix(key).ignore_then(segments.clone()).map_with_span(make_path(kind)); choice(( path_kind(Keyword::Crate, PathKind::Crate), path_kind(Keyword::Dep, PathKind::Dep), path_kind(Keyword::Super, PathKind::Super), - idents().map_with_span(make_path(PathKind::Plain)), + segments.map_with_span(make_path(PathKind::Plain)), )) } @@ -45,14 +55,13 @@ mod test { ("std::hash", vec!["std", "hash"]), ("std::hash::collections", vec!["std", "hash", "collections"]), ("foo::bar", vec!["foo", "bar"]), - ("foo::bar", vec!["foo", "bar"]), ("crate::std::hash", vec!["std", "hash"]), ]; for (src, expected_segments) in cases { let path: Path = parse_with(path(), src).unwrap(); for (segment, expected) in path.segments.into_iter().zip(expected_segments) { - assert_eq!(segment.0.contents, expected); + assert_eq!(segment.ident.0.contents, expected); } } diff --git a/compiler/noirc_frontend/src/parser/parser/primitives.rs b/compiler/noirc_frontend/src/parser/parser/primitives.rs index 88f9e591aba..eb8d67b751a 100644 --- a/compiler/noirc_frontend/src/parser/parser/primitives.rs +++ b/compiler/noirc_frontend/src/parser/parser/primitives.rs @@ -1,13 +1,14 @@ use chumsky::prelude::*; -use crate::ast::{ExpressionKind, Ident, UnaryOp}; +use crate::ast::{ExpressionKind, Ident, PathSegment, UnaryOp}; use crate::macros_api::UnresolvedType; use crate::{ parser::{labels::ParsingRuleLabel, ExprParser, NoirParser, ParserError}, token::{Keyword, Token, TokenKind}, }; -use super::path; +use super::path::{path, path_no_turbofish}; +use super::types::required_generic_type_args; /// This parser always parses no input and fails pub(super) fn nothing() -> impl NoirParser { @@ -32,6 +33,16 @@ pub(super) fn token_kind(token_kind: TokenKind) -> impl NoirParser { }) } +pub(super) fn path_segment() -> impl NoirParser { + ident() + .then(turbofish(super::parse_type())) + .map_with_span(|(ident, generics), span| PathSegment { ident, generics, span }) +} + +pub(super) fn path_segment_no_turbofish() -> impl NoirParser { + ident().map(PathSegment::from) +} + pub(super) fn ident() -> impl NoirParser { token_kind(TokenKind::Ident).map_with_span(Ident::from_token) } @@ -81,17 +92,15 @@ where 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() + just(Token::DoubleColon).ignore_then(required_generic_type_args(type_parser)).or_not() } pub(super) fn variable() -> impl NoirParser { - path() - .then(turbofish(super::parse_type())) - .map(|(path, generics)| ExpressionKind::Variable(path, generics)) + path().map(ExpressionKind::Variable) } pub(super) fn variable_no_turbofish() -> impl NoirParser { - path().map(|path| ExpressionKind::Variable(path, None)) + path_no_turbofish().map(ExpressionKind::Variable) } pub(super) fn macro_quote_marker() -> impl NoirParser { diff --git a/compiler/noirc_frontend/src/parser/parser/traits.rs b/compiler/noirc_frontend/src/parser/parser/traits.rs index 2c64ea31b75..8115255368f 100644 --- a/compiler/noirc_frontend/src/parser/parser/traits.rs +++ b/compiler/noirc_frontend/src/parser/parser/traits.rs @@ -2,6 +2,7 @@ use chumsky::prelude::*; use super::attributes::{attributes, validate_secondary_attributes}; use super::function::function_return_type; +use super::path::path_no_turbofish; use super::types::maybe_comp_time; use super::{block, expression, fresh_statement, function, function_declaration_parameters}; @@ -17,7 +18,7 @@ use crate::{ token::{Keyword, Token}, }; -use super::{generic_type_args, parse_type, path, primitives::ident}; +use super::{generic_type_args, parse_type, primitives::ident}; pub(super) fn trait_definition() -> impl NoirParser { attributes() @@ -107,7 +108,7 @@ pub(super) fn trait_implementation() -> impl NoirParser { maybe_comp_time() .then_ignore(keyword(Keyword::Impl)) .then(function::generics()) - .then(path()) + .then(path_no_turbofish()) .then(generic_type_args(parse_type())) .then_ignore(keyword(Keyword::For)) .then(parse_type()) @@ -185,10 +186,8 @@ fn trait_bounds() -> impl NoirParser> { } pub(super) fn trait_bound() -> impl NoirParser { - path().then(generic_type_args(parse_type())).map(|(trait_path, trait_generics)| TraitBound { - trait_path, - trait_generics, - trait_id: None, + path_no_turbofish().then(generic_type_args(parse_type())).map(|(trait_path, trait_generics)| { + TraitBound { trait_path, trait_generics, trait_id: None } }) } diff --git a/compiler/noirc_frontend/src/parser/parser/types.rs b/compiler/noirc_frontend/src/parser/parser/types.rs index f859227b82b..fca29bf1e7a 100644 --- a/compiler/noirc_frontend/src/parser/parser/types.rs +++ b/compiler/noirc_frontend/src/parser/parser/types.rs @@ -1,6 +1,7 @@ +use super::path::path_no_turbofish; use super::primitives::token_kind; use super::{ - expression_with_precedence, keyword, nothing, parenthesized, path, NoirParser, ParserError, + expression_with_precedence, keyword, nothing, parenthesized, NoirParser, ParserError, ParserErrorReason, Precedence, }; use crate::ast::{ @@ -180,7 +181,7 @@ pub(super) fn int_type() -> impl NoirParser { pub(super) 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| { + path_no_turbofish().then(generic_type_args(type_parser)).map_with_span(|(path, args), span| { UnresolvedTypeData::Named(path, args, false).with_span(span) }) } @@ -188,13 +189,22 @@ pub(super) fn named_type<'a>( pub(super) 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), - ) + keyword(Keyword::Impl) + .ignore_then(path_no_turbofish()) + .then(generic_type_args(type_parser)) + .map_with_span(|(path, args), span| { + UnresolvedTypeData::TraitAsType(path, args).with_span(span) + }) } pub(super) fn generic_type_args<'a>( type_parser: impl NoirParser + 'a, +) -> impl NoirParser> + 'a { + required_generic_type_args(type_parser).or_not().map(Option::unwrap_or_default) +} + +pub(super) fn required_generic_type_args<'a>( + type_parser: impl NoirParser + 'a, ) -> impl NoirParser> + 'a { type_parser .clone() @@ -208,8 +218,6 @@ pub(super) fn generic_type_args<'a>( .allow_trailing() .at_least(1) .delimited_by(just(Token::Less), just(Token::Greater)) - .or_not() - .map(Option::unwrap_or_default) } pub(super) fn array_type<'a>( @@ -241,7 +249,7 @@ fn type_expression() -> impl NoirParser { /// This parser is the same as `type_expression()`, however, it continues parsing and /// emits a parser error in the case of an invalid type expression rather than halting the parser. -fn type_expression_validated() -> impl NoirParser { +pub(super) fn type_expression_validated() -> impl NoirParser { type_expression_inner().validate(|expr, span, emit| { let type_expr = UnresolvedTypeExpression::from_expr(expr, span); match type_expr { diff --git a/compiler/noirc_frontend/src/tests.rs b/compiler/noirc_frontend/src/tests.rs index 80ec22139dd..de4f1a15eae 100644 --- a/compiler/noirc_frontend/src/tests.rs +++ b/compiler/noirc_frontend/src/tests.rs @@ -2537,3 +2537,49 @@ fn trait_constraint_on_tuple_type() { fn main() {}"#; assert_no_errors(src); } + +#[test] +fn turbofish_in_constructor_unsupported_yet() { + let src = r#" + struct Foo { + x: T + } + + fn main() { + let _ = Foo:: { x: 1 }; + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + assert!(matches!( + errors[0].0, + CompilationError::TypeError(TypeCheckError::UnsupportedTurbofishUsage { .. }), + )); +} + +#[test] +fn turbofish_in_middle_of_variable_unsupported_yet() { + let src = r#" + struct Foo { + x: T + } + + impl Foo { + fn new(x: T) -> Self { + Foo { x } + } + } + + fn main() { + let _ = Foo::::new(1); + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + assert!(matches!( + errors[0].0, + CompilationError::TypeError(TypeCheckError::UnsupportedTurbofishUsage { .. }), + )); +} diff --git a/tooling/lsp/src/requests/document_symbol.rs b/tooling/lsp/src/requests/document_symbol.rs index 67e2505d8fd..20fdfb6ece7 100644 --- a/tooling/lsp/src/requests/document_symbol.rs +++ b/tooling/lsp/src/requests/document_symbol.rs @@ -427,7 +427,7 @@ impl<'a> DocumentSymbolCollector<'a> { return; }; - let name = name_path.last_segment(); + let name = name_path.last_ident(); let Some(name_location) = self.to_lsp_location(name.span()) else { return; diff --git a/tooling/lsp/src/requests/inlay_hint.rs b/tooling/lsp/src/requests/inlay_hint.rs index 2afa5fa44fd..3fc6a6752df 100644 --- a/tooling/lsp/src/requests/inlay_hint.rs +++ b/tooling/lsp/src/requests/inlay_hint.rs @@ -609,7 +609,7 @@ fn push_type_variable_parts( fn get_expression_name(expression: &Expression) -> Option { match &expression.kind { - ExpressionKind::Variable(path, _) => Some(path.last_segment().to_string()), + ExpressionKind::Variable(path) => Some(path.last_name().to_string()), ExpressionKind::Prefix(prefix) => get_expression_name(&prefix.rhs), ExpressionKind::MemberAccess(member_access) => Some(member_access.rhs.to_string()), ExpressionKind::Call(call) => get_expression_name(&call.func), diff --git a/tooling/nargo_fmt/src/rewrite/expr.rs b/tooling/nargo_fmt/src/rewrite/expr.rs index 015644c15cb..5673baf2893 100644 --- a/tooling/nargo_fmt/src/rewrite/expr.rs +++ b/tooling/nargo_fmt/src/rewrite/expr.rs @@ -1,5 +1,6 @@ use noirc_frontend::ast::{ - ArrayLiteral, BlockExpression, Expression, ExpressionKind, Literal, UnaryOp, UnresolvedType, + ArrayLiteral, BlockExpression, Expression, ExpressionKind, Literal, Path, PathKind, UnaryOp, + UnresolvedType, }; use noirc_frontend::{macros_api::Span, token::Token}; @@ -161,12 +162,7 @@ pub(crate) fn rewrite( visitor.format_if(*if_expr) } - ExpressionKind::Variable(path, generics) => { - let path_string = visitor.slice(path.span); - - let turbofish = rewrite_turbofish(visitor, shape, generics); - format!("{path_string}{turbofish}") - } + ExpressionKind::Variable(path) => rewrite_path(visitor, shape, path), ExpressionKind::Lambda(_) => visitor.slice(span).to_string(), ExpressionKind::Quote(_) => visitor.slice(span).to_string(), ExpressionKind::Comptime(block, block_span) => { @@ -192,6 +188,25 @@ fn rewrite_block(visitor: &FmtVisitor, block: BlockExpression, span: Span) -> St visitor.finish() } +fn rewrite_path(visitor: &FmtVisitor, shape: Shape, path: Path) -> String { + let mut string = String::new(); + + if path.kind != PathKind::Plain { + string.push_str(&path.kind.to_string()); + string.push_str("::"); + } + + for (index, segment) in path.segments.iter().enumerate() { + if index > 0 { + string.push_str("::"); + } + string.push_str(&segment.ident.to_string()); + string.push_str(&rewrite_turbofish(visitor, shape, segment.generics.clone())); + } + + string +} + fn rewrite_turbofish( visitor: &FmtVisitor, shape: Shape,