Repository: rust-analyzer/rust-analyzer
Pull request #3119 (2020-02-12 09:04:55)

Add error context to failures in `ra_project_model` using `anyhow` crate
Addresses https://github.com/rust-analyzer/rust-analyzer/issues/3085

matklad: Looks like CI is failing due to `-D warnings`?
adamrk: > Looks like CI is failing due to `-D warnings`?

My mistake - just mixed up changes addressing separate comments from the review.

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 45804c087e..f1651edaad 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1247,6 +1247,7 @@ dependencies = [
 name = "ra_project_model"
 version = "0.1.0"
 dependencies = [
+ "anyhow",
  "cargo_metadata",
  "log",
  "ra_arena",
diff --git a/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
index b70c88ec29..eac4524134 100644
--- a/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
@@ -431,7 +431,12 @@ fn best_action_for_target(
                 .find(|n| n.text_range().start() < anchor.text_range().start())
                 .or_else(|| Some(anchor));
 
-            ImportAction::add_new_use(anchor, false)
+            let add_after_anchor = anchor
+                .clone()
+                .and_then(ast::Attr::cast)
+                .map(|attr| attr.kind() == ast::AttrKind::Inner)
+                .unwrap_or(false);
+            ImportAction::add_new_use(anchor, add_after_anchor)
         }
     }
 }
@@ -958,6 +963,28 @@ mod foo {
 
         Debug<|>
     }
+}
+    ",
+        );
+    }
+
+    #[test]
+    fn inserts_imports_after_inner_attributes() {
+        check_assist(
+            replace_qualified_name_with_use,
+            "
+#![allow(dead_code)]
+
+fn main() {
+    std::fmt::Debug<|>
+}
+    ",
+            "
+#![allow(dead_code)]
+use std::fmt::Debug;
+
+fn main() {
+    Debug<|>
 }
     ",
         );
diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs
index 4d96417285..4fb679f6dd 100644
--- a/crates/ra_hir/src/code_model.rs
+++ b/crates/ra_hir/src/code_model.rs
@@ -10,9 +10,9 @@ use hir_def::{
     per_ns::PerNs,
     resolver::HasResolver,
     type_ref::{Mutability, TypeRef},
-    AdtId, ConstId, DefWithBodyId, EnumId, FunctionId, GenericDefId, HasModule, ImplId,
-    LocalEnumVariantId, LocalModuleId, LocalStructFieldId, Lookup, ModuleId, StaticId, StructId,
-    TraitId, TypeAliasId, TypeParamId, UnionId,
+    AdtId, AssocContainerId, ConstId, DefWithBodyId, EnumId, FunctionId, GenericDefId, HasModule,
+    ImplId, LocalEnumVariantId, LocalModuleId, LocalStructFieldId, Lookup, ModuleId, StaticId,
+    StructId, TraitId, TypeAliasId, TypeParamId, UnionId,
 };
 use hir_expand::{
     diagnostics::DiagnosticSink,
@@ -25,7 +25,10 @@ use hir_ty::{
 };
 use ra_db::{CrateId, Edition, FileId};
 use ra_prof::profile;
-use ra_syntax::ast::{self, AttrsOwner};
+use ra_syntax::{
+    ast::{self, AttrsOwner},
+    AstNode,
+};
 
 use crate::{
     db::{DefDatabase, HirDatabase},
@@ -119,7 +122,9 @@ impl_froms!(
     BuiltinType
 );
 
-pub use hir_def::{attr::Attrs, item_scope::ItemInNs, visibility::Visibility, AssocItemId};
+pub use hir_def::{
+    attr::Attrs, item_scope::ItemInNs, visibility::Visibility, AssocItemId, AssocItemLoc,
+};
 use rustc_hash::FxHashSet;
 
 impl Module {
@@ -639,17 +644,49 @@ pub struct MacroDef {
     pub(crate) id: MacroDefId,
 }
 
+/// Invariant: `inner.as_assoc_item(db).is_some()`
+/// We do not actively enforce this invariant.
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub enum AssocItem {
     Function(Function),
     Const(Const),
     TypeAlias(TypeAlias),
 }
-// FIXME: not every function, ... is actually an assoc item. maybe we should make
-// sure that you can only turn actual assoc items into AssocItems. This would
-// require not implementing From, and instead having some checked way of
-// casting them, and somehow making the constructors private, which would be annoying.
-impl_froms!(AssocItem: Function, Const, TypeAlias);
+pub enum AssocItemContainer {
+    Trait(Trait),
+    ImplBlock(ImplBlock),
+}
+pub trait AsAssocItem {
+    fn as_assoc_item(self, db: &impl DefDatabase) -> Option<AssocItem>;
+}
+
+impl AsAssocItem for Function {
+    fn as_assoc_item(self, db: &impl DefDatabase) -> Option<AssocItem> {
+        as_assoc_item(db, AssocItem::Function, self.id)
+    }
+}
+impl AsAssocItem for Const {
+    fn as_assoc_item(self, db: &impl DefDatabase) -> Option<AssocItem> {
+        as_assoc_item(db, AssocItem::Const, self.id)
+    }
+}
+impl AsAssocItem for TypeAlias {
+    fn as_assoc_item(self, db: &impl DefDatabase) -> Option<AssocItem> {
+        as_assoc_item(db, AssocItem::TypeAlias, self.id)
+    }
+}
+fn as_assoc_item<ID, DEF, CTOR, AST>(db: &impl DefDatabase, ctor: CTOR, id: ID) -> Option<AssocItem>
+where
+    ID: Lookup<Data = AssocItemLoc<AST>>,
+    DEF: From<ID>,
+    CTOR: FnOnce(DEF) -> AssocItem,
+    AST: AstNode,
+{
+    match id.lookup(db).container {
+        AssocContainerId::TraitId(_) | AssocContainerId::ImplId(_) => Some(ctor(DEF::from(id))),
+        AssocContainerId::ContainerId(_) => None,
+    }
+}
 
 impl AssocItem {
     pub fn module(self, db: &impl DefDatabase) -> Module {
@@ -659,6 +696,18 @@ impl AssocItem {
             AssocItem::TypeAlias(t) => t.module(db),
         }
     }
+    pub fn container(self, db: &impl DefDatabase) -> AssocItemContainer {
+        let container = match self {
+            AssocItem::Function(it) => it.id.lookup(db).container,
+            AssocItem::Const(it) => it.id.lookup(db).container,
+            AssocItem::TypeAlias(it) => it.id.lookup(db).container,
+        };
+        match container {
+            AssocContainerId::TraitId(id) => AssocItemContainer::Trait(id.into()),
+            AssocContainerId::ImplId(id) => AssocItemContainer::ImplBlock(id.into()),
+            AssocContainerId::ContainerId(_) => panic!("invalid AssocItem"),
+        }
+    }
 }
 
 #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
@@ -769,6 +818,7 @@ impl TypeParam {
     }
 }
 
+// FIXME: rename to `ImplBlock`
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 pub struct ImplBlock {
     pub(crate) id: ImplId,
diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs
index e1c7b7a202..5cd965f7a4 100644
--- a/crates/ra_hir/src/lib.rs
+++ b/crates/ra_hir/src/lib.rs
@@ -39,10 +39,10 @@ mod has_source;
 
 pub use crate::{
     code_model::{
-        Adt, AssocItem, AttrDef, Const, Crate, CrateDependency, DefWithBody, Docs, Enum,
-        EnumVariant, FieldSource, Function, GenericDef, HasAttrs, HasVisibility, ImplBlock, Local,
-        MacroDef, Module, ModuleDef, ScopeDef, Static, Struct, StructField, Trait, Type, TypeAlias,
-        TypeParam, Union, VariantDef,
+        Adt, AsAssocItem, AssocItem, AssocItemContainer, AttrDef, Const, Crate, CrateDependency,
+        DefWithBody, Docs, Enum, EnumVariant, FieldSource, Function, GenericDef, HasAttrs,
+        HasVisibility, ImplBlock, Local, MacroDef, Module, ModuleDef, ScopeDef, Static, Struct,
+        StructField, Trait, Type, TypeAlias, TypeParam, Union, VariantDef,
     },
     has_source::HasSource,
     source_analyzer::{PathResolution, ScopeEntryWithSyntax, SourceAnalyzer},
diff --git a/crates/ra_hir_expand/src/lib.rs b/crates/ra_hir_expand/src/lib.rs
index 7cf3b59a79..9506f2e1cd 100644
--- a/crates/ra_hir_expand/src/lib.rs
+++ b/crates/ra_hir_expand/src/lib.rs
@@ -323,11 +323,18 @@ impl<T: Clone> InFile<&T> {
     }
 }
 
+impl<T> InFile<Option<T>> {
+    pub fn transpose(self) -> Option<InFile<T>> {
+        let value = self.value?;
+        Some(InFile::new(self.file_id, value))
+    }
+}
+
 impl InFile<SyntaxNode> {
-    pub fn ancestors_with_macros<'a>(
+    pub fn ancestors_with_macros(
         self,
-        db: &'a impl crate::db::AstDatabase,
-    ) -> impl Iterator<Item = InFile<SyntaxNode>> + 'a {
+        db: &impl crate::db::AstDatabase,
+    ) -> impl Iterator<Item = InFile<SyntaxNode>> + '_ {
         std::iter::successors(Some(self), move |node| match node.value.parent() {
             Some(parent) => Some(node.with_value(parent)),
             None => {
@@ -338,6 +345,15 @@ impl InFile<SyntaxNode> {
     }
 }
 
+impl InFile<SyntaxToken> {
+    pub fn ancestors_with_macros(
+        self,
+        db: &impl crate::db::AstDatabase,
+    ) -> impl Iterator<Item = InFile<SyntaxNode>> + '_ {
+        self.map(|it| it.parent()).ancestors_with_macros(db)
+    }
+}
+
 impl<N: AstNode> InFile<N> {
     pub fn descendants<T: AstNode>(self) -> impl Iterator<Item = InFile<T>> {
         self.value.syntax().descendants().filter_map(T::cast).map(move |n| self.with_value(n))
diff --git a/crates/ra_ide/src/goto_type_definition.rs b/crates/ra_ide/src/goto_type_definition.rs
index 11ad6d1372..69940fc36a 100644
--- a/crates/ra_ide/src/goto_type_definition.rs
+++ b/crates/ra_ide/src/goto_type_definition.rs
@@ -16,24 +16,16 @@ pub(crate) fn goto_type_definition(
     let token = pick_best(file.token_at_offset(position.offset))?;
     let token = descend_into_macros(db, position.file_id, token);
 
-    let node = token.value.ancestors().find_map(|token| {
-        token
-            .ancestors()
-            .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())
-    })?;
+    let node = token
+        .value
+        .ancestors()
+        .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?;
 
     let analyzer = hir::SourceAnalyzer::new(db, token.with_value(&node), None);
 
-    let ty: hir::Type = if let Some(ty) =
-        ast::Expr::cast(node.clone()).and_then(|e| analyzer.type_of(db, &e))
-    {
-        ty
-    } else if let Some(ty) = ast::Pat::cast(node.clone()).and_then(|p| analyzer.type_of_pat(db, &p))
-    {
-        ty
-    } else {
-        return None;
-    };
+    let ty: hir::Type = ast::Expr::cast(node.clone())
+        .and_then(|e| analyzer.type_of(db, &e))
+        .or_else(|| ast::Pat::cast(node.clone()).and_then(|p| analyzer.type_of_pat(db, &p)))?;
 
     let adt_def = ty.autoderef(db).find_map(|ty| ty.as_adt())?;
 
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index 59c86bbfa4..2e598fdcdf 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -758,7 +758,7 @@ pub fn handle_code_lens(
     // Gather runnables
     for runnable in world.analysis().runnables(file_id)? {
         let title = match &runnable.kind {
-            RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => "▶️Run Test",
+            RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => "▶️\u{fe0e}Run Test",
             RunnableKind::Bench { .. } => "Run Bench",
             RunnableKind::Bin => "Run",
         }
diff --git a/crates/ra_parser/src/grammar/params.rs b/crates/ra_parser/src/grammar/params.rs
index ed4f93347a..272661b1da 100644
--- a/crates/ra_parser/src/grammar/params.rs
+++ b/crates/ra_parser/src/grammar/params.rs
@@ -114,8 +114,11 @@ fn value_parameter(p: &mut Parser, flavor: Flavor) {
         // test fn_pointer_param_ident_path
         // type Foo = fn(Bar::Baz);
         // type Qux = fn(baz: Bar::Baz);
+
+        // test fn_pointer_unnamed_arg
+        // type Foo = fn(_: bar);
         Flavor::FnPointer => {
-            if p.at(IDENT) && p.nth(1) == T![:] && !p.nth_at(1, T![::]) {
+            if (p.at(IDENT) || p.at(UNDERSCORE)) && p.nth(1) == T![:] && !p.nth_at(1, T![::]) {
                 patterns::pattern_single(p);
                 types::ascription(p);
             } else {
diff --git a/crates/ra_project_model/Cargo.toml b/crates/ra_project_model/Cargo.toml
index 69edc3c66b..653d5bd145 100644
--- a/crates/ra_project_model/Cargo.toml
+++ b/crates/ra_project_model/Cargo.toml
@@ -19,3 +19,5 @@ ra_cfg = { path = "../ra_cfg" }
 
 serde = { version = "1.0.89", features = ["derive"] }
 serde_json = "1.0.39"
+
+anyhow = "1.0.26"
diff --git a/crates/ra_project_model/src/cargo_workspace.rs b/crates/ra_project_model/src/cargo_workspace.rs
index 60cb8c1eb2..22d226a745 100644
--- a/crates/ra_project_model/src/cargo_workspace.rs
+++ b/crates/ra_project_model/src/cargo_workspace.rs
@@ -2,14 +2,13 @@
 
 use std::path::{Path, PathBuf};
 
+use anyhow::{Context, Result};
 use cargo_metadata::{CargoOpt, MetadataCommand};
 use ra_arena::{impl_arena_id, Arena, RawId};
 use ra_db::Edition;
 use rustc_hash::FxHashMap;
 use serde::Deserialize;
 
-use crate::Result;
-
 /// `CargoWorkspace` represents the logical structure of, well, a Cargo
 /// workspace. It pretty closely mirrors `cargo metadata` output.
 ///
@@ -171,7 +170,9 @@ impl CargoWorkspace {
         if let Some(parent) = cargo_toml.parent() {
             meta.current_dir(parent);
         }
-        let meta = meta.exec().map_err(|e| format!("cargo metadata failed: {}", e))?;
+        let meta = meta.exec().with_context(|| {
+            format!("Failed to run `cargo metadata --manifest-path {}`", cargo_toml.display())
+        })?;
         let mut pkg_by_id = FxHashMap::default();
         let mut packages = Arena::default();
         let mut targets = Arena::default();
@@ -181,7 +182,9 @@ impl CargoWorkspace {
         for meta_pkg in meta.packages {
             let cargo_metadata::Package { id, edition, name, manifest_path, .. } = meta_pkg;
             let is_member = ws_members.contains(&id);
-            let edition = edition.parse::<Edition>()?;
+            let edition = edition
+                .parse::<Edition>()
+                .with_context(|| format!("Failed to parse edition {}", edition))?;
             let pkg = packages.alloc(PackageData {
                 name,
                 manifest: manifest_path,
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs
index bc1d15406b..fef405b7f5 100644
--- a/crates/ra_project_model/src/lib.rs
+++ b/crates/ra_project_model/src/lib.rs
@@ -12,6 +12,7 @@ use std::{
     process::Command,
 };
 
+use anyhow::{bail, Context, Result};
 use ra_cfg::CfgOptions;
 use ra_db::{CrateGraph, CrateId, CrateName, Edition, Env, FileId};
 use rustc_hash::FxHashMap;
@@ -23,8 +24,6 @@ pub use crate::{
     sysroot::Sysroot,
 };
 
-pub type Result<T> = ::std::result::Result<T, Box<dyn Error + Send + Sync>>;
-
 #[derive(Clone, PartialEq, Eq, Hash, Debug)]
 pub struct CargoTomlNotFoundError(pub PathBuf);
 
@@ -81,15 +80,36 @@ impl ProjectWorkspace {
     ) -> Result<ProjectWorkspace> {
         match find_rust_project_json(path) {
             Some(json_path) => {
-                let file = File::open(json_path)?;
+                let file = File::open(&json_path)
+                    .with_context(|| format!("Failed to open json file {}", json_path.display()))?;
                 let reader = BufReader::new(file);
-                Ok(ProjectWorkspace::Json { project: from_reader(reader)? })
+                Ok(ProjectWorkspace::Json {
+                    project: from_reader(reader).with_context(|| {
+                        format!("Failed to deserialize json file {}", json_path.display())
+                    })?,
+                })
             }
             None => {
-                let cargo_toml = find_cargo_toml(path)?;
-                let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features)?;
-                let sysroot =
-                    if with_sysroot { Sysroot::discover(&cargo_toml)? } else { Sysroot::default() };
+                let cargo_toml = find_cargo_toml(path).with_context(|| {
+                    format!("Failed to find Cargo.toml for path {}", path.display())
+                })?;
+                let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features)
+                    .with_context(|| {
+                        format!(
+                            "Failed to read Cargo metadata from Cargo.toml file {}",
+                            cargo_toml.display()
+                        )
+                    })?;
+                let sysroot = if with_sysroot {
+                    Sysroot::discover(&cargo_toml).with_context(|| {
+                        format!(
+                            "Failed to find sysroot for Cargo.toml file {}",
+                            cargo_toml.display()
+                        )
+                    })?
+                } else {
+                    Sysroot::default()
+                };
                 Ok(ProjectWorkspace::Cargo { cargo, sysroot })
             }
         }
@@ -403,11 +423,20 @@ pub fn get_rustc_cfg_options() -> CfgOptions {
         }
     }
 
-    match (|| -> Result<_> {
+    match (|| -> Result<String> {
         // `cfg(test)` and `cfg(debug_assertion)` are handled outside, so we suppress them here.
-        let output = Command::new("rustc").args(&["--print", "cfg", "-O"]).output()?;
+        let output = Command::new("rustc")
+            .args(&["--print", "cfg", "-O"])
+            .output()
+            .context("Failed to get output from rustc --print cfg -O")?;
         if !output.status.success() {
-            Err("failed to get rustc cfgs")?;
+            bail!(
+                "rustc --print cfg -O exited with exit code ({})",
+                output
+                    .status
+                    .code()
+                    .map_or(String::from("no exit code"), |code| format!("{}", code))
+            );
         }
         Ok(String::from_utf8(output.stdout)?)
     })() {
diff --git a/crates/ra_project_model/src/sysroot.rs b/crates/ra_project_model/src/sysroot.rs
index a23265fc08..7b9cc899c0 100644
--- a/crates/ra_project_model/src/sysroot.rs
+++ b/crates/ra_project_model/src/sysroot.rs
@@ -1,5 +1,6 @@
 //! FIXME: write short doc here
 
+use anyhow::{anyhow, bail, Context, Result};
 use std::{
     env,
     path::{Path, PathBuf},
@@ -8,8 +9,6 @@ use std::{
 
 use ra_arena::{impl_arena_id, Arena, RawId};
 
-use crate::Result;
-
 #[derive(Default, Debug, Clone)]
 pub struct Sysroot {
     crates: Arena<SysrootCrate, SysrootCrateData>,
@@ -51,7 +50,7 @@ impl Sysroot {
         let src = try_find_src_path(cargo_toml)?;
 
         if !src.exists() {
-            Err(format!(
+            Err(anyhow!(
                 "can't load standard library from sysroot\n\
                  {}\n\
                  (discovered via `rustc --print sysroot`)\n\
@@ -100,9 +99,14 @@ fn try_find_src_path(cargo_toml: &Path) -> Result<PathBuf> {
         .current_dir(cargo_toml.parent().unwrap())
         .args(&["--print", "sysroot"])
         .output()
-        .map_err(|e| format!("rustc --print sysroot failed: {}", e))?;
+        .context("rustc --print sysroot failed")?;
     if !rustc_output.status.success() {
-        Err("failed to locate sysroot")?;
+        match rustc_output.status.code() {
+            Some(code) => {
+                bail!("failed to locate sysroot: rustc --print sysroot exited with code {}", code)
+            }
+            None => bail!("failed to locate sysroot: rustc --print sysroot terminated by signal"),
+        };
     }
     let stdout = String::from_utf8(rustc_output.stdout)?;
     let sysroot_path = Path::new(stdout.trim());
diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs
index d3e8888bd5..9cc7930f74 100644
--- a/crates/ra_syntax/src/ast.rs
+++ b/crates/ra_syntax/src/ast.rs
@@ -18,8 +18,8 @@ use crate::{
 pub use self::{
     expr_extensions::{ArrayExprKind, BinOp, ElseBranch, LiteralKind, PrefixOp, RangeOp},
     extensions::{
-        FieldKind, PathSegmentKind, SelfParamKind, SlicePatComponents, StructKind, TypeBoundKind,
-        VisibilityKind,
+        AttrKind, FieldKind, PathSegmentKind, SelfParamKind, SlicePatComponents, StructKind,
+        TypeBoundKind, VisibilityKind,
     },
     generated::*,
     tokens::*,
@@ -217,10 +217,7 @@ fn test_doc_comment_multi_line_block_strips_suffix() {
 #[test]
 fn test_comments_preserve_trailing_whitespace() {
     let file = SourceFile::parse(
-        r#"
-/// Representation of a Realm.   
-/// In the specification these are called Realm Records.
-struct Realm {}"#,
+        "\n/// Representation of a Realm.   \n/// In the specification these are called Realm Records.\nstruct Realm {}",
     )
     .ok()
     .unwrap();
diff --git a/crates/ra_syntax/src/ast/extensions.rs b/crates/ra_syntax/src/ast/extensions.rs
index 7dcf084de9..44de4af892 100644
--- a/crates/ra_syntax/src/ast/extensions.rs
+++ b/crates/ra_syntax/src/ast/extensions.rs
@@ -37,6 +37,12 @@ fn text_of_first_token(node: &SyntaxNode) -> &SmolStr {
     node.green().children().next().and_then(|it| it.into_token()).unwrap().text()
 }
 
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum AttrKind {
+    Inner,
+    Outer,
+}
+
 impl ast::Attr {
     pub fn as_simple_atom(&self) -> Option<SmolStr> {
         match self.input() {
@@ -71,6 +77,18 @@ impl ast::Attr {
             _ => None,
         }
     }
+
+    pub fn kind(&self) -> AttrKind {
+        let first_token = self.syntax().first_token();
+        let first_token_kind = first_token.as_ref().map(SyntaxToken::kind);
+        let second_token_kind =
+            first_token.and_then(|token| token.next_token()).as_ref().map(SyntaxToken::kind);
+
+        match (first_token_kind, second_token_kind) {
+            (Some(SyntaxKind::POUND), Some(SyntaxKind::EXCL)) => AttrKind::Inner,
+            _ => AttrKind::Outer,
+        }
+    }
 }
 
 #[derive(Debug, Clone, PartialEq, Eq)]


Test patch:
diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0157_fn_pointer_unnamed_arg.rs b/crates/ra_syntax/test_data/parser/inline/ok/0157_fn_pointer_unnamed_arg.rs
new file mode 100644
index 0000000000..1ebbe5b035
--- /dev/null
+++ b/crates/ra_syntax/test_data/parser/inline/ok/0157_fn_pointer_unnamed_arg.rs
@@ -0,0 +1,1 @@
+type Foo = fn(_: bar);
diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0157_fn_pointer_unnamed_arg.txt b/crates/ra_syntax/test_data/parser/inline/ok/0157_fn_pointer_unnamed_arg.txt
new file mode 100644
index 0000000000..52d8f21a47
--- /dev/null
+++ b/crates/ra_syntax/test_data/parser/inline/ok/0157_fn_pointer_unnamed_arg.txt
@@ -0,0 +1,26 @@
+SOURCE_FILE@[0; 23)
+  TYPE_ALIAS_DEF@[0; 22)
+    TYPE_KW@[0; 4) "type"
+    WHITESPACE@[4; 5) " "
+    NAME@[5; 8)
+      IDENT@[5; 8) "Foo"
+    WHITESPACE@[8; 9) " "
+    EQ@[9; 10) "="
+    WHITESPACE@[10; 11) " "
+    FN_POINTER_TYPE@[11; 21)
+      FN_KW@[11; 13) "fn"
+      PARAM_LIST@[13; 21)
+        L_PAREN@[13; 14) "("
+        PARAM@[14; 20)
+          PLACEHOLDER_PAT@[14; 15)
+            UNDERSCORE@[14; 15) "_"
+          COLON@[15; 16) ":"
+          WHITESPACE@[16; 17) " "
+          PATH_TYPE@[17; 20)
+            PATH@[17; 20)
+              PATH_SEGMENT@[17; 20)
+                NAME_REF@[17; 20)
+                  IDENT@[17; 20) "bar"
+        R_PAREN@[20; 21) ")"
+    SEMI@[21; 22) ";"
+  WHITESPACE@[22; 23) "\n"