Auto merge of #120852 - matthiaskrgr:rollup-01pr8gj, r=matthiaskrgr
Rollup of 11 pull requests Successful merges: - #120351 (Implement SystemTime for UEFI) - #120354 (improve normalization of `Pointee::Metadata`) - #120776 (Move path implementations into `sys`) - #120790 (better error message on download CI LLVM failure) - #120806 (Clippy subtree update) - #120815 (Improve `Option::inspect` docs) - #120822 (Emit more specific diagnostics when enums fail to cast with `as`) - #120827 (Print image input file and checksum in CI only) - #120836 (hide impls if trait bound is proven from env) - #120844 (Build DebugInfo for async closures) - #120851 (Remove duplicate release note) r? `@ghost` `@rustbot` modify labels: rollup
This commit is contained in:
commit
d44e3b95cb
195 changed files with 4743 additions and 889 deletions
10
Cargo.lock
10
Cargo.lock
|
@ -556,7 +556,7 @@ checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
|
|||
|
||||
[[package]]
|
||||
name = "clippy"
|
||||
version = "0.1.77"
|
||||
version = "0.1.78"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"clippy_config",
|
||||
|
@ -584,7 +584,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clippy_config"
|
||||
version = "0.1.77"
|
||||
version = "0.1.78"
|
||||
dependencies = [
|
||||
"rustc-semver",
|
||||
"serde",
|
||||
|
@ -607,7 +607,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clippy_lints"
|
||||
version = "0.1.77"
|
||||
version = "0.1.78"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"cargo_metadata 0.18.0",
|
||||
|
@ -632,7 +632,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clippy_utils"
|
||||
version = "0.1.77"
|
||||
version = "0.1.78"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"clippy_config",
|
||||
|
@ -1003,7 +1003,7 @@ checksum = "a0afaad2b26fa326569eb264b1363e8ae3357618c43982b3f285f0774ce76b69"
|
|||
|
||||
[[package]]
|
||||
name = "declare_clippy_lint"
|
||||
version = "0.1.77"
|
||||
version = "0.1.78"
|
||||
dependencies = [
|
||||
"itertools",
|
||||
"quote",
|
||||
|
|
|
@ -7,7 +7,6 @@ Language
|
|||
--------
|
||||
- [Document Rust ABI compatibility between various types](https://github.com/rust-lang/rust/pull/115476/)
|
||||
- [Also: guarantee that char and u32 are ABI-compatible](https://github.com/rust-lang/rust/pull/118032/)
|
||||
- [Warn against ambiguous wide pointer comparisons](https://github.com/rust-lang/rust/pull/117758/)
|
||||
- [Add lint `ambiguous_wide_pointer_comparisons` that supersedes `clippy::vtable_address_comparisons`](https://github.com/rust-lang/rust/pull/117758)
|
||||
|
||||
<a id="1.76.0-Compiler"></a>
|
||||
|
|
|
@ -461,6 +461,7 @@ pub fn type_di_node<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>, t: Ty<'tcx>) -> &'ll D
|
|||
}
|
||||
ty::FnDef(..) | ty::FnPtr(_) => build_subroutine_type_di_node(cx, unique_type_id),
|
||||
ty::Closure(..) => build_closure_env_di_node(cx, unique_type_id),
|
||||
ty::CoroutineClosure(..) => build_closure_env_di_node(cx, unique_type_id),
|
||||
ty::Coroutine(..) => enums::build_coroutine_di_node(cx, unique_type_id),
|
||||
ty::Adt(def, ..) => match def.adt_kind() {
|
||||
AdtKind::Struct => build_struct_type_di_node(cx, unique_type_id),
|
||||
|
@ -1068,6 +1069,7 @@ fn build_upvar_field_di_nodes<'ll, 'tcx>(
|
|||
let (&def_id, up_var_tys) = match closure_or_coroutine_ty.kind() {
|
||||
ty::Coroutine(def_id, args) => (def_id, args.as_coroutine().prefix_tys()),
|
||||
ty::Closure(def_id, args) => (def_id, args.as_closure().upvar_tys()),
|
||||
ty::CoroutineClosure(def_id, args) => (def_id, args.as_coroutine_closure().upvar_tys()),
|
||||
_ => {
|
||||
bug!(
|
||||
"build_upvar_field_di_nodes() called with non-closure-or-coroutine-type: {:?}",
|
||||
|
@ -1153,7 +1155,8 @@ fn build_closure_env_di_node<'ll, 'tcx>(
|
|||
unique_type_id: UniqueTypeId<'tcx>,
|
||||
) -> DINodeCreationResult<'ll> {
|
||||
let closure_env_type = unique_type_id.expect_ty();
|
||||
let &ty::Closure(def_id, _args) = closure_env_type.kind() else {
|
||||
let &(ty::Closure(def_id, _) | ty::CoroutineClosure(def_id, _)) = closure_env_type.kind()
|
||||
else {
|
||||
bug!("build_closure_env_di_node() called with non-closure-type: {:?}", closure_env_type)
|
||||
};
|
||||
let containing_scope = get_namespace_for_item(cx, def_id);
|
||||
|
|
|
@ -1985,10 +1985,9 @@ fn generic_simd_intrinsic<'ll, 'tcx>(
|
|||
|
||||
match in_elem.kind() {
|
||||
ty::RawPtr(p) => {
|
||||
let (metadata, check_sized) = p.ty.ptr_metadata_ty(bx.tcx, |ty| {
|
||||
let metadata = p.ty.ptr_metadata_ty(bx.tcx, |ty| {
|
||||
bx.tcx.normalize_erasing_regions(ty::ParamEnv::reveal_all(), ty)
|
||||
});
|
||||
assert!(!check_sized); // we are in codegen, so we shouldn't see these types
|
||||
require!(
|
||||
metadata.is_unit(),
|
||||
InvalidMonomorphization::CastFatPointer { span, name, ty: in_elem }
|
||||
|
@ -2000,10 +1999,9 @@ fn generic_simd_intrinsic<'ll, 'tcx>(
|
|||
}
|
||||
match out_elem.kind() {
|
||||
ty::RawPtr(p) => {
|
||||
let (metadata, check_sized) = p.ty.ptr_metadata_ty(bx.tcx, |ty| {
|
||||
let metadata = p.ty.ptr_metadata_ty(bx.tcx, |ty| {
|
||||
bx.tcx.normalize_erasing_regions(ty::ParamEnv::reveal_all(), ty)
|
||||
});
|
||||
assert!(!check_sized); // we are in codegen, so we shouldn't see these types
|
||||
require!(
|
||||
metadata.is_unit(),
|
||||
InvalidMonomorphization::CastFatPointer { span, name, ty: out_elem }
|
||||
|
|
|
@ -377,12 +377,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
// to fields, which can yield non-normalized types. So we need to provide a
|
||||
// normalization function.
|
||||
let normalize = |ty| self.tcx.normalize_erasing_regions(self.param_env, ty);
|
||||
let (meta, only_if_sized) = ty.ptr_metadata_ty(*self.tcx, normalize);
|
||||
assert!(
|
||||
!only_if_sized,
|
||||
"there should be no more 'maybe has that metadata' types during interpretation"
|
||||
);
|
||||
meta
|
||||
ty.ptr_metadata_ty(*self.tcx, normalize)
|
||||
};
|
||||
return Ok(meta_ty(caller) == meta_ty(callee));
|
||||
}
|
||||
|
|
|
@ -448,13 +448,35 @@ impl<'a, 'tcx> CastCheck<'tcx> {
|
|||
);
|
||||
}
|
||||
}
|
||||
let msg = "an `as` expression can only be used to convert between primitive \
|
||||
types or to coerce to a specific trait object";
|
||||
|
||||
let (msg, note) = if let ty::Adt(adt, _) = self.expr_ty.kind()
|
||||
&& adt.is_enum()
|
||||
&& self.cast_ty.is_numeric()
|
||||
{
|
||||
(
|
||||
"an `as` expression can be used to convert enum types to numeric \
|
||||
types only if the enum type is unit-only or field-less",
|
||||
Some(
|
||||
"see https://doc.rust-lang.org/reference/items/enumerations.html#casting for more information",
|
||||
),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
"an `as` expression can only be used to convert between primitive \
|
||||
types or to coerce to a specific trait object",
|
||||
None,
|
||||
)
|
||||
};
|
||||
|
||||
if label {
|
||||
err.span_label(self.span, msg);
|
||||
} else {
|
||||
err.note(msg);
|
||||
}
|
||||
|
||||
if let Some(note) = note {
|
||||
err.note(note);
|
||||
}
|
||||
} else {
|
||||
err.span_label(self.span, "invalid cast");
|
||||
}
|
||||
|
|
|
@ -2279,12 +2279,12 @@ impl<'tcx> Ty<'tcx> {
|
|||
}
|
||||
|
||||
/// Returns the type of metadata for (potentially fat) pointers to this type,
|
||||
/// and a boolean signifying if this is conditional on this type being `Sized`.
|
||||
pub fn ptr_metadata_ty(
|
||||
/// or the struct tail if the metadata type cannot be determined.
|
||||
pub fn ptr_metadata_ty_or_tail(
|
||||
self,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
normalize: impl FnMut(Ty<'tcx>) -> Ty<'tcx>,
|
||||
) -> (Ty<'tcx>, bool) {
|
||||
) -> Result<Ty<'tcx>, Ty<'tcx>> {
|
||||
let tail = tcx.struct_tail_with_normalize(self, normalize, || {});
|
||||
match tail.kind() {
|
||||
// Sized types
|
||||
|
@ -2307,31 +2307,47 @@ impl<'tcx> Ty<'tcx> {
|
|||
| ty::Error(_)
|
||||
// Extern types have metadata = ().
|
||||
| ty::Foreign(..)
|
||||
// `dyn*` has no metadata
|
||||
// `dyn*` has metadata = ().
|
||||
| ty::Dynamic(_, _, ty::DynStar)
|
||||
// If returned by `struct_tail_without_normalization` this is a unit struct
|
||||
// If returned by `struct_tail_with_normalize` this is a unit struct
|
||||
// without any fields, or not a struct, and therefore is Sized.
|
||||
| ty::Adt(..)
|
||||
// If returned by `struct_tail_without_normalization` this is the empty tuple,
|
||||
// If returned by `struct_tail_with_normalize` this is the empty tuple,
|
||||
// a.k.a. unit type, which is Sized
|
||||
| ty::Tuple(..) => (tcx.types.unit, false),
|
||||
| ty::Tuple(..) => Ok(tcx.types.unit),
|
||||
|
||||
ty::Str | ty::Slice(_) => Ok(tcx.types.usize),
|
||||
|
||||
ty::Str | ty::Slice(_) => (tcx.types.usize, false),
|
||||
ty::Dynamic(_, _, ty::Dyn) => {
|
||||
let dyn_metadata = tcx.require_lang_item(LangItem::DynMetadata, None);
|
||||
(tcx.type_of(dyn_metadata).instantiate(tcx, &[tail.into()]), false)
|
||||
},
|
||||
Ok(tcx.type_of(dyn_metadata).instantiate(tcx, &[tail.into()]))
|
||||
}
|
||||
|
||||
// type parameters only have unit metadata if they're sized, so return true
|
||||
// to make sure we double check this during confirmation
|
||||
ty::Param(_) | ty::Alias(..) => (tcx.types.unit, true),
|
||||
// We don't know the metadata of `self`, but it must be equal to the
|
||||
// metadata of `tail`.
|
||||
ty::Param(_) | ty::Alias(..) => Err(tail),
|
||||
|
||||
ty::Infer(ty::TyVar(_))
|
||||
| ty::Bound(..)
|
||||
| ty::Placeholder(..)
|
||||
| ty::Infer(ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => {
|
||||
bug!("`ptr_metadata_ty` applied to unexpected type: {:?} (tail = {:?})", self, tail)
|
||||
}
|
||||
| ty::Infer(ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => bug!(
|
||||
"`ptr_metadata_ty_or_tail` applied to unexpected type: {self:?} (tail = {tail:?})"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the type of metadata for (potentially fat) pointers to this type.
|
||||
/// Causes an ICE if the metadata type cannot be determined.
|
||||
pub fn ptr_metadata_ty(
|
||||
self,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
normalize: impl FnMut(Ty<'tcx>) -> Ty<'tcx>,
|
||||
) -> Ty<'tcx> {
|
||||
match self.ptr_metadata_ty_or_tail(tcx, normalize) {
|
||||
Ok(metadata) => metadata,
|
||||
Err(tail) => bug!(
|
||||
"`ptr_metadata_ty` failed to get metadata for type: {self:?} (tail = {tail:?})"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ use rustc_infer::traits::query::NoSolution;
|
|||
use rustc_infer::traits::Reveal;
|
||||
use rustc_middle::traits::solve::inspect::ProbeKind;
|
||||
use rustc_middle::traits::solve::{
|
||||
CandidateSource, CanonicalResponse, Certainty, Goal, QueryResult,
|
||||
CandidateSource, CanonicalResponse, Certainty, Goal, MaybeCause, QueryResult,
|
||||
};
|
||||
use rustc_middle::traits::BuiltinImplSource;
|
||||
use rustc_middle::ty::fast_reject::{SimplifiedType, TreatParams};
|
||||
|
@ -276,25 +276,16 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
|||
&mut self,
|
||||
goal: Goal<'tcx, G>,
|
||||
) -> Vec<Candidate<'tcx>> {
|
||||
let dummy_candidate = |this: &mut EvalCtxt<'_, 'tcx>, certainty| {
|
||||
let source = CandidateSource::BuiltinImpl(BuiltinImplSource::Misc);
|
||||
let result = this.evaluate_added_goals_and_make_canonical_response(certainty).unwrap();
|
||||
let mut dummy_probe = this.inspect.new_probe();
|
||||
dummy_probe.probe_kind(ProbeKind::TraitCandidate { source, result: Ok(result) });
|
||||
this.inspect.finish_probe(dummy_probe);
|
||||
vec![Candidate { source, result }]
|
||||
};
|
||||
|
||||
let Some(normalized_self_ty) =
|
||||
self.try_normalize_ty(goal.param_env, goal.predicate.self_ty())
|
||||
else {
|
||||
debug!("overflow while evaluating self type");
|
||||
return dummy_candidate(self, Certainty::OVERFLOW);
|
||||
return self.forced_ambiguity(MaybeCause::Overflow);
|
||||
};
|
||||
|
||||
if normalized_self_ty.is_ty_var() {
|
||||
debug!("self type has been normalized to infer");
|
||||
return dummy_candidate(self, Certainty::AMBIGUOUS);
|
||||
return self.forced_ambiguity(MaybeCause::Ambiguity);
|
||||
}
|
||||
|
||||
let goal =
|
||||
|
@ -315,11 +306,26 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
|||
|
||||
self.assemble_param_env_candidates(goal, &mut candidates);
|
||||
|
||||
self.assemble_coherence_unknowable_candidates(goal, &mut candidates);
|
||||
match self.solver_mode() {
|
||||
SolverMode::Normal => self.discard_impls_shadowed_by_env(goal, &mut candidates),
|
||||
SolverMode::Coherence => {
|
||||
self.assemble_coherence_unknowable_candidates(goal, &mut candidates)
|
||||
}
|
||||
}
|
||||
|
||||
candidates
|
||||
}
|
||||
|
||||
fn forced_ambiguity(&mut self, cause: MaybeCause) -> Vec<Candidate<'tcx>> {
|
||||
let source = CandidateSource::BuiltinImpl(BuiltinImplSource::Misc);
|
||||
let certainty = Certainty::Maybe(cause);
|
||||
let result = self.evaluate_added_goals_and_make_canonical_response(certainty).unwrap();
|
||||
let mut dummy_probe = self.inspect.new_probe();
|
||||
dummy_probe.probe_kind(ProbeKind::TraitCandidate { source, result: Ok(result) });
|
||||
self.inspect.finish_probe(dummy_probe);
|
||||
vec![Candidate { source, result }]
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
fn assemble_non_blanket_impl_candidates<G: GoalKind<'tcx>>(
|
||||
&mut self,
|
||||
|
@ -779,6 +785,12 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
/// In coherence we have to not only care about all impls we know about, but
|
||||
/// also consider impls which may get added in a downstream or sibling crate
|
||||
/// or which an upstream impl may add in a minor release.
|
||||
///
|
||||
/// To do so we add an ambiguous candidate in case such an unknown impl could
|
||||
/// apply to the current goal.
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
fn assemble_coherence_unknowable_candidates<G: GoalKind<'tcx>>(
|
||||
&mut self,
|
||||
|
@ -786,11 +798,6 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
|||
candidates: &mut Vec<Candidate<'tcx>>,
|
||||
) {
|
||||
let tcx = self.tcx();
|
||||
match self.solver_mode() {
|
||||
SolverMode::Normal => return,
|
||||
SolverMode::Coherence => {}
|
||||
};
|
||||
|
||||
let result = self.probe_misc_candidate("coherence unknowable").enter(|ecx| {
|
||||
let trait_ref = goal.predicate.trait_ref(tcx);
|
||||
#[derive(Debug)]
|
||||
|
@ -820,6 +827,51 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
/// If there's a where-bound for the current goal, do not use any impl candidates
|
||||
/// to prove the current goal. Most importantly, if there is a where-bound which does
|
||||
/// not specify any associated types, we do not allow normalizing the associated type
|
||||
/// by using an impl, even if it would apply.
|
||||
///
|
||||
/// <https://github.com/rust-lang/trait-system-refactor-initiative/issues/76>
|
||||
// FIXME(@lcnr): The current structure here makes me unhappy and feels ugly. idk how
|
||||
// to improve this however. However, this should make it fairly straightforward to refine
|
||||
// the filtering going forward, so it seems alright-ish for now.
|
||||
fn discard_impls_shadowed_by_env<G: GoalKind<'tcx>>(
|
||||
&mut self,
|
||||
goal: Goal<'tcx, G>,
|
||||
candidates: &mut Vec<Candidate<'tcx>>,
|
||||
) {
|
||||
let tcx = self.tcx();
|
||||
let trait_goal: Goal<'tcx, ty::TraitPredicate<'tcx>> =
|
||||
goal.with(tcx, goal.predicate.trait_ref(tcx));
|
||||
let mut trait_candidates_from_env = Vec::new();
|
||||
self.assemble_param_env_candidates(trait_goal, &mut trait_candidates_from_env);
|
||||
self.assemble_alias_bound_candidates(trait_goal, &mut trait_candidates_from_env);
|
||||
if !trait_candidates_from_env.is_empty() {
|
||||
let trait_env_result = self.merge_candidates(trait_candidates_from_env);
|
||||
match trait_env_result.unwrap().value.certainty {
|
||||
// If proving the trait goal succeeds by using the env,
|
||||
// we freely drop all impl candidates.
|
||||
//
|
||||
// FIXME(@lcnr): It feels like this could easily hide
|
||||
// a forced ambiguity candidate added earlier.
|
||||
// This feels dangerous.
|
||||
Certainty::Yes => {
|
||||
candidates.retain(|c| match c.source {
|
||||
CandidateSource::Impl(_) | CandidateSource::BuiltinImpl(_) => false,
|
||||
CandidateSource::ParamEnv(_) | CandidateSource::AliasBound => true,
|
||||
});
|
||||
}
|
||||
// If it is still ambiguous we instead just force the whole goal
|
||||
// to be ambig and wait for inference constraints. See
|
||||
// tests/ui/traits/next-solver/env-shadows-impls/ambig-env-no-shadow.rs
|
||||
Certainty::Maybe(cause) => {
|
||||
*candidates = self.forced_ambiguity(cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// If there are multiple ways to prove a trait or projection goal, we have
|
||||
/// to somehow try to merge the candidates into one. If that fails, we return
|
||||
/// ambiguity.
|
||||
|
@ -832,34 +884,8 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
|||
let responses = candidates.iter().map(|c| c.result).collect::<Vec<_>>();
|
||||
if let Some(result) = self.try_merge_responses(&responses) {
|
||||
return Ok(result);
|
||||
} else {
|
||||
self.flounder(&responses)
|
||||
}
|
||||
|
||||
// We then check whether we should prioritize `ParamEnv` candidates.
|
||||
//
|
||||
// Doing so is incomplete and would therefore be unsound during coherence.
|
||||
match self.solver_mode() {
|
||||
SolverMode::Coherence => (),
|
||||
// Prioritize `ParamEnv` candidates only if they do not guide inference.
|
||||
//
|
||||
// This is still incomplete as we may add incorrect region bounds.
|
||||
SolverMode::Normal => {
|
||||
let param_env_responses = candidates
|
||||
.iter()
|
||||
.filter(|c| {
|
||||
matches!(
|
||||
c.source,
|
||||
CandidateSource::ParamEnv(_) | CandidateSource::AliasBound
|
||||
)
|
||||
})
|
||||
.map(|c| c.result)
|
||||
.collect::<Vec<_>>();
|
||||
if let Some(result) = self.try_merge_responses(¶m_env_responses) {
|
||||
// We strongly prefer alias and param-env bounds here, even if they affect inference.
|
||||
// See https://github.com/rust-lang/trait-system-refactor-initiative/issues/11.
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.flounder(&responses)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -491,6 +491,8 @@ impl<'tcx> assembly::GoalKind<'tcx> for NormalizesTo<'tcx> {
|
|||
goal: Goal<'tcx, Self>,
|
||||
) -> QueryResult<'tcx> {
|
||||
let tcx = ecx.tcx();
|
||||
let metadata_def_id = tcx.require_lang_item(LangItem::Metadata, None);
|
||||
assert_eq!(metadata_def_id, goal.predicate.def_id());
|
||||
ecx.probe_misc_candidate("builtin pointee").enter(|ecx| {
|
||||
let metadata_ty = match goal.predicate.self_ty().kind() {
|
||||
ty::Bool
|
||||
|
@ -522,7 +524,10 @@ impl<'tcx> assembly::GoalKind<'tcx> for NormalizesTo<'tcx> {
|
|||
}
|
||||
|
||||
ty::Alias(_, _) | ty::Param(_) | ty::Placeholder(..) => {
|
||||
// FIXME(ptr_metadata): It would also be possible to return a `Ok(Ambig)` with no constraints.
|
||||
// This is the "fallback impl" for type parameters, unnormalizable projections
|
||||
// and opaque types: If the `self_ty` is `Sized`, then the metadata is `()`.
|
||||
// FIXME(ptr_metadata): This impl overlaps with the other impls and shouldn't
|
||||
// exist. Instead, `Pointee<Metadata = ()>` should be a supertrait of `Sized`.
|
||||
let sized_predicate = ty::TraitRef::from_lang_item(
|
||||
tcx,
|
||||
LangItem::Sized,
|
||||
|
@ -536,30 +541,16 @@ impl<'tcx> assembly::GoalKind<'tcx> for NormalizesTo<'tcx> {
|
|||
|
||||
ty::Adt(def, args) if def.is_struct() => match def.non_enum_variant().tail_opt() {
|
||||
None => tcx.types.unit,
|
||||
Some(field_def) => {
|
||||
let self_ty = field_def.ty(tcx, args);
|
||||
// FIXME(-Znext-solver=coinductive): Should this be `GoalSource::ImplWhereBound`?
|
||||
ecx.add_goal(
|
||||
GoalSource::Misc,
|
||||
goal.with(tcx, goal.predicate.with_self_ty(tcx, self_ty)),
|
||||
);
|
||||
return ecx
|
||||
.evaluate_added_goals_and_make_canonical_response(Certainty::Yes);
|
||||
Some(tail_def) => {
|
||||
let tail_ty = tail_def.ty(tcx, args);
|
||||
Ty::new_projection(tcx, metadata_def_id, [tail_ty])
|
||||
}
|
||||
},
|
||||
ty::Adt(_, _) => tcx.types.unit,
|
||||
|
||||
ty::Tuple(elements) => match elements.last() {
|
||||
None => tcx.types.unit,
|
||||
Some(&self_ty) => {
|
||||
// FIXME(-Znext-solver=coinductive): Should this be `GoalSource::ImplWhereBound`?
|
||||
ecx.add_goal(
|
||||
GoalSource::Misc,
|
||||
goal.with(tcx, goal.predicate.with_self_ty(tcx, self_ty)),
|
||||
);
|
||||
return ecx
|
||||
.evaluate_added_goals_and_make_canonical_response(Certainty::Yes);
|
||||
}
|
||||
Some(&tail_ty) => Ty::new_projection(tcx, metadata_def_id, [tail_ty]),
|
||||
},
|
||||
|
||||
ty::Infer(
|
||||
|
|
|
@ -1935,10 +1935,11 @@ fn assemble_candidates_from_impls<'cx, 'tcx>(
|
|||
// Integers and floats are always Sized, and so have unit type metadata.
|
||||
| ty::Infer(ty::InferTy::IntVar(_) | ty::InferTy::FloatVar(..)) => true,
|
||||
|
||||
// type parameters, opaques, and unnormalized projections have pointer
|
||||
// metadata if they're known (e.g. by the param_env) to be sized
|
||||
// We normalize from `Wrapper<Tail>::Metadata` to `Tail::Metadata` if able.
|
||||
// Otherwise, type parameters, opaques, and unnormalized projections have
|
||||
// unit metadata if they're known (e.g. by the param_env) to be sized.
|
||||
ty::Param(_) | ty::Alias(..)
|
||||
if selcx.infcx.predicate_must_hold_modulo_regions(
|
||||
if self_ty != tail || selcx.infcx.predicate_must_hold_modulo_regions(
|
||||
&obligation.with(
|
||||
selcx.tcx(),
|
||||
ty::TraitRef::from_lang_item(selcx.tcx(), LangItem::Sized, obligation.cause.span(),[self_ty]),
|
||||
|
@ -2312,7 +2313,7 @@ fn confirm_builtin_candidate<'cx, 'tcx>(
|
|||
assert_eq!(metadata_def_id, item_def_id);
|
||||
|
||||
let mut obligations = Vec::new();
|
||||
let (metadata_ty, check_is_sized) = self_ty.ptr_metadata_ty(tcx, |ty| {
|
||||
let normalize = |ty| {
|
||||
normalize_with_depth_to(
|
||||
selcx,
|
||||
obligation.param_env,
|
||||
|
@ -2321,16 +2322,27 @@ fn confirm_builtin_candidate<'cx, 'tcx>(
|
|||
ty,
|
||||
&mut obligations,
|
||||
)
|
||||
};
|
||||
let metadata_ty = self_ty.ptr_metadata_ty_or_tail(tcx, normalize).unwrap_or_else(|tail| {
|
||||
if tail == self_ty {
|
||||
// This is the "fallback impl" for type parameters, unnormalizable projections
|
||||
// and opaque types: If the `self_ty` is `Sized`, then the metadata is `()`.
|
||||
// FIXME(ptr_metadata): This impl overlaps with the other impls and shouldn't
|
||||
// exist. Instead, `Pointee<Metadata = ()>` should be a supertrait of `Sized`.
|
||||
let sized_predicate = ty::TraitRef::from_lang_item(
|
||||
tcx,
|
||||
LangItem::Sized,
|
||||
obligation.cause.span(),
|
||||
[self_ty],
|
||||
);
|
||||
obligations.push(obligation.with(tcx, sized_predicate));
|
||||
tcx.types.unit
|
||||
} else {
|
||||
// We know that `self_ty` has the same metadata as `tail`. This allows us
|
||||
// to prove predicates like `Wrapper<Tail>::Metadata == Tail::Metadata`.
|
||||
Ty::new_projection(tcx, metadata_def_id, [tail])
|
||||
}
|
||||
});
|
||||
if check_is_sized {
|
||||
let sized_predicate = ty::TraitRef::from_lang_item(
|
||||
tcx,
|
||||
LangItem::Sized,
|
||||
obligation.cause.span(),
|
||||
[self_ty],
|
||||
);
|
||||
obligations.push(obligation.with(tcx, sized_predicate));
|
||||
}
|
||||
(metadata_ty.into(), obligations)
|
||||
} else {
|
||||
bug!("unexpected builtin trait with associated type: {:?}", obligation.predicate);
|
||||
|
|
|
@ -41,7 +41,28 @@ impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> {
|
|||
/// not entirely accurate if inference variables are involved.
|
||||
///
|
||||
/// This version may conservatively fail when outlives obligations
|
||||
/// are required.
|
||||
/// are required. Therefore, this version should only be used for
|
||||
/// optimizations or diagnostics and be treated as if it can always
|
||||
/// return `false`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # #![allow(dead_code)]
|
||||
/// trait Trait {}
|
||||
///
|
||||
/// fn check<T: Trait>() {}
|
||||
///
|
||||
/// fn foo<T: 'static>()
|
||||
/// where
|
||||
/// &'static T: Trait,
|
||||
/// {
|
||||
/// // Evaluating `&'?0 T: Trait` adds a `'?0: 'static` outlives obligation,
|
||||
/// // which means that `predicate_must_hold_considering_regions` will return
|
||||
/// // `false`.
|
||||
/// check::<&'_ T>();
|
||||
/// }
|
||||
/// ```
|
||||
fn predicate_must_hold_considering_regions(
|
||||
&self,
|
||||
obligation: &PredicateObligation<'tcx>,
|
||||
|
|
|
@ -1073,18 +1073,23 @@ impl<T> Option<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Calls the provided closure with a reference to the contained value (if [`Some`]).
|
||||
/// Calls a function with a reference to the contained value if [`Some`].
|
||||
///
|
||||
/// Returns the original option.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// let v = vec![1, 2, 3, 4, 5];
|
||||
/// let list = vec![1, 2, 3];
|
||||
///
|
||||
/// // prints "got: 4"
|
||||
/// let x: Option<&usize> = v.get(3).inspect(|x| println!("got: {x}"));
|
||||
/// // prints "got: 2"
|
||||
/// let x = list
|
||||
/// .get(1)
|
||||
/// .inspect(|x| println!("got: {x}"))
|
||||
/// .expect("list should be long enough");
|
||||
///
|
||||
/// // prints nothing
|
||||
/// let x: Option<&usize> = v.get(5).inspect(|x| println!("got: {x}"));
|
||||
/// list.get(5).inspect(|x| println!("got: {x}"));
|
||||
/// ```
|
||||
#[inline]
|
||||
#[stable(feature = "result_option_inspect", since = "1.76.0")]
|
||||
|
|
|
@ -830,7 +830,9 @@ impl<T, E> Result<T, E> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Calls the provided closure with a reference to the contained value (if [`Ok`]).
|
||||
/// Calls a function with a reference to the contained value if [`Ok`].
|
||||
///
|
||||
/// Returns the original result.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
@ -851,7 +853,9 @@ impl<T, E> Result<T, E> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Calls the provided closure with a reference to the contained error (if [`Err`]).
|
||||
/// Calls a function with a reference to the contained value if [`Err`].
|
||||
///
|
||||
/// Returns the original result.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
|
|
@ -7,6 +7,7 @@ mod personality;
|
|||
|
||||
pub mod cmath;
|
||||
pub mod os_str;
|
||||
pub mod path;
|
||||
|
||||
// FIXME(117276): remove this, move feature implementations into individual
|
||||
// submodules.
|
||||
|
|
|
@ -28,8 +28,6 @@ pub mod io;
|
|||
pub mod memchr;
|
||||
pub mod net;
|
||||
pub mod os;
|
||||
#[path = "../unix/path.rs"]
|
||||
pub mod path;
|
||||
#[path = "../unsupported/pipe.rs"]
|
||||
pub mod pipe;
|
||||
#[path = "../unsupported/process.rs"]
|
||||
|
|
|
@ -22,7 +22,6 @@ pub mod io;
|
|||
pub mod memchr;
|
||||
pub mod net;
|
||||
pub mod os;
|
||||
pub mod path;
|
||||
#[path = "../unsupported/pipe.rs"]
|
||||
pub mod pipe;
|
||||
#[path = "../unsupported/process.rs"]
|
||||
|
|
|
@ -29,7 +29,6 @@ pub mod fs;
|
|||
pub mod io;
|
||||
pub mod net;
|
||||
pub mod os;
|
||||
pub mod path;
|
||||
#[path = "../unsupported/pipe.rs"]
|
||||
pub mod pipe;
|
||||
#[path = "../unsupported/process.rs"]
|
||||
|
|
|
@ -25,8 +25,6 @@ pub mod net;
|
|||
#[path = "../unsupported/once.rs"]
|
||||
pub mod once;
|
||||
pub mod os;
|
||||
#[path = "../unix/path.rs"]
|
||||
pub mod path;
|
||||
#[path = "../unsupported/pipe.rs"]
|
||||
pub mod pipe;
|
||||
#[path = "../unsupported/process.rs"]
|
||||
|
|
|
@ -146,3 +146,11 @@ pub(crate) fn image_handle_protocol<T>(protocol_guid: Guid) -> Option<NonNull<T>
|
|||
let system_handle = uefi::env::try_image_handle()?;
|
||||
open_protocol(system_handle, protocol_guid).ok()
|
||||
}
|
||||
|
||||
/// Get RuntimeServices
|
||||
pub(crate) fn runtime_services() -> Option<NonNull<r_efi::efi::RuntimeServices>> {
|
||||
let system_table: NonNull<r_efi::efi::SystemTable> =
|
||||
crate::os::uefi::env::try_system_table()?.cast();
|
||||
let runtime_services = unsafe { (*system_table.as_ptr()).runtime_services };
|
||||
NonNull::new(runtime_services)
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ pub mod net;
|
|||
#[path = "../unsupported/once.rs"]
|
||||
pub mod once;
|
||||
pub mod os;
|
||||
pub mod path;
|
||||
#[path = "../unsupported/pipe.rs"]
|
||||
pub mod pipe;
|
||||
#[path = "../unsupported/process.rs"]
|
||||
|
@ -38,7 +37,6 @@ pub mod thread;
|
|||
pub mod thread_local_key;
|
||||
#[path = "../unsupported/thread_parking.rs"]
|
||||
pub mod thread_parking;
|
||||
#[path = "../unsupported/time.rs"]
|
||||
pub mod time;
|
||||
|
||||
mod helpers;
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
use super::unsupported;
|
||||
use crate::ffi::OsStr;
|
||||
use crate::io;
|
||||
use crate::path::{Path, PathBuf, Prefix};
|
||||
|
||||
pub const MAIN_SEP_STR: &str = "\\";
|
||||
pub const MAIN_SEP: char = '\\';
|
||||
|
||||
#[inline]
|
||||
pub fn is_sep_byte(b: u8) -> bool {
|
||||
b == b'\\'
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_verbatim_sep(b: u8) -> bool {
|
||||
b == b'\\'
|
||||
}
|
||||
|
||||
pub fn parse_prefix(_p: &OsStr) -> Option<Prefix<'_>> {
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn absolute(_path: &Path) -> io::Result<PathBuf> {
|
||||
unsupported()
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
use super::alloc::*;
|
||||
use super::time::*;
|
||||
use crate::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn align() {
|
||||
|
@ -19,3 +21,21 @@ fn align() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn epoch() {
|
||||
let t = r_efi::system::Time {
|
||||
year: 1970,
|
||||
month: 1,
|
||||
day: 1,
|
||||
hour: 0,
|
||||
minute: 0,
|
||||
second: 0,
|
||||
nanosecond: 0,
|
||||
timezone: r_efi::efi::UNSPECIFIED_TIMEZONE,
|
||||
daylight: 0,
|
||||
pad1: 0,
|
||||
pad2: 0,
|
||||
};
|
||||
assert_eq!(system_time_internal::uefi_time_to_duration(t), Duration::new(0, 0));
|
||||
}
|
||||
|
|
105
library/std/src/sys/pal/uefi/time.rs
Normal file
105
library/std/src/sys/pal/uefi/time.rs
Normal file
|
@ -0,0 +1,105 @@
|
|||
use crate::time::Duration;
|
||||
|
||||
const SECS_IN_MINUTE: u64 = 60;
|
||||
const SECS_IN_HOUR: u64 = SECS_IN_MINUTE * 60;
|
||||
const SECS_IN_DAY: u64 = SECS_IN_HOUR * 24;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
|
||||
pub struct Instant(Duration);
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
|
||||
pub struct SystemTime(Duration);
|
||||
|
||||
pub const UNIX_EPOCH: SystemTime = SystemTime(Duration::from_secs(0));
|
||||
|
||||
impl Instant {
|
||||
pub fn now() -> Instant {
|
||||
panic!("time not implemented on this platform")
|
||||
}
|
||||
|
||||
pub fn checked_sub_instant(&self, other: &Instant) -> Option<Duration> {
|
||||
self.0.checked_sub(other.0)
|
||||
}
|
||||
|
||||
pub fn checked_add_duration(&self, other: &Duration) -> Option<Instant> {
|
||||
Some(Instant(self.0.checked_add(*other)?))
|
||||
}
|
||||
|
||||
pub fn checked_sub_duration(&self, other: &Duration) -> Option<Instant> {
|
||||
Some(Instant(self.0.checked_sub(*other)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl SystemTime {
|
||||
pub fn now() -> SystemTime {
|
||||
system_time_internal::now()
|
||||
.unwrap_or_else(|| panic!("time not implemented on this platform"))
|
||||
}
|
||||
|
||||
pub fn sub_time(&self, other: &SystemTime) -> Result<Duration, Duration> {
|
||||
self.0.checked_sub(other.0).ok_or_else(|| other.0 - self.0)
|
||||
}
|
||||
|
||||
pub fn checked_add_duration(&self, other: &Duration) -> Option<SystemTime> {
|
||||
Some(SystemTime(self.0.checked_add(*other)?))
|
||||
}
|
||||
|
||||
pub fn checked_sub_duration(&self, other: &Duration) -> Option<SystemTime> {
|
||||
Some(SystemTime(self.0.checked_sub(*other)?))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) mod system_time_internal {
|
||||
use super::super::helpers;
|
||||
use super::*;
|
||||
use crate::mem::MaybeUninit;
|
||||
use crate::ptr::NonNull;
|
||||
use r_efi::efi::{RuntimeServices, Time};
|
||||
|
||||
pub fn now() -> Option<SystemTime> {
|
||||
let runtime_services: NonNull<RuntimeServices> = helpers::runtime_services()?;
|
||||
let mut t: MaybeUninit<Time> = MaybeUninit::uninit();
|
||||
let r = unsafe {
|
||||
((*runtime_services.as_ptr()).get_time)(t.as_mut_ptr(), crate::ptr::null_mut())
|
||||
};
|
||||
|
||||
if r.is_error() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let t = unsafe { t.assume_init() };
|
||||
|
||||
Some(SystemTime(uefi_time_to_duration(t)))
|
||||
}
|
||||
|
||||
// This algorithm is based on the one described in the post
|
||||
// https://blog.reverberate.org/2020/05/12/optimizing-date-algorithms.html
|
||||
pub const fn uefi_time_to_duration(t: r_efi::system::Time) -> Duration {
|
||||
assert!(t.month <= 12);
|
||||
assert!(t.month != 0);
|
||||
|
||||
const YEAR_BASE: u32 = 4800; /* Before min year, multiple of 400. */
|
||||
|
||||
// Calculate the number of days since 1/1/1970
|
||||
// Use 1 March as the start
|
||||
let (m_adj, overflow): (u32, bool) = (t.month as u32).overflowing_sub(3);
|
||||
let (carry, adjust): (u32, u32) = if overflow { (1, 12) } else { (0, 0) };
|
||||
let y_adj: u32 = (t.year as u32) + YEAR_BASE - carry;
|
||||
let month_days: u32 = (m_adj.wrapping_add(adjust) * 62719 + 769) / 2048;
|
||||
let leap_days: u32 = y_adj / 4 - y_adj / 100 + y_adj / 400;
|
||||
let days: u32 = y_adj * 365 + leap_days + month_days + (t.day as u32 - 1) - 2472632;
|
||||
|
||||
let localtime_epoch: u64 = (days as u64) * SECS_IN_DAY
|
||||
+ (t.second as u64)
|
||||
+ (t.minute as u64) * SECS_IN_MINUTE
|
||||
+ (t.hour as u64) * SECS_IN_HOUR;
|
||||
|
||||
let utc_epoch: u64 = if t.timezone == r_efi::efi::UNSPECIFIED_TIMEZONE {
|
||||
localtime_epoch
|
||||
} else {
|
||||
(localtime_epoch as i64 + (t.timezone as i64) * SECS_IN_MINUTE as i64) as u64
|
||||
};
|
||||
|
||||
Duration::new(utc_epoch, t.nanosecond)
|
||||
}
|
||||
}
|
|
@ -27,7 +27,6 @@ pub mod net;
|
|||
#[cfg(target_os = "l4re")]
|
||||
pub use self::l4re::net;
|
||||
pub mod os;
|
||||
pub mod path;
|
||||
pub mod pipe;
|
||||
pub mod process;
|
||||
pub mod rand;
|
||||
|
|
|
@ -9,8 +9,6 @@ pub mod locks;
|
|||
pub mod net;
|
||||
pub mod once;
|
||||
pub mod os;
|
||||
#[path = "../unix/path.rs"]
|
||||
pub mod path;
|
||||
pub mod pipe;
|
||||
pub mod process;
|
||||
pub mod stdio;
|
||||
|
|
|
@ -30,8 +30,6 @@ pub mod io;
|
|||
|
||||
pub mod net;
|
||||
pub mod os;
|
||||
#[path = "../unix/path.rs"]
|
||||
pub mod path;
|
||||
#[path = "../unsupported/pipe.rs"]
|
||||
pub mod pipe;
|
||||
#[path = "../unsupported/process.rs"]
|
||||
|
|
|
@ -28,8 +28,6 @@ pub mod io;
|
|||
pub mod net;
|
||||
#[path = "../unsupported/os.rs"]
|
||||
pub mod os;
|
||||
#[path = "../unix/path.rs"]
|
||||
pub mod path;
|
||||
#[path = "../unsupported/pipe.rs"]
|
||||
pub mod pipe;
|
||||
#[path = "../unsupported/process.rs"]
|
||||
|
|
|
@ -16,8 +16,8 @@ use crate::sys::{c, cvt, Align8};
|
|||
use crate::sys_common::{AsInner, FromInner, IntoInner};
|
||||
use crate::thread;
|
||||
|
||||
use super::path::maybe_verbatim;
|
||||
use super::{api, to_u16s, IoResult};
|
||||
use crate::sys::path::maybe_verbatim;
|
||||
|
||||
pub struct File {
|
||||
handle: Handle,
|
||||
|
|
|
@ -23,7 +23,6 @@ pub mod locks;
|
|||
pub mod memchr;
|
||||
pub mod net;
|
||||
pub mod os;
|
||||
pub mod path;
|
||||
pub mod pipe;
|
||||
pub mod process;
|
||||
pub mod rand;
|
||||
|
@ -210,7 +209,7 @@ pub fn to_u16s<S: AsRef<OsStr>>(s: S) -> crate::io::Result<Vec<u16>> {
|
|||
// Once the syscall has completed (errors bail out early) the second closure is
|
||||
// yielded the data which has been read from the syscall. The return value
|
||||
// from this closure is then the return value of the function.
|
||||
fn fill_utf16_buf<F1, F2, T>(mut f1: F1, f2: F2) -> crate::io::Result<T>
|
||||
pub fn fill_utf16_buf<F1, F2, T>(mut f1: F1, f2: F2) -> crate::io::Result<T>
|
||||
where
|
||||
F1: FnMut(*mut u16, c::DWORD) -> c::DWORD,
|
||||
F2: FnOnce(&[u16]) -> T,
|
||||
|
@ -274,7 +273,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn os2path(s: &[u16]) -> PathBuf {
|
||||
pub fn os2path(s: &[u16]) -> PathBuf {
|
||||
PathBuf::from(OsString::from_wide(s))
|
||||
}
|
||||
|
||||
|
|
|
@ -12,8 +12,6 @@ pub mod io;
|
|||
pub mod locks;
|
||||
pub mod net;
|
||||
pub mod os;
|
||||
#[path = "../unix/path.rs"]
|
||||
pub mod path;
|
||||
#[path = "../unsupported/pipe.rs"]
|
||||
pub mod pipe;
|
||||
#[path = "../unsupported/process.rs"]
|
||||
|
|
|
@ -24,10 +24,6 @@ pub mod net;
|
|||
#[path = "../unsupported/once.rs"]
|
||||
pub mod once;
|
||||
pub mod os;
|
||||
#[path = "../unix/os_str.rs"]
|
||||
pub mod os_str;
|
||||
#[path = "../unix/path.rs"]
|
||||
pub mod path;
|
||||
#[path = "../unsupported/pipe.rs"]
|
||||
pub mod pipe;
|
||||
#[path = "../unsupported/process.rs"]
|
||||
|
|
18
library/std/src/sys/path/mod.rs
Normal file
18
library/std/src/sys/path/mod.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "windows")] {
|
||||
mod windows;
|
||||
pub use windows::*;
|
||||
} else if #[cfg(all(target_vendor = "fortanix", target_env = "sgx"))] {
|
||||
mod sgx;
|
||||
pub use sgx::*;
|
||||
} else if #[cfg(any(
|
||||
target_os = "uefi",
|
||||
target_os = "solid_asp3",
|
||||
))] {
|
||||
mod unsupported_backslash;
|
||||
pub use unsupported_backslash::*;
|
||||
} else {
|
||||
mod unix;
|
||||
pub use unix::*;
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
use super::{c, fill_utf16_buf, to_u16s};
|
||||
use crate::ffi::{OsStr, OsString};
|
||||
use crate::io;
|
||||
use crate::path::{Path, PathBuf, Prefix};
|
||||
use crate::ptr;
|
||||
use crate::sys::pal::{c, fill_utf16_buf, os2path, to_u16s};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
@ -339,6 +339,6 @@ pub(crate) fn absolute(path: &Path) -> io::Result<PathBuf> {
|
|||
// `lpfilename` is a pointer to a null terminated string that is not
|
||||
// invalidated until after `GetFullPathNameW` returns successfully.
|
||||
|buffer, size| unsafe { c::GetFullPathNameW(lpfilename, size, buffer, ptr::null_mut()) },
|
||||
super::os2path,
|
||||
os2path,
|
||||
)
|
||||
}
|
|
@ -720,8 +720,10 @@ download-rustc = false
|
|||
if !tarball.exists() {
|
||||
let help_on_error = "ERROR: failed to download llvm from ci
|
||||
|
||||
HELP: old builds get deleted after a certain time
|
||||
HELP: if trying to compile an old commit of rustc, disable `download-ci-llvm` in config.toml:
|
||||
HELP: There could be two reasons behind this:
|
||||
1) The host triple is not supported for `download-ci-llvm`.
|
||||
2) Old builds get deleted after a certain time.
|
||||
HELP: In either case, disable `download-ci-llvm` in your config.toml:
|
||||
|
||||
[llvm]
|
||||
download-ci-llvm = false
|
||||
|
|
|
@ -72,8 +72,12 @@ if [ -f "$docker_dir/$image/Dockerfile" ]; then
|
|||
# Include cache version. Can be used to manually bust the Docker cache.
|
||||
echo "2" >> $hash_key
|
||||
|
||||
echo "Image input"
|
||||
cat $hash_key
|
||||
|
||||
cksum=$(sha512sum $hash_key | \
|
||||
awk '{print $1}')
|
||||
echo "Image input checksum ${cksum}"
|
||||
fi
|
||||
|
||||
dockerfile="$docker_dir/$image/Dockerfile"
|
||||
|
@ -84,9 +88,6 @@ if [ -f "$docker_dir/$image/Dockerfile" ]; then
|
|||
context="$script_dir"
|
||||
fi
|
||||
echo "::group::Building docker image for $image"
|
||||
echo "Image input"
|
||||
cat $hash_key
|
||||
echo "Image input checksum ${cksum}"
|
||||
|
||||
# Print docker version
|
||||
docker --version
|
||||
|
|
15
src/tools/clippy/.github/driver.sh
vendored
Normal file → Executable file
15
src/tools/clippy/.github/driver.sh
vendored
Normal file → Executable file
|
@ -11,9 +11,16 @@ if [[ ${OS} == "Windows" ]]; then
|
|||
else
|
||||
desired_sysroot=/tmp
|
||||
fi
|
||||
# Set --sysroot in command line
|
||||
sysroot=$(./target/debug/clippy-driver --sysroot $desired_sysroot --print sysroot)
|
||||
test "$sysroot" = $desired_sysroot
|
||||
|
||||
# Set --sysroot in arg_file.txt and pass @arg_file.txt to command line
|
||||
echo "--sysroot=$desired_sysroot" > arg_file.txt
|
||||
sysroot=$(./target/debug/clippy-driver @arg_file.txt --print sysroot)
|
||||
test "$sysroot" = $desired_sysroot
|
||||
|
||||
# Setting SYSROOT in command line
|
||||
sysroot=$(SYSROOT=$desired_sysroot ./target/debug/clippy-driver --print sysroot)
|
||||
test "$sysroot" = $desired_sysroot
|
||||
|
||||
|
@ -24,6 +31,14 @@ test "$sysroot" = $desired_sysroot
|
|||
SYSROOT=/tmp RUSTFLAGS="--sysroot=$(rustc --print sysroot)" ../target/debug/cargo-clippy clippy --verbose
|
||||
)
|
||||
|
||||
# Check that the --sysroot argument is only passed once via arg_file.txt (SYSROOT is ignored)
|
||||
(
|
||||
echo "fn main() {}" > target/driver_test.rs
|
||||
echo "--sysroot="$(./target/debug/clippy-driver --print sysroot)"" > arg_file.txt
|
||||
echo "--verbose" >> arg_file.txt
|
||||
SYSROOT=/tmp ./target/debug/clippy-driver @arg_file.txt ./target/driver_test.rs
|
||||
)
|
||||
|
||||
# Make sure this isn't set - clippy-driver should cope without it
|
||||
unset CARGO_MANIFEST_DIR
|
||||
|
||||
|
|
|
@ -6,11 +6,65 @@ document.
|
|||
|
||||
## Unreleased / Beta / In Rust Nightly
|
||||
|
||||
[09ac14c9...master](https://github.com/rust-lang/rust-clippy/compare/09ac14c9...master)
|
||||
[a859e5cc...master](https://github.com/rust-lang/rust-clippy/compare/a859e5cc...master)
|
||||
|
||||
## Rust 1.76
|
||||
|
||||
Current stable, released 2024-02-08
|
||||
|
||||
[View all 85 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2023-11-02T20%3A23%3A40Z..2023-12-16T13%3A11%3A08Z+base%3Amaster)
|
||||
|
||||
### New Lints
|
||||
|
||||
- [`infinite_loop`]
|
||||
[#11829](https://github.com/rust-lang/rust-clippy/pull/11829)
|
||||
- [`ineffective_open_options`]
|
||||
[#11902](https://github.com/rust-lang/rust-clippy/pull/11902)
|
||||
- [`uninhabited_references`]
|
||||
[#11878](https://github.com/rust-lang/rust-clippy/pull/11878)
|
||||
- [`repeat_vec_with_capacity`]
|
||||
[#11597](https://github.com/rust-lang/rust-clippy/pull/11597)
|
||||
- [`test_attr_in_doctest`]
|
||||
[#11872](https://github.com/rust-lang/rust-clippy/pull/11872)
|
||||
- [`option_map_or_err_ok`]
|
||||
[#11864](https://github.com/rust-lang/rust-clippy/pull/11864)
|
||||
- [`join_absolute_paths`]
|
||||
[#11453](https://github.com/rust-lang/rust-clippy/pull/11453)
|
||||
- [`impl_hash_borrow_with_str_and_bytes`]
|
||||
[#11781](https://github.com/rust-lang/rust-clippy/pull/11781)
|
||||
- [`iter_over_hash_type`]
|
||||
[#11791](https://github.com/rust-lang/rust-clippy/pull/11791)
|
||||
|
||||
### Moves and Deprecations
|
||||
|
||||
- Renamed `blocks_in_if_conditions` to [`blocks_in_conditions`]
|
||||
[#11853](https://github.com/rust-lang/rust-clippy/pull/11853)
|
||||
- Moved [`implied_bounds_in_impls`] to `complexity` (Now warn-by-default)
|
||||
[#11867](https://github.com/rust-lang/rust-clippy/pull/11867)
|
||||
- Moved [`if_same_then_else`] to `style` (Now warn-by-default)
|
||||
[#11809](https://github.com/rust-lang/rust-clippy/pull/11809)
|
||||
|
||||
### Enhancements
|
||||
|
||||
- [`missing_safety_doc`], [`unnecessary_safety_doc`], [`missing_panics_doc`], [`missing_errors_doc`]:
|
||||
Added the [`check-private-items`] configuration to enable lints on private items
|
||||
[#11842](https://github.com/rust-lang/rust-clippy/pull/11842)
|
||||
|
||||
### ICE Fixes
|
||||
|
||||
- [`impl_trait_in_params`]: No longer crashes when a function has generics but no function parameters
|
||||
[#11804](https://github.com/rust-lang/rust-clippy/pull/11804)
|
||||
- [`unused_enumerate_index`]: No longer crashes on empty tuples
|
||||
[#11756](https://github.com/rust-lang/rust-clippy/pull/11756)
|
||||
|
||||
### Others
|
||||
|
||||
- Clippy now respects the `CARGO` environment value
|
||||
[#11944](https://github.com/rust-lang/rust-clippy/pull/11944)
|
||||
|
||||
## Rust 1.75
|
||||
|
||||
Current stable, released 2023-12-28
|
||||
Released 2023-12-28
|
||||
|
||||
[View all 69 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2023-09-25T11%3A47%3A47Z..2023-11-02T16%3A41%3A59Z+base%3Amaster)
|
||||
|
||||
|
@ -5198,6 +5252,7 @@ Released 2018-09-13
|
|||
[`implied_bounds_in_impls`]: https://rust-lang.github.io/rust-clippy/master/index.html#implied_bounds_in_impls
|
||||
[`impossible_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#impossible_comparisons
|
||||
[`imprecise_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#imprecise_flops
|
||||
[`incompatible_msrv`]: https://rust-lang.github.io/rust-clippy/master/index.html#incompatible_msrv
|
||||
[`inconsistent_digit_grouping`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_digit_grouping
|
||||
[`inconsistent_struct_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_struct_constructor
|
||||
[`incorrect_clone_impl_on_copy_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#incorrect_clone_impl_on_copy_type
|
||||
|
@ -5276,6 +5331,7 @@ Released 2018-09-13
|
|||
[`let_with_type_underscore`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_with_type_underscore
|
||||
[`lines_filter_map_ok`]: https://rust-lang.github.io/rust-clippy/master/index.html#lines_filter_map_ok
|
||||
[`linkedlist`]: https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist
|
||||
[`lint_groups_priority`]: https://rust-lang.github.io/rust-clippy/master/index.html#lint_groups_priority
|
||||
[`little_endian_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#little_endian_bytes
|
||||
[`logic_bug`]: https://rust-lang.github.io/rust-clippy/master/index.html#logic_bug
|
||||
[`lossy_float_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#lossy_float_literal
|
||||
|
@ -5284,6 +5340,7 @@ Released 2018-09-13
|
|||
[`manual_assert`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_assert
|
||||
[`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn
|
||||
[`manual_bits`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits
|
||||
[`manual_c_str_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_c_str_literals
|
||||
[`manual_clamp`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_clamp
|
||||
[`manual_filter`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter
|
||||
[`manual_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter_map
|
||||
|
@ -5523,6 +5580,7 @@ Released 2018-09-13
|
|||
[`redundant_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_slicing
|
||||
[`redundant_static_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes
|
||||
[`redundant_type_annotations`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_type_annotations
|
||||
[`ref_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_as_ptr
|
||||
[`ref_binding_to_reference`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_binding_to_reference
|
||||
[`ref_in_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_in_deref
|
||||
[`ref_option_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_option_ref
|
||||
|
@ -5622,6 +5680,7 @@ Released 2018-09-13
|
|||
[`to_digit_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_digit_is_some
|
||||
[`to_string_in_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_display
|
||||
[`to_string_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_format_args
|
||||
[`to_string_trait_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_trait_impl
|
||||
[`todo`]: https://rust-lang.github.io/rust-clippy/master/index.html#todo
|
||||
[`too_many_arguments`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_arguments
|
||||
[`too_many_lines`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines
|
||||
|
@ -5677,6 +5736,7 @@ Released 2018-09-13
|
|||
[`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed
|
||||
[`unnecessary_operation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_operation
|
||||
[`unnecessary_owned_empty_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_owned_empty_strings
|
||||
[`unnecessary_result_map_or_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_result_map_or_else
|
||||
[`unnecessary_safety_comment`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_safety_comment
|
||||
[`unnecessary_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_safety_doc
|
||||
[`unnecessary_self_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_self_imports
|
||||
|
@ -5819,4 +5879,6 @@ Released 2018-09-13
|
|||
[`enforce-iter-loop-reborrow`]: https://doc.rust-lang.org/clippy/lint_configuration.html#enforce-iter-loop-reborrow
|
||||
[`check-private-items`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-private-items
|
||||
[`pub-underscore-fields-behavior`]: https://doc.rust-lang.org/clippy/lint_configuration.html#pub-underscore-fields-behavior
|
||||
[`allow-comparison-to-zero`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-comparison-to-zero
|
||||
[`allowed-wildcard-imports`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allowed-wildcard-imports
|
||||
<!-- end autogenerated links to configuration documentation -->
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "clippy"
|
||||
version = "0.1.77"
|
||||
version = "0.1.78"
|
||||
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
|
||||
repository = "https://github.com/rust-lang/rust-clippy"
|
||||
readme = "README.md"
|
||||
|
|
|
@ -151,6 +151,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
|
|||
* [`manual_try_fold`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_try_fold)
|
||||
* [`manual_hash_one`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_hash_one)
|
||||
* [`iter_kv_map`](https://rust-lang.github.io/rust-clippy/master/index.html#iter_kv_map)
|
||||
* [`manual_c_str_literals`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_c_str_literals)
|
||||
|
||||
|
||||
## `cognitive-complexity-threshold`
|
||||
|
@ -828,3 +829,35 @@ exported visibility, or whether they are marked as "pub".
|
|||
* [`pub_underscore_fields`](https://rust-lang.github.io/rust-clippy/master/index.html#pub_underscore_fields)
|
||||
|
||||
|
||||
## `allow-comparison-to-zero`
|
||||
Don't lint when comparing the result of a modulo operation to zero.
|
||||
|
||||
**Default Value:** `true`
|
||||
|
||||
---
|
||||
**Affected lints:**
|
||||
* [`modulo_arithmetic`](https://rust-lang.github.io/rust-clippy/master/index.html#modulo_arithmetic)
|
||||
|
||||
|
||||
## `allowed-wildcard-imports`
|
||||
List of path segments allowed to have wildcard imports.
|
||||
|
||||
#### Example
|
||||
|
||||
```toml
|
||||
allowed-wildcard-imports = [ "utils", "common" ]
|
||||
```
|
||||
|
||||
#### Noteworthy
|
||||
|
||||
1. This configuration has no effects if used with `warn_on_all_wildcard_imports = true`.
|
||||
2. Paths with any segment that containing the word 'prelude'
|
||||
are already allowed by default.
|
||||
|
||||
**Default Value:** `[]`
|
||||
|
||||
---
|
||||
**Affected lints:**
|
||||
* [`wildcard_imports`](https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_imports)
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "clippy_config"
|
||||
version = "0.1.77"
|
||||
version = "0.1.78"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
|
|
@ -260,7 +260,7 @@ define_Conf! {
|
|||
///
|
||||
/// Suppress lints whenever the suggested change would cause breakage for other crates.
|
||||
(avoid_breaking_exported_api: bool = true),
|
||||
/// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, OPTION_MAP_UNWRAP_OR, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, MANUAL_LET_ELSE, UNCHECKED_DURATION_SUBTRACTION, COLLAPSIBLE_STR_REPLACE, SEEK_FROM_CURRENT, SEEK_REWIND, UNNECESSARY_LAZY_EVALUATIONS, TRANSMUTE_PTR_TO_REF, ALMOST_COMPLETE_RANGE, NEEDLESS_BORROW, DERIVABLE_IMPLS, MANUAL_IS_ASCII_CHECK, MANUAL_REM_EUCLID, MANUAL_RETAIN, TYPE_REPETITION_IN_BOUNDS, TUPLE_ARRAY_CONVERSIONS, MANUAL_TRY_FOLD, MANUAL_HASH_ONE, ITER_KV_MAP.
|
||||
/// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, OPTION_MAP_UNWRAP_OR, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, MANUAL_LET_ELSE, UNCHECKED_DURATION_SUBTRACTION, COLLAPSIBLE_STR_REPLACE, SEEK_FROM_CURRENT, SEEK_REWIND, UNNECESSARY_LAZY_EVALUATIONS, TRANSMUTE_PTR_TO_REF, ALMOST_COMPLETE_RANGE, NEEDLESS_BORROW, DERIVABLE_IMPLS, MANUAL_IS_ASCII_CHECK, MANUAL_REM_EUCLID, MANUAL_RETAIN, TYPE_REPETITION_IN_BOUNDS, TUPLE_ARRAY_CONVERSIONS, MANUAL_TRY_FOLD, MANUAL_HASH_ONE, ITER_KV_MAP, MANUAL_C_STR_LITERALS.
|
||||
///
|
||||
/// The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml`
|
||||
#[default_text = ""]
|
||||
|
@ -567,6 +567,26 @@ define_Conf! {
|
|||
/// Lint "public" fields in a struct that are prefixed with an underscore based on their
|
||||
/// exported visibility, or whether they are marked as "pub".
|
||||
(pub_underscore_fields_behavior: PubUnderscoreFieldsBehaviour = PubUnderscoreFieldsBehaviour::PubliclyExported),
|
||||
/// Lint: MODULO_ARITHMETIC.
|
||||
///
|
||||
/// Don't lint when comparing the result of a modulo operation to zero.
|
||||
(allow_comparison_to_zero: bool = true),
|
||||
/// Lint: WILDCARD_IMPORTS.
|
||||
///
|
||||
/// List of path segments allowed to have wildcard imports.
|
||||
///
|
||||
/// #### Example
|
||||
///
|
||||
/// ```toml
|
||||
/// allowed-wildcard-imports = [ "utils", "common" ]
|
||||
/// ```
|
||||
///
|
||||
/// #### Noteworthy
|
||||
///
|
||||
/// 1. This configuration has no effects if used with `warn_on_all_wildcard_imports = true`.
|
||||
/// 2. Paths with any segment that containing the word 'prelude'
|
||||
/// are already allowed by default.
|
||||
(allowed_wildcard_imports: FxHashSet<String> = FxHashSet::default()),
|
||||
}
|
||||
|
||||
/// Search for the configuration file.
|
||||
|
|
|
@ -3,6 +3,7 @@ use rustc_semver::RustcVersion;
|
|||
use rustc_session::Session;
|
||||
use rustc_span::{sym, Symbol};
|
||||
use serde::Deserialize;
|
||||
use std::fmt;
|
||||
|
||||
macro_rules! msrv_aliases {
|
||||
($($major:literal,$minor:literal,$patch:literal {
|
||||
|
@ -16,6 +17,8 @@ macro_rules! msrv_aliases {
|
|||
|
||||
// names may refer to stabilized feature flags or library items
|
||||
msrv_aliases! {
|
||||
1,77,0 { C_STR_LITERALS }
|
||||
1,76,0 { PTR_FROM_REF }
|
||||
1,71,0 { TUPLE_ARRAY_CONVERSIONS, BUILD_HASHER_HASH_ONE }
|
||||
1,70,0 { OPTION_RESULT_IS_VARIANT_AND, BINARY_HEAP_RETAIN }
|
||||
1,68,0 { PATH_MAIN_SEPARATOR_STR }
|
||||
|
@ -58,6 +61,16 @@ pub struct Msrv {
|
|||
stack: Vec<RustcVersion>,
|
||||
}
|
||||
|
||||
impl fmt::Display for Msrv {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if let Some(msrv) = self.current() {
|
||||
write!(f, "{msrv}")
|
||||
} else {
|
||||
f.write_str("1.0.0")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Msrv {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "clippy_lints"
|
||||
version = "0.1.77"
|
||||
version = "0.1.78"
|
||||
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
|
||||
repository = "https://github.com/rust-lang/rust-clippy"
|
||||
readme = "README.md"
|
||||
|
|
|
@ -499,6 +499,7 @@ struct NotSimplificationVisitor<'a, 'tcx> {
|
|||
impl<'a, 'tcx> Visitor<'tcx> for NotSimplificationVisitor<'a, 'tcx> {
|
||||
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
|
||||
if let ExprKind::Unary(UnOp::Not, inner) = &expr.kind
|
||||
&& !expr.span.from_expansion()
|
||||
&& !inner.span.from_expansion()
|
||||
&& let Some(suggestion) = simplify_not(self.cx, inner)
|
||||
&& self.cx.tcx.lint_level_at_node(NONMINIMAL_BOOL, expr.hir_id).0 != Level::Allow
|
||||
|
|
168
src/tools/clippy/clippy_lints/src/cargo/lint_groups_priority.rs
Normal file
168
src/tools/clippy/clippy_lints/src/cargo/lint_groups_priority.rs
Normal file
|
@ -0,0 +1,168 @@
|
|||
use super::LINT_GROUPS_PRIORITY;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::{unerased_lint_store, LateContext};
|
||||
use rustc_span::{BytePos, Pos, SourceFile, Span, SyntaxContext};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use std::ops::Range;
|
||||
use std::path::Path;
|
||||
use toml::Spanned;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
struct LintConfigTable {
|
||||
level: String,
|
||||
priority: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(untagged)]
|
||||
enum LintConfig {
|
||||
Level(String),
|
||||
Table(LintConfigTable),
|
||||
}
|
||||
|
||||
impl LintConfig {
|
||||
fn level(&self) -> &str {
|
||||
match self {
|
||||
LintConfig::Level(level) => level,
|
||||
LintConfig::Table(table) => &table.level,
|
||||
}
|
||||
}
|
||||
|
||||
fn priority(&self) -> i64 {
|
||||
match self {
|
||||
LintConfig::Level(_) => 0,
|
||||
LintConfig::Table(table) => table.priority.unwrap_or(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_implicit(&self) -> bool {
|
||||
if let LintConfig::Table(table) = self {
|
||||
table.priority.is_none()
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type LintTable = BTreeMap<Spanned<String>, Spanned<LintConfig>>;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Lints {
|
||||
#[serde(default)]
|
||||
rust: LintTable,
|
||||
#[serde(default)]
|
||||
clippy: LintTable,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct CargoToml {
|
||||
lints: Lints,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
struct LintsAndGroups {
|
||||
lints: Vec<Spanned<String>>,
|
||||
groups: Vec<(Spanned<String>, Spanned<LintConfig>)>,
|
||||
}
|
||||
|
||||
fn toml_span(range: Range<usize>, file: &SourceFile) -> Span {
|
||||
Span::new(
|
||||
file.start_pos + BytePos::from_usize(range.start),
|
||||
file.start_pos + BytePos::from_usize(range.end),
|
||||
SyntaxContext::root(),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn check_table(cx: &LateContext<'_>, table: LintTable, groups: &FxHashSet<&str>, file: &SourceFile) {
|
||||
let mut by_priority = BTreeMap::<_, LintsAndGroups>::new();
|
||||
for (name, config) in table {
|
||||
let lints_and_groups = by_priority.entry(config.as_ref().priority()).or_default();
|
||||
if groups.contains(name.get_ref().as_str()) {
|
||||
lints_and_groups.groups.push((name, config));
|
||||
} else {
|
||||
lints_and_groups.lints.push(name);
|
||||
}
|
||||
}
|
||||
let low_priority = by_priority
|
||||
.iter()
|
||||
.find(|(_, lints_and_groups)| !lints_and_groups.lints.is_empty())
|
||||
.map_or(-1, |(&lowest_lint_priority, _)| lowest_lint_priority - 1);
|
||||
|
||||
for (priority, LintsAndGroups { lints, groups }) in by_priority {
|
||||
let Some(last_lint_alphabetically) = lints.last() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
for (group, config) in groups {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
LINT_GROUPS_PRIORITY,
|
||||
toml_span(group.span(), file),
|
||||
&format!(
|
||||
"lint group `{}` has the same priority ({priority}) as a lint",
|
||||
group.as_ref()
|
||||
),
|
||||
|diag| {
|
||||
let config_span = toml_span(config.span(), file);
|
||||
if config.as_ref().is_implicit() {
|
||||
diag.span_label(config_span, "has an implicit priority of 0");
|
||||
}
|
||||
// add the label to next lint after this group that has the same priority
|
||||
let lint = lints
|
||||
.iter()
|
||||
.filter(|lint| lint.span().start > group.span().start)
|
||||
.min_by_key(|lint| lint.span().start)
|
||||
.unwrap_or(last_lint_alphabetically);
|
||||
diag.span_label(toml_span(lint.span(), file), "has the same priority as this lint");
|
||||
diag.note("the order of the lints in the table is ignored by Cargo");
|
||||
let mut suggestion = String::new();
|
||||
Serialize::serialize(
|
||||
&LintConfigTable {
|
||||
level: config.as_ref().level().into(),
|
||||
priority: Some(low_priority),
|
||||
},
|
||||
toml::ser::ValueSerializer::new(&mut suggestion),
|
||||
)
|
||||
.unwrap();
|
||||
diag.span_suggestion_verbose(
|
||||
config_span,
|
||||
format!(
|
||||
"to have lints override the group set `{}` to a lower priority",
|
||||
group.as_ref()
|
||||
),
|
||||
suggestion,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check(cx: &LateContext<'_>) {
|
||||
if let Ok(file) = cx.tcx.sess.source_map().load_file(Path::new("Cargo.toml"))
|
||||
&& let Some(src) = file.src.as_deref()
|
||||
&& let Ok(cargo_toml) = toml::from_str::<CargoToml>(src)
|
||||
{
|
||||
let mut rustc_groups = FxHashSet::default();
|
||||
let mut clippy_groups = FxHashSet::default();
|
||||
for (group, ..) in unerased_lint_store(cx.tcx.sess).get_lint_groups() {
|
||||
match group.split_once("::") {
|
||||
None => {
|
||||
rustc_groups.insert(group);
|
||||
},
|
||||
Some(("clippy", group)) => {
|
||||
clippy_groups.insert(group);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
check_table(cx, cargo_toml.lints.rust, &rustc_groups, &file);
|
||||
check_table(cx, cargo_toml.lints.clippy, &clippy_groups, &file);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
mod common_metadata;
|
||||
mod feature_name;
|
||||
mod lint_groups_priority;
|
||||
mod multiple_crate_versions;
|
||||
mod wildcard_dependencies;
|
||||
|
||||
|
@ -165,6 +166,43 @@ declare_clippy_lint! {
|
|||
"wildcard dependencies being used"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for lint groups with the same priority as lints in the `Cargo.toml`
|
||||
/// [`[lints]` table](https://doc.rust-lang.org/cargo/reference/manifest.html#the-lints-section).
|
||||
///
|
||||
/// This lint will be removed once [cargo#12918](https://github.com/rust-lang/cargo/issues/12918)
|
||||
/// is resolved.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The order of lints in the `[lints]` is ignored, to have a lint override a group the
|
||||
/// `priority` field needs to be used, otherwise the sort order is undefined.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// Does not check lints inherited using `lints.workspace = true`
|
||||
///
|
||||
/// ### Example
|
||||
/// ```toml
|
||||
/// # Passed as `--allow=clippy::similar_names --warn=clippy::pedantic`
|
||||
/// # which results in `similar_names` being `warn`
|
||||
/// [lints.clippy]
|
||||
/// pedantic = "warn"
|
||||
/// similar_names = "allow"
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```toml
|
||||
/// # Passed as `--warn=clippy::pedantic --allow=clippy::similar_names`
|
||||
/// # which results in `similar_names` being `allow`
|
||||
/// [lints.clippy]
|
||||
/// pedantic = { level = "warn", priority = -1 }
|
||||
/// similar_names = "allow"
|
||||
/// ```
|
||||
#[clippy::version = "1.76.0"]
|
||||
pub LINT_GROUPS_PRIORITY,
|
||||
correctness,
|
||||
"a lint group in `Cargo.toml` at the same priority as a lint"
|
||||
}
|
||||
|
||||
pub struct Cargo {
|
||||
pub allowed_duplicate_crates: FxHashSet<String>,
|
||||
pub ignore_publish: bool,
|
||||
|
@ -175,7 +213,8 @@ impl_lint_pass!(Cargo => [
|
|||
REDUNDANT_FEATURE_NAMES,
|
||||
NEGATIVE_FEATURE_NAMES,
|
||||
MULTIPLE_CRATE_VERSIONS,
|
||||
WILDCARD_DEPENDENCIES
|
||||
WILDCARD_DEPENDENCIES,
|
||||
LINT_GROUPS_PRIORITY,
|
||||
]);
|
||||
|
||||
impl LateLintPass<'_> for Cargo {
|
||||
|
@ -188,6 +227,8 @@ impl LateLintPass<'_> for Cargo {
|
|||
];
|
||||
static WITH_DEPS_LINTS: &[&Lint] = &[MULTIPLE_CRATE_VERSIONS];
|
||||
|
||||
lint_groups_priority::check(cx);
|
||||
|
||||
if !NO_DEPS_LINTS
|
||||
.iter()
|
||||
.all(|&lint| is_lint_allowed(cx, lint, CRATE_HIR_ID))
|
||||
|
|
|
@ -18,6 +18,7 @@ mod fn_to_numeric_cast_any;
|
|||
mod fn_to_numeric_cast_with_truncation;
|
||||
mod ptr_as_ptr;
|
||||
mod ptr_cast_constness;
|
||||
mod ref_as_ptr;
|
||||
mod unnecessary_cast;
|
||||
mod utils;
|
||||
mod zero_ptr;
|
||||
|
@ -689,6 +690,30 @@ declare_clippy_lint! {
|
|||
"using `0 as *{const, mut} T`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for casts of references to pointer using `as`
|
||||
/// and suggests `std::ptr::from_ref` and `std::ptr::from_mut` instead.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Using `as` casts may result in silently changing mutability or type.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// let a_ref = &1;
|
||||
/// let a_ptr = a_ref as *const _;
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// let a_ref = &1;
|
||||
/// let a_ptr = std::ptr::from_ref(a_ref);
|
||||
/// ```
|
||||
#[clippy::version = "1.77.0"]
|
||||
pub REF_AS_PTR,
|
||||
pedantic,
|
||||
"using `as` to cast a reference to pointer"
|
||||
}
|
||||
|
||||
pub struct Casts {
|
||||
msrv: Msrv,
|
||||
}
|
||||
|
@ -724,6 +749,7 @@ impl_lint_pass!(Casts => [
|
|||
AS_PTR_CAST_MUT,
|
||||
CAST_NAN_TO_INT,
|
||||
ZERO_PTR,
|
||||
REF_AS_PTR,
|
||||
]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for Casts {
|
||||
|
@ -771,7 +797,9 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
|
|||
|
||||
as_underscore::check(cx, expr, cast_to_hir);
|
||||
|
||||
if self.msrv.meets(msrvs::BORROW_AS_PTR) {
|
||||
if self.msrv.meets(msrvs::PTR_FROM_REF) {
|
||||
ref_as_ptr::check(cx, expr, cast_expr, cast_to_hir);
|
||||
} else if self.msrv.meets(msrvs::BORROW_AS_PTR) {
|
||||
borrow_as_ptr::check(cx, expr, cast_expr, cast_to_hir);
|
||||
}
|
||||
}
|
||||
|
|
55
src/tools/clippy/clippy_lints/src/casts/ref_as_ptr.rs
Normal file
55
src/tools/clippy/clippy_lints/src/casts/ref_as_ptr.rs
Normal file
|
@ -0,0 +1,55 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::is_no_std_crate;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, Mutability, Ty, TyKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, TypeAndMut};
|
||||
|
||||
use super::REF_AS_PTR;
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_to_hir_ty: &Ty<'_>) {
|
||||
let (cast_from, cast_to) = (
|
||||
cx.typeck_results().expr_ty(cast_expr),
|
||||
cx.typeck_results().expr_ty(expr),
|
||||
);
|
||||
|
||||
if matches!(cast_from.kind(), ty::Ref(..))
|
||||
&& let ty::RawPtr(TypeAndMut { mutbl: to_mutbl, .. }) = cast_to.kind()
|
||||
{
|
||||
let core_or_std = if is_no_std_crate(cx) { "core" } else { "std" };
|
||||
let fn_name = match to_mutbl {
|
||||
Mutability::Not => "from_ref",
|
||||
Mutability::Mut => "from_mut",
|
||||
};
|
||||
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let turbofish = match &cast_to_hir_ty.kind {
|
||||
TyKind::Infer => String::new(),
|
||||
TyKind::Ptr(mut_ty) => {
|
||||
if matches!(mut_ty.ty.kind, TyKind::Infer) {
|
||||
String::new()
|
||||
} else {
|
||||
format!(
|
||||
"::<{}>",
|
||||
snippet_with_applicability(cx, mut_ty.ty.span, "/* type */", &mut app)
|
||||
)
|
||||
}
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let cast_expr_sugg = Sugg::hir_with_applicability(cx, cast_expr, "_", &mut app);
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
REF_AS_PTR,
|
||||
expr.span,
|
||||
"reference as raw pointer",
|
||||
"try",
|
||||
format!("{core_or_std}::ptr::{fn_name}{turbofish}({cast_expr_sugg})"),
|
||||
app,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -71,6 +71,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::borrow_deref_ref::BORROW_DEREF_REF_INFO,
|
||||
crate::box_default::BOX_DEFAULT_INFO,
|
||||
crate::cargo::CARGO_COMMON_METADATA_INFO,
|
||||
crate::cargo::LINT_GROUPS_PRIORITY_INFO,
|
||||
crate::cargo::MULTIPLE_CRATE_VERSIONS_INFO,
|
||||
crate::cargo::NEGATIVE_FEATURE_NAMES_INFO,
|
||||
crate::cargo::REDUNDANT_FEATURE_NAMES_INFO,
|
||||
|
@ -96,6 +97,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION_INFO,
|
||||
crate::casts::PTR_AS_PTR_INFO,
|
||||
crate::casts::PTR_CAST_CONSTNESS_INFO,
|
||||
crate::casts::REF_AS_PTR_INFO,
|
||||
crate::casts::UNNECESSARY_CAST_INFO,
|
||||
crate::casts::ZERO_PTR_INFO,
|
||||
crate::checked_conversions::CHECKED_CONVERSIONS_INFO,
|
||||
|
@ -212,6 +214,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::implicit_saturating_add::IMPLICIT_SATURATING_ADD_INFO,
|
||||
crate::implicit_saturating_sub::IMPLICIT_SATURATING_SUB_INFO,
|
||||
crate::implied_bounds_in_impls::IMPLIED_BOUNDS_IN_IMPLS_INFO,
|
||||
crate::incompatible_msrv::INCOMPATIBLE_MSRV_INFO,
|
||||
crate::inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR_INFO,
|
||||
crate::index_refutable_slice::INDEX_REFUTABLE_SLICE_INFO,
|
||||
crate::indexing_slicing::INDEXING_SLICING_INFO,
|
||||
|
@ -384,6 +387,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::methods::ITER_SKIP_ZERO_INFO,
|
||||
crate::methods::ITER_WITH_DRAIN_INFO,
|
||||
crate::methods::JOIN_ABSOLUTE_PATHS_INFO,
|
||||
crate::methods::MANUAL_C_STR_LITERALS_INFO,
|
||||
crate::methods::MANUAL_FILTER_MAP_INFO,
|
||||
crate::methods::MANUAL_FIND_MAP_INFO,
|
||||
crate::methods::MANUAL_IS_VARIANT_AND_INFO,
|
||||
|
@ -452,6 +456,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::methods::UNNECESSARY_JOIN_INFO,
|
||||
crate::methods::UNNECESSARY_LAZY_EVALUATIONS_INFO,
|
||||
crate::methods::UNNECESSARY_LITERAL_UNWRAP_INFO,
|
||||
crate::methods::UNNECESSARY_RESULT_MAP_OR_ELSE_INFO,
|
||||
crate::methods::UNNECESSARY_SORT_BY_INFO,
|
||||
crate::methods::UNNECESSARY_TO_OWNED_INFO,
|
||||
crate::methods::UNWRAP_OR_DEFAULT_INFO,
|
||||
|
@ -656,6 +661,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::tests_outside_test_module::TESTS_OUTSIDE_TEST_MODULE_INFO,
|
||||
crate::thread_local_initializer_can_be_made_const::THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST_INFO,
|
||||
crate::to_digit_is_some::TO_DIGIT_IS_SOME_INFO,
|
||||
crate::to_string_trait_impl::TO_STRING_TRAIT_IMPL_INFO,
|
||||
crate::trailing_empty_array::TRAILING_EMPTY_ARRAY_INFO,
|
||||
crate::trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS_INFO,
|
||||
crate::trait_bounds::TYPE_REPETITION_IN_BOUNDS_INFO,
|
||||
|
|
|
@ -226,7 +226,7 @@ declare_clippy_lint! {
|
|||
/// unimplemented!();
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.40.0"]
|
||||
#[clippy::version = "1.76.0"]
|
||||
pub TEST_ATTR_IN_DOCTEST,
|
||||
suspicious,
|
||||
"presence of `#[test]` in code examples"
|
||||
|
|
|
@ -3,15 +3,14 @@ use clippy_utils::higher::VecArgs;
|
|||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::ty::type_diagnostic_name;
|
||||
use clippy_utils::usage::{local_used_after_expr, local_used_in};
|
||||
use clippy_utils::{higher, is_adjusted, path_to_local, path_to_local_id};
|
||||
use clippy_utils::{get_path_from_caller_to_method_type, higher, is_adjusted, path_to_local, path_to_local_id};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::{BindingAnnotation, Expr, ExprKind, FnRetTy, Param, PatKind, QPath, TyKind, Unsafety};
|
||||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::{
|
||||
self, Binder, ClosureArgs, ClosureKind, EarlyBinder, FnSig, GenericArg, GenericArgKind, GenericArgsRef,
|
||||
ImplPolarity, List, Region, RegionKind, Ty, TypeVisitableExt, TypeckResults,
|
||||
self, Binder, ClosureArgs, ClosureKind, FnSig, GenericArg, GenericArgKind, ImplPolarity, List, Region, RegionKind,
|
||||
Ty, TypeVisitableExt, TypeckResults,
|
||||
};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::symbol::sym;
|
||||
|
@ -21,8 +20,8 @@ use rustc_trait_selection::traits::error_reporting::InferCtxtExt as _;
|
|||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for closures which just call another function where
|
||||
/// the function can be called directly. `unsafe` functions or calls where types
|
||||
/// get adjusted are ignored.
|
||||
/// the function can be called directly. `unsafe` functions, calls where types
|
||||
/// get adjusted or where the callee is marked `#[track_caller]` are ignored.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Needlessly creating a closure adds code for no benefit
|
||||
|
@ -136,7 +135,14 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
|
|||
.map_or(callee_ty, |a| a.target.peel_refs());
|
||||
|
||||
let sig = match callee_ty_adjusted.kind() {
|
||||
ty::FnDef(def, _) => cx.tcx.fn_sig(def).skip_binder().skip_binder(),
|
||||
ty::FnDef(def, _) => {
|
||||
// Rewriting `x(|| f())` to `x(f)` where f is marked `#[track_caller]` moves the `Location`
|
||||
if cx.tcx.has_attr(*def, sym::track_caller) {
|
||||
return;
|
||||
}
|
||||
|
||||
cx.tcx.fn_sig(def).skip_binder().skip_binder()
|
||||
},
|
||||
ty::FnPtr(sig) => sig.skip_binder(),
|
||||
ty::Closure(_, subs) => cx
|
||||
.tcx
|
||||
|
@ -186,6 +192,7 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
|
|||
},
|
||||
ExprKind::MethodCall(path, self_, args, _) if check_inputs(typeck, body.params, Some(self_), args) => {
|
||||
if let Some(method_def_id) = typeck.type_dependent_def_id(body.value.hir_id)
|
||||
&& !cx.tcx.has_attr(method_def_id, sym::track_caller)
|
||||
&& check_sig(cx, closure, cx.tcx.fn_sig(method_def_id).skip_binder().skip_binder())
|
||||
{
|
||||
span_lint_and_then(
|
||||
|
@ -195,11 +202,12 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
|
|||
"redundant closure",
|
||||
|diag| {
|
||||
let args = typeck.node_args(body.value.hir_id);
|
||||
let name = get_ufcs_type_name(cx, method_def_id, args);
|
||||
let caller = self_.hir_id.owner.def_id;
|
||||
let type_name = get_path_from_caller_to_method_type(cx.tcx, caller, method_def_id, args);
|
||||
diag.span_suggestion(
|
||||
expr.span,
|
||||
"replace the closure with the method itself",
|
||||
format!("{}::{}", name, path.ident.name),
|
||||
format!("{}::{}", type_name, path.ident.name),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
|
@ -301,27 +309,3 @@ fn has_late_bound_to_non_late_bound_regions(from_sig: FnSig<'_>, to_sig: FnSig<'
|
|||
.zip(to_sig.inputs_and_output)
|
||||
.any(|(from_ty, to_ty)| check_ty(from_ty, to_ty))
|
||||
}
|
||||
|
||||
fn get_ufcs_type_name<'tcx>(cx: &LateContext<'tcx>, method_def_id: DefId, args: GenericArgsRef<'tcx>) -> String {
|
||||
let assoc_item = cx.tcx.associated_item(method_def_id);
|
||||
let def_id = assoc_item.container_id(cx.tcx);
|
||||
match assoc_item.container {
|
||||
ty::TraitContainer => cx.tcx.def_path_str(def_id),
|
||||
ty::ImplContainer => {
|
||||
let ty = cx.tcx.type_of(def_id).instantiate_identity();
|
||||
match ty.kind() {
|
||||
ty::Adt(adt, _) => cx.tcx.def_path_str(adt.did()),
|
||||
ty::Array(..)
|
||||
| ty::Dynamic(..)
|
||||
| ty::Never
|
||||
| ty::RawPtr(_)
|
||||
| ty::Ref(..)
|
||||
| ty::Slice(_)
|
||||
| ty::Tuple(_) => {
|
||||
format!("<{}>", EarlyBinder::bind(ty).instantiate(cx.tcx, args))
|
||||
},
|
||||
_ => ty.to_string(),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
133
src/tools/clippy/clippy_lints/src/incompatible_msrv.rs
Normal file
133
src/tools/clippy/clippy_lints/src/incompatible_msrv.rs
Normal file
|
@ -0,0 +1,133 @@
|
|||
use clippy_config::msrvs::Msrv;
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use rustc_attr::{StabilityLevel, StableSince};
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_semver::RustcVersion;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::def_id::DefId;
|
||||
use rustc_span::Span;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
///
|
||||
/// This lint checks that no function newer than the defined MSRV (minimum
|
||||
/// supported rust version) is used in the crate.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
///
|
||||
/// It would prevent the crate to be actually used with the specified MSRV.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// // MSRV of 1.3.0
|
||||
/// use std::thread::sleep;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// // Sleep was defined in `1.4.0`.
|
||||
/// sleep(Duration::new(1, 0));
|
||||
/// ```
|
||||
///
|
||||
/// To fix this problem, either increase your MSRV or use another item
|
||||
/// available in your current MSRV.
|
||||
#[clippy::version = "1.77.0"]
|
||||
pub INCOMPATIBLE_MSRV,
|
||||
suspicious,
|
||||
"ensures that all items used in the crate are available for the current MSRV"
|
||||
}
|
||||
|
||||
pub struct IncompatibleMsrv {
|
||||
msrv: Msrv,
|
||||
is_above_msrv: FxHashMap<DefId, RustcVersion>,
|
||||
}
|
||||
|
||||
impl_lint_pass!(IncompatibleMsrv => [INCOMPATIBLE_MSRV]);
|
||||
|
||||
impl IncompatibleMsrv {
|
||||
pub fn new(msrv: Msrv) -> Self {
|
||||
Self {
|
||||
msrv,
|
||||
is_above_msrv: FxHashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_lossless)]
|
||||
fn get_def_id_version(&mut self, tcx: TyCtxt<'_>, def_id: DefId) -> RustcVersion {
|
||||
if let Some(version) = self.is_above_msrv.get(&def_id) {
|
||||
return *version;
|
||||
}
|
||||
let version = if let Some(version) = tcx
|
||||
.lookup_stability(def_id)
|
||||
.and_then(|stability| match stability.level {
|
||||
StabilityLevel::Stable {
|
||||
since: StableSince::Version(version),
|
||||
..
|
||||
} => Some(RustcVersion::new(
|
||||
version.major as _,
|
||||
version.minor as _,
|
||||
version.patch as _,
|
||||
)),
|
||||
_ => None,
|
||||
}) {
|
||||
version
|
||||
} else if let Some(parent_def_id) = tcx.opt_parent(def_id) {
|
||||
self.get_def_id_version(tcx, parent_def_id)
|
||||
} else {
|
||||
RustcVersion::new(1, 0, 0)
|
||||
};
|
||||
self.is_above_msrv.insert(def_id, version);
|
||||
version
|
||||
}
|
||||
|
||||
fn emit_lint_if_under_msrv(&mut self, cx: &LateContext<'_>, def_id: DefId, span: Span) {
|
||||
if def_id.is_local() {
|
||||
// We don't check local items since their MSRV is supposed to always be valid.
|
||||
return;
|
||||
}
|
||||
let version = self.get_def_id_version(cx.tcx, def_id);
|
||||
if self.msrv.meets(version) {
|
||||
return;
|
||||
}
|
||||
self.emit_lint_for(cx, span, version);
|
||||
}
|
||||
|
||||
fn emit_lint_for(&self, cx: &LateContext<'_>, span: Span, version: RustcVersion) {
|
||||
span_lint(
|
||||
cx,
|
||||
INCOMPATIBLE_MSRV,
|
||||
span,
|
||||
&format!(
|
||||
"current MSRV (Minimum Supported Rust Version) is `{}` but this item is stable since `{version}`",
|
||||
self.msrv
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for IncompatibleMsrv {
|
||||
extract_msrv_attr!(LateContext);
|
||||
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||
if self.msrv.current().is_none() {
|
||||
// If there is no MSRV, then no need to check anything...
|
||||
return;
|
||||
}
|
||||
match expr.kind {
|
||||
ExprKind::MethodCall(_, _, _, span) => {
|
||||
if let Some(method_did) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
|
||||
self.emit_lint_if_under_msrv(cx, method_did, span);
|
||||
}
|
||||
},
|
||||
ExprKind::Call(call, [_]) => {
|
||||
if let ExprKind::Path(qpath) = call.kind
|
||||
&& let Some(path_def_id) = cx.qpath_res(&qpath, call.hir_id).opt_def_id()
|
||||
{
|
||||
self.emit_lint_if_under_msrv(cx, path_def_id, call.span);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
|
@ -34,7 +34,7 @@ declare_clippy_lint! {
|
|||
/// let value = &my_map[key];
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.75.0"]
|
||||
#[clippy::version = "1.76.0"]
|
||||
pub ITER_OVER_HASH_TYPE,
|
||||
restriction,
|
||||
"iterating over unordered hash-based types (`HashMap` and `HashSet`)"
|
||||
|
|
|
@ -31,6 +31,7 @@ extern crate rustc_abi;
|
|||
extern crate rustc_arena;
|
||||
extern crate rustc_ast;
|
||||
extern crate rustc_ast_pretty;
|
||||
extern crate rustc_attr;
|
||||
extern crate rustc_data_structures;
|
||||
extern crate rustc_driver;
|
||||
extern crate rustc_errors;
|
||||
|
@ -158,6 +159,7 @@ mod implicit_return;
|
|||
mod implicit_saturating_add;
|
||||
mod implicit_saturating_sub;
|
||||
mod implied_bounds_in_impls;
|
||||
mod incompatible_msrv;
|
||||
mod inconsistent_struct_constructor;
|
||||
mod index_refutable_slice;
|
||||
mod indexing_slicing;
|
||||
|
@ -330,6 +332,7 @@ mod temporary_assignment;
|
|||
mod tests_outside_test_module;
|
||||
mod thread_local_initializer_can_be_made_const;
|
||||
mod to_digit_is_some;
|
||||
mod to_string_trait_impl;
|
||||
mod trailing_empty_array;
|
||||
mod trait_bounds;
|
||||
mod transmute;
|
||||
|
@ -526,6 +529,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
|||
ref allowed_dotfiles,
|
||||
ref allowed_idents_below_min_chars,
|
||||
ref allowed_scripts,
|
||||
ref allowed_wildcard_imports,
|
||||
ref arithmetic_side_effects_allowed_binary,
|
||||
ref arithmetic_side_effects_allowed_unary,
|
||||
ref arithmetic_side_effects_allowed,
|
||||
|
@ -580,6 +584,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
|||
check_private_items,
|
||||
pub_underscore_fields_behavior,
|
||||
ref allowed_duplicate_crates,
|
||||
allow_comparison_to_zero,
|
||||
|
||||
blacklisted_names: _,
|
||||
cyclomatic_complexity_threshold: _,
|
||||
|
@ -877,7 +882,12 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
|||
))
|
||||
});
|
||||
store.register_early_pass(|| Box::new(option_env_unwrap::OptionEnvUnwrap));
|
||||
store.register_late_pass(move |_| Box::new(wildcard_imports::WildcardImports::new(warn_on_all_wildcard_imports)));
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(wildcard_imports::WildcardImports::new(
|
||||
warn_on_all_wildcard_imports,
|
||||
allowed_wildcard_imports.clone(),
|
||||
))
|
||||
});
|
||||
store.register_late_pass(|_| Box::<redundant_pub_crate::RedundantPubCrate>::default());
|
||||
store.register_late_pass(|_| Box::new(unnamed_address::UnnamedAddress));
|
||||
store.register_late_pass(|_| Box::<dereference::Dereferencing<'_>>::default());
|
||||
|
@ -973,7 +983,12 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
|||
store.register_late_pass(|_| Box::new(default_instead_of_iter_empty::DefaultIterEmpty));
|
||||
store.register_late_pass(move |_| Box::new(manual_rem_euclid::ManualRemEuclid::new(msrv())));
|
||||
store.register_late_pass(move |_| Box::new(manual_retain::ManualRetain::new(msrv())));
|
||||
store.register_late_pass(move |_| Box::new(operators::Operators::new(verbose_bit_mask_threshold)));
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(operators::Operators::new(
|
||||
verbose_bit_mask_threshold,
|
||||
allow_comparison_to_zero,
|
||||
))
|
||||
});
|
||||
store.register_late_pass(|_| Box::<std_instead_of_core::StdReexports>::default());
|
||||
store.register_late_pass(move |_| Box::new(instant_subtraction::InstantSubtraction::new(msrv())));
|
||||
store.register_late_pass(|_| Box::new(partialeq_to_none::PartialeqToNone));
|
||||
|
@ -1099,6 +1114,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
|||
store.register_late_pass(move |_| {
|
||||
Box::new(thread_local_initializer_can_be_made_const::ThreadLocalInitializerCanBeMadeConst::new(msrv()))
|
||||
});
|
||||
store.register_late_pass(move |_| Box::new(incompatible_msrv::IncompatibleMsrv::new(msrv())));
|
||||
store.register_late_pass(|_| Box::new(to_string_trait_impl::ToStringTraitImpl));
|
||||
// add lints here, do not remove this comment, it's used in `new_lint`
|
||||
}
|
||||
|
||||
|
|
|
@ -672,7 +672,7 @@ declare_clippy_lint! {
|
|||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.75.0"]
|
||||
#[clippy::version = "1.76.0"]
|
||||
pub INFINITE_LOOP,
|
||||
restriction,
|
||||
"possibly unintended infinite loop"
|
||||
|
|
|
@ -201,12 +201,12 @@ fn never_loop_expr<'tcx>(
|
|||
})
|
||||
})
|
||||
},
|
||||
ExprKind::Block(b, l) => {
|
||||
if l.is_some() {
|
||||
ExprKind::Block(b, _) => {
|
||||
if b.targeted_by_break {
|
||||
local_labels.push((b.hir_id, false));
|
||||
}
|
||||
let ret = never_loop_block(cx, b, local_labels, main_loop_id);
|
||||
let jumped_to = l.is_some() && local_labels.pop().unwrap().1;
|
||||
let jumped_to = b.targeted_by_break && local_labels.pop().unwrap().1;
|
||||
match ret {
|
||||
NeverLoopResult::Diverging if jumped_to => NeverLoopResult::Normal,
|
||||
_ => ret,
|
||||
|
|
|
@ -11,6 +11,7 @@ use rustc_lint::{LateContext, LateLintPass};
|
|||
use rustc_semver::RustcVersion;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::symbol::sym;
|
||||
use rustc_span::Span;
|
||||
|
||||
const ACCEPTABLE_METHODS: [&[&str]; 5] = [
|
||||
&paths::BINARYHEAP_ITER,
|
||||
|
@ -28,6 +29,7 @@ const ACCEPTABLE_TYPES: [(rustc_span::Symbol, Option<RustcVersion>); 7] = [
|
|||
(sym::Vec, None),
|
||||
(sym::VecDeque, None),
|
||||
];
|
||||
const MAP_TYPES: [rustc_span::Symbol; 2] = [sym::BTreeMap, sym::HashMap];
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
@ -44,6 +46,7 @@ declare_clippy_lint! {
|
|||
/// ```no_run
|
||||
/// let mut vec = vec![0, 1, 2];
|
||||
/// vec.retain(|x| x % 2 == 0);
|
||||
/// vec.retain(|x| x % 2 == 0);
|
||||
/// ```
|
||||
#[clippy::version = "1.64.0"]
|
||||
pub MANUAL_RETAIN,
|
||||
|
@ -74,9 +77,9 @@ impl<'tcx> LateLintPass<'tcx> for ManualRetain {
|
|||
&& let Some(collect_def_id) = cx.typeck_results().type_dependent_def_id(collect_expr.hir_id)
|
||||
&& cx.tcx.is_diagnostic_item(sym::iterator_collect_fn, collect_def_id)
|
||||
{
|
||||
check_into_iter(cx, parent_expr, left_expr, target_expr, &self.msrv);
|
||||
check_iter(cx, parent_expr, left_expr, target_expr, &self.msrv);
|
||||
check_to_owned(cx, parent_expr, left_expr, target_expr, &self.msrv);
|
||||
check_into_iter(cx, left_expr, target_expr, parent_expr.span, &self.msrv);
|
||||
check_iter(cx, left_expr, target_expr, parent_expr.span, &self.msrv);
|
||||
check_to_owned(cx, left_expr, target_expr, parent_expr.span, &self.msrv);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,9 +88,9 @@ impl<'tcx> LateLintPass<'tcx> for ManualRetain {
|
|||
|
||||
fn check_into_iter(
|
||||
cx: &LateContext<'_>,
|
||||
parent_expr: &hir::Expr<'_>,
|
||||
left_expr: &hir::Expr<'_>,
|
||||
target_expr: &hir::Expr<'_>,
|
||||
parent_expr_span: Span,
|
||||
msrv: &Msrv,
|
||||
) {
|
||||
if let hir::ExprKind::MethodCall(_, into_iter_expr, [_], _) = &target_expr.kind
|
||||
|
@ -98,16 +101,39 @@ fn check_into_iter(
|
|||
&& Some(into_iter_def_id) == cx.tcx.lang_items().into_iter_fn()
|
||||
&& match_acceptable_type(cx, left_expr, msrv)
|
||||
&& SpanlessEq::new(cx).eq_expr(left_expr, struct_expr)
|
||||
&& let hir::ExprKind::MethodCall(_, _, [closure_expr], _) = target_expr.kind
|
||||
&& let hir::ExprKind::Closure(closure) = closure_expr.kind
|
||||
&& let filter_body = cx.tcx.hir().body(closure.body)
|
||||
&& let [filter_params] = filter_body.params
|
||||
{
|
||||
suggest(cx, parent_expr, left_expr, target_expr);
|
||||
if match_map_type(cx, left_expr) {
|
||||
if let hir::PatKind::Tuple([key_pat, value_pat], _) = filter_params.pat.kind {
|
||||
if let Some(sugg) = make_sugg(cx, key_pat, value_pat, left_expr, filter_body) {
|
||||
make_span_lint_and_sugg(cx, parent_expr_span, sugg);
|
||||
}
|
||||
}
|
||||
// Cannot lint other cases because `retain` requires two parameters
|
||||
} else {
|
||||
// Can always move because `retain` and `filter` have the same bound on the predicate
|
||||
// for other types
|
||||
make_span_lint_and_sugg(
|
||||
cx,
|
||||
parent_expr_span,
|
||||
format!(
|
||||
"{}.retain({})",
|
||||
snippet(cx, left_expr.span, ".."),
|
||||
snippet(cx, closure_expr.span, "..")
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_iter(
|
||||
cx: &LateContext<'_>,
|
||||
parent_expr: &hir::Expr<'_>,
|
||||
left_expr: &hir::Expr<'_>,
|
||||
target_expr: &hir::Expr<'_>,
|
||||
parent_expr_span: Span,
|
||||
msrv: &Msrv,
|
||||
) {
|
||||
if let hir::ExprKind::MethodCall(_, filter_expr, [], _) = &target_expr.kind
|
||||
|
@ -122,16 +148,50 @@ fn check_iter(
|
|||
&& match_acceptable_def_path(cx, iter_expr_def_id)
|
||||
&& match_acceptable_type(cx, left_expr, msrv)
|
||||
&& SpanlessEq::new(cx).eq_expr(left_expr, struct_expr)
|
||||
&& let hir::ExprKind::MethodCall(_, _, [closure_expr], _) = filter_expr.kind
|
||||
&& let hir::ExprKind::Closure(closure) = closure_expr.kind
|
||||
&& let filter_body = cx.tcx.hir().body(closure.body)
|
||||
&& let [filter_params] = filter_body.params
|
||||
{
|
||||
suggest(cx, parent_expr, left_expr, filter_expr);
|
||||
match filter_params.pat.kind {
|
||||
// hir::PatKind::Binding(_, _, _, None) => {
|
||||
// // Be conservative now. Do nothing here.
|
||||
// // TODO: Ideally, we can rewrite the lambda by stripping one level of reference
|
||||
// },
|
||||
hir::PatKind::Tuple([_, _], _) => {
|
||||
// the `&&` reference for the `filter` method will be auto derefed to `ref`
|
||||
// so, we can directly use the lambda
|
||||
// https://doc.rust-lang.org/reference/patterns.html#binding-modes
|
||||
make_span_lint_and_sugg(
|
||||
cx,
|
||||
parent_expr_span,
|
||||
format!(
|
||||
"{}.retain({})",
|
||||
snippet(cx, left_expr.span, ".."),
|
||||
snippet(cx, closure_expr.span, "..")
|
||||
),
|
||||
);
|
||||
},
|
||||
hir::PatKind::Ref(pat, _) => make_span_lint_and_sugg(
|
||||
cx,
|
||||
parent_expr_span,
|
||||
format!(
|
||||
"{}.retain(|{}| {})",
|
||||
snippet(cx, left_expr.span, ".."),
|
||||
snippet(cx, pat.span, ".."),
|
||||
snippet(cx, filter_body.value.span, "..")
|
||||
),
|
||||
),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_to_owned(
|
||||
cx: &LateContext<'_>,
|
||||
parent_expr: &hir::Expr<'_>,
|
||||
left_expr: &hir::Expr<'_>,
|
||||
target_expr: &hir::Expr<'_>,
|
||||
parent_expr_span: Span,
|
||||
msrv: &Msrv,
|
||||
) {
|
||||
if msrv.meets(msrvs::STRING_RETAIN)
|
||||
|
@ -147,43 +207,25 @@ fn check_to_owned(
|
|||
&& let ty = cx.typeck_results().expr_ty(str_expr).peel_refs()
|
||||
&& is_type_lang_item(cx, ty, hir::LangItem::String)
|
||||
&& SpanlessEq::new(cx).eq_expr(left_expr, str_expr)
|
||||
{
|
||||
suggest(cx, parent_expr, left_expr, filter_expr);
|
||||
}
|
||||
}
|
||||
|
||||
fn suggest(cx: &LateContext<'_>, parent_expr: &hir::Expr<'_>, left_expr: &hir::Expr<'_>, filter_expr: &hir::Expr<'_>) {
|
||||
if let hir::ExprKind::MethodCall(_, _, [closure], _) = filter_expr.kind
|
||||
&& let hir::ExprKind::Closure(&hir::Closure { body, .. }) = closure.kind
|
||||
&& let filter_body = cx.tcx.hir().body(body)
|
||||
&& let hir::ExprKind::MethodCall(_, _, [closure_expr], _) = filter_expr.kind
|
||||
&& let hir::ExprKind::Closure(closure) = closure_expr.kind
|
||||
&& let filter_body = cx.tcx.hir().body(closure.body)
|
||||
&& let [filter_params] = filter_body.params
|
||||
&& let Some(sugg) = match filter_params.pat.kind {
|
||||
hir::PatKind::Binding(_, _, filter_param_ident, None) => Some(format!(
|
||||
"{}.retain(|{filter_param_ident}| {})",
|
||||
snippet(cx, left_expr.span, ".."),
|
||||
snippet(cx, filter_body.value.span, "..")
|
||||
)),
|
||||
hir::PatKind::Tuple([key_pat, value_pat], _) => make_sugg(cx, key_pat, value_pat, left_expr, filter_body),
|
||||
hir::PatKind::Ref(pat, _) => match pat.kind {
|
||||
hir::PatKind::Binding(_, _, filter_param_ident, None) => Some(format!(
|
||||
"{}.retain(|{filter_param_ident}| {})",
|
||||
snippet(cx, left_expr.span, ".."),
|
||||
snippet(cx, filter_body.value.span, "..")
|
||||
)),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_RETAIN,
|
||||
parent_expr.span,
|
||||
"this expression can be written more simply using `.retain()`",
|
||||
"consider calling `.retain()` instead",
|
||||
sugg,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
if let hir::PatKind::Ref(pat, _) = filter_params.pat.kind {
|
||||
make_span_lint_and_sugg(
|
||||
cx,
|
||||
parent_expr_span,
|
||||
format!(
|
||||
"{}.retain(|{}| {})",
|
||||
snippet(cx, left_expr.span, ".."),
|
||||
snippet(cx, pat.span, ".."),
|
||||
snippet(cx, filter_body.value.span, "..")
|
||||
),
|
||||
);
|
||||
}
|
||||
// Be conservative now. Do nothing for the `Binding` case.
|
||||
// TODO: Ideally, we can rewrite the lambda by stripping one level of reference
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -229,3 +271,20 @@ fn match_acceptable_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>, msrv: &Msrv
|
|||
&& acceptable_msrv.map_or(true, |acceptable_msrv| msrv.meets(acceptable_msrv))
|
||||
})
|
||||
}
|
||||
|
||||
fn match_map_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
|
||||
let expr_ty = cx.typeck_results().expr_ty(expr).peel_refs();
|
||||
MAP_TYPES.iter().any(|ty| is_type_diagnostic_item(cx, expr_ty, *ty))
|
||||
}
|
||||
|
||||
fn make_span_lint_and_sugg(cx: &LateContext<'_>, span: Span, sugg: String) {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_RETAIN,
|
||||
span,
|
||||
"this expression can be written more simply using `.retain()`",
|
||||
"consider calling `.retain()` instead",
|
||||
sugg,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,197 @@
|
|||
use clippy_config::msrvs::{self, Msrv};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::get_parent_expr;
|
||||
use clippy_utils::source::snippet;
|
||||
use rustc_ast::{LitKind, StrStyle};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind, Node, QPath, TyKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::{sym, Span, Symbol};
|
||||
|
||||
use super::MANUAL_C_STR_LITERALS;
|
||||
|
||||
/// Checks:
|
||||
/// - `b"...".as_ptr()`
|
||||
/// - `b"...".as_ptr().cast()`
|
||||
/// - `"...".as_ptr()`
|
||||
/// - `"...".as_ptr().cast()`
|
||||
///
|
||||
/// Iff the parent call of `.cast()` isn't `CStr::from_ptr`, to avoid linting twice.
|
||||
pub(super) fn check_as_ptr<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'tcx>,
|
||||
receiver: &'tcx Expr<'tcx>,
|
||||
msrv: &Msrv,
|
||||
) {
|
||||
if let ExprKind::Lit(lit) = receiver.kind
|
||||
&& let LitKind::ByteStr(_, StrStyle::Cooked) | LitKind::Str(_, StrStyle::Cooked) = lit.node
|
||||
&& let casts_removed = peel_ptr_cast_ancestors(cx, expr)
|
||||
&& !get_parent_expr(cx, casts_removed).is_some_and(
|
||||
|parent| matches!(parent.kind, ExprKind::Call(func, _) if is_c_str_function(cx, func).is_some()),
|
||||
)
|
||||
&& let Some(sugg) = rewrite_as_cstr(cx, lit.span)
|
||||
&& msrv.meets(msrvs::C_STR_LITERALS)
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_C_STR_LITERALS,
|
||||
receiver.span,
|
||||
"manually constructing a nul-terminated string",
|
||||
r#"use a `c""` literal"#,
|
||||
sugg,
|
||||
// an additional cast may be needed, since the type of `CStr::as_ptr` and
|
||||
// `"".as_ptr()` can differ and is platform dependent
|
||||
Applicability::HasPlaceholders,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the callee is a "relevant" `CStr` function considered by this lint.
|
||||
/// Returns the function name.
|
||||
fn is_c_str_function(cx: &LateContext<'_>, func: &Expr<'_>) -> Option<Symbol> {
|
||||
if let ExprKind::Path(QPath::TypeRelative(cstr, fn_name)) = &func.kind
|
||||
&& let TyKind::Path(QPath::Resolved(_, ty_path)) = &cstr.kind
|
||||
&& cx.tcx.lang_items().c_str() == ty_path.res.opt_def_id()
|
||||
{
|
||||
Some(fn_name.ident.name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks calls to the `CStr` constructor functions:
|
||||
/// - `CStr::from_bytes_with_nul(..)`
|
||||
/// - `CStr::from_bytes_with_nul_unchecked(..)`
|
||||
/// - `CStr::from_ptr(..)`
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args: &[Expr<'_>], msrv: &Msrv) {
|
||||
if let Some(fn_name) = is_c_str_function(cx, func)
|
||||
&& let [arg] = args
|
||||
&& msrv.meets(msrvs::C_STR_LITERALS)
|
||||
{
|
||||
match fn_name.as_str() {
|
||||
name @ ("from_bytes_with_nul" | "from_bytes_with_nul_unchecked")
|
||||
if !arg.span.from_expansion()
|
||||
&& let ExprKind::Lit(lit) = arg.kind
|
||||
&& let LitKind::ByteStr(_, StrStyle::Cooked) | LitKind::Str(_, StrStyle::Cooked) = lit.node =>
|
||||
{
|
||||
check_from_bytes(cx, expr, arg, name);
|
||||
},
|
||||
"from_ptr" => check_from_ptr(cx, expr, arg),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks `CStr::from_ptr(b"foo\0".as_ptr().cast())`
|
||||
fn check_from_ptr(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>) {
|
||||
if let ExprKind::MethodCall(method, lit, ..) = peel_ptr_cast(arg).kind
|
||||
&& method.ident.name == sym::as_ptr
|
||||
&& !lit.span.from_expansion()
|
||||
&& let ExprKind::Lit(lit) = lit.kind
|
||||
&& let LitKind::ByteStr(_, StrStyle::Cooked) = lit.node
|
||||
&& let Some(sugg) = rewrite_as_cstr(cx, lit.span)
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_C_STR_LITERALS,
|
||||
expr.span,
|
||||
"calling `CStr::from_ptr` with a byte string literal",
|
||||
r#"use a `c""` literal"#,
|
||||
sugg,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
/// Checks `CStr::from_bytes_with_nul(b"foo\0")`
|
||||
fn check_from_bytes(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, method: &str) {
|
||||
let (span, applicability) = if let Some(parent) = get_parent_expr(cx, expr)
|
||||
&& let ExprKind::MethodCall(method, ..) = parent.kind
|
||||
&& [sym::unwrap, sym::expect].contains(&method.ident.name)
|
||||
{
|
||||
(parent.span, Applicability::MachineApplicable)
|
||||
} else if method == "from_bytes_with_nul_unchecked" {
|
||||
// `*_unchecked` returns `&CStr` directly, nothing needs to be changed
|
||||
(expr.span, Applicability::MachineApplicable)
|
||||
} else {
|
||||
// User needs to remove error handling, can't be machine applicable
|
||||
(expr.span, Applicability::HasPlaceholders)
|
||||
};
|
||||
|
||||
let Some(sugg) = rewrite_as_cstr(cx, arg.span) else {
|
||||
return;
|
||||
};
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_C_STR_LITERALS,
|
||||
span,
|
||||
"calling `CStr::new` with a byte string literal",
|
||||
r#"use a `c""` literal"#,
|
||||
sugg,
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
|
||||
/// Rewrites a byte string literal to a c-str literal.
|
||||
/// `b"foo\0"` -> `c"foo"`
|
||||
///
|
||||
/// Returns `None` if it doesn't end in a NUL byte.
|
||||
fn rewrite_as_cstr(cx: &LateContext<'_>, span: Span) -> Option<String> {
|
||||
let mut sugg = String::from("c") + snippet(cx, span.source_callsite(), "..").trim_start_matches('b');
|
||||
|
||||
// NUL byte should always be right before the closing quote.
|
||||
if let Some(quote_pos) = sugg.rfind('"') {
|
||||
// Possible values right before the quote:
|
||||
// - literal NUL value
|
||||
if sugg.as_bytes()[quote_pos - 1] == b'\0' {
|
||||
sugg.remove(quote_pos - 1);
|
||||
}
|
||||
// - \x00
|
||||
else if sugg[..quote_pos].ends_with("\\x00") {
|
||||
sugg.replace_range(quote_pos - 4..quote_pos, "");
|
||||
}
|
||||
// - \0
|
||||
else if sugg[..quote_pos].ends_with("\\0") {
|
||||
sugg.replace_range(quote_pos - 2..quote_pos, "");
|
||||
}
|
||||
// No known suffix, so assume it's not a C-string.
|
||||
else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
Some(sugg)
|
||||
}
|
||||
|
||||
fn get_cast_target<'tcx>(e: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
|
||||
match &e.kind {
|
||||
ExprKind::MethodCall(method, receiver, [], _) if method.ident.as_str() == "cast" => Some(receiver),
|
||||
ExprKind::Cast(expr, _) => Some(expr),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// `x.cast()` -> `x`
|
||||
/// `x as *const _` -> `x`
|
||||
/// `x` -> `x` (returns the same expression for non-cast exprs)
|
||||
fn peel_ptr_cast<'tcx>(e: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
|
||||
get_cast_target(e).map_or(e, peel_ptr_cast)
|
||||
}
|
||||
|
||||
/// Same as `peel_ptr_cast`, but the other way around, by walking up the ancestor cast expressions:
|
||||
///
|
||||
/// `foo(x.cast() as *const _)`
|
||||
/// ^ given this `x` expression, returns the `foo(...)` expression
|
||||
fn peel_ptr_cast_ancestors<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
|
||||
let mut prev = e;
|
||||
for (_, node) in cx.tcx.hir().parent_iter(e.hir_id) {
|
||||
if let Node::Expr(e) = node
|
||||
&& get_cast_target(e).is_some()
|
||||
{
|
||||
prev = e;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
prev
|
||||
}
|
|
@ -21,7 +21,7 @@ pub(super) fn check<'tcx>(
|
|||
unwrap_arg: &'tcx hir::Expr<'_>,
|
||||
msrv: &Msrv,
|
||||
) -> bool {
|
||||
// lint if the caller of `map()` is an `Option`
|
||||
// lint if the caller of `map()` is an `Option` or a `Result`.
|
||||
let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option);
|
||||
let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ mod iter_skip_zero;
|
|||
mod iter_with_drain;
|
||||
mod iterator_step_by_zero;
|
||||
mod join_absolute_paths;
|
||||
mod manual_c_str_literals;
|
||||
mod manual_is_variant_and;
|
||||
mod manual_next_back;
|
||||
mod manual_ok_or;
|
||||
|
@ -113,6 +114,7 @@ mod unnecessary_iter_cloned;
|
|||
mod unnecessary_join;
|
||||
mod unnecessary_lazy_eval;
|
||||
mod unnecessary_literal_unwrap;
|
||||
mod unnecessary_result_map_or_else;
|
||||
mod unnecessary_sort_by;
|
||||
mod unnecessary_to_owned;
|
||||
mod unwrap_expect_used;
|
||||
|
@ -3951,6 +3953,64 @@ declare_clippy_lint! {
|
|||
"cloning an `Option` via `as_ref().cloned()`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for usage of `.map_or_else()` "map closure" for `Result` type.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This can be written more concisely by using `unwrap_or_else()`.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// # fn handle_error(_: ()) -> u32 { 0 }
|
||||
/// let x: Result<u32, ()> = Ok(0);
|
||||
/// let y = x.map_or_else(|err| handle_error(err), |n| n);
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// # fn handle_error(_: ()) -> u32 { 0 }
|
||||
/// let x: Result<u32, ()> = Ok(0);
|
||||
/// let y = x.unwrap_or_else(|err| handle_error(err));
|
||||
/// ```
|
||||
#[clippy::version = "1.77.0"]
|
||||
pub UNNECESSARY_RESULT_MAP_OR_ELSE,
|
||||
suspicious,
|
||||
"making no use of the \"map closure\" when calling `.map_or_else(|err| handle_error(err), |n| n)`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// Checks for the manual creation of C strings (a string with a `NUL` byte at the end), either
|
||||
/// through one of the `CStr` constructor functions, or more plainly by calling `.as_ptr()`
|
||||
/// on a (byte) string literal with a hardcoded `\0` byte at the end.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This can be written more concisely using `c"str"` literals and is also less error-prone,
|
||||
/// because the compiler checks for interior `NUL` bytes and the terminating `NUL` byte is inserted automatically.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// # use std::ffi::CStr;
|
||||
/// # mod libc { pub unsafe fn puts(_: *const i8) {} }
|
||||
/// fn needs_cstr(_: &CStr) {}
|
||||
///
|
||||
/// needs_cstr(CStr::from_bytes_with_nul(b"Hello\0").unwrap());
|
||||
/// unsafe { libc::puts("World\0".as_ptr().cast()) }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// # use std::ffi::CStr;
|
||||
/// # mod libc { pub unsafe fn puts(_: *const i8) {} }
|
||||
/// fn needs_cstr(_: &CStr) {}
|
||||
///
|
||||
/// needs_cstr(c"Hello");
|
||||
/// unsafe { libc::puts(c"World".as_ptr()) }
|
||||
/// ```
|
||||
#[clippy::version = "1.76.0"]
|
||||
pub MANUAL_C_STR_LITERALS,
|
||||
pedantic,
|
||||
r#"creating a `CStr` through functions when `c""` literals can be used"#
|
||||
}
|
||||
|
||||
pub struct Methods {
|
||||
avoid_breaking_exported_api: bool,
|
||||
msrv: Msrv,
|
||||
|
@ -4109,6 +4169,8 @@ impl_lint_pass!(Methods => [
|
|||
MANUAL_IS_VARIANT_AND,
|
||||
STR_SPLIT_AT_NEWLINE,
|
||||
OPTION_AS_REF_CLONED,
|
||||
UNNECESSARY_RESULT_MAP_OR_ELSE,
|
||||
MANUAL_C_STR_LITERALS,
|
||||
]);
|
||||
|
||||
/// Extracts a method call name, args, and `Span` of the method name.
|
||||
|
@ -4136,6 +4198,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
|
|||
hir::ExprKind::Call(func, args) => {
|
||||
from_iter_instead_of_collect::check(cx, expr, args, func);
|
||||
unnecessary_fallible_conversions::check_function(cx, expr, func);
|
||||
manual_c_str_literals::check(cx, expr, func, args, &self.msrv);
|
||||
},
|
||||
hir::ExprKind::MethodCall(method_call, receiver, args, _) => {
|
||||
let method_span = method_call.ident.span;
|
||||
|
@ -4354,6 +4417,7 @@ impl Methods {
|
|||
}
|
||||
},
|
||||
("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv),
|
||||
("as_ptr", []) => manual_c_str_literals::check_as_ptr(cx, expr, recv, &self.msrv),
|
||||
("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv),
|
||||
("assume_init", []) => uninit_assumed_init::check(cx, expr, recv),
|
||||
("cloned", []) => {
|
||||
|
@ -4592,6 +4656,7 @@ impl Methods {
|
|||
},
|
||||
("map_or_else", [def, map]) => {
|
||||
result_map_or_else_none::check(cx, expr, recv, def, map);
|
||||
unnecessary_result_map_or_else::check(cx, expr, recv, def, map);
|
||||
},
|
||||
("next", []) => {
|
||||
if let Some((name2, recv2, args2, _, _)) = method_call(recv) {
|
||||
|
|
|
@ -99,7 +99,6 @@ fn check_fold_with_op(
|
|||
cx,
|
||||
UNNECESSARY_FOLD,
|
||||
fold_span.with_hi(expr.span.hi()),
|
||||
// TODO #2371 don't suggest e.g., .any(|x| f(x)) if we can suggest .any(f)
|
||||
"this `.fold` can be written more succinctly using another method",
|
||||
"try",
|
||||
sugg,
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::peel_blocks;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::{Closure, Expr, ExprKind, HirId, QPath, Stmt, StmtKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::symbol::sym;
|
||||
|
||||
use super::UNNECESSARY_RESULT_MAP_OR_ELSE;
|
||||
|
||||
fn emit_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, def_arg: &Expr<'_>) {
|
||||
let msg = "unused \"map closure\" when calling `Result::map_or_else` value";
|
||||
let self_snippet = snippet(cx, recv.span, "..");
|
||||
let err_snippet = snippet(cx, def_arg.span, "..");
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
UNNECESSARY_RESULT_MAP_OR_ELSE,
|
||||
expr.span,
|
||||
msg,
|
||||
"consider using `unwrap_or_else`",
|
||||
format!("{self_snippet}.unwrap_or_else({err_snippet})"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
|
||||
fn get_last_chain_binding_hir_id(mut hir_id: HirId, statements: &[Stmt<'_>]) -> Option<HirId> {
|
||||
for stmt in statements {
|
||||
if let StmtKind::Local(local) = stmt.kind
|
||||
&& let Some(init) = local.init
|
||||
&& let ExprKind::Path(QPath::Resolved(_, path)) = init.kind
|
||||
&& let hir::def::Res::Local(local_hir_id) = path.res
|
||||
&& local_hir_id == hir_id
|
||||
{
|
||||
hir_id = local.pat.hir_id;
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(hir_id)
|
||||
}
|
||||
|
||||
fn handle_qpath(
|
||||
cx: &LateContext<'_>,
|
||||
expr: &Expr<'_>,
|
||||
recv: &Expr<'_>,
|
||||
def_arg: &Expr<'_>,
|
||||
expected_hir_id: HirId,
|
||||
qpath: QPath<'_>,
|
||||
) {
|
||||
if let QPath::Resolved(_, path) = qpath
|
||||
&& let hir::def::Res::Local(hir_id) = path.res
|
||||
&& expected_hir_id == hir_id
|
||||
{
|
||||
emit_lint(cx, expr, recv, def_arg);
|
||||
}
|
||||
}
|
||||
|
||||
/// lint use of `_.map_or_else(|err| err, |n| n)` for `Result`s.
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'_>,
|
||||
recv: &'tcx Expr<'_>,
|
||||
def_arg: &'tcx Expr<'_>,
|
||||
map_arg: &'tcx Expr<'_>,
|
||||
) {
|
||||
// lint if the caller of `map_or_else()` is a `Result`
|
||||
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result)
|
||||
&& let ExprKind::Closure(&Closure { body, .. }) = map_arg.kind
|
||||
&& let body = cx.tcx.hir().body(body)
|
||||
&& let Some(first_param) = body.params.first()
|
||||
{
|
||||
let body_expr = peel_blocks(body.value);
|
||||
|
||||
match body_expr.kind {
|
||||
ExprKind::Path(qpath) => {
|
||||
handle_qpath(cx, expr, recv, def_arg, first_param.pat.hir_id, qpath);
|
||||
},
|
||||
// If this is a block (that wasn't peeled off), then it means there are statements.
|
||||
ExprKind::Block(block, _) => {
|
||||
if let Some(block_expr) = block.expr
|
||||
// First we ensure that this is a "binding chain" (each statement is a binding
|
||||
// of the previous one) and that it is a binding of the closure argument.
|
||||
&& let Some(last_chain_binding_id) =
|
||||
get_last_chain_binding_hir_id(first_param.pat.hir_id, block.stmts)
|
||||
&& let ExprKind::Path(qpath) = block_expr.kind
|
||||
{
|
||||
handle_qpath(cx, expr, recv, def_arg, last_chain_binding_id, qpath);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
|
@ -357,7 +357,7 @@ fn reduce_expression<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<Vec
|
|||
}
|
||||
},
|
||||
ExprKind::Block(block, _) => {
|
||||
if block.stmts.is_empty() {
|
||||
if block.stmts.is_empty() && !block.targeted_by_break {
|
||||
block.expr.as_ref().and_then(|e| {
|
||||
match block.rules {
|
||||
BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) => None,
|
||||
|
|
|
@ -252,7 +252,7 @@ impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion {
|
|||
{
|
||||
(
|
||||
trait_item_id,
|
||||
FnKind::ImplTraitFn(cx.tcx.erase_regions(trait_ref.args) as *const _ as usize),
|
||||
FnKind::ImplTraitFn(std::ptr::from_ref(cx.tcx.erase_regions(trait_ref.args)) as usize),
|
||||
usize::from(sig.decl.implicit_self.has_implicit_self()),
|
||||
)
|
||||
} else {
|
||||
|
@ -390,7 +390,6 @@ fn has_matching_args(kind: FnKind, args: GenericArgsRef<'_>) -> bool {
|
|||
GenericArgKind::Type(ty) => matches!(*ty.kind(), ty::Param(ty) if ty.index as usize == idx),
|
||||
GenericArgKind::Const(c) => matches!(c.kind(), ConstKind::Param(c) if c.index as usize == idx),
|
||||
}),
|
||||
#[allow(trivial_casts)]
|
||||
FnKind::ImplTraitFn(expected_args) => args as *const _ as usize == expected_args,
|
||||
FnKind::ImplTraitFn(expected_args) => std::ptr::from_ref(args) as usize == expected_args,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -771,6 +771,7 @@ declare_clippy_lint! {
|
|||
pub struct Operators {
|
||||
arithmetic_context: numeric_arithmetic::Context,
|
||||
verbose_bit_mask_threshold: u64,
|
||||
modulo_arithmetic_allow_comparison_to_zero: bool,
|
||||
}
|
||||
impl_lint_pass!(Operators => [
|
||||
ABSURD_EXTREME_COMPARISONS,
|
||||
|
@ -801,10 +802,11 @@ impl_lint_pass!(Operators => [
|
|||
SELF_ASSIGNMENT,
|
||||
]);
|
||||
impl Operators {
|
||||
pub fn new(verbose_bit_mask_threshold: u64) -> Self {
|
||||
pub fn new(verbose_bit_mask_threshold: u64, modulo_arithmetic_allow_comparison_to_zero: bool) -> Self {
|
||||
Self {
|
||||
arithmetic_context: numeric_arithmetic::Context::default(),
|
||||
verbose_bit_mask_threshold,
|
||||
modulo_arithmetic_allow_comparison_to_zero,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -835,12 +837,19 @@ impl<'tcx> LateLintPass<'tcx> for Operators {
|
|||
cmp_owned::check(cx, op.node, lhs, rhs);
|
||||
float_cmp::check(cx, e, op.node, lhs, rhs);
|
||||
modulo_one::check(cx, e, op.node, rhs);
|
||||
modulo_arithmetic::check(cx, e, op.node, lhs, rhs);
|
||||
modulo_arithmetic::check(
|
||||
cx,
|
||||
e,
|
||||
op.node,
|
||||
lhs,
|
||||
rhs,
|
||||
self.modulo_arithmetic_allow_comparison_to_zero,
|
||||
);
|
||||
},
|
||||
ExprKind::AssignOp(op, lhs, rhs) => {
|
||||
self.arithmetic_context.check_binary(cx, e, op.node, lhs, rhs);
|
||||
misrefactored_assign_op::check(cx, e, op.node, lhs, rhs);
|
||||
modulo_arithmetic::check(cx, e, op.node, lhs, rhs);
|
||||
modulo_arithmetic::check(cx, e, op.node, lhs, rhs, false);
|
||||
},
|
||||
ExprKind::Assign(lhs, rhs, _) => {
|
||||
assign_op_pattern::check(cx, e, lhs, rhs);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use clippy_utils::consts::{constant, Constant};
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::sext;
|
||||
use rustc_hir::{BinOpKind, Expr};
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind, Node};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use std::fmt::Display;
|
||||
|
@ -14,8 +14,13 @@ pub(super) fn check<'tcx>(
|
|||
op: BinOpKind,
|
||||
lhs: &'tcx Expr<'_>,
|
||||
rhs: &'tcx Expr<'_>,
|
||||
allow_comparison_to_zero: bool,
|
||||
) {
|
||||
if op == BinOpKind::Rem {
|
||||
if allow_comparison_to_zero && used_in_comparison_with_zero(cx, e) {
|
||||
return;
|
||||
}
|
||||
|
||||
let lhs_operand = analyze_operand(lhs, cx, e);
|
||||
let rhs_operand = analyze_operand(rhs, cx, e);
|
||||
if let Some(lhs_operand) = lhs_operand
|
||||
|
@ -28,6 +33,26 @@ pub(super) fn check<'tcx>(
|
|||
};
|
||||
}
|
||||
|
||||
fn used_in_comparison_with_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
let Some(Node::Expr(parent_expr)) = cx.tcx.hir().find_parent(expr.hir_id) else {
|
||||
return false;
|
||||
};
|
||||
let ExprKind::Binary(op, lhs, rhs) = parent_expr.kind else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if op.node == BinOpKind::Eq || op.node == BinOpKind::Ne {
|
||||
if let Some(Constant::Int(0)) = constant(cx, cx.typeck_results(), rhs) {
|
||||
return true;
|
||||
}
|
||||
if let Some(Constant::Int(0)) = constant(cx, cx.typeck_results(), lhs) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
struct OperandInfo {
|
||||
string_representation: Option<String>,
|
||||
is_negative: bool,
|
||||
|
|
|
@ -2,6 +2,7 @@ use crate::rustc_lint::LintContext;
|
|||
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
|
||||
use clippy_utils::get_parent_expr;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use hir::Param;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::intravisit::{Visitor as HirVisitor, Visitor};
|
||||
|
@ -13,6 +14,7 @@ use rustc_middle::hir::nested_filter;
|
|||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::ExpnKind;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
@ -89,7 +91,12 @@ fn find_innermost_closure<'tcx>(
|
|||
cx: &LateContext<'tcx>,
|
||||
mut expr: &'tcx hir::Expr<'tcx>,
|
||||
mut steps: usize,
|
||||
) -> Option<(&'tcx hir::Expr<'tcx>, &'tcx hir::FnDecl<'tcx>, ty::Asyncness)> {
|
||||
) -> Option<(
|
||||
&'tcx hir::Expr<'tcx>,
|
||||
&'tcx hir::FnDecl<'tcx>,
|
||||
ty::Asyncness,
|
||||
&'tcx [Param<'tcx>],
|
||||
)> {
|
||||
let mut data = None;
|
||||
|
||||
while let hir::ExprKind::Closure(closure) = expr.kind
|
||||
|
@ -110,6 +117,7 @@ fn find_innermost_closure<'tcx>(
|
|||
} else {
|
||||
ty::Asyncness::No
|
||||
},
|
||||
body.params,
|
||||
));
|
||||
steps -= 1;
|
||||
}
|
||||
|
@ -152,7 +160,9 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClosureCall {
|
|||
// without this check, we'd end up linting twice.
|
||||
&& !matches!(recv.kind, hir::ExprKind::Call(..))
|
||||
&& let (full_expr, call_depth) = get_parent_call_exprs(cx, expr)
|
||||
&& let Some((body, fn_decl, coroutine_kind)) = find_innermost_closure(cx, recv, call_depth)
|
||||
&& let Some((body, fn_decl, coroutine_kind, params)) = find_innermost_closure(cx, recv, call_depth)
|
||||
// outside macros we lint properly. Inside macros, we lint only ||() style closures.
|
||||
&& (!matches!(expr.span.ctxt().outer_expn_data().kind, ExpnKind::Macro(_, _)) || params.is_empty())
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
|
|
|
@ -4,8 +4,10 @@ use clippy_utils::ty::needs_ordered_drop;
|
|||
use rustc_ast::Mutability;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::{BindingAnnotation, ByRef, ExprKind, HirId, Local, Node, Pat, PatKind, QPath};
|
||||
use rustc_hir_typeck::expr_use_visitor::PlaceBase;
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty::UpvarCapture;
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::symbol::Ident;
|
||||
use rustc_span::DesugaringKind;
|
||||
|
@ -69,6 +71,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantLocals {
|
|||
// the local is user-controlled
|
||||
&& !in_external_macro(cx.sess(), local.span)
|
||||
&& !is_from_proc_macro(cx, expr)
|
||||
&& !is_by_value_closure_capture(cx, local.hir_id, binding_id)
|
||||
{
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
|
@ -82,6 +85,29 @@ impl<'tcx> LateLintPass<'tcx> for RedundantLocals {
|
|||
}
|
||||
}
|
||||
|
||||
/// Checks if the enclosing body is a closure and if the given local is captured by value.
|
||||
///
|
||||
/// In those cases, the redefinition may be necessary to force a move:
|
||||
/// ```
|
||||
/// fn assert_static<T: 'static>(_: T) {}
|
||||
///
|
||||
/// let v = String::new();
|
||||
/// let closure = || {
|
||||
/// let v = v; // <- removing this redefinition makes `closure` no longer `'static`
|
||||
/// dbg!(&v);
|
||||
/// };
|
||||
/// assert_static(closure);
|
||||
/// ```
|
||||
fn is_by_value_closure_capture(cx: &LateContext<'_>, redefinition: HirId, root_variable: HirId) -> bool {
|
||||
let closure_def_id = cx.tcx.hir().enclosing_body_owner(redefinition);
|
||||
|
||||
cx.tcx.is_closure_or_coroutine(closure_def_id.to_def_id())
|
||||
&& cx.tcx.closure_captures(closure_def_id).iter().any(|c| {
|
||||
matches!(c.info.capture_kind, UpvarCapture::ByValue)
|
||||
&& matches!(c.place.base, PlaceBase::Upvar(upvar) if upvar.var_path.hir_id == root_variable)
|
||||
})
|
||||
}
|
||||
|
||||
/// Find the annotation of a binding introduced by a pattern, or `None` if it's not introduced.
|
||||
fn find_binding(pat: &Pat<'_>, name: Ident) -> Option<BindingAnnotation> {
|
||||
let mut ret = None;
|
||||
|
|
|
@ -188,7 +188,6 @@ impl LateLintPass<'_> for RedundantTypeAnnotations {
|
|||
match init_lit.node {
|
||||
// In these cases the annotation is redundant
|
||||
LitKind::Str(..)
|
||||
| LitKind::ByteStr(..)
|
||||
| LitKind::Byte(..)
|
||||
| LitKind::Char(..)
|
||||
| LitKind::Bool(..)
|
||||
|
@ -202,6 +201,16 @@ impl LateLintPass<'_> for RedundantTypeAnnotations {
|
|||
}
|
||||
},
|
||||
LitKind::Err => (),
|
||||
LitKind::ByteStr(..) => {
|
||||
// We only lint if the type annotation is an array type (e.g. &[u8; 4]).
|
||||
// If instead it is a slice (e.g. &[u8]) it may not be redundant, so we
|
||||
// don't lint.
|
||||
if let hir::TyKind::Ref(_, mut_ty) = ty.kind
|
||||
&& matches!(mut_ty.ty.kind, hir::TyKind::Array(..))
|
||||
{
|
||||
span_lint(cx, REDUNDANT_TYPE_ANNOTATIONS, local.span, "redundant type annotation");
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
|
|
|
@ -42,7 +42,7 @@ declare_clippy_lint! {
|
|||
/// // ^^^ this closure executes 123 times
|
||||
/// // and the vecs will have the expected capacity
|
||||
/// ```
|
||||
#[clippy::version = "1.74.0"]
|
||||
#[clippy::version = "1.76.0"]
|
||||
pub REPEAT_VEC_WITH_CAPACITY,
|
||||
suspicious,
|
||||
"repeating a `Vec::with_capacity` expression which does not retain capacity"
|
||||
|
|
|
@ -2,10 +2,14 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then, span_lin
|
|||
use clippy_utils::source::{snippet_opt, snippet_with_context};
|
||||
use clippy_utils::sugg::has_enclosing_paren;
|
||||
use clippy_utils::visitors::{for_each_expr_with_closures, Descend};
|
||||
use clippy_utils::{fn_def_id, is_from_proc_macro, is_inside_let_else, path_to_local_id, span_find_starting_semi};
|
||||
use clippy_utils::{
|
||||
fn_def_id, is_from_proc_macro, is_inside_let_else, is_res_lang_ctor, path_res, path_to_local_id,
|
||||
span_find_starting_semi,
|
||||
};
|
||||
use core::ops::ControlFlow;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::LangItem::ResultErr;
|
||||
use rustc_hir::{
|
||||
Block, Body, Expr, ExprKind, FnDecl, HirId, ItemKind, LangItem, MatchSource, Node, OwnerNode, PatKind, QPath, Stmt,
|
||||
StmtKind,
|
||||
|
@ -18,6 +22,7 @@ use rustc_session::declare_lint_pass;
|
|||
use rustc_span::def_id::LocalDefId;
|
||||
use rustc_span::{BytePos, Pos, Span};
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Display;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
@ -146,14 +151,14 @@ impl<'tcx> RetReplacement<'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'tcx> ToString for RetReplacement<'tcx> {
|
||||
fn to_string(&self) -> String {
|
||||
impl<'tcx> Display for RetReplacement<'tcx> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Empty => String::new(),
|
||||
Self::Block => "{}".to_string(),
|
||||
Self::Unit => "()".to_string(),
|
||||
Self::IfSequence(inner, _) => format!("({inner})"),
|
||||
Self::Expr(inner, _) => inner.to_string(),
|
||||
Self::Empty => write!(f, ""),
|
||||
Self::Block => write!(f, "{{}}"),
|
||||
Self::Unit => write!(f, "()"),
|
||||
Self::IfSequence(inner, _) => write!(f, "({inner})"),
|
||||
Self::Expr(inner, _) => write!(f, "{inner}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -181,7 +186,15 @@ impl<'tcx> LateLintPass<'tcx> for Return {
|
|||
if !in_external_macro(cx.sess(), stmt.span)
|
||||
&& let StmtKind::Semi(expr) = stmt.kind
|
||||
&& let ExprKind::Ret(Some(ret)) = expr.kind
|
||||
&& let ExprKind::Match(.., MatchSource::TryDesugar(_)) = ret.kind
|
||||
// return Err(...)? desugars to a match
|
||||
// over a Err(...).branch()
|
||||
// which breaks down to a branch call, with the callee being
|
||||
// the constructor of the Err variant
|
||||
&& let ExprKind::Match(maybe_cons, _, MatchSource::TryDesugar(_)) = ret.kind
|
||||
&& let ExprKind::Call(_, [maybe_result_err]) = maybe_cons.kind
|
||||
&& let ExprKind::Call(maybe_constr, _) = maybe_result_err.kind
|
||||
&& is_res_lang_ctor(cx, path_res(cx, maybe_constr), ResultErr)
|
||||
|
||||
// Ensure this is not the final stmt, otherwise removing it would cause a compile error
|
||||
&& let OwnerNode::Item(item) = cx.tcx.hir_owner_node(cx.tcx.hir().get_parent_item(expr.hir_id))
|
||||
&& let ItemKind::Fn(_, _, body) = item.kind
|
||||
|
|
67
src/tools/clippy/clippy_lints/src/to_string_trait_impl.rs
Normal file
67
src/tools/clippy/clippy_lints/src/to_string_trait_impl.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use rustc_hir::{Impl, Item, ItemKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::sym;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for direct implementations of `ToString`.
|
||||
/// ### Why is this bad?
|
||||
/// This trait is automatically implemented for any type which implements the `Display` trait.
|
||||
/// As such, `ToString` shouldn’t be implemented directly: `Display` should be implemented instead,
|
||||
/// and you get the `ToString` implementation for free.
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// struct Point {
|
||||
/// x: usize,
|
||||
/// y: usize,
|
||||
/// }
|
||||
///
|
||||
/// impl ToString for Point {
|
||||
/// fn to_string(&self) -> String {
|
||||
/// format!("({}, {})", self.x, self.y)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// struct Point {
|
||||
/// x: usize,
|
||||
/// y: usize,
|
||||
/// }
|
||||
///
|
||||
/// impl std::fmt::Display for Point {
|
||||
/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
/// write!(f, "({}, {})", self.x, self.y)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.77.0"]
|
||||
pub TO_STRING_TRAIT_IMPL,
|
||||
style,
|
||||
"check for direct implementations of `ToString`"
|
||||
}
|
||||
|
||||
declare_lint_pass!(ToStringTraitImpl => [TO_STRING_TRAIT_IMPL]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for ToStringTraitImpl {
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx Item<'tcx>) {
|
||||
if let ItemKind::Impl(Impl {
|
||||
of_trait: Some(trait_ref),
|
||||
..
|
||||
}) = it.kind
|
||||
&& let Some(trait_did) = trait_ref.trait_def_id()
|
||||
&& cx.tcx.is_diagnostic_item(sym::ToString, trait_did)
|
||||
{
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
TO_STRING_TRAIT_IMPL,
|
||||
it.span,
|
||||
"direct implementation of `ToString`",
|
||||
None,
|
||||
"prefer implementing `Display` instead",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -69,14 +69,6 @@ fn span_error(cx: &LateContext<'_>, method_span: Span, expr: &Expr<'_>) {
|
|||
);
|
||||
}
|
||||
|
||||
fn get_ty_def_id(ty: Ty<'_>) -> Option<DefId> {
|
||||
match ty.peel_refs().kind() {
|
||||
ty::Adt(adt, _) => Some(adt.did()),
|
||||
ty::Foreign(def_id) => Some(*def_id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_hir_ty_def_id<'tcx>(tcx: TyCtxt<'tcx>, hir_ty: rustc_hir::Ty<'tcx>) -> Option<DefId> {
|
||||
let TyKind::Path(qpath) = hir_ty.kind else { return None };
|
||||
match qpath {
|
||||
|
@ -131,21 +123,49 @@ fn get_impl_trait_def_id(cx: &LateContext<'_>, method_def_id: LocalDefId) -> Opt
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::unnecessary_def_path)]
|
||||
/// When we have `x == y` where `x = &T` and `y = &T`, then that resolves to
|
||||
/// `<&T as PartialEq<&T>>::eq`, which is not the same as `<T as PartialEq<T>>::eq`,
|
||||
/// however we still would want to treat it the same, because we know that it's a blanket impl
|
||||
/// that simply delegates to the `PartialEq` impl with one reference removed.
|
||||
///
|
||||
/// Still, we can't just do `lty.peel_refs() == rty.peel_refs()` because when we have `x = &T` and
|
||||
/// `y = &&T`, this is not necessarily the same as `<T as PartialEq<T>>::eq`
|
||||
///
|
||||
/// So to avoid these FNs and FPs, we keep removing a layer of references from *both* sides
|
||||
/// until both sides match the expected LHS and RHS type (or they don't).
|
||||
fn matches_ty<'tcx>(
|
||||
mut left: Ty<'tcx>,
|
||||
mut right: Ty<'tcx>,
|
||||
expected_left: Ty<'tcx>,
|
||||
expected_right: Ty<'tcx>,
|
||||
) -> bool {
|
||||
while let (&ty::Ref(_, lty, _), &ty::Ref(_, rty, _)) = (left.kind(), right.kind()) {
|
||||
if lty == expected_left && rty == expected_right {
|
||||
return true;
|
||||
}
|
||||
left = lty;
|
||||
right = rty;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: LocalDefId, name: Ident, expr: &Expr<'_>) {
|
||||
let args = cx
|
||||
.tcx
|
||||
.instantiate_bound_regions_with_erased(cx.tcx.fn_sig(method_def_id).skip_binder())
|
||||
.inputs();
|
||||
let Some(sig) = cx
|
||||
.typeck_results()
|
||||
.liberated_fn_sigs()
|
||||
.get(cx.tcx.local_def_id_to_hir_id(method_def_id))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
// That has two arguments.
|
||||
if let [self_arg, other_arg] = args
|
||||
&& let Some(self_arg) = get_ty_def_id(*self_arg)
|
||||
&& let Some(other_arg) = get_ty_def_id(*other_arg)
|
||||
if let [self_arg, other_arg] = sig.inputs()
|
||||
&& let &ty::Ref(_, self_arg, _) = self_arg.kind()
|
||||
&& let &ty::Ref(_, other_arg, _) = other_arg.kind()
|
||||
// The two arguments are of the same type.
|
||||
&& self_arg == other_arg
|
||||
&& let Some(trait_def_id) = get_impl_trait_def_id(cx, method_def_id)
|
||||
// The trait is `PartialEq`.
|
||||
&& Some(trait_def_id) == get_trait_def_id(cx, &["core", "cmp", "PartialEq"])
|
||||
&& cx.tcx.is_diagnostic_item(sym::PartialEq, trait_def_id)
|
||||
{
|
||||
let to_check_op = if name.name == sym::eq {
|
||||
BinOpKind::Eq
|
||||
|
@ -154,31 +174,19 @@ fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: Loca
|
|||
};
|
||||
let is_bad = match expr.kind {
|
||||
ExprKind::Binary(op, left, right) if op.node == to_check_op => {
|
||||
// Then we check if the left-hand element is of the same type as `self`.
|
||||
if let Some(left_ty) = cx.typeck_results().expr_ty_opt(left)
|
||||
&& let Some(left_id) = get_ty_def_id(left_ty)
|
||||
&& self_arg == left_id
|
||||
&& let Some(right_ty) = cx.typeck_results().expr_ty_opt(right)
|
||||
&& let Some(right_id) = get_ty_def_id(right_ty)
|
||||
&& other_arg == right_id
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
// Then we check if the LHS matches self_arg and RHS matches other_arg
|
||||
let left_ty = cx.typeck_results().expr_ty_adjusted(left);
|
||||
let right_ty = cx.typeck_results().expr_ty_adjusted(right);
|
||||
matches_ty(left_ty, right_ty, self_arg, other_arg)
|
||||
},
|
||||
ExprKind::MethodCall(segment, receiver, &[_arg], _) if segment.ident.name == name.name => {
|
||||
if let Some(ty) = cx.typeck_results().expr_ty_opt(receiver)
|
||||
&& let Some(ty_id) = get_ty_def_id(ty)
|
||||
&& self_arg != ty_id
|
||||
{
|
||||
// Since this called on a different type, the lint should not be
|
||||
// triggered here.
|
||||
return;
|
||||
}
|
||||
ExprKind::MethodCall(segment, receiver, [arg], _) if segment.ident.name == name.name => {
|
||||
let receiver_ty = cx.typeck_results().expr_ty_adjusted(receiver);
|
||||
let arg_ty = cx.typeck_results().expr_ty_adjusted(arg);
|
||||
|
||||
if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
|
||||
&& let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
|
||||
&& trait_id == trait_def_id
|
||||
&& matches_ty(receiver_ty, arg_ty, self_arg, other_arg)
|
||||
{
|
||||
true
|
||||
} else {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::{is_res_lang_ctor, is_trait_method, match_trait_method, paths};
|
||||
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
|
||||
use clippy_utils::{is_res_lang_ctor, is_trait_method, match_trait_method, paths, peel_blocks};
|
||||
use hir::{ExprKind, PatKind};
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
|
@ -82,37 +83,72 @@ impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount {
|
|||
}
|
||||
|
||||
if let Some(exp) = block.expr
|
||||
&& matches!(exp.kind, hir::ExprKind::If(_, _, _) | hir::ExprKind::Match(_, _, _))
|
||||
&& matches!(
|
||||
exp.kind,
|
||||
hir::ExprKind::If(_, _, _) | hir::ExprKind::Match(_, _, hir::MatchSource::Normal)
|
||||
)
|
||||
{
|
||||
check_expr(cx, exp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn non_consuming_err_arm<'a>(cx: &LateContext<'a>, arm: &hir::Arm<'a>) -> bool {
|
||||
// if there is a guard, we consider the result to be consumed
|
||||
if arm.guard.is_some() {
|
||||
return false;
|
||||
}
|
||||
if is_unreachable_or_panic(cx, arm.body) {
|
||||
// if the body is unreachable or there is a panic,
|
||||
// we consider the result to be consumed
|
||||
return false;
|
||||
}
|
||||
|
||||
if let PatKind::TupleStruct(ref path, [inner_pat], _) = arm.pat.kind {
|
||||
return is_res_lang_ctor(cx, cx.qpath_res(path, inner_pat.hir_id), hir::LangItem::ResultErr);
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn non_consuming_ok_arm<'a>(cx: &LateContext<'a>, arm: &hir::Arm<'a>) -> bool {
|
||||
// if there is a guard, we consider the result to be consumed
|
||||
if arm.guard.is_some() {
|
||||
return false;
|
||||
}
|
||||
if is_unreachable_or_panic(cx, arm.body) {
|
||||
// if the body is unreachable or there is a panic,
|
||||
// we consider the result to be consumed
|
||||
return false;
|
||||
}
|
||||
|
||||
if is_ok_wild_or_dotdot_pattern(cx, arm.pat) {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn check_expr<'a>(cx: &LateContext<'a>, expr: &'a hir::Expr<'a>) {
|
||||
match expr.kind {
|
||||
hir::ExprKind::If(cond, _, _)
|
||||
if let ExprKind::Let(hir::Let { pat, init, .. }) = cond.kind
|
||||
&& pattern_is_ignored_ok(cx, pat)
|
||||
&& is_ok_wild_or_dotdot_pattern(cx, pat)
|
||||
&& let Some(op) = should_lint(cx, init) =>
|
||||
{
|
||||
emit_lint(cx, cond.span, op, &[pat.span]);
|
||||
},
|
||||
hir::ExprKind::Match(expr, arms, hir::MatchSource::Normal) if let Some(op) = should_lint(cx, expr) => {
|
||||
let found_arms: Vec<_> = arms
|
||||
.iter()
|
||||
.filter_map(|arm| {
|
||||
if pattern_is_ignored_ok(cx, arm.pat) {
|
||||
Some(arm.span)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
if !found_arms.is_empty() {
|
||||
emit_lint(cx, expr.span, op, found_arms.as_slice());
|
||||
// we will capture only the case where the match is Ok( ) or Err( )
|
||||
// prefer to match the minimum possible, and expand later if needed
|
||||
// to avoid false positives on something as used as this
|
||||
hir::ExprKind::Match(expr, [arm1, arm2], hir::MatchSource::Normal) if let Some(op) = should_lint(cx, expr) => {
|
||||
if non_consuming_ok_arm(cx, arm1) && non_consuming_err_arm(cx, arm2) {
|
||||
emit_lint(cx, expr.span, op, &[arm1.pat.span]);
|
||||
}
|
||||
if non_consuming_ok_arm(cx, arm2) && non_consuming_err_arm(cx, arm1) {
|
||||
emit_lint(cx, expr.span, op, &[arm2.pat.span]);
|
||||
}
|
||||
},
|
||||
hir::ExprKind::Match(_, _, hir::MatchSource::Normal) => {},
|
||||
_ if let Some(op) = should_lint(cx, expr) => {
|
||||
emit_lint(cx, expr.span, op, &[]);
|
||||
},
|
||||
|
@ -130,25 +166,40 @@ fn should_lint<'a>(cx: &LateContext<'a>, mut inner: &'a hir::Expr<'a>) -> Option
|
|||
check_io_mode(cx, inner)
|
||||
}
|
||||
|
||||
fn pattern_is_ignored_ok(cx: &LateContext<'_>, pat: &hir::Pat<'_>) -> bool {
|
||||
fn is_ok_wild_or_dotdot_pattern<'a>(cx: &LateContext<'a>, pat: &hir::Pat<'a>) -> bool {
|
||||
// the if checks whether we are in a result Ok( ) pattern
|
||||
// and the return checks whether it is unhandled
|
||||
|
||||
if let PatKind::TupleStruct(ref path, inner_pat, ddp) = pat.kind
|
||||
if let PatKind::TupleStruct(ref path, inner_pat, _) = pat.kind
|
||||
// we check against Result::Ok to avoid linting on Err(_) or something else.
|
||||
&& is_res_lang_ctor(cx, cx.qpath_res(path, pat.hir_id), hir::LangItem::ResultOk)
|
||||
{
|
||||
return match (inner_pat, ddp.as_opt_usize()) {
|
||||
// Ok(_) pattern
|
||||
([inner_pat], None) if matches!(inner_pat.kind, PatKind::Wild) => true,
|
||||
// Ok(..) pattern
|
||||
([], Some(0)) => true,
|
||||
_ => false,
|
||||
};
|
||||
if matches!(inner_pat, []) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if let [cons_pat] = inner_pat
|
||||
&& matches!(cons_pat.kind, PatKind::Wild)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
// this is partially taken from panic_unimplemented
|
||||
fn is_unreachable_or_panic(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
|
||||
let expr = peel_blocks(expr);
|
||||
let Some(macro_call) = root_macro_call_first_node(cx, expr) else {
|
||||
return false;
|
||||
};
|
||||
if is_panic(cx, macro_call.def_id) {
|
||||
return !cx.tcx.hir().is_inside_const_context(expr.hir_id);
|
||||
}
|
||||
matches!(cx.tcx.item_name(macro_call.def_id).as_str(), "unreachable")
|
||||
}
|
||||
|
||||
fn unpack_call_chain<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
|
||||
while let hir::ExprKind::MethodCall(path, receiver, ..) = expr.kind {
|
||||
if matches!(
|
||||
|
|
|
@ -490,9 +490,9 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
|
|||
format!("ClosureKind::Coroutine(CoroutineKind::Coroutine(Movability::{movability:?})")
|
||||
},
|
||||
},
|
||||
ClosureKind::CoroutineClosure(desugaring) => format!(
|
||||
"ClosureKind::CoroutineClosure(CoroutineDesugaring::{desugaring:?})"
|
||||
),
|
||||
ClosureKind::CoroutineClosure(desugaring) => {
|
||||
format!("ClosureKind::CoroutineClosure(CoroutineDesugaring::{desugaring:?})")
|
||||
},
|
||||
};
|
||||
|
||||
let ret_ty = match fn_decl.output {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::is_test_module_or_function;
|
||||
use clippy_utils::source::{snippet, snippet_with_applicability};
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{Item, ItemKind, PathSegment, UseKind};
|
||||
|
@ -100,13 +101,15 @@ declare_clippy_lint! {
|
|||
pub struct WildcardImports {
|
||||
warn_on_all: bool,
|
||||
test_modules_deep: u32,
|
||||
allowed_segments: FxHashSet<String>,
|
||||
}
|
||||
|
||||
impl WildcardImports {
|
||||
pub fn new(warn_on_all: bool) -> Self {
|
||||
pub fn new(warn_on_all: bool, allowed_wildcard_imports: FxHashSet<String>) -> Self {
|
||||
Self {
|
||||
warn_on_all,
|
||||
test_modules_deep: 0,
|
||||
allowed_segments: allowed_wildcard_imports,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -190,6 +193,7 @@ impl WildcardImports {
|
|||
item.span.from_expansion()
|
||||
|| is_prelude_import(segments)
|
||||
|| (is_super_only_import(segments) && self.test_modules_deep > 0)
|
||||
|| is_allowed_via_config(segments, &self.allowed_segments)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -198,10 +202,18 @@ impl WildcardImports {
|
|||
fn is_prelude_import(segments: &[PathSegment<'_>]) -> bool {
|
||||
segments
|
||||
.iter()
|
||||
.any(|ps| ps.ident.name.as_str().contains(sym::prelude.as_str()))
|
||||
.any(|ps| ps.ident.as_str().contains(sym::prelude.as_str()))
|
||||
}
|
||||
|
||||
// Allow "super::*" imports in tests.
|
||||
fn is_super_only_import(segments: &[PathSegment<'_>]) -> bool {
|
||||
segments.len() == 1 && segments[0].ident.name == kw::Super
|
||||
}
|
||||
|
||||
// Allow skipping imports containing user configured segments,
|
||||
// i.e. "...::utils::...::*" if user put `allowed-wildcard-imports = ["utils"]` in `Clippy.toml`
|
||||
fn is_allowed_via_config(segments: &[PathSegment<'_>], allowed_segments: &FxHashSet<String>) -> bool {
|
||||
// segment matching need to be exact instead of using 'contains', in case user unintentionaly put
|
||||
// a single character in the config thus skipping most of the warnings.
|
||||
segments.iter().any(|seg| allowed_segments.contains(seg.ident.as_str()))
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "clippy_utils"
|
||||
version = "0.1.77"
|
||||
version = "0.1.78"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#![feature(array_chunks)]
|
||||
#![feature(box_patterns)]
|
||||
#![feature(control_flow_enum)]
|
||||
#![feature(if_let_guard)]
|
||||
#![feature(let_chains)]
|
||||
#![feature(lint_reasons)]
|
||||
|
@ -81,6 +82,7 @@ use core::mem;
|
|||
use core::ops::ControlFlow;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::hash::BuildHasherDefault;
|
||||
use std::iter::{once, repeat};
|
||||
use std::sync::{Mutex, MutexGuard, OnceLock};
|
||||
|
||||
use itertools::Itertools;
|
||||
|
@ -90,6 +92,7 @@ use rustc_data_structures::packed::Pu128;
|
|||
use rustc_data_structures::unhash::UnhashMap;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::def_id::{CrateNum, DefId, LocalDefId, LocalModDefId, LOCAL_CRATE};
|
||||
use rustc_hir::definitions::{DefPath, DefPathData};
|
||||
use rustc_hir::hir_id::{HirIdMap, HirIdSet};
|
||||
use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
|
||||
use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
|
||||
|
@ -108,8 +111,8 @@ use rustc_middle::ty::binding::BindingMode;
|
|||
use rustc_middle::ty::fast_reject::SimplifiedType;
|
||||
use rustc_middle::ty::layout::IntegerExt;
|
||||
use rustc_middle::ty::{
|
||||
self as rustc_ty, Binder, BorrowKind, ClosureKind, FloatTy, IntTy, ParamEnv, ParamEnvAnd, Ty, TyCtxt, TypeAndMut,
|
||||
TypeVisitableExt, UintTy, UpvarCapture,
|
||||
self as rustc_ty, Binder, BorrowKind, ClosureKind, EarlyBinder, FloatTy, GenericArgsRef, IntTy, ParamEnv,
|
||||
ParamEnvAnd, Ty, TyCtxt, TypeAndMut, TypeVisitableExt, UintTy, UpvarCapture,
|
||||
};
|
||||
use rustc_span::hygiene::{ExpnKind, MacroKind};
|
||||
use rustc_span::source_map::SourceMap;
|
||||
|
@ -120,10 +123,7 @@ use visitors::Visitable;
|
|||
|
||||
use crate::consts::{constant, mir_to_const, Constant};
|
||||
use crate::higher::Range;
|
||||
use crate::ty::{
|
||||
adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type,
|
||||
ty_is_fn_once_param,
|
||||
};
|
||||
use crate::ty::{adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type};
|
||||
use crate::visitors::for_each_expr;
|
||||
|
||||
use rustc_middle::hir::nested_filter;
|
||||
|
@ -1359,46 +1359,12 @@ pub fn get_enclosing_loop_or_multi_call_closure<'tcx>(
|
|||
for (_, node) in cx.tcx.hir().parent_iter(expr.hir_id) {
|
||||
match node {
|
||||
Node::Expr(e) => match e.kind {
|
||||
ExprKind::Closure { .. } => {
|
||||
ExprKind::Closure { .. }
|
||||
if let rustc_ty::Closure(_, subs) = cx.typeck_results().expr_ty(e).kind()
|
||||
&& subs.as_closure().kind() == ClosureKind::FnOnce
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let is_once = walk_to_expr_usage(cx, e, |node, id| {
|
||||
let Node::Expr(e) = node else {
|
||||
return None;
|
||||
};
|
||||
match e.kind {
|
||||
ExprKind::Call(f, _) if f.hir_id == id => Some(()),
|
||||
ExprKind::Call(f, args) => {
|
||||
let i = args.iter().position(|arg| arg.hir_id == id)?;
|
||||
let sig = expr_sig(cx, f)?;
|
||||
let predicates = sig
|
||||
.predicates_id()
|
||||
.map_or(cx.param_env, |id| cx.tcx.param_env(id))
|
||||
.caller_bounds();
|
||||
sig.input(i).and_then(|ty| {
|
||||
ty_is_fn_once_param(cx.tcx, ty.skip_binder(), predicates).then_some(())
|
||||
})
|
||||
},
|
||||
ExprKind::MethodCall(_, receiver, args, _) => {
|
||||
let i = std::iter::once(receiver)
|
||||
.chain(args.iter())
|
||||
.position(|arg| arg.hir_id == id)?;
|
||||
let id = cx.typeck_results().type_dependent_def_id(e.hir_id)?;
|
||||
let ty = cx.tcx.fn_sig(id).instantiate_identity().skip_binder().inputs()[i];
|
||||
ty_is_fn_once_param(cx.tcx, ty, cx.tcx.param_env(id).caller_bounds()).then_some(())
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.is_some();
|
||||
if !is_once {
|
||||
return Some(e);
|
||||
}
|
||||
},
|
||||
ExprKind::Loop(..) => return Some(e),
|
||||
&& subs.as_closure().kind() == ClosureKind::FnOnce => {},
|
||||
|
||||
// Note: A closure's kind is determined by how it's used, not it's captures.
|
||||
ExprKind::Closure { .. } | ExprKind::Loop(..) => return Some(e),
|
||||
_ => (),
|
||||
},
|
||||
Node::Stmt(_) | Node::Block(_) | Node::Local(_) | Node::Arm(_) => (),
|
||||
|
@ -2596,26 +2562,30 @@ pub fn is_test_module_or_function(tcx: TyCtxt<'_>, item: &Item<'_>) -> bool {
|
|||
&& item.ident.name.as_str().split('_').any(|a| a == "test" || a == "tests")
|
||||
}
|
||||
|
||||
/// Walks the HIR tree from the given expression, up to the node where the value produced by the
|
||||
/// expression is consumed. Calls the function for every node encountered this way until it returns
|
||||
/// `Some`.
|
||||
/// Walks up the HIR tree from the given expression in an attempt to find where the value is
|
||||
/// consumed.
|
||||
///
|
||||
/// This allows walking through `if`, `match`, `break`, block expressions to find where the value
|
||||
/// produced by the expression is consumed.
|
||||
/// Termination has three conditions:
|
||||
/// - The given function returns `Break`. This function will return the value.
|
||||
/// - The consuming node is found. This function will return `Continue(use_node, child_id)`.
|
||||
/// - No further parent nodes are found. This will trigger a debug assert or return `None`.
|
||||
///
|
||||
/// This allows walking through `if`, `match`, `break`, and block expressions to find where the
|
||||
/// value produced by the expression is consumed.
|
||||
pub fn walk_to_expr_usage<'tcx, T>(
|
||||
cx: &LateContext<'tcx>,
|
||||
e: &Expr<'tcx>,
|
||||
mut f: impl FnMut(Node<'tcx>, HirId) -> Option<T>,
|
||||
) -> Option<T> {
|
||||
mut f: impl FnMut(HirId, Node<'tcx>, HirId) -> ControlFlow<T>,
|
||||
) -> Option<ControlFlow<T, (Node<'tcx>, HirId)>> {
|
||||
let map = cx.tcx.hir();
|
||||
let mut iter = map.parent_iter(e.hir_id);
|
||||
let mut child_id = e.hir_id;
|
||||
|
||||
while let Some((parent_id, parent)) = iter.next() {
|
||||
if let Some(x) = f(parent, child_id) {
|
||||
return Some(x);
|
||||
if let ControlFlow::Break(x) = f(parent_id, parent, child_id) {
|
||||
return Some(ControlFlow::Break(x));
|
||||
}
|
||||
let parent = match parent {
|
||||
let parent_expr = match parent {
|
||||
Node::Expr(e) => e,
|
||||
Node::Block(Block { expr: Some(body), .. }) | Node::Arm(Arm { body, .. }) if body.hir_id == child_id => {
|
||||
child_id = parent_id;
|
||||
|
@ -2625,18 +2595,19 @@ pub fn walk_to_expr_usage<'tcx, T>(
|
|||
child_id = parent_id;
|
||||
continue;
|
||||
},
|
||||
_ => return None,
|
||||
_ => return Some(ControlFlow::Continue((parent, child_id))),
|
||||
};
|
||||
match parent.kind {
|
||||
match parent_expr.kind {
|
||||
ExprKind::If(child, ..) | ExprKind::Match(child, ..) if child.hir_id != child_id => child_id = parent_id,
|
||||
ExprKind::Break(Destination { target_id: Ok(id), .. }, _) => {
|
||||
child_id = id;
|
||||
iter = map.parent_iter(id);
|
||||
},
|
||||
ExprKind::Block(..) => child_id = parent_id,
|
||||
_ => return None,
|
||||
ExprKind::Block(..) | ExprKind::DropTemps(_) => child_id = parent_id,
|
||||
_ => return Some(ControlFlow::Continue((parent, child_id))),
|
||||
}
|
||||
}
|
||||
debug_assert!(false, "no parent node found for `{child_id:?}`");
|
||||
None
|
||||
}
|
||||
|
||||
|
@ -2680,6 +2651,8 @@ pub enum ExprUseNode<'tcx> {
|
|||
Callee,
|
||||
/// Access of a field.
|
||||
FieldAccess(Ident),
|
||||
Expr,
|
||||
Other,
|
||||
}
|
||||
impl<'tcx> ExprUseNode<'tcx> {
|
||||
/// Checks if the value is returned from the function.
|
||||
|
@ -2756,144 +2729,104 @@ impl<'tcx> ExprUseNode<'tcx> {
|
|||
let sig = cx.tcx.fn_sig(id).skip_binder();
|
||||
Some(DefinedTy::Mir(cx.tcx.param_env(id).and(sig.input(i))))
|
||||
},
|
||||
Self::Local(_) | Self::FieldAccess(..) | Self::Callee => None,
|
||||
Self::Local(_) | Self::FieldAccess(..) | Self::Callee | Self::Expr | Self::Other => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the context an expression's value is used in.
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> Option<ExprUseCtxt<'tcx>> {
|
||||
let mut adjustments = [].as_slice();
|
||||
let mut is_ty_unified = false;
|
||||
let mut moved_before_use = false;
|
||||
let ctxt = e.span.ctxt();
|
||||
walk_to_expr_usage(cx, e, &mut |parent, child_id| {
|
||||
// LocalTableInContext returns the wrong lifetime, so go use `expr_adjustments` instead.
|
||||
walk_to_expr_usage(cx, e, &mut |parent_id, parent, child_id| {
|
||||
if adjustments.is_empty()
|
||||
&& let Node::Expr(e) = cx.tcx.hir_node(child_id)
|
||||
{
|
||||
adjustments = cx.typeck_results().expr_adjustments(e);
|
||||
}
|
||||
match parent {
|
||||
Node::Local(l) if l.span.ctxt() == ctxt => Some(ExprUseCtxt {
|
||||
node: ExprUseNode::Local(l),
|
||||
adjustments,
|
||||
is_ty_unified,
|
||||
moved_before_use,
|
||||
}),
|
||||
if cx.tcx.hir().span(parent_id).ctxt() != ctxt {
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
if let Node::Expr(e) = parent {
|
||||
match e.kind {
|
||||
ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id != child_id => {
|
||||
is_ty_unified = true;
|
||||
moved_before_use = true;
|
||||
},
|
||||
ExprKind::Block(_, Some(_)) | ExprKind::Break(..) => {
|
||||
is_ty_unified = true;
|
||||
moved_before_use = true;
|
||||
},
|
||||
ExprKind::Block(..) => moved_before_use = true,
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
ControlFlow::Continue(())
|
||||
})?
|
||||
.continue_value()
|
||||
.map(|(use_node, child_id)| {
|
||||
let node = match use_node {
|
||||
Node::Local(l) => ExprUseNode::Local(l),
|
||||
Node::ExprField(field) => ExprUseNode::Field(field),
|
||||
|
||||
Node::Item(&Item {
|
||||
kind: ItemKind::Static(..) | ItemKind::Const(..),
|
||||
owner_id,
|
||||
span,
|
||||
..
|
||||
})
|
||||
| Node::TraitItem(&TraitItem {
|
||||
kind: TraitItemKind::Const(..),
|
||||
owner_id,
|
||||
span,
|
||||
..
|
||||
})
|
||||
| Node::ImplItem(&ImplItem {
|
||||
kind: ImplItemKind::Const(..),
|
||||
owner_id,
|
||||
span,
|
||||
..
|
||||
}) if span.ctxt() == ctxt => Some(ExprUseCtxt {
|
||||
node: ExprUseNode::ConstStatic(owner_id),
|
||||
adjustments,
|
||||
is_ty_unified,
|
||||
moved_before_use,
|
||||
}),
|
||||
}) => ExprUseNode::ConstStatic(owner_id),
|
||||
|
||||
Node::Item(&Item {
|
||||
kind: ItemKind::Fn(..),
|
||||
owner_id,
|
||||
span,
|
||||
..
|
||||
})
|
||||
| Node::TraitItem(&TraitItem {
|
||||
kind: TraitItemKind::Fn(..),
|
||||
owner_id,
|
||||
span,
|
||||
..
|
||||
})
|
||||
| Node::ImplItem(&ImplItem {
|
||||
kind: ImplItemKind::Fn(..),
|
||||
owner_id,
|
||||
span,
|
||||
..
|
||||
}) if span.ctxt() == ctxt => Some(ExprUseCtxt {
|
||||
node: ExprUseNode::Return(owner_id),
|
||||
adjustments,
|
||||
is_ty_unified,
|
||||
moved_before_use,
|
||||
}),
|
||||
}) => ExprUseNode::Return(owner_id),
|
||||
|
||||
Node::ExprField(field) if field.span.ctxt() == ctxt => Some(ExprUseCtxt {
|
||||
node: ExprUseNode::Field(field),
|
||||
adjustments,
|
||||
is_ty_unified,
|
||||
moved_before_use,
|
||||
}),
|
||||
|
||||
Node::Expr(parent) if parent.span.ctxt() == ctxt => match parent.kind {
|
||||
ExprKind::Ret(_) => Some(ExprUseCtxt {
|
||||
node: ExprUseNode::Return(OwnerId {
|
||||
def_id: cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()),
|
||||
}),
|
||||
adjustments,
|
||||
is_ty_unified,
|
||||
moved_before_use,
|
||||
Node::Expr(use_expr) => match use_expr.kind {
|
||||
ExprKind::Ret(_) => ExprUseNode::Return(OwnerId {
|
||||
def_id: cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()),
|
||||
}),
|
||||
ExprKind::Closure(closure) => Some(ExprUseCtxt {
|
||||
node: ExprUseNode::Return(OwnerId { def_id: closure.def_id }),
|
||||
adjustments,
|
||||
is_ty_unified,
|
||||
moved_before_use,
|
||||
}),
|
||||
ExprKind::Call(func, args) => Some(ExprUseCtxt {
|
||||
node: match args.iter().position(|arg| arg.hir_id == child_id) {
|
||||
Some(i) => ExprUseNode::FnArg(func, i),
|
||||
None => ExprUseNode::Callee,
|
||||
},
|
||||
adjustments,
|
||||
is_ty_unified,
|
||||
moved_before_use,
|
||||
}),
|
||||
ExprKind::MethodCall(name, _, args, _) => Some(ExprUseCtxt {
|
||||
node: ExprUseNode::MethodArg(
|
||||
parent.hir_id,
|
||||
name.args,
|
||||
args.iter().position(|arg| arg.hir_id == child_id).map_or(0, |i| i + 1),
|
||||
),
|
||||
adjustments,
|
||||
is_ty_unified,
|
||||
moved_before_use,
|
||||
}),
|
||||
ExprKind::Field(child, name) if child.hir_id == e.hir_id => Some(ExprUseCtxt {
|
||||
node: ExprUseNode::FieldAccess(name),
|
||||
adjustments,
|
||||
is_ty_unified,
|
||||
moved_before_use,
|
||||
}),
|
||||
ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id != child_id => {
|
||||
is_ty_unified = true;
|
||||
moved_before_use = true;
|
||||
None
|
||||
ExprKind::Closure(closure) => ExprUseNode::Return(OwnerId { def_id: closure.def_id }),
|
||||
ExprKind::Call(func, args) => match args.iter().position(|arg| arg.hir_id == child_id) {
|
||||
Some(i) => ExprUseNode::FnArg(func, i),
|
||||
None => ExprUseNode::Callee,
|
||||
},
|
||||
ExprKind::Block(_, Some(_)) | ExprKind::Break(..) => {
|
||||
is_ty_unified = true;
|
||||
moved_before_use = true;
|
||||
None
|
||||
},
|
||||
ExprKind::Block(..) => {
|
||||
moved_before_use = true;
|
||||
None
|
||||
},
|
||||
_ => None,
|
||||
ExprKind::MethodCall(name, _, args, _) => ExprUseNode::MethodArg(
|
||||
use_expr.hir_id,
|
||||
name.args,
|
||||
args.iter().position(|arg| arg.hir_id == child_id).map_or(0, |i| i + 1),
|
||||
),
|
||||
ExprKind::Field(child, name) if child.hir_id == e.hir_id => ExprUseNode::FieldAccess(name),
|
||||
_ => ExprUseNode::Expr,
|
||||
},
|
||||
_ => None,
|
||||
_ => ExprUseNode::Other,
|
||||
};
|
||||
ExprUseCtxt {
|
||||
node,
|
||||
adjustments,
|
||||
is_ty_unified,
|
||||
moved_before_use,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -3270,3 +3203,131 @@ pub fn is_never_expr<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> Option<
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Produces a path from a local caller to the type of the called method. Suitable for user
|
||||
/// output/suggestions.
|
||||
///
|
||||
/// Returned path can be either absolute (for methods defined non-locally), or relative (for local
|
||||
/// methods).
|
||||
pub fn get_path_from_caller_to_method_type<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
from: LocalDefId,
|
||||
method: DefId,
|
||||
args: GenericArgsRef<'tcx>,
|
||||
) -> String {
|
||||
let assoc_item = tcx.associated_item(method);
|
||||
let def_id = assoc_item.container_id(tcx);
|
||||
match assoc_item.container {
|
||||
rustc_ty::TraitContainer => get_path_to_callee(tcx, from, def_id),
|
||||
rustc_ty::ImplContainer => {
|
||||
let ty = tcx.type_of(def_id).instantiate_identity();
|
||||
get_path_to_ty(tcx, from, ty, args)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn get_path_to_ty<'tcx>(tcx: TyCtxt<'tcx>, from: LocalDefId, ty: Ty<'tcx>, args: GenericArgsRef<'tcx>) -> String {
|
||||
match ty.kind() {
|
||||
rustc_ty::Adt(adt, _) => get_path_to_callee(tcx, from, adt.did()),
|
||||
// TODO these types need to be recursively resolved as well
|
||||
rustc_ty::Array(..)
|
||||
| rustc_ty::Dynamic(..)
|
||||
| rustc_ty::Never
|
||||
| rustc_ty::RawPtr(_)
|
||||
| rustc_ty::Ref(..)
|
||||
| rustc_ty::Slice(_)
|
||||
| rustc_ty::Tuple(_) => format!("<{}>", EarlyBinder::bind(ty).instantiate(tcx, args)),
|
||||
_ => ty.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Produce a path from some local caller to the callee. Suitable for user output/suggestions.
|
||||
fn get_path_to_callee(tcx: TyCtxt<'_>, from: LocalDefId, callee: DefId) -> String {
|
||||
// only search for a relative path if the call is fully local
|
||||
if callee.is_local() {
|
||||
let callee_path = tcx.def_path(callee);
|
||||
let caller_path = tcx.def_path(from.to_def_id());
|
||||
maybe_get_relative_path(&caller_path, &callee_path, 2)
|
||||
} else {
|
||||
tcx.def_path_str(callee)
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to produce a relative path from `from` to `to`; if such a path would contain more than
|
||||
/// `max_super` `super` items, produces an absolute path instead. Both `from` and `to` should be in
|
||||
/// the local crate.
|
||||
///
|
||||
/// Suitable for user output/suggestions.
|
||||
///
|
||||
/// This ignores use items, and assumes that the target path is visible from the source
|
||||
/// path (which _should_ be a reasonable assumption since we in order to be able to use an object of
|
||||
/// certain type T, T is required to be visible).
|
||||
///
|
||||
/// TODO make use of `use` items. Maybe we should have something more sophisticated like
|
||||
/// rust-analyzer does? <https://docs.rs/ra_ap_hir_def/0.0.169/src/ra_ap_hir_def/find_path.rs.html#19-27>
|
||||
fn maybe_get_relative_path(from: &DefPath, to: &DefPath, max_super: usize) -> String {
|
||||
use itertools::EitherOrBoth::{Both, Left, Right};
|
||||
|
||||
// 1. skip the segments common for both paths (regardless of their type)
|
||||
let unique_parts = to
|
||||
.data
|
||||
.iter()
|
||||
.zip_longest(from.data.iter())
|
||||
.skip_while(|el| matches!(el, Both(l, r) if l == r))
|
||||
.map(|el| match el {
|
||||
Both(l, r) => Both(l.data, r.data),
|
||||
Left(l) => Left(l.data),
|
||||
Right(r) => Right(r.data),
|
||||
});
|
||||
|
||||
// 2. for the remaning segments, construct relative path using only mod names and `super`
|
||||
let mut go_up_by = 0;
|
||||
let mut path = Vec::new();
|
||||
for el in unique_parts {
|
||||
match el {
|
||||
Both(l, r) => {
|
||||
// consider:
|
||||
// a::b::sym:: :: refers to
|
||||
// c::d::e ::f::sym
|
||||
// result should be super::super::c::d::e::f
|
||||
//
|
||||
// alternatively:
|
||||
// a::b::c ::d::sym refers to
|
||||
// e::f::sym:: ::
|
||||
// result should be super::super::super::super::e::f
|
||||
if let DefPathData::TypeNs(s) = l {
|
||||
path.push(s.to_string());
|
||||
}
|
||||
if let DefPathData::TypeNs(_) = r {
|
||||
go_up_by += 1;
|
||||
}
|
||||
},
|
||||
// consider:
|
||||
// a::b::sym:: :: refers to
|
||||
// c::d::e ::f::sym
|
||||
// when looking at `f`
|
||||
Left(DefPathData::TypeNs(sym)) => path.push(sym.to_string()),
|
||||
// consider:
|
||||
// a::b::c ::d::sym refers to
|
||||
// e::f::sym:: ::
|
||||
// when looking at `d`
|
||||
Right(DefPathData::TypeNs(_)) => go_up_by += 1,
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
if go_up_by > max_super {
|
||||
// `super` chain would be too long, just use the absolute path instead
|
||||
once(String::from("crate"))
|
||||
.chain(to.data.iter().filter_map(|el| {
|
||||
if let DefPathData::TypeNs(sym) = el.data {
|
||||
Some(sym.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}))
|
||||
.join("::")
|
||||
} else {
|
||||
repeat(String::from("super")).take(go_up_by).chain(path).join("::")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1000,35 +1000,6 @@ pub fn adt_and_variant_of_res<'tcx>(cx: &LateContext<'tcx>, res: Res) -> Option<
|
|||
}
|
||||
}
|
||||
|
||||
/// Checks if the type is a type parameter implementing `FnOnce`, but not `FnMut`.
|
||||
pub fn ty_is_fn_once_param<'tcx>(tcx: TyCtxt<'_>, ty: Ty<'tcx>, predicates: &'tcx [ty::Clause<'_>]) -> bool {
|
||||
let ty::Param(ty) = *ty.kind() else {
|
||||
return false;
|
||||
};
|
||||
let lang = tcx.lang_items();
|
||||
let (Some(fn_once_id), Some(fn_mut_id), Some(fn_id)) = (lang.fn_once_trait(), lang.fn_mut_trait(), lang.fn_trait())
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
predicates
|
||||
.iter()
|
||||
.try_fold(false, |found, p| {
|
||||
if let ty::ClauseKind::Trait(p) = p.kind().skip_binder()
|
||||
&& let ty::Param(self_ty) = p.trait_ref.self_ty().kind()
|
||||
&& ty.index == self_ty.index
|
||||
{
|
||||
// This should use `super_traits_of`, but that's a private function.
|
||||
if p.trait_ref.def_id == fn_once_id {
|
||||
return Some(true);
|
||||
} else if p.trait_ref.def_id == fn_mut_id || p.trait_ref.def_id == fn_id {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(found)
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Comes up with an "at least" guesstimate for the type's size, not taking into
|
||||
/// account the layout of type parameters.
|
||||
pub fn approx_ty_size<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> u64 {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "declare_clippy_lint"
|
||||
version = "0.1.77"
|
||||
version = "0.1.78"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
[toolchain]
|
||||
channel = "nightly-2024-01-25"
|
||||
channel = "nightly-2024-02-08"
|
||||
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
|
||||
|
|
|
@ -24,9 +24,11 @@ use rustc_session::EarlyDiagCtxt;
|
|||
use rustc_span::symbol::Symbol;
|
||||
|
||||
use std::env;
|
||||
use std::fs::read_to_string;
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
use std::process::exit;
|
||||
use std::string::ToString;
|
||||
|
||||
use anstream::println;
|
||||
|
||||
|
@ -190,12 +192,31 @@ pub fn main() {
|
|||
|
||||
exit(rustc_driver::catch_with_exit_code(move || {
|
||||
let mut orig_args: Vec<String> = env::args().collect();
|
||||
let has_sysroot_arg = arg_value(&orig_args, "--sysroot", |_| true).is_some();
|
||||
|
||||
let has_sysroot_arg = |args: &mut [String]| -> bool {
|
||||
if arg_value(args, "--sysroot", |_| true).is_some() {
|
||||
return true;
|
||||
}
|
||||
// https://doc.rust-lang.org/rustc/command-line-arguments.html#path-load-command-line-flags-from-a-path
|
||||
// Beside checking for existence of `--sysroot` on the command line, we need to
|
||||
// check for the arg files that are prefixed with @ as well to be consistent with rustc
|
||||
for arg in args.iter() {
|
||||
if let Some(arg_file_path) = arg.strip_prefix('@') {
|
||||
if let Ok(arg_file) = read_to_string(arg_file_path) {
|
||||
let split_arg_file: Vec<String> = arg_file.lines().map(ToString::to_string).collect();
|
||||
if arg_value(&split_arg_file, "--sysroot", |_| true).is_some() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
};
|
||||
|
||||
let sys_root_env = std::env::var("SYSROOT").ok();
|
||||
let pass_sysroot_env_if_given = |args: &mut Vec<String>, sys_root_env| {
|
||||
if let Some(sys_root) = sys_root_env {
|
||||
if !has_sysroot_arg {
|
||||
if !has_sysroot_arg(args) {
|
||||
args.extend(vec!["--sysroot".into(), sys_root]);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
error: lint group `rust_2018_idioms` has the same priority (0) as a lint
|
||||
--> Cargo.toml:7:1
|
||||
|
|
||||
7 | rust_2018_idioms = "warn"
|
||||
| ^^^^^^^^^^^^^^^^ ------ has an implicit priority of 0
|
||||
8 | bare_trait_objects = "allow"
|
||||
| ------------------ has the same priority as this lint
|
||||
|
|
||||
= note: the order of the lints in the table is ignored by Cargo
|
||||
= note: `#[deny(clippy::lint_groups_priority)]` on by default
|
||||
help: to have lints override the group set `rust_2018_idioms` to a lower priority
|
||||
|
|
||||
7 | rust_2018_idioms = { level = "warn", priority = -1 }
|
||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
error: lint group `unused` has the same priority (0) as a lint
|
||||
--> Cargo.toml:10:1
|
||||
|
|
||||
10 | unused = { level = "deny" }
|
||||
| ^^^^^^ ------------------ has an implicit priority of 0
|
||||
11 | unused_braces = { level = "allow", priority = 1 }
|
||||
12 | unused_attributes = { level = "allow" }
|
||||
| ----------------- has the same priority as this lint
|
||||
|
|
||||
= note: the order of the lints in the table is ignored by Cargo
|
||||
help: to have lints override the group set `unused` to a lower priority
|
||||
|
|
||||
10 | unused = { level = "deny", priority = -1 }
|
||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
error: lint group `pedantic` has the same priority (-1) as a lint
|
||||
--> Cargo.toml:19:1
|
||||
|
|
||||
19 | pedantic = { level = "warn", priority = -1 }
|
||||
| ^^^^^^^^
|
||||
20 | similar_names = { level = "allow", priority = -1 }
|
||||
| ------------- has the same priority as this lint
|
||||
|
|
||||
= note: the order of the lints in the table is ignored by Cargo
|
||||
help: to have lints override the group set `pedantic` to a lower priority
|
||||
|
|
||||
19 | pedantic = { level = "warn", priority = -2 }
|
||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
error: could not compile `fail` (lib) due to 3 previous errors
|
|
@ -0,0 +1,20 @@
|
|||
[package]
|
||||
name = "fail"
|
||||
version = "0.1.0"
|
||||
publish = false
|
||||
|
||||
[lints.rust]
|
||||
rust_2018_idioms = "warn"
|
||||
bare_trait_objects = "allow"
|
||||
|
||||
unused = { level = "deny" }
|
||||
unused_braces = { level = "allow", priority = 1 }
|
||||
unused_attributes = { level = "allow" }
|
||||
|
||||
# `warnings` is not a group so the order it is passed does not matter
|
||||
warnings = "deny"
|
||||
deprecated = "allow"
|
||||
|
||||
[lints.clippy]
|
||||
pedantic = { level = "warn", priority = -1 }
|
||||
similar_names = { level = "allow", priority = -1 }
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "pass"
|
||||
version = "0.1.0"
|
||||
publish = false
|
||||
|
||||
[lints.clippy]
|
||||
pedantic = { level = "warn", priority = -1 }
|
||||
style = { level = "warn", priority = 1 }
|
||||
similar_names = "allow"
|
||||
dbg_macro = { level = "warn", priority = 2 }
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#![allow(clippy::redundant_clone, clippy::unnecessary_operation)]
|
||||
#![allow(clippy::redundant_clone, clippy::unnecessary_operation, clippy::incompatible_msrv)]
|
||||
#![warn(clippy::manual_non_exhaustive, clippy::borrow_as_ptr, clippy::manual_bits)]
|
||||
|
||||
use std::mem::{size_of, size_of_val};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#![allow(clippy::redundant_clone, clippy::unnecessary_operation)]
|
||||
#![allow(clippy::redundant_clone, clippy::unnecessary_operation, clippy::incompatible_msrv)]
|
||||
#![warn(clippy::manual_non_exhaustive, clippy::borrow_as_ptr, clippy::manual_bits)]
|
||||
|
||||
use std::mem::{size_of, size_of_val};
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
allow-comparison-to-zero = false
|
|
@ -0,0 +1,10 @@
|
|||
#![warn(clippy::modulo_arithmetic)]
|
||||
|
||||
fn main() {
|
||||
let a = -1;
|
||||
let b = 2;
|
||||
let c = a % b == 0;
|
||||
let c = a % b != 0;
|
||||
let c = 0 == a % b;
|
||||
let c = 0 != a % b;
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
error: you are using modulo operator on types that might have different signs
|
||||
--> $DIR/modulo_arithmetic.rs:6:13
|
||||
|
|
||||
LL | let c = a % b == 0;
|
||||
| ^^^^^
|
||||
|
|
||||
= note: double check for expected result especially when interoperating with different languages
|
||||
= note: or consider using `rem_euclid` or similar function
|
||||
= note: `-D clippy::modulo-arithmetic` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::modulo_arithmetic)]`
|
||||
|
||||
error: you are using modulo operator on types that might have different signs
|
||||
--> $DIR/modulo_arithmetic.rs:7:13
|
||||
|
|
||||
LL | let c = a % b != 0;
|
||||
| ^^^^^
|
||||
|
|
||||
= note: double check for expected result especially when interoperating with different languages
|
||||
= note: or consider using `rem_euclid` or similar function
|
||||
|
||||
error: you are using modulo operator on types that might have different signs
|
||||
--> $DIR/modulo_arithmetic.rs:8:18
|
||||
|
|
||||
LL | let c = 0 == a % b;
|
||||
| ^^^^^
|
||||
|
|
||||
= note: double check for expected result especially when interoperating with different languages
|
||||
= note: or consider using `rem_euclid` or similar function
|
||||
|
||||
error: you are using modulo operator on types that might have different signs
|
||||
--> $DIR/modulo_arithmetic.rs:9:18
|
||||
|
|
||||
LL | let c = 0 != a % b;
|
||||
| ^^^^^
|
||||
|
|
||||
= note: double check for expected result especially when interoperating with different languages
|
||||
= note: or consider using `rem_euclid` or similar function
|
||||
|
||||
error: aborting due to 4 previous errors
|
||||
|
|
@ -3,6 +3,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect
|
|||
absolute-paths-max-segments
|
||||
accept-comment-above-attributes
|
||||
accept-comment-above-statement
|
||||
allow-comparison-to-zero
|
||||
allow-dbg-in-tests
|
||||
allow-expect-in-tests
|
||||
allow-mixed-uninlined-format-args
|
||||
|
@ -14,6 +15,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect
|
|||
allowed-duplicate-crates
|
||||
allowed-idents-below-min-chars
|
||||
allowed-scripts
|
||||
allowed-wildcard-imports
|
||||
arithmetic-side-effects-allowed
|
||||
arithmetic-side-effects-allowed-binary
|
||||
arithmetic-side-effects-allowed-unary
|
||||
|
@ -80,6 +82,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
|
|||
absolute-paths-max-segments
|
||||
accept-comment-above-attributes
|
||||
accept-comment-above-statement
|
||||
allow-comparison-to-zero
|
||||
allow-dbg-in-tests
|
||||
allow-expect-in-tests
|
||||
allow-mixed-uninlined-format-args
|
||||
|
@ -91,6 +94,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
|
|||
allowed-duplicate-crates
|
||||
allowed-idents-below-min-chars
|
||||
allowed-scripts
|
||||
allowed-wildcard-imports
|
||||
arithmetic-side-effects-allowed
|
||||
arithmetic-side-effects-allowed-binary
|
||||
arithmetic-side-effects-allowed-unary
|
||||
|
@ -157,6 +161,7 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni
|
|||
absolute-paths-max-segments
|
||||
accept-comment-above-attributes
|
||||
accept-comment-above-statement
|
||||
allow-comparison-to-zero
|
||||
allow-dbg-in-tests
|
||||
allow-expect-in-tests
|
||||
allow-mixed-uninlined-format-args
|
||||
|
@ -168,6 +173,7 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni
|
|||
allowed-duplicate-crates
|
||||
allowed-idents-below-min-chars
|
||||
allowed-scripts
|
||||
allowed-wildcard-imports
|
||||
arithmetic-side-effects-allowed
|
||||
arithmetic-side-effects-allowed-binary
|
||||
arithmetic-side-effects-allowed-unary
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
warn-on-all-wildcard-imports = true
|
||||
|
||||
# This should be ignored since `warn-on-all-wildcard-imports` has higher precedence
|
||||
allowed-wildcard-imports = ["utils"]
|
||||
|
|
|
@ -3,9 +3,28 @@
|
|||
mod prelude {
|
||||
pub const FOO: u8 = 1;
|
||||
}
|
||||
|
||||
mod utils {
|
||||
pub const BAR: u8 = 1;
|
||||
pub fn print() {}
|
||||
}
|
||||
|
||||
mod my_crate {
|
||||
pub mod utils {
|
||||
pub fn my_util_fn() {}
|
||||
}
|
||||
}
|
||||
|
||||
use utils::{BAR, print};
|
||||
//~^ ERROR: usage of wildcard import
|
||||
use my_crate::utils::my_util_fn;
|
||||
//~^ ERROR: usage of wildcard import
|
||||
use prelude::FOO;
|
||||
//~^ ERROR: usage of wildcard import
|
||||
|
||||
fn main() {
|
||||
let _ = FOO;
|
||||
let _ = BAR;
|
||||
print();
|
||||
my_util_fn();
|
||||
}
|
||||
|
|
|
@ -3,9 +3,28 @@
|
|||
mod prelude {
|
||||
pub const FOO: u8 = 1;
|
||||
}
|
||||
|
||||
mod utils {
|
||||
pub const BAR: u8 = 1;
|
||||
pub fn print() {}
|
||||
}
|
||||
|
||||
mod my_crate {
|
||||
pub mod utils {
|
||||
pub fn my_util_fn() {}
|
||||
}
|
||||
}
|
||||
|
||||
use utils::*;
|
||||
//~^ ERROR: usage of wildcard import
|
||||
use my_crate::utils::*;
|
||||
//~^ ERROR: usage of wildcard import
|
||||
use prelude::*;
|
||||
//~^ ERROR: usage of wildcard import
|
||||
|
||||
fn main() {
|
||||
let _ = FOO;
|
||||
let _ = BAR;
|
||||
print();
|
||||
my_util_fn();
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue