Skip to content

Commit 65cd65b

Browse files
committed
feat: introduce crate_attrs field in rust-project.json
Since the commit 5038446 ("Rewrite method resolution to follow rustc more closely"), the method resolution logic has changed: rust-analyzer only looks up inherent methods for primitive types in sysroot crates. Unfortunately, this change broke at least one project that relies on `rust-project.json`: Rust-for-Linux. Its auto-generated `rust-project.json` directly embeds `core`, `alloc`, and `std` in the `crates` list without defining `sysroot_src`. Consequently, rust-analyzer fails to identify them as sysroot crates, breaking IDE support for primitive methods (e.g., `0_i32.rotate_left(0)`). However, specifying `sysroot_src` creates a new issue: it implicitly adds `std` as a dependency to all kernel module crates, which are actually compiled with `-Zcrate-attr=no_std`. Since rust-analyzer cannot see compiler flags passed outside of the project definition, we need a method to explicitly specify `#![no_std]` or, more generally, crate-level attributes through the project configuration. To resolve this, extend the `rust-project.json` format with a new `crate_attrs` field. This allows users to specify crate-level attributes such as `#![no_std]` directly into the configuration, enabling rust-analyzer to respect them when analyzing crates. References: - The original Zulip discussion: https://rust-lang.zulipchat.com/#narrow/channel/185405-t-compiler.2Frust-analyzer/topic/Primitive.20type.20inherent.20method.20lookup.20fails/with/562983853
1 parent 76cd4d6 commit 65cd65b

25 files changed

+713
-28
lines changed

crates/base-db/src/input.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,8 @@ pub struct CrateData<Id> {
351351
/// declared in source via `extern crate test`.
352352
pub dependencies: Vec<Dependency<Id>>,
353353
pub origin: CrateOrigin,
354+
/// Extra crate-level attributes, including the surrounding `#![]`.
355+
pub crate_attrs: Vec<String>,
354356
pub is_proc_macro: bool,
355357
/// The working directory to run proc-macros in invoked in the context of this crate.
356358
/// This is the workspace root of the cargo workspace for workspace members, the crate manifest
@@ -530,12 +532,16 @@ impl CrateGraphBuilder {
530532
mut potential_cfg_options: Option<CfgOptions>,
531533
mut env: Env,
532534
origin: CrateOrigin,
535+
crate_attrs: Vec<String>,
533536
is_proc_macro: bool,
534537
proc_macro_cwd: Arc<AbsPathBuf>,
535538
ws_data: Arc<CrateWorkspaceData>,
536539
) -> CrateBuilderId {
537540
env.entries.shrink_to_fit();
538541
cfg_options.shrink_to_fit();
542+
let mut crate_attrs: Vec<_> =
543+
crate_attrs.into_iter().map(|raw_attr| format!("#![{raw_attr}]")).collect();
544+
crate_attrs.shrink_to_fit();
539545
if let Some(potential_cfg_options) = &mut potential_cfg_options {
540546
potential_cfg_options.shrink_to_fit();
541547
}
@@ -545,6 +551,7 @@ impl CrateGraphBuilder {
545551
edition,
546552
dependencies: Vec::new(),
547553
origin,
554+
crate_attrs,
548555
is_proc_macro,
549556
proc_macro_cwd,
550557
},
@@ -648,6 +655,7 @@ impl CrateGraphBuilder {
648655
edition: krate.basic.edition,
649656
is_proc_macro: krate.basic.is_proc_macro,
650657
origin: krate.basic.origin.clone(),
658+
crate_attrs: krate.basic.crate_attrs.clone(),
651659
root_file_id: krate.basic.root_file_id,
652660
proc_macro_cwd: krate.basic.proc_macro_cwd.clone(),
653661
};
@@ -975,6 +983,7 @@ mod tests {
975983
Default::default(),
976984
Env::default(),
977985
CrateOrigin::Local { repo: None, name: None },
986+
Vec::new(),
978987
false,
979988
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
980989
empty_ws_data(),
@@ -988,6 +997,7 @@ mod tests {
988997
Default::default(),
989998
Env::default(),
990999
CrateOrigin::Local { repo: None, name: None },
1000+
Vec::new(),
9911001
false,
9921002
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
9931003
empty_ws_data(),
@@ -1001,6 +1011,7 @@ mod tests {
10011011
Default::default(),
10021012
Env::default(),
10031013
CrateOrigin::Local { repo: None, name: None },
1014+
Vec::new(),
10041015
false,
10051016
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
10061017
empty_ws_data(),
@@ -1034,6 +1045,7 @@ mod tests {
10341045
Default::default(),
10351046
Env::default(),
10361047
CrateOrigin::Local { repo: None, name: None },
1048+
Vec::new(),
10371049
false,
10381050
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
10391051
empty_ws_data(),
@@ -1047,6 +1059,7 @@ mod tests {
10471059
Default::default(),
10481060
Env::default(),
10491061
CrateOrigin::Local { repo: None, name: None },
1062+
Vec::new(),
10501063
false,
10511064
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
10521065
empty_ws_data(),
@@ -1075,6 +1088,7 @@ mod tests {
10751088
Default::default(),
10761089
Env::default(),
10771090
CrateOrigin::Local { repo: None, name: None },
1091+
Vec::new(),
10781092
false,
10791093
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
10801094
empty_ws_data(),
@@ -1088,6 +1102,7 @@ mod tests {
10881102
Default::default(),
10891103
Env::default(),
10901104
CrateOrigin::Local { repo: None, name: None },
1105+
Vec::new(),
10911106
false,
10921107
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
10931108
empty_ws_data(),
@@ -1101,6 +1116,7 @@ mod tests {
11011116
Default::default(),
11021117
Env::default(),
11031118
CrateOrigin::Local { repo: None, name: None },
1119+
Vec::new(),
11041120
false,
11051121
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
11061122
empty_ws_data(),
@@ -1129,6 +1145,7 @@ mod tests {
11291145
Default::default(),
11301146
Env::default(),
11311147
CrateOrigin::Local { repo: None, name: None },
1148+
Vec::new(),
11321149
false,
11331150
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
11341151
empty_ws_data(),
@@ -1142,6 +1159,7 @@ mod tests {
11421159
Default::default(),
11431160
Env::default(),
11441161
CrateOrigin::Local { repo: None, name: None },
1162+
Vec::new(),
11451163
false,
11461164
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
11471165
empty_ws_data(),

crates/hir-def/src/attrs.rs

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ use la_arena::ArenaMap;
3838
use rustc_abi::ReprOptions;
3939
use rustc_hash::FxHashSet;
4040
use smallvec::SmallVec;
41+
use span::Edition;
4142
use syntax::{
42-
AstNode, AstToken, NodeOrToken, SmolStr, SyntaxNode, SyntaxToken, T,
43+
AstNode, AstToken, NodeOrToken, SmolStr, SourceFile, SyntaxNode, SyntaxToken, T,
4344
ast::{self, AttrDocCommentIter, HasAttrs, IsString, TokenTreeChildren},
4445
};
4546
use tt::{TextRange, TextSize};
@@ -292,6 +293,37 @@ bitflags::bitflags! {
292293
}
293294
}
294295

296+
pub fn parse_extra_crate_attrs(
297+
crate_attrs: &[String],
298+
edition: Edition,
299+
crate_name: &str,
300+
) -> impl Iterator<Item = SourceFile> {
301+
use std::sync::{LazyLock, Mutex};
302+
static SEEN_MSGS: LazyLock<Mutex<FxHashSet<String>>> =
303+
LazyLock::new(|| Mutex::new(FxHashSet::default()));
304+
macro_rules! error_once {
305+
($msg_set:ident, $($arg:tt)+) => {{
306+
let mut seen_msgs = $msg_set.lock().expect("Mutex should not be poisoned");
307+
if seen_msgs.insert(format!($($arg)+)) {
308+
tracing::error!($($arg)+);
309+
}
310+
}};
311+
}
312+
313+
crate_attrs.iter().filter_map(move |raw_attr| {
314+
// All attributes are already enclosed in `#![]`.
315+
let p = syntax::SourceFile::parse(raw_attr, edition);
316+
let errors = p.errors();
317+
if !errors.is_empty() {
318+
let base_msg = "Failed to parse extra crate-level attribute";
319+
error_once!(SEEN_MSGS, "{base_msg} `{raw_attr}` for crate `{crate_name}`");
320+
None
321+
} else {
322+
Some(p.tree())
323+
}
324+
})
325+
}
326+
295327
fn attrs_source(
296328
db: &dyn DefDatabase,
297329
owner: AttrDefId,
@@ -349,12 +381,29 @@ fn collect_attrs<BreakValue>(
349381
) -> Option<BreakValue> {
350382
let (source, outer_mod_decl, krate) = attrs_source(db, owner);
351383

384+
let file_id = source.file_id;
385+
let crate_attrs = (file_id == krate.root_file_id(db))
386+
.then(|| {
387+
let crate_name = krate
388+
.extra_data(db)
389+
.display_name
390+
.as_ref()
391+
.map_or("{unknown}", |name| name.as_str());
392+
parse_extra_crate_attrs(&krate.data(db).crate_attrs, file_id.edition(db), crate_name)
393+
.flat_map(|src| src.attrs())
394+
})
395+
.into_iter()
396+
.flatten()
397+
.chain(
398+
outer_mod_decl
399+
.into_iter()
400+
.flat_map(|it| it.value.attrs())
401+
.chain(ast::attrs_including_inner(&source.value)),
402+
);
403+
352404
let mut cfg_options = None;
353405
expand_cfg_attr(
354-
outer_mod_decl
355-
.into_iter()
356-
.flat_map(|it| it.value.attrs())
357-
.chain(ast::attrs_including_inner(&source.value)),
406+
crate_attrs,
358407
|| cfg_options.get_or_insert_with(|| krate.cfg_options(db)),
359408
move |meta, _, _, _| callback(meta),
360409
)
@@ -1480,8 +1529,9 @@ mod tests {
14801529
use test_fixture::WithFixture;
14811530
use tt::{TextRange, TextSize};
14821531

1483-
use crate::attrs::IsInnerDoc;
1484-
use crate::{attrs::Docs, test_db::TestDB};
1532+
use crate::AttrDefId;
1533+
use crate::attrs::{AttrFlags, Docs, IsInnerDoc};
1534+
use crate::test_db::TestDB;
14851535

14861536
#[test]
14871537
fn docs() {
@@ -1617,4 +1667,15 @@ mod tests {
16171667
Some((in_file(range(263, 265)), IsInnerDoc::Yes))
16181668
);
16191669
}
1670+
1671+
#[test]
1672+
fn crate_attrs() {
1673+
let fixture = r#"
1674+
//- /lib.rs crate:foo crate-attrs:no_std,cfg(target_arch="x86")
1675+
"#;
1676+
let (db, file_id) = TestDB::with_single_file(fixture);
1677+
let module = db.module_for_file(file_id.file_id(&db));
1678+
let attrs = AttrFlags::query(&db, AttrDefId::ModuleId(module));
1679+
assert!(attrs.contains(AttrFlags::IS_NO_STD | AttrFlags::HAS_CFG));
1680+
}
16201681
}

crates/hir-def/src/item_tree.rs

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ use std::{
4444
};
4545

4646
use ast::{AstNode, StructKind};
47+
use cfg::CfgOptions;
4748
use hir_expand::{
4849
ExpandTo, HirFileId,
4950
mod_path::{ModPath, PathKind},
@@ -52,13 +53,17 @@ use hir_expand::{
5253
use intern::Interned;
5354
use la_arena::{Idx, RawIdx};
5455
use rustc_hash::FxHashMap;
55-
use span::{AstIdNode, Edition, FileAstId, SyntaxContext};
56+
use span::{
57+
AstIdNode, Edition, FileAstId, NO_DOWNMAP_ERASED_FILE_AST_ID_MARKER, Span, SpanAnchor,
58+
SyntaxContext,
59+
};
5660
use stdx::never;
57-
use syntax::{SyntaxKind, ast, match_ast};
61+
use syntax::{SourceFile, SyntaxKind, ast, match_ast};
5862
use thin_vec::ThinVec;
5963
use triomphe::Arc;
64+
use tt::TextRange;
6065

61-
use crate::{BlockId, Lookup, db::DefDatabase};
66+
use crate::{BlockId, Lookup, attrs::parse_extra_crate_attrs, db::DefDatabase};
6267

6368
pub(crate) use crate::item_tree::{
6469
attrs::*,
@@ -88,6 +93,35 @@ impl fmt::Debug for RawVisibilityId {
8893
}
8994
}
9095

96+
fn lower_extra_crate_attrs<'a>(
97+
db: &dyn DefDatabase,
98+
parsed_attr_srcs: impl Iterator<Item = SourceFile>,
99+
file_id: span::EditionedFileId,
100+
cfg_options: &dyn Fn() -> &'a CfgOptions,
101+
) -> AttrsOrCfg {
102+
#[derive(Copy, Clone)]
103+
struct FakeSpanMap {
104+
file_id: span::EditionedFileId,
105+
}
106+
impl syntax_bridge::SpanMapper<Span> for FakeSpanMap {
107+
fn span_for(&self, range: TextRange) -> Span {
108+
Span {
109+
range,
110+
anchor: SpanAnchor {
111+
file_id: self.file_id,
112+
ast_id: NO_DOWNMAP_ERASED_FILE_AST_ID_MARKER,
113+
},
114+
ctx: SyntaxContext::root(self.file_id.edition()),
115+
}
116+
}
117+
}
118+
119+
let span_map = FakeSpanMap { file_id };
120+
parsed_attr_srcs.fold(AttrsOrCfg::empty(), |acc, src| {
121+
acc.merge(AttrsOrCfg::lower(db, &src, cfg_options, span_map))
122+
})
123+
}
124+
91125
#[salsa_macros::tracked(returns(deref))]
92126
pub(crate) fn file_item_tree_query(db: &dyn DefDatabase, file_id: HirFileId) -> Arc<ItemTree> {
93127
let _p = tracing::info_span!("file_item_tree_query", ?file_id).entered();
@@ -98,7 +132,27 @@ pub(crate) fn file_item_tree_query(db: &dyn DefDatabase, file_id: HirFileId) ->
98132
let mut item_tree = match_ast! {
99133
match syntax {
100134
ast::SourceFile(file) => {
101-
let top_attrs = ctx.lower_attrs(&file);
135+
let krate = file_id.krate(db);
136+
let root_file_id = krate.root_file_id(db);
137+
let extra_top_attrs = (file_id == root_file_id)
138+
.then(|| &krate.data(db).crate_attrs)
139+
.and_then(|crate_attrs| {
140+
(!crate_attrs.is_empty()).then(|| {
141+
let file_id = root_file_id.editioned_file_id(db);
142+
let crate_name = krate
143+
.extra_data(db)
144+
.display_name
145+
.as_ref()
146+
.map_or("{unknown}", |name| name.as_str());
147+
let crate_attrs = parse_extra_crate_attrs(crate_attrs, file_id.edition(), crate_name);
148+
lower_extra_crate_attrs(db, crate_attrs, file_id, &|| ctx.cfg_options())
149+
})
150+
});
151+
let top_attrs = match extra_top_attrs {
152+
Some(attrs @ AttrsOrCfg::Enabled { .. }) => attrs.merge(ctx.lower_attrs(&file)),
153+
Some(attrs @ AttrsOrCfg::CfgDisabled(_)) => attrs,
154+
None => ctx.lower_attrs(&file)
155+
};
102156
let mut item_tree = ctx.lower_module_items(&file);
103157
item_tree.top_attrs = top_attrs;
104158
item_tree

0 commit comments

Comments
 (0)