Auto merge of #13189 - unexge:unconfigured-diagnostics-for-fields, r=Veykril

Emit unconfigured code diagnostics for enum variants and struct/union fields

Fixes https://github.com/rust-lang/rust-analyzer/issues/12664
This commit is contained in:
bors 2022-09-27 12:59:45 +00:00
commit b01cdd89d5
13 changed files with 255 additions and 57 deletions

View File

@ -6,7 +6,7 @@ use base_db::CrateId;
use either::Either;
use hir_expand::{
name::{AsName, Name},
InFile,
HirFileId, InFile,
};
use la_arena::{Arena, ArenaMap};
use syntax::ast::{self, HasName, HasVisibility};
@ -17,13 +17,15 @@ use crate::{
builtin_type::{BuiltinInt, BuiltinUint},
db::DefDatabase,
intern::Interned,
item_tree::{AttrOwner, Field, Fields, ItemTree, ModItem, RawVisibilityId},
item_tree::{AttrOwner, Field, FieldAstId, Fields, ItemTree, ModItem, RawVisibilityId},
nameres::diagnostics::DefDiagnostic,
src::HasChildSource,
src::HasSource,
trace::Trace,
type_ref::TypeRef,
visibility::RawVisibility,
EnumId, LocalEnumVariantId, LocalFieldId, Lookup, ModuleId, StructId, UnionId, VariantId,
EnumId, LocalEnumVariantId, LocalFieldId, LocalModuleId, Lookup, ModuleId, StructId, UnionId,
VariantId,
};
use cfg::CfgOptions;
@ -143,6 +145,13 @@ fn parse_repr_tt(tt: &Subtree) -> Option<ReprData> {
impl StructData {
pub(crate) fn struct_data_query(db: &dyn DefDatabase, id: StructId) -> Arc<StructData> {
db.struct_data_with_diagnostics(id).0
}
pub(crate) fn struct_data_with_diagnostics_query(
db: &dyn DefDatabase,
id: StructId,
) -> (Arc<StructData>, Arc<[DefDiagnostic]>) {
let loc = id.lookup(db);
let krate = loc.container.krate;
let item_tree = loc.id.item_tree(db);
@ -150,16 +159,35 @@ impl StructData {
let cfg_options = db.crate_graph()[loc.container.krate].cfg_options.clone();
let strukt = &item_tree[loc.id.value];
let variant_data = lower_fields(db, krate, &item_tree, &cfg_options, &strukt.fields, None);
Arc::new(StructData {
name: strukt.name.clone(),
variant_data: Arc::new(variant_data),
repr,
visibility: item_tree[strukt.visibility].clone(),
})
let (variant_data, diagnostics) = lower_fields(
db,
krate,
loc.id.file_id(),
loc.container.local_id,
&item_tree,
&cfg_options,
&strukt.fields,
None,
);
(
Arc::new(StructData {
name: strukt.name.clone(),
variant_data: Arc::new(variant_data),
repr,
visibility: item_tree[strukt.visibility].clone(),
}),
diagnostics.into(),
)
}
pub(crate) fn union_data_query(db: &dyn DefDatabase, id: UnionId) -> Arc<StructData> {
db.union_data_with_diagnostics(id).0
}
pub(crate) fn union_data_with_diagnostics_query(
db: &dyn DefDatabase,
id: UnionId,
) -> (Arc<StructData>, Arc<[DefDiagnostic]>) {
let loc = id.lookup(db);
let krate = loc.container.krate;
let item_tree = loc.id.item_tree(db);
@ -167,19 +195,37 @@ impl StructData {
let cfg_options = db.crate_graph()[loc.container.krate].cfg_options.clone();
let union = &item_tree[loc.id.value];
let variant_data = lower_fields(db, krate, &item_tree, &cfg_options, &union.fields, None);
Arc::new(StructData {
name: union.name.clone(),
variant_data: Arc::new(variant_data),
repr,
visibility: item_tree[union.visibility].clone(),
})
let (variant_data, diagnostics) = lower_fields(
db,
krate,
loc.id.file_id(),
loc.container.local_id,
&item_tree,
&cfg_options,
&union.fields,
None,
);
(
Arc::new(StructData {
name: union.name.clone(),
variant_data: Arc::new(variant_data),
repr,
visibility: item_tree[union.visibility].clone(),
}),
diagnostics.into(),
)
}
}
impl EnumData {
pub(crate) fn enum_data_query(db: &dyn DefDatabase, e: EnumId) -> Arc<EnumData> {
db.enum_data_with_diagnostics(e).0
}
pub(crate) fn enum_data_with_diagnostics_query(
db: &dyn DefDatabase,
e: EnumId,
) -> (Arc<EnumData>, Arc<[DefDiagnostic]>) {
let loc = e.lookup(db);
let krate = loc.container.krate;
let item_tree = loc.id.item_tree(db);
@ -188,31 +234,46 @@ impl EnumData {
let enum_ = &item_tree[loc.id.value];
let mut variants = Arena::new();
let mut diagnostics = Vec::new();
for tree_id in enum_.variants.clone() {
if item_tree.attrs(db, krate, tree_id.into()).is_cfg_enabled(&cfg_options) {
let var = &item_tree[tree_id];
let var_data = lower_fields(
let attrs = item_tree.attrs(db, krate, tree_id.into());
let var = &item_tree[tree_id];
if attrs.is_cfg_enabled(&cfg_options) {
let (var_data, field_diagnostics) = lower_fields(
db,
krate,
loc.id.file_id(),
loc.container.local_id,
&item_tree,
&cfg_options,
&var.fields,
Some(enum_.visibility),
);
diagnostics.extend(field_diagnostics);
variants.alloc(EnumVariantData {
name: var.name.clone(),
variant_data: Arc::new(var_data),
});
} else {
diagnostics.push(DefDiagnostic::unconfigured_code(
loc.container.local_id,
InFile::new(loc.id.file_id(), var.ast_id.upcast()),
attrs.cfg().unwrap(),
cfg_options.clone(),
))
}
}
Arc::new(EnumData {
name: enum_.name.clone(),
variants,
repr,
visibility: item_tree[enum_.visibility].clone(),
})
(
Arc::new(EnumData {
name: enum_.name.clone(),
variants,
repr,
visibility: item_tree[enum_.visibility].clone(),
}),
diagnostics.into(),
)
}
pub fn variant(&self, name: &Name) -> Option<LocalEnumVariantId> {
@ -384,31 +445,64 @@ fn lower_struct(
fn lower_fields(
db: &dyn DefDatabase,
krate: CrateId,
current_file_id: HirFileId,
container: LocalModuleId,
item_tree: &ItemTree,
cfg_options: &CfgOptions,
fields: &Fields,
override_visibility: Option<RawVisibilityId>,
) -> VariantData {
) -> (VariantData, Vec<DefDiagnostic>) {
let mut diagnostics = Vec::new();
match fields {
Fields::Record(flds) => {
let mut arena = Arena::new();
for field_id in flds.clone() {
if item_tree.attrs(db, krate, field_id.into()).is_cfg_enabled(cfg_options) {
arena.alloc(lower_field(item_tree, &item_tree[field_id], override_visibility));
let attrs = item_tree.attrs(db, krate, field_id.into());
let field = &item_tree[field_id];
if attrs.is_cfg_enabled(cfg_options) {
arena.alloc(lower_field(item_tree, field, override_visibility));
} else {
diagnostics.push(DefDiagnostic::unconfigured_code(
container,
InFile::new(
current_file_id,
match field.ast_id {
FieldAstId::Record(it) => it.upcast(),
FieldAstId::Tuple(it) => it.upcast(),
},
),
attrs.cfg().unwrap(),
cfg_options.clone(),
))
}
}
VariantData::Record(arena)
(VariantData::Record(arena), diagnostics)
}
Fields::Tuple(flds) => {
let mut arena = Arena::new();
for field_id in flds.clone() {
if item_tree.attrs(db, krate, field_id.into()).is_cfg_enabled(cfg_options) {
arena.alloc(lower_field(item_tree, &item_tree[field_id], override_visibility));
let attrs = item_tree.attrs(db, krate, field_id.into());
let field = &item_tree[field_id];
if attrs.is_cfg_enabled(cfg_options) {
arena.alloc(lower_field(item_tree, field, override_visibility));
} else {
diagnostics.push(DefDiagnostic::unconfigured_code(
container,
InFile::new(
current_file_id,
match field.ast_id {
FieldAstId::Record(it) => it.upcast(),
FieldAstId::Tuple(it) => it.upcast(),
},
),
attrs.cfg().unwrap(),
cfg_options.clone(),
))
}
}
VariantData::Tuple(arena)
(VariantData::Tuple(arena), diagnostics)
}
Fields::Unit => VariantData::Unit,
Fields::Unit => (VariantData::Unit, diagnostics),
}
}

View File

@ -219,7 +219,7 @@ impl TraitData {
pub(crate) fn trait_data_with_diagnostics_query(
db: &dyn DefDatabase,
tr: TraitId,
) -> (Arc<TraitData>, Arc<Vec<DefDiagnostic>>) {
) -> (Arc<TraitData>, Arc<[DefDiagnostic]>) {
let tr_loc @ ItemLoc { container: module_id, id: tree_id } = tr.lookup(db);
let item_tree = tree_id.item_tree(db);
let tr_def = &item_tree[tree_id.value];
@ -251,7 +251,7 @@ impl TraitData {
visibility,
skip_array_during_method_dispatch,
}),
Arc::new(diagnostics),
diagnostics.into(),
)
}
@ -299,7 +299,7 @@ impl ImplData {
pub(crate) fn impl_data_with_diagnostics_query(
db: &dyn DefDatabase,
id: ImplId,
) -> (Arc<ImplData>, Arc<Vec<DefDiagnostic>>) {
) -> (Arc<ImplData>, Arc<[DefDiagnostic]>) {
let _p = profile::span("impl_data_with_diagnostics_query");
let ItemLoc { container: module_id, id: tree_id } = id.lookup(db);
@ -318,7 +318,7 @@ impl ImplData {
(
Arc::new(ImplData { target_trait, self_ty, items, is_negative, attribute_calls }),
Arc::new(diagnostics),
diagnostics.into(),
)
}

View File

@ -97,24 +97,33 @@ pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> {
#[salsa::invoke(StructData::struct_data_query)]
fn struct_data(&self, id: StructId) -> Arc<StructData>;
#[salsa::invoke(StructData::struct_data_with_diagnostics_query)]
fn struct_data_with_diagnostics(&self, id: StructId)
-> (Arc<StructData>, Arc<[DefDiagnostic]>);
#[salsa::invoke(StructData::union_data_query)]
fn union_data(&self, id: UnionId) -> Arc<StructData>;
#[salsa::invoke(StructData::union_data_with_diagnostics_query)]
fn union_data_with_diagnostics(&self, id: UnionId) -> (Arc<StructData>, Arc<[DefDiagnostic]>);
#[salsa::invoke(EnumData::enum_data_query)]
fn enum_data(&self, e: EnumId) -> Arc<EnumData>;
#[salsa::invoke(EnumData::enum_data_with_diagnostics_query)]
fn enum_data_with_diagnostics(&self, e: EnumId) -> (Arc<EnumData>, Arc<[DefDiagnostic]>);
#[salsa::invoke(ImplData::impl_data_query)]
fn impl_data(&self, e: ImplId) -> Arc<ImplData>;
#[salsa::invoke(ImplData::impl_data_with_diagnostics_query)]
fn impl_data_with_diagnostics(&self, e: ImplId) -> (Arc<ImplData>, Arc<Vec<DefDiagnostic>>);
fn impl_data_with_diagnostics(&self, e: ImplId) -> (Arc<ImplData>, Arc<[DefDiagnostic]>);
#[salsa::invoke(TraitData::trait_data_query)]
fn trait_data(&self, e: TraitId) -> Arc<TraitData>;
#[salsa::invoke(TraitData::trait_data_with_diagnostics_query)]
fn trait_data_with_diagnostics(&self, tr: TraitId)
-> (Arc<TraitData>, Arc<Vec<DefDiagnostic>>);
fn trait_data_with_diagnostics(&self, tr: TraitId) -> (Arc<TraitData>, Arc<[DefDiagnostic]>);
#[salsa::invoke(TypeAliasData::type_alias_data_query)]
fn type_alias_data(&self, e: TypeAliasId) -> Arc<TypeAliasData>;

View File

@ -943,6 +943,7 @@ impl AssocItem {
pub struct Variant {
pub name: Name,
pub fields: Fields,
pub ast_id: FileAstId<ast::Variant>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
@ -952,10 +953,17 @@ pub enum Fields {
Unit,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FieldAstId {
Record(FileAstId<ast::RecordField>),
Tuple(FileAstId<ast::TupleField>),
}
/// A single field of an enum variant or struct
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Field {
pub name: Name,
pub type_ref: Interned<TypeRef>,
pub visibility: RawVisibilityId,
pub ast_id: FieldAstId,
}

View File

@ -184,7 +184,8 @@ impl<'a> Ctx<'a> {
let name = field.name()?.as_name();
let visibility = self.lower_visibility(field);
let type_ref = self.lower_type_ref_opt(field.ty());
let res = Field { name, type_ref, visibility };
let ast_id = FieldAstId::Record(self.source_ast_id_map.ast_id(field));
let res = Field { name, type_ref, visibility, ast_id };
Some(res)
}
@ -203,7 +204,8 @@ impl<'a> Ctx<'a> {
let name = Name::new_tuple_field(idx);
let visibility = self.lower_visibility(field);
let type_ref = self.lower_type_ref_opt(field.ty());
Field { name, type_ref, visibility }
let ast_id = FieldAstId::Tuple(self.source_ast_id_map.ast_id(field));
Field { name, type_ref, visibility, ast_id }
}
fn lower_union(&mut self, union: &ast::Union) -> Option<FileItemTreeId<Union>> {
@ -247,7 +249,8 @@ impl<'a> Ctx<'a> {
fn lower_variant(&mut self, variant: &ast::Variant) -> Option<Variant> {
let name = variant.name()?.as_name();
let fields = self.lower_fields(&variant.kind());
let res = Variant { name, fields };
let ast_id = self.source_ast_id_map.ast_id(variant);
let res = Variant { name, fields, ast_id };
Some(res)
}

View File

@ -115,7 +115,7 @@ impl<'a> Printer<'a> {
w!(self, "{{");
self.indented(|this| {
for field in fields.clone() {
let Field { visibility, name, type_ref } = &this.tree[field];
let Field { visibility, name, type_ref, ast_id: _ } = &this.tree[field];
this.print_attrs_of(field);
this.print_visibility(*visibility);
w!(this, "{}: ", name);
@ -129,7 +129,7 @@ impl<'a> Printer<'a> {
w!(self, "(");
self.indented(|this| {
for field in fields.clone() {
let Field { visibility, name, type_ref } = &this.tree[field];
let Field { visibility, name, type_ref, ast_id: _ } = &this.tree[field];
this.print_attrs_of(field);
this.print_visibility(*visibility);
w!(this, "{}: ", name);
@ -323,7 +323,7 @@ impl<'a> Printer<'a> {
self.print_where_clause_and_opening_brace(generic_params);
self.indented(|this| {
for variant in variants.clone() {
let Variant { name, fields } = &this.tree[variant];
let Variant { name, fields, ast_id: _ } = &this.tree[variant];
this.print_attrs_of(variant);
w!(this, "{}", name);
this.print_fields(fields);

View File

@ -2122,7 +2122,7 @@ impl ModCollector<'_, '_> {
fn emit_unconfigured_diagnostic(&mut self, item: ModItem, cfg: &CfgExpr) {
let ast_id = item.ast_id(self.item_tree);
let ast_id = InFile::new(self.file_id(), ast_id);
let ast_id = InFile::new(self.file_id(), ast_id.upcast());
self.def_collector.def_map.diagnostics.push(DefDiagnostic::unconfigured_code(
self.module_id,
ast_id,

View File

@ -4,7 +4,7 @@ use base_db::CrateId;
use cfg::{CfgExpr, CfgOptions};
use hir_expand::MacroCallKind;
use la_arena::Idx;
use syntax::ast;
use syntax::ast::{self, AnyHasAttrs};
use crate::{
attr::AttrId,
@ -22,7 +22,7 @@ pub enum DefDiagnosticKind {
UnresolvedImport { id: ItemTreeId<item_tree::Import>, index: Idx<ast::UseTree> },
UnconfiguredCode { ast: AstId<ast::Item>, cfg: CfgExpr, opts: CfgOptions },
UnconfiguredCode { ast: AstId<AnyHasAttrs>, cfg: CfgExpr, opts: CfgOptions },
UnresolvedProcMacro { ast: MacroCallKind, krate: CrateId },
@ -75,7 +75,7 @@ impl DefDiagnostic {
pub fn unconfigured_code(
container: LocalModuleId,
ast: AstId<ast::Item>,
ast: AstId<ast::AnyHasAttrs>,
cfg: CfgExpr,
opts: CfgOptions,
) -> Self {

View File

@ -93,7 +93,12 @@ impl AstIdMap {
// trait does not change ids of top-level items, which helps caching.
bdfs(node, |it| {
let kind = it.kind();
if ast::Item::can_cast(kind) || ast::BlockExpr::can_cast(kind) {
if ast::Item::can_cast(kind)
|| ast::BlockExpr::can_cast(kind)
|| ast::Variant::can_cast(kind)
|| ast::RecordField::can_cast(kind)
|| ast::TupleField::can_cast(kind)
{
res.alloc(&it);
true
} else {

View File

@ -1,7 +1,7 @@
//! Re-export diagnostics such that clients of `hir` don't have to depend on
//! low-level crates.
//!
//! This probably isn't the best way to do this -- ideally, diagnistics should
//! This probably isn't the best way to do this -- ideally, diagnostics should
//! be expressed in terms of hir types themselves.
use base_db::CrateId;
use cfg::{CfgExpr, CfgOptions};

View File

@ -540,9 +540,27 @@ impl Module {
}
acc.extend(decl.diagnostics(db))
}
ModuleDef::Adt(Adt::Enum(e)) => {
for v in e.variants(db) {
acc.extend(ModuleDef::Variant(v).diagnostics(db));
ModuleDef::Adt(adt) => {
match adt {
Adt::Struct(s) => {
for diag in db.struct_data_with_diagnostics(s.id).1.iter() {
emit_def_diagnostic(db, acc, diag);
}
}
Adt::Union(u) => {
for diag in db.union_data_with_diagnostics(u.id).1.iter() {
emit_def_diagnostic(db, acc, diag);
}
}
Adt::Enum(e) => {
for v in e.variants(db) {
acc.extend(ModuleDef::Variant(v).diagnostics(db));
}
for diag in db.enum_data_with_diagnostics(e.id).1.iter() {
emit_def_diagnostic(db, acc, diag);
}
}
}
acc.extend(decl.diagnostics(db))
}

View File

@ -137,6 +137,37 @@ trait Bar {
#[cfg_attr(not(never), inline, cfg(no))] fn h() {}
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: no is disabled
"#,
);
}
#[test]
fn inactive_fields_and_variants() {
check(
r#"
enum Foo {
#[cfg(a)] Bar,
//^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
Baz {
#[cfg(a)] baz: String,
//^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
},
Qux(#[cfg(a)] String),
//^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
}
struct Baz {
#[cfg(a)] baz: String,
//^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
}
struct Qux(#[cfg(a)] String);
//^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
union FooBar {
#[cfg(a)] baz: u32,
//^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
}
"#,
);
}

View File

@ -873,3 +873,33 @@ impl ast::MatchGuard {
support::child(&self.syntax)
}
}
impl From<ast::Item> for ast::AnyHasAttrs {
fn from(node: ast::Item) -> Self {
Self::new(node)
}
}
impl From<ast::AssocItem> for ast::AnyHasAttrs {
fn from(node: ast::AssocItem) -> Self {
Self::new(node)
}
}
impl From<ast::Variant> for ast::AnyHasAttrs {
fn from(node: ast::Variant) -> Self {
Self::new(node)
}
}
impl From<ast::RecordField> for ast::AnyHasAttrs {
fn from(node: ast::RecordField) -> Self {
Self::new(node)
}
}
impl From<ast::TupleField> for ast::AnyHasAttrs {
fn from(node: ast::TupleField) -> Self {
Self::new(node)
}
}