diff options
Diffstat (limited to 'compiler/rustc_lint')
25 files changed, 1170 insertions, 838 deletions
diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 22e22c833..c4a7f7178 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -72,6 +72,9 @@ lint_builtin_incomplete_features = the feature `{$name}` is incomplete and may n .note = see issue #{$n} <https://github.com/rust-lang/rust/issues/{$n}> for more information .help = consider using `min_{$name}` instead, which is more stable and complete +lint_builtin_internal_features = the feature `{$name}` is internal to the compiler or standard library + .note = using it is strongly discouraged + lint_builtin_keyword_idents = `{$kw}` is a keyword in the {$next} edition .suggestion = you can use a raw identifier to stay compatible @@ -127,8 +130,6 @@ lint_builtin_unexpected_cli_config_name = unexpected `{$name}` as condition name lint_builtin_unexpected_cli_config_value = unexpected condition value `{$value}` for condition name `{$name}` .help = was set with `--cfg` but isn't in the `--check-cfg` expected values -lint_builtin_unnameable_test_items = cannot test inner items - lint_builtin_unpermitted_type_init_label = this code causes undefined behavior when executed lint_builtin_unpermitted_type_init_label_suggestion = help: use `MaybeUninit<T>` instead, and only call `assume_init` after initialization is done @@ -166,8 +167,9 @@ lint_check_name_warning = {$msg} lint_command_line_source = `forbid` lint level was set on command line -lint_confusable_identifier_pair = identifier pair considered confusable between `{$existing_sym}` and `{$sym}` - .label = this is where the previous identifier occurred +lint_confusable_identifier_pair = found both `{$existing_sym}` and `{$sym}` as identifiers, which look alike + .current_use = this identifier can be confused with `{$existing_sym}` + .other_use = other identifier used here lint_cstring_ptr = getting the inner pointer of a temporary `CString` .as_ptr_label = this pointer will be invalid @@ -315,7 +317,11 @@ lint_invalid_nan_comparisons_eq_ne = incorrect NaN comparison, NaN cannot be dir lint_invalid_nan_comparisons_lt_le_gt_ge = incorrect NaN comparison, NaN is not orderable -lint_invalid_reference_casting = casting `&T` to `&mut T` is undefined behavior, even if the reference is unused, consider instead using an `UnsafeCell` +lint_invalid_reference_casting_assign_to_ref = assigning to `&T` is undefined behavior, consider using an `UnsafeCell` + .label = casting happend here + +lint_invalid_reference_casting_borrow_as_mut = casting `&T` to `&mut T` is undefined behavior, even if the reference is unused, consider instead using an `UnsafeCell` + .label = casting happend here lint_lintpass_by_hand = implementing `LintPass` by hand .help = try using `declare_lint_pass!` or `impl_lint_pass!` instead @@ -407,8 +413,8 @@ lint_non_upper_case_global = {$sort} `{$name}` should have an upper case name .label = should have an UPPER_CASE name lint_noop_method_call = call to `.{$method}()` on a reference in this situation does nothing - .label = unnecessary method call - .note = the type `{$receiver_ty}` which `{$method}` is being called on is the same as the type returned from `{$method}`, so the method call does not do anything and can be removed + .suggestion = remove this redundant call + .note = the type `{$orig_ty}` does not implement `{$trait_}`, so calling `{$method}` on `&{$orig_ty}` copies the reference, which does not do anything and can be removed lint_only_cast_u8_to_char = only `u8` can be cast into `char` .suggestion = use a `char` literal instead @@ -447,6 +453,13 @@ lint_path_statement_drop = path statement drops value lint_path_statement_no_effect = path statement with no effect +lint_ptr_null_checks_fn_ptr = function pointers are not nullable, so checking them for null will always return false + .help = wrap the function pointer inside an `Option` and use `Option::is_none` to check for null pointer value + .label = expression has type `{$orig_ty}` + +lint_ptr_null_checks_ref = references are not nullable, so checking them for null will always return false + .label = expression has type `{$orig_ty}` + lint_query_instability = using `{$query}` can result in unstable query results .note = if you believe this case to be fine, allow this lint and add a comment explaining your rationale diff --git a/compiler/rustc_lint/src/array_into_iter.rs b/compiler/rustc_lint/src/array_into_iter.rs index bccb0a94e..d0967ba56 100644 --- a/compiler/rustc_lint/src/array_into_iter.rs +++ b/compiler/rustc_lint/src/array_into_iter.rs @@ -81,7 +81,7 @@ impl<'tcx> LateLintPass<'tcx> for ArrayIntoIter { let adjustments = cx.typeck_results().expr_adjustments(receiver_arg); let Some(Adjustment { kind: Adjust::Borrow(_), target }) = adjustments.last() else { - return + return; }; let types = diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index b821933e9..4b6917fdf 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -24,24 +24,22 @@ use crate::fluent_generated as fluent; use crate::{ errors::BuiltinEllipsisInclusiveRangePatterns, lints::{ - BuiltinAnonymousParams, BuiltinBoxPointers, BuiltinClashingExtern, - BuiltinClashingExternSub, BuiltinConstNoMangle, BuiltinDeprecatedAttrLink, - BuiltinDeprecatedAttrLinkSuggestion, BuiltinDeprecatedAttrUsed, BuiltinDerefNullptr, - BuiltinEllipsisInclusiveRangePatternsLint, BuiltinExplicitOutlives, - BuiltinExplicitOutlivesSuggestion, BuiltinIncompleteFeatures, - BuiltinIncompleteFeaturesHelp, BuiltinIncompleteFeaturesNote, BuiltinKeywordIdents, + BuiltinAnonymousParams, BuiltinBoxPointers, BuiltinConstNoMangle, + BuiltinDeprecatedAttrLink, BuiltinDeprecatedAttrLinkSuggestion, BuiltinDeprecatedAttrUsed, + BuiltinDerefNullptr, BuiltinEllipsisInclusiveRangePatternsLint, BuiltinExplicitOutlives, + BuiltinExplicitOutlivesSuggestion, BuiltinFeatureIssueNote, BuiltinIncompleteFeatures, + BuiltinIncompleteFeaturesHelp, BuiltinInternalFeatures, BuiltinKeywordIdents, BuiltinMissingCopyImpl, BuiltinMissingDebugImpl, BuiltinMissingDoc, BuiltinMutablesTransmutes, BuiltinNoMangleGeneric, BuiltinNonShorthandFieldPatterns, BuiltinSpecialModuleNameUsed, BuiltinTrivialBounds, BuiltinTypeAliasGenericBounds, BuiltinTypeAliasGenericBoundsSuggestion, BuiltinTypeAliasWhereClause, BuiltinUnexpectedCliConfigName, BuiltinUnexpectedCliConfigValue, - BuiltinUngatedAsyncFnTrackCaller, BuiltinUnnameableTestItems, BuiltinUnpermittedTypeInit, + BuiltinUngatedAsyncFnTrackCaller, BuiltinUnpermittedTypeInit, BuiltinUnpermittedTypeInitSub, BuiltinUnreachablePub, BuiltinUnsafe, BuiltinUnstableFeatures, BuiltinUnusedDocComment, BuiltinUnusedDocCommentSub, BuiltinWhileTrue, SuggestChangingAssocTypes, }, - types::{transparent_newtype_field, CItemKind}, - EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext, + EarlyContext, EarlyLintPass, LateContext, LateLintPass, Level, LintContext, }; use hir::IsAsync; use rustc_ast::attr; @@ -49,29 +47,29 @@ use rustc_ast::tokenstream::{TokenStream, TokenTree}; use rustc_ast::visit::{FnCtxt, FnKind}; use rustc_ast::{self as ast, *}; use rustc_ast_pretty::pprust::{self, expr_to_string}; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; -use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_errors::{Applicability, DecorateLint, MultiSpan}; use rustc_feature::{deprecated_attributes, AttributeGate, BuiltinAttribute, GateIssue, Stability}; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::def_id::{DefId, LocalDefId, LocalDefIdSet, CRATE_DEF_ID}; +use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_ID}; use rustc_hir::intravisit::FnKind as HirFnKind; -use rustc_hir::{Body, FnDecl, ForeignItemKind, GenericParamKind, Node, PatKind, PredicateOrigin}; +use rustc_hir::{Body, FnDecl, GenericParamKind, Node, PatKind, PredicateOrigin}; use rustc_middle::lint::in_external_macro; -use rustc_middle::ty::layout::{LayoutError, LayoutOf}; +use rustc_middle::ty::layout::LayoutOf; use rustc_middle::ty::print::with_no_trimmed_paths; -use rustc_middle::ty::subst::GenericArgKind; +use rustc_middle::ty::GenericArgKind; +use rustc_middle::ty::ToPredicate; use rustc_middle::ty::TypeVisitableExt; -use rustc_middle::ty::{self, Instance, Ty, TyCtxt, VariantDef}; +use rustc_middle::ty::{self, Ty, TyCtxt, VariantDef}; use rustc_session::config::ExpectedValues; use rustc_session::lint::{BuiltinLintDiagnostics, FutureIncompatibilityReason}; use rustc_span::edition::Edition; use rustc_span::source_map::Spanned; use rustc_span::symbol::{kw, sym, Ident, Symbol}; use rustc_span::{BytePos, InnerSpan, Span}; -use rustc_target::abi::{Abi, FIRST_VARIANT}; +use rustc_target::abi::Abi; use rustc_trait_selection::infer::{InferCtxtExt, TyCtxtInferExt}; +use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; use rustc_trait_selection::traits::{self, misc::type_allowed_to_implement_copy}; use crate::nonstandard_style::{method_context, MethodLateContext}; @@ -181,9 +179,11 @@ impl<'tcx> LateLintPass<'tcx> for BoxPointers { | hir::ItemKind::TyAlias(..) | hir::ItemKind::Enum(..) | hir::ItemKind::Struct(..) - | hir::ItemKind::Union(..) => { - self.check_heap_type(cx, it.span, cx.tcx.type_of(it.owner_id).subst_identity()) - } + | hir::ItemKind::Union(..) => self.check_heap_type( + cx, + it.span, + cx.tcx.type_of(it.owner_id).instantiate_identity(), + ), _ => (), } @@ -194,7 +194,7 @@ impl<'tcx> LateLintPass<'tcx> for BoxPointers { self.check_heap_type( cx, field.span, - cx.tcx.type_of(field.def_id).subst_identity(), + cx.tcx.type_of(field.def_id).instantiate_identity(), ); } } @@ -459,10 +459,7 @@ declare_lint! { report_in_external_macro } -pub struct MissingDoc { - /// Stack of whether `#[doc(hidden)]` is set at each level which has lint attributes. - doc_hidden_stack: Vec<bool>, -} +pub struct MissingDoc; impl_lint_pass!(MissingDoc => [MISSING_DOCS]); @@ -491,14 +488,6 @@ fn has_doc(attr: &ast::Attribute) -> bool { } impl MissingDoc { - pub fn new() -> MissingDoc { - MissingDoc { doc_hidden_stack: vec![false] } - } - - fn doc_hidden(&self) -> bool { - *self.doc_hidden_stack.last().expect("empty doc_hidden_stack") - } - fn check_missing_docs_attrs( &self, cx: &LateContext<'_>, @@ -512,11 +501,6 @@ impl MissingDoc { return; } - // `#[doc(hidden)]` disables missing_docs check. - if self.doc_hidden() { - return; - } - // Only check publicly-visible items, using the result from the privacy pass. // It's an option so the crate root can also use this function (it doesn't // have a `NodeId`). @@ -539,23 +523,6 @@ impl MissingDoc { } impl<'tcx> LateLintPass<'tcx> for MissingDoc { - #[inline] - fn enter_lint_attrs(&mut self, _cx: &LateContext<'_>, attrs: &[ast::Attribute]) { - let doc_hidden = self.doc_hidden() - || attrs.iter().any(|attr| { - attr.has_name(sym::doc) - && match attr.meta_item_list() { - None => false, - Some(l) => attr::list_contains_name(&l, sym::hidden), - } - }); - self.doc_hidden_stack.push(doc_hidden); - } - - fn exit_lint_attrs(&mut self, _: &LateContext<'_>, _attrs: &[ast::Attribute]) { - self.doc_hidden_stack.pop().expect("empty doc_hidden_stack"); - } - fn check_crate(&mut self, cx: &LateContext<'_>) { self.check_missing_docs_attrs(cx, CRATE_DEF_ID, "the", "crate"); } @@ -591,7 +558,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc { // If the method is an impl for an item with docs_hidden, don't doc. MethodLateContext::PlainImpl => { let parent = cx.tcx.hir().get_parent_item(impl_item.hir_id()); - let impl_ty = cx.tcx.type_of(parent).subst_identity(); + let impl_ty = cx.tcx.type_of(parent).instantiate_identity(); let outerdef = match impl_ty.kind() { ty::Adt(def, _) => Some(def.did()), ty::Foreign(def_id) => Some(*def_id), @@ -700,7 +667,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingCopyImplementations { // and recommending Copy might be a bad idea. for field in def.all_fields() { let did = field.did; - if cx.tcx.type_of(did).subst_identity().is_unsafe_ptr() { + if cx.tcx.type_of(did).instantiate_identity().is_unsafe_ptr() { return; } } @@ -708,6 +675,9 @@ impl<'tcx> LateLintPass<'tcx> for MissingCopyImplementations { if ty.is_copy_modulo_regions(cx.tcx, param_env) { return; } + if type_implements_negative_copy_modulo_regions(cx.tcx, ty, param_env) { + return; + } // We shouldn't recommend implementing `Copy` on stateful things, // such as iterators. @@ -743,6 +713,24 @@ impl<'tcx> LateLintPass<'tcx> for MissingCopyImplementations { } } +/// Check whether a `ty` has a negative `Copy` implementation, ignoring outlives constraints. +fn type_implements_negative_copy_modulo_regions<'tcx>( + tcx: TyCtxt<'tcx>, + ty: Ty<'tcx>, + param_env: ty::ParamEnv<'tcx>, +) -> bool { + let trait_ref = ty::TraitRef::new(tcx, tcx.require_lang_item(hir::LangItem::Copy, None), [ty]); + let pred = ty::TraitPredicate { trait_ref, polarity: ty::ImplPolarity::Negative }; + let obligation = traits::Obligation { + cause: traits::ObligationCause::dummy(), + param_env, + recursion_depth: 0, + predicate: ty::Binder::dummy(pred).to_predicate(tcx), + }; + + tcx.infer_ctxt().build().predicate_must_hold_modulo_regions(&obligation) +} + declare_lint! { /// The `missing_debug_implementations` lint detects missing /// implementations of [`fmt::Debug`] for public types. @@ -776,9 +764,7 @@ declare_lint! { } #[derive(Default)] -pub struct MissingDebugImplementations { - impling_types: Option<LocalDefIdSet>, -} +pub(crate) struct MissingDebugImplementations; impl_lint_pass!(MissingDebugImplementations => [MISSING_DEBUG_IMPLEMENTATIONS]); @@ -793,25 +779,20 @@ impl<'tcx> LateLintPass<'tcx> for MissingDebugImplementations { _ => return, } - let Some(debug) = cx.tcx.get_diagnostic_item(sym::Debug) else { - return - }; - - if self.impling_types.is_none() { - let mut impls = LocalDefIdSet::default(); - cx.tcx.for_each_impl(debug, |d| { - if let Some(ty_def) = cx.tcx.type_of(d).subst_identity().ty_adt_def() { - if let Some(def_id) = ty_def.did().as_local() { - impls.insert(def_id); - } - } - }); - - self.impling_types = Some(impls); - debug!("{:?}", self.impling_types); + // Avoid listing trait impls if the trait is allowed. + let (level, _) = cx.tcx.lint_level_at_node(MISSING_DEBUG_IMPLEMENTATIONS, item.hir_id()); + if level == Level::Allow { + return; } - if !self.impling_types.as_ref().unwrap().contains(&item.owner_id.def_id) { + let Some(debug) = cx.tcx.get_diagnostic_item(sym::Debug) else { return }; + + let has_impl = cx + .tcx + .non_blanket_impls_for_ty(debug, cx.tcx.type_of(item.owner_id).instantiate_identity()) + .next() + .is_some(); + if !has_impl { cx.emit_spanned_lint( MISSING_DEBUG_IMPLEMENTATIONS, item.span, @@ -1259,8 +1240,8 @@ impl<'tcx> LateLintPass<'tcx> for UnstableFeatures { declare_lint! { /// The `ungated_async_fn_track_caller` lint warns when the - /// `#[track_caller]` attribute is used on an async function, method, or - /// closure, without enabling the corresponding unstable feature flag. + /// `#[track_caller]` attribute is used on an async function + /// without enabling the corresponding unstable feature flag. /// /// ### Example /// @@ -1274,13 +1255,13 @@ declare_lint! { /// ### Explanation /// /// The attribute must be used in conjunction with the - /// [`closure_track_caller` feature flag]. Otherwise, the `#[track_caller]` + /// [`async_fn_track_caller` feature flag]. Otherwise, the `#[track_caller]` /// annotation will function as a no-op. /// - /// [`closure_track_caller` feature flag]: https://doc.rust-lang.org/beta/unstable-book/language-features/closure-track-caller.html + /// [`async_fn_track_caller` feature flag]: https://doc.rust-lang.org/beta/unstable-book/language-features/async-fn-track-caller.html UNGATED_ASYNC_FN_TRACK_CALLER, Warn, - "enabling track_caller on an async fn is a no-op unless the closure_track_caller feature is enabled" + "enabling track_caller on an async fn is a no-op unless the async_fn_track_caller feature is enabled" } declare_lint_pass!( @@ -1300,7 +1281,7 @@ impl<'tcx> LateLintPass<'tcx> for UngatedAsyncFnTrackCaller { def_id: LocalDefId, ) { if fn_kind.asyncness() == IsAsync::Async - && !cx.tcx.features().closure_track_caller + && !cx.tcx.features().async_fn_track_caller // Now, check if the function has the `#[track_caller]` attribute && let Some(attr) = cx.tcx.get_attr(def_id, sym::track_caller) { @@ -1458,17 +1439,20 @@ impl TypeAliasBounds { impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds { fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) { - let hir::ItemKind::TyAlias(ty, type_alias_generics) = &item.kind else { - return - }; - if cx.tcx.type_of(item.owner_id.def_id).skip_binder().has_opaque_types() { - // Bounds are respected for `type X = impl Trait` and `type X = (impl Trait, Y);` + let hir::ItemKind::TyAlias(hir_ty, type_alias_generics) = &item.kind else { return }; + + if cx.tcx.features().lazy_type_alias { + // Bounds of lazy type aliases are respected. return; } - if cx.tcx.type_of(item.owner_id).skip_binder().has_inherent_projections() { - // Bounds are respected for `type X = … Type::Inherent …` + + let ty = cx.tcx.type_of(item.owner_id).skip_binder(); + if ty.has_opaque_types() || ty.has_inherent_projections() { + // Bounds of type aliases that contain opaque types or inherent projections are respected. + // E.g: `type X = impl Trait;`, `type X = (impl Trait, Y);`, `type X = Type::Inherent;`. return; } + // There must not be a where clause if type_alias_generics.predicates.is_empty() { return; @@ -1493,7 +1477,7 @@ impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds { if !where_spans.is_empty() { let sub = (!suggested_changing_assoc_types).then(|| { suggested_changing_assoc_types = true; - SuggestChangingAssocTypes { ty } + SuggestChangingAssocTypes { ty: hir_ty } }); cx.emit_spanned_lint( TYPE_ALIAS_BOUNDS, @@ -1509,7 +1493,7 @@ impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds { let suggestion = BuiltinTypeAliasGenericBoundsSuggestion { suggestions: inline_sugg }; let sub = (!suggested_changing_assoc_types).then(|| { suggested_changing_assoc_types = true; - SuggestChangingAssocTypes { ty } + SuggestChangingAssocTypes { ty: hir_ty } }); cx.emit_spanned_lint( TYPE_ALIAS_BOUNDS, @@ -1531,9 +1515,10 @@ declare_lint_pass!( impl<'tcx> LateLintPass<'tcx> for UnusedBrokenConst { fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) { match it.kind { - hir::ItemKind::Const(_, body_id) => { + hir::ItemKind::Const(_, _, body_id) => { let def_id = cx.tcx.hir().body_owner_def_id(body_id).to_def_id(); // trigger the query once for all constants since that will already report the errors + // FIXME(generic_const_items): Does this work properly with generic const items? cx.tcx.ensure().const_eval_poly(def_id); } hir::ItemKind::Static(_, _, body_id) => { @@ -1718,7 +1703,7 @@ impl EarlyLintPass for EllipsisInclusiveRangePatterns { let end = expr_to_string(&end); let replace = match start { Some(start) => format!("&({}..={})", expr_to_string(&start), end), - None => format!("&(..={})", end), + None => format!("&(..={end})"), }; if join.edition() >= Edition::Edition2021 { cx.sess().emit_err(BuiltinEllipsisInclusiveRangePatterns { @@ -1767,82 +1752,6 @@ impl EarlyLintPass for EllipsisInclusiveRangePatterns { } declare_lint! { - /// The `unnameable_test_items` lint detects [`#[test]`][test] functions - /// that are not able to be run by the test harness because they are in a - /// position where they are not nameable. - /// - /// [test]: https://doc.rust-lang.org/reference/attributes/testing.html#the-test-attribute - /// - /// ### Example - /// - /// ```rust,test - /// fn main() { - /// #[test] - /// fn foo() { - /// // This test will not fail because it does not run. - /// assert_eq!(1, 2); - /// } - /// } - /// ``` - /// - /// {{produces}} - /// - /// ### Explanation - /// - /// In order for the test harness to run a test, the test function must be - /// located in a position where it can be accessed from the crate root. - /// This generally means it must be defined in a module, and not anywhere - /// else such as inside another function. The compiler previously allowed - /// this without an error, so a lint was added as an alert that a test is - /// not being used. Whether or not this should be allowed has not yet been - /// decided, see [RFC 2471] and [issue #36629]. - /// - /// [RFC 2471]: https://github.com/rust-lang/rfcs/pull/2471#issuecomment-397414443 - /// [issue #36629]: https://github.com/rust-lang/rust/issues/36629 - UNNAMEABLE_TEST_ITEMS, - Warn, - "detects an item that cannot be named being marked as `#[test_case]`", - report_in_external_macro -} - -pub struct UnnameableTestItems { - boundary: Option<hir::OwnerId>, // Id of the item under which things are not nameable - items_nameable: bool, -} - -impl_lint_pass!(UnnameableTestItems => [UNNAMEABLE_TEST_ITEMS]); - -impl UnnameableTestItems { - pub fn new() -> Self { - Self { boundary: None, items_nameable: true } - } -} - -impl<'tcx> LateLintPass<'tcx> for UnnameableTestItems { - fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) { - if self.items_nameable { - if let hir::ItemKind::Mod(..) = it.kind { - } else { - self.items_nameable = false; - self.boundary = Some(it.owner_id); - } - return; - } - - let attrs = cx.tcx.hir().attrs(it.hir_id()); - if let Some(attr) = attr::find_by_name(attrs, sym::rustc_test_marker) { - cx.emit_spanned_lint(UNNAMEABLE_TEST_ITEMS, attr.span, BuiltinUnnameableTestItems); - } - } - - fn check_item_post(&mut self, _cx: &LateContext<'_>, it: &hir::Item<'_>) { - if !self.items_nameable && self.boundary == Some(it.owner_id) { - self.items_nameable = true; - } - } -} - -declare_lint! { /// The `keyword_idents` lint detects edition keywords being used as an /// identifier. /// @@ -2147,8 +2056,8 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements { match predicate.bounded_ty.kind { hir::TyKind::Path(hir::QPath::Resolved(None, path)) => { let Res::Def(DefKind::TyParam, def_id) = path.res else { - continue; - }; + continue; + }; let index = ty_generics.param_def_id_to_index[&def_id]; ( Self::lifetimes_outliving_type(inferred_outlives, index), @@ -2297,30 +2206,63 @@ declare_lint! { "incomplete features that may function improperly in some or all cases" } +declare_lint! { + /// The `internal_features` lint detects unstable features enabled with + /// the [`feature` attribute] that are internal to the compiler or standard + /// library. + /// + /// [`feature` attribute]: https://doc.rust-lang.org/nightly/unstable-book/ + /// + /// ### Example + /// + /// ```rust + /// #![feature(rustc_attrs)] + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// These features are an implementation detail of the compiler and standard + /// library and are not supposed to be used in user code. + pub INTERNAL_FEATURES, + Warn, + "internal features are not supposed to be used" +} + declare_lint_pass!( /// Check for used feature gates in `INCOMPLETE_FEATURES` in `rustc_feature/src/active.rs`. - IncompleteFeatures => [INCOMPLETE_FEATURES] + IncompleteInternalFeatures => [INCOMPLETE_FEATURES, INTERNAL_FEATURES] ); -impl EarlyLintPass for IncompleteFeatures { +impl EarlyLintPass for IncompleteInternalFeatures { fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) { - let features = cx.sess().features_untracked(); + let features = cx.builder.features(); features .declared_lang_features .iter() .map(|(name, span, _)| (name, span)) .chain(features.declared_lib_features.iter().map(|(name, span)| (name, span))) - .filter(|(&name, _)| features.incomplete(name)) + .filter(|(&name, _)| features.incomplete(name) || features.internal(name)) .for_each(|(&name, &span)| { let note = rustc_feature::find_feature_issue(name, GateIssue::Language) - .map(|n| BuiltinIncompleteFeaturesNote { n }); - let help = - HAS_MIN_FEATURES.contains(&name).then_some(BuiltinIncompleteFeaturesHelp); - cx.emit_spanned_lint( - INCOMPLETE_FEATURES, - span, - BuiltinIncompleteFeatures { name, note, help }, - ); + .map(|n| BuiltinFeatureIssueNote { n }); + + if features.incomplete(name) { + let help = + HAS_MIN_FEATURES.contains(&name).then_some(BuiltinIncompleteFeaturesHelp); + cx.emit_spanned_lint( + INCOMPLETE_FEATURES, + span, + BuiltinIncompleteFeatures { name, note, help }, + ); + } else { + cx.emit_spanned_lint( + INTERNAL_FEATURES, + span, + BuiltinInternalFeatures { name, note }, + ); + } }); } } @@ -2459,12 +2401,12 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { cx: &LateContext<'tcx>, ty: Ty<'tcx>, variant: &VariantDef, - substs: ty::SubstsRef<'tcx>, + args: ty::GenericArgsRef<'tcx>, descr: &str, init: InitKind, ) -> Option<InitError> { let mut field_err = variant.fields.iter().find_map(|field| { - ty_find_init_error(cx, field.ty(cx.tcx, substs), init).map(|mut err| { + ty_find_init_error(cx, field.ty(cx.tcx, args), init).map(|mut err| { if !field.did.is_local() { err } else if err.span.is_none() { @@ -2541,14 +2483,14 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { Some("raw pointers must be initialized".into()) } // Recurse and checks for some compound types. (but not unions) - Adt(adt_def, substs) if !adt_def.is_union() => { + Adt(adt_def, args) if !adt_def.is_union() => { // Handle structs. if adt_def.is_struct() { return variant_find_init_error( cx, ty, adt_def.non_enum_variant(), - substs, + args, "struct field", init, ); @@ -2558,7 +2500,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { let mut potential_variants = adt_def.variants().iter().filter_map(|variant| { let definitely_inhabited = match variant .inhabited_predicate(cx.tcx, *adt_def) - .subst(cx.tcx, substs) + .instantiate(cx.tcx, args) .apply_any_module(cx.tcx, cx.param_env) { // Entirely skip uninhabited variants. @@ -2570,7 +2512,10 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { Some((variant, definitely_inhabited)) }); let Some(first_variant) = potential_variants.next() else { - return Some(InitError::from("enums with no inhabited variants have no valid value").spanned(span)); + return Some( + InitError::from("enums with no inhabited variants have no valid value") + .spanned(span), + ); }; // So we have at least one potentially inhabited variant. Might we have two? let Some(second_variant) = potential_variants.next() else { @@ -2579,7 +2524,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { cx, ty, &first_variant.0, - substs, + args, "field of the only potentially inhabited enum variant", init, ); @@ -2649,381 +2594,6 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { } declare_lint! { - /// The `clashing_extern_declarations` lint detects when an `extern fn` - /// has been declared with the same name but different types. - /// - /// ### Example - /// - /// ```rust - /// mod m { - /// extern "C" { - /// fn foo(); - /// } - /// } - /// - /// extern "C" { - /// fn foo(_: u32); - /// } - /// ``` - /// - /// {{produces}} - /// - /// ### Explanation - /// - /// Because two symbols of the same name cannot be resolved to two - /// different functions at link time, and one function cannot possibly - /// have two types, a clashing extern declaration is almost certainly a - /// mistake. Check to make sure that the `extern` definitions are correct - /// and equivalent, and possibly consider unifying them in one location. - /// - /// This lint does not run between crates because a project may have - /// dependencies which both rely on the same extern function, but declare - /// it in a different (but valid) way. For example, they may both declare - /// an opaque type for one or more of the arguments (which would end up - /// distinct types), or use types that are valid conversions in the - /// language the `extern fn` is defined in. In these cases, the compiler - /// can't say that the clashing declaration is incorrect. - pub CLASHING_EXTERN_DECLARATIONS, - Warn, - "detects when an extern fn has been declared with the same name but different types" -} - -pub struct ClashingExternDeclarations { - /// Map of function symbol name to the first-seen hir id for that symbol name.. If seen_decls - /// contains an entry for key K, it means a symbol with name K has been seen by this lint and - /// the symbol should be reported as a clashing declaration. - // FIXME: Technically, we could just store a &'tcx str here without issue; however, the - // `impl_lint_pass` macro doesn't currently support lints parametric over a lifetime. - seen_decls: FxHashMap<Symbol, hir::OwnerId>, -} - -/// Differentiate between whether the name for an extern decl came from the link_name attribute or -/// just from declaration itself. This is important because we don't want to report clashes on -/// symbol name if they don't actually clash because one or the other links against a symbol with a -/// different name. -enum SymbolName { - /// The name of the symbol + the span of the annotation which introduced the link name. - Link(Symbol, Span), - /// No link name, so just the name of the symbol. - Normal(Symbol), -} - -impl SymbolName { - fn get_name(&self) -> Symbol { - match self { - SymbolName::Link(s, _) | SymbolName::Normal(s) => *s, - } - } -} - -impl ClashingExternDeclarations { - pub(crate) fn new() -> Self { - ClashingExternDeclarations { seen_decls: FxHashMap::default() } - } - - /// Insert a new foreign item into the seen set. If a symbol with the same name already exists - /// for the item, return its HirId without updating the set. - fn insert(&mut self, tcx: TyCtxt<'_>, fi: &hir::ForeignItem<'_>) -> Option<hir::OwnerId> { - let did = fi.owner_id.to_def_id(); - let instance = Instance::new(did, ty::List::identity_for_item(tcx, did)); - let name = Symbol::intern(tcx.symbol_name(instance).name); - if let Some(&existing_id) = self.seen_decls.get(&name) { - // Avoid updating the map with the new entry when we do find a collision. We want to - // make sure we're always pointing to the first definition as the previous declaration. - // This lets us avoid emitting "knock-on" diagnostics. - Some(existing_id) - } else { - self.seen_decls.insert(name, fi.owner_id) - } - } - - /// Get the name of the symbol that's linked against for a given extern declaration. That is, - /// the name specified in a #[link_name = ...] attribute if one was specified, else, just the - /// symbol's name. - fn name_of_extern_decl(tcx: TyCtxt<'_>, fi: &hir::ForeignItem<'_>) -> SymbolName { - if let Some((overridden_link_name, overridden_link_name_span)) = - tcx.codegen_fn_attrs(fi.owner_id).link_name.map(|overridden_link_name| { - // FIXME: Instead of searching through the attributes again to get span - // information, we could have codegen_fn_attrs also give span information back for - // where the attribute was defined. However, until this is found to be a - // bottleneck, this does just fine. - (overridden_link_name, tcx.get_attr(fi.owner_id, sym::link_name).unwrap().span) - }) - { - SymbolName::Link(overridden_link_name, overridden_link_name_span) - } else { - SymbolName::Normal(fi.ident.name) - } - } - - /// Checks whether two types are structurally the same enough that the declarations shouldn't - /// clash. We need this so we don't emit a lint when two modules both declare an extern struct, - /// with the same members (as the declarations shouldn't clash). - fn structurally_same_type<'tcx>( - cx: &LateContext<'tcx>, - a: Ty<'tcx>, - b: Ty<'tcx>, - ckind: CItemKind, - ) -> bool { - fn structurally_same_type_impl<'tcx>( - seen_types: &mut FxHashSet<(Ty<'tcx>, Ty<'tcx>)>, - cx: &LateContext<'tcx>, - a: Ty<'tcx>, - b: Ty<'tcx>, - ckind: CItemKind, - ) -> bool { - debug!("structurally_same_type_impl(cx, a = {:?}, b = {:?})", a, b); - let tcx = cx.tcx; - - // Given a transparent newtype, reach through and grab the inner - // type unless the newtype makes the type non-null. - let non_transparent_ty = |mut ty: Ty<'tcx>| -> Ty<'tcx> { - loop { - if let ty::Adt(def, substs) = *ty.kind() { - let is_transparent = def.repr().transparent(); - let is_non_null = crate::types::nonnull_optimization_guaranteed(tcx, def); - debug!( - "non_transparent_ty({:?}) -- type is transparent? {}, type is non-null? {}", - ty, is_transparent, is_non_null - ); - if is_transparent && !is_non_null { - debug_assert_eq!(def.variants().len(), 1); - let v = &def.variant(FIRST_VARIANT); - // continue with `ty`'s non-ZST field, - // otherwise `ty` is a ZST and we can return - if let Some(field) = transparent_newtype_field(tcx, v) { - ty = field.ty(tcx, substs); - continue; - } - } - } - debug!("non_transparent_ty -> {:?}", ty); - return ty; - } - }; - - let a = non_transparent_ty(a); - let b = non_transparent_ty(b); - - if !seen_types.insert((a, b)) { - // We've encountered a cycle. There's no point going any further -- the types are - // structurally the same. - true - } else if a == b { - // All nominally-same types are structurally same, too. - true - } else { - // Do a full, depth-first comparison between the two. - use rustc_type_ir::sty::TyKind::*; - let a_kind = a.kind(); - let b_kind = b.kind(); - - let compare_layouts = |a, b| -> Result<bool, LayoutError<'tcx>> { - debug!("compare_layouts({:?}, {:?})", a, b); - let a_layout = &cx.layout_of(a)?.layout.abi(); - let b_layout = &cx.layout_of(b)?.layout.abi(); - debug!( - "comparing layouts: {:?} == {:?} = {}", - a_layout, - b_layout, - a_layout == b_layout - ); - Ok(a_layout == b_layout) - }; - - #[allow(rustc::usage_of_ty_tykind)] - let is_primitive_or_pointer = |kind: &ty::TyKind<'_>| { - kind.is_primitive() || matches!(kind, RawPtr(..) | Ref(..)) - }; - - ensure_sufficient_stack(|| { - match (a_kind, b_kind) { - (Adt(a_def, _), Adt(b_def, _)) => { - // We can immediately rule out these types as structurally same if - // their layouts differ. - match compare_layouts(a, b) { - Ok(false) => return false, - _ => (), // otherwise, continue onto the full, fields comparison - } - - // Grab a flattened representation of all fields. - let a_fields = a_def.variants().iter().flat_map(|v| v.fields.iter()); - let b_fields = b_def.variants().iter().flat_map(|v| v.fields.iter()); - - // Perform a structural comparison for each field. - a_fields.eq_by( - b_fields, - |&ty::FieldDef { did: a_did, .. }, - &ty::FieldDef { did: b_did, .. }| { - structurally_same_type_impl( - seen_types, - cx, - tcx.type_of(a_did).subst_identity(), - tcx.type_of(b_did).subst_identity(), - ckind, - ) - }, - ) - } - (Array(a_ty, a_const), Array(b_ty, b_const)) => { - // For arrays, we also check the constness of the type. - a_const.kind() == b_const.kind() - && structurally_same_type_impl(seen_types, cx, *a_ty, *b_ty, ckind) - } - (Slice(a_ty), Slice(b_ty)) => { - structurally_same_type_impl(seen_types, cx, *a_ty, *b_ty, ckind) - } - (RawPtr(a_tymut), RawPtr(b_tymut)) => { - a_tymut.mutbl == b_tymut.mutbl - && structurally_same_type_impl( - seen_types, cx, a_tymut.ty, b_tymut.ty, ckind, - ) - } - (Ref(_a_region, a_ty, a_mut), Ref(_b_region, b_ty, b_mut)) => { - // For structural sameness, we don't need the region to be same. - a_mut == b_mut - && structurally_same_type_impl(seen_types, cx, *a_ty, *b_ty, ckind) - } - (FnDef(..), FnDef(..)) => { - let a_poly_sig = a.fn_sig(tcx); - let b_poly_sig = b.fn_sig(tcx); - - // We don't compare regions, but leaving bound regions around ICEs, so - // we erase them. - let a_sig = tcx.erase_late_bound_regions(a_poly_sig); - let b_sig = tcx.erase_late_bound_regions(b_poly_sig); - - (a_sig.abi, a_sig.unsafety, a_sig.c_variadic) - == (b_sig.abi, b_sig.unsafety, b_sig.c_variadic) - && a_sig.inputs().iter().eq_by(b_sig.inputs().iter(), |a, b| { - structurally_same_type_impl(seen_types, cx, *a, *b, ckind) - }) - && structurally_same_type_impl( - seen_types, - cx, - a_sig.output(), - b_sig.output(), - ckind, - ) - } - (Tuple(a_substs), Tuple(b_substs)) => { - a_substs.iter().eq_by(b_substs.iter(), |a_ty, b_ty| { - structurally_same_type_impl(seen_types, cx, a_ty, b_ty, ckind) - }) - } - // For these, it's not quite as easy to define structural-sameness quite so easily. - // For the purposes of this lint, take the conservative approach and mark them as - // not structurally same. - (Dynamic(..), Dynamic(..)) - | (Error(..), Error(..)) - | (Closure(..), Closure(..)) - | (Generator(..), Generator(..)) - | (GeneratorWitness(..), GeneratorWitness(..)) - | (Alias(ty::Projection, ..), Alias(ty::Projection, ..)) - | (Alias(ty::Inherent, ..), Alias(ty::Inherent, ..)) - | (Alias(ty::Opaque, ..), Alias(ty::Opaque, ..)) => false, - - // These definitely should have been caught above. - (Bool, Bool) | (Char, Char) | (Never, Never) | (Str, Str) => unreachable!(), - - // An Adt and a primitive or pointer type. This can be FFI-safe if non-null - // enum layout optimisation is being applied. - (Adt(..), other_kind) | (other_kind, Adt(..)) - if is_primitive_or_pointer(other_kind) => - { - let (primitive, adt) = - if is_primitive_or_pointer(a.kind()) { (a, b) } else { (b, a) }; - if let Some(ty) = crate::types::repr_nullable_ptr(cx, adt, ckind) { - ty == primitive - } else { - compare_layouts(a, b).unwrap_or(false) - } - } - // Otherwise, just compare the layouts. This may fail to lint for some - // incompatible types, but at the very least, will stop reads into - // uninitialised memory. - _ => compare_layouts(a, b).unwrap_or(false), - } - }) - } - } - let mut seen_types = FxHashSet::default(); - structurally_same_type_impl(&mut seen_types, cx, a, b, ckind) - } -} - -impl_lint_pass!(ClashingExternDeclarations => [CLASHING_EXTERN_DECLARATIONS]); - -impl<'tcx> LateLintPass<'tcx> for ClashingExternDeclarations { - #[instrument(level = "trace", skip(self, cx))] - fn check_foreign_item(&mut self, cx: &LateContext<'tcx>, this_fi: &hir::ForeignItem<'_>) { - if let ForeignItemKind::Fn(..) = this_fi.kind { - let tcx = cx.tcx; - if let Some(existing_did) = self.insert(tcx, this_fi) { - let existing_decl_ty = tcx.type_of(existing_did).skip_binder(); - let this_decl_ty = tcx.type_of(this_fi.owner_id).subst_identity(); - debug!( - "ClashingExternDeclarations: Comparing existing {:?}: {:?} to this {:?}: {:?}", - existing_did, existing_decl_ty, this_fi.owner_id, this_decl_ty - ); - // Check that the declarations match. - if !Self::structurally_same_type( - cx, - existing_decl_ty, - this_decl_ty, - CItemKind::Declaration, - ) { - let orig_fi = tcx.hir().expect_foreign_item(existing_did); - let orig = Self::name_of_extern_decl(tcx, orig_fi); - - // We want to ensure that we use spans for both decls that include where the - // name was defined, whether that was from the link_name attribute or not. - let get_relevant_span = - |fi: &hir::ForeignItem<'_>| match Self::name_of_extern_decl(tcx, fi) { - SymbolName::Normal(_) => fi.span, - SymbolName::Link(_, annot_span) => fi.span.to(annot_span), - }; - - // Finally, emit the diagnostic. - let this = this_fi.ident.name; - let orig = orig.get_name(); - let previous_decl_label = get_relevant_span(orig_fi); - let mismatch_label = get_relevant_span(this_fi); - let sub = BuiltinClashingExternSub { - tcx, - expected: existing_decl_ty, - found: this_decl_ty, - }; - let decorator = if orig == this { - BuiltinClashingExtern::SameName { - this, - orig, - previous_decl_label, - mismatch_label, - sub, - } - } else { - BuiltinClashingExtern::DiffName { - this, - orig, - previous_decl_label, - mismatch_label, - sub, - } - }; - tcx.emit_spanned_lint( - CLASHING_EXTERN_DECLARATIONS, - this_fi.hir_id(), - get_relevant_span(this_fi), - decorator, - ); - } - } - } - } -} - -declare_lint! { /// The `deref_nullptr` lint detects when an null pointer is dereferenced, /// which causes [undefined behavior]. /// @@ -3181,7 +2751,7 @@ impl<'tcx> LateLintPass<'tcx> for NamedAsmLabels { let mut chars = possible_label.chars(); let Some(c) = chars.next() else { // Empty string means a leading ':' in this section, which is not a label - break + break; }; // A label starts with an alphabetic character or . or _ and continues with alphanumeric characters, _, or $ if (c.is_alphabetic() || matches!(c, '.' | '_')) diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs index 3761754f3..f73797415 100644 --- a/compiler/rustc_lint/src/context.rs +++ b/compiler/rustc_lint/src/context.rs @@ -27,6 +27,7 @@ use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::sync; use rustc_errors::{add_elided_lifetime_in_path_suggestion, DiagnosticBuilder, DiagnosticMessage}; use rustc_errors::{Applicability, DecorateLint, MultiSpan, SuggestionStyle}; +use rustc_feature::Features; use rustc_hir as hir; use rustc_hir::def::Res; use rustc_hir::def_id::{CrateNum, DefId}; @@ -35,7 +36,7 @@ use rustc_middle::middle::privacy::EffectiveVisibilities; use rustc_middle::middle::stability; use rustc_middle::ty::layout::{LayoutError, LayoutOfHelpers, TyAndLayout}; use rustc_middle::ty::print::with_no_trimmed_paths; -use rustc_middle::ty::{self, print::Printer, subst::GenericArg, RegisteredTools, Ty, TyCtxt}; +use rustc_middle::ty::{self, print::Printer, GenericArg, RegisteredTools, Ty, TyCtxt}; use rustc_session::config::ExpectedValues; use rustc_session::lint::{BuiltinLintDiagnostics, LintExpectationId}; use rustc_session::lint::{FutureIncompatibleInfo, Level, Lint, LintBuffer, LintId}; @@ -411,7 +412,7 @@ impl LintStore { } let complete_name = if let Some(tool_name) = tool_name { - format!("{}::{}", tool_name, lint_name) + format!("{tool_name}::{lint_name}") } else { lint_name.to_string() }; @@ -424,7 +425,7 @@ impl LintStore { // 1. The tool is currently running, so this lint really doesn't exist. // FIXME: should this handle tools that never register a lint, like rustfmt? debug!("lints={:?}", self.by_name.keys().collect::<Vec<_>>()); - let tool_prefix = format!("{}::", tool_name); + let tool_prefix = format!("{tool_name}::"); return if self.by_name.keys().any(|lint| lint.starts_with(&tool_prefix)) { self.no_lint_suggestion(&complete_name) } else { @@ -445,11 +446,11 @@ impl LintStore { } match self.by_name.get(&complete_name) { Some(Renamed(new_name, _)) => CheckLintNameResult::Warning( - format!("lint `{}` has been renamed to `{}`", complete_name, new_name), + format!("lint `{complete_name}` has been renamed to `{new_name}`"), Some(new_name.to_owned()), ), Some(Removed(reason)) => CheckLintNameResult::Warning( - format!("lint `{}` has been removed: {}", complete_name, reason), + format!("lint `{complete_name}` has been removed: {reason}"), None, ), None => match self.lint_groups.get(&*complete_name) { @@ -503,7 +504,7 @@ impl LintStore { lint_name: &str, tool_name: &str, ) -> CheckLintNameResult<'_> { - let complete_name = format!("{}::{}", tool_name, lint_name); + let complete_name = format!("{tool_name}::{lint_name}"); match self.by_name.get(&complete_name) { None => match self.lint_groups.get(&*complete_name) { // Now we are sure, that this lint exists nowhere @@ -618,12 +619,10 @@ pub trait LintContext: Sized { _ => ("", "s"), }; db.span_label(span, format!( - "this comment contains {}invisible unicode text flow control codepoint{}", - an, - s, + "this comment contains {an}invisible unicode text flow control codepoint{s}", )); for (c, span) in &spans { - db.span_label(*span, format!("{:?}", c)); + db.span_label(*span, format!("{c:?}")); } db.note( "these kind of unicode codepoints change the way text flows on \ @@ -648,7 +647,7 @@ pub trait LintContext: Sized { let opt_colon = if s.trim_start().starts_with("::") { "" } else { "::" }; - (format!("crate{}{}", opt_colon, s), Applicability::MachineApplicable) + (format!("crate{opt_colon}{s}"), Applicability::MachineApplicable) } Err(_) => ("crate::<path>".to_string(), Applicability::HasPlaceholders), }; @@ -704,7 +703,7 @@ pub trait LintContext: Sized { let introduced = if is_imported { "imported" } else { "defined" }; db.span_label( span, - format!("the item `{}` is already {} here", ident, introduced), + format!("the item `{ident}` is already {introduced} here"), ); } } @@ -908,7 +907,7 @@ pub trait LintContext: Sized { BuiltinLintDiagnostics::NamedArgumentUsedPositionally{ position_sp_to_replace, position_sp_for_msg, named_arg_sp, named_arg_name, is_formatting_arg} => { db.span_label(named_arg_sp, "this named argument is referred to by position in formatting string"); if let Some(positional_arg_for_msg) = position_sp_for_msg { - let msg = format!("this formatting argument uses named argument `{}` by position", named_arg_name); + let msg = format!("this formatting argument uses named argument `{named_arg_name}` by position"); db.span_label(positional_arg_for_msg, msg); } @@ -948,14 +947,25 @@ pub trait LintContext: Sized { Applicability::MachineApplicable, ); } + BuiltinLintDiagnostics::AmbiguousGlobImports { diag } => { + rustc_errors::report_ambiguity_error(db, diag); + } BuiltinLintDiagnostics::AmbiguousGlobReexports { name, namespace, first_reexport_span, duplicate_reexport_span } => { - db.span_label(first_reexport_span, format!("the name `{}` in the {} namespace is first re-exported here", name, namespace)); - db.span_label(duplicate_reexport_span, format!("but the name `{}` in the {} namespace is also re-exported here", name, namespace)); + db.span_label(first_reexport_span, format!("the name `{name}` in the {namespace} namespace is first re-exported here")); + db.span_label(duplicate_reexport_span, format!("but the name `{name}` in the {namespace} namespace is also re-exported here")); } BuiltinLintDiagnostics::HiddenGlobReexports { name, namespace, glob_reexport_span, private_item_span } => { - db.span_note(glob_reexport_span, format!("the name `{}` in the {} namespace is supposed to be publicly re-exported here", name, namespace)); + db.span_note(glob_reexport_span, format!("the name `{name}` in the {namespace} namespace is supposed to be publicly re-exported here")); db.span_note(private_item_span, "but the private item here shadows it".to_owned()); } + BuiltinLintDiagnostics::UnusedQualifications { removal_span } => { + db.span_suggestion_verbose( + removal_span, + "remove the unnecessary path segments", + "", + Applicability::MachineApplicable + ); + } } // Rewrap `db`, and pass control to the user. decorate(db) @@ -1062,6 +1072,7 @@ pub trait LintContext: Sized { impl<'a> EarlyContext<'a> { pub(crate) fn new( sess: &'a Session, + features: &'a Features, warn_about_weird_lints: bool, lint_store: &'a LintStore, registered_tools: &'a RegisteredTools, @@ -1070,6 +1081,7 @@ impl<'a> EarlyContext<'a> { EarlyContext { builder: LintLevelsBuilder::new( sess, + features, warn_about_weird_lints, lint_store, registered_tools, @@ -1265,16 +1277,16 @@ impl<'tcx> LateContext<'tcx> { trait_ref: Option<ty::TraitRef<'tcx>>, ) -> Result<Self::Path, Self::Error> { if trait_ref.is_none() { - if let ty::Adt(def, substs) = self_ty.kind() { - return self.print_def_path(def.did(), substs); + if let ty::Adt(def, args) = self_ty.kind() { + return self.print_def_path(def.did(), args); } } // This shouldn't ever be needed, but just in case: with_no_trimmed_paths!({ Ok(vec![match trait_ref { - Some(trait_ref) => Symbol::intern(&format!("{:?}", trait_ref)), - None => Symbol::intern(&format!("<{}>", self_ty)), + Some(trait_ref) => Symbol::intern(&format!("{trait_ref:?}")), + None => Symbol::intern(&format!("<{self_ty}>")), }]) }) } @@ -1298,7 +1310,7 @@ impl<'tcx> LateContext<'tcx> { ))) } None => { - with_no_trimmed_paths!(Symbol::intern(&format!("<impl {}>", self_ty))) + with_no_trimmed_paths!(Symbol::intern(&format!("<impl {self_ty}>"))) } }); diff --git a/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs b/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs index ccf95992a..851c6493d 100644 --- a/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs +++ b/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs @@ -59,7 +59,7 @@ impl<'tcx> LateLintPass<'tcx> for DerefIntoDynSupertrait { // `Deref` is being implemented for `t` if let hir::ItemKind::Impl(impl_) = item.kind && let Some(trait_) = &impl_.of_trait - && let t = cx.tcx.type_of(item.owner_id).subst_identity() + && let t = cx.tcx.type_of(item.owner_id).instantiate_identity() && let opt_did @ Some(did) = trait_.trait_def_id() && opt_did == cx.tcx.lang_items().deref_trait() // `t` is `dyn t_principal` diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs index 9f1f5a26e..211ea8f43 100644 --- a/compiler/rustc_lint/src/early.rs +++ b/compiler/rustc_lint/src/early.rs @@ -20,6 +20,7 @@ use rustc_ast::ptr::P; use rustc_ast::visit::{self as ast_visit, Visitor}; use rustc_ast::{self as ast, walk_list, HasAttrs}; use rustc_data_structures::stack::ensure_sufficient_stack; +use rustc_feature::Features; use rustc_middle::ty::RegisteredTools; use rustc_session::lint::{BufferedEarlyLint, LintBuffer, LintPass}; use rustc_session::Session; @@ -381,6 +382,7 @@ impl<'a> EarlyCheckNode<'a> for (ast::NodeId, &'a [ast::Attribute], &'a [P<ast:: pub fn check_ast_node<'a>( sess: &Session, + features: &Features, pre_expansion: bool, lint_store: &LintStore, registered_tools: &RegisteredTools, @@ -390,6 +392,7 @@ pub fn check_ast_node<'a>( ) { let context = EarlyContext::new( sess, + features, !pre_expansion, lint_store, registered_tools, diff --git a/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs b/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs index 2ce28f3a0..05fe64830 100644 --- a/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs +++ b/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs @@ -51,7 +51,7 @@ fn enforce_mem_discriminant( expr_span: Span, args_span: Span, ) { - let ty_param = cx.typeck_results().node_substs(func_expr.hir_id).type_at(0); + let ty_param = cx.typeck_results().node_args(func_expr.hir_id).type_at(0); if is_non_enum(ty_param) { cx.emit_spanned_lint( ENUM_INTRINSICS_NON_ENUMS, @@ -62,7 +62,7 @@ fn enforce_mem_discriminant( } fn enforce_mem_variant_count(cx: &LateContext<'_>, func_expr: &hir::Expr<'_>, span: Span) { - let ty_param = cx.typeck_results().node_substs(func_expr.hir_id).type_at(0); + let ty_param = cx.typeck_results().node_args(func_expr.hir_id).type_at(0); if is_non_enum(ty_param) { cx.emit_spanned_lint( ENUM_INTRINSICS_NON_ENUMS, diff --git a/compiler/rustc_lint/src/for_loops_over_fallibles.rs b/compiler/rustc_lint/src/for_loops_over_fallibles.rs index 7b58bf03b..c299e3884 100644 --- a/compiler/rustc_lint/src/for_loops_over_fallibles.rs +++ b/compiler/rustc_lint/src/for_loops_over_fallibles.rs @@ -51,7 +51,7 @@ impl<'tcx> LateLintPass<'tcx> for ForLoopsOverFallibles { let ty = cx.typeck_results().expr_ty(arg); - let &ty::Adt(adt, substs) = ty.kind() else { return }; + let &ty::Adt(adt, args) = ty.kind() else { return }; let (article, ty, var) = match adt.did() { did if cx.tcx.is_diagnostic_item(sym::Option, did) => ("an", "Option", "Some"), @@ -66,7 +66,7 @@ impl<'tcx> LateLintPass<'tcx> for ForLoopsOverFallibles { } else { ForLoopsOverFalliblesLoopSub::UseWhileLet { start_span: expr.span.with_hi(pat.span.lo()), end_span: pat.span.between(arg.span), var } } ; - let question_mark = suggest_question_mark(cx, adt, substs, expr.span) + let question_mark = suggest_question_mark(cx, adt, args, expr.span) .then(|| ForLoopsOverFalliblesQuestionMark { suggestion: arg.span.shrink_to_hi() }); let suggestion = ForLoopsOverFalliblesSuggestion { var, @@ -115,11 +115,13 @@ fn extract_iterator_next_call<'tcx>( fn suggest_question_mark<'tcx>( cx: &LateContext<'tcx>, adt: ty::AdtDef<'tcx>, - substs: &List<ty::GenericArg<'tcx>>, + args: &List<ty::GenericArg<'tcx>>, span: Span, ) -> bool { let Some(body_id) = cx.enclosing_body else { return false }; - let Some(into_iterator_did) = cx.tcx.get_diagnostic_item(sym::IntoIterator) else { return false }; + let Some(into_iterator_did) = cx.tcx.get_diagnostic_item(sym::IntoIterator) else { + return false; + }; if !cx.tcx.is_diagnostic_item(sym::Result, adt.did()) { return false; @@ -135,7 +137,7 @@ fn suggest_question_mark<'tcx>( } } - let ty = substs.type_at(0); + let ty = args.type_at(0); let infcx = cx.tcx.infer_ctxt().build(); let ocx = ObligationCtxt::new(&infcx); diff --git a/compiler/rustc_lint/src/foreign_modules.rs b/compiler/rustc_lint/src/foreign_modules.rs new file mode 100644 index 000000000..7b291d558 --- /dev/null +++ b/compiler/rustc_lint/src/foreign_modules.rs @@ -0,0 +1,402 @@ +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::stack::ensure_sufficient_stack; +use rustc_hir as hir; +use rustc_hir::def::DefKind; +use rustc_middle::query::Providers; +use rustc_middle::ty::layout::LayoutError; +use rustc_middle::ty::{self, Instance, Ty, TyCtxt}; +use rustc_session::lint::{lint_array, LintArray}; +use rustc_span::{sym, Span, Symbol}; +use rustc_target::abi::FIRST_VARIANT; + +use crate::lints::{BuiltinClashingExtern, BuiltinClashingExternSub}; +use crate::types; + +pub(crate) fn provide(providers: &mut Providers) { + *providers = Providers { clashing_extern_declarations, ..*providers }; +} + +pub(crate) fn get_lints() -> LintArray { + lint_array!(CLASHING_EXTERN_DECLARATIONS) +} + +fn clashing_extern_declarations(tcx: TyCtxt<'_>, (): ()) { + let mut lint = ClashingExternDeclarations::new(); + for id in tcx.hir_crate_items(()).foreign_items() { + lint.check_foreign_item(tcx, id); + } +} + +declare_lint! { + /// The `clashing_extern_declarations` lint detects when an `extern fn` + /// has been declared with the same name but different types. + /// + /// ### Example + /// + /// ```rust + /// mod m { + /// extern "C" { + /// fn foo(); + /// } + /// } + /// + /// extern "C" { + /// fn foo(_: u32); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Because two symbols of the same name cannot be resolved to two + /// different functions at link time, and one function cannot possibly + /// have two types, a clashing extern declaration is almost certainly a + /// mistake. Check to make sure that the `extern` definitions are correct + /// and equivalent, and possibly consider unifying them in one location. + /// + /// This lint does not run between crates because a project may have + /// dependencies which both rely on the same extern function, but declare + /// it in a different (but valid) way. For example, they may both declare + /// an opaque type for one or more of the arguments (which would end up + /// distinct types), or use types that are valid conversions in the + /// language the `extern fn` is defined in. In these cases, the compiler + /// can't say that the clashing declaration is incorrect. + pub CLASHING_EXTERN_DECLARATIONS, + Warn, + "detects when an extern fn has been declared with the same name but different types" +} + +struct ClashingExternDeclarations { + /// Map of function symbol name to the first-seen hir id for that symbol name.. If seen_decls + /// contains an entry for key K, it means a symbol with name K has been seen by this lint and + /// the symbol should be reported as a clashing declaration. + // FIXME: Technically, we could just store a &'tcx str here without issue; however, the + // `impl_lint_pass` macro doesn't currently support lints parametric over a lifetime. + seen_decls: FxHashMap<Symbol, hir::OwnerId>, +} + +/// Differentiate between whether the name for an extern decl came from the link_name attribute or +/// just from declaration itself. This is important because we don't want to report clashes on +/// symbol name if they don't actually clash because one or the other links against a symbol with a +/// different name. +enum SymbolName { + /// The name of the symbol + the span of the annotation which introduced the link name. + Link(Symbol, Span), + /// No link name, so just the name of the symbol. + Normal(Symbol), +} + +impl SymbolName { + fn get_name(&self) -> Symbol { + match self { + SymbolName::Link(s, _) | SymbolName::Normal(s) => *s, + } + } +} + +impl ClashingExternDeclarations { + pub(crate) fn new() -> Self { + ClashingExternDeclarations { seen_decls: FxHashMap::default() } + } + + /// Insert a new foreign item into the seen set. If a symbol with the same name already exists + /// for the item, return its HirId without updating the set. + fn insert(&mut self, tcx: TyCtxt<'_>, fi: hir::ForeignItemId) -> Option<hir::OwnerId> { + let did = fi.owner_id.to_def_id(); + let instance = Instance::new(did, ty::List::identity_for_item(tcx, did)); + let name = Symbol::intern(tcx.symbol_name(instance).name); + if let Some(&existing_id) = self.seen_decls.get(&name) { + // Avoid updating the map with the new entry when we do find a collision. We want to + // make sure we're always pointing to the first definition as the previous declaration. + // This lets us avoid emitting "knock-on" diagnostics. + Some(existing_id) + } else { + self.seen_decls.insert(name, fi.owner_id) + } + } + + #[instrument(level = "trace", skip(self, tcx))] + fn check_foreign_item<'tcx>(&mut self, tcx: TyCtxt<'tcx>, this_fi: hir::ForeignItemId) { + let DefKind::Fn = tcx.def_kind(this_fi.owner_id) else { return }; + let Some(existing_did) = self.insert(tcx, this_fi) else { return }; + + let existing_decl_ty = tcx.type_of(existing_did).skip_binder(); + let this_decl_ty = tcx.type_of(this_fi.owner_id).instantiate_identity(); + debug!( + "ClashingExternDeclarations: Comparing existing {:?}: {:?} to this {:?}: {:?}", + existing_did, existing_decl_ty, this_fi.owner_id, this_decl_ty + ); + + // Check that the declarations match. + if !structurally_same_type( + tcx, + tcx.param_env(this_fi.owner_id), + existing_decl_ty, + this_decl_ty, + types::CItemKind::Declaration, + ) { + let orig = name_of_extern_decl(tcx, existing_did); + + // Finally, emit the diagnostic. + let this = tcx.item_name(this_fi.owner_id.to_def_id()); + let orig = orig.get_name(); + let previous_decl_label = get_relevant_span(tcx, existing_did); + let mismatch_label = get_relevant_span(tcx, this_fi.owner_id); + let sub = + BuiltinClashingExternSub { tcx, expected: existing_decl_ty, found: this_decl_ty }; + let decorator = if orig == this { + BuiltinClashingExtern::SameName { + this, + orig, + previous_decl_label, + mismatch_label, + sub, + } + } else { + BuiltinClashingExtern::DiffName { + this, + orig, + previous_decl_label, + mismatch_label, + sub, + } + }; + tcx.emit_spanned_lint( + CLASHING_EXTERN_DECLARATIONS, + this_fi.hir_id(), + mismatch_label, + decorator, + ); + } + } +} + +/// Get the name of the symbol that's linked against for a given extern declaration. That is, +/// the name specified in a #[link_name = ...] attribute if one was specified, else, just the +/// symbol's name. +fn name_of_extern_decl(tcx: TyCtxt<'_>, fi: hir::OwnerId) -> SymbolName { + if let Some((overridden_link_name, overridden_link_name_span)) = + tcx.codegen_fn_attrs(fi).link_name.map(|overridden_link_name| { + // FIXME: Instead of searching through the attributes again to get span + // information, we could have codegen_fn_attrs also give span information back for + // where the attribute was defined. However, until this is found to be a + // bottleneck, this does just fine. + (overridden_link_name, tcx.get_attr(fi, sym::link_name).unwrap().span) + }) + { + SymbolName::Link(overridden_link_name, overridden_link_name_span) + } else { + SymbolName::Normal(tcx.item_name(fi.to_def_id())) + } +} + +/// We want to ensure that we use spans for both decls that include where the +/// name was defined, whether that was from the link_name attribute or not. +fn get_relevant_span(tcx: TyCtxt<'_>, fi: hir::OwnerId) -> Span { + match name_of_extern_decl(tcx, fi) { + SymbolName::Normal(_) => tcx.def_span(fi), + SymbolName::Link(_, annot_span) => annot_span, + } +} + +/// Checks whether two types are structurally the same enough that the declarations shouldn't +/// clash. We need this so we don't emit a lint when two modules both declare an extern struct, +/// with the same members (as the declarations shouldn't clash). +fn structurally_same_type<'tcx>( + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + a: Ty<'tcx>, + b: Ty<'tcx>, + ckind: types::CItemKind, +) -> bool { + let mut seen_types = FxHashSet::default(); + structurally_same_type_impl(&mut seen_types, tcx, param_env, a, b, ckind) +} + +fn structurally_same_type_impl<'tcx>( + seen_types: &mut FxHashSet<(Ty<'tcx>, Ty<'tcx>)>, + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + a: Ty<'tcx>, + b: Ty<'tcx>, + ckind: types::CItemKind, +) -> bool { + debug!("structurally_same_type_impl(tcx, a = {:?}, b = {:?})", a, b); + + // Given a transparent newtype, reach through and grab the inner + // type unless the newtype makes the type non-null. + let non_transparent_ty = |mut ty: Ty<'tcx>| -> Ty<'tcx> { + loop { + if let ty::Adt(def, args) = *ty.kind() { + let is_transparent = def.repr().transparent(); + let is_non_null = types::nonnull_optimization_guaranteed(tcx, def); + debug!( + "non_transparent_ty({:?}) -- type is transparent? {}, type is non-null? {}", + ty, is_transparent, is_non_null + ); + if is_transparent && !is_non_null { + debug_assert_eq!(def.variants().len(), 1); + let v = &def.variant(FIRST_VARIANT); + // continue with `ty`'s non-ZST field, + // otherwise `ty` is a ZST and we can return + if let Some(field) = types::transparent_newtype_field(tcx, v) { + ty = field.ty(tcx, args); + continue; + } + } + } + debug!("non_transparent_ty -> {:?}", ty); + return ty; + } + }; + + let a = non_transparent_ty(a); + let b = non_transparent_ty(b); + + if !seen_types.insert((a, b)) { + // We've encountered a cycle. There's no point going any further -- the types are + // structurally the same. + true + } else if a == b { + // All nominally-same types are structurally same, too. + true + } else { + // Do a full, depth-first comparison between the two. + use rustc_type_ir::sty::TyKind::*; + let a_kind = a.kind(); + let b_kind = b.kind(); + + let compare_layouts = |a, b| -> Result<bool, &'tcx LayoutError<'tcx>> { + debug!("compare_layouts({:?}, {:?})", a, b); + let a_layout = &tcx.layout_of(param_env.and(a))?.layout.abi(); + let b_layout = &tcx.layout_of(param_env.and(b))?.layout.abi(); + debug!( + "comparing layouts: {:?} == {:?} = {}", + a_layout, + b_layout, + a_layout == b_layout + ); + Ok(a_layout == b_layout) + }; + + #[allow(rustc::usage_of_ty_tykind)] + let is_primitive_or_pointer = + |kind: &ty::TyKind<'_>| kind.is_primitive() || matches!(kind, RawPtr(..) | Ref(..)); + + ensure_sufficient_stack(|| { + match (a_kind, b_kind) { + (Adt(a_def, _), Adt(b_def, _)) => { + // We can immediately rule out these types as structurally same if + // their layouts differ. + match compare_layouts(a, b) { + Ok(false) => return false, + _ => (), // otherwise, continue onto the full, fields comparison + } + + // Grab a flattened representation of all fields. + let a_fields = a_def.variants().iter().flat_map(|v| v.fields.iter()); + let b_fields = b_def.variants().iter().flat_map(|v| v.fields.iter()); + + // Perform a structural comparison for each field. + a_fields.eq_by( + b_fields, + |&ty::FieldDef { did: a_did, .. }, &ty::FieldDef { did: b_did, .. }| { + structurally_same_type_impl( + seen_types, + tcx, + param_env, + tcx.type_of(a_did).instantiate_identity(), + tcx.type_of(b_did).instantiate_identity(), + ckind, + ) + }, + ) + } + (Array(a_ty, a_const), Array(b_ty, b_const)) => { + // For arrays, we also check the constness of the type. + a_const.kind() == b_const.kind() + && structurally_same_type_impl( + seen_types, tcx, param_env, *a_ty, *b_ty, ckind, + ) + } + (Slice(a_ty), Slice(b_ty)) => { + structurally_same_type_impl(seen_types, tcx, param_env, *a_ty, *b_ty, ckind) + } + (RawPtr(a_tymut), RawPtr(b_tymut)) => { + a_tymut.mutbl == b_tymut.mutbl + && structurally_same_type_impl( + seen_types, tcx, param_env, a_tymut.ty, b_tymut.ty, ckind, + ) + } + (Ref(_a_region, a_ty, a_mut), Ref(_b_region, b_ty, b_mut)) => { + // For structural sameness, we don't need the region to be same. + a_mut == b_mut + && structurally_same_type_impl( + seen_types, tcx, param_env, *a_ty, *b_ty, ckind, + ) + } + (FnDef(..), FnDef(..)) => { + let a_poly_sig = a.fn_sig(tcx); + let b_poly_sig = b.fn_sig(tcx); + + // We don't compare regions, but leaving bound regions around ICEs, so + // we erase them. + let a_sig = tcx.erase_late_bound_regions(a_poly_sig); + let b_sig = tcx.erase_late_bound_regions(b_poly_sig); + + (a_sig.abi, a_sig.unsafety, a_sig.c_variadic) + == (b_sig.abi, b_sig.unsafety, b_sig.c_variadic) + && a_sig.inputs().iter().eq_by(b_sig.inputs().iter(), |a, b| { + structurally_same_type_impl(seen_types, tcx, param_env, *a, *b, ckind) + }) + && structurally_same_type_impl( + seen_types, + tcx, + param_env, + a_sig.output(), + b_sig.output(), + ckind, + ) + } + (Tuple(a_args), Tuple(b_args)) => { + a_args.iter().eq_by(b_args.iter(), |a_ty, b_ty| { + structurally_same_type_impl(seen_types, tcx, param_env, a_ty, b_ty, ckind) + }) + } + // For these, it's not quite as easy to define structural-sameness quite so easily. + // For the purposes of this lint, take the conservative approach and mark them as + // not structurally same. + (Dynamic(..), Dynamic(..)) + | (Error(..), Error(..)) + | (Closure(..), Closure(..)) + | (Generator(..), Generator(..)) + | (GeneratorWitness(..), GeneratorWitness(..)) + | (Alias(ty::Projection, ..), Alias(ty::Projection, ..)) + | (Alias(ty::Inherent, ..), Alias(ty::Inherent, ..)) + | (Alias(ty::Opaque, ..), Alias(ty::Opaque, ..)) => false, + + // These definitely should have been caught above. + (Bool, Bool) | (Char, Char) | (Never, Never) | (Str, Str) => unreachable!(), + + // An Adt and a primitive or pointer type. This can be FFI-safe if non-null + // enum layout optimisation is being applied. + (Adt(..), other_kind) | (other_kind, Adt(..)) + if is_primitive_or_pointer(other_kind) => + { + let (primitive, adt) = + if is_primitive_or_pointer(a.kind()) { (a, b) } else { (b, a) }; + if let Some(ty) = types::repr_nullable_ptr(tcx, param_env, adt, ckind) { + ty == primitive + } else { + compare_layouts(a, b).unwrap_or(false) + } + } + // Otherwise, just compare the layouts. This may fail to lint for some + // incompatible types, but at the very least, will stop reads into + // uninitialised memory. + _ => compare_layouts(a, b).unwrap_or(false), + } + }) + } +} diff --git a/compiler/rustc_lint/src/internal.rs b/compiler/rustc_lint/src/internal.rs index 6f773e04a..4b803621f 100644 --- a/compiler/rustc_lint/src/internal.rs +++ b/compiler/rustc_lint/src/internal.rs @@ -52,20 +52,20 @@ impl LateLintPass<'_> for DefaultHashTypes { } /// Helper function for lints that check for expressions with calls and use typeck results to -/// get the `DefId` and `SubstsRef` of the function. +/// get the `DefId` and `GenericArgsRef` of the function. fn typeck_results_of_method_fn<'tcx>( cx: &LateContext<'tcx>, expr: &Expr<'_>, -) -> Option<(Span, DefId, ty::subst::SubstsRef<'tcx>)> { +) -> Option<(Span, DefId, ty::GenericArgsRef<'tcx>)> { match expr.kind { ExprKind::MethodCall(segment, ..) if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) => { - Some((segment.ident.span, def_id, cx.typeck_results().node_substs(expr.hir_id))) + Some((segment.ident.span, def_id, cx.typeck_results().node_args(expr.hir_id))) }, _ => { match cx.typeck_results().node_type(expr.hir_id).kind() { - &ty::FnDef(def_id, substs) => Some((expr.span, def_id, substs)), + &ty::FnDef(def_id, args) => Some((expr.span, def_id, args)), _ => None, } } @@ -89,8 +89,8 @@ declare_lint_pass!(QueryStability => [POTENTIAL_QUERY_INSTABILITY]); impl LateLintPass<'_> for QueryStability { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - let Some((span, def_id, substs)) = typeck_results_of_method_fn(cx, expr) else { return }; - if let Ok(Some(instance)) = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, substs) { + let Some((span, def_id, args)) = typeck_results_of_method_fn(cx, expr) else { return }; + if let Ok(Some(instance)) = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, args) { let def_id = instance.def_id(); if cx.tcx.has_attr(def_id, sym::rustc_lint_query_instability) { cx.emit_spanned_lint( @@ -232,7 +232,7 @@ fn is_ty_or_ty_ctxt(cx: &LateContext<'_>, path: &Path<'_>) -> Option<String> { } // Only lint on `&Ty` and `&TyCtxt` if it is used outside of a trait. Res::SelfTyAlias { alias_to: did, is_trait_impl: false, .. } => { - if let ty::Adt(adt, substs) = cx.tcx.type_of(did).subst_identity().kind() { + if let ty::Adt(adt, args) = cx.tcx.type_of(did).instantiate_identity().kind() { if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(adt.did()) { // NOTE: This path is currently unreachable as `Ty<'tcx>` is @@ -241,7 +241,7 @@ fn is_ty_or_ty_ctxt(cx: &LateContext<'_>, path: &Path<'_>) -> Option<String> { // // I(@lcnr) still kept this branch in so we don't miss this // if we ever change it in the future. - return Some(format!("{}<{}>", name, substs[0])); + return Some(format!("{}<{}>", name, args[0])); } } } @@ -379,9 +379,9 @@ declare_lint_pass!(Diagnostics => [ UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSID impl LateLintPass<'_> for Diagnostics { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - let Some((span, def_id, substs)) = typeck_results_of_method_fn(cx, expr) else { return }; - debug!(?span, ?def_id, ?substs); - let has_attr = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, substs) + let Some((span, def_id, args)) = typeck_results_of_method_fn(cx, expr) else { return }; + debug!(?span, ?def_id, ?args); + let has_attr = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, args) .ok() .flatten() .is_some_and(|inst| cx.tcx.has_attr(inst.def_id(), sym::rustc_lint_diagnostics)); @@ -414,7 +414,7 @@ impl LateLintPass<'_> for Diagnostics { } let mut found_diagnostic_message = false; - for ty in substs.types() { + for ty in args.types() { debug!(?ty); if let Some(adt_def) = ty.ty_adt_def() && let Some(name) = cx.tcx.get_diagnostic_name(adt_def.did()) && diff --git a/compiler/rustc_lint/src/late.rs b/compiler/rustc_lint/src/late.rs index fb12ded71..73af51d9e 100644 --- a/compiler/rustc_lint/src/late.rs +++ b/compiler/rustc_lint/src/late.rs @@ -17,9 +17,9 @@ use crate::{passes::LateLintPassObject, LateContext, LateLintPass, LintStore}; use rustc_ast as ast; use rustc_data_structures::stack::ensure_sufficient_stack; -use rustc_data_structures::sync::{join, DynSend}; +use rustc_data_structures::sync::join; use rustc_hir as hir; -use rustc_hir::def_id::LocalDefId; +use rustc_hir::def_id::{LocalDefId, LocalModDefId}; use rustc_hir::intravisit as hir_visit; use rustc_hir::intravisit::Visitor; use rustc_middle::hir::nested_filter; @@ -336,9 +336,9 @@ macro_rules! impl_late_lint_pass { crate::late_lint_methods!(impl_late_lint_pass, []); -pub(super) fn late_lint_mod<'tcx, T: LateLintPass<'tcx> + 'tcx>( +pub fn late_lint_mod<'tcx, T: LateLintPass<'tcx> + 'tcx>( tcx: TyCtxt<'tcx>, - module_def_id: LocalDefId, + module_def_id: LocalModDefId, builtin_lints: T, ) { let context = LateContext { @@ -369,13 +369,19 @@ pub(super) fn late_lint_mod<'tcx, T: LateLintPass<'tcx> + 'tcx>( fn late_lint_mod_inner<'tcx, T: LateLintPass<'tcx>>( tcx: TyCtxt<'tcx>, - module_def_id: LocalDefId, + module_def_id: LocalModDefId, context: LateContext<'tcx>, pass: T, ) { let mut cx = LateContextAndPass { context, pass }; let (module, _span, hir_id) = tcx.hir().get_module(module_def_id); + + // There is no module lint that will have the crate itself as an item, so check it here. + if hir_id == hir::CRATE_HIR_ID { + lint_callback!(cx, check_crate,); + } + cx.process_mod(module, hir_id); // Visit the crate attributes @@ -383,10 +389,19 @@ fn late_lint_mod_inner<'tcx, T: LateLintPass<'tcx>>( for attr in tcx.hir().attrs(hir::CRATE_HIR_ID).iter() { cx.visit_attribute(attr) } + lint_callback!(cx, check_crate_post,); } } -fn late_lint_crate<'tcx, T: LateLintPass<'tcx> + 'tcx>(tcx: TyCtxt<'tcx>, builtin_lints: T) { +fn late_lint_crate<'tcx>(tcx: TyCtxt<'tcx>) { + // Note: `passes` is often empty. + let mut passes: Vec<_> = + unerased_lint_store(tcx).late_passes.iter().map(|mk_pass| (mk_pass)(tcx)).collect(); + + if passes.is_empty() { + return; + } + let context = LateContext { tcx, enclosing_body: None, @@ -399,18 +414,8 @@ fn late_lint_crate<'tcx, T: LateLintPass<'tcx> + 'tcx>(tcx: TyCtxt<'tcx>, builti only_module: false, }; - // Note: `passes` is often empty. In that case, it's faster to run - // `builtin_lints` directly rather than bundling it up into the - // `RuntimeCombinedLateLintPass`. - let mut passes: Vec<_> = - unerased_lint_store(tcx).late_passes.iter().map(|mk_pass| (mk_pass)(tcx)).collect(); - if passes.is_empty() { - late_lint_crate_inner(tcx, context, builtin_lints); - } else { - passes.push(Box::new(builtin_lints)); - let pass = RuntimeCombinedLateLintPass { passes: &mut passes[..] }; - late_lint_crate_inner(tcx, context, pass); - } + let pass = RuntimeCombinedLateLintPass { passes: &mut passes[..] }; + late_lint_crate_inner(tcx, context, pass); } fn late_lint_crate_inner<'tcx, T: LateLintPass<'tcx>>( @@ -432,15 +437,12 @@ fn late_lint_crate_inner<'tcx, T: LateLintPass<'tcx>>( } /// Performs lint checking on a crate. -pub fn check_crate<'tcx, T: LateLintPass<'tcx> + 'tcx>( - tcx: TyCtxt<'tcx>, - builtin_lints: impl FnOnce() -> T + Send + DynSend, -) { +pub fn check_crate<'tcx>(tcx: TyCtxt<'tcx>) { join( || { tcx.sess.time("crate_lints", || { // Run whole crate non-incremental lints - late_lint_crate(tcx, builtin_lints()); + late_lint_crate(tcx); }); }, || { diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs index 8376835f5..1f4e5fa4d 100644 --- a/compiler/rustc_lint/src/levels.rs +++ b/compiler/rustc_lint/src/levels.rs @@ -1,4 +1,5 @@ use crate::{ + builtin::MISSING_DOCS, context::{CheckLintNameResult, LintStore}, fluent_generated as fluent, late::unerased_lint_store, @@ -11,6 +12,7 @@ use rustc_ast as ast; use rustc_ast_pretty::pprust; use rustc_data_structures::fx::FxHashMap; use rustc_errors::{DecorateLint, DiagnosticBuilder, DiagnosticMessage, MultiSpan}; +use rustc_feature::Features; use rustc_hir as hir; use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::HirId; @@ -118,6 +120,7 @@ fn lint_expectations(tcx: TyCtxt<'_>, (): ()) -> Vec<(LintExpectationId, LintExp let mut builder = LintLevelsBuilder { sess: tcx.sess, + features: tcx.features(), provider: QueryMapExpectationsWrapper { tcx, cur: hir::CRATE_HIR_ID, @@ -147,6 +150,7 @@ fn shallow_lint_levels_on(tcx: TyCtxt<'_>, owner: hir::OwnerId) -> ShallowLintLe let mut levels = LintLevelsBuilder { sess: tcx.sess, + features: tcx.features(), provider: LintLevelQueryMap { tcx, cur: owner.into(), @@ -265,7 +269,10 @@ impl LintLevelsProvider for QueryMapExpectationsWrapper<'_> { self.specs.lint_level_id_at_node(self.tcx, LintId::of(lint), self.cur) } fn push_expectation(&mut self, id: LintExpectationId, expectation: LintExpectation) { - let LintExpectationId::Stable { attr_id: Some(attr_id), hir_id, attr_index, .. } = id else { bug!("unstable expectation id should already be mapped") }; + let LintExpectationId::Stable { attr_id: Some(attr_id), hir_id, attr_index, .. } = id + else { + bug!("unstable expectation id should already be mapped") + }; let key = LintExpectationId::Unstable { attr_id, lint_index: None }; self.unstable_to_stable_ids.entry(key).or_insert(LintExpectationId::Stable { @@ -431,6 +438,7 @@ impl<'tcx> Visitor<'tcx> for LintLevelsBuilder<'_, QueryMapExpectationsWrapper<' pub struct LintLevelsBuilder<'s, P> { sess: &'s Session, + features: &'s Features, provider: P, warn_about_weird_lints: bool, store: &'s LintStore, @@ -444,12 +452,14 @@ pub(crate) struct BuilderPush { impl<'s> LintLevelsBuilder<'s, TopDown> { pub(crate) fn new( sess: &'s Session, + features: &'s Features, warn_about_weird_lints: bool, store: &'s LintStore, registered_tools: &'s RegisteredTools, ) -> Self { let mut builder = LintLevelsBuilder { sess, + features, provider: TopDown { sets: LintLevelSets::new(), cur: COMMAND_LINE }, warn_about_weird_lints, store, @@ -522,6 +532,10 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { self.sess } + pub(crate) fn features(&self) -> &Features { + self.features + } + pub(crate) fn lint_store(&self) -> &LintStore { self.store } @@ -542,7 +556,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { let Ok(ids) = self.store.find_lints(&lint_name) else { // errors handled in check_lint_name_cmdline above - continue + continue; }; for id in ids { // ForceWarn and Forbid cannot be overridden @@ -664,6 +678,16 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { continue; } + // `#[doc(hidden)]` disables missing_docs check. + if attr.has_name(sym::doc) + && attr + .meta_item_list() + .map_or(false, |l| ast::attr::list_contains_name(&l, sym::hidden)) + { + self.insert(LintId::of(MISSING_DOCS), (Level::Allow, LintLevelSource::Default)); + continue; + } + let level = match Level::from_attr(attr) { None => continue, // This is the only lint level with a `LintExpectationId` that can be created from an attribute @@ -685,9 +709,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { Some(lvl) => lvl, }; - let Some(mut metas) = attr.meta_item_list() else { - continue - }; + let Some(mut metas) = attr.meta_item_list() else { continue }; if metas.is_empty() { // This emits the unused_attributes lint for `#[level()]` @@ -704,7 +726,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { ast::MetaItemKind::NameValue(ref name_value) => { if item.path == sym::reason { if let ast::LitKind::Str(rationale, _) = name_value.kind { - if !self.sess.features_untracked().lint_reasons { + if !self.features.lint_reasons { feature_err( &self.sess.parse_sess, sym::lint_reasons, @@ -944,7 +966,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { ); } } else { - panic!("renamed lint does not exist: {}", new_name); + panic!("renamed lint does not exist: {new_name}"); } } } @@ -956,8 +978,9 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { continue; } - let LintLevelSource::Node { name: lint_attr_name, span: lint_attr_span, .. } = *src else { - continue + let LintLevelSource::Node { name: lint_attr_name, span: lint_attr_span, .. } = *src + else { + continue; }; self.emit_spanned_lint( @@ -976,9 +999,10 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { /// Returns `true` if the lint's feature is enabled. // FIXME only emit this once for each attribute, instead of repeating it 4 times for // pre-expansion lints, post-expansion lints, `shallow_lint_levels_on` and `lint_expectations`. + #[track_caller] fn check_gated_lint(&self, lint_id: LintId, span: Span) -> bool { if let Some(feature) = lint_id.lint.feature_gate { - if !self.sess.features_untracked().enabled(feature) { + if !self.features.enabled(feature) { let lint = builtin::UNKNOWN_LINTS; let (level, src) = self.lint_level(builtin::UNKNOWN_LINTS); struct_lint_level( @@ -1013,6 +1037,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { /// /// [`struct_lint_level`]: rustc_middle::lint::struct_lint_level#decorate-signature #[rustc_lint_diagnostics] + #[track_caller] pub(crate) fn struct_lint( &self, lint: &'static Lint, @@ -1026,6 +1051,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { struct_lint_level(self.sess, lint, level, src, span, msg, decorate) } + #[track_caller] pub fn emit_spanned_lint( &self, lint: &'static Lint, @@ -1038,6 +1064,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { }); } + #[track_caller] pub fn emit_lint(&self, lint: &'static Lint, decorate: impl for<'a> DecorateLint<'a, ()>) { let (level, src) = self.lint_level(lint); struct_lint_level(self.sess, lint, level, src, None, decorate.msg(), |lint| { diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 602071a55..585b10e79 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -40,6 +40,7 @@ #![recursion_limit = "256"] #![deny(rustc::untranslatable_diagnostic)] #![deny(rustc::diagnostic_outside_of_impl)] +#![cfg_attr(not(bootstrap), allow(internal_features))] #[macro_use] extern crate rustc_middle; @@ -58,6 +59,7 @@ mod enum_intrinsics_non_enums; mod errors; mod expect; mod for_loops_over_fallibles; +mod foreign_modules; pub mod hidden_unicode_codepoints; mod internal; mod invalid_from_utf8; @@ -75,6 +77,7 @@ mod noop_method_call; mod opaque_hidden_inferred_bound; mod pass_by_value; mod passes; +mod ptr_nulls; mod redundant_semicolon; mod reference_casting; mod traits; @@ -87,7 +90,7 @@ use rustc_ast as ast; use rustc_errors::{DiagnosticMessage, SubdiagnosticMessage}; use rustc_fluent_macro::fluent_messages; use rustc_hir as hir; -use rustc_hir::def_id::LocalDefId; +use rustc_hir::def_id::{LocalDefId, LocalModDefId}; use rustc_middle::query::Providers; use rustc_middle::ty::TyCtxt; use rustc_session::lint::builtin::{ @@ -115,6 +118,7 @@ use nonstandard_style::*; use noop_method_call::*; use opaque_hidden_inferred_bound::*; use pass_by_value::*; +use ptr_nulls::*; use redundant_semicolon::*; use reference_casting::*; use traits::*; @@ -122,11 +126,11 @@ use types::*; use unused::*; /// Useful for other parts of the compiler / Clippy. -pub use builtin::SoftLints; +pub use builtin::{MissingDoc, SoftLints}; pub use context::{CheckLintNameResult, FindLintError, LintStore}; pub use context::{EarlyContext, LateContext, LintContext}; pub use early::{check_ast_node, EarlyCheckNode}; -pub use late::{check_crate, unerased_lint_store}; +pub use late::{check_crate, late_lint_mod, unerased_lint_store}; pub use passes::{EarlyLintPass, LateLintPass}; pub use rustc_session::lint::Level::{self, *}; pub use rustc_session::lint::{BufferedEarlyLint, FutureIncompatibleInfo, Lint, LintId}; @@ -137,11 +141,12 @@ fluent_messages! { "../messages.ftl" } pub fn provide(providers: &mut Providers) { levels::provide(providers); expect::provide(providers); + foreign_modules::provide(providers); *providers = Providers { lint_mod, ..*providers }; } -fn lint_mod(tcx: TyCtxt<'_>, module_def_id: LocalDefId) { - late::late_lint_mod(tcx, module_def_id, BuiltinCombinedModuleLateLintPass::new()); +fn lint_mod(tcx: TyCtxt<'_>, module_def_id: LocalModDefId) { + late_lint_mod(tcx, module_def_id, BuiltinCombinedModuleLateLintPass::new()); } early_lint_methods!( @@ -171,7 +176,7 @@ early_lint_methods!( WhileTrue: WhileTrue, NonAsciiIdents: NonAsciiIdents, HiddenUnicodeCodepoints: HiddenUnicodeCodepoints, - IncompleteFeatures: IncompleteFeatures, + IncompleteInternalFeatures: IncompleteInternalFeatures, RedundantSemicolons: RedundantSemicolons, UnusedDocComment: UnusedDocComment, UnexpectedCfgs: UnexpectedCfgs, @@ -179,27 +184,6 @@ early_lint_methods!( ] ); -// FIXME: Make a separate lint type which does not require typeck tables. - -late_lint_methods!( - declare_combined_late_lint_pass, - [ - pub BuiltinCombinedLateLintPass, - [ - // Tracks state across modules - UnnameableTestItems: UnnameableTestItems::new(), - // Tracks attributes of parents - MissingDoc: MissingDoc::new(), - // Builds a global list of all impls of `Debug`. - // FIXME: Turn the computation of types which implement Debug into a query - // and change this to a module lint pass - MissingDebugImplementations: MissingDebugImplementations::default(), - // Keeps a global list of foreign declarations. - ClashingExternDeclarations: ClashingExternDeclarations::new(), - ] - ] -); - late_lint_methods!( declare_combined_late_lint_pass, [ @@ -216,7 +200,7 @@ late_lint_methods!( BoxPointers: BoxPointers, PathStatements: PathStatements, LetUnderscore: LetUnderscore, - InvalidReferenceCasting: InvalidReferenceCasting, + InvalidReferenceCasting: InvalidReferenceCasting::default(), // Depends on referenced function signatures in expressions UnusedResults: UnusedResults, NonUpperCaseGlobals: NonUpperCaseGlobals, @@ -225,6 +209,7 @@ late_lint_methods!( // Depends on types used in type definitions MissingCopyImplementations: MissingCopyImplementations, // Depends on referenced function signatures in expressions + PtrNullChecks: PtrNullChecks, MutableTransmutes: MutableTransmutes, TypeAliasBounds: TypeAliasBounds, TrivialConstraints: TrivialConstraints, @@ -251,6 +236,8 @@ late_lint_methods!( OpaqueHiddenInferredBound: OpaqueHiddenInferredBound, MultipleSupertraitUpcastable: MultipleSupertraitUpcastable, MapUnitFn: MapUnitFn, + MissingDebugImplementations: MissingDebugImplementations, + MissingDoc: MissingDoc, ] ] ); @@ -279,7 +266,7 @@ fn register_builtins(store: &mut LintStore) { store.register_lints(&BuiltinCombinedPreExpansionLintPass::get_lints()); store.register_lints(&BuiltinCombinedEarlyLintPass::get_lints()); store.register_lints(&BuiltinCombinedModuleLateLintPass::get_lints()); - store.register_lints(&BuiltinCombinedLateLintPass::get_lints()); + store.register_lints(&foreign_modules::get_lints()); add_lint_group!( "nonstandard_style", @@ -519,20 +506,20 @@ fn register_internals(store: &mut LintStore) { store.register_lints(&LintPassImpl::get_lints()); store.register_early_pass(|| Box::new(LintPassImpl)); store.register_lints(&DefaultHashTypes::get_lints()); - store.register_late_pass(|_| Box::new(DefaultHashTypes)); + store.register_late_mod_pass(|_| Box::new(DefaultHashTypes)); store.register_lints(&QueryStability::get_lints()); - store.register_late_pass(|_| Box::new(QueryStability)); + store.register_late_mod_pass(|_| Box::new(QueryStability)); store.register_lints(&ExistingDocKeyword::get_lints()); - store.register_late_pass(|_| Box::new(ExistingDocKeyword)); + store.register_late_mod_pass(|_| Box::new(ExistingDocKeyword)); store.register_lints(&TyTyKind::get_lints()); - store.register_late_pass(|_| Box::new(TyTyKind)); + store.register_late_mod_pass(|_| Box::new(TyTyKind)); store.register_lints(&Diagnostics::get_lints()); store.register_early_pass(|| Box::new(Diagnostics)); - store.register_late_pass(|_| Box::new(Diagnostics)); + store.register_late_mod_pass(|_| Box::new(Diagnostics)); store.register_lints(&BadOptAccess::get_lints()); - store.register_late_pass(|_| Box::new(BadOptAccess)); + store.register_late_mod_pass(|_| Box::new(BadOptAccess)); store.register_lints(&PassByValue::get_lints()); - store.register_late_pass(|_| Box::new(PassByValue)); + store.register_late_mod_pass(|_| Box::new(PassByValue)); // FIXME(davidtwco): deliberately do not include `UNTRANSLATABLE_DIAGNOSTIC` and // `DIAGNOSTIC_OUTSIDE_OF_IMPL` here because `-Wrustc::internal` is provided to every crate and // these lints will trigger all of the time - change this once migration to diagnostic structs diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 9260237fb..25982a458 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -250,7 +250,7 @@ impl<'a> DecorateLint<'a, ()> for BuiltinUngatedAsyncFnTrackCaller<'_> { rustc_session::parse::add_feature_diagnostics( diag, &self.parse_sess, - sym::closure_track_caller, + sym::async_fn_track_caller, ); diag } @@ -371,10 +371,6 @@ pub enum BuiltinEllipsisInclusiveRangePatternsLint { } #[derive(LintDiagnostic)] -#[diag(lint_builtin_unnameable_test_items)] -pub struct BuiltinUnnameableTestItems; - -#[derive(LintDiagnostic)] #[diag(lint_builtin_keyword_idents)] pub struct BuiltinKeywordIdents { pub kw: Ident, @@ -405,18 +401,27 @@ pub struct BuiltinExplicitOutlivesSuggestion { pub struct BuiltinIncompleteFeatures { pub name: Symbol, #[subdiagnostic] - pub note: Option<BuiltinIncompleteFeaturesNote>, + pub note: Option<BuiltinFeatureIssueNote>, #[subdiagnostic] pub help: Option<BuiltinIncompleteFeaturesHelp>, } +#[derive(LintDiagnostic)] +#[diag(lint_builtin_internal_features)] +#[note] +pub struct BuiltinInternalFeatures { + pub name: Symbol, + #[subdiagnostic] + pub note: Option<BuiltinFeatureIssueNote>, +} + #[derive(Subdiagnostic)] #[help(lint_help)] pub struct BuiltinIncompleteFeaturesHelp; #[derive(Subdiagnostic)] #[note(lint_note)] -pub struct BuiltinIncompleteFeaturesNote { +pub struct BuiltinFeatureIssueNote { pub n: NonZeroU32, } @@ -613,6 +618,24 @@ pub struct ExpectationNote { pub rationale: Symbol, } +// ptr_nulls.rs +#[derive(LintDiagnostic)] +pub enum PtrNullChecksDiag<'a> { + #[diag(lint_ptr_null_checks_fn_ptr)] + #[help(lint_help)] + FnPtr { + orig_ty: Ty<'a>, + #[label] + label: Span, + }, + #[diag(lint_ptr_null_checks_ref)] + Ref { + orig_ty: Ty<'a>, + #[label] + label: Span, + }, +} + // for_loops_over_fallibles.rs #[derive(LintDiagnostic)] #[diag(lint_for_loops_over_fallibles)] @@ -739,8 +762,18 @@ pub enum InvalidFromUtf8Diag { // reference_casting.rs #[derive(LintDiagnostic)] -#[diag(lint_invalid_reference_casting)] -pub struct InvalidReferenceCastingDiag; +pub enum InvalidReferenceCastingDiag { + #[diag(lint_invalid_reference_casting_borrow_as_mut)] + BorrowAsMut { + #[label] + orig_cast: Option<Span>, + }, + #[diag(lint_invalid_reference_casting_assign_to_ref)] + AssignToRef { + #[label] + orig_cast: Option<Span>, + }, +} // hidden_unicode_codepoints.rs #[derive(LintDiagnostic)] @@ -770,7 +803,7 @@ impl AddToDiagnostic for HiddenUnicodeCodepointsDiagLabels { ) -> rustc_errors::SubdiagnosticMessage, { for (c, span) in self.spans { - diag.span_label(span, format!("{:?}", c)); + diag.span_label(span, format!("{c:?}")); } } } @@ -802,7 +835,7 @@ impl AddToDiagnostic for HiddenUnicodeCodepointsDiagSub { spans .into_iter() .map(|(c, span)| { - let c = format!("{:?}", c); + let c = format!("{c:?}"); (span, c[1..c.len() - 1].to_string()) }) .collect(), @@ -817,7 +850,7 @@ impl AddToDiagnostic for HiddenUnicodeCodepointsDiagSub { "escaped", spans .into_iter() - .map(|(c, _)| format!("{:?}", c)) + .map(|(c, _)| format!("{c:?}")) .collect::<Vec<String>>() .join(", "), ); @@ -1050,8 +1083,10 @@ pub struct IdentifierUncommonCodepoints; pub struct ConfusableIdentifierPair { pub existing_sym: Symbol, pub sym: Symbol, - #[label] + #[label(lint_other_use)] pub label: Span, + #[label(lint_current_use)] + pub main_label: Span, } #[derive(LintDiagnostic)] @@ -1225,8 +1260,9 @@ pub enum NonUpperCaseGlobalSub { #[note] pub struct NoopMethodCallDiag<'a> { pub method: Symbol, - pub receiver_ty: Ty<'a>, - #[label] + pub orig_ty: Ty<'a>, + pub trait_: Symbol, + #[suggestion(code = "", applicability = "machine-applicable")] pub label: Span, } @@ -1460,7 +1496,7 @@ pub enum InvalidNanComparisons { #[diag(lint_invalid_nan_comparisons_eq_ne)] EqNe { #[subdiagnostic] - suggestion: InvalidNanComparisonsSuggestion, + suggestion: Option<InvalidNanComparisonsSuggestion>, }, #[diag(lint_invalid_nan_comparisons_lt_le_gt_ge)] LtLeGtGe, diff --git a/compiler/rustc_lint/src/methods.rs b/compiler/rustc_lint/src/methods.rs index 4c25d94a1..5b63b19c5 100644 --- a/compiler/rustc_lint/src/methods.rs +++ b/compiler/rustc_lint/src/methods.rs @@ -53,9 +53,9 @@ fn lint_cstring_as_ptr( unwrap: &rustc_hir::Expr<'_>, ) { let source_type = cx.typeck_results().expr_ty(source); - if let ty::Adt(def, substs) = source_type.kind() { + if let ty::Adt(def, args) = source_type.kind() { if cx.tcx.is_diagnostic_item(sym::Result, def.did()) { - if let ty::Adt(adt, _) = substs.type_at(0).kind() { + if let ty::Adt(adt, _) = args.type_at(0).kind() { if cx.tcx.is_diagnostic_item(sym::cstring_type, adt.did()) { cx.emit_spanned_lint( TEMPORARY_CSTRING_AS_PTR, diff --git a/compiler/rustc_lint/src/multiple_supertrait_upcastable.rs b/compiler/rustc_lint/src/multiple_supertrait_upcastable.rs index 53fe0ceb2..84558ee1f 100644 --- a/compiler/rustc_lint/src/multiple_supertrait_upcastable.rs +++ b/compiler/rustc_lint/src/multiple_supertrait_upcastable.rs @@ -10,6 +10,7 @@ declare_lint! { /// ### Example /// /// ```rust + /// #![feature(multiple_supertrait_upcastable)] /// trait A {} /// trait B {} /// diff --git a/compiler/rustc_lint/src/non_ascii_idents.rs b/compiler/rustc_lint/src/non_ascii_idents.rs index 4af879b4e..62bb8c2c6 100644 --- a/compiler/rustc_lint/src/non_ascii_idents.rs +++ b/compiler/rustc_lint/src/non_ascii_idents.rs @@ -222,6 +222,7 @@ impl EarlyLintPass for NonAsciiIdents { existing_sym: *existing_symbol, sym: symbol, label: *existing_span, + main_label: sp, }, ); } diff --git a/compiler/rustc_lint/src/noop_method_call.rs b/compiler/rustc_lint/src/noop_method_call.rs index d56c35bb6..bc0b9d6d8 100644 --- a/compiler/rustc_lint/src/noop_method_call.rs +++ b/compiler/rustc_lint/src/noop_method_call.rs @@ -18,7 +18,6 @@ declare_lint! { /// /// ```rust /// # #![allow(unused)] - /// #![warn(noop_method_call)] /// struct Foo; /// let foo = &Foo; /// let clone: &Foo = foo.clone(); @@ -34,7 +33,7 @@ declare_lint! { /// calling `clone` on a `&T` where `T` does not implement clone, actually doesn't do anything /// as references are copy. This lint detects these calls and warns the user about them. pub NOOP_METHOD_CALL, - Allow, + Warn, "detects the use of well-known noop methods" } @@ -79,28 +78,24 @@ impl<'tcx> LateLintPass<'tcx> for NoopMethodCall { // We only care about method calls corresponding to the `Clone`, `Deref` and `Borrow` // traits and ignore any other method call. - let Some((DefKind::AssocFn, did)) = - cx.typeck_results().type_dependent_def(expr.hir_id) + let Some((DefKind::AssocFn, did)) = cx.typeck_results().type_dependent_def(expr.hir_id) else { return; }; let Some(trait_id) = cx.tcx.trait_of_item(did) else { return }; - if !matches!( - cx.tcx.get_diagnostic_name(trait_id), - Some(sym::Borrow | sym::Clone | sym::Deref) - ) { + let Some(trait_) = cx.tcx.get_diagnostic_name(trait_id) else { return }; + + if !matches!(trait_, sym::Borrow | sym::Clone | sym::Deref) { return; }; - let substs = cx + let args = cx .tcx - .normalize_erasing_regions(cx.param_env, cx.typeck_results().node_substs(expr.hir_id)); + .normalize_erasing_regions(cx.param_env, cx.typeck_results().node_args(expr.hir_id)); // Resolve the trait method instance. - let Ok(Some(i)) = ty::Instance::resolve(cx.tcx, cx.param_env, did, substs) else { - return - }; + let Ok(Some(i)) = ty::Instance::resolve(cx.tcx, cx.param_env, did, args) else { return }; // (Re)check that it implements the noop diagnostic. let Some(name) = cx.tcx.get_diagnostic_name(i.def_id()) else { return }; @@ -117,11 +112,13 @@ impl<'tcx> LateLintPass<'tcx> for NoopMethodCall { let expr_span = expr.span; let span = expr_span.with_lo(receiver.span.hi()); + let orig_ty = expr_ty.peel_refs(); + if receiver_ty == expr_ty { cx.emit_spanned_lint( NOOP_METHOD_CALL, span, - NoopMethodCallDiag { method: call.ident.name, receiver_ty, label: span }, + NoopMethodCallDiag { method: call.ident.name, orig_ty, trait_, label: span }, ); } else { match name { diff --git a/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs b/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs index 09a1651c2..79b0b32be 100644 --- a/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs +++ b/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs @@ -68,13 +68,17 @@ declare_lint_pass!(OpaqueHiddenInferredBound => [OPAQUE_HIDDEN_INFERRED_BOUND]); impl<'tcx> LateLintPass<'tcx> for OpaqueHiddenInferredBound { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { - let hir::ItemKind::OpaqueTy(opaque) = &item.kind else { return; }; + let hir::ItemKind::OpaqueTy(opaque) = &item.kind else { + return; + }; let def_id = item.owner_id.def_id.to_def_id(); let infcx = &cx.tcx.infer_ctxt().build(); // For every projection predicate in the opaque type's explicit bounds, // check that the type that we're assigning actually satisfies the bounds // of the associated type. - for (pred, pred_span) in cx.tcx.explicit_item_bounds(def_id).subst_identity_iter_copied() { + for (pred, pred_span) in + cx.tcx.explicit_item_bounds(def_id).instantiate_identity_iter_copied() + { // Liberate bound regions in the predicate since we // don't actually care about lifetimes in this check. let predicate = cx.tcx.liberate_late_bound_regions(def_id, pred.kind()); @@ -97,7 +101,7 @@ impl<'tcx> LateLintPass<'tcx> for OpaqueHiddenInferredBound { } let proj_ty = - Ty::new_projection(cx.tcx, proj.projection_ty.def_id, proj.projection_ty.substs); + Ty::new_projection(cx.tcx, proj.projection_ty.def_id, proj.projection_ty.args); // For every instance of the projection type in the bounds, // replace them with the term we're assigning to the associated // type in our opaque type. @@ -113,10 +117,15 @@ impl<'tcx> LateLintPass<'tcx> for OpaqueHiddenInferredBound { for (assoc_pred, assoc_pred_span) in cx .tcx .explicit_item_bounds(proj.projection_ty.def_id) - .subst_iter_copied(cx.tcx, &proj.projection_ty.substs) + .iter_instantiated_copied(cx.tcx, &proj.projection_ty.args) { let assoc_pred = assoc_pred.fold_with(proj_replacer); - let Ok(assoc_pred) = traits::fully_normalize(infcx, traits::ObligationCause::dummy(), cx.param_env, assoc_pred) else { + let Ok(assoc_pred) = traits::fully_normalize( + infcx, + traits::ObligationCause::dummy(), + cx.param_env, + assoc_pred, + ) else { continue; }; // If that predicate doesn't hold modulo regions (but passed during type-check), @@ -147,7 +156,7 @@ impl<'tcx> LateLintPass<'tcx> for OpaqueHiddenInferredBound { ty: Ty::new_opaque( cx.tcx, def_id, - ty::InternalSubsts::identity_for_item(cx.tcx, def_id), + ty::GenericArgs::identity_for_item(cx.tcx, def_id), ), proj_ty: proj_term, assoc_pred_span, diff --git a/compiler/rustc_lint/src/pass_by_value.rs b/compiler/rustc_lint/src/pass_by_value.rs index 2bb2a3aab..cad2cd7fa 100644 --- a/compiler/rustc_lint/src/pass_by_value.rs +++ b/compiler/rustc_lint/src/pass_by_value.rs @@ -50,9 +50,9 @@ fn path_for_pass_by_value(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> Option<Stri return Some(format!("{}{}", name, gen_args(cx, path_segment))); } Res::SelfTyAlias { alias_to: did, is_trait_impl: false, .. } => { - if let ty::Adt(adt, substs) = cx.tcx.type_of(did).subst_identity().kind() { + if let ty::Adt(adt, args) = cx.tcx.type_of(did).instantiate_identity().kind() { if cx.tcx.has_attr(adt.did(), sym::rustc_pass_by_value) { - return Some(cx.tcx.def_path_str_with_substs(adt.did(), substs)); + return Some(cx.tcx.def_path_str_with_args(adt.did(), args)); } } } diff --git a/compiler/rustc_lint/src/ptr_nulls.rs b/compiler/rustc_lint/src/ptr_nulls.rs new file mode 100644 index 000000000..02aff9103 --- /dev/null +++ b/compiler/rustc_lint/src/ptr_nulls.rs @@ -0,0 +1,146 @@ +use crate::{lints::PtrNullChecksDiag, LateContext, LateLintPass, LintContext}; +use rustc_ast::LitKind; +use rustc_hir::{BinOpKind, Expr, ExprKind, TyKind}; +use rustc_session::{declare_lint, declare_lint_pass}; +use rustc_span::sym; + +declare_lint! { + /// The `useless_ptr_null_checks` lint checks for useless null checks against pointers + /// obtained from non-null types. + /// + /// ### Example + /// + /// ```rust + /// # fn test() {} + /// let fn_ptr: fn() = /* somehow obtained nullable function pointer */ + /// # test; + /// + /// if (fn_ptr as *const ()).is_null() { /* ... */ } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Function pointers and references are assumed to be non-null, checking them for null + /// will always return false. + USELESS_PTR_NULL_CHECKS, + Warn, + "useless checking of non-null-typed pointer" +} + +declare_lint_pass!(PtrNullChecks => [USELESS_PTR_NULL_CHECKS]); + +/// This function detects and returns the original expression from a series of consecutive casts, +/// ie. `(my_fn as *const _ as *mut _).cast_mut()` would return the expression for `my_fn`. +fn ptr_cast_chain<'a>(cx: &'a LateContext<'_>, mut e: &'a Expr<'a>) -> Option<&'a Expr<'a>> { + let mut had_at_least_one_cast = false; + loop { + e = e.peel_blocks(); + e = if let ExprKind::Cast(expr, t) = e.kind + && let TyKind::Ptr(_) = t.kind { + had_at_least_one_cast = true; + expr + } else if let ExprKind::MethodCall(_, expr, [], _) = e.kind + && let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) + && matches!(cx.tcx.get_diagnostic_name(def_id), Some(sym::ptr_cast | sym::ptr_cast_mut)) { + had_at_least_one_cast = true; + expr + } else if let ExprKind::Call(path, [arg]) = e.kind + && let ExprKind::Path(ref qpath) = path.kind + && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() + && matches!(cx.tcx.get_diagnostic_name(def_id), Some(sym::ptr_from_ref | sym::ptr_from_mut)) { + had_at_least_one_cast = true; + arg + } else if had_at_least_one_cast { + return Some(e); + } else { + return None; + }; + } +} + +fn incorrect_check<'a>(cx: &LateContext<'a>, expr: &Expr<'_>) -> Option<PtrNullChecksDiag<'a>> { + let expr = ptr_cast_chain(cx, expr)?; + + let orig_ty = cx.typeck_results().expr_ty(expr); + if orig_ty.is_fn() { + Some(PtrNullChecksDiag::FnPtr { orig_ty, label: expr.span }) + } else if orig_ty.is_ref() { + Some(PtrNullChecksDiag::Ref { orig_ty, label: expr.span }) + } else { + None + } +} + +impl<'tcx> LateLintPass<'tcx> for PtrNullChecks { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + match expr.kind { + // Catching: + // <*<const/mut> <ty>>::is_null(fn_ptr as *<const/mut> <ty>) + ExprKind::Call(path, [arg]) + if let ExprKind::Path(ref qpath) = path.kind + && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() + && matches!( + cx.tcx.get_diagnostic_name(def_id), + Some(sym::ptr_const_is_null | sym::ptr_is_null) + ) + && let Some(diag) = incorrect_check(cx, arg) => + { + cx.emit_spanned_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag) + } + + // Catching: + // (fn_ptr as *<const/mut> <ty>).is_null() + ExprKind::MethodCall(_, receiver, _, _) + if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) + && matches!( + cx.tcx.get_diagnostic_name(def_id), + Some(sym::ptr_const_is_null | sym::ptr_is_null) + ) + && let Some(diag) = incorrect_check(cx, receiver) => + { + cx.emit_spanned_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag) + } + + ExprKind::Binary(op, left, right) if matches!(op.node, BinOpKind::Eq) => { + let to_check: &Expr<'_>; + let diag: PtrNullChecksDiag<'_>; + if let Some(ddiag) = incorrect_check(cx, left) { + to_check = right; + diag = ddiag; + } else if let Some(ddiag) = incorrect_check(cx, right) { + to_check = left; + diag = ddiag; + } else { + return; + } + + match to_check.kind { + // Catching: + // (fn_ptr as *<const/mut> <ty>) == (0 as <ty>) + ExprKind::Cast(cast_expr, _) + if let ExprKind::Lit(spanned) = cast_expr.kind + && let LitKind::Int(v, _) = spanned.node && v == 0 => + { + cx.emit_spanned_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag) + }, + + // Catching: + // (fn_ptr as *<const/mut> <ty>) == std::ptr::null() + ExprKind::Call(path, []) + if let ExprKind::Path(ref qpath) = path.kind + && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() + && let Some(diag_item) = cx.tcx.get_diagnostic_name(def_id) + && (diag_item == sym::ptr_null || diag_item == sym::ptr_null_mut) => + { + cx.emit_spanned_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag) + }, + + _ => {}, + } + } + _ => {} + } + } +} diff --git a/compiler/rustc_lint/src/reference_casting.rs b/compiler/rustc_lint/src/reference_casting.rs index db8d7302a..2577cabb3 100644 --- a/compiler/rustc_lint/src/reference_casting.rs +++ b/compiler/rustc_lint/src/reference_casting.rs @@ -1,7 +1,8 @@ use rustc_ast::Mutability; -use rustc_hir::{Expr, ExprKind, MutTy, TyKind, UnOp}; -use rustc_middle::ty; -use rustc_span::sym; +use rustc_data_structures::fx::FxHashMap; +use rustc_hir::{def::Res, Expr, ExprKind, HirId, Local, QPath, StmtKind, UnOp}; +use rustc_middle::ty::{self, TypeAndMut}; +use rustc_span::{sym, Span}; use crate::{lints::InvalidReferenceCastingDiag, LateContext, LateLintPass, LintContext}; @@ -12,7 +13,6 @@ declare_lint! { /// ### Example /// /// ```rust,compile_fail - /// # #![deny(invalid_reference_casting)] /// fn x(r: &i32) { /// unsafe { /// *(r as *const i32 as *mut i32) += 1; @@ -30,44 +30,140 @@ declare_lint! { /// `UnsafeCell` is the only way to obtain aliasable data that is considered /// mutable. INVALID_REFERENCE_CASTING, - Allow, + Deny, "casts of `&T` to `&mut T` without interior mutability" } -declare_lint_pass!(InvalidReferenceCasting => [INVALID_REFERENCE_CASTING]); +#[derive(Default)] +pub struct InvalidReferenceCasting { + casted: FxHashMap<HirId, Span>, +} + +impl_lint_pass!(InvalidReferenceCasting => [INVALID_REFERENCE_CASTING]); impl<'tcx> LateLintPass<'tcx> for InvalidReferenceCasting { + fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx rustc_hir::Stmt<'tcx>) { + let StmtKind::Local(local) = stmt.kind else { + return; + }; + let Local { init: Some(init), els: None, .. } = local else { + return; + }; + + if is_cast_from_const_to_mut(cx, init) { + self.casted.insert(local.pat.hir_id, init.span); + } + } + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { - let ExprKind::Unary(UnOp::Deref, e) = &expr.kind else { return; }; + // &mut <expr> + let inner = if let ExprKind::AddrOf(_, Mutability::Mut, expr) = expr.kind { + expr + // <expr> = ... + } else if let ExprKind::Assign(expr, _, _) = expr.kind { + expr + // <expr> += ... + } else if let ExprKind::AssignOp(_, expr, _) = expr.kind { + expr + } else { + return; + }; + + let ExprKind::Unary(UnOp::Deref, e) = &inner.kind else { + return; + }; - let e = e.peel_blocks(); - let e = if let ExprKind::Cast(e, t) = e.kind - && let TyKind::Ptr(MutTy { mutbl: Mutability::Mut, .. }) = t.kind { + let orig_cast = if is_cast_from_const_to_mut(cx, e) { + None + } else if let ExprKind::Path(QPath::Resolved(_, path)) = e.kind + && let Res::Local(hir_id) = &path.res + && let Some(orig_cast) = self.casted.get(hir_id) { + Some(*orig_cast) + } else { + return; + }; + + cx.emit_spanned_lint( + INVALID_REFERENCE_CASTING, + expr.span, + if matches!(expr.kind, ExprKind::AddrOf(..)) { + InvalidReferenceCastingDiag::BorrowAsMut { orig_cast } + } else { + InvalidReferenceCastingDiag::AssignToRef { orig_cast } + }, + ); + } +} + +fn is_cast_from_const_to_mut<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool { + let e = e.peel_blocks(); + + fn from_casts<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { + // <expr> as *mut ... + let mut e = if let ExprKind::Cast(e, t) = e.kind + && let ty::RawPtr(TypeAndMut { mutbl: Mutability::Mut, .. }) = cx.typeck_results().node_type(t.hir_id).kind() { e + // <expr>.cast_mut() } else if let ExprKind::MethodCall(_, expr, [], _) = e.kind && let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) && cx.tcx.is_diagnostic_item(sym::ptr_cast_mut, def_id) { expr } else { - return; + return None; }; - let e = e.peel_blocks(); - let e = if let ExprKind::Cast(e, t) = e.kind - && let TyKind::Ptr(MutTy { mutbl: Mutability::Not, .. }) = t.kind { - e - } else if let ExprKind::Call(path, [arg]) = e.kind + let mut had_at_least_one_cast = false; + loop { + e = e.peel_blocks(); + // <expr> as *mut/const ... or <expr> as <uint> + e = if let ExprKind::Cast(expr, t) = e.kind + && matches!(cx.typeck_results().node_type(t.hir_id).kind(), ty::RawPtr(_) | ty::Uint(_)) { + had_at_least_one_cast = true; + expr + // <expr>.cast(), <expr>.cast_mut() or <expr>.cast_const() + } else if let ExprKind::MethodCall(_, expr, [], _) = e.kind + && let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) + && matches!( + cx.tcx.get_diagnostic_name(def_id), + Some(sym::ptr_cast | sym::const_ptr_cast | sym::ptr_cast_mut | sym::ptr_cast_const) + ) + { + had_at_least_one_cast = true; + expr + // ptr::from_ref(<expr>) + } else if let ExprKind::Call(path, [arg]) = e.kind + && let ExprKind::Path(ref qpath) = path.kind + && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() + && cx.tcx.is_diagnostic_item(sym::ptr_from_ref, def_id) { + return Some(arg); + } else if had_at_least_one_cast { + return Some(e); + } else { + return None; + }; + } + } + + fn from_transmute<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'tcx>, + ) -> Option<&'tcx Expr<'tcx>> { + // mem::transmute::<_, *mut _>(<expr>) + if let ExprKind::Call(path, [arg]) = e.kind && let ExprKind::Path(ref qpath) = path.kind && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() - && cx.tcx.is_diagnostic_item(sym::ptr_from_ref, def_id) { - arg + && cx.tcx.is_diagnostic_item(sym::transmute, def_id) + && let ty::RawPtr(TypeAndMut { mutbl: Mutability::Mut, .. }) = cx.typeck_results().node_type(e.hir_id).kind() { + Some(arg) } else { - return; - }; - - let e = e.peel_blocks(); - if let ty::Ref(..) = cx.typeck_results().node_type(e.hir_id).kind() { - cx.emit_spanned_lint(INVALID_REFERENCE_CASTING, expr.span, InvalidReferenceCastingDiag); + None } } + + let Some(e) = from_casts(cx, e).or_else(|| from_transmute(cx, e)) else { + return false; + }; + + let e = e.peel_blocks(); + matches!(cx.typeck_results().node_type(e.hir_id).kind(), ty::Ref(_, _, Mutability::Not)) } diff --git a/compiler/rustc_lint/src/traits.rs b/compiler/rustc_lint/src/traits.rs index de1120806..56508a2a6 100644 --- a/compiler/rustc_lint/src/traits.rs +++ b/compiler/rustc_lint/src/traits.rs @@ -92,7 +92,7 @@ impl<'tcx> LateLintPass<'tcx> for DropTraitConstraints { let predicates = cx.tcx.explicit_predicates_of(item.owner_id); for &(predicate, span) in predicates.predicates { let ClauseKind::Trait(trait_predicate) = predicate.kind().skip_binder() else { - continue + continue; }; let def_id = trait_predicate.trait_ref.def_id; if cx.tcx.lang_items().drop_trait() == Some(def_id) { @@ -100,9 +100,7 @@ impl<'tcx> LateLintPass<'tcx> for DropTraitConstraints { if trait_predicate.trait_ref.self_ty().is_impl_trait() { continue; } - let Some(def_id) = cx.tcx.get_diagnostic_item(sym::needs_drop) else { - return - }; + let Some(def_id) = cx.tcx.get_diagnostic_item(sym::needs_drop) else { return }; cx.emit_spanned_lint( DROP_BOUNDS, span, @@ -113,15 +111,11 @@ impl<'tcx> LateLintPass<'tcx> for DropTraitConstraints { } fn check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx hir::Ty<'tcx>) { - let hir::TyKind::TraitObject(bounds, _lifetime, _syntax) = &ty.kind else { - return - }; + let hir::TyKind::TraitObject(bounds, _lifetime, _syntax) = &ty.kind else { return }; for bound in &bounds[..] { let def_id = bound.trait_ref.trait_def_id(); if cx.tcx.lang_items().drop_trait() == def_id { - let Some(def_id) = cx.tcx.get_diagnostic_item(sym::needs_drop) else { - return - }; + let Some(def_id) = cx.tcx.get_diagnostic_item(sym::needs_drop) else { return }; cx.emit_spanned_lint(DYN_DROP, bound.span, DropGlue { tcx: cx.tcx, def_id }); } } diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs index ed4fb9860..1ba746edd 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -17,7 +17,7 @@ use rustc_errors::DiagnosticMessage; use rustc_hir as hir; use rustc_hir::{is_range_literal, Expr, ExprKind, Node}; use rustc_middle::ty::layout::{IntegerExt, LayoutOf, SizeSkeleton}; -use rustc_middle::ty::subst::SubstsRef; +use rustc_middle::ty::GenericArgsRef; use rustc_middle::ty::{ self, AdtKind, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, }; @@ -560,7 +560,10 @@ fn lint_nan<'tcx>( let expr = expr.peel_blocks().peel_borrows(); match expr.kind { ExprKind::Path(qpath) => { - let Some(def_id) = cx.typeck_results().qpath_res(&qpath, expr.hir_id).opt_def_id() else { return false; }; + let Some(def_id) = cx.typeck_results().qpath_res(&qpath, expr.hir_id).opt_def_id() + else { + return false; + }; matches!(cx.tcx.get_diagnostic_name(def_id), Some(sym::f32_nan | sym::f64_nan)) } @@ -569,32 +572,36 @@ fn lint_nan<'tcx>( } fn eq_ne( + cx: &LateContext<'_>, e: &hir::Expr<'_>, l: &hir::Expr<'_>, r: &hir::Expr<'_>, f: impl FnOnce(Span, Span) -> InvalidNanComparisonsSuggestion, ) -> InvalidNanComparisons { - let suggestion = + // FIXME(#72505): This suggestion can be restored if `f{32,64}::is_nan` is made const. + let suggestion = (!cx.tcx.hir().is_inside_const_context(e.hir_id)).then(|| { if let Some(l_span) = l.span.find_ancestor_inside(e.span) && - let Some(r_span) = r.span.find_ancestor_inside(e.span) { + let Some(r_span) = r.span.find_ancestor_inside(e.span) + { f(l_span, r_span) } else { InvalidNanComparisonsSuggestion::Spanless - }; + } + }); InvalidNanComparisons::EqNe { suggestion } } let lint = match binop.node { hir::BinOpKind::Eq | hir::BinOpKind::Ne if is_nan(cx, l) => { - eq_ne(e, l, r, |l_span, r_span| InvalidNanComparisonsSuggestion::Spanful { + eq_ne(cx, e, l, r, |l_span, r_span| InvalidNanComparisonsSuggestion::Spanful { nan_plus_binop: l_span.until(r_span), float: r_span.shrink_to_hi(), neg: (binop.node == hir::BinOpKind::Ne).then(|| r_span.shrink_to_lo()), }) } hir::BinOpKind::Eq | hir::BinOpKind::Ne if is_nan(cx, r) => { - eq_ne(e, l, r, |l_span, r_span| InvalidNanComparisonsSuggestion::Spanful { + eq_ne(cx, e, l, r, |l_span, r_span| InvalidNanComparisonsSuggestion::Spanful { nan_plus_binop: l_span.shrink_to_hi().to(r_span), float: l_span.shrink_to_hi(), neg: (binop.node == hir::BinOpKind::Ne).then(|| l_span.shrink_to_lo()), @@ -805,20 +812,19 @@ pub fn transparent_newtype_field<'a, 'tcx>( ) -> Option<&'a ty::FieldDef> { let param_env = tcx.param_env(variant.def_id); variant.fields.iter().find(|field| { - let field_ty = tcx.type_of(field.did).subst_identity(); + let field_ty = tcx.type_of(field.did).instantiate_identity(); let is_zst = tcx.layout_of(param_env.and(field_ty)).is_ok_and(|layout| layout.is_zst()); !is_zst }) } /// Is type known to be non-null? -fn ty_is_known_nonnull<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, mode: CItemKind) -> bool { - let tcx = cx.tcx; +fn ty_is_known_nonnull<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, mode: CItemKind) -> bool { match ty.kind() { ty::FnPtr(_) => true, ty::Ref(..) => true, ty::Adt(def, _) if def.is_box() && matches!(mode, CItemKind::Definition) => true, - ty::Adt(def, substs) if def.repr().transparent() && !def.is_union() => { + ty::Adt(def, args) if def.repr().transparent() && !def.is_union() => { let marked_non_null = nonnull_optimization_guaranteed(tcx, *def); if marked_non_null { @@ -832,8 +838,8 @@ fn ty_is_known_nonnull<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, mode: CItemKi def.variants() .iter() - .filter_map(|variant| transparent_newtype_field(cx.tcx, variant)) - .any(|field| ty_is_known_nonnull(cx, field.ty(tcx, substs), mode)) + .filter_map(|variant| transparent_newtype_field(tcx, variant)) + .any(|field| ty_is_known_nonnull(tcx, field.ty(tcx, args), mode)) } _ => false, } @@ -841,15 +847,12 @@ fn ty_is_known_nonnull<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, mode: CItemKi /// Given a non-null scalar (or transparent) type `ty`, return the nullable version of that type. /// If the type passed in was not scalar, returns None. -fn get_nullable_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> { - let tcx = cx.tcx; +fn get_nullable_type<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> { Some(match *ty.kind() { - ty::Adt(field_def, field_substs) => { + ty::Adt(field_def, field_args) => { let inner_field_ty = { - let mut first_non_zst_ty = field_def - .variants() - .iter() - .filter_map(|v| transparent_newtype_field(cx.tcx, v)); + let mut first_non_zst_ty = + field_def.variants().iter().filter_map(|v| transparent_newtype_field(tcx, v)); debug_assert_eq!( first_non_zst_ty.clone().count(), 1, @@ -858,9 +861,9 @@ fn get_nullable_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'t first_non_zst_ty .next_back() .expect("No non-zst fields in transparent type.") - .ty(tcx, field_substs) + .ty(tcx, field_args) }; - return get_nullable_type(cx, inner_field_ty); + return get_nullable_type(tcx, inner_field_ty); } ty::Int(ty) => Ty::new_int(tcx, ty), ty::Uint(ty) => Ty::new_uint(tcx, ty), @@ -892,43 +895,44 @@ fn get_nullable_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'t /// `core::ptr::NonNull`, and `#[repr(transparent)]` newtypes. /// FIXME: This duplicates code in codegen. pub(crate) fn repr_nullable_ptr<'tcx>( - cx: &LateContext<'tcx>, + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, ty: Ty<'tcx>, ckind: CItemKind, ) -> Option<Ty<'tcx>> { - debug!("is_repr_nullable_ptr(cx, ty = {:?})", ty); - if let ty::Adt(ty_def, substs) = ty.kind() { + debug!("is_repr_nullable_ptr(tcx, ty = {:?})", ty); + if let ty::Adt(ty_def, args) = ty.kind() { let field_ty = match &ty_def.variants().raw[..] { [var_one, var_two] => match (&var_one.fields.raw[..], &var_two.fields.raw[..]) { - ([], [field]) | ([field], []) => field.ty(cx.tcx, substs), + ([], [field]) | ([field], []) => field.ty(tcx, args), _ => return None, }, _ => return None, }; - if !ty_is_known_nonnull(cx, field_ty, ckind) { + if !ty_is_known_nonnull(tcx, field_ty, ckind) { return None; } // At this point, the field's type is known to be nonnull and the parent enum is Option-like. // If the computed size for the field and the enum are different, the nonnull optimization isn't // being applied (and we've got a problem somewhere). - let compute_size_skeleton = |t| SizeSkeleton::compute(t, cx.tcx, cx.param_env).unwrap(); + let compute_size_skeleton = |t| SizeSkeleton::compute(t, tcx, param_env).unwrap(); if !compute_size_skeleton(ty).same_size(compute_size_skeleton(field_ty)) { bug!("improper_ctypes: Option nonnull optimization not applied?"); } // Return the nullable type this Option-like enum can be safely represented with. - let field_ty_abi = &cx.layout_of(field_ty).unwrap().abi; + let field_ty_abi = &tcx.layout_of(param_env.and(field_ty)).unwrap().abi; if let Abi::Scalar(field_ty_scalar) = field_ty_abi { - match field_ty_scalar.valid_range(cx) { + match field_ty_scalar.valid_range(&tcx) { WrappingRange { start: 0, end } - if end == field_ty_scalar.size(&cx.tcx).unsigned_int_max() - 1 => + if end == field_ty_scalar.size(&tcx).unsigned_int_max() - 1 => { - return Some(get_nullable_type(cx, field_ty).unwrap()); + return Some(get_nullable_type(tcx, field_ty).unwrap()); } WrappingRange { start: 1, .. } => { - return Some(get_nullable_type(cx, field_ty).unwrap()); + return Some(get_nullable_type(tcx, field_ty).unwrap()); } WrappingRange { start, end } => { unreachable!("Unhandled start and end range: ({}, {})", start, end) @@ -960,9 +964,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { &self, cache: &mut FxHashSet<Ty<'tcx>>, field: &ty::FieldDef, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) -> FfiResult<'tcx> { - let field_ty = field.ty(self.cx.tcx, substs); + let field_ty = field.ty(self.cx.tcx, args); let field_ty = self .cx .tcx @@ -978,14 +982,14 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { ty: Ty<'tcx>, def: ty::AdtDef<'tcx>, variant: &ty::VariantDef, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) -> FfiResult<'tcx> { use FfiResult::*; let transparent_with_all_zst_fields = if def.repr().transparent() { if let Some(field) = transparent_newtype_field(self.cx.tcx, variant) { // Transparent newtypes have at most one non-ZST field which needs to be checked.. - match self.check_field_type_for_ffi(cache, field, substs) { + match self.check_field_type_for_ffi(cache, field, args) { FfiUnsafe { ty, .. } if ty.is_unit() => (), r => return r, } @@ -1003,7 +1007,7 @@ 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 { - all_phantom &= match self.check_field_type_for_ffi(cache, &field, substs) { + all_phantom &= match self.check_field_type_for_ffi(cache, &field, args) { FfiSafe => false, // `()` fields are FFI-safe! FfiUnsafe { ty, .. } if ty.is_unit() => false, @@ -1037,7 +1041,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } match *ty.kind() { - ty::Adt(def, substs) => { + ty::Adt(def, args) => { if def.is_box() && matches!(self.mode, CItemKind::Definition) { if ty.boxed_ty().is_sized(tcx, self.cx.param_env) { return FfiSafe; @@ -1100,7 +1104,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { }; } - self.check_variant_for_ffi(cache, ty, def, def.non_enum_variant(), substs) + self.check_variant_for_ffi(cache, ty, def, def.non_enum_variant(), args) } AdtKind::Enum => { if def.variants().is_empty() { @@ -1113,7 +1117,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { if !def.repr().c() && !def.repr().transparent() && def.repr().int.is_none() { // Special-case types like `Option<extern fn()>`. - if repr_nullable_ptr(self.cx, ty, self.mode).is_none() { + if repr_nullable_ptr(self.cx.tcx, self.cx.param_env, ty, self.mode) + .is_none() + { return FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_enum_repr_reason, @@ -1141,7 +1147,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { }; } - match self.check_variant_for_ffi(cache, ty, def, variant, substs) { + match self.check_variant_for_ffi(cache, ty, def, variant, args) { FfiSafe => (), r => return r, } @@ -1376,7 +1382,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { /// For a external ABI function, argument types and the result type are walked to find fn-ptr /// types that have external ABIs, as these still need checked. fn check_fn(&mut self, def_id: LocalDefId, decl: &'tcx hir::FnDecl<'_>) { - let sig = self.cx.tcx.fn_sig(def_id).subst_identity(); + let sig = self.cx.tcx.fn_sig(def_id).instantiate_identity(); let sig = self.cx.tcx.erase_late_bound_regions(sig); for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { @@ -1394,7 +1400,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { /// Check if a function's argument types and result type are "ffi-safe". fn check_foreign_fn(&mut self, def_id: LocalDefId, decl: &'tcx hir::FnDecl<'_>) { - let sig = self.cx.tcx.fn_sig(def_id).subst_identity(); + let sig = self.cx.tcx.fn_sig(def_id).instantiate_identity(); let sig = self.cx.tcx.erase_late_bound_regions(sig); for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { @@ -1407,7 +1413,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } fn check_foreign_static(&mut self, id: hir::OwnerId, span: Span) { - let ty = self.cx.tcx.type_of(id).subst_identity(); + let ty = self.cx.tcx.type_of(id).instantiate_identity(); self.check_type_for_ffi_and_report_errors(span, ty, true, false); } @@ -1513,7 +1519,7 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDefinitions { self.check_ty_maybe_containing_foreign_fnptr( cx, ty, - cx.tcx.type_of(item.owner_id).subst_identity(), + cx.tcx.type_of(item.owner_id).instantiate_identity(), ); } // See `check_fn`.. @@ -1538,7 +1544,7 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDefinitions { self.check_ty_maybe_containing_foreign_fnptr( cx, field.ty, - cx.tcx.type_of(field.def_id).subst_identity(), + cx.tcx.type_of(field.def_id).instantiate_identity(), ); } @@ -1573,13 +1579,13 @@ declare_lint_pass!(VariantSizeDifferences => [VARIANT_SIZE_DIFFERENCES]); impl<'tcx> LateLintPass<'tcx> for VariantSizeDifferences { fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) { if let hir::ItemKind::Enum(ref enum_definition, _) = it.kind { - let t = cx.tcx.type_of(it.owner_id).subst_identity(); + let t = cx.tcx.type_of(it.owner_id).instantiate_identity(); let ty = cx.tcx.erase_regions(t); let Ok(layout) = cx.layout_of(ty) else { return }; - let Variants::Multiple { - tag_encoding: TagEncoding::Direct, tag, ref variants, .. - } = &layout.variants else { - return + let Variants::Multiple { tag_encoding: TagEncoding::Direct, tag, ref variants, .. } = + &layout.variants + else { + return; }; let tag_size = tag.size(&cx.tcx).bytes(); @@ -1693,7 +1699,7 @@ impl InvalidAtomicOrdering { && recognized_names.contains(&method_path.ident.name) && let Some(m_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) && let Some(impl_did) = cx.tcx.impl_of_method(m_def_id) - && let Some(adt) = cx.tcx.type_of(impl_did).subst_identity().ty_adt_def() + && let Some(adt) = cx.tcx.type_of(impl_did).instantiate_identity().ty_adt_def() // skip extension traits, only lint functions from the standard library && cx.tcx.trait_id_of_impl(impl_did).is_none() && let parent = cx.tcx.parent(adt.did()) @@ -1752,8 +1758,13 @@ impl InvalidAtomicOrdering { } fn check_atomic_compare_exchange(cx: &LateContext<'_>, expr: &Expr<'_>) { - let Some((method, args)) = Self::inherent_atomic_method_call(cx, expr, &[sym::fetch_update, sym::compare_exchange, sym::compare_exchange_weak]) - else {return }; + let Some((method, args)) = Self::inherent_atomic_method_call( + cx, + expr, + &[sym::fetch_update, sym::compare_exchange, sym::compare_exchange_weak], + ) else { + return; + }; let fail_order_arg = match method { sym::fetch_update => &args[1], diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs index 5015b751e..6041f8075 100644 --- a/compiler/rustc_lint/src/unused.rs +++ b/compiler/rustc_lint/src/unused.rs @@ -94,7 +94,9 @@ declare_lint_pass!(UnusedResults => [UNUSED_MUST_USE, UNUSED_RESULTS]); impl<'tcx> LateLintPass<'tcx> for UnusedResults { fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) { - let hir::StmtKind::Semi(mut expr) = s.kind else { return; }; + let hir::StmtKind::Semi(mut expr) = s.kind else { + return; + }; let mut expr_is_from_block = false; while let hir::ExprKind::Block(blk, ..) = expr.kind @@ -284,22 +286,25 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { } ty::Adt(def, _) => is_def_must_use(cx, def.did(), span), ty::Alias(ty::Opaque, ty::AliasTy { def_id: def, .. }) => { - elaborate(cx.tcx, cx.tcx.explicit_item_bounds(def).subst_identity_iter_copied()) - // We only care about self bounds for the impl-trait - .filter_only_self() - .find_map(|(pred, _span)| { - // We only look at the `DefId`, so it is safe to skip the binder here. - if let ty::ClauseKind::Trait(ref poly_trait_predicate) = - pred.kind().skip_binder() - { - let def_id = poly_trait_predicate.trait_ref.def_id; - - is_def_must_use(cx, def_id, span) - } else { - None - } - }) - .map(|inner| MustUsePath::Opaque(Box::new(inner))) + elaborate( + cx.tcx, + cx.tcx.explicit_item_bounds(def).instantiate_identity_iter_copied(), + ) + // We only care about self bounds for the impl-trait + .filter_only_self() + .find_map(|(pred, _span)| { + // We only look at the `DefId`, so it is safe to skip the binder here. + if let ty::ClauseKind::Trait(ref poly_trait_predicate) = + pred.kind().skip_binder() + { + let def_id = poly_trait_predicate.trait_ref.def_id; + + is_def_must_use(cx, def_id, span) + } else { + None + } + }) + .map(|inner| MustUsePath::Opaque(Box::new(inner))) } ty::Dynamic(binders, _, _) => binders.iter().find_map(|predicate| { if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() @@ -409,7 +414,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { match path { MustUsePath::Suppressed => {} MustUsePath::Boxed(path) => { - let descr_pre = &format!("{}boxed ", descr_pre); + let descr_pre = &format!("{descr_pre}boxed "); emit_must_use_untranslated( cx, path, @@ -421,7 +426,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { ); } MustUsePath::Opaque(path) => { - let descr_pre = &format!("{}implementer{} of ", descr_pre, plural_suffix); + let descr_pre = &format!("{descr_pre}implementer{plural_suffix} of "); emit_must_use_untranslated( cx, path, @@ -433,7 +438,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { ); } MustUsePath::TraitObject(path) => { - let descr_post = &format!(" trait object{}{}", plural_suffix, descr_post); + let descr_post = &format!(" trait object{plural_suffix}{descr_post}"); emit_must_use_untranslated( cx, path, @@ -446,7 +451,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { } MustUsePath::TupleElement(elems) => { for (index, path) in elems { - let descr_post = &format!(" in tuple element {}", index); + let descr_post = &format!(" in tuple element {index}"); emit_must_use_untranslated( cx, path, @@ -459,7 +464,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { } } MustUsePath::Array(path, len) => { - let descr_pre = &format!("{}array{} of ", descr_pre, plural_suffix); + let descr_pre = &format!("{descr_pre}array{plural_suffix} of "); emit_must_use_untranslated( cx, path, @@ -648,7 +653,7 @@ trait UnusedDelimLint { ExprKind::Call(fn_, _params) => fn_, ExprKind::Cast(expr, _ty) => expr, ExprKind::Type(expr, _ty) => expr, - ExprKind::Index(base, _subscript) => base, + ExprKind::Index(base, _subscript, _) => base, _ => break, }; if !classify::expr_requires_semi_to_be_stmt(innermost) { @@ -661,6 +666,24 @@ trait UnusedDelimLint { if !followed_by_block { return false; } + + // Check if we need parens for `match &( Struct { feild: }) {}`. + { + let mut innermost = inner; + loop { + innermost = match &innermost.kind { + ExprKind::AddrOf(_, _, expr) => expr, + _ => { + if parser::contains_exterior_struct_lit(&innermost) { + return true; + } else { + break; + } + } + } + } + } + let mut innermost = inner; loop { innermost = match &innermost.kind { @@ -825,7 +848,7 @@ trait UnusedDelimLint { (value, UnusedDelimsCtx::ReturnValue, false, Some(left), None, true) } - Index(_, ref value) => (value, UnusedDelimsCtx::IndexExpr, false, None, None, false), + Index(_, ref value, _) => (value, UnusedDelimsCtx::IndexExpr, false, None, None, false), Assign(_, ref value, _) | AssignOp(.., ref value) => { (value, UnusedDelimsCtx::AssignedValue, false, None, None, false) |