mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-12-27 16:07:46 +00:00
feat: introduce crate_attrs field in rust-project.json
Since the commit 50384460c68f
("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
This commit is contained in:
parent
efdb3de4f2
commit
7f6858fd09
@ -351,6 +351,8 @@ pub struct CrateData<Id> {
|
||||
/// declared in source via `extern crate test`.
|
||||
pub dependencies: Vec<Dependency<Id>>,
|
||||
pub origin: CrateOrigin,
|
||||
/// Extra crate-level attributes, including the surrounding `#![]`.
|
||||
pub crate_attrs: Box<[Box<str>]>,
|
||||
pub is_proc_macro: bool,
|
||||
/// The working directory to run proc-macros in invoked in the context of this crate.
|
||||
/// This is the workspace root of the cargo workspace for workspace members, the crate manifest
|
||||
@ -530,6 +532,7 @@ impl CrateGraphBuilder {
|
||||
mut potential_cfg_options: Option<CfgOptions>,
|
||||
mut env: Env,
|
||||
origin: CrateOrigin,
|
||||
crate_attrs: Vec<String>,
|
||||
is_proc_macro: bool,
|
||||
proc_macro_cwd: Arc<AbsPathBuf>,
|
||||
ws_data: Arc<CrateWorkspaceData>,
|
||||
@ -539,12 +542,17 @@ impl CrateGraphBuilder {
|
||||
if let Some(potential_cfg_options) = &mut potential_cfg_options {
|
||||
potential_cfg_options.shrink_to_fit();
|
||||
}
|
||||
let crate_attrs: Vec<_> = crate_attrs
|
||||
.into_iter()
|
||||
.map(|raw_attr| format!("#![{raw_attr}]").into_boxed_str())
|
||||
.collect();
|
||||
self.arena.alloc(CrateBuilder {
|
||||
basic: CrateData {
|
||||
root_file_id,
|
||||
edition,
|
||||
dependencies: Vec::new(),
|
||||
origin,
|
||||
crate_attrs: crate_attrs.into_boxed_slice(),
|
||||
is_proc_macro,
|
||||
proc_macro_cwd,
|
||||
},
|
||||
@ -648,6 +656,7 @@ impl CrateGraphBuilder {
|
||||
edition: krate.basic.edition,
|
||||
is_proc_macro: krate.basic.is_proc_macro,
|
||||
origin: krate.basic.origin.clone(),
|
||||
crate_attrs: krate.basic.crate_attrs.clone(),
|
||||
root_file_id: krate.basic.root_file_id,
|
||||
proc_macro_cwd: krate.basic.proc_macro_cwd.clone(),
|
||||
};
|
||||
@ -975,6 +984,7 @@ mod tests {
|
||||
Default::default(),
|
||||
Env::default(),
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
Vec::new(),
|
||||
false,
|
||||
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
|
||||
empty_ws_data(),
|
||||
@ -988,6 +998,7 @@ mod tests {
|
||||
Default::default(),
|
||||
Env::default(),
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
Vec::new(),
|
||||
false,
|
||||
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
|
||||
empty_ws_data(),
|
||||
@ -1001,6 +1012,7 @@ mod tests {
|
||||
Default::default(),
|
||||
Env::default(),
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
Vec::new(),
|
||||
false,
|
||||
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
|
||||
empty_ws_data(),
|
||||
@ -1034,6 +1046,7 @@ mod tests {
|
||||
Default::default(),
|
||||
Env::default(),
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
Vec::new(),
|
||||
false,
|
||||
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
|
||||
empty_ws_data(),
|
||||
@ -1047,6 +1060,7 @@ mod tests {
|
||||
Default::default(),
|
||||
Env::default(),
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
Vec::new(),
|
||||
false,
|
||||
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
|
||||
empty_ws_data(),
|
||||
@ -1075,6 +1089,7 @@ mod tests {
|
||||
Default::default(),
|
||||
Env::default(),
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
Vec::new(),
|
||||
false,
|
||||
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
|
||||
empty_ws_data(),
|
||||
@ -1088,6 +1103,7 @@ mod tests {
|
||||
Default::default(),
|
||||
Env::default(),
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
Vec::new(),
|
||||
false,
|
||||
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
|
||||
empty_ws_data(),
|
||||
@ -1101,6 +1117,7 @@ mod tests {
|
||||
Default::default(),
|
||||
Env::default(),
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
Vec::new(),
|
||||
false,
|
||||
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
|
||||
empty_ws_data(),
|
||||
@ -1129,6 +1146,7 @@ mod tests {
|
||||
Default::default(),
|
||||
Env::default(),
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
Vec::new(),
|
||||
false,
|
||||
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
|
||||
empty_ws_data(),
|
||||
@ -1142,6 +1160,7 @@ mod tests {
|
||||
Default::default(),
|
||||
Env::default(),
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
Vec::new(),
|
||||
false,
|
||||
Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
|
||||
empty_ws_data(),
|
||||
|
||||
@ -39,7 +39,7 @@ use rustc_abi::ReprOptions;
|
||||
use rustc_hash::FxHashSet;
|
||||
use smallvec::SmallVec;
|
||||
use syntax::{
|
||||
AstNode, AstToken, NodeOrToken, SmolStr, SyntaxNode, SyntaxToken, T,
|
||||
AstNode, AstToken, NodeOrToken, SmolStr, SourceFile, SyntaxNode, SyntaxToken, T,
|
||||
ast::{self, AttrDocCommentIter, HasAttrs, IsString, TokenTreeChildren},
|
||||
};
|
||||
use tt::{TextRange, TextSize};
|
||||
@ -292,35 +292,69 @@ bitflags::bitflags! {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_extra_crate_attrs(db: &dyn DefDatabase, krate: Crate) -> Option<SourceFile> {
|
||||
let crate_data = krate.data(db);
|
||||
let crate_attrs = &crate_data.crate_attrs;
|
||||
if crate_attrs.is_empty() {
|
||||
return None;
|
||||
}
|
||||
// All attributes are already enclosed in `#![]`.
|
||||
let combined = crate_attrs.concat();
|
||||
let p = SourceFile::parse(&combined, crate_data.edition);
|
||||
|
||||
let errs = p.errors();
|
||||
if !errs.is_empty() {
|
||||
let base_msg = "Failed to parse extra crate-level attribute";
|
||||
let crate_name =
|
||||
krate.extra_data(db).display_name.as_ref().map_or("{unknown}", |name| name.as_str());
|
||||
let mut errs = errs.iter().peekable();
|
||||
let mut offset = TextSize::from(0);
|
||||
for raw_attr in crate_attrs {
|
||||
let attr_end = offset + TextSize::of(&**raw_attr);
|
||||
if errs.peeking_take_while(|e| e.range().start() < attr_end).count() > 0 {
|
||||
tracing::error!("{base_msg} {raw_attr} for crate {crate_name}");
|
||||
}
|
||||
offset = attr_end
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(p.tree())
|
||||
}
|
||||
|
||||
fn attrs_source(
|
||||
db: &dyn DefDatabase,
|
||||
owner: AttrDefId,
|
||||
) -> (InFile<ast::AnyHasAttrs>, Option<InFile<ast::Module>>, Crate) {
|
||||
) -> (InFile<ast::AnyHasAttrs>, Option<InFile<ast::Module>>, Option<SourceFile>, Crate) {
|
||||
let (owner, krate) = match owner {
|
||||
AttrDefId::ModuleId(id) => {
|
||||
let def_map = id.def_map(db);
|
||||
let (definition, declaration) = match def_map[id].origin {
|
||||
let krate = def_map.krate();
|
||||
let (definition, declaration, extra_crate_attrs) = match def_map[id].origin {
|
||||
ModuleOrigin::CrateRoot { definition } => {
|
||||
let file = db.parse(definition).tree();
|
||||
(InFile::new(definition.into(), ast::AnyHasAttrs::from(file)), None)
|
||||
let definition_source = db.parse(definition).tree();
|
||||
let definition = InFile::new(definition.into(), definition_source.into());
|
||||
let extra_crate_attrs = parse_extra_crate_attrs(db, krate);
|
||||
(definition, None, extra_crate_attrs)
|
||||
}
|
||||
ModuleOrigin::File { declaration, declaration_tree_id, definition, .. } => {
|
||||
let definition_source = db.parse(definition).tree();
|
||||
let definition = InFile::new(definition.into(), definition_source.into());
|
||||
let declaration = InFile::new(declaration_tree_id.file_id(), declaration);
|
||||
let declaration = declaration.with_value(declaration.to_node(db));
|
||||
let definition_source = db.parse(definition).tree();
|
||||
(InFile::new(definition.into(), definition_source.into()), Some(declaration))
|
||||
(definition, Some(declaration), None)
|
||||
}
|
||||
ModuleOrigin::Inline { definition_tree_id, definition } => {
|
||||
let definition = InFile::new(definition_tree_id.file_id(), definition);
|
||||
let definition = definition.with_value(definition.to_node(db).into());
|
||||
(definition, None)
|
||||
(definition, None, None)
|
||||
}
|
||||
ModuleOrigin::BlockExpr { block, .. } => {
|
||||
let definition = block.to_node(db);
|
||||
(block.with_value(definition.into()), None)
|
||||
(block.with_value(definition.into()), None, None)
|
||||
}
|
||||
};
|
||||
return (definition, declaration, def_map.krate());
|
||||
return (definition, declaration, extra_crate_attrs, krate);
|
||||
}
|
||||
AttrDefId::AdtId(AdtId::StructId(it)) => attrs_from_ast_id_loc(db, it),
|
||||
AttrDefId::AdtId(AdtId::UnionId(it)) => attrs_from_ast_id_loc(db, it),
|
||||
@ -339,7 +373,7 @@ fn attrs_source(
|
||||
AttrDefId::ExternCrateId(it) => attrs_from_ast_id_loc(db, it),
|
||||
AttrDefId::UseId(it) => attrs_from_ast_id_loc(db, it),
|
||||
};
|
||||
(owner, None, krate)
|
||||
(owner, None, None, krate)
|
||||
}
|
||||
|
||||
fn collect_attrs<BreakValue>(
|
||||
@ -347,14 +381,15 @@ fn collect_attrs<BreakValue>(
|
||||
owner: AttrDefId,
|
||||
mut callback: impl FnMut(Meta) -> ControlFlow<BreakValue>,
|
||||
) -> Option<BreakValue> {
|
||||
let (source, outer_mod_decl, krate) = attrs_source(db, owner);
|
||||
let (source, outer_mod_decl, extra_crate_attrs, krate) = attrs_source(db, owner);
|
||||
let extra_attrs = extra_crate_attrs
|
||||
.into_iter()
|
||||
.flat_map(|src| src.attrs())
|
||||
.chain(outer_mod_decl.into_iter().flat_map(|it| it.value.attrs()));
|
||||
|
||||
let mut cfg_options = None;
|
||||
expand_cfg_attr(
|
||||
outer_mod_decl
|
||||
.into_iter()
|
||||
.flat_map(|it| it.value.attrs())
|
||||
.chain(ast::attrs_including_inner(&source.value)),
|
||||
extra_attrs.chain(ast::attrs_including_inner(&source.value)),
|
||||
|| cfg_options.get_or_insert_with(|| krate.cfg_options(db)),
|
||||
move |meta, _, _, _| callback(meta),
|
||||
)
|
||||
@ -1013,10 +1048,12 @@ impl AttrFlags {
|
||||
pub fn doc_html_root_url(db: &dyn DefDatabase, krate: Crate) -> Option<SmolStr> {
|
||||
let root_file_id = krate.root_file_id(db);
|
||||
let syntax = db.parse(root_file_id).tree();
|
||||
let extra_crate_attrs =
|
||||
parse_extra_crate_attrs(db, krate).into_iter().flat_map(|src| src.attrs());
|
||||
|
||||
let mut cfg_options = None;
|
||||
expand_cfg_attr(
|
||||
syntax.attrs(),
|
||||
extra_crate_attrs.chain(syntax.attrs()),
|
||||
|| cfg_options.get_or_insert(krate.cfg_options(db)),
|
||||
|attr, _, _, _| {
|
||||
if let Meta::TokenTree { path, tt } = attr
|
||||
@ -1231,8 +1268,11 @@ impl AttrFlags {
|
||||
// We LRU this query because it is only used by IDE.
|
||||
#[salsa::tracked(returns(ref), lru = 250)]
|
||||
pub fn docs(db: &dyn DefDatabase, owner: AttrDefId) -> Option<Box<Docs>> {
|
||||
let (source, outer_mod_decl, krate) = attrs_source(db, owner);
|
||||
let (source, outer_mod_decl, _extra_crate_attrs, krate) = attrs_source(db, owner);
|
||||
let inner_attrs_node = source.value.inner_attributes_node();
|
||||
// Note: we don't have to pass down `_extra_crate_attrs` here, since `extract_docs`
|
||||
// does not handle crate-level attributes related to docs.
|
||||
// See: https://doc.rust-lang.org/rustdoc/write-documentation/the-doc-attribute.html#at-the-crate-level
|
||||
extract_docs(&|| krate.cfg_options(db), source, outer_mod_decl, inner_attrs_node)
|
||||
}
|
||||
|
||||
@ -1480,8 +1520,9 @@ mod tests {
|
||||
use test_fixture::WithFixture;
|
||||
use tt::{TextRange, TextSize};
|
||||
|
||||
use crate::attrs::IsInnerDoc;
|
||||
use crate::{attrs::Docs, test_db::TestDB};
|
||||
use crate::AttrDefId;
|
||||
use crate::attrs::{AttrFlags, Docs, IsInnerDoc};
|
||||
use crate::test_db::TestDB;
|
||||
|
||||
#[test]
|
||||
fn docs() {
|
||||
@ -1617,4 +1658,15 @@ mod tests {
|
||||
Some((in_file(range(263, 265)), IsInnerDoc::Yes))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crate_attrs() {
|
||||
let fixture = r#"
|
||||
//- /lib.rs crate:foo crate-attr:no_std crate-attr:cfg(target_arch="x86")
|
||||
"#;
|
||||
let (db, file_id) = TestDB::with_single_file(fixture);
|
||||
let module = db.module_for_file(file_id.file_id(&db));
|
||||
let attrs = AttrFlags::query(&db, AttrDefId::ModuleId(module));
|
||||
assert!(attrs.contains(AttrFlags::IS_NO_STD | AttrFlags::HAS_CFG));
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,6 +44,7 @@ use std::{
|
||||
};
|
||||
|
||||
use ast::{AstNode, StructKind};
|
||||
use cfg::CfgOptions;
|
||||
use hir_expand::{
|
||||
ExpandTo, HirFileId,
|
||||
mod_path::{ModPath, PathKind},
|
||||
@ -52,13 +53,17 @@ use hir_expand::{
|
||||
use intern::Interned;
|
||||
use la_arena::{Idx, RawIdx};
|
||||
use rustc_hash::FxHashMap;
|
||||
use span::{AstIdNode, Edition, FileAstId, SyntaxContext};
|
||||
use span::{
|
||||
AstIdNode, Edition, FileAstId, NO_DOWNMAP_ERASED_FILE_AST_ID_MARKER, Span, SpanAnchor,
|
||||
SyntaxContext,
|
||||
};
|
||||
use stdx::never;
|
||||
use syntax::{SyntaxKind, ast, match_ast};
|
||||
use syntax::{SourceFile, SyntaxKind, ast, match_ast};
|
||||
use thin_vec::ThinVec;
|
||||
use triomphe::Arc;
|
||||
use tt::TextRange;
|
||||
|
||||
use crate::{BlockId, Lookup, db::DefDatabase};
|
||||
use crate::{BlockId, Lookup, attrs::parse_extra_crate_attrs, db::DefDatabase};
|
||||
|
||||
pub(crate) use crate::item_tree::{
|
||||
attrs::*,
|
||||
@ -88,6 +93,33 @@ impl fmt::Debug for RawVisibilityId {
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_extra_crate_attrs<'a>(
|
||||
db: &dyn DefDatabase,
|
||||
crate_attrs_as_src: SourceFile,
|
||||
file_id: span::EditionedFileId,
|
||||
cfg_options: &dyn Fn() -> &'a CfgOptions,
|
||||
) -> AttrsOrCfg {
|
||||
#[derive(Copy, Clone)]
|
||||
struct FakeSpanMap {
|
||||
file_id: span::EditionedFileId,
|
||||
}
|
||||
impl syntax_bridge::SpanMapper<Span> for FakeSpanMap {
|
||||
fn span_for(&self, range: TextRange) -> Span {
|
||||
Span {
|
||||
range,
|
||||
anchor: SpanAnchor {
|
||||
file_id: self.file_id,
|
||||
ast_id: NO_DOWNMAP_ERASED_FILE_AST_ID_MARKER,
|
||||
},
|
||||
ctx: SyntaxContext::root(self.file_id.edition()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let span_map = FakeSpanMap { file_id };
|
||||
AttrsOrCfg::lower(db, &crate_attrs_as_src, cfg_options, span_map)
|
||||
}
|
||||
|
||||
#[salsa_macros::tracked(returns(deref))]
|
||||
pub(crate) fn file_item_tree_query(db: &dyn DefDatabase, file_id: HirFileId) -> Arc<ItemTree> {
|
||||
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) ->
|
||||
let mut item_tree = match_ast! {
|
||||
match syntax {
|
||||
ast::SourceFile(file) => {
|
||||
let top_attrs = ctx.lower_attrs(&file);
|
||||
let krate = file_id.krate(db);
|
||||
let root_file_id = krate.root_file_id(db);
|
||||
let extra_top_attrs = (file_id == root_file_id).then(|| {
|
||||
parse_extra_crate_attrs(db, krate).map(|crate_attrs| {
|
||||
let file_id = root_file_id.editioned_file_id(db);
|
||||
lower_extra_crate_attrs(db, crate_attrs, file_id, &|| ctx.cfg_options())
|
||||
})
|
||||
}).flatten();
|
||||
let top_attrs = match extra_top_attrs {
|
||||
Some(attrs @ AttrsOrCfg::Enabled { .. }) => attrs.merge(ctx.lower_attrs(&file)),
|
||||
Some(attrs @ AttrsOrCfg::CfgDisabled(_)) => attrs,
|
||||
None => ctx.lower_attrs(&file)
|
||||
};
|
||||
let mut item_tree = ctx.lower_module_items(&file);
|
||||
item_tree.top_attrs = top_attrs;
|
||||
item_tree
|
||||
|
||||
@ -16,9 +16,9 @@ use hir_expand::{
|
||||
attrs::{Attr, AttrId, AttrInput, Meta, collect_item_tree_attrs},
|
||||
mod_path::ModPath,
|
||||
name::Name,
|
||||
span_map::SpanMapRef,
|
||||
};
|
||||
use intern::{Interned, Symbol, sym};
|
||||
use span::Span;
|
||||
use syntax::{AstNode, T, ast};
|
||||
use syntax_bridge::DocCommentDesugarMode;
|
||||
use tt::token_to_literal;
|
||||
@ -42,12 +42,15 @@ impl Default for AttrsOrCfg {
|
||||
}
|
||||
|
||||
impl AttrsOrCfg {
|
||||
pub(crate) fn lower<'a>(
|
||||
pub(crate) fn lower<'a, S>(
|
||||
db: &dyn DefDatabase,
|
||||
owner: &dyn ast::HasAttrs,
|
||||
cfg_options: &dyn Fn() -> &'a CfgOptions,
|
||||
span_map: SpanMapRef<'_>,
|
||||
) -> AttrsOrCfg {
|
||||
span_map: S,
|
||||
) -> AttrsOrCfg
|
||||
where
|
||||
S: syntax_bridge::SpanMapper<Span> + Copy,
|
||||
{
|
||||
let mut attrs = Vec::new();
|
||||
let result =
|
||||
collect_item_tree_attrs::<Infallible>(owner, cfg_options, |meta, container, _, _| {
|
||||
@ -55,17 +58,17 @@ impl AttrsOrCfg {
|
||||
// tracking.
|
||||
let (span, path_range, input) = match meta {
|
||||
Meta::NamedKeyValue { path_range, name: _, value } => {
|
||||
let span = span_map.span_for_range(path_range);
|
||||
let span = span_map.span_for(path_range);
|
||||
let input = value.map(|value| {
|
||||
Box::new(AttrInput::Literal(token_to_literal(
|
||||
value.text(),
|
||||
span_map.span_for_range(value.text_range()),
|
||||
span_map.span_for(value.text_range()),
|
||||
)))
|
||||
});
|
||||
(span, path_range, input)
|
||||
}
|
||||
Meta::TokenTree { path, tt } => {
|
||||
let span = span_map.span_for_range(path.range);
|
||||
let span = span_map.span_for(path.range);
|
||||
let tt = syntax_bridge::syntax_node_to_token_tree(
|
||||
tt.syntax(),
|
||||
span_map,
|
||||
@ -76,7 +79,7 @@ impl AttrsOrCfg {
|
||||
(span, path.range, input)
|
||||
}
|
||||
Meta::Path { path } => {
|
||||
let span = span_map.span_for_range(path.range);
|
||||
let span = span_map.span_for(path.range);
|
||||
(span, path.range, None)
|
||||
}
|
||||
};
|
||||
@ -90,7 +93,7 @@ impl AttrsOrCfg {
|
||||
.filter(|it| it.kind().is_any_identifier());
|
||||
ModPath::from_tokens(
|
||||
db,
|
||||
&mut |range| span_map.span_for_range(range).ctx,
|
||||
&mut |range| span_map.span_for(range).ctx,
|
||||
is_abs,
|
||||
segments,
|
||||
)
|
||||
@ -107,6 +110,44 @@ impl AttrsOrCfg {
|
||||
None => AttrsOrCfg::Enabled { attrs },
|
||||
}
|
||||
}
|
||||
|
||||
// Merges two `AttrsOrCfg`s, assuming `self` is placed before `other` in the source code.
|
||||
// The operation follows these rules:
|
||||
//
|
||||
// - If `self` and `other` are both `AttrsOrCfg::Enabled`, the result is a new
|
||||
// `AttrsOrCfg::Enabled`. It contains the concatenation of `self`'s attributes followed by
|
||||
// `other`'s.
|
||||
// - If `self` is `AttrsOrCfg::Enabled` but `other` is `AttrsOrCfg::CfgDisabled`, the result
|
||||
// is a new `AttrsOrCfg::CfgDisabled`. It contains the concatenation of `self`'s attributes
|
||||
// followed by `other`'s.
|
||||
// - If `self` is `AttrsOrCfg::CfgDisabled`, return `self` as-is.
|
||||
//
|
||||
// The rationale is that attribute collection is sequential and order-sensitive. This operation
|
||||
// preserves those semantics when combining attributes from two different sources.
|
||||
// `AttrsOrCfg::CfgDisabled` marks a point where collection stops due to a false `#![cfg(...)]`
|
||||
// condition. It acts as a "breakpoint": attributes beyond it are not collected. Therefore,
|
||||
// when merging, an `AttrsOrCfg::CfgDisabled` on the left-hand side short-circuits the
|
||||
// operation, while an `AttrsOrCfg::CfgDisabled` on the right-hand side preserves all
|
||||
// attributes collected up to that point.
|
||||
//
|
||||
// Note that this operation is neither commutative nor associative.
|
||||
pub(crate) fn merge(self, other: AttrsOrCfg) -> AttrsOrCfg {
|
||||
match (self, other) {
|
||||
(AttrsOrCfg::Enabled { attrs }, AttrsOrCfg::Enabled { attrs: other_attrs }) => {
|
||||
let mut v = attrs.0.into_vec();
|
||||
v.extend(other_attrs.0);
|
||||
AttrsOrCfg::Enabled { attrs: AttrsOwned(v.into_boxed_slice()) }
|
||||
}
|
||||
(AttrsOrCfg::Enabled { attrs }, AttrsOrCfg::CfgDisabled(mut other)) => {
|
||||
let other_attrs = &mut other.1;
|
||||
let mut v = attrs.0.into_vec();
|
||||
v.extend(std::mem::take(&mut other_attrs.0));
|
||||
other_attrs.0 = v.into_boxed_slice();
|
||||
AttrsOrCfg::CfgDisabled(other)
|
||||
}
|
||||
(this @ AttrsOrCfg::CfgDisabled(_), _) => this,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
|
||||
@ -244,3 +244,45 @@ pub(self) struct S;
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crate_attrs_should_preserve_order() {
|
||||
check(
|
||||
r#"
|
||||
//- /main.rs crate:foo crate-attr:no_std crate-attr:features(f16) crate-attr:crate_type="bin"
|
||||
"#,
|
||||
expect![[r##"
|
||||
#![no_std]
|
||||
#![features(f16)]
|
||||
#![crate_type = "bin"]
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crate_attrs_with_disabled_cfg_injected() {
|
||||
check(
|
||||
r#"
|
||||
//- /main.rs crate:foo crate-attr:no_std crate-attr:cfg(false) crate-attr:features(f16,f128) crate-attr:crate_type="bin"
|
||||
"#,
|
||||
expect![[r#"
|
||||
#![no_std]
|
||||
#![cfg(false)]
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crate_attrs_with_disabled_cfg_in_source() {
|
||||
check(
|
||||
r#"
|
||||
//- /lib.rs crate:foo crate-attr:no_std
|
||||
#![cfg(false)]
|
||||
#![no_core]
|
||||
"#,
|
||||
expect![[r#"
|
||||
#![no_std]
|
||||
#![cfg(false)]
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
@ -2608,4 +2608,17 @@ foo!(KABOOM);
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crate_attrs() {
|
||||
let fixture = r#"
|
||||
//- /lib.rs crate:foo crate-attr:recursion_limit="4" crate-attr:no_core crate-attr:no_std crate-attr:feature(register_tool)
|
||||
"#;
|
||||
let (db, file_id) = TestDB::with_single_file(fixture);
|
||||
let def_map = crate_def_map(&db, file_id.krate(&db));
|
||||
assert_eq!(def_map.recursion_limit(), 4);
|
||||
assert!(def_map.is_no_core());
|
||||
assert!(def_map.is_no_std());
|
||||
assert!(def_map.is_unstable_feature_enabled(&sym::register_tool));
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,6 +76,7 @@ pub const BAZ: u32 = 0;
|
||||
None,
|
||||
Env::default(),
|
||||
CrateOrigin::Local { repo: None, name: Some(Symbol::intern(crate_name)) },
|
||||
Vec::new(),
|
||||
false,
|
||||
Arc::new(
|
||||
// FIXME: This is less than ideal
|
||||
@ -117,6 +118,7 @@ pub const BAZ: u32 = 0;
|
||||
expect![[r#"
|
||||
[
|
||||
"crate_local_def_map",
|
||||
"file_item_tree_query",
|
||||
"crate_local_def_map",
|
||||
]
|
||||
"#]],
|
||||
|
||||
@ -14,6 +14,7 @@ use base_db::FxIndexSet;
|
||||
use either::Either;
|
||||
use hir_def::{
|
||||
DefWithBodyId, FunctionId, MacroId, StructId, TraitId, VariantId,
|
||||
attrs::parse_extra_crate_attrs,
|
||||
expr_store::{Body, ExprOrPatSource, HygieneId, path::Path},
|
||||
hir::{BindingId, Expr, ExprId, ExprOrPatId, Pat},
|
||||
nameres::{ModuleOrigin, crate_def_map},
|
||||
@ -266,14 +267,27 @@ impl<DB: HirDatabase + ?Sized> Semantics<'_, DB> {
|
||||
|
||||
pub fn lint_attrs(
|
||||
&self,
|
||||
file_id: FileId,
|
||||
krate: Crate,
|
||||
item: ast::AnyHasAttrs,
|
||||
) -> impl DoubleEndedIterator<Item = (LintAttr, SmolStr)> {
|
||||
let mut cfg_options = None;
|
||||
let cfg_options = || *cfg_options.get_or_insert_with(|| krate.id.cfg_options(self.db));
|
||||
|
||||
let is_crate_root = file_id == krate.root_file(self.imp.db);
|
||||
let is_source_file = ast::SourceFile::can_cast(item.syntax().kind());
|
||||
let extra_crate_attrs = (is_crate_root && is_source_file)
|
||||
.then(|| {
|
||||
parse_extra_crate_attrs(self.imp.db, krate.id)
|
||||
.into_iter()
|
||||
.flat_map(|src| src.attrs())
|
||||
})
|
||||
.into_iter()
|
||||
.flatten();
|
||||
|
||||
let mut result = Vec::new();
|
||||
hir_expand::attrs::expand_cfg_attr::<Infallible>(
|
||||
ast::attrs_including_inner(&item),
|
||||
extra_crate_attrs.chain(ast::attrs_including_inner(&item)),
|
||||
cfg_options,
|
||||
|attr, _, _, _| {
|
||||
let hir_expand::attrs::Meta::TokenTree { path, tt } = attr else {
|
||||
|
||||
@ -387,6 +387,46 @@ struct S { field : u32 }
|
||||
fn f(S { field }: error) {
|
||||
// ^^^^^ 💡 warn: unused variable
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crate_attrs_lint_smoke_test() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
//- /lib.rs crate:foo crate-attr:deny(unused_variables)
|
||||
fn main() {
|
||||
let x = 2;
|
||||
//^ 💡 error: unused variable
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crate_attrs_should_not_override_lints_in_source() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
//- /lib.rs crate:foo crate-attr:allow(unused_variables)
|
||||
#![deny(unused_variables)]
|
||||
fn main() {
|
||||
let x = 2;
|
||||
//^ 💡 error: unused variable
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crate_attrs_should_preserve_lint_order() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
//- /lib.rs crate:foo crate-attr:allow(unused_variables) crate-attr:warn(unused_variables)
|
||||
fn main() {
|
||||
let x = 2;
|
||||
//^ 💡 warn: unused variable
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
@ -485,7 +485,7 @@ pub fn semantic_diagnostics(
|
||||
|
||||
// The edition isn't accurate (each diagnostics may have its own edition due to macros),
|
||||
// but it's okay as it's only being used for error recovery.
|
||||
handle_lints(&ctx.sema, krate, &mut lints, editioned_file_id.edition(db));
|
||||
handle_lints(&ctx.sema, file_id, krate, &mut lints, editioned_file_id.edition(db));
|
||||
|
||||
res.retain(|d| d.severity != Severity::Allow);
|
||||
|
||||
@ -593,6 +593,7 @@ fn build_lints_map(
|
||||
|
||||
fn handle_lints(
|
||||
sema: &Semantics<'_, RootDatabase>,
|
||||
file_id: FileId,
|
||||
krate: hir::Crate,
|
||||
diagnostics: &mut [(InFile<SyntaxNode>, &mut Diagnostic)],
|
||||
edition: Edition,
|
||||
@ -609,10 +610,10 @@ fn handle_lints(
|
||||
}
|
||||
|
||||
let mut diag_severity =
|
||||
lint_severity_at(sema, krate, node, &lint_groups(&diag.code, edition));
|
||||
lint_severity_at(sema, file_id, krate, node, &lint_groups(&diag.code, edition));
|
||||
|
||||
if let outline_diag_severity @ Some(_) =
|
||||
find_outline_mod_lint_severity(sema, krate, node, diag, edition)
|
||||
find_outline_mod_lint_severity(sema, file_id, krate, node, diag, edition)
|
||||
{
|
||||
diag_severity = outline_diag_severity;
|
||||
}
|
||||
@ -635,6 +636,7 @@ fn default_lint_severity(lint: &Lint, edition: Edition) -> Severity {
|
||||
|
||||
fn find_outline_mod_lint_severity(
|
||||
sema: &Semantics<'_, RootDatabase>,
|
||||
file_id: FileId,
|
||||
krate: hir::Crate,
|
||||
node: &InFile<SyntaxNode>,
|
||||
diag: &Diagnostic,
|
||||
@ -651,6 +653,7 @@ fn find_outline_mod_lint_severity(
|
||||
let lint_groups = lint_groups(&diag.code, edition);
|
||||
lint_attrs(
|
||||
sema,
|
||||
file_id,
|
||||
krate,
|
||||
ast::AnyHasAttrs::cast(module_source_file.value).expect("SourceFile always has attrs"),
|
||||
)
|
||||
@ -659,6 +662,7 @@ fn find_outline_mod_lint_severity(
|
||||
|
||||
fn lint_severity_at(
|
||||
sema: &Semantics<'_, RootDatabase>,
|
||||
file_id: FileId,
|
||||
krate: hir::Crate,
|
||||
node: &InFile<SyntaxNode>,
|
||||
lint_groups: &LintGroups,
|
||||
@ -667,21 +671,28 @@ fn lint_severity_at(
|
||||
.ancestors()
|
||||
.filter_map(ast::AnyHasAttrs::cast)
|
||||
.find_map(|ancestor| {
|
||||
lint_attrs(sema, krate, ancestor)
|
||||
lint_attrs(sema, file_id, krate, ancestor)
|
||||
.find_map(|(lint, severity)| lint_groups.contains(&lint).then_some(severity))
|
||||
})
|
||||
.or_else(|| {
|
||||
lint_severity_at(sema, krate, &sema.find_parent_file(node.file_id)?, lint_groups)
|
||||
lint_severity_at(
|
||||
sema,
|
||||
file_id,
|
||||
krate,
|
||||
&sema.find_parent_file(node.file_id)?,
|
||||
lint_groups,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// FIXME: Switch this to analysis' `expand_cfg_attr`.
|
||||
fn lint_attrs(
|
||||
sema: &Semantics<'_, RootDatabase>,
|
||||
file_id: FileId,
|
||||
krate: hir::Crate,
|
||||
ancestor: ast::AnyHasAttrs,
|
||||
) -> impl Iterator<Item = (SmolStr, Severity)> {
|
||||
sema.lint_attrs(krate, ancestor).rev().map(|(lint_attr, lint)| {
|
||||
sema.lint_attrs(file_id, krate, ancestor).rev().map(|(lint_attr, lint)| {
|
||||
let severity = match lint_attr {
|
||||
hir::LintAttr::Allow | hir::LintAttr::Expect => Severity::Allow,
|
||||
hir::LintAttr::Warn => Severity::Warning,
|
||||
|
||||
@ -658,6 +658,21 @@ pub struct B$0ar
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rewrite_html_root_url_using_crate_attr() {
|
||||
check_rewrite(
|
||||
r#"
|
||||
//- /main.rs crate:foo crate-attr:doc(arbitrary_attribute="test",html_root_url="https:/example.com",arbitrary_attribute2)
|
||||
pub mod foo {
|
||||
pub struct Foo;
|
||||
}
|
||||
/// [Foo](foo::Foo)
|
||||
pub struct B$0ar
|
||||
"#,
|
||||
expect"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rewrite_on_field() {
|
||||
check_rewrite(
|
||||
|
||||
@ -254,6 +254,7 @@ impl Analysis {
|
||||
TryFrom::try_from(&*std::env::current_dir().unwrap().as_path().to_string_lossy())
|
||||
.unwrap(),
|
||||
);
|
||||
let crate_attrs = Vec::new();
|
||||
cfg_options.insert_atom(sym::test);
|
||||
crate_graph.add_crate_root(
|
||||
file_id,
|
||||
@ -264,6 +265,7 @@ impl Analysis {
|
||||
None,
|
||||
Env::default(),
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
crate_attrs,
|
||||
false,
|
||||
proc_macro_cwd,
|
||||
Arc::new(CrateWorkspaceData {
|
||||
|
||||
@ -40,6 +40,7 @@ pub(crate) fn status(db: &RootDatabase, file_id: Option<FileId>) -> String {
|
||||
edition,
|
||||
dependencies,
|
||||
origin,
|
||||
crate_attrs,
|
||||
is_proc_macro,
|
||||
proc_macro_cwd,
|
||||
} = crate_id.data(db);
|
||||
@ -62,6 +63,7 @@ pub(crate) fn status(db: &RootDatabase, file_id: Option<FileId>) -> String {
|
||||
format_to!(buf, " Potential cfgs: {:?}\n", potential_cfg_options);
|
||||
format_to!(buf, " Env: {:?}\n", env);
|
||||
format_to!(buf, " Origin: {:?}\n", origin);
|
||||
format_to!(buf, " Extra crate-level attrs: {:?}\n", crate_attrs);
|
||||
format_to!(buf, " Is a proc macro crate: {}\n", is_proc_macro);
|
||||
format_to!(buf, " Proc macro cwd: {:?}\n", proc_macro_cwd);
|
||||
let deps = dependencies
|
||||
|
||||
@ -163,6 +163,7 @@ impl ProjectJson {
|
||||
cfg,
|
||||
target: crate_data.target,
|
||||
env: crate_data.env,
|
||||
crate_attrs: crate_data.crate_attrs,
|
||||
proc_macro_dylib_path: crate_data
|
||||
.proc_macro_dylib_path
|
||||
.map(absolutize_on_base),
|
||||
@ -244,6 +245,8 @@ pub struct Crate {
|
||||
pub(crate) cfg: Vec<CfgAtom>,
|
||||
pub(crate) target: Option<String>,
|
||||
pub(crate) env: FxHashMap<String, String>,
|
||||
// Extra crate-level attributes, without the surrounding `#![]`.
|
||||
pub(crate) crate_attrs: Vec<String>,
|
||||
pub(crate) proc_macro_dylib_path: Option<AbsPathBuf>,
|
||||
pub(crate) is_workspace_member: bool,
|
||||
pub(crate) include: Vec<AbsPathBuf>,
|
||||
@ -365,6 +368,8 @@ struct CrateData {
|
||||
target: Option<String>,
|
||||
#[serde(default)]
|
||||
env: FxHashMap<String, String>,
|
||||
#[serde(default)]
|
||||
crate_attrs: Vec<String>,
|
||||
proc_macro_dylib_path: Option<Utf8PathBuf>,
|
||||
is_workspace_member: Option<bool>,
|
||||
source: Option<CrateSource>,
|
||||
|
||||
@ -198,6 +198,15 @@ fn rust_project_cfg_groups() {
|
||||
check_crate_graph(crate_graph, expect_file!["../test_data/output/rust_project_cfg_groups.txt"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rust_project_crate_attrs() {
|
||||
let (crate_graph, _proc_macros) = load_rust_project("crate-attrs.json");
|
||||
check_crate_graph(
|
||||
crate_graph,
|
||||
expect_file!["../test_data/output/rust_project_crate_attrs.txt"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crate_graph_dedup_identical() {
|
||||
let (mut crate_graph, proc_macros) = load_cargo("regex-metadata.json");
|
||||
|
||||
@ -1093,6 +1093,7 @@ fn project_json_to_crate_graph(
|
||||
cfg,
|
||||
target,
|
||||
env,
|
||||
crate_attrs,
|
||||
proc_macro_dylib_path,
|
||||
is_proc_macro,
|
||||
repository,
|
||||
@ -1163,6 +1164,7 @@ fn project_json_to_crate_graph(
|
||||
} else {
|
||||
CrateOrigin::Local { repo: None, name: None }
|
||||
},
|
||||
crate_attrs.clone(),
|
||||
*is_proc_macro,
|
||||
match proc_macro_cwd {
|
||||
Some(path) => Arc::new(path.clone()),
|
||||
@ -1467,6 +1469,7 @@ fn detached_file_to_crate_graph(
|
||||
repo: None,
|
||||
name: display_name.map(|n| n.canonical_name().to_owned()),
|
||||
},
|
||||
Vec::new(),
|
||||
false,
|
||||
Arc::new(detached_file.parent().to_path_buf()),
|
||||
crate_ws_data,
|
||||
@ -1647,6 +1650,7 @@ fn add_target_crate_root(
|
||||
potential_cfg_options,
|
||||
env,
|
||||
origin,
|
||||
Vec::new(),
|
||||
matches!(kind, TargetKind::Lib { is_proc_macro: true }),
|
||||
proc_macro_cwd,
|
||||
crate_ws_data,
|
||||
@ -1830,6 +1834,7 @@ fn sysroot_to_crate_graph(
|
||||
None,
|
||||
Env::default(),
|
||||
CrateOrigin::Lang(LangCrateOrigin::from(&*stitched[krate].name)),
|
||||
Vec::new(),
|
||||
false,
|
||||
Arc::new(stitched[krate].root.parent().to_path_buf()),
|
||||
crate_ws_data.clone(),
|
||||
|
||||
13
crates/project-model/test_data/crate-attrs.json
Normal file
13
crates/project-model/test_data/crate-attrs.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"sysroot_src": null,
|
||||
"crates": [
|
||||
{
|
||||
"display_name": "foo",
|
||||
"root_module": "$ROOT$src/lib.rs",
|
||||
"edition": "2024",
|
||||
"deps": [],
|
||||
"crate_attrs": ["no_std", "feature(f16,f128)", "crate_type = \"lib\""],
|
||||
"is_workspace_member": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -21,6 +21,7 @@
|
||||
"hello-world",
|
||||
),
|
||||
},
|
||||
crate_attrs: [],
|
||||
is_proc_macro: false,
|
||||
proc_macro_cwd: AbsPathBuf(
|
||||
"$ROOT$hello-world",
|
||||
@ -106,6 +107,7 @@
|
||||
"hello-world",
|
||||
),
|
||||
},
|
||||
crate_attrs: [],
|
||||
is_proc_macro: false,
|
||||
proc_macro_cwd: AbsPathBuf(
|
||||
"$ROOT$hello-world",
|
||||
@ -191,6 +193,7 @@
|
||||
"hello-world",
|
||||
),
|
||||
},
|
||||
crate_attrs: [],
|
||||
is_proc_macro: false,
|
||||
proc_macro_cwd: AbsPathBuf(
|
||||
"$ROOT$hello-world",
|
||||
@ -276,6 +279,7 @@
|
||||
"hello-world",
|
||||
),
|
||||
},
|
||||
crate_attrs: [],
|
||||
is_proc_macro: false,
|
||||
proc_macro_cwd: AbsPathBuf(
|
||||
"$ROOT$hello-world",
|
||||
@ -344,6 +348,7 @@
|
||||
),
|
||||
name: "libc",
|
||||
},
|
||||
crate_attrs: [],
|
||||
is_proc_macro: false,
|
||||
proc_macro_cwd: AbsPathBuf(
|
||||
"$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.98",
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
"hello-world",
|
||||
),
|
||||
},
|
||||
crate_attrs: [],
|
||||
is_proc_macro: false,
|
||||
proc_macro_cwd: AbsPathBuf(
|
||||
"$ROOT$hello-world",
|
||||
@ -106,6 +107,7 @@
|
||||
"hello-world",
|
||||
),
|
||||
},
|
||||
crate_attrs: [],
|
||||
is_proc_macro: false,
|
||||
proc_macro_cwd: AbsPathBuf(
|
||||
"$ROOT$hello-world",
|
||||
@ -191,6 +193,7 @@
|
||||
"hello-world",
|
||||
),
|
||||
},
|
||||
crate_attrs: [],
|
||||
is_proc_macro: false,
|
||||
proc_macro_cwd: AbsPathBuf(
|
||||
"$ROOT$hello-world",
|
||||
@ -276,6 +279,7 @@
|
||||
"hello-world",
|
||||
),
|
||||
},
|
||||
crate_attrs: [],
|
||||
is_proc_macro: false,
|
||||
proc_macro_cwd: AbsPathBuf(
|
||||
"$ROOT$hello-world",
|
||||
@ -344,6 +348,7 @@
|
||||
),
|
||||
name: "libc",
|
||||
},
|
||||
crate_attrs: [],
|
||||
is_proc_macro: false,
|
||||
proc_macro_cwd: AbsPathBuf(
|
||||
"$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.98",
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
"hello-world",
|
||||
),
|
||||
},
|
||||
crate_attrs: [],
|
||||
is_proc_macro: false,
|
||||
proc_macro_cwd: AbsPathBuf(
|
||||
"$ROOT$hello-world",
|
||||
@ -105,6 +106,7 @@
|
||||
"hello-world",
|
||||
),
|
||||
},
|
||||
crate_attrs: [],
|
||||
is_proc_macro: false,
|
||||
proc_macro_cwd: AbsPathBuf(
|
||||
"$ROOT$hello-world",
|
||||
@ -189,6 +191,7 @@
|
||||
"hello-world",
|
||||
),
|
||||
},
|
||||
crate_attrs: [],
|
||||
is_proc_macro: false,
|
||||
proc_macro_cwd: AbsPathBuf(
|
||||
"$ROOT$hello-world",
|
||||
@ -273,6 +276,7 @@
|
||||
"hello-world",
|
||||
),
|
||||
},
|
||||
crate_attrs: [],
|
||||
is_proc_macro: false,
|
||||
proc_macro_cwd: AbsPathBuf(
|
||||
"$ROOT$hello-world",
|
||||
@ -340,6 +344,7 @@
|
||||
),
|
||||
name: "libc",
|
||||
},
|
||||
crate_attrs: [],
|
||||
is_proc_macro: false,
|
||||
proc_macro_cwd: AbsPathBuf(
|
||||
"$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.98",
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
"hello_world",
|
||||
),
|
||||
},
|
||||
crate_attrs: [],
|
||||
is_proc_macro: false,
|
||||
proc_macro_cwd: AbsPathBuf(
|
||||
"$ROOT$",
|
||||
@ -62,6 +63,7 @@
|
||||
"other_crate",
|
||||
),
|
||||
},
|
||||
crate_attrs: [],
|
||||
is_proc_macro: false,
|
||||
proc_macro_cwd: AbsPathBuf(
|
||||
"$ROOT$",
|
||||
|
||||
@ -0,0 +1,54 @@
|
||||
{
|
||||
0: CrateBuilder {
|
||||
basic: CrateData {
|
||||
root_file_id: FileId(
|
||||
1,
|
||||
),
|
||||
edition: Edition2024,
|
||||
dependencies: [],
|
||||
origin: Local {
|
||||
repo: None,
|
||||
name: Some(
|
||||
"foo",
|
||||
),
|
||||
},
|
||||
crate_attrs: [
|
||||
"#![no_std]",
|
||||
"#![feature(f16,f128)]",
|
||||
"#![crate_type = \"lib\"]",
|
||||
],
|
||||
is_proc_macro: false,
|
||||
proc_macro_cwd: AbsPathBuf(
|
||||
"$ROOT$",
|
||||
),
|
||||
},
|
||||
extra: ExtraCrateData {
|
||||
version: None,
|
||||
display_name: Some(
|
||||
CrateDisplayName {
|
||||
crate_name: CrateName(
|
||||
"foo",
|
||||
),
|
||||
canonical_name: "foo",
|
||||
},
|
||||
),
|
||||
potential_cfg_options: None,
|
||||
},
|
||||
cfg_options: CfgOptions(
|
||||
[
|
||||
"rust_analyzer",
|
||||
"test",
|
||||
"true",
|
||||
],
|
||||
),
|
||||
env: Env {
|
||||
entries: {},
|
||||
},
|
||||
ws_data: CrateWorkspaceData {
|
||||
target: Err(
|
||||
"test has no target data",
|
||||
),
|
||||
toolchain: None,
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -12,6 +12,7 @@
|
||||
"hello_world",
|
||||
),
|
||||
},
|
||||
crate_attrs: [],
|
||||
is_proc_macro: false,
|
||||
proc_macro_cwd: AbsPathBuf(
|
||||
"$ROOT$",
|
||||
|
||||
@ -239,6 +239,7 @@ impl ChangeFixture {
|
||||
Some(meta.cfg),
|
||||
meta.env,
|
||||
origin,
|
||||
meta.crate_attrs,
|
||||
false,
|
||||
proc_macro_cwd.clone(),
|
||||
crate_ws_data.clone(),
|
||||
@ -292,6 +293,7 @@ impl ChangeFixture {
|
||||
String::from("__ra_is_test_fixture"),
|
||||
)]),
|
||||
CrateOrigin::Lang(LangCrateOrigin::Core),
|
||||
Vec::new(),
|
||||
false,
|
||||
proc_macro_cwd.clone(),
|
||||
crate_ws_data.clone(),
|
||||
@ -322,6 +324,7 @@ impl ChangeFixture {
|
||||
Some(default_cfg),
|
||||
default_env,
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
Vec::new(),
|
||||
false,
|
||||
proc_macro_cwd.clone(),
|
||||
crate_ws_data.clone(),
|
||||
@ -385,6 +388,7 @@ impl ChangeFixture {
|
||||
String::from("__ra_is_test_fixture"),
|
||||
)]),
|
||||
CrateOrigin::Local { repo: None, name: None },
|
||||
Vec::new(),
|
||||
true,
|
||||
proc_macro_cwd,
|
||||
crate_ws_data,
|
||||
@ -635,6 +639,7 @@ struct FileMeta {
|
||||
cfg: CfgOptions,
|
||||
edition: Edition,
|
||||
env: Env,
|
||||
crate_attrs: Vec<String>,
|
||||
introduce_new_source_root: Option<SourceRootKind>,
|
||||
}
|
||||
|
||||
@ -666,6 +671,7 @@ impl FileMeta {
|
||||
cfg,
|
||||
edition: f.edition.map_or(Edition::CURRENT, |v| Edition::from_str(&v).unwrap()),
|
||||
env: f.env.into_iter().collect(),
|
||||
crate_attrs: f.crate_attrs,
|
||||
introduce_new_source_root,
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,6 +107,11 @@ pub struct Fixture {
|
||||
///
|
||||
/// Syntax: `env:PATH=/bin,RUST_LOG=debug`
|
||||
pub env: FxHashMap<String, String>,
|
||||
/// Specifies extra crate-level attributes injected at the top of the crate root file.
|
||||
/// This must be used with `crate` meta.
|
||||
///
|
||||
/// Syntax: `crate-attr:no_std crate-attr:features(f16,f128) crate-attr:cfg(target_arch="x86")`
|
||||
pub crate_attrs: Vec<String>,
|
||||
/// Introduces a new source root. This file **and the following
|
||||
/// files** will belong the new source root. This must be used
|
||||
/// with `crate` meta.
|
||||
@ -275,6 +280,7 @@ impl FixtureWithProjectMeta {
|
||||
|
||||
let mut krate = None;
|
||||
let mut deps = Vec::new();
|
||||
let mut crate_attrs = Vec::new();
|
||||
let mut extern_prelude = None;
|
||||
let mut edition = None;
|
||||
let mut cfgs = Vec::new();
|
||||
@ -292,6 +298,7 @@ impl FixtureWithProjectMeta {
|
||||
match key {
|
||||
"crate" => krate = Some(value.to_owned()),
|
||||
"deps" => deps = value.split(',').map(|it| it.to_owned()).collect(),
|
||||
"crate-attr" => crate_attrs.push(value.to_owned()),
|
||||
"extern-prelude" => {
|
||||
if value.is_empty() {
|
||||
extern_prelude = Some(Vec::new());
|
||||
@ -334,6 +341,7 @@ impl FixtureWithProjectMeta {
|
||||
line,
|
||||
krate,
|
||||
deps,
|
||||
crate_attrs,
|
||||
extern_prelude,
|
||||
cfgs,
|
||||
edition,
|
||||
@ -548,7 +556,7 @@ fn parse_fixture_gets_full_meta() {
|
||||
//- toolchain: nightly
|
||||
//- proc_macros: identity
|
||||
//- minicore: coerce_unsized
|
||||
//- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b,atom env:OUTDIR=path/to,OTHER=foo
|
||||
//- /lib.rs crate:foo deps:bar,baz crate-attr:no_std crate-attr:features(f16,f128) crate-attr:cfg(target_arch="x86") cfg:foo=a,bar=b,atom env:OUTDIR=path/to,OTHER=foo
|
||||
mod m;
|
||||
"#,
|
||||
);
|
||||
@ -561,6 +569,14 @@ mod m;
|
||||
assert_eq!("mod m;\n", meta.text);
|
||||
|
||||
assert_eq!("foo", meta.krate.as_ref().unwrap());
|
||||
assert_eq!(
|
||||
vec![
|
||||
"no_std".to_owned(),
|
||||
"features(f16,f128)".to_owned(),
|
||||
"cfg(target_arch=\"x86\")".to_owned()
|
||||
],
|
||||
meta.crate_attrs
|
||||
);
|
||||
assert_eq!("/lib.rs", meta.path);
|
||||
assert_eq!(2, meta.env.len());
|
||||
}
|
||||
|
||||
@ -144,6 +144,15 @@ interface Crate {
|
||||
/// Environment variables, used for
|
||||
/// the `env!` macro
|
||||
env: { [key: string]: string; };
|
||||
/// Extra crate-level attributes applied to this crate.
|
||||
///
|
||||
/// rust-analyzer will behave as if these attributes
|
||||
/// were present before the first source line of the
|
||||
/// crate root.
|
||||
///
|
||||
/// Each string should contain the contents of a `#![...]`
|
||||
/// crate-level attribute, without the surrounding `#![]`.
|
||||
crate_attrs?: string[];
|
||||
|
||||
/// Whether the crate is a proc-macro crate.
|
||||
is_proc_macro: boolean;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user