From 1c97c1ac11459d45f7bfc57dc72428d2b294520c Mon Sep 17 00:00:00 2001 From: Ville Penttinen Date: Sun, 17 Feb 2019 19:08:34 +0200 Subject: Enable parsing of attributes inside a match block We allow invalid inner attributes to be parsed, e.g. inner attributes that are not directly after the opening brace of the match block. Instead we run validation on `MatchArmList` to allow better reporting of errors. --- crates/ra_syntax/src/ast.rs | 14 ++++++++++ crates/ra_syntax/src/ast/generated.rs | 2 ++ crates/ra_syntax/src/grammar.ron | 4 ++- crates/ra_syntax/src/grammar/attributes.rs | 10 +++++++ crates/ra_syntax/src/grammar/expressions/atom.rs | 33 ++++++++++++++++++++++++ crates/ra_syntax/src/syntax_node/syntax_error.rs | 4 +++ crates/ra_syntax/src/validation.rs | 2 ++ crates/ra_syntax/src/validation/match_armlist.rs | 28 ++++++++++++++++++++ 8 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 crates/ra_syntax/src/validation/match_armlist.rs (limited to 'crates/ra_syntax/src') diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs index 22105d6c9..350f01f33 100644 --- a/crates/ra_syntax/src/ast.rs +++ b/crates/ra_syntax/src/ast.rs @@ -153,6 +153,20 @@ impl FnDef { } impl Attr { + pub fn is_inner(&self) -> bool { + let tt = match self.value() { + None => return false, + Some(tt) => tt, + }; + + let prev = match tt.syntax().prev_sibling() { + None => return false, + Some(prev) => prev, + }; + + prev.kind() == EXCL + } + pub fn as_atom(&self) -> Option { let tt = self.value()?; let (_bra, attr, _ket) = tt.syntax().children().collect_tuple()?; diff --git a/crates/ra_syntax/src/ast/generated.rs b/crates/ra_syntax/src/ast/generated.rs index dd91b5063..c7aaccc95 100644 --- a/crates/ra_syntax/src/ast/generated.rs +++ b/crates/ra_syntax/src/ast/generated.rs @@ -1946,6 +1946,7 @@ impl ToOwned for MatchArm { } +impl ast::AttrsOwner for MatchArm {} impl MatchArm { pub fn pats(&self) -> impl Iterator { super::children(self) @@ -1986,6 +1987,7 @@ impl ToOwned for MatchArmList { } +impl ast::AttrsOwner for MatchArmList {} impl MatchArmList { pub fn arms(&self) -> impl Iterator { super::children(self) diff --git a/crates/ra_syntax/src/grammar.ron b/crates/ra_syntax/src/grammar.ron index 27a123681..193b563aa 100644 --- a/crates/ra_syntax/src/grammar.ron +++ b/crates/ra_syntax/src/grammar.ron @@ -413,13 +413,15 @@ Grammar( ), "MatchArmList": ( collections: [ ["arms", "MatchArm"] ], + traits: [ "AttrsOwner" ] ), "MatchArm": ( options: [ [ "guard", "MatchGuard" ], "Expr", ], - collections: [ [ "pats", "Pat" ] ] + collections: [ [ "pats", "Pat" ] ], + traits: [ "AttrsOwner" ] ), "MatchGuard": (options: ["Expr"]), "StructLit": (options: ["Path", "NamedFieldList", ["spread", "Expr"]]), diff --git a/crates/ra_syntax/src/grammar/attributes.rs b/crates/ra_syntax/src/grammar/attributes.rs index cd30e8a45..2624d2e16 100644 --- a/crates/ra_syntax/src/grammar/attributes.rs +++ b/crates/ra_syntax/src/grammar/attributes.rs @@ -1,5 +1,15 @@ use super::*; +/// Parses both inner & outer attributes. +/// +/// Allowing to run validation for reporting errors +/// regarding attributes +pub(super) fn all_attributes(p: &mut Parser) { + while p.at(POUND) { + attribute(p, p.nth(1) == EXCL) + } +} + pub(super) fn inner_attributes(p: &mut Parser) { while p.current() == POUND && p.nth(1) == EXCL { attribute(p, true) diff --git a/crates/ra_syntax/src/grammar/expressions/atom.rs b/crates/ra_syntax/src/grammar/expressions/atom.rs index 27ba87657..67cd7e6b0 100644 --- a/crates/ra_syntax/src/grammar/expressions/atom.rs +++ b/crates/ra_syntax/src/grammar/expressions/atom.rs @@ -313,11 +313,44 @@ pub(crate) fn match_arm_list(p: &mut Parser) { assert!(p.at(L_CURLY)); let m = p.start(); p.eat(L_CURLY); + + // test match_arms_inner_attribute + // fn foo() { + // match () { + // #![doc("Inner attribute")] + // #![doc("Can be")] + // #![doc("Stacked")] + // _ => (), + // } + // } + attributes::inner_attributes(p); + while !p.at(EOF) && !p.at(R_CURLY) { if p.at(L_CURLY) { error_block(p, "expected match arm"); continue; } + + // This may result in invalid attributes + // if there are inner attributes mixed in together + // with the outer attributes, but we allow parsing + // those so we can run validation and report better errors + + // test match_arms_outer_attributes + // fn foo() { + // match () { + // #[cfg(feature = "some")] + // _ => (), + // #[cfg(feature = "other")] + // _ => (), + // #[cfg(feature = "many")] + // #[cfg(feature = "attributes")] + // #[cfg(feature = "before")] + // _ => (), + // } + // } + attributes::all_attributes(p); + // test match_arms_commas // fn foo() { // match () { diff --git a/crates/ra_syntax/src/syntax_node/syntax_error.rs b/crates/ra_syntax/src/syntax_node/syntax_error.rs index 412cf82cc..4ff998090 100644 --- a/crates/ra_syntax/src/syntax_node/syntax_error.rs +++ b/crates/ra_syntax/src/syntax_node/syntax_error.rs @@ -92,6 +92,7 @@ pub enum SyntaxErrorKind { UnclosedString, InvalidSuffix, InvalidBlockAttr, + InvalidMatchInnerAttr, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -136,6 +137,9 @@ impl fmt::Display for SyntaxErrorKind { InvalidBlockAttr => { write!(f, "A block in this position cannot accept inner attributes") } + InvalidMatchInnerAttr => { + write!(f, "Inner attributes are only allowed directly after the opening brace of the match expression") + } ParseError(msg) => write!(f, "{}", msg.0), } } diff --git a/crates/ra_syntax/src/validation.rs b/crates/ra_syntax/src/validation.rs index 10672d6bf..27a465a78 100644 --- a/crates/ra_syntax/src/validation.rs +++ b/crates/ra_syntax/src/validation.rs @@ -3,6 +3,7 @@ mod byte_string; mod char; mod string; mod block; +mod match_armlist; use crate::{ SourceFile, syntax_node::SyntaxError, AstNode, @@ -19,6 +20,7 @@ pub(crate) fn validate(file: &SourceFile) -> Vec { .visit::(self::char::validate_char_node) .visit::(self::string::validate_string_node) .visit::(self::block::validate_block_node) + .visit::(self::match_armlist::validate_match_armlist) .accept(node); } errors diff --git a/crates/ra_syntax/src/validation/match_armlist.rs b/crates/ra_syntax/src/validation/match_armlist.rs new file mode 100644 index 000000000..c43ed7092 --- /dev/null +++ b/crates/ra_syntax/src/validation/match_armlist.rs @@ -0,0 +1,28 @@ +use crate::{ + ast::{self, AttrsOwner, AstNode}, + syntax_node::{ + SyntaxError, + SyntaxErrorKind::*, + Direction, + }, +}; + +pub(crate) fn validate_match_armlist(node: &ast::MatchArmList, errors: &mut Vec) { + // Report errors for any inner attribute + // which has a preceding matcharm or an outer attribute + for inner_attr in node.attrs().filter(|s| s.is_inner()) { + let any_errors = inner_attr.syntax().siblings(Direction::Prev).any(|s| { + match (ast::MatchArm::cast(s), ast::Attr::cast(s)) { + (Some(_), _) => true, + // Outer attributes which preceed an inner attribute are not allowed + (_, Some(a)) if !a.is_inner() => true, + (_, Some(_)) => false, + (None, None) => false, + } + }); + + if any_errors { + errors.push(SyntaxError::new(InvalidMatchInnerAttr, inner_attr.syntax().range())); + } + } +} -- cgit v1.2.3