Auto merge of #148356 - matthiaskrgr:rollup-mh4l2yi, r=matthiaskrgr

Rollup of 10 pull requests

Successful merges:

 - rust-lang/rust#135602 (Tweak output of missing lifetime on associated type)
 - rust-lang/rust#139751 (Implement pin-project in pattern matching for `&pin mut|const T`)
 - rust-lang/rust#142682 (Update bundled musl to 1.2.5)
 - rust-lang/rust#148171 (Simplify code to generate line numbers in highlight)
 - rust-lang/rust#148263 (Unpin `libc` and `rustix` in `compiler` and `rustbook`)
 - rust-lang/rust#148301 ([rustdoc search] Include extern crates when filtering on `import`)
 - rust-lang/rust#148330 (Don't require dlltool with the dummy backend on MinGW)
 - rust-lang/rust#148338 (cleanup: upstream dropped amx-transpose functionality)
 - rust-lang/rust#148340 (Clippy subtree update)
 - rust-lang/rust#148343 (`nonpoison::Condvar` should take `MutexGuard` by reference)

r? `@ghost`
`@rustbot` modify labels: rollup
This commit is contained in:
bors 2025-11-01 16:44:42 +00:00
commit fca2e941f8
356 changed files with 8005 additions and 3376 deletions

View file

@ -580,7 +580,7 @@ checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
[[package]]
name = "clippy"
version = "0.1.92"
version = "0.1.93"
dependencies = [
"anstream",
"askama",
@ -607,7 +607,7 @@ dependencies = [
[[package]]
name = "clippy_config"
version = "0.1.92"
version = "0.1.93"
dependencies = [
"clippy_utils",
"itertools",
@ -630,7 +630,7 @@ dependencies = [
[[package]]
name = "clippy_lints"
version = "0.1.92"
version = "0.1.93"
dependencies = [
"arrayvec",
"cargo_metadata 0.18.1",
@ -662,7 +662,7 @@ dependencies = [
[[package]]
name = "clippy_utils"
version = "0.1.92"
version = "0.1.93"
dependencies = [
"arrayvec",
"itertools",
@ -1066,7 +1066,7 @@ dependencies = [
[[package]]
name = "declare_clippy_lint"
version = "0.1.92"
version = "0.1.93"
[[package]]
name = "derive-where"
@ -1288,7 +1288,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
dependencies = [
"libc",
"windows-sys 0.60.2",
"windows-sys 0.52.0",
]
[[package]]
@ -2101,9 +2101,9 @@ checksum = "9fa0e2a1fcbe2f6be6c42e342259976206b383122fc152e872795338b5a3f3a7"
[[package]]
name = "libc"
version = "0.2.174"
version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[package]]
name = "libdbus-sys"
@ -2154,7 +2154,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
dependencies = [
"cfg-if",
"windows-targets 0.53.3",
"windows-targets 0.52.6",
]
[[package]]
@ -2216,9 +2216,9 @@ dependencies = [
[[package]]
name = "linux-raw-sys"
version = "0.9.4"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
[[package]]
name = "litemap"
@ -4897,15 +4897,15 @@ dependencies = [
[[package]]
name = "rustix"
version = "1.0.8"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.60.2",
"windows-sys 0.52.0",
]
[[package]]

View file

@ -789,14 +789,14 @@ pub struct PatField {
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[derive(Encodable, Decodable, HashStable_Generic, Walkable)]
pub enum ByRef {
Yes(Mutability),
Yes(Pinnedness, Mutability),
No,
}
impl ByRef {
#[must_use]
pub fn cap_ref_mutability(mut self, mutbl: Mutability) -> Self {
if let ByRef::Yes(old_mutbl) = &mut self {
if let ByRef::Yes(_, old_mutbl) = &mut self {
*old_mutbl = cmp::min(*old_mutbl, mutbl);
}
self
@ -814,20 +814,33 @@ pub struct BindingMode(pub ByRef, pub Mutability);
impl BindingMode {
pub const NONE: Self = Self(ByRef::No, Mutability::Not);
pub const REF: Self = Self(ByRef::Yes(Mutability::Not), Mutability::Not);
pub const REF: Self = Self(ByRef::Yes(Pinnedness::Not, Mutability::Not), Mutability::Not);
pub const REF_PIN: Self =
Self(ByRef::Yes(Pinnedness::Pinned, Mutability::Not), Mutability::Not);
pub const MUT: Self = Self(ByRef::No, Mutability::Mut);
pub const REF_MUT: Self = Self(ByRef::Yes(Mutability::Mut), Mutability::Not);
pub const MUT_REF: Self = Self(ByRef::Yes(Mutability::Not), Mutability::Mut);
pub const MUT_REF_MUT: Self = Self(ByRef::Yes(Mutability::Mut), Mutability::Mut);
pub const REF_MUT: Self = Self(ByRef::Yes(Pinnedness::Not, Mutability::Mut), Mutability::Not);
pub const REF_PIN_MUT: Self =
Self(ByRef::Yes(Pinnedness::Pinned, Mutability::Mut), Mutability::Not);
pub const MUT_REF: Self = Self(ByRef::Yes(Pinnedness::Not, Mutability::Not), Mutability::Mut);
pub const MUT_REF_PIN: Self =
Self(ByRef::Yes(Pinnedness::Pinned, Mutability::Not), Mutability::Mut);
pub const MUT_REF_MUT: Self =
Self(ByRef::Yes(Pinnedness::Not, Mutability::Mut), Mutability::Mut);
pub const MUT_REF_PIN_MUT: Self =
Self(ByRef::Yes(Pinnedness::Pinned, Mutability::Mut), Mutability::Mut);
pub fn prefix_str(self) -> &'static str {
match self {
Self::NONE => "",
Self::REF => "ref ",
Self::REF_PIN => "ref pin const ",
Self::MUT => "mut ",
Self::REF_MUT => "ref mut ",
Self::REF_PIN_MUT => "ref pin mut ",
Self::MUT_REF => "mut ref ",
Self::MUT_REF_PIN => "mut ref pin ",
Self::MUT_REF_MUT => "mut ref mut ",
Self::MUT_REF_PIN_MUT => "mut ref pin mut ",
}
}
}

View file

@ -368,6 +368,7 @@ macro_rules! common_visitor_and_walkers {
crate::tokenstream::TokenStream,
Movability,
Mutability,
Pinnedness,
Result<(), rustc_span::ErrorGuaranteed>,
rustc_data_structures::fx::FxHashMap<Symbol, usize>,
rustc_span::ErrorGuaranteed,

View file

@ -311,3 +311,10 @@ pub enum Pinnedness {
Not,
Pinned,
}
impl Pinnedness {
/// Return `true` if self is pinned
pub fn is_pinned(self) -> bool {
matches!(self, Self::Pinned)
}
}

View file

@ -1712,10 +1712,15 @@ impl<'a> State<'a> {
if mutbl.is_mut() {
self.word_nbsp("mut");
}
if let ByRef::Yes(rmutbl) = by_ref {
if let ByRef::Yes(pinnedness, rmutbl) = by_ref {
self.word_nbsp("ref");
if pinnedness.is_pinned() {
self.word_nbsp("pin");
}
if rmutbl.is_mut() {
self.word_nbsp("mut");
} else if pinnedness.is_pinned() {
self.word_nbsp("const");
}
}
self.print_ident(*ident);

View file

@ -48,6 +48,7 @@ pub(crate) mod must_use;
pub(crate) mod no_implicit_prelude;
pub(crate) mod non_exhaustive;
pub(crate) mod path;
pub(crate) mod pin_v2;
pub(crate) mod proc_macro_attrs;
pub(crate) mod prototype;
pub(crate) mod repr;

View file

@ -0,0 +1,21 @@
use rustc_hir::Target;
use rustc_hir::attrs::AttributeKind;
use rustc_span::{Span, Symbol, sym};
use crate::attributes::{NoArgsAttributeParser, OnDuplicate};
use crate::context::Stage;
use crate::target_checking::AllowedTargets;
use crate::target_checking::Policy::Allow;
pub(crate) struct PinV2Parser;
impl<S: Stage> NoArgsAttributeParser<S> for PinV2Parser {
const PATH: &[Symbol] = &[sym::pin_v2];
const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
Allow(Target::Enum),
Allow(Target::Struct),
Allow(Target::Union),
]);
const CREATE: fn(Span) -> AttributeKind = AttributeKind::PinV2;
}

View file

@ -47,6 +47,7 @@ use crate::attributes::must_use::MustUseParser;
use crate::attributes::no_implicit_prelude::NoImplicitPreludeParser;
use crate::attributes::non_exhaustive::NonExhaustiveParser;
use crate::attributes::path::PathParser as PathAttributeParser;
use crate::attributes::pin_v2::PinV2Parser;
use crate::attributes::proc_macro_attrs::{
ProcMacroAttributeParser, ProcMacroDeriveParser, ProcMacroParser, RustcBuiltinMacroParser,
};
@ -233,6 +234,7 @@ attribute_parsers!(
Single<WithoutArgs<NonExhaustiveParser>>,
Single<WithoutArgs<ParenSugarParser>>,
Single<WithoutArgs<PassByValueParser>>,
Single<WithoutArgs<PinV2Parser>>,
Single<WithoutArgs<PointeeParser>>,
Single<WithoutArgs<ProcMacroAttributeParser>>,
Single<WithoutArgs<ProcMacroParser>>,

View file

@ -1188,7 +1188,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
}
LocalInfo::User(mir::BindingForm::Var(mir::VarBindingForm {
binding_mode: BindingMode(ByRef::Yes(_), _),
binding_mode: BindingMode(ByRef::Yes(..), _),
..
})) => {
let pattern_span: Span = local_decl.source_info.span;

View file

@ -2582,6 +2582,16 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
_ => bug!("Deref of unexpected type: {:?}", base_ty),
}
}
// Check as the inner reference type if it is a field projection
// from the `&pin` pattern
ProjectionElem::Field(FieldIdx::ZERO, _)
if let Some(adt) =
place_base.ty(self.body(), self.infcx.tcx).ty.ty_adt_def()
&& adt.is_pin()
&& self.infcx.tcx.features().pin_ergonomics() =>
{
self.is_mutable(place_base, is_local_mutation_allowed)
}
// All other projections are owned by their base path, so mutable if
// base path is mutable
ProjectionElem::Field(..)

View file

@ -893,6 +893,15 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
EncodeCrossCrate::No, loop_match, experimental!(loop_match)
),
// The `#[pin_v2]` attribute is part of the `pin_ergonomics` experiment
// that allows structurally pinning, tracked in:
//
// - https://github.com/rust-lang/rust/issues/130494
gated!(
pin_v2, Normal, template!(Word), ErrorFollowing,
EncodeCrossCrate::Yes, pin_ergonomics, experimental!(pin_v2),
),
// ==========================================================================
// Internal attributes: Stability, deprecation, and unsafe:
// ==========================================================================

View file

@ -637,6 +637,9 @@ pub enum AttributeKind {
/// Represents `#[pattern_complexity_limit]`
PatternComplexityLimit { attr_span: Span, limit_span: Span, limit: Limit },
/// Represents `#[pin_v2]`
PinV2(Span),
/// Represents `#[pointee]`
Pointee(Span),

View file

@ -77,6 +77,7 @@ impl AttributeKind {
PassByValue(..) => Yes,
Path(..) => No,
PatternComplexityLimit { .. } => No,
PinV2(..) => Yes,
Pointee(..) => No,
ProcMacro(..) => No,
ProcMacroAttribute(..) => No,

View file

@ -13,7 +13,7 @@ use rustc_ast::{
pub use rustc_ast::{
AssignOp, AssignOpKind, AttrId, AttrStyle, BinOp, BinOpKind, BindingMode, BorrowKind,
BoundConstness, BoundPolarity, ByRef, CaptureBy, DelimArgs, ImplPolarity, IsAuto,
MetaItemInner, MetaItemLit, Movability, Mutability, UnOp,
MetaItemInner, MetaItemLit, Movability, Mutability, Pinnedness, UnOp,
};
use rustc_data_structures::fingerprint::Fingerprint;
use rustc_data_structures::sorted_map::SortedMap;

View file

@ -575,7 +575,7 @@ fn resolve_local<'tcx>(
// & expression, and its lifetime would be extended to the end of the block (due
// to a different rule, not the below code).
match pat.kind {
PatKind::Binding(hir::BindingMode(hir::ByRef::Yes(_), _), ..) => true,
PatKind::Binding(hir::BindingMode(hir::ByRef::Yes(..), _), ..) => true,
PatKind::Struct(_, field_pats, _) => field_pats.iter().any(|fp| is_binding_pat(fp.pat)),

View file

@ -12,11 +12,12 @@ use std::assert_matches::debug_assert_matches;
use min_specialization::check_min_specialization;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_errors::codes::*;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::LocalDefId;
use rustc_middle::ty::{self, TyCtxt, TypeVisitableExt};
use rustc_span::ErrorGuaranteed;
use rustc_span::{ErrorGuaranteed, kw};
use crate::constrained_generic_params as cgp;
use crate::errors::UnconstrainedGenericParameter;
@ -150,6 +151,27 @@ pub(crate) fn enforce_impl_lifetime_params_are_constrained(
const_param_note2: false,
});
diag.code(E0207);
for p in &impl_generics.own_params {
if p.name == kw::UnderscoreLifetime {
let span = tcx.def_span(p.def_id);
let Ok(snippet) = tcx.sess.source_map().span_to_snippet(span) else {
continue;
};
let (span, sugg) = if &snippet == "'_" {
(span, param.name.to_string())
} else {
(span.shrink_to_hi(), format!("{} ", param.name))
};
diag.span_suggestion_verbose(
span,
"consider using the named lifetime here instead of an implicit \
lifetime",
sugg,
Applicability::MaybeIncorrect,
);
}
}
res = Err(diag.emit());
}
}

View file

@ -1925,10 +1925,15 @@ impl<'a> State<'a> {
if mutbl.is_mut() {
self.word_nbsp("mut");
}
if let ByRef::Yes(rmutbl) = by_ref {
if let ByRef::Yes(pinnedness, rmutbl) = by_ref {
self.word_nbsp("ref");
if pinnedness.is_pinned() {
self.word_nbsp("pin");
}
if rmutbl.is_mut() {
self.word_nbsp("mut");
} else if pinnedness.is_pinned() {
self.word_nbsp("const");
}
}
self.print_ident(ident);

View file

@ -227,6 +227,10 @@ hir_typeck_pass_to_variadic_function = can't pass `{$ty}` to variadic function
.suggestion = cast the value to `{$cast_ty}`
.teach_help = certain types, like `{$ty}`, must be cast before passing them to a variadic function to match the implicit cast that a C compiler would perform as part of C's numeric promotion rules
hir_typeck_project_on_non_pin_project_type = cannot project on type that is not `#[pin_v2]`
.note = type defined here
.suggestion = add `#[pin_v2]` here
hir_typeck_ptr_cast_add_auto_to_object = cannot add {$traits_len ->
[1] auto trait {$traits}
*[other] auto traits {$traits}

View file

@ -1156,3 +1156,14 @@ pub(crate) struct ConstContinueBadLabel {
#[primary_span]
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(hir_typeck_project_on_non_pin_project_type)]
pub(crate) struct ProjectOnNonPinProjectType {
#[primary_span]
pub span: Span,
#[note]
pub def_span: Option<Span>,
#[suggestion(code = "#[pin_v2]\n", applicability = "machine-applicable")]
pub sugg_span: Option<Span>,
}

View file

@ -986,7 +986,7 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
// of the pattern, as this just looks confusing, instead use the span
// of the discriminant.
match bm.0 {
hir::ByRef::Yes(m) => {
hir::ByRef::Yes(_, m) => {
let bk = ty::BorrowKind::from_mutbl(m);
self.delegate.borrow_mut().borrow(place, discr_place.hir_id, bk);
}
@ -1004,7 +1004,7 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
// Deref patterns on boxes don't borrow, so we ignore them here.
// HACK: this could be a fake pattern corresponding to a deref inserted by match
// ergonomics, in which case `pat.hir_id` will be the id of the subpattern.
if let hir::ByRef::Yes(mutability) =
if let hir::ByRef::Yes(_, mutability) =
self.cx.typeck_results().deref_pat_borrow_mode(place.place.ty(), subpattern)
{
let bk = ty::BorrowKind::from_mutbl(mutability);
@ -1256,7 +1256,15 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
.get(pat.hir_id)
.expect("missing binding mode");
if matches!(bm.0, hir::ByRef::Yes(_)) {
if let hir::ByRef::Yes(pinnedness, _) = bm.0 {
let base_ty = if pinnedness.is_pinned() {
base_ty.pinned_ty().ok_or_else(|| {
debug!("By-pin-ref binding of non-`Pin` type: {base_ty:?}");
self.cx.report_bug(pat.span, "by-pin-ref binding of non-`Pin` type")
})?
} else {
base_ty
};
// a bind-by-ref means that the base_ty will be the type of the ident itself,
// but what we want here is the type of the underlying value being borrowed.
// So peel off one-level, turning the &T into T.
@ -1264,7 +1272,7 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
{
Some(ty) => Ok(ty),
None => {
debug!("By-ref binding of non-derefable type");
debug!("By-ref binding of non-derefable type: {base_ty:?}");
Err(self
.cx
.report_bug(pat.span, "by-ref binding of non-derefable type"))
@ -1706,6 +1714,18 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
};
self.pat_deref_place(pat.hir_id, place_with_id, pat, target_ty)?
}
adjustment::PatAdjust::PinDeref => {
debug!("`PinDeref` of non-pinned-reference type: {:?}", adjust.source);
let target_ty = adjust.source.pinned_ty().ok_or_else(|| {
self.cx.report_bug(
self.cx.tcx().hir_span(pat.hir_id),
"`PinDeref` of non-pinned-reference type",
)
})?;
let kind = ProjectionKind::Field(FieldIdx::ZERO, FIRST_VARIANT);
place_with_id = self.cat_projection(pat.hir_id, place_with_id, target_ty, kind);
self.cat_deref(pat.hir_id, place_with_id)?
}
};
}
drop(typeck_results); // explicitly release borrow of typeck results, just in case.
@ -1877,7 +1897,7 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
// Deref patterns on boxes are lowered using a built-in deref.
hir::ByRef::No => self.cat_deref(hir_id, base_place),
// For other types, we create a temporary to match on.
hir::ByRef::Yes(mutability) => {
hir::ByRef::Yes(_, mutability) => {
let re_erased = self.cx.tcx().lifetimes.re_erased;
let ty = Ty::new_ref(self.cx.tcx(), re_erased, target_ty, mutability);
// A deref pattern stores the result of `Deref::deref` or `DerefMut::deref_mut` ...

View file

@ -18,7 +18,7 @@ use rustc_hir::{
use rustc_hir_analysis::autoderef::report_autoderef_recursion_limit_error;
use rustc_infer::infer::RegionVariableOrigin;
use rustc_middle::traits::PatternOriginExpr;
use rustc_middle::ty::{self, Ty, TypeVisitableExt};
use rustc_middle::ty::{self, Pinnedness, Ty, TypeVisitableExt};
use rustc_middle::{bug, span_bug};
use rustc_session::lint::builtin::NON_EXHAUSTIVE_OMITTED_PATTERNS;
use rustc_session::parse::feature_err;
@ -403,7 +403,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let ty = self.check_pat_inner(pat, opt_path_res, adjust_mode, expected, pat_info);
self.write_ty(pat.hir_id, ty);
// If we implicitly inserted overloaded dereferences before matching, check the pattern to
// If we implicitly inserted overloaded dereferences before matching check the pattern to
// see if the dereferenced types need `DerefMut` bounds.
if let Some(derefed_tys) = self.typeck_results.borrow().pat_adjustments().get(pat.hir_id)
&& derefed_tys.iter().any(|adjust| adjust.kind == PatAdjust::OverloadedDeref)
@ -413,7 +413,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
pat,
derefed_tys.iter().filter_map(|adjust| match adjust.kind {
PatAdjust::OverloadedDeref => Some(adjust.source),
PatAdjust::BuiltinDeref => None,
PatAdjust::BuiltinDeref | PatAdjust::PinDeref => None,
}),
);
}
@ -471,7 +471,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
pat_info: PatInfo<'tcx>,
) -> Ty<'tcx> {
#[cfg(debug_assertions)]
if pat_info.binding_mode == ByRef::Yes(Mutability::Mut)
if matches!(pat_info.binding_mode, ByRef::Yes(_, Mutability::Mut))
&& pat_info.max_ref_mutbl != MutblCap::Mut
&& self.downgrade_mut_inside_shared()
{
@ -489,12 +489,28 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let old_pat_info = pat_info;
let pat_info = PatInfo { current_depth: old_pat_info.current_depth + 1, ..old_pat_info };
let adjust_binding_mode = |inner_pinnedness, inner_mutability| {
match pat_info.binding_mode {
// If default binding mode is by value, make it `ref`, `ref mut`, `ref pin const`
// or `ref pin mut` (depending on whether we observe `&`, `&mut`, `&pin const` or
// `&pin mut`).
ByRef::No => ByRef::Yes(inner_pinnedness, inner_mutability),
// When `ref mut`, stay a `ref mut` (on `&mut`) or downgrade to `ref` (on `&`).
// Pinnedness is preserved.
ByRef::Yes(pinnedness, Mutability::Mut) => ByRef::Yes(pinnedness, inner_mutability),
// Once a `ref`, always a `ref`.
// This is because a `& &mut` cannot mutate the underlying value.
// Pinnedness is preserved.
ByRef::Yes(pinnedness, Mutability::Not) => ByRef::Yes(pinnedness, Mutability::Not),
}
};
match pat.kind {
// Peel off a `&` or `&mut` from the scrutinee type. See the examples in
// Peel off a `&` or `&mut`from the scrutinee type. See the examples in
// `tests/ui/rfcs/rfc-2005-default-binding-mode`.
_ if let AdjustMode::Peel { kind: peel_kind } = adjust_mode
&& pat.default_binding_modes
&& let ty::Ref(_, inner_ty, inner_mutability) = *expected.kind()
&& let &ty::Ref(_, inner_ty, inner_mutability) = expected.kind()
&& self.should_peel_ref(peel_kind, expected) =>
{
debug!("inspecting {:?}", expected);
@ -508,22 +524,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
.or_default()
.push(PatAdjustment { kind: PatAdjust::BuiltinDeref, source: expected });
let mut binding_mode = ByRef::Yes(match pat_info.binding_mode {
// If default binding mode is by value, make it `ref` or `ref mut`
// (depending on whether we observe `&` or `&mut`).
ByRef::No |
// When `ref mut`, stay a `ref mut` (on `&mut`) or downgrade to `ref` (on `&`).
ByRef::Yes(Mutability::Mut) => inner_mutability,
// Once a `ref`, always a `ref`.
// This is because a `& &mut` cannot mutate the underlying value.
ByRef::Yes(Mutability::Not) => Mutability::Not,
});
let mut binding_mode = adjust_binding_mode(Pinnedness::Not, inner_mutability);
let mut max_ref_mutbl = pat_info.max_ref_mutbl;
if self.downgrade_mut_inside_shared() {
binding_mode = binding_mode.cap_ref_mutability(max_ref_mutbl.as_mutbl());
}
if binding_mode == ByRef::Yes(Mutability::Not) {
if matches!(binding_mode, ByRef::Yes(_, Mutability::Not)) {
max_ref_mutbl = MutblCap::Not;
}
debug!("default binding mode is now {:?}", binding_mode);
@ -533,6 +540,60 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// Recurse with the new expected type.
self.check_pat_inner(pat, opt_path_res, adjust_mode, inner_ty, new_pat_info)
}
// If `pin_ergonomics` is enabled, peel the `&pin` from the pinned reference type. See the
// examples in `tests/ui/async-await/pin-ergonomics/`.
_ if self.tcx.features().pin_ergonomics()
&& let AdjustMode::Peel { kind: peel_kind } = adjust_mode
&& pat.default_binding_modes
&& self.should_peel_smart_pointer(peel_kind, expected)
&& let Some(pinned_ty) = expected.pinned_ty()
// Currently, only pinned reference is specially handled, leaving other
// pinned types (e.g. `Pin<Box<T>>` to deref patterns) handled as a
// deref pattern.
&& let &ty::Ref(_, inner_ty, inner_mutability) = pinned_ty.kind() =>
{
debug!("scrutinee ty {expected:?} is a pinned reference, inserting pin deref");
// if the inner_ty is an ADT, make sure that it can be structurally pinned
// (i.e., it is `#[pin_v2]`).
if let Some(adt) = inner_ty.ty_adt_def()
&& !adt.is_pin_project()
&& !adt.is_pin()
{
let def_span: Option<Span> = self.tcx.hir_span_if_local(adt.did());
let sugg_span = def_span.map(|span| span.shrink_to_lo());
self.dcx().emit_err(crate::errors::ProjectOnNonPinProjectType {
span: pat.span,
def_span,
sugg_span,
});
}
let binding_mode = adjust_binding_mode(Pinnedness::Pinned, inner_mutability);
// If the pinnedness is `Not`, it means the pattern is unpinned
// and thus requires an `Unpin` bound.
if matches!(binding_mode, ByRef::Yes(Pinnedness::Not, _)) {
self.register_bound(
inner_ty,
self.tcx.require_lang_item(hir::LangItem::Unpin, pat.span),
self.misc(pat.span),
)
}
debug!("default binding mode is now {:?}", binding_mode);
// Use the old pat info to keep `current_depth` to its old value.
let new_pat_info = PatInfo { binding_mode, ..old_pat_info };
self.check_deref_pattern(
pat,
opt_path_res,
adjust_mode,
expected,
inner_ty,
PatAdjust::PinDeref,
new_pat_info,
)
}
// If `deref_patterns` is enabled, peel a smart pointer from the scrutinee type. See the
// examples in `tests/ui/pattern/deref_patterns/`.
_ if self.tcx.features().deref_patterns()
@ -540,35 +601,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
&& pat.default_binding_modes
&& self.should_peel_smart_pointer(peel_kind, expected) =>
{
debug!("scrutinee ty {expected:?} is a smart pointer, inserting overloaded deref");
debug!("scrutinee ty {expected:?} is a smart pointer, inserting pin deref");
// The scrutinee is a smart pointer; implicitly dereference it. This adds a
// requirement that `expected: DerefPure`.
let mut inner_ty = self.deref_pat_target(pat.span, expected);
let inner_ty = self.deref_pat_target(pat.span, expected);
// Once we've checked `pat`, we'll add a `DerefMut` bound if it contains any
// `ref mut` bindings. See `Self::register_deref_mut_bounds_if_needed`.
let mut typeck_results = self.typeck_results.borrow_mut();
let mut pat_adjustments_table = typeck_results.pat_adjustments_mut();
let pat_adjustments = pat_adjustments_table.entry(pat.hir_id).or_default();
// We may reach the recursion limit if a user matches on a type `T` satisfying
// `T: Deref<Target = T>`; error gracefully in this case.
// FIXME(deref_patterns): If `deref_patterns` stabilizes, it may make sense to move
// this check out of this branch. Alternatively, this loop could be implemented with
// autoderef and this check removed. For now though, don't break code compiling on
// stable with lots of `&`s and a low recursion limit, if anyone's done that.
if self.tcx.recursion_limit().value_within_limit(pat_adjustments.len()) {
// Preserve the smart pointer type for THIR lowering and closure upvar analysis.
pat_adjustments
.push(PatAdjustment { kind: PatAdjust::OverloadedDeref, source: expected });
} else {
let guar = report_autoderef_recursion_limit_error(self.tcx, pat.span, expected);
inner_ty = Ty::new_error(self.tcx, guar);
}
drop(typeck_results);
// Recurse, using the old pat info to keep `current_depth` to its old value.
// Peeling smart pointers does not update the default binding mode.
self.check_pat_inner(pat, opt_path_res, adjust_mode, inner_ty, old_pat_info)
self.check_deref_pattern(
pat,
opt_path_res,
adjust_mode,
expected,
inner_ty,
PatAdjust::OverloadedDeref,
old_pat_info,
)
}
PatKind::Missing | PatKind::Wild | PatKind::Err(_) => expected,
// We allow any type here; we ensure that the type is uninhabited during match checking.
@ -647,6 +696,44 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}
fn check_deref_pattern(
&self,
pat: &'tcx Pat<'tcx>,
opt_path_res: Option<Result<ResolvedPat<'tcx>, ErrorGuaranteed>>,
adjust_mode: AdjustMode,
expected: Ty<'tcx>,
mut inner_ty: Ty<'tcx>,
pat_adjust_kind: PatAdjust,
pat_info: PatInfo<'tcx>,
) -> Ty<'tcx> {
debug_assert!(
!matches!(pat_adjust_kind, PatAdjust::BuiltinDeref),
"unexpected deref pattern for builtin reference type {expected:?}",
);
let mut typeck_results = self.typeck_results.borrow_mut();
let mut pat_adjustments_table = typeck_results.pat_adjustments_mut();
let pat_adjustments = pat_adjustments_table.entry(pat.hir_id).or_default();
// We may reach the recursion limit if a user matches on a type `T` satisfying
// `T: Deref<Target = T>`; error gracefully in this case.
// FIXME(deref_patterns): If `deref_patterns` stabilizes, it may make sense to move
// this check out of this branch. Alternatively, this loop could be implemented with
// autoderef and this check removed. For now though, don't break code compiling on
// stable with lots of `&`s and a low recursion limit, if anyone's done that.
if self.tcx.recursion_limit().value_within_limit(pat_adjustments.len()) {
// Preserve the smart pointer type for THIR lowering and closure upvar analysis.
pat_adjustments.push(PatAdjustment { kind: pat_adjust_kind, source: expected });
} else {
let guar = report_autoderef_recursion_limit_error(self.tcx, pat.span, expected);
inner_ty = Ty::new_error(self.tcx, guar);
}
drop(typeck_results);
// Recurse, using the old pat info to keep `current_depth` to its old value.
// Peeling smart pointers does not update the default binding mode.
self.check_pat_inner(pat, opt_path_res, adjust_mode, inner_ty, pat_info)
}
/// How should the binding mode and expected type be adjusted?
///
/// When the pattern contains a path, `opt_path_res` must be `Some(path_res)`.
@ -1061,7 +1148,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// Determine the binding mode...
let bm = match user_bind_annot {
BindingMode(ByRef::No, Mutability::Mut) if let ByRef::Yes(def_br_mutbl) = def_br => {
BindingMode(ByRef::No, Mutability::Mut) if let ByRef::Yes(_, def_br_mutbl) = def_br => {
// Only mention the experimental `mut_ref` feature if if we're in edition 2024 and
// using other experimental matching features compatible with it.
if pat.span.at_least_rust_2024()
@ -1091,8 +1178,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}
BindingMode(ByRef::No, mutbl) => BindingMode(def_br, mutbl),
BindingMode(ByRef::Yes(user_br_mutbl), _) => {
if let ByRef::Yes(def_br_mutbl) = def_br {
BindingMode(ByRef::Yes(_, user_br_mutbl), _) => {
if let ByRef::Yes(_, def_br_mutbl) = def_br {
// `ref`/`ref mut` overrides the binding mode on edition <= 2021
self.add_rust_2024_migration_desugared_pat(
pat_info.top_info.hir_id,
@ -1108,7 +1195,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
};
if bm.0 == ByRef::Yes(Mutability::Mut)
if matches!(bm.0, ByRef::Yes(_, Mutability::Mut))
&& let MutblCap::WeaklyNot(and_pat_span) = pat_info.max_ref_mutbl
{
let mut err = struct_span_code_err!(
@ -1136,7 +1223,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let local_ty = self.local_ty(pat.span, pat.hir_id);
let eq_ty = match bm.0 {
ByRef::Yes(mutbl) => {
ByRef::Yes(Pinnedness::Not, mutbl) => {
// If the binding is like `ref x | ref mut x`,
// then `x` is assigned a value of type `&M T` where M is the
// mutability and T is the expected type.
@ -1146,6 +1233,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// See (note_1) for an explanation.
self.new_ref_ty(pat.span, mutbl, expected)
}
// Wrapping the type into `Pin` if the binding is like `ref pin const|mut x`
ByRef::Yes(Pinnedness::Pinned, mutbl) => Ty::new_adt(
self.tcx,
self.tcx.adt_def(self.tcx.require_lang_item(hir::LangItem::Pin, pat.span)),
self.tcx.mk_args(&[self.new_ref_ty(pat.span, mutbl, expected).into()]),
),
// Otherwise, the type of x is the expected type `T`.
ByRef::No => expected, // As above, `T <: typeof(x)` is required, but we use equality, see (note_1).
};
@ -2605,7 +2698,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
expected = self.try_structurally_resolve_type(pat.span, expected);
// Determine whether we're consuming an inherited reference and resetting the default
// binding mode, based on edition and enabled experimental features.
if let ByRef::Yes(inh_mut) = pat_info.binding_mode {
if let ByRef::Yes(inh_pin, inh_mut) = pat_info.binding_mode
// FIXME(pin_ergonomics): since `&pin` pattern is supported, the condition here
// should be adjusted to `pat_pin == inh_pin`
&& (!self.tcx.features().pin_ergonomics() || inh_pin == Pinnedness::Not)
{
match self.ref_pat_matches_inherited_ref(pat.span.edition()) {
InheritedRefMatchRule::EatOuter => {
// ref pattern attempts to consume inherited reference
@ -3126,8 +3223,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// If the user-provided binding modifier doesn't match the default binding mode, we'll
// need to suggest reference patterns, which can affect other bindings.
// For simplicity, we opt to suggest making the pattern fully explicit.
info.suggest_eliding_modes &=
user_bind_annot == BindingMode(ByRef::Yes(def_br_mutbl), Mutability::Not);
info.suggest_eliding_modes &= matches!(
user_bind_annot,
BindingMode(ByRef::Yes(_, mutbl), Mutability::Not) if mutbl == def_br_mutbl
);
if user_bind_annot == BindingMode(ByRef::No, Mutability::Mut) {
info.bad_mut_modifiers = true;
"`mut` binding modifier"

View file

@ -7,7 +7,7 @@ use std::{env, thread};
use rustc_ast as ast;
use rustc_attr_parsing::{ShouldEmit, validate_attr};
use rustc_codegen_ssa::back::archive::ArArchiveBuilderBuilder;
use rustc_codegen_ssa::back::archive::{ArArchiveBuilderBuilder, ArchiveBuilderBuilder};
use rustc_codegen_ssa::back::link::link_binary;
use rustc_codegen_ssa::target_features::{self, cfg_target_feature};
use rustc_codegen_ssa::traits::CodegenBackend;
@ -440,7 +440,7 @@ impl CodegenBackend for DummyCodegenBackend {
link_binary(
sess,
&ArArchiveBuilderBuilder,
&DummyArchiveBuilderBuilder,
codegen_results,
metadata,
outputs,
@ -449,6 +449,28 @@ impl CodegenBackend for DummyCodegenBackend {
}
}
struct DummyArchiveBuilderBuilder;
impl ArchiveBuilderBuilder for DummyArchiveBuilderBuilder {
fn new_archive_builder<'a>(
&self,
sess: &'a Session,
) -> Box<dyn rustc_codegen_ssa::back::archive::ArchiveBuilder + 'a> {
ArArchiveBuilderBuilder.new_archive_builder(sess)
}
fn create_dll_import_lib(
&self,
sess: &Session,
_lib_name: &str,
_items: Vec<rustc_codegen_ssa::back::archive::ImportLibraryItem>,
output_path: &Path,
) {
// Build an empty static library to avoid calling an external dlltool on mingw
ArArchiveBuilderBuilder.new_archive_builder(sess).build(output_path);
}
}
// This is used for rustdoc, but it uses similar machinery to codegen backend
// loading, so we leave the code here. It is potentially useful for other tools
// that want to invoke the rustc binary while linking to rustc as well.

View file

@ -108,7 +108,7 @@ impl<'tcx> LateLintPass<'tcx> for StaticMutRefs {
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &Stmt<'_>) {
if let hir::StmtKind::Let(loc) = stmt.kind
&& let hir::PatKind::Binding(ba, _, _, _) = loc.pat.kind
&& let hir::ByRef::Yes(m) = ba.0
&& let hir::ByRef::Yes(_, m) = ba.0
&& let Some(init) = loc.init
&& let Some(err_span) = path_is_static_mut(init, init.span)
{

View file

@ -232,4 +232,7 @@ pub enum PatAdjust {
/// An implicit call to `Deref(Mut)::deref(_mut)` before matching, such as when matching the
/// pattern `[..]` against a scrutinee of type `Vec<T>`.
OverloadedDeref,
/// An implicit dereference before matching a `&pin` reference (under feature `pin_ergonomics`),
/// which will be lowered as a builtin deref of the private field `__pointer` in `Pin`
PinDeref,
}

View file

@ -55,6 +55,10 @@ bitflags::bitflags! {
const IS_UNSAFE_CELL = 1 << 9;
/// Indicates whether the type is `UnsafePinned`.
const IS_UNSAFE_PINNED = 1 << 10;
/// Indicates whether the type is `Pin`.
const IS_PIN = 1 << 11;
/// Indicates whether the type is `#[pin_project]`.
const IS_PIN_PROJECT = 1 << 12;
}
}
rustc_data_structures::external_bitflags_debug! { AdtFlags }
@ -284,6 +288,10 @@ impl AdtDefData {
debug!("found non-exhaustive variant list for {:?}", did);
flags = flags | AdtFlags::IS_VARIANT_LIST_NON_EXHAUSTIVE;
}
if find_attr!(tcx.get_all_attrs(did), AttributeKind::PinV2(..)) {
debug!("found pin-project type {:?}", did);
flags |= AdtFlags::IS_PIN_PROJECT;
}
flags |= match kind {
AdtKind::Enum => AdtFlags::IS_ENUM,
@ -313,6 +321,9 @@ impl AdtDefData {
if tcx.is_lang_item(did, LangItem::UnsafePinned) {
flags |= AdtFlags::IS_UNSAFE_PINNED;
}
if tcx.is_lang_item(did, LangItem::Pin) {
flags |= AdtFlags::IS_PIN;
}
AdtDefData { did, variants, flags, repr }
}
@ -428,6 +439,19 @@ impl<'tcx> AdtDef<'tcx> {
self.flags().contains(AdtFlags::IS_MANUALLY_DROP)
}
/// Returns `true` if this is `Pin<T>`.
#[inline]
pub fn is_pin(self) -> bool {
self.flags().contains(AdtFlags::IS_PIN)
}
/// Returns `true` is this is `#[pin_v2]` for the purposes
/// of structural pinning.
#[inline]
pub fn is_pin_project(self) -> bool {
self.flags().contains(AdtFlags::IS_PIN_PROJECT)
}
/// Returns `true` if this type has a destructor.
pub fn has_dtor(self, tcx: TyCtxt<'tcx>) -> bool {
self.destructor(tcx).is_some()

View file

@ -1349,6 +1349,23 @@ impl<'tcx> Ty<'tcx> {
}
}
pub fn pinned_ty(self) -> Option<Ty<'tcx>> {
match self.kind() {
Adt(def, args) if def.is_pin() => Some(args.type_at(0)),
_ => None,
}
}
pub fn pinned_ref(self) -> Option<(Ty<'tcx>, ty::Mutability)> {
if let Adt(def, args) = self.kind()
&& def.is_pin()
&& let &ty::Ref(_, ty, mutbl) = args.type_at(0).kind()
{
return Some((ty, mutbl));
}
None
}
/// Panics if called on any type other than `Box<T>`.
pub fn expect_boxed_ty(self) -> Ty<'tcx> {
self.boxed_ty()

View file

@ -11,6 +11,7 @@ use rustc_hir::def_id::{DefId, LocalDefId, LocalDefIdMap};
use rustc_hir::hir_id::OwnerId;
use rustc_hir::{
self as hir, BindingMode, ByRef, HirId, ItemLocalId, ItemLocalMap, ItemLocalSet, Mutability,
Pinnedness,
};
use rustc_index::IndexVec;
use rustc_macros::{HashStable, TyDecodable, TyEncodable, TypeFoldable, TypeVisitable};
@ -479,7 +480,7 @@ impl<'tcx> TypeckResults<'tcx> {
let mut has_ref_mut = false;
pat.walk(|pat| {
if let hir::PatKind::Binding(_, id, _, _) = pat.kind
&& let Some(BindingMode(ByRef::Yes(Mutability::Mut), _)) =
&& let Some(BindingMode(ByRef::Yes(_, Mutability::Mut), _)) =
self.pat_binding_modes().get(id)
{
has_ref_mut = true;
@ -503,7 +504,7 @@ impl<'tcx> TypeckResults<'tcx> {
ByRef::No
} else {
let mutable = self.pat_has_ref_mut_binding(inner);
ByRef::Yes(if mutable { Mutability::Mut } else { Mutability::Not })
ByRef::Yes(Pinnedness::Not, if mutable { Mutability::Mut } else { Mutability::Not })
}
}

View file

@ -1,9 +1,10 @@
use std::sync::Arc;
use rustc_abi::FieldIdx;
use rustc_hir::ByRef;
use rustc_middle::mir::*;
use rustc_middle::thir::*;
use rustc_middle::ty::{self, Ty, TypeVisitableExt};
use rustc_middle::ty::{self, Pinnedness, Ty, TypeVisitableExt};
use crate::builder::Builder;
use crate::builder::expr::as_place::{PlaceBase, PlaceBuilder};
@ -299,7 +300,24 @@ impl<'tcx> MatchPairTree<'tcx> {
None
}
PatKind::DerefPattern { ref subpattern, borrow: ByRef::Yes(mutability) } => {
PatKind::DerefPattern { ref subpattern, borrow: ByRef::Yes(Pinnedness::Pinned, _) } => {
let Some(ref_ty) = pattern.ty.pinned_ty() else {
rustc_middle::bug!("RefPin pattern on non-`Pin` type {:?}", pattern.ty);
};
MatchPairTree::for_pattern(
place_builder.field(FieldIdx::ZERO, ref_ty).deref(),
subpattern,
cx,
&mut subpairs,
extra_data,
);
None
}
PatKind::DerefPattern {
ref subpattern,
borrow: ByRef::Yes(Pinnedness::Not, mutability),
} => {
// Create a new temporary for each deref pattern.
// FIXME(deref_patterns): dedup temporaries to avoid multiple `deref()` calls?
let temp = cx.temp(

View file

@ -5,20 +5,21 @@
//! This also includes code for pattern bindings in `let` statements and
//! function parameters.
use std::assert_matches::debug_assert_matches;
use std::borrow::Borrow;
use std::mem;
use std::sync::Arc;
use itertools::{Itertools, Position};
use rustc_abi::VariantIdx;
use rustc_abi::{FIRST_VARIANT, FieldIdx, VariantIdx};
use rustc_data_structures::fx::FxIndexMap;
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_hir::{BindingMode, ByRef, LetStmt, LocalSource, Node};
use rustc_middle::bug;
use rustc_hir::{BindingMode, ByRef, LangItem, LetStmt, LocalSource, Node, Pinnedness};
use rustc_middle::middle::region;
use rustc_middle::mir::*;
use rustc_middle::thir::{self, *};
use rustc_middle::ty::{self, CanonicalUserTypeAnnotation, Ty, ValTree, ValTreeKind};
use rustc_middle::{bug, span_bug};
use rustc_pattern_analysis::constructor::RangeEnd;
use rustc_pattern_analysis::rustc::{DeconstructedPat, RustcPatCtxt};
use rustc_span::{BytePos, Pos, Span, Symbol, sym};
@ -917,6 +918,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
visit_subpat(self, subpattern, &user_tys.deref(), f);
}
PatKind::DerefPattern { ref subpattern, borrow: ByRef::Yes(Pinnedness::Pinned, _) } => {
visit_subpat(self, subpattern, &user_tys.leaf(FieldIdx::ZERO).deref(), f);
}
PatKind::DerefPattern { ref subpattern, .. } => {
visit_subpat(self, subpattern, &ProjectedUserTypesNode::None, f);
}
@ -2747,9 +2752,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
let rvalue = Rvalue::Ref(re_erased, BorrowKind::Shared, binding.source);
self.cfg.push_assign(block, source_info, ref_for_guard, rvalue);
}
ByRef::Yes(mutbl) => {
// The arm binding will be by reference, so eagerly create it now. Drops must
// be scheduled to emit `StorageDead` on the guard's failure/break branches.
ByRef::Yes(pinnedness, mutbl) => {
// The arm binding will be by reference, so eagerly create it now // be scheduled to emit `StorageDead` on the guard's failure/break branches.
let value_for_arm = self.storage_live_binding(
block,
binding.var_id,
@ -2761,6 +2765,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
let rvalue =
Rvalue::Ref(re_erased, util::ref_pat_borrow_kind(mutbl), binding.source);
let rvalue = match pinnedness {
ty::Pinnedness::Not => rvalue,
ty::Pinnedness::Pinned => {
self.pin_borrowed_local(block, value_for_arm.local, rvalue, source_info)
}
};
self.cfg.push_assign(block, source_info, value_for_arm, rvalue);
// For the guard binding, take a shared reference to that reference.
let rvalue = Rvalue::Ref(re_erased, BorrowKind::Shared, value_for_arm);
@ -2797,14 +2807,59 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
}
let rvalue = match binding.binding_mode.0 {
ByRef::No => Rvalue::Use(self.consume_by_copy_or_move(binding.source)),
ByRef::Yes(mutbl) => {
Rvalue::Ref(re_erased, util::ref_pat_borrow_kind(mutbl), binding.source)
ByRef::Yes(pinnedness, mutbl) => {
let rvalue =
Rvalue::Ref(re_erased, util::ref_pat_borrow_kind(mutbl), binding.source);
match pinnedness {
ty::Pinnedness::Not => rvalue,
ty::Pinnedness::Pinned => {
self.pin_borrowed_local(block, local.local, rvalue, source_info)
}
}
}
};
self.cfg.push_assign(block, source_info, local, rvalue);
}
}
/// Given an rvalue `&[mut]borrow` and a local `local`, generate the pinned borrow for it:
/// ```ignore (illustrative)
/// pinned_temp = &borrow;
/// local = Pin { __pointer: move pinned_temp };
/// ```
fn pin_borrowed_local(
&mut self,
block: BasicBlock,
local: Local,
borrow: Rvalue<'tcx>,
source_info: SourceInfo,
) -> Rvalue<'tcx> {
debug_assert_matches!(borrow, Rvalue::Ref(..));
let local_ty = self.local_decls[local].ty;
let pinned_ty = local_ty.pinned_ty().unwrap_or_else(|| {
span_bug!(
source_info.span,
"expect type `Pin` for a pinned binding, found type {:?}",
local_ty
)
});
let pinned_temp =
Place::from(self.local_decls.push(LocalDecl::new(pinned_ty, source_info.span)));
self.cfg.push_assign(block, source_info, pinned_temp, borrow);
Rvalue::Aggregate(
Box::new(AggregateKind::Adt(
self.tcx.require_lang_item(LangItem::Pin, source_info.span),
FIRST_VARIANT,
self.tcx.mk_args(&[pinned_ty.into()]),
None,
None,
)),
std::iter::once(Operand::Move(pinned_temp)).collect(),
)
}
/// Each binding (`ref mut var`/`ref var`/`mut var`/`var`, where the bound
/// `var` has type `T` in the arm body) in a pattern maps to 2 locals. The
/// first local is a binding for occurrences of `var` in the guard, which

View file

@ -383,7 +383,7 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> {
}
visit::walk_pat(self, pat);
}
PatKind::Binding { mode: BindingMode(ByRef::Yes(rm), _), ty, .. } => {
PatKind::Binding { mode: BindingMode(ByRef::Yes(_, rm), _), ty, .. } => {
if self.inside_adt {
let ty::Ref(_, ty, _) = ty.kind() else {
span_bug!(

View file

@ -797,7 +797,7 @@ fn check_borrow_conflicts_in_at_patterns<'tcx>(cx: &MatchVisitor<'_, 'tcx>, pat:
// We have `x @ pat` where `x` is by-move. Reject all borrows in `pat`.
let mut conflicts_ref = Vec::new();
sub.each_binding(|_, mode, _, span| {
if matches!(mode, ByRef::Yes(_)) {
if matches!(mode, ByRef::Yes(..)) {
conflicts_ref.push(span)
}
});
@ -813,7 +813,7 @@ fn check_borrow_conflicts_in_at_patterns<'tcx>(cx: &MatchVisitor<'_, 'tcx>, pat:
return;
}
ByRef::No => return,
ByRef::Yes(m) => m,
ByRef::Yes(_, m) => m,
};
// We now have `ref $mut_outer binding @ sub` (semantically).
@ -823,7 +823,7 @@ fn check_borrow_conflicts_in_at_patterns<'tcx>(cx: &MatchVisitor<'_, 'tcx>, pat:
let mut conflicts_mut_ref = Vec::new();
sub.each_binding(|name, mode, ty, span| {
match mode {
ByRef::Yes(mut_inner) => match (mut_outer, mut_inner) {
ByRef::Yes(_, mut_inner) => match (mut_outer, mut_inner) {
// Both sides are `ref`.
(Mutability::Not, Mutability::Not) => {}
// 2x `ref mut`.

View file

@ -212,7 +212,7 @@ impl<'a> PatMigration<'a> {
}
if !self.info.suggest_eliding_modes
&& explicit_ba.0 == ByRef::No
&& let ByRef::Yes(mutbl) = mode.0
&& let ByRef::Yes(_, mutbl) = mode.0
{
// If we can't fix the pattern by eliding modifiers, we'll need to make the pattern
// fully explicit. i.e. we'll need to suggest reference patterns for this.

View file

@ -11,7 +11,7 @@ use rustc_abi::{FieldIdx, Integer};
use rustc_errors::codes::*;
use rustc_hir::def::{CtorOf, DefKind, Res};
use rustc_hir::pat_util::EnumerateAndAdjustIterator;
use rustc_hir::{self as hir, LangItem, RangeEnd};
use rustc_hir::{self as hir, ByRef, LangItem, Mutability, Pinnedness, RangeEnd};
use rustc_index::Idx;
use rustc_infer::infer::TyCtxtInferExt;
use rustc_middle::mir::interpret::LitToConstInput;
@ -114,6 +114,16 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
let borrow = self.typeck_results.deref_pat_borrow_mode(adjust.source, pat);
PatKind::DerefPattern { subpattern: thir_pat, borrow }
}
PatAdjust::PinDeref => {
let mutable = self.typeck_results.pat_has_ref_mut_binding(pat);
PatKind::DerefPattern {
subpattern: thir_pat,
borrow: ByRef::Yes(
Pinnedness::Pinned,
if mutable { Mutability::Mut } else { Mutability::Not },
),
}
}
};
Box::new(Pat { span, ty: adjust.source, kind })
});
@ -354,11 +364,22 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
// A ref x pattern is the same node used for x, and as such it has
// x's type, which is &T, where we want T (the type being matched).
let var_ty = ty;
if let hir::ByRef::Yes(_) = mode.0 {
if let ty::Ref(_, rty, _) = ty.kind() {
ty = *rty;
} else {
bug!("`ref {}` has wrong type {}", ident, ty);
if let hir::ByRef::Yes(pinnedness, _) = mode.0 {
match pinnedness {
hir::Pinnedness::Pinned
if let Some(pty) = ty.pinned_ty()
&& let &ty::Ref(_, rty, _) = pty.kind() =>
{
debug_assert!(
self.tcx.features().pin_ergonomics(),
"`pin_ergonomics` must be enabled to have a by-pin-ref binding"
);
ty = rty;
}
hir::Pinnedness::Not if let &ty::Ref(_, rty, _) = ty.kind() => {
ty = rty;
}
_ => bug!("`ref {}` has wrong type {}", ident, ty),
}
};

View file

@ -36,8 +36,8 @@ use rustc_ast::tokenstream::{
use rustc_ast::util::case::Case;
use rustc_ast::{
self as ast, AnonConst, AttrArgs, AttrId, ByRef, Const, CoroutineKind, DUMMY_NODE_ID,
DelimArgs, Expr, ExprKind, Extern, HasAttrs, HasTokens, Mutability, Recovered, Safety, StrLit,
Visibility, VisibilityKind,
DelimArgs, Expr, ExprKind, Extern, HasAttrs, HasTokens, Mutability, Pinnedness, Recovered,
Safety, StrLit, Visibility, VisibilityKind,
};
use rustc_ast_pretty::pprust;
use rustc_data_structures::fx::FxHashMap;
@ -1317,7 +1317,12 @@ impl<'a> Parser<'a> {
/// Parses reference binding mode (`ref`, `ref mut`, or nothing).
fn parse_byref(&mut self) -> ByRef {
if self.eat_keyword(exp!(Ref)) { ByRef::Yes(self.parse_mutability()) } else { ByRef::No }
if self.eat_keyword(exp!(Ref)) {
// FIXME(pin_ergonomics): support `ref pin const|mut` bindings
ByRef::Yes(Pinnedness::Not, self.parse_mutability())
} else {
ByRef::No
}
}
/// Possibly parses mutability (`const` or `mut`).

View file

@ -7,7 +7,8 @@ use rustc_ast::util::parser::ExprPrecedence;
use rustc_ast::visit::{self, Visitor};
use rustc_ast::{
self as ast, Arm, AttrVec, BindingMode, ByRef, Expr, ExprKind, LocalKind, MacCall, Mutability,
Pat, PatField, PatFieldsRest, PatKind, Path, QSelf, RangeEnd, RangeSyntax, Stmt, StmtKind,
Pat, PatField, PatFieldsRest, PatKind, Path, Pinnedness, QSelf, RangeEnd, RangeSyntax, Stmt,
StmtKind,
};
use rustc_ast_pretty::pprust;
use rustc_errors::{Applicability, Diag, DiagArgValue, PResult, StashKey};
@ -778,7 +779,11 @@ impl<'a> Parser<'a> {
}
// Parse ref ident @ pat / ref mut ident @ pat
let mutbl = self.parse_mutability();
self.parse_pat_ident(BindingMode(ByRef::Yes(mutbl), Mutability::Not), syntax_loc)?
self.parse_pat_ident(
// FIXME(pin_ergonomics): support `ref pin const|mut` bindings
BindingMode(ByRef::Yes(Pinnedness::Not, mutbl), Mutability::Not),
syntax_loc,
)?
} else if self.eat_keyword(exp!(Box)) {
self.parse_pat_box()?
} else if self.check_inline_const(0) {
@ -1093,7 +1098,7 @@ impl<'a> Parser<'a> {
self.ban_mut_general_pat(mut_span, &pat, changed_any_binding);
}
if matches!(pat.kind, PatKind::Ident(BindingMode(ByRef::Yes(_), Mutability::Mut), ..)) {
if matches!(pat.kind, PatKind::Ident(BindingMode(ByRef::Yes(..), Mutability::Mut), ..)) {
self.psess.gated_spans.gate(sym::mut_ref, pat.span);
}
Ok(pat.kind)

View file

@ -283,7 +283,8 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
| AttributeKind::ObjcSelector { .. }
| AttributeKind::RustcCoherenceIsCore(..)
| AttributeKind::DebuggerVisualizer(..)
| AttributeKind::RustcMain,
| AttributeKind::RustcMain
| AttributeKind::PinV2(..),
) => { /* do nothing */ }
Attribute::Unparsed(attr_item) => {
style = Some(attr_item.style);

View file

@ -19,7 +19,8 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
use rustc_data_structures::unord::{UnordMap, UnordSet};
use rustc_errors::codes::*;
use rustc_errors::{
Applicability, DiagArgValue, ErrorGuaranteed, IntoDiagArg, StashKey, Suggestions,
Applicability, Diag, DiagArgValue, ErrorGuaranteed, IntoDiagArg, StashKey, Suggestions,
pluralize,
};
use rustc_hir::def::Namespace::{self, *};
use rustc_hir::def::{self, CtorKind, DefKind, LifetimeRes, NonMacroAttrKind, PartialRes, PerNS};
@ -377,6 +378,7 @@ enum LifetimeBinderKind {
Function,
Closure,
ImplBlock,
ImplAssocType,
}
impl LifetimeBinderKind {
@ -387,6 +389,7 @@ impl LifetimeBinderKind {
PolyTrait => "bound",
WhereBound => "bound",
Item | ConstItem => "item",
ImplAssocType => "associated type",
ImplBlock => "impl block",
Function => "function",
Closure => "closure",
@ -1874,9 +1877,13 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
ty: ty.span,
});
} else {
self.r.dcx().emit_err(errors::AnonymousLifetimeNonGatReportError {
lifetime: lifetime.ident.span,
});
let mut err = self.r.dcx().create_err(
errors::AnonymousLifetimeNonGatReportError {
lifetime: lifetime.ident.span,
},
);
self.point_at_impl_lifetimes(&mut err, i, lifetime.ident.span);
err.emit();
}
} else {
self.r.dcx().emit_err(errors::ElidedAnonymousLifetimeReportError {
@ -1913,6 +1920,47 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
self.report_missing_lifetime_specifiers(vec![missing_lifetime], None);
}
fn point_at_impl_lifetimes(&mut self, err: &mut Diag<'_>, i: usize, lifetime: Span) {
let Some((rib, span)) = self.lifetime_ribs[..i]
.iter()
.rev()
.skip(1)
.filter_map(|rib| match rib.kind {
LifetimeRibKind::Generics { span, kind: LifetimeBinderKind::ImplBlock, .. } => {
Some((rib, span))
}
_ => None,
})
.next()
else {
return;
};
if !rib.bindings.is_empty() {
err.span_label(
span,
format!(
"there {} named lifetime{} specified on the impl block you could use",
if rib.bindings.len() == 1 { "is a" } else { "are" },
pluralize!(rib.bindings.len()),
),
);
if rib.bindings.len() == 1 {
err.span_suggestion_verbose(
lifetime.shrink_to_hi(),
"consider using the lifetime from the impl block",
format!("{} ", rib.bindings.keys().next().unwrap()),
Applicability::MaybeIncorrect,
);
}
} else {
err.span_label(
span,
"you could add a lifetime on the impl block, if the trait or the self type can \
have one",
);
}
}
#[instrument(level = "debug", skip(self))]
fn resolve_elided_lifetime(&mut self, anchor_id: NodeId, span: Span) {
let id = self.r.next_node_id();
@ -3352,7 +3400,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
&generics.params,
RibKind::AssocItem,
item.id,
LifetimeBinderKind::Item,
LifetimeBinderKind::ImplAssocType,
generics.span,
|this| {
this.with_lifetime_rib(LifetimeRibKind::AnonymousReportError, |this| {

View file

@ -3178,6 +3178,9 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
{
continue;
}
if let LifetimeBinderKind::ImplAssocType = kind {
continue;
}
if !span.can_be_used_for_suggestions()
&& suggest_note

View file

@ -27,10 +27,8 @@ tracing = "0.1"
# tidy-alphabetical-end
[target.'cfg(unix)'.dependencies]
# FIXME: Remove this pin once this rustix issue is resolved
# https://github.com/bytecodealliance/rustix/issues/1496
# tidy-alphabetical-start
libc = "=0.2.174"
libc = "0.2"
# tidy-alphabetical-end
[target.'cfg(windows)'.dependencies.windows]

View file

@ -1668,6 +1668,7 @@ symbols! {
pin,
pin_ergonomics,
pin_macro,
pin_v2,
platform_intrinsics,
plugin,
plugin_registrar,

View file

@ -20,7 +20,7 @@ pub(crate) fn target() -> Target {
Target {
llvm_target: "aarch64-unknown-linux-musl".into(),
metadata: TargetMetadata {
description: Some("ARM64 Linux with musl 1.2.3".into()),
description: Some("ARM64 Linux with musl 1.2.5".into()),
tier: Some(2),
host_tools: Some(true),
std: Some(true),

View file

@ -4,7 +4,7 @@ pub(crate) fn target() -> Target {
Target {
llvm_target: "arm-unknown-linux-musleabi".into(),
metadata: TargetMetadata {
description: Some("Armv6 Linux with musl 1.2.3".into()),
description: Some("Armv6 Linux with musl 1.2.5".into()),
tier: Some(2),
host_tools: Some(false),
std: Some(true),

View file

@ -4,7 +4,7 @@ pub(crate) fn target() -> Target {
Target {
llvm_target: "arm-unknown-linux-musleabihf".into(),
metadata: TargetMetadata {
description: Some("Armv6 Linux with musl 1.2.3, hardfloat".into()),
description: Some("Armv6 Linux with musl 1.2.5, hardfloat".into()),
tier: Some(2),
host_tools: Some(false),
std: Some(true),

View file

@ -4,7 +4,7 @@ pub(crate) fn target() -> Target {
Target {
llvm_target: "armv5te-unknown-linux-musleabi".into(),
metadata: TargetMetadata {
description: Some("Armv5TE Linux with musl 1.2.3".into()),
description: Some("Armv5TE Linux with musl 1.2.5".into()),
tier: Some(2),
host_tools: Some(false),
std: Some(true),

View file

@ -9,7 +9,7 @@ pub(crate) fn target() -> Target {
Target {
llvm_target: "armv7-unknown-linux-musleabi".into(),
metadata: TargetMetadata {
description: Some("Armv7-A Linux with musl 1.2.3".into()),
description: Some("Armv7-A Linux with musl 1.2.5".into()),
tier: Some(2),
host_tools: Some(false),
std: Some(true),

View file

@ -6,7 +6,7 @@ pub(crate) fn target() -> Target {
Target {
llvm_target: "armv7-unknown-linux-musleabihf".into(),
metadata: TargetMetadata {
description: Some("Armv7-A Linux with musl 1.2.3, hardfloat".into()),
description: Some("Armv7-A Linux with musl 1.2.5, hardfloat".into()),
tier: Some(2),
host_tools: Some(false),
std: Some(true),

View file

@ -15,7 +15,7 @@ pub(crate) fn target() -> Target {
Target {
llvm_target: "hexagon-unknown-linux-musl".into(),
metadata: TargetMetadata {
description: Some("Hexagon Linux with musl 1.2.3".into()),
description: Some("Hexagon Linux with musl 1.2.5".into()),
tier: Some(3),
host_tools: Some(false),
std: Some(true),

View file

@ -31,7 +31,7 @@ pub(crate) fn target() -> Target {
Target {
llvm_target: "i686-unknown-linux-musl".into(),
metadata: TargetMetadata {
description: Some("32-bit Linux with musl 1.2.3".into()),
description: Some("32-bit Linux with musl 1.2.5".into()),
tier: Some(2),
host_tools: Some(false),
std: Some(true),

View file

@ -14,7 +14,7 @@ pub(crate) fn target() -> Target {
// LLVM doesn't recognize "muslabi64" yet.
llvm_target: "mips64-unknown-linux-musl".into(),
metadata: TargetMetadata {
description: Some("MIPS64 for OpenWrt Linux musl 1.2.3".into()),
description: Some("MIPS64 for OpenWrt Linux musl 1.2.5".into()),
tier: Some(3),
host_tools: Some(false),
std: Some(true),

View file

@ -11,7 +11,7 @@ pub(crate) fn target() -> Target {
// LLVM doesn't recognize "muslabi64" yet.
llvm_target: "mips64-unknown-linux-musl".into(),
metadata: TargetMetadata {
description: Some("MIPS64 Linux, N64 ABI, musl 1.2.3".into()),
description: Some("MIPS64 Linux, N64 ABI, musl 1.2.5".into()),
tier: Some(3),
host_tools: Some(false),
std: Some(true),

View file

@ -9,7 +9,7 @@ pub(crate) fn target() -> Target {
// LLVM doesn't recognize "muslabi64" yet.
llvm_target: "mips64el-unknown-linux-musl".into(),
metadata: TargetMetadata {
description: Some("MIPS64 Linux, N64 ABI, musl 1.2.3".into()),
description: Some("MIPS64 Linux, N64 ABI, musl 1.2.5".into()),
tier: Some(3),
host_tools: Some(false),
std: Some(true),

View file

@ -10,7 +10,7 @@ pub(crate) fn target() -> Target {
Target {
llvm_target: "mips-unknown-linux-musl".into(),
metadata: TargetMetadata {
description: Some("MIPS Linux with musl 1.2.3".into()),
description: Some("MIPS Linux with musl 1.2.5".into()),
tier: Some(3),
host_tools: Some(false),
std: Some(true),

View file

@ -8,7 +8,7 @@ pub(crate) fn target() -> Target {
Target {
llvm_target: "mipsel-unknown-linux-musl".into(),
metadata: TargetMetadata {
description: Some("MIPS (little endian) Linux with musl 1.2.3".into()),
description: Some("MIPS (little endian) Linux with musl 1.2.5".into()),
tier: Some(3),
host_tools: Some(false),
std: Some(true),

View file

@ -16,7 +16,7 @@ pub(crate) fn target() -> Target {
Target {
llvm_target: "powerpc64-unknown-linux-musl".into(),
metadata: TargetMetadata {
description: Some("64-bit PowerPC Linux with musl 1.2.3".into()),
description: Some("64-bit PowerPC Linux with musl 1.2.5".into()),
tier: Some(3),
host_tools: Some(false),
std: Some(true),

View file

@ -16,7 +16,7 @@ pub(crate) fn target() -> Target {
Target {
llvm_target: "powerpc64le-unknown-linux-musl".into(),
metadata: TargetMetadata {
description: Some("64-bit PowerPC Linux with musl 1.2.3, Little Endian".into()),
description: Some("64-bit PowerPC Linux with musl 1.2.5, Little Endian".into()),
tier: Some(2),
host_tools: Some(true),
std: Some(true),

View file

@ -13,7 +13,7 @@ pub(crate) fn target() -> Target {
Target {
llvm_target: "powerpc-unknown-linux-musl".into(),
metadata: TargetMetadata {
description: Some("PowerPC Linux with musl 1.2.3".into()),
description: Some("PowerPC Linux with musl 1.2.5".into()),
tier: Some(3),
host_tools: Some(false),
std: Some(true),

View file

@ -6,9 +6,7 @@ pub(crate) fn target() -> Target {
Target {
llvm_target: "riscv32-unknown-linux-musl".into(),
metadata: TargetMetadata {
description: Some(
"RISC-V Linux (kernel 5.4, musl 1.2.3 + RISCV32 support patches".into(),
),
description: Some("RISC-V Linux (kernel 5.4, musl 1.2.5)".into()),
tier: Some(3),
host_tools: Some(false),
std: Some(true),

View file

@ -6,7 +6,7 @@ pub(crate) fn target() -> Target {
Target {
llvm_target: "riscv64-unknown-linux-musl".into(),
metadata: TargetMetadata {
description: Some("RISC-V Linux (kernel 4.20, musl 1.2.3)".into()),
description: Some("RISC-V Linux (kernel 4.20, musl 1.2.5)".into()),
tier: Some(3),
host_tools: Some(false),
std: Some(true),

View file

@ -17,7 +17,7 @@ pub(crate) fn target() -> Target {
Target {
llvm_target: "s390x-unknown-linux-musl".into(),
metadata: TargetMetadata {
description: Some("S390x Linux (kernel 3.2, musl 1.2.3)".into()),
description: Some("S390x Linux (kernel 3.2, musl 1.2.5)".into()),
tier: Some(3),
host_tools: Some(false),
std: Some(true),

View file

@ -10,7 +10,7 @@ pub(crate) fn target() -> Target {
Target {
llvm_target: "armv7-unknown-linux-musleabihf".into(),
metadata: TargetMetadata {
description: Some("Thumb2-mode ARMv7-A Linux with NEON, musl 1.2.3".into()),
description: Some("Thumb2-mode ARMv7-A Linux with NEON, musl 1.2.5".into()),
tier: Some(3),
host_tools: Some(false),
std: Some(true),

View file

@ -6,7 +6,7 @@ pub(crate) fn target() -> Target {
Target {
llvm_target: "x86_64-unknown-linux-musl".into(),
metadata: TargetMetadata {
description: Some("64-bit Unikraft with musl 1.2.3".into()),
description: Some("64-bit Unikraft with musl 1.2.5".into()),
tier: Some(3),
host_tools: Some(false),
std: Some(true),

View file

@ -22,7 +22,7 @@ pub(crate) fn target() -> Target {
Target {
llvm_target: "x86_64-unknown-linux-musl".into(),
metadata: TargetMetadata {
description: Some("64-bit Linux with musl 1.2.3".into()),
description: Some("64-bit Linux with musl 1.2.5".into()),
tier: Some(2),
host_tools: Some(true),
std: Some(true),

View file

@ -386,7 +386,6 @@ static X86_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
("amx-movrs", Unstable(sym::x86_amx_intrinsics), &["amx-tile"]),
("amx-tf32", Unstable(sym::x86_amx_intrinsics), &["amx-tile"]),
("amx-tile", Unstable(sym::x86_amx_intrinsics), &[]),
("amx-transpose", Unstable(sym::x86_amx_intrinsics), &["amx-tile"]),
("apxf", Unstable(sym::apx_target_feature), &[]),
("avx", Stable, &["sse4.2"]),
("avx2", Stable, &["avx"]),

View file

@ -125,7 +125,7 @@ impl Barrier {
let local_gen = lock.generation_id;
lock.count += 1;
if lock.count < self.num_threads {
let _guard = self.cvar.wait_while(lock, |state| local_gen == state.generation_id);
self.cvar.wait_while(&mut lock, |state| local_gen == state.generation_id);
BarrierWaitResult(false)
} else {
lock.count = 0;

View file

@ -1,4 +1,5 @@
use crate::fmt;
use crate::ops::DerefMut;
use crate::sync::WaitTimeoutResult;
use crate::sync::nonpoison::{MutexGuard, mutex};
use crate::sys::sync as sys;
@ -38,7 +39,7 @@ use crate::time::{Duration, Instant};
/// let (lock, cvar) = &*pair;
/// let mut started = lock.lock();
/// while !*started {
/// started = cvar.wait(started);
/// cvar.wait(&mut started);
/// }
/// ```
///
@ -115,16 +116,15 @@ impl Condvar {
/// let mut started = lock.lock();
/// // As long as the value inside the `Mutex<bool>` is `false`, we wait.
/// while !*started {
/// started = cvar.wait(started);
/// cvar.wait(&mut started);
/// }
/// ```
#[unstable(feature = "nonpoison_condvar", issue = "134645")]
pub fn wait<'a, T>(&self, guard: MutexGuard<'a, T>) -> MutexGuard<'a, T> {
pub fn wait<T>(&self, guard: &mut MutexGuard<'_, T>) {
unsafe {
let lock = mutex::guard_lock(&guard);
let lock = mutex::guard_lock(guard);
self.inner.wait(lock);
}
guard
}
/// Blocks the current thread until the provided condition becomes false.
@ -167,21 +167,17 @@ impl Condvar {
/// // Wait for the thread to start up.
/// let (lock, cvar) = &*pair;
/// // As long as the value inside the `Mutex<bool>` is `true`, we wait.
/// let _guard = cvar.wait_while(lock.lock(), |pending| { *pending });
/// let mut guard = lock.lock();
/// cvar.wait_while(&mut guard, |pending| { *pending });
/// ```
#[unstable(feature = "nonpoison_condvar", issue = "134645")]
pub fn wait_while<'a, T, F>(
&self,
mut guard: MutexGuard<'a, T>,
mut condition: F,
) -> MutexGuard<'a, T>
pub fn wait_while<T, F>(&self, guard: &mut MutexGuard<'_, T>, mut condition: F)
where
F: FnMut(&mut T) -> bool,
{
while condition(&mut *guard) {
guard = self.wait(guard);
while condition(guard.deref_mut()) {
self.wait(guard);
}
guard
}
/// Waits on this condition variable for a notification, timing out after a
@ -206,7 +202,7 @@ impl Condvar {
/// The returned [`WaitTimeoutResult`] value indicates if the timeout is
/// known to have elapsed.
///
/// Like [`wait`], the lock specified will be re-acquired when this function
/// Like [`wait`], the lock specified will have been re-acquired when this function
/// returns, regardless of whether the timeout elapsed or not.
///
/// [`wait`]: Self::wait
@ -239,9 +235,8 @@ impl Condvar {
/// let mut started = lock.lock();
/// // as long as the value inside the `Mutex<bool>` is `false`, we wait
/// loop {
/// let result = cvar.wait_timeout(started, Duration::from_millis(10));
/// let result = cvar.wait_timeout(&mut started, Duration::from_millis(10));
/// // 10 milliseconds have passed, or maybe the value changed!
/// started = result.0;
/// if *started == true {
/// // We received the notification and the value has been updated, we can leave.
/// break
@ -249,16 +244,16 @@ impl Condvar {
/// }
/// ```
#[unstable(feature = "nonpoison_condvar", issue = "134645")]
pub fn wait_timeout<'a, T>(
pub fn wait_timeout<T>(
&self,
guard: MutexGuard<'a, T>,
guard: &mut MutexGuard<'_, T>,
dur: Duration,
) -> (MutexGuard<'a, T>, WaitTimeoutResult) {
) -> WaitTimeoutResult {
let success = unsafe {
let lock = mutex::guard_lock(&guard);
let lock = mutex::guard_lock(guard);
self.inner.wait_timeout(lock, dur)
};
(guard, WaitTimeoutResult(!success))
WaitTimeoutResult(!success)
}
/// Waits on this condition variable for a notification, timing out after a
@ -277,7 +272,7 @@ impl Condvar {
/// The returned [`WaitTimeoutResult`] value indicates if the timeout is
/// known to have elapsed without the condition being met.
///
/// Like [`wait_while`], the lock specified will be re-acquired when this
/// Like [`wait_while`], the lock specified will have been re-acquired when this
/// function returns, regardless of whether the timeout elapsed or not.
///
/// [`wait_while`]: Self::wait_while
@ -307,37 +302,39 @@ impl Condvar {
///
/// // wait for the thread to start up
/// let (lock, cvar) = &*pair;
/// let mut guard = lock.lock();
/// let result = cvar.wait_timeout_while(
/// lock.lock(),
/// &mut guard,
/// Duration::from_millis(100),
/// |&mut pending| pending,
/// );
/// if result.1.timed_out() {
/// if result.timed_out() {
/// // timed-out without the condition ever evaluating to false.
/// }
/// // access the locked mutex via result.0
/// // access the locked mutex via guard
/// ```
#[unstable(feature = "nonpoison_condvar", issue = "134645")]
pub fn wait_timeout_while<'a, T, F>(
pub fn wait_timeout_while<T, F>(
&self,
mut guard: MutexGuard<'a, T>,
guard: &mut MutexGuard<'_, T>,
dur: Duration,
mut condition: F,
) -> (MutexGuard<'a, T>, WaitTimeoutResult)
) -> WaitTimeoutResult
where
F: FnMut(&mut T) -> bool,
{
let start = Instant::now();
loop {
if !condition(&mut *guard) {
return (guard, WaitTimeoutResult(false));
}
while condition(guard.deref_mut()) {
let timeout = match dur.checked_sub(start.elapsed()) {
Some(timeout) => timeout,
None => return (guard, WaitTimeoutResult(true)),
None => return WaitTimeoutResult(true),
};
guard = self.wait_timeout(guard, timeout).0;
self.wait_timeout(guard, timeout);
}
WaitTimeoutResult(false)
}
/// Wakes up one blocked thread on this condvar.
@ -378,7 +375,7 @@ impl Condvar {
/// let mut started = lock.lock();
/// // As long as the value inside the `Mutex<bool>` is `false`, we wait.
/// while !*started {
/// started = cvar.wait(started);
/// cvar.wait(&mut started);
/// }
/// ```
#[unstable(feature = "nonpoison_condvar", issue = "134645")]
@ -422,7 +419,7 @@ impl Condvar {
/// let mut started = lock.lock();
/// // As long as the value inside the `Mutex<bool>` is `false`, we wait.
/// while !*started {
/// started = cvar.wait(started);
/// cvar.wait(&mut started);
/// }
/// ```
#[unstable(feature = "nonpoison_condvar", issue = "134645")]

View file

@ -17,256 +17,469 @@ nonpoison_and_poison_unwrap_test!(
}
);
#[test]
#[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads.
nonpoison_and_poison_unwrap_test!(
name: notify_one,
test_body: {
use locks::{Condvar, Mutex};
fn poison_notify_one() {
use std::sync::poison::{Condvar, Mutex};
let m = Arc::new(Mutex::new(()));
let m2 = m.clone();
let c = Arc::new(Condvar::new());
let c2 = c.clone();
let g = m.lock().unwrap();
let _t = thread::spawn(move || {
let _g = m2.lock().unwrap();
c2.notify_one();
});
let g = c.wait(g).unwrap();
drop(g);
}
#[test]
#[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads.
fn nonpoison_notify_one() {
use std::sync::nonpoison::{Condvar, Mutex};
let m = Arc::new(Mutex::new(()));
let m2 = m.clone();
let c = Arc::new(Condvar::new());
let c2 = c.clone();
let mut g = m.lock();
let _t = thread::spawn(move || {
let _g = m2.lock();
c2.notify_one();
});
c.wait(&mut g);
drop(g);
}
#[test]
#[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads.
fn poison_notify_all() {
use std::sync::poison::{Condvar, Mutex};
const N: usize = 10;
let data = Arc::new((Mutex::new(0), Condvar::new()));
let (tx, rx) = channel();
for _ in 0..N {
let data = data.clone();
let tx = tx.clone();
thread::spawn(move || {
let &(ref lock, ref cond) = &*data;
let mut cnt = lock.lock().unwrap();
*cnt += 1;
if *cnt == N {
tx.send(()).unwrap();
}
while *cnt != 0 {
cnt = cond.wait(cnt).unwrap();
}
tx.send(()).unwrap();
});
}
drop(tx);
let &(ref lock, ref cond) = &*data;
rx.recv().unwrap();
let mut cnt = lock.lock().unwrap();
*cnt = 0;
cond.notify_all();
drop(cnt);
for _ in 0..N {
rx.recv().unwrap();
}
}
#[test]
#[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads.
fn nonpoison_notify_all() {
use std::sync::nonpoison::{Condvar, Mutex};
const N: usize = 10;
let data = Arc::new((Mutex::new(0), Condvar::new()));
let (tx, rx) = channel();
for _ in 0..N {
let data = data.clone();
let tx = tx.clone();
thread::spawn(move || {
let &(ref lock, ref cond) = &*data;
let mut cnt = lock.lock();
*cnt += 1;
if *cnt == N {
tx.send(()).unwrap();
}
while *cnt != 0 {
cond.wait(&mut cnt);
}
tx.send(()).unwrap();
});
}
drop(tx);
let &(ref lock, ref cond) = &*data;
rx.recv().unwrap();
let mut cnt = lock.lock();
*cnt = 0;
cond.notify_all();
drop(cnt);
for _ in 0..N {
rx.recv().unwrap();
}
}
#[test]
#[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads.
fn poison_test_mutex_arc_condvar() {
use std::sync::poison::{Condvar, Mutex};
struct Packet<T>(Arc<(Mutex<T>, Condvar)>);
let packet = Packet(Arc::new((Mutex::new(false), Condvar::new())));
let packet2 = Packet(packet.0.clone());
let (tx, rx) = channel();
let _t = thread::spawn(move || {
// Wait until our parent has taken the lock.
rx.recv().unwrap();
let &(ref lock, ref cvar) = &*packet2.0;
// Set the data to `true` and wake up our parent.
let mut guard = lock.lock().unwrap();
*guard = true;
cvar.notify_one();
});
let &(ref lock, ref cvar) = &*packet.0;
let mut guard = lock.lock().unwrap();
// Wake up our child.
tx.send(()).unwrap();
// Wait until our child has set the data to `true`.
assert!(!*guard);
while !*guard {
guard = cvar.wait(guard).unwrap();
}
}
#[test]
#[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads.
fn nonpoison_test_mutex_arc_condvar() {
use std::sync::nonpoison::{Condvar, Mutex};
struct Packet<T>(Arc<(Mutex<T>, Condvar)>);
let packet = Packet(Arc::new((Mutex::new(false), Condvar::new())));
let packet2 = Packet(packet.0.clone());
let (tx, rx) = channel();
let _t = thread::spawn(move || {
// Wait until our parent has taken the lock.
rx.recv().unwrap();
let &(ref lock, ref cvar) = &*packet2.0;
// Set the data to `true` and wake up our parent.
let mut guard = lock.lock();
*guard = true;
cvar.notify_one();
});
let &(ref lock, ref cvar) = &*packet.0;
let mut guard = lock.lock();
// Wake up our child.
tx.send(()).unwrap();
// Wait until our child has set the data to `true`.
assert!(!*guard);
while !*guard {
cvar.wait(&mut guard);
}
}
#[test]
#[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads.
fn poison_wait_while() {
use std::sync::poison::{Condvar, Mutex};
let pair = Arc::new((Mutex::new(false), Condvar::new()));
let pair2 = pair.clone();
// Inside of our lock, spawn a new thread, and then wait for it to start.
thread::spawn(move || {
let &(ref lock, ref cvar) = &*pair2;
let mut started = lock.lock().unwrap();
*started = true;
// We notify the condvar that the value has changed.
cvar.notify_one();
});
// Wait for the thread to start up.
let &(ref lock, ref cvar) = &*pair;
let guard = cvar.wait_while(lock.lock().unwrap(), |started| !*started).unwrap();
assert!(*guard);
}
#[test]
#[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads.
fn nonpoison_wait_while() {
use std::sync::nonpoison::{Condvar, Mutex};
let pair = Arc::new((Mutex::new(false), Condvar::new()));
let pair2 = pair.clone();
// Inside of our lock, spawn a new thread, and then wait for it to start.
thread::spawn(move || {
let &(ref lock, ref cvar) = &*pair2;
let mut started = lock.lock();
*started = true;
// We notify the condvar that the value has changed.
cvar.notify_one();
});
// Wait for the thread to start up.
let &(ref lock, ref cvar) = &*pair;
let mut guard = lock.lock();
cvar.wait_while(&mut guard, |started| !*started);
assert!(*guard);
}
#[test]
#[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads.
fn poison_wait_timeout_wait() {
use std::sync::poison::{Condvar, Mutex};
let m = Arc::new(Mutex::new(()));
let c = Arc::new(Condvar::new());
loop {
let g = m.lock().unwrap();
let (_g, no_timeout) = c.wait_timeout(g, Duration::from_millis(1)).unwrap();
// spurious wakeups mean this isn't necessarily true
// so execute test again, if not timeout
if !no_timeout.timed_out() {
continue;
}
break;
}
}
#[test]
#[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads.
fn nonpoison_wait_timeout_wait() {
use std::sync::nonpoison::{Condvar, Mutex};
let m = Arc::new(Mutex::new(()));
let c = Arc::new(Condvar::new());
loop {
let mut g = m.lock();
let no_timeout = c.wait_timeout(&mut g, Duration::from_millis(1));
// spurious wakeups mean this isn't necessarily true
// so execute test again, if not timeout
if !no_timeout.timed_out() {
continue;
}
break;
}
}
#[test]
#[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads.
fn poison_wait_timeout_while_wait() {
use std::sync::poison::{Condvar, Mutex};
let m = Arc::new(Mutex::new(()));
let c = Arc::new(Condvar::new());
let g = m.lock().unwrap();
let (_g, wait) = c.wait_timeout_while(g, Duration::from_millis(1), |_| true).unwrap();
// no spurious wakeups. ensure it timed-out
assert!(wait.timed_out());
}
#[test]
#[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads.
fn nonpoison_wait_timeout_while_wait() {
use std::sync::nonpoison::{Condvar, Mutex};
let m = Arc::new(Mutex::new(()));
let c = Arc::new(Condvar::new());
let mut g = m.lock();
let wait = c.wait_timeout_while(&mut g, Duration::from_millis(1), |_| true);
// no spurious wakeups. ensure it timed-out
assert!(wait.timed_out());
}
#[test]
#[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads.
fn poison_wait_timeout_while_instant_satisfy() {
use std::sync::poison::{Condvar, Mutex};
let m = Arc::new(Mutex::new(()));
let c = Arc::new(Condvar::new());
let g = m.lock().unwrap();
let (_g, wait) = c.wait_timeout_while(g, Duration::from_millis(0), |_| false).unwrap();
// ensure it didn't time-out even if we were not given any time.
assert!(!wait.timed_out());
}
#[test]
#[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads.
fn nonpoison_wait_timeout_while_instant_satisfy() {
use std::sync::nonpoison::{Condvar, Mutex};
let m = Arc::new(Mutex::new(()));
let c = Arc::new(Condvar::new());
let mut g = m.lock();
let wait = c.wait_timeout_while(&mut g, Duration::from_millis(0), |_| false);
// ensure it didn't time-out even if we were not given any time.
assert!(!wait.timed_out());
}
#[test]
#[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads.
fn poison_wait_timeout_while_wake() {
use std::sync::poison::{Condvar, Mutex};
let pair = Arc::new((Mutex::new(false), Condvar::new()));
let pair_copy = pair.clone();
let &(ref m, ref c) = &*pair;
let g = m.lock().unwrap();
let _t = thread::spawn(move || {
let &(ref lock, ref cvar) = &*pair_copy;
let mut started = lock.lock().unwrap();
thread::sleep(Duration::from_millis(1));
*started = true;
cvar.notify_one();
});
let (g2, wait) = c
.wait_timeout_while(g, Duration::from_millis(u64::MAX), |&mut notified| !notified)
.unwrap();
// ensure it didn't time-out even if we were not given any time.
assert!(!wait.timed_out());
assert!(*g2);
}
#[test]
#[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads.
fn nonpoison_wait_timeout_while_wake() {
use std::sync::nonpoison::{Condvar, Mutex};
let pair = Arc::new((Mutex::new(false), Condvar::new()));
let pair_copy = pair.clone();
let &(ref m, ref c) = &*pair;
let mut g = m.lock();
let _t = thread::spawn(move || {
let &(ref lock, ref cvar) = &*pair_copy;
let mut started = lock.lock();
thread::sleep(Duration::from_millis(1));
*started = true;
cvar.notify_one();
});
let wait =
c.wait_timeout_while(&mut g, Duration::from_millis(u64::MAX), |&mut notified| !notified);
// ensure it didn't time-out even if we were not given any time.
assert!(!wait.timed_out());
assert!(*g);
}
#[test]
#[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads.
fn poison_wait_timeout_wake() {
use std::sync::poison::{Condvar, Mutex};
let m = Arc::new(Mutex::new(()));
let c = Arc::new(Condvar::new());
loop {
let g = m.lock().unwrap();
let m = Arc::new(Mutex::new(()));
let m2 = m.clone();
let c = Arc::new(Condvar::new());
let c2 = c.clone();
let m2 = m.clone();
let g = maybe_unwrap(m.lock());
let _t = thread::spawn(move || {
let _g = maybe_unwrap(m2.lock());
let notified = Arc::new(AtomicBool::new(false));
let notified_copy = notified.clone();
let t = thread::spawn(move || {
let _g = m2.lock().unwrap();
thread::sleep(Duration::from_millis(1));
notified_copy.store(true, Ordering::Relaxed);
c2.notify_one();
});
let g = maybe_unwrap(c.wait(g));
drop(g);
}
);
#[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads.
nonpoison_and_poison_unwrap_test!(
name: notify_all,
test_body: {
use locks::{Condvar, Mutex};
const N: usize = 10;
let data = Arc::new((Mutex::new(0), Condvar::new()));
let (tx, rx) = channel();
for _ in 0..N {
let data = data.clone();
let tx = tx.clone();
thread::spawn(move || {
let &(ref lock, ref cond) = &*data;
let mut cnt = maybe_unwrap(lock.lock());
*cnt += 1;
if *cnt == N {
tx.send(()).unwrap();
}
while *cnt != 0 {
cnt = maybe_unwrap(cond.wait(cnt));
}
tx.send(()).unwrap();
});
}
drop(tx);
let &(ref lock, ref cond) = &*data;
rx.recv().unwrap();
let mut cnt = maybe_unwrap(lock.lock());
*cnt = 0;
cond.notify_all();
drop(cnt);
for _ in 0..N {
rx.recv().unwrap();
}
}
);
#[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads.
nonpoison_and_poison_unwrap_test!(
name: test_mutex_arc_condvar,
test_body: {
use locks::{Condvar, Mutex};
struct Packet<T>(Arc<(Mutex<T>, Condvar)>);
let packet = Packet(Arc::new((Mutex::new(false), Condvar::new())));
let packet2 = Packet(packet.0.clone());
let (tx, rx) = channel();
let _t = thread::spawn(move || {
// Wait until our parent has taken the lock.
rx.recv().unwrap();
let &(ref lock, ref cvar) = &*packet2.0;
// Set the data to `true` and wake up our parent.
let mut guard = maybe_unwrap(lock.lock());
*guard = true;
cvar.notify_one();
});
let &(ref lock, ref cvar) = &*packet.0;
let mut guard = maybe_unwrap(lock.lock());
// Wake up our child.
tx.send(()).unwrap();
// Wait until our child has set the data to `true`.
assert!(!*guard);
while !*guard {
guard = maybe_unwrap(cvar.wait(guard));
}
}
);
#[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads.
nonpoison_and_poison_unwrap_test!(
name: wait_while,
test_body: {
use locks::{Condvar, Mutex};
let pair = Arc::new((Mutex::new(false), Condvar::new()));
let pair2 = pair.clone();
// Inside of our lock, spawn a new thread, and then wait for it to start.
thread::spawn(move || {
let &(ref lock, ref cvar) = &*pair2;
let mut started = maybe_unwrap(lock.lock());
*started = true;
// We notify the condvar that the value has changed.
cvar.notify_one();
});
// Wait for the thread to start up.
let &(ref lock, ref cvar) = &*pair;
let guard = cvar.wait_while(maybe_unwrap(lock.lock()), |started| !*started);
assert!(*maybe_unwrap(guard));
}
);
#[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads.
nonpoison_and_poison_unwrap_test!(
name: wait_timeout_wait,
test_body: {
use locks::{Condvar, Mutex};
let m = Arc::new(Mutex::new(()));
let c = Arc::new(Condvar::new());
loop {
let g = maybe_unwrap(m.lock());
let (_g, no_timeout) = maybe_unwrap(c.wait_timeout(g, Duration::from_millis(1)));
// spurious wakeups mean this isn't necessarily true
// so execute test again, if not timeout
if !no_timeout.timed_out() {
continue;
}
break;
}
}
);
#[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads.
nonpoison_and_poison_unwrap_test!(
name: wait_timeout_while_wait,
test_body: {
use locks::{Condvar, Mutex};
let m = Arc::new(Mutex::new(()));
let c = Arc::new(Condvar::new());
let g = maybe_unwrap(m.lock());
let (_g, wait) = maybe_unwrap(c.wait_timeout_while(g, Duration::from_millis(1), |_| true));
// no spurious wakeups. ensure it timed-out
assert!(wait.timed_out());
}
);
#[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads.
nonpoison_and_poison_unwrap_test!(
name: wait_timeout_while_instant_satisfy,
test_body: {
use locks::{Condvar, Mutex};
let m = Arc::new(Mutex::new(()));
let c = Arc::new(Condvar::new());
let g = maybe_unwrap(m.lock());
let (_g, wait) =
maybe_unwrap(c.wait_timeout_while(g, Duration::from_millis(0), |_| false));
// ensure it didn't time-out even if we were not given any time.
assert!(!wait.timed_out());
}
);
#[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads.
nonpoison_and_poison_unwrap_test!(
name: wait_timeout_while_wake,
test_body: {
use locks::{Condvar, Mutex};
let pair = Arc::new((Mutex::new(false), Condvar::new()));
let pair_copy = pair.clone();
let &(ref m, ref c) = &*pair;
let g = maybe_unwrap(m.lock());
let _t = thread::spawn(move || {
let &(ref lock, ref cvar) = &*pair_copy;
let mut started = maybe_unwrap(lock.lock());
thread::sleep(Duration::from_millis(1));
*started = true;
cvar.notify_one();
});
let (g2, wait) = maybe_unwrap(c.wait_timeout_while(
g,
Duration::from_millis(u64::MAX),
|&mut notified| !notified
));
// ensure it didn't time-out even if we were not given any time.
assert!(!wait.timed_out());
assert!(*g2);
}
);
#[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads.
nonpoison_and_poison_unwrap_test!(
name: wait_timeout_wake,
test_body: {
use locks::{Condvar, Mutex};
let m = Arc::new(Mutex::new(()));
let c = Arc::new(Condvar::new());
loop {
let g = maybe_unwrap(m.lock());
let c2 = c.clone();
let m2 = m.clone();
let notified = Arc::new(AtomicBool::new(false));
let notified_copy = notified.clone();
let t = thread::spawn(move || {
let _g = maybe_unwrap(m2.lock());
thread::sleep(Duration::from_millis(1));
notified_copy.store(true, Ordering::Relaxed);
c2.notify_one();
});
let (g, timeout_res) =
maybe_unwrap(c.wait_timeout(g, Duration::from_millis(u64::MAX)));
assert!(!timeout_res.timed_out());
// spurious wakeups mean this isn't necessarily true
// so execute test again, if not notified
if !notified.load(Ordering::Relaxed) {
t.join().unwrap();
continue;
}
drop(g);
let (g, timeout_res) = c.wait_timeout(g, Duration::from_millis(u64::MAX)).unwrap();
assert!(!timeout_res.timed_out());
// spurious wakeups mean this isn't necessarily true
// so execute test again, if not notified
if !notified.load(Ordering::Relaxed) {
t.join().unwrap();
break;
continue;
}
drop(g);
t.join().unwrap();
break;
}
);
}
#[test]
#[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads.
fn nonpoison_wait_timeout_wake() {
use std::sync::nonpoison::{Condvar, Mutex};
let m = Arc::new(Mutex::new(()));
let c = Arc::new(Condvar::new());
loop {
let mut g = m.lock();
let c2 = c.clone();
let m2 = m.clone();
let notified = Arc::new(AtomicBool::new(false));
let notified_copy = notified.clone();
let t = thread::spawn(move || {
let _g = m2.lock();
thread::sleep(Duration::from_millis(1));
notified_copy.store(true, Ordering::Relaxed);
c2.notify_one();
});
let timeout_res = c.wait_timeout(&mut g, Duration::from_millis(u64::MAX));
assert!(!timeout_res.timed_out());
// spurious wakeups mean this isn't necessarily true
// so execute test again, if not notified
if !notified.load(Ordering::Relaxed) {
t.join().unwrap();
continue;
}
drop(g);
t.join().unwrap();
break;
}
}
// Some platforms internally cast the timeout duration into nanoseconds.
// If they fail to consider overflow during the conversion (I'm looking
@ -274,42 +487,112 @@ nonpoison_and_poison_unwrap_test!(
// timeout for durations that are slightly longer than u64::MAX nanoseconds.
// `std` should guard against this by clamping the timeout.
// See #37440 for context.
nonpoison_and_poison_unwrap_test!(
name: timeout_nanoseconds,
test_body: {
use locks::Mutex;
use locks::Condvar;
#[test]
fn poison_timeout_nanoseconds() {
use std::sync::poison::{Condvar, Mutex};
let sent = Mutex::new(false);
let cond = Condvar::new();
let sent = Mutex::new(false);
let cond = Condvar::new();
thread::scope(|s| {
s.spawn(|| {
// Sleep so that the other thread has a chance to encounter the
// timeout.
thread::sleep(Duration::from_secs(2));
maybe_unwrap(sent.set(true));
cond.notify_all();
});
thread::scope(|s| {
s.spawn(|| {
// Sleep so that the other thread has a chance to encounter the
// timeout.
thread::sleep(Duration::from_secs(2));
*sent.lock().unwrap() = true;
cond.notify_all();
});
let mut guard = maybe_unwrap(sent.lock());
// Loop until `sent` is set by the thread to guard against spurious
// wakeups. If the `wait_timeout` happens just before the signal by
// the other thread, such a spurious wakeup might prevent the
// miscalculated timeout from occurring, but this is basically just
// a smoke test anyway.
loop {
if *guard {
break;
}
// If there is internal overflow, this call will return almost
// immediately, before the other thread has reached the `notify_all`,
// and indicate a timeout.
let (g, res) = maybe_unwrap(cond.wait_timeout(guard, Duration::from_secs(u64::MAX.div_ceil(1_000_000_000))));
assert!(!res.timed_out());
guard = g;
let mut guard = sent.lock().unwrap();
// Loop until `sent` is set by the thread to guard against spurious
// wakeups. If the `wait_timeout` happens just before the signal by
// the other thread, such a spurious wakeup might prevent the
// miscalculated timeout from occurring, but this is basically just
// a smoke test anyway.
loop {
if *guard {
break;
}
})
// If there is internal overflow, this call will return almost
// immediately, before the other thread has reached the `notify_all`,
// and indicate a timeout.
let (g, res) = cond
.wait_timeout(guard, Duration::from_secs(u64::MAX.div_ceil(1_000_000_000)))
.unwrap();
assert!(!res.timed_out());
guard = g;
}
})
}
#[test]
fn nonpoison_timeout_nanoseconds() {
use std::sync::nonpoison::{Condvar, Mutex};
let sent = Mutex::new(false);
let cond = Condvar::new();
thread::scope(|s| {
s.spawn(|| {
// Sleep so that the other thread has a chance to encounter the
// timeout.
thread::sleep(Duration::from_secs(2));
sent.set(true);
cond.notify_all();
});
let mut guard = sent.lock();
// Loop until `sent` is set by the thread to guard against spurious
// wakeups. If the `wait_timeout` happens just before the signal by
// the other thread, such a spurious wakeup might prevent the
// miscalculated timeout from occurring, but this is basically just
// a smoke test anyway.
loop {
if *guard {
break;
}
// If there is internal overflow, this call will return almost
// immediately, before the other thread has reached the `notify_all`,
// and indicate a timeout.
let res = cond
.wait_timeout(&mut guard, Duration::from_secs(u64::MAX.div_ceil(1_000_000_000)));
assert!(!res.timed_out());
}
})
}
#[test]
#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
fn test_arc_condvar_poison() {
use std::sync::poison::{Condvar, Mutex};
struct Packet<T>(Arc<(Mutex<T>, Condvar)>);
let packet = Packet(Arc::new((Mutex::new(1), Condvar::new())));
let packet2 = Packet(packet.0.clone());
let (tx, rx) = channel();
let _t = thread::spawn(move || -> () {
rx.recv().unwrap();
let &(ref lock, ref cvar) = &*packet2.0;
let _g = lock.lock().unwrap();
cvar.notify_one();
// Parent should fail when it wakes up.
panic!();
});
let &(ref lock, ref cvar) = &*packet.0;
let mut lock = lock.lock().unwrap();
tx.send(()).unwrap();
while *lock == 1 {
match cvar.wait(lock) {
Ok(l) => {
lock = l;
assert_eq!(*lock, 1);
}
Err(..) => break,
}
}
);
}

View file

@ -3,7 +3,7 @@ use std::ops::FnMut;
use std::panic::{self, AssertUnwindSafe};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::mpsc::channel;
use std::sync::{Arc, Condvar, MappedMutexGuard, Mutex, MutexGuard, TryLockError};
use std::sync::{Arc, MappedMutexGuard, Mutex, MutexGuard, TryLockError};
use std::{hint, mem, thread};
////////////////////////////////////////////////////////////////////////////////////////////////////
@ -423,38 +423,6 @@ fn test_replace_poison() {
inner(|| NonCopyNeedsDrop(10), || NonCopyNeedsDrop(20));
}
#[test]
#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
fn test_arc_condvar_poison() {
struct Packet<T>(Arc<(Mutex<T>, Condvar)>);
let packet = Packet(Arc::new((Mutex::new(1), Condvar::new())));
let packet2 = Packet(packet.0.clone());
let (tx, rx) = channel();
let _t = thread::spawn(move || -> () {
rx.recv().unwrap();
let &(ref lock, ref cvar) = &*packet2.0;
let _g = lock.lock().unwrap();
cvar.notify_one();
// Parent should fail when it wakes up.
panic!();
});
let &(ref lock, ref cvar) = &*packet.0;
let mut lock = lock.lock().unwrap();
tx.send(()).unwrap();
while *lock == 1 {
match cvar.wait(lock) {
Ok(l) => {
lock = l;
assert_eq!(*lock, 1);
}
Err(..) => break,
}
}
}
#[test]
#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
fn test_mutex_arc_poison() {

View file

@ -93,7 +93,6 @@ features! {
/// * `"amx-fp8"`
/// * `"amx-movrs"`
/// * `"amx-tf32"`
/// * `"amx-transpose"`
/// * `"f16c"`
/// * `"fma"`
/// * `"bmi1"`
@ -231,8 +230,6 @@ features! {
/// AMX-MOVRS (Matrix MOVERS operations)
@FEATURE: #[unstable(feature = "x86_amx_intrinsics", issue = "126622")] amx_tf32: "amx-tf32";
/// AMX-TF32 (TensorFloat32 Operations)
@FEATURE: #[unstable(feature = "x86_amx_intrinsics", issue = "126622")] amx_transpose: "amx-transpose";
/// AMX-TRANSPOSE (Matrix Transpose Operations)
@FEATURE: #[unstable(feature = "apx_target_feature", issue = "139284")] apxf: "apxf";
/// APX-F (Advanced Performance Extensions - Foundation)
@FEATURE: #[unstable(feature = "avx10_target_feature", issue = "138843")] avx10_1: "avx10.1";

View file

@ -285,7 +285,6 @@ pub(crate) fn detect_features() -> cache::Initializer {
unsafe { __cpuid_count(0x1e_u32, 1) };
enable(amx_feature_flags_eax, 4, Feature::amx_fp8);
enable(amx_feature_flags_eax, 5, Feature::amx_transpose);
enable(amx_feature_flags_eax, 6, Feature::amx_tf32);
enable(amx_feature_flags_eax, 7, Feature::amx_avx512);
enable(amx_feature_flags_eax, 8, Feature::amx_movrs);

View file

@ -76,7 +76,6 @@ fn dump() {
println!("widekl: {:?}", is_x86_feature_detected!("widekl"));
println!("movrs: {:?}", is_x86_feature_detected!("movrs"));
println!("amx-fp8: {:?}", is_x86_feature_detected!("amx-fp8"));
println!("amx-transpose: {:?}", is_x86_feature_detected!("amx-transpose"));
println!("amx-tf32: {:?}", is_x86_feature_detected!("amx-tf32"));
println!("amx-avx512: {:?}", is_x86_feature_detected!("amx-avx512"));
println!("amx-movrs: {:?}", is_x86_feature_detected!("amx-movrs"));

View file

@ -11,3 +11,4 @@ CT_BINUTILS_V_2_32=y
CT_GLIBC_V_2_17=y
CT_GCC_V_8=y
CT_CC_LANG_CXX=y
CT_MUSL_V_1_2_5=y

View file

@ -12,3 +12,4 @@ CT_BINUTILS_EXTRA_CONFIG_ARRAY="--enable-compressed-debug-sections=none"
CT_GLIBC_V_2_17=y
CT_GCC_V_8=y
CT_CC_LANG_CXX=y
CT_MUSL_V_1_2_5=y

View file

@ -11,6 +11,6 @@ CT_ARCH_ARCH="powerpc64le"
CT_KERNEL_LINUX=y
CT_LINUX_V_4_19=y
CT_LIBC_MUSL=y
CT_MUSL_V_1_2_3=y
CT_MUSL_V_1_2_5=y
CT_CC_LANG_CXX=y
CT_GETTEXT_NEEDED=y

View file

@ -4,7 +4,7 @@
#
# Versions of the toolchain components are configurable in `musl-cross-make/Makefile` and
# musl unlike GLIBC is forward compatible so upgrading it shouldn't break old distributions.
# Right now we have: Binutils 2.31.1, GCC 9.2.0, musl 1.2.3.
# Right now we have: Binutils 2.31.1, GCC 9.2.0, musl 1.2.5.
# ignore-tidy-linelength
@ -45,11 +45,11 @@ export CFLAGS="-fPIC -g1 $CFLAGS"
git clone https://github.com/richfelker/musl-cross-make # -b v0.9.9
cd musl-cross-make
# A version that includes support for building musl 1.2.3
git checkout fe915821b652a7fa37b34a596f47d8e20bc72338
# A version that includes support for building musl 1.2.5
git checkout 3635262e4524c991552789af6f36211a335a77b3
hide_output make -j$(nproc) TARGET=$TARGET MUSL_VER=1.2.3 LINUX_HEADERS_SITE=$LINUX_HEADERS_SITE LINUX_VER=$LINUX_VER
hide_output make install TARGET=$TARGET MUSL_VER=1.2.3 LINUX_HEADERS_SITE=$LINUX_HEADERS_SITE LINUX_VER=$LINUX_VER OUTPUT=$OUTPUT
hide_output make -j$(nproc) TARGET=$TARGET MUSL_VER=1.2.5 LINUX_HEADERS_SITE=$LINUX_HEADERS_SITE LINUX_VER=$LINUX_VER
hide_output make install TARGET=$TARGET MUSL_VER=1.2.5 LINUX_HEADERS_SITE=$LINUX_HEADERS_SITE LINUX_VER=$LINUX_VER OUTPUT=$OUTPUT
cd -

View file

@ -25,7 +25,7 @@ shift
# Apparently applying `-fPIC` everywhere allows them to link successfully.
export CFLAGS="-fPIC $CFLAGS"
MUSL=musl-1.2.3
MUSL=musl-1.2.5
# may have been downloaded in a previous run
if [ ! -d $MUSL ]; then

View file

@ -89,7 +89,7 @@ so Rustup may install the documentation for a similar tier 1 target instead.
target | notes
-------|-------
[`aarch64-pc-windows-gnullvm`](platform-support/windows-gnullvm.md) | ARM64 MinGW (Windows 10+), LLVM ABI
[`aarch64-unknown-linux-musl`](platform-support/aarch64-unknown-linux-musl.md) | ARM64 Linux with musl 1.2.3
[`aarch64-unknown-linux-musl`](platform-support/aarch64-unknown-linux-musl.md) | ARM64 Linux with musl 1.2.5
[`aarch64-unknown-linux-ohos`](platform-support/openharmony.md) | ARM64 OpenHarmony
`arm-unknown-linux-gnueabi` | Armv6 Linux (kernel 3.2+, glibc 2.17)
`arm-unknown-linux-gnueabihf` | Armv6 Linux, hardfloat (kernel 3.2+, glibc 2.17)
@ -101,14 +101,14 @@ target | notes
`powerpc-unknown-linux-gnu` | PowerPC Linux (kernel 3.2+, glibc 2.17)
`powerpc64-unknown-linux-gnu` | PPC64 Linux (kernel 3.2+, glibc 2.17)
[`powerpc64le-unknown-linux-gnu`](platform-support/powerpc64le-unknown-linux-gnu.md) | PPC64LE Linux (kernel 3.10+, glibc 2.17)
[`powerpc64le-unknown-linux-musl`](platform-support/powerpc64le-unknown-linux-musl.md) | PPC64LE Linux (kernel 4.19+, musl 1.2.3)
[`powerpc64le-unknown-linux-musl`](platform-support/powerpc64le-unknown-linux-musl.md) | PPC64LE Linux (kernel 4.19+, musl 1.2.5)
[`riscv64gc-unknown-linux-gnu`](platform-support/riscv64gc-unknown-linux-gnu.md) | RISC-V Linux (kernel 4.20+, glibc 2.29)
[`s390x-unknown-linux-gnu`](platform-support/s390x-unknown-linux-gnu.md) | S390x Linux (kernel 3.2+, glibc 2.17)
[`x86_64-apple-darwin`](platform-support/apple-darwin.md) | 64-bit macOS (10.12+, Sierra+)
[`x86_64-pc-windows-gnullvm`](platform-support/windows-gnullvm.md) | 64-bit x86 MinGW (Windows 10+), LLVM ABI
[`x86_64-unknown-freebsd`](platform-support/freebsd.md) | 64-bit x86 FreeBSD
[`x86_64-unknown-illumos`](platform-support/illumos.md) | illumos
`x86_64-unknown-linux-musl` | 64-bit Linux with musl 1.2.3
`x86_64-unknown-linux-musl` | 64-bit Linux with musl 1.2.5
[`x86_64-unknown-linux-ohos`](platform-support/openharmony.md) | x86_64 OpenHarmony
[`x86_64-unknown-netbsd`](platform-support/netbsd.md) | NetBSD/amd64
[`x86_64-pc-solaris`](platform-support/solaris.md) | 64-bit x86 Solaris 11.4
@ -153,26 +153,26 @@ target | std | notes
[`aarch64-unknown-none-softfloat`](platform-support/aarch64-unknown-none.md) | * | Bare ARM64, softfloat
[`aarch64-unknown-uefi`](platform-support/unknown-uefi.md) | ? | ARM64 UEFI
[`arm-linux-androideabi`](platform-support/android.md) | ✓ | Armv6 Android
`arm-unknown-linux-musleabi` | ✓ | Armv6 Linux with musl 1.2.3
`arm-unknown-linux-musleabihf` | ✓ | Armv6 Linux with musl 1.2.3, hardfloat
`arm-unknown-linux-musleabi` | ✓ | Armv6 Linux with musl 1.2.5
`arm-unknown-linux-musleabihf` | ✓ | Armv6 Linux with musl 1.2.5, hardfloat
[`arm64ec-pc-windows-msvc`](platform-support/arm64ec-pc-windows-msvc.md) | ✓ | Arm64EC Windows MSVC
[`armv5te-unknown-linux-gnueabi`](platform-support/armv5te-unknown-linux-gnueabi.md) | ✓ | Armv5TE Linux (kernel 4.4+, glibc 2.23)
`armv5te-unknown-linux-musleabi` | ✓ | Armv5TE Linux with musl 1.2.3
`armv5te-unknown-linux-musleabi` | ✓ | Armv5TE Linux with musl 1.2.5
[`armv7-linux-androideabi`](platform-support/android.md) | ✓ | Armv7-A Android
[`armv7-unknown-linux-gnueabi`](platform-support/armv7-unknown-linux-gnueabi.md) | ✓ | Armv7-A Linux (kernel 4.15+, glibc 2.27)
`armv7-unknown-linux-musleabi` | ✓ | Armv7-A Linux with musl 1.2.3
`armv7-unknown-linux-musleabihf` | ✓ | Armv7-A Linux with musl 1.2.3, hardfloat
`armv7-unknown-linux-musleabi` | ✓ | Armv7-A Linux with musl 1.2.5
`armv7-unknown-linux-musleabihf` | ✓ | Armv7-A Linux with musl 1.2.5, hardfloat
[`armv7a-none-eabi`](platform-support/armv7a-none-eabi.md) | * | Bare Armv7-A
[`armv7a-none-eabihf`](platform-support/armv7a-none-eabi.md) | * | | Bare Armv7-A, hardfloat
[`armv7r-none-eabi`](platform-support/armv7r-none-eabi.md) | * | Bare Armv7-R
[`armv7r-none-eabihf`](platform-support/armv7r-none-eabi.md) | * | Bare Armv7-R, hardfloat
[`armv8r-none-eabihf`](platform-support/armv8r-none-eabihf.md) | * | | Bare Armv8-R, hardfloat
`i586-unknown-linux-gnu` | ✓ | 32-bit Linux (kernel 3.2+, glibc 2.17, original Pentium) [^x86_32-floats-x87]
`i586-unknown-linux-musl` | ✓ | 32-bit Linux (musl 1.2.3, original Pentium) [^x86_32-floats-x87]
`i586-unknown-linux-musl` | ✓ | 32-bit Linux (musl 1.2.5, original Pentium) [^x86_32-floats-x87]
[`i686-linux-android`](platform-support/android.md) | ✓ | 32-bit x86 Android ([Pentium 4 plus various extensions](https://developer.android.com/ndk/guides/abis.html#x86)) [^x86_32-floats-return-ABI]
[`i686-pc-windows-gnullvm`](platform-support/windows-gnullvm.md) | ✓ | 32-bit x86 MinGW (Windows 10+, Pentium 4), LLVM ABI [^x86_32-floats-return-ABI]
[`i686-unknown-freebsd`](platform-support/freebsd.md) | ✓ | 32-bit x86 FreeBSD (Pentium 4) [^x86_32-floats-return-ABI]
`i686-unknown-linux-musl` | ✓ | 32-bit Linux with musl 1.2.3 (Pentium 4) [^x86_32-floats-return-ABI]
`i686-unknown-linux-musl` | ✓ | 32-bit Linux with musl 1.2.5 (Pentium 4) [^x86_32-floats-return-ABI]
[`i686-unknown-uefi`](platform-support/unknown-uefi.md) | ? | 32-bit UEFI (Pentium 4, softfloat) [^win32-msvc-alignment]
[`loongarch64-unknown-none`](platform-support/loongarch-none.md) | * | LoongArch64 Bare-metal (LP64D ABI)
[`loongarch64-unknown-none-softfloat`](platform-support/loongarch-none.md) | * | LoongArch64 Bare-metal (LP64S ABI)
@ -182,7 +182,7 @@ target | std | notes
[`riscv32imac-unknown-none-elf`](platform-support/riscv32-unknown-none-elf.md) | * | Bare RISC-V (RV32IMAC ISA)
[`riscv32imafc-unknown-none-elf`](platform-support/riscv32-unknown-none-elf.md) | * | Bare RISC-V (RV32IMAFC ISA)
[`riscv32imc-unknown-none-elf`](platform-support/riscv32-unknown-none-elf.md) | * | Bare RISC-V (RV32IMC ISA)
[`riscv64gc-unknown-linux-musl`](platform-support/riscv64gc-unknown-linux-musl.md) | RISC-V Linux (kernel 4.20+, musl 1.2.3)
[`riscv64gc-unknown-linux-musl`](platform-support/riscv64gc-unknown-linux-musl.md) | RISC-V Linux (kernel 4.20+, musl 1.2.5)
`riscv64gc-unknown-none-elf` | * | Bare RISC-V (RV64IMAFDC ISA)
`riscv64imac-unknown-none-elf` | * | Bare RISC-V (RV64IMAC ISA)
`sparc64-unknown-linux-gnu` | ✓ | SPARC Linux (kernel 4.4+, glibc 2.23)
@ -313,7 +313,7 @@ target | std | host | notes
`bpfel-unknown-none` | * | | BPF (little endian)
`csky-unknown-linux-gnuabiv2` | ✓ | | C-SKY abiv2 Linux (little endian)
`csky-unknown-linux-gnuabiv2hf` | ✓ | | C-SKY abiv2 Linux, hardfloat (little endian)
[`hexagon-unknown-linux-musl`](platform-support/hexagon-unknown-linux-musl.md) | ✓ | | Hexagon Linux with musl 1.2.3
[`hexagon-unknown-linux-musl`](platform-support/hexagon-unknown-linux-musl.md) | ✓ | | Hexagon Linux with musl 1.2.5
[`hexagon-unknown-none-elf`](platform-support/hexagon-unknown-none-elf.md)| * | | Bare Hexagon (v60+, HVX)
[`i386-apple-ios`](platform-support/apple-ios.md) | ✓ | | 32-bit x86 iOS (Penryn) [^x86_32-floats-return-ABI]
[`i586-unknown-netbsd`](platform-support/netbsd.md) | ✓ | | 32-bit x86 (original Pentium) [^x86_32-floats-x87]
@ -336,17 +336,17 @@ target | std | host | notes
[`m68k-unknown-linux-gnu`](platform-support/m68k-unknown-linux-gnu.md) | ? | | Motorola 680x0 Linux
[`m68k-unknown-none-elf`](platform-support/m68k-unknown-none-elf.md) | | | Motorola 680x0
`mips-unknown-linux-gnu` | ✓ | ✓ | MIPS Linux (kernel 4.4, glibc 2.23)
`mips-unknown-linux-musl` | ✓ | | MIPS Linux with musl 1.2.3
`mips-unknown-linux-musl` | ✓ | | MIPS Linux with musl 1.2.5
`mips-unknown-linux-uclibc` | ✓ | | MIPS Linux with uClibc
[`mips64-openwrt-linux-musl`](platform-support/mips64-openwrt-linux-musl.md) | ? | | MIPS64 for OpenWrt Linux musl 1.2.3
[`mips64-openwrt-linux-musl`](platform-support/mips64-openwrt-linux-musl.md) | ? | | MIPS64 for OpenWrt Linux musl 1.2.5
`mips64-unknown-linux-gnuabi64` | ✓ | ✓ | MIPS64 Linux, N64 ABI (kernel 4.4, glibc 2.23)
[`mips64-unknown-linux-muslabi64`](platform-support/mips64-unknown-linux-muslabi64.md) | ✓ | ✓ | MIPS64 Linux, N64 ABI, musl 1.2.3
[`mips64-unknown-linux-muslabi64`](platform-support/mips64-unknown-linux-muslabi64.md) | ✓ | ✓ | MIPS64 Linux, N64 ABI, musl 1.2.5
`mips64el-unknown-linux-gnuabi64` | ✓ | ✓ | MIPS64 (little endian) Linux, N64 ABI (kernel 4.4, glibc 2.23)
`mips64el-unknown-linux-muslabi64` | ✓ | | MIPS64 (little endian) Linux, N64 ABI, musl 1.2.3
`mips64el-unknown-linux-muslabi64` | ✓ | | MIPS64 (little endian) Linux, N64 ABI, musl 1.2.5
`mipsel-sony-psp` | * | | MIPS (LE) Sony PlayStation Portable (PSP)
[`mipsel-sony-psx`](platform-support/mipsel-sony-psx.md) | * | | MIPS (LE) Sony PlayStation 1 (PSX)
[`mipsel-unknown-linux-gnu`](platform-support/mipsel-unknown-linux-gnu.md) | ✓ | ✓ | MIPS (little endian) Linux (kernel 4.4, glibc 2.23)
`mipsel-unknown-linux-musl` | ✓ | | MIPS (little endian) Linux with musl 1.2.3
`mipsel-unknown-linux-musl` | ✓ | | MIPS (little endian) Linux with musl 1.2.5
`mipsel-unknown-linux-uclibc` | ✓ | | MIPS (LE) Linux with uClibc
[`mipsel-unknown-netbsd`](platform-support/netbsd.md) | ✓ | ✓ | 32-bit MIPS (LE), requires mips32 cpu support
`mipsel-unknown-none` | * | | Bare MIPS (LE) softfloat
@ -360,15 +360,15 @@ target | std | host | notes
[`powerpc-unknown-freebsd`](platform-support/freebsd.md) | ? | | PowerPC FreeBSD
[`powerpc-unknown-helenos`](platform-support/helenos.md) | ✓ | | PowerPC HelenOS
[`powerpc-unknown-linux-gnuspe`](platform-support/powerpc-unknown-linux-gnuspe.md) | ✓ | | PowerPC SPE Linux
`powerpc-unknown-linux-musl` | ? | | PowerPC Linux with musl 1.2.3
[`powerpc-unknown-linux-muslspe`](platform-support/powerpc-unknown-linux-muslspe.md) | ? | | PowerPC SPE Linux with musl 1.2.3
`powerpc-unknown-linux-musl` | ? | | PowerPC Linux with musl 1.2.5
[`powerpc-unknown-linux-muslspe`](platform-support/powerpc-unknown-linux-muslspe.md) | ? | | PowerPC SPE Linux with musl 1.2.5
[`powerpc-unknown-netbsd`](platform-support/netbsd.md) | ✓ | ✓ | NetBSD 32-bit powerpc systems
[`powerpc-unknown-openbsd`](platform-support/powerpc-unknown-openbsd.md) | * | |
[`powerpc-wrs-vxworks`](platform-support/vxworks.md) | ✓ | |
[`powerpc-wrs-vxworks-spe`](platform-support/vxworks.md) | ✓ | |
[`powerpc64-ibm-aix`](platform-support/aix.md) | ? | | 64-bit AIX (7.2 and newer)
[`powerpc64-unknown-freebsd`](platform-support/freebsd.md) | ✓ | ✓ | PPC64 FreeBSD (ELFv2)
[`powerpc64-unknown-linux-musl`](platform-support/powerpc64-unknown-linux-musl.md) | ✓ | ✓ | PPC64 Linux (kernel 4.19, musl 1.2.3)
[`powerpc64-unknown-linux-musl`](platform-support/powerpc64-unknown-linux-musl.md) | ✓ | ✓ | PPC64 Linux (kernel 4.19, musl 1.2.5)
[`powerpc64-unknown-openbsd`](platform-support/openbsd.md) | ✓ | ✓ | OpenBSD/powerpc64
[`powerpc64-wrs-vxworks`](platform-support/vxworks.md) | ✓ | |
[`powerpc64le-unknown-freebsd`](platform-support/freebsd.md) | ✓ | ✓ | PPC64LE FreeBSD
@ -377,7 +377,7 @@ target | std | host | notes
[`riscv32em-unknown-none-elf`](platform-support/riscv32e-unknown-none-elf.md) | * | | Bare RISC-V (RV32EM ISA)
[`riscv32emc-unknown-none-elf`](platform-support/riscv32e-unknown-none-elf.md) | * | | Bare RISC-V (RV32EMC ISA)
`riscv32gc-unknown-linux-gnu` | ✓ | | RISC-V Linux (kernel 5.4, glibc 2.33)
`riscv32gc-unknown-linux-musl` | ? | | RISC-V Linux (kernel 5.4, musl 1.2.3 + RISCV32 support patches)
`riscv32gc-unknown-linux-musl` | ? | | RISC-V Linux (kernel 5.4, musl 1.2.5)
[`riscv32im-risc0-zkvm-elf`](platform-support/riscv32im-risc0-zkvm-elf.md) | ? | | RISC Zero's zero-knowledge Virtual Machine (RV32IM ISA)
[`riscv32ima-unknown-none-elf`](platform-support/riscv32-unknown-none-elf.md) | * | | Bare RISC-V (RV32IMA ISA)
[`riscv32imac-esp-espidf`](platform-support/esp-idf.md) | ✓ | | RISC-V ESP-IDF
@ -399,7 +399,7 @@ target | std | host | notes
[`riscv64gc-unknown-redox`](platform-support/redox.md) | ✓ | | RISC-V 64bit Redox OS
[`riscv64imac-unknown-nuttx-elf`](platform-support/nuttx.md) | ✓ | | RISC-V 64bit with NuttX
[`riscv64a23-unknown-linux-gnu`](platform-support/riscv64a23-unknown-linux-gnu.md) | ✓ | ✓ | RISC-V Linux (kernel 6.8.0+, glibc 2.39)
[`s390x-unknown-linux-musl`](platform-support/s390x-unknown-linux-musl.md) | ✓ | | S390x Linux (kernel 3.2, musl 1.2.3)
[`s390x-unknown-linux-musl`](platform-support/s390x-unknown-linux-musl.md) | ✓ | | S390x Linux (kernel 3.2, musl 1.2.5)
`sparc-unknown-linux-gnu` | ✓ | | 32-bit SPARC Linux
[`sparc-unknown-none-elf`](./platform-support/sparc-unknown-none-elf.md) | * | | Bare 32-bit SPARC V7+
[`sparc64-unknown-helenos`](platform-support/helenos.md) | ✓ | | sparc64 HelenOS
@ -415,7 +415,7 @@ target | std | host | notes
[`thumbv7em-nuttx-eabi`](platform-support/nuttx.md) | ✓ | | ARMv7EM with NuttX
[`thumbv7em-nuttx-eabihf`](platform-support/nuttx.md) | ✓ | | ARMv7EM with NuttX, hardfloat
[`thumbv7m-nuttx-eabi`](platform-support/nuttx.md) | ✓ | | ARMv7M with NuttX
`thumbv7neon-unknown-linux-musleabihf` | ? | | Thumb2-mode Armv7-A Linux with NEON, musl 1.2.3
`thumbv7neon-unknown-linux-musleabihf` | ? | | Thumb2-mode Armv7-A Linux with NEON, musl 1.2.5
[`thumbv8m.base-nuttx-eabi`](platform-support/nuttx.md) | ✓ | | ARMv8M Baseline with NuttX
[`thumbv8m.main-nuttx-eabi`](platform-support/nuttx.md) | ✓ | | ARMv8M Mainline with NuttX
[`thumbv8m.main-nuttx-eabihf`](platform-support/nuttx.md) | ✓ | | ARMv8M Mainline with NuttX, hardfloat
@ -429,7 +429,7 @@ target | std | host | notes
[`x86_64-pc-nto-qnx710`](platform-support/nto-qnx.md) | ✓ | | x86 64-bit QNX Neutrino 7.1 RTOS with default network stack (io-pkt) |
[`x86_64-pc-nto-qnx710_iosock`](platform-support/nto-qnx.md) | ✓ | | x86 64-bit QNX Neutrino 7.1 RTOS with new network stack (io-sock) |
[`x86_64-pc-nto-qnx800`](platform-support/nto-qnx.md) | ✓ | | x86 64-bit QNX Neutrino 8.0 RTOS |
[`x86_64-unikraft-linux-musl`](platform-support/unikraft-linux-musl.md) | ✓ | | 64-bit Unikraft with musl 1.2.3
[`x86_64-unikraft-linux-musl`](platform-support/unikraft-linux-musl.md) | ✓ | | 64-bit Unikraft with musl 1.2.5
`x86_64-unknown-dragonfly` | ✓ | ✓ | 64-bit DragonFlyBSD
`x86_64-unknown-haiku` | ✓ | ✓ | 64-bit Haiku
[`x86_64-unknown-hermit`](platform-support/hermit.md) | ✓ | | x86_64 Hermit

View file

@ -13,7 +13,7 @@ IBM z/Architecture (s390x) targets (including IBM Z and LinuxONE) running Linux.
This target requires:
* Linux Kernel version 3.2 or later
* musl 1.2.3 or later
* musl 1.2.5 or later
Code generated by the target uses the z/Architecture ISA assuming a minimum
architecture level of z10 (Eighth Edition of the z/Architecture Principles

View file

@ -343,7 +343,7 @@ struct TokenHandler<'a, 'tcx, F: Write> {
/// We need to keep the `Class` for each element because it could contain a `Span` which is
/// used to generate links.
href_context: Option<HrefContext<'a, 'tcx>>,
write_line_number: fn(u32) -> String,
line_number_kind: LineNumberKind,
line: u32,
max_lines: u32,
}
@ -355,10 +355,10 @@ impl<F: Write> std::fmt::Debug for TokenHandler<'_, '_, F> {
}
impl<'a, F: Write> TokenHandler<'a, '_, F> {
fn handle_backline(&mut self) -> Option<String> {
fn handle_backline(&mut self) -> Option<impl Display + use<F>> {
self.line += 1;
if self.line < self.max_lines {
return Some((self.write_line_number)(self.line));
return Some(self.line_number_kind.render(self.line));
}
None
}
@ -376,8 +376,7 @@ impl<'a, F: Write> TokenHandler<'a, '_, F> {
if text == "\n"
&& let Some(backline) = self.handle_backline()
{
self.out.write_str(&text).unwrap();
self.out.write_str(&backline).unwrap();
write!(self.out, "{text}{backline}").unwrap();
} else {
self.push_token_without_backline_check(class, text, true);
}
@ -437,20 +436,29 @@ impl<F: Write> Drop for TokenHandler<'_, '_, F> {
}
}
fn scraped_line_number(line: u32) -> String {
// https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#data-nosnippet-attr
// Do not show "1 2 3 4 5 ..." in web search results.
format!("<span data-nosnippet>{line}</span>")
/// Represents the type of line number to be generated as HTML.
#[derive(Clone, Copy)]
enum LineNumberKind {
/// Used for scraped code examples.
Scraped,
/// Used for source code pages.
Normal,
/// Code examples in documentation don't have line number generated by rustdoc.
Empty,
}
fn line_number(line: u32) -> String {
// https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#data-nosnippet-attr
// Do not show "1 2 3 4 5 ..." in web search results.
format!("<a href=#{line} id={line} data-nosnippet>{line}</a>")
}
fn empty_line_number(_: u32) -> String {
String::new()
impl LineNumberKind {
fn render(self, line: u32) -> impl Display {
fmt::from_fn(move |f| {
match self {
// https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#data-nosnippet-attr
// Do not show "1 2 3 4 5 ..." in web search results.
Self::Scraped => write!(f, "<span data-nosnippet>{line}</span>"),
Self::Normal => write!(f, "<a href=#{line} id={line} data-nosnippet>{line}</a>"),
Self::Empty => Ok(()),
}
})
}
}
fn get_next_expansion(
@ -534,15 +542,15 @@ pub(super) fn write_code(
let mut token_handler = TokenHandler {
out,
href_context,
write_line_number: match line_info {
line_number_kind: match line_info {
Some(line_info) => {
if line_info.is_scraped_example {
scraped_line_number
LineNumberKind::Scraped
} else {
line_number
LineNumberKind::Normal
}
}
None => empty_line_number,
None => LineNumberKind::Empty,
},
line: 0,
max_lines: u32::MAX,
@ -552,8 +560,12 @@ pub(super) fn write_code(
if let Some(line_info) = line_info {
token_handler.line = line_info.start_line - 1;
token_handler.max_lines = line_info.max_lines;
if let Some(text) = token_handler.handle_backline() {
token_handler.push_token_without_backline_check(None, Cow::Owned(text), false);
if let Some(backline) = token_handler.handle_backline() {
token_handler.push_token_without_backline_check(
None,
Cow::Owned(backline.to_string()),
false,
);
}
}

View file

@ -3906,6 +3906,8 @@ class DocSearch {
return name === "traitalias";
case "macro":
return name === "attr" || name === "derive";
case "import":
return name === "externcrate";
}
// No match

View file

@ -8,6 +8,89 @@ document.
[e9b7045...master](https://github.com/rust-lang/rust-clippy/compare/e9b7045...master)
## Rust 1.91
Current stable, released 2025-10-30
[View all 146 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2025-07-25T21%3A05%3A11Z..2025-09-04T22%3A34%3A27Z+base%3Amaster)
### New Lints
* Added [`possible_missing_else`] to `suspicious`
[#15317](https://github.com/rust-lang/rust-clippy/pull/15317)
### Moves and Deprecations
* Moved [`cognitive_complexity`] from `nursery` to `restriction`
[#15415](https://github.com/rust-lang/rust-clippy/pull/15415)
* Moved [`declare_interior_mutable_const`] from `style` to `suspicious`
[#15454](https://github.com/rust-lang/rust-clippy/pull/15454)
* Moved [`crosspointer_transmute`] from `complexity` to `suspicious`
[#15403](https://github.com/rust-lang/rust-clippy/pull/15403)
### Enhancements
* [`excessive_precision`] added `const_literal_digits_threshold` option to suppress overly precise constants.
[#15193](https://github.com/rust-lang/rust-clippy/pull/15193)
* [`unwrap_in_result`] rewritten for better accuracy; now lints on `.unwrap()` and `.expect()`
directly and no longer mixes `Result` and `Option`.
[#15445](https://github.com/rust-lang/rust-clippy/pull/15445)
* [`panic`] now works in `const` contexts.
[#15565](https://github.com/rust-lang/rust-clippy/pull/15565)
* [`implicit_clone`] now also lints `to_string` calls (merging [`string_to_string`] behavior).
[#14177](https://github.com/rust-lang/rust-clippy/pull/14177)
* [`collapsible_match`] improved suggestions to handle necessary ref/dereferencing.
[#14221](https://github.com/rust-lang/rust-clippy/pull/14221)
* [`map_identity`] now suggests making variables mutable when required; recognizes tuple struct restructuring.
[#15261](https://github.com/rust-lang/rust-clippy/pull/15261)
* [`option_map_unit_fn`] preserves `unsafe` blocks in suggestions.
[#15570](https://github.com/rust-lang/rust-clippy/pull/15570)
* [`unnecessary_mut_passed`] provides structured, clearer fix suggestions.
[#15438](https://github.com/rust-lang/rust-clippy/pull/15438)
* [`float_equality_without_abs`] now checks `f16` and `f128` types.
[#15054](https://github.com/rust-lang/rust-clippy/pull/15054)
* [`doc_markdown`] expanded whitelist (`InfiniBand`, `RoCE`, `PowerPC`) and improved handling of
identifiers like NixOS.
[#15558](https://github.com/rust-lang/rust-clippy/pull/15558)
* [`clone_on_ref_ptr`] now suggests fully qualified paths to avoid resolution errors.
[#15561](https://github.com/rust-lang/rust-clippy/pull/15561)
* [`manual_assert`] simplifies boolean expressions in suggested fixes.
[#15368](https://github.com/rust-lang/rust-clippy/pull/15368)
* [`four_forward_slashes`] warns about bare CR in comments and avoids invalid autofixes.
[#15175](https://github.com/rust-lang/rust-clippy/pull/15175)
### False Positive Fixes
* [`alloc_instead_of_core`] fixed FP when `alloc` is an alias
[#15581](https://github.com/rust-lang/rust-clippy/pull/15581)
* [`needless_range_loop`] fixed FP and FN when meeting multidimensional array
[#15486](https://github.com/rust-lang/rust-clippy/pull/15486)
* [`semicolon_inside_block`] fixed FP when attribute over expr is not enabled
[#15476](https://github.com/rust-lang/rust-clippy/pull/15476)
* [`unnested_or_patterns`] fixed FP on structs with only shorthand field patterns
[#15343](https://github.com/rust-lang/rust-clippy/pull/15343)
* [`match_ref_pats`] fixed FP on match scrutinee of never type
[#15474](https://github.com/rust-lang/rust-clippy/pull/15474)
* [`infinite_loop`] fixed FP in async blocks that are not awaited
[#15157](https://github.com/rust-lang/rust-clippy/pull/15157)
* [`iter_on_single_items`] fixed FP on function pointers and let statements
[#15013](https://github.com/rust-lang/rust-clippy/pull/15013)
### ICE Fixes
* [`len_zero`] fix ICE when fn len has a return type without generic type params
[#15660](https://github.com/rust-lang/rust-clippy/pull/15660)
### Documentation Improvements
* [`cognitive_complexity`] corrected documentation to state lint is in `restriction`, not `nursery`
[#15563](https://github.com/rust-lang/rust-clippy/pull/15563)
### Performance Improvements
* [`doc_broken_link`] optimized by 99.77% (12M → 27k instructions)
[#15385](https://github.com/rust-lang/rust-clippy/pull/15385)
## Rust 1.90
Current stable, released 2025-09-18
@ -6253,6 +6336,7 @@ Released 2018-09-13
[`empty_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_drop
[`empty_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enum
[`empty_enum_variants_with_brackets`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enum_variants_with_brackets
[`empty_enums`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enums
[`empty_line_after_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_line_after_doc_comments
[`empty_line_after_outer_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_line_after_outer_attr
[`empty_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_loop
@ -6583,6 +6667,7 @@ Released 2018-09-13
[`needless_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_else
[`needless_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_for_each
[`needless_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_if
[`needless_ifs`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_ifs
[`needless_late_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_late_init
[`needless_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes
[`needless_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_match

View file

@ -1,3 +1,3 @@
# The Rust Code of Conduct
The Code of Conduct for this repository [can be found online](https://www.rust-lang.org/conduct.html).
The Code of Conduct for this repository [can be found online](https://rust-lang.org/policies/code-of-conduct/).

View file

@ -1,6 +1,6 @@
[package]
name = "clippy"
version = "0.1.92"
version = "0.1.93"
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy"
readme = "README.md"

View file

@ -1,8 +1,6 @@
[book]
authors = ["The Rust Clippy Developers"]
language = "en"
multilingual = false
src = "src"
title = "Clippy Documentation"
[rust]

View file

@ -21,14 +21,13 @@ use clippy_utils::sym;
impl<'tcx> LateLintPass<'tcx> for OurFancyMethodLint {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
// Check our expr is calling a method with pattern matching
if let hir::ExprKind::MethodCall(path, _, [self_arg, ..], _) = &expr.kind
if let hir::ExprKind::MethodCall(path, _, _, _) = &expr.kind
// Check if the name of this method is `our_fancy_method`
&& path.ident.name == sym::our_fancy_method
// We can check the type of the self argument whenever necessary.
// (It's necessary if we want to check that method is specifically belonging to a specific trait,
// for example, a `map` method could belong to user-defined trait instead of to `Iterator`)
// Check if the method belongs to the `sym::OurFancyTrait` trait.
// (for example, a `map` method could belong to user-defined trait instead of to `Iterator`)
// See the next section for more information.
&& cx.ty_based_def(self_arg).opt_parent(cx).is_diag_item(cx, sym::OurFancyTrait)
&& cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::OurFancyTrait)
{
println!("`expr` is a method call for `our_fancy_method`");
}
@ -45,6 +44,12 @@ New symbols such as `our_fancy_method` need to be added to the `clippy_utils::sy
This module extends the list of symbols already provided by the compiler crates
in `rustc_span::sym`.
If a trait defines only one method (such as the `std::ops::Deref` trait, which only has the `deref()` method),
one might be tempted to omit the method name check. This would work, but is not always advisable because:
- If a new method (possibly with a default implementation) were to be added to the trait, there would be a risk of
matching the wrong method.
- Comparing symbols is very cheap and might prevent a more expensive lookup.
## Checking if a `impl` block implements a method
While sometimes we want to check whether a method is being called or not, other

View file

@ -859,6 +859,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
* [`io_other_error`](https://rust-lang.github.io/rust-clippy/master/index.html#io_other_error)
* [`iter_kv_map`](https://rust-lang.github.io/rust-clippy/master/index.html#iter_kv_map)
* [`legacy_numeric_constants`](https://rust-lang.github.io/rust-clippy/master/index.html#legacy_numeric_constants)
* [`len_zero`](https://rust-lang.github.io/rust-clippy/master/index.html#len_zero)
* [`lines_filter_map_ok`](https://rust-lang.github.io/rust-clippy/master/index.html#lines_filter_map_ok)
* [`manual_abs_diff`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_abs_diff)
* [`manual_bits`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits)

View file

@ -1,6 +1,6 @@
[package]
name = "clippy_config"
version = "0.1.92"
version = "0.1.93"
edition = "2024"
publish = false

View file

@ -748,6 +748,7 @@ define_Conf! {
io_other_error,
iter_kv_map,
legacy_numeric_constants,
len_zero,
lines_filter_map_ok,
manual_abs_diff,
manual_bits,

View file

@ -1,4 +1,5 @@
use crate::update_lints::{DeprecatedLint, Lint, find_lint_decls, generate_lint_files, read_deprecated_lints};
use crate::parse::{DeprecatedLint, Lint, ParseCx};
use crate::update_lints::generate_lint_files;
use crate::utils::{UpdateMode, Version};
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
@ -13,21 +14,20 @@ use std::{fs, io};
/// # Panics
///
/// If a file path could not read from or written to
pub fn deprecate(clippy_version: Version, name: &str, reason: &str) {
if let Some((prefix, _)) = name.split_once("::") {
panic!("`{name}` should not contain the `{prefix}` prefix");
}
let mut lints = find_lint_decls();
let (mut deprecated_lints, renamed_lints) = read_deprecated_lints();
pub fn deprecate<'cx>(cx: ParseCx<'cx>, clippy_version: Version, name: &'cx str, reason: &'cx str) {
let mut lints = cx.find_lint_decls();
let (mut deprecated_lints, renamed_lints) = cx.read_deprecated_lints();
let Some(lint) = lints.iter().find(|l| l.name == name) else {
eprintln!("error: failed to find lint `{name}`");
return;
};
let prefixed_name = String::from_iter(["clippy::", name]);
match deprecated_lints.binary_search_by(|x| x.name.cmp(&prefixed_name)) {
let prefixed_name = cx.str_buf.with(|buf| {
buf.extend(["clippy::", name]);
cx.arena.alloc_str(buf)
});
match deprecated_lints.binary_search_by(|x| x.name.cmp(prefixed_name)) {
Ok(_) => {
println!("`{name}` is already deprecated");
return;
@ -36,8 +36,8 @@ pub fn deprecate(clippy_version: Version, name: &str, reason: &str) {
idx,
DeprecatedLint {
name: prefixed_name,
reason: reason.into(),
version: clippy_version.rust_display().to_string(),
reason,
version: cx.str_buf.alloc_display(cx.arena, clippy_version.rust_display()),
},
),
}
@ -61,8 +61,8 @@ pub fn deprecate(clippy_version: Version, name: &str, reason: &str) {
}
}
fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint>) -> io::Result<bool> {
fn remove_lint(name: &str, lints: &mut Vec<Lint>) {
fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint<'_>>) -> io::Result<bool> {
fn remove_lint(name: &str, lints: &mut Vec<Lint<'_>>) {
lints.iter().position(|l| l.name == name).map(|pos| lints.remove(pos));
}
@ -135,14 +135,14 @@ fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint>) -> io
);
assert!(
content[lint.declaration_range.clone()].contains(&name.to_uppercase()),
content[lint.declaration_range].contains(&name.to_uppercase()),
"error: `{}` does not contain lint `{}`'s declaration",
path.display(),
lint.name
);
// Remove lint declaration (declare_clippy_lint!)
content.replace_range(lint.declaration_range.clone(), "");
content.replace_range(lint.declaration_range, "");
// Remove the module declaration (mod xyz;)
let mod_decl = format!("\nmod {name};");

View file

@ -268,7 +268,7 @@ fn run_rustfmt(update_mode: UpdateMode) {
.expect("invalid rustfmt path");
rustfmt_path.truncate(rustfmt_path.trim_end().len());
let args: Vec<_> = walk_dir_no_dot_or_target()
let args: Vec<_> = walk_dir_no_dot_or_target(".")
.filter_map(|e| {
let e = expect_action(e, ErrAction::Read, ".");
e.path()

View file

@ -1,9 +1,12 @@
#![feature(
rustc_private,
exit_status_error,
if_let_guard,
new_range,
new_range_api,
os_str_slice,
os_string_truncate,
pattern,
rustc_private,
slice_split_once
)]
#![warn(
@ -15,6 +18,7 @@
)]
#![allow(clippy::missing_panics_doc)]
extern crate rustc_arena;
#[expect(unused_extern_crates, reason = "required to link to rustc crates")]
extern crate rustc_driver;
extern crate rustc_lexer;
@ -32,5 +36,8 @@ pub mod setup;
pub mod sync;
pub mod update_lints;
mod parse;
mod utils;
pub use utils::{ClippyInfo, UpdateMode};
pub use self::parse::{ParseCx, new_parse_cx};
pub use self::utils::{ClippyInfo, UpdateMode};

View file

@ -4,10 +4,9 @@
use clap::{Args, Parser, Subcommand};
use clippy_dev::{
ClippyInfo, UpdateMode, deprecate_lint, dogfood, fmt, lint, new_lint, release, rename_lint, serve, setup, sync,
update_lints,
ClippyInfo, UpdateMode, deprecate_lint, dogfood, fmt, lint, new_lint, new_parse_cx, release, rename_lint, serve,
setup, sync, update_lints,
};
use std::convert::Infallible;
use std::env;
fn main() {
@ -28,7 +27,7 @@ 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 } => update_lints::update(UpdateMode::from_check(check)),
DevCommand::UpdateLints { check } => new_parse_cx(|cx| update_lints::update(cx, UpdateMode::from_check(check))),
DevCommand::NewLint {
pass,
name,
@ -36,7 +35,7 @@ fn main() {
r#type,
msrv,
} => match new_lint::create(clippy.version, pass, &name, &category, r#type.as_deref(), msrv) {
Ok(()) => update_lints::update(UpdateMode::Change),
Ok(()) => new_parse_cx(|cx| update_lints::update(cx, UpdateMode::Change)),
Err(e) => eprintln!("Unable to create lint: {e}"),
},
DevCommand::Setup(SetupCommand { subcommand }) => match subcommand {
@ -79,13 +78,18 @@ fn main() {
old_name,
new_name,
uplift,
} => rename_lint::rename(
clippy.version,
&old_name,
new_name.as_ref().unwrap_or(&old_name),
uplift,
),
DevCommand::Deprecate { name, reason } => deprecate_lint::deprecate(clippy.version, &name, &reason),
} => new_parse_cx(|cx| {
rename_lint::rename(
cx,
clippy.version,
&old_name,
new_name.as_ref().unwrap_or(&old_name),
uplift,
);
}),
DevCommand::Deprecate { name, reason } => {
new_parse_cx(|cx| deprecate_lint::deprecate(cx, clippy.version, &name, &reason));
},
DevCommand::Sync(SyncCommand { subcommand }) => match subcommand {
SyncSubcommand::UpdateNightly => sync::update_nightly(),
},
@ -95,6 +99,20 @@ fn main() {
}
}
fn lint_name(name: &str) -> Result<String, String> {
let name = name.replace('-', "_");
if let Some((pre, _)) = name.split_once("::") {
Err(format!("lint name should not contain the `{pre}` prefix"))
} else if name
.bytes()
.any(|x| !matches!(x, b'_' | b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z'))
{
Err("lint name contains invalid characters".to_owned())
} else {
Ok(name)
}
}
#[derive(Parser)]
#[command(name = "dev", about)]
struct Dev {
@ -150,7 +168,7 @@ enum DevCommand {
#[arg(
short,
long,
value_parser = |name: &str| Ok::<_, Infallible>(name.replace('-', "_")),
value_parser = lint_name,
)]
/// Name of the new lint in snake case, ex: `fn_too_long`
name: String,
@ -223,8 +241,12 @@ enum DevCommand {
/// Rename a lint
RenameLint {
/// The name of the lint to rename
#[arg(value_parser = lint_name)]
old_name: String,
#[arg(required_unless_present = "uplift")]
#[arg(
required_unless_present = "uplift",
value_parser = lint_name,
)]
/// The new name of the lint
new_name: Option<String>,
#[arg(long)]
@ -234,6 +256,7 @@ enum DevCommand {
/// Deprecate the given lint
Deprecate {
/// The name of the lint to deprecate
#[arg(value_parser = lint_name)]
name: String,
#[arg(long, short)]
/// The reason for deprecation

View file

@ -1,4 +1,5 @@
use crate::utils::{RustSearcher, Token, Version};
use crate::parse::cursor::{self, Capture, Cursor};
use crate::utils::Version;
use clap::ValueEnum;
use indoc::{formatdoc, writedoc};
use std::fmt::{self, Write as _};
@ -516,22 +517,22 @@ fn setup_mod_file(path: &Path, lint: &LintData<'_>) -> io::Result<&'static str>
// Find both the last lint declaration (declare_clippy_lint!) and the lint pass impl
fn parse_mod_file(path: &Path, contents: &str) -> (&'static str, usize) {
#[allow(clippy::enum_glob_use)]
use Token::*;
use cursor::Pat::*;
let mut context = None;
let mut decl_end = None;
let mut searcher = RustSearcher::new(contents);
while let Some(name) = searcher.find_capture_token(CaptureIdent) {
match name {
let mut cursor = Cursor::new(contents);
let mut captures = [Capture::EMPTY];
while let Some(name) = cursor.find_any_ident() {
match cursor.get_text(name) {
"declare_clippy_lint" => {
if searcher.match_tokens(&[Bang, OpenBrace], &mut []) && searcher.find_token(CloseBrace) {
decl_end = Some(searcher.pos());
if cursor.match_all(&[Bang, OpenBrace], &mut []) && cursor.find_pat(CloseBrace) {
decl_end = Some(cursor.pos());
}
},
"impl" => {
let mut capture = "";
if searcher.match_tokens(&[Lt, Lifetime, Gt, CaptureIdent], &mut [&mut capture]) {
match capture {
if cursor.match_all(&[Lt, Lifetime, Gt, CaptureIdent], &mut captures) {
match cursor.get_text(captures[0]) {
"LateLintPass" => context = Some("LateContext"),
"EarlyLintPass" => context = Some("EarlyContext"),
_ => {},

View file

@ -0,0 +1,285 @@
pub mod cursor;
use self::cursor::{Capture, Cursor};
use crate::utils::{ErrAction, File, Scoped, expect_action, walk_dir_no_dot_or_target};
use core::fmt::{Display, Write as _};
use core::range::Range;
use rustc_arena::DroplessArena;
use std::fs;
use std::path::{self, Path, PathBuf};
use std::str::pattern::Pattern;
pub struct ParseCxImpl<'cx> {
pub arena: &'cx DroplessArena,
pub str_buf: StrBuf,
}
pub type ParseCx<'cx> = &'cx mut ParseCxImpl<'cx>;
/// Calls the given function inside a newly created parsing context.
pub fn new_parse_cx<'env, T>(f: impl for<'cx> FnOnce(&'cx mut Scoped<'cx, 'env, ParseCxImpl<'cx>>) -> T) -> T {
let arena = DroplessArena::default();
f(&mut Scoped::new(ParseCxImpl {
arena: &arena,
str_buf: StrBuf::with_capacity(128),
}))
}
/// A string used as a temporary buffer used to avoid allocating for short lived strings.
pub struct StrBuf(String);
impl StrBuf {
/// Creates a new buffer with the specified initial capacity.
pub fn with_capacity(cap: usize) -> Self {
Self(String::with_capacity(cap))
}
/// Allocates the result of formatting the given value onto the arena.
pub fn alloc_display<'cx>(&mut self, arena: &'cx DroplessArena, value: impl Display) -> &'cx str {
self.0.clear();
write!(self.0, "{value}").expect("`Display` impl returned an error");
arena.alloc_str(&self.0)
}
/// Allocates the string onto the arena with all ascii characters converted to
/// lowercase.
pub fn alloc_ascii_lower<'cx>(&mut self, arena: &'cx DroplessArena, s: &str) -> &'cx str {
self.0.clear();
self.0.push_str(s);
self.0.make_ascii_lowercase();
arena.alloc_str(&self.0)
}
/// Allocates the result of replacing all instances the pattern with the given string
/// onto the arena.
pub fn alloc_replaced<'cx>(
&mut self,
arena: &'cx DroplessArena,
s: &str,
pat: impl Pattern,
replacement: &str,
) -> &'cx str {
let mut parts = s.split(pat);
let Some(first) = parts.next() else {
return "";
};
self.0.clear();
self.0.push_str(first);
for part in parts {
self.0.push_str(replacement);
self.0.push_str(part);
}
if self.0.is_empty() {
""
} else {
arena.alloc_str(&self.0)
}
}
/// Performs an operation with the freshly cleared buffer.
pub fn with<T>(&mut self, f: impl FnOnce(&mut String) -> T) -> T {
self.0.clear();
f(&mut self.0)
}
}
pub struct Lint<'cx> {
pub name: &'cx str,
pub group: &'cx str,
pub module: &'cx str,
pub path: PathBuf,
pub declaration_range: Range<usize>,
}
pub struct DeprecatedLint<'cx> {
pub name: &'cx str,
pub reason: &'cx str,
pub version: &'cx str,
}
pub struct RenamedLint<'cx> {
pub old_name: &'cx str,
pub new_name: &'cx str,
pub version: &'cx str,
}
impl<'cx> ParseCxImpl<'cx> {
/// Finds all lint declarations (`declare_clippy_lint!`)
#[must_use]
pub fn find_lint_decls(&mut self) -> Vec<Lint<'cx>> {
let mut lints = Vec::with_capacity(1000);
let mut contents = String::new();
for e in expect_action(fs::read_dir("."), ErrAction::Read, ".") {
let e = expect_action(e, ErrAction::Read, ".");
// Skip if this isn't a lint crate's directory.
let mut crate_path = if expect_action(e.file_type(), ErrAction::Read, ".").is_dir()
&& let Ok(crate_path) = e.file_name().into_string()
&& crate_path.starts_with("clippy_lints")
&& crate_path != "clippy_lints_internal"
{
crate_path
} else {
continue;
};
crate_path.push(path::MAIN_SEPARATOR);
crate_path.push_str("src");
for e in walk_dir_no_dot_or_target(&crate_path) {
let e = expect_action(e, ErrAction::Read, &crate_path);
if let Some(path) = e.path().to_str()
&& let Some(path) = path.strip_suffix(".rs")
&& let Some(path) = path.get(crate_path.len() + 1..)
{
let module = if path == "lib" {
""
} else {
let path = path
.strip_suffix("mod")
.and_then(|x| x.strip_suffix(path::MAIN_SEPARATOR))
.unwrap_or(path);
self.str_buf
.alloc_replaced(self.arena, path, path::MAIN_SEPARATOR, "::")
};
self.parse_clippy_lint_decls(
e.path(),
File::open_read_to_cleared_string(e.path(), &mut contents),
module,
&mut lints,
);
}
}
}
lints.sort_by(|lhs, rhs| lhs.name.cmp(rhs.name));
lints
}
/// 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, lints: &mut Vec<Lint<'cx>>) {
#[allow(clippy::enum_glob_use)]
use cursor::Pat::*;
#[rustfmt::skip]
static DECL_TOKENS: &[cursor::Pat<'_>] = &[
// !{ /// docs
Bang, OpenBrace, AnyComment,
// #[clippy::version = "version"]
Pound, OpenBracket, Ident("clippy"), DoubleColon, Ident("version"), Eq, LitStr, CloseBracket,
// pub NAME, GROUP,
Ident("pub"), CaptureIdent, Comma, AnyComment, CaptureIdent, Comma,
];
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) {
lints.push(Lint {
name: self.str_buf.alloc_ascii_lower(self.arena, cursor.get_text(captures[0])),
group: self.arena.alloc_str(cursor.get_text(captures[1])),
module,
path: path.into(),
declaration_range: start as usize..cursor.pos() as usize,
});
}
}
}
#[must_use]
pub fn read_deprecated_lints(&mut self) -> (Vec<DeprecatedLint<'cx>>, Vec<RenamedLint<'cx>>) {
#[allow(clippy::enum_glob_use)]
use cursor::Pat::*;
#[rustfmt::skip]
static DECL_TOKENS: &[cursor::Pat<'_>] = &[
// #[clippy::version = "version"]
Pound, OpenBracket, Ident("clippy"), DoubleColon, Ident("version"), Eq, CaptureLitStr, CloseBracket,
// ("first", "second"),
OpenParen, CaptureLitStr, Comma, CaptureLitStr, CloseParen, Comma,
];
#[rustfmt::skip]
static DEPRECATED_TOKENS: &[cursor::Pat<'_>] = &[
// !{ DEPRECATED(DEPRECATED_VERSION) = [
Bang, OpenBrace, Ident("DEPRECATED"), OpenParen, Ident("DEPRECATED_VERSION"), CloseParen, Eq, OpenBracket,
];
#[rustfmt::skip]
static RENAMED_TOKENS: &[cursor::Pat<'_>] = &[
// !{ RENAMED(RENAMED_VERSION) = [
Bang, OpenBrace, Ident("RENAMED"), OpenParen, Ident("RENAMED_VERSION"), CloseParen, Eq, OpenBracket,
];
let path = "clippy_lints/src/deprecated_lints.rs";
let mut deprecated = Vec::with_capacity(30);
let mut renamed = Vec::with_capacity(80);
let mut contents = String::new();
File::open_read_to_cleared_string(path, &mut contents);
let mut cursor = Cursor::new(&contents);
let mut captures = [Capture::EMPTY; 3];
// First instance is the macro definition.
assert!(
cursor.find_ident("declare_with_version").is_some(),
"error reading deprecated lints"
);
if cursor.find_ident("declare_with_version").is_some() && cursor.match_all(DEPRECATED_TOKENS, &mut []) {
while cursor.match_all(DECL_TOKENS, &mut captures) {
deprecated.push(DeprecatedLint {
name: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[1])),
reason: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[2])),
version: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[0])),
});
}
} else {
panic!("error reading deprecated lints");
}
if cursor.find_ident("declare_with_version").is_some() && cursor.match_all(RENAMED_TOKENS, &mut []) {
while cursor.match_all(DECL_TOKENS, &mut captures) {
renamed.push(RenamedLint {
old_name: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[1])),
new_name: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[2])),
version: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[0])),
});
}
} else {
panic!("error reading renamed lints");
}
deprecated.sort_by(|lhs, rhs| lhs.name.cmp(rhs.name));
renamed.sort_by(|lhs, rhs| lhs.old_name.cmp(rhs.old_name));
(deprecated, renamed)
}
/// Removes the line splices and surrounding quotes from a string literal
fn parse_str_lit(&mut self, s: &str) -> &'cx str {
let (s, is_raw) = if let Some(s) = s.strip_prefix("r") {
(s.trim_matches('#'), true)
} else {
(s, false)
};
let s = s
.strip_prefix('"')
.and_then(|s| s.strip_suffix('"'))
.unwrap_or_else(|| panic!("expected quoted string, found `{s}`"));
if is_raw {
if s.is_empty() { "" } else { self.arena.alloc_str(s) }
} else {
self.str_buf.with(|buf| {
rustc_literal_escaper::unescape_str(s, &mut |_, ch| {
if let Ok(ch) = ch {
buf.push(ch);
}
});
if buf.is_empty() { "" } else { self.arena.alloc_str(buf) }
})
}
}
fn parse_str_single_line(&mut self, path: &Path, s: &str) -> &'cx str {
let value = self.parse_str_lit(s);
assert!(
!value.contains('\n'),
"error parsing `{}`: `{s}` should be a single line string",
path.display(),
);
value
}
}

View file

@ -0,0 +1,263 @@
use core::slice;
use rustc_lexer::{self as lex, LiteralKind, Token, TokenKind};
/// A token pattern used for searching and matching by the [`Cursor`].
///
/// In the event that a pattern is a multi-token sequence, earlier tokens will be consumed
/// even if the pattern ultimately isn't matched. e.g. With the sequence `:*` matching
/// `DoubleColon` will consume the first `:` and then fail to match, leaving the cursor at
/// the `*`.
#[derive(Clone, Copy)]
pub enum Pat<'a> {
/// Matches any number of comments and doc comments.
AnyComment,
Ident(&'a str),
CaptureIdent,
LitStr,
CaptureLitStr,
Bang,
CloseBrace,
CloseBracket,
CloseParen,
Comma,
DoubleColon,
Eq,
Lifetime,
Lt,
Gt,
OpenBrace,
OpenBracket,
OpenParen,
Pound,
Semi,
}
#[derive(Clone, Copy)]
pub struct Capture {
pub pos: u32,
pub len: u32,
}
impl Capture {
pub const EMPTY: Self = Self { pos: 0, len: 0 };
}
/// A unidirectional cursor over a token stream that is lexed on demand.
pub struct Cursor<'txt> {
next_token: Token,
pos: u32,
inner: lex::Cursor<'txt>,
text: &'txt str,
}
impl<'txt> Cursor<'txt> {
#[must_use]
pub fn new(text: &'txt str) -> Self {
let mut inner = lex::Cursor::new(text, lex::FrontmatterAllowed::Yes);
Self {
next_token: inner.advance_token(),
pos: 0,
inner,
text,
}
}
/// Gets the text of the captured token assuming it came from this cursor.
#[must_use]
pub fn get_text(&self, capture: Capture) -> &'txt str {
&self.text[capture.pos as usize..(capture.pos + capture.len) as usize]
}
/// Gets the text that makes up the next token in the stream, or the empty string if
/// stream is exhausted.
#[must_use]
pub fn peek_text(&self) -> &'txt str {
&self.text[self.pos as usize..(self.pos + self.next_token.len) as usize]
}
/// Gets the length of the next token in bytes, or zero if the stream is exhausted.
#[must_use]
pub fn peek_len(&self) -> u32 {
self.next_token.len
}
/// Gets the next token in the stream, or [`TokenKind::Eof`] if the stream is
/// exhausted.
#[must_use]
pub fn peek(&self) -> TokenKind {
self.next_token.kind
}
/// Gets the offset of the next token in the source string, or the string's length if
/// the stream is exhausted.
#[must_use]
pub fn pos(&self) -> u32 {
self.pos
}
/// Gets whether the cursor has exhausted its input.
#[must_use]
pub fn at_end(&self) -> bool {
self.next_token.kind == TokenKind::Eof
}
/// Advances the cursor to the next token. If the stream is exhausted this will set
/// the next token to [`TokenKind::Eof`].
pub fn step(&mut self) {
// `next_token.len` is zero for the eof marker.
self.pos += self.next_token.len;
self.next_token = self.inner.advance_token();
}
/// Consumes tokens until the given pattern is either fully matched of fails to match.
/// Returns whether the pattern was fully matched.
///
/// For each capture made by the pattern one item will be taken from the capture
/// sequence with the result placed inside.
fn match_impl(&mut self, pat: Pat<'_>, captures: &mut slice::IterMut<'_, Capture>) -> bool {
loop {
match (pat, self.next_token.kind) {
#[rustfmt::skip] // rustfmt bug: https://github.com/rust-lang/rustfmt/issues/6697
(_, TokenKind::Whitespace)
| (
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)
| (Pat::CloseParen, TokenKind::CloseParen)
| (Pat::Comma, TokenKind::Comma)
| (Pat::Eq, TokenKind::Eq)
| (Pat::Lifetime, TokenKind::Lifetime { .. })
| (Pat::Lt, TokenKind::Lt)
| (Pat::Gt, TokenKind::Gt)
| (Pat::OpenBrace, TokenKind::OpenBrace)
| (Pat::OpenBracket, TokenKind::OpenBracket)
| (Pat::OpenParen, TokenKind::OpenParen)
| (Pat::Pound, TokenKind::Pound)
| (Pat::Semi, TokenKind::Semi)
| (
Pat::LitStr,
TokenKind::Literal {
kind: LiteralKind::Str { terminated: true } | LiteralKind::RawStr { .. },
..
},
) => {
self.step();
return true;
},
(Pat::Ident(x), TokenKind::Ident) if x == self.peek_text() => {
self.step();
return true;
},
(Pat::DoubleColon, TokenKind::Colon) => {
self.step();
if !self.at_end() && matches!(self.next_token.kind, TokenKind::Colon) {
self.step();
return true;
}
return false;
},
#[rustfmt::skip]
(
Pat::CaptureLitStr,
TokenKind::Literal {
kind:
LiteralKind::Str { terminated: true }
| LiteralKind::RawStr { n_hashes: Some(_) },
..
},
)
| (Pat::CaptureIdent, TokenKind::Ident) => {
*captures.next().unwrap() = Capture { pos: self.pos, len: self.next_token.len };
self.step();
return true;
},
_ => return false,
}
}
}
/// Consumes all tokens until the specified identifier is found and returns its
/// position. Returns `None` if the identifier could not be found.
///
/// The cursor will be positioned immediately after the identifier, or at the end if
/// it is not.
pub fn find_ident(&mut self, ident: &str) -> Option<u32> {
loop {
match self.next_token.kind {
TokenKind::Ident if self.peek_text() == ident => {
let pos = self.pos;
self.step();
return Some(pos);
},
TokenKind::Eof => return None,
_ => self.step(),
}
}
}
/// Consumes all tokens until the next identifier is found and captures it. Returns
/// `None` if no identifier could be found.
///
/// The cursor will be positioned immediately after the identifier, or at the end if
/// it is not.
pub fn find_any_ident(&mut self) -> Option<Capture> {
loop {
match self.next_token.kind {
TokenKind::Ident => {
let res = Capture {
pos: self.pos,
len: self.next_token.len,
};
self.step();
return Some(res);
},
TokenKind::Eof => return None,
_ => self.step(),
}
}
}
/// Continually attempt to match the pattern on subsequent tokens until a match is
/// found. Returns whether the pattern was successfully matched.
///
/// Not generally suitable for multi-token patterns or patterns that can match
/// nothing.
#[must_use]
pub fn find_pat(&mut self, pat: Pat<'_>) -> bool {
let mut capture = [].iter_mut();
while !self.match_impl(pat, &mut capture) {
self.step();
if self.at_end() {
return false;
}
}
true
}
/// Attempts to match a sequence of patterns at the current position. Returns whether
/// all patterns were successfully matched.
///
/// Captures will be written to the given slice in the order they're matched. If a
/// capture is matched, but there are no more capture slots this will panic. If the
/// match is completed without filling all the capture slots they will be left
/// unmodified.
///
/// If the match fails the cursor will be positioned at the first failing token.
#[must_use]
pub fn match_all(&mut self, pats: &[Pat<'_>], captures: &mut [Capture]) -> bool {
let mut captures = captures.iter_mut();
pats.iter().all(|&p| self.match_impl(p, &mut captures))
}
/// Attempts to match a single pattern at the current position. Returns whether the
/// pattern was successfully matched.
///
/// If the pattern attempts to capture anything this will panic. If the match fails
/// the cursor will be positioned at the first failing token.
#[must_use]
pub fn match_pat(&mut self, pat: Pat<'_>) -> bool {
self.match_impl(pat, &mut [].iter_mut())
}
}

View file

@ -23,7 +23,7 @@ pub fn bump_version(mut version: Version) {
dst.push_str(&src[..package.version_range.start]);
write!(dst, "\"{}\"", version.toml_display()).unwrap();
dst.push_str(&src[package.version_range.end..]);
UpdateStatus::from_changed(src.get(package.version_range.clone()) != dst.get(package.version_range))
UpdateStatus::from_changed(src.get(package.version_range) != dst.get(package.version_range))
}
});
}

View file

@ -1,7 +1,9 @@
use crate::update_lints::{RenamedLint, find_lint_decls, generate_lint_files, read_deprecated_lints};
use crate::parse::cursor::{self, Capture, Cursor};
use crate::parse::{ParseCx, RenamedLint};
use crate::update_lints::generate_lint_files;
use crate::utils::{
ErrAction, FileUpdater, RustSearcher, Token, 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,
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,
};
use rustc_lexer::TokenKind;
use std::ffi::OsString;
@ -24,36 +26,35 @@ use std::path::Path;
/// * If `old_name` doesn't name an existing lint.
/// * If `old_name` names a deprecated or renamed lint.
#[expect(clippy::too_many_lines)]
pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: bool) {
if let Some((prefix, _)) = old_name.split_once("::") {
panic!("`{old_name}` should not contain the `{prefix}` prefix");
}
if let Some((prefix, _)) = new_name.split_once("::") {
panic!("`{new_name}` should not contain the `{prefix}` prefix");
}
pub fn rename<'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_name: &'cx str, new_name: &'cx str, uplift: bool) {
let mut updater = FileUpdater::default();
let mut lints = find_lint_decls();
let (deprecated_lints, mut renamed_lints) = read_deprecated_lints();
let mut lints = cx.find_lint_decls();
let (deprecated_lints, mut renamed_lints) = cx.read_deprecated_lints();
let Ok(lint_idx) = lints.binary_search_by(|x| x.name.as_str().cmp(old_name)) else {
let Ok(lint_idx) = lints.binary_search_by(|x| x.name.cmp(old_name)) else {
panic!("could not find lint `{old_name}`");
};
let lint = &lints[lint_idx];
let old_name_prefixed = String::from_iter(["clippy::", old_name]);
let old_name_prefixed = cx.str_buf.with(|buf| {
buf.extend(["clippy::", old_name]);
cx.arena.alloc_str(buf)
});
let new_name_prefixed = if uplift {
new_name.to_owned()
new_name
} else {
String::from_iter(["clippy::", new_name])
cx.str_buf.with(|buf| {
buf.extend(["clippy::", new_name]);
cx.arena.alloc_str(buf)
})
};
for lint in &mut renamed_lints {
if lint.new_name == old_name_prefixed {
lint.new_name.clone_from(&new_name_prefixed);
lint.new_name = new_name_prefixed;
}
}
match renamed_lints.binary_search_by(|x| x.old_name.cmp(&old_name_prefixed)) {
match renamed_lints.binary_search_by(|x| x.old_name.cmp(old_name_prefixed)) {
Ok(_) => {
println!("`{old_name}` already has a rename registered");
return;
@ -63,12 +64,8 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b
idx,
RenamedLint {
old_name: old_name_prefixed,
new_name: if uplift {
new_name.to_owned()
} else {
String::from_iter(["clippy::", new_name])
},
version: clippy_version.rust_display().to_string(),
new_name: new_name_prefixed,
version: cx.str_buf.alloc_display(cx.arena, clippy_version.rust_display()),
},
);
},
@ -104,7 +101,7 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b
}
delete_test_files(old_name, change_prefixed_tests);
lints.remove(lint_idx);
} else if lints.binary_search_by(|x| x.name.as_str().cmp(new_name)).is_err() {
} else if lints.binary_search_by(|x| x.name.cmp(new_name)).is_err() {
let lint = &mut lints[lint_idx];
if lint.module.ends_with(old_name)
&& lint
@ -118,13 +115,15 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b
mod_edit = ModEdit::Rename;
}
let mod_len = lint.module.len();
lint.module.truncate(mod_len - old_name.len());
lint.module.push_str(new_name);
lint.module = cx.str_buf.with(|buf| {
buf.push_str(&lint.module[..lint.module.len() - old_name.len()]);
buf.push_str(new_name);
cx.arena.alloc_str(buf)
});
}
rename_test_files(old_name, new_name, change_prefixed_tests);
new_name.clone_into(&mut lints[lint_idx].name);
lints.sort_by(|lhs, rhs| lhs.name.cmp(&rhs.name));
lints[lint_idx].name = new_name;
lints.sort_by(|lhs, rhs| lhs.name.cmp(rhs.name));
} else {
println!("Renamed `clippy::{old_name}` to `clippy::{new_name}`");
println!("Since `{new_name}` already exists the existing code has not been changed");
@ -132,7 +131,7 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b
}
let mut update_fn = file_update_fn(old_name, new_name, mod_edit);
for e in walk_dir_no_dot_or_target() {
for e in walk_dir_no_dot_or_target(".") {
let e = expect_action(e, ErrAction::Read, ".");
if e.path().as_os_str().as_encoded_bytes().ends_with(b".rs") {
updater.update_file(e.path(), &mut update_fn);
@ -285,47 +284,46 @@ fn file_update_fn<'a, 'b>(
move |_, src, dst| {
let mut copy_pos = 0u32;
let mut changed = false;
let mut searcher = RustSearcher::new(src);
let mut capture = "";
let mut cursor = Cursor::new(src);
let mut captures = [Capture::EMPTY];
loop {
match searcher.peek() {
match cursor.peek() {
TokenKind::Eof => break,
TokenKind::Ident => {
let match_start = searcher.pos();
let text = searcher.peek_text();
searcher.step();
let match_start = cursor.pos();
let text = cursor.peek_text();
cursor.step();
match text {
// clippy::line_name or clippy::lint-name
"clippy" => {
if searcher.match_tokens(&[Token::DoubleColon, Token::CaptureIdent], &mut [&mut capture])
&& capture == old_name
if cursor.match_all(&[cursor::Pat::DoubleColon, cursor::Pat::CaptureIdent], &mut captures)
&& cursor.get_text(captures[0]) == old_name
{
dst.push_str(&src[copy_pos as usize..searcher.pos() as usize - capture.len()]);
dst.push_str(&src[copy_pos as usize..captures[0].pos as usize]);
dst.push_str(new_name);
copy_pos = searcher.pos();
copy_pos = cursor.pos();
changed = true;
}
},
// mod lint_name
"mod" => {
if !matches!(mod_edit, ModEdit::None)
&& searcher.match_tokens(&[Token::CaptureIdent], &mut [&mut capture])
&& capture == old_name
&& let Some(pos) = cursor.find_ident(old_name)
{
match mod_edit {
ModEdit::Rename => {
dst.push_str(&src[copy_pos as usize..searcher.pos() as usize - capture.len()]);
dst.push_str(&src[copy_pos as usize..pos as usize]);
dst.push_str(new_name);
copy_pos = searcher.pos();
copy_pos = cursor.pos();
changed = true;
},
ModEdit::Delete if searcher.match_tokens(&[Token::Semi], &mut []) => {
ModEdit::Delete if cursor.match_pat(cursor::Pat::Semi) => {
let mut start = &src[copy_pos as usize..match_start as usize];
if start.ends_with("\n\n") {
start = &start[..start.len() - 1];
}
dst.push_str(start);
copy_pos = searcher.pos();
copy_pos = cursor.pos();
if src[copy_pos as usize..].starts_with("\n\n") {
copy_pos += 1;
}
@ -337,8 +335,8 @@ fn file_update_fn<'a, 'b>(
},
// lint_name::
name if matches!(mod_edit, ModEdit::Rename) && name == old_name => {
let name_end = searcher.pos();
if searcher.match_tokens(&[Token::DoubleColon], &mut []) {
let name_end = cursor.pos();
if cursor.match_pat(cursor::Pat::DoubleColon) {
dst.push_str(&src[copy_pos as usize..match_start as usize]);
dst.push_str(new_name);
copy_pos = name_end;
@ -356,36 +354,36 @@ fn file_update_fn<'a, 'b>(
};
dst.push_str(&src[copy_pos as usize..match_start as usize]);
dst.push_str(replacement);
copy_pos = searcher.pos();
copy_pos = cursor.pos();
changed = true;
},
}
},
// //~ lint_name
TokenKind::LineComment { doc_style: None } => {
let text = searcher.peek_text();
let text = cursor.peek_text();
if text.starts_with("//~")
&& let Some(text) = text.strip_suffix(old_name)
&& !text.ends_with(|c| matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '_'))
{
dst.push_str(&src[copy_pos as usize..searcher.pos() as usize + text.len()]);
dst.push_str(&src[copy_pos as usize..cursor.pos() as usize + text.len()]);
dst.push_str(new_name);
copy_pos = searcher.pos() + searcher.peek_len();
copy_pos = cursor.pos() + cursor.peek_len();
changed = true;
}
searcher.step();
cursor.step();
},
// ::lint_name
TokenKind::Colon
if searcher.match_tokens(&[Token::DoubleColon, Token::CaptureIdent], &mut [&mut capture])
&& capture == old_name =>
if cursor.match_all(&[cursor::Pat::DoubleColon, cursor::Pat::CaptureIdent], &mut captures)
&& cursor.get_text(captures[0]) == old_name =>
{
dst.push_str(&src[copy_pos as usize..searcher.pos() as usize - capture.len()]);
dst.push_str(&src[copy_pos as usize..captures[0].pos as usize]);
dst.push_str(new_name);
copy_pos = searcher.pos();
copy_pos = cursor.pos();
changed = true;
},
_ => searcher.step(),
_ => cursor.step(),
}
}

View file

@ -1,13 +1,10 @@
use crate::utils::{
ErrAction, File, FileUpdater, RustSearcher, Token, UpdateMode, UpdateStatus, expect_action, update_text_region_fn,
};
use crate::parse::cursor::Cursor;
use crate::parse::{DeprecatedLint, Lint, ParseCx, RenamedLint};
use crate::utils::{FileUpdater, UpdateMode, UpdateStatus, update_text_region_fn};
use itertools::Itertools;
use std::collections::HashSet;
use std::fmt::Write;
use std::fs;
use std::ops::Range;
use std::path::{self, Path, PathBuf};
use walkdir::{DirEntry, WalkDir};
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\
@ -24,18 +21,18 @@ const DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.ht
/// # Panics
///
/// Panics if a file path could not read from or then written to
pub fn update(update_mode: UpdateMode) {
let lints = find_lint_decls();
let (deprecated, renamed) = read_deprecated_lints();
pub fn update(cx: ParseCx<'_>, update_mode: UpdateMode) {
let lints = cx.find_lint_decls();
let (deprecated, renamed) = cx.read_deprecated_lints();
generate_lint_files(update_mode, &lints, &deprecated, &renamed);
}
#[expect(clippy::too_many_lines)]
pub fn generate_lint_files(
update_mode: UpdateMode,
lints: &[Lint],
deprecated: &[DeprecatedLint],
renamed: &[RenamedLint],
lints: &[Lint<'_>],
deprecated: &[DeprecatedLint<'_>],
renamed: &[RenamedLint<'_>],
) {
let mut updater = FileUpdater::default();
updater.update_file_checked(
@ -64,7 +61,7 @@ pub fn generate_lint_files(
|dst| {
for lint in lints
.iter()
.map(|l| &*l.name)
.map(|l| l.name)
.chain(deprecated.iter().filter_map(|l| l.name.strip_prefix("clippy::")))
.chain(renamed.iter().filter_map(|l| l.old_name.strip_prefix("clippy::")))
.sorted()
@ -79,13 +76,13 @@ pub fn generate_lint_files(
update_mode,
"clippy_lints/src/deprecated_lints.rs",
&mut |_, src, dst| {
let mut searcher = RustSearcher::new(src);
let mut cursor = Cursor::new(src);
assert!(
searcher.find_token(Token::Ident("declare_with_version"))
&& searcher.find_token(Token::Ident("declare_with_version")),
cursor.find_ident("declare_with_version").is_some()
&& cursor.find_ident("declare_with_version").is_some(),
"error reading deprecated lints"
);
dst.push_str(&src[..searcher.pos() as usize]);
dst.push_str(&src[..cursor.pos() as usize]);
dst.push_str("! { DEPRECATED(DEPRECATED_VERSION) = [\n");
for lint in deprecated {
write!(
@ -135,13 +132,13 @@ pub fn generate_lint_files(
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) {
if seen_lints.insert(lint.new_name) {
writeln!(dst, "#![allow({})]", lint.new_name).unwrap();
}
}
seen_lints.clear();
for lint in renamed {
if seen_lints.insert(&lint.old_name) {
if seen_lints.insert(lint.old_name) {
writeln!(dst, "#![warn({})] //~ ERROR: lint `{}`", lint.old_name, lint.old_name).unwrap();
}
}
@ -167,7 +164,7 @@ pub fn generate_lint_files(
for lint_mod in lints
.iter()
.filter(|l| !l.module.is_empty())
.map(|l| l.module.split_once("::").map_or(&*l.module, |x| x.0))
.map(|l| l.module.split_once("::").map_or(l.module, |x| x.0))
.sorted()
.dedup()
{
@ -200,260 +197,3 @@ pub fn generate_lint_files(
fn round_to_fifty(count: usize) -> usize {
count / 50 * 50
}
/// Lint data parsed from the Clippy source code.
#[derive(PartialEq, Eq, Debug)]
pub struct Lint {
pub name: String,
pub group: String,
pub module: String,
pub path: PathBuf,
pub declaration_range: Range<usize>,
}
pub struct DeprecatedLint {
pub name: String,
pub reason: String,
pub version: String,
}
pub struct RenamedLint {
pub old_name: String,
pub new_name: String,
pub version: String,
}
/// Finds all lint declarations (`declare_clippy_lint!`)
#[must_use]
pub fn find_lint_decls() -> Vec<Lint> {
let mut lints = Vec::with_capacity(1000);
let mut contents = String::new();
for e in expect_action(fs::read_dir("."), ErrAction::Read, ".") {
let e = expect_action(e, ErrAction::Read, ".");
if !expect_action(e.file_type(), ErrAction::Read, ".").is_dir() {
continue;
}
let Ok(mut name) = e.file_name().into_string() else {
continue;
};
if name.starts_with("clippy_lints") && name != "clippy_lints_internal" {
name.push_str("/src");
for (file, module) in read_src_with_module(name.as_ref()) {
parse_clippy_lint_decls(
file.path(),
File::open_read_to_cleared_string(file.path(), &mut contents),
&module,
&mut lints,
);
}
}
}
lints.sort_by(|lhs, rhs| lhs.name.cmp(&rhs.name));
lints
}
/// Reads the source files from the given root directory
fn read_src_with_module(src_root: &Path) -> impl use<'_> + Iterator<Item = (DirEntry, String)> {
WalkDir::new(src_root).into_iter().filter_map(move |e| {
let e = expect_action(e, ErrAction::Read, src_root);
let path = e.path().as_os_str().as_encoded_bytes();
if let Some(path) = path.strip_suffix(b".rs")
&& let Some(path) = path.get(src_root.as_os_str().len() + 1..)
{
if path == b"lib" {
Some((e, String::new()))
} else {
let path = if let Some(path) = path.strip_suffix(b"mod")
&& let Some(path) = path.strip_suffix(b"/").or_else(|| path.strip_suffix(b"\\"))
{
path
} else {
path
};
if let Ok(path) = str::from_utf8(path) {
let path = path.replace(['/', '\\'], "::");
Some((e, path))
} else {
None
}
}
} else {
None
}
})
}
/// Parse a source file looking for `declare_clippy_lint` macro invocations.
fn parse_clippy_lint_decls(path: &Path, contents: &str, module: &str, lints: &mut Vec<Lint>) {
#[allow(clippy::enum_glob_use)]
use Token::*;
#[rustfmt::skip]
static DECL_TOKENS: &[Token<'_>] = &[
// !{ /// docs
Bang, OpenBrace, AnyComment,
// #[clippy::version = "version"]
Pound, OpenBracket, Ident("clippy"), DoubleColon, Ident("version"), Eq, LitStr, CloseBracket,
// pub NAME, GROUP,
Ident("pub"), CaptureIdent, Comma, AnyComment, CaptureIdent, Comma,
];
let mut searcher = RustSearcher::new(contents);
while searcher.find_token(Ident("declare_clippy_lint")) {
let start = searcher.pos() as usize - "declare_clippy_lint".len();
let (mut name, mut group) = ("", "");
if searcher.match_tokens(DECL_TOKENS, &mut [&mut name, &mut group]) && searcher.find_token(CloseBrace) {
lints.push(Lint {
name: name.to_lowercase(),
group: group.into(),
module: module.into(),
path: path.into(),
declaration_range: start..searcher.pos() as usize,
});
}
}
}
#[must_use]
pub fn read_deprecated_lints() -> (Vec<DeprecatedLint>, Vec<RenamedLint>) {
#[allow(clippy::enum_glob_use)]
use Token::*;
#[rustfmt::skip]
static DECL_TOKENS: &[Token<'_>] = &[
// #[clippy::version = "version"]
Pound, OpenBracket, Ident("clippy"), DoubleColon, Ident("version"), Eq, CaptureLitStr, CloseBracket,
// ("first", "second"),
OpenParen, CaptureLitStr, Comma, CaptureLitStr, CloseParen, Comma,
];
#[rustfmt::skip]
static DEPRECATED_TOKENS: &[Token<'_>] = &[
// !{ DEPRECATED(DEPRECATED_VERSION) = [
Bang, OpenBrace, Ident("DEPRECATED"), OpenParen, Ident("DEPRECATED_VERSION"), CloseParen, Eq, OpenBracket,
];
#[rustfmt::skip]
static RENAMED_TOKENS: &[Token<'_>] = &[
// !{ RENAMED(RENAMED_VERSION) = [
Bang, OpenBrace, Ident("RENAMED"), OpenParen, Ident("RENAMED_VERSION"), CloseParen, Eq, OpenBracket,
];
let path = "clippy_lints/src/deprecated_lints.rs";
let mut deprecated = Vec::with_capacity(30);
let mut renamed = Vec::with_capacity(80);
let mut contents = String::new();
File::open_read_to_cleared_string(path, &mut contents);
let mut searcher = RustSearcher::new(&contents);
// First instance is the macro definition.
assert!(
searcher.find_token(Ident("declare_with_version")),
"error reading deprecated lints"
);
if searcher.find_token(Ident("declare_with_version")) && searcher.match_tokens(DEPRECATED_TOKENS, &mut []) {
let mut version = "";
let mut name = "";
let mut reason = "";
while searcher.match_tokens(DECL_TOKENS, &mut [&mut version, &mut name, &mut reason]) {
deprecated.push(DeprecatedLint {
name: parse_str_single_line(path.as_ref(), name),
reason: parse_str_single_line(path.as_ref(), reason),
version: parse_str_single_line(path.as_ref(), version),
});
}
} else {
panic!("error reading deprecated lints");
}
if searcher.find_token(Ident("declare_with_version")) && searcher.match_tokens(RENAMED_TOKENS, &mut []) {
let mut version = "";
let mut old_name = "";
let mut new_name = "";
while searcher.match_tokens(DECL_TOKENS, &mut [&mut version, &mut old_name, &mut new_name]) {
renamed.push(RenamedLint {
old_name: parse_str_single_line(path.as_ref(), old_name),
new_name: parse_str_single_line(path.as_ref(), new_name),
version: parse_str_single_line(path.as_ref(), version),
});
}
} else {
panic!("error reading renamed lints");
}
deprecated.sort_by(|lhs, rhs| lhs.name.cmp(&rhs.name));
renamed.sort_by(|lhs, rhs| lhs.old_name.cmp(&rhs.old_name));
(deprecated, renamed)
}
/// Removes the line splices and surrounding quotes from a string literal
fn parse_str_lit(s: &str) -> String {
let s = s.strip_prefix("r").unwrap_or(s).trim_matches('#');
let s = s
.strip_prefix('"')
.and_then(|s| s.strip_suffix('"'))
.unwrap_or_else(|| panic!("expected quoted string, found `{s}`"));
let mut res = String::with_capacity(s.len());
rustc_literal_escaper::unescape_str(s, &mut |_, ch| {
if let Ok(ch) = ch {
res.push(ch);
}
});
res
}
fn parse_str_single_line(path: &Path, s: &str) -> String {
let value = parse_str_lit(s);
assert!(
!value.contains('\n'),
"error parsing `{}`: `{s}` should be a single line string",
path.display(),
);
value
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_clippy_lint_decls() {
static CONTENTS: &str = r#"
declare_clippy_lint! {
#[clippy::version = "Hello Clippy!"]
pub PTR_ARG,
style,
"really long \
text"
}
declare_clippy_lint!{
#[clippy::version = "Test version"]
pub DOC_MARKDOWN,
pedantic,
"single line"
}
"#;
let mut result = Vec::new();
parse_clippy_lint_decls("".as_ref(), CONTENTS, "module_name", &mut result);
for r in &mut result {
r.declaration_range = Range::default();
}
let expected = vec![
Lint {
name: "ptr_arg".into(),
group: "style".into(),
module: "module_name".into(),
path: PathBuf::new(),
declaration_range: Range::default(),
},
Lint {
name: "doc_markdown".into(),
group: "pedantic".into(),
module: "module_name".into(),
path: PathBuf::new(),
declaration_range: Range::default(),
},
];
assert_eq!(expected, result);
}
}

View file

@ -1,9 +1,9 @@
use core::fmt::{self, Display};
use core::marker::PhantomData;
use core::num::NonZero;
use core::ops::Range;
use core::slice;
use core::ops::{Deref, DerefMut};
use core::range::Range;
use core::str::FromStr;
use rustc_lexer::{self as lexer, FrontmatterAllowed};
use std::ffi::OsStr;
use std::fs::{self, OpenOptions};
use std::io::{self, Read as _, Seek as _, SeekFrom, Write};
@ -12,6 +12,24 @@ use std::process::{self, Command, Stdio};
use std::{env, thread};
use walkdir::WalkDir;
pub struct Scoped<'inner, 'outer: 'inner, T>(T, PhantomData<&'inner mut T>, PhantomData<&'outer mut ()>);
impl<T> Scoped<'_, '_, T> {
pub fn new(value: T) -> Self {
Self(value, PhantomData, PhantomData)
}
}
impl<T> Deref for Scoped<'_, '_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for Scoped<'_, '_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[derive(Clone, Copy)]
pub enum ErrAction {
Open,
@ -410,179 +428,6 @@ pub fn update_text_region_fn(
move |path, src, dst| update_text_region(path, start, end, src, dst, &mut insert)
}
#[derive(Clone, Copy)]
pub enum Token<'a> {
/// Matches any number of comments / doc comments.
AnyComment,
Ident(&'a str),
CaptureIdent,
LitStr,
CaptureLitStr,
Bang,
CloseBrace,
CloseBracket,
CloseParen,
/// This will consume the first colon even if the second doesn't exist.
DoubleColon,
Comma,
Eq,
Lifetime,
Lt,
Gt,
OpenBrace,
OpenBracket,
OpenParen,
Pound,
Semi,
}
pub struct RustSearcher<'txt> {
text: &'txt str,
cursor: lexer::Cursor<'txt>,
pos: u32,
next_token: lexer::Token,
}
impl<'txt> RustSearcher<'txt> {
#[must_use]
#[expect(clippy::inconsistent_struct_constructor)]
pub fn new(text: &'txt str) -> Self {
let mut cursor = lexer::Cursor::new(text, FrontmatterAllowed::Yes);
Self {
text,
pos: 0,
next_token: cursor.advance_token(),
cursor,
}
}
#[must_use]
pub fn peek_text(&self) -> &'txt str {
&self.text[self.pos as usize..(self.pos + self.next_token.len) as usize]
}
#[must_use]
pub fn peek_len(&self) -> u32 {
self.next_token.len
}
#[must_use]
pub fn peek(&self) -> lexer::TokenKind {
self.next_token.kind
}
#[must_use]
pub fn pos(&self) -> u32 {
self.pos
}
#[must_use]
pub fn at_end(&self) -> bool {
self.next_token.kind == lexer::TokenKind::Eof
}
pub fn step(&mut self) {
// `next_len` is zero for the sentinel value and the eof marker.
self.pos += self.next_token.len;
self.next_token = self.cursor.advance_token();
}
/// Consumes the next token if it matches the requested value and captures the value if
/// requested. Returns true if a token was matched.
fn read_token(&mut self, token: Token<'_>, captures: &mut slice::IterMut<'_, &mut &'txt str>) -> bool {
loop {
match (token, self.next_token.kind) {
(_, lexer::TokenKind::Whitespace)
| (
Token::AnyComment,
lexer::TokenKind::BlockComment { terminated: true, .. } | lexer::TokenKind::LineComment { .. },
) => self.step(),
(Token::AnyComment, _) => return true,
(Token::Bang, lexer::TokenKind::Bang)
| (Token::CloseBrace, lexer::TokenKind::CloseBrace)
| (Token::CloseBracket, lexer::TokenKind::CloseBracket)
| (Token::CloseParen, lexer::TokenKind::CloseParen)
| (Token::Comma, lexer::TokenKind::Comma)
| (Token::Eq, lexer::TokenKind::Eq)
| (Token::Lifetime, lexer::TokenKind::Lifetime { .. })
| (Token::Lt, lexer::TokenKind::Lt)
| (Token::Gt, lexer::TokenKind::Gt)
| (Token::OpenBrace, lexer::TokenKind::OpenBrace)
| (Token::OpenBracket, lexer::TokenKind::OpenBracket)
| (Token::OpenParen, lexer::TokenKind::OpenParen)
| (Token::Pound, lexer::TokenKind::Pound)
| (Token::Semi, lexer::TokenKind::Semi)
| (
Token::LitStr,
lexer::TokenKind::Literal {
kind: lexer::LiteralKind::Str { terminated: true } | lexer::LiteralKind::RawStr { .. },
..
},
) => {
self.step();
return true;
},
(Token::Ident(x), lexer::TokenKind::Ident) if x == self.peek_text() => {
self.step();
return true;
},
(Token::DoubleColon, lexer::TokenKind::Colon) => {
self.step();
if !self.at_end() && matches!(self.next_token.kind, lexer::TokenKind::Colon) {
self.step();
return true;
}
return false;
},
(
Token::CaptureLitStr,
lexer::TokenKind::Literal {
kind: lexer::LiteralKind::Str { terminated: true } | lexer::LiteralKind::RawStr { .. },
..
},
)
| (Token::CaptureIdent, lexer::TokenKind::Ident) => {
**captures.next().unwrap() = self.peek_text();
self.step();
return true;
},
_ => return false,
}
}
}
#[must_use]
pub fn find_token(&mut self, token: Token<'_>) -> bool {
let mut capture = [].iter_mut();
while !self.read_token(token, &mut capture) {
self.step();
if self.at_end() {
return false;
}
}
true
}
#[must_use]
pub fn find_capture_token(&mut self, token: Token<'_>) -> Option<&'txt str> {
let mut res = "";
let mut capture = &mut res;
let mut capture = slice::from_mut(&mut capture).iter_mut();
while !self.read_token(token, &mut capture) {
self.step();
if self.at_end() {
return None;
}
}
Some(res)
}
#[must_use]
pub fn match_tokens(&mut self, tokens: &[Token<'_>], captures: &mut [&mut &'txt str]) -> bool {
let mut captures = captures.iter_mut();
tokens.iter().all(|&t| self.read_token(t, &mut captures))
}
}
#[track_caller]
pub fn try_rename_file(old_name: &Path, new_name: &Path) -> bool {
match OpenOptions::new().create_new(true).write(true).open(new_name) {
@ -748,8 +593,8 @@ pub fn delete_dir_if_exists(path: &Path) {
}
/// Walks all items excluding top-level dot files/directories and any target directories.
pub fn walk_dir_no_dot_or_target() -> impl Iterator<Item = ::walkdir::Result<::walkdir::DirEntry>> {
WalkDir::new(".").into_iter().filter_entry(|e| {
pub fn walk_dir_no_dot_or_target(p: impl AsRef<Path>) -> impl Iterator<Item = ::walkdir::Result<::walkdir::DirEntry>> {
WalkDir::new(p).into_iter().filter_entry(|e| {
e.path()
.file_name()
.is_none_or(|x| x != "target" && x.as_encoded_bytes().first().copied() != Some(b'.'))

View file

@ -1,6 +1,6 @@
[package]
name = "clippy_lints"
version = "0.1.92"
version = "0.1.93"
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy"
readme = "README.md"

View file

@ -135,7 +135,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::duplicate_mod::DUPLICATE_MOD_INFO,
crate::else_if_without_else::ELSE_IF_WITHOUT_ELSE_INFO,
crate::empty_drop::EMPTY_DROP_INFO,
crate::empty_enum::EMPTY_ENUM_INFO,
crate::empty_enums::EMPTY_ENUMS_INFO,
crate::empty_line_after::EMPTY_LINE_AFTER_DOC_COMMENTS_INFO,
crate::empty_line_after::EMPTY_LINE_AFTER_OUTER_ATTR_INFO,
crate::empty_with_brackets::EMPTY_ENUM_VARIANTS_WITH_BRACKETS_INFO,
@ -227,8 +227,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::init_numbered_fields::INIT_NUMBERED_FIELDS_INFO,
crate::inline_fn_without_body::INLINE_FN_WITHOUT_BODY_INFO,
crate::int_plus_one::INT_PLUS_ONE_INFO,
crate::integer_division_remainder_used::INTEGER_DIVISION_REMAINDER_USED_INFO,
crate::invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS_INFO,
crate::item_name_repetitions::ENUM_VARIANT_NAMES_INFO,
crate::item_name_repetitions::MODULE_INCEPTION_INFO,
crate::item_name_repetitions::MODULE_NAME_REPETITIONS_INFO,
@ -258,7 +256,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::lifetimes::ELIDABLE_LIFETIME_NAMES_INFO,
crate::lifetimes::EXTRA_UNUSED_LIFETIMES_INFO,
crate::lifetimes::NEEDLESS_LIFETIMES_INFO,
crate::lines_filter_map_ok::LINES_FILTER_MAP_OK_INFO,
crate::literal_representation::DECIMAL_LITERAL_REPRESENTATION_INFO,
crate::literal_representation::INCONSISTENT_DIGIT_GROUPING_INFO,
crate::literal_representation::LARGE_DIGIT_GROUPS_INFO,
@ -298,7 +295,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::manual_async_fn::MANUAL_ASYNC_FN_INFO,
crate::manual_bits::MANUAL_BITS_INFO,
crate::manual_clamp::MANUAL_CLAMP_INFO,
crate::manual_div_ceil::MANUAL_DIV_CEIL_INFO,
crate::manual_float_methods::MANUAL_IS_FINITE_INFO,
crate::manual_float_methods::MANUAL_IS_INFINITE_INFO,
crate::manual_hash_one::MANUAL_HASH_ONE_INFO,
@ -404,6 +400,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::methods::ITER_SKIP_ZERO_INFO,
crate::methods::ITER_WITH_DRAIN_INFO,
crate::methods::JOIN_ABSOLUTE_PATHS_INFO,
crate::methods::LINES_FILTER_MAP_OK_INFO,
crate::methods::MANUAL_CONTAINS_INFO,
crate::methods::MANUAL_C_STR_LITERALS_INFO,
crate::methods::MANUAL_FILTER_MAP_INFO,
@ -545,7 +542,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::needless_continue::NEEDLESS_CONTINUE_INFO,
crate::needless_else::NEEDLESS_ELSE_INFO,
crate::needless_for_each::NEEDLESS_FOR_EACH_INFO,
crate::needless_if::NEEDLESS_IF_INFO,
crate::needless_ifs::NEEDLESS_IFS_INFO,
crate::needless_late_init::NEEDLESS_LATE_INIT_INFO,
crate::needless_maybe_sized::NEEDLESS_MAYBE_SIZED_INFO,
crate::needless_parens_on_range_literals::NEEDLESS_PARENS_ON_RANGE_LITERALS_INFO,
@ -592,6 +589,9 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::operators::IMPOSSIBLE_COMPARISONS_INFO,
crate::operators::INEFFECTIVE_BIT_MASK_INFO,
crate::operators::INTEGER_DIVISION_INFO,
crate::operators::INTEGER_DIVISION_REMAINDER_USED_INFO,
crate::operators::INVALID_UPCAST_COMPARISONS_INFO,
crate::operators::MANUAL_DIV_CEIL_INFO,
crate::operators::MANUAL_IS_MULTIPLE_OF_INFO,
crate::operators::MANUAL_MIDPOINT_INFO,
crate::operators::MISREFACTORED_ASSIGN_OP_INFO,

Some files were not shown because too many files have changed in this diff Show more