From 122fb0b64658730d7447e78972ec8deee7e365d0 Mon Sep 17 00:00:00 2001 From: Daniel Frederico Lins Leite Date: Wed, 23 Aug 2023 15:32:02 +0100 Subject: [PATCH] Parser recovery inside trait/abi (#4979) ## Description Closes https://github.com/FuelLabs/sway/issues/4729. The trick part of this PR is that `TraitItems` were being parsed as `(TraitItem, SemicolonToken)`. The problem is that to recover from an error we call the `error` method of the `Parser` trait. And it is very hard to come up with a generic implementation for the tuple. Which type of tuple do we call its `error` method, and how do we return a valid tuple in all cases? Impossible. So that is why I moved the `SemicolonToken` to inside the `TraitItem`. Another benefit is that we can recover from missing semicolons with nice error messages. ## Checklist - [x] I have linked to any relevant issues. - [x] I have commented my code, particularly in hard-to-understand areas. - [x] I have updated the documentation where relevant (API docs, the reference, and the Sway book). - [x] I have added tests that prove my fix is effective or that my feature works. - [x] I have added (or requested a maintainer to add) the necessary `Breaking*` or `New Feature` labels where relevant. - [x] I have done my best to ensure that my PR adheres to [the Fuel Labs Code Review Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md). - [x] I have requested a review from the relevant team or maintainers. --- sway-ast/src/item/item_abi.rs | 2 +- sway-ast/src/item/item_trait.rs | 23 ++++++-- .../src/language/parsed/declaration/trait.rs | 3 + .../ast_node/declaration/abi.rs | 3 + .../ast_node/declaration/trait.rs | 3 + .../semantic_analysis/node_dependencies.rs | 2 + .../to_parsed_lang/convert_parse_tree.rs | 14 +++-- sway-error/src/error.rs | 1 - sway-error/src/parser_error.rs | 2 +- sway-lsp/src/traverse/lexed_tree.rs | 14 +++-- sway-lsp/src/traverse/parsed_tree.rs | 2 + sway-parse/src/expr/mod.rs | 2 +- sway-parse/src/item/item_abi.rs | 8 ++- sway-parse/src/item/item_trait.rs | 22 +++++-- sway-parse/src/item/mod.rs | 8 +-- sway-parse/src/parse.rs | 4 +- sway-parse/src/parser.rs | 59 ++++++++----------- swayfmt/src/items/item_abi.rs | 7 +-- swayfmt/src/items/item_trait/mod.rs | 39 ++++++++---- swayfmt/src/items/item_trait/tests.rs | 18 ++++++ .../annotated_missing_item/test.toml | 2 +- .../double_underscore_trait_fn/test.toml | 5 +- .../Forc.lock | 0 .../Forc.toml | 0 .../recover_at_level_inside_items/src/main.sw | 29 +++++++++ .../recover_at_level_inside_items/test.toml | 5 ++ .../Forc.lock | 0 .../Forc.toml | 0 .../src/main.sw | 0 .../test.toml | 0 .../recover_at_level_top/Forc.lock | 3 + .../recover_at_level_top/Forc.toml | 6 ++ .../src/main.sw | 0 .../test.toml | 0 34 files changed, 196 insertions(+), 90 deletions(-) rename test/src/e2e_vm_tests/test_programs/should_fail/{recover_invalid_items => recover_at_level_inside_items}/Forc.lock (100%) rename test/src/e2e_vm_tests/test_programs/should_fail/{recover_invalid_items => recover_at_level_inside_items}/Forc.toml (100%) create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_inside_items/src/main.sw create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_inside_items/test.toml rename test/src/e2e_vm_tests/test_programs/should_fail/{recover_letx_no_expr => recover_at_level_statement}/Forc.lock (100%) rename test/src/e2e_vm_tests/test_programs/should_fail/{recover_letx_no_expr => recover_at_level_statement}/Forc.toml (100%) rename test/src/e2e_vm_tests/test_programs/should_fail/{recover_letx_no_expr => recover_at_level_statement}/src/main.sw (100%) rename test/src/e2e_vm_tests/test_programs/should_fail/{recover_letx_no_expr => recover_at_level_statement}/test.toml (100%) create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_top/Forc.lock create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_top/Forc.toml rename test/src/e2e_vm_tests/test_programs/should_fail/{recover_invalid_items => recover_at_level_top}/src/main.sw (100%) rename test/src/e2e_vm_tests/test_programs/should_fail/{recover_invalid_items => recover_at_level_top}/test.toml (100%) diff --git a/sway-ast/src/item/item_abi.rs b/sway-ast/src/item/item_abi.rs index 46fecb07867..6dcf73f3e58 100644 --- a/sway-ast/src/item/item_abi.rs +++ b/sway-ast/src/item/item_abi.rs @@ -5,7 +5,7 @@ pub struct ItemAbi { pub abi_token: AbiToken, pub name: Ident, pub super_traits: Option<(ColonToken, Traits)>, - pub abi_items: Braces, SemicolonToken)>>, + pub abi_items: Braces>>, pub abi_defs_opt: Option>>>, } diff --git a/sway-ast/src/item/item_trait.rs b/sway-ast/src/item/item_trait.rs index 3e1c4ceaee7..5efa4bf6894 100644 --- a/sway-ast/src/item/item_trait.rs +++ b/sway-ast/src/item/item_trait.rs @@ -1,9 +1,13 @@ +use sway_error::handler::ErrorEmitted; + use crate::priv_prelude::*; #[derive(Clone, Debug, Serialize)] pub enum ItemTraitItem { - Fn(FnSignature), - Const(ItemConst), + Fn(FnSignature, Option), + Const(ItemConst, Option), + // to handle parser recovery: Error represents an incomplete trait item + Error(Box<[Span]>, #[serde(skip_serializing)] ErrorEmitted), } #[derive(Clone, Debug, Serialize)] @@ -14,7 +18,7 @@ pub struct ItemTrait { pub generics: Option, pub where_clause_opt: Option, pub super_traits: Option<(ColonToken, Traits)>, - pub trait_items: Braces, SemicolonToken)>>, + pub trait_items: Braces>>, pub trait_defs_opt: Option>>>, } @@ -35,8 +39,17 @@ impl Spanned for ItemTrait { impl Spanned for ItemTraitItem { fn span(&self) -> Span { match self { - ItemTraitItem::Fn(fn_decl) => fn_decl.span(), - ItemTraitItem::Const(const_decl) => const_decl.span(), + ItemTraitItem::Fn(fn_decl, semicolon) => match semicolon.as_ref().map(|x| x.span()) { + Some(semicolon) => Span::join(fn_decl.span(), semicolon), + None => fn_decl.span(), + }, + ItemTraitItem::Const(const_decl, semicolon) => { + match semicolon.as_ref().map(|x| x.span()) { + Some(semicolon) => Span::join(const_decl.span(), semicolon), + None => const_decl.span(), + } + } + ItemTraitItem::Error(spans, _) => Span::join_all(spans.iter().cloned()), } } } diff --git a/sway-core/src/language/parsed/declaration/trait.rs b/sway-core/src/language/parsed/declaration/trait.rs index f57ffb2a66b..3b6dff40610 100644 --- a/sway-core/src/language/parsed/declaration/trait.rs +++ b/sway-core/src/language/parsed/declaration/trait.rs @@ -5,12 +5,15 @@ use super::{ConstantDeclaration, FunctionDeclaration, FunctionParameter}; use crate::{ decl_engine::DeclRefTrait, engine_threading::*, language::*, transform, type_system::*, }; +use sway_error::handler::ErrorEmitted; use sway_types::{ident::Ident, span::Span, Spanned}; #[derive(Debug, Clone)] pub enum TraitItem { TraitFn(TraitFn), Constant(ConstantDeclaration), + // to handle parser recovery: Error represents an incomplete trait item + Error(Box<[Span]>, ErrorEmitted), } #[derive(Debug, Clone)] diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/abi.rs b/sway-core/src/semantic_analysis/ast_node/declaration/abi.rs index b1699177b55..f22283ad2a0 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/abi.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/abi.rs @@ -133,6 +133,9 @@ impl ty::TyAbiDecl { const_name } + TraitItem::Error(_, _) => { + continue; + } }; if !ids.insert(decl_name.clone()) { diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/trait.rs b/sway-core/src/semantic_analysis/ast_node/declaration/trait.rs index a5499e1df7b..39ef990e877 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/trait.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/trait.rs @@ -112,6 +112,9 @@ impl ty::TyTraitDecl { const_name } + TraitItem::Error(_, _) => { + continue; + } }; if !ids.insert(decl_name.clone()) { diff --git a/sway-core/src/semantic_analysis/node_dependencies.rs b/sway-core/src/semantic_analysis/node_dependencies.rs index b4fb845cfef..392afb0549e 100644 --- a/sway-core/src/semantic_analysis/node_dependencies.rs +++ b/sway-core/src/semantic_analysis/node_dependencies.rs @@ -345,6 +345,7 @@ impl Dependencies { TraitItem::Constant(const_decl) => { deps.gather_from_constant_decl(engines, const_decl) } + TraitItem::Error(_, _) => deps, }) .gather_from_iter(methods.iter(), |deps, fn_decl| { deps.gather_from_fn_decl(engines, fn_decl) @@ -395,6 +396,7 @@ impl Dependencies { TraitItem::Constant(const_decl) => { deps.gather_from_constant_decl(engines, const_decl) } + TraitItem::Error(_, _) => deps, }) .gather_from_iter(methods.iter(), |deps, fn_decl| { deps.gather_from_fn_decl(engines, fn_decl) diff --git a/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs b/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs index e7a680005a1..718302bfc83 100644 --- a/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs +++ b/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs @@ -586,20 +586,21 @@ fn item_trait_to_trait_declaration( .trait_items .into_inner() .into_iter() - .map(|(annotated, _)| { + .map(|annotated| { let attributes = item_attrs_to_map(context, handler, &annotated.attribute_list)?; if !cfg_eval(context, handler, &attributes)? { return Ok(None); } Ok(Some(match annotated.value { - ItemTraitItem::Fn(fn_sig) => { + ItemTraitItem::Fn(fn_sig, _) => { fn_signature_to_trait_fn(context, handler, engines, fn_sig, attributes) .map(TraitItem::TraitFn) } - ItemTraitItem::Const(const_decl) => item_const_to_constant_declaration( + ItemTraitItem::Const(const_decl, _) => item_const_to_constant_declaration( context, handler, engines, const_decl, attributes, false, ) .map(TraitItem::Constant), + ItemTraitItem::Error(spans, error) => Ok(TraitItem::Error(spans, error)), }?)) }) .filter_map_ok(|item| item) @@ -759,14 +760,14 @@ fn item_abi_to_abi_declaration( .abi_items .into_inner() .into_iter() - .map(|(annotated, _)| { + .map(|annotated| { let attributes = item_attrs_to_map(context, handler, &annotated.attribute_list)?; if !cfg_eval(context, handler, &attributes)? { return Ok(None); } Ok(Some(match annotated.value { - ItemTraitItem::Fn(fn_signature) => { + ItemTraitItem::Fn(fn_signature, _) => { let trait_fn = fn_signature_to_trait_fn( context, handler, @@ -783,10 +784,11 @@ fn item_abi_to_abi_declaration( )?; Ok(TraitItem::TraitFn(trait_fn)) } - ItemTraitItem::Const(const_decl) => item_const_to_constant_declaration( + ItemTraitItem::Const(const_decl, _) => item_const_to_constant_declaration( context, handler, engines, const_decl, attributes, false, ) .map(TraitItem::Constant), + ItemTraitItem::Error(spans, error) => Ok(TraitItem::Error(spans, error)), }?)) }) .filter_map_ok(|item| item) diff --git a/sway-error/src/error.rs b/sway-error/src/error.rs index 2ef111dfe1b..97c5fe0f700 100644 --- a/sway-error/src/error.rs +++ b/sway-error/src/error.rs @@ -286,7 +286,6 @@ pub enum CompileError { }, #[error("This opcode takes an immediate value but none was provided.")] MissingImmediate { span: Span }, - #[error("This immediate value is invalid.")] InvalidImmediateValue { span: Span }, #[error("Variant \"{variant_name}\" does not exist on enum \"{enum_name}\"")] diff --git a/sway-error/src/parser_error.rs b/sway-error/src/parser_error.rs index 2c34fe54a2d..4d712a9610b 100644 --- a/sway-error/src/parser_error.rs +++ b/sway-error/src/parser_error.rs @@ -8,7 +8,7 @@ pub enum ParseErrorKind { ExpectedImportNameGroupOrGlob, #[error("Expected an item.")] ExpectedAnItem, - #[error("Expected item after doc comment.")] + #[error("Expected an item after doc comment.")] ExpectedAnItemAfterDocComment, #[error("Expected a comma or closing parenthesis in function arguments.")] ExpectedCommaOrCloseParenInFnArgs, diff --git a/sway-lsp/src/traverse/lexed_tree.rs b/sway-lsp/src/traverse/lexed_tree.rs index b0090df39f4..9501036afde 100644 --- a/sway-lsp/src/traverse/lexed_tree.rs +++ b/sway-lsp/src/traverse/lexed_tree.rs @@ -300,9 +300,10 @@ impl Parse for ItemTrait { self.trait_items .get() .iter() - .for_each(|(annotated, _)| match &annotated.value { - sway_ast::ItemTraitItem::Fn(fn_sig) => fn_sig.parse(ctx), - sway_ast::ItemTraitItem::Const(item_const) => item_const.parse(ctx), + .for_each(|annotated| match &annotated.value { + sway_ast::ItemTraitItem::Fn(fn_sig, _) => fn_sig.parse(ctx), + sway_ast::ItemTraitItem::Const(item_const, _) => item_const.parse(ctx), + sway_ast::ItemTraitItem::Error(_, _) => {} }); if let Some(trait_defs_opt) = &self.trait_defs_opt { @@ -345,9 +346,10 @@ impl Parse for ItemAbi { self.abi_items .get() .iter() - .for_each(|(annotated, _)| match &annotated.value { - sway_ast::ItemTraitItem::Fn(fn_sig) => fn_sig.parse(ctx), - sway_ast::ItemTraitItem::Const(item_const) => item_const.parse(ctx), + .for_each(|annotated| match &annotated.value { + sway_ast::ItemTraitItem::Fn(fn_sig, _) => fn_sig.parse(ctx), + sway_ast::ItemTraitItem::Const(item_const, _) => item_const.parse(ctx), + sway_ast::ItemTraitItem::Error(_, _) => {} }); if let Some(abi_defs_opt) = self.abi_defs_opt.as_ref() { diff --git a/sway-lsp/src/traverse/parsed_tree.rs b/sway-lsp/src/traverse/parsed_tree.rs index 4e2a51c07ac..8d56c9b12f1 100644 --- a/sway-lsp/src/traverse/parsed_tree.rs +++ b/sway-lsp/src/traverse/parsed_tree.rs @@ -732,6 +732,7 @@ impl Parse for TraitDeclaration { self.interface_surface.iter().for_each(|item| match item { TraitItem::TraitFn(trait_fn) => trait_fn.parse(ctx), TraitItem::Constant(const_decl) => const_decl.parse(ctx), + TraitItem::Error(_, _) => {} }); self.methods.iter().for_each(|func_dec| { func_dec.parse(ctx); @@ -848,6 +849,7 @@ impl Parse for AbiDeclaration { self.interface_surface.iter().for_each(|item| match item { TraitItem::TraitFn(trait_fn) => trait_fn.parse(ctx), TraitItem::Constant(const_decl) => const_decl.parse(ctx), + TraitItem::Error(_, _) => {} }); self.supertraits.iter().for_each(|supertrait| { supertrait.parse(ctx); diff --git a/sway-parse/src/expr/mod.rs b/sway-parse/src/expr/mod.rs index 16cb7cc951d..f976c0be091 100644 --- a/sway-parse/src/expr/mod.rs +++ b/sway-parse/src/expr/mod.rs @@ -138,7 +138,7 @@ impl ParseToEnd for CodeBlockContents { break (None, consumed); } - match parser.parse_fn_with_recovery(parse_stmt) { + match parser.call_parsing_function_with_recovery(parse_stmt) { Ok(StmtOrTail::Stmt(s)) => statements.push(s), Ok(StmtOrTail::Tail(e, c)) => break (Some(e), c), Err(r) => { diff --git a/sway-parse/src/item/item_abi.rs b/sway-parse/src/item/item_abi.rs index 37396836727..2970956d61a 100644 --- a/sway-parse/src/item/item_abi.rs +++ b/sway-parse/src/item/item_abi.rs @@ -14,13 +14,15 @@ impl Parse for ItemAbi { } None => None, }; - let abi_items: Braces, _)>> = parser.parse()?; - for (annotated, _) in abi_items.get().iter() { + + let abi_items: Braces>> = parser.parse()?; + for annotated in abi_items.get().iter() { #[allow(irrefutable_let_patterns)] - if let ItemTraitItem::Fn(fn_signature) = &annotated.value { + if let ItemTraitItem::Fn(fn_signature, _) = &annotated.value { parser.ban_visibility_qualifier(&fn_signature.visibility)?; } } + let abi_defs_opt: Option>>> = Braces::try_parse(parser)?; if let Some(abi_defs) = &abi_defs_opt { for item_fn in abi_defs.get().iter() { diff --git a/sway-parse/src/item/item_trait.rs b/sway-parse/src/item/item_trait.rs index 4c2ce313f04..de3e49e7208 100644 --- a/sway-parse/src/item/item_trait.rs +++ b/sway-parse/src/item/item_trait.rs @@ -9,14 +9,26 @@ impl Parse for ItemTraitItem { fn parse(parser: &mut Parser) -> ParseResult { if parser.peek::().is_some() || parser.peek::().is_some() { let fn_decl = parser.parse()?; - Ok(ItemTraitItem::Fn(fn_decl)) + let semicolon = parser.parse().ok(); + Ok(ItemTraitItem::Fn(fn_decl, semicolon)) } else if let Some(_const_keyword) = parser.peek::() { let const_decl = parser.parse()?; - Ok(ItemTraitItem::Const(const_decl)) + let semicolon = parser.parse().ok(); + Ok(ItemTraitItem::Const(const_decl, semicolon)) } else { Err(parser.emit_error(ParseErrorKind::ExpectedAnItem)) } } + + fn error( + spans: Box<[sway_types::Span]>, + error: sway_error::handler::ErrorEmitted, + ) -> Option + where + Self: Sized, + { + Some(ItemTraitItem::Error(spans, error)) + } } impl Parse for ItemTrait { @@ -34,9 +46,9 @@ impl Parse for ItemTrait { }; let where_clause_opt = parser.guarded_parse::()?; - let trait_items: Braces, _)>> = parser.parse()?; - for (annotated, _) in trait_items.get().iter() { - if let ItemTraitItem::Fn(fn_sig) = &annotated.value { + let trait_items: Braces>> = parser.parse()?; + for item in trait_items.get().iter() { + if let ItemTraitItem::Fn(fn_sig, _) = &item.value { parser.ban_visibility_qualifier(&fn_sig.visibility)?; } } diff --git a/sway-parse/src/item/mod.rs b/sway-parse/src/item/mod.rs index f887276eddd..d215582afec 100644 --- a/sway-parse/src/item/mod.rs +++ b/sway-parse/src/item/mod.rs @@ -518,8 +518,8 @@ mod tests { let trait_item = decls.next(); assert!(trait_item.is_some()); - let (annotated, _) = trait_item.unwrap(); - if let ItemTraitItem::Fn(_fn_sig) = &annotated.value { + let annotated = trait_item.unwrap(); + if let ItemTraitItem::Fn(_fn_sig, _) = &annotated.value { assert_eq!( attributes(&annotated.attribute_list), vec![[("foo", Some(vec!["one"]))], [("bar", None)]] @@ -575,7 +575,7 @@ mod tests { assert!(f_sig.is_some()); assert_eq!( - attributes(&f_sig.unwrap().0.attribute_list), + attributes(&f_sig.unwrap().attribute_list), vec![[("bar", Some(vec!["one", "two", "three"]))],] ); @@ -583,7 +583,7 @@ mod tests { assert!(g_sig.is_some()); assert_eq!( - attributes(&g_sig.unwrap().0.attribute_list), + attributes(&g_sig.unwrap().attribute_list), vec![[("foo", None)],] ); assert!(decls.next().is_none()); diff --git a/sway-parse/src/parse.rs b/sway-parse/src/parse.rs index e0e72abb2c4..068929514c8 100644 --- a/sway-parse/src/parse.rs +++ b/sway-parse/src/parse.rs @@ -6,6 +6,8 @@ use sway_error::parser_error::ParseErrorKind; use sway_types::{ast::Delimiter, Ident, Spanned}; pub trait Parse { + const FALLBACK_ERROR: ParseErrorKind = ParseErrorKind::InvalidItem; + fn parse(parser: &mut Parser) -> ParseResult where Self: Sized; @@ -110,7 +112,7 @@ where Ok(value) => ret.push(value), Err(r) => { let (spans, error) = - r.recover_at_next_line_with_fallback_error(ParseErrorKind::InvalidItem); + r.recover_at_next_line_with_fallback_error(T::FALLBACK_ERROR); if let Some(error) = T::error(spans, error) { ret.push(error); } else { diff --git a/sway-parse/src/parser.rs b/sway-parse/src/parser.rs index 44a3b43f5e1..c8b6b62a32b 100644 --- a/sway-parse/src/parser.rs +++ b/sway-parse/src/parser.rs @@ -76,13 +76,20 @@ impl<'a, 'e> Parser<'a, 'e> { Peeker::with(self.token_trees).map(|(v, _)| v) } - pub fn parse_fn_with_recovery< + /// This function will fork the current parse, and call the parsing function. + /// If it succeeds it will sync the original parser with the forked one; + /// + /// If it fails it will return a `Recoverer` together with the `ErrorEmited`. + /// + /// This recoverer can be used to put the forked parsed back in track and then + /// sync the original parser to allow the parsing to continue. + pub fn call_parsing_function_with_recovery< 'original, T, F: FnOnce(&mut Parser<'a, '_>) -> ParseResult, >( &'original mut self, - f: F, + parsing_function: F, ) -> Result> { let handler = Handler::default(); let mut fork = Parser { @@ -91,7 +98,7 @@ impl<'a, 'e> Parser<'a, 'e> { handler: &handler, }; - match f(&mut fork) { + match parsing_function(&mut fork) { Ok(result) => { self.token_trees = fork.token_trees; self.handler.append(handler); @@ -114,45 +121,25 @@ impl<'a, 'e> Parser<'a, 'e> { } } + /// This function will fork the current parse, and try to parse + /// T using the fork. If it succeeds it will sync the original parser with the forked one; + /// + /// If it fails it will return a `Recoverer` together with the `ErrorEmited`. + /// + /// This recoverer can be used to put the forked parsed back in track and then + /// sync the original parser to allow the parsing to continue. pub fn parse_with_recovery<'original, T: Parse>( &'original mut self, ) -> Result> { - let handler = Handler::default(); - let mut fork = Parser { - token_trees: self.token_trees, - full_span: self.full_span.clone(), - handler: &handler, - }; - - match fork.parse() { - Ok(result) => { - self.token_trees = fork.token_trees; - self.handler.append(handler); - Ok(result) - } - Err(error) => { - let Parser { - token_trees, - full_span, - .. - } = fork; - Err(Recoverer { - original: RefCell::new(self), - handler, - fork_token_trees: token_trees, - fork_full_span: full_span, - error, - }) - } - } + self.call_parsing_function_with_recovery(|p| p.parse()) } - /// This function do three things + /// This function does three things /// 1 - it peeks P; - /// 2 - it forks the current parse, and try to parse - /// T using the fork. If it success it put the original - /// parse at the same state as the forked; - /// 3 - if it fails ir returns a `Recoverer` together with the `ErrorEmited`. + /// 2 - it forks the current parser and tries to parse + /// T using this fork. If it succeeds it syncs the original + /// parser with the forked one; + /// 3 - if it fails it will return a `Recoverer` together with the `ErrorEmited`. /// /// This recoverer can be used to put the forked parsed back in track and then /// sync the original parser to allow the parsing to continue. diff --git a/swayfmt/src/items/item_abi.rs b/swayfmt/src/items/item_abi.rs index d5c4657c02b..ee24e74056f 100644 --- a/swayfmt/src/items/item_abi.rs +++ b/swayfmt/src/items/item_abi.rs @@ -33,15 +33,10 @@ impl Format for ItemAbi { let abi_items = self.abi_items.get(); // abi_items - for (annotated, semicolon) in abi_items.iter() { + for annotated in abi_items.iter() { // add indent + format item write!(formatted_code, "{}", formatter.indent_str()?)?; annotated.format(formatted_code, formatter)?; - writeln!( - formatted_code, - "{}", - semicolon.ident().as_str() // SemicolonToken - )?; } if abi_items.is_empty() { diff --git a/swayfmt/src/items/item_trait/mod.rs b/swayfmt/src/items/item_trait/mod.rs index c626533c931..bb74bbb30e0 100644 --- a/swayfmt/src/items/item_trait/mod.rs +++ b/swayfmt/src/items/item_trait/mod.rs @@ -69,22 +69,23 @@ impl Format for ItemTrait { if trait_items.is_empty() { write_comments(formatted_code, self.trait_items.span().into(), formatter)?; } else { - for (annotated, semicolon_token) in trait_items { - for attr in &annotated.attribute_list { + for item in trait_items { + for attr in &item.attribute_list { write!(formatted_code, "{}", &formatter.indent_str()?,)?; attr.format(formatted_code, formatter)?; } - match &annotated.value { - sway_ast::ItemTraitItem::Fn(fn_signature) => { + match &item.value { + sway_ast::ItemTraitItem::Fn(fn_signature, _) => { write!(formatted_code, "{}", formatter.indent_str()?,)?; fn_signature.format(formatted_code, formatter)?; - writeln!(formatted_code, "{}", semicolon_token.ident().as_str())?; + writeln!(formatted_code, ";")?; } - sway_ast::ItemTraitItem::Const(const_decl) => { + sway_ast::ItemTraitItem::Const(const_decl, _) => { write!(formatted_code, "{}", formatter.indent_str()?,)?; const_decl.format(formatted_code, formatter)?; - writeln!(formatted_code, "{}", semicolon_token.ident().as_str())?; + writeln!(formatted_code, ";")?; } + ItemTraitItem::Error(_, _) => {} } } } @@ -120,8 +121,17 @@ impl Format for ItemTraitItem { formatter: &mut Formatter, ) -> Result<(), FormatterError> { match self { - ItemTraitItem::Fn(fn_decl) => fn_decl.format(formatted_code, formatter), - ItemTraitItem::Const(const_decl) => const_decl.format(formatted_code, formatter), + ItemTraitItem::Fn(fn_decl, _) => { + fn_decl.format(formatted_code, formatter)?; + writeln!(formatted_code, ";")?; + Ok(()) + } + ItemTraitItem::Const(const_decl, _) => { + const_decl.format(formatted_code, formatter)?; + writeln!(formatted_code, ";")?; + Ok(()) + } + ItemTraitItem::Error(_, _) => Ok(()), } } } @@ -199,10 +209,15 @@ impl LeafSpans for ItemTraitItem { fn leaf_spans(&self) -> Vec { let mut collected_spans = Vec::new(); match &self { - ItemTraitItem::Fn(fn_sig) => collected_spans.append(&mut fn_sig.leaf_spans()), - ItemTraitItem::Const(const_decl) => { - collected_spans.append(&mut const_decl.leaf_spans()) + ItemTraitItem::Fn(fn_sig, semicolon) => { + collected_spans.append(&mut fn_sig.leaf_spans()); + collected_spans.extend(semicolon.as_ref().into_iter().flat_map(|x| x.leaf_spans())); + } + ItemTraitItem::Const(const_decl, semicolon) => { + collected_spans.append(&mut const_decl.leaf_spans()); + collected_spans.extend(semicolon.as_ref().into_iter().flat_map(|x| x.leaf_spans())); } + ItemTraitItem::Error(_, _) => {} }; collected_spans } diff --git a/swayfmt/src/items/item_trait/tests.rs b/swayfmt/src/items/item_trait/tests.rs index 572891ddc1a..e1daf791822 100644 --- a/swayfmt/src/items/item_trait/tests.rs +++ b/swayfmt/src/items/item_trait/tests.rs @@ -66,3 +66,21 @@ intermediate_whitespace fn foo(self); } " ); + +fmt_test_item!( +trait_normal_comment_two_fns +"pub trait MyTrait { + // Before A + fn a(self); + // Before b + fn b(self); +}", + +intermediate_whitespace +" pub trait MyTrait { + // Before A + fn a(self); + // Before b + fn b(self); + } " +); diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/annotated_missing_item/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/annotated_missing_item/test.toml index 0206c403aa0..33b6d7e66dd 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/annotated_missing_item/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/annotated_missing_item/test.toml @@ -1,3 +1,3 @@ category = "fail" -# check: $()Expected item after doc comment +# check: $()Expected an item after doc comment diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/double_underscore_trait_fn/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/double_underscore_trait_fn/test.toml index 4543ad5a87d..5561e64a3ef 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/double_underscore_trait_fn/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/double_underscore_trait_fn/test.toml @@ -1,4 +1,7 @@ category = "fail" -# check: __test() +# This appears in the warning for the trait having no implementation. +# check: fn __test() + +# check: fn __test() # nextln: $()Identifiers cannot begin with a double underscore, as that naming convention is reserved for compiler intrinsics. diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/recover_invalid_items/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_inside_items/Forc.lock similarity index 100% rename from test/src/e2e_vm_tests/test_programs/should_fail/recover_invalid_items/Forc.lock rename to test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_inside_items/Forc.lock diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/recover_invalid_items/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_inside_items/Forc.toml similarity index 100% rename from test/src/e2e_vm_tests/test_programs/should_fail/recover_invalid_items/Forc.toml rename to test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_inside_items/Forc.toml diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_inside_items/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_inside_items/src/main.sw new file mode 100644 index 00000000000..6ad86b0b16c --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_inside_items/src/main.sw @@ -0,0 +1,29 @@ +script; + +trait T1 { + garbage + fn f1(self); + / more garbage + fn f2(self); + fn f3(self) + fn f4(self); +} + +struct S1 { + +} + +impl T1 for S1 { + fn f1(self) {} + fn f2(self) {} + fn f3(self) {} + fn f4(self) {} +} + +fn main() { + let s1 = S1 {}; + s1.f1(); + s1.f2(); + s1.f3(); + s1.f4(); +} diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_inside_items/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_inside_items/test.toml new file mode 100644 index 00000000000..1c2c308103b --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_inside_items/test.toml @@ -0,0 +1,5 @@ +category = "fail" + +# check: $()Expected an item +# check: $()Expected an item +# check: $()Expected `;` diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/recover_letx_no_expr/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_statement/Forc.lock similarity index 100% rename from test/src/e2e_vm_tests/test_programs/should_fail/recover_letx_no_expr/Forc.lock rename to test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_statement/Forc.lock diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/recover_letx_no_expr/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_statement/Forc.toml similarity index 100% rename from test/src/e2e_vm_tests/test_programs/should_fail/recover_letx_no_expr/Forc.toml rename to test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_statement/Forc.toml diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/recover_letx_no_expr/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_statement/src/main.sw similarity index 100% rename from test/src/e2e_vm_tests/test_programs/should_fail/recover_letx_no_expr/src/main.sw rename to test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_statement/src/main.sw diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/recover_letx_no_expr/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_statement/test.toml similarity index 100% rename from test/src/e2e_vm_tests/test_programs/should_fail/recover_letx_no_expr/test.toml rename to test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_statement/test.toml diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_top/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_top/Forc.lock new file mode 100644 index 00000000000..226fc150475 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_top/Forc.lock @@ -0,0 +1,3 @@ +[[package]] +name = 'recover_invalid_items' +source = 'member' diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_top/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_top/Forc.toml new file mode 100644 index 00000000000..75b6b75d787 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_top/Forc.toml @@ -0,0 +1,6 @@ +[project] +name = "recover_invalid_items" +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +implicit-std = false diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/recover_invalid_items/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_top/src/main.sw similarity index 100% rename from test/src/e2e_vm_tests/test_programs/should_fail/recover_invalid_items/src/main.sw rename to test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_top/src/main.sw diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/recover_invalid_items/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_top/test.toml similarity index 100% rename from test/src/e2e_vm_tests/test_programs/should_fail/recover_invalid_items/test.toml rename to test/src/e2e_vm_tests/test_programs/should_fail/recover_at_level_top/test.toml