summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_lint
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_lint')
-rw-r--r--compiler/rustc_lint/messages.ftl27
-rw-r--r--compiler/rustc_lint/src/array_into_iter.rs2
-rw-r--r--compiler/rustc_lint/src/builtin.rs704
-rw-r--r--compiler/rustc_lint/src/context.rs54
-rw-r--r--compiler/rustc_lint/src/deref_into_dyn_supertrait.rs2
-rw-r--r--compiler/rustc_lint/src/early.rs3
-rw-r--r--compiler/rustc_lint/src/enum_intrinsics_non_enums.rs4
-rw-r--r--compiler/rustc_lint/src/for_loops_over_fallibles.rs12
-rw-r--r--compiler/rustc_lint/src/foreign_modules.rs402
-rw-r--r--compiler/rustc_lint/src/internal.rs24
-rw-r--r--compiler/rustc_lint/src/late.rs48
-rw-r--r--compiler/rustc_lint/src/levels.rs47
-rw-r--r--compiler/rustc_lint/src/lib.rs59
-rw-r--r--compiler/rustc_lint/src/lints.rs68
-rw-r--r--compiler/rustc_lint/src/methods.rs4
-rw-r--r--compiler/rustc_lint/src/multiple_supertrait_upcastable.rs1
-rw-r--r--compiler/rustc_lint/src/non_ascii_idents.rs1
-rw-r--r--compiler/rustc_lint/src/noop_method_call.rs25
-rw-r--r--compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs21
-rw-r--r--compiler/rustc_lint/src/pass_by_value.rs4
-rw-r--r--compiler/rustc_lint/src/ptr_nulls.rs146
-rw-r--r--compiler/rustc_lint/src/reference_casting.rs144
-rw-r--r--compiler/rustc_lint/src/traits.rs14
-rw-r--r--compiler/rustc_lint/src/types.rs121
-rw-r--r--compiler/rustc_lint/src/unused.rs71
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)