Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions crates/base-db/src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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>,
Expand All @@ -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,
},
Expand Down Expand Up @@ -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(),
};
Expand Down Expand Up @@ -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(),
Expand All @@ -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(),
Expand All @@ -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(),
Expand Down Expand Up @@ -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(),
Expand All @@ -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(),
Expand Down Expand Up @@ -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(),
Expand All @@ -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(),
Expand All @@ -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(),
Expand Down Expand Up @@ -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(),
Expand All @@ -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(),
Expand Down
92 changes: 72 additions & 20 deletions crates/hir-def/src/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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),
Expand All @@ -339,22 +373,23 @@ 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>(
db: &dyn DefDatabase,
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),
)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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));
}
}
52 changes: 48 additions & 4 deletions crates/hir-def/src/item_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ use std::{
};

use ast::{AstNode, StructKind};
use cfg::CfgOptions;
use hir_expand::{
ExpandTo, HirFileId,
mod_path::{ModPath, PathKind},
Expand All @@ -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::*,
Expand Down Expand Up @@ -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();
Expand All @@ -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
Expand Down
Loading