Repository: rust-analyzer/rust-analyzer (rust)

Issue:
Allow inner attribtes inside blocks
```rust
    fn salsa_event(&self, event_fn: impl Fn() -> Event<Self>) {
        #![allow(unused_variables)] // this is  `inner_attr` of the block
    }
```

cc @DJMcNab 

Pull request description:
[WIP] Correctly parse attributes 
Reference - https://doc.rust-lang.org/reference/attributes.html
This fixes/investigates inner attributes for:

- [x] `impl` blocks
- [x] `extern` blocks
- [x] `fn`s (fixes #689)
- [x] `mod`s (already supported)
- [x] 'block expressions' (the long text just describes all 'blocks' used as statements)

This also investigates/fixes outer attributes for:

- [ ] 'most statements' (see also: #685, https://doc.rust-lang.org/reference/expressions.html#expression-attributes)
- [x] Enum variants, Struct and Union fields (Fixed in #507)
- [ ] 'Match expression arms' (@matklad can you provide a test case which explains what this means?)
- [ ] 'Generic lifetime or type parameters'
- [ ] 'Elements of array expressions, tuple expressions, call expressions, tuple-style struct and enum variant expressions'
- [ ] 'The tail expression of block expressions'

Patch:
diff --git a/crates/ra_db/src/lib.rs b/crates/ra_db/src/lib.rs
index ca775030d0..6e17f33f07 100644
--- a/crates/ra_db/src/lib.rs
+++ b/crates/ra_db/src/lib.rs
@@ -2,6 +2,7 @@
 mod cancellation;
 mod input;
 mod loc2id;
+pub mod mock;
 
 use std::{
     panic, sync::Arc,
diff --git a/crates/ra_db/src/mock.rs b/crates/ra_db/src/mock.rs
new file mode 100644
index 0000000000..5e185062b4
--- /dev/null
+++ b/crates/ra_db/src/mock.rs
@@ -0,0 +1,30 @@
+use rustc_hash::FxHashSet;
+use relative_path::{RelativePath, RelativePathBuf};
+
+use crate::{FileId};
+
+#[derive(Default, Debug, Clone)]
+pub struct FileMap(Vec<(FileId, RelativePathBuf)>);
+
+impl FileMap {
+    pub fn add(&mut self, path: RelativePathBuf) -> FileId {
+        let file_id = FileId((self.0.len() + 1) as u32);
+        self.0.push((file_id, path));
+        file_id
+    }
+
+    pub fn files(&self) -> FxHashSet<FileId> {
+        self.iter().map(|(id, _)| id).collect()
+    }
+
+    pub fn file_id(&self, path: &str) -> FileId {
+        assert!(path.starts_with('/'));
+        self.iter().find(|(_, p)| p == &path[1..]).unwrap().0
+    }
+
+    fn iter<'a>(&'a self) -> impl Iterator<Item = (FileId, &'a RelativePath)> + 'a {
+        self.0
+            .iter()
+            .map(|(id, path)| (*id, path.as_relative_path()))
+    }
+}
diff --git a/crates/ra_ide_api/src/mock_analysis.rs b/crates/ra_ide_api/src/mock_analysis.rs
index 0f2d22ab24..846c76cfe7 100644
--- a/crates/ra_ide_api/src/mock_analysis.rs
+++ b/crates/ra_ide_api/src/mock_analysis.rs
@@ -2,6 +2,7 @@ use std::sync::Arc;
 
 use relative_path::RelativePathBuf;
 use test_utils::{extract_offset, extract_range, parse_fixture, CURSOR_MARKER};
+use ra_db::mock::FileMap;
 
 use crate::{Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, FilePosition, FileRange, SourceRootId};
 
@@ -82,14 +83,15 @@ impl MockAnalysis {
     }
     pub fn analysis_host(self) -> AnalysisHost {
         let mut host = AnalysisHost::default();
+        let mut file_map = FileMap::default();
         let source_root = SourceRootId(0);
         let mut change = AnalysisChange::new();
         change.add_root(source_root, true);
         let mut crate_graph = CrateGraph::default();
-        for (i, (path, contents)) in self.files.into_iter().enumerate() {
+        for (path, contents) in self.files.into_iter() {
             assert!(path.starts_with('/'));
             let path = RelativePathBuf::from_path(&path[1..]).unwrap();
-            let file_id = FileId(i as u32 + 1);
+            let file_id = file_map.add(path.clone());
             if path == "/lib.rs" || path == "/main.rs" {
                 crate_graph.add_crate_root(file_id);
             }
diff --git a/crates/ra_syntax/src/ast/generated.rs b/crates/ra_syntax/src/ast/generated.rs
index 3ace6533c4..ce559882bd 100644
--- a/crates/ra_syntax/src/ast/generated.rs
+++ b/crates/ra_syntax/src/ast/generated.rs
@@ -272,6 +272,7 @@ impl ToOwned for Block {
 }
 
 
+impl ast::AttrsOwner for Block {}
 impl Block {
     pub fn statements(&self) -> impl Iterator<Item = &Stmt> {
         super::children(self)
diff --git a/crates/ra_syntax/src/grammar.ron b/crates/ra_syntax/src/grammar.ron
index 85fc79038c..f4841241f1 100644
--- a/crates/ra_syntax/src/grammar.ron
+++ b/crates/ra_syntax/src/grammar.ron
@@ -571,6 +571,9 @@ Grammar(
             options: [ "Expr" ],
             collections: [
                 ["statements", "Stmt"],
+            ],
+            traits: [
+                "AttrsOwner",
             ]
         ),
         "ParamList": (
diff --git a/crates/ra_syntax/src/grammar/expressions.rs b/crates/ra_syntax/src/grammar/expressions.rs
index d27eb8b7e1..6b88c56853 100644
--- a/crates/ra_syntax/src/grammar/expressions.rs
+++ b/crates/ra_syntax/src/grammar/expressions.rs
@@ -42,6 +42,8 @@ pub(crate) fn block(p: &mut Parser) {
     }
     let m = p.start();
     p.bump();
+    // This is checked by a validator
+    attributes::inner_attributes(p);
 
     while !p.at(EOF) && !p.at(R_CURLY) {
         match p.current() {
diff --git a/crates/ra_syntax/src/grammar/items/traits.rs b/crates/ra_syntax/src/grammar/items/traits.rs
index 0a0621753f..d5a8ccd98e 100644
--- a/crates/ra_syntax/src/grammar/items/traits.rs
+++ b/crates/ra_syntax/src/grammar/items/traits.rs
@@ -78,6 +78,13 @@ pub(crate) fn impl_item_list(p: &mut Parser) {
     assert!(p.at(L_CURLY));
     let m = p.start();
     p.bump();
+    // test impl_inner_attributes
+    // enum F{}
+    // impl F {
+    //      //! This is a doc comment
+    //      #![doc("This is also a doc comment")]
+    // }
+    attributes::inner_attributes(p);
 
     while !p.at(EOF) && !p.at(R_CURLY) {
         if p.at(L_CURLY) {
diff --git a/crates/ra_syntax/src/validation.rs b/crates/ra_syntax/src/validation.rs
index 73e1d20b9f..ac6cc3dd61 100644
--- a/crates/ra_syntax/src/validation.rs
+++ b/crates/ra_syntax/src/validation.rs
@@ -2,6 +2,7 @@ mod byte;
 mod byte_string;
 mod char;
 mod string;
+mod block;
 
 use crate::{
     SourceFile, yellow::SyntaxError, AstNode,
@@ -17,6 +18,7 @@ pub(crate) fn validate(file: &SourceFile) -> Vec<SyntaxError> {
             .visit::<ast::ByteString, _>(self::byte_string::validate_byte_string_node)
             .visit::<ast::Char, _>(self::char::validate_char_node)
             .visit::<ast::String, _>(self::string::validate_string_node)
+            .visit::<ast::Block, _>(self::block::validate_block_node)
             .accept(node);
     }
     errors
diff --git a/crates/ra_syntax/src/validation/block.rs b/crates/ra_syntax/src/validation/block.rs
new file mode 100644
index 0000000000..9e1949124f
--- /dev/null
+++ b/crates/ra_syntax/src/validation/block.rs
@@ -0,0 +1,24 @@
+use crate::{SyntaxKind::*,
+    ast::{self, AttrsOwner, AstNode},
+    yellow::{
+        SyntaxError,
+        SyntaxErrorKind::*,
+    },
+};
+
+pub(crate) fn validate_block_node(node: &ast::Block, errors: &mut Vec<SyntaxError>) {
+    if let Some(parent) = node.syntax().parent() {
+        match parent.kind() {
+            FN_DEF => return,
+            BLOCK_EXPR => match parent.parent().map(|v| v.kind()) {
+                Some(EXPR_STMT) | Some(BLOCK) => return,
+                _ => {}
+            },
+            _ => {}
+        }
+    }
+    errors.extend(
+        node.attrs()
+            .map(|attr| SyntaxError::new(InvalidBlockAttr, attr.syntax().range())),
+    )
+}
diff --git a/crates/ra_syntax/src/yellow/syntax_error.rs b/crates/ra_syntax/src/yellow/syntax_error.rs
index 534f3511e3..c52c44cc38 100644
--- a/crates/ra_syntax/src/yellow/syntax_error.rs
+++ b/crates/ra_syntax/src/yellow/syntax_error.rs
@@ -94,6 +94,7 @@ pub enum SyntaxErrorKind {
     UnicodeEscapeOutOfRange,
     UnclosedString,
     InvalidSuffix,
+    InvalidBlockAttr,
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -136,6 +137,9 @@ impl fmt::Display for SyntaxErrorKind {
             UnicodeEscapeOutOfRange => write!(f, "Unicode escape code should be at most 0x10FFFF"),
             UnclosedString => write!(f, "Unclosed string literal"),
             InvalidSuffix => write!(f, "Invalid literal suffix"),
+            InvalidBlockAttr => {
+                write!(f, "A block in this position cannot accept inner attributes")
+            }
             ParseError(msg) => write!(f, "{}", msg.0),
         }
     }


Test patch:
diff --git a/crates/ra_syntax/tests/data/parser/err/0031_block_inner_attrs.rs b/crates/ra_syntax/tests/data/parser/err/0031_block_inner_attrs.rs
new file mode 100644
index 0000000000..6a04f2d0aa
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/err/0031_block_inner_attrs.rs
@@ -0,0 +1,15 @@
+fn block() {
+    let inner = {
+        #![doc("Inner attributes not allowed here")]
+        //! Nor are ModuleDoc comments
+    };
+    if true {
+        #![doc("Nor here")]
+        #![doc("We error on each attr")]
+        //! Nor are ModuleDoc comments
+    }
+    while true {
+        #![doc("Nor here")]
+        //! Nor are ModuleDoc comments
+    }
+}
diff --git a/crates/ra_syntax/tests/data/parser/err/0031_block_inner_attrs.txt b/crates/ra_syntax/tests/data/parser/err/0031_block_inner_attrs.txt
new file mode 100644
index 0000000000..086aa79acd
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/err/0031_block_inner_attrs.txt
@@ -0,0 +1,114 @@
+SOURCE_FILE@[0; 350)
+  FN_DEF@[0; 349)
+    FN_KW@[0; 2)
+    WHITESPACE@[2; 3)
+    NAME@[3; 8)
+      IDENT@[3; 8) "block"
+    PARAM_LIST@[8; 10)
+      L_PAREN@[8; 9)
+      R_PAREN@[9; 10)
+    WHITESPACE@[10; 11)
+    BLOCK@[11; 349)
+      L_CURLY@[11; 12)
+      WHITESPACE@[12; 17)
+      LET_STMT@[17; 129)
+        LET_KW@[17; 20)
+        WHITESPACE@[20; 21)
+        BIND_PAT@[21; 26)
+          NAME@[21; 26)
+            IDENT@[21; 26) "inner"
+        WHITESPACE@[26; 27)
+        EQ@[27; 28)
+        WHITESPACE@[28; 29)
+        BLOCK_EXPR@[29; 128)
+          BLOCK@[29; 128)
+            L_CURLY@[29; 30)
+            WHITESPACE@[30; 39)
+            err: `A block in this position cannot accept inner attributes`
+            ATTR@[39; 83)
+              POUND@[39; 40)
+              EXCL@[40; 41)
+              TOKEN_TREE@[41; 83)
+                L_BRACK@[41; 42)
+                IDENT@[42; 45) "doc"
+                TOKEN_TREE@[45; 82)
+                  L_PAREN@[45; 46)
+                  STRING@[46; 81)
+                  R_PAREN@[81; 82)
+                R_BRACK@[82; 83)
+            WHITESPACE@[83; 92)
+            COMMENT@[92; 122)
+            WHITESPACE@[122; 127)
+            R_CURLY@[127; 128)
+        SEMI@[128; 129)
+      WHITESPACE@[129; 134)
+      EXPR_STMT@[134; 257)
+        IF_EXPR@[134; 257)
+          IF_KW@[134; 136)
+          WHITESPACE@[136; 137)
+          CONDITION@[137; 141)
+            LITERAL@[137; 141)
+              TRUE_KW@[137; 141)
+          WHITESPACE@[141; 142)
+          BLOCK@[142; 257)
+            L_CURLY@[142; 143)
+            WHITESPACE@[143; 152)
+            err: `A block in this position cannot accept inner attributes`
+            ATTR@[152; 171)
+              POUND@[152; 153)
+              EXCL@[153; 154)
+              TOKEN_TREE@[154; 171)
+                L_BRACK@[154; 155)
+                IDENT@[155; 158) "doc"
+                TOKEN_TREE@[158; 170)
+                  L_PAREN@[158; 159)
+                  STRING@[159; 169)
+                  R_PAREN@[169; 170)
+                R_BRACK@[170; 171)
+            WHITESPACE@[171; 180)
+            err: `A block in this position cannot accept inner attributes`
+            ATTR@[180; 212)
+              POUND@[180; 181)
+              EXCL@[181; 182)
+              TOKEN_TREE@[182; 212)
+                L_BRACK@[182; 183)
+                IDENT@[183; 186) "doc"
+                TOKEN_TREE@[186; 211)
+                  L_PAREN@[186; 187)
+                  STRING@[187; 210)
+                  R_PAREN@[210; 211)
+                R_BRACK@[211; 212)
+            WHITESPACE@[212; 221)
+            COMMENT@[221; 251)
+            WHITESPACE@[251; 256)
+            R_CURLY@[256; 257)
+      WHITESPACE@[257; 262)
+      WHILE_EXPR@[262; 347)
+        WHILE_KW@[262; 267)
+        WHITESPACE@[267; 268)
+        CONDITION@[268; 272)
+          LITERAL@[268; 272)
+            TRUE_KW@[268; 272)
+        WHITESPACE@[272; 273)
+        BLOCK@[273; 347)
+          L_CURLY@[273; 274)
+          WHITESPACE@[274; 283)
+          err: `A block in this position cannot accept inner attributes`
+          ATTR@[283; 302)
+            POUND@[283; 284)
+            EXCL@[284; 285)
+            TOKEN_TREE@[285; 302)
+              L_BRACK@[285; 286)
+              IDENT@[286; 289) "doc"
+              TOKEN_TREE@[289; 301)
+                L_PAREN@[289; 290)
+                STRING@[290; 300)
+                R_PAREN@[300; 301)
+              R_BRACK@[301; 302)
+          WHITESPACE@[302; 311)
+          COMMENT@[311; 341)
+          WHITESPACE@[341; 346)
+          R_CURLY@[346; 347)
+      WHITESPACE@[347; 348)
+      R_CURLY@[348; 349)
+  WHITESPACE@[349; 350)
diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0118_impl_inner_attributes.rs b/crates/ra_syntax/tests/data/parser/inline/ok/0118_impl_inner_attributes.rs
new file mode 100644
index 0000000000..4d68cceb71
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/inline/ok/0118_impl_inner_attributes.rs
@@ -0,0 +1,5 @@
+enum F{}
+impl F {
+     //! This is a doc comment
+     #![doc("This is also a doc comment")]
+}
diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0118_impl_inner_attributes.txt b/crates/ra_syntax/tests/data/parser/inline/ok/0118_impl_inner_attributes.txt
new file mode 100644
index 0000000000..3b761b7bb7
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/inline/ok/0118_impl_inner_attributes.txt
@@ -0,0 +1,38 @@
+SOURCE_FILE@[0; 94)
+  ENUM_DEF@[0; 8)
+    ENUM_KW@[0; 4)
+    WHITESPACE@[4; 5)
+    NAME@[5; 6)
+      IDENT@[5; 6) "F"
+    ENUM_VARIANT_LIST@[6; 8)
+      L_CURLY@[6; 7)
+      R_CURLY@[7; 8)
+  WHITESPACE@[8; 9)
+  IMPL_BLOCK@[9; 93)
+    IMPL_KW@[9; 13)
+    WHITESPACE@[13; 14)
+    PATH_TYPE@[14; 15)
+      PATH@[14; 15)
+        PATH_SEGMENT@[14; 15)
+          NAME_REF@[14; 15)
+            IDENT@[14; 15) "F"
+    WHITESPACE@[15; 16)
+    ITEM_LIST@[16; 93)
+      L_CURLY@[16; 17)
+      WHITESPACE@[17; 23)
+      COMMENT@[23; 48)
+      WHITESPACE@[48; 54)
+      ATTR@[54; 91)
+        POUND@[54; 55)
+        EXCL@[55; 56)
+        TOKEN_TREE@[56; 91)
+          L_BRACK@[56; 57)
+          IDENT@[57; 60) "doc"
+          TOKEN_TREE@[60; 90)
+            L_PAREN@[60; 61)
+            STRING@[61; 89)
+            R_PAREN@[89; 90)
+          R_BRACK@[90; 91)
+      WHITESPACE@[91; 92)
+      R_CURLY@[92; 93)
+  WHITESPACE@[93; 94)
diff --git a/crates/ra_syntax/tests/data/parser/ok/0045_block_inner_attrs.rs b/crates/ra_syntax/tests/data/parser/ok/0045_block_inner_attrs.rs
new file mode 100644
index 0000000000..88df8138ed
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/ok/0045_block_inner_attrs.rs
@@ -0,0 +1,20 @@
+fn block() {
+    #![doc("Inner attributes allowed here")]
+    //! As are ModuleDoc style comments
+    {
+        #![doc("Inner attributes are allowed in blocks used as statements")]
+        #![doc("Being validated is not affected by duplcates")]
+        //! As are ModuleDoc style comments
+    };
+    {
+        #![doc("Inner attributes are allowed in blocks when they are the last statement of another block")]
+        //! As are ModuleDoc style comments
+    }
+}
+
+// https://github.com/rust-analyzer/rust-analyzer/issues/689
+impl Whatever {
+    fn salsa_event(&self, event_fn: impl Fn() -> Event<Self>) {
+        #![allow(unused_variables)] // this is  `inner_attr` of the block
+    }
+}
diff --git a/crates/ra_syntax/tests/data/parser/ok/0045_block_inner_attrs.txt b/crates/ra_syntax/tests/data/parser/ok/0045_block_inner_attrs.txt
new file mode 100644
index 0000000000..a1ba645ef6
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/ok/0045_block_inner_attrs.txt
@@ -0,0 +1,167 @@
+SOURCE_FILE@[0; 686)
+  FN_DEF@[0; 461)
+    FN_KW@[0; 2)
+    WHITESPACE@[2; 3)
+    NAME@[3; 8)
+      IDENT@[3; 8) "block"
+    PARAM_LIST@[8; 10)
+      L_PAREN@[8; 9)
+      R_PAREN@[9; 10)
+    WHITESPACE@[10; 11)
+    BLOCK@[11; 461)
+      L_CURLY@[11; 12)
+      WHITESPACE@[12; 17)
+      ATTR@[17; 57)
+        POUND@[17; 18)
+        EXCL@[18; 19)
+        TOKEN_TREE@[19; 57)
+          L_BRACK@[19; 20)
+          IDENT@[20; 23) "doc"
+          TOKEN_TREE@[23; 56)
+            L_PAREN@[23; 24)
+            STRING@[24; 55)
+            R_PAREN@[55; 56)
+          R_BRACK@[56; 57)
+      WHITESPACE@[57; 62)
+      COMMENT@[62; 97)
+      WHITESPACE@[97; 102)
+      EXPR_STMT@[102; 295)
+        BLOCK_EXPR@[102; 294)
+          BLOCK@[102; 294)
+            L_CURLY@[102; 103)
+            WHITESPACE@[103; 112)
+            ATTR@[112; 180)
+              POUND@[112; 113)
+              EXCL@[113; 114)
+              TOKEN_TREE@[114; 180)
+                L_BRACK@[114; 115)
+                IDENT@[115; 118) "doc"
+                TOKEN_TREE@[118; 179)
+                  L_PAREN@[118; 119)
+                  STRING@[119; 178)
+                  R_PAREN@[178; 179)
+                R_BRACK@[179; 180)
+            WHITESPACE@[180; 189)
+            ATTR@[189; 244)
+              POUND@[189; 190)
+              EXCL@[190; 191)
+              TOKEN_TREE@[191; 244)
+                L_BRACK@[191; 192)
+                IDENT@[192; 195) "doc"
+                TOKEN_TREE@[195; 243)
+                  L_PAREN@[195; 196)
+                  STRING@[196; 242)
+                  R_PAREN@[242; 243)
+                R_BRACK@[243; 244)
+            WHITESPACE@[244; 253)
+            COMMENT@[253; 288)
+            WHITESPACE@[288; 293)
+            R_CURLY@[293; 294)
+        SEMI@[294; 295)
+      WHITESPACE@[295; 300)
+      BLOCK_EXPR@[300; 459)
+        BLOCK@[300; 459)
+          L_CURLY@[300; 301)
+          WHITESPACE@[301; 310)
+          ATTR@[310; 409)
+            POUND@[310; 311)
+            EXCL@[311; 312)
+            TOKEN_TREE@[312; 409)
+              L_BRACK@[312; 313)
+              IDENT@[313; 316) "doc"
+              TOKEN_TREE@[316; 408)
+                L_PAREN@[316; 317)
+                STRING@[317; 407)
+                R_PAREN@[407; 408)
+              R_BRACK@[408; 409)
+          WHITESPACE@[409; 418)
+          COMMENT@[418; 453)
+          WHITESPACE@[453; 458)
+          R_CURLY@[458; 459)
+      WHITESPACE@[459; 460)
+      R_CURLY@[460; 461)
+  WHITESPACE@[461; 463)
+  COMMENT@[463; 523)
+  WHITESPACE@[523; 524)
+  IMPL_BLOCK@[524; 685)
+    IMPL_KW@[524; 528)
+    WHITESPACE@[528; 529)
+    PATH_TYPE@[529; 537)
+      PATH@[529; 537)
+        PATH_SEGMENT@[529; 537)
+          NAME_REF@[529; 537)
+            IDENT@[529; 537) "Whatever"
+    WHITESPACE@[537; 538)
+    ITEM_LIST@[538; 685)
+      L_CURLY@[538; 539)
+      WHITESPACE@[539; 544)
+      FN_DEF@[544; 683)
+        FN_KW@[544; 546)
+        WHITESPACE@[546; 547)
+        NAME@[547; 558)
+          IDENT@[547; 558) "salsa_event"
+        PARAM_LIST@[558; 601)
+          L_PAREN@[558; 559)
+          SELF_PARAM@[559; 564)
+            AMP@[559; 560)
+            SELF_KW@[560; 564)
+          COMMA@[564; 565)
+          WHITESPACE@[565; 566)
+          PARAM@[566; 600)
+            BIND_PAT@[566; 574)
+              NAME@[566; 574)
+                IDENT@[566; 574) "event_fn"
+            COLON@[574; 575)
+            WHITESPACE@[575; 576)
+            IMPL_TRAIT_TYPE@[576; 600)
+              IMPL_KW@[576; 580)
+              WHITESPACE@[580; 581)
+              PATH_TYPE@[581; 600)
+                PATH@[581; 600)
+                  PATH_SEGMENT@[581; 600)
+                    NAME_REF@[581; 583)
+                      IDENT@[581; 583) "Fn"
+                    PARAM_LIST@[583; 585)
+                      L_PAREN@[583; 584)
+                      R_PAREN@[584; 585)
+                    WHITESPACE@[585; 586)
+                    RET_TYPE@[586; 600)
+                      THIN_ARROW@[586; 588)
+                      WHITESPACE@[588; 589)
+                      PATH_TYPE@[589; 600)
+                        PATH@[589; 600)
+                          PATH_SEGMENT@[589; 600)
+                            NAME_REF@[589; 594)
+                              IDENT@[589; 594) "Event"
+                            TYPE_ARG_LIST@[594; 600)
+                              L_ANGLE@[594; 595)
+                              TYPE_ARG@[595; 599)
+                                PATH_TYPE@[595; 599)
+                                  PATH@[595; 599)
+                                    PATH_SEGMENT@[595; 599)
+                                      NAME_REF@[595; 599)
+                                        IDENT@[595; 599) "Self"
+                              R_ANGLE@[599; 600)
+          R_PAREN@[600; 601)
+        WHITESPACE@[601; 602)
+        BLOCK@[602; 683)
+          L_CURLY@[602; 603)
+          WHITESPACE@[603; 612)
+          ATTR@[612; 639)
+            POUND@[612; 613)
+            EXCL@[613; 614)
+            TOKEN_TREE@[614; 639)
+              L_BRACK@[614; 615)
+              IDENT@[615; 620) "allow"
+              TOKEN_TREE@[620; 638)
+                L_PAREN@[620; 621)
+                IDENT@[621; 637) "unused_variables"
+                R_PAREN@[637; 638)
+              R_BRACK@[638; 639)
+          WHITESPACE@[639; 640)
+          COMMENT@[640; 677)
+          WHITESPACE@[677; 682)
+          R_CURLY@[682; 683)
+      WHITESPACE@[683; 684)
+      R_CURLY@[684; 685)
+  WHITESPACE@[685; 686)
diff --git a/crates/ra_syntax/tests/data/parser/ok/0045_extern_inner_attributes.rs b/crates/ra_syntax/tests/data/parser/ok/0045_extern_inner_attributes.rs
new file mode 100644
index 0000000000..fe67e2df43
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/ok/0045_extern_inner_attributes.rs
@@ -0,0 +1,4 @@
+extern "C" {
+    //! This is a doc comment
+    #![doc("This is also a doc comment")]
+}
diff --git a/crates/ra_syntax/tests/data/parser/ok/0045_extern_inner_attributes.txt b/crates/ra_syntax/tests/data/parser/ok/0045_extern_inner_attributes.txt
new file mode 100644
index 0000000000..c68e1b271f
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/ok/0045_extern_inner_attributes.txt
@@ -0,0 +1,26 @@
+SOURCE_FILE@[0; 87)
+  EXTERN_BLOCK@[0; 86)
+    ABI@[0; 10)
+      EXTERN_KW@[0; 6)
+      WHITESPACE@[6; 7)
+      STRING@[7; 10)
+    WHITESPACE@[10; 11)
+    EXTERN_ITEM_LIST@[11; 86)
+      L_CURLY@[11; 12)
+      WHITESPACE@[12; 17)
+      COMMENT@[17; 42)
+      WHITESPACE@[42; 47)
+      ATTR@[47; 84)
+        POUND@[47; 48)
+        EXCL@[48; 49)
+        TOKEN_TREE@[49; 84)
+          L_BRACK@[49; 50)
+          IDENT@[50; 53) "doc"
+          TOKEN_TREE@[53; 83)
+            L_PAREN@[53; 54)
+            STRING@[54; 82)
+            R_PAREN@[82; 83)
+          R_BRACK@[83; 84)
+      WHITESPACE@[84; 85)
+      R_CURLY@[85; 86)
+  WHITESPACE@[86; 87)