Skip to content

Commit 7f6858f

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 efdb3de commit 7f6858f

26 files changed

+473
-41
lines changed

crates/base-db/src/input.rs

Lines changed: 19 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: Box<[Box<str>]>,
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,6 +532,7 @@ 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>,
@@ -539,12 +542,17 @@ impl CrateGraphBuilder {
539542
if let Some(potential_cfg_options) = &mut potential_cfg_options {
540543
potential_cfg_options.shrink_to_fit();
541544
}
545+
let crate_attrs: Vec<_> = crate_attrs
546+
.into_iter()
547+
.map(|raw_attr| format!("#![{raw_attr}]").into_boxed_str())
548+
.collect();
542549
self.arena.alloc(CrateBuilder {
543550
basic: CrateData {
544551
root_file_id,
545552
edition,
546553
dependencies: Vec::new(),
547554
origin,
555+
crate_attrs: crate_attrs.into_boxed_slice(),
548556
is_proc_macro,
549557
proc_macro_cwd,
550558
},
@@ -648,6 +656,7 @@ impl CrateGraphBuilder {
648656
edition: krate.basic.edition,
649657
is_proc_macro: krate.basic.is_proc_macro,
650658
origin: krate.basic.origin.clone(),
659+
crate_attrs: krate.basic.crate_attrs.clone(),
651660
root_file_id: krate.basic.root_file_id,
652661
proc_macro_cwd: krate.basic.proc_macro_cwd.clone(),
653662
};
@@ -975,6 +984,7 @@ mod tests {
975984
Default::default(),
976985
Env::default(),
977986
CrateOrigin::Local { repo: None, name: None },
987+
Vec::new(),
978988
false,
979989
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
980990
empty_ws_data(),
@@ -988,6 +998,7 @@ mod tests {
988998
Default::default(),
989999
Env::default(),
9901000
CrateOrigin::Local { repo: None, name: None },
1001+
Vec::new(),
9911002
false,
9921003
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
9931004
empty_ws_data(),
@@ -1001,6 +1012,7 @@ mod tests {
10011012
Default::default(),
10021013
Env::default(),
10031014
CrateOrigin::Local { repo: None, name: None },
1015+
Vec::new(),
10041016
false,
10051017
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
10061018
empty_ws_data(),
@@ -1034,6 +1046,7 @@ mod tests {
10341046
Default::default(),
10351047
Env::default(),
10361048
CrateOrigin::Local { repo: None, name: None },
1049+
Vec::new(),
10371050
false,
10381051
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
10391052
empty_ws_data(),
@@ -1047,6 +1060,7 @@ mod tests {
10471060
Default::default(),
10481061
Env::default(),
10491062
CrateOrigin::Local { repo: None, name: None },
1063+
Vec::new(),
10501064
false,
10511065
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
10521066
empty_ws_data(),
@@ -1075,6 +1089,7 @@ mod tests {
10751089
Default::default(),
10761090
Env::default(),
10771091
CrateOrigin::Local { repo: None, name: None },
1092+
Vec::new(),
10781093
false,
10791094
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
10801095
empty_ws_data(),
@@ -1088,6 +1103,7 @@ mod tests {
10881103
Default::default(),
10891104
Env::default(),
10901105
CrateOrigin::Local { repo: None, name: None },
1106+
Vec::new(),
10911107
false,
10921108
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
10931109
empty_ws_data(),
@@ -1101,6 +1117,7 @@ mod tests {
11011117
Default::default(),
11021118
Env::default(),
11031119
CrateOrigin::Local { repo: None, name: None },
1120+
Vec::new(),
11041121
false,
11051122
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
11061123
empty_ws_data(),
@@ -1129,6 +1146,7 @@ mod tests {
11291146
Default::default(),
11301147
Env::default(),
11311148
CrateOrigin::Local { repo: None, name: None },
1149+
Vec::new(),
11321150
false,
11331151
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
11341152
empty_ws_data(),
@@ -1142,6 +1160,7 @@ mod tests {
11421160
Default::default(),
11431161
Env::default(),
11441162
CrateOrigin::Local { repo: None, name: None },
1163+
Vec::new(),
11451164
false,
11461165
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
11471166
empty_ws_data(),

crates/hir-def/src/attrs.rs

Lines changed: 72 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ use rustc_abi::ReprOptions;
3939
use rustc_hash::FxHashSet;
4040
use smallvec::SmallVec;
4141
use syntax::{
42-
AstNode, AstToken, NodeOrToken, SmolStr, SyntaxNode, SyntaxToken, T,
42+
AstNode, AstToken, NodeOrToken, SmolStr, SourceFile, SyntaxNode, SyntaxToken, T,
4343
ast::{self, AttrDocCommentIter, HasAttrs, IsString, TokenTreeChildren},
4444
};
4545
use tt::{TextRange, TextSize};
@@ -292,35 +292,69 @@ bitflags::bitflags! {
292292
}
293293
}
294294

295+
pub fn parse_extra_crate_attrs(db: &dyn DefDatabase, krate: Crate) -> Option<SourceFile> {
296+
let crate_data = krate.data(db);
297+
let crate_attrs = &crate_data.crate_attrs;
298+
if crate_attrs.is_empty() {
299+
return None;
300+
}
301+
// All attributes are already enclosed in `#![]`.
302+
let combined = crate_attrs.concat();
303+
let p = SourceFile::parse(&combined, crate_data.edition);
304+
305+
let errs = p.errors();
306+
if !errs.is_empty() {
307+
let base_msg = "Failed to parse extra crate-level attribute";
308+
let crate_name =
309+
krate.extra_data(db).display_name.as_ref().map_or("{unknown}", |name| name.as_str());
310+
let mut errs = errs.iter().peekable();
311+
let mut offset = TextSize::from(0);
312+
for raw_attr in crate_attrs {
313+
let attr_end = offset + TextSize::of(&**raw_attr);
314+
if errs.peeking_take_while(|e| e.range().start() < attr_end).count() > 0 {
315+
tracing::error!("{base_msg} {raw_attr} for crate {crate_name}");
316+
}
317+
offset = attr_end
318+
}
319+
return None;
320+
}
321+
322+
Some(p.tree())
323+
}
324+
295325
fn attrs_source(
296326
db: &dyn DefDatabase,
297327
owner: AttrDefId,
298-
) -> (InFile<ast::AnyHasAttrs>, Option<InFile<ast::Module>>, Crate) {
328+
) -> (InFile<ast::AnyHasAttrs>, Option<InFile<ast::Module>>, Option<SourceFile>, Crate) {
299329
let (owner, krate) = match owner {
300330
AttrDefId::ModuleId(id) => {
301331
let def_map = id.def_map(db);
302-
let (definition, declaration) = match def_map[id].origin {
332+
let krate = def_map.krate();
333+
let (definition, declaration, extra_crate_attrs) = match def_map[id].origin {
303334
ModuleOrigin::CrateRoot { definition } => {
304-
let file = db.parse(definition).tree();
305-
(InFile::new(definition.into(), ast::AnyHasAttrs::from(file)), None)
335+
let definition_source = db.parse(definition).tree();
336+
let definition = InFile::new(definition.into(), definition_source.into());
337+
let extra_crate_attrs = parse_extra_crate_attrs(db, krate);
338+
(definition, None, extra_crate_attrs)
306339
}
307340
ModuleOrigin::File { declaration, declaration_tree_id, definition, .. } => {
341+
let definition_source = db.parse(definition).tree();
342+
let definition = InFile::new(definition.into(), definition_source.into());
308343
let declaration = InFile::new(declaration_tree_id.file_id(), declaration);
309344
let declaration = declaration.with_value(declaration.to_node(db));
310-
let definition_source = db.parse(definition).tree();
311-
(InFile::new(definition.into(), definition_source.into()), Some(declaration))
345+
(definition, Some(declaration), None)
312346
}
313347
ModuleOrigin::Inline { definition_tree_id, definition } => {
314348
let definition = InFile::new(definition_tree_id.file_id(), definition);
315349
let definition = definition.with_value(definition.to_node(db).into());
316-
(definition, None)
350+
(definition, None, None)
317351
}
318352
ModuleOrigin::BlockExpr { block, .. } => {
319353
let definition = block.to_node(db);
320-
(block.with_value(definition.into()), None)
354+
(block.with_value(definition.into()), None, None)
321355
}
322356
};
323-
return (definition, declaration, def_map.krate());
357+
return (definition, declaration, extra_crate_attrs, krate);
324358
}
325359
AttrDefId::AdtId(AdtId::StructId(it)) => attrs_from_ast_id_loc(db, it),
326360
AttrDefId::AdtId(AdtId::UnionId(it)) => attrs_from_ast_id_loc(db, it),
@@ -339,22 +373,23 @@ fn attrs_source(
339373
AttrDefId::ExternCrateId(it) => attrs_from_ast_id_loc(db, it),
340374
AttrDefId::UseId(it) => attrs_from_ast_id_loc(db, it),
341375
};
342-
(owner, None, krate)
376+
(owner, None, None, krate)
343377
}
344378

345379
fn collect_attrs<BreakValue>(
346380
db: &dyn DefDatabase,
347381
owner: AttrDefId,
348382
mut callback: impl FnMut(Meta) -> ControlFlow<BreakValue>,
349383
) -> Option<BreakValue> {
350-
let (source, outer_mod_decl, krate) = attrs_source(db, owner);
384+
let (source, outer_mod_decl, extra_crate_attrs, krate) = attrs_source(db, owner);
385+
let extra_attrs = extra_crate_attrs
386+
.into_iter()
387+
.flat_map(|src| src.attrs())
388+
.chain(outer_mod_decl.into_iter().flat_map(|it| it.value.attrs()));
351389

352390
let mut cfg_options = None;
353391
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)),
392+
extra_attrs.chain(ast::attrs_including_inner(&source.value)),
358393
|| cfg_options.get_or_insert_with(|| krate.cfg_options(db)),
359394
move |meta, _, _, _| callback(meta),
360395
)
@@ -1013,10 +1048,12 @@ impl AttrFlags {
10131048
pub fn doc_html_root_url(db: &dyn DefDatabase, krate: Crate) -> Option<SmolStr> {
10141049
let root_file_id = krate.root_file_id(db);
10151050
let syntax = db.parse(root_file_id).tree();
1051+
let extra_crate_attrs =
1052+
parse_extra_crate_attrs(db, krate).into_iter().flat_map(|src| src.attrs());
10161053

10171054
let mut cfg_options = None;
10181055
expand_cfg_attr(
1019-
syntax.attrs(),
1056+
extra_crate_attrs.chain(syntax.attrs()),
10201057
|| cfg_options.get_or_insert(krate.cfg_options(db)),
10211058
|attr, _, _, _| {
10221059
if let Meta::TokenTree { path, tt } = attr
@@ -1231,8 +1268,11 @@ impl AttrFlags {
12311268
// We LRU this query because it is only used by IDE.
12321269
#[salsa::tracked(returns(ref), lru = 250)]
12331270
pub fn docs(db: &dyn DefDatabase, owner: AttrDefId) -> Option<Box<Docs>> {
1234-
let (source, outer_mod_decl, krate) = attrs_source(db, owner);
1271+
let (source, outer_mod_decl, _extra_crate_attrs, krate) = attrs_source(db, owner);
12351272
let inner_attrs_node = source.value.inner_attributes_node();
1273+
// Note: we don't have to pass down `_extra_crate_attrs` here, since `extract_docs`
1274+
// does not handle crate-level attributes related to docs.
1275+
// See: https://doc.rust-lang.org/rustdoc/write-documentation/the-doc-attribute.html#at-the-crate-level
12361276
extract_docs(&|| krate.cfg_options(db), source, outer_mod_decl, inner_attrs_node)
12371277
}
12381278

@@ -1480,8 +1520,9 @@ mod tests {
14801520
use test_fixture::WithFixture;
14811521
use tt::{TextRange, TextSize};
14821522

1483-
use crate::attrs::IsInnerDoc;
1484-
use crate::{attrs::Docs, test_db::TestDB};
1523+
use crate::AttrDefId;
1524+
use crate::attrs::{AttrFlags, Docs, IsInnerDoc};
1525+
use crate::test_db::TestDB;
14851526

14861527
#[test]
14871528
fn docs() {
@@ -1617,4 +1658,15 @@ mod tests {
16171658
Some((in_file(range(263, 265)), IsInnerDoc::Yes))
16181659
);
16191660
}
1661+
1662+
#[test]
1663+
fn crate_attrs() {
1664+
let fixture = r#"
1665+
//- /lib.rs crate:foo crate-attr:no_std crate-attr:cfg(target_arch="x86")
1666+
"#;
1667+
let (db, file_id) = TestDB::with_single_file(fixture);
1668+
let module = db.module_for_file(file_id.file_id(&db));
1669+
let attrs = AttrFlags::query(&db, AttrDefId::ModuleId(module));
1670+
assert!(attrs.contains(AttrFlags::IS_NO_STD | AttrFlags::HAS_CFG));
1671+
}
16201672
}

crates/hir-def/src/item_tree.rs

Lines changed: 48 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,33 @@ impl fmt::Debug for RawVisibilityId {
8893
}
8994
}
9095

96+
fn lower_extra_crate_attrs<'a>(
97+
db: &dyn DefDatabase,
98+
crate_attrs_as_src: 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+
AttrsOrCfg::lower(db, &crate_attrs_as_src, cfg_options, span_map)
121+
}
122+
91123
#[salsa_macros::tracked(returns(deref))]
92124
pub(crate) fn file_item_tree_query(db: &dyn DefDatabase, file_id: HirFileId) -> Arc<ItemTree> {
93125
let _p = tracing::info_span!("file_item_tree_query", ?file_id).entered();
@@ -98,7 +130,19 @@ pub(crate) fn file_item_tree_query(db: &dyn DefDatabase, file_id: HirFileId) ->
98130
let mut item_tree = match_ast! {
99131
match syntax {
100132
ast::SourceFile(file) => {
101-
let top_attrs = ctx.lower_attrs(&file);
133+
let krate = file_id.krate(db);
134+
let root_file_id = krate.root_file_id(db);
135+
let extra_top_attrs = (file_id == root_file_id).then(|| {
136+
parse_extra_crate_attrs(db, krate).map(|crate_attrs| {
137+
let file_id = root_file_id.editioned_file_id(db);
138+
lower_extra_crate_attrs(db, crate_attrs, file_id, &|| ctx.cfg_options())
139+
})
140+
}).flatten();
141+
let top_attrs = match extra_top_attrs {
142+
Some(attrs @ AttrsOrCfg::Enabled { .. }) => attrs.merge(ctx.lower_attrs(&file)),
143+
Some(attrs @ AttrsOrCfg::CfgDisabled(_)) => attrs,
144+
None => ctx.lower_attrs(&file)
145+
};
102146
let mut item_tree = ctx.lower_module_items(&file);
103147
item_tree.top_attrs = top_attrs;
104148
item_tree

0 commit comments

Comments
 (0)