From 4da823ba4be74cdd840c6db854ee57cc0c864d31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Miku=C5=82a?= Date: Thu, 19 Feb 2026 22:11:58 +0100 Subject: [PATCH 001/128] Do not enable split debuginfo for windows-gnu Because rustc doesn't handle split debuginfo for these targets, enabling that option causes them to be missing some of the debuginfo. --- bootstrap.example.toml | 7 ++++--- src/bootstrap/src/core/config/target_selection.rs | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/bootstrap.example.toml b/bootstrap.example.toml index e0cbb0c0e747..432a68140361 100644 --- a/bootstrap.example.toml +++ b/bootstrap.example.toml @@ -1008,9 +1008,10 @@ # its historical default, but when compiling the compiler itself, we skip it by # default since we know it's safe to do so in that case. # -# On Windows platforms, packed debuginfo is the only supported option, -# producing a `.pdb` file. -#split-debuginfo = if linux { off } else if windows { packed } else if apple { unpacked } +# On Windows MSVC platforms, packed debuginfo is the only supported option, +# producing a `.pdb` file. On Windows GNU rustc doesn't support splitting debuginfo, +# and enabling it causes issues. +#split-debuginfo = if linux || windows-gnu { off } else if windows-msvc { packed } else if apple { unpacked } # Path to the `llvm-config` binary of the installation of a custom LLVM to link # against. Note that if this is specified we don't compile LLVM at all for this diff --git a/src/bootstrap/src/core/config/target_selection.rs b/src/bootstrap/src/core/config/target_selection.rs index 47f6d6f386df..8457607b897d 100644 --- a/src/bootstrap/src/core/config/target_selection.rs +++ b/src/bootstrap/src/core/config/target_selection.rs @@ -142,7 +142,7 @@ impl SplitDebuginfo { pub fn default_for_platform(target: TargetSelection) -> Self { if target.contains("apple") { SplitDebuginfo::Unpacked - } else if target.is_windows() { + } else if target.is_msvc() { SplitDebuginfo::Packed } else { SplitDebuginfo::Off From 9b2b57085d8e2c2707028ef8a82c2c51b235d2c0 Mon Sep 17 00:00:00 2001 From: linshuy2 Date: Thu, 12 Feb 2026 23:10:44 +0000 Subject: [PATCH 002/128] fix: `unnecessary_safety_comment` FP on code blocks inside inner docs --- clippy_lints/src/undocumented_unsafe_blocks.rs | 4 +++- tests/ui/unnecessary_safety_comment.rs | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/undocumented_unsafe_blocks.rs b/clippy_lints/src/undocumented_unsafe_blocks.rs index 1b26b1b32b80..c56a1fc8c510 100644 --- a/clippy_lints/src/undocumented_unsafe_blocks.rs +++ b/clippy_lints/src/undocumented_unsafe_blocks.rs @@ -822,7 +822,9 @@ fn text_has_safety_comment( // Don't lint if the safety comment is part of a codeblock in a doc comment. // It may or may not be required, and we can't very easily check it (and we shouldn't, since // the safety comment isn't referring to the node we're currently checking) - if line.trim_start_matches("///").trim_start().starts_with("```") { + if let Some(doc) = line.strip_prefix("///").or_else(|| line.strip_prefix("//!")) + && doc.trim_start().starts_with("```") + { in_codeblock = !in_codeblock; } diff --git a/tests/ui/unnecessary_safety_comment.rs b/tests/ui/unnecessary_safety_comment.rs index d82a7b969080..75e8315343d3 100644 --- a/tests/ui/unnecessary_safety_comment.rs +++ b/tests/ui/unnecessary_safety_comment.rs @@ -100,3 +100,13 @@ pub fn point_to_five() -> *const u8 { } fn main() {} + +mod issue16553 { + //! ``` + //! // SAFETY: All is well. + //! unsafe { + //! foo() + //! } + //! ``` + mod blah {} +} From 29df68bb1243a0d90217b4cd9cfb84673f34046a Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 24 Feb 2026 20:41:24 +0100 Subject: [PATCH 003/128] Remove one DOM level for lint additional info --- util/gh-pages/index_template.html | 8 ++------ util/gh-pages/style.css | 6 +++--- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/util/gh-pages/index_template.html b/util/gh-pages/index_template.html index 91a5c1263195..dd9e9b837640 100644 --- a/util/gh-pages/index_template.html +++ b/util/gh-pages/index_template.html @@ -203,15 +203,11 @@ Otherwise, have a great day =^.^= {{lint.version}} {# #} {# Open related issues #} -
{# #} - Related Issues {# #} -
+ Related Issues {# Jump to source #} {% if let Some(id_location) = lint.id_location %} -
{# #} - View Source {# #} -
+ View Source {% endif %} {# #} {# #} diff --git a/util/gh-pages/style.css b/util/gh-pages/style.css index ce478a3e18d0..59868ddce847 100644 --- a/util/gh-pages/style.css +++ b/util/gh-pages/style.css @@ -459,7 +459,7 @@ article:hover .panel-title-name .anchor { display: inline;} display: flex; flex-flow: column; } - .lint-additional-info > div + div { + .lint-additional-info > * + * { border-top: 1px solid var(--theme-popup-border); } } @@ -468,12 +468,12 @@ article:hover .panel-title-name .anchor { display: inline;} display: flex; flex-flow: row; } - .lint-additional-info > div + div { + .lint-additional-info > * + * { border-left: 1px solid var(--theme-popup-border); } } -.lint-additional-info > div { +.lint-additional-info > * { display: inline-flex; min-width: 200px; flex-grow: 1; From 0a22189ba132980e36dda24eee99d3bae064e919 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 24 Feb 2026 20:46:05 +0100 Subject: [PATCH 004/128] Remove unused `label-default` CSS class --- util/gh-pages/index_template.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/util/gh-pages/index_template.html b/util/gh-pages/index_template.html index dd9e9b837640..35254fcfab79 100644 --- a/util/gh-pages/index_template.html +++ b/util/gh-pages/index_template.html @@ -194,13 +194,13 @@ Otherwise, have a great day =^.^= {# Applicability #}
{# #} Applicability: {#+ #} - {{ lint.applicability_str() }} {# #} + {{ lint.applicability_str() }} {# #} (?) {# #}
{# Clippy version #}
{# #} {% if lint.group == "deprecated" %}Deprecated{% else %} Added{% endif +%} in: {#+ #} - {{lint.version}} {# #} + {{lint.version}} {# #}
{# Open related issues #} Related Issues From f0336511fa7bc27e05fbab5c851b8cf592844132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Tue, 27 Jan 2026 16:09:52 +0100 Subject: [PATCH 005/128] remove debug requirement from hooks --- compiler/rustc_middle/src/hooks/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_middle/src/hooks/mod.rs b/compiler/rustc_middle/src/hooks/mod.rs index 0ddcdac817b8..1a7a8f5cae60 100644 --- a/compiler/rustc_middle/src/hooks/mod.rs +++ b/compiler/rustc_middle/src/hooks/mod.rs @@ -35,8 +35,10 @@ pub struct Providers { impl Default for Providers { fn default() -> Self { + #[allow(unused)] Providers { - $($name: |_, $($arg,)*| default_hook(stringify!($name), &($($arg,)*))),* + $($name: + |_, $($arg,)*| default_hook(stringify!($name))),* } } } @@ -118,8 +120,6 @@ fn clone(&self) -> Self { *self } } #[cold] -fn default_hook(name: &str, args: &dyn std::fmt::Debug) -> ! { - bug!( - "`tcx.{name}{args:?}` cannot be called as `{name}` was never assigned to a provider function" - ) +fn default_hook(name: &str) -> ! { + bug!("`tcx.{name}` cannot be called as `{name}` was never assigned to a provider function") } From 244b6d5ca8fd6257df414c8b8d48c9c8d26269de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Tue, 27 Jan 2026 15:00:36 +0100 Subject: [PATCH 006/128] Regression test for trait-system-refactor#262 --- .../generalize/eagely-normalizing-aliases.rs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/ui/traits/next-solver/generalize/eagely-normalizing-aliases.rs diff --git a/tests/ui/traits/next-solver/generalize/eagely-normalizing-aliases.rs b/tests/ui/traits/next-solver/generalize/eagely-normalizing-aliases.rs new file mode 100644 index 000000000000..463fe49e5531 --- /dev/null +++ b/tests/ui/traits/next-solver/generalize/eagely-normalizing-aliases.rs @@ -0,0 +1,26 @@ +//@ revisions: old next +//@[next] compile-flags: -Znext-solver +//@ ignore-compare-mode-next-solver (explicit revisions) +//@ check-pass +// Regression test for trait-system-refactor-initiative#262 + +trait View {} +trait HasAssoc { + type Assoc; +} + +struct StableVec(T); +impl View for StableVec {} + +fn assert_view(f: F) -> F { f } + + +fn store(x: StableVec) +where + T: HasAssoc, + StableVec: View, +{ + let _: StableVec = assert_view(x); +} + +fn main() {} From 5d0863b147c0a0084dfb15e6435004909363865f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Tue, 27 Jan 2026 15:00:36 +0100 Subject: [PATCH 007/128] eagerly normalize during generalization --- .../src/type_check/relate_tys.rs | 5 + compiler/rustc_infer/src/infer/at.rs | 6 + .../src/infer/relate/generalize.rs | 103 ++++++++++++++---- .../rustc_infer/src/infer/relate/lattice.rs | 5 + .../src/infer/relate/type_relating.rs | 9 ++ .../src/solve/assembly/structural_traits.rs | 7 +- .../src/solve/eval_ctxt/mod.rs | 35 +++++- compiler/rustc_type_ir/src/relate/combine.rs | 2 + .../src/relate/solver_relating.rs | 49 ++++++--- 9 files changed, 179 insertions(+), 42 deletions(-) diff --git a/compiler/rustc_borrowck/src/type_check/relate_tys.rs b/compiler/rustc_borrowck/src/type_check/relate_tys.rs index e2d684e12a81..152a7674490c 100644 --- a/compiler/rustc_borrowck/src/type_check/relate_tys.rs +++ b/compiler/rustc_borrowck/src/type_check/relate_tys.rs @@ -616,4 +616,9 @@ fn register_alias_relate_predicate(&mut self, a: Ty<'tcx>, b: Ty<'tcx>) { } })]); } + + fn try_eagerly_normalize_alias(&mut self, _alias: ty::AliasTy<'tcx>) -> Ty<'tcx> { + // Past hir typeck, so we don't have to worry about type inference anymore. + self.type_checker.infcx.next_ty_var(self.span()) + } } diff --git a/compiler/rustc_infer/src/infer/at.rs b/compiler/rustc_infer/src/infer/at.rs index 70e3d7dc9fef..84ad05fa8ea2 100644 --- a/compiler/rustc_infer/src/infer/at.rs +++ b/compiler/rustc_infer/src/infer/at.rs @@ -140,6 +140,8 @@ pub fn sup( ty::Contravariant, actual, self.cause.span, + // TODO: should normalize + &mut |_alias| self.infcx.next_ty_var(self.cause.span), ) .map(|goals| self.goals_to_obligations(goals)) } else { @@ -173,6 +175,8 @@ pub fn sub( ty::Covariant, actual, self.cause.span, + // TODO: should normalize + &mut |_alias| self.infcx.next_ty_var(self.cause.span), ) .map(|goals| self.goals_to_obligations(goals)) } else { @@ -225,6 +229,8 @@ pub fn eq_trace( ty::Invariant, actual, self.cause.span, + // TODO: should normalize + &mut |_alias| self.infcx.next_ty_var(self.cause.span), ) .map(|goals| self.goals_to_obligations(goals)) } else { diff --git a/compiler/rustc_infer/src/infer/relate/generalize.rs b/compiler/rustc_infer/src/infer/relate/generalize.rs index 69c090b662e5..34fd32c45bd2 100644 --- a/compiler/rustc_infer/src/infer/relate/generalize.rs +++ b/compiler/rustc_infer/src/infer/relate/generalize.rs @@ -76,6 +76,7 @@ pub fn instantiate_ty_var>>( target_vid, instantiation_variance, source_ty, + &mut |alias| relation.try_eagerly_normalize_alias(alias), )?; // Constrain `b_vid` to the generalized type `generalized_ty`. @@ -210,6 +211,7 @@ pub(crate) fn instantiate_const_var target_vid, ty::Invariant, source_ct, + &mut |alias| relation.try_eagerly_normalize_alias(alias), )?; debug_assert!(!generalized_ct.is_ct_infer()); @@ -249,6 +251,7 @@ fn generalize> + Relate>>( target_vid: impl Into, ambient_variance: ty::Variance, source_term: T, + normalize: &mut dyn FnMut(ty::AliasTy<'tcx>) -> Ty<'tcx>, ) -> RelateResult<'tcx, Generalization> { assert!(!source_term.has_escaping_bound_vars()); let (for_universe, root_vid) = match target_vid.into() { @@ -269,8 +272,9 @@ fn generalize> + Relate>>( for_universe, root_term: source_term.into(), ambient_variance, - in_alias: false, + state: GeneralizationState::Default, cache: Default::default(), + normalize, }; let value_may_be_infer = generalizer.relate(source_term, source_term)?; @@ -317,6 +321,13 @@ fn visit_region(&mut self, r: ty::Region<'tcx>) { } } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +enum GeneralizationState { + Default, + InAlias, + InNormalizedAlias, +} + /// The "generalizer" is used when handling inference variables. /// /// The basic strategy for handling a constraint like `?A <: B` is to @@ -361,9 +372,14 @@ struct Generalizer<'me, 'tcx> { /// This is necessary to correctly handle /// `::Assoc>::Assoc == ?0`. This equality can /// hold by either normalizing the outer or the inner associated type. - in_alias: bool, + // TODO: update doc comment + state: GeneralizationState, - cache: SsoHashMap<(Ty<'tcx>, ty::Variance, bool), Ty<'tcx>>, + cache: SsoHashMap<(Ty<'tcx>, ty::Variance, GeneralizationState), Ty<'tcx>>, + + /// Normalize an alias in the trait solver. + /// If normalization fails, a fresh infer var is returned. + normalize: &'me mut dyn FnMut(ty::AliasTy<'tcx>) -> Ty<'tcx>, } impl<'tcx> Generalizer<'_, 'tcx> { @@ -409,17 +425,34 @@ fn generalize_alias_ty( // with inference variables can cause incorrect ambiguity. // // cc trait-system-refactor-initiative#110 - if self.infcx.next_trait_solver() && !alias.has_escaping_bound_vars() && !self.in_alias { - return Ok(self.next_ty_var_for_alias()); + if self.infcx.next_trait_solver() + && !alias.has_escaping_bound_vars() + && match self.state { + GeneralizationState::Default => true, + GeneralizationState::InAlias => false, + // When generalizing an alias after normalizing, + // the outer alias should be treated as rigid and we shouldn't try generalizing it again. + // If we recursively find more aliases, the state should have been set back to InAlias. + GeneralizationState::InNormalizedAlias => unreachable!(), + } + { + let normalized_alias = (self.normalize)(alias); + + self.state = GeneralizationState::InNormalizedAlias; + // recursively generalize, treat the outer alias as rigid to avoid infinite recursion + let res = self.relate(normalized_alias, normalized_alias); + + // only one way to get here + self.state = GeneralizationState::Default; + + return res; } - let is_nested_alias = mem::replace(&mut self.in_alias, true); + let previous_state = mem::replace(&mut self.state, GeneralizationState::InAlias); let result = match self.relate(alias, alias) { Ok(alias) => Ok(alias.to_ty(self.cx())), - Err(e) => { - if is_nested_alias { - return Err(e); - } else { + Err(e) => match previous_state { + GeneralizationState::Default => { let mut visitor = MaxUniverse::new(); alias.visit_with(&mut visitor); let infer_replacement_is_complete = @@ -432,9 +465,14 @@ fn generalize_alias_ty( debug!("generalization failure in alias"); Ok(self.next_ty_var_for_alias()) } - } + GeneralizationState::InAlias => return Err(e), + // When generalizing an alias after normalizing, + // the outer alias should be treated as rigid and we shouldn't try generalizing it again. + // If we recursively find more aliases, the state should have been set back to InAlias. + GeneralizationState::InNormalizedAlias => unreachable!(), + }, }; - self.in_alias = is_nested_alias; + self.state = previous_state; result } } @@ -488,7 +526,7 @@ fn relate_with_variance>>( fn tys(&mut self, t: Ty<'tcx>, t2: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> { assert_eq!(t, t2); // we are misusing TypeRelation here; both LHS and RHS ought to be == - if let Some(&result) = self.cache.get(&(t, self.ambient_variance, self.in_alias)) { + if let Some(&result) = self.cache.get(&(t, self.ambient_variance, self.state)) { return Ok(result); } @@ -553,9 +591,17 @@ fn tys(&mut self, t: Ty<'tcx>, t2: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> { // cc trait-system-refactor-initiative#108 if self.infcx.next_trait_solver() && !matches!(self.infcx.typing_mode(), TypingMode::Coherence) - && self.in_alias { - inner.type_variables().equate(vid, new_var_id); + match self.state { + GeneralizationState::InAlias => { + inner.type_variables().equate(vid, new_var_id); + } + GeneralizationState::Default + | GeneralizationState::InNormalizedAlias => {} + } + GeneralizerState::Default + | GeneralizerState::ShallowStructurallyRelateAliases + | GeneralizerState::StructurallyRelateAliases => {} } debug!("replacing original vid={:?} with new={:?}", vid, new_var_id); @@ -585,14 +631,25 @@ fn tys(&mut self, t: Ty<'tcx>, t2: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> { } ty::Alias(_, data) => match self.structurally_relate_aliases { - StructurallyRelateAliases::No => self.generalize_alias_ty(data), + StructurallyRelateAliases::No => match self.state { + GeneralizationState::Default | GeneralizationState::InAlias => { + self.generalize_alias_ty(data) + } + GeneralizationState::InNormalizedAlias => { + // We can switch back to default, we've treated one layer as rigid by doing this operation. + self.state = GeneralizationState::Default; + let res = relate::structurally_relate_tys(self, t, t); + self.state = GeneralizationState::InNormalizedAlias; + res + } + }, StructurallyRelateAliases::Yes => relate::structurally_relate_tys(self, t, t), }, _ => relate::structurally_relate_tys(self, t, t), }?; - self.cache.insert((t, self.ambient_variance, self.in_alias), g); + self.cache.insert((t, self.ambient_variance, self.state), g); Ok(g) } @@ -683,9 +740,17 @@ fn consts( // for more details. if self.infcx.next_trait_solver() && !matches!(self.infcx.typing_mode(), TypingMode::Coherence) - && self.in_alias { - variable_table.union(vid, new_var_id); + match self.state { + GeneralizationState::InAlias => { + variable_table.union(vid, new_var_id); + } + GeneralizationState::Default + | GeneralizationState::InNormalizedAlias => {} + } + GeneralizerState::Default + | GeneralizerState::ShallowStructurallyRelateAliases + | GeneralizerState::StructurallyRelateAliases => {} } Ok(ty::Const::new_var(self.cx(), new_var_id)) } diff --git a/compiler/rustc_infer/src/infer/relate/lattice.rs b/compiler/rustc_infer/src/infer/relate/lattice.rs index a05e2d40e829..2cb256b1c63c 100644 --- a/compiler/rustc_infer/src/infer/relate/lattice.rs +++ b/compiler/rustc_infer/src/infer/relate/lattice.rs @@ -299,4 +299,9 @@ fn register_alias_relate_predicate(&mut self, a: Ty<'tcx>, b: Ty<'tcx>) { ty::AliasRelationDirection::Equate, ))]); } + + fn try_eagerly_normalize_alias(&mut self, _alias: ty::AliasTy<'tcx>) -> Ty<'tcx> { + // TODO: this should actually normalize + self.infcx.next_ty_var(self.span()) + } } diff --git a/compiler/rustc_infer/src/infer/relate/type_relating.rs b/compiler/rustc_infer/src/infer/relate/type_relating.rs index 96a0375f5fba..67f9dc69a4a6 100644 --- a/compiler/rustc_infer/src/infer/relate/type_relating.rs +++ b/compiler/rustc_infer/src/infer/relate/type_relating.rs @@ -396,4 +396,13 @@ fn register_alias_relate_predicate(&mut self, a: Ty<'tcx>, b: Ty<'tcx>) { } })]); } + + fn try_eagerly_normalize_alias( + &mut self, + _alias: rustc_type_ir::AliasTy< as rustc_type_ir::InferCtxtLike>::Interner>, + ) -> < as rustc_type_ir::InferCtxtLike>::Interner as rustc_type_ir::Interner>::Ty + { + // We only try to eagerly normalize aliases if we're using the new solver. + unreachable!() + } } diff --git a/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs b/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs index 05ea217c1de0..f94b0f0c30ed 100644 --- a/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs +++ b/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs @@ -976,7 +976,12 @@ fn try_eagerly_replace_alias( let replacement = self.ecx.instantiate_binder_with_infer(*replacement); self.nested.extend( self.ecx - .eq_and_get_goals(self.param_env, alias_term, replacement.projection_term) + .relate_and_get_goals( + self.param_env, + alias_term, + ty::Invariant, + replacement.projection_term, + ) .expect("expected to be able to unify goal projection with dyn's projection"), ); diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs index 6841fe1c5124..fedb6390d958 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs @@ -408,7 +408,7 @@ pub(super) fn ignore_candidate_head_usages(&mut self, usages: CandidateHeadUsage /// Recursively evaluates `goal`, returning whether any inference vars have /// been constrained and the certainty of the result. - fn evaluate_goal( + pub(super) fn evaluate_goal( &mut self, source: GoalSource, goal: Goal, @@ -1018,7 +1018,8 @@ pub(super) fn relate>( variance: ty::Variance, rhs: T, ) -> Result<(), NoSolution> { - let goals = self.delegate.relate(param_env, lhs, variance, rhs, self.origin_span)?; + let goals = self.relate_and_get_goals(param_env, lhs, variance, rhs)?; + for &goal in goals.iter() { let source = match goal.predicate.kind().skip_binder() { ty::PredicateKind::Subtype { .. } | ty::PredicateKind::AliasRelate(..) => { @@ -1039,13 +1040,37 @@ pub(super) fn relate>( /// If possible, try using `eq` instead which automatically handles nested /// goals correctly. #[instrument(level = "trace", skip(self, param_env), ret)] - pub(super) fn eq_and_get_goals>( - &self, + pub(super) fn relate_and_get_goals>( + &mut self, param_env: I::ParamEnv, lhs: T, + variance: ty::Variance, rhs: T, ) -> Result>, NoSolution> { - Ok(self.delegate.relate(param_env, lhs, ty::Variance::Invariant, rhs, self.origin_span)?) + let cx = self.cx(); + let delegate = self.delegate; + let origin_span = self.origin_span; + + let mut normalize = |alias: ty::AliasTy| { + let inference_var = self.next_ty_infer(); + + let goal = Goal::new( + cx, + param_env, + ty::PredicateKind::AliasRelate( + alias.to_ty(cx).into(), + inference_var.into(), + ty::AliasRelationDirection::Equate, + ), + ); + + // Ignore the result. If we can't eagerly normalize, returning the inference variable is enough. + let _ = self.evaluate_goal(GoalSource::TypeRelating, goal, None); + + self.resolve_vars_if_possible(inference_var) + }; + + Ok(delegate.relate(param_env, lhs, variance, rhs, origin_span, &mut normalize)?) } pub(super) fn instantiate_binder_with_infer + Copy>( diff --git a/compiler/rustc_type_ir/src/relate/combine.rs b/compiler/rustc_type_ir/src/relate/combine.rs index 64b87fac77f9..72d54c23733e 100644 --- a/compiler/rustc_type_ir/src/relate/combine.rs +++ b/compiler/rustc_type_ir/src/relate/combine.rs @@ -40,6 +40,8 @@ fn register_predicates( /// Register `AliasRelate` obligation(s) that both types must be related to each other. fn register_alias_relate_predicate(&mut self, a: I::Ty, b: I::Ty); + + fn try_eagerly_normalize_alias(&mut self, alias: ty::AliasTy) -> I::Ty; } pub fn super_combine_tys( diff --git a/compiler/rustc_type_ir/src/relate/solver_relating.rs b/compiler/rustc_type_ir/src/relate/solver_relating.rs index 82ee4f75fcb0..541b2531fe74 100644 --- a/compiler/rustc_type_ir/src/relate/solver_relating.rs +++ b/compiler/rustc_type_ir/src/relate/solver_relating.rs @@ -15,6 +15,7 @@ fn relate>( variance: ty::Variance, rhs: T, span: ::Span, + normalize: &mut dyn FnMut(ty::AliasTy) -> ::Ty, ) -> Result< Vec::Predicate>>, TypeError, @@ -32,40 +33,46 @@ fn eq_structurally_relating_aliases>( >; } -impl RelateExt for Infcx { - fn relate>( +impl> RelateExt for Infcx { + fn relate>( &self, - param_env: ::ParamEnv, + param_env: I::ParamEnv, lhs: T, variance: ty::Variance, rhs: T, - span: ::Span, - ) -> Result< - Vec::Predicate>>, - TypeError, - > { - let mut relate = - SolverRelating::new(self, StructurallyRelateAliases::No, variance, param_env, span); + span: I::Span, + normalize: &mut dyn FnMut(ty::AliasTy) -> I::Ty, + ) -> Result>, TypeError> { + let mut relate = SolverRelating::new( + self, + StructurallyRelateAliases::No, + variance, + param_env, + span, + normalize, + ); relate.relate(lhs, rhs)?; Ok(relate.goals) } - fn eq_structurally_relating_aliases>( + fn eq_structurally_relating_aliases>( &self, - param_env: ::ParamEnv, + param_env: I::ParamEnv, lhs: T, rhs: T, - span: ::Span, - ) -> Result< - Vec::Predicate>>, - TypeError, - > { + span: I::Span, + ) -> Result>, TypeError> { + // Structurally relating, we treat aliases as rigid, + // so we shouldn't ever try to normalize them. + let mut normalize_unreachable = |_alias| unreachable!(); + let mut relate = SolverRelating::new( self, StructurallyRelateAliases::Yes, ty::Invariant, param_env, span, + &mut normalize_unreachable, ); relate.relate(lhs, rhs)?; Ok(relate.goals) @@ -75,12 +82,14 @@ fn eq_structurally_relating_aliases>( /// Enforce that `a` is equal to or a subtype of `b`. pub struct SolverRelating<'infcx, Infcx, I: Interner> { infcx: &'infcx Infcx, + // Immutable fields. structurally_relate_aliases: StructurallyRelateAliases, param_env: I::ParamEnv, span: I::Span, // Mutable fields. ambient_variance: ty::Variance, + normalize: &'infcx mut dyn FnMut(ty::AliasTy) -> I::Ty, goals: Vec>, /// The cache only tracks the `ambient_variance` as it's the /// only field which is mutable and which meaningfully changes @@ -118,12 +127,14 @@ pub fn new( ambient_variance: ty::Variance, param_env: I::ParamEnv, span: I::Span, + normalize: &'infcx mut dyn FnMut(ty::AliasTy) -> I::Ty, ) -> Self { SolverRelating { infcx, structurally_relate_aliases, span, ambient_variance, + normalize, param_env, goals: vec![], cache: Default::default(), @@ -406,4 +417,8 @@ fn register_alias_relate_predicate(&mut self, a: I::Ty, b: I::Ty) { } })]); } + + fn try_eagerly_normalize_alias(&mut self, alias: ty::AliasTy) -> I::Ty { + (self.normalize)(alias) + } } From 04f2c0191ebe7a1018e00cf6db5f8394352c2da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Tue, 27 Jan 2026 15:46:09 +0100 Subject: [PATCH 008/128] implement eager normalization in a fresh context during typeck --- compiler/rustc_infer/src/infer/at.rs | 15 +++-- compiler/rustc_infer/src/infer/mod.rs | 13 ++++- .../rustc_infer/src/infer/relate/lattice.rs | 5 +- compiler/rustc_interface/src/passes.rs | 2 +- compiler/rustc_middle/src/hooks/mod.rs | 24 +++++++- compiler/rustc_trait_selection/src/solve.rs | 56 +++++++++++++++++-- 6 files changed, 98 insertions(+), 17 deletions(-) diff --git a/compiler/rustc_infer/src/infer/at.rs b/compiler/rustc_infer/src/infer/at.rs index 84ad05fa8ea2..3487286d5883 100644 --- a/compiler/rustc_infer/src/infer/at.rs +++ b/compiler/rustc_infer/src/infer/at.rs @@ -140,8 +140,9 @@ pub fn sup( ty::Contravariant, actual, self.cause.span, - // TODO: should normalize - &mut |_alias| self.infcx.next_ty_var(self.cause.span), + &mut |alias| { + self.infcx.try_eagerly_normalize_alias(self.param_env, self.cause.span, alias) + }, ) .map(|goals| self.goals_to_obligations(goals)) } else { @@ -175,8 +176,9 @@ pub fn sub( ty::Covariant, actual, self.cause.span, - // TODO: should normalize - &mut |_alias| self.infcx.next_ty_var(self.cause.span), + &mut |alias| { + self.infcx.try_eagerly_normalize_alias(self.param_env, self.cause.span, alias) + }, ) .map(|goals| self.goals_to_obligations(goals)) } else { @@ -229,8 +231,9 @@ pub fn eq_trace( ty::Invariant, actual, self.cause.span, - // TODO: should normalize - &mut |_alias| self.infcx.next_ty_var(self.cause.span), + &mut |alias| { + self.infcx.try_eagerly_normalize_alias(self.param_env, self.cause.span, alias) + }, ) .map(|goals| self.goals_to_obligations(goals)) } else { diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index b57306536260..2a2ac7c15924 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -1,5 +1,5 @@ use std::cell::{Cell, RefCell}; -use std::fmt; +use std::{fmt, mem}; pub use at::DefineOpaqueTypes; use free_regions::RegionRelations; @@ -21,6 +21,7 @@ use rustc_macros::extension; pub use rustc_macros::{TypeFoldable, TypeVisitable}; use rustc_middle::bug; +use rustc_middle::hooks::TypeErasedInfcx; use rustc_middle::infer::canonical::{CanonicalQueryInput, CanonicalVarValues}; use rustc_middle::mir::ConstraintCategory; use rustc_middle::traits::select; @@ -1498,6 +1499,16 @@ pub fn ty_or_const_infer_var_changed(&self, infer_var: TyOrConstInferVar) -> boo } } + pub fn try_eagerly_normalize_alias<'a>( + &'a self, + param_env: ty::ParamEnv<'tcx>, + span: Span, + alias: ty::AliasTy<'tcx>, + ) -> Ty<'tcx> { + let erased = unsafe { mem::transmute::<_, TypeErasedInfcx<'a, 'tcx>>(self) }; + self.tcx.try_eagerly_normalize_alias(erased, param_env, span, alias) + } + /// Attach a callback to be invoked on each root obligation evaluated in the new trait solver. pub fn attach_obligation_inspector(&self, inspector: ObligationInspector<'tcx>) { debug_assert!( diff --git a/compiler/rustc_infer/src/infer/relate/lattice.rs b/compiler/rustc_infer/src/infer/relate/lattice.rs index 2cb256b1c63c..7e480df7dda6 100644 --- a/compiler/rustc_infer/src/infer/relate/lattice.rs +++ b/compiler/rustc_infer/src/infer/relate/lattice.rs @@ -300,8 +300,7 @@ fn register_alias_relate_predicate(&mut self, a: Ty<'tcx>, b: Ty<'tcx>) { ))]); } - fn try_eagerly_normalize_alias(&mut self, _alias: ty::AliasTy<'tcx>) -> Ty<'tcx> { - // TODO: this should actually normalize - self.infcx.next_ty_var(self.span()) + fn try_eagerly_normalize_alias(&mut self, alias: ty::AliasTy<'tcx>) -> Ty<'tcx> { + self.infcx.try_eagerly_normalize_alias(self.param_env(), self.span(), alias) } } diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 15addd240785..225fff380ef5 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -900,7 +900,7 @@ pub fn write_interface<'tcx>(tcx: TyCtxt<'tcx>) { rustc_hir_typeck::provide(&mut providers.queries); ty::provide(&mut providers.queries); traits::provide(&mut providers.queries); - solve::provide(&mut providers.queries); + solve::provide(providers); rustc_passes::provide(&mut providers.queries); rustc_traits::provide(&mut providers.queries); rustc_ty_utils::provide(&mut providers.queries); diff --git a/compiler/rustc_middle/src/hooks/mod.rs b/compiler/rustc_middle/src/hooks/mod.rs index 1a7a8f5cae60..c09d9fc2d468 100644 --- a/compiler/rustc_middle/src/hooks/mod.rs +++ b/compiler/rustc_middle/src/hooks/mod.rs @@ -3,14 +3,16 @@ //! similar to queries, but queries come with a lot of machinery for caching and incremental //! compilation, whereas hooks are just plain function pointers without any of the query magic. +use std::marker::PhantomData; + use rustc_hir::def_id::{DefId, DefPathHash}; use rustc_session::StableCrateId; use rustc_span::def_id::{CrateNum, LocalDefId}; -use rustc_span::{ExpnHash, ExpnId}; +use rustc_span::{ExpnHash, ExpnId, Span}; -use crate::mir; use crate::query::on_disk_cache::{CacheEncoder, EncodedDepNodeIndex}; use crate::ty::{Ty, TyCtxt}; +use crate::{mir, ty}; macro_rules! declare_hooks { ($($(#[$attr:meta])*hook $name:ident($($arg:ident: $K:ty),*) -> $V:ty;)*) => { @@ -117,6 +119,24 @@ fn clone(&self) -> Self { *self } encoder: &mut CacheEncoder<'_, 'tcx>, query_result_index: &mut EncodedDepNodeIndex ) -> (); + + /// Tries to normalize an alias, ignoring any errors. + /// + /// Generalization with the new trait solver calls into this, + /// when generalizing outside of the trait solver in `hir_typeck`. + hook try_eagerly_normalize_alias( + type_erased_infcx: TypeErasedInfcx<'_, 'tcx>, + param_env: ty::ParamEnv<'tcx>, + span: Span, + alias: ty::AliasTy<'tcx> + ) -> Ty<'tcx>; +} + +// `repr(transparent)` so we can transmute a `&'a Infcx<'tcx>` to this struct. +#[repr(transparent)] +pub struct TypeErasedInfcx<'a, 'tcx> { + _infcx: *const (), + phantom: PhantomData<&'a mut &'tcx ()>, } #[cold] diff --git a/compiler/rustc_trait_selection/src/solve.rs b/compiler/rustc_trait_selection/src/solve.rs index 5d200c4d340b..cb0288513903 100644 --- a/compiler/rustc_trait_selection/src/solve.rs +++ b/compiler/rustc_trait_selection/src/solve.rs @@ -1,3 +1,8 @@ +use std::mem; + +use rustc_infer::infer::InferCtxt; +use rustc_infer::traits::{Obligation, ObligationCause}; +use rustc_middle::hooks::TypeErasedInfcx; pub use rustc_next_trait_solver::solve::*; mod delegate; @@ -13,10 +18,13 @@ deeply_normalize, deeply_normalize_with_skipped_universes, deeply_normalize_with_skipped_universes_and_ambiguous_coroutine_goals, }; -use rustc_middle::query::Providers; -use rustc_middle::ty::TyCtxt; +use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_middle::util::Providers; +use rustc_span::Span; pub use select::InferCtxtSelectExt; +use crate::traits::ObligationCtxt; + fn evaluate_root_goal_for_proof_tree_raw<'tcx>( tcx: TyCtxt<'tcx>, canonical_input: CanonicalInput>, @@ -27,6 +35,46 @@ fn evaluate_root_goal_for_proof_tree_raw<'tcx>( ) } -pub fn provide(providers: &mut Providers) { - *providers = Providers { evaluate_root_goal_for_proof_tree_raw, ..*providers }; +fn try_eagerly_normalize_alias<'a, 'tcx>( + tcx: TyCtxt<'tcx>, + type_erased_infcx: TypeErasedInfcx<'a, 'tcx>, + param_env: ty::ParamEnv<'tcx>, + span: Span, + alias: ty::AliasTy<'tcx>, +) -> Ty<'tcx> { + let infcx = unsafe { + mem::transmute::, &'a InferCtxt<'tcx>>(type_erased_infcx) + }; + + let ocx = ObligationCtxt::new(infcx); + + let infer_term = infcx.next_ty_var(span); + + // Dummy because we ignore the error anyway. + // We do provide a span, because this span is used when registering opaque types. + // For example, if we don't provide a span here, some diagnostics talking about TAIT will refer to a dummy span. + let cause = ObligationCause::dummy_with_span(span); + let obligation = Obligation::new( + tcx, + // we ignore the error anyway + ObligationCause::dummy(), + param_env, + ty::PredicateKind::AliasRelate( + alias.to_ty(tcx).into(), + infer_term.into(), + ty::AliasRelationDirection::Equate, + ), + ); + + ocx.register_obligation(obligation); + + // This only tries to eagerly resolve, if it errors we don't care. + let _ = ocx.try_evaluate_obligations(); + + infcx.resolve_vars_if_possible(infer_term) +} + +pub fn provide(providers: &mut Providers) { + providers.hooks.try_eagerly_normalize_alias = try_eagerly_normalize_alias; + providers.queries.evaluate_root_goal_for_proof_tree_raw = evaluate_root_goal_for_proof_tree_raw; } From 917713b4db3c627f2a35174c09d3ca715dd814b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Tue, 27 Jan 2026 16:03:10 +0100 Subject: [PATCH 009/128] merge generalizer state and structurally relate aliases --- .../src/infer/relate/generalize.rs | 117 ++++++++---------- 1 file changed, 53 insertions(+), 64 deletions(-) diff --git a/compiler/rustc_infer/src/infer/relate/generalize.rs b/compiler/rustc_infer/src/infer/relate/generalize.rs index 34fd32c45bd2..d5c0726ed9a8 100644 --- a/compiler/rustc_infer/src/infer/relate/generalize.rs +++ b/compiler/rustc_infer/src/infer/relate/generalize.rs @@ -267,12 +267,14 @@ fn generalize> + Relate>>( let mut generalizer = Generalizer { infcx: self, span, - structurally_relate_aliases, root_vid, for_universe, root_term: source_term.into(), ambient_variance, - state: GeneralizationState::Default, + state: match structurally_relate_aliases { + StructurallyRelateAliases::No => GeneralizerState::Default, + StructurallyRelateAliases::Yes => GeneralizerState::StructurallyRelateAliases, + }, cache: Default::default(), normalize, }; @@ -322,10 +324,13 @@ fn visit_region(&mut self, r: ty::Region<'tcx>) { } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -enum GeneralizationState { +enum GeneralizerState { + /// Treat aliases as potentially normalizable. Default, - InAlias, - InNormalizedAlias, + IncompletelyRelateHigherRankedAlias, + /// Only one layer + ShallowStructurallyRelateAliases, + StructurallyRelateAliases, } /// The "generalizer" is used when handling inference variables. @@ -346,10 +351,6 @@ struct Generalizer<'me, 'tcx> { span: Span, - /// Whether aliases should be related structurally. If not, we have to - /// be careful when generalizing aliases. - structurally_relate_aliases: StructurallyRelateAliases, - /// The vid of the type variable that is in the process of being /// instantiated. If we find this within the value we are folding, /// that means we would have created a cyclic value. @@ -372,10 +373,9 @@ struct Generalizer<'me, 'tcx> { /// This is necessary to correctly handle /// `::Assoc>::Assoc == ?0`. This equality can /// hold by either normalizing the outer or the inner associated type. - // TODO: update doc comment - state: GeneralizationState, + state: GeneralizerState, - cache: SsoHashMap<(Ty<'tcx>, ty::Variance, GeneralizationState), Ty<'tcx>>, + cache: SsoHashMap<(Ty<'tcx>, ty::Variance, GeneralizerState), Ty<'tcx>>, /// Normalize an alias in the trait solver. /// If normalization fails, a fresh infer var is returned. @@ -415,44 +415,51 @@ fn next_ty_var_for_alias(&self) -> Ty<'tcx> { /// continue generalizing the alias. This ends up pulling down the universe of the /// inference variable and is incomplete in case the alias would normalize to a type /// which does not mention that inference variable. - fn generalize_alias_ty( + fn handle_alias_ty( &mut self, + alias_ty: Ty<'tcx>, alias: ty::AliasTy<'tcx>, ) -> Result, TypeError<'tcx>> { - // We do not eagerly replace aliases with inference variables if they have - // escaping bound vars, see the method comment for details. However, when we - // are inside of an alias with escaping bound vars replacing nested aliases - // with inference variables can cause incorrect ambiguity. - // - // cc trait-system-refactor-initiative#110 - if self.infcx.next_trait_solver() - && !alias.has_escaping_bound_vars() - && match self.state { - GeneralizationState::Default => true, - GeneralizationState::InAlias => false, - // When generalizing an alias after normalizing, - // the outer alias should be treated as rigid and we shouldn't try generalizing it again. - // If we recursively find more aliases, the state should have been set back to InAlias. - GeneralizationState::InNormalizedAlias => unreachable!(), + match self.state { + GeneralizerState::ShallowStructurallyRelateAliases => { + // We can switch back to default, we've treated one layer as rigid by doing this operation. + self.state = GeneralizerState::Default; + let res = relate::structurally_relate_tys(self, alias_ty, alias_ty); + self.state = GeneralizerState::ShallowStructurallyRelateAliases; + return res; } - { - let normalized_alias = (self.normalize)(alias); + GeneralizerState::StructurallyRelateAliases => { + return relate::structurally_relate_tys(self, alias_ty, alias_ty); + } + GeneralizerState::Default + if self.infcx.next_trait_solver() && !alias.has_escaping_bound_vars() => + { + // We do not eagerly replace aliases with inference variables if they have + // escaping bound vars, see the method comment for details. However, when we + // are inside of an alias with escaping bound vars replacing nested aliases + // with inference variables can cause incorrect ambiguity. + // + // cc trait-system-refactor-initiative#110 + let normalized_alias = (self.normalize)(alias); - self.state = GeneralizationState::InNormalizedAlias; - // recursively generalize, treat the outer alias as rigid to avoid infinite recursion - let res = self.relate(normalized_alias, normalized_alias); + self.state = GeneralizerState::ShallowStructurallyRelateAliases; + // recursively generalize, treat the outer alias as rigid to avoid infinite recursion + let res = self.relate(normalized_alias, normalized_alias); - // only one way to get here - self.state = GeneralizationState::Default; + // only one way to get here + self.state = GeneralizerState::Default; - return res; + return res; + } + GeneralizerState::Default | GeneralizerState::IncompletelyRelateHigherRankedAlias => {} } - let previous_state = mem::replace(&mut self.state, GeneralizationState::InAlias); + let previous_state = + mem::replace(&mut self.state, GeneralizerState::IncompletelyRelateHigherRankedAlias); let result = match self.relate(alias, alias) { Ok(alias) => Ok(alias.to_ty(self.cx())), Err(e) => match previous_state { - GeneralizationState::Default => { + GeneralizerState::Default => { let mut visitor = MaxUniverse::new(); alias.visit_with(&mut visitor); let infer_replacement_is_complete = @@ -465,11 +472,11 @@ fn generalize_alias_ty( debug!("generalization failure in alias"); Ok(self.next_ty_var_for_alias()) } - GeneralizationState::InAlias => return Err(e), - // When generalizing an alias after normalizing, - // the outer alias should be treated as rigid and we shouldn't try generalizing it again. - // If we recursively find more aliases, the state should have been set back to InAlias. - GeneralizationState::InNormalizedAlias => unreachable!(), + GeneralizerState::IncompletelyRelateHigherRankedAlias => return Err(e), + + // Early return. + GeneralizerState::ShallowStructurallyRelateAliases + | GeneralizerState::StructurallyRelateAliases => unreachable!(), }, }; self.state = previous_state; @@ -593,11 +600,9 @@ fn tys(&mut self, t: Ty<'tcx>, t2: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> { && !matches!(self.infcx.typing_mode(), TypingMode::Coherence) { match self.state { - GeneralizationState::InAlias => { + GeneralizerState::IncompletelyRelateHigherRankedAlias => { inner.type_variables().equate(vid, new_var_id); } - GeneralizationState::Default - | GeneralizationState::InNormalizedAlias => {} } GeneralizerState::Default | GeneralizerState::ShallowStructurallyRelateAliases @@ -630,21 +635,7 @@ fn tys(&mut self, t: Ty<'tcx>, t2: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> { } } - ty::Alias(_, data) => match self.structurally_relate_aliases { - StructurallyRelateAliases::No => match self.state { - GeneralizationState::Default | GeneralizationState::InAlias => { - self.generalize_alias_ty(data) - } - GeneralizationState::InNormalizedAlias => { - // We can switch back to default, we've treated one layer as rigid by doing this operation. - self.state = GeneralizationState::Default; - let res = relate::structurally_relate_tys(self, t, t); - self.state = GeneralizationState::InNormalizedAlias; - res - } - }, - StructurallyRelateAliases::Yes => relate::structurally_relate_tys(self, t, t), - }, + ty::Alias(_, data) => self.handle_alias_ty(t, data), _ => relate::structurally_relate_tys(self, t, t), }?; @@ -742,11 +733,9 @@ fn consts( && !matches!(self.infcx.typing_mode(), TypingMode::Coherence) { match self.state { - GeneralizationState::InAlias => { + GeneralizerState::IncompletelyRelateHigherRankedAlias => { variable_table.union(vid, new_var_id); } - GeneralizationState::Default - | GeneralizationState::InNormalizedAlias => {} } GeneralizerState::Default | GeneralizerState::ShallowStructurallyRelateAliases From 7a123e87b21cd95fef62b80571ee6c644ac3a1c3 Mon Sep 17 00:00:00 2001 From: lcnr Date: Thu, 29 Jan 2026 18:49:58 +0100 Subject: [PATCH 010/128] explicitly provide type in transmute --- compiler/rustc_infer/src/infer/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index 2a2ac7c15924..351d0001c677 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -1505,7 +1505,8 @@ pub fn try_eagerly_normalize_alias<'a>( span: Span, alias: ty::AliasTy<'tcx>, ) -> Ty<'tcx> { - let erased = unsafe { mem::transmute::<_, TypeErasedInfcx<'a, 'tcx>>(self) }; + let erased = + unsafe { mem::transmute::<&'a InferCtxt<'tcx>, TypeErasedInfcx<'a, 'tcx>>(self) }; self.tcx.try_eagerly_normalize_alias(erased, param_env, span, alias) } From f50591fc1059d98ba57c79094f6e2d46db4b25b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Fri, 30 Jan 2026 19:00:20 +0100 Subject: [PATCH 011/128] normalize at the start of generalize if we can --- .../src/infer/relate/generalize.rs | 82 +++++++++++++------ .../src/canonical/canonicalizer.rs | 6 +- 2 files changed, 62 insertions(+), 26 deletions(-) diff --git a/compiler/rustc_infer/src/infer/relate/generalize.rs b/compiler/rustc_infer/src/infer/relate/generalize.rs index d5c0726ed9a8..1d4dab001616 100644 --- a/compiler/rustc_infer/src/infer/relate/generalize.rs +++ b/compiler/rustc_infer/src/infer/relate/generalize.rs @@ -38,6 +38,31 @@ fn from(value: ty::ConstVid) -> Self { } impl<'tcx> InferCtxt<'tcx> { + fn check_generalized_alias_normalizes_to_tyvar>( + &self, + relation: &mut R, + source_ty: Ty<'tcx>, + ) -> Option> { + if !self.next_trait_solver() + || matches!(relation.structurally_relate_aliases(), StructurallyRelateAliases::Yes) + { + return None; + } + + // If we get an alias + let ty::Alias(_, alias) = source_ty.kind() else { + return None; + }; + + if alias.has_escaping_bound_vars() { + return None; + } + + let normalized_alias = relation.try_eagerly_normalize_alias(*alias); + + normalized_alias.is_ty_var().then_some(normalized_alias) + } + /// The idea is that we should ensure that the type variable `target_vid` /// is equal to, a subtype of, or a supertype of `source_ty`. /// @@ -51,7 +76,7 @@ impl<'tcx> InferCtxt<'tcx> { /// `TypeRelation`. Do not use this, and instead please use `At::eq`, for all /// other usecases (i.e. setting the value of a type var). #[instrument(level = "debug", skip(self, relation))] - pub fn instantiate_ty_var>>( + pub fn instantiate_ty_var>( &self, relation: &mut R, target_is_expected: bool, @@ -61,30 +86,31 @@ pub fn instantiate_ty_var>>( ) -> RelateResult<'tcx, ()> { debug_assert!(self.inner.borrow_mut().type_variables().probe(target_vid).is_unknown()); - // Generalize `source_ty` depending on the current variance. As an example, assume - // `?target <: &'x ?1`, where `'x` is some free region and `?1` is an inference - // variable. - // - // Then the `generalized_ty` would be `&'?2 ?3`, where `'?2` and `?3` are fresh - // region/type inference variables. - // - // We then relate `generalized_ty <: source_ty`, adding constraints like `'x: '?2` and - // `?1 <: ?3`. - let Generalization { value_may_be_infer: generalized_ty } = self.generalize( - relation.span(), - relation.structurally_relate_aliases(), - target_vid, - instantiation_variance, - source_ty, - &mut |alias| relation.try_eagerly_normalize_alias(alias), - )?; + let generalized_ty = + match self.check_generalized_alias_normalizes_to_tyvar(relation, source_ty) { + Some(tyvar) => tyvar, + None => { + // Generalize `source_ty` depending on the current variance. As an example, assume + // `?target <: &'x ?1`, where `'x` is some free region and `?1` is an inference + // variable. + // + // Then the `generalized_ty` would be `&'?2 ?3`, where `'?2` and `?3` are fresh + // region/type inference variables. + // + // We then relate `generalized_ty <: source_ty`, adding constraints like `'x: '?2` and + // `?1 <: ?3`. + let generalizer = self.generalize( + relation.span(), + relation.structurally_relate_aliases(), + target_vid, + instantiation_variance, + source_ty, + &mut |alias| relation.try_eagerly_normalize_alias(alias), + )?; - // Constrain `b_vid` to the generalized type `generalized_ty`. - if let &ty::Infer(ty::TyVar(generalized_vid)) = generalized_ty.kind() { - self.inner.borrow_mut().type_variables().equate(target_vid, generalized_vid); - } else { - self.inner.borrow_mut().type_variables().instantiate(target_vid, generalized_ty); - } + generalizer.value_may_be_infer + } + }; // Finally, relate `generalized_ty` to `source_ty`, as described in previous comment. // @@ -92,7 +118,10 @@ pub fn instantiate_ty_var>>( // relations wind up attributed to the same spans. We need // to associate causes/spans with each of the relations in // the stack to get this right. - if generalized_ty.is_ty_var() { + if let &ty::Infer(ty::TyVar(generalized_vid)) = generalized_ty.kind() { + // Constrain `b_vid` to the generalized type variable. + self.inner.borrow_mut().type_variables().equate(target_vid, generalized_vid); + // This happens for cases like `::Assoc == ?0`. // We can't instantiate `?0` here as that would result in a // cyclic type. We instead delay the unification in case @@ -133,6 +162,9 @@ pub fn instantiate_ty_var>>( } } } else { + // Constrain `b_vid` to the generalized type `generalized_ty`. + self.inner.borrow_mut().type_variables().instantiate(target_vid, generalized_ty); + // NOTE: The `instantiation_variance` is not the same variance as // used by the relation. When instantiating `b`, `target_is_expected` // is flipped and the `instantiation_variance` is also flipped. To diff --git a/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs b/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs index ce2be24adc58..e469451da993 100644 --- a/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs +++ b/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs @@ -177,7 +177,11 @@ fn canonicalize_param_env( cache: Default::default(), }; let param_env = param_env.fold_with(&mut env_canonicalizer); - debug_assert!(env_canonicalizer.sub_root_lookup_table.is_empty()); + debug_assert!( + env_canonicalizer.sub_root_lookup_table.is_empty(), + "{:?}", + env_canonicalizer.sub_root_lookup_table + ); ( param_env, env_canonicalizer.variables, From e8e3544a71f23606c85e8586e37bd98389bc1ebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Tue, 3 Feb 2026 15:14:44 +0100 Subject: [PATCH 012/128] try generalizing if normalization isn't a tyvar --- .../src/infer/relate/generalize.rs | 113 +++++++++--------- 1 file changed, 58 insertions(+), 55 deletions(-) diff --git a/compiler/rustc_infer/src/infer/relate/generalize.rs b/compiler/rustc_infer/src/infer/relate/generalize.rs index 1d4dab001616..e1d3f4e6bd48 100644 --- a/compiler/rustc_infer/src/infer/relate/generalize.rs +++ b/compiler/rustc_infer/src/infer/relate/generalize.rs @@ -38,31 +38,6 @@ fn from(value: ty::ConstVid) -> Self { } impl<'tcx> InferCtxt<'tcx> { - fn check_generalized_alias_normalizes_to_tyvar>( - &self, - relation: &mut R, - source_ty: Ty<'tcx>, - ) -> Option> { - if !self.next_trait_solver() - || matches!(relation.structurally_relate_aliases(), StructurallyRelateAliases::Yes) - { - return None; - } - - // If we get an alias - let ty::Alias(_, alias) = source_ty.kind() else { - return None; - }; - - if alias.has_escaping_bound_vars() { - return None; - } - - let normalized_alias = relation.try_eagerly_normalize_alias(*alias); - - normalized_alias.is_ty_var().then_some(normalized_alias) - } - /// The idea is that we should ensure that the type variable `target_vid` /// is equal to, a subtype of, or a supertype of `source_ty`. /// @@ -86,31 +61,53 @@ pub fn instantiate_ty_var>( ) -> RelateResult<'tcx, ()> { debug_assert!(self.inner.borrow_mut().type_variables().probe(target_vid).is_unknown()); - let generalized_ty = - match self.check_generalized_alias_normalizes_to_tyvar(relation, source_ty) { - Some(tyvar) => tyvar, - None => { - // Generalize `source_ty` depending on the current variance. As an example, assume - // `?target <: &'x ?1`, where `'x` is some free region and `?1` is an inference - // variable. - // - // Then the `generalized_ty` would be `&'?2 ?3`, where `'?2` and `?3` are fresh - // region/type inference variables. - // - // We then relate `generalized_ty <: source_ty`, adding constraints like `'x: '?2` and - // `?1 <: ?3`. - let generalizer = self.generalize( - relation.span(), - relation.structurally_relate_aliases(), - target_vid, - instantiation_variance, - source_ty, - &mut |alias| relation.try_eagerly_normalize_alias(alias), - )?; + let generalized_ty = if self.next_trait_solver() + && matches!(relation.structurally_relate_aliases(), StructurallyRelateAliases::No) + && let ty::Alias(_, alias) = source_ty.kind() + { + let normalized_alias = relation.try_eagerly_normalize_alias(*alias); - generalizer.value_may_be_infer - } - }; + if normalized_alias.is_ty_var() { + normalized_alias + } else { + let Generalization { value_may_be_infer: generalized_ty } = self.generalize( + relation.span(), + GeneralizerState::ShallowStructurallyRelateAliases, + target_vid, + instantiation_variance, + normalized_alias, + &mut |alias| relation.try_eagerly_normalize_alias(alias), + )?; + + // The only way to get a tyvar back is if the outermost type is an alias. + // However, here, though we know it *is* an alias, we initialize the generalizer + // with `ShallowStructurallyRelateAliases` so we treat the outermost alias as rigid, + // ensuring this is never a tyvar. + assert!(!generalized_ty.is_ty_var()); + + generalized_ty + } + } else { + // Generalize `source_ty` depending on the current variance. As an example, assume + // `?target <: &'x ?1`, where `'x` is some free region and `?1` is an inference + // variable. + // + // Then the `generalized_ty` would be `&'?2 ?3`, where `'?2` and `?3` are fresh + // region/type inference variables. + // + // We then relate `generalized_ty <: source_ty`, adding constraints like `'x: '?2` and + // `?1 <: ?3`. + let Generalization { value_may_be_infer: generalized_ty } = self.generalize( + relation.span(), + relation.structurally_relate_aliases().into(), + target_vid, + instantiation_variance, + source_ty, + &mut |alias| relation.try_eagerly_normalize_alias(alias), + )?; + + generalized_ty + }; // Finally, relate `generalized_ty` to `source_ty`, as described in previous comment. // @@ -239,7 +236,7 @@ pub(crate) fn instantiate_const_var // constants and generic expressions are not yet handled correctly. let Generalization { value_may_be_infer: generalized_ct } = self.generalize( relation.span(), - relation.structurally_relate_aliases(), + relation.structurally_relate_aliases().into(), target_vid, ty::Invariant, source_ct, @@ -279,7 +276,7 @@ pub(crate) fn instantiate_const_var fn generalize> + Relate>>( &self, span: Span, - structurally_relate_aliases: StructurallyRelateAliases, + initial_state: GeneralizerState, target_vid: impl Into, ambient_variance: ty::Variance, source_term: T, @@ -303,10 +300,7 @@ fn generalize> + Relate>>( for_universe, root_term: source_term.into(), ambient_variance, - state: match structurally_relate_aliases { - StructurallyRelateAliases::No => GeneralizerState::Default, - StructurallyRelateAliases::Yes => GeneralizerState::StructurallyRelateAliases, - }, + state: initial_state, cache: Default::default(), normalize, }; @@ -365,6 +359,15 @@ enum GeneralizerState { StructurallyRelateAliases, } +impl From for GeneralizerState { + fn from(structurally_relate_aliases: StructurallyRelateAliases) -> Self { + match structurally_relate_aliases { + StructurallyRelateAliases::No => GeneralizerState::Default, + StructurallyRelateAliases::Yes => GeneralizerState::StructurallyRelateAliases, + } + } +} + /// The "generalizer" is used when handling inference variables. /// /// The basic strategy for handling a constraint like `?A <: B` is to From 2d411a0faad447b5bfc968b954fd3e9c10596325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Thu, 5 Feb 2026 11:35:47 +0100 Subject: [PATCH 013/128] fixup span in obligation cause --- compiler/rustc_trait_selection/src/solve.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_trait_selection/src/solve.rs b/compiler/rustc_trait_selection/src/solve.rs index cb0288513903..118bd8c81b1e 100644 --- a/compiler/rustc_trait_selection/src/solve.rs +++ b/compiler/rustc_trait_selection/src/solve.rs @@ -57,7 +57,7 @@ fn try_eagerly_normalize_alias<'a, 'tcx>( let obligation = Obligation::new( tcx, // we ignore the error anyway - ObligationCause::dummy(), + ObligationCause::dummy_with_span(span), param_env, ty::PredicateKind::AliasRelate( alias.to_ty(tcx).into(), From 6edf58fab80d713b5e017d98a57c4075c12b833e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Mon, 2 Feb 2026 19:31:10 +0100 Subject: [PATCH 014/128] bless some tests --- .../impl-trait/unsized_coercion.next.stderr | 11 ------ tests/ui/impl-trait/unsized_coercion.rs | 2 +- .../impl-trait/unsized_coercion3.next.stderr | 36 +++++++++++++++++-- .../impl-trait/unsized_coercion3.old.stderr | 2 +- tests/ui/impl-trait/unsized_coercion3.rs | 2 ++ ...id-alias-bound-is-not-inherent.next.stderr | 2 +- ...rg-type-mismatch-issue-45727.current.fixed | 3 +- ...-arg-type-mismatch-issue-45727.next.stderr | 11 ++---- .../closure-arg-type-mismatch-issue-45727.rs | 3 +- 9 files changed, 42 insertions(+), 30 deletions(-) delete mode 100644 tests/ui/impl-trait/unsized_coercion.next.stderr diff --git a/tests/ui/impl-trait/unsized_coercion.next.stderr b/tests/ui/impl-trait/unsized_coercion.next.stderr deleted file mode 100644 index bea5ddb0aefc..000000000000 --- a/tests/ui/impl-trait/unsized_coercion.next.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error[E0277]: the size for values of type `dyn Trait` cannot be known at compilation time - --> $DIR/unsized_coercion.rs:14:17 - | -LL | let x = hello(); - | ^^^^^^^ doesn't have a size known at compile-time - | - = help: the trait `Sized` is not implemented for `dyn Trait` - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/impl-trait/unsized_coercion.rs b/tests/ui/impl-trait/unsized_coercion.rs index 2cbf0d25d7ec..6a9a53903fed 100644 --- a/tests/ui/impl-trait/unsized_coercion.rs +++ b/tests/ui/impl-trait/unsized_coercion.rs @@ -4,6 +4,7 @@ //@ revisions: next old //@[next] compile-flags: -Znext-solver //@[old] check-pass +//@[next] check-pass trait Trait {} @@ -12,7 +13,6 @@ impl Trait for u32 {} fn hello() -> Box { if true { let x = hello(); - //[next]~^ ERROR: the size for values of type `dyn Trait` cannot be known at compilation time let y: Box = x; } Box::new(1u32) diff --git a/tests/ui/impl-trait/unsized_coercion3.next.stderr b/tests/ui/impl-trait/unsized_coercion3.next.stderr index a480a69a3864..db758761d795 100644 --- a/tests/ui/impl-trait/unsized_coercion3.next.stderr +++ b/tests/ui/impl-trait/unsized_coercion3.next.stderr @@ -1,5 +1,5 @@ error[E0277]: the trait bound `dyn Send: Trait` is not satisfied - --> $DIR/unsized_coercion3.rs:13:17 + --> $DIR/unsized_coercion3.rs:14:17 | LL | let x = hello(); | ^^^^^^^ the trait `Trait` is not implemented for `dyn Send` @@ -9,7 +9,37 @@ help: the trait `Trait` is implemented for `u32` | LL | impl Trait for u32 {} | ^^^^^^^^^^^^^^^^^^ +note: required by a bound in `Box` + --> $SRC_DIR/alloc/src/boxed.rs:LL:COL -error: aborting due to 1 previous error +error[E0308]: mismatched types + --> $DIR/unsized_coercion3.rs:19:5 + | +LL | fn hello() -> Box { + | ------------------------ + | | | + | | the expected opaque type + | expected `Box` because of return type +... +LL | Box::new(1u32) + | ^^^^^^^^^^^^^^ types differ + | + = note: expected struct `Box` + found struct `Box` -For more information about this error, try `rustc --explain E0277`. +error[E0277]: the trait bound `dyn Send: Trait` is not satisfied + --> $DIR/unsized_coercion3.rs:11:1 + | +LL | fn hello() -> Box { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Trait` is not implemented for `dyn Send` + | +help: the trait `Trait` is implemented for `u32` + --> $DIR/unsized_coercion3.rs:9:1 + | +LL | impl Trait for u32 {} + | ^^^^^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0277, E0308. +For more information about an error, try `rustc --explain E0277`. diff --git a/tests/ui/impl-trait/unsized_coercion3.old.stderr b/tests/ui/impl-trait/unsized_coercion3.old.stderr index 52a72b84a8dd..3bb9f9c20951 100644 --- a/tests/ui/impl-trait/unsized_coercion3.old.stderr +++ b/tests/ui/impl-trait/unsized_coercion3.old.stderr @@ -1,5 +1,5 @@ error[E0277]: the size for values of type `impl Trait + ?Sized` cannot be known at compilation time - --> $DIR/unsized_coercion3.rs:15:32 + --> $DIR/unsized_coercion3.rs:16:32 | LL | let y: Box = x; | ^ doesn't have a size known at compile-time diff --git a/tests/ui/impl-trait/unsized_coercion3.rs b/tests/ui/impl-trait/unsized_coercion3.rs index ebfbb2955de5..c1dd5350e229 100644 --- a/tests/ui/impl-trait/unsized_coercion3.rs +++ b/tests/ui/impl-trait/unsized_coercion3.rs @@ -9,6 +9,7 @@ trait Trait {} impl Trait for u32 {} fn hello() -> Box { + //[next]~^ ERROR: the trait bound `dyn Send: Trait` is not satisfied if true { let x = hello(); //[next]~^ ERROR: the trait bound `dyn Send: Trait` is not satisfied @@ -16,6 +17,7 @@ fn hello() -> Box { //[old]~^ ERROR: the size for values of type `impl Trait + ?Sized` cannot be know } Box::new(1u32) + //[next]~^ ERROR: mismatched types } fn main() {} diff --git a/tests/ui/methods/rigid-alias-bound-is-not-inherent.next.stderr b/tests/ui/methods/rigid-alias-bound-is-not-inherent.next.stderr index afacb3a7d521..4652bf5e3c58 100644 --- a/tests/ui/methods/rigid-alias-bound-is-not-inherent.next.stderr +++ b/tests/ui/methods/rigid-alias-bound-is-not-inherent.next.stderr @@ -9,7 +9,7 @@ note: candidate #1 is defined in the trait `Trait1` | LL | fn method(&self) { | ^^^^^^^^^^^^^^^^ -note: candidate #2 is defined in the trait `Trait2` +note: candidate #2 is defined in an impl of the trait `Trait2` for the type `T` --> $DIR/rigid-alias-bound-is-not-inherent.rs:27:5 | LL | fn method(&self) { diff --git a/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.current.fixed b/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.current.fixed index ba46a447802c..1c45a2c0adb3 100644 --- a/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.current.fixed +++ b/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.current.fixed @@ -8,6 +8,5 @@ fn main() { //[next]~^^ ERROR expected a `FnMut(& as Iterator>::Item)` closure, found let _ = (-10..=10).find(|x: &i32| x.signum() == 0); //[current]~^ ERROR type mismatch in closure arguments - //[next]~^^ ERROR expected `RangeInclusive<{integer}>` to be an iterator that yields `&&i32`, but it yields `{integer}` - //[next]~| ERROR expected a `FnMut(& as Iterator>::Item)` closure, found + //[next]~^^ ERROR expected a `FnMut(& as Iterator>::Item)` closure, found } diff --git a/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.next.stderr b/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.next.stderr index 7912ed4d7071..36e49c20c433 100644 --- a/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.next.stderr +++ b/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.next.stderr @@ -12,12 +12,6 @@ LL | let _ = (-10..=10).find(|x: i32| x.signum() == 0); note: required by a bound in `find` --> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL -error[E0271]: expected `RangeInclusive<{integer}>` to be an iterator that yields `&&i32`, but it yields `{integer}` - --> $DIR/closure-arg-type-mismatch-issue-45727.rs:9:24 - | -LL | let _ = (-10..=10).find(|x: &&&i32| x.signum() == 0); - | ^^^^ expected `&&i32`, found integer - error[E0277]: expected a `FnMut(& as Iterator>::Item)` closure, found `{closure@$DIR/closure-arg-type-mismatch-issue-45727.rs:9:29: 9:40}` --> $DIR/closure-arg-type-mismatch-issue-45727.rs:9:29 | @@ -32,7 +26,6 @@ LL | let _ = (-10..=10).find(|x: &&&i32| x.signum() == 0); note: required by a bound in `find` --> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL -error: aborting due to 3 previous errors +error: aborting due to 2 previous errors -Some errors have detailed explanations: E0271, E0277. -For more information about an error, try `rustc --explain E0271`. +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.rs b/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.rs index 0fd56707763e..20d6fed3b35b 100644 --- a/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.rs +++ b/tests/ui/mismatched_types/closure-arg-type-mismatch-issue-45727.rs @@ -8,6 +8,5 @@ fn main() { //[next]~^^ ERROR expected a `FnMut(& as Iterator>::Item)` closure, found let _ = (-10..=10).find(|x: &&&i32| x.signum() == 0); //[current]~^ ERROR type mismatch in closure arguments - //[next]~^^ ERROR expected `RangeInclusive<{integer}>` to be an iterator that yields `&&i32`, but it yields `{integer}` - //[next]~| ERROR expected a `FnMut(& as Iterator>::Item)` closure, found + //[next]~^^ ERROR expected a `FnMut(& as Iterator>::Item)` closure, found } From 9aa065bc49e5c9a0e07dbfc4bb0823821e5f130d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Mon, 9 Feb 2026 12:31:37 +0100 Subject: [PATCH 015/128] inline into --- .../src/infer/relate/generalize.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_infer/src/infer/relate/generalize.rs b/compiler/rustc_infer/src/infer/relate/generalize.rs index e1d3f4e6bd48..f3843c371e2c 100644 --- a/compiler/rustc_infer/src/infer/relate/generalize.rs +++ b/compiler/rustc_infer/src/infer/relate/generalize.rs @@ -99,7 +99,10 @@ pub fn instantiate_ty_var>( // `?1 <: ?3`. let Generalization { value_may_be_infer: generalized_ty } = self.generalize( relation.span(), - relation.structurally_relate_aliases().into(), + match relation.structurally_relate_aliases() { + StructurallyRelateAliases::No => GeneralizerState::Default, + StructurallyRelateAliases::Yes => GeneralizerState::StructurallyRelateAliases, + }, target_vid, instantiation_variance, source_ty, @@ -236,7 +239,10 @@ pub(crate) fn instantiate_const_var // constants and generic expressions are not yet handled correctly. let Generalization { value_may_be_infer: generalized_ct } = self.generalize( relation.span(), - relation.structurally_relate_aliases().into(), + match relation.structurally_relate_aliases() { + StructurallyRelateAliases::No => GeneralizerState::Default, + StructurallyRelateAliases::Yes => GeneralizerState::StructurallyRelateAliases, + }, target_vid, ty::Invariant, source_ct, @@ -359,15 +365,6 @@ enum GeneralizerState { StructurallyRelateAliases, } -impl From for GeneralizerState { - fn from(structurally_relate_aliases: StructurallyRelateAliases) -> Self { - match structurally_relate_aliases { - StructurallyRelateAliases::No => GeneralizerState::Default, - StructurallyRelateAliases::Yes => GeneralizerState::StructurallyRelateAliases, - } - } -} - /// The "generalizer" is used when handling inference variables. /// /// The basic strategy for handling a constraint like `?A <: B` is to From 7adb9bac0c7d2bf469e6b76c6cc47099afb02123 Mon Sep 17 00:00:00 2001 From: "LevitatingBusinessMan (Rein Fernhout)" Date: Fri, 27 Feb 2026 09:30:27 +0100 Subject: [PATCH 016/128] Add is_disconnected functions to mpsc and mpmc channels --- library/std/src/sync/mpmc/mod.rs | 46 +++++++++++++++++++++++++++++++ library/std/src/sync/mpmc/zero.rs | 6 ++++ library/std/src/sync/mpsc.rs | 38 +++++++++++++++++++++++++ 3 files changed, 90 insertions(+) diff --git a/library/std/src/sync/mpmc/mod.rs b/library/std/src/sync/mpmc/mod.rs index 8df81a580f7b..6249e8a033e8 100644 --- a/library/std/src/sync/mpmc/mod.rs +++ b/library/std/src/sync/mpmc/mod.rs @@ -623,6 +623,29 @@ pub fn same_channel(&self, other: &Sender) -> bool { _ => false, } } + + /// Returns `true` if the channel is disconnected. + /// + /// # Examples + /// + /// ``` + /// #![feature(mpmc_channel)] + /// + /// use std::sync::mpmc::channel; + /// + /// let (tx, rx) = channel::(); + /// assert!(!tx.is_disconnected()); + /// drop(rx); + /// assert!(tx.is_disconnected()); + /// ``` + #[unstable(feature = "mpmc_channel", issue = "126840")] + pub fn is_disconnected(&self) -> bool { + match &self.flavor { + SenderFlavor::Array(chan) => chan.is_disconnected(), + SenderFlavor::List(chan) => chan.is_disconnected(), + SenderFlavor::Zero(chan) => chan.is_disconnected(), + } + } } #[unstable(feature = "mpmc_channel", issue = "126840")] @@ -1349,6 +1372,29 @@ pub fn same_channel(&self, other: &Receiver) -> bool { pub fn iter(&self) -> Iter<'_, T> { Iter { rx: self } } + + /// Returns `true` if the channel is disconnected. + /// + /// # Examples + /// + /// ``` + /// #![feature(mpmc_channel)] + /// + /// use std::sync::mpmc::channel; + /// + /// let (tx, rx) = channel::(); + /// assert!(!rx.is_disconnected()); + /// drop(tx); + /// assert!(rx.is_disconnected()); + /// ``` + #[unstable(feature = "mpmc_channel", issue = "126840")] + pub fn is_disconnected(&self) -> bool { + match &self.flavor { + ReceiverFlavor::Array(chan) => chan.is_disconnected(), + ReceiverFlavor::List(chan) => chan.is_disconnected(), + ReceiverFlavor::Zero(chan) => chan.is_disconnected(), + } + } } #[unstable(feature = "mpmc_channel", issue = "126840")] diff --git a/library/std/src/sync/mpmc/zero.rs b/library/std/src/sync/mpmc/zero.rs index f1ecf80fcb9f..c74346250192 100644 --- a/library/std/src/sync/mpmc/zero.rs +++ b/library/std/src/sync/mpmc/zero.rs @@ -316,4 +316,10 @@ pub(crate) fn is_empty(&self) -> bool { pub(crate) fn is_full(&self) -> bool { true } + + /// Returns `true` if the channel is disconnected. + pub(crate) fn is_disconnected(&self) -> bool { + let inner = self.inner.lock().unwrap(); + inner.is_disconnected + } } diff --git a/library/std/src/sync/mpsc.rs b/library/std/src/sync/mpsc.rs index 0ae23f6e13bf..81bb5849fb77 100644 --- a/library/std/src/sync/mpsc.rs +++ b/library/std/src/sync/mpsc.rs @@ -607,6 +607,25 @@ impl Sender { pub fn send(&self, t: T) -> Result<(), SendError> { self.inner.send(t) } + + /// Returns `true` if the channel is disconnected. + /// + /// # Examples + /// + /// ``` + /// #![feature(mpsc_is_disconnected)] + /// + /// use std::sync::mpsc::channel; + /// + /// let (tx, rx) = channel::(); + /// assert!(!tx.is_disconnected()); + /// drop(rx); + /// assert!(tx.is_disconnected()); + /// ``` + #[unstable(feature = "mpsc_is_disconnected", issue = "none")] + pub fn is_disconnected(&self) -> bool { + self.inner.is_disconnected() + } } #[stable(feature = "rust1", since = "1.0.0")] @@ -1038,6 +1057,25 @@ pub fn iter(&self) -> Iter<'_, T> { pub fn try_iter(&self) -> TryIter<'_, T> { TryIter { rx: self } } + + /// Returns `true` if the channel is disconnected. + /// + /// # Examples + /// + /// ``` + /// #![feature(mpsc_is_disconnected)] + /// + /// use std::sync::mpsc::channel; + /// + /// let (tx, rx) = channel::(); + /// assert!(!rx.is_disconnected()); + /// drop(tx); + /// assert!(rx.is_disconnected()); + /// ``` + #[unstable(feature = "mpsc_is_disconnected", issue = "none")] + pub fn is_disconnected(&self) -> bool { + self.inner.is_disconnected() + } } #[stable(feature = "rust1", since = "1.0.0")] From feef7b4eafedfae6c8fd40caf711343b6cc3fe43 Mon Sep 17 00:00:00 2001 From: "LevitatingBusinessMan (Rein Fernhout)" Date: Sat, 28 Feb 2026 19:52:50 +0100 Subject: [PATCH 017/128] warn of possible race condition in channel is_disconnected doc --- library/std/src/sync/mpmc/mod.rs | 8 ++++++++ library/std/src/sync/mpsc.rs | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/library/std/src/sync/mpmc/mod.rs b/library/std/src/sync/mpmc/mod.rs index 6249e8a033e8..16ae8a88370b 100644 --- a/library/std/src/sync/mpmc/mod.rs +++ b/library/std/src/sync/mpmc/mod.rs @@ -626,6 +626,10 @@ pub fn same_channel(&self, other: &Sender) -> bool { /// Returns `true` if the channel is disconnected. /// + /// Note that a return value of `false` does not guarantee the channel will + /// remain connected. The channel may be disconnected immediately after this method + /// returns, so a subsequent [`Sender::send`] may still fail with [`SendError`]. + /// /// # Examples /// /// ``` @@ -1375,6 +1379,10 @@ pub fn iter(&self) -> Iter<'_, T> { /// Returns `true` if the channel is disconnected. /// + /// Note that a return value of `false` does not guarantee the channel will + /// remain connected. The channel may be disconnected immediately after this method + /// returns, so a subsequent [`Receiver::recv`] may still fail with [`RecvError`]. + /// /// # Examples /// /// ``` diff --git a/library/std/src/sync/mpsc.rs b/library/std/src/sync/mpsc.rs index 81bb5849fb77..2abf7bfe341d 100644 --- a/library/std/src/sync/mpsc.rs +++ b/library/std/src/sync/mpsc.rs @@ -610,6 +610,10 @@ pub fn send(&self, t: T) -> Result<(), SendError> { /// Returns `true` if the channel is disconnected. /// + /// Note that a return value of `false` does not guarantee the channel will + /// remain connected. The channel may be disconnected immediately after this method + /// returns, so a subsequent [`Sender::send`] may still fail with [`SendError`]. + /// /// # Examples /// /// ``` @@ -1060,6 +1064,10 @@ pub fn try_iter(&self) -> TryIter<'_, T> { /// Returns `true` if the channel is disconnected. /// + /// Note that a return value of `false` does not guarantee the channel will + /// remain connected. The channel may be disconnected immediately after this method + /// returns, so a subsequent [`Receiver::recv`] may still fail with [`RecvError`]. + /// /// # Examples /// /// ``` From fd8bb2167640600d59ac9199a4ddeb1a2edf7f4f Mon Sep 17 00:00:00 2001 From: irelaxcn Date: Mon, 2 Mar 2026 00:34:09 +0800 Subject: [PATCH 018/128] fix: `question_mark` suggestion caused error --- clippy_lints/src/question_mark.rs | 5 +++-- tests/ui/question_mark.fixed | 13 +++++++++++++ tests/ui/question_mark.rs | 19 +++++++++++++++++++ tests/ui/question_mark.stderr | 21 ++++++++++++++++++++- 4 files changed, 55 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index 719f364357d3..e4bb3f13aea0 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -501,7 +501,8 @@ fn check_if_let_some_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: let mut applicability = Applicability::MachineApplicable; let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability); - let requires_semi = matches!(cx.tcx.parent_hir_node(expr.hir_id), Node::Stmt(_)); + let parent = cx.tcx.parent_hir_node(expr.hir_id); + let requires_semi = matches!(parent, Node::Stmt(_)) || cx.typeck_results().expr_ty(expr).is_unit(); let method_call_str = match by_ref { ByRef::Yes(_, Mutability::Mut) => ".as_mut()", ByRef::Yes(_, Mutability::Not) => ".as_ref()", @@ -512,7 +513,7 @@ fn check_if_let_some_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: "{receiver_str}{method_call_str}?{}", if requires_semi { ";" } else { "" } ); - if is_else_clause(cx.tcx, expr) { + if is_else_clause(cx.tcx, expr) || (requires_semi && !matches!(parent, Node::Stmt(_) | Node::Block(_))) { sugg = format!("{{ {sugg} }}"); } diff --git a/tests/ui/question_mark.fixed b/tests/ui/question_mark.fixed index 102517d34c61..e209da5c8258 100644 --- a/tests/ui/question_mark.fixed +++ b/tests/ui/question_mark.fixed @@ -524,3 +524,16 @@ fn issue16429(b: i32) -> Option { Some(0) } + +fn issue16654() -> Result<(), i32> { + let result = func_returning_result(); + + #[allow(clippy::collapsible_if)] + if true { + result?; + } + + _ = [{ result?; }]; + + Ok(()) +} diff --git a/tests/ui/question_mark.rs b/tests/ui/question_mark.rs index cfea1277fe76..579b51461d13 100644 --- a/tests/ui/question_mark.rs +++ b/tests/ui/question_mark.rs @@ -649,3 +649,22 @@ fn issue16429(b: i32) -> Option { Some(0) } + +fn issue16654() -> Result<(), i32> { + let result = func_returning_result(); + + #[allow(clippy::collapsible_if)] + if true { + if let Err(err) = result { + //~^ question_mark + return Err(err); + } + } + + _ = [if let Err(err) = result { + //~^ question_mark + return Err(err); + }]; + + Ok(()) +} diff --git a/tests/ui/question_mark.stderr b/tests/ui/question_mark.stderr index c243f12de040..1d7f665a2662 100644 --- a/tests/ui/question_mark.stderr +++ b/tests/ui/question_mark.stderr @@ -362,5 +362,24 @@ LL | | return None; LL | | }; | |_____^ help: replace it with: `{ a? }` -error: aborting due to 38 previous errors +error: this block may be rewritten with the `?` operator + --> tests/ui/question_mark.rs:658:9 + | +LL | / if let Err(err) = result { +LL | | +LL | | return Err(err); +LL | | } + | |_________^ help: replace it with: `result?;` + +error: this block may be rewritten with the `?` operator + --> tests/ui/question_mark.rs:664:10 + | +LL | _ = [if let Err(err) = result { + | __________^ +LL | | +LL | | return Err(err); +LL | | }]; + | |_____^ help: replace it with: `{ result?; }` + +error: aborting due to 40 previous errors From f5a2bb6263ad332ceb2d9b3b82afe935259a7723 Mon Sep 17 00:00:00 2001 From: "Andrew V. Teylu" Date: Mon, 2 Mar 2026 20:28:16 +0000 Subject: [PATCH 019/128] Add hygiene annotations for tokens in macro_rules! bodies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `-Zunpretty=expanded,hygiene` was not printing syntax context annotations for identifiers and lifetimes inside `macro_rules!` bodies. These tokens are printed via `print_tt()` → `token_to_string_ext()`, which converts tokens to strings without calling `ann_post()`. This meant that macro-generated `macro_rules!` definitions with hygienic metavar parameters (e.g. multiple `$marg` distinguished only by hygiene) were printed with no way to tell them apart. This was fixed by adding a match on `token.kind` in `print_tt()` to call `ann_post()` for `Ident`, `NtIdent`, `Lifetime`, and `NtLifetime` tokens, matching how `print_ident()` and `print_lifetime()` already handle AST-level identifiers and lifetimes. Signed-off-by: Andrew V. Teylu --- compiler/rustc_ast_pretty/src/pprust/state.rs | 17 ++++++ .../hygiene/unpretty-debug-lifetimes.stdout | 8 +-- tests/ui/hygiene/unpretty-debug-metavars.rs | 25 +++++++++ .../ui/hygiene/unpretty-debug-metavars.stdout | 52 +++++++++++++++++++ tests/ui/hygiene/unpretty-debug.stdout | 10 +++- tests/ui/proc-macro/meta-macro-hygiene.stdout | 5 +- .../nonterminal-token-hygiene.stdout | 17 ++++-- 7 files changed, 122 insertions(+), 12 deletions(-) create mode 100644 tests/ui/hygiene/unpretty-debug-metavars.rs create mode 100644 tests/ui/hygiene/unpretty-debug-metavars.stdout diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs index f4168301bc5d..7d963dd2037c 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state.rs @@ -735,6 +735,23 @@ fn print_tt(&mut self, tt: &TokenTree, convert_dollar_crate: bool) -> Spacing { TokenTree::Token(token, spacing) => { let token_str = self.token_to_string_ext(token, convert_dollar_crate); self.word(token_str); + // Emit hygiene annotations for identity-bearing tokens, + // matching how print_ident() and print_lifetime() call ann_post(). + match token.kind { + token::Ident(name, _) => { + self.ann_post(Ident::new(name, token.span)); + } + token::NtIdent(ident, _) => { + self.ann_post(ident); + } + token::Lifetime(name, _) => { + self.ann_post(Ident::new(name, token.span)); + } + token::NtLifetime(ident, _) => { + self.ann_post(ident); + } + _ => {} + } if let token::DocComment(..) = token.kind { self.hardbreak() } diff --git a/tests/ui/hygiene/unpretty-debug-lifetimes.stdout b/tests/ui/hygiene/unpretty-debug-lifetimes.stdout index 28a5c70a02d7..689453326c0b 100644 --- a/tests/ui/hygiene/unpretty-debug-lifetimes.stdout +++ b/tests/ui/hygiene/unpretty-debug-lifetimes.stdout @@ -7,15 +7,17 @@ // Don't break whenever Symbol numbering changes //@ normalize-stdout: "\d+#" -> "0#" -#![feature /* 0#0 */(decl_macro)] -#![feature /* 0#0 */(no_core)] +#![feature /* 0#0 */(decl_macro /* 0#0 */)] +#![feature /* 0#0 */(no_core /* 0#0 */)] #![no_core /* 0#0 */] macro lifetime_hygiene /* 0#0 */ { - ($f:ident<$a:lifetime>) => { fn $f<$a, 'a>() {} } + ($f /* 0#0 */:ident /* 0#0 */<$a /* 0#0 */:lifetime /* 0#0 */>) + => + { fn /* 0#0 */ $f /* 0#0 */<$a /* 0#0 */, 'a /* 0#0 */>() {} } } fn f /* 0#0 */<'a /* 0#0 */, 'a /* 0#1 */>() {} diff --git a/tests/ui/hygiene/unpretty-debug-metavars.rs b/tests/ui/hygiene/unpretty-debug-metavars.rs new file mode 100644 index 000000000000..a0c47bec3ccf --- /dev/null +++ b/tests/ui/hygiene/unpretty-debug-metavars.rs @@ -0,0 +1,25 @@ +//@ check-pass +//@ compile-flags: -Zunpretty=expanded,hygiene + +// Regression test: metavar parameters in macro-generated macro_rules! +// definitions should have hygiene annotations so that textually identical +// `$marg` bindings are distinguishable by their syntax contexts. + +// Don't break whenever Symbol numbering changes +//@ normalize-stdout: "\d+#" -> "0#" + +#![feature(no_core)] +#![no_core] + +macro_rules! make_macro { + (@inner $name:ident ($dol:tt) $a:ident) => { + macro_rules! $name { + ($dol $a : expr, $dol marg : expr) => {} + } + }; + ($name:ident) => { + make_macro!{@inner $name ($) marg} + }; +} + +make_macro!(add2); diff --git a/tests/ui/hygiene/unpretty-debug-metavars.stdout b/tests/ui/hygiene/unpretty-debug-metavars.stdout new file mode 100644 index 000000000000..307c5e1b8de9 --- /dev/null +++ b/tests/ui/hygiene/unpretty-debug-metavars.stdout @@ -0,0 +1,52 @@ +//@ check-pass +//@ compile-flags: -Zunpretty=expanded,hygiene + +// Regression test: metavar parameters in macro-generated macro_rules! +// definitions should have hygiene annotations so that textually identical +// `$marg` bindings are distinguishable by their syntax contexts. + +// Don't break whenever Symbol numbering changes +//@ normalize-stdout: "\d+#" -> "0#" + +#![feature /* 0#0 */(no_core /* 0#0 */)] +#![no_core /* 0#0 */] + +macro_rules! make_macro + /* + 0#0 + */ { + (@inner /* 0#0 */ $name /* 0#0 */:ident /* 0#0 + */($dol /* 0#0 */:tt /* 0#0 */) $a /* 0#0 */:ident /* 0#0 */) + => + { + macro_rules /* 0#0 */! $name /* 0#0 */ + { + ($dol /* 0#0 */ $a /* 0#0 */ : expr /* 0#0 */, $dol /* + 0#0 */ marg /* 0#0 */ : expr /* 0#0 */) => {} + } + }; ($name /* 0#0 */:ident /* 0#0 */) => + { + make_macro /* 0#0 + */!{@inner /* 0#0 */ $name /* 0#0 */($) marg /* 0#0 */} + }; +} +macro_rules! add2 + /* + 0#0 + */ { + ($ marg /* 0#1 */ : expr /* 0#2 */, $marg /* 0#2 */ : expr /* + 0#2 */) => {} +} + + +/* +Expansions: +crate0::{{expn0}}: parent: crate0::{{expn0}}, call_site_ctxt: #0, def_site_ctxt: #0, kind: Root +crate0::{{expn1}}: parent: crate0::{{expn0}}, call_site_ctxt: #0, def_site_ctxt: #0, kind: Macro(Bang, "make_macro") +crate0::{{expn2}}: parent: crate0::{{expn1}}, call_site_ctxt: #1, def_site_ctxt: #0, kind: Macro(Bang, "make_macro") + +SyntaxContexts: +#0: parent: #0, outer_mark: (crate0::{{expn0}}, Opaque) +#1: parent: #0, outer_mark: (crate0::{{expn1}}, SemiOpaque) +#2: parent: #0, outer_mark: (crate0::{{expn2}}, SemiOpaque) +*/ diff --git a/tests/ui/hygiene/unpretty-debug.stdout b/tests/ui/hygiene/unpretty-debug.stdout index f35bd7a7cb2c..ac6051e2d542 100644 --- a/tests/ui/hygiene/unpretty-debug.stdout +++ b/tests/ui/hygiene/unpretty-debug.stdout @@ -5,10 +5,16 @@ //@ normalize-stdout: "\d+#" -> "0#" // minimal junk -#![feature /* 0#0 */(no_core)] +#![feature /* 0#0 */(no_core /* 0#0 */)] #![no_core /* 0#0 */] -macro_rules! foo /* 0#0 */ { ($x: ident) => { y + $x } } +macro_rules! foo + /* + 0#0 + */ { + ($x /* 0#0 */: ident /* 0#0 */) => + { y /* 0#0 */ + $x /* 0#0 */ } +} fn bar /* 0#0 */() { let x /* 0#0 */ = 1; diff --git a/tests/ui/proc-macro/meta-macro-hygiene.stdout b/tests/ui/proc-macro/meta-macro-hygiene.stdout index b5db9922b31a..a03e567bd48a 100644 --- a/tests/ui/proc-macro/meta-macro-hygiene.stdout +++ b/tests/ui/proc-macro/meta-macro-hygiene.stdout @@ -1,7 +1,7 @@ Def site: $DIR/auxiliary/make-macro.rs:7:9: 7:56 (#4) Input: TokenStream [Ident { ident: "$crate", span: $DIR/meta-macro-hygiene.rs:26:37: 26:43 (#3) }, Punct { ch: ':', spacing: Joint, span: $DIR/meta-macro-hygiene.rs:26:43: 26:44 (#3) }, Punct { ch: ':', spacing: Alone, span: $DIR/meta-macro-hygiene.rs:26:44: 26:45 (#3) }, Ident { ident: "dummy", span: $DIR/meta-macro-hygiene.rs:26:45: 26:50 (#3) }, Punct { ch: '!', spacing: Alone, span: $DIR/meta-macro-hygiene.rs:26:50: 26:51 (#3) }, Group { delimiter: Parenthesis, stream: TokenStream [], span: $DIR/meta-macro-hygiene.rs:26:51: 26:53 (#3) }] Respanned: TokenStream [Ident { ident: "$crate", span: $DIR/auxiliary/make-macro.rs:7:9: 7:56 (#4) }, Punct { ch: ':', spacing: Joint, span: $DIR/auxiliary/make-macro.rs:7:9: 7:56 (#4) }, Punct { ch: ':', spacing: Alone, span: $DIR/auxiliary/make-macro.rs:7:9: 7:56 (#4) }, Ident { ident: "dummy", span: $DIR/auxiliary/make-macro.rs:7:9: 7:56 (#4) }, Punct { ch: '!', spacing: Alone, span: $DIR/auxiliary/make-macro.rs:7:9: 7:56 (#4) }, Group { delimiter: Parenthesis, stream: TokenStream [], span: $DIR/auxiliary/make-macro.rs:7:9: 7:56 (#4) }] -#![feature /* 0#0 */(prelude_import)] +#![feature /* 0#0 */(prelude_import /* 0#0 */)] //@ aux-build:make-macro.rs //@ proc-macro: meta-macro.rs //@ edition:2018 @@ -30,7 +30,8 @@ macro_rules! produce_it */ { () => { - meta_macro::print_def_site!($crate::dummy!()); + meta_macro /* 0#0 */::print_def_site /* 0#0 + */!($crate /* 0#0 */::dummy /* 0#0 */!()); // `print_def_site!` will respan the `$crate` identifier // with `Span::def_site()`. This should cause it to resolve // relative to `meta_macro`, *not* `make_macro` (despite diff --git a/tests/ui/proc-macro/nonterminal-token-hygiene.stdout b/tests/ui/proc-macro/nonterminal-token-hygiene.stdout index e45abab03b4c..61b55782e6e9 100644 --- a/tests/ui/proc-macro/nonterminal-token-hygiene.stdout +++ b/tests/ui/proc-macro/nonterminal-token-hygiene.stdout @@ -20,7 +20,7 @@ PRINT-BANG INPUT (DEBUG): TokenStream [ span: $DIR/nonterminal-token-hygiene.rs:23:27: 23:32 (#4), }, ] -#![feature /* 0#0 */(prelude_import)] +#![feature /* 0#0 */(prelude_import /* 0#0 */)] #![no_std /* 0#0 */] // Make sure that marks from declarative macros are applied to tokens in nonterminal. @@ -34,7 +34,7 @@ PRINT-BANG INPUT (DEBUG): TokenStream [ //@ proc-macro: test-macros.rs //@ edition: 2015 -#![feature /* 0#0 */(decl_macro)] +#![feature /* 0#0 */(decl_macro /* 0#0 */)] #![no_std /* 0#0 */] extern crate core /* 0#2 */; #[prelude_import /* 0#1 */] @@ -49,15 +49,22 @@ macro_rules! outer /* 0#0 */ { - ($item:item) => + ($item /* 0#0 */:item /* 0#0 */) => { - macro inner() { print_bang! { $item } } inner!(); + macro /* 0#0 */ inner /* 0#0 */() + { print_bang /* 0#0 */! { $item /* 0#0 */ } } inner /* 0#0 + */!(); }; } struct S /* 0#0 */; -macro inner /* 0#3 */ { () => { print_bang! { struct S; } } } +macro inner + /* + 0#3 + */ { + () => { print_bang /* 0#3 */! { struct /* 0#0 */ S /* 0#0 */; } } +} struct S /* 0#5 */; // OK, not a duplicate definition of `S` From bf6db4f345799e1f922b3109a7278d8be11f3058 Mon Sep 17 00:00:00 2001 From: "Andrew V. Teylu" Date: Tue, 3 Mar 2026 13:06:55 +0000 Subject: [PATCH 020/128] Add regression tests for token hygiene annotations in macro bodies Add `unpretty-debug-shadow` test covering macro body tokens that reference a shadowed variable, and simplify the `unpretty-debug-metavars` test macro. Signed-off-by: Andrew V. Teylu --- tests/ui/hygiene/unpretty-debug-metavars.rs | 7 +++-- .../ui/hygiene/unpretty-debug-metavars.stdout | 7 +++-- tests/ui/hygiene/unpretty-debug-shadow.rs | 20 +++++++++++++ tests/ui/hygiene/unpretty-debug-shadow.stdout | 30 +++++++++++++++++++ 4 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 tests/ui/hygiene/unpretty-debug-shadow.rs create mode 100644 tests/ui/hygiene/unpretty-debug-shadow.stdout diff --git a/tests/ui/hygiene/unpretty-debug-metavars.rs b/tests/ui/hygiene/unpretty-debug-metavars.rs index a0c47bec3ccf..41bf75cd0d98 100644 --- a/tests/ui/hygiene/unpretty-debug-metavars.rs +++ b/tests/ui/hygiene/unpretty-debug-metavars.rs @@ -1,9 +1,10 @@ //@ check-pass //@ compile-flags: -Zunpretty=expanded,hygiene -// Regression test: metavar parameters in macro-generated macro_rules! -// definitions should have hygiene annotations so that textually identical -// `$marg` bindings are distinguishable by their syntax contexts. +// Regression test for token hygiene annotations in -Zunpretty=expanded,hygiene +// Previously, metavar parameters in macro-generated macro_rules! definitions +// were missing hygiene annotations, making identical `$marg` bindings +// indistinguishable. // Don't break whenever Symbol numbering changes //@ normalize-stdout: "\d+#" -> "0#" diff --git a/tests/ui/hygiene/unpretty-debug-metavars.stdout b/tests/ui/hygiene/unpretty-debug-metavars.stdout index 307c5e1b8de9..89658bc909a1 100644 --- a/tests/ui/hygiene/unpretty-debug-metavars.stdout +++ b/tests/ui/hygiene/unpretty-debug-metavars.stdout @@ -1,9 +1,10 @@ //@ check-pass //@ compile-flags: -Zunpretty=expanded,hygiene -// Regression test: metavar parameters in macro-generated macro_rules! -// definitions should have hygiene annotations so that textually identical -// `$marg` bindings are distinguishable by their syntax contexts. +// Regression test for token hygiene annotations in -Zunpretty=expanded,hygiene +// Previously, metavar parameters in macro-generated macro_rules! definitions +// were missing hygiene annotations, making identical `$marg` bindings +// indistinguishable. // Don't break whenever Symbol numbering changes //@ normalize-stdout: "\d+#" -> "0#" diff --git a/tests/ui/hygiene/unpretty-debug-shadow.rs b/tests/ui/hygiene/unpretty-debug-shadow.rs new file mode 100644 index 000000000000..2aa68c8aec11 --- /dev/null +++ b/tests/ui/hygiene/unpretty-debug-shadow.rs @@ -0,0 +1,20 @@ +//@ check-pass +//@ compile-flags: -Zunpretty=expanded,hygiene + +// Regression test for token hygiene annotations in -Zunpretty=expanded,hygiene +// Previously, tokens in macro_rules! bodies were missing hygiene annotations, +// making it impossible to see how a macro's reference to a shadowed variable +// is distinguished from the shadowing binding. + +// Don't break whenever Symbol numbering changes +//@ normalize-stdout: "\d+#" -> "0#" + +#![feature(no_core)] +#![no_core] + +fn f() { + let x = 0; + macro_rules! use_x { () => { x }; } + let x = 1; + use_x!(); +} diff --git a/tests/ui/hygiene/unpretty-debug-shadow.stdout b/tests/ui/hygiene/unpretty-debug-shadow.stdout new file mode 100644 index 000000000000..36076b6a968f --- /dev/null +++ b/tests/ui/hygiene/unpretty-debug-shadow.stdout @@ -0,0 +1,30 @@ +//@ check-pass +//@ compile-flags: -Zunpretty=expanded,hygiene + +// Regression test for token hygiene annotations in -Zunpretty=expanded,hygiene +// Previously, tokens in macro_rules! bodies were missing hygiene annotations, +// making it impossible to see how a macro's reference to a shadowed variable +// is distinguished from the shadowing binding. + +// Don't break whenever Symbol numbering changes +//@ normalize-stdout: "\d+#" -> "0#" + +#![feature /* 0#0 */(no_core /* 0#0 */)] +#![no_core /* 0#0 */] + +fn f /* 0#0 */() { + let x /* 0#0 */ = 0; + macro_rules! use_x /* 0#0 */ { () => { x /* 0#0 */ }; } + let x /* 0#0 */ = 1; + x /* 0#1 */; +} + +/* +Expansions: +crate0::{{expn0}}: parent: crate0::{{expn0}}, call_site_ctxt: #0, def_site_ctxt: #0, kind: Root +crate0::{{expn1}}: parent: crate0::{{expn0}}, call_site_ctxt: #0, def_site_ctxt: #0, kind: Macro(Bang, "use_x") + +SyntaxContexts: +#0: parent: #0, outer_mark: (crate0::{{expn0}}, Opaque) +#1: parent: #0, outer_mark: (crate0::{{expn1}}, SemiOpaque) +*/ From 9a224202e434c2e16f92f9803c65e07dc8e06875 Mon Sep 17 00:00:00 2001 From: Aliaksei Semianiuk Date: Sat, 21 Feb 2026 15:40:37 +0500 Subject: [PATCH 021/128] Changelog for Clippy 1.94 --- CHANGELOG.md | 98 +++++++++++++++++++- clippy_lints/src/casts/mod.rs | 2 +- clippy_lints/src/manual_ilog2.rs | 2 +- clippy_lints/src/methods/mod.rs | 2 +- clippy_lints/src/operators/mod.rs | 2 +- clippy_lints/src/same_length_and_capacity.rs | 2 +- 6 files changed, 102 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c798a3c2e5a..531c8794a572 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,103 @@ document. ## Unreleased / Beta / In Rust Nightly -[92b4b68...master](https://github.com/rust-lang/rust-clippy/compare/92b4b68...master) +[500e0ff...master](https://github.com/rust-lang/rust-clippy/compare/500e0ff...master) + +## Rust 1.94 + +Current stable, released 2026-03-05 + +[View all 94 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2025-11-29T21%3A01%3A29Z..2026-01-08T20%3A33%3A22Z+base%3Amaster) + +### New Lints + +* Added [`same_length_and_capacity`] to `pedantic` + [#15656](https://github.com/rust-lang/rust-clippy/pull/15656) +* Added [`manual_ilog2`] to `pedantic` + [#15865](https://github.com/rust-lang/rust-clippy/pull/15865) +* Added [`needless_type_cast`] to `nursery` + [#16139](https://github.com/rust-lang/rust-clippy/pull/16139) +* Added [`ptr_offset_by_literal`] to `pedantic` + [#15606](https://github.com/rust-lang/rust-clippy/pull/15606) +* Added [`decimal_bitwise_operands`] to `pedantic` + [#15215](https://github.com/rust-lang/rust-clippy/pull/15215) + +### Moves and Deprecations + +* Moved [`multiple_bound_locations`] from `suspicious` to `style` + [#16302](https://github.com/rust-lang/rust-clippy/pull/16302) +* Moved [`collapsible_else_if`] from `style` to `pedantic` + [#16211](https://github.com/rust-lang/rust-clippy/pull/16211) +* Moved [`needless_type_cast`] from `pedantic` to `nursery` + [#16246](https://github.com/rust-lang/rust-clippy/pull/16246) + +### Enhancements + +* [`never_loop`] do not consider `return` as preventing the iterator from looping; lint diverging + iterator reduction closures like `for_each` and `fold` + [#16364](https://github.com/rust-lang/rust-clippy/pull/16364) +* [`single_range_in_vec_init`] don't apply the suggestion automatically + [#16365](https://github.com/rust-lang/rust-clippy/pull/16365) +* [`useless_conversion`] refine `.into_iter()` suggestions to stop at final target type + [#16238](https://github.com/rust-lang/rust-clippy/pull/16238) +* Multiple lints fix wrongly unmangled macros + [#16337](https://github.com/rust-lang/rust-clippy/pull/16337) +* [`large_stack_arrays`] do not warn for libtest harness + [#16347](https://github.com/rust-lang/rust-clippy/pull/16347) +* [`derive_ord_xor_partial_ord`] allow `expect` on `impl` block + [#16303](https://github.com/rust-lang/rust-clippy/pull/16303) +* [`match_bool`] restrict to 2 arms + [#16333](https://github.com/rust-lang/rust-clippy/pull/16333) +* [`multiple_inherent_impl`] fix false negatives for generic impl blocks + [#16284](https://github.com/rust-lang/rust-clippy/pull/16284) +* [`unnecessary_fold`] warn about semantics change and lint `Add::add`/`Mul::mul` folds + [#16324](https://github.com/rust-lang/rust-clippy/pull/16324) +* [`transmuting_null`] check const blocks and const integer casts + [#16260](https://github.com/rust-lang/rust-clippy/pull/16260) +* [`needless_pass_by_ref_mut`] preserve user-provided lifetime information + [#16273](https://github.com/rust-lang/rust-clippy/pull/16273) +* [`while_let_on_iterator`] use reborrow for non-`Sized` trait references + [#16100](https://github.com/rust-lang/rust-clippy/pull/16100) +* [`collapsible_else_if`] prevent emitting when arms only `if {..} else {..}` + [#16286](https://github.com/rust-lang/rust-clippy/pull/16286) +* [`multiple_unsafe_ops_per_block`] count only towards innermost unsafe block + [#16117](https://github.com/rust-lang/rust-clippy/pull/16117) +* [`manual_saturating_arithmetic`] lint `x.checked_sub(y).unwrap_or_default()` + [#15845](https://github.com/rust-lang/rust-clippy/pull/15845) +* [`transmute_ptr_to_ref`] handle pointer in struct + [#15948](https://github.com/rust-lang/rust-clippy/pull/15948) +* [`disallowed_methods`] skip compiler-generated code + [#16186](https://github.com/rust-lang/rust-clippy/pull/16186) +* [`missing_enforced_import_renames`] do not enforce for "as _" + [#16352](https://github.com/rust-lang/rust-clippy/pull/16352) + +### False Positive Fixes + +* [`double_parens`] fix FP on macro repetition patterns + [#16301](https://github.com/rust-lang/rust-clippy/pull/16301) +* [`assertions_on_constants`] fix false positive when there is non-constant value in condition expr + [#16297](https://github.com/rust-lang/rust-clippy/pull/16297) +* [`use_self`] fix FP on type in const generics + [#16172](https://github.com/rust-lang/rust-clippy/pull/16172) +* [`set_contains_or_insert`] fix FP when set is mutated before `insert` + [#16009](https://github.com/rust-lang/rust-clippy/pull/16009) +* [`if_then_some_else_none`] fix FP when then block contains `await` + [#16178](https://github.com/rust-lang/rust-clippy/pull/16178) +* [`match_like_matches_macro`] fix FP with guards containing `if let` + [#15876](https://github.com/rust-lang/rust-clippy/pull/15876) +* [`tuple_array_conversions`] fix FP when binded vars are used before conversion + [#16197](https://github.com/rust-lang/rust-clippy/pull/16197) +* [`map_entry`] fix FP when it would cause `MutexGuard` to be held across await + [#16199](https://github.com/rust-lang/rust-clippy/pull/16199) +* [`panicking_unwrap`] fix FP on field access with implicit deref + [#16196](https://github.com/rust-lang/rust-clippy/pull/16196) +* [`large_stack_frames`] fix FP on compiler generated targets + [#15101](https://github.com/rust-lang/rust-clippy/pull/15101) + +### ICE Fixes + +* [`needless_type_cast`] do not ICE on struct constructor + [#16245](https://github.com/rust-lang/rust-clippy/pull/16245) ### New Lints diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index 75761de4ae73..151f9c956c6d 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -691,7 +691,7 @@ /// const SIZE: usize = 15; /// let arr: [u8; SIZE] = [0; SIZE]; /// ``` - #[clippy::version = "1.93.0"] + #[clippy::version = "1.94.0"] pub NEEDLESS_TYPE_CAST, nursery, "binding defined with one type but always cast to another" diff --git a/clippy_lints/src/manual_ilog2.rs b/clippy_lints/src/manual_ilog2.rs index 7c397bd3f5e8..2c368f15d670 100644 --- a/clippy_lints/src/manual_ilog2.rs +++ b/clippy_lints/src/manual_ilog2.rs @@ -31,7 +31,7 @@ /// let log = x.ilog2(); /// let log = x.ilog2(); /// ``` - #[clippy::version = "1.93.0"] + #[clippy::version = "1.94.0"] pub MANUAL_ILOG2, pedantic, "manually reimplementing `ilog2`" diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 0ed166f8dd5b..48ab42d587c8 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -3030,7 +3030,7 @@ /// ptr.sub(8); /// } /// ``` - #[clippy::version = "1.92.0"] + #[clippy::version = "1.94.0"] pub PTR_OFFSET_BY_LITERAL, pedantic, "unneeded pointer offset" diff --git a/clippy_lints/src/operators/mod.rs b/clippy_lints/src/operators/mod.rs index 567443b8e86d..b4519d654722 100644 --- a/clippy_lints/src/operators/mod.rs +++ b/clippy_lints/src/operators/mod.rs @@ -221,7 +221,7 @@ /// ```rust,no_run /// let a = 0b1110 & 0b0110; /// ``` - #[clippy::version = "1.93.0"] + #[clippy::version = "1.94.0"] pub DECIMAL_BITWISE_OPERANDS, pedantic, "use binary, hex, or octal literals for bitwise operations" diff --git a/clippy_lints/src/same_length_and_capacity.rs b/clippy_lints/src/same_length_and_capacity.rs index b200fd1fe25f..ebf649c24307 100644 --- a/clippy_lints/src/same_length_and_capacity.rs +++ b/clippy_lints/src/same_length_and_capacity.rs @@ -65,7 +65,7 @@ /// // This time, leverage the previously saved capacity: /// let reconstructed = unsafe { Vec::from_raw_parts(ptr, len, cap) }; /// ``` - #[clippy::version = "1.93.0"] + #[clippy::version = "1.94.0"] pub SAME_LENGTH_AND_CAPACITY, pedantic, "`from_raw_parts` with same length and capacity" From b4d244543239d6be118a3640f1691ab9fbc19cb7 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 5 Mar 2026 17:18:20 +0100 Subject: [PATCH 022/128] Merge commit 'e645f93552c3926a0bb481a777df120b7bce986f' --- .github/workflows/clippy_changelog.yml | 3 +- Cargo.toml | 2 +- book/src/development/the_team.md | 2 +- clippy_config/Cargo.toml | 2 +- clippy_dev/src/edit_lints.rs | 11 +- clippy_dev/src/fmt.rs | 30 +- clippy_dev/src/generate.rs | 316 + clippy_dev/src/lib.rs | 3 +- clippy_dev/src/main.rs | 7 +- clippy_dev/src/parse.rs | 187 +- clippy_dev/src/parse/cursor.rs | 86 +- clippy_dev/src/update_lints.rs | 204 - clippy_dev/src/utils.rs | 27 + clippy_lints/Cargo.toml | 2 +- clippy_lints/src/absolute_paths.rs | 1 + clippy_lints/src/almost_complete_range.rs | 1 + clippy_lints/src/approx_const.rs | 4 +- clippy_lints/src/arc_with_non_send_sync.rs | 1 + clippy_lints/src/asm_syntax.rs | 96 +- clippy_lints/src/assertions_on_constants.rs | 1 + clippy_lints/src/assigning_clones.rs | 4 +- clippy_lints/src/attrs/mod.rs | 623 +- clippy_lints/src/await_holding_invalid.rs | 80 +- clippy_lints/src/bool_to_int_with_if.rs | 1 + clippy_lints/src/booleans.rs | 4 +- clippy_lints/src/box_default.rs | 2 +- clippy_lints/src/byte_char_slices.rs | 1 + clippy_lints/src/cargo/mod.rs | 254 +- clippy_lints/src/casts/mod.rs | 1010 +-- clippy_lints/src/checked_conversions.rs | 4 +- clippy_lints/src/cloned_ref_to_slice_refs.rs | 4 +- clippy_lints/src/coerce_container_to_any.rs | 1 + clippy_lints/src/cognitive_complexity.rs | 4 +- clippy_lints/src/collapsible_if.rs | 68 +- clippy_lints/src/collection_is_never_read.rs | 3 +- clippy_lints/src/crate_in_macro_def.rs | 1 + clippy_lints/src/dbg_macro.rs | 6 +- clippy_lints/src/default.rs | 4 +- .../src/default_constructed_unit_structs.rs | 5 +- .../src/default_instead_of_iter_empty.rs | 1 + .../src/default_union_representation.rs | 1 + clippy_lints/src/dereference.rs | 52 +- clippy_lints/src/derivable_impls.rs | 4 +- clippy_lints/src/derive/mod.rs | 128 +- clippy_lints/src/disallowed_fields.rs | 4 +- clippy_lints/src/disallowed_macros.rs | 4 +- clippy_lints/src/disallowed_methods.rs | 4 +- clippy_lints/src/disallowed_names.rs | 4 +- clippy_lints/src/disallowed_script_idents.rs | 4 +- clippy_lints/src/disallowed_types.rs | 4 +- .../doc/doc_paragraphs_missing_punctuation.rs | 22 +- clippy_lints/src/doc/mod.rs | 832 +- clippy_lints/src/drop_forget_ref.rs | 8 +- clippy_lints/src/duplicate_mod.rs | 4 +- clippy_lints/src/empty_drop.rs | 1 + clippy_lints/src/empty_line_after.rs | 80 +- clippy_lints/src/empty_with_brackets.rs | 63 +- clippy_lints/src/endian_bytes.rs | 42 +- clippy_lints/src/entry.rs | 5 +- clippy_lints/src/error_impl_error.rs | 1 + clippy_lints/src/escape.rs | 4 +- clippy_lints/src/eta_reduction.rs | 29 +- clippy_lints/src/excessive_bools.rs | 81 +- clippy_lints/src/excessive_nesting.rs | 1 + clippy_lints/src/explicit_write.rs | 4 +- .../src/extra_unused_type_parameters.rs | 4 +- .../src/field_scoped_visibility_modifiers.rs | 4 +- clippy_lints/src/float_literal.rs | 6 +- .../src/floating_point_arithmetic/mod.rs | 5 +- .../src/floating_point_arithmetic/mul_add.rs | 159 +- clippy_lints/src/format.rs | 4 +- clippy_lints/src/format_args.rs | 136 +- clippy_lints/src/format_impl.rs | 82 +- clippy_lints/src/format_push_string.rs | 1 + clippy_lints/src/formatting.rs | 138 +- clippy_lints/src/four_forward_slashes.rs | 1 + clippy_lints/src/from_over_into.rs | 4 +- clippy_lints/src/from_raw_with_void_ptr.rs | 1 + clippy_lints/src/functions/mod.rs | 552 +- clippy_lints/src/functions/must_use.rs | 2 +- clippy_lints/src/if_then_some_else_none.rs | 4 +- clippy_lints/src/ifs/mod.rs | 124 +- clippy_lints/src/ignored_unit_patterns.rs | 1 + .../impl_hash_with_borrow_str_and_bytes.rs | 4 +- clippy_lints/src/implicit_saturating_add.rs | 1 + clippy_lints/src/implicit_saturating_sub.rs | 7 +- clippy_lints/src/implied_bounds_in_impls.rs | 1 + clippy_lints/src/incompatible_msrv.rs | 4 +- .../src/inconsistent_struct_constructor.rs | 6 +- clippy_lints/src/index_refutable_slice.rs | 4 +- clippy_lints/src/indexing_slicing.rs | 60 +- clippy_lints/src/infallible_try_from.rs | 1 + clippy_lints/src/inherent_to_string.rs | 5 +- clippy_lints/src/item_name_repetitions.rs | 75 +- clippy_lints/src/iter_without_into_iter.rs | 93 +- clippy_lints/src/large_const_arrays.rs | 4 +- clippy_lints/src/large_enum_variant.rs | 4 +- clippy_lints/src/large_futures.rs | 4 +- clippy_lints/src/large_include_file.rs | 4 +- clippy_lints/src/large_stack_arrays.rs | 6 +- clippy_lints/src/large_stack_frames.rs | 4 +- clippy_lints/src/legacy_numeric_constants.rs | 9 +- clippy_lints/src/len_zero.rs | 74 +- clippy_lints/src/let_underscore.rs | 73 +- clippy_lints/src/let_with_type_underscore.rs | 1 + clippy_lints/src/lifetimes.rs | 82 +- clippy_lints/src/literal_representation.rs | 160 +- .../literal_string_with_formatting_args.rs | 4 +- .../src/loops/explicit_counter_loop.rs | 133 +- clippy_lints/src/loops/infinite_loop.rs | 12 +- .../src/loops/manual_while_let_some.rs | 2 +- clippy_lints/src/loops/mod.rs | 1004 +-- clippy_lints/src/macro_metavars_in_unsafe.rs | 1 + clippy_lints/src/macro_use.rs | 4 +- clippy_lints/src/main_recursion.rs | 4 +- clippy_lints/src/manual_bits.rs | 6 +- clippy_lints/src/manual_checked_ops.rs | 1 + clippy_lints/src/manual_clamp.rs | 1 + clippy_lints/src/manual_float_methods.rs | 50 +- clippy_lints/src/manual_hash_one.rs | 4 +- clippy_lints/src/manual_ilog2.rs | 4 +- clippy_lints/src/manual_is_ascii_check.rs | 1 + clippy_lints/src/manual_is_power_of_two.rs | 4 +- clippy_lints/src/manual_main_separator_str.rs | 4 +- clippy_lints/src/manual_non_exhaustive.rs | 4 +- clippy_lints/src/manual_option_as_slice.rs | 4 +- clippy_lints/src/manual_range_patterns.rs | 1 + clippy_lints/src/manual_rem_euclid.rs | 4 +- clippy_lints/src/manual_retain.rs | 6 +- .../src/manual_slice_size_calculation.rs | 1 + clippy_lints/src/manual_string_new.rs | 1 + clippy_lints/src/manual_strip.rs | 4 +- clippy_lints/src/manual_take.rs | 5 +- clippy_lints/src/matches/mod.rs | 1682 ++-- clippy_lints/src/mem_replace.rs | 62 +- clippy_lints/src/methods/clear_with_drain.rs | 2 +- clippy_lints/src/methods/filetype_is_file.rs | 2 +- clippy_lints/src/methods/filter_next.rs | 2 +- .../methods/from_iter_instead_of_collect.rs | 2 +- clippy_lints/src/methods/get_unwrap.rs | 2 +- .../src/methods/iter_out_of_bounds.rs | 2 +- .../src/methods/join_absolute_paths.rs | 2 +- clippy_lints/src/methods/mod.rs | 7192 +++++++++-------- .../src/methods/option_as_ref_deref.rs | 2 +- clippy_lints/src/methods/or_fun_call.rs | 3 +- .../src/methods/readonly_write_lock.rs | 5 +- .../methods/suspicious_command_arg_space.rs | 3 +- .../src/methods/unwrap_expect_used.rs | 80 +- clippy_lints/src/min_ident_chars.rs | 1 + clippy_lints/src/misc.rs | 44 +- clippy_lints/src/misc_early/mod.rs | 340 +- .../src/mismatching_type_param_order.rs | 1 + .../src/missing_asserts_for_indexing.rs | 1 + .../src/missing_const_for_thread_local.rs | 4 +- clippy_lints/src/missing_doc.rs | 4 +- .../src/missing_enforced_import_rename.rs | 4 +- clippy_lints/src/missing_fields_in_debug.rs | 1 + clippy_lints/src/missing_inline.rs | 4 +- clippy_lints/src/missing_trait_methods.rs | 1 + .../src/mixed_read_write_in_expression.rs | 61 +- .../src/multiple_unsafe_ops_per_block.rs | 1 + clippy_lints/src/mut_key.rs | 6 +- clippy_lints/src/needless_bool.rs | 1 + .../src/needless_borrows_for_generic_args.rs | 6 +- clippy_lints/src/needless_else.rs | 1 + clippy_lints/src/needless_ifs.rs | 1 + clippy_lints/src/needless_late_init.rs | 1 + clippy_lints/src/needless_maybe_sized.rs | 1 + .../src/needless_parens_on_range_literals.rs | 4 +- clippy_lints/src/needless_pass_by_ref_mut.rs | 4 +- clippy_lints/src/new_without_default.rs | 4 +- clippy_lints/src/no_effect.rs | 8 +- clippy_lints/src/no_mangle_with_rust_abi.rs | 1 + clippy_lints/src/non_canonical_impls.rs | 7 +- clippy_lints/src/non_copy_const.rs | 117 +- clippy_lints/src/non_expressive_names.rs | 86 +- .../src/non_send_fields_in_send_ty.rs | 4 +- clippy_lints/src/non_std_lazy_statics.rs | 4 +- clippy_lints/src/nonstandard_macro_braces.rs | 4 +- clippy_lints/src/only_used_in_recursion.rs | 6 +- .../src/operators/arithmetic_side_effects.rs | 3 +- clippy_lints/src/operators/cmp_owned.rs | 6 +- clippy_lints/src/operators/mod.rs | 840 +- clippy_lints/src/panic_in_result_fn.rs | 2 +- clippy_lints/src/panic_unimplemented.rs | 36 +- clippy_lints/src/partial_pub_fields.rs | 1 + clippy_lints/src/partialeq_to_none.rs | 1 + clippy_lints/src/pass_by_ref_or_value.rs | 71 +- .../src/permissions_set_readonly_false.rs | 5 +- clippy_lints/src/ptr/mod.rs | 74 +- clippy_lints/src/pub_underscore_fields.rs | 4 +- clippy_lints/src/pub_use.rs | 1 + clippy_lints/src/question_mark.rs | 4 +- clippy_lints/src/ranges.rs | 142 +- clippy_lints/src/raw_strings.rs | 44 +- clippy_lints/src/rc_clone_in_vec_init.rs | 1 + clippy_lints/src/read_zero_byte_vec.rs | 1 + clippy_lints/src/redundant_async_block.rs | 1 + clippy_lints/src/redundant_clone.rs | 30 +- clippy_lints/src/redundant_field_names.rs | 4 +- clippy_lints/src/redundant_locals.rs | 1 + clippy_lints/src/redundant_pub_crate.rs | 4 +- clippy_lints/src/redundant_slicing.rs | 50 +- .../src/redundant_static_lifetimes.rs | 4 +- .../src/redundant_type_annotations.rs | 1 + clippy_lints/src/ref_patterns.rs | 1 + clippy_lints/src/regex.rs | 64 +- clippy_lints/src/replace_box.rs | 4 +- .../src/reserve_after_initialization.rs | 1 + clippy_lints/src/returns/mod.rs | 6 +- clippy_lints/src/same_length_and_capacity.rs | 1 + clippy_lints/src/semicolon_block.rs | 2 + clippy_lints/src/shadow.rs | 58 +- clippy_lints/src/single_call_fn.rs | 1 + .../src/single_component_path_imports.rs | 4 +- clippy_lints/src/single_range_in_vec_init.rs | 1 + clippy_lints/src/size_of_ref.rs | 1 + clippy_lints/src/std_instead_of_core.rs | 102 +- clippy_lints/src/string_patterns.rs | 7 +- clippy_lints/src/strings.rs | 174 +- clippy_lints/src/strlen_on_c_strings.rs | 4 +- .../src/suspicious_operation_groupings.rs | 4 +- clippy_lints/src/suspicious_trait_impl.rs | 5 +- .../src/suspicious_xor_used_as_pow.rs | 1 + clippy_lints/src/swap.rs | 54 +- clippy_lints/src/swap_ptr_to_ref.rs | 1 + clippy_lints/src/temporary_assignment.rs | 4 +- clippy_lints/src/time_subtraction.rs | 7 +- clippy_lints/src/trailing_empty_array.rs | 1 + clippy_lints/src/trait_bounds.rs | 53 +- clippy_lints/src/transmute/mod.rs | 656 +- .../src/transmute/transmuting_null.rs | 2 +- clippy_lints/src/tuple_array_conversions.rs | 1 + clippy_lints/src/types/box_collection.rs | 2 +- clippy_lints/src/types/linked_list.rs | 2 +- clippy_lints/src/types/mod.rs | 330 +- clippy_lints/src/types/owned_cow.rs | 2 +- clippy_lints/src/types/rc_buffer.rs | 2 +- clippy_lints/src/unconditional_recursion.rs | 6 +- .../src/undocumented_unsafe_blocks.rs | 8 +- clippy_lints/src/unicode.rs | 6 +- clippy_lints/src/unit_types/mod.rs | 44 +- clippy_lints/src/unnecessary_box_returns.rs | 4 +- .../src/unnecessary_map_on_constructor.rs | 5 +- .../src/unnecessary_owned_empty_strings.rs | 5 +- clippy_lints/src/unnecessary_semicolon.rs | 4 +- .../src/unnecessary_struct_initialization.rs | 1 + clippy_lints/src/unnecessary_wraps.rs | 4 +- clippy_lints/src/unnested_or_patterns.rs | 4 +- clippy_lints/src/unused_async.rs | 4 +- clippy_lints/src/unused_result_ok.rs | 1 + clippy_lints/src/unused_rounding.rs | 1 + clippy_lints/src/unused_self.rs | 4 +- clippy_lints/src/unused_trait_names.rs | 4 +- clippy_lints/src/unwrap.rs | 56 +- clippy_lints/src/unwrap_in_result.rs | 2 +- clippy_lints/src/upper_case_acronyms.rs | 4 +- clippy_lints/src/use_self.rs | 4 +- clippy_lints/src/useless_conversion.rs | 4 +- clippy_lints/src/visibility.rs | 9 +- clippy_lints/src/volatile_composites.rs | 1 + clippy_lints/src/wildcard_imports.rs | 4 +- clippy_lints/src/write/mod.rs | 182 +- clippy_lints/src/zombie_processes.rs | 1 + .../derive_deserialize_allowing_unknown.rs | 6 +- .../src/repeated_is_diagnostic_item.rs | 6 +- clippy_lints_internal/src/symbols.rs | 2 +- clippy_utils/Cargo.toml | 2 +- clippy_utils/README.md | 2 +- clippy_utils/src/hir_utils.rs | 69 +- clippy_utils/src/macros.rs | 1 + clippy_utils/src/mir/mod.rs | 63 +- clippy_utils/src/sym.rs | 2 - declare_clippy_lint/Cargo.toml | 2 +- rust-toolchain.toml | 2 +- tests/compile-test.rs | 9 +- tests/test_utils/mod.rs | 7 +- tests/ui/cmp_owned/with_suggestion.fixed | 8 + tests/ui/cmp_owned/with_suggestion.rs | 8 + tests/ui/cmp_owned/with_suggestion.stderr | 8 +- ...oc_paragraphs_missing_punctuation_emoji.rs | 12 + ...aragraphs_missing_punctuation_emoji.stderr | 11 + tests/ui/eta.fixed | 9 + tests/ui/eta.rs | 9 + tests/ui/eta.stderr | 8 +- tests/ui/explicit_counter_loop.rs | 15 + tests/ui/floating_point_mul_add.fixed | 12 + tests/ui/floating_point_mul_add.rs | 12 + tests/ui/floating_point_mul_add.stderr | 14 +- tests/ui/if_same_then_else.rs | 17 + tests/ui/infinite_loops.rs | 53 + tests/ui/infinite_loops.stderr | 131 +- tests/ui/must_use_candidates.fixed | 14 +- tests/ui/must_use_candidates.stderr | 14 +- ...gle_component_path_imports_nested_first.rs | 1 + ...component_path_imports_nested_first.stderr | 4 +- tests/ui/uninlined_format_args.fixed | 21 + tests/ui/uninlined_format_args.rs | 21 + ...nlined_format_args_panic.edition2018.fixed | 34 +- ...lined_format_args_panic.edition2018.stderr | 2 +- ...nlined_format_args_panic.edition2021.fixed | 34 +- ...lined_format_args_panic.edition2021.stderr | 50 +- ...nlined_format_args_panic.edition2024.fixed | 70 + ...lined_format_args_panic.edition2024.stderr | 112 + tests/ui/uninlined_format_args_panic.rs | 34 +- triagebot.toml | 25 + 306 files changed, 11449 insertions(+), 10159 deletions(-) create mode 100644 clippy_dev/src/generate.rs delete mode 100644 clippy_dev/src/update_lints.rs create mode 100644 tests/ui/doc/doc_paragraphs_missing_punctuation_emoji.rs create mode 100644 tests/ui/doc/doc_paragraphs_missing_punctuation_emoji.stderr create mode 100644 tests/ui/uninlined_format_args_panic.edition2024.fixed create mode 100644 tests/ui/uninlined_format_args_panic.edition2024.stderr diff --git a/.github/workflows/clippy_changelog.yml b/.github/workflows/clippy_changelog.yml index 4d84d6b6dae4..121e1b84543f 100644 --- a/.github/workflows/clippy_changelog.yml +++ b/.github/workflows/clippy_changelog.yml @@ -20,7 +20,8 @@ jobs: - name: Check Changelog if: ${{ github.event_name == 'pull_request' }} run: | - if [[ -z $(grep -oP 'changelog: *\K\S+' <<< "$PR_BODY") ]]; then + # Checks that the PR body contains a changelog entry, ignoring the placeholder from the PR template. + if [[ -z $(grep -oP 'changelog: *\K(?!\[`lint_name`\])\S+' <<< "$PR_BODY") ]]; then echo "::error::Pull request message must contain 'changelog: ...' with your changelog. Please add it." exit 1 fi diff --git a/Cargo.toml b/Cargo.toml index 8f5ef9ca2f8f..bcd800930c51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy" -version = "0.1.95" +version = "0.1.96" description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" diff --git a/book/src/development/the_team.md b/book/src/development/the_team.md index d22123231868..a663158c949b 100644 --- a/book/src/development/the_team.md +++ b/book/src/development/the_team.md @@ -51,7 +51,7 @@ this group to help with triaging, which can include: busy or wants to abandon it. If the reviewer is busy, the PR can be reassigned to someone else. - Checkout: https://triage.rust-lang.org/triage/rust-lang/rust-clippy to + Checkout: to monitor PRs. While not part of their duties, contributors are encouraged to review PRs diff --git a/clippy_config/Cargo.toml b/clippy_config/Cargo.toml index da5166392b4e..366c776b8f1a 100644 --- a/clippy_config/Cargo.toml +++ b/clippy_config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_config" -version = "0.1.95" +version = "0.1.96" edition = "2024" publish = false diff --git a/clippy_dev/src/edit_lints.rs b/clippy_dev/src/edit_lints.rs index 411bf530b22b..70c096783af8 100644 --- a/clippy_dev/src/edit_lints.rs +++ b/clippy_dev/src/edit_lints.rs @@ -1,6 +1,5 @@ use crate::parse::cursor::{self, Capture, Cursor}; use crate::parse::{ActiveLint, DeprecatedLint, Lint, LintData, LintName, ParseCx, RenamedLint}; -use crate::update_lints::generate_lint_files; use crate::utils::{ ErrAction, FileUpdater, UpdateMode, UpdateStatus, Version, delete_dir_if_exists, delete_file_if_exists, expect_action, try_rename_dir, try_rename_file, walk_dir_no_dot_or_target, @@ -40,7 +39,7 @@ pub fn deprecate<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, name }; remove_lint_declaration(name, &prev_lint, &data, &mut FileUpdater::default()); - generate_lint_files(UpdateMode::Change, &data); + data.gen_decls(UpdateMode::Change); println!("info: `{name}` has successfully been deprecated"); println!("note: you must run `cargo uitest` to update the test results"); } @@ -74,7 +73,7 @@ pub fn uplift<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_nam updater.update_file(e.path(), &mut update_fn); } } - generate_lint_files(UpdateMode::Change, &data); + data.gen_decls(UpdateMode::Change); println!("info: `{old_name}` has successfully been uplifted as `{new_name}`"); println!("note: you must run `cargo uitest` to update the test results"); } @@ -151,7 +150,7 @@ pub fn rename<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_nam updater.update_file(e.path(), &mut update_fn); } } - generate_lint_files(UpdateMode::Change, &data); + data.gen_decls(UpdateMode::Change); println!("Renamed `{old_name}` to `{new_name}`"); println!("All code referencing the old name has been updated"); @@ -172,11 +171,11 @@ fn remove_lint_declaration(name: &str, lint: &ActiveLint<'_>, data: &LintData<'_ delete_file_if_exists(lint.path.as_ref()) } else { updater.update_file(&lint.path, &mut |_, src, dst| -> UpdateStatus { - let mut start = &src[..lint.declaration_range.start]; + let mut start = &src[..lint.declaration_range.start as usize]; if start.ends_with("\n\n") { start = &start[..start.len() - 1]; } - let mut end = &src[lint.declaration_range.end..]; + let mut end = &src[lint.declaration_range.end as usize..]; if end.starts_with("\n\n") { end = &end[1..]; } diff --git a/clippy_dev/src/fmt.rs b/clippy_dev/src/fmt.rs index 781e37e6144e..d13f966b19c3 100644 --- a/clippy_dev/src/fmt.rs +++ b/clippy_dev/src/fmt.rs @@ -1,3 +1,6 @@ +use crate::generate::gen_sorted_lints_file; +use crate::new_parse_cx; +use crate::parse::VecBuf; use crate::utils::{ ErrAction, FileUpdater, UpdateMode, UpdateStatus, expect_action, run_with_output, split_args_for_threads, walk_dir_no_dot_or_target, @@ -326,10 +329,35 @@ fn run_rustfmt(update_mode: UpdateMode) { // the "main" function of cargo dev fmt pub fn run(update_mode: UpdateMode) { - run_rustfmt(update_mode); fmt_syms(update_mode); if let Err(e) = fmt_conf(update_mode.is_check()) { e.display(); process::exit(1); } + + new_parse_cx(|cx| { + let mut data = cx.parse_lint_decls(); + let (mut lints, passes) = data.split_by_lint_file(); + let mut updater = FileUpdater::default(); + let mut ranges = VecBuf::with_capacity(256); + + for passes in passes { + let path = passes[0].path.clone(); + let mut lints = lints.remove(&*path); + let lints = lints.as_deref_mut().unwrap_or_default(); + updater.update_file_checked("cargo dev fmt", update_mode, &path, &mut |_, src, dst| { + gen_sorted_lints_file(src, dst, lints, passes, &mut ranges); + UpdateStatus::from_changed(src != dst) + }); + } + + for (&path, lints) in &mut lints { + updater.update_file_checked("cargo dev fmt", update_mode, path, &mut |_, src, dst| { + gen_sorted_lints_file(src, dst, lints, &mut [], &mut ranges); + UpdateStatus::from_changed(src != dst) + }); + } + }); + + run_rustfmt(update_mode); } diff --git a/clippy_dev/src/generate.rs b/clippy_dev/src/generate.rs new file mode 100644 index 000000000000..24e4218716ab --- /dev/null +++ b/clippy_dev/src/generate.rs @@ -0,0 +1,316 @@ +use crate::parse::cursor::Cursor; +use crate::parse::{Lint, LintData, LintPass, VecBuf}; +use crate::utils::{FileUpdater, UpdateMode, UpdateStatus, update_text_region_fn}; +use core::range::Range; +use itertools::Itertools; +use std::collections::HashSet; +use std::fmt::Write; +use std::path::{self, Path}; + +const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev update_lints`.\n\ + // Use that command to update this file and do not edit by hand.\n\ + // Manual edits will be overwritten.\n\n"; + +const DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.html"; + +impl LintData<'_> { + #[expect(clippy::too_many_lines)] + pub fn gen_decls(&self, update_mode: UpdateMode) { + let mut updater = FileUpdater::default(); + + let mut lints: Vec<_> = self.lints.iter().map(|(&x, y)| (x, y)).collect(); + lints.sort_by_key(|&(x, _)| x); + updater.update_file_checked( + "cargo dev update_lints", + update_mode, + "CHANGELOG.md", + &mut update_text_region_fn( + "\n", + "", + |dst| { + for &(lint, _) in &lints { + writeln!(dst, "[`{lint}`]: {DOCS_LINK}#{lint}").unwrap(); + } + }, + ), + ); + + let mut active = Vec::with_capacity(lints.len()); + let mut deprecated = Vec::with_capacity(lints.len() / 8); + let mut renamed = Vec::with_capacity(lints.len() / 8); + for &(name, lint) in &lints { + match lint { + Lint::Active(lint) => active.push((name, lint)), + Lint::Deprecated(lint) => deprecated.push((name, lint)), + Lint::Renamed(lint) => renamed.push((name, lint)), + } + } + active.sort_by_key(|&(_, lint)| lint.module); + + // Round to avoid updating the readme every time a lint is added/deprecated. + let lint_count = active.len() / 50 * 50; + updater.update_file_checked( + "cargo dev update_lints", + update_mode, + "README.md", + &mut update_text_region_fn("[There are over ", " lints included in this crate!]", |dst| { + write!(dst, "{lint_count}").unwrap(); + }), + ); + updater.update_file_checked( + "cargo dev update_lints", + update_mode, + "book/src/README.md", + &mut update_text_region_fn("[There are over ", " lints included in this crate!]", |dst| { + write!(dst, "{lint_count}").unwrap(); + }), + ); + + updater.update_file_checked( + "cargo dev update_lints", + update_mode, + "clippy_lints/src/deprecated_lints.rs", + &mut |_, src, dst| { + let mut cursor = Cursor::new(src); + assert!( + cursor.find_ident("declare_with_version").is_some() + && cursor.find_ident("declare_with_version").is_some(), + "error reading deprecated lints" + ); + dst.push_str(&src[..cursor.pos() as usize]); + dst.push_str("! { DEPRECATED(DEPRECATED_VERSION) = [\n"); + for &(name, data) in &deprecated { + write!( + dst, + " #[clippy::version = \"{}\"]\n (\"clippy::{name}\", \"{}\"),\n", + data.version, data.reason, + ) + .unwrap(); + } + dst.push_str( + "]}\n\n\ + #[rustfmt::skip]\n\ + declare_with_version! { RENAMED(RENAMED_VERSION) = [\n\ + ", + ); + for &(name, data) in &renamed { + write!( + dst, + " #[clippy::version = \"{}\"]\n (\"clippy::{name}\", \"{}\"),\n", + data.version, data.new_name, + ) + .unwrap(); + } + dst.push_str("]}\n"); + UpdateStatus::from_changed(src != dst) + }, + ); + updater.update_file_checked( + "cargo dev update_lints", + update_mode, + "tests/ui/deprecated.rs", + &mut |_, src, dst| { + dst.push_str(GENERATED_FILE_COMMENT); + for &(lint, _) in &deprecated { + writeln!(dst, "#![warn(clippy::{lint})] //~ ERROR: lint `clippy::{lint}`").unwrap(); + } + dst.push_str("\nfn main() {}\n"); + UpdateStatus::from_changed(src != dst) + }, + ); + updater.update_file_checked( + "cargo dev update_lints", + update_mode, + "tests/ui/rename.rs", + &mut move |_, src, dst| { + let mut seen_lints = HashSet::new(); + dst.push_str(GENERATED_FILE_COMMENT); + dst.push_str("#![allow(clippy::duplicated_attributes)]\n"); + for &(_, lint) in &renamed { + if seen_lints.insert(lint.new_name) { + writeln!(dst, "#![allow({})]", lint.new_name).unwrap(); + } + } + for &(lint, _) in &renamed { + writeln!(dst, "#![warn(clippy::{lint})] //~ ERROR: lint `clippy::{lint}`").unwrap(); + } + dst.push_str("\nfn main() {}\n"); + UpdateStatus::from_changed(src != dst) + }, + ); + for (crate_name, lints) in active.iter().copied().into_group_map_by(|&(_, lint)| { + let Some(path::Component::Normal(name)) = lint.path.components().next() else { + // All paths should start with `{crate_name}/src` when parsed from `find_lint_decls` + panic!( + "internal error: can't read crate name from path `{}`", + lint.path.display() + ); + }; + name + }) { + updater.update_file_checked( + "cargo dev update_lints", + update_mode, + Path::new(crate_name).join("src/lib.rs"), + &mut update_text_region_fn( + "// begin lints modules, do not remove this comment, it's used in `update_lints`\n", + "// end lints modules, do not remove this comment, it's used in `update_lints`", + |dst| { + let mut prev = ""; + for &(_, lint) in &lints { + if lint.module != prev { + writeln!(dst, "mod {};", lint.module).unwrap(); + prev = lint.module; + } + } + }, + ), + ); + updater.update_file_checked( + "cargo dev update_lints", + update_mode, + Path::new(crate_name).join("src/declared_lints.rs"), + &mut |_, src, dst| { + dst.push_str(GENERATED_FILE_COMMENT); + dst.push_str("pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[\n"); + let mut buf = String::new(); + for &(name, lint) in &lints { + buf.clear(); + buf.push_str(name); + buf.make_ascii_uppercase(); + if lint.module.is_empty() { + writeln!(dst, " crate::{buf}_INFO,").unwrap(); + } else { + writeln!(dst, " crate::{}::{buf}_INFO,", lint.module).unwrap(); + } + } + dst.push_str("];\n"); + UpdateStatus::from_changed(src != dst) + }, + ); + } + } +} + +impl LintPass<'_> { + pub fn gen_mac(&self, dst: &mut String) { + let mut line_start = dst.len(); + dst.extend([self.mac.name(), "!("]); + let has_docs = write_comment_lines(self.docs, "\n ", dst); + let (list_indent, list_multi_end, end) = if has_docs { + dst.push_str("\n "); + line_start = dst.len() - 4; + (" ", "\n ", "]\n);") + } else { + (" ", "\n", "]);") + }; + dst.push_str(self.name); + if let Some(lt) = self.lt { + dst.extend(["<", lt, ">"]); + } + dst.push_str(" => ["); + let fmt = write_list( + self.lints.iter().copied(), + 80usize.saturating_sub(dst.len() - line_start), + list_indent, + dst, + ); + if matches!(fmt, ListFmt::MultiLine) { + dst.push_str(list_multi_end); + } + dst.push_str(end); + } +} + +fn write_comment_lines(s: &str, prefix: &str, dst: &mut String) -> bool { + let mut has_doc = false; + for line in s.split('\n') { + let line = line.trim_start(); + if !line.is_empty() { + has_doc = true; + dst.extend([prefix, line]); + } + } + has_doc +} + +#[derive(Clone, Copy)] +enum ListFmt { + SingleLine, + MultiLine, +} + +fn write_list<'a>( + items: impl Iterator + Clone, + single_line_limit: usize, + indent: &str, + dst: &mut String, +) -> ListFmt { + let len = items.clone().map(str::len).sum::(); + if len > single_line_limit { + for item in items { + dst.extend(["\n", indent, item, ","]); + } + ListFmt::MultiLine + } else { + let _ = write!(dst, "{}", items.format(", ")); + ListFmt::SingleLine + } +} + +/// Generates the contents of a lint's source file with all the lint and lint pass +/// declarations sorted. +pub fn gen_sorted_lints_file( + src: &str, + dst: &mut String, + lints: &mut [(&str, Range)], + passes: &mut [LintPass<'_>], + ranges: &mut VecBuf>, +) { + ranges.with(|ranges| { + ranges.extend(lints.iter().map(|&(_, x)| x)); + ranges.extend(passes.iter().map(|x| x.decl_range)); + ranges.sort_unstable_by_key(|x| x.start); + + lints.sort_unstable_by_key(|&(x, _)| x); + passes.sort_by_key(|x| x.name); + + let mut ranges = ranges.iter(); + let pos = if let Some(range) = ranges.next() { + dst.push_str(&src[..range.start as usize]); + for &(_, range) in &*lints { + dst.push_str(&src[range.start as usize..range.end as usize]); + dst.push_str("\n\n"); + } + for pass in passes { + pass.gen_mac(dst); + dst.push_str("\n\n"); + } + range.end + } else { + dst.push_str(src); + return; + }; + + let pos = ranges.fold(pos, |start, range| { + let s = &src[start as usize..range.start as usize]; + dst.push_str(if s.trim_start().is_empty() { + // Only whitespace between this and the previous item. No need to keep that. + "" + } else if src[..pos as usize].ends_with("\n\n") + && let Some(s) = s.strip_prefix("\n\n") + { + // Empty line before and after. Remove one of them. + s + } else { + // Remove only full lines unless something is in the way. + s.strip_prefix('\n').unwrap_or(s) + }); + range.end + }); + + // Since we always generate an empty line at the end, make sure to always skip it. + let s = &src[pos as usize..]; + dst.push_str(s.strip_prefix('\n').map_or(s, |s| s.strip_prefix('\n').unwrap_or(s))); + }); +} diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs index cff51d34c30e..359bf17c68c5 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -1,6 +1,5 @@ #![feature( exit_status_error, - if_let_guard, new_range, new_range_api, os_str_slice, @@ -33,8 +32,8 @@ pub mod serve; pub mod setup; pub mod sync; -pub mod update_lints; +mod generate; mod parse; mod utils; diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index 8dc2290df8e4..5fe2295a1e21 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -5,7 +5,6 @@ use clap::{Args, Parser, Subcommand}; use clippy_dev::{ ClippyInfo, UpdateMode, dogfood, edit_lints, fmt, lint, new_lint, new_parse_cx, release, serve, setup, sync, - update_lints, }; use std::env; @@ -27,7 +26,9 @@ fn main() { allow_no_vcs, } => dogfood::dogfood(fix, allow_dirty, allow_staged, allow_no_vcs), DevCommand::Fmt { check } => fmt::run(UpdateMode::from_check(check)), - DevCommand::UpdateLints { check } => new_parse_cx(|cx| update_lints::update(cx, UpdateMode::from_check(check))), + DevCommand::UpdateLints { check } => { + new_parse_cx(|cx| cx.parse_lint_decls().gen_decls(UpdateMode::from_check(check))); + }, DevCommand::NewLint { pass, name, @@ -35,7 +36,7 @@ fn main() { r#type, msrv, } => match new_lint::create(clippy.version, pass, &name, &category, r#type.as_deref(), msrv) { - Ok(()) => new_parse_cx(|cx| update_lints::update(cx, UpdateMode::Change)), + Ok(()) => new_parse_cx(|cx| cx.parse_lint_decls().gen_decls(UpdateMode::Change)), Err(e) => eprintln!("Unable to create lint: {e}"), }, DevCommand::Setup(SetupCommand { subcommand }) => match subcommand { diff --git a/clippy_dev/src/parse.rs b/clippy_dev/src/parse.rs index ffb50784a9a7..d51fb25552f9 100644 --- a/clippy_dev/src/parse.rs +++ b/clippy_dev/src/parse.rs @@ -1,7 +1,7 @@ pub mod cursor; use self::cursor::{Capture, Cursor}; -use crate::utils::{ErrAction, File, Scoped, expect_action, walk_dir_no_dot_or_target}; +use crate::utils::{ErrAction, File, Scoped, expect_action, slice_groups_mut, walk_dir_no_dot_or_target}; use core::fmt::{self, Display, Write as _}; use core::range::Range; use rustc_arena::DroplessArena; @@ -13,6 +13,7 @@ pub struct ParseCxImpl<'cx> { pub arena: &'cx DroplessArena, pub str_buf: StrBuf, + pub str_list_buf: VecBuf<&'cx str>, } pub type ParseCx<'cx> = &'cx mut ParseCxImpl<'cx>; @@ -22,6 +23,7 @@ pub fn new_parse_cx<'env, T>(f: impl for<'cx> FnOnce(&'cx mut Scoped<'cx, 'env, f(&mut Scoped::new(ParseCxImpl { arena: &arena, str_buf: StrBuf::with_capacity(128), + str_list_buf: VecBuf::with_capacity(128), })) } @@ -82,6 +84,20 @@ pub fn with(&mut self, f: impl FnOnce(&mut String) -> T) -> T { } } +pub struct VecBuf(Vec); +impl VecBuf { + /// Creates a new buffer with the specified initial capacity. + pub fn with_capacity(cap: usize) -> Self { + Self(Vec::with_capacity(cap)) + } + + /// Performs an operation with the freshly cleared buffer. + pub fn with(&mut self, f: impl FnOnce(&mut Vec) -> R) -> R { + self.0.clear(); + f(&mut self.0) + } +} + #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub enum LintTool { Rustc, @@ -128,7 +144,7 @@ pub struct ActiveLint<'cx> { pub group: &'cx str, pub module: &'cx str, pub path: PathBuf, - pub declaration_range: Range, + pub declaration_range: Range, } pub struct DeprecatedLint<'cx> { @@ -147,8 +163,59 @@ pub enum Lint<'cx> { Renamed(RenamedLint<'cx>), } +#[derive(Clone, Copy)] +pub enum LintPassMac { + Declare, + Impl, +} +impl LintPassMac { + pub fn name(self) -> &'static str { + match self { + Self::Declare => "declare_lint_pass", + Self::Impl => "impl_lint_pass", + } + } +} + +pub struct LintPass<'cx> { + /// The raw text of the documentation comments. May include leading/trailing + /// whitespace and empty lines. + pub docs: &'cx str, + pub name: &'cx str, + pub lt: Option<&'cx str>, + pub mac: LintPassMac, + pub decl_range: Range, + pub lints: &'cx [&'cx str], + pub path: PathBuf, +} + pub struct LintData<'cx> { pub lints: FxHashMap<&'cx str, Lint<'cx>>, + pub lint_passes: Vec>, +} +impl<'cx> LintData<'cx> { + #[expect(clippy::type_complexity)] + pub fn split_by_lint_file<'s>( + &'s mut self, + ) -> ( + FxHashMap<&'s Path, Vec<(&'s str, Range)>>, + impl Iterator]>, + ) { + #[expect(clippy::default_trait_access)] + let mut lints = FxHashMap::with_capacity_and_hasher(500, Default::default()); + for (&name, lint) in &self.lints { + if let Lint::Active(lint) = lint { + lints + .entry(&*lint.path) + .or_insert_with(|| Vec::with_capacity(8)) + .push((name, lint.declaration_range)); + } + } + let passes = slice_groups_mut(&mut self.lint_passes, |head, tail| { + tail.iter().take_while(|&x| x.path == head.path).count() + }); + (lints, passes) + } } impl<'cx> ParseCxImpl<'cx> { @@ -158,6 +225,7 @@ pub fn parse_lint_decls(&mut self) -> LintData<'cx> { let mut data = LintData { #[expect(clippy::default_trait_access)] lints: FxHashMap::with_capacity_and_hasher(1000, Default::default()), + lint_passes: Vec::with_capacity(400), }; let mut contents = String::new(); @@ -193,7 +261,7 @@ pub fn parse_lint_decls(&mut self) -> LintData<'cx> { self.str_buf .alloc_replaced(self.arena, path, path::MAIN_SEPARATOR, "::") }; - self.parse_clippy_lint_decls( + self.parse_lint_src_file( e.path(), File::open_read_to_cleared_string(e.path(), &mut contents), module, @@ -208,11 +276,11 @@ pub fn parse_lint_decls(&mut self) -> LintData<'cx> { } /// Parse a source file looking for `declare_clippy_lint` macro invocations. - fn parse_clippy_lint_decls(&mut self, path: &Path, contents: &str, module: &'cx str, data: &mut LintData<'cx>) { + fn parse_lint_src_file(&mut self, path: &Path, contents: &str, module: &'cx str, data: &mut LintData<'cx>) { #[allow(clippy::enum_glob_use)] use cursor::Pat::*; #[rustfmt::skip] - static DECL_TOKENS: &[cursor::Pat<'_>] = &[ + static LINT_DECL_TOKENS: &[cursor::Pat<'_>] = &[ // !{ /// docs Bang, OpenBrace, AnyComment, // #[clippy::version = "version"] @@ -220,24 +288,101 @@ fn parse_clippy_lint_decls(&mut self, path: &Path, contents: &str, module: &'cx // pub NAME, GROUP, Ident("pub"), CaptureIdent, Comma, AnyComment, CaptureIdent, Comma, ]; + #[rustfmt::skip] + static PASS_DECL_TOKENS: &[cursor::Pat<'_>] = &[ + // !( NAME <'lt> => [ + Bang, OpenParen, CaptureDocLines, CaptureIdent, CaptureOptLifetimeArg, FatArrow, OpenBracket, + ]; let mut cursor = Cursor::new(contents); - let mut captures = [Capture::EMPTY; 2]; - while let Some(start) = cursor.find_ident("declare_clippy_lint") { - if cursor.match_all(DECL_TOKENS, &mut captures) && cursor.find_pat(CloseBrace) { - assert!( - data.lints - .insert( - self.str_buf.alloc_ascii_lower(self.arena, cursor.get_text(captures[0])), - Lint::Active(ActiveLint { - group: self.arena.alloc_str(cursor.get_text(captures[1])), - module, - path: path.into(), - declaration_range: start as usize..cursor.pos() as usize, - }), - ) - .is_none() - ); + let mut captures = [Capture::EMPTY; 3]; + while let Some(mac_name) = cursor.find_any_ident() { + match cursor.get_text(mac_name) { + "declare_clippy_lint" + if cursor.match_all(LINT_DECL_TOKENS, &mut captures) && cursor.find_pat(CloseBrace) => + { + assert!( + data.lints + .insert( + self.str_buf.alloc_ascii_lower(self.arena, cursor.get_text(captures[0])), + Lint::Active(ActiveLint { + group: self.arena.alloc_str(cursor.get_text(captures[1])), + module, + path: path.into(), + declaration_range: mac_name.pos..cursor.pos(), + }), + ) + .is_none() + ); + }, + mac @ ("declare_lint_pass" | "impl_lint_pass") if cursor.match_all(PASS_DECL_TOKENS, &mut captures) => { + let mac = if matches!(mac, "declare_lint_pass") { + LintPassMac::Declare + } else { + LintPassMac::Impl + }; + let docs = match cursor.get_text(captures[0]) { + "" => "", + x => self.arena.alloc_str(x), + }; + let name = self.arena.alloc_str(cursor.get_text(captures[1])); + let lt = cursor.get_text(captures[2]); + let lt = if lt.is_empty() { + None + } else { + Some(self.arena.alloc_str(lt)) + }; + + let lints = self.str_list_buf.with(|buf| { + // Parses a comma separated list of paths and converts each path + // to a string with whitespace removed. + while !cursor.match_pat(CloseBracket) { + buf.push(self.str_buf.with(|buf| { + if cursor.match_pat(DoubleColon) { + buf.push_str("::"); + } + let capture = cursor.capture_ident()?; + buf.push_str(cursor.get_text(capture)); + while cursor.match_pat(DoubleColon) { + buf.push_str("::"); + let capture = cursor.capture_ident()?; + buf.push_str(cursor.get_text(capture)); + } + Some(self.arena.alloc_str(buf)) + })?); + + if !cursor.match_pat(Comma) { + if !cursor.match_pat(CloseBracket) { + return None; + } + break; + } + } + + // The arena panics when allocating a size of zero. + Some(if buf.is_empty() { + &[] + } else { + buf.sort_unstable(); + &*self.arena.alloc_slice(buf) + }) + }); + + if let Some(lints) = lints + && cursor.match_all(&[CloseParen, Semi], &mut []) + { + data.lint_passes.push(LintPass { + docs, + name, + lt, + mac, + decl_range: mac_name.pos..cursor.pos(), + lints, + path: path.into(), + }); + } + }, + _ => {}, } } } diff --git a/clippy_dev/src/parse/cursor.rs b/clippy_dev/src/parse/cursor.rs index 2c142af4883a..af840532c27b 100644 --- a/clippy_dev/src/parse/cursor.rs +++ b/clippy_dev/src/parse/cursor.rs @@ -12,6 +12,7 @@ pub enum Pat<'a> { /// Matches any number of comments and doc comments. AnyComment, Ident(&'a str), + CaptureDocLines, CaptureIdent, LitStr, CaptureLitStr, @@ -22,12 +23,14 @@ pub enum Pat<'a> { Comma, DoubleColon, Eq, + FatArrow, Lifetime, Lt, Gt, OpenBrace, OpenBracket, OpenParen, + CaptureOptLifetimeArg, Pound, Semi, } @@ -112,6 +115,7 @@ pub fn step(&mut self) { /// /// For each capture made by the pattern one item will be taken from the capture /// sequence with the result placed inside. + #[expect(clippy::too_many_lines)] fn match_impl(&mut self, pat: Pat<'_>, captures: &mut slice::IterMut<'_, Capture>) -> bool { loop { match (pat, self.next_token.kind) { @@ -121,7 +125,6 @@ fn match_impl(&mut self, pat: Pat<'_>, captures: &mut slice::IterMut<'_, Capture Pat::AnyComment, TokenKind::BlockComment { terminated: true, .. } | TokenKind::LineComment { .. }, ) => self.step(), - (Pat::AnyComment, _) => return true, (Pat::Bang, TokenKind::Bang) | (Pat::CloseBrace, TokenKind::CloseBrace) | (Pat::CloseBracket, TokenKind::CloseBracket) @@ -152,12 +155,48 @@ fn match_impl(&mut self, pat: Pat<'_>, captures: &mut slice::IterMut<'_, Capture }, (Pat::DoubleColon, TokenKind::Colon) => { self.step(); - if !self.at_end() && matches!(self.next_token.kind, TokenKind::Colon) { + if matches!(self.next_token.kind, TokenKind::Colon) { self.step(); return true; } return false; }, + (Pat::FatArrow, TokenKind::Eq) => { + self.step(); + if matches!(self.next_token.kind, TokenKind::Gt) { + self.step(); + return true; + } + return false; + }, + (Pat::CaptureOptLifetimeArg, TokenKind::Lt) => { + self.step(); + loop { + match self.next_token.kind { + TokenKind::Lifetime { .. } => break, + TokenKind::Whitespace => self.step(), + _ => return false, + } + } + *captures.next().unwrap() = Capture { + pos: self.pos, + len: self.next_token.len, + }; + self.step(); + loop { + match self.next_token.kind { + TokenKind::Gt => break, + TokenKind::Whitespace => self.step(), + _ => return false, + } + } + self.step(); + return true; + }, + (Pat::CaptureOptLifetimeArg, _) => { + *captures.next().unwrap() = Capture { pos: 0, len: 0 }; + return true; + }, #[rustfmt::skip] ( Pat::CaptureLitStr, @@ -173,6 +212,28 @@ fn match_impl(&mut self, pat: Pat<'_>, captures: &mut slice::IterMut<'_, Capture self.step(); return true; }, + (Pat::CaptureDocLines, TokenKind::LineComment { doc_style: Some(_) }) => { + let pos = self.pos; + loop { + self.step(); + if !matches!( + self.next_token.kind, + TokenKind::Whitespace | TokenKind::LineComment { doc_style: Some(_) } + ) { + break; + } + } + *captures.next().unwrap() = Capture { + pos, + len: self.pos - pos, + }; + return true; + }, + (Pat::CaptureDocLines, _) => { + *captures.next().unwrap() = Capture::EMPTY; + return true; + }, + (Pat::AnyComment, _) => return true, _ => return false, } } @@ -219,8 +280,8 @@ pub fn find_any_ident(&mut self) -> Option { } } - /// Consume the returns the position of the next non-whitespace token if it's an - /// identifier. Returns `None` otherwise. + /// Consume the returns the position of the next non-whitespace token if it's the + /// specified identifier. Returns `None` otherwise. pub fn match_ident(&mut self, s: &str) -> Option { loop { match self.next_token.kind { @@ -235,6 +296,23 @@ pub fn match_ident(&mut self, s: &str) -> Option { } } + /// Consumes and captures the next non-whitespace token if it's an identifier. Returns + /// `None` otherwise. + pub fn capture_ident(&mut self) -> Option { + loop { + match self.next_token.kind { + TokenKind::Ident => { + let pos = self.pos; + let len = self.next_token.len; + self.step(); + return Some(Capture { pos, len }); + }, + TokenKind::Whitespace => self.step(), + _ => return None, + } + } + } + /// Continually attempt to match the pattern on subsequent tokens until a match is /// found. Returns whether the pattern was successfully matched. /// diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs deleted file mode 100644 index a4cf15058986..000000000000 --- a/clippy_dev/src/update_lints.rs +++ /dev/null @@ -1,204 +0,0 @@ -use crate::parse::cursor::Cursor; -use crate::parse::{Lint, LintData, ParseCx}; -use crate::utils::{FileUpdater, UpdateMode, UpdateStatus, update_text_region_fn}; -use itertools::Itertools; -use std::collections::HashSet; -use std::fmt::Write; -use std::path::{self, Path}; - -const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev update_lints`.\n\ - // Use that command to update this file and do not edit by hand.\n\ - // Manual edits will be overwritten.\n\n"; - -const DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.html"; - -/// Runs the `update_lints` command. -/// -/// This updates various generated values from the lint source code. -/// -/// `update_mode` indicates if the files should be updated or if updates should be checked for. -/// -/// # Panics -/// -/// Panics if a file path could not read from or then written to -pub fn update(cx: ParseCx<'_>, update_mode: UpdateMode) { - let data = cx.parse_lint_decls(); - generate_lint_files(update_mode, &data); -} - -#[expect(clippy::too_many_lines)] -pub fn generate_lint_files(update_mode: UpdateMode, data: &LintData<'_>) { - let mut updater = FileUpdater::default(); - - let mut lints: Vec<_> = data.lints.iter().map(|(&x, y)| (x, y)).collect(); - lints.sort_by_key(|&(x, _)| x); - updater.update_file_checked( - "cargo dev update_lints", - update_mode, - "CHANGELOG.md", - &mut update_text_region_fn( - "\n", - "", - |dst| { - for &(lint, _) in &lints { - writeln!(dst, "[`{lint}`]: {DOCS_LINK}#{lint}").unwrap(); - } - }, - ), - ); - - let mut active = Vec::with_capacity(lints.len()); - let mut deprecated = Vec::with_capacity(lints.len() / 8); - let mut renamed = Vec::with_capacity(lints.len() / 8); - for &(name, lint) in &lints { - match lint { - Lint::Active(lint) => active.push((name, lint)), - Lint::Deprecated(lint) => deprecated.push((name, lint)), - Lint::Renamed(lint) => renamed.push((name, lint)), - } - } - active.sort_by_key(|&(_, lint)| lint.module); - - // Round to avoid updating the readme every time a lint is added/deprecated. - let lint_count = active.len() / 50 * 50; - updater.update_file_checked( - "cargo dev update_lints", - update_mode, - "README.md", - &mut update_text_region_fn("[There are over ", " lints included in this crate!]", |dst| { - write!(dst, "{lint_count}").unwrap(); - }), - ); - updater.update_file_checked( - "cargo dev update_lints", - update_mode, - "book/src/README.md", - &mut update_text_region_fn("[There are over ", " lints included in this crate!]", |dst| { - write!(dst, "{lint_count}").unwrap(); - }), - ); - - updater.update_file_checked( - "cargo dev update_lints", - update_mode, - "clippy_lints/src/deprecated_lints.rs", - &mut |_, src, dst| { - let mut cursor = Cursor::new(src); - assert!( - cursor.find_ident("declare_with_version").is_some() - && cursor.find_ident("declare_with_version").is_some(), - "error reading deprecated lints" - ); - dst.push_str(&src[..cursor.pos() as usize]); - dst.push_str("! { DEPRECATED(DEPRECATED_VERSION) = [\n"); - for &(name, data) in &deprecated { - write!( - dst, - " #[clippy::version = \"{}\"]\n (\"clippy::{name}\", \"{}\"),\n", - data.version, data.reason, - ) - .unwrap(); - } - dst.push_str( - "]}\n\n\ - #[rustfmt::skip]\n\ - declare_with_version! { RENAMED(RENAMED_VERSION) = [\n\ - ", - ); - for &(name, data) in &renamed { - write!( - dst, - " #[clippy::version = \"{}\"]\n (\"clippy::{name}\", \"{}\"),\n", - data.version, data.new_name, - ) - .unwrap(); - } - dst.push_str("]}\n"); - UpdateStatus::from_changed(src != dst) - }, - ); - updater.update_file_checked( - "cargo dev update_lints", - update_mode, - "tests/ui/deprecated.rs", - &mut |_, src, dst| { - dst.push_str(GENERATED_FILE_COMMENT); - for &(lint, _) in &deprecated { - writeln!(dst, "#![warn(clippy::{lint})] //~ ERROR: lint `clippy::{lint}`").unwrap(); - } - dst.push_str("\nfn main() {}\n"); - UpdateStatus::from_changed(src != dst) - }, - ); - updater.update_file_checked( - "cargo dev update_lints", - update_mode, - "tests/ui/rename.rs", - &mut move |_, src, dst| { - let mut seen_lints = HashSet::new(); - dst.push_str(GENERATED_FILE_COMMENT); - dst.push_str("#![allow(clippy::duplicated_attributes)]\n"); - for &(_, lint) in &renamed { - if seen_lints.insert(lint.new_name) { - writeln!(dst, "#![allow({})]", lint.new_name).unwrap(); - } - } - for &(lint, _) in &renamed { - writeln!(dst, "#![warn(clippy::{lint})] //~ ERROR: lint `clippy::{lint}`").unwrap(); - } - dst.push_str("\nfn main() {}\n"); - UpdateStatus::from_changed(src != dst) - }, - ); - for (crate_name, lints) in active.iter().copied().into_group_map_by(|&(_, lint)| { - let Some(path::Component::Normal(name)) = lint.path.components().next() else { - // All paths should start with `{crate_name}/src` when parsed from `find_lint_decls` - panic!( - "internal error: can't read crate name from path `{}`", - lint.path.display() - ); - }; - name - }) { - updater.update_file_checked( - "cargo dev update_lints", - update_mode, - Path::new(crate_name).join("src/lib.rs"), - &mut update_text_region_fn( - "// begin lints modules, do not remove this comment, it's used in `update_lints`\n", - "// end lints modules, do not remove this comment, it's used in `update_lints`", - |dst| { - let mut prev = ""; - for &(_, lint) in &lints { - if lint.module != prev { - writeln!(dst, "mod {};", lint.module).unwrap(); - prev = lint.module; - } - } - }, - ), - ); - updater.update_file_checked( - "cargo dev update_lints", - update_mode, - Path::new(crate_name).join("src/declared_lints.rs"), - &mut |_, src, dst| { - dst.push_str(GENERATED_FILE_COMMENT); - dst.push_str("pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[\n"); - let mut buf = String::new(); - for &(name, lint) in &lints { - buf.clear(); - buf.push_str(name); - buf.make_ascii_uppercase(); - if lint.module.is_empty() { - writeln!(dst, " crate::{buf}_INFO,").unwrap(); - } else { - writeln!(dst, " crate::{}::{buf}_INFO,", lint.module).unwrap(); - } - } - dst.push_str("];\n"); - UpdateStatus::from_changed(src != dst) - }, - ); - } -} diff --git a/clippy_dev/src/utils.rs b/clippy_dev/src/utils.rs index 52452dd86b49..1f931140467e 100644 --- a/clippy_dev/src/utils.rs +++ b/clippy_dev/src/utils.rs @@ -1,5 +1,6 @@ use core::fmt::{self, Display}; use core::marker::PhantomData; +use core::mem; use core::num::NonZero; use core::ops::{Deref, DerefMut}; use core::range::Range; @@ -600,3 +601,29 @@ pub fn walk_dir_no_dot_or_target(p: impl AsRef) -> impl Iterator( + slice: &mut [T], + split_idx: impl FnMut(&T, &[T]) -> usize, +) -> impl Iterator { + struct I<'a, T, F> { + slice: &'a mut [T], + split_idx: F, + } + impl<'a, T, F: FnMut(&T, &[T]) -> usize> Iterator for I<'a, T, F> { + type Item = &'a mut [T]; + fn next(&mut self) -> Option { + let (head, tail) = self.slice.split_first()?; + let idx = (self.split_idx)(head, tail) + 1; + // `mem::take` makes it so `self.slice` isn't reborrowed. + if let Some((head, tail)) = mem::take(&mut self.slice).split_at_mut_checked(idx) { + self.slice = tail; + Some(head) + } else { + self.slice = &mut []; + None + } + } + } + I { slice, split_idx } +} diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index c0804dbb0492..51e753efb52e 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_lints" -version = "0.1.95" +version = "0.1.96" description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" diff --git a/clippy_lints/src/absolute_paths.rs b/clippy_lints/src/absolute_paths.rs index 1af6d448a93c..fd515939dfb8 100644 --- a/clippy_lints/src/absolute_paths.rs +++ b/clippy_lints/src/absolute_paths.rs @@ -52,6 +52,7 @@ restriction, "checks for usage of an item without a `use` statement" } + impl_lint_pass!(AbsolutePaths => [ABSOLUTE_PATHS]); pub struct AbsolutePaths { diff --git a/clippy_lints/src/almost_complete_range.rs b/clippy_lints/src/almost_complete_range.rs index 4f55968d5625..258970393023 100644 --- a/clippy_lints/src/almost_complete_range.rs +++ b/clippy_lints/src/almost_complete_range.rs @@ -28,6 +28,7 @@ suspicious, "almost complete range" } + impl_lint_pass!(AlmostCompleteRange => [ALMOST_COMPLETE_RANGE]); pub struct AlmostCompleteRange { diff --git a/clippy_lints/src/approx_const.rs b/clippy_lints/src/approx_const.rs index a3710ca51655..2ea921e5d461 100644 --- a/clippy_lints/src/approx_const.rs +++ b/clippy_lints/src/approx_const.rs @@ -39,6 +39,8 @@ "the approximate of a known float constant (in `std::fXX::consts`)" } +impl_lint_pass!(ApproxConstant => [APPROX_CONSTANT]); + // Tuples are of the form (constant, name, min_digits, msrv) const KNOWN_CONSTS: [(f64, &str, usize, Option); 19] = [ (f64::E, "E", 4, None), @@ -111,8 +113,6 @@ fn check_known_consts(&self, cx: &LateContext<'_>, span: Span, s: symbol::Symbol } } -impl_lint_pass!(ApproxConstant => [APPROX_CONSTANT]); - fn count_digits_after_dot(input: &str) -> usize { input .char_indices() diff --git a/clippy_lints/src/arc_with_non_send_sync.rs b/clippy_lints/src/arc_with_non_send_sync.rs index acfdfa65baed..e449b06199d3 100644 --- a/clippy_lints/src/arc_with_non_send_sync.rs +++ b/clippy_lints/src/arc_with_non_send_sync.rs @@ -39,6 +39,7 @@ suspicious, "using `Arc` with a type that does not implement `Send` and `Sync`" } + declare_lint_pass!(ArcWithNonSendSync => [ARC_WITH_NON_SEND_SYNC]); impl<'tcx> LateLintPass<'tcx> for ArcWithNonSendSync { diff --git a/clippy_lints/src/asm_syntax.rs b/clippy_lints/src/asm_syntax.rs index 69a8eb7d94e7..3c5cf74d5a17 100644 --- a/clippy_lints/src/asm_syntax.rs +++ b/clippy_lints/src/asm_syntax.rs @@ -57,54 +57,6 @@ fn check_asm_syntax( } } -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of Intel x86 assembly syntax. - /// - /// ### Why restrict this? - /// To enforce consistent use of AT&T x86 assembly syntax. - /// - /// ### Example - /// - /// ```rust,no_run - /// # #![feature(asm)] - /// # #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - /// # unsafe { let ptr = "".as_ptr(); - /// # use std::arch::asm; - /// asm!("lea {}, [{}]", lateout(reg) _, in(reg) ptr); - /// # } - /// ``` - /// Use instead: - /// ```rust,no_run - /// # #![feature(asm)] - /// # #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - /// # unsafe { let ptr = "".as_ptr(); - /// # use std::arch::asm; - /// asm!("lea ({}), {}", in(reg) ptr, lateout(reg) _, options(att_syntax)); - /// # } - /// ``` - #[clippy::version = "1.49.0"] - pub INLINE_ASM_X86_INTEL_SYNTAX, - restriction, - "prefer AT&T x86 assembly syntax" -} - -declare_lint_pass!(InlineAsmX86IntelSyntax => [INLINE_ASM_X86_INTEL_SYNTAX]); - -impl EarlyLintPass for InlineAsmX86IntelSyntax { - fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { - if let ExprKind::InlineAsm(inline_asm) = &expr.kind { - check_asm_syntax(INLINE_ASM_X86_INTEL_SYNTAX, cx, inline_asm, expr.span, AsmStyle::Intel); - } - } - - fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { - if let ItemKind::GlobalAsm(inline_asm) = &item.kind { - check_asm_syntax(INLINE_ASM_X86_INTEL_SYNTAX, cx, inline_asm, item.span, AsmStyle::Intel); - } - } -} - declare_clippy_lint! { /// ### What it does /// Checks for usage of AT&T x86 assembly syntax. @@ -137,8 +89,56 @@ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { "prefer Intel x86 assembly syntax" } +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of Intel x86 assembly syntax. + /// + /// ### Why restrict this? + /// To enforce consistent use of AT&T x86 assembly syntax. + /// + /// ### Example + /// + /// ```rust,no_run + /// # #![feature(asm)] + /// # #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + /// # unsafe { let ptr = "".as_ptr(); + /// # use std::arch::asm; + /// asm!("lea {}, [{}]", lateout(reg) _, in(reg) ptr); + /// # } + /// ``` + /// Use instead: + /// ```rust,no_run + /// # #![feature(asm)] + /// # #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + /// # unsafe { let ptr = "".as_ptr(); + /// # use std::arch::asm; + /// asm!("lea ({}), {}", in(reg) ptr, lateout(reg) _, options(att_syntax)); + /// # } + /// ``` + #[clippy::version = "1.49.0"] + pub INLINE_ASM_X86_INTEL_SYNTAX, + restriction, + "prefer AT&T x86 assembly syntax" +} + declare_lint_pass!(InlineAsmX86AttSyntax => [INLINE_ASM_X86_ATT_SYNTAX]); +declare_lint_pass!(InlineAsmX86IntelSyntax => [INLINE_ASM_X86_INTEL_SYNTAX]); + +impl EarlyLintPass for InlineAsmX86IntelSyntax { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if let ExprKind::InlineAsm(inline_asm) = &expr.kind { + check_asm_syntax(INLINE_ASM_X86_INTEL_SYNTAX, cx, inline_asm, expr.span, AsmStyle::Intel); + } + } + + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + if let ItemKind::GlobalAsm(inline_asm) = &item.kind { + check_asm_syntax(INLINE_ASM_X86_INTEL_SYNTAX, cx, inline_asm, item.span, AsmStyle::Intel); + } + } +} + impl EarlyLintPass for InlineAsmX86AttSyntax { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { if let ExprKind::InlineAsm(inline_asm) = &expr.kind { diff --git a/clippy_lints/src/assertions_on_constants.rs b/clippy_lints/src/assertions_on_constants.rs index 4aa55e53445c..6e57d0608bed 100644 --- a/clippy_lints/src/assertions_on_constants.rs +++ b/clippy_lints/src/assertions_on_constants.rs @@ -33,6 +33,7 @@ } impl_lint_pass!(AssertionsOnConstants => [ASSERTIONS_ON_CONSTANTS]); + pub struct AssertionsOnConstants { msrv: Msrv, } diff --git a/clippy_lints/src/assigning_clones.rs b/clippy_lints/src/assigning_clones.rs index efce23d13a38..60bc9b2b5b85 100644 --- a/clippy_lints/src/assigning_clones.rs +++ b/clippy_lints/src/assigning_clones.rs @@ -53,6 +53,8 @@ "assigning the result of cloning may be inefficient" } +impl_lint_pass!(AssigningClones => [ASSIGNING_CLONES]); + pub struct AssigningClones { msrv: Msrv, } @@ -63,8 +65,6 @@ pub fn new(conf: &'static Conf) -> Self { } } -impl_lint_pass!(AssigningClones => [ASSIGNING_CLONES]); - impl<'tcx> LateLintPass<'tcx> for AssigningClones { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { if let ExprKind::Assign(lhs, rhs, _) = e.kind diff --git a/clippy_lints/src/attrs/mod.rs b/clippy_lints/src/attrs/mod.rs index fa2951d91934..c15a378053e3 100644 --- a/clippy_lints/src/attrs/mod.rs +++ b/clippy_lints/src/attrs/mod.rs @@ -25,106 +25,60 @@ declare_clippy_lint! { /// ### What it does - /// Checks for items annotated with `#[inline(always)]`, - /// unless the annotated function is empty or simply panics. + /// Checks for usage of the `#[allow]` attribute and suggests replacing it with + /// the `#[expect]` attribute (See [RFC 2383](https://rust-lang.github.io/rfcs/2383-lint-reasons.html)) + /// + /// This lint only warns outer attributes (`#[allow]`), as inner attributes + /// (`#![allow]`) are usually used to enable or disable lints on a global scale. /// /// ### Why is this bad? - /// While there are valid uses of this annotation (and once - /// you know when to use it, by all means `allow` this lint), it's a common - /// newbie-mistake to pepper one's code with it. - /// - /// As a rule of thumb, before slapping `#[inline(always)]` on a function, - /// measure if that additional function call really affects your runtime profile - /// sufficiently to make up for the increase in compile time. - /// - /// ### Known problems - /// False positives, big time. This lint is meant to be - /// deactivated by everyone doing serious performance work. This means having - /// done the measurement. + /// `#[expect]` attributes suppress the lint emission, but emit a warning, if + /// the expectation is unfulfilled. This can be useful to be notified when the + /// lint is no longer triggered. /// /// ### Example - /// ```ignore - /// #[inline(always)] - /// fn not_quite_hot_code(..) { ... } + /// ```rust,ignore + /// #[allow(unused_mut)] + /// fn foo() -> usize { + /// let mut a = Vec::new(); + /// a.len() + /// } /// ``` - #[clippy::version = "pre 1.29.0"] - pub INLINE_ALWAYS, - pedantic, - "use of `#[inline(always)]`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `extern crate` and `use` items annotated with - /// lint attributes. - /// - /// This lint permits lint attributes for lints emitted on the items themself. - /// For `use` items these lints are: - /// * ambiguous_glob_reexports - /// * dead_code - /// * deprecated - /// * hidden_glob_reexports - /// * unreachable_pub - /// * unused - /// * unused_braces - /// * unused_import_braces - /// * clippy::disallowed_types - /// * clippy::enum_glob_use - /// * clippy::macro_use_imports - /// * clippy::module_name_repetitions - /// * clippy::redundant_pub_crate - /// * clippy::single_component_path_imports - /// * clippy::unsafe_removed_from_name - /// * clippy::wildcard_imports - /// - /// For `extern crate` items these lints are: - /// * `unused_imports` on items with `#[macro_use]` - /// - /// ### Why is this bad? - /// Lint attributes have no effect on crate imports. Most - /// likely a `!` was forgotten. - /// - /// ### Example - /// ```ignore - /// #[deny(dead_code)] - /// extern crate foo; - /// #[forbid(dead_code)] - /// use foo::bar; - /// ``` - /// /// Use instead: /// ```rust,ignore - /// #[allow(unused_imports)] - /// use foo::baz; - /// #[allow(unused_imports)] - /// #[macro_use] - /// extern crate baz; + /// #[expect(unused_mut)] + /// fn foo() -> usize { + /// let mut a = Vec::new(); + /// a.len() + /// } /// ``` - #[clippy::version = "pre 1.29.0"] - pub USELESS_ATTRIBUTE, - correctness, - "use of lint attributes on `extern crate` items" + #[clippy::version = "1.70.0"] + pub ALLOW_ATTRIBUTES, + restriction, + "`#[allow]` will not trigger if a warning isn't found. `#[expect]` triggers if there are no warnings." } declare_clippy_lint! { /// ### What it does - /// Checks for `#[deprecated]` annotations with a `since` - /// field that is not a valid semantic version. Also allows "TBD" to signal - /// future deprecation. + /// Checks for attributes that allow lints without a reason. /// - /// ### Why is this bad? - /// For checking the version of the deprecation, it must be - /// a valid semver. Failing that, the contained information is useless. + /// ### Why restrict this? + /// Justifying each `allow` helps readers understand the reasoning, + /// and may allow removing `allow` attributes if their purpose is obsolete. /// /// ### Example /// ```no_run - /// #[deprecated(since = "forever")] - /// fn something_else() { /* ... */ } + /// #![allow(clippy::some_lint)] /// ``` - #[clippy::version = "pre 1.29.0"] - pub DEPRECATED_SEMVER, - correctness, - "use of `#[deprecated(since = \"x\")]` where x is not semver" + /// + /// Use instead: + /// ```no_run + /// #![allow(clippy::some_lint, reason = "False positive rust-lang/rust-clippy#1002020")] + /// ``` + #[clippy::version = "1.61.0"] + pub ALLOW_ATTRIBUTES_WITHOUT_REASON, + restriction, + "ensures that all `allow` and `expect` attributes have a reason" } declare_clippy_lint! { @@ -181,161 +135,6 @@ "usage of `cfg_attr(rustfmt)` instead of tool attributes" } -declare_clippy_lint! { - /// ### What it does - /// Checks for attributes that allow lints without a reason. - /// - /// ### Why restrict this? - /// Justifying each `allow` helps readers understand the reasoning, - /// and may allow removing `allow` attributes if their purpose is obsolete. - /// - /// ### Example - /// ```no_run - /// #![allow(clippy::some_lint)] - /// ``` - /// - /// Use instead: - /// ```no_run - /// #![allow(clippy::some_lint, reason = "False positive rust-lang/rust-clippy#1002020")] - /// ``` - #[clippy::version = "1.61.0"] - pub ALLOW_ATTRIBUTES_WITHOUT_REASON, - restriction, - "ensures that all `allow` and `expect` attributes have a reason" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of the `#[allow]` attribute and suggests replacing it with - /// the `#[expect]` attribute (See [RFC 2383](https://rust-lang.github.io/rfcs/2383-lint-reasons.html)) - /// - /// This lint only warns outer attributes (`#[allow]`), as inner attributes - /// (`#![allow]`) are usually used to enable or disable lints on a global scale. - /// - /// ### Why is this bad? - /// `#[expect]` attributes suppress the lint emission, but emit a warning, if - /// the expectation is unfulfilled. This can be useful to be notified when the - /// lint is no longer triggered. - /// - /// ### Example - /// ```rust,ignore - /// #[allow(unused_mut)] - /// fn foo() -> usize { - /// let mut a = Vec::new(); - /// a.len() - /// } - /// ``` - /// Use instead: - /// ```rust,ignore - /// #[expect(unused_mut)] - /// fn foo() -> usize { - /// let mut a = Vec::new(); - /// a.len() - /// } - /// ``` - #[clippy::version = "1.70.0"] - pub ALLOW_ATTRIBUTES, - restriction, - "`#[allow]` will not trigger if a warning isn't found. `#[expect]` triggers if there are no warnings." -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `#[should_panic]` attributes without specifying the expected panic message. - /// - /// ### Why is this bad? - /// The expected panic message should be specified to ensure that the test is actually - /// panicking with the expected message, and not another unrelated panic. - /// - /// ### Example - /// ```no_run - /// fn random() -> i32 { 0 } - /// - /// #[should_panic] - /// #[test] - /// fn my_test() { - /// let _ = 1 / random(); - /// } - /// ``` - /// - /// Use instead: - /// ```no_run - /// fn random() -> i32 { 0 } - /// - /// #[should_panic = "attempt to divide by zero"] - /// #[test] - /// fn my_test() { - /// let _ = 1 / random(); - /// } - /// ``` - #[clippy::version = "1.74.0"] - pub SHOULD_PANIC_WITHOUT_EXPECT, - pedantic, - "ensures that all `should_panic` attributes specify its expected panic message" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for items with `#[repr(packed)]`-attribute without ABI qualification - /// - /// ### Why is this bad? - /// Without qualification, `repr(packed)` implies `repr(Rust)`. The Rust-ABI is inherently unstable. - /// While this is fine as long as the type is accessed correctly within Rust-code, most uses - /// of `#[repr(packed)]` involve FFI and/or data structures specified by network-protocols or - /// other external specifications. In such situations, the unstable Rust-ABI implied in - /// `#[repr(packed)]` may lead to future bugs should the Rust-ABI change. - /// - /// In case you are relying on a well defined and stable memory layout, qualify the type's - /// representation using the `C`-ABI. Otherwise, if the type in question is only ever - /// accessed from Rust-code according to Rust's rules, use the `Rust`-ABI explicitly. - /// - /// ### Example - /// ```no_run - /// #[repr(packed)] - /// struct NetworkPacketHeader { - /// header_length: u8, - /// header_version: u16 - /// } - /// ``` - /// - /// Use instead: - /// ```no_run - /// #[repr(C, packed)] - /// struct NetworkPacketHeader { - /// header_length: u8, - /// header_version: u16 - /// } - /// ``` - #[clippy::version = "1.85.0"] - pub REPR_PACKED_WITHOUT_ABI, - suspicious, - "ensures that `repr(packed)` always comes with a qualified ABI" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `any` and `all` combinators in `cfg` with only one condition. - /// - /// ### Why is this bad? - /// If there is only one condition, no need to wrap it into `any` or `all` combinators. - /// - /// ### Example - /// ```no_run - /// #[cfg(any(unix))] - /// pub struct Bar; - /// ``` - /// - /// Use instead: - /// ```no_run - /// #[cfg(unix)] - /// pub struct Bar; - /// ``` - #[clippy::version = "1.71.0"] - pub NON_MINIMAL_CFG, - style, - "ensure that all `cfg(any())` and `cfg(all())` have more than one condition" -} - declare_clippy_lint! { /// ### What it does /// Checks for `#[cfg_attr(feature = "cargo-clippy", ...)]` and for @@ -364,63 +163,23 @@ declare_clippy_lint! { /// ### What it does - /// Checks for `#[cfg_attr(clippy, allow(clippy::lint))]` - /// and suggests to replace it with `#[allow(clippy::lint)]`. + /// Checks for `#[deprecated]` annotations with a `since` + /// field that is not a valid semantic version. Also allows "TBD" to signal + /// future deprecation. /// /// ### Why is this bad? - /// There is no reason to put clippy attributes behind a clippy `cfg` as they are not - /// run by anything else than clippy. + /// For checking the version of the deprecation, it must be + /// a valid semver. Failing that, the contained information is useless. /// /// ### Example /// ```no_run - /// #![cfg_attr(clippy, allow(clippy::deprecated_cfg_attr))] + /// #[deprecated(since = "forever")] + /// fn something_else() { /* ... */ } /// ``` - /// - /// Use instead: - /// ```no_run - /// #![allow(clippy::deprecated_cfg_attr)] - /// ``` - #[clippy::version = "1.78.0"] - pub UNNECESSARY_CLIPPY_CFG, - suspicious, - "usage of `cfg_attr(clippy, allow(clippy::lint))` instead of `allow(clippy::lint)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for items that have the same kind of attributes with mixed styles (inner/outer). - /// - /// ### Why is this bad? - /// Having both style of said attributes makes it more complicated to read code. - /// - /// ### Known problems - /// This lint currently has false-negatives when mixing same attributes - /// but they have different path symbols, for example: - /// ```ignore - /// #[custom_attribute] - /// pub fn foo() { - /// #![my_crate::custom_attribute] - /// } - /// ``` - /// - /// ### Example - /// ```no_run - /// #[cfg(linux)] - /// pub fn foo() { - /// #![cfg(windows)] - /// } - /// ``` - /// Use instead: - /// ```no_run - /// #[cfg(linux)] - /// #[cfg(windows)] - /// pub fn foo() { - /// } - /// ``` - #[clippy::version = "1.78.0"] - pub MIXED_ATTRIBUTES_STYLE, - style, - "item has both inner and outer attributes" + #[clippy::version = "pre 1.29.0"] + pub DEPRECATED_SEMVER, + correctness, + "use of `#[deprecated(since = \"x\")]` where x is not semver" } declare_clippy_lint! { @@ -478,15 +237,272 @@ "ignored tests without messages" } +declare_clippy_lint! { + /// ### What it does + /// Checks for items annotated with `#[inline(always)]`, + /// unless the annotated function is empty or simply panics. + /// + /// ### Why is this bad? + /// While there are valid uses of this annotation (and once + /// you know when to use it, by all means `allow` this lint), it's a common + /// newbie-mistake to pepper one's code with it. + /// + /// As a rule of thumb, before slapping `#[inline(always)]` on a function, + /// measure if that additional function call really affects your runtime profile + /// sufficiently to make up for the increase in compile time. + /// + /// ### Known problems + /// False positives, big time. This lint is meant to be + /// deactivated by everyone doing serious performance work. This means having + /// done the measurement. + /// + /// ### Example + /// ```ignore + /// #[inline(always)] + /// fn not_quite_hot_code(..) { ... } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub INLINE_ALWAYS, + pedantic, + "use of `#[inline(always)]`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for items that have the same kind of attributes with mixed styles (inner/outer). + /// + /// ### Why is this bad? + /// Having both style of said attributes makes it more complicated to read code. + /// + /// ### Known problems + /// This lint currently has false-negatives when mixing same attributes + /// but they have different path symbols, for example: + /// ```ignore + /// #[custom_attribute] + /// pub fn foo() { + /// #![my_crate::custom_attribute] + /// } + /// ``` + /// + /// ### Example + /// ```no_run + /// #[cfg(linux)] + /// pub fn foo() { + /// #![cfg(windows)] + /// } + /// ``` + /// Use instead: + /// ```no_run + /// #[cfg(linux)] + /// #[cfg(windows)] + /// pub fn foo() { + /// } + /// ``` + #[clippy::version = "1.78.0"] + pub MIXED_ATTRIBUTES_STYLE, + style, + "item has both inner and outer attributes" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for `any` and `all` combinators in `cfg` with only one condition. + /// + /// ### Why is this bad? + /// If there is only one condition, no need to wrap it into `any` or `all` combinators. + /// + /// ### Example + /// ```no_run + /// #[cfg(any(unix))] + /// pub struct Bar; + /// ``` + /// + /// Use instead: + /// ```no_run + /// #[cfg(unix)] + /// pub struct Bar; + /// ``` + #[clippy::version = "1.71.0"] + pub NON_MINIMAL_CFG, + style, + "ensure that all `cfg(any())` and `cfg(all())` have more than one condition" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for items with `#[repr(packed)]`-attribute without ABI qualification + /// + /// ### Why is this bad? + /// Without qualification, `repr(packed)` implies `repr(Rust)`. The Rust-ABI is inherently unstable. + /// While this is fine as long as the type is accessed correctly within Rust-code, most uses + /// of `#[repr(packed)]` involve FFI and/or data structures specified by network-protocols or + /// other external specifications. In such situations, the unstable Rust-ABI implied in + /// `#[repr(packed)]` may lead to future bugs should the Rust-ABI change. + /// + /// In case you are relying on a well defined and stable memory layout, qualify the type's + /// representation using the `C`-ABI. Otherwise, if the type in question is only ever + /// accessed from Rust-code according to Rust's rules, use the `Rust`-ABI explicitly. + /// + /// ### Example + /// ```no_run + /// #[repr(packed)] + /// struct NetworkPacketHeader { + /// header_length: u8, + /// header_version: u16 + /// } + /// ``` + /// + /// Use instead: + /// ```no_run + /// #[repr(C, packed)] + /// struct NetworkPacketHeader { + /// header_length: u8, + /// header_version: u16 + /// } + /// ``` + #[clippy::version = "1.85.0"] + pub REPR_PACKED_WITHOUT_ABI, + suspicious, + "ensures that `repr(packed)` always comes with a qualified ABI" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for `#[should_panic]` attributes without specifying the expected panic message. + /// + /// ### Why is this bad? + /// The expected panic message should be specified to ensure that the test is actually + /// panicking with the expected message, and not another unrelated panic. + /// + /// ### Example + /// ```no_run + /// fn random() -> i32 { 0 } + /// + /// #[should_panic] + /// #[test] + /// fn my_test() { + /// let _ = 1 / random(); + /// } + /// ``` + /// + /// Use instead: + /// ```no_run + /// fn random() -> i32 { 0 } + /// + /// #[should_panic = "attempt to divide by zero"] + /// #[test] + /// fn my_test() { + /// let _ = 1 / random(); + /// } + /// ``` + #[clippy::version = "1.74.0"] + pub SHOULD_PANIC_WITHOUT_EXPECT, + pedantic, + "ensures that all `should_panic` attributes specify its expected panic message" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for `#[cfg_attr(clippy, allow(clippy::lint))]` + /// and suggests to replace it with `#[allow(clippy::lint)]`. + /// + /// ### Why is this bad? + /// There is no reason to put clippy attributes behind a clippy `cfg` as they are not + /// run by anything else than clippy. + /// + /// ### Example + /// ```no_run + /// #![cfg_attr(clippy, allow(clippy::deprecated_cfg_attr))] + /// ``` + /// + /// Use instead: + /// ```no_run + /// #![allow(clippy::deprecated_cfg_attr)] + /// ``` + #[clippy::version = "1.78.0"] + pub UNNECESSARY_CLIPPY_CFG, + suspicious, + "usage of `cfg_attr(clippy, allow(clippy::lint))` instead of `allow(clippy::lint)`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for `extern crate` and `use` items annotated with + /// lint attributes. + /// + /// This lint permits lint attributes for lints emitted on the items themself. + /// For `use` items these lints are: + /// * ambiguous_glob_reexports + /// * dead_code + /// * deprecated + /// * hidden_glob_reexports + /// * unreachable_pub + /// * unused + /// * unused_braces + /// * unused_import_braces + /// * clippy::disallowed_types + /// * clippy::enum_glob_use + /// * clippy::macro_use_imports + /// * clippy::module_name_repetitions + /// * clippy::redundant_pub_crate + /// * clippy::single_component_path_imports + /// * clippy::unsafe_removed_from_name + /// * clippy::wildcard_imports + /// + /// For `extern crate` items these lints are: + /// * `unused_imports` on items with `#[macro_use]` + /// + /// ### Why is this bad? + /// Lint attributes have no effect on crate imports. Most + /// likely a `!` was forgotten. + /// + /// ### Example + /// ```ignore + /// #[deny(dead_code)] + /// extern crate foo; + /// #[forbid(dead_code)] + /// use foo::bar; + /// ``` + /// + /// Use instead: + /// ```rust,ignore + /// #[allow(unused_imports)] + /// use foo::baz; + /// #[allow(unused_imports)] + /// #[macro_use] + /// extern crate baz; + /// ``` + #[clippy::version = "pre 1.29.0"] + pub USELESS_ATTRIBUTE, + correctness, + "use of lint attributes on `extern crate` items" +} + +impl_lint_pass!(Attributes => [INLINE_ALWAYS, REPR_PACKED_WITHOUT_ABI]); + +impl_lint_pass!(EarlyAttributes => [ + DEPRECATED_CFG_ATTR, + DEPRECATED_CLIPPY_CFG_ATTR, + NON_MINIMAL_CFG, + UNNECESSARY_CLIPPY_CFG, +]); + +impl_lint_pass!(PostExpansionEarlyAttributes => [ + ALLOW_ATTRIBUTES, + ALLOW_ATTRIBUTES_WITHOUT_REASON, + BLANKET_CLIPPY_RESTRICTION_LINTS, + DEPRECATED_SEMVER, + DUPLICATED_ATTRIBUTES, + IGNORE_WITHOUT_REASON, + MIXED_ATTRIBUTES_STYLE, + SHOULD_PANIC_WITHOUT_EXPECT, + USELESS_ATTRIBUTE, +]); + pub struct Attributes { msrv: Msrv, } -impl_lint_pass!(Attributes => [ - INLINE_ALWAYS, - REPR_PACKED_WITHOUT_ABI, -]); - impl Attributes { pub fn new(conf: &'static Conf) -> Self { Self { msrv: conf.msrv } @@ -529,13 +545,6 @@ pub fn new(conf: &'static Conf) -> Self { } } -impl_lint_pass!(EarlyAttributes => [ - DEPRECATED_CFG_ATTR, - NON_MINIMAL_CFG, - DEPRECATED_CLIPPY_CFG_ATTR, - UNNECESSARY_CLIPPY_CFG, -]); - impl EarlyLintPass for EarlyAttributes { fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) { deprecated_cfg_attr::check(cx, attr, &self.msrv); @@ -558,18 +567,6 @@ pub fn new(conf: &'static Conf) -> Self { } } -impl_lint_pass!(PostExpansionEarlyAttributes => [ - ALLOW_ATTRIBUTES, - ALLOW_ATTRIBUTES_WITHOUT_REASON, - DEPRECATED_SEMVER, - IGNORE_WITHOUT_REASON, - USELESS_ATTRIBUTE, - BLANKET_CLIPPY_RESTRICTION_LINTS, - SHOULD_PANIC_WITHOUT_EXPECT, - MIXED_ATTRIBUTES_STYLE, - DUPLICATED_ATTRIBUTES, -]); - impl EarlyLintPass for PostExpansionEarlyAttributes { fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &ast::Crate) { blanket_clippy_restriction_lints::check_command_line(cx); diff --git a/clippy_lints/src/await_holding_invalid.rs b/clippy_lints/src/await_holding_invalid.rs index 4ee955c035da..9e5b57a05580 100644 --- a/clippy_lints/src/await_holding_invalid.rs +++ b/clippy_lints/src/await_holding_invalid.rs @@ -11,6 +11,43 @@ use rustc_session::impl_lint_pass; use rustc_span::Span; +declare_clippy_lint! { + /// ### What it does + /// Allows users to configure types which should not be held across await + /// suspension points. + /// + /// ### Why is this bad? + /// There are some types which are perfectly safe to use concurrently from + /// a memory access perspective, but that will cause bugs at runtime if + /// they are held in such a way. + /// + /// ### Example + /// + /// ```toml + /// await-holding-invalid-types = [ + /// # You can specify a type name + /// "CustomLockType", + /// # You can (optionally) specify a reason + /// { path = "OtherCustomLockType", reason = "Relies on a thread local" } + /// ] + /// ``` + /// + /// ```no_run + /// # async fn baz() {} + /// struct CustomLockType; + /// struct OtherCustomLockType; + /// async fn foo() { + /// let _x = CustomLockType; + /// let _y = OtherCustomLockType; + /// baz().await; // Lint violation + /// } + /// ``` + #[clippy::version = "1.62.0"] + pub AWAIT_HOLDING_INVALID_TYPE, + suspicious, + "holding a type across an await point which is not allowed to be held as per the configuration" +} + declare_clippy_lint! { /// ### What it does /// Checks for calls to `await` while holding a non-async-aware @@ -135,44 +172,11 @@ "inside an async function, holding a `RefCell` ref while calling `await`" } -declare_clippy_lint! { - /// ### What it does - /// Allows users to configure types which should not be held across await - /// suspension points. - /// - /// ### Why is this bad? - /// There are some types which are perfectly safe to use concurrently from - /// a memory access perspective, but that will cause bugs at runtime if - /// they are held in such a way. - /// - /// ### Example - /// - /// ```toml - /// await-holding-invalid-types = [ - /// # You can specify a type name - /// "CustomLockType", - /// # You can (optionally) specify a reason - /// { path = "OtherCustomLockType", reason = "Relies on a thread local" } - /// ] - /// ``` - /// - /// ```no_run - /// # async fn baz() {} - /// struct CustomLockType; - /// struct OtherCustomLockType; - /// async fn foo() { - /// let _x = CustomLockType; - /// let _y = OtherCustomLockType; - /// baz().await; // Lint violation - /// } - /// ``` - #[clippy::version = "1.62.0"] - pub AWAIT_HOLDING_INVALID_TYPE, - suspicious, - "holding a type across an await point which is not allowed to be held as per the configuration" -} - -impl_lint_pass!(AwaitHolding => [AWAIT_HOLDING_LOCK, AWAIT_HOLDING_REFCELL_REF, AWAIT_HOLDING_INVALID_TYPE]); +impl_lint_pass!(AwaitHolding => [ + AWAIT_HOLDING_INVALID_TYPE, + AWAIT_HOLDING_LOCK, + AWAIT_HOLDING_REFCELL_REF, +]); pub struct AwaitHolding { def_ids: DefIdMap<(&'static str, &'static DisallowedPathWithoutReplacement)>, diff --git a/clippy_lints/src/bool_to_int_with_if.rs b/clippy_lints/src/bool_to_int_with_if.rs index 129e77478406..b98a20a90ccb 100644 --- a/clippy_lints/src/bool_to_int_with_if.rs +++ b/clippy_lints/src/bool_to_int_with_if.rs @@ -43,6 +43,7 @@ pedantic, "using if to convert bool to int" } + declare_lint_pass!(BoolToIntWithIf => [BOOL_TO_INT_WITH_IF]); impl<'tcx> LateLintPass<'tcx> for BoolToIntWithIf { diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs index 0bd459d8b021..8b7619d11a83 100644 --- a/clippy_lints/src/booleans.rs +++ b/clippy_lints/src/booleans.rs @@ -74,6 +74,8 @@ "boolean expressions that contain terminals which can be eliminated" } +impl_lint_pass!(NonminimalBool => [NONMINIMAL_BOOL, OVERLY_COMPLEX_BOOL_EXPR]); + // For each pairs, both orders are considered. const METHODS_WITH_NEGATION: [(Option, Symbol, Symbol); 3] = [ (None, sym::is_some, sym::is_none), @@ -91,8 +93,6 @@ pub fn new(conf: &'static Conf) -> Self { } } -impl_lint_pass!(NonminimalBool => [NONMINIMAL_BOOL, OVERLY_COMPLEX_BOOL_EXPR]); - impl<'tcx> LateLintPass<'tcx> for NonminimalBool { fn check_fn( &mut self, diff --git a/clippy_lints/src/box_default.rs b/clippy_lints/src/box_default.rs index b78aa1f1b63d..34b96b1d9d7a 100644 --- a/clippy_lints/src/box_default.rs +++ b/clippy_lints/src/box_default.rs @@ -1,8 +1,8 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::{is_default_equivalent, sym}; use clippy_utils::macros::macro_backtrace; use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::ty::expr_sig; +use clippy_utils::{is_default_equivalent, sym}; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::intravisit::{InferKind, Visitor, VisitorExt, walk_ty}; diff --git a/clippy_lints/src/byte_char_slices.rs b/clippy_lints/src/byte_char_slices.rs index fc9931439e93..6c023189f69e 100644 --- a/clippy_lints/src/byte_char_slices.rs +++ b/clippy_lints/src/byte_char_slices.rs @@ -27,6 +27,7 @@ style, "hard to read byte char slice" } + declare_lint_pass!(ByteCharSlice => [BYTE_CHAR_SLICES]); impl EarlyLintPass for ByteCharSlice { diff --git a/clippy_lints/src/cargo/mod.rs b/clippy_lints/src/cargo/mod.rs index 08d92adbacef..0aa6e4c3e8e2 100644 --- a/clippy_lints/src/cargo/mod.rs +++ b/clippy_lints/src/cargo/mod.rs @@ -56,126 +56,6 @@ "common metadata is defined in `Cargo.toml`" } -declare_clippy_lint! { - /// ### What it does - /// Checks for feature names with prefix `use-`, `with-` or suffix `-support` - /// - /// ### Why is this bad? - /// These prefixes and suffixes have no significant meaning. - /// - /// ### Example - /// ```toml - /// # The `Cargo.toml` with feature name redundancy - /// [features] - /// default = ["use-abc", "with-def", "ghi-support"] - /// use-abc = [] // redundant - /// with-def = [] // redundant - /// ghi-support = [] // redundant - /// ``` - /// - /// Use instead: - /// ```toml - /// [features] - /// default = ["abc", "def", "ghi"] - /// abc = [] - /// def = [] - /// ghi = [] - /// ``` - /// - #[clippy::version = "1.57.0"] - pub REDUNDANT_FEATURE_NAMES, - cargo, - "usage of a redundant feature name" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for negative feature names with prefix `no-` or `not-` - /// - /// ### Why is this bad? - /// Features are supposed to be additive, and negatively-named features violate it. - /// - /// ### Example - /// ```toml - /// # The `Cargo.toml` with negative feature names - /// [features] - /// default = [] - /// no-abc = [] - /// not-def = [] - /// - /// ``` - /// Use instead: - /// ```toml - /// [features] - /// default = ["abc", "def"] - /// abc = [] - /// def = [] - /// - /// ``` - #[clippy::version = "1.57.0"] - pub NEGATIVE_FEATURE_NAMES, - cargo, - "usage of a negative feature name" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks to see if multiple versions of a crate are being - /// used. - /// - /// ### Why is this bad? - /// This bloats the size of targets, and can lead to - /// confusing error messages when structs or traits are used interchangeably - /// between different versions of a crate. - /// - /// ### Known problems - /// Because this can be caused purely by the dependencies - /// themselves, it's not always possible to fix this issue. - /// In those cases, you can allow that specific crate using - /// the `allowed-duplicate-crates` configuration option. - /// - /// ### Example - /// ```toml - /// # This will pull in both winapi v0.3.x and v0.2.x, triggering a warning. - /// [dependencies] - /// ctrlc = "=3.1.0" - /// ansi_term = "=0.11.0" - /// ``` - #[clippy::version = "pre 1.29.0"] - pub MULTIPLE_CRATE_VERSIONS, - cargo, - "multiple versions of the same crate being used" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for wildcard dependencies in the `Cargo.toml`. - /// - /// ### Why is this bad? - /// [As the edition guide says](https://rust-lang-nursery.github.io/edition-guide/rust-2018/cargo-and-crates-io/crates-io-disallows-wildcard-dependencies.html), - /// it is highly unlikely that you work with any possible version of your dependency, - /// and wildcard dependencies would cause unnecessary breakage in the ecosystem. - /// - /// ### Example - /// ```toml - /// [dependencies] - /// regex = "*" - /// ``` - /// Use instead: - /// ```toml - /// [dependencies] - /// # allow patch updates, but not minor or major version changes - /// some_crate_1 = "~1.2.3" - /// - /// # pin the version to a specific version - /// some_crate_2 = "=1.2.3" - /// ``` - #[clippy::version = "1.32.0"] - pub WILDCARD_DEPENDENCIES, - cargo, - "wildcard dependencies being used" -} - declare_clippy_lint! { /// ### What it does /// Checks for lint groups with the same priority as lints in the `Cargo.toml` @@ -213,20 +93,140 @@ "a lint group in `Cargo.toml` at the same priority as a lint" } -pub struct Cargo { - allowed_duplicate_crates: FxHashSet, - ignore_publish: bool, +declare_clippy_lint! { + /// ### What it does + /// Checks to see if multiple versions of a crate are being + /// used. + /// + /// ### Why is this bad? + /// This bloats the size of targets, and can lead to + /// confusing error messages when structs or traits are used interchangeably + /// between different versions of a crate. + /// + /// ### Known problems + /// Because this can be caused purely by the dependencies + /// themselves, it's not always possible to fix this issue. + /// In those cases, you can allow that specific crate using + /// the `allowed-duplicate-crates` configuration option. + /// + /// ### Example + /// ```toml + /// # This will pull in both winapi v0.3.x and v0.2.x, triggering a warning. + /// [dependencies] + /// ctrlc = "=3.1.0" + /// ansi_term = "=0.11.0" + /// ``` + #[clippy::version = "pre 1.29.0"] + pub MULTIPLE_CRATE_VERSIONS, + cargo, + "multiple versions of the same crate being used" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for negative feature names with prefix `no-` or `not-` + /// + /// ### Why is this bad? + /// Features are supposed to be additive, and negatively-named features violate it. + /// + /// ### Example + /// ```toml + /// # The `Cargo.toml` with negative feature names + /// [features] + /// default = [] + /// no-abc = [] + /// not-def = [] + /// + /// ``` + /// Use instead: + /// ```toml + /// [features] + /// default = ["abc", "def"] + /// abc = [] + /// def = [] + /// + /// ``` + #[clippy::version = "1.57.0"] + pub NEGATIVE_FEATURE_NAMES, + cargo, + "usage of a negative feature name" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for feature names with prefix `use-`, `with-` or suffix `-support` + /// + /// ### Why is this bad? + /// These prefixes and suffixes have no significant meaning. + /// + /// ### Example + /// ```toml + /// # The `Cargo.toml` with feature name redundancy + /// [features] + /// default = ["use-abc", "with-def", "ghi-support"] + /// use-abc = [] // redundant + /// with-def = [] // redundant + /// ghi-support = [] // redundant + /// ``` + /// + /// Use instead: + /// ```toml + /// [features] + /// default = ["abc", "def", "ghi"] + /// abc = [] + /// def = [] + /// ghi = [] + /// ``` + /// + #[clippy::version = "1.57.0"] + pub REDUNDANT_FEATURE_NAMES, + cargo, + "usage of a redundant feature name" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for wildcard dependencies in the `Cargo.toml`. + /// + /// ### Why is this bad? + /// [As the edition guide says](https://rust-lang-nursery.github.io/edition-guide/rust-2018/cargo-and-crates-io/crates-io-disallows-wildcard-dependencies.html), + /// it is highly unlikely that you work with any possible version of your dependency, + /// and wildcard dependencies would cause unnecessary breakage in the ecosystem. + /// + /// ### Example + /// ```toml + /// [dependencies] + /// regex = "*" + /// ``` + /// Use instead: + /// ```toml + /// [dependencies] + /// # allow patch updates, but not minor or major version changes + /// some_crate_1 = "~1.2.3" + /// + /// # pin the version to a specific version + /// some_crate_2 = "=1.2.3" + /// ``` + #[clippy::version = "1.32.0"] + pub WILDCARD_DEPENDENCIES, + cargo, + "wildcard dependencies being used" } impl_lint_pass!(Cargo => [ CARGO_COMMON_METADATA, - REDUNDANT_FEATURE_NAMES, - NEGATIVE_FEATURE_NAMES, - MULTIPLE_CRATE_VERSIONS, - WILDCARD_DEPENDENCIES, LINT_GROUPS_PRIORITY, + MULTIPLE_CRATE_VERSIONS, + NEGATIVE_FEATURE_NAMES, + REDUNDANT_FEATURE_NAMES, + WILDCARD_DEPENDENCIES, ]); +pub struct Cargo { + allowed_duplicate_crates: FxHashSet, + ignore_publish: bool, +} + impl Cargo { pub fn new(conf: &'static Conf) -> Self { Self { diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index 3c9ebef73f0d..75761de4ae73 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -36,50 +36,231 @@ declare_clippy_lint! { /// ### What it does - /// Checks for casts from any numeric type to a float type where - /// the receiving type cannot store all values from the original type without - /// rounding errors. This possible rounding is to be expected, so this lint is - /// `Allow` by default. + /// Checks for the usage of `as *const _` or `as *mut _` conversion using inferred type. /// - /// Basically, this warns on casting any integer with 32 or more bits to `f32` - /// or any 64-bit integer to `f64`. - /// - /// ### Why is this bad? - /// It's not bad at all. But in some applications it can be - /// helpful to know where precision loss can take place. This lint can help find - /// those places in the code. + /// ### Why restrict this? + /// The conversion might include a dangerous cast that might go undetected due to the type being inferred. /// /// ### Example /// ```no_run - /// let x = u64::MAX; - /// x as f64; + /// fn as_usize(t: &T) -> usize { + /// // BUG: `t` is already a reference, so we will here + /// // return a dangling pointer to a temporary value instead + /// &t as *const _ as usize + /// } /// ``` - #[clippy::version = "pre 1.29.0"] - pub CAST_PRECISION_LOSS, - pedantic, - "casts that cause loss of precision, e.g., `x as f32` where `x: u64`" + /// Use instead: + /// ```no_run + /// fn as_usize(t: &T) -> usize { + /// t as *const T as usize + /// } + /// ``` + #[clippy::version = "1.85.0"] + pub AS_POINTER_UNDERSCORE, + restriction, + "detects `as *mut _` and `as *const _` conversion" } declare_clippy_lint! { /// ### What it does - /// Checks for casts from a signed to an unsigned numeric - /// type. In this case, negative values wrap around to large positive values, - /// which can be quite surprising in practice. However, since the cast works as - /// defined, this lint is `Allow` by default. + /// Checks for the result of a `&self`-taking `as_ptr` being cast to a mutable pointer. /// /// ### Why is this bad? - /// Possibly surprising results. You can activate this lint - /// as a one-time check to see where numeric wrapping can arise. + /// Since `as_ptr` takes a `&self`, the pointer won't have write permissions unless interior + /// mutability is used, making it unlikely that having it as a mutable pointer is correct. /// /// ### Example /// ```no_run - /// let y: i8 = -1; - /// y as u64; // will return 18446744073709551615 + /// let mut vec = Vec::::with_capacity(1); + /// let ptr = vec.as_ptr() as *mut u8; + /// unsafe { ptr.write(4) }; // UNDEFINED BEHAVIOUR + /// ``` + /// Use instead: + /// ```no_run + /// let mut vec = Vec::::with_capacity(1); + /// let ptr = vec.as_mut_ptr(); + /// unsafe { ptr.write(4) }; + /// ``` + #[clippy::version = "1.66.0"] + pub AS_PTR_CAST_MUT, + nursery, + "casting the result of the `&self`-taking `as_ptr` to a mutable pointer" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for the usage of `as _` conversion using inferred type. + /// + /// ### Why restrict this? + /// The conversion might include lossy conversion or a dangerous cast that might go + /// undetected due to the type being inferred. + /// + /// The lint is allowed by default as using `_` is less wordy than always specifying the type. + /// + /// ### Example + /// ```no_run + /// fn foo(n: usize) {} + /// let n: u16 = 256; + /// foo(n as _); + /// ``` + /// Use instead: + /// ```no_run + /// fn foo(n: usize) {} + /// let n: u16 = 256; + /// foo(n as usize); + /// ``` + #[clippy::version = "1.63.0"] + pub AS_UNDERSCORE, + restriction, + "detects `as _` conversion" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for the usage of `&expr as *const T` or + /// `&mut expr as *mut T`, and suggest using `&raw const` or + /// `&raw mut` instead. + /// + /// ### Why is this bad? + /// This would improve readability and avoid creating a reference + /// that points to an uninitialized value or unaligned place. + /// Read the `&raw` explanation in the Reference for more information. + /// + /// ### Example + /// ```no_run + /// let val = 1; + /// let p = &val as *const i32; + /// + /// let mut val_mut = 1; + /// let p_mut = &mut val_mut as *mut i32; + /// ``` + /// Use instead: + /// ```no_run + /// let val = 1; + /// let p = &raw const val; + /// + /// let mut val_mut = 1; + /// let p_mut = &raw mut val_mut; + /// ``` + #[clippy::version = "1.60.0"] + pub BORROW_AS_PTR, + pedantic, + "borrowing just to cast to a raw pointer" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of the `abs()` method that cast the result to unsigned. + /// + /// ### Why is this bad? + /// The `unsigned_abs()` method avoids panic when called on the MIN value. + /// + /// ### Example + /// ```no_run + /// let x: i32 = -42; + /// let y: u32 = x.abs() as u32; + /// ``` + /// Use instead: + /// ```no_run + /// let x: i32 = -42; + /// let y: u32 = x.unsigned_abs(); + /// ``` + #[clippy::version = "1.62.0"] + pub CAST_ABS_TO_UNSIGNED, + suspicious, + "casting the result of `abs()` to an unsigned integer can panic" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for casts from an enum tuple constructor to an integer. + /// + /// ### Why is this bad? + /// The cast is easily confused with casting a c-like enum value to an integer. + /// + /// ### Example + /// ```no_run + /// enum E { X(i32) }; + /// let _ = E::X as usize; + /// ``` + #[clippy::version = "1.61.0"] + pub CAST_ENUM_CONSTRUCTOR, + suspicious, + "casts from an enum tuple constructor to an integer" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for casts from an enum type to an integral type that will definitely truncate the + /// value. + /// + /// ### Why is this bad? + /// The resulting integral value will not match the value of the variant it came from. + /// + /// ### Example + /// ```no_run + /// enum E { X = 256 }; + /// let _ = E::X as u8; + /// ``` + #[clippy::version = "1.61.0"] + pub CAST_ENUM_TRUNCATION, + suspicious, + "casts from an enum type to an integral type that will truncate the value" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for casts between numeric types that can be replaced by safe + /// conversion functions. + /// + /// ### Why is this bad? + /// Rust's `as` keyword will perform many kinds of conversions, including + /// silently lossy conversions. Conversion functions such as `i32::from` + /// will only perform lossless conversions. Using the conversion functions + /// prevents conversions from becoming silently lossy if the input types + /// ever change, and makes it clear for people reading the code that the + /// conversion is lossless. + /// + /// ### Example + /// ```no_run + /// fn as_u64(x: u8) -> u64 { + /// x as u64 + /// } + /// ``` + /// + /// Using `::from` would look like this: + /// + /// ```no_run + /// fn as_u64(x: u8) -> u64 { + /// u64::from(x) + /// } /// ``` #[clippy::version = "pre 1.29.0"] - pub CAST_SIGN_LOSS, + pub CAST_LOSSLESS, pedantic, - "casts from signed types to unsigned types, e.g., `x as u32` where `x: i32`" + "casts using `as` that are known to be lossless, e.g., `x as u64` where `x: u8`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for a known NaN float being cast to an integer + /// + /// ### Why is this bad? + /// NaNs are cast into zero, so one could simply use this and make the + /// code more readable. The lint could also hint at a programmer error. + /// + /// ### Example + /// ```rust,ignore + /// let _ = (0.0_f32 / 0.0) as u64; + /// ``` + /// Use instead: + /// ```rust,ignore + /// let _ = 0_u64; + /// ``` + #[clippy::version = "1.66.0"] + pub CAST_NAN_TO_INT, + suspicious, + "casting a known floating-point NaN into an integer" } declare_clippy_lint! { @@ -159,71 +340,28 @@ declare_clippy_lint! { /// ### What it does - /// Checks for casts between numeric types that can be replaced by safe - /// conversion functions. + /// Checks for casts from any numeric type to a float type where + /// the receiving type cannot store all values from the original type without + /// rounding errors. This possible rounding is to be expected, so this lint is + /// `Allow` by default. + /// + /// Basically, this warns on casting any integer with 32 or more bits to `f32` + /// or any 64-bit integer to `f64`. /// /// ### Why is this bad? - /// Rust's `as` keyword will perform many kinds of conversions, including - /// silently lossy conversions. Conversion functions such as `i32::from` - /// will only perform lossless conversions. Using the conversion functions - /// prevents conversions from becoming silently lossy if the input types - /// ever change, and makes it clear for people reading the code that the - /// conversion is lossless. + /// It's not bad at all. But in some applications it can be + /// helpful to know where precision loss can take place. This lint can help find + /// those places in the code. /// /// ### Example /// ```no_run - /// fn as_u64(x: u8) -> u64 { - /// x as u64 - /// } - /// ``` - /// - /// Using `::from` would look like this: - /// - /// ```no_run - /// fn as_u64(x: u8) -> u64 { - /// u64::from(x) - /// } + /// let x = u64::MAX; + /// x as f64; /// ``` #[clippy::version = "pre 1.29.0"] - pub CAST_LOSSLESS, + pub CAST_PRECISION_LOSS, pedantic, - "casts using `as` that are known to be lossless, e.g., `x as u64` where `x: u8`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for casts to the same type, casts of int literals to integer - /// types, casts of float literals to float types, and casts between raw - /// pointers that don't change type or constness. - /// - /// ### Why is this bad? - /// It's just unnecessary. - /// - /// ### Known problems - /// When the expression on the left is a function call, the lint considers - /// the return type to be a type alias if it's aliased through a `use` - /// statement (like `use std::io::Result as IoResult`). It will not lint - /// such cases. - /// - /// This check will only work on primitive types without any intermediate - /// references: raw pointers and trait objects may or may not work. - /// - /// ### Example - /// ```no_run - /// let _ = 2i32 as i32; - /// let _ = 0.5 as f32; - /// ``` - /// - /// Better: - /// - /// ```no_run - /// let _ = 2_i32; - /// let _ = 0.5_f32; - /// ``` - #[clippy::version = "pre 1.29.0"] - pub UNNECESSARY_CAST, - complexity, - "cast to the same type, e.g., `x as i32` where `x: i32`" + "casts that cause loss of precision, e.g., `x as f32` where `x: u64`" } declare_clippy_lint! { @@ -254,6 +392,154 @@ "cast from a pointer to a more strictly aligned pointer" } +declare_clippy_lint! { + /// ### What it does + /// Checks for casts from a signed to an unsigned numeric + /// type. In this case, negative values wrap around to large positive values, + /// which can be quite surprising in practice. However, since the cast works as + /// defined, this lint is `Allow` by default. + /// + /// ### Why is this bad? + /// Possibly surprising results. You can activate this lint + /// as a one-time check to see where numeric wrapping can arise. + /// + /// ### Example + /// ```no_run + /// let y: i8 = -1; + /// y as u64; // will return 18446744073709551615 + /// ``` + #[clippy::version = "pre 1.29.0"] + pub CAST_SIGN_LOSS, + pedantic, + "casts from signed types to unsigned types, e.g., `x as u32` where `x: i32`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for `as` casts between raw pointers to slices with differently sized elements. + /// + /// ### Why is this bad? + /// The produced raw pointer to a slice does not update its length metadata. The produced + /// pointer will point to a different number of bytes than the original pointer because the + /// length metadata of a raw slice pointer is in elements rather than bytes. + /// Producing a slice reference from the raw pointer will either create a slice with + /// less data (which can be surprising) or create a slice with more data and cause Undefined Behavior. + /// + /// ### Example + /// // Missing data + /// ```no_run + /// let a = [1_i32, 2, 3, 4]; + /// let p = &a as *const [i32] as *const [u8]; + /// unsafe { + /// println!("{:?}", &*p); + /// } + /// ``` + /// // Undefined Behavior (note: also potential alignment issues) + /// ```no_run + /// let a = [1_u8, 2, 3, 4]; + /// let p = &a as *const [u8] as *const [u32]; + /// unsafe { + /// println!("{:?}", &*p); + /// } + /// ``` + /// Instead use `ptr::slice_from_raw_parts` to construct a slice from a data pointer and the correct length + /// ```no_run + /// let a = [1_i32, 2, 3, 4]; + /// let old_ptr = &a as *const [i32]; + /// // The data pointer is cast to a pointer to the target `u8` not `[u8]` + /// // The length comes from the known length of 4 i32s times the 4 bytes per i32 + /// let new_ptr = core::ptr::slice_from_raw_parts(old_ptr as *const u8, 16); + /// unsafe { + /// println!("{:?}", &*new_ptr); + /// } + /// ``` + #[clippy::version = "1.61.0"] + pub CAST_SLICE_DIFFERENT_SIZES, + correctness, + "casting using `as` between raw pointers to slices of types with different sizes" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for a raw slice being cast to a slice pointer + /// + /// ### Why is this bad? + /// This can result in multiple `&mut` references to the same location when only a pointer is + /// required. + /// `ptr::slice_from_raw_parts` is a safe alternative that doesn't require + /// the same [safety requirements] to be upheld. + /// + /// ### Example + /// ```rust,ignore + /// let _: *const [u8] = std::slice::from_raw_parts(ptr, len) as *const _; + /// let _: *mut [u8] = std::slice::from_raw_parts_mut(ptr, len) as *mut _; + /// ``` + /// Use instead: + /// ```rust,ignore + /// let _: *const [u8] = std::ptr::slice_from_raw_parts(ptr, len); + /// let _: *mut [u8] = std::ptr::slice_from_raw_parts_mut(ptr, len); + /// ``` + /// [safety requirements]: https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html#safety + #[clippy::version = "1.65.0"] + pub CAST_SLICE_FROM_RAW_PARTS, + suspicious, + "casting a slice created from a pointer and length to a slice pointer" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for expressions where a character literal is cast + /// to `u8` and suggests using a byte literal instead. + /// + /// ### Why is this bad? + /// In general, casting values to smaller types is + /// error-prone and should be avoided where possible. In the particular case of + /// converting a character literal to `u8`, it is easy to avoid by just using a + /// byte literal instead. As an added bonus, `b'a'` is also slightly shorter + /// than `'a' as u8`. + /// + /// ### Example + /// ```rust,ignore + /// 'x' as u8 + /// ``` + /// + /// A better version, using the byte literal: + /// + /// ```rust,ignore + /// b'x' + /// ``` + #[clippy::version = "pre 1.29.0"] + pub CHAR_LIT_AS_U8, + complexity, + "casting a character literal to `u8` truncates" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for casts of a primitive method pointer like `max`/`min` to any integer type. + /// + /// ### Why restrict this? + /// Casting a function pointer to an integer can have surprising results and can occur + /// accidentally if parentheses are omitted from a function call. If you aren't doing anything + /// low-level with function pointers then you can opt out of casting functions to integers in + /// order to avoid mistakes. Alternatively, you can use this lint to audit all uses of function + /// pointer casts in your code. + /// + /// ### Example + /// ```no_run + /// let _ = u16::max as usize; + /// ``` + /// + /// Use instead: + /// ```no_run + /// let _ = u16::MAX as usize; + /// ``` + #[clippy::version = "1.89.0"] + pub CONFUSING_METHOD_TO_NUMERIC_CAST, + suspicious, + "casting a primitive method pointer to any integer type" +} + declare_clippy_lint! { /// ### What it does /// Checks for casts of function pointers to something other than `usize`. @@ -284,39 +570,6 @@ "casting a function pointer to a numeric type other than `usize`" } -declare_clippy_lint! { - /// ### What it does - /// Checks for casts of a function pointer to a numeric type not wide enough to - /// store an address. - /// - /// ### Why is this bad? - /// Such a cast discards some bits of the function's address. If this is intended, it would be more - /// clearly expressed by casting to `usize` first, then casting the `usize` to the intended type (with - /// a comment) to perform the truncation. - /// - /// ### Example - /// ```no_run - /// fn fn1() -> i16 { - /// 1 - /// }; - /// let _ = fn1 as i32; - /// ``` - /// - /// Use instead: - /// ```no_run - /// // Cast to usize first, then comment with the reason for the truncation - /// fn fn1() -> i16 { - /// 1 - /// }; - /// let fn_ptr = fn1 as usize; - /// let fn_ptr_truncated = fn_ptr as i32; - /// ``` - #[clippy::version = "pre 1.29.0"] - pub FN_TO_NUMERIC_CAST_WITH_TRUNCATION, - style, - "casting a function pointer to a numeric type not wide enough to store the address" -} - declare_clippy_lint! { /// ### What it does /// Checks for casts of a function pointer to any integer type. @@ -361,30 +614,87 @@ declare_clippy_lint! { /// ### What it does - /// Checks for expressions where a character literal is cast - /// to `u8` and suggests using a byte literal instead. + /// Checks for casts of a function pointer to a numeric type not wide enough to + /// store an address. /// /// ### Why is this bad? - /// In general, casting values to smaller types is - /// error-prone and should be avoided where possible. In the particular case of - /// converting a character literal to `u8`, it is easy to avoid by just using a - /// byte literal instead. As an added bonus, `b'a'` is also slightly shorter - /// than `'a' as u8`. + /// Such a cast discards some bits of the function's address. If this is intended, it would be more + /// clearly expressed by casting to `usize` first, then casting the `usize` to the intended type (with + /// a comment) to perform the truncation. /// /// ### Example - /// ```rust,ignore - /// 'x' as u8 + /// ```no_run + /// fn fn1() -> i16 { + /// 1 + /// }; + /// let _ = fn1 as i32; /// ``` /// - /// A better version, using the byte literal: - /// - /// ```rust,ignore - /// b'x' + /// Use instead: + /// ```no_run + /// // Cast to usize first, then comment with the reason for the truncation + /// fn fn1() -> i16 { + /// 1 + /// }; + /// let fn_ptr = fn1 as usize; + /// let fn_ptr_truncated = fn_ptr as i32; /// ``` #[clippy::version = "pre 1.29.0"] - pub CHAR_LIT_AS_U8, - complexity, - "casting a character literal to `u8` truncates" + pub FN_TO_NUMERIC_CAST_WITH_TRUNCATION, + style, + "casting a function pointer to a numeric type not wide enough to store the address" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for casts of small constant literals or `mem::align_of` results to raw pointers. + /// + /// ### Why is this bad? + /// This creates a dangling pointer and is better expressed as + /// {`std`, `core`}`::ptr::`{`dangling`, `dangling_mut`}. + /// + /// ### Example + /// ```no_run + /// let ptr = 4 as *const u32; + /// let aligned = std::mem::align_of::() as *const u32; + /// let mut_ptr: *mut i64 = 8 as *mut _; + /// ``` + /// Use instead: + /// ```no_run + /// let ptr = std::ptr::dangling::(); + /// let aligned = std::ptr::dangling::(); + /// let mut_ptr: *mut i64 = std::ptr::dangling_mut(); + /// ``` + #[clippy::version = "1.88.0"] + pub MANUAL_DANGLING_PTR, + style, + "casting small constant literals to pointers to create dangling pointers" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for bindings (constants, statics, or let bindings) that are defined + /// with one numeric type but are consistently cast to a different type in all usages. + /// + /// ### Why is this bad? + /// If a binding is always cast to a different type when used, it would be clearer + /// and more efficient to define it with the target type from the start. + /// + /// ### Example + /// ```no_run + /// const SIZE: u16 = 15; + /// let arr: [u8; SIZE as usize] = [0; SIZE as usize]; + /// ``` + /// + /// Use instead: + /// ```no_run + /// const SIZE: usize = 15; + /// let arr: [u8; SIZE] = [0; SIZE]; + /// ``` + #[clippy::version = "1.93.0"] + pub NEEDLESS_TYPE_CAST, + nursery, + "binding defined with one type but always cast to another" } declare_clippy_lint! { @@ -455,243 +765,62 @@ declare_clippy_lint! { /// ### What it does - /// Checks for casts from an enum type to an integral type that will definitely truncate the - /// value. + /// Checks for casts of references to pointer using `as` + /// and suggests `std::ptr::from_ref` and `std::ptr::from_mut` instead. /// /// ### Why is this bad? - /// The resulting integral value will not match the value of the variant it came from. + /// Using `as` casts may result in silently changing mutability or type. /// /// ### Example /// ```no_run - /// enum E { X = 256 }; - /// let _ = E::X as u8; - /// ``` - #[clippy::version = "1.61.0"] - pub CAST_ENUM_TRUNCATION, - suspicious, - "casts from an enum type to an integral type that will truncate the value" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `as` casts between raw pointers to slices with differently sized elements. - /// - /// ### Why is this bad? - /// The produced raw pointer to a slice does not update its length metadata. The produced - /// pointer will point to a different number of bytes than the original pointer because the - /// length metadata of a raw slice pointer is in elements rather than bytes. - /// Producing a slice reference from the raw pointer will either create a slice with - /// less data (which can be surprising) or create a slice with more data and cause Undefined Behavior. - /// - /// ### Example - /// // Missing data - /// ```no_run - /// let a = [1_i32, 2, 3, 4]; - /// let p = &a as *const [i32] as *const [u8]; - /// unsafe { - /// println!("{:?}", &*p); - /// } - /// ``` - /// // Undefined Behavior (note: also potential alignment issues) - /// ```no_run - /// let a = [1_u8, 2, 3, 4]; - /// let p = &a as *const [u8] as *const [u32]; - /// unsafe { - /// println!("{:?}", &*p); - /// } - /// ``` - /// Instead use `ptr::slice_from_raw_parts` to construct a slice from a data pointer and the correct length - /// ```no_run - /// let a = [1_i32, 2, 3, 4]; - /// let old_ptr = &a as *const [i32]; - /// // The data pointer is cast to a pointer to the target `u8` not `[u8]` - /// // The length comes from the known length of 4 i32s times the 4 bytes per i32 - /// let new_ptr = core::ptr::slice_from_raw_parts(old_ptr as *const u8, 16); - /// unsafe { - /// println!("{:?}", &*new_ptr); - /// } - /// ``` - #[clippy::version = "1.61.0"] - pub CAST_SLICE_DIFFERENT_SIZES, - correctness, - "casting using `as` between raw pointers to slices of types with different sizes" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for casts from an enum tuple constructor to an integer. - /// - /// ### Why is this bad? - /// The cast is easily confused with casting a c-like enum value to an integer. - /// - /// ### Example - /// ```no_run - /// enum E { X(i32) }; - /// let _ = E::X as usize; - /// ``` - #[clippy::version = "1.61.0"] - pub CAST_ENUM_CONSTRUCTOR, - suspicious, - "casts from an enum tuple constructor to an integer" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of the `abs()` method that cast the result to unsigned. - /// - /// ### Why is this bad? - /// The `unsigned_abs()` method avoids panic when called on the MIN value. - /// - /// ### Example - /// ```no_run - /// let x: i32 = -42; - /// let y: u32 = x.abs() as u32; + /// let a_ref = &1; + /// let a_ptr = a_ref as *const _; /// ``` /// Use instead: /// ```no_run - /// let x: i32 = -42; - /// let y: u32 = x.unsigned_abs(); + /// let a_ref = &1; + /// let a_ptr = std::ptr::from_ref(a_ref); /// ``` - #[clippy::version = "1.62.0"] - pub CAST_ABS_TO_UNSIGNED, - suspicious, - "casting the result of `abs()` to an unsigned integer can panic" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the usage of `as _` conversion using inferred type. - /// - /// ### Why restrict this? - /// The conversion might include lossy conversion or a dangerous cast that might go - /// undetected due to the type being inferred. - /// - /// The lint is allowed by default as using `_` is less wordy than always specifying the type. - /// - /// ### Example - /// ```no_run - /// fn foo(n: usize) {} - /// let n: u16 = 256; - /// foo(n as _); - /// ``` - /// Use instead: - /// ```no_run - /// fn foo(n: usize) {} - /// let n: u16 = 256; - /// foo(n as usize); - /// ``` - #[clippy::version = "1.63.0"] - pub AS_UNDERSCORE, - restriction, - "detects `as _` conversion" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the usage of `&expr as *const T` or - /// `&mut expr as *mut T`, and suggest using `&raw const` or - /// `&raw mut` instead. - /// - /// ### Why is this bad? - /// This would improve readability and avoid creating a reference - /// that points to an uninitialized value or unaligned place. - /// Read the `&raw` explanation in the Reference for more information. - /// - /// ### Example - /// ```no_run - /// let val = 1; - /// let p = &val as *const i32; - /// - /// let mut val_mut = 1; - /// let p_mut = &mut val_mut as *mut i32; - /// ``` - /// Use instead: - /// ```no_run - /// let val = 1; - /// let p = &raw const val; - /// - /// let mut val_mut = 1; - /// let p_mut = &raw mut val_mut; - /// ``` - #[clippy::version = "1.60.0"] - pub BORROW_AS_PTR, + #[clippy::version = "1.78.0"] + pub REF_AS_PTR, pedantic, - "borrowing just to cast to a raw pointer" + "using `as` to cast a reference to pointer" } declare_clippy_lint! { /// ### What it does - /// Checks for a raw slice being cast to a slice pointer + /// Checks for casts to the same type, casts of int literals to integer + /// types, casts of float literals to float types, and casts between raw + /// pointers that don't change type or constness. /// /// ### Why is this bad? - /// This can result in multiple `&mut` references to the same location when only a pointer is - /// required. - /// `ptr::slice_from_raw_parts` is a safe alternative that doesn't require - /// the same [safety requirements] to be upheld. + /// It's just unnecessary. /// - /// ### Example - /// ```rust,ignore - /// let _: *const [u8] = std::slice::from_raw_parts(ptr, len) as *const _; - /// let _: *mut [u8] = std::slice::from_raw_parts_mut(ptr, len) as *mut _; - /// ``` - /// Use instead: - /// ```rust,ignore - /// let _: *const [u8] = std::ptr::slice_from_raw_parts(ptr, len); - /// let _: *mut [u8] = std::ptr::slice_from_raw_parts_mut(ptr, len); - /// ``` - /// [safety requirements]: https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html#safety - #[clippy::version = "1.65.0"] - pub CAST_SLICE_FROM_RAW_PARTS, - suspicious, - "casting a slice created from a pointer and length to a slice pointer" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the result of a `&self`-taking `as_ptr` being cast to a mutable pointer. + /// ### Known problems + /// When the expression on the left is a function call, the lint considers + /// the return type to be a type alias if it's aliased through a `use` + /// statement (like `use std::io::Result as IoResult`). It will not lint + /// such cases. /// - /// ### Why is this bad? - /// Since `as_ptr` takes a `&self`, the pointer won't have write permissions unless interior - /// mutability is used, making it unlikely that having it as a mutable pointer is correct. + /// This check will only work on primitive types without any intermediate + /// references: raw pointers and trait objects may or may not work. /// /// ### Example /// ```no_run - /// let mut vec = Vec::::with_capacity(1); - /// let ptr = vec.as_ptr() as *mut u8; - /// unsafe { ptr.write(4) }; // UNDEFINED BEHAVIOUR + /// let _ = 2i32 as i32; + /// let _ = 0.5 as f32; /// ``` - /// Use instead: + /// + /// Better: + /// /// ```no_run - /// let mut vec = Vec::::with_capacity(1); - /// let ptr = vec.as_mut_ptr(); - /// unsafe { ptr.write(4) }; + /// let _ = 2_i32; + /// let _ = 0.5_f32; /// ``` - #[clippy::version = "1.66.0"] - pub AS_PTR_CAST_MUT, - nursery, - "casting the result of the `&self`-taking `as_ptr` to a mutable pointer" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for a known NaN float being cast to an integer - /// - /// ### Why is this bad? - /// NaNs are cast into zero, so one could simply use this and make the - /// code more readable. The lint could also hint at a programmer error. - /// - /// ### Example - /// ```rust,ignore - /// let _ = (0.0_f32 / 0.0) as u64; - /// ``` - /// Use instead: - /// ```rust,ignore - /// let _ = 0_u64; - /// ``` - #[clippy::version = "1.66.0"] - pub CAST_NAN_TO_INT, - suspicious, - "casting a known floating-point NaN into an integer" + #[clippy::version = "pre 1.29.0"] + pub UNNECESSARY_CAST, + complexity, + "cast to the same type, e.g., `x as i32` where `x: i32`" } declare_clippy_lint! { @@ -717,134 +846,36 @@ "using `0 as *{const, mut} T`" } -declare_clippy_lint! { - /// ### What it does - /// Checks for casts of references to pointer using `as` - /// and suggests `std::ptr::from_ref` and `std::ptr::from_mut` instead. - /// - /// ### Why is this bad? - /// Using `as` casts may result in silently changing mutability or type. - /// - /// ### Example - /// ```no_run - /// let a_ref = &1; - /// let a_ptr = a_ref as *const _; - /// ``` - /// Use instead: - /// ```no_run - /// let a_ref = &1; - /// let a_ptr = std::ptr::from_ref(a_ref); - /// ``` - #[clippy::version = "1.78.0"] - pub REF_AS_PTR, - pedantic, - "using `as` to cast a reference to pointer" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the usage of `as *const _` or `as *mut _` conversion using inferred type. - /// - /// ### Why restrict this? - /// The conversion might include a dangerous cast that might go undetected due to the type being inferred. - /// - /// ### Example - /// ```no_run - /// fn as_usize(t: &T) -> usize { - /// // BUG: `t` is already a reference, so we will here - /// // return a dangling pointer to a temporary value instead - /// &t as *const _ as usize - /// } - /// ``` - /// Use instead: - /// ```no_run - /// fn as_usize(t: &T) -> usize { - /// t as *const T as usize - /// } - /// ``` - #[clippy::version = "1.85.0"] - pub AS_POINTER_UNDERSCORE, - restriction, - "detects `as *mut _` and `as *const _` conversion" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for casts of small constant literals or `mem::align_of` results to raw pointers. - /// - /// ### Why is this bad? - /// This creates a dangling pointer and is better expressed as - /// {`std`, `core`}`::ptr::`{`dangling`, `dangling_mut`}. - /// - /// ### Example - /// ```no_run - /// let ptr = 4 as *const u32; - /// let aligned = std::mem::align_of::() as *const u32; - /// let mut_ptr: *mut i64 = 8 as *mut _; - /// ``` - /// Use instead: - /// ```no_run - /// let ptr = std::ptr::dangling::(); - /// let aligned = std::ptr::dangling::(); - /// let mut_ptr: *mut i64 = std::ptr::dangling_mut(); - /// ``` - #[clippy::version = "1.88.0"] - pub MANUAL_DANGLING_PTR, - style, - "casting small constant literals to pointers to create dangling pointers" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for casts of a primitive method pointer like `max`/`min` to any integer type. - /// - /// ### Why restrict this? - /// Casting a function pointer to an integer can have surprising results and can occur - /// accidentally if parentheses are omitted from a function call. If you aren't doing anything - /// low-level with function pointers then you can opt out of casting functions to integers in - /// order to avoid mistakes. Alternatively, you can use this lint to audit all uses of function - /// pointer casts in your code. - /// - /// ### Example - /// ```no_run - /// let _ = u16::max as usize; - /// ``` - /// - /// Use instead: - /// ```no_run - /// let _ = u16::MAX as usize; - /// ``` - #[clippy::version = "1.89.0"] - pub CONFUSING_METHOD_TO_NUMERIC_CAST, - suspicious, - "casting a primitive method pointer to any integer type" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for bindings (constants, statics, or let bindings) that are defined - /// with one numeric type but are consistently cast to a different type in all usages. - /// - /// ### Why is this bad? - /// If a binding is always cast to a different type when used, it would be clearer - /// and more efficient to define it with the target type from the start. - /// - /// ### Example - /// ```no_run - /// const SIZE: u16 = 15; - /// let arr: [u8; SIZE as usize] = [0; SIZE as usize]; - /// ``` - /// - /// Use instead: - /// ```no_run - /// const SIZE: usize = 15; - /// let arr: [u8; SIZE] = [0; SIZE]; - /// ``` - #[clippy::version = "1.93.0"] - pub NEEDLESS_TYPE_CAST, - nursery, - "binding defined with one type but always cast to another" -} +impl_lint_pass!(Casts => [ + AS_POINTER_UNDERSCORE, + AS_PTR_CAST_MUT, + AS_UNDERSCORE, + BORROW_AS_PTR, + CAST_ABS_TO_UNSIGNED, + CAST_ENUM_CONSTRUCTOR, + CAST_ENUM_TRUNCATION, + CAST_LOSSLESS, + CAST_NAN_TO_INT, + CAST_POSSIBLE_TRUNCATION, + CAST_POSSIBLE_WRAP, + CAST_PRECISION_LOSS, + CAST_PTR_ALIGNMENT, + CAST_SIGN_LOSS, + CAST_SLICE_DIFFERENT_SIZES, + CAST_SLICE_FROM_RAW_PARTS, + CHAR_LIT_AS_U8, + CONFUSING_METHOD_TO_NUMERIC_CAST, + FN_TO_NUMERIC_CAST, + FN_TO_NUMERIC_CAST_ANY, + FN_TO_NUMERIC_CAST_WITH_TRUNCATION, + MANUAL_DANGLING_PTR, + NEEDLESS_TYPE_CAST, + PTR_AS_PTR, + PTR_CAST_CONSTNESS, + REF_AS_PTR, + UNNECESSARY_CAST, + ZERO_PTR, +]); pub struct Casts { msrv: Msrv, @@ -856,37 +887,6 @@ pub fn new(conf: &'static Conf) -> Self { } } -impl_lint_pass!(Casts => [ - CAST_PRECISION_LOSS, - CAST_SIGN_LOSS, - CAST_POSSIBLE_TRUNCATION, - CAST_POSSIBLE_WRAP, - CAST_LOSSLESS, - CAST_PTR_ALIGNMENT, - CAST_SLICE_DIFFERENT_SIZES, - UNNECESSARY_CAST, - FN_TO_NUMERIC_CAST_ANY, - FN_TO_NUMERIC_CAST, - FN_TO_NUMERIC_CAST_WITH_TRUNCATION, - CHAR_LIT_AS_U8, - PTR_AS_PTR, - PTR_CAST_CONSTNESS, - CAST_ENUM_TRUNCATION, - CAST_ENUM_CONSTRUCTOR, - CAST_ABS_TO_UNSIGNED, - AS_UNDERSCORE, - BORROW_AS_PTR, - CAST_SLICE_FROM_RAW_PARTS, - AS_PTR_CAST_MUT, - CAST_NAN_TO_INT, - ZERO_PTR, - REF_AS_PTR, - AS_POINTER_UNDERSCORE, - MANUAL_DANGLING_PTR, - CONFUSING_METHOD_TO_NUMERIC_CAST, - NEEDLESS_TYPE_CAST, -]); - impl<'tcx> LateLintPass<'tcx> for Casts { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if expr.span.in_external_macro(cx.sess().source_map()) { diff --git a/clippy_lints/src/checked_conversions.rs b/clippy_lints/src/checked_conversions.rs index 8303897d1294..5e9009c67197 100644 --- a/clippy_lints/src/checked_conversions.rs +++ b/clippy_lints/src/checked_conversions.rs @@ -34,6 +34,8 @@ "`try_from` could replace manual bounds checking when casting" } +impl_lint_pass!(CheckedConversions => [CHECKED_CONVERSIONS]); + pub struct CheckedConversions { msrv: Msrv, } @@ -44,8 +46,6 @@ pub fn new(conf: &'static Conf) -> Self { } } -impl_lint_pass!(CheckedConversions => [CHECKED_CONVERSIONS]); - impl LateLintPass<'_> for CheckedConversions { fn check_expr(&mut self, cx: &LateContext<'_>, item: &Expr<'_>) { if let ExprKind::Binary(op, lhs, rhs) = item.kind diff --git a/clippy_lints/src/cloned_ref_to_slice_refs.rs b/clippy_lints/src/cloned_ref_to_slice_refs.rs index 35b799aefb04..c5eabe4c2b88 100644 --- a/clippy_lints/src/cloned_ref_to_slice_refs.rs +++ b/clippy_lints/src/cloned_ref_to_slice_refs.rs @@ -44,6 +44,8 @@ "cloning a reference for slice references" } +impl_lint_pass!(ClonedRefToSliceRefs<'_> => [CLONED_REF_TO_SLICE_REFS]); + pub struct ClonedRefToSliceRefs<'a> { msrv: &'a Msrv, } @@ -53,8 +55,6 @@ pub fn new(conf: &'a Conf) -> Self { } } -impl_lint_pass!(ClonedRefToSliceRefs<'_> => [CLONED_REF_TO_SLICE_REFS]); - impl<'tcx> LateLintPass<'tcx> for ClonedRefToSliceRefs<'_> { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { if self.msrv.meets(cx, { diff --git a/clippy_lints/src/coerce_container_to_any.rs b/clippy_lints/src/coerce_container_to_any.rs index 2e3acb7748e2..1a7e20b98271 100644 --- a/clippy_lints/src/coerce_container_to_any.rs +++ b/clippy_lints/src/coerce_container_to_any.rs @@ -46,6 +46,7 @@ nursery, "coercing to `&dyn Any` when dereferencing could produce a `dyn Any` without coercion is usually not intended" } + declare_lint_pass!(CoerceContainerToAny => [COERCE_CONTAINER_TO_ANY]); impl<'tcx> LateLintPass<'tcx> for CoerceContainerToAny { diff --git a/clippy_lints/src/cognitive_complexity.rs b/clippy_lints/src/cognitive_complexity.rs index c256ad9b06a4..911ca306aca4 100644 --- a/clippy_lints/src/cognitive_complexity.rs +++ b/clippy_lints/src/cognitive_complexity.rs @@ -40,6 +40,8 @@ @eval_always = true } +impl_lint_pass!(CognitiveComplexity => [COGNITIVE_COMPLEXITY]); + pub struct CognitiveComplexity { limit: LimitStack, } @@ -52,8 +54,6 @@ pub fn new(conf: &'static Conf) -> Self { } } -impl_lint_pass!(CognitiveComplexity => [COGNITIVE_COMPLEXITY]); - impl CognitiveComplexity { fn check<'tcx>( &self, diff --git a/clippy_lints/src/collapsible_if.rs b/clippy_lints/src/collapsible_if.rs index 17e11b8b281d..a76027caebc8 100644 --- a/clippy_lints/src/collapsible_if.rs +++ b/clippy_lints/src/collapsible_if.rs @@ -12,38 +12,6 @@ use rustc_span::source_map::SourceMap; use rustc_span::{BytePos, Span, Symbol}; -declare_clippy_lint! { - /// ### What it does - /// Checks for nested `if` statements which can be collapsed - /// by `&&`-combining their conditions. - /// - /// ### Why is this bad? - /// Each `if`-statement adds one level of nesting, which - /// makes code look more complex than it really is. - /// - /// ### Example - /// ```no_run - /// # let (x, y) = (true, true); - /// if x { - /// if y { - /// // … - /// } - /// } - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let (x, y) = (true, true); - /// if x && y { - /// // … - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub COLLAPSIBLE_IF, - style, - "nested `if`s that can be collapsed (e.g., `if x { if y { ... } }`" -} - declare_clippy_lint! { /// ### What it does /// Checks for collapsible `else { if ... }` expressions @@ -80,6 +48,40 @@ "nested `else`-`if` expressions that can be collapsed (e.g., `else { if x { ... } }`)" } +declare_clippy_lint! { + /// ### What it does + /// Checks for nested `if` statements which can be collapsed + /// by `&&`-combining their conditions. + /// + /// ### Why is this bad? + /// Each `if`-statement adds one level of nesting, which + /// makes code look more complex than it really is. + /// + /// ### Example + /// ```no_run + /// # let (x, y) = (true, true); + /// if x { + /// if y { + /// // … + /// } + /// } + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let (x, y) = (true, true); + /// if x && y { + /// // … + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub COLLAPSIBLE_IF, + style, + "nested `if`s that can be collapsed (e.g., `if x { if y { ... } }`" +} + +impl_lint_pass!(CollapsibleIf => [COLLAPSIBLE_ELSE_IF, COLLAPSIBLE_IF]); + pub struct CollapsibleIf { msrv: Msrv, lint_commented_code: bool, @@ -259,8 +261,6 @@ fn check_significant_tokens_and_expect_attrs( } } -impl_lint_pass!(CollapsibleIf => [COLLAPSIBLE_IF, COLLAPSIBLE_ELSE_IF]); - impl LateLintPass<'_> for CollapsibleIf { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { if let ExprKind::If(cond, then, else_) = &expr.kind diff --git a/clippy_lints/src/collection_is_never_read.rs b/clippy_lints/src/collection_is_never_read.rs index 5bf582f4da65..ddd3e8b805d1 100644 --- a/clippy_lints/src/collection_is_never_read.rs +++ b/clippy_lints/src/collection_is_never_read.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint; -use clippy_utils::{get_enclosing_block, sym}; use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::visitors::{Visitable, for_each_expr}; +use clippy_utils::{get_enclosing_block, sym}; use core::ops::ControlFlow; use rustc_hir::{Body, ExprKind, HirId, LangItem, LetStmt, Node, PatKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -40,6 +40,7 @@ nursery, "a collection is never queried" } + declare_lint_pass!(CollectionIsNeverRead => [COLLECTION_IS_NEVER_READ]); impl<'tcx> LateLintPass<'tcx> for CollectionIsNeverRead { diff --git a/clippy_lints/src/crate_in_macro_def.rs b/clippy_lints/src/crate_in_macro_def.rs index 19f62e8bf79c..509b345048c1 100644 --- a/clippy_lints/src/crate_in_macro_def.rs +++ b/clippy_lints/src/crate_in_macro_def.rs @@ -49,6 +49,7 @@ suspicious, "using `crate` in a macro definition" } + declare_lint_pass!(CrateInMacroDef => [CRATE_IN_MACRO_DEF]); impl EarlyLintPass for CrateInMacroDef { diff --git a/clippy_lints/src/dbg_macro.rs b/clippy_lints/src/dbg_macro.rs index f47d0d4835a9..eb14ef18c03d 100644 --- a/clippy_lints/src/dbg_macro.rs +++ b/clippy_lints/src/dbg_macro.rs @@ -1,8 +1,8 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::{is_in_test, sym}; use clippy_utils::macros::{MacroCall, macro_backtrace}; use clippy_utils::source::snippet_with_applicability; +use clippy_utils::{is_in_test, sym}; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir::{Arm, Closure, ClosureKind, CoroutineKind, Expr, ExprKind, LetStmt, LocalSource, Node, Stmt, StmtKind}; @@ -33,6 +33,8 @@ "`dbg!` macro is intended as a debugging tool" } +impl_lint_pass!(DbgMacro => [DBG_MACRO]); + pub struct DbgMacro { allow_dbg_in_tests: bool, /// Tracks the `dbg!` macro callsites that are already checked. @@ -41,8 +43,6 @@ pub struct DbgMacro { prev_ctxt: SyntaxContext, } -impl_lint_pass!(DbgMacro => [DBG_MACRO]); - impl DbgMacro { pub fn new(conf: &'static Conf) -> Self { DbgMacro { diff --git a/clippy_lints/src/default.rs b/clippy_lints/src/default.rs index a48e4d2fbd57..2064d896861b 100644 --- a/clippy_lints/src/default.rs +++ b/clippy_lints/src/default.rs @@ -69,14 +69,14 @@ "binding initialized with Default should have its fields set in the initializer" } +impl_lint_pass!(Default => [DEFAULT_TRAIT_ACCESS, FIELD_REASSIGN_WITH_DEFAULT]); + #[derive(Default)] pub struct Default { // Spans linted by `field_reassign_with_default`. reassigned_linted: FxHashSet, } -impl_lint_pass!(Default => [DEFAULT_TRAIT_ACCESS, FIELD_REASSIGN_WITH_DEFAULT]); - impl<'tcx> LateLintPass<'tcx> for Default { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if !expr.span.from_expansion() diff --git a/clippy_lints/src/default_constructed_unit_structs.rs b/clippy_lints/src/default_constructed_unit_structs.rs index 641f8ae03b72..c831f96443c6 100644 --- a/clippy_lints/src/default_constructed_unit_structs.rs +++ b/clippy_lints/src/default_constructed_unit_structs.rs @@ -45,7 +45,10 @@ complexity, "unit structs can be constructed without calling `default`" } -declare_lint_pass!(DefaultConstructedUnitStructs => [DEFAULT_CONSTRUCTED_UNIT_STRUCTS]); + +declare_lint_pass!(DefaultConstructedUnitStructs => [ + DEFAULT_CONSTRUCTED_UNIT_STRUCTS, +]); fn is_alias(ty: hir::Ty<'_>) -> bool { if let hir::TyKind::Path(ref qpath) = ty.kind { diff --git a/clippy_lints/src/default_instead_of_iter_empty.rs b/clippy_lints/src/default_instead_of_iter_empty.rs index c7807f7b122a..fffdea863f51 100644 --- a/clippy_lints/src/default_instead_of_iter_empty.rs +++ b/clippy_lints/src/default_instead_of_iter_empty.rs @@ -28,6 +28,7 @@ style, "check `std::iter::Empty::default()` and replace with `std::iter::empty()`" } + declare_lint_pass!(DefaultIterEmpty => [DEFAULT_INSTEAD_OF_ITER_EMPTY]); impl<'tcx> LateLintPass<'tcx> for DefaultIterEmpty { diff --git a/clippy_lints/src/default_union_representation.rs b/clippy_lints/src/default_union_representation.rs index c1005688a921..61656d6cede5 100644 --- a/clippy_lints/src/default_union_representation.rs +++ b/clippy_lints/src/default_union_representation.rs @@ -48,6 +48,7 @@ restriction, "unions without a `#[repr(C)]` attribute" } + declare_lint_pass!(DefaultUnionRepresentation => [DEFAULT_UNION_REPRESENTATION]); impl<'tcx> LateLintPass<'tcx> for DefaultUnionRepresentation { diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 3433ab511b07..26dfa7593f22 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -3,7 +3,9 @@ use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::has_enclosing_paren; use clippy_utils::ty::{adjust_derefs_manually_drop, implements_trait, is_manually_drop, peel_and_count_ty_refs}; -use clippy_utils::{DefinedTy, ExprUseNode, expr_use_ctxt, get_parent_expr, is_block_like, is_from_proc_macro, is_lint_allowed, sym}; +use clippy_utils::{ + DefinedTy, ExprUseNode, expr_use_ctxt, get_parent_expr, is_block_like, is_from_proc_macro, is_lint_allowed, sym, +}; use rustc_ast::util::parser::ExprPrecedence; use rustc_data_structures::fx::FxIndexMap; use rustc_errors::Applicability; @@ -20,6 +22,29 @@ use rustc_span::{Span, Symbol}; use std::borrow::Cow; +declare_clippy_lint! { + /// ### What it does + /// Checks for dereferencing expressions which would be covered by auto-deref. + /// + /// ### Why is this bad? + /// This unnecessarily complicates the code. + /// + /// ### Example + /// ```no_run + /// let x = String::new(); + /// let y: &str = &*x; + /// ``` + /// Use instead: + /// ```no_run + /// let x = String::new(); + /// let y: &str = &x; + /// ``` + #[clippy::version = "1.64.0"] + pub EXPLICIT_AUTO_DEREF, + complexity, + "dereferencing when the compiler would automatically dereference" +} + declare_clippy_lint! { /// ### What it does /// Checks for explicit `deref()` or `deref_mut()` method calls. @@ -117,34 +142,11 @@ "`ref` binding to a reference" } -declare_clippy_lint! { - /// ### What it does - /// Checks for dereferencing expressions which would be covered by auto-deref. - /// - /// ### Why is this bad? - /// This unnecessarily complicates the code. - /// - /// ### Example - /// ```no_run - /// let x = String::new(); - /// let y: &str = &*x; - /// ``` - /// Use instead: - /// ```no_run - /// let x = String::new(); - /// let y: &str = &x; - /// ``` - #[clippy::version = "1.64.0"] - pub EXPLICIT_AUTO_DEREF, - complexity, - "dereferencing when the compiler would automatically dereference" -} - impl_lint_pass!(Dereferencing<'_> => [ + EXPLICIT_AUTO_DEREF, EXPLICIT_DEREF_METHODS, NEEDLESS_BORROW, REF_BINDING_TO_REFERENCE, - EXPLICIT_AUTO_DEREF, ]); #[derive(Default)] diff --git a/clippy_lints/src/derivable_impls.rs b/clippy_lints/src/derivable_impls.rs index 992ed320ce68..c04163b1c7a0 100644 --- a/clippy_lints/src/derivable_impls.rs +++ b/clippy_lints/src/derivable_impls.rs @@ -56,6 +56,8 @@ "manual implementation of the `Default` trait which is equal to a derive" } +impl_lint_pass!(DerivableImpls => [DERIVABLE_IMPLS]); + pub struct DerivableImpls { msrv: Msrv, } @@ -66,8 +68,6 @@ pub fn new(conf: &'static Conf) -> Self { } } -impl_lint_pass!(DerivableImpls => [DERIVABLE_IMPLS]); - fn is_path_self(e: &Expr<'_>) -> bool { if let ExprKind::Path(QPath::Resolved(_, p)) = e.kind { matches!(p.res, Res::SelfCtor(..) | Res::Def(DefKind::Ctor(..), _)) diff --git a/clippy_lints/src/derive/mod.rs b/clippy_lints/src/derive/mod.rs index 86614201c406..fa1a7037154e 100644 --- a/clippy_lints/src/derive/mod.rs +++ b/clippy_lints/src/derive/mod.rs @@ -10,36 +10,6 @@ mod expl_impl_clone_on_copy; mod unsafe_derive_deserialize; -declare_clippy_lint! { - /// ### What it does - /// Lints against manual `PartialEq` implementations for types with a derived `Hash` - /// implementation. - /// - /// ### Why is this bad? - /// The implementation of these traits must agree (for - /// example for use with `HashMap`) so it’s probably a bad idea to use a - /// default-generated `Hash` implementation with an explicitly defined - /// `PartialEq`. In particular, the following must hold for any type: - /// - /// ```text - /// k1 == k2 ⇒ hash(k1) == hash(k2) - /// ``` - /// - /// ### Example - /// ```ignore - /// #[derive(Hash)] - /// struct Foo; - /// - /// impl PartialEq for Foo { - /// ... - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub DERIVED_HASH_WITH_MANUAL_EQ, - correctness, - "deriving `Hash` but implementing `PartialEq` explicitly" -} - declare_clippy_lint! { /// ### What it does /// Lints against manual `PartialOrd` and `Ord` implementations for types with a derived `Ord` @@ -91,6 +61,68 @@ "deriving `Ord` but implementing `PartialOrd` explicitly" } +declare_clippy_lint! { + /// ### What it does + /// Checks for types that derive `PartialEq` and could implement `Eq`. + /// + /// ### Why is this bad? + /// If a type `T` derives `PartialEq` and all of its members implement `Eq`, + /// then `T` can always implement `Eq`. Implementing `Eq` allows `T` to be used + /// in APIs that require `Eq` types. It also allows structs containing `T` to derive + /// `Eq` themselves. + /// + /// ### Example + /// ```no_run + /// #[derive(PartialEq)] + /// struct Foo { + /// i_am_eq: i32, + /// i_am_eq_too: Vec, + /// } + /// ``` + /// Use instead: + /// ```no_run + /// #[derive(PartialEq, Eq)] + /// struct Foo { + /// i_am_eq: i32, + /// i_am_eq_too: Vec, + /// } + /// ``` + #[clippy::version = "1.63.0"] + pub DERIVE_PARTIAL_EQ_WITHOUT_EQ, + nursery, + "deriving `PartialEq` on a type that can implement `Eq`, without implementing `Eq`" +} + +declare_clippy_lint! { + /// ### What it does + /// Lints against manual `PartialEq` implementations for types with a derived `Hash` + /// implementation. + /// + /// ### Why is this bad? + /// The implementation of these traits must agree (for + /// example for use with `HashMap`) so it’s probably a bad idea to use a + /// default-generated `Hash` implementation with an explicitly defined + /// `PartialEq`. In particular, the following must hold for any type: + /// + /// ```text + /// k1 == k2 ⇒ hash(k1) == hash(k2) + /// ``` + /// + /// ### Example + /// ```ignore + /// #[derive(Hash)] + /// struct Foo; + /// + /// impl PartialEq for Foo { + /// ... + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub DERIVED_HASH_WITH_MANUAL_EQ, + correctness, + "deriving `Hash` but implementing `PartialEq` explicitly" +} + declare_clippy_lint! { /// ### What it does /// Checks for explicit `Clone` implementations for `Copy` @@ -152,44 +184,12 @@ "deriving `serde::Deserialize` on a type that has methods using `unsafe`" } -declare_clippy_lint! { - /// ### What it does - /// Checks for types that derive `PartialEq` and could implement `Eq`. - /// - /// ### Why is this bad? - /// If a type `T` derives `PartialEq` and all of its members implement `Eq`, - /// then `T` can always implement `Eq`. Implementing `Eq` allows `T` to be used - /// in APIs that require `Eq` types. It also allows structs containing `T` to derive - /// `Eq` themselves. - /// - /// ### Example - /// ```no_run - /// #[derive(PartialEq)] - /// struct Foo { - /// i_am_eq: i32, - /// i_am_eq_too: Vec, - /// } - /// ``` - /// Use instead: - /// ```no_run - /// #[derive(PartialEq, Eq)] - /// struct Foo { - /// i_am_eq: i32, - /// i_am_eq_too: Vec, - /// } - /// ``` - #[clippy::version = "1.63.0"] - pub DERIVE_PARTIAL_EQ_WITHOUT_EQ, - nursery, - "deriving `PartialEq` on a type that can implement `Eq`, without implementing `Eq`" -} - declare_lint_pass!(Derive => [ - EXPL_IMPL_CLONE_ON_COPY, DERIVED_HASH_WITH_MANUAL_EQ, DERIVE_ORD_XOR_PARTIAL_ORD, + DERIVE_PARTIAL_EQ_WITHOUT_EQ, + EXPL_IMPL_CLONE_ON_COPY, UNSAFE_DERIVE_DESERIALIZE, - DERIVE_PARTIAL_EQ_WITHOUT_EQ ]); impl<'tcx> LateLintPass<'tcx> for Derive { diff --git a/clippy_lints/src/disallowed_fields.rs b/clippy_lints/src/disallowed_fields.rs index f1136556e2ed..9873c32f427f 100644 --- a/clippy_lints/src/disallowed_fields.rs +++ b/clippy_lints/src/disallowed_fields.rs @@ -56,6 +56,8 @@ "declaration of a disallowed field use" } +impl_lint_pass!(DisallowedFields => [DISALLOWED_FIELDS]); + pub struct DisallowedFields { disallowed: DefIdMap<(&'static str, &'static DisallowedPath)>, } @@ -74,8 +76,6 @@ pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self { } } -impl_lint_pass!(DisallowedFields => [DISALLOWED_FIELDS]); - impl<'tcx> LateLintPass<'tcx> for DisallowedFields { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { let (id, span) = match &expr.kind { diff --git a/clippy_lints/src/disallowed_macros.rs b/clippy_lints/src/disallowed_macros.rs index 1c9c971730f6..7253b65e4337 100644 --- a/clippy_lints/src/disallowed_macros.rs +++ b/clippy_lints/src/disallowed_macros.rs @@ -63,6 +63,8 @@ "use of a disallowed macro" } +impl_lint_pass!(DisallowedMacros => [DISALLOWED_MACROS]); + pub struct DisallowedMacros { disallowed: DefIdMap<(&'static str, &'static DisallowedPath)>, seen: FxHashSet, @@ -125,8 +127,6 @@ fn check(&mut self, cx: &LateContext<'_>, span: Span, derive_src: Option [DISALLOWED_MACROS]); - impl LateLintPass<'_> for DisallowedMacros { fn check_crate(&mut self, cx: &LateContext<'_>) { // once we check a crate in the late pass we can emit the early pass lints diff --git a/clippy_lints/src/disallowed_methods.rs b/clippy_lints/src/disallowed_methods.rs index 58403ad19235..e2fd71b7d990 100644 --- a/clippy_lints/src/disallowed_methods.rs +++ b/clippy_lints/src/disallowed_methods.rs @@ -61,6 +61,8 @@ "use of a disallowed method call" } +impl_lint_pass!(DisallowedMethods => [DISALLOWED_METHODS]); + pub struct DisallowedMethods { disallowed: DefIdMap<(&'static str, &'static DisallowedPath)>, } @@ -84,8 +86,6 @@ pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self { } } -impl_lint_pass!(DisallowedMethods => [DISALLOWED_METHODS]); - impl<'tcx> LateLintPass<'tcx> for DisallowedMethods { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if expr.span.desugaring_kind().is_some() { diff --git a/clippy_lints/src/disallowed_names.rs b/clippy_lints/src/disallowed_names.rs index 566aa12b08f5..1f658b2aa068 100644 --- a/clippy_lints/src/disallowed_names.rs +++ b/clippy_lints/src/disallowed_names.rs @@ -26,6 +26,8 @@ "usage of a disallowed/placeholder name" } +impl_lint_pass!(DisallowedNames => [DISALLOWED_NAMES]); + pub struct DisallowedNames { disallow: FxHashSet, } @@ -38,8 +40,6 @@ pub fn new(conf: &'static Conf) -> Self { } } -impl_lint_pass!(DisallowedNames => [DISALLOWED_NAMES]); - impl<'tcx> LateLintPass<'tcx> for DisallowedNames { fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) { if let PatKind::Binding(.., ident, _) = pat.kind diff --git a/clippy_lints/src/disallowed_script_idents.rs b/clippy_lints/src/disallowed_script_idents.rs index 5b4fe2a6b803..4596d9457c0b 100644 --- a/clippy_lints/src/disallowed_script_idents.rs +++ b/clippy_lints/src/disallowed_script_idents.rs @@ -45,6 +45,8 @@ "usage of non-allowed Unicode scripts" } +impl_lint_pass!(DisallowedScriptIdents => [DISALLOWED_SCRIPT_IDENTS]); + pub struct DisallowedScriptIdents { whitelist: FxHashSet