Repository: rust-lang/rust-analyzer
Pull request #17016 (2024-04-04 23:12:27)

Apply cargo flags in test explorer
cc #16827 


Patch:
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 3ad7a37ec1..11014338d7 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -95,7 +95,7 @@ jobs:
         run: sudo apt-get install gcc-arm-linux-gnueabihf
 
       - name: Dist
-        run: cargo xtask dist --jemalloc --client-patch-version ${{ github.run_number }}
+        run: cargo xtask dist --client-patch-version ${{ github.run_number }}
 
       - run: npm ci
         working-directory: editors/code
@@ -159,7 +159,7 @@ jobs:
           fetch-depth: ${{ env.FETCH_DEPTH }}
 
       - name: Dist
-        run: cargo xtask dist --jemalloc --client-patch-version ${{ github.run_number }}
+        run: cargo xtask dist --client-patch-version ${{ github.run_number }}
 
       - run: npm ci
         working-directory: editors/code
diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs
index 4ee86954ac..fe8e882bf6 100644
--- a/crates/flycheck/src/lib.rs
+++ b/crates/flycheck/src/lib.rs
@@ -41,19 +41,50 @@ pub enum InvocationLocation {
     Workspace,
 }
 
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct CargoOptions {
+    pub target_triples: Vec<String>,
+    pub all_targets: bool,
+    pub no_default_features: bool,
+    pub all_features: bool,
+    pub features: Vec<String>,
+    pub extra_args: Vec<String>,
+    pub extra_env: FxHashMap<String, String>,
+    pub target_dir: Option<Utf8PathBuf>,
+}
+
+impl CargoOptions {
+    fn apply_on_command(&self, cmd: &mut Command) {
+        for target in &self.target_triples {
+            cmd.args(["--target", target.as_str()]);
+        }
+        if self.all_targets {
+            cmd.arg("--all-targets");
+        }
+        if self.all_features {
+            cmd.arg("--all-features");
+        } else {
+            if self.no_default_features {
+                cmd.arg("--no-default-features");
+            }
+            if !self.features.is_empty() {
+                cmd.arg("--features");
+                cmd.arg(self.features.join(" "));
+            }
+        }
+        if let Some(target_dir) = &self.target_dir {
+            cmd.arg("--target-dir").arg(target_dir);
+        }
+        cmd.envs(&self.extra_env);
+    }
+}
+
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub enum FlycheckConfig {
     CargoCommand {
         command: String,
-        target_triples: Vec<String>,
-        all_targets: bool,
-        no_default_features: bool,
-        all_features: bool,
-        features: Vec<String>,
-        extra_args: Vec<String>,
-        extra_env: FxHashMap<String, String>,
+        options: CargoOptions,
         ansi_color_output: bool,
-        target_dir: Option<Utf8PathBuf>,
     },
     CustomCommand {
         command: String,
@@ -332,18 +363,7 @@ impl FlycheckActor {
         saved_file: Option<&AbsPath>,
     ) -> Option<Command> {
         let (mut cmd, args) = match &self.config {
-            FlycheckConfig::CargoCommand {
-                command,
-                target_triples,
-                no_default_features,
-                all_targets,
-                all_features,
-                extra_args,
-                features,
-                extra_env,
-                ansi_color_output,
-                target_dir,
-            } => {
+            FlycheckConfig::CargoCommand { command, options, ansi_color_output } => {
                 let mut cmd = Command::new(Tool::Cargo.path());
                 if let Some(sysroot_root) = &self.sysroot_root {
                     cmd.env("RUSTUP_TOOLCHAIN", AsRef::<std::path::Path>::as_ref(sysroot_root));
@@ -365,28 +385,8 @@ impl FlycheckActor {
                 cmd.arg("--manifest-path");
                 cmd.arg(self.root.join("Cargo.toml"));
 
-                for target in target_triples {
-                    cmd.args(["--target", target.as_str()]);
-                }
-                if *all_targets {
-                    cmd.arg("--all-targets");
-                }
-                if *all_features {
-                    cmd.arg("--all-features");
-                } else {
-                    if *no_default_features {
-                        cmd.arg("--no-default-features");
-                    }
-                    if !features.is_empty() {
-                        cmd.arg("--features");
-                        cmd.arg(features.join(" "));
-                    }
-                }
-                if let Some(target_dir) = target_dir {
-                    cmd.arg("--target-dir").arg(target_dir);
-                }
-                cmd.envs(extra_env);
-                (cmd, extra_args.clone())
+                options.apply_on_command(&mut cmd);
+                (cmd, options.extra_args.clone())
             }
             FlycheckConfig::CustomCommand {
                 command,
diff --git a/crates/hir-def/src/attr.rs b/crates/hir-def/src/attr.rs
index f4f78ce6a4..fa7730f302 100644
--- a/crates/hir-def/src/attr.rs
+++ b/crates/hir-def/src/attr.rs
@@ -141,10 +141,6 @@ impl Attrs {
         }
     }
 
-    pub fn cfgs(&self) -> impl Iterator<Item = CfgExpr> + '_ {
-        self.by_key("cfg").tt_values().map(CfgExpr::parse)
-    }
-
     pub(crate) fn is_cfg_enabled(&self, cfg_options: &CfgOptions) -> bool {
         match self.cfg() {
             None => true,
diff --git a/crates/hir-expand/src/cfg_process.rs b/crates/hir-expand/src/cfg_process.rs
index f37ce8ba6d..db3558a84e 100644
--- a/crates/hir-expand/src/cfg_process.rs
+++ b/crates/hir-expand/src/cfg_process.rs
@@ -1,59 +1,57 @@
 //! Processes out #[cfg] and #[cfg_attr] attributes from the input for the derive macro
 use std::iter::Peekable;
 
-use base_db::CrateId;
 use cfg::{CfgAtom, CfgExpr};
 use rustc_hash::FxHashSet;
 use syntax::{
     ast::{self, Attr, HasAttrs, Meta, VariantList},
-    AstNode, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, T,
+    AstNode, NodeOrToken, SyntaxElement, SyntaxNode, T,
 };
 use tracing::{debug, warn};
 use tt::SmolStr;
 
 use crate::{db::ExpandDatabase, proc_macro::ProcMacroKind, MacroCallLoc, MacroDefKind};
 
-fn check_cfg(db: &dyn ExpandDatabase, attr: &Attr, krate: CrateId) -> Option<bool> {
+fn check_cfg_attr(attr: &Attr, loc: &MacroCallLoc, db: &dyn ExpandDatabase) -> Option<bool> {
     if !attr.simple_name().as_deref().map(|v| v == "cfg")? {
         return None;
     }
+    debug!("Evaluating cfg {}", attr);
     let cfg = parse_from_attr_meta(attr.meta()?)?;
-    let enabled = db.crate_graph()[krate].cfg_options.check(&cfg) != Some(false);
+    debug!("Checking cfg {:?}", cfg);
+    let enabled = db.crate_graph()[loc.krate].cfg_options.check(&cfg) != Some(false);
     Some(enabled)
 }
 
-fn check_cfg_attr(db: &dyn ExpandDatabase, attr: &Attr, krate: CrateId) -> Option<bool> {
+fn check_cfg_attr_attr(attr: &Attr, loc: &MacroCallLoc, db: &dyn ExpandDatabase) -> Option<bool> {
     if !attr.simple_name().as_deref().map(|v| v == "cfg_attr")? {
         return None;
     }
+    debug!("Evaluating cfg_attr {}", attr);
     let cfg_expr = parse_from_attr_meta(attr.meta()?)?;
-    let enabled = db.crate_graph()[krate].cfg_options.check(&cfg_expr) != Some(false);
+    debug!("Checking cfg_attr {:?}", cfg_expr);
+    let enabled = db.crate_graph()[loc.krate].cfg_options.check(&cfg_expr) != Some(false);
     Some(enabled)
 }
 
 fn process_has_attrs_with_possible_comma<I: HasAttrs>(
-    db: &dyn ExpandDatabase,
     items: impl Iterator<Item = I>,
-    krate: CrateId,
+    loc: &MacroCallLoc,
+    db: &dyn ExpandDatabase,
     remove: &mut FxHashSet<SyntaxElement>,
 ) -> Option<()> {
     for item in items {
         let field_attrs = item.attrs();
         'attrs: for attr in field_attrs {
-            if let Some(enabled) = check_cfg(db, &attr, krate) {
-                if enabled {
-                    debug!("censoring {:?}", attr.syntax());
-                    remove.insert(attr.syntax().clone().into());
-                } else {
-                    debug!("censoring {:?}", item.syntax());
-                    remove.insert(item.syntax().clone().into());
-                    // We need to remove the , as well
-                    remove_possible_comma(&item, remove);
-                    break 'attrs;
-                }
+            if check_cfg_attr(&attr, loc, db).map(|enabled| !enabled).unwrap_or_default() {
+                debug!("censoring type {:?}", item.syntax());
+                remove.insert(item.syntax().clone().into());
+                // We need to remove the , as well
+                remove_possible_comma(&item, remove);
+                break 'attrs;
             }
 
-            if let Some(enabled) = check_cfg_attr(db, &attr, krate) {
+            if let Some(enabled) = check_cfg_attr_attr(&attr, loc, db) {
                 if enabled {
                     debug!("Removing cfg_attr tokens {:?}", attr);
                     let meta = attr.meta()?;
@@ -62,13 +60,13 @@ fn process_has_attrs_with_possible_comma<I: HasAttrs>(
                 } else {
                     debug!("censoring type cfg_attr {:?}", item.syntax());
                     remove.insert(attr.syntax().clone().into());
+                    continue;
                 }
             }
         }
     }
     Some(())
 }
-
 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
 enum CfgExprStage {
     /// Stripping the CFGExpr part of the attribute
@@ -80,7 +78,6 @@ enum CfgExprStage {
     // Related Issue: https://github.com/rust-lang/rust-analyzer/issues/10110
     EverythingElse,
 }
-
 /// This function creates its own set of tokens to remove. To help prevent malformed syntax as input.
 fn remove_tokens_within_cfg_attr(meta: Meta) -> Option<FxHashSet<SyntaxElement>> {
     let mut remove: FxHashSet<SyntaxElement> = FxHashSet::default();
@@ -134,28 +131,23 @@ fn remove_possible_comma(item: &impl AstNode, res: &mut FxHashSet<SyntaxElement>
     }
 }
 fn process_enum(
-    db: &dyn ExpandDatabase,
     variants: VariantList,
-    krate: CrateId,
+    loc: &MacroCallLoc,
+    db: &dyn ExpandDatabase,
     remove: &mut FxHashSet<SyntaxElement>,
 ) -> Option<()> {
     'variant: for variant in variants.variants() {
         for attr in variant.attrs() {
-            if let Some(enabled) = check_cfg(db, &attr, krate) {
-                if enabled {
-                    debug!("censoring {:?}", attr.syntax());
-                    remove.insert(attr.syntax().clone().into());
-                } else {
-                    // Rustc does not strip the attribute if it is enabled. So we will leave it
-                    debug!("censoring type {:?}", variant.syntax());
-                    remove.insert(variant.syntax().clone().into());
-                    // We need to remove the , as well
-                    remove_possible_comma(&variant, remove);
-                    continue 'variant;
-                }
-            }
+            if check_cfg_attr(&attr, loc, db).map(|enabled| !enabled).unwrap_or_default() {
+                // Rustc does not strip the attribute if it is enabled. So we will leave it
+                debug!("censoring type {:?}", variant.syntax());
+                remove.insert(variant.syntax().clone().into());
+                // We need to remove the , as well
+                remove_possible_comma(&variant, remove);
+                continue 'variant;
+            };
 
-            if let Some(enabled) = check_cfg_attr(db, &attr, krate) {
+            if let Some(enabled) = check_cfg_attr_attr(&attr, loc, db) {
                 if enabled {
                     debug!("Removing cfg_attr tokens {:?}", attr);
                     let meta = attr.meta()?;
@@ -164,16 +156,17 @@ fn process_enum(
                 } else {
                     debug!("censoring type cfg_attr {:?}", variant.syntax());
                     remove.insert(attr.syntax().clone().into());
+                    continue;
                 }
             }
         }
         if let Some(fields) = variant.field_list() {
             match fields {
                 ast::FieldList::RecordFieldList(fields) => {
-                    process_has_attrs_with_possible_comma(db, fields.fields(), krate, remove)?;
+                    process_has_attrs_with_possible_comma(fields.fields(), loc, db, remove)?;
                 }
                 ast::FieldList::TupleFieldList(fields) => {
-                    process_has_attrs_with_possible_comma(db, fields.fields(), krate, remove)?;
+                    process_has_attrs_with_possible_comma(fields.fields(), loc, db, remove)?;
                 }
             }
         }
@@ -182,9 +175,9 @@ fn process_enum(
 }
 
 pub(crate) fn process_cfg_attrs(
-    db: &dyn ExpandDatabase,
     node: &SyntaxNode,
     loc: &MacroCallLoc,
+    db: &dyn ExpandDatabase,
 ) -> Option<FxHashSet<SyntaxElement>> {
     // FIXME: #[cfg_eval] is not implemented. But it is not stable yet
     let is_derive = match loc.def.kind {
@@ -200,35 +193,36 @@ pub(crate) fn process_cfg_attrs(
 
     let item = ast::Item::cast(node.clone())?;
     for attr in item.attrs() {
-        if let Some(enabled) = check_cfg_attr(db, &attr, loc.krate) {
+        if let Some(enabled) = check_cfg_attr_attr(&attr, loc, db) {
             if enabled {
                 debug!("Removing cfg_attr tokens {:?}", attr);
                 let meta = attr.meta()?;
                 let removes_from_cfg_attr = remove_tokens_within_cfg_attr(meta)?;
                 remove.extend(removes_from_cfg_attr);
             } else {
-                debug!("Removing type cfg_attr {:?}", item.syntax());
+                debug!("censoring type cfg_attr {:?}", item.syntax());
                 remove.insert(attr.syntax().clone().into());
+                continue;
             }
         }
     }
     match item {
         ast::Item::Struct(it) => match it.field_list()? {
             ast::FieldList::RecordFieldList(fields) => {
-                process_has_attrs_with_possible_comma(db, fields.fields(), loc.krate, &mut remove)?;
+                process_has_attrs_with_possible_comma(fields.fields(), loc, db, &mut remove)?;
             }
             ast::FieldList::TupleFieldList(fields) => {
-                process_has_attrs_with_possible_comma(db, fields.fields(), loc.krate, &mut remove)?;
+                process_has_attrs_with_possible_comma(fields.fields(), loc, db, &mut remove)?;
             }
         },
         ast::Item::Enum(it) => {
-            process_enum(db, it.variant_list()?, loc.krate, &mut remove)?;
+            process_enum(it.variant_list()?, loc, db, &mut remove)?;
         }
         ast::Item::Union(it) => {
             process_has_attrs_with_possible_comma(
-                db,
                 it.record_field_list()?.fields(),
-                loc.krate,
+                loc,
+                db,
                 &mut remove,
             )?;
         }
@@ -240,22 +234,10 @@ pub(crate) fn process_cfg_attrs(
 /// Parses a `cfg` attribute from the meta
 fn parse_from_attr_meta(meta: Meta) -> Option<CfgExpr> {
     let tt = meta.token_tree()?;
-    let mut iter = tt
-        .token_trees_and_tokens()
-        .filter(is_not_whitespace)
-        .skip(1)
-        .take_while(is_not_closing_paren)
-        .peekable();
+    let mut iter = tt.token_trees_and_tokens().skip(1).peekable();
     next_cfg_expr_from_syntax(&mut iter)
 }
 
-fn is_not_closing_paren(element: &NodeOrToken<ast::TokenTree, syntax::SyntaxToken>) -> bool {
-    !matches!(element, NodeOrToken::Token(token) if (token.kind() == syntax::T![')']))
-}
-fn is_not_whitespace(element: &NodeOrToken<ast::TokenTree, syntax::SyntaxToken>) -> bool {
-    !matches!(element, NodeOrToken::Token(token) if (token.kind() == SyntaxKind::WHITESPACE))
-}
-
 fn next_cfg_expr_from_syntax<I>(iter: &mut Peekable<I>) -> Option<CfgExpr>
 where
     I: Iterator<Item = NodeOrToken<ast::TokenTree, syntax::SyntaxToken>>,
@@ -274,13 +256,14 @@ where
             let Some(NodeOrToken::Node(tree)) = iter.next() else {
                 return Some(CfgExpr::Invalid);
             };
-            let mut tree_iter = tree
-                .token_trees_and_tokens()
-                .filter(is_not_whitespace)
-                .skip(1)
-                .take_while(is_not_closing_paren)
-                .peekable();
-            while tree_iter.peek().is_some() {
+            let mut tree_iter = tree.token_trees_and_tokens().skip(1).peekable();
+            while tree_iter
+                .peek()
+                .filter(
+                    |element| matches!(element, NodeOrToken::Token(token) if (token.kind() != syntax::T![')'])),
+                )
+                .is_some()
+            {
                 let pred = next_cfg_expr_from_syntax(&mut tree_iter);
                 if let Some(pred) = pred {
                     preds.push(pred);
diff --git a/crates/hir-expand/src/db.rs b/crates/hir-expand/src/db.rs
index a961ad14a6..5461c1c49a 100644
--- a/crates/hir-expand/src/db.rs
+++ b/crates/hir-expand/src/db.rs
@@ -175,7 +175,7 @@ pub fn expand_speculative(
             };
 
             let censor_cfg =
-                cfg_process::process_cfg_attrs(db, speculative_args, &loc).unwrap_or_default();
+                cfg_process::process_cfg_attrs(speculative_args, &loc, db).unwrap_or_default();
             let mut fixups = fixup::fixup_syntax(span_map, speculative_args, span);
             fixups.append.retain(|it, _| match it {
                 syntax::NodeOrToken::Token(_) => true,
@@ -462,7 +462,7 @@ fn macro_arg(db: &dyn ExpandDatabase, id: MacroCallId) -> MacroArgResult {
 
     let (mut tt, undo_info) = {
         let syntax = item_node.syntax();
-        let censor_cfg = cfg_process::process_cfg_attrs(db, syntax, &loc).unwrap_or_default();
+        let censor_cfg = cfg_process::process_cfg_attrs(syntax, &loc, db).unwrap_or_default();
         let mut fixups = fixup::fixup_syntax(map.as_ref(), syntax, span);
         fixups.append.retain(|it, _| match it {
             syntax::NodeOrToken::Token(_) => true,
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 9b9f710d9a..503c549327 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -2006,15 +2006,12 @@ impl Function {
 
     /// is this a `fn main` or a function with an `export_name` of `main`?
     pub fn is_main(self, db: &dyn HirDatabase) -> bool {
+        if !self.module(db).is_crate_root() {
+            return false;
+        }
         let data = db.function_data(self.id);
-        data.attrs.export_name() == Some("main")
-            || self.module(db).is_crate_root() && data.name.to_smol_str() == "main"
-    }
 
-    /// Is this a function with an `export_name` of `main`?
-    pub fn exported_main(self, db: &dyn HirDatabase) -> bool {
-        let data = db.function_data(self.id);
-        data.attrs.export_name() == Some("main")
+        data.name.to_smol_str() == "main" || data.attrs.export_name() == Some("main")
     }
 
     /// Does this function have the ignore attribute?
diff --git a/crates/ide-assists/src/handlers/toggle_ignore.rs b/crates/ide-assists/src/handlers/toggle_ignore.rs
index 264a2f0326..f864ee50c8 100644
--- a/crates/ide-assists/src/handlers/toggle_ignore.rs
+++ b/crates/ide-assists/src/handlers/toggle_ignore.rs
@@ -3,7 +3,7 @@ use syntax::{
     AstNode, AstToken,
 };
 
-use crate::{utils::test_related_attribute_syn, AssistContext, AssistId, AssistKind, Assists};
+use crate::{utils::test_related_attribute, AssistContext, AssistId, AssistKind, Assists};
 
 // Assist: toggle_ignore
 //
@@ -26,7 +26,7 @@ use crate::{utils::test_related_attribute_syn, AssistContext, AssistId, AssistKi
 pub(crate) fn toggle_ignore(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
     let attr: ast::Attr = ctx.find_node_at_offset()?;
     let func = attr.syntax().parent().and_then(ast::Fn::cast)?;
-    let attr = test_related_attribute_syn(&func)?;
+    let attr = test_related_attribute(&func)?;
 
     match has_ignore_attribute(&func) {
         None => acc.add(
diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs
index bc0c9b79c7..8bd5d17933 100644
--- a/crates/ide-assists/src/utils.rs
+++ b/crates/ide-assists/src/utils.rs
@@ -71,7 +71,7 @@ pub fn extract_trivial_expression(block_expr: &ast::BlockExpr) -> Option<ast::Ex
 ///
 /// It may produce false positives, for example, `#[wasm_bindgen_test]` requires a different command to run the test,
 /// but it's better than not to have the runnables for the tests at all.
-pub fn test_related_attribute_syn(fn_def: &ast::Fn) -> Option<ast::Attr> {
+pub fn test_related_attribute(fn_def: &ast::Fn) -> Option<ast::Attr> {
     fn_def.attrs().find_map(|attr| {
         let path = attr.path()?;
         let text = path.syntax().text().to_string();
@@ -83,19 +83,6 @@ pub fn test_related_attribute_syn(fn_def: &ast::Fn) -> Option<ast::Attr> {
     })
 }
 
-pub fn has_test_related_attribute(attrs: &hir::AttrsWithOwner) -> bool {
-    attrs.iter().any(|attr| {
-        let path = attr.path();
-        (|| {
-            Some(
-                path.segments().first()?.as_text()?.starts_with("test")
-                    || path.segments().last()?.as_text()?.ends_with("test"),
-            )
-        })()
-        .unwrap_or_default()
-    })
-}
-
 #[derive(Clone, Copy, PartialEq)]
 pub enum IgnoreAssocItems {
     DocHiddenAttrPresent,
diff --git a/crates/ide-completion/src/completions/postfix.rs b/crates/ide-completion/src/completions/postfix.rs
index ae061c8ce3..554f7e081e 100644
--- a/crates/ide-completion/src/completions/postfix.rs
+++ b/crates/ide-completion/src/completions/postfix.rs
@@ -9,7 +9,6 @@ use ide_db::{
     ty_filter::TryEnum,
     SnippetCap,
 };
-use stdx::never;
 use syntax::{
     ast::{self, make, AstNode, AstToken},
     SyntaxKind::{BLOCK_EXPR, EXPR_STMT, FOR_EXPR, IF_EXPR, LOOP_EXPR, STMT_LIST, WHILE_EXPR},
@@ -320,9 +319,7 @@ fn build_postfix_snippet_builder<'ctx>(
 ) -> Option<impl Fn(&str, &str, &str) -> Builder + 'ctx> {
     let receiver_range = ctx.sema.original_range_opt(receiver.syntax())?.range;
     if ctx.source_range().end() < receiver_range.start() {
-        // This shouldn't happen, yet it does. I assume this might be due to an incorrect token
-        // mapping.
-        never!();
+        // This shouldn't happen, yet it does. I assume this might be due to an incorrect token mapping.
         return None;
     }
     let delete_range = TextRange::new(receiver_range.start(), ctx.source_range().end());
diff --git a/crates/ide/src/annotations/fn_references.rs b/crates/ide/src/annotations/fn_references.rs
index 2e7e230e5a..a090b60413 100644
--- a/crates/ide/src/annotations/fn_references.rs
+++ b/crates/ide/src/annotations/fn_references.rs
@@ -2,7 +2,7 @@
 //! We have to skip tests, so cannot reuse file_structure module.
 
 use hir::Semantics;
-use ide_assists::utils::test_related_attribute_syn;
+use ide_assists::utils::test_related_attribute;
 use ide_db::RootDatabase;
 use syntax::{ast, ast::HasName, AstNode, SyntaxNode, TextRange};
 
@@ -19,7 +19,7 @@ pub(super) fn find_all_methods(
 
 fn method_range(item: SyntaxNode) -> Option<(TextRange, Option<TextRange>)> {
     ast::Fn::cast(item).and_then(|fn_def| {
-        if test_related_attribute_syn(&fn_def).is_some() {
+        if test_related_attribute(&fn_def).is_some() {
             None
         } else {
             Some((
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs
index b6c6753755..79324bf387 100644
--- a/crates/ide/src/runnables.rs
+++ b/crates/ide/src/runnables.rs
@@ -1,11 +1,9 @@
 use std::fmt;
 
 use ast::HasName;
-use cfg::{CfgAtom, CfgExpr};
-use hir::{
-    db::HirDatabase, AsAssocItem, AttrsWithOwner, HasAttrs, HasSource, HirFileIdExt, Semantics,
-};
-use ide_assists::utils::{has_test_related_attribute, test_related_attribute_syn};
+use cfg::CfgExpr;
+use hir::{db::HirDatabase, AsAssocItem, HasAttrs, HasSource, HirFileIdExt, Semantics};
+use ide_assists::utils::test_related_attribute;
 use ide_db::{
     base_db::{FilePosition, FileRange},
     defs::Definition,
@@ -282,7 +280,7 @@ fn find_related_tests_in_module(
 }
 
 fn as_test_runnable(sema: &Semantics<'_, RootDatabase>, fn_def: &ast::Fn) -> Option<Runnable> {
-    if test_related_attribute_syn(fn_def).is_some() {
+    if test_related_attribute(fn_def).is_some() {
         let function = sema.to_def(fn_def)?;
         runnable_fn(sema, function)
     } else {
@@ -295,7 +293,7 @@ fn parent_test_module(sema: &Semantics<'_, RootDatabase>, fn_def: &ast::Fn) -> O
         let module = ast::Module::cast(node)?;
         let module = sema.to_def(&module)?;
 
-        if has_test_function_or_multiple_test_submodules(sema, &module, false) {
+        if has_test_function_or_multiple_test_submodules(sema, &module) {
             Some(module)
         } else {
             None
@@ -307,8 +305,7 @@ pub(crate) fn runnable_fn(
     sema: &Semantics<'_, RootDatabase>,
     def: hir::Function,
 ) -> Option<Runnable> {
-    let under_cfg_test = has_cfg_test(def.module(sema.db).attrs(sema.db));
-    let kind = if !under_cfg_test && def.is_main(sema.db) {
+    let kind = if def.is_main(sema.db) {
         RunnableKind::Bin
     } else {
         let test_id = || {
@@ -345,8 +342,7 @@ pub(crate) fn runnable_mod(
     sema: &Semantics<'_, RootDatabase>,
     def: hir::Module,
 ) -> Option<Runnable> {
-    if !has_test_function_or_multiple_test_submodules(sema, &def, has_cfg_test(def.attrs(sema.db)))
-    {
+    if !has_test_function_or_multiple_test_submodules(sema, &def) {
         return None;
     }
     let path = def
@@ -388,17 +384,12 @@ pub(crate) fn runnable_impl(
     Some(Runnable { use_name_in_title: false, nav, kind: RunnableKind::DocTest { test_id }, cfg })
 }
 
-fn has_cfg_test(attrs: AttrsWithOwner) -> bool {
-    attrs.cfgs().any(|cfg| matches!(cfg, CfgExpr::Atom(CfgAtom::Flag(s)) if s == "test"))
-}
-
 /// Creates a test mod runnable for outline modules at the top of their definition.
 fn runnable_mod_outline_definition(
     sema: &Semantics<'_, RootDatabase>,
     def: hir::Module,
 ) -> Option<Runnable> {
-    if !has_test_function_or_multiple_test_submodules(sema, &def, has_cfg_test(def.attrs(sema.db)))
-    {
+    if !has_test_function_or_multiple_test_submodules(sema, &def) {
         return None;
     }
     let path = def
@@ -531,28 +522,20 @@ fn has_runnable_doc_test(attrs: &hir::Attrs) -> bool {
 fn has_test_function_or_multiple_test_submodules(
     sema: &Semantics<'_, RootDatabase>,
     module: &hir::Module,
-    consider_exported_main: bool,
 ) -> bool {
     let mut number_of_test_submodules = 0;
 
     for item in module.declarations(sema.db) {
         match item {
             hir::ModuleDef::Function(f) => {
-                if has_test_related_attribute(&f.attrs(sema.db)) {
-                    return true;
-                }
-                if consider_exported_main && f.exported_main(sema.db) {
-                    // an exported main in a test module can be considered a test wrt to custom test
-                    // runners
-                    return true;
+                if let Some(it) = f.source(sema.db) {
+                    if test_related_attribute(&it.value).is_some() {
+                        return true;
+                    }
                 }
             }
             hir::ModuleDef::Module(submodule) => {
-                if has_test_function_or_multiple_test_submodules(
-                    sema,
-                    &submodule,
-                    consider_exported_main,
-                ) {
+                if has_test_function_or_multiple_test_submodules(sema, &submodule) {
                     number_of_test_submodules += 1;
                 }
             }
@@ -1501,39 +1484,4 @@ mod r#mod {
             "#]],
         )
     }
-
-    #[test]
-    fn exported_main_is_test_in_cfg_test_mod() {
-        check(
-            r#"
-//- /lib.rs crate:foo cfg:test
-$0
-mod not_a_test_module_inline {
-    #[export_name = "main"]
-    fn exp_main() {}
-}
-#[cfg(test)]
-mod test_mod_inline {
-    #[export_name = "main"]
-    fn exp_main() {}
-}
-mod not_a_test_module;
-#[cfg(test)]
-mod test_mod;
-//- /not_a_test_module.rs
-#[export_name = "main"]
-fn exp_main() {}
-//- /test_mod.rs
-#[export_name = "main"]
-fn exp_main() {}
-"#,
-            expect![[r#"
-                [
-                    "(Bin, NavigationTarget { file_id: FileId(0), full_range: 36..80, focus_range: 67..75, name: \"exp_main\", kind: Function })",
-                    "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 83..168, focus_range: 100..115, name: \"test_mod_inline\", kind: Module, description: \"mod test_mod_inline\" }, Atom(Flag(\"test\")))",
-                    "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 192..218, focus_range: 209..217, name: \"test_mod\", kind: Module, description: \"mod test_mod\" }, Atom(Flag(\"test\")))",
-                ]
-            "#]],
-        )
-    }
 }
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 80f024184a..5890af17eb 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -10,7 +10,7 @@
 use std::{fmt, iter, ops::Not};
 
 use cfg::{CfgAtom, CfgDiff};
-use flycheck::FlycheckConfig;
+use flycheck::{CargoOptions, FlycheckConfig};
 use ide::{
     AssistConfig, CallableSnippets, CompletionConfig, DiagnosticsConfig, ExprFillDefaultMode,
     HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayFieldsToResolve,
@@ -1364,6 +1364,22 @@ impl Config {
         self.data.check_workspace
     }
 
+    pub fn cargo_test_options(&self) -> CargoOptions {
+        CargoOptions {
+            target_triples: self.data.cargo_target.clone().into_iter().collect(),
+            all_targets: false,
+            no_default_features: self.data.cargo_noDefaultFeatures,
+            all_features: matches!(self.data.cargo_features, CargoFeaturesDef::All),
+            features: match self.data.cargo_features.clone() {
+                CargoFeaturesDef::All => vec![],
+                CargoFeaturesDef::Selected(it) => it,
+            },
+            extra_args: self.extra_args().clone(),
+            extra_env: self.extra_env().clone(),
+            target_dir: self.target_dir_from_config(),
+        }
+    }
+
     pub fn flycheck(&self) -> FlycheckConfig {
         match &self.data.check_overrideCommand {
             Some(args) if !args.is_empty() => {
@@ -1389,37 +1405,39 @@ impl Config {
             }
             Some(_) | None => FlycheckConfig::CargoCommand {
                 command: self.data.check_command.clone(),
-                target_triples: self
-                    .data
-                    .check_targets
-                    .clone()
-                    .and_then(|targets| match &targets.0[..] {
-                        [] => None,
-                        targets => Some(targets.into()),
-                    })
-                    .unwrap_or_else(|| self.data.cargo_target.clone().into_iter().collect()),
-                all_targets: self.data.check_allTargets.unwrap_or(self.data.cargo_allTargets),
-                no_default_features: self
-                    .data
-                    .check_noDefaultFeatures
-                    .unwrap_or(self.data.cargo_noDefaultFeatures),
-                all_features: matches!(
-                    self.data.check_features.as_ref().unwrap_or(&self.data.cargo_features),
-                    CargoFeaturesDef::All
-                ),
-                features: match self
-                    .data
-                    .check_features
-                    .clone()
-                    .unwrap_or_else(|| self.data.cargo_features.clone())
-                {
-                    CargoFeaturesDef::All => vec![],
-                    CargoFeaturesDef::Selected(it) => it,
+                options: CargoOptions {
+                    target_triples: self
+                        .data
+                        .check_targets
+                        .clone()
+                        .and_then(|targets| match &targets.0[..] {
+                            [] => None,
+                            targets => Some(targets.into()),
+                        })
+                        .unwrap_or_else(|| self.data.cargo_target.clone().into_iter().collect()),
+                    all_targets: self.data.check_allTargets.unwrap_or(self.data.cargo_allTargets),
+                    no_default_features: self
+                        .data
+                        .check_noDefaultFeatures
+                        .unwrap_or(self.data.cargo_noDefaultFeatures),
+                    all_features: matches!(
+                        self.data.check_features.as_ref().unwrap_or(&self.data.cargo_features),
+                        CargoFeaturesDef::All
+                    ),
+                    features: match self
+                        .data
+                        .check_features
+                        .clone()
+                        .unwrap_or_else(|| self.data.cargo_features.clone())
+                    {
+                        CargoFeaturesDef::All => vec![],
+                        CargoFeaturesDef::Selected(it) => it,
+                    },
+                    extra_args: self.check_extra_args(),
+                    extra_env: self.check_extra_env(),
+                    target_dir: self.target_dir_from_config(),
                 },
-                extra_args: self.check_extra_args(),
-                extra_env: self.check_extra_env(),
                 ansi_color_output: self.color_diagnostic_output(),
-                target_dir: self.target_dir_from_config(),
             },
         }
     }
@@ -2772,7 +2790,7 @@ mod tests {
             .unwrap();
         assert_eq!(config.data.cargo_targetDir, None);
         assert!(
-            matches!(config.flycheck(), FlycheckConfig::CargoCommand { target_dir, .. } if target_dir.is_none())
+            matches!(config.flycheck(), FlycheckConfig::CargoCommand { options, .. } if options.target_dir.is_none())
         );
     }
 
@@ -2791,7 +2809,7 @@ mod tests {
             .unwrap();
         assert_eq!(config.data.cargo_targetDir, Some(TargetDirectory::UseSubdirectory(true)));
         assert!(
-            matches!(config.flycheck(), FlycheckConfig::CargoCommand { target_dir, .. } if target_dir == Some(Utf8PathBuf::from("target/rust-analyzer")))
+            matches!(config.flycheck(), FlycheckConfig::CargoCommand { options, .. } if options.target_dir == Some(Utf8PathBuf::from("target/rust-analyzer")))
         );
     }
 
@@ -2813,7 +2831,7 @@ mod tests {
             Some(TargetDirectory::Directory(Utf8PathBuf::from("other_folder")))
         );
         assert!(
-            matches!(config.flycheck(), FlycheckConfig::CargoCommand { target_dir, .. } if target_dir == Some(Utf8PathBuf::from("other_folder")))
+            matches!(config.flycheck(), FlycheckConfig::CargoCommand { options, .. } if options.target_dir == Some(Utf8PathBuf::from("other_folder")))
         );
     }
 }
diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs
index 3d3f944019..7c4deac93f 100644
--- a/crates/rust-analyzer/src/diagnostics/to_proto.rs
+++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs
@@ -68,13 +68,8 @@ fn location(
     let range = {
         let position_encoding = snap.config.position_encoding();
         lsp_types::Range::new(
-            position(
-                &position_encoding,
-                span,
-                span.line_start,
-                span.column_start.saturating_sub(1),
-            ),
-            position(&position_encoding, span, span.line_end, span.column_end.saturating_sub(1)),
+            position(&position_encoding, span, span.line_start, span.column_start),
+            position(&position_encoding, span, span.line_end, span.column_end),
         )
     };
     lsp_types::Location::new(uri, range)
@@ -83,10 +78,10 @@ fn location(
 fn position(
     position_encoding: &PositionEncoding,
     span: &DiagnosticSpan,
-    line_number: usize,
+    line_offset: usize,
     column_offset_utf32: usize,
 ) -> lsp_types::Position {
-    let line_index = line_number - span.line_start;
+    let line_index = line_offset - span.line_start;
 
     let column_offset_encoded = match span.text.get(line_index) {
         // Fast path.
@@ -109,8 +104,8 @@ fn position(
     };
 
     lsp_types::Position {
-        line: (line_number as u32).saturating_sub(1),
-        character: column_offset_encoded as u32,
+        line: (line_offset as u32).saturating_sub(1),
+        character: (column_offset_encoded as u32).saturating_sub(1),
     }
 }
 
diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs
index 77692ed3ae..77c4ad32cc 100644
--- a/crates/rust-analyzer/src/handlers/request.rs
+++ b/crates/rust-analyzer/src/handlers/request.rs
@@ -220,11 +220,11 @@ pub(crate) fn handle_run_test(
         None => "".to_owned(),
     };
     let handle = if lca.is_empty() {
-        flycheck::CargoTestHandle::new(None)
+        flycheck::CargoTestHandle::new(None, state.config.cargo_test_options())
     } else if let Some((_, path)) = lca.split_once("::") {
-        flycheck::CargoTestHandle::new(Some(path))
+        flycheck::CargoTestHandle::new(Some(path), state.config.cargo_test_options())
     } else {
-        flycheck::CargoTestHandle::new(None)
+        flycheck::CargoTestHandle::new(None, state.config.cargo_test_options())
     };
     state.test_run_session = Some(handle?);
     Ok(())


Test patch:
diff --git a/crates/flycheck/src/test_runner.rs b/crates/flycheck/src/test_runner.rs
index 9f761c9ead..94c05ebc74 100644
--- a/crates/flycheck/src/test_runner.rs
+++ b/crates/flycheck/src/test_runner.rs
@@ -7,7 +7,10 @@ use crossbeam_channel::Receiver;
 use serde::Deserialize;
 use toolchain::Tool;
 
-use crate::command::{CommandHandle, ParseFromLine};
+use crate::{
+    command::{CommandHandle, ParseFromLine},
+    CargoOptions,
+};
 
 #[derive(Debug, Deserialize)]
 #[serde(tag = "event", rename_all = "camelCase")]
@@ -58,13 +61,14 @@ pub struct CargoTestHandle {
 // cargo test --workspace --no-fail-fast -- module::func -Z unstable-options --format=json
 
 impl CargoTestHandle {
-    pub fn new(path: Option<&str>) -> std::io::Result<Self> {
+    pub fn new(path: Option<&str>, options: CargoOptions) -> std::io::Result<Self> {
         let mut cmd = Command::new(Tool::Cargo.path());
         cmd.env("RUSTC_BOOTSTRAP", "1");
         cmd.arg("test");
         cmd.arg("--workspace");
         // --no-fail-fast is needed to ensure that all requested tests will run
         cmd.arg("--no-fail-fast");
+        options.apply_on_command(&mut cmd);
         cmd.arg("--");
         if let Some(path) = path {
             cmd.arg(path);
diff --git a/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs b/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs
index 163211fea5..89c1b44608 100644
--- a/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs
+++ b/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs
@@ -610,10 +610,6 @@ struct Foo {
     field1: i32,
     #[cfg(never)]
     field2: (),
-    #[cfg(feature = "never")]
-    field3: (),
-    #[cfg(not(feature = "never"))]
-    field4: (),
 }
 #[derive(Default)]
 enum Bar {
@@ -622,16 +618,12 @@ enum Bar {
     Bar,
 }
 "#,
-        expect![[r##"
+        expect![[r#"
 #[derive(Default)]
 struct Foo {
     field1: i32,
     #[cfg(never)]
     field2: (),
-    #[cfg(feature = "never")]
-    field3: (),
-    #[cfg(not(feature = "never"))]
-    field4: (),
 }
 #[derive(Default)]
 enum Bar {
@@ -643,7 +635,7 @@ enum Bar {
 impl < > $crate::default::Default for Foo< > where {
     fn default() -> Self {
         Foo {
-            field1: $crate::default::Default::default(), field4: $crate::default::Default::default(),
+            field1: $crate::default::Default::default(),
         }
     }
 }
@@ -651,6 +643,6 @@ impl < > $crate::default::Default for Bar< > where {
     fn default() -> Self {
         Bar::Bar
     }
-}"##]],
+}"#]],
     );
 }