-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Warn about dead tuple struct fields #95977
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,7 @@ | |
|
||
use itertools::Itertools; | ||
use rustc_data_structures::fx::{FxHashMap, FxHashSet}; | ||
use rustc_errors::{pluralize, MultiSpan}; | ||
use rustc_errors::{pluralize, Applicability, MultiSpan}; | ||
use rustc_hir as hir; | ||
use rustc_hir::def::{CtorOf, DefKind, Res}; | ||
use rustc_hir::def_id::{DefId, LocalDefId}; | ||
|
@@ -42,6 +42,7 @@ struct MarkSymbolVisitor<'tcx> { | |
maybe_typeck_results: Option<&'tcx ty::TypeckResults<'tcx>>, | ||
live_symbols: FxHashSet<LocalDefId>, | ||
repr_has_repr_c: bool, | ||
repr_has_repr_simd: bool, | ||
in_pat: bool, | ||
ignore_variant_stack: Vec<DefId>, | ||
// maps from tuple struct constructors to tuple struct items | ||
|
@@ -220,6 +221,32 @@ impl<'tcx> MarkSymbolVisitor<'tcx> { | |
} | ||
} | ||
|
||
fn handle_tuple_field_pattern_match( | ||
&mut self, | ||
lhs: &hir::Pat<'_>, | ||
res: Res, | ||
pats: &[hir::Pat<'_>], | ||
dotdot: Option<usize>, | ||
) { | ||
let variant = match self.typeck_results().node_type(lhs.hir_id).kind() { | ||
ty::Adt(adt, _) => adt.variant_of_res(res), | ||
_ => span_bug!(lhs.span, "non-ADT in tuple struct pattern"), | ||
}; | ||
let first_n = pats.iter().enumerate().take(dotdot.unwrap_or(pats.len())); | ||
let missing = variant.fields.len() - pats.len(); | ||
let last_n = pats | ||
.iter() | ||
.enumerate() | ||
.skip(dotdot.unwrap_or(pats.len())) | ||
.map(|(idx, pat)| (idx + missing, pat)); | ||
for (idx, pat) in first_n.chain(last_n) { | ||
if let PatKind::Wild = pat.kind { | ||
continue; | ||
} | ||
self.insert_def_id(variant.fields[idx].did); | ||
} | ||
} | ||
|
||
fn mark_live_symbols(&mut self) { | ||
let mut scanned = FxHashSet::default(); | ||
while let Some(id) = self.worklist.pop() { | ||
|
@@ -274,12 +301,15 @@ impl<'tcx> MarkSymbolVisitor<'tcx> { | |
} | ||
|
||
let had_repr_c = self.repr_has_repr_c; | ||
let had_repr_simd = self.repr_has_repr_simd; | ||
self.repr_has_repr_c = false; | ||
self.repr_has_repr_simd = false; | ||
match node { | ||
Node::Item(item) => match item.kind { | ||
hir::ItemKind::Struct(..) | hir::ItemKind::Union(..) => { | ||
let def = self.tcx.adt_def(item.def_id); | ||
self.repr_has_repr_c = def.repr().c(); | ||
self.repr_has_repr_simd = def.repr().simd(); | ||
|
||
intravisit::walk_item(self, &item) | ||
} | ||
|
@@ -315,6 +345,7 @@ impl<'tcx> MarkSymbolVisitor<'tcx> { | |
} | ||
_ => {} | ||
} | ||
self.repr_has_repr_simd = had_repr_simd; | ||
self.repr_has_repr_c = had_repr_c; | ||
} | ||
|
||
|
@@ -347,9 +378,10 @@ impl<'tcx> Visitor<'tcx> for MarkSymbolVisitor<'tcx> { | |
) { | ||
let tcx = self.tcx; | ||
let has_repr_c = self.repr_has_repr_c; | ||
let has_repr_simd = self.repr_has_repr_simd; | ||
let live_fields = def.fields().iter().filter_map(|f| { | ||
let def_id = tcx.hir().local_def_id(f.hir_id); | ||
if has_repr_c { | ||
if has_repr_c || (f.is_positional() && has_repr_simd) { | ||
return Some(def_id); | ||
} | ||
if !tcx.visibility(f.hir_id.owner).is_public() { | ||
|
@@ -408,6 +440,10 @@ impl<'tcx> Visitor<'tcx> for MarkSymbolVisitor<'tcx> { | |
let res = self.typeck_results().qpath_res(qpath, pat.hir_id); | ||
self.handle_res(res); | ||
} | ||
PatKind::TupleStruct(ref qpath, ref fields, dotdot) => { | ||
let res = self.typeck_results().qpath_res(qpath, pat.hir_id); | ||
self.handle_tuple_field_pattern_match(pat, res, fields, dotdot); | ||
} | ||
_ => (), | ||
} | ||
|
||
|
@@ -440,7 +476,11 @@ impl<'tcx> Visitor<'tcx> for MarkSymbolVisitor<'tcx> { | |
} | ||
} | ||
|
||
fn has_allow_dead_code_or_lang_attr(tcx: TyCtxt<'_>, id: hir::HirId) -> bool { | ||
fn has_allow_dead_code_or_lang_attr_helper( | ||
tcx: TyCtxt<'_>, | ||
id: hir::HirId, | ||
lint: &'static lint::Lint, | ||
) -> bool { | ||
let attrs = tcx.hir().attrs(id); | ||
if tcx.sess.contains_name(attrs, sym::lang) { | ||
return true; | ||
|
@@ -470,7 +510,11 @@ fn has_allow_dead_code_or_lang_attr(tcx: TyCtxt<'_>, id: hir::HirId) -> bool { | |
} | ||
} | ||
|
||
tcx.lint_level_at_node(lint::builtin::DEAD_CODE, id).0 == lint::Allow | ||
tcx.lint_level_at_node(lint, id).0 == lint::Allow | ||
} | ||
|
||
fn has_allow_dead_code_or_lang_attr(tcx: TyCtxt<'_>, id: hir::HirId) -> bool { | ||
has_allow_dead_code_or_lang_attr_helper(tcx, id, lint::builtin::DEAD_CODE) | ||
} | ||
|
||
// These check_* functions seeds items that | ||
|
@@ -623,6 +667,7 @@ fn live_symbols_and_ignored_derived_traits<'tcx>( | |
maybe_typeck_results: None, | ||
live_symbols: Default::default(), | ||
repr_has_repr_c: false, | ||
repr_has_repr_simd: false, | ||
in_pat: false, | ||
ignore_variant_stack: vec![], | ||
struct_constructors, | ||
|
@@ -644,32 +689,46 @@ struct DeadVisitor<'tcx> { | |
ignored_derived_traits: &'tcx FxHashMap<LocalDefId, Vec<(DefId, DefId)>>, | ||
} | ||
|
||
enum ShouldWarnAboutField { | ||
Yes(bool), // positional? | ||
No, | ||
} | ||
Comment on lines
+692
to
+695
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would normally represent this as
but I don't care strongly either way. |
||
|
||
impl<'tcx> DeadVisitor<'tcx> { | ||
fn should_warn_about_field(&mut self, field: &ty::FieldDef) -> bool { | ||
fn should_warn_about_field(&mut self, field: &ty::FieldDef) -> ShouldWarnAboutField { | ||
if self.live_symbols.contains(&field.did.expect_local()) { | ||
return false; | ||
return ShouldWarnAboutField::No; | ||
} | ||
let field_type = self.tcx.type_of(field.did); | ||
if field_type.is_phantom_data() { | ||
return ShouldWarnAboutField::No; | ||
} | ||
let is_positional = field.name.as_str().starts_with(|c: char| c.is_ascii_digit()); | ||
if is_positional { | ||
return false; | ||
if is_positional | ||
&& self | ||
.tcx | ||
.layout_of(self.tcx.param_env(field.did).and(field_type)) | ||
.map_or(true, |layout| layout.is_zst()) | ||
{ | ||
return ShouldWarnAboutField::No; | ||
} | ||
let field_type = self.tcx.type_of(field.did); | ||
!field_type.is_phantom_data() | ||
ShouldWarnAboutField::Yes(is_positional) | ||
} | ||
|
||
fn warn_multiple_dead_codes( | ||
&self, | ||
dead_codes: &[LocalDefId], | ||
participle: &str, | ||
parent_item: Option<LocalDefId>, | ||
is_positional: bool, | ||
) { | ||
if let Some(&first_id) = dead_codes.first() { | ||
let tcx = self.tcx; | ||
let names: Vec<_> = dead_codes | ||
.iter() | ||
.map(|&def_id| tcx.item_name(def_id.to_def_id()).to_string()) | ||
.collect(); | ||
let spans = dead_codes | ||
let spans: Vec<_> = dead_codes | ||
.iter() | ||
.map(|&def_id| match tcx.def_ident_span(def_id) { | ||
Some(s) => s.with_ctxt(tcx.def_span(def_id).ctxt()), | ||
|
@@ -678,9 +737,13 @@ impl<'tcx> DeadVisitor<'tcx> { | |
.collect(); | ||
|
||
tcx.struct_span_lint_hir( | ||
lint::builtin::DEAD_CODE, | ||
if is_positional { | ||
lint::builtin::UNUSED_TUPLE_STRUCT_FIELDS | ||
} else { | ||
lint::builtin::DEAD_CODE | ||
}, | ||
tcx.hir().local_def_id_to_hir_id(first_id), | ||
MultiSpan::from_spans(spans), | ||
MultiSpan::from_spans(spans.clone()), | ||
|lint| { | ||
let descr = tcx.def_kind(first_id).descr(first_id.to_def_id()); | ||
let span_len = dead_codes.len(); | ||
|
@@ -702,6 +765,21 @@ impl<'tcx> DeadVisitor<'tcx> { | |
are = pluralize!("is", span_len), | ||
)); | ||
|
||
if is_positional { | ||
err.multipart_suggestion( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The suggestion seems a bit convoluted, and may be more confusing that helping. Since we don't give a suggestion in the non-positional case, do we need a suggestion? |
||
&format!( | ||
"consider changing the field{s} to be of unit type to \ | ||
suppress this warning while preserving the field \ | ||
numbering, or remove the field{s}", | ||
s = pluralize!(span_len) | ||
), | ||
spans.iter().map(|sp| (*sp, "()".to_string())).collect(), | ||
// "HasPlaceholders" because applying this fix by itself isn't | ||
// enough: All constructor calls have to be adjusted as well | ||
Applicability::HasPlaceholders, | ||
); | ||
} | ||
|
||
if let Some(parent_item) = parent_item { | ||
let parent_descr = tcx.def_kind(parent_item).descr(parent_item.to_def_id()); | ||
err.span_label( | ||
|
@@ -743,6 +821,7 @@ impl<'tcx> DeadVisitor<'tcx> { | |
def_id: LocalDefId, | ||
participle: &str, | ||
dead_codes: Vec<DeadVariant>, | ||
is_positional: bool, | ||
) { | ||
let mut dead_codes = dead_codes | ||
.iter() | ||
|
@@ -758,12 +837,13 @@ impl<'tcx> DeadVisitor<'tcx> { | |
&group.map(|v| v.def_id).collect::<Vec<_>>(), | ||
participle, | ||
Some(def_id), | ||
is_positional, | ||
); | ||
} | ||
} | ||
|
||
fn warn_dead_code(&mut self, id: LocalDefId, participle: &str) { | ||
self.warn_multiple_dead_codes(&[id], participle, None); | ||
self.warn_multiple_dead_codes(&[id], participle, None, false); | ||
} | ||
|
||
fn check_definition(&mut self, def_id: LocalDefId) { | ||
|
@@ -829,24 +909,37 @@ fn check_mod_deathness(tcx: TyCtxt<'_>, module: LocalDefId) { | |
continue; | ||
} | ||
|
||
let mut is_positional = false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Positional fields are all or nothing. Can |
||
let dead_fields = variant | ||
.fields | ||
.iter() | ||
.filter_map(|field| { | ||
let def_id = field.did.expect_local(); | ||
let hir_id = tcx.hir().local_def_id_to_hir_id(def_id); | ||
if visitor.should_warn_about_field(&field) { | ||
let level = tcx.lint_level_at_node(lint::builtin::DEAD_CODE, hir_id).0; | ||
if let ShouldWarnAboutField::Yes(is_pos) = | ||
visitor.should_warn_about_field(&field) | ||
{ | ||
let level = tcx | ||
.lint_level_at_node( | ||
if is_pos { | ||
is_positional = true; | ||
lint::builtin::UNUSED_TUPLE_STRUCT_FIELDS | ||
} else { | ||
lint::builtin::DEAD_CODE | ||
}, | ||
hir_id, | ||
) | ||
.0; | ||
Some(DeadVariant { def_id, name: field.name, level }) | ||
} else { | ||
None | ||
} | ||
}) | ||
.collect(); | ||
visitor.warn_dead_fields_and_variants(def_id, "read", dead_fields) | ||
visitor.warn_dead_fields_and_variants(def_id, "read", dead_fields, is_positional) | ||
} | ||
|
||
visitor.warn_dead_fields_and_variants(item.def_id, "constructed", dead_variants); | ||
visitor.warn_dead_fields_and_variants(item.def_id, "constructed", dead_variants, false); | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will probably be clearer.