Auto merge of #107143 - compiler-errors:rollup-zabvmo5, r=compiler-errors
Rollup of 9 pull requests Successful merges: - #104154 (Change `bindings_with_variant_name` to deny-by-default) - #104347 (diagnostics: suggest changing `s@self::{macro}`@::macro`` for exported) - #104672 (Unify stable and unstable sort implementations in same core module) - #107048 (check for x version updates) - #107061 (Implement some more new solver candidates and fix some bugs) - #107095 (rustdoc: remove redundant CSS selector `.sidebar .current`) - #107112 (Fix typo in opaque_types.rs) - #107124 (fix check macro expansion) - #107131 (rustdoc: use CSS inline layout for radio line instead of flexbox) Failed merges: r? `@ghost` `@rustbot` modify labels: rollup
This commit is contained in:
commit
e098eb17e1
@ -5608,6 +5608,7 @@ dependencies = [
|
|||||||
"lazy_static",
|
"lazy_static",
|
||||||
"miropt-test-tools",
|
"miropt-test-tools",
|
||||||
"regex",
|
"regex",
|
||||||
|
"semver",
|
||||||
"termcolor",
|
"termcolor",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
@ -207,7 +207,12 @@ pub fn drain_enumerated<'a, R: RangeBounds<usize>>(
|
|||||||
&'a mut self,
|
&'a mut self,
|
||||||
range: R,
|
range: R,
|
||||||
) -> impl Iterator<Item = (I, T)> + 'a {
|
) -> impl Iterator<Item = (I, T)> + 'a {
|
||||||
self.raw.drain(range).enumerate().map(|(n, t)| (I::new(n), t))
|
let begin = match range.start_bound() {
|
||||||
|
std::ops::Bound::Included(i) => *i,
|
||||||
|
std::ops::Bound::Excluded(i) => i.checked_add(1).unwrap(),
|
||||||
|
std::ops::Bound::Unbounded => 0,
|
||||||
|
};
|
||||||
|
self.raw.drain(range).enumerate().map(move |(n, t)| (I::new(begin + n), t))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -479,7 +479,7 @@ fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ty::Alias(ty::Opaque, ty::AliasTy { def_id, ref substs, .. }) => {
|
ty::Alias(ty::Opaque, ty::AliasTy { def_id, ref substs, .. }) => {
|
||||||
// Skip lifetime paramters that are not captures.
|
// Skip lifetime parameters that are not captures.
|
||||||
let variances = self.tcx.variances_of(*def_id);
|
let variances = self.tcx.variances_of(*def_id);
|
||||||
|
|
||||||
for (v, s) in std::iter::zip(variances, substs.iter()) {
|
for (v, s) in std::iter::zip(variances, substs.iter()) {
|
||||||
@ -492,7 +492,7 @@ fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
|
|||||||
ty::Alias(ty::Projection, proj)
|
ty::Alias(ty::Projection, proj)
|
||||||
if self.tcx.def_kind(proj.def_id) == DefKind::ImplTraitPlaceholder =>
|
if self.tcx.def_kind(proj.def_id) == DefKind::ImplTraitPlaceholder =>
|
||||||
{
|
{
|
||||||
// Skip lifetime paramters that are not captures.
|
// Skip lifetime parameters that are not captures.
|
||||||
let variances = self.tcx.variances_of(proj.def_id);
|
let variances = self.tcx.variances_of(proj.def_id);
|
||||||
|
|
||||||
for (v, s) in std::iter::zip(variances, proj.substs.iter()) {
|
for (v, s) in std::iter::zip(variances, proj.substs.iter()) {
|
||||||
|
@ -708,7 +708,7 @@
|
|||||||
///
|
///
|
||||||
/// ### Example
|
/// ### Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust,compile_fail
|
||||||
/// pub enum Enum {
|
/// pub enum Enum {
|
||||||
/// Foo,
|
/// Foo,
|
||||||
/// Bar,
|
/// Bar,
|
||||||
@ -743,7 +743,7 @@
|
|||||||
/// [identifier pattern]: https://doc.rust-lang.org/reference/patterns.html#identifier-patterns
|
/// [identifier pattern]: https://doc.rust-lang.org/reference/patterns.html#identifier-patterns
|
||||||
/// [path pattern]: https://doc.rust-lang.org/reference/patterns.html#path-patterns
|
/// [path pattern]: https://doc.rust-lang.org/reference/patterns.html#path-patterns
|
||||||
pub BINDINGS_WITH_VARIANT_NAME,
|
pub BINDINGS_WITH_VARIANT_NAME,
|
||||||
Warn,
|
Deny,
|
||||||
"detects pattern bindings with the same name as one of the matched variants"
|
"detects pattern bindings with the same name as one of the matched variants"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2125,9 +2125,15 @@ pub(crate) fn check_for_module_export_macro(
|
|||||||
|
|
||||||
let source_map = self.r.session.source_map();
|
let source_map = self.r.session.source_map();
|
||||||
|
|
||||||
|
// Make sure this is actually crate-relative.
|
||||||
|
let is_definitely_crate = import
|
||||||
|
.module_path
|
||||||
|
.first()
|
||||||
|
.map_or(false, |f| f.ident.name != kw::SelfLower && f.ident.name != kw::Super);
|
||||||
|
|
||||||
// Add the import to the start, with a `{` if required.
|
// Add the import to the start, with a `{` if required.
|
||||||
let start_point = source_map.start_point(after_crate_name);
|
let start_point = source_map.start_point(after_crate_name);
|
||||||
if let Ok(start_snippet) = source_map.span_to_snippet(start_point) {
|
if is_definitely_crate && let Ok(start_snippet) = source_map.span_to_snippet(start_point) {
|
||||||
corrections.push((
|
corrections.push((
|
||||||
start_point,
|
start_point,
|
||||||
if has_nested {
|
if has_nested {
|
||||||
@ -2139,11 +2145,17 @@ pub(crate) fn check_for_module_export_macro(
|
|||||||
format!("{{{}, {}", import_snippet, start_snippet)
|
format!("{{{}, {}", import_snippet, start_snippet)
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
|
||||||
|
|
||||||
// Add a `};` to the end if nested, matching the `{` added at the start.
|
// Add a `};` to the end if nested, matching the `{` added at the start.
|
||||||
if !has_nested {
|
if !has_nested {
|
||||||
corrections.push((source_map.end_point(after_crate_name), "};".to_string()));
|
corrections.push((source_map.end_point(after_crate_name), "};".to_string()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If the root import is module-relative, add the import separately
|
||||||
|
corrections.push((
|
||||||
|
import.use_span.shrink_to_lo(),
|
||||||
|
format!("use {module_name}::{import_snippet};\n"),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! Code shared by trait and projection goals for candidate assembly.
|
//! Code shared by trait and projection goals for candidate assembly.
|
||||||
|
|
||||||
use super::infcx_ext::InferCtxtExt;
|
use super::infcx_ext::InferCtxtExt;
|
||||||
use super::{CanonicalResponse, EvalCtxt, Goal, QueryResult};
|
use super::{CanonicalResponse, Certainty, EvalCtxt, Goal, MaybeCause, QueryResult};
|
||||||
use rustc_hir::def_id::DefId;
|
use rustc_hir::def_id::DefId;
|
||||||
use rustc_infer::traits::query::NoSolution;
|
use rustc_infer::traits::query::NoSolution;
|
||||||
use rustc_infer::traits::util::elaborate_predicates;
|
use rustc_infer::traits::util::elaborate_predicates;
|
||||||
@ -79,7 +79,7 @@ pub(super) enum CandidateSource {
|
|||||||
AliasBound(usize),
|
AliasBound(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) trait GoalKind<'tcx>: TypeFoldable<'tcx> + Copy {
|
pub(super) trait GoalKind<'tcx>: TypeFoldable<'tcx> + Copy + Eq {
|
||||||
fn self_ty(self) -> Ty<'tcx>;
|
fn self_ty(self) -> Ty<'tcx>;
|
||||||
|
|
||||||
fn with_self_ty(self, tcx: TyCtxt<'tcx>, self_ty: Ty<'tcx>) -> Self;
|
fn with_self_ty(self, tcx: TyCtxt<'tcx>, self_ty: Ty<'tcx>) -> Self;
|
||||||
@ -117,6 +117,22 @@ fn consider_builtin_copy_clone_candidate(
|
|||||||
ecx: &mut EvalCtxt<'_, 'tcx>,
|
ecx: &mut EvalCtxt<'_, 'tcx>,
|
||||||
goal: Goal<'tcx, Self>,
|
goal: Goal<'tcx, Self>,
|
||||||
) -> QueryResult<'tcx>;
|
) -> QueryResult<'tcx>;
|
||||||
|
|
||||||
|
fn consider_builtin_pointer_sized_candidate(
|
||||||
|
ecx: &mut EvalCtxt<'_, 'tcx>,
|
||||||
|
goal: Goal<'tcx, Self>,
|
||||||
|
) -> QueryResult<'tcx>;
|
||||||
|
|
||||||
|
fn consider_builtin_fn_trait_candidates(
|
||||||
|
ecx: &mut EvalCtxt<'_, 'tcx>,
|
||||||
|
goal: Goal<'tcx, Self>,
|
||||||
|
kind: ty::ClosureKind,
|
||||||
|
) -> QueryResult<'tcx>;
|
||||||
|
|
||||||
|
fn consider_builtin_tuple_candidate(
|
||||||
|
ecx: &mut EvalCtxt<'_, 'tcx>,
|
||||||
|
goal: Goal<'tcx, Self>,
|
||||||
|
) -> QueryResult<'tcx>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'tcx> EvalCtxt<'_, 'tcx> {
|
impl<'tcx> EvalCtxt<'_, 'tcx> {
|
||||||
@ -124,6 +140,20 @@ pub(super) fn assemble_and_evaluate_candidates<G: GoalKind<'tcx>>(
|
|||||||
&mut self,
|
&mut self,
|
||||||
goal: Goal<'tcx, G>,
|
goal: Goal<'tcx, G>,
|
||||||
) -> Vec<Candidate<'tcx>> {
|
) -> Vec<Candidate<'tcx>> {
|
||||||
|
debug_assert_eq!(goal, self.infcx.resolve_vars_if_possible(goal));
|
||||||
|
|
||||||
|
// HACK: `_: Trait` is ambiguous, because it may be satisfied via a builtin rule,
|
||||||
|
// object bound, alias bound, etc. We are unable to determine this until we can at
|
||||||
|
// least structually resolve the type one layer.
|
||||||
|
if goal.predicate.self_ty().is_ty_var() {
|
||||||
|
return vec![Candidate {
|
||||||
|
source: CandidateSource::BuiltinImpl,
|
||||||
|
result: self
|
||||||
|
.make_canonical_response(Certainty::Maybe(MaybeCause::Ambiguity))
|
||||||
|
.unwrap(),
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
let mut candidates = Vec::new();
|
let mut candidates = Vec::new();
|
||||||
|
|
||||||
self.assemble_candidates_after_normalizing_self_ty(goal, &mut candidates);
|
self.assemble_candidates_after_normalizing_self_ty(goal, &mut candidates);
|
||||||
@ -169,6 +199,7 @@ fn assemble_candidates_after_normalizing_self_ty<G: GoalKind<'tcx>>(
|
|||||||
Ok((_, certainty)) => certainty,
|
Ok((_, certainty)) => certainty,
|
||||||
Err(NoSolution) => return,
|
Err(NoSolution) => return,
|
||||||
};
|
};
|
||||||
|
let normalized_ty = self.infcx.resolve_vars_if_possible(normalized_ty);
|
||||||
|
|
||||||
// NOTE: Alternatively we could call `evaluate_goal` here and only have a `Normalized` candidate.
|
// NOTE: Alternatively we could call `evaluate_goal` here and only have a `Normalized` candidate.
|
||||||
// This doesn't work as long as we use `CandidateSource` in winnowing.
|
// This doesn't work as long as we use `CandidateSource` in winnowing.
|
||||||
@ -224,6 +255,12 @@ fn assemble_builtin_impl_candidates<G: GoalKind<'tcx>>(
|
|||||||
|| lang_items.clone_trait() == Some(trait_def_id)
|
|| lang_items.clone_trait() == Some(trait_def_id)
|
||||||
{
|
{
|
||||||
G::consider_builtin_copy_clone_candidate(self, goal)
|
G::consider_builtin_copy_clone_candidate(self, goal)
|
||||||
|
} else if lang_items.pointer_sized() == Some(trait_def_id) {
|
||||||
|
G::consider_builtin_pointer_sized_candidate(self, goal)
|
||||||
|
} else if let Some(kind) = self.tcx().fn_trait_kind_from_def_id(trait_def_id) {
|
||||||
|
G::consider_builtin_fn_trait_candidates(self, goal, kind)
|
||||||
|
} else if lang_items.tuple_trait() == Some(trait_def_id) {
|
||||||
|
G::consider_builtin_tuple_candidate(self, goal)
|
||||||
} else {
|
} else {
|
||||||
Err(NoSolution)
|
Err(NoSolution)
|
||||||
};
|
};
|
||||||
|
@ -52,7 +52,7 @@ fn select_all_or_error(&mut self, infcx: &InferCtxt<'tcx>) -> Vec<FulfillmentErr
|
|||||||
.drain(..)
|
.drain(..)
|
||||||
.map(|obligation| FulfillmentError {
|
.map(|obligation| FulfillmentError {
|
||||||
obligation: obligation.clone(),
|
obligation: obligation.clone(),
|
||||||
code: FulfillmentErrorCode::CodeSelectionError(SelectionError::Unimplemented),
|
code: FulfillmentErrorCode::CodeAmbiguity,
|
||||||
root_obligation: obligation,
|
root_obligation: obligation,
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
@ -75,7 +75,9 @@ fn select_where_possible(&mut self, infcx: &InferCtxt<'tcx>) -> Vec<FulfillmentE
|
|||||||
Err(NoSolution) => {
|
Err(NoSolution) => {
|
||||||
errors.push(FulfillmentError {
|
errors.push(FulfillmentError {
|
||||||
obligation: obligation.clone(),
|
obligation: obligation.clone(),
|
||||||
code: FulfillmentErrorCode::CodeAmbiguity,
|
code: FulfillmentErrorCode::CodeSelectionError(
|
||||||
|
SelectionError::Unimplemented,
|
||||||
|
),
|
||||||
root_obligation: obligation,
|
root_obligation: obligation,
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use super::assembly::{self, Candidate, CandidateSource};
|
use super::assembly::{self, Candidate, CandidateSource};
|
||||||
use super::infcx_ext::InferCtxtExt;
|
use super::infcx_ext::InferCtxtExt;
|
||||||
|
use super::trait_goals::structural_traits;
|
||||||
use super::{Certainty, EvalCtxt, Goal, MaybeCause, QueryResult};
|
use super::{Certainty, EvalCtxt, Goal, MaybeCause, QueryResult};
|
||||||
use rustc_errors::ErrorGuaranteed;
|
use rustc_errors::ErrorGuaranteed;
|
||||||
use rustc_hir::def::DefKind;
|
use rustc_hir::def::DefKind;
|
||||||
@ -11,9 +12,9 @@
|
|||||||
use rustc_infer::traits::specialization_graph::LeafDef;
|
use rustc_infer::traits::specialization_graph::LeafDef;
|
||||||
use rustc_infer::traits::Reveal;
|
use rustc_infer::traits::Reveal;
|
||||||
use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
|
use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
|
||||||
use rustc_middle::ty::TypeVisitable;
|
|
||||||
use rustc_middle::ty::{self, Ty, TyCtxt};
|
use rustc_middle::ty::{self, Ty, TyCtxt};
|
||||||
use rustc_middle::ty::{ProjectionPredicate, TypeSuperVisitable, TypeVisitor};
|
use rustc_middle::ty::{ProjectionPredicate, TypeSuperVisitable, TypeVisitor};
|
||||||
|
use rustc_middle::ty::{ToPredicate, TypeVisitable};
|
||||||
use rustc_span::DUMMY_SP;
|
use rustc_span::DUMMY_SP;
|
||||||
use std::iter;
|
use std::iter;
|
||||||
use std::ops::ControlFlow;
|
use std::ops::ControlFlow;
|
||||||
@ -351,6 +352,46 @@ fn consider_builtin_copy_clone_candidate(
|
|||||||
) -> QueryResult<'tcx> {
|
) -> QueryResult<'tcx> {
|
||||||
bug!("`Copy`/`Clone` does not have an associated type: {:?}", goal);
|
bug!("`Copy`/`Clone` does not have an associated type: {:?}", goal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn consider_builtin_pointer_sized_candidate(
|
||||||
|
_ecx: &mut EvalCtxt<'_, 'tcx>,
|
||||||
|
goal: Goal<'tcx, Self>,
|
||||||
|
) -> QueryResult<'tcx> {
|
||||||
|
bug!("`PointerSized` does not have an associated type: {:?}", goal);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn consider_builtin_fn_trait_candidates(
|
||||||
|
ecx: &mut EvalCtxt<'_, 'tcx>,
|
||||||
|
goal: Goal<'tcx, Self>,
|
||||||
|
goal_kind: ty::ClosureKind,
|
||||||
|
) -> QueryResult<'tcx> {
|
||||||
|
if let Some(tupled_inputs_and_output) =
|
||||||
|
structural_traits::extract_tupled_inputs_and_output_from_callable(
|
||||||
|
ecx.tcx(),
|
||||||
|
goal.predicate.self_ty(),
|
||||||
|
goal_kind,
|
||||||
|
)?
|
||||||
|
{
|
||||||
|
let pred = tupled_inputs_and_output
|
||||||
|
.map_bound(|(inputs, output)| ty::ProjectionPredicate {
|
||||||
|
projection_ty: ecx
|
||||||
|
.tcx()
|
||||||
|
.mk_alias_ty(goal.predicate.def_id(), [goal.predicate.self_ty(), inputs]),
|
||||||
|
term: output.into(),
|
||||||
|
})
|
||||||
|
.to_predicate(ecx.tcx());
|
||||||
|
Self::consider_assumption(ecx, goal, pred)
|
||||||
|
} else {
|
||||||
|
ecx.make_canonical_response(Certainty::Maybe(MaybeCause::Ambiguity))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn consider_builtin_tuple_candidate(
|
||||||
|
_ecx: &mut EvalCtxt<'_, 'tcx>,
|
||||||
|
goal: Goal<'tcx, Self>,
|
||||||
|
) -> QueryResult<'tcx> {
|
||||||
|
bug!("`Tuple` does not have an associated type: {:?}", goal);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This behavior is also implemented in `rustc_ty_utils` and in the old `project` code.
|
/// This behavior is also implemented in `rustc_ty_utils` and in the old `project` code.
|
||||||
|
@ -4,16 +4,16 @@
|
|||||||
|
|
||||||
use super::assembly::{self, Candidate, CandidateSource};
|
use super::assembly::{self, Candidate, CandidateSource};
|
||||||
use super::infcx_ext::InferCtxtExt;
|
use super::infcx_ext::InferCtxtExt;
|
||||||
use super::{EvalCtxt, Goal, QueryResult};
|
use super::{Certainty, EvalCtxt, Goal, MaybeCause, QueryResult};
|
||||||
use rustc_hir::def_id::DefId;
|
use rustc_hir::def_id::DefId;
|
||||||
use rustc_infer::infer::InferCtxt;
|
use rustc_infer::infer::InferCtxt;
|
||||||
use rustc_infer::traits::query::NoSolution;
|
use rustc_infer::traits::query::NoSolution;
|
||||||
use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
|
use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
|
||||||
use rustc_middle::ty::TraitPredicate;
|
use rustc_middle::ty::{self, ToPredicate, Ty, TyCtxt};
|
||||||
use rustc_middle::ty::{self, Ty, TyCtxt};
|
use rustc_middle::ty::{TraitPredicate, TypeVisitable};
|
||||||
use rustc_span::DUMMY_SP;
|
use rustc_span::DUMMY_SP;
|
||||||
|
|
||||||
mod structural_traits;
|
pub mod structural_traits;
|
||||||
|
|
||||||
impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
|
impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
|
||||||
fn self_ty(self) -> Ty<'tcx> {
|
fn self_ty(self) -> Ty<'tcx> {
|
||||||
@ -127,6 +127,64 @@ fn consider_builtin_copy_clone_candidate(
|
|||||||
structural_traits::instantiate_constituent_tys_for_copy_clone_trait,
|
structural_traits::instantiate_constituent_tys_for_copy_clone_trait,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn consider_builtin_pointer_sized_candidate(
|
||||||
|
ecx: &mut EvalCtxt<'_, 'tcx>,
|
||||||
|
goal: Goal<'tcx, Self>,
|
||||||
|
) -> QueryResult<'tcx> {
|
||||||
|
if goal.predicate.self_ty().has_non_region_infer() {
|
||||||
|
return ecx.make_canonical_response(Certainty::Maybe(MaybeCause::Ambiguity));
|
||||||
|
}
|
||||||
|
|
||||||
|
let tcx = ecx.tcx();
|
||||||
|
let self_ty = tcx.erase_regions(goal.predicate.self_ty());
|
||||||
|
|
||||||
|
if let Ok(layout) = tcx.layout_of(goal.param_env.and(self_ty))
|
||||||
|
&& let usize_layout = tcx.layout_of(ty::ParamEnv::empty().and(tcx.types.usize)).unwrap().layout
|
||||||
|
&& layout.layout.size() == usize_layout.size()
|
||||||
|
&& layout.layout.align().abi == usize_layout.align().abi
|
||||||
|
{
|
||||||
|
// FIXME: We could make this faster by making a no-constraints response
|
||||||
|
ecx.make_canonical_response(Certainty::Yes)
|
||||||
|
} else {
|
||||||
|
Err(NoSolution)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn consider_builtin_fn_trait_candidates(
|
||||||
|
ecx: &mut EvalCtxt<'_, 'tcx>,
|
||||||
|
goal: Goal<'tcx, Self>,
|
||||||
|
goal_kind: ty::ClosureKind,
|
||||||
|
) -> QueryResult<'tcx> {
|
||||||
|
if let Some(tupled_inputs_and_output) =
|
||||||
|
structural_traits::extract_tupled_inputs_and_output_from_callable(
|
||||||
|
ecx.tcx(),
|
||||||
|
goal.predicate.self_ty(),
|
||||||
|
goal_kind,
|
||||||
|
)?
|
||||||
|
{
|
||||||
|
let pred = tupled_inputs_and_output
|
||||||
|
.map_bound(|(inputs, _)| {
|
||||||
|
ecx.tcx()
|
||||||
|
.mk_trait_ref(goal.predicate.def_id(), [goal.predicate.self_ty(), inputs])
|
||||||
|
})
|
||||||
|
.to_predicate(ecx.tcx());
|
||||||
|
Self::consider_assumption(ecx, goal, pred)
|
||||||
|
} else {
|
||||||
|
ecx.make_canonical_response(Certainty::Maybe(MaybeCause::Ambiguity))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn consider_builtin_tuple_candidate(
|
||||||
|
ecx: &mut EvalCtxt<'_, 'tcx>,
|
||||||
|
goal: Goal<'tcx, Self>,
|
||||||
|
) -> QueryResult<'tcx> {
|
||||||
|
if let ty::Tuple(..) = goal.predicate.self_ty().kind() {
|
||||||
|
ecx.make_canonical_response(Certainty::Yes)
|
||||||
|
} else {
|
||||||
|
Err(NoSolution)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'tcx> EvalCtxt<'_, 'tcx> {
|
impl<'tcx> EvalCtxt<'_, 'tcx> {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use rustc_hir::{Movability, Mutability};
|
use rustc_hir::{Movability, Mutability};
|
||||||
use rustc_infer::{infer::InferCtxt, traits::query::NoSolution};
|
use rustc_infer::{infer::InferCtxt, traits::query::NoSolution};
|
||||||
use rustc_middle::ty::{self, Ty};
|
use rustc_middle::ty::{self, Ty, TyCtxt};
|
||||||
|
|
||||||
// Calculates the constituent types of a type for `auto trait` purposes.
|
// Calculates the constituent types of a type for `auto trait` purposes.
|
||||||
//
|
//
|
||||||
@ -30,10 +30,7 @@ pub(super) fn instantiate_constituent_tys_for_auto_trait<'tcx>(
|
|||||||
| ty::Foreign(..)
|
| ty::Foreign(..)
|
||||||
| ty::Alias(ty::Projection, ..)
|
| ty::Alias(ty::Projection, ..)
|
||||||
| ty::Bound(..)
|
| ty::Bound(..)
|
||||||
| ty::Infer(ty::TyVar(_)) => {
|
| ty::Infer(ty::TyVar(_)) => Err(NoSolution),
|
||||||
// FIXME: Do we need to mark anything as ambiguous here? Yeah?
|
|
||||||
Err(NoSolution)
|
|
||||||
}
|
|
||||||
|
|
||||||
ty::Infer(ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => bug!(),
|
ty::Infer(ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => bug!(),
|
||||||
|
|
||||||
@ -101,9 +98,8 @@ pub(super) fn instantiate_constituent_tys_for_sized_trait<'tcx>(
|
|||||||
| ty::Dynamic(..)
|
| ty::Dynamic(..)
|
||||||
| ty::Foreign(..)
|
| ty::Foreign(..)
|
||||||
| ty::Alias(..)
|
| ty::Alias(..)
|
||||||
| ty::Param(_) => Err(NoSolution),
|
| ty::Param(_)
|
||||||
|
| ty::Infer(ty::TyVar(_)) => Err(NoSolution),
|
||||||
ty::Infer(ty::TyVar(_)) => bug!("FIXME: ambiguous"),
|
|
||||||
|
|
||||||
ty::Placeholder(..)
|
ty::Placeholder(..)
|
||||||
| ty::Bound(..)
|
| ty::Bound(..)
|
||||||
@ -151,9 +147,8 @@ pub(super) fn instantiate_constituent_tys_for_copy_clone_trait<'tcx>(
|
|||||||
| ty::Ref(_, _, Mutability::Mut)
|
| ty::Ref(_, _, Mutability::Mut)
|
||||||
| ty::Adt(_, _)
|
| ty::Adt(_, _)
|
||||||
| ty::Alias(_, _)
|
| ty::Alias(_, _)
|
||||||
| ty::Param(_) => Err(NoSolution),
|
| ty::Param(_)
|
||||||
|
| ty::Infer(ty::TyVar(_)) => Err(NoSolution),
|
||||||
ty::Infer(ty::TyVar(_)) => bug!("FIXME: ambiguous"),
|
|
||||||
|
|
||||||
ty::Placeholder(..)
|
ty::Placeholder(..)
|
||||||
| ty::Bound(..)
|
| ty::Bound(..)
|
||||||
@ -177,3 +172,52 @@ pub(super) fn instantiate_constituent_tys_for_copy_clone_trait<'tcx>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn extract_tupled_inputs_and_output_from_callable<'tcx>(
|
||||||
|
tcx: TyCtxt<'tcx>,
|
||||||
|
self_ty: Ty<'tcx>,
|
||||||
|
goal_kind: ty::ClosureKind,
|
||||||
|
) -> Result<Option<ty::Binder<'tcx, (Ty<'tcx>, Ty<'tcx>)>>, NoSolution> {
|
||||||
|
match *self_ty.kind() {
|
||||||
|
ty::FnDef(def_id, substs) => Ok(Some(
|
||||||
|
tcx.bound_fn_sig(def_id)
|
||||||
|
.subst(tcx, substs)
|
||||||
|
.map_bound(|sig| (tcx.mk_tup(sig.inputs().iter()), sig.output())),
|
||||||
|
)),
|
||||||
|
ty::FnPtr(sig) => {
|
||||||
|
Ok(Some(sig.map_bound(|sig| (tcx.mk_tup(sig.inputs().iter()), sig.output()))))
|
||||||
|
}
|
||||||
|
ty::Closure(_, substs) => {
|
||||||
|
let closure_substs = substs.as_closure();
|
||||||
|
match closure_substs.kind_ty().to_opt_closure_kind() {
|
||||||
|
Some(closure_kind) if closure_kind.extends(goal_kind) => {}
|
||||||
|
None => return Ok(None),
|
||||||
|
_ => return Err(NoSolution),
|
||||||
|
}
|
||||||
|
Ok(Some(closure_substs.sig().map_bound(|sig| (sig.inputs()[0], sig.output()))))
|
||||||
|
}
|
||||||
|
ty::Bool
|
||||||
|
| ty::Char
|
||||||
|
| ty::Int(_)
|
||||||
|
| ty::Uint(_)
|
||||||
|
| ty::Float(_)
|
||||||
|
| ty::Adt(_, _)
|
||||||
|
| ty::Foreign(_)
|
||||||
|
| ty::Str
|
||||||
|
| ty::Array(_, _)
|
||||||
|
| ty::Slice(_)
|
||||||
|
| ty::RawPtr(_)
|
||||||
|
| ty::Ref(_, _, _)
|
||||||
|
| ty::Dynamic(_, _, _)
|
||||||
|
| ty::Generator(_, _, _)
|
||||||
|
| ty::GeneratorWitness(_)
|
||||||
|
| ty::Never
|
||||||
|
| ty::Tuple(_)
|
||||||
|
| ty::Alias(_, _)
|
||||||
|
| ty::Param(_)
|
||||||
|
| ty::Placeholder(_)
|
||||||
|
| ty::Bound(_, _)
|
||||||
|
| ty::Infer(_)
|
||||||
|
| ty::Error(_) => Err(NoSolution),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -19,10 +19,12 @@
|
|||||||
use core::mem::{self, SizedTypeProperties};
|
use core::mem::{self, SizedTypeProperties};
|
||||||
#[cfg(not(no_global_oom_handling))]
|
#[cfg(not(no_global_oom_handling))]
|
||||||
use core::ptr;
|
use core::ptr;
|
||||||
|
#[cfg(not(no_global_oom_handling))]
|
||||||
|
use core::slice::sort;
|
||||||
|
|
||||||
use crate::alloc::Allocator;
|
use crate::alloc::Allocator;
|
||||||
#[cfg(not(no_global_oom_handling))]
|
#[cfg(not(no_global_oom_handling))]
|
||||||
use crate::alloc::Global;
|
use crate::alloc::{self, Global};
|
||||||
#[cfg(not(no_global_oom_handling))]
|
#[cfg(not(no_global_oom_handling))]
|
||||||
use crate::borrow::ToOwned;
|
use crate::borrow::ToOwned;
|
||||||
use crate::boxed::Box;
|
use crate::boxed::Box;
|
||||||
@ -206,7 +208,7 @@ pub fn sort(&mut self)
|
|||||||
where
|
where
|
||||||
T: Ord,
|
T: Ord,
|
||||||
{
|
{
|
||||||
merge_sort(self, T::lt);
|
stable_sort(self, T::lt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sorts the slice with a comparator function.
|
/// Sorts the slice with a comparator function.
|
||||||
@ -262,7 +264,7 @@ pub fn sort_by<F>(&mut self, mut compare: F)
|
|||||||
where
|
where
|
||||||
F: FnMut(&T, &T) -> Ordering,
|
F: FnMut(&T, &T) -> Ordering,
|
||||||
{
|
{
|
||||||
merge_sort(self, |a, b| compare(a, b) == Less);
|
stable_sort(self, |a, b| compare(a, b) == Less);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sorts the slice with a key extraction function.
|
/// Sorts the slice with a key extraction function.
|
||||||
@ -305,7 +307,7 @@ pub fn sort_by_key<K, F>(&mut self, mut f: F)
|
|||||||
F: FnMut(&T) -> K,
|
F: FnMut(&T) -> K,
|
||||||
K: Ord,
|
K: Ord,
|
||||||
{
|
{
|
||||||
merge_sort(self, |a, b| f(a).lt(&f(b)));
|
stable_sort(self, |a, b| f(a).lt(&f(b)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sorts the slice with a key extraction function.
|
/// Sorts the slice with a key extraction function.
|
||||||
@ -812,324 +814,52 @@ fn clone_into(&self, target: &mut Vec<T>) {
|
|||||||
// Sorting
|
// Sorting
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
/// Inserts `v[0]` into pre-sorted sequence `v[1..]` so that whole `v[..]` becomes sorted.
|
#[inline]
|
||||||
///
|
|
||||||
/// This is the integral subroutine of insertion sort.
|
|
||||||
#[cfg(not(no_global_oom_handling))]
|
#[cfg(not(no_global_oom_handling))]
|
||||||
fn insert_head<T, F>(v: &mut [T], is_less: &mut F)
|
fn stable_sort<T, F>(v: &mut [T], mut is_less: F)
|
||||||
where
|
where
|
||||||
F: FnMut(&T, &T) -> bool,
|
F: FnMut(&T, &T) -> bool,
|
||||||
{
|
{
|
||||||
if v.len() >= 2 && is_less(&v[1], &v[0]) {
|
|
||||||
unsafe {
|
|
||||||
// There are three ways to implement insertion here:
|
|
||||||
//
|
|
||||||
// 1. Swap adjacent elements until the first one gets to its final destination.
|
|
||||||
// However, this way we copy data around more than is necessary. If elements are big
|
|
||||||
// structures (costly to copy), this method will be slow.
|
|
||||||
//
|
|
||||||
// 2. Iterate until the right place for the first element is found. Then shift the
|
|
||||||
// elements succeeding it to make room for it and finally place it into the
|
|
||||||
// remaining hole. This is a good method.
|
|
||||||
//
|
|
||||||
// 3. Copy the first element into a temporary variable. Iterate until the right place
|
|
||||||
// for it is found. As we go along, copy every traversed element into the slot
|
|
||||||
// preceding it. Finally, copy data from the temporary variable into the remaining
|
|
||||||
// hole. This method is very good. Benchmarks demonstrated slightly better
|
|
||||||
// performance than with the 2nd method.
|
|
||||||
//
|
|
||||||
// All methods were benchmarked, and the 3rd showed best results. So we chose that one.
|
|
||||||
let tmp = mem::ManuallyDrop::new(ptr::read(&v[0]));
|
|
||||||
|
|
||||||
// Intermediate state of the insertion process is always tracked by `hole`, which
|
|
||||||
// serves two purposes:
|
|
||||||
// 1. Protects integrity of `v` from panics in `is_less`.
|
|
||||||
// 2. Fills the remaining hole in `v` in the end.
|
|
||||||
//
|
|
||||||
// Panic safety:
|
|
||||||
//
|
|
||||||
// If `is_less` panics at any point during the process, `hole` will get dropped and
|
|
||||||
// fill the hole in `v` with `tmp`, thus ensuring that `v` still holds every object it
|
|
||||||
// initially held exactly once.
|
|
||||||
let mut hole = InsertionHole { src: &*tmp, dest: &mut v[1] };
|
|
||||||
ptr::copy_nonoverlapping(&v[1], &mut v[0], 1);
|
|
||||||
|
|
||||||
for i in 2..v.len() {
|
|
||||||
if !is_less(&v[i], &*tmp) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ptr::copy_nonoverlapping(&v[i], &mut v[i - 1], 1);
|
|
||||||
hole.dest = &mut v[i];
|
|
||||||
}
|
|
||||||
// `hole` gets dropped and thus copies `tmp` into the remaining hole in `v`.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// When dropped, copies from `src` into `dest`.
|
|
||||||
struct InsertionHole<T> {
|
|
||||||
src: *const T,
|
|
||||||
dest: *mut T,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Drop for InsertionHole<T> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
ptr::copy_nonoverlapping(self.src, self.dest, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Merges non-decreasing runs `v[..mid]` and `v[mid..]` using `buf` as temporary storage, and
|
|
||||||
/// stores the result into `v[..]`.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// The two slices must be non-empty and `mid` must be in bounds. Buffer `buf` must be long enough
|
|
||||||
/// to hold a copy of the shorter slice. Also, `T` must not be a zero-sized type.
|
|
||||||
#[cfg(not(no_global_oom_handling))]
|
|
||||||
unsafe fn merge<T, F>(v: &mut [T], mid: usize, buf: *mut T, is_less: &mut F)
|
|
||||||
where
|
|
||||||
F: FnMut(&T, &T) -> bool,
|
|
||||||
{
|
|
||||||
let len = v.len();
|
|
||||||
let v = v.as_mut_ptr();
|
|
||||||
let (v_mid, v_end) = unsafe { (v.add(mid), v.add(len)) };
|
|
||||||
|
|
||||||
// The merge process first copies the shorter run into `buf`. Then it traces the newly copied
|
|
||||||
// run and the longer run forwards (or backwards), comparing their next unconsumed elements and
|
|
||||||
// copying the lesser (or greater) one into `v`.
|
|
||||||
//
|
|
||||||
// As soon as the shorter run is fully consumed, the process is done. If the longer run gets
|
|
||||||
// consumed first, then we must copy whatever is left of the shorter run into the remaining
|
|
||||||
// hole in `v`.
|
|
||||||
//
|
|
||||||
// Intermediate state of the process is always tracked by `hole`, which serves two purposes:
|
|
||||||
// 1. Protects integrity of `v` from panics in `is_less`.
|
|
||||||
// 2. Fills the remaining hole in `v` if the longer run gets consumed first.
|
|
||||||
//
|
|
||||||
// Panic safety:
|
|
||||||
//
|
|
||||||
// If `is_less` panics at any point during the process, `hole` will get dropped and fill the
|
|
||||||
// hole in `v` with the unconsumed range in `buf`, thus ensuring that `v` still holds every
|
|
||||||
// object it initially held exactly once.
|
|
||||||
let mut hole;
|
|
||||||
|
|
||||||
if mid <= len - mid {
|
|
||||||
// The left run is shorter.
|
|
||||||
unsafe {
|
|
||||||
ptr::copy_nonoverlapping(v, buf, mid);
|
|
||||||
hole = MergeHole { start: buf, end: buf.add(mid), dest: v };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initially, these pointers point to the beginnings of their arrays.
|
|
||||||
let left = &mut hole.start;
|
|
||||||
let mut right = v_mid;
|
|
||||||
let out = &mut hole.dest;
|
|
||||||
|
|
||||||
while *left < hole.end && right < v_end {
|
|
||||||
// Consume the lesser side.
|
|
||||||
// If equal, prefer the left run to maintain stability.
|
|
||||||
unsafe {
|
|
||||||
let to_copy = if is_less(&*right, &**left) {
|
|
||||||
get_and_increment(&mut right)
|
|
||||||
} else {
|
|
||||||
get_and_increment(left)
|
|
||||||
};
|
|
||||||
ptr::copy_nonoverlapping(to_copy, get_and_increment(out), 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// The right run is shorter.
|
|
||||||
unsafe {
|
|
||||||
ptr::copy_nonoverlapping(v_mid, buf, len - mid);
|
|
||||||
hole = MergeHole { start: buf, end: buf.add(len - mid), dest: v_mid };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initially, these pointers point past the ends of their arrays.
|
|
||||||
let left = &mut hole.dest;
|
|
||||||
let right = &mut hole.end;
|
|
||||||
let mut out = v_end;
|
|
||||||
|
|
||||||
while v < *left && buf < *right {
|
|
||||||
// Consume the greater side.
|
|
||||||
// If equal, prefer the right run to maintain stability.
|
|
||||||
unsafe {
|
|
||||||
let to_copy = if is_less(&*right.sub(1), &*left.sub(1)) {
|
|
||||||
decrement_and_get(left)
|
|
||||||
} else {
|
|
||||||
decrement_and_get(right)
|
|
||||||
};
|
|
||||||
ptr::copy_nonoverlapping(to_copy, decrement_and_get(&mut out), 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Finally, `hole` gets dropped. If the shorter run was not fully consumed, whatever remains of
|
|
||||||
// it will now be copied into the hole in `v`.
|
|
||||||
|
|
||||||
unsafe fn get_and_increment<T>(ptr: &mut *mut T) -> *mut T {
|
|
||||||
let old = *ptr;
|
|
||||||
*ptr = unsafe { ptr.add(1) };
|
|
||||||
old
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn decrement_and_get<T>(ptr: &mut *mut T) -> *mut T {
|
|
||||||
*ptr = unsafe { ptr.sub(1) };
|
|
||||||
*ptr
|
|
||||||
}
|
|
||||||
|
|
||||||
// When dropped, copies the range `start..end` into `dest..`.
|
|
||||||
struct MergeHole<T> {
|
|
||||||
start: *mut T,
|
|
||||||
end: *mut T,
|
|
||||||
dest: *mut T,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Drop for MergeHole<T> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
// `T` is not a zero-sized type, and these are pointers into a slice's elements.
|
|
||||||
unsafe {
|
|
||||||
let len = self.end.sub_ptr(self.start);
|
|
||||||
ptr::copy_nonoverlapping(self.start, self.dest, len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This merge sort borrows some (but not all) ideas from TimSort, which is described in detail
|
|
||||||
/// [here](https://github.com/python/cpython/blob/main/Objects/listsort.txt).
|
|
||||||
///
|
|
||||||
/// The algorithm identifies strictly descending and non-descending subsequences, which are called
|
|
||||||
/// natural runs. There is a stack of pending runs yet to be merged. Each newly found run is pushed
|
|
||||||
/// onto the stack, and then some pairs of adjacent runs are merged until these two invariants are
|
|
||||||
/// satisfied:
|
|
||||||
///
|
|
||||||
/// 1. for every `i` in `1..runs.len()`: `runs[i - 1].len > runs[i].len`
|
|
||||||
/// 2. for every `i` in `2..runs.len()`: `runs[i - 2].len > runs[i - 1].len + runs[i].len`
|
|
||||||
///
|
|
||||||
/// The invariants ensure that the total running time is *O*(*n* \* log(*n*)) worst-case.
|
|
||||||
#[cfg(not(no_global_oom_handling))]
|
|
||||||
fn merge_sort<T, F>(v: &mut [T], mut is_less: F)
|
|
||||||
where
|
|
||||||
F: FnMut(&T, &T) -> bool,
|
|
||||||
{
|
|
||||||
// Slices of up to this length get sorted using insertion sort.
|
|
||||||
const MAX_INSERTION: usize = 20;
|
|
||||||
// Very short runs are extended using insertion sort to span at least this many elements.
|
|
||||||
const MIN_RUN: usize = 10;
|
|
||||||
|
|
||||||
// Sorting has no meaningful behavior on zero-sized types.
|
|
||||||
if T::IS_ZST {
|
if T::IS_ZST {
|
||||||
|
// Sorting has no meaningful behavior on zero-sized types. Do nothing.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let len = v.len();
|
let elem_alloc_fn = |len: usize| -> *mut T {
|
||||||
|
// SAFETY: Creating the layout is safe as long as merge_sort never calls this with len >
|
||||||
|
// v.len(). Alloc in general will only be used as 'shadow-region' to store temporary swap
|
||||||
|
// elements.
|
||||||
|
unsafe { alloc::alloc(alloc::Layout::array::<T>(len).unwrap_unchecked()) as *mut T }
|
||||||
|
};
|
||||||
|
|
||||||
// Short arrays get sorted in-place via insertion sort to avoid allocations.
|
let elem_dealloc_fn = |buf_ptr: *mut T, len: usize| {
|
||||||
if len <= MAX_INSERTION {
|
// SAFETY: Creating the layout is safe as long as merge_sort never calls this with len >
|
||||||
if len >= 2 {
|
// v.len(). The caller must ensure that buf_ptr was created by elem_alloc_fn with the same
|
||||||
for i in (0..len - 1).rev() {
|
// len.
|
||||||
insert_head(&mut v[i..], &mut is_less);
|
unsafe {
|
||||||
}
|
alloc::dealloc(buf_ptr as *mut u8, alloc::Layout::array::<T>(len).unwrap_unchecked());
|
||||||
}
|
}
|
||||||
return;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
// Allocate a buffer to use as scratch memory. We keep the length 0 so we can keep in it
|
let run_alloc_fn = |len: usize| -> *mut sort::TimSortRun {
|
||||||
// shallow copies of the contents of `v` without risking the dtors running on copies if
|
// SAFETY: Creating the layout is safe as long as merge_sort never calls this with an
|
||||||
// `is_less` panics. When merging two sorted runs, this buffer holds a copy of the shorter run,
|
// obscene length or 0.
|
||||||
// which will always have length at most `len / 2`.
|
unsafe {
|
||||||
let mut buf = Vec::with_capacity(len / 2);
|
alloc::alloc(alloc::Layout::array::<sort::TimSortRun>(len).unwrap_unchecked())
|
||||||
|
as *mut sort::TimSortRun
|
||||||
// In order to identify natural runs in `v`, we traverse it backwards. That might seem like a
|
|
||||||
// strange decision, but consider the fact that merges more often go in the opposite direction
|
|
||||||
// (forwards). According to benchmarks, merging forwards is slightly faster than merging
|
|
||||||
// backwards. To conclude, identifying runs by traversing backwards improves performance.
|
|
||||||
let mut runs = vec![];
|
|
||||||
let mut end = len;
|
|
||||||
while end > 0 {
|
|
||||||
// Find the next natural run, and reverse it if it's strictly descending.
|
|
||||||
let mut start = end - 1;
|
|
||||||
if start > 0 {
|
|
||||||
start -= 1;
|
|
||||||
unsafe {
|
|
||||||
if is_less(v.get_unchecked(start + 1), v.get_unchecked(start)) {
|
|
||||||
while start > 0 && is_less(v.get_unchecked(start), v.get_unchecked(start - 1)) {
|
|
||||||
start -= 1;
|
|
||||||
}
|
|
||||||
v[start..end].reverse();
|
|
||||||
} else {
|
|
||||||
while start > 0 && !is_less(v.get_unchecked(start), v.get_unchecked(start - 1))
|
|
||||||
{
|
|
||||||
start -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Insert some more elements into the run if it's too short. Insertion sort is faster than
|
let run_dealloc_fn = |buf_ptr: *mut sort::TimSortRun, len: usize| {
|
||||||
// merge sort on short sequences, so this significantly improves performance.
|
// SAFETY: The caller must ensure that buf_ptr was created by elem_alloc_fn with the same
|
||||||
while start > 0 && end - start < MIN_RUN {
|
// len.
|
||||||
start -= 1;
|
unsafe {
|
||||||
insert_head(&mut v[start..end], &mut is_less);
|
alloc::dealloc(
|
||||||
|
buf_ptr as *mut u8,
|
||||||
|
alloc::Layout::array::<sort::TimSortRun>(len).unwrap_unchecked(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Push this run onto the stack.
|
sort::merge_sort(v, &mut is_less, elem_alloc_fn, elem_dealloc_fn, run_alloc_fn, run_dealloc_fn);
|
||||||
runs.push(Run { start, len: end - start });
|
|
||||||
end = start;
|
|
||||||
|
|
||||||
// Merge some pairs of adjacent runs to satisfy the invariants.
|
|
||||||
while let Some(r) = collapse(&runs) {
|
|
||||||
let left = runs[r + 1];
|
|
||||||
let right = runs[r];
|
|
||||||
unsafe {
|
|
||||||
merge(
|
|
||||||
&mut v[left.start..right.start + right.len],
|
|
||||||
left.len,
|
|
||||||
buf.as_mut_ptr(),
|
|
||||||
&mut is_less,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
runs[r] = Run { start: left.start, len: left.len + right.len };
|
|
||||||
runs.remove(r + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally, exactly one run must remain in the stack.
|
|
||||||
debug_assert!(runs.len() == 1 && runs[0].start == 0 && runs[0].len == len);
|
|
||||||
|
|
||||||
// Examines the stack of runs and identifies the next pair of runs to merge. More specifically,
|
|
||||||
// if `Some(r)` is returned, that means `runs[r]` and `runs[r + 1]` must be merged next. If the
|
|
||||||
// algorithm should continue building a new run instead, `None` is returned.
|
|
||||||
//
|
|
||||||
// TimSort is infamous for its buggy implementations, as described here:
|
|
||||||
// http://envisage-project.eu/timsort-specification-and-verification/
|
|
||||||
//
|
|
||||||
// The gist of the story is: we must enforce the invariants on the top four runs on the stack.
|
|
||||||
// Enforcing them on just top three is not sufficient to ensure that the invariants will still
|
|
||||||
// hold for *all* runs in the stack.
|
|
||||||
//
|
|
||||||
// This function correctly checks invariants for the top four runs. Additionally, if the top
|
|
||||||
// run starts at index 0, it will always demand a merge operation until the stack is fully
|
|
||||||
// collapsed, in order to complete the sort.
|
|
||||||
#[inline]
|
|
||||||
fn collapse(runs: &[Run]) -> Option<usize> {
|
|
||||||
let n = runs.len();
|
|
||||||
if n >= 2
|
|
||||||
&& (runs[n - 1].start == 0
|
|
||||||
|| runs[n - 2].len <= runs[n - 1].len
|
|
||||||
|| (n >= 3 && runs[n - 3].len <= runs[n - 2].len + runs[n - 1].len)
|
|
||||||
|| (n >= 4 && runs[n - 4].len <= runs[n - 3].len + runs[n - 2].len))
|
|
||||||
{
|
|
||||||
if n >= 3 && runs[n - 3].len < runs[n - 1].len { Some(n - 3) } else { Some(n - 2) }
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
struct Run {
|
|
||||||
start: usize,
|
|
||||||
len: usize,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -29,13 +29,19 @@
|
|||||||
/// Pure rust memchr implementation, taken from rust-memchr
|
/// Pure rust memchr implementation, taken from rust-memchr
|
||||||
pub mod memchr;
|
pub mod memchr;
|
||||||
|
|
||||||
|
#[unstable(
|
||||||
|
feature = "slice_internals",
|
||||||
|
issue = "none",
|
||||||
|
reason = "exposed from core to be reused in std;"
|
||||||
|
)]
|
||||||
|
pub mod sort;
|
||||||
|
|
||||||
mod ascii;
|
mod ascii;
|
||||||
mod cmp;
|
mod cmp;
|
||||||
mod index;
|
mod index;
|
||||||
mod iter;
|
mod iter;
|
||||||
mod raw;
|
mod raw;
|
||||||
mod rotate;
|
mod rotate;
|
||||||
mod sort;
|
|
||||||
mod specialize;
|
mod specialize;
|
||||||
|
|
||||||
#[stable(feature = "rust1", since = "1.0.0")]
|
#[stable(feature = "rust1", since = "1.0.0")]
|
||||||
|
@ -5,6 +5,9 @@
|
|||||||
//!
|
//!
|
||||||
//! Unstable sorting is compatible with core because it doesn't allocate memory, unlike our
|
//! Unstable sorting is compatible with core because it doesn't allocate memory, unlike our
|
||||||
//! stable sorting implementation.
|
//! stable sorting implementation.
|
||||||
|
//!
|
||||||
|
//! In addition it also contains the core logic of the stable sort used by `slice::sort` based on
|
||||||
|
//! TimSort.
|
||||||
|
|
||||||
use crate::cmp;
|
use crate::cmp;
|
||||||
use crate::mem::{self, MaybeUninit, SizedTypeProperties};
|
use crate::mem::{self, MaybeUninit, SizedTypeProperties};
|
||||||
@ -905,6 +908,7 @@ fn partition_at_index_loop<'a, T, F>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reorder the slice such that the element at `index` is at its final sorted position.
|
||||||
pub fn partition_at_index<T, F>(
|
pub fn partition_at_index<T, F>(
|
||||||
v: &mut [T],
|
v: &mut [T],
|
||||||
index: usize,
|
index: usize,
|
||||||
@ -949,3 +953,513 @@ pub fn partition_at_index<T, F>(
|
|||||||
let pivot = &mut pivot[0];
|
let pivot = &mut pivot[0];
|
||||||
(left, pivot, right)
|
(left, pivot, right)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Inserts `v[0]` into pre-sorted sequence `v[1..]` so that whole `v[..]` becomes sorted.
|
||||||
|
///
|
||||||
|
/// This is the integral subroutine of insertion sort.
|
||||||
|
fn insert_head<T, F>(v: &mut [T], is_less: &mut F)
|
||||||
|
where
|
||||||
|
F: FnMut(&T, &T) -> bool,
|
||||||
|
{
|
||||||
|
if v.len() >= 2 && is_less(&v[1], &v[0]) {
|
||||||
|
// SAFETY: Copy tmp back even if panic, and ensure unique observation.
|
||||||
|
unsafe {
|
||||||
|
// There are three ways to implement insertion here:
|
||||||
|
//
|
||||||
|
// 1. Swap adjacent elements until the first one gets to its final destination.
|
||||||
|
// However, this way we copy data around more than is necessary. If elements are big
|
||||||
|
// structures (costly to copy), this method will be slow.
|
||||||
|
//
|
||||||
|
// 2. Iterate until the right place for the first element is found. Then shift the
|
||||||
|
// elements succeeding it to make room for it and finally place it into the
|
||||||
|
// remaining hole. This is a good method.
|
||||||
|
//
|
||||||
|
// 3. Copy the first element into a temporary variable. Iterate until the right place
|
||||||
|
// for it is found. As we go along, copy every traversed element into the slot
|
||||||
|
// preceding it. Finally, copy data from the temporary variable into the remaining
|
||||||
|
// hole. This method is very good. Benchmarks demonstrated slightly better
|
||||||
|
// performance than with the 2nd method.
|
||||||
|
//
|
||||||
|
// All methods were benchmarked, and the 3rd showed best results. So we chose that one.
|
||||||
|
let tmp = mem::ManuallyDrop::new(ptr::read(&v[0]));
|
||||||
|
|
||||||
|
// Intermediate state of the insertion process is always tracked by `hole`, which
|
||||||
|
// serves two purposes:
|
||||||
|
// 1. Protects integrity of `v` from panics in `is_less`.
|
||||||
|
// 2. Fills the remaining hole in `v` in the end.
|
||||||
|
//
|
||||||
|
// Panic safety:
|
||||||
|
//
|
||||||
|
// If `is_less` panics at any point during the process, `hole` will get dropped and
|
||||||
|
// fill the hole in `v` with `tmp`, thus ensuring that `v` still holds every object it
|
||||||
|
// initially held exactly once.
|
||||||
|
let mut hole = InsertionHole { src: &*tmp, dest: &mut v[1] };
|
||||||
|
ptr::copy_nonoverlapping(&v[1], &mut v[0], 1);
|
||||||
|
|
||||||
|
for i in 2..v.len() {
|
||||||
|
if !is_less(&v[i], &*tmp) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ptr::copy_nonoverlapping(&v[i], &mut v[i - 1], 1);
|
||||||
|
hole.dest = &mut v[i];
|
||||||
|
}
|
||||||
|
// `hole` gets dropped and thus copies `tmp` into the remaining hole in `v`.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When dropped, copies from `src` into `dest`.
|
||||||
|
struct InsertionHole<T> {
|
||||||
|
src: *const T,
|
||||||
|
dest: *mut T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Drop for InsertionHole<T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// SAFETY: The caller must ensure that src and dest are correctly set.
|
||||||
|
unsafe {
|
||||||
|
ptr::copy_nonoverlapping(self.src, self.dest, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Merges non-decreasing runs `v[..mid]` and `v[mid..]` using `buf` as temporary storage, and
|
||||||
|
/// stores the result into `v[..]`.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The two slices must be non-empty and `mid` must be in bounds. Buffer `buf` must be long enough
|
||||||
|
/// to hold a copy of the shorter slice. Also, `T` must not be a zero-sized type.
|
||||||
|
unsafe fn merge<T, F>(v: &mut [T], mid: usize, buf: *mut T, is_less: &mut F)
|
||||||
|
where
|
||||||
|
F: FnMut(&T, &T) -> bool,
|
||||||
|
{
|
||||||
|
let len = v.len();
|
||||||
|
let v = v.as_mut_ptr();
|
||||||
|
|
||||||
|
// SAFETY: mid and len must be in-bounds of v.
|
||||||
|
let (v_mid, v_end) = unsafe { (v.add(mid), v.add(len)) };
|
||||||
|
|
||||||
|
// The merge process first copies the shorter run into `buf`. Then it traces the newly copied
|
||||||
|
// run and the longer run forwards (or backwards), comparing their next unconsumed elements and
|
||||||
|
// copying the lesser (or greater) one into `v`.
|
||||||
|
//
|
||||||
|
// As soon as the shorter run is fully consumed, the process is done. If the longer run gets
|
||||||
|
// consumed first, then we must copy whatever is left of the shorter run into the remaining
|
||||||
|
// hole in `v`.
|
||||||
|
//
|
||||||
|
// Intermediate state of the process is always tracked by `hole`, which serves two purposes:
|
||||||
|
// 1. Protects integrity of `v` from panics in `is_less`.
|
||||||
|
// 2. Fills the remaining hole in `v` if the longer run gets consumed first.
|
||||||
|
//
|
||||||
|
// Panic safety:
|
||||||
|
//
|
||||||
|
// If `is_less` panics at any point during the process, `hole` will get dropped and fill the
|
||||||
|
// hole in `v` with the unconsumed range in `buf`, thus ensuring that `v` still holds every
|
||||||
|
// object it initially held exactly once.
|
||||||
|
let mut hole;
|
||||||
|
|
||||||
|
if mid <= len - mid {
|
||||||
|
// The left run is shorter.
|
||||||
|
|
||||||
|
// SAFETY: buf must have enough capacity for `v[..mid]`.
|
||||||
|
unsafe {
|
||||||
|
ptr::copy_nonoverlapping(v, buf, mid);
|
||||||
|
hole = MergeHole { start: buf, end: buf.add(mid), dest: v };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initially, these pointers point to the beginnings of their arrays.
|
||||||
|
let left = &mut hole.start;
|
||||||
|
let mut right = v_mid;
|
||||||
|
let out = &mut hole.dest;
|
||||||
|
|
||||||
|
while *left < hole.end && right < v_end {
|
||||||
|
// Consume the lesser side.
|
||||||
|
// If equal, prefer the left run to maintain stability.
|
||||||
|
|
||||||
|
// SAFETY: left and right must be valid and part of v same for out.
|
||||||
|
unsafe {
|
||||||
|
let to_copy = if is_less(&*right, &**left) {
|
||||||
|
get_and_increment(&mut right)
|
||||||
|
} else {
|
||||||
|
get_and_increment(left)
|
||||||
|
};
|
||||||
|
ptr::copy_nonoverlapping(to_copy, get_and_increment(out), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The right run is shorter.
|
||||||
|
|
||||||
|
// SAFETY: buf must have enough capacity for `v[mid..]`.
|
||||||
|
unsafe {
|
||||||
|
ptr::copy_nonoverlapping(v_mid, buf, len - mid);
|
||||||
|
hole = MergeHole { start: buf, end: buf.add(len - mid), dest: v_mid };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initially, these pointers point past the ends of their arrays.
|
||||||
|
let left = &mut hole.dest;
|
||||||
|
let right = &mut hole.end;
|
||||||
|
let mut out = v_end;
|
||||||
|
|
||||||
|
while v < *left && buf < *right {
|
||||||
|
// Consume the greater side.
|
||||||
|
// If equal, prefer the right run to maintain stability.
|
||||||
|
|
||||||
|
// SAFETY: left and right must be valid and part of v same for out.
|
||||||
|
unsafe {
|
||||||
|
let to_copy = if is_less(&*right.sub(1), &*left.sub(1)) {
|
||||||
|
decrement_and_get(left)
|
||||||
|
} else {
|
||||||
|
decrement_and_get(right)
|
||||||
|
};
|
||||||
|
ptr::copy_nonoverlapping(to_copy, decrement_and_get(&mut out), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Finally, `hole` gets dropped. If the shorter run was not fully consumed, whatever remains of
|
||||||
|
// it will now be copied into the hole in `v`.
|
||||||
|
|
||||||
|
unsafe fn get_and_increment<T>(ptr: &mut *mut T) -> *mut T {
|
||||||
|
let old = *ptr;
|
||||||
|
|
||||||
|
// SAFETY: ptr.add(1) must still be a valid pointer and part of `v`.
|
||||||
|
*ptr = unsafe { ptr.add(1) };
|
||||||
|
old
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn decrement_and_get<T>(ptr: &mut *mut T) -> *mut T {
|
||||||
|
// SAFETY: ptr.sub(1) must still be a valid pointer and part of `v`.
|
||||||
|
*ptr = unsafe { ptr.sub(1) };
|
||||||
|
*ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// When dropped, copies the range `start..end` into `dest..`.
|
||||||
|
struct MergeHole<T> {
|
||||||
|
start: *mut T,
|
||||||
|
end: *mut T,
|
||||||
|
dest: *mut T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Drop for MergeHole<T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// SAFETY: `T` is not a zero-sized type, and these are pointers into a slice's elements.
|
||||||
|
unsafe {
|
||||||
|
let len = self.end.sub_ptr(self.start);
|
||||||
|
ptr::copy_nonoverlapping(self.start, self.dest, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This merge sort borrows some (but not all) ideas from TimSort, which used to be described in
|
||||||
|
/// detail [here](https://github.com/python/cpython/blob/main/Objects/listsort.txt). However Python
|
||||||
|
/// has switched to a Powersort based implementation.
|
||||||
|
///
|
||||||
|
/// The algorithm identifies strictly descending and non-descending subsequences, which are called
|
||||||
|
/// natural runs. There is a stack of pending runs yet to be merged. Each newly found run is pushed
|
||||||
|
/// onto the stack, and then some pairs of adjacent runs are merged until these two invariants are
|
||||||
|
/// satisfied:
|
||||||
|
///
|
||||||
|
/// 1. for every `i` in `1..runs.len()`: `runs[i - 1].len > runs[i].len`
|
||||||
|
/// 2. for every `i` in `2..runs.len()`: `runs[i - 2].len > runs[i - 1].len + runs[i].len`
|
||||||
|
///
|
||||||
|
/// The invariants ensure that the total running time is *O*(*n* \* log(*n*)) worst-case.
|
||||||
|
pub fn merge_sort<T, CmpF, ElemAllocF, ElemDeallocF, RunAllocF, RunDeallocF>(
|
||||||
|
v: &mut [T],
|
||||||
|
is_less: &mut CmpF,
|
||||||
|
elem_alloc_fn: ElemAllocF,
|
||||||
|
elem_dealloc_fn: ElemDeallocF,
|
||||||
|
run_alloc_fn: RunAllocF,
|
||||||
|
run_dealloc_fn: RunDeallocF,
|
||||||
|
) where
|
||||||
|
CmpF: FnMut(&T, &T) -> bool,
|
||||||
|
ElemAllocF: Fn(usize) -> *mut T,
|
||||||
|
ElemDeallocF: Fn(*mut T, usize),
|
||||||
|
RunAllocF: Fn(usize) -> *mut TimSortRun,
|
||||||
|
RunDeallocF: Fn(*mut TimSortRun, usize),
|
||||||
|
{
|
||||||
|
// Slices of up to this length get sorted using insertion sort.
|
||||||
|
const MAX_INSERTION: usize = 20;
|
||||||
|
// Very short runs are extended using insertion sort to span at least this many elements.
|
||||||
|
const MIN_RUN: usize = 10;
|
||||||
|
|
||||||
|
// The caller should have already checked that.
|
||||||
|
debug_assert!(!T::IS_ZST);
|
||||||
|
|
||||||
|
let len = v.len();
|
||||||
|
|
||||||
|
// Short arrays get sorted in-place via insertion sort to avoid allocations.
|
||||||
|
if len <= MAX_INSERTION {
|
||||||
|
if len >= 2 {
|
||||||
|
for i in (0..len - 1).rev() {
|
||||||
|
insert_head(&mut v[i..], is_less);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate a buffer to use as scratch memory. We keep the length 0 so we can keep in it
|
||||||
|
// shallow copies of the contents of `v` without risking the dtors running on copies if
|
||||||
|
// `is_less` panics. When merging two sorted runs, this buffer holds a copy of the shorter run,
|
||||||
|
// which will always have length at most `len / 2`.
|
||||||
|
let buf = BufGuard::new(len / 2, elem_alloc_fn, elem_dealloc_fn);
|
||||||
|
let buf_ptr = buf.buf_ptr;
|
||||||
|
|
||||||
|
let mut runs = RunVec::new(run_alloc_fn, run_dealloc_fn);
|
||||||
|
|
||||||
|
// In order to identify natural runs in `v`, we traverse it backwards. That might seem like a
|
||||||
|
// strange decision, but consider the fact that merges more often go in the opposite direction
|
||||||
|
// (forwards). According to benchmarks, merging forwards is slightly faster than merging
|
||||||
|
// backwards. To conclude, identifying runs by traversing backwards improves performance.
|
||||||
|
let mut end = len;
|
||||||
|
while end > 0 {
|
||||||
|
// Find the next natural run, and reverse it if it's strictly descending.
|
||||||
|
let mut start = end - 1;
|
||||||
|
if start > 0 {
|
||||||
|
start -= 1;
|
||||||
|
|
||||||
|
// SAFETY: The v.get_unchecked must be fed with correct inbound indicies.
|
||||||
|
unsafe {
|
||||||
|
if is_less(v.get_unchecked(start + 1), v.get_unchecked(start)) {
|
||||||
|
while start > 0 && is_less(v.get_unchecked(start), v.get_unchecked(start - 1)) {
|
||||||
|
start -= 1;
|
||||||
|
}
|
||||||
|
v[start..end].reverse();
|
||||||
|
} else {
|
||||||
|
while start > 0 && !is_less(v.get_unchecked(start), v.get_unchecked(start - 1))
|
||||||
|
{
|
||||||
|
start -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert some more elements into the run if it's too short. Insertion sort is faster than
|
||||||
|
// merge sort on short sequences, so this significantly improves performance.
|
||||||
|
while start > 0 && end - start < MIN_RUN {
|
||||||
|
start -= 1;
|
||||||
|
insert_head(&mut v[start..end], is_less);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push this run onto the stack.
|
||||||
|
runs.push(TimSortRun { start, len: end - start });
|
||||||
|
end = start;
|
||||||
|
|
||||||
|
// Merge some pairs of adjacent runs to satisfy the invariants.
|
||||||
|
while let Some(r) = collapse(runs.as_slice()) {
|
||||||
|
let left = runs[r + 1];
|
||||||
|
let right = runs[r];
|
||||||
|
// SAFETY: `buf_ptr` must hold enough capacity for the shorter of the two sides, and
|
||||||
|
// neither side may be on length 0.
|
||||||
|
unsafe {
|
||||||
|
merge(&mut v[left.start..right.start + right.len], left.len, buf_ptr, is_less);
|
||||||
|
}
|
||||||
|
runs[r] = TimSortRun { start: left.start, len: left.len + right.len };
|
||||||
|
runs.remove(r + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, exactly one run must remain in the stack.
|
||||||
|
debug_assert!(runs.len() == 1 && runs[0].start == 0 && runs[0].len == len);
|
||||||
|
|
||||||
|
// Examines the stack of runs and identifies the next pair of runs to merge. More specifically,
|
||||||
|
// if `Some(r)` is returned, that means `runs[r]` and `runs[r + 1]` must be merged next. If the
|
||||||
|
// algorithm should continue building a new run instead, `None` is returned.
|
||||||
|
//
|
||||||
|
// TimSort is infamous for its buggy implementations, as described here:
|
||||||
|
// http://envisage-project.eu/timsort-specification-and-verification/
|
||||||
|
//
|
||||||
|
// The gist of the story is: we must enforce the invariants on the top four runs on the stack.
|
||||||
|
// Enforcing them on just top three is not sufficient to ensure that the invariants will still
|
||||||
|
// hold for *all* runs in the stack.
|
||||||
|
//
|
||||||
|
// This function correctly checks invariants for the top four runs. Additionally, if the top
|
||||||
|
// run starts at index 0, it will always demand a merge operation until the stack is fully
|
||||||
|
// collapsed, in order to complete the sort.
|
||||||
|
#[inline]
|
||||||
|
fn collapse(runs: &[TimSortRun]) -> Option<usize> {
|
||||||
|
let n = runs.len();
|
||||||
|
if n >= 2
|
||||||
|
&& (runs[n - 1].start == 0
|
||||||
|
|| runs[n - 2].len <= runs[n - 1].len
|
||||||
|
|| (n >= 3 && runs[n - 3].len <= runs[n - 2].len + runs[n - 1].len)
|
||||||
|
|| (n >= 4 && runs[n - 4].len <= runs[n - 3].len + runs[n - 2].len))
|
||||||
|
{
|
||||||
|
if n >= 3 && runs[n - 3].len < runs[n - 1].len { Some(n - 3) } else { Some(n - 2) }
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extremely basic versions of Vec.
|
||||||
|
// Their use is super limited and by having the code here, it allows reuse between the sort
|
||||||
|
// implementations.
|
||||||
|
struct BufGuard<T, ElemDeallocF>
|
||||||
|
where
|
||||||
|
ElemDeallocF: Fn(*mut T, usize),
|
||||||
|
{
|
||||||
|
buf_ptr: *mut T,
|
||||||
|
capacity: usize,
|
||||||
|
elem_dealloc_fn: ElemDeallocF,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, ElemDeallocF> BufGuard<T, ElemDeallocF>
|
||||||
|
where
|
||||||
|
ElemDeallocF: Fn(*mut T, usize),
|
||||||
|
{
|
||||||
|
fn new<ElemAllocF>(
|
||||||
|
len: usize,
|
||||||
|
elem_alloc_fn: ElemAllocF,
|
||||||
|
elem_dealloc_fn: ElemDeallocF,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
ElemAllocF: Fn(usize) -> *mut T,
|
||||||
|
{
|
||||||
|
Self { buf_ptr: elem_alloc_fn(len), capacity: len, elem_dealloc_fn }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, ElemDeallocF> Drop for BufGuard<T, ElemDeallocF>
|
||||||
|
where
|
||||||
|
ElemDeallocF: Fn(*mut T, usize),
|
||||||
|
{
|
||||||
|
fn drop(&mut self) {
|
||||||
|
(self.elem_dealloc_fn)(self.buf_ptr, self.capacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RunVec<RunAllocF, RunDeallocF>
|
||||||
|
where
|
||||||
|
RunAllocF: Fn(usize) -> *mut TimSortRun,
|
||||||
|
RunDeallocF: Fn(*mut TimSortRun, usize),
|
||||||
|
{
|
||||||
|
buf_ptr: *mut TimSortRun,
|
||||||
|
capacity: usize,
|
||||||
|
len: usize,
|
||||||
|
run_alloc_fn: RunAllocF,
|
||||||
|
run_dealloc_fn: RunDeallocF,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<RunAllocF, RunDeallocF> RunVec<RunAllocF, RunDeallocF>
|
||||||
|
where
|
||||||
|
RunAllocF: Fn(usize) -> *mut TimSortRun,
|
||||||
|
RunDeallocF: Fn(*mut TimSortRun, usize),
|
||||||
|
{
|
||||||
|
fn new(run_alloc_fn: RunAllocF, run_dealloc_fn: RunDeallocF) -> Self {
|
||||||
|
// Most slices can be sorted with at most 16 runs in-flight.
|
||||||
|
const START_RUN_CAPACITY: usize = 16;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
buf_ptr: run_alloc_fn(START_RUN_CAPACITY),
|
||||||
|
capacity: START_RUN_CAPACITY,
|
||||||
|
len: 0,
|
||||||
|
run_alloc_fn,
|
||||||
|
run_dealloc_fn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push(&mut self, val: TimSortRun) {
|
||||||
|
if self.len == self.capacity {
|
||||||
|
let old_capacity = self.capacity;
|
||||||
|
let old_buf_ptr = self.buf_ptr;
|
||||||
|
|
||||||
|
self.capacity = self.capacity * 2;
|
||||||
|
self.buf_ptr = (self.run_alloc_fn)(self.capacity);
|
||||||
|
|
||||||
|
// SAFETY: buf_ptr new and old were correctly allocated and old_buf_ptr has
|
||||||
|
// old_capacity valid elements.
|
||||||
|
unsafe {
|
||||||
|
ptr::copy_nonoverlapping(old_buf_ptr, self.buf_ptr, old_capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
(self.run_dealloc_fn)(old_buf_ptr, old_capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: The invariant was just checked.
|
||||||
|
unsafe {
|
||||||
|
self.buf_ptr.add(self.len).write(val);
|
||||||
|
}
|
||||||
|
self.len += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&mut self, index: usize) {
|
||||||
|
if index >= self.len {
|
||||||
|
panic!("Index out of bounds");
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: buf_ptr needs to be valid and len invariant upheld.
|
||||||
|
unsafe {
|
||||||
|
// the place we are taking from.
|
||||||
|
let ptr = self.buf_ptr.add(index);
|
||||||
|
|
||||||
|
// Shift everything down to fill in that spot.
|
||||||
|
ptr::copy(ptr.add(1), ptr, self.len - index - 1);
|
||||||
|
}
|
||||||
|
self.len -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_slice(&self) -> &[TimSortRun] {
|
||||||
|
// SAFETY: Safe as long as buf_ptr is valid and len invariant was upheld.
|
||||||
|
unsafe { &*ptr::slice_from_raw_parts(self.buf_ptr, self.len) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
self.len
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<RunAllocF, RunDeallocF> core::ops::Index<usize> for RunVec<RunAllocF, RunDeallocF>
|
||||||
|
where
|
||||||
|
RunAllocF: Fn(usize) -> *mut TimSortRun,
|
||||||
|
RunDeallocF: Fn(*mut TimSortRun, usize),
|
||||||
|
{
|
||||||
|
type Output = TimSortRun;
|
||||||
|
|
||||||
|
fn index(&self, index: usize) -> &Self::Output {
|
||||||
|
if index < self.len {
|
||||||
|
// SAFETY: buf_ptr and len invariant must be upheld.
|
||||||
|
unsafe {
|
||||||
|
return &*(self.buf_ptr.add(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
panic!("Index out of bounds");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<RunAllocF, RunDeallocF> core::ops::IndexMut<usize> for RunVec<RunAllocF, RunDeallocF>
|
||||||
|
where
|
||||||
|
RunAllocF: Fn(usize) -> *mut TimSortRun,
|
||||||
|
RunDeallocF: Fn(*mut TimSortRun, usize),
|
||||||
|
{
|
||||||
|
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||||
|
if index < self.len {
|
||||||
|
// SAFETY: buf_ptr and len invariant must be upheld.
|
||||||
|
unsafe {
|
||||||
|
return &mut *(self.buf_ptr.add(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
panic!("Index out of bounds");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<RunAllocF, RunDeallocF> Drop for RunVec<RunAllocF, RunDeallocF>
|
||||||
|
where
|
||||||
|
RunAllocF: Fn(usize) -> *mut TimSortRun,
|
||||||
|
RunDeallocF: Fn(*mut TimSortRun, usize),
|
||||||
|
{
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// As long as TimSortRun is Copy we don't need to drop them individually but just the
|
||||||
|
// whole allocation.
|
||||||
|
(self.run_dealloc_fn)(self.buf_ptr, self.capacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal type used by merge_sort.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct TimSortRun {
|
||||||
|
len: usize,
|
||||||
|
start: usize,
|
||||||
|
}
|
||||||
|
@ -497,7 +497,7 @@ ul.block, .block li {
|
|||||||
padding-left: 24px;
|
padding-left: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar a, .sidebar .current {
|
.sidebar a {
|
||||||
color: var(--sidebar-link-color);
|
color: var(--sidebar-link-color);
|
||||||
}
|
}
|
||||||
.sidebar .current,
|
.sidebar .current,
|
||||||
|
@ -3,11 +3,6 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.setting-line .choices {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.setting-line .radio-line input,
|
.setting-line .radio-line input,
|
||||||
.setting-line .settings-toggle input {
|
.setting-line .settings-toggle input {
|
||||||
margin-right: 0.3em;
|
margin-right: 0.3em;
|
||||||
@ -38,7 +33,7 @@
|
|||||||
margin-bottom: 0.1em;
|
margin-bottom: 0.1em;
|
||||||
min-width: 3.8em;
|
min-width: 3.8em;
|
||||||
padding: 0.3em;
|
padding: 0.3em;
|
||||||
display: flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ miropt-test-tools = { path = "../miropt-test-tools" }
|
|||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
walkdir = "2"
|
walkdir = "2"
|
||||||
ignore = "0.4.18"
|
ignore = "0.4.18"
|
||||||
|
semver = "1.0"
|
||||||
termcolor = "1.1.3"
|
termcolor = "1.1.3"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
@ -70,3 +70,4 @@ fn tidy_error(bad: &mut bool, args: impl Display) -> std::io::Result<()> {
|
|||||||
pub mod unit_tests;
|
pub mod unit_tests;
|
||||||
pub mod unstable_book;
|
pub mod unstable_book;
|
||||||
pub mod walk;
|
pub mod walk;
|
||||||
|
pub mod x_version;
|
||||||
|
@ -60,7 +60,7 @@ macro_rules! check {
|
|||||||
|
|
||||||
let handle = s.spawn(|| {
|
let handle = s.spawn(|| {
|
||||||
let mut flag = false;
|
let mut flag = false;
|
||||||
$p::check($($args),* , &mut flag);
|
$p::check($($args, )* &mut flag);
|
||||||
if (flag) {
|
if (flag) {
|
||||||
bad.store(true, Ordering::Relaxed);
|
bad.store(true, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
@ -114,6 +114,8 @@ macro_rules! check {
|
|||||||
check!(alphabetical, &compiler_path);
|
check!(alphabetical, &compiler_path);
|
||||||
check!(alphabetical, &library_path);
|
check!(alphabetical, &library_path);
|
||||||
|
|
||||||
|
check!(x_version, &root_path, &cargo);
|
||||||
|
|
||||||
let collected = {
|
let collected = {
|
||||||
drain_handles(&mut handles);
|
drain_handles(&mut handles);
|
||||||
|
|
||||||
|
68
src/tools/tidy/src/x_version.rs
Normal file
68
src/tools/tidy/src/x_version.rs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
use semver::Version;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
|
pub fn check(root: &Path, cargo: &Path, bad: &mut bool) {
|
||||||
|
let cargo_list = Command::new(cargo).args(["install", "--list"]).stdout(Stdio::piped()).spawn();
|
||||||
|
|
||||||
|
let child = match cargo_list {
|
||||||
|
Ok(child) => child,
|
||||||
|
Err(e) => return tidy_error!(bad, "failed to run `cargo`: {}", e),
|
||||||
|
};
|
||||||
|
|
||||||
|
let cargo_list = child.wait_with_output().unwrap();
|
||||||
|
|
||||||
|
if cargo_list.status.success() {
|
||||||
|
let exe_list = String::from_utf8_lossy(&cargo_list.stdout);
|
||||||
|
let exe_list = exe_list.lines();
|
||||||
|
|
||||||
|
let mut installed: Option<Version> = None;
|
||||||
|
|
||||||
|
for line in exe_list {
|
||||||
|
let mut iter = line.split_whitespace();
|
||||||
|
if iter.next() == Some("x") {
|
||||||
|
if let Some(version) = iter.next() {
|
||||||
|
// Check this is the rust-lang/rust x tool installation since it should be
|
||||||
|
// installed at a path containing `src/tools/x`.
|
||||||
|
if let Some(path) = iter.next() {
|
||||||
|
if path.contains(&"src/tools/x") {
|
||||||
|
let version = version.strip_prefix("v").unwrap();
|
||||||
|
installed = Some(Version::parse(version).unwrap());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Unwrap the some if x is installed, otherwise return because it's fine if x isn't installed.
|
||||||
|
let installed = if let Some(i) = installed { i } else { return };
|
||||||
|
|
||||||
|
if let Some(expected) = get_x_wrapper_version(root, cargo) {
|
||||||
|
if installed < expected {
|
||||||
|
return println!(
|
||||||
|
"Current version of x is {installed}, but the latest version is {expected}\nConsider updating to the newer version of x by running `cargo install --path src/tools/x`"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return tidy_error!(
|
||||||
|
bad,
|
||||||
|
"Unable to parse the latest version of `x` at `src/tools/x/Cargo.toml`"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return tidy_error!(bad, "failed to check version of `x`: {}", cargo_list.status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse latest version out of `x` Cargo.toml
|
||||||
|
fn get_x_wrapper_version(root: &Path, cargo: &Path) -> Option<Version> {
|
||||||
|
let mut cmd = cargo_metadata::MetadataCommand::new();
|
||||||
|
cmd.cargo_path(cargo)
|
||||||
|
.manifest_path(root.join("src/tools/x/Cargo.toml"))
|
||||||
|
.no_deps()
|
||||||
|
.features(cargo_metadata::CargoOpt::AllFeatures);
|
||||||
|
let mut metadata = t!(cmd.exec());
|
||||||
|
metadata.packages.pop().map(|x| x.version)
|
||||||
|
}
|
20
tests/ui/imports/issue-99695-b.fixed
Normal file
20
tests/ui/imports/issue-99695-b.fixed
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// run-rustfix
|
||||||
|
#![allow(unused, nonstandard_style)]
|
||||||
|
mod m {
|
||||||
|
|
||||||
|
mod p {
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! nu {
|
||||||
|
{} => {};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct other_item;
|
||||||
|
}
|
||||||
|
|
||||||
|
use ::nu;
|
||||||
|
pub use self::p::{other_item as _};
|
||||||
|
//~^ ERROR unresolved import `self::p::nu` [E0432]
|
||||||
|
//~| HELP a macro with this name exists at the root of the crate
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
19
tests/ui/imports/issue-99695-b.rs
Normal file
19
tests/ui/imports/issue-99695-b.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// run-rustfix
|
||||||
|
#![allow(unused, nonstandard_style)]
|
||||||
|
mod m {
|
||||||
|
|
||||||
|
mod p {
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! nu {
|
||||||
|
{} => {};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct other_item;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use self::p::{nu, other_item as _};
|
||||||
|
//~^ ERROR unresolved import `self::p::nu` [E0432]
|
||||||
|
//~| HELP a macro with this name exists at the root of the crate
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
16
tests/ui/imports/issue-99695-b.stderr
Normal file
16
tests/ui/imports/issue-99695-b.stderr
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
error[E0432]: unresolved import `self::p::nu`
|
||||||
|
--> $DIR/issue-99695-b.rs:14:23
|
||||||
|
|
|
||||||
|
LL | pub use self::p::{nu, other_item as _};
|
||||||
|
| ^^ no `nu` in `m::p`
|
||||||
|
|
|
||||||
|
= note: this could be because a macro annotated with `#[macro_export]` will be exported at the root of the crate instead of the module where it is defined
|
||||||
|
help: a macro with this name exists at the root of the crate
|
||||||
|
|
|
||||||
|
LL ~ use ::nu;
|
||||||
|
LL ~ pub use self::p::{other_item as _};
|
||||||
|
|
|
||||||
|
|
||||||
|
error: aborting due to previous error
|
||||||
|
|
||||||
|
For more information about this error, try `rustc --explain E0432`.
|
17
tests/ui/imports/issue-99695.fixed
Normal file
17
tests/ui/imports/issue-99695.fixed
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// run-rustfix
|
||||||
|
#![allow(unused, nonstandard_style)]
|
||||||
|
mod m {
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! nu {
|
||||||
|
{} => {};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct other_item;
|
||||||
|
|
||||||
|
use ::nu;
|
||||||
|
pub use self::{other_item as _};
|
||||||
|
//~^ ERROR unresolved import `self::nu` [E0432]
|
||||||
|
//~| HELP a macro with this name exists at the root of the crate
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
16
tests/ui/imports/issue-99695.rs
Normal file
16
tests/ui/imports/issue-99695.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// run-rustfix
|
||||||
|
#![allow(unused, nonstandard_style)]
|
||||||
|
mod m {
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! nu {
|
||||||
|
{} => {};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct other_item;
|
||||||
|
|
||||||
|
pub use self::{nu, other_item as _};
|
||||||
|
//~^ ERROR unresolved import `self::nu` [E0432]
|
||||||
|
//~| HELP a macro with this name exists at the root of the crate
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
16
tests/ui/imports/issue-99695.stderr
Normal file
16
tests/ui/imports/issue-99695.stderr
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
error[E0432]: unresolved import `self::nu`
|
||||||
|
--> $DIR/issue-99695.rs:11:20
|
||||||
|
|
|
||||||
|
LL | pub use self::{nu, other_item as _};
|
||||||
|
| ^^ no `nu` in `m`
|
||||||
|
|
|
||||||
|
= note: this could be because a macro annotated with `#[macro_export]` will be exported at the root of the crate instead of the module where it is defined
|
||||||
|
help: a macro with this name exists at the root of the crate
|
||||||
|
|
|
||||||
|
LL ~ use ::nu;
|
||||||
|
LL ~ pub use self::{other_item as _};
|
||||||
|
|
|
||||||
|
|
||||||
|
error: aborting due to previous error
|
||||||
|
|
||||||
|
For more information about this error, try `rustc --explain E0432`.
|
@ -1,4 +1,3 @@
|
|||||||
// run-pass
|
|
||||||
// run-rustfix
|
// run-rustfix
|
||||||
|
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
@ -16,11 +15,11 @@ impl Foo {
|
|||||||
match self {
|
match self {
|
||||||
&
|
&
|
||||||
Foo::Bar if true
|
Foo::Bar if true
|
||||||
//~^ WARN pattern binding `Bar` is named the same as one of the variants of the type `Foo`
|
//~^ ERROR pattern binding `Bar` is named the same as one of the variants of the type `Foo`
|
||||||
=> println!("bar"),
|
=> println!("bar"),
|
||||||
&
|
&
|
||||||
Foo::Baz if false
|
Foo::Baz if false
|
||||||
//~^ WARN pattern binding `Baz` is named the same as one of the variants of the type `Foo`
|
//~^ ERROR pattern binding `Baz` is named the same as one of the variants of the type `Foo`
|
||||||
=> println!("baz"),
|
=> println!("baz"),
|
||||||
_ => ()
|
_ => ()
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
// run-pass
|
|
||||||
// run-rustfix
|
// run-rustfix
|
||||||
|
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
@ -16,11 +15,11 @@ fn foo(&self) {
|
|||||||
match self {
|
match self {
|
||||||
&
|
&
|
||||||
Bar if true
|
Bar if true
|
||||||
//~^ WARN pattern binding `Bar` is named the same as one of the variants of the type `Foo`
|
//~^ ERROR pattern binding `Bar` is named the same as one of the variants of the type `Foo`
|
||||||
=> println!("bar"),
|
=> println!("bar"),
|
||||||
&
|
&
|
||||||
Baz if false
|
Baz if false
|
||||||
//~^ WARN pattern binding `Baz` is named the same as one of the variants of the type `Foo`
|
//~^ ERROR pattern binding `Baz` is named the same as one of the variants of the type `Foo`
|
||||||
=> println!("baz"),
|
=> println!("baz"),
|
||||||
_ => ()
|
_ => ()
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
warning[E0170]: pattern binding `Bar` is named the same as one of the variants of the type `Foo`
|
error[E0170]: pattern binding `Bar` is named the same as one of the variants of the type `Foo`
|
||||||
--> $DIR/issue-19100.rs:18:1
|
--> $DIR/issue-19100.rs:17:1
|
||||||
|
|
|
|
||||||
LL | Bar if true
|
LL | Bar if true
|
||||||
| ^^^ help: to match on the variant, qualify the path: `Foo::Bar`
|
| ^^^ help: to match on the variant, qualify the path: `Foo::Bar`
|
||||||
|
|
|
|
||||||
= note: `#[warn(bindings_with_variant_name)]` on by default
|
= note: `#[deny(bindings_with_variant_name)]` on by default
|
||||||
|
|
||||||
warning[E0170]: pattern binding `Baz` is named the same as one of the variants of the type `Foo`
|
error[E0170]: pattern binding `Baz` is named the same as one of the variants of the type `Foo`
|
||||||
--> $DIR/issue-19100.rs:22:1
|
--> $DIR/issue-19100.rs:21:1
|
||||||
|
|
|
|
||||||
LL | Baz if false
|
LL | Baz if false
|
||||||
| ^^^ help: to match on the variant, qualify the path: `Foo::Baz`
|
| ^^^ help: to match on the variant, qualify the path: `Foo::Baz`
|
||||||
|
|
||||||
warning: 2 warnings emitted
|
error: aborting due to 2 previous errors
|
||||||
|
|
||||||
For more information about this error, try `rustc --explain E0170`.
|
For more information about this error, try `rustc --explain E0170`.
|
||||||
|
@ -11,7 +11,7 @@ enum Stack<T> {
|
|||||||
fn is_empty<T>(s: Stack<T>) -> bool {
|
fn is_empty<T>(s: Stack<T>) -> bool {
|
||||||
match s {
|
match s {
|
||||||
Nil => true,
|
Nil => true,
|
||||||
//~^ WARN pattern binding `Nil` is named the same as one of the variants of the type `Stack`
|
//~^ ERROR pattern binding `Nil` is named the same as one of the variants of the type `Stack`
|
||||||
_ => false
|
_ => false
|
||||||
//~^ ERROR unreachable pattern
|
//~^ ERROR unreachable pattern
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
warning[E0170]: pattern binding `Nil` is named the same as one of the variants of the type `Stack`
|
error[E0170]: pattern binding `Nil` is named the same as one of the variants of the type `Stack`
|
||||||
--> $DIR/issue-30302.rs:13:9
|
--> $DIR/issue-30302.rs:13:9
|
||||||
|
|
|
|
||||||
LL | Nil => true,
|
LL | Nil => true,
|
||||||
| ^^^ help: to match on the variant, qualify the path: `Stack::Nil`
|
| ^^^ help: to match on the variant, qualify the path: `Stack::Nil`
|
||||||
|
|
|
|
||||||
= note: `#[warn(bindings_with_variant_name)]` on by default
|
= note: `#[deny(bindings_with_variant_name)]` on by default
|
||||||
|
|
||||||
error: unreachable pattern
|
error: unreachable pattern
|
||||||
--> $DIR/issue-30302.rs:15:9
|
--> $DIR/issue-30302.rs:15:9
|
||||||
@ -21,6 +21,6 @@ note: the lint level is defined here
|
|||||||
LL | #![deny(unreachable_patterns)]
|
LL | #![deny(unreachable_patterns)]
|
||||||
| ^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
error: aborting due to previous error; 1 warning emitted
|
error: aborting due to 2 previous errors
|
||||||
|
|
||||||
For more information about this error, try `rustc --explain E0170`.
|
For more information about this error, try `rustc --explain E0170`.
|
||||||
|
@ -21,18 +21,18 @@ fn main() {
|
|||||||
match foo::Foo::Foo {
|
match foo::Foo::Foo {
|
||||||
Foo => {}
|
Foo => {}
|
||||||
//~^ ERROR variable `Foo` should have a snake case name
|
//~^ ERROR variable `Foo` should have a snake case name
|
||||||
//~^^ WARN `Foo` is named the same as one of the variants of the type `foo::Foo`
|
//~^^ ERROR `Foo` is named the same as one of the variants of the type `foo::Foo`
|
||||||
//~^^^ WARN unused variable: `Foo`
|
//~^^^ WARN unused variable: `Foo`
|
||||||
}
|
}
|
||||||
|
|
||||||
let Foo = foo::Foo::Foo;
|
let Foo = foo::Foo::Foo;
|
||||||
//~^ ERROR variable `Foo` should have a snake case name
|
//~^ ERROR variable `Foo` should have a snake case name
|
||||||
//~^^ WARN `Foo` is named the same as one of the variants of the type `foo::Foo`
|
//~^^ ERROR `Foo` is named the same as one of the variants of the type `foo::Foo`
|
||||||
//~^^^ WARN unused variable: `Foo`
|
//~^^^ WARN unused variable: `Foo`
|
||||||
|
|
||||||
fn in_param(Foo: foo::Foo) {}
|
fn in_param(Foo: foo::Foo) {}
|
||||||
//~^ ERROR variable `Foo` should have a snake case name
|
//~^ ERROR variable `Foo` should have a snake case name
|
||||||
//~^^ WARN `Foo` is named the same as one of the variants of the type `foo::Foo`
|
//~^^ ERROR `Foo` is named the same as one of the variants of the type `foo::Foo`
|
||||||
//~^^^ WARN unused variable: `Foo`
|
//~^^^ WARN unused variable: `Foo`
|
||||||
|
|
||||||
test(1);
|
test(1);
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
warning[E0170]: pattern binding `Foo` is named the same as one of the variants of the type `foo::Foo`
|
error[E0170]: pattern binding `Foo` is named the same as one of the variants of the type `foo::Foo`
|
||||||
--> $DIR/lint-uppercase-variables.rs:22:9
|
--> $DIR/lint-uppercase-variables.rs:22:9
|
||||||
|
|
|
|
||||||
LL | Foo => {}
|
LL | Foo => {}
|
||||||
| ^^^ help: to match on the variant, qualify the path: `foo::Foo::Foo`
|
| ^^^ help: to match on the variant, qualify the path: `foo::Foo::Foo`
|
||||||
|
|
|
|
||||||
= note: `#[warn(bindings_with_variant_name)]` on by default
|
= note: `#[deny(bindings_with_variant_name)]` on by default
|
||||||
|
|
||||||
warning[E0170]: pattern binding `Foo` is named the same as one of the variants of the type `foo::Foo`
|
error[E0170]: pattern binding `Foo` is named the same as one of the variants of the type `foo::Foo`
|
||||||
--> $DIR/lint-uppercase-variables.rs:28:9
|
--> $DIR/lint-uppercase-variables.rs:28:9
|
||||||
|
|
|
|
||||||
LL | let Foo = foo::Foo::Foo;
|
LL | let Foo = foo::Foo::Foo;
|
||||||
| ^^^ help: to match on the variant, qualify the path: `foo::Foo::Foo`
|
| ^^^ help: to match on the variant, qualify the path: `foo::Foo::Foo`
|
||||||
|
|
||||||
warning[E0170]: pattern binding `Foo` is named the same as one of the variants of the type `foo::Foo`
|
error[E0170]: pattern binding `Foo` is named the same as one of the variants of the type `foo::Foo`
|
||||||
--> $DIR/lint-uppercase-variables.rs:33:17
|
--> $DIR/lint-uppercase-variables.rs:33:17
|
||||||
|
|
|
|
||||||
LL | fn in_param(Foo: foo::Foo) {}
|
LL | fn in_param(Foo: foo::Foo) {}
|
||||||
@ -85,6 +85,6 @@ error: variable `Foo` should have a snake case name
|
|||||||
LL | fn in_param(Foo: foo::Foo) {}
|
LL | fn in_param(Foo: foo::Foo) {}
|
||||||
| ^^^ help: convert the identifier to snake case (notice the capitalization): `foo`
|
| ^^^ help: convert the identifier to snake case (notice the capitalization): `foo`
|
||||||
|
|
||||||
error: aborting due to 6 previous errors; 6 warnings emitted
|
error: aborting due to 9 previous errors; 3 warnings emitted
|
||||||
|
|
||||||
For more information about this error, try `rustc --explain E0170`.
|
For more information about this error, try `rustc --explain E0170`.
|
||||||
|
@ -11,9 +11,9 @@ pub mod b {
|
|||||||
pub fn key(e: ::E) -> &'static str {
|
pub fn key(e: ::E) -> &'static str {
|
||||||
match e {
|
match e {
|
||||||
A => "A",
|
A => "A",
|
||||||
//~^ WARN pattern binding `A` is named the same as one of the variants of the type `E`
|
//~^ ERROR pattern binding `A` is named the same as one of the variants of the type `E`
|
||||||
B => "B", //~ ERROR: unreachable pattern
|
B => "B", //~ ERROR: unreachable pattern
|
||||||
//~^ WARN pattern binding `B` is named the same as one of the variants of the type `E`
|
//~^ ERROR pattern binding `B` is named the same as one of the variants of the type `E`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
warning[E0170]: pattern binding `A` is named the same as one of the variants of the type `E`
|
error[E0170]: pattern binding `A` is named the same as one of the variants of the type `E`
|
||||||
--> $DIR/issue-14221.rs:13:13
|
--> $DIR/issue-14221.rs:13:13
|
||||||
|
|
|
|
||||||
LL | A => "A",
|
LL | A => "A",
|
||||||
| ^ help: to match on the variant, qualify the path: `E::A`
|
| ^ help: to match on the variant, qualify the path: `E::A`
|
||||||
|
|
|
|
||||||
= note: `#[warn(bindings_with_variant_name)]` on by default
|
= note: `#[deny(bindings_with_variant_name)]` on by default
|
||||||
|
|
||||||
warning[E0170]: pattern binding `B` is named the same as one of the variants of the type `E`
|
error[E0170]: pattern binding `B` is named the same as one of the variants of the type `E`
|
||||||
--> $DIR/issue-14221.rs:15:13
|
--> $DIR/issue-14221.rs:15:13
|
||||||
|
|
|
|
||||||
LL | B => "B",
|
LL | B => "B",
|
||||||
@ -27,6 +27,6 @@ note: the lint level is defined here
|
|||||||
LL | #![deny(unreachable_patterns)]
|
LL | #![deny(unreachable_patterns)]
|
||||||
| ^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
error: aborting due to previous error; 2 warnings emitted
|
error: aborting due to 3 previous errors
|
||||||
|
|
||||||
For more information about this error, try `rustc --explain E0170`.
|
For more information about this error, try `rustc --explain E0170`.
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
// Test for issue #67776: binding named the same as enum variant
|
// Test for issue #67776: binding named the same as enum variant
|
||||||
// should report a warning even when matching against a reference type
|
// should report an error even when matching against a reference type
|
||||||
|
|
||||||
// check-pass
|
|
||||||
|
|
||||||
#![allow(unused_variables)]
|
#![allow(unused_variables)]
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
@ -15,27 +13,27 @@ enum Foo {
|
|||||||
fn fn1(e: Foo) {
|
fn fn1(e: Foo) {
|
||||||
match e {
|
match e {
|
||||||
Bar => {},
|
Bar => {},
|
||||||
//~^ WARNING named the same as one of the variants of the type `Foo`
|
//~^ ERROR named the same as one of the variants of the type `Foo`
|
||||||
Baz => {},
|
Baz => {},
|
||||||
//~^ WARNING named the same as one of the variants of the type `Foo`
|
//~^ ERROR named the same as one of the variants of the type `Foo`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fn2(e: &Foo) {
|
fn fn2(e: &Foo) {
|
||||||
match e {
|
match e {
|
||||||
Bar => {},
|
Bar => {},
|
||||||
//~^ WARNING named the same as one of the variants of the type `Foo`
|
//~^ ERROR named the same as one of the variants of the type `Foo`
|
||||||
Baz => {},
|
Baz => {},
|
||||||
//~^ WARNING named the same as one of the variants of the type `Foo`
|
//~^ ERROR named the same as one of the variants of the type `Foo`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fn3(e: &mut &&mut Foo) {
|
fn fn3(e: &mut &&mut Foo) {
|
||||||
match e {
|
match e {
|
||||||
Bar => {},
|
Bar => {},
|
||||||
//~^ WARNING named the same as one of the variants of the type `Foo`
|
//~^ ERROR named the same as one of the variants of the type `Foo`
|
||||||
Baz => {},
|
Baz => {},
|
||||||
//~^ WARNING named the same as one of the variants of the type `Foo`
|
//~^ ERROR named the same as one of the variants of the type `Foo`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,41 +1,41 @@
|
|||||||
warning[E0170]: pattern binding `Bar` is named the same as one of the variants of the type `Foo`
|
error[E0170]: pattern binding `Bar` is named the same as one of the variants of the type `Foo`
|
||||||
|
--> $DIR/issue-67776-match-same-name-enum-variant-refs.rs:15:9
|
||||||
|
|
|
||||||
|
LL | Bar => {},
|
||||||
|
| ^^^ help: to match on the variant, qualify the path: `Foo::Bar`
|
||||||
|
|
|
||||||
|
= note: `#[deny(bindings_with_variant_name)]` on by default
|
||||||
|
|
||||||
|
error[E0170]: pattern binding `Baz` is named the same as one of the variants of the type `Foo`
|
||||||
--> $DIR/issue-67776-match-same-name-enum-variant-refs.rs:17:9
|
--> $DIR/issue-67776-match-same-name-enum-variant-refs.rs:17:9
|
||||||
|
|
|
|
||||||
LL | Bar => {},
|
|
||||||
| ^^^ help: to match on the variant, qualify the path: `Foo::Bar`
|
|
||||||
|
|
|
||||||
= note: `#[warn(bindings_with_variant_name)]` on by default
|
|
||||||
|
|
||||||
warning[E0170]: pattern binding `Baz` is named the same as one of the variants of the type `Foo`
|
|
||||||
--> $DIR/issue-67776-match-same-name-enum-variant-refs.rs:19:9
|
|
||||||
|
|
|
||||||
LL | Baz => {},
|
LL | Baz => {},
|
||||||
| ^^^ help: to match on the variant, qualify the path: `Foo::Baz`
|
| ^^^ help: to match on the variant, qualify the path: `Foo::Baz`
|
||||||
|
|
||||||
warning[E0170]: pattern binding `Bar` is named the same as one of the variants of the type `Foo`
|
error[E0170]: pattern binding `Bar` is named the same as one of the variants of the type `Foo`
|
||||||
|
--> $DIR/issue-67776-match-same-name-enum-variant-refs.rs:24:9
|
||||||
|
|
|
||||||
|
LL | Bar => {},
|
||||||
|
| ^^^ help: to match on the variant, qualify the path: `Foo::Bar`
|
||||||
|
|
||||||
|
error[E0170]: pattern binding `Baz` is named the same as one of the variants of the type `Foo`
|
||||||
--> $DIR/issue-67776-match-same-name-enum-variant-refs.rs:26:9
|
--> $DIR/issue-67776-match-same-name-enum-variant-refs.rs:26:9
|
||||||
|
|
|
|
||||||
LL | Bar => {},
|
|
||||||
| ^^^ help: to match on the variant, qualify the path: `Foo::Bar`
|
|
||||||
|
|
||||||
warning[E0170]: pattern binding `Baz` is named the same as one of the variants of the type `Foo`
|
|
||||||
--> $DIR/issue-67776-match-same-name-enum-variant-refs.rs:28:9
|
|
||||||
|
|
|
||||||
LL | Baz => {},
|
LL | Baz => {},
|
||||||
| ^^^ help: to match on the variant, qualify the path: `Foo::Baz`
|
| ^^^ help: to match on the variant, qualify the path: `Foo::Baz`
|
||||||
|
|
||||||
warning[E0170]: pattern binding `Bar` is named the same as one of the variants of the type `Foo`
|
error[E0170]: pattern binding `Bar` is named the same as one of the variants of the type `Foo`
|
||||||
|
--> $DIR/issue-67776-match-same-name-enum-variant-refs.rs:33:9
|
||||||
|
|
|
||||||
|
LL | Bar => {},
|
||||||
|
| ^^^ help: to match on the variant, qualify the path: `Foo::Bar`
|
||||||
|
|
||||||
|
error[E0170]: pattern binding `Baz` is named the same as one of the variants of the type `Foo`
|
||||||
--> $DIR/issue-67776-match-same-name-enum-variant-refs.rs:35:9
|
--> $DIR/issue-67776-match-same-name-enum-variant-refs.rs:35:9
|
||||||
|
|
|
|
||||||
LL | Bar => {},
|
|
||||||
| ^^^ help: to match on the variant, qualify the path: `Foo::Bar`
|
|
||||||
|
|
||||||
warning[E0170]: pattern binding `Baz` is named the same as one of the variants of the type `Foo`
|
|
||||||
--> $DIR/issue-67776-match-same-name-enum-variant-refs.rs:37:9
|
|
||||||
|
|
|
||||||
LL | Baz => {},
|
LL | Baz => {},
|
||||||
| ^^^ help: to match on the variant, qualify the path: `Foo::Baz`
|
| ^^^ help: to match on the variant, qualify the path: `Foo::Baz`
|
||||||
|
|
||||||
warning: 6 warnings emitted
|
error: aborting due to 6 previous errors
|
||||||
|
|
||||||
For more information about this error, try `rustc --explain E0170`.
|
For more information about this error, try `rustc --explain E0170`.
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
#![allow(unused, nonstandard_style)]
|
#![allow(unused, nonstandard_style)]
|
||||||
#![deny(bindings_with_variant_name)]
|
|
||||||
|
|
||||||
// If an enum has two different variants,
|
// If an enum has two different variants,
|
||||||
// then it cannot be matched upon in a function argument.
|
// then it cannot be matched upon in a function argument.
|
||||||
// It still gets a warning, but no suggestions.
|
// It still gets an error, but no suggestions.
|
||||||
enum Foo {
|
enum Foo {
|
||||||
C,
|
C,
|
||||||
D,
|
D,
|
||||||
|
@ -1,17 +1,13 @@
|
|||||||
error[E0170]: pattern binding `C` is named the same as one of the variants of the type `Foo`
|
error[E0170]: pattern binding `C` is named the same as one of the variants of the type `Foo`
|
||||||
--> $DIR/issue-88730.rs:12:8
|
--> $DIR/issue-88730.rs:11:8
|
||||||
|
|
|
|
||||||
LL | fn foo(C: Foo) {}
|
LL | fn foo(C: Foo) {}
|
||||||
| ^
|
| ^
|
||||||
|
|
|
|
||||||
note: the lint level is defined here
|
= note: `#[deny(bindings_with_variant_name)]` on by default
|
||||||
--> $DIR/issue-88730.rs:2:9
|
|
||||||
|
|
|
||||||
LL | #![deny(bindings_with_variant_name)]
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
error[E0170]: pattern binding `C` is named the same as one of the variants of the type `Foo`
|
error[E0170]: pattern binding `C` is named the same as one of the variants of the type `Foo`
|
||||||
--> $DIR/issue-88730.rs:15:9
|
--> $DIR/issue-88730.rs:14:9
|
||||||
|
|
|
|
||||||
LL | let C = Foo::D;
|
LL | let C = Foo::D;
|
||||||
| ^
|
| ^
|
||||||
|
15
tests/ui/traits/new-solver/fn-trait-closure.rs
Normal file
15
tests/ui/traits/new-solver/fn-trait-closure.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// compile-flags: -Ztrait-solver=next
|
||||||
|
// known-bug: unknown
|
||||||
|
// failure-status: 101
|
||||||
|
// dont-check-compiler-stderr
|
||||||
|
|
||||||
|
// This test will fail until we fix `FulfillmentCtxt::relationships`. That's
|
||||||
|
// because we create a type variable for closure upvar types, which is not
|
||||||
|
// constrained until after we try to do fallback on diverging type variables.
|
||||||
|
// Thus, we will call that function, which is unimplemented.
|
||||||
|
|
||||||
|
fn require_fn(_: impl Fn() -> i32) {}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
require_fn(|| -> i32 { 1i32 });
|
||||||
|
}
|
13
tests/ui/traits/new-solver/fn-trait.rs
Normal file
13
tests/ui/traits/new-solver/fn-trait.rs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// compile-flags: -Ztrait-solver=next
|
||||||
|
// check-pass
|
||||||
|
|
||||||
|
fn require_fn(_: impl Fn() -> i32) {}
|
||||||
|
|
||||||
|
fn f() -> i32 {
|
||||||
|
1i32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
require_fn(f);
|
||||||
|
require_fn(f as fn() -> i32);
|
||||||
|
}
|
12
tests/ui/traits/new-solver/pointer-sized.rs
Normal file
12
tests/ui/traits/new-solver/pointer-sized.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#![feature(pointer_sized_trait)]
|
||||||
|
|
||||||
|
use std::marker::PointerSized;
|
||||||
|
|
||||||
|
fn require_pointer_sized(_: impl PointerSized) {}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
require_pointer_sized(1usize);
|
||||||
|
require_pointer_sized(1u16);
|
||||||
|
//~^ ERROR `u16` needs to be a pointer-sized type
|
||||||
|
require_pointer_sized(&1i16);
|
||||||
|
}
|
24
tests/ui/traits/new-solver/pointer-sized.stderr
Normal file
24
tests/ui/traits/new-solver/pointer-sized.stderr
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
error[E0277]: `u16` needs to be a pointer-sized type
|
||||||
|
--> $DIR/pointer-sized.rs:9:27
|
||||||
|
|
|
||||||
|
LL | require_pointer_sized(1u16);
|
||||||
|
| --------------------- ^^^^ the trait `PointerSized` is not implemented for `u16`
|
||||||
|
| |
|
||||||
|
| required by a bound introduced by this call
|
||||||
|
|
|
||||||
|
= note: the trait bound `u16: PointerSized` is not satisfied
|
||||||
|
note: required by a bound in `require_pointer_sized`
|
||||||
|
--> $DIR/pointer-sized.rs:5:34
|
||||||
|
|
|
||||||
|
LL | fn require_pointer_sized(_: impl PointerSized) {}
|
||||||
|
| ^^^^^^^^^^^^ required by this bound in `require_pointer_sized`
|
||||||
|
help: consider borrowing here
|
||||||
|
|
|
||||||
|
LL | require_pointer_sized(&1u16);
|
||||||
|
| +
|
||||||
|
LL | require_pointer_sized(&mut 1u16);
|
||||||
|
| ++++
|
||||||
|
|
||||||
|
error: aborting due to previous error
|
||||||
|
|
||||||
|
For more information about this error, try `rustc --explain E0277`.
|
Loading…
Reference in New Issue
Block a user