diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index dd9ebf298b229..84c1e8e6f3d47 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -2086,7 +2086,7 @@ fn generic_simd_intrinsic<'ll, 'tcx>( } if name == sym::simd_splat { - let (_out_len, out_ty) = require_simd!(ret_ty, SimdReturn); + let (out_len, out_ty) = require_simd!(ret_ty, SimdReturn); require!( args[0].layout.ty == out_ty, @@ -2105,7 +2105,8 @@ fn generic_simd_intrinsic<'ll, 'tcx>( // `shufflevector v0, poison, zeroinitializer` // The masks is all zeros, so this splats lane 0 (which has our element in it). - let splat = bx.shuffle_vector(v0, poison_vec, bx.const_null(llret_ty)); + let mask_ty = bx.type_vector(bx.type_i32(), out_len); + let splat = bx.shuffle_vector(v0, poison_vec, bx.const_null(mask_ty)); return Ok(splat); } diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index bb047e9782d44..f45c242800830 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -138,17 +138,16 @@ declare_lint_pass!(ImproperCTypesLint => [ USES_POWER_ALIGNMENT ]); -/// Getting the (normalized) type out of a field (for, e.g., an enum variant or a tuple). -#[inline] -fn get_type_from_field<'tcx>( +/// A common pattern in this lint is to attempt normalize_erasing_regions, +/// but keep the original type if it were to fail. +/// This may or may not be supported in the logic behind the `Unnormalized` wrapper, +/// (FIXME?) +/// but it should be enough for non-wrapped types to be as normalised as this lint needs them to be. +fn maybe_normalize_erasing_regions<'tcx>( cx: &LateContext<'tcx>, - field: &ty::FieldDef, - args: GenericArgsRef<'tcx>, + value: Unnormalized<'tcx, Ty<'tcx>>, ) -> Ty<'tcx> { - let field_ty = field.ty(cx.tcx, args); - cx.tcx - .try_normalize_erasing_regions(cx.typing_env(), Unnormalized::new_wip(field_ty)) - .unwrap_or(field_ty) + cx.tcx.try_normalize_erasing_regions(cx.typing_env(), value).unwrap_or(value.skip_norm_wip()) } /// Check a variant of a non-exhaustive enum for improper ctypes @@ -257,12 +256,22 @@ fn check_struct_for_power_alignment<'tcx>( } } +/// Annotates whether we are in the context of an item *defined* in rust +/// and exposed to an FFI boundary, +/// or the context of an item from elsewhere, whose interface is re-*declared* in rust. #[derive(Clone, Copy)] enum CItemKind { Declaration, Definition, } +/// Annotates whether we are in the context of a function's argument types or return type. +#[derive(Clone, Copy)] +enum FnPos { + Arg, + Ret, +} + enum FfiResult<'tcx> { FfiSafe, FfiPhantom(Ty<'tcx>), @@ -286,8 +295,10 @@ enum IndirectionKind { } bitflags! { + /// VisitorState flags that are linked with the root type's use. + /// (These are the permanent part of the state, kept when visiting new Ty.) #[derive(Clone, Copy, Debug, PartialEq, Eq)] - struct VisitorState: u8 { + struct RootUseFlags: u8 { /// For use in (externally-linked) static variables. const STATIC = 0b000001; /// For use in functions in general. @@ -302,7 +313,45 @@ bitflags! { } } -impl VisitorState { +/// Description of the relationship between current Ty and +/// the type (or lack thereof) immediately containing it +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +enum OuterTyKind { + None, + /// A variant that should not exist, + /// but is needed because we don't change the lint's behavior yet + NoneThroughFnPtr, + /// Placeholder for properties that will be used eventually + Other, +} + +impl OuterTyKind { + /// Computes the relationship by providing the containing Ty itself + fn from_ty<'tcx>(ty: Ty<'tcx>) -> Self { + match ty.kind() { + ty::FnPtr(..) => Self::NoneThroughFnPtr, + ty::RawPtr(..) + | ty::Ref(..) + | ty::Adt(..) + | ty::Tuple(..) + | ty::Array(..) + | ty::Slice(_) => OuterTyKind::Other, + _ => bug!("Unexpected outer type {ty:?}"), + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +struct VisitorState { + /// Flags describing both the overall context in which the current Ty is, + /// linked to how the Visitor's original Ty was used. + root_use_flags: RootUseFlags, + /// Flags describing both the immediate context in which the current Ty is, + /// linked to how it relates to its parent Ty (or lack thereof). + outer_ty_kind: OuterTyKind, +} + +impl RootUseFlags { // The values that can be set. const STATIC_TY: Self = Self::STATIC; const ARGUMENT_TY_IN_DEFINITION: Self = @@ -317,86 +366,85 @@ impl VisitorState { const RETURN_TY_IN_FNPTR: Self = Self::from_bits(Self::FUNC.bits() | Self::THEORETICAL.bits() | Self::FN_RETURN.bits()) .unwrap(); +} - /// Get the proper visitor state for a given function's arguments. - fn argument_from_fnmode(fn_mode: CItemKind) -> Self { - match fn_mode { - CItemKind::Definition => VisitorState::ARGUMENT_TY_IN_DEFINITION, - CItemKind::Declaration => VisitorState::ARGUMENT_TY_IN_DECLARATION, +impl VisitorState { + /// From an existing state, compute the state of any subtype of the current type. + /// (General case. For the case where the current type is a function pointer, see `next_in_fnptr`.) + fn next(&self, current_ty: Ty<'_>) -> Self { + assert!(!matches!(current_ty.kind(), ty::FnPtr(..))); + VisitorState { + root_use_flags: self.root_use_flags, + outer_ty_kind: OuterTyKind::from_ty(current_ty), } } - /// Get the proper visitor state for a given function's return type. - fn return_from_fnmode(fn_mode: CItemKind) -> Self { - match fn_mode { - CItemKind::Definition => VisitorState::RETURN_TY_IN_DEFINITION, - CItemKind::Declaration => VisitorState::RETURN_TY_IN_DECLARATION, + /// From an existing state, compute the state of any subtype of the current type. + /// (Case where the current type is a function pointer, + /// meaning we need to specify if the subtype is an argument or the return.) + fn next_in_fnptr(&self, current_ty: Ty<'_>, fn_pos: FnPos) -> Self { + assert!(matches!(current_ty.kind(), ty::FnPtr(..))); + VisitorState { + root_use_flags: match fn_pos { + FnPos::Ret => RootUseFlags::RETURN_TY_IN_FNPTR, + FnPos::Arg => RootUseFlags::ARGUMENT_TY_IN_FNPTR, + }, + outer_ty_kind: OuterTyKind::from_ty(current_ty), } } + /// Get the proper visitor state for a given function's arguments or return type. + fn fn_entry_point(fn_mode: CItemKind, fn_pos: FnPos) -> Self { + let p_flags = match (fn_mode, fn_pos) { + (CItemKind::Definition, FnPos::Ret) => RootUseFlags::RETURN_TY_IN_DEFINITION, + (CItemKind::Declaration, FnPos::Ret) => RootUseFlags::RETURN_TY_IN_DECLARATION, + (CItemKind::Definition, FnPos::Arg) => RootUseFlags::ARGUMENT_TY_IN_DEFINITION, + (CItemKind::Declaration, FnPos::Arg) => RootUseFlags::ARGUMENT_TY_IN_DECLARATION, + }; + VisitorState { root_use_flags: p_flags, outer_ty_kind: OuterTyKind::None } + } + + /// Get the proper visitor state for a static variable's type + fn static_entry_point() -> Self { + VisitorState { root_use_flags: RootUseFlags::STATIC_TY, outer_ty_kind: OuterTyKind::None } + } + /// Whether the type is used in a function. - fn is_in_function(self) -> bool { - let ret = self.contains(Self::FUNC); + fn is_in_function(&self) -> bool { + let ret = self.root_use_flags.contains(RootUseFlags::FUNC); if ret { - debug_assert!(!self.contains(Self::STATIC)); + debug_assert!(!self.root_use_flags.contains(RootUseFlags::STATIC)); } ret } + /// Whether the type is used (directly or not) in a function, in return position. - fn is_in_function_return(self) -> bool { - let ret = self.contains(Self::FN_RETURN); + fn is_in_function_return(&self) -> bool { + let ret = self.root_use_flags.contains(RootUseFlags::FN_RETURN); if ret { debug_assert!(self.is_in_function()); } ret } + /// Whether the type is used (directly or not) in a defined function. /// In other words, whether or not we allow non-FFI-safe types behind a C pointer, /// to be treated as an opaque type on the other side of the FFI boundary. - fn is_in_defined_function(self) -> bool { - self.contains(Self::DEFINED) && self.is_in_function() + fn is_in_defined_function(&self) -> bool { + self.root_use_flags.contains(RootUseFlags::DEFINED) && self.is_in_function() } /// Whether the type is used (directly or not) in a function pointer type. /// Here, we also allow non-FFI-safe types behind a C pointer, /// to be treated as an opaque type on the other side of the FFI boundary. - fn is_in_fnptr(self) -> bool { - self.contains(Self::THEORETICAL) && self.is_in_function() + fn is_in_fnptr(&self) -> bool { + self.root_use_flags.contains(RootUseFlags::THEORETICAL) && self.is_in_function() } /// Whether we can expect type parameters and co in a given type. - fn can_expect_ty_params(self) -> bool { + fn can_expect_ty_params(&self) -> bool { // rust-defined functions, as well as FnPtrs - self.contains(Self::THEORETICAL) || self.is_in_defined_function() - } -} - -bitflags! { - /// Data that summarises how an "outer type" surrounds its inner type(s) - #[derive(Clone, Copy, Debug, PartialEq, Eq)] - struct OuterTyData: u8 { - /// To show that there is no outer type, the current type is directly used by a `static` - /// variable or a function/FnPtr - const NO_OUTER_TY = 0b01; - /// For NO_OUTER_TY cases, show that we are being directly used by a FnPtr specifically - /// FIXME(ctypes): this is only used for "bad behaviour" reproduced for compatibility's sake - const NO_OUTER_TY_FNPTR = 0b10; - } -} - -impl OuterTyData { - /// Get the proper data for a given outer type. - fn from_ty<'tcx>(ty: Ty<'tcx>) -> Self { - match ty.kind() { - ty::FnPtr(..) => Self::NO_OUTER_TY | Self::NO_OUTER_TY_FNPTR, - ty::RawPtr(..) - | ty::Ref(..) - | ty::Adt(..) - | ty::Tuple(..) - | ty::Array(..) - | ty::Slice(_) => Self::empty(), - k @ _ => bug!("unexpected outer type {:?} of kind {:?}", ty, k), - } + self.root_use_flags.contains(RootUseFlags::THEORETICAL) || self.is_in_defined_function() } } @@ -415,8 +463,17 @@ struct ImproperCTypesVisitor<'a, 'tcx> { } impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { - fn new(cx: &'a LateContext<'tcx>, base_ty: Ty<'tcx>, base_fn_mode: CItemKind) -> Self { - Self { cx, base_ty, base_fn_mode, cache: FxHashSet::default() } + fn new( + cx: &'a LateContext<'tcx>, + base_ty: Unnormalized<'tcx, Ty<'tcx>>, + base_fn_mode: CItemKind, + ) -> Self { + ImproperCTypesVisitor { + cx, + base_ty: maybe_normalize_erasing_regions(cx, base_ty), + base_fn_mode, + cache: FxHashSet::default(), + } } /// Checks if the given indirection (box,ref,pointer) is "ffi-safe". @@ -485,7 +542,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { { FfiSafe } else { - self.visit_type(state, OuterTyData::from_ty(ty), inner_ty) + self.visit_type(state.next(ty), inner_ty) } } } @@ -505,8 +562,11 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { let transparent_with_all_zst_fields = if def.repr().transparent() { if let Some(field) = super::transparent_newtype_field(self.cx.tcx, variant) { // Transparent newtypes have at most one non-ZST field which needs to be checked.. - let field_ty = get_type_from_field(self.cx, field, args); - match self.visit_type(state, OuterTyData::from_ty(ty), field_ty) { + let field_ty = maybe_normalize_erasing_regions( + self.cx, + Unnormalized::new_wip(field.ty(self.cx.tcx, args)), + ); + match self.visit_type(state.next(ty), field_ty) { FfiUnsafe { ty, .. } if ty.is_unit() => (), r => return r, } @@ -524,8 +584,11 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // We can't completely trust `repr(C)` markings, so make sure the fields are actually safe. let mut all_phantom = !variant.fields.is_empty(); for field in &variant.fields { - let field_ty = get_type_from_field(self.cx, field, args); - all_phantom &= match self.visit_type(state, OuterTyData::from_ty(ty), field_ty) { + let field_ty = maybe_normalize_erasing_regions( + self.cx, + Unnormalized::new_wip(field.ty(self.cx.tcx, args)), + ); + all_phantom &= match self.visit_type(state.next(ty), field_ty) { FfiSafe => false, // `()` fields are FFI-safe! FfiUnsafe { ty, .. } if ty.is_unit() => false, @@ -570,7 +633,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { "consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct" )) } else { - // FIXME(ctypes): confirm that this makes sense for unions once #60405 / RFC2645 stabilises + // FIXME(#60405): confirm that this makes sense for unions once #60405 / RFC2645 stabilises Some(msg!( "consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this union" )) @@ -628,7 +691,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { if !def.repr().c() && !def.repr().transparent() && def.repr().int.is_none() { // Special-case types like `Option` and `Result` if let Some(inner_ty) = repr_nullable_ptr(self.cx.tcx, self.cx.typing_env(), ty) { - return self.visit_type(state, OuterTyData::from_ty(ty), inner_ty); + return self.visit_type(state.next(ty), inner_ty); } return FfiUnsafe { @@ -660,12 +723,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { /// Checks if the given type is "ffi-safe" (has a stable, well-defined /// representation which can be exported to C code). - fn visit_type( - &mut self, - state: VisitorState, - outer_ty: OuterTyData, - ty: Ty<'tcx>, - ) -> FfiResult<'tcx> { + fn visit_type(&mut self, state: VisitorState, ty: Ty<'tcx>) -> FfiResult<'tcx> { use FfiResult::*; let tcx = self.cx.tcx; @@ -709,7 +767,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // Pattern types are just extra invariants on the type that you need to uphold, // but only the base type is relevant for being representable in FFI. // (note: this lint was written when pattern types could only be integers constrained to ranges) - ty::Pat(pat_ty, _) => self.visit_type(state, outer_ty, pat_ty), + // (also note: the lack of ".next(ty)" on the state is on purpose) + ty::Pat(pat_ty, _) => self.visit_type(state, pat_ty), // types which likely have a stable representation, if the target architecture defines those // note: before rust 1.77, 128-bit ints were not FFI-safe on x86_64 @@ -740,12 +799,14 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { }, ty::Tuple(tuple) => { - // C functions can return void - let empty_and_safe = tuple.is_empty() - && outer_ty.contains(OuterTyData::NO_OUTER_TY) - && state.is_in_function_return(); - - if empty_and_safe { + if tuple.is_empty() + && state.is_in_function_return() + && matches!( + state.outer_ty_kind, + OuterTyKind::None | OuterTyKind::NoneThroughFnPtr + ) + { + // C functions can return void FfiSafe } else { FfiUnsafe { @@ -774,9 +835,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { ty::Array(inner_ty, _) => { if state.is_in_function() - && outer_ty.contains(OuterTyData::NO_OUTER_TY) - // FIXME(ctypes): VVV-this-VVV shouldn't be the case - && !outer_ty.contains(OuterTyData::NO_OUTER_TY_FNPTR) + // FIXME(ctypes): VVV-this-VVV shouldn't make a difference between ::None and ::NoneThroughFnPtr + && matches!(state.outer_ty_kind, OuterTyKind::None) { // C doesn't really support passing arrays by value - the only way to pass an array by value // is through a struct. @@ -788,7 +848,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } else { // let's allow phantoms to go through, // since an array of 1-ZSTs is also a 1-ZST - self.visit_type(state, OuterTyData::from_ty(ty), inner_ty) + self.visit_type(state.next(ty), inner_ty) } } @@ -806,19 +866,14 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { let sig = tcx.instantiate_bound_regions_with_erased(sig); for arg in sig.inputs() { - match self.visit_type( - VisitorState::ARGUMENT_TY_IN_FNPTR, - OuterTyData::from_ty(ty), - *arg, - ) { + match self.visit_type(state.next_in_fnptr(ty, FnPos::Arg), *arg) { FfiSafe => {} r => return r, } } let ret_ty = sig.output(); - - self.visit_type(VisitorState::RETURN_TY_IN_FNPTR, OuterTyData::from_ty(ty), ret_ty) + self.visit_type(state.next_in_fnptr(ty, FnPos::Ret), ret_ty) } ty::Foreign(..) => FfiSafe, @@ -886,17 +941,17 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { }) } - fn check_type(&mut self, state: VisitorState, ty: Ty<'tcx>) -> FfiResult<'tcx> { - let ty = self - .cx - .tcx - .try_normalize_erasing_regions(self.cx.typing_env(), Unnormalized::new_wip(ty)) - .unwrap_or(ty); + fn check_type( + &mut self, + state: VisitorState, + ty: Unnormalized<'tcx, Ty<'tcx>>, + ) -> FfiResult<'tcx> { + let ty = maybe_normalize_erasing_regions(self.cx, ty); if let Some(res) = self.visit_for_opaque_ty(ty) { return res; } - self.visit_type(state, OuterTyData::NO_OUTER_TY, ty) + self.visit_type(state, ty) } } @@ -925,7 +980,7 @@ impl<'tcx> ImproperCTypesLint { self.spans.push(ty.span); } - hir::intravisit::walk_ty(self, ty) + hir::intravisit::walk_ty(self, ty); } } @@ -949,6 +1004,7 @@ impl<'tcx> ImproperCTypesLint { let all_types = iter::zip(visitor.tys.drain(..), visitor.spans.drain(..)); for (fn_ptr_ty, span) in all_types { + let fn_ptr_ty = Unnormalized::new_wip(fn_ptr_ty); let mut visitor = ImproperCTypesVisitor::new(cx, fn_ptr_ty, fn_mode); // FIXME(ctypes): make a check_for_fnptr let ffi_res = visitor.check_type(state, fn_ptr_ty); @@ -970,12 +1026,12 @@ impl<'tcx> ImproperCTypesLint { let sig = cx.tcx.instantiate_bound_regions_with_erased(sig); for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { - let state = VisitorState::argument_from_fnmode(fn_mode); + let state = VisitorState::fn_entry_point(fn_mode, FnPos::Arg); self.check_type_for_external_abi_fnptr(cx, state, input_hir, *input_ty, fn_mode); } if let hir::FnRetTy::Return(ret_hir) = decl.output { - let state = VisitorState::return_from_fnmode(fn_mode); + let state = VisitorState::fn_entry_point(fn_mode, FnPos::Ret); self.check_type_for_external_abi_fnptr(cx, state, ret_hir, sig.output(), fn_mode); } } @@ -998,9 +1054,9 @@ impl<'tcx> ImproperCTypesLint { } fn check_foreign_static(&mut self, cx: &LateContext<'tcx>, id: hir::OwnerId, span: Span) { - let ty = cx.tcx.type_of(id).instantiate_identity().skip_norm_wip(); + let ty = cx.tcx.type_of(id).instantiate_identity(); let mut visitor = ImproperCTypesVisitor::new(cx, ty, CItemKind::Declaration); - let ffi_res = visitor.check_type(VisitorState::STATIC_TY, ty); + let ffi_res = visitor.check_type(VisitorState::static_entry_point(), ty); self.process_ffi_result(cx, span, ffi_res, CItemKind::Declaration); } @@ -1016,16 +1072,18 @@ impl<'tcx> ImproperCTypesLint { let sig = cx.tcx.instantiate_bound_regions_with_erased(sig); for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { - let state = VisitorState::argument_from_fnmode(fn_mode); - let mut visitor = ImproperCTypesVisitor::new(cx, *input_ty, fn_mode); - let ffi_res = visitor.check_type(state, *input_ty); + let input_ty = Unnormalized::new_wip(*input_ty); + let state = VisitorState::fn_entry_point(fn_mode, FnPos::Arg); + let mut visitor = ImproperCTypesVisitor::new(cx, input_ty, fn_mode); + let ffi_res = visitor.check_type(state, input_ty); self.process_ffi_result(cx, input_hir.span, ffi_res, fn_mode); } if let hir::FnRetTy::Return(ret_hir) = decl.output { - let state = VisitorState::return_from_fnmode(fn_mode); - let mut visitor = ImproperCTypesVisitor::new(cx, sig.output(), fn_mode); - let ffi_res = visitor.check_type(state, sig.output()); + let output_ty = Unnormalized::new_wip(sig.output()); + let state = VisitorState::fn_entry_point(fn_mode, FnPos::Ret); + let mut visitor = ImproperCTypesVisitor::new(cx, output_ty, fn_mode); + let ffi_res = visitor.check_type(state, output_ty); self.process_ffi_result(cx, ret_hir.span, ffi_res, fn_mode); } } @@ -1124,7 +1182,7 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { | hir::ItemKind::TyAlias(_, _, ty) => { self.check_type_for_external_abi_fnptr( cx, - VisitorState::STATIC_TY, + VisitorState::static_entry_point(), ty, cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip(), CItemKind::Definition, @@ -1158,7 +1216,7 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { fn check_field_def(&mut self, cx: &LateContext<'tcx>, field: &'tcx hir::FieldDef<'tcx>) { self.check_type_for_external_abi_fnptr( cx, - VisitorState::STATIC_TY, + VisitorState::static_entry_point(), field.ty, cx.tcx.type_of(field.def_id).instantiate_identity().skip_norm_wip(), CItemKind::Definition, diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 4858e65992cd3..c6892419443f8 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -34,6 +34,7 @@ declare_lint_pass! { CONST_EVALUATABLE_UNCHECKED, CONST_ITEM_MUTATION, DEAD_CODE, + DEAD_CODE_PUB_IN_BINARY, DEPENDENCY_ON_UNIT_NEVER_TYPE_FALLBACK, DEPRECATED, DEPRECATED_IN_FUTURE, @@ -789,6 +790,37 @@ declare_lint! { "detect unused, unexported items" } +declare_lint! { + /// The `dead_code_pub_in_binary` lint detects unused `pub` items in + /// executable crates. + /// + /// ### Example + /// + /// ```rust + /// #![deny(dead_code_pub_in_binary)] + /// + /// pub fn unused_pub_fn() {} + /// + /// fn main() {} + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// In executable crates, `pub` items are often implementation details + /// rather than part of an external API. This lint helps find those items + /// when they are never used. + /// + /// This lint only applies to executable crates. In library crates, public + /// items are considered part of the crate's API and are not reported by + /// this lint. + pub DEAD_CODE_PUB_IN_BINARY, + Allow, + "detect public items in executable crates that are never used", + crate_level_only +} + declare_lint! { /// The `unused_attributes` lint detects attributes that were not used by /// the compiler. diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index a00fb59963ac2..cbd6afd68473a 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -261,7 +261,8 @@ provide! { tcx, def_id, other, cdata, .coerce_unsized_info .get(cdata, def_id.index) .map(|lazy| lazy.decode((cdata, tcx))) - .process_decoded(tcx, || panic!("{def_id:?} does not have coerce_unsized_info"))) } + .process_decoded(tcx, || panic!("{def_id:?} does not have coerce_unsized_info"))) + } mir_const_qualif => { table } rendered_const => { table } rendered_precise_capturing_args => { table } @@ -300,10 +301,10 @@ provide! { tcx, def_id, other, cdata, Ok(cdata .root .tables - .trait_impl_trait_tys + .collect_return_position_impl_trait_in_trait_tys .get(cdata, def_id.index) .map(|lazy| lazy.decode((cdata, tcx))) - .process_decoded(tcx, || panic!("{def_id:?} does not have trait_impl_trait_tys"))) + .process_decoded(tcx, || panic!("{def_id:?} does not have collect_return_position_impl_trait_in_trait_tys"))) } associated_types_for_impl_traits_in_trait_or_impl => { table } @@ -695,6 +696,7 @@ impl CrateStore for CStore { fn as_any(&self) -> &dyn Any { self } + fn untracked_as_any(&mut self) -> &mut dyn Any { self } diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 729a0dda7cf3b..57fe9210c6276 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -1628,7 +1628,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { if tcx.impl_method_has_trait_impl_trait_tys(def_id) && let Ok(table) = self.tcx.collect_return_position_impl_trait_in_trait_tys(def_id) { - record!(self.tables.trait_impl_trait_tys[def_id] <- table); + record!(self.tables.collect_return_position_impl_trait_in_trait_tys[def_id] <- table); } if let DefKind::Impl { .. } | DefKind::Trait = def_kind { let table = tcx.associated_types_for_impl_traits_in_trait_or_impl(def_id); diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index c7b2eaa15ebfb..a3645a5556bf3 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -466,7 +466,7 @@ define_tables! { macro_definition: Table>, proc_macro: Table, deduced_param_attrs: Table>, - trait_impl_trait_tys: Table>>>>, + collect_return_position_impl_trait_in_trait_tys: Table>>>>, doc_link_resolutions: Table>, doc_link_traits_in_scope: Table>, assumed_wf_types_for_rpitit: Table, Span)>>, diff --git a/compiler/rustc_middle/src/arena.rs b/compiler/rustc_middle/src/arena.rs index de6a105ee2b7b..e12583f38fe72 100644 --- a/compiler/rustc_middle/src/arena.rs +++ b/compiler/rustc_middle/src/arena.rs @@ -1,7 +1,7 @@ /// This higher-order macro declares a list of types which can be allocated by `Arena`. /// /// Specifying the `decode` modifier will add decode impls for `&T` and `&[T]` where `T` is the type -/// listed. These impls will appear in the implement_ty_decoder! macro. +/// listed. See the `impl_arena_allocatable_decoder!` macro for more. #[macro_export] macro_rules! arena_types { ($macro:path) => ( @@ -9,8 +9,7 @@ macro_rules! arena_types { [] layout: rustc_abi::LayoutData, [] proxy_coroutine_layout: rustc_middle::mir::CoroutineLayout<'tcx>, [] fn_abi: rustc_target::callconv::FnAbi<'tcx, rustc_middle::ty::Ty<'tcx>>, - // AdtDef are interned and compared by address - [decode] adt_def: rustc_middle::ty::AdtDefData, + [] adt_def: rustc_middle::ty::AdtDefData, [] steal_thir: rustc_data_structures::steal::Steal>, [] steal_mir: rustc_data_structures::steal::Steal>, [decode] mir: rustc_middle::mir::Body<'tcx>, @@ -27,7 +26,7 @@ macro_rules! arena_types { rustc_middle::mir::Body<'tcx> >, [decode] typeck_results: rustc_middle::ty::TypeckResults<'tcx>, - [decode] borrowck_result: rustc_data_structures::fx::FxIndexMap< + [] borrowck_result: rustc_data_structures::fx::FxIndexMap< rustc_hir::def_id::LocalDefId, rustc_middle::ty::DefinitionSiteHiddenType<'tcx>, >, @@ -100,7 +99,7 @@ macro_rules! arena_types { // (during lowering) and the `rustc_middle` arena (for decoding MIR) [decode] asm_template: rustc_ast::InlineAsmTemplatePiece, [decode] used_trait_imports: rustc_data_structures::unord::UnordSet, - [decode] is_late_bound_map: rustc_data_structures::fx::FxIndexSet, + [] is_late_bound_map: rustc_data_structures::fx::FxIndexSet, [decode] impl_source: rustc_middle::traits::ImplSource<'tcx, ()>, [] dep_kind_vtable: rustc_middle::dep_graph::DepKindVTable<'tcx>, @@ -111,7 +110,7 @@ macro_rules! arena_types { rustc_middle::ty::EarlyBinder<'tcx, rustc_middle::ty::Ty<'tcx>> >, [] external_constraints: rustc_middle::traits::solve::ExternalConstraintsData>, - [decode] doc_link_resolutions: rustc_hir::def::DocLinkResMap, + [] doc_link_resolutions: rustc_hir::def::DocLinkResMap, [] stripped_cfg_items: rustc_hir::attrs::StrippedCfgItem, [] mod_child: rustc_middle::metadata::ModChild, [] features: rustc_feature::Features, diff --git a/compiler/rustc_middle/src/middle/dead_code.rs b/compiler/rustc_middle/src/middle/dead_code.rs new file mode 100644 index 0000000000000..06275e1e0f3a0 --- /dev/null +++ b/compiler/rustc_middle/src/middle/dead_code.rs @@ -0,0 +1,23 @@ +use rustc_data_structures::fx::FxIndexSet; +use rustc_hir::def_id::{DefId, LocalDefIdMap, LocalDefIdSet}; +use rustc_macros::StableHash; + +/// A single snapshot of dead-code liveness analysis state. +#[derive(Clone, Debug, StableHash)] +pub struct DeadCodeLivenessSnapshot { + pub live_symbols: LocalDefIdSet, + /// Maps each ADT to derived traits (for example `Debug` and `Clone`) that should be ignored + /// when checking for dead code diagnostics. + pub ignored_derived_traits: LocalDefIdMap>, +} + +/// Dead-code liveness data for both analysis phases. +/// +/// `pre_deferred_seeding` is computed before reachable-public and `#[allow(dead_code)]` seeding, +/// and is used for lint `dead_code_pub_in_binary`. +/// `final_result` is the final liveness snapshot used for lint `dead_code`. +#[derive(Clone, Debug, StableHash)] +pub struct DeadCodeLivenessSummary { + pub pre_deferred_seeding: DeadCodeLivenessSnapshot, + pub final_result: DeadCodeLivenessSnapshot, +} diff --git a/compiler/rustc_middle/src/middle/mod.rs b/compiler/rustc_middle/src/middle/mod.rs index 203845143fa06..7967a6222c3be 100644 --- a/compiler/rustc_middle/src/middle/mod.rs +++ b/compiler/rustc_middle/src/middle/mod.rs @@ -1,4 +1,5 @@ pub mod codegen_fn_attrs; +pub mod dead_code; pub mod debugger_visualizer; pub mod deduced_param_attrs; pub mod dependency_format; diff --git a/compiler/rustc_middle/src/queries.rs b/compiler/rustc_middle/src/queries.rs index f0c367beefd65..f8200badb345b 100644 --- a/compiler/rustc_middle/src/queries.rs +++ b/compiler/rustc_middle/src/queries.rs @@ -64,9 +64,7 @@ use rustc_errors::{ErrorGuaranteed, catch_fatal_errors}; use rustc_hir as hir; use rustc_hir::attrs::{EiiDecl, EiiImpl, StrippedCfgItem}; use rustc_hir::def::{DefKind, DocLinkResMap}; -use rustc_hir::def_id::{ - CrateNum, DefId, DefIdMap, LocalDefId, LocalDefIdMap, LocalDefIdSet, LocalModDefId, -}; +use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, LocalDefId, LocalDefIdSet, LocalModDefId}; use rustc_hir::lang_items::{LangItem, LanguageItems}; use rustc_hir::{ItemLocalId, ItemLocalMap, PreciseCapturingArgKind, TraitCandidate}; use rustc_index::IndexVec; @@ -87,6 +85,7 @@ use crate::infer::canonical::{self, Canonical}; use crate::lint::LintExpectation; use crate::metadata::ModChild; use crate::middle::codegen_fn_attrs::{CodegenFnAttrs, SanitizerFnAttrs}; +use crate::middle::dead_code::DeadCodeLivenessSummary; use crate::middle::debugger_visualizer::DebuggerVisualizerFile; use crate::middle::deduced_param_attrs::DeducedParamAttrs; use crate::middle::exported_symbols::{ExportedSymbol, SymbolExportInfo}; @@ -1203,13 +1202,8 @@ rustc_queries! { desc { "checking liveness of variables in `{}`", tcx.def_path_str(key.to_def_id()) } } - /// Return the live symbols in the crate for dead code check. - /// - /// The second return value maps from ADTs to ignored derived traits (e.g. Debug and Clone). - query live_symbols_and_ignored_derived_traits(_: ()) -> Result<&'tcx ( - LocalDefIdSet, - LocalDefIdMap>, - ), ErrorGuaranteed> { + /// Return dead-code liveness summary for the crate. + query live_symbols_and_ignored_derived_traits(_: ()) -> Result<&'tcx DeadCodeLivenessSummary, ErrorGuaranteed> { arena_cache desc { "finding live symbols in crate" } } diff --git a/compiler/rustc_middle/src/ty/codec.rs b/compiler/rustc_middle/src/ty/codec.rs index 28bdeabf34dc1..8b1e812582783 100644 --- a/compiler/rustc_middle/src/ty/codec.rs +++ b/compiler/rustc_middle/src/ty/codec.rs @@ -513,9 +513,8 @@ macro_rules! __impl_decoder_methods { } macro_rules! impl_arena_allocatable_decoder { - ([]$args:tt) => {}; - ([decode $(, $attrs:ident)*] - [$name:ident: $ty:ty]) => { + ([] $name:ident: $ty:ty) => {}; + ([decode] $name:ident: $ty:ty) => { impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for $ty { #[inline] fn decode(decoder: &mut D) -> &'tcx Self { @@ -535,7 +534,7 @@ macro_rules! impl_arena_allocatable_decoder { macro_rules! impl_arena_allocatable_decoders { ([$($a:tt $name:ident: $ty:ty,)*]) => { $( - impl_arena_allocatable_decoder!($a [$name: $ty]); + impl_arena_allocatable_decoder!($a $name: $ty); )* } } diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index a978138e3e1ff..f1596a0685768 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -1526,6 +1526,15 @@ impl<'tcx> CheckAttrVisitor<'tcx> { return; } } + } else if hir_id == CRATE_HIR_ID + && attr.has_any_name(&[sym::allow, sym::warn, sym::deny, sym::forbid, sym::expect]) + && let Some(meta) = attr.meta_item_list() + && meta.iter().any(|meta| { + meta.meta_item().is_some_and(|item| item.path == sym::dead_code_pub_in_binary) + }) + && !self.tcx.crate_types().contains(&CrateType::Executable) + { + errors::UnusedNote::NoEffectDeadCodePubInBinary } else if attr.has_name(sym::default_method_body_is_const) { errors::UnusedNote::DefaultMethodBodyConst } else { diff --git a/compiler/rustc_passes/src/dead.rs b/compiler/rustc_passes/src/dead.rs index 7eb484b1d57a7..2ee094c87616c 100644 --- a/compiler/rustc_passes/src/dead.rs +++ b/compiler/rustc_passes/src/dead.rs @@ -13,18 +13,21 @@ use rustc_errors::{ErrorGuaranteed, MultiSpan}; use rustc_hir::def::{CtorOf, DefKind, Res}; use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId}; use rustc_hir::intravisit::{self, Visitor}; -use rustc_hir::{self as hir, Node, PatKind, QPath, find_attr}; +use rustc_hir::{self as hir, ForeignItemId, ItemId, Node, PatKind, QPath, find_attr}; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; +use rustc_middle::middle::dead_code::{DeadCodeLivenessSnapshot, DeadCodeLivenessSummary}; use rustc_middle::middle::privacy::Level; use rustc_middle::query::Providers; use rustc_middle::ty::{self, AssocTag, TyCtxt}; use rustc_middle::{bug, span_bug}; -use rustc_session::lint::builtin::DEAD_CODE; -use rustc_session::lint::{self, LintExpectationId}; +use rustc_session::config::CrateType; +use rustc_session::lint::builtin::{DEAD_CODE, DEAD_CODE_PUB_IN_BINARY}; +use rustc_session::lint::{self, Lint, LintExpectationId}; use rustc_span::{Symbol, kw}; use crate::errors::{ - ChangeFields, IgnoredDerivedImpls, MultipleDeadCodes, ParentInfo, UselessAssignment, + ChangeFields, DeadCodePubInBinaryNote, IgnoredDerivedImpls, MultipleDeadCodes, ParentInfo, + UselessAssignment, }; /// Any local definition that may call something in its body block should be explored. For example, @@ -819,12 +822,12 @@ fn has_allow_dead_code_or_lang_attr( fn maybe_record_as_seed<'tcx>( tcx: TyCtxt<'tcx>, owner_id: hir::OwnerId, - worklist: &mut Vec, + push_into_worklist: &mut impl FnMut(WorkItem), unsolved_items: &mut Vec, ) { let allow_dead_code = has_allow_dead_code_or_lang_attr(tcx, owner_id.def_id); if let Some(comes_from_allow) = allow_dead_code { - worklist.push(WorkItem { + push_into_worklist(WorkItem { id: owner_id.def_id, propagated: comes_from_allow, own: comes_from_allow, @@ -835,11 +838,13 @@ fn maybe_record_as_seed<'tcx>( DefKind::Enum => { if let Some(comes_from_allow) = allow_dead_code { let adt = tcx.adt_def(owner_id); - worklist.extend(adt.variants().iter().map(|variant| WorkItem { - id: variant.def_id.expect_local(), - propagated: comes_from_allow, - own: comes_from_allow, - })); + for variant in adt.variants().iter() { + push_into_worklist(WorkItem { + id: variant.def_id.expect_local(), + propagated: comes_from_allow, + own: comes_from_allow, + }); + } } } DefKind::AssocFn | DefKind::AssocConst { .. } | DefKind::AssocTy => { @@ -854,7 +859,7 @@ fn maybe_record_as_seed<'tcx>( && let Some(comes_from_allow) = has_allow_dead_code_or_lang_attr(tcx, trait_item_local_def_id) { - worklist.push(WorkItem { + push_into_worklist(WorkItem { id: owner_id.def_id, propagated: comes_from_allow, own: comes_from_allow, @@ -879,7 +884,7 @@ fn maybe_record_as_seed<'tcx>( && let Some(comes_from_allow) = has_allow_dead_code_or_lang_attr(tcx, trait_def_id) { - worklist.push(WorkItem { + push_into_worklist(WorkItem { id: owner_id.def_id, propagated: comes_from_allow, own: comes_from_allow, @@ -891,7 +896,7 @@ fn maybe_record_as_seed<'tcx>( } DefKind::GlobalAsm => { // global_asm! is always live. - worklist.push(WorkItem { + push_into_worklist(WorkItem { id: owner_id.def_id, propagated: ComesFromAllowExpect::No, own: ComesFromAllowExpect::No, @@ -902,7 +907,7 @@ fn maybe_record_as_seed<'tcx>( // `const _` is always live, as that syntax only exists for the side effects // of type checking and evaluating the constant expression, and marking them // as dead code would defeat that purpose. - worklist.push(WorkItem { + push_into_worklist(WorkItem { id: owner_id.def_id, propagated: ComesFromAllowExpect::No, own: ComesFromAllowExpect::No, @@ -913,41 +918,55 @@ fn maybe_record_as_seed<'tcx>( } } -fn create_and_seed_worklist(tcx: TyCtxt<'_>) -> (Vec, Vec) { - let effective_visibilities = &tcx.effective_visibilities(()); - let mut unsolved_impl_item = Vec::new(); - let mut worklist = effective_visibilities - .iter() - .filter_map(|(&id, effective_vis)| { - effective_vis.is_public_at_level(Level::Reachable).then_some(id).map(|id| WorkItem { - id, - propagated: ComesFromAllowExpect::No, - own: ComesFromAllowExpect::No, - }) - }) - // Seed entry point - .chain(tcx.entry_fn(()).and_then(|(def_id, _)| { - def_id.as_local().map(|id| WorkItem { - id, +struct SeedWorklists { + worklist: Vec, + deferred_seeds: Vec, + unsolved_items: Vec, +} + +fn create_and_seed_worklist(tcx: TyCtxt<'_>) -> SeedWorklists { + let mut unsolved_items = Vec::new(); + let mut deferred_seeds = Vec::new(); + let mut worklist = Vec::new(); + + if let Some((def_id, _)) = tcx.entry_fn(()) + && let Some(local_def_id) = def_id.as_local() + { + worklist.push(WorkItem { + id: local_def_id, + propagated: ComesFromAllowExpect::No, + own: ComesFromAllowExpect::No, + }); + } + + for (id, effective_vis) in tcx.effective_visibilities(()).iter() { + if effective_vis.is_public_at_level(Level::Reachable) { + deferred_seeds.push(WorkItem { + id: *id, propagated: ComesFromAllowExpect::No, own: ComesFromAllowExpect::No, - }) - })) - .collect::>(); + }); + } + } + let mut push_into_worklist = |work_item: WorkItem| match work_item.own { + ComesFromAllowExpect::Yes => deferred_seeds.push(work_item), + ComesFromAllowExpect::No => worklist.push(work_item), + }; let crate_items = tcx.hir_crate_items(()); for id in crate_items.owners() { - maybe_record_as_seed(tcx, id, &mut worklist, &mut unsolved_impl_item); + maybe_record_as_seed(tcx, id, &mut push_into_worklist, &mut unsolved_items); } - (worklist, unsolved_impl_item) + SeedWorklists { worklist, deferred_seeds, unsolved_items } } fn live_symbols_and_ignored_derived_traits( tcx: TyCtxt<'_>, (): (), -) -> Result<(LocalDefIdSet, LocalDefIdMap>), ErrorGuaranteed> { - let (worklist, mut unsolved_items) = create_and_seed_worklist(tcx); +) -> Result { + let SeedWorklists { worklist, deferred_seeds, mut unsolved_items } = + create_and_seed_worklist(tcx); let mut symbol_visitor = MarkSymbolVisitor { worklist, tcx, @@ -961,6 +980,28 @@ fn live_symbols_and_ignored_derived_traits( ignored_derived_traits: Default::default(), propagated_comes_from_allow_expect: ComesFromAllowExpect::No, }; + mark_live_symbols_and_ignored_derived_traits(&mut symbol_visitor, &mut unsolved_items)?; + let pre_deferred_seeding = DeadCodeLivenessSnapshot { + live_symbols: symbol_visitor.live_symbols.clone(), + ignored_derived_traits: symbol_visitor.ignored_derived_traits.clone(), + }; + + symbol_visitor.worklist.extend(deferred_seeds); + mark_live_symbols_and_ignored_derived_traits(&mut symbol_visitor, &mut unsolved_items)?; + + Ok(DeadCodeLivenessSummary { + pre_deferred_seeding, + final_result: DeadCodeLivenessSnapshot { + live_symbols: symbol_visitor.live_symbols, + ignored_derived_traits: symbol_visitor.ignored_derived_traits, + }, + }) +} + +fn mark_live_symbols_and_ignored_derived_traits( + symbol_visitor: &mut MarkSymbolVisitor<'_>, + unsolved_items: &mut Vec, +) -> Result<(), ErrorGuaranteed> { if let ControlFlow::Break(guar) = symbol_visitor.mark_live_symbols() { return Err(guar); } @@ -988,7 +1029,7 @@ fn live_symbols_and_ignored_derived_traits( })); } - Ok((symbol_visitor.live_symbols, symbol_visitor.ignored_derived_traits)) + Ok(()) } struct DeadItem { @@ -999,6 +1040,7 @@ struct DeadItem { struct DeadVisitor<'tcx> { tcx: TyCtxt<'tcx>, + target_lint: &'static Lint, live_symbols: &'tcx LocalDefIdSet, ignored_derived_traits: &'tcx LocalDefIdMap>, } @@ -1042,10 +1084,14 @@ impl<'tcx> DeadVisitor<'tcx> { fn def_lint_level(&self, id: LocalDefId) -> (lint::Level, Option) { let hir_id = self.tcx.local_def_id_to_hir_id(id); - let level = self.tcx.lint_level_at_node(DEAD_CODE, hir_id); + let level = self.tcx.lint_level_at_node(self.target_lint, hir_id); (level.level, level.lint_id) } + fn dead_code_pub_in_binary_note(&self) -> Option { + self.target_lint.name.eq(DEAD_CODE_PUB_IN_BINARY.name).then_some(DeadCodePubInBinaryNote) + } + // # Panics // All `dead_codes` must have the same lint level, otherwise we will intentionally ICE. // This is because we emit a multi-spanned lint using the lint level of the `dead_codes`'s @@ -1157,6 +1203,7 @@ impl<'tcx> DeadVisitor<'tcx> { descr, participle, name_list, + dead_code_pub_in_binary_note: self.dead_code_pub_in_binary_note(), change_fields_suggestion: fields_suggestion, parent_info, ignored_derived_impls, @@ -1193,6 +1240,7 @@ impl<'tcx> DeadVisitor<'tcx> { descr, participle, name_list, + dead_code_pub_in_binary_note: self.dead_code_pub_in_binary_note(), parent_info, ignored_derived_impls, enum_variants_with_same_name, @@ -1201,7 +1249,7 @@ impl<'tcx> DeadVisitor<'tcx> { }; let hir_id = tcx.local_def_id_to_hir_id(first_item.def_id); - self.tcx.emit_node_span_lint(DEAD_CODE, hir_id, MultiSpan::from_spans(spans), diag); + self.tcx.emit_node_span_lint(self.target_lint, hir_id, MultiSpan::from_spans(spans), diag); } fn warn_multiple( @@ -1268,17 +1316,55 @@ impl<'tcx> DeadVisitor<'tcx> { } fn check_mod_deathness(tcx: TyCtxt<'_>, module: LocalModDefId) { - let Ok((live_symbols, ignored_derived_traits)) = + let Ok(DeadCodeLivenessSummary { pre_deferred_seeding, final_result }) = tcx.live_symbols_and_ignored_derived_traits(()).as_ref() else { return; }; - let mut visitor = DeadVisitor { tcx, live_symbols, ignored_derived_traits }; - let module_items = tcx.hir_module_items(module); - for item in module_items.free_items() { + if tcx.crate_types().contains(&CrateType::Executable) { + let is_unused_pub = |def_id: LocalDefId| { + tcx.effective_visibilities(()).is_public_at_level(def_id, Level::Reachable) + && !pre_deferred_seeding.live_symbols.contains(&def_id) + }; + + lint_dead_codes( + tcx, + DEAD_CODE_PUB_IN_BINARY, + module, + &pre_deferred_seeding.live_symbols, + &pre_deferred_seeding.ignored_derived_traits, + module_items.free_items().filter(|free_item| is_unused_pub(free_item.owner_id.def_id)), + module_items + .foreign_items() + .filter(|foreign_item| is_unused_pub(foreign_item.owner_id.def_id)), + ); + } + + lint_dead_codes( + tcx, + DEAD_CODE, + module, + &final_result.live_symbols, + &final_result.ignored_derived_traits, + module_items.free_items(), + module_items.foreign_items(), + ); +} + +fn lint_dead_codes<'tcx>( + tcx: TyCtxt<'tcx>, + target_lint: &'static Lint, + module: LocalModDefId, + live_symbols: &'tcx LocalDefIdSet, + ignored_derived_traits: &'tcx LocalDefIdMap>, + free_items: impl Iterator, + foreign_items: impl Iterator, +) { + let mut visitor = DeadVisitor { tcx, target_lint, live_symbols, ignored_derived_traits }; + for item in free_items { let def_kind = tcx.def_kind(item.owner_id); let mut dead_codes = Vec::new(); @@ -1357,7 +1443,7 @@ fn check_mod_deathness(tcx: TyCtxt<'_>, module: LocalModDefId) { } } - for foreign_item in module_items.foreign_items() { + for foreign_item in foreign_items { visitor.check_definition(foreign_item.owner_id.def_id); } } diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index a916b4670fded..b87a43e9fcde0 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -298,6 +298,8 @@ pub(crate) enum UnusedNote { "the `linker_messages` and `linker_info` lints can only be controlled at the root of a crate that needs to be linked" )] LinkerMessagesBinaryCrateOnly, + #[note("the `dead_code_pub_in_binary` lint has no effect in library crates")] + NoEffectDeadCodePubInBinary, } #[derive(Diagnostic)] @@ -920,6 +922,8 @@ pub(crate) enum MultipleDeadCodes<'tcx> { participle: &'tcx str, name_list: DiagSymbolList, #[subdiagnostic] + dead_code_pub_in_binary_note: Option, + #[subdiagnostic] // only on DeadCodes since it's never a problem for tuple struct fields enum_variants_with_same_name: Vec>, #[subdiagnostic] @@ -943,6 +947,8 @@ pub(crate) enum MultipleDeadCodes<'tcx> { participle: &'tcx str, name_list: DiagSymbolList, #[subdiagnostic] + dead_code_pub_in_binary_note: Option, + #[subdiagnostic] change_fields_suggestion: ChangeFields, #[subdiagnostic] parent_info: Option>, @@ -951,6 +957,12 @@ pub(crate) enum MultipleDeadCodes<'tcx> { }, } +#[derive(Subdiagnostic)] +#[note( + "in libraries, `pub` items can be used by dependent crates; in binaries, they cannot, so this `pub` item is unused" +)] +pub(crate) struct DeadCodePubInBinaryNote; + #[derive(Subdiagnostic)] #[note( "it is impossible to refer to the {$dead_descr} `{$dead_name}` because it is shadowed by this enum variant with the same name" diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index 524c9ce8ad5d7..da75238bacd56 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -720,13 +720,15 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { decls: Default::default(), nested, id, + def_id: feed.def_id(), }; self.add_import(module_path, kind, use_tree.span(), item, root_span, item.id, vis); } ast::UseTreeKind::Glob(_) => { if !ast::attr::contains_name(&item.attrs, sym::prelude_import) { - let kind = ImportKind::Glob { max_vis: CmCell::new(None), id }; + let kind = + ImportKind::Glob { max_vis: CmCell::new(None), id, def_id: feed.def_id() }; self.add_import(prefix, kind, use_tree.span(), item, root_span, item.id, vis); } else { // Resolve the prelude import early. @@ -1018,7 +1020,12 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { }) .unwrap_or((true, None, self.r.dummy_decl)); let import = self.r.arenas.alloc_import(ImportData { - kind: ImportKind::ExternCrate { source: orig_name, target: orig_ident, id: item.id }, + kind: ImportKind::ExternCrate { + source: orig_name, + target: orig_ident, + id: item.id, + def_id: local_def_id, + }, root_id: item.id, parent_scope, imported_module: CmCell::new(module), @@ -1270,7 +1277,7 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { if !ident.as_str().starts_with('_') { self.r.unused_macros.insert(def_id, (node_id, ident)); let nrules = self.r.local_macro_map[&def_id].nrules; - self.r.unused_macro_rules.insert(node_id, DenseBitSet::new_filled(nrules)); + self.r.unused_macro_rules.insert(node_id, (def_id, DenseBitSet::new_filled(nrules))); } } diff --git a/compiler/rustc_resolve/src/check_unused.rs b/compiler/rustc_resolve/src/check_unused.rs index 7e1b1bce3ff74..8ab0087ae6158 100644 --- a/compiler/rustc_resolve/src/check_unused.rs +++ b/compiler/rustc_resolve/src/check_unused.rs @@ -431,8 +431,7 @@ impl Resolver<'_, '_> { } } } - ImportKind::ExternCrate { id, .. } => { - let def_id = self.local_def_id(id); + ImportKind::ExternCrate { id, def_id, .. } => { if self.extern_crate_map.get(&def_id).is_none_or(|&cnum| { !tcx.is_compiler_builtins(cnum) && !tcx.is_panic_runtime(cnum) diff --git a/compiler/rustc_resolve/src/effective_visibilities.rs b/compiler/rustc_resolve/src/effective_visibilities.rs index b5614106fe997..693e49995c1cc 100644 --- a/compiler/rustc_resolve/src/effective_visibilities.rs +++ b/compiler/rustc_resolve/src/effective_visibilities.rs @@ -45,10 +45,7 @@ impl Resolver<'_, '_> { fn private_vis_import(&self, decl: Decl<'_>) -> Visibility { let DeclKind::Import { import, .. } = decl.kind else { unreachable!() }; Visibility::Restricted( - import - .id() - .map(|id| self.nearest_normal_mod(self.local_def_id(id))) - .unwrap_or(CRATE_DEF_ID), + import.def_id().map(|id| self.nearest_normal_mod(id)).unwrap_or(CRATE_DEF_ID), ) } @@ -96,8 +93,8 @@ impl<'a, 'ra, 'tcx> EffectiveVisibilitiesVisitor<'a, 'ra, 'tcx> { // is the maximum value among visibilities of declarations corresponding to that def id. for (decl, eff_vis) in visitor.import_effective_visibilities.iter() { let DeclKind::Import { import, .. } = decl.kind else { unreachable!() }; - if let Some(node_id) = import.id() { - r.effective_visibilities.update_eff_vis(r.local_def_id(node_id), eff_vis, r.tcx) + if let Some(def_id) = import.def_id() { + r.effective_visibilities.update_eff_vis(def_id, eff_vis, r.tcx) } if decl.ambiguity.get().is_some() && eff_vis.is_public_at_level(Level::Reexported) { exported_ambiguities.insert(*decl); diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index e63ab9f21a6ad..1b9e5d2a2daa6 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -16,7 +16,7 @@ use rustc_hir::Attribute; use rustc_hir::attrs::AttributeKind; use rustc_hir::attrs::diagnostic::{CustomDiagnostic, Directive, FormatArgs}; use rustc_hir::def::{self, DefKind, PartialRes}; -use rustc_hir::def_id::{DefId, LocalDefIdMap}; +use rustc_hir::def_id::{DefId, LocalDefId, LocalDefIdMap}; use rustc_middle::metadata::{AmbigModChild, ModChild, Reexport}; use rustc_middle::span_bug; use rustc_middle::ty::{TyCtxt, Visibility}; @@ -90,17 +90,20 @@ pub(crate) enum ImportKind<'ra> { /// If this is the import for `foo::bar::a`, we would have the ID of the `UseTree` /// for `a` in this field. id: NodeId, + def_id: LocalDefId, }, Glob { // The visibility of the greatest re-export. // n.b. `max_vis` is only used in `finalize_import` to check for re-export errors. max_vis: CmCell>, id: NodeId, + def_id: LocalDefId, }, ExternCrate { source: Option, target: Ident, id: NodeId, + def_id: LocalDefId, }, MacroUse { /// A field has been added indicating whether it should be reported as a lint, @@ -116,7 +119,7 @@ impl<'ra> std::fmt::Debug for ImportKind<'ra> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use ImportKind::*; match self { - Single { source, target, decls, nested, id, .. } => f + Single { source, target, decls, nested, id, def_id } => f .debug_struct("Single") .field("source", source) .field("target", target) @@ -127,15 +130,20 @@ impl<'ra> std::fmt::Debug for ImportKind<'ra> { ) .field("nested", nested) .field("id", id) + .field("def_id", def_id) .finish(), - Glob { max_vis, id } => { - f.debug_struct("Glob").field("max_vis", max_vis).field("id", id).finish() - } - ExternCrate { source, target, id } => f + Glob { max_vis, id, def_id } => f + .debug_struct("Glob") + .field("max_vis", max_vis) + .field("id", id) + .field("def_id", def_id) + .finish(), + ExternCrate { source, target, id, def_id } => f .debug_struct("ExternCrate") .field("source", source) .field("target", target) .field("id", id) + .field("def_id", def_id) .finish(), MacroUse { warn_private } => { f.debug_struct("MacroUse").field("warn_private", warn_private).finish() @@ -260,12 +268,20 @@ impl<'ra> ImportData<'ra> { } } - pub(crate) fn simplify(&self, r: &Resolver<'_, '_>) -> Reexport { - let to_def_id = |id| r.local_def_id(id).to_def_id(); + pub(crate) fn def_id(&self) -> Option { + match self.kind { + ImportKind::Single { def_id, .. } + | ImportKind::Glob { def_id, .. } + | ImportKind::ExternCrate { def_id, .. } => Some(def_id), + ImportKind::MacroUse { .. } | ImportKind::MacroExport => None, + } + } + + pub(crate) fn simplify(&self) -> Reexport { match self.kind { - ImportKind::Single { id, .. } => Reexport::Single(to_def_id(id)), - ImportKind::Glob { id, .. } => Reexport::Glob(to_def_id(id)), - ImportKind::ExternCrate { id, .. } => Reexport::ExternCrate(to_def_id(id)), + ImportKind::Single { def_id, .. } => Reexport::Single(def_id.to_def_id()), + ImportKind::Glob { def_id, .. } => Reexport::Glob(def_id.to_def_id()), + ImportKind::ExternCrate { def_id, .. } => Reexport::ExternCrate(def_id.to_def_id()), ImportKind::MacroUse { .. } => Reexport::MacroUse, ImportKind::MacroExport => Reexport::MacroExport, } @@ -340,13 +356,16 @@ struct UnresolvedImportError { // Reexports of the form `pub use foo as bar;` where `foo` is `extern crate foo;` // are permitted for backward-compatibility under a deprecation lint. -fn pub_use_of_private_extern_crate_hack(import: ImportSummary, decl: Decl<'_>) -> Option { +fn pub_use_of_private_extern_crate_hack( + import: ImportSummary, + decl: Decl<'_>, +) -> Option { match (import.is_single, decl.kind) { (true, DeclKind::Import { import: decl_import, .. }) - if let ImportKind::ExternCrate { id, .. } = decl_import.kind + if let ImportKind::ExternCrate { def_id, .. } = decl_import.kind && import.vis.is_public() => { - Some(id) + Some(def_id) } _ => None, } @@ -845,8 +864,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { if binding.res() != Res::Err && glob_decl.res() != Res::Err && let DeclKind::Import { import: glob_import, .. } = glob_decl.kind - && let Some(glob_import_id) = glob_import.id() - && let glob_import_def_id = self.local_def_id(glob_import_id) + && let Some(glob_import_def_id) = glob_import.def_id() && self.effective_visibilities.is_exported(glob_import_def_id) && glob_decl.vis().is_public() && !binding.vis().is_public() @@ -875,7 +893,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { if let DeclKind::Import { import, .. } = binding.kind && let Some(binding_id) = import.id() - && let import_def_id = self.local_def_id(binding_id) + && let import_def_id = import.def_id().unwrap() && self.effective_visibilities.is_exported(import_def_id) && let Res::Def(reexported_kind, reexported_def_id) = binding.res() && !matches!(reexported_kind, DefKind::Ctor(..)) @@ -1267,7 +1285,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let (ident, target, bindings, import_id) = match import.kind { ImportKind::Single { source, target, ref decls, id, .. } => (source, target, decls, id), - ImportKind::Glob { ref max_vis, id } => { + ImportKind::Glob { ref max_vis, id, def_id } => { if import.module_path.len() <= 1 { // HACK(eddyb) `lint_if_path_starts_with_module` needs at least // 2 segments, so the `resolve_path` above won't trigger it. @@ -1294,7 +1312,6 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { if let Some(max_vis) = max_vis.get() && import.vis.greater_than(max_vis, self.tcx) { - let def_id = self.local_def_id(id); self.lint_buffer.buffer_lint( UNUSED_IMPORTS, id, @@ -1614,7 +1631,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { if let Some(extern_crate_id) = pub_use_of_private_extern_crate_hack(import.summary(), decl) { let ImportKind::Single { id, .. } = import.kind else { unreachable!() }; - let sugg = self.tcx.source_span(self.local_def_id(extern_crate_id)).shrink_to_lo(); + let sugg = self.tcx.source_span(extern_crate_id).shrink_to_lo(); let diagnostic = crate::errors::PrivateExternCrateReexport { ident, sugg }; return Some(BufferedEarlyLint { lint_id: LintId::of(PUB_USE_OF_PRIVATE_EXTERN_CRATE), @@ -1656,7 +1673,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { pub(crate) fn check_for_redundant_imports(&mut self, import: Import<'ra>) -> bool { // This function is only called for single imports. - let ImportKind::Single { source, target, ref decls, id, .. } = import.kind else { + let ImportKind::Single { source, target, ref decls, id, def_id, .. } = import.kind else { unreachable!() }; @@ -1675,7 +1692,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // Skip if the import is public or was used through non scope-based resolution, // e.g. through a module-relative path. if self.import_use_map.get(&import) == Some(&Used::Other) - || self.effective_visibilities.is_exported(self.local_def_id(id)) + || self.effective_visibilities.is_exported(def_id) { return false; } @@ -1829,23 +1846,23 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let mut children = Vec::new(); let mut ambig_children = Vec::new(); - module.to_module().for_each_child(self, |this, ident, orig_ident_span, _, binding| { + module.to_module().for_each_child(self, |_this, ident, orig_ident_span, _, binding| { let res = binding.res().expect_non_local(); if res != def::Res::Err { let ident = ident.orig(orig_ident_span); let child = |reexport_chain| ModChild { ident, res, vis: binding.vis(), reexport_chain }; if let Some((ambig_binding1, ambig_binding2)) = binding.descent_to_ambiguity() { - let main = child(ambig_binding1.reexport_chain(this)); + let main = child(ambig_binding1.reexport_chain()); let second = ModChild { ident, res: ambig_binding2.res().expect_non_local(), vis: ambig_binding2.vis(), - reexport_chain: ambig_binding2.reexport_chain(this), + reexport_chain: ambig_binding2.reexport_chain(), }; ambig_children.push(AmbigModChild { main, second }) } else { - children.push(child(binding.reexport_chain(this))); + children.push(child(binding.reexport_chain())); } } }); diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 1141ac75ce4c0..68e1dd9291dfe 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -1171,11 +1171,11 @@ impl<'ra> DeclData<'ra> { self.res().macro_kinds() } - fn reexport_chain(self: Decl<'ra>, r: &Resolver<'_, '_>) -> SmallVec<[Reexport; 2]> { + fn reexport_chain(self: Decl<'ra>) -> SmallVec<[Reexport; 2]> { let mut reexport_chain = SmallVec::new(); let mut next_binding = self; while let DeclKind::Import { source_decl, import, .. } = next_binding.kind { - reexport_chain.push(import.simplify(r)); + reexport_chain.push(import.simplify()); next_binding = source_decl; } reexport_chain @@ -1405,8 +1405,8 @@ pub struct Resolver<'ra, 'tcx> { local_macro_def_scopes: FxHashMap> = default::fx_hash_map(), ast_transform_scopes: FxHashMap> = default::fx_hash_map(), unused_macros: FxIndexMap, - /// A map from the macro to all its potentially unused arms. - unused_macro_rules: FxIndexMap>, + /// A map from the macro to all its potentially unused arms and the `LocalDefId` of the macro itself. + unused_macro_rules: FxIndexMap)>, proc_macro_stubs: FxHashSet = default::fx_hash_set(), /// Traces collected during macro resolution and validated when it's complete. single_segment_macro_resolutions: @@ -2155,8 +2155,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ) -> &'tcx [LocalDefId] { let mut import_ids: SmallVec<[LocalDefId; 1]> = smallvec![]; while let DeclKind::Import { import, source_decl, .. } = kind { - if let Some(node_id) = import.id() { - let def_id = self.local_def_id(node_id); + if let Some(def_id) = import.def_id() { self.maybe_unused_trait_imports.insert(def_id); import_ids.push(def_id); } @@ -2292,8 +2291,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { #[inline] fn add_to_glob_map(&mut self, import: Import<'_>, name: Symbol) { - if let ImportKind::Glob { id, .. } = import.kind { - let def_id = self.local_def_id(id); + if let ImportKind::Glob { def_id, .. } = import.kind { self.glob_map.entry(def_id).or_default().insert(name); } } diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index d4349b9d73a77..27390c0394d22 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -339,7 +339,7 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> { } fn record_macro_rule_usage(&mut self, id: NodeId, rule_i: usize) { - if let Some(rules) = self.unused_macro_rules.get_mut(&id) { + if let Some((_, rules)) = self.unused_macro_rules.get_mut(&id) { rules.remove(rule_i); } } @@ -356,11 +356,10 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> { self.unused_macro_rules.swap_remove(&node_id); } - for (&node_id, unused_arms) in self.unused_macro_rules.iter() { + for (&node_id, (def_id, unused_arms)) in self.unused_macro_rules.iter() { if unused_arms.is_empty() { continue; } - let def_id = self.local_def_id(node_id); let m = &self.local_macro_map[&def_id]; let SyntaxExtensionKind::MacroRules(ref m) = m.ext.kind else { continue; diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 695103a249b49..fb4a9cbd8c53f 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -753,6 +753,7 @@ symbols! { d32, dbg_macro, dead_code, + dead_code_pub_in_binary, dealloc, debug, debug_assert_eq_macro, diff --git a/compiler/rustc_type_ir/src/binder.rs b/compiler/rustc_type_ir/src/binder.rs index b55cbc3164d96..de3e04626f823 100644 --- a/compiler/rustc_type_ir/src/binder.rs +++ b/compiler/rustc_type_ir/src/binder.rs @@ -375,13 +375,17 @@ pub struct EarlyBinder { impl Eq for EarlyBinder {} -/// For early binders, you should first call `instantiate` before using any visitors. +// FIXME(154045): Recommended as per https://github.com/rust-lang/rust/issues/154045, this is so sad :(( #[cfg(feature = "nightly")] -impl !TypeFoldable for ty::EarlyBinder {} +macro_rules! generate { ($( $tt:tt )*) => { $( $tt )* } } -/// For early binders, you should first call `instantiate` before using any visitors. #[cfg(feature = "nightly")] -impl !TypeVisitable for ty::EarlyBinder {} +generate!( + /// For early binders, you should first call `instantiate` before using any visitors. + impl !TypeFoldable for ty::EarlyBinder {} + /// For early binders, you should first call `instantiate` before using any visitors. + impl !TypeVisitable for ty::EarlyBinder {} +); impl EarlyBinder { pub fn bind(value: T) -> EarlyBinder { diff --git a/compiler/rustc_type_ir/src/flags.rs b/compiler/rustc_type_ir/src/flags.rs index 50c30f4252703..19c59df0604c3 100644 --- a/compiler/rustc_type_ir/src/flags.rs +++ b/compiler/rustc_type_ir/src/flags.rs @@ -118,6 +118,11 @@ bitflags::bitflags! { /// Does this have any `ReErased` regions? const HAS_RE_ERASED = 1 << 21; + /// Does this have any regions of any kind? + const HAS_REGIONS = TypeFlags::HAS_FREE_REGIONS.bits() + | TypeFlags::HAS_RE_BOUND.bits() + | TypeFlags::HAS_RE_ERASED.bits(); + /// Does this value have parameters/placeholders/inference variables which could be /// replaced later, in a way that would change the results of `impl` specialization? const STILL_FURTHER_SPECIALIZABLE = TypeFlags::HAS_TY_PARAM.bits() @@ -139,7 +144,7 @@ bitflags::bitflags! { /// Does this type have any coroutines in it? const HAS_TY_CORO = 1 << 25; - /// Does this have have a `Bound(BoundVarIndexKind::Canonical, _)`? + /// Does this have a `Bound(BoundVarIndexKind::Canonical, _)`? const HAS_CANONICAL_BOUND = 1 << 26; } } @@ -192,7 +197,7 @@ impl FlagComputation { } fn add_flags(&mut self, flags: TypeFlags) { - self.flags = self.flags | flags; + self.flags |= flags; } /// indicates that `self` refers to something at binding level `binder` diff --git a/compiler/rustc_type_ir/src/fold.rs b/compiler/rustc_type_ir/src/fold.rs index d1a50599e8b9c..0fe68b5256691 100644 --- a/compiler/rustc_type_ir/src/fold.rs +++ b/compiler/rustc_type_ir/src/fold.rs @@ -55,7 +55,7 @@ use tracing::{debug, instrument}; use crate::inherent::*; use crate::visit::{TypeVisitable, TypeVisitableExt as _}; -use crate::{self as ty, BoundVarIndexKind, Interner, TypeFlags}; +use crate::{self as ty, BoundVarIndexKind, Interner}; /// This trait is implemented for every type that can be folded, /// providing the skeleton of the traversal. @@ -121,10 +121,6 @@ pub trait TypeSuperFoldable: TypeFoldable { /// default that does an "identity" fold. Implementations of these methods /// often fall back to a `super_fold_with` method if the primary argument /// doesn't satisfy a particular condition. -/// -/// A blanket implementation of [`FallibleTypeFolder`] will defer to -/// the infallible methods of this trait to ensure that the two APIs -/// are coherent. pub trait TypeFolder: Sized { fn cx(&self) -> I; @@ -437,6 +433,10 @@ impl TypeFolder for Shifter { fn fold_predicate(&mut self, p: I::Predicate) -> I::Predicate { if p.has_vars_bound_at_or_above(self.current_index) { p.super_fold_with(self) } else { p } } + + fn fold_clauses(&mut self, c: I::Clauses) -> I::Clauses { + if c.has_vars_bound_at_or_above(self.current_index) { c.super_fold_with(self) } else { c } + } } pub fn shift_region(cx: I, region: I::Region, amount: u32) -> I::Region { @@ -477,10 +477,10 @@ where /// Folds over the substructure of a type, visiting its component /// types and all regions that occur *free* within it. /// -/// That is, function pointer types and trait object can introduce -/// new bound regions which are not visited by this visitors as +/// That is, function pointer types and trait objects can introduce +/// new bound regions which are not visited by this visitor as /// they are not free; only regions that occur free will be -/// visited by `fld_r`. +/// visited by `fold_region_fn`. pub struct RegionFolder { cx: I, @@ -489,7 +489,7 @@ pub struct RegionFolder { /// binder, it is incremented (via `shift_in`). current_index: ty::DebruijnIndex, - /// Callback invokes for each free region. The `DebruijnIndex` + /// Callback invoked for each free region. The `DebruijnIndex` /// points to the binder *just outside* the ones we have passed /// through. fold_region_fn: F, @@ -539,32 +539,18 @@ where } fn fold_ty(&mut self, t: I::Ty) -> I::Ty { - if t.has_type_flags( - TypeFlags::HAS_FREE_REGIONS | TypeFlags::HAS_RE_BOUND | TypeFlags::HAS_RE_ERASED, - ) { - t.super_fold_with(self) - } else { - t - } + if t.has_regions() { t.super_fold_with(self) } else { t } } fn fold_const(&mut self, ct: I::Const) -> I::Const { - if ct.has_type_flags( - TypeFlags::HAS_FREE_REGIONS | TypeFlags::HAS_RE_BOUND | TypeFlags::HAS_RE_ERASED, - ) { - ct.super_fold_with(self) - } else { - ct - } + if ct.has_regions() { ct.super_fold_with(self) } else { ct } } fn fold_predicate(&mut self, p: I::Predicate) -> I::Predicate { - if p.has_type_flags( - TypeFlags::HAS_FREE_REGIONS | TypeFlags::HAS_RE_BOUND | TypeFlags::HAS_RE_ERASED, - ) { - p.super_fold_with(self) - } else { - p - } + if p.has_regions() { p.super_fold_with(self) } else { p } + } + + fn fold_clauses(&mut self, c: I::Clauses) -> I::Clauses { + if c.has_regions() { c.super_fold_with(self) } else { c } } } diff --git a/compiler/rustc_type_ir/src/ir_print.rs b/compiler/rustc_type_ir/src/ir_print.rs index 5af2bd811bab4..c6ab804d81c9f 100644 --- a/compiler/rustc_type_ir/src/ir_print.rs +++ b/compiler/rustc_type_ir/src/ir_print.rs @@ -1,11 +1,12 @@ use std::fmt; use crate::{ - AliasTerm, AliasTy, Binder, ClosureKind, CoercePredicate, ExistentialProjection, - ExistentialTraitRef, FnSig, HostEffectPredicate, Interner, NormalizesTo, OutlivesPredicate, - PatternKind, Placeholder, ProjectionPredicate, SubtypePredicate, TraitPredicate, TraitRef, - UnevaluatedConst, + AliasTerm, AliasTy, Binder, CoercePredicate, ExistentialProjection, ExistentialTraitRef, FnSig, + HostEffectPredicate, Interner, NormalizesTo, OutlivesPredicate, PatternKind, Placeholder, + ProjectionPredicate, SubtypePredicate, TraitPredicate, TraitRef, }; +#[cfg(feature = "nightly")] +use crate::{ClosureKind, UnevaluatedConst}; pub trait IrPrint { fn print(t: &T, fmt: &mut fmt::Formatter<'_>) -> fmt::Result; diff --git a/compiler/rustc_type_ir/src/visit.rs b/compiler/rustc_type_ir/src/visit.rs index a078b860be774..492c37481298b 100644 --- a/compiler/rustc_type_ir/src/visit.rs +++ b/compiler/rustc_type_ir/src/visit.rs @@ -67,7 +67,7 @@ pub trait TypeVisitable: fmt::Debug { /// each field/element. /// /// For types of interest (such as `Ty`), the implementation of this method - /// that calls a visitor method specifically for that type (such as + /// calls a visitor method specifically for that type (such as /// `V::visit_ty`). This is where control transfers from `TypeVisitable` to /// `TypeVisitor`. fn visit_with>(&self, visitor: &mut V) -> V::Result; @@ -102,8 +102,8 @@ pub trait TypeVisitor: Sized { t.super_visit_with(self) } - // The default region visitor is a no-op because `Region` is non-recursive - // and has no `super_visit_with` method to call. + // `Region` is non-recursive so the default region visitor has no + // `super_visit_with` method to call. fn visit_region(&mut self, r: I::Region) -> Self::Result { if let ty::ReError(guar) = r.kind() { self.visit_error(guar) @@ -251,10 +251,10 @@ pub trait TypeVisitableExt: TypeVisitable { self.has_vars_bound_at_or_above(binder.shifted_in(1)) } - /// Return `true` if this type has regions that are not a part of the type. - /// For example, `for<'a> fn(&'a i32)` return `false`, while `fn(&'a i32)` - /// would return `true`. The latter can occur when traversing through the - /// former. + /// Returns `true` if this type has regions that are not a part of the + /// type. For example, given a `for<'a> fn(&'a i32)` this function returns + /// `false`, while given a `fn(&'a i32)` it returns `true`. The latter can + /// occur when traversing through the former. /// /// See [`HasEscapingVarsVisitor`] for more information. fn has_escaping_bound_vars(&self) -> bool { @@ -285,6 +285,10 @@ pub trait TypeVisitableExt: TypeVisitable { self.has_type_flags(TypeFlags::HAS_PARAM - TypeFlags::HAS_RE_PARAM) } + fn has_regions(&self) -> bool { + self.has_type_flags(TypeFlags::HAS_REGIONS) + } + fn has_infer_regions(&self) -> bool { self.has_type_flags(TypeFlags::HAS_RE_INFER) } @@ -363,13 +367,12 @@ pub trait TypeVisitableExt: TypeVisitable { impl> TypeVisitableExt for T { fn has_type_flags(&self, flags: TypeFlags) -> bool { - let res = - self.visit_with(&mut HasTypeFlagsVisitor { flags }) == ControlFlow::Break(FoundFlags); - res + self.visit_with(&mut HasTypeFlagsVisitor { flags }) == ControlFlow::Break(FoundFlags) } fn has_vars_bound_at_or_above(&self, binder: ty::DebruijnIndex) -> bool { - self.visit_with(&mut HasEscapingVarsVisitor { outer_index: binder }).is_break() + self.visit_with(&mut HasEscapingVarsVisitor { outer_index: binder }) + == ControlFlow::Break(FoundEscapingVars) } fn error_reported(&self) -> Result<(), I::ErrorGuaranteed> { @@ -438,8 +441,7 @@ impl TypeVisitor for HasTypeFlagsVisitor { #[inline] fn visit_ty(&mut self, t: I::Ty) -> Self::Result { // Note: no `super_visit_with` call. - let flags = t.flags(); - if flags.intersects(self.flags) { + if t.flags().intersects(self.flags) { ControlFlow::Break(FoundFlags) } else { ControlFlow::Continue(()) @@ -449,8 +451,7 @@ impl TypeVisitor for HasTypeFlagsVisitor { #[inline] fn visit_region(&mut self, r: I::Region) -> Self::Result { // Note: no `super_visit_with` call, as usual for `Region`. - let flags = r.flags(); - if flags.intersects(self.flags) { + if r.flags().intersects(self.flags) { ControlFlow::Break(FoundFlags) } else { ControlFlow::Continue(()) @@ -571,7 +572,7 @@ impl TypeVisitor for HasEscapingVarsVisitor { // `outer_index`, that means that `ct` contains some content // bound at `outer_index` or above (because // `outer_exclusive_binder` is always 1 higher than the - // content in `t`). Therefore, `t` has some escaping vars. + // content in `ct`). Therefore, `ct` has some escaping vars. if ct.outer_exclusive_binder() > self.outer_index { ControlFlow::Break(FoundEscapingVars) } else { diff --git a/tests/run-make-cargo/rustc-crates-on-stable/rmake.rs b/tests/run-make-cargo/rustc-crates-on-stable/rmake.rs index cbc1f24b8c16e..de29abfd7c3a2 100644 --- a/tests/run-make-cargo/rustc-crates-on-stable/rmake.rs +++ b/tests/run-make-cargo/rustc-crates-on-stable/rmake.rs @@ -10,7 +10,7 @@ fn main() { .env("RUSTC_STAGE", "0") .env("RUSTC", rustc_path()) // We want to disallow all nightly features to simulate a stable build - .env("RUSTFLAGS", "-Zallow-features=") + .env("RUSTFLAGS", "-D warnings -Zallow-features=") .arg("build") .arg("--manifest-path") .arg(source_root().join("Cargo.toml")) diff --git a/tests/ui/lint/dead-code-pub-in-binary/allow-dead-code-transitive.rs b/tests/ui/lint/dead-code-pub-in-binary/allow-dead-code-transitive.rs new file mode 100644 index 0000000000000..a991317bec7c5 --- /dev/null +++ b/tests/ui/lint/dead-code-pub-in-binary/allow-dead-code-transitive.rs @@ -0,0 +1,14 @@ +#![deny(dead_code)] +#![deny(dead_code_pub_in_binary)] + +pub fn g() {} //~ ERROR function `g` is never used + +fn h() {} + +#[allow(dead_code)] +fn f() { + g(); + h(); +} + +fn main() {} diff --git a/tests/ui/lint/dead-code-pub-in-binary/allow-dead-code-transitive.stderr b/tests/ui/lint/dead-code-pub-in-binary/allow-dead-code-transitive.stderr new file mode 100644 index 0000000000000..a45e88646bd70 --- /dev/null +++ b/tests/ui/lint/dead-code-pub-in-binary/allow-dead-code-transitive.stderr @@ -0,0 +1,15 @@ +error: function `g` is never used + --> $DIR/allow-dead-code-transitive.rs:4:8 + | +LL | pub fn g() {} + | ^ + | + = note: in libraries, `pub` items can be used by dependent crates; in binaries, they cannot, so this `pub` item is unused +note: the lint level is defined here + --> $DIR/allow-dead-code-transitive.rs:2:9 + | +LL | #![deny(dead_code_pub_in_binary)] + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/lint/dead-code-pub-in-binary/allow-lint.rs b/tests/ui/lint/dead-code-pub-in-binary/allow-lint.rs new file mode 100644 index 0000000000000..5daf188542d8f --- /dev/null +++ b/tests/ui/lint/dead-code-pub-in-binary/allow-lint.rs @@ -0,0 +1,12 @@ +#![deny(dead_code)] +#![allow(dead_code_pub_in_binary)] + +pub fn unused_pub_fn() { + helper(); +} + +fn helper() {} + +fn unused_priv_fn() {} //~ ERROR function `unused_priv_fn` is never used + +fn main() {} diff --git a/tests/ui/lint/dead-code-pub-in-binary/allow-lint.stderr b/tests/ui/lint/dead-code-pub-in-binary/allow-lint.stderr new file mode 100644 index 0000000000000..ae864e50fcf22 --- /dev/null +++ b/tests/ui/lint/dead-code-pub-in-binary/allow-lint.stderr @@ -0,0 +1,14 @@ +error: function `unused_priv_fn` is never used + --> $DIR/allow-lint.rs:10:4 + | +LL | fn unused_priv_fn() {} + | ^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/allow-lint.rs:1:9 + | +LL | #![deny(dead_code)] + | ^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/lint/dead-code-pub-in-binary/basic.rs b/tests/ui/lint/dead-code-pub-in-binary/basic.rs new file mode 100644 index 0000000000000..6af8bd4448cf5 --- /dev/null +++ b/tests/ui/lint/dead-code-pub-in-binary/basic.rs @@ -0,0 +1,12 @@ +#![deny(dead_code)] +#![deny(dead_code_pub_in_binary)] + +pub fn unused_pub_fn() {} //~ ERROR function `unused_pub_fn` is never used + +pub fn used_pub_fn() {} + +fn unused_priv_fn() {} //~ ERROR function `unused_priv_fn` is never used + +fn main() { + used_pub_fn(); +} diff --git a/tests/ui/lint/dead-code-pub-in-binary/basic.stderr b/tests/ui/lint/dead-code-pub-in-binary/basic.stderr new file mode 100644 index 0000000000000..aa6ac974f2eb5 --- /dev/null +++ b/tests/ui/lint/dead-code-pub-in-binary/basic.stderr @@ -0,0 +1,27 @@ +error: function `unused_pub_fn` is never used + --> $DIR/basic.rs:4:8 + | +LL | pub fn unused_pub_fn() {} + | ^^^^^^^^^^^^^ + | + = note: in libraries, `pub` items can be used by dependent crates; in binaries, they cannot, so this `pub` item is unused +note: the lint level is defined here + --> $DIR/basic.rs:2:9 + | +LL | #![deny(dead_code_pub_in_binary)] + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: function `unused_priv_fn` is never used + --> $DIR/basic.rs:8:4 + | +LL | fn unused_priv_fn() {} + | ^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/basic.rs:1:9 + | +LL | #![deny(dead_code)] + | ^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/tests/ui/lint/dead-code-pub-in-binary/deny-lint.rs b/tests/ui/lint/dead-code-pub-in-binary/deny-lint.rs new file mode 100644 index 0000000000000..336e59e9a99fc --- /dev/null +++ b/tests/ui/lint/dead-code-pub-in-binary/deny-lint.rs @@ -0,0 +1,6 @@ +#![deny(dead_code_pub_in_binary)] +#![allow(dead_code)] + +pub fn unused_pub_fn() {} //~ ERROR function `unused_pub_fn` is never used + +fn main() {} diff --git a/tests/ui/lint/dead-code-pub-in-binary/deny-lint.stderr b/tests/ui/lint/dead-code-pub-in-binary/deny-lint.stderr new file mode 100644 index 0000000000000..679edfe7ba722 --- /dev/null +++ b/tests/ui/lint/dead-code-pub-in-binary/deny-lint.stderr @@ -0,0 +1,15 @@ +error: function `unused_pub_fn` is never used + --> $DIR/deny-lint.rs:4:8 + | +LL | pub fn unused_pub_fn() {} + | ^^^^^^^^^^^^^ + | + = note: in libraries, `pub` items can be used by dependent crates; in binaries, they cannot, so this `pub` item is unused +note: the lint level is defined here + --> $DIR/deny-lint.rs:1:9 + | +LL | #![deny(dead_code_pub_in_binary)] + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/lint/dead-code-pub-in-binary/interaction-allow-dead-code.rs b/tests/ui/lint/dead-code-pub-in-binary/interaction-allow-dead-code.rs new file mode 100644 index 0000000000000..212a84c4d71b9 --- /dev/null +++ b/tests/ui/lint/dead-code-pub-in-binary/interaction-allow-dead-code.rs @@ -0,0 +1,7 @@ +#![allow(dead_code)] +#![deny(dead_code_pub_in_binary)] + +pub fn unused_pub_fn() {} //~ ERROR function `unused_pub_fn` is never used +fn unused_priv_fn() {} + +fn main() {} diff --git a/tests/ui/lint/dead-code-pub-in-binary/interaction-allow-dead-code.stderr b/tests/ui/lint/dead-code-pub-in-binary/interaction-allow-dead-code.stderr new file mode 100644 index 0000000000000..e3ded09678df0 --- /dev/null +++ b/tests/ui/lint/dead-code-pub-in-binary/interaction-allow-dead-code.stderr @@ -0,0 +1,15 @@ +error: function `unused_pub_fn` is never used + --> $DIR/interaction-allow-dead-code.rs:4:8 + | +LL | pub fn unused_pub_fn() {} + | ^^^^^^^^^^^^^ + | + = note: in libraries, `pub` items can be used by dependent crates; in binaries, they cannot, so this `pub` item is unused +note: the lint level is defined here + --> $DIR/interaction-allow-dead-code.rs:2:9 + | +LL | #![deny(dead_code_pub_in_binary)] + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/lint/dead-code-pub-in-binary/interaction-allow-unused-pub.rs b/tests/ui/lint/dead-code-pub-in-binary/interaction-allow-unused-pub.rs new file mode 100644 index 0000000000000..d30df29d966b1 --- /dev/null +++ b/tests/ui/lint/dead-code-pub-in-binary/interaction-allow-unused-pub.rs @@ -0,0 +1,7 @@ +#![allow(dead_code_pub_in_binary)] +#![deny(dead_code)] + +pub fn unused_pub_fn() {} +fn unused_priv_fn() {} //~ ERROR function `unused_priv_fn` is never used + +fn main() {} diff --git a/tests/ui/lint/dead-code-pub-in-binary/interaction-allow-unused-pub.stderr b/tests/ui/lint/dead-code-pub-in-binary/interaction-allow-unused-pub.stderr new file mode 100644 index 0000000000000..3c7924257967d --- /dev/null +++ b/tests/ui/lint/dead-code-pub-in-binary/interaction-allow-unused-pub.stderr @@ -0,0 +1,14 @@ +error: function `unused_priv_fn` is never used + --> $DIR/interaction-allow-unused-pub.rs:5:4 + | +LL | fn unused_priv_fn() {} + | ^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/interaction-allow-unused-pub.rs:2:9 + | +LL | #![deny(dead_code)] + | ^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/lint/dead-code-pub-in-binary/interaction-deny-dead-code-only.rs b/tests/ui/lint/dead-code-pub-in-binary/interaction-deny-dead-code-only.rs new file mode 100644 index 0000000000000..0680ba7d4965d --- /dev/null +++ b/tests/ui/lint/dead-code-pub-in-binary/interaction-deny-dead-code-only.rs @@ -0,0 +1,7 @@ +//@ check-pass + +#![deny(dead_code)] + +pub fn unused_pub_fn() {} + +fn main() {} diff --git a/tests/ui/lint/dead-code-pub-in-binary/library.rs b/tests/ui/lint/dead-code-pub-in-binary/library.rs new file mode 100644 index 0000000000000..1641ace1f0d9f --- /dev/null +++ b/tests/ui/lint/dead-code-pub-in-binary/library.rs @@ -0,0 +1,9 @@ +#![deny(dead_code)] +#![deny(dead_code_pub_in_binary)] +//~^ WARN unused attribute + +#![crate_type = "lib"] + +pub fn unused_pub_fn() {} // Should NOT error because it's a library + +fn unused_priv_fn() {} //~ ERROR function `unused_priv_fn` is never used diff --git a/tests/ui/lint/dead-code-pub-in-binary/library.stderr b/tests/ui/lint/dead-code-pub-in-binary/library.stderr new file mode 100644 index 0000000000000..8ac71b01c6627 --- /dev/null +++ b/tests/ui/lint/dead-code-pub-in-binary/library.stderr @@ -0,0 +1,23 @@ +warning: unused attribute + --> $DIR/library.rs:2:1 + | +LL | #![deny(dead_code_pub_in_binary)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove this attribute + | + = note: the `dead_code_pub_in_binary` lint has no effect in library crates + = note: requested on the command line with `-W unused-attributes` + +error: function `unused_priv_fn` is never used + --> $DIR/library.rs:9:4 + | +LL | fn unused_priv_fn() {} + | ^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/library.rs:1:9 + | +LL | #![deny(dead_code)] + | ^^^^^^^^^ + +error: aborting due to 1 previous error; 1 warning emitted + diff --git a/tests/ui/lint/dead-code-pub-in-binary/no-mangle-items.rs b/tests/ui/lint/dead-code-pub-in-binary/no-mangle-items.rs new file mode 100644 index 0000000000000..60afa99dc0dee --- /dev/null +++ b/tests/ui/lint/dead-code-pub-in-binary/no-mangle-items.rs @@ -0,0 +1,14 @@ +#![deny(dead_code)] +#![deny(dead_code_pub_in_binary)] + +#[no_mangle] +pub fn pub_fn_no_mangle() {} + +pub fn unused_pub_fn() {} //~ ERROR function `unused_pub_fn` is never used + +#[no_mangle] +pub fn unused_priv_fn_no_mangle() {} + +fn unused_priv_fn() {} //~ ERROR function `unused_priv_fn` is never used + +fn main() {} diff --git a/tests/ui/lint/dead-code-pub-in-binary/no-mangle-items.stderr b/tests/ui/lint/dead-code-pub-in-binary/no-mangle-items.stderr new file mode 100644 index 0000000000000..80be42c547652 --- /dev/null +++ b/tests/ui/lint/dead-code-pub-in-binary/no-mangle-items.stderr @@ -0,0 +1,27 @@ +error: function `unused_pub_fn` is never used + --> $DIR/no-mangle-items.rs:7:8 + | +LL | pub fn unused_pub_fn() {} + | ^^^^^^^^^^^^^ + | + = note: in libraries, `pub` items can be used by dependent crates; in binaries, they cannot, so this `pub` item is unused +note: the lint level is defined here + --> $DIR/no-mangle-items.rs:2:9 + | +LL | #![deny(dead_code_pub_in_binary)] + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: function `unused_priv_fn` is never used + --> $DIR/no-mangle-items.rs:12:4 + | +LL | fn unused_priv_fn() {} + | ^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/no-mangle-items.rs:1:9 + | +LL | #![deny(dead_code)] + | ^^^^^^^^^ + +error: aborting due to 2 previous errors +