diff --git a/CHANGELOG.md b/CHANGELOG.md index c773cf15756b..0b0506ce1d5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6118,6 +6118,7 @@ Released 2018-09-13 [`expl_impl_clone_on_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#expl_impl_clone_on_copy [`explicit_auto_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_auto_deref [`explicit_counter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_counter_loop +[`explicit_default_arguments`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_default_arguments [`explicit_deref_methods`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_deref_methods [`explicit_into_iter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_into_iter_loop [`explicit_iter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_iter_loop diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 5563b8094f01..72a887f29d95 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -160,6 +160,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::exhaustive_items::EXHAUSTIVE_ENUMS_INFO, crate::exhaustive_items::EXHAUSTIVE_STRUCTS_INFO, crate::exit::EXIT_INFO, + crate::explicit_default_arguments::EXPLICIT_DEFAULT_ARGUMENTS_INFO, crate::explicit_write::EXPLICIT_WRITE_INFO, crate::extra_unused_type_parameters::EXTRA_UNUSED_TYPE_PARAMETERS_INFO, crate::fallible_impl_from::FALLIBLE_IMPL_FROM_INFO, diff --git a/clippy_lints/src/explicit_default_arguments.rs b/clippy_lints/src/explicit_default_arguments.rs new file mode 100644 index 000000000000..da2af9286f5b --- /dev/null +++ b/clippy_lints/src/explicit_default_arguments.rs @@ -0,0 +1,635 @@ +use std::iter; + +use clippy_utils::MaybePath; +use clippy_utils::diagnostics::span_lint_hir; +use clippy_utils::source::snippet; +use rustc_data_structures::fx::FxHashMap; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::{ + self as hir, AssocItemConstraint, AssocItemConstraintKind, EnumDef, FnDecl, FnPtrTy, FnRetTy, FnSig, GenericArgs, + GenericParam, GenericParamKind, Generics, Impl, ImplItemKind, Item, ItemKind, MutTy, OpaqueTy, OwnerId, Path, + PathSegment, QPath, Term, TraitItemKind, TyKind, Variant, +}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{ + self, AliasTy, ExistentialPredicate, ExistentialProjection, ExistentialTraitRef, GenericArg, GenericParamDef, + TraitPredicate, TraitRef, Ty, TyCtxt, +}; + +use rustc_session::declare_lint_pass; +use rustc_span::Ident; + +declare_clippy_lint! { + /// ### What it does + /// Checks for the usage of a generic argument when the type already defines a default. + /// + /// ### Why is this bad? + /// It is redundant and adds visual clutter. + /// + /// ### Example + /// ```no_run + /// type Result = core::result::Result; + /// fn foo() -> Result<()> { + /// Ok(()) + /// } + /// ``` + /// Use instead: + /// ```no_run + /// type Result = core::result::Result; + /// fn foo() -> Result { + /// Ok(()) + ///} + /// ``` + #[clippy::version = "1.90.0"] + pub EXPLICIT_DEFAULT_ARGUMENTS, + style, + "default lint description" +} + +declare_lint_pass!(ExplicitDefaultArguments => [EXPLICIT_DEFAULT_ARGUMENTS]); + +/// Map 1: Iterates through the aliased type's generic args and finds the defaults given by the type +/// alias definition. Returns a map of the index in the aliased ty's generics args to its found +/// default. +/// +/// Map 2: Iterates through the aliased type's generics args and finds the index of it in the +/// actual alias definition. Returns a map of the index in the aliased type's generics args to the +/// corresponding index in the alias definition's generics params. +fn match_generics<'tcx>( + cx: &LateContext<'tcx>, + aliased_ty_args: &[GenericArg<'tcx>], + alias_ty_params: &[GenericParamDef], +) -> (FxHashMap>, FxHashMap) { + aliased_ty_args + .iter() + .enumerate() + .filter_map(|(i, generic_arg)| { + generic_arg.as_type().and_then(|ty| { + if let ty::Param(param) = ty.kind() { + Some((i, param)) + } else { + None + } + }) + }) + .fold( + (FxHashMap::default(), FxHashMap::default()), + |(mut map1, mut map2), (i, param)| { + if let Some(alias_ty_param) = alias_ty_params.iter().find(|param_def| param_def.name == param.name) + && let Some(default_value) = alias_ty_param + .default_value(cx.tcx) + .and_then(|default_value| default_value.skip_binder().as_type()) + { + map1.insert(i, default_value); + map2.insert(i, alias_ty_param.index); + } + (map1, map2) + }, + ) +} + +// NOTE: this whole algorithm avoids using `lower_ty +fn check_alias_args<'tcx>(cx: &LateContext<'tcx>, resolved_ty: Ty<'tcx>, hir_ty: hir::Ty<'tcx>) { + println!("resolved alias (ty::Ty): {resolved_ty}"); + println!( + "instantiated alias (hir::Ty): {}", + snippet(&cx.tcx, hir_ty.span, "") + ); + let (alias_ty_params, aliased_ty_args, hir_ty_args) = { + let TyKind::Path( + qpath @ QPath::Resolved( + _, + Path { + segments: + [ + .., + PathSegment { + args: Some(hir_ty_generics), + .. + }, + ], + .. + }, + ), + ) = hir_ty.kind + else { + return; + }; + let Res::Def(DefKind::TyAlias, alias_def_id) = cx.qpath_res(&qpath, hir_ty.hir_id()) else { + // The ty doesn't refer to a type alias + return; + }; + let aliased_ty = cx.tcx.type_of(alias_def_id).skip_binder(); + println!("aliased ty: {aliased_ty}"); + let ty::Adt(_, aliased_ty_args) = aliased_ty.kind() else { + // The ty alias doesn't refer to an ADT + return; + }; + ( + &cx.tcx.generics_of(alias_def_id).own_params, + aliased_ty_args, + hir_ty_generics, + ) + }; + let ty::Adt(_, resolved_ty_args) = resolved_ty.kind() else { + return; + }; + let (defaults, aliased_to_alias) = match_generics(cx, aliased_ty_args.as_slice(), alias_ty_params.as_slice()); + + println!("map1: {defaults:?}"); + println!("map2: {aliased_to_alias:?}"); + + // TODO: this could probably be broken up into a function + for (i, generic_arg) in resolved_ty_args.iter().enumerate() { + // Was the default explicitly written, or was it there because just because it got resolved? + // If something was specified and the resolved form of the type alias had the default, + // then it is redundant + if let Some(redundant_ty) = aliased_to_alias.get(&i).and_then(|i| hir_ty_args.args.get(*i as usize)) + && defaults.get(&i).copied() == generic_arg.as_type() + { + // TODO: show a hint + span_lint_hir( + &cx, + EXPLICIT_DEFAULT_ARGUMENTS, + redundant_ty.hir_id(), + redundant_ty.span(), + "redudant usage of default argument", + ); + println!("\tIt was there! `{}`", snippet(&cx.tcx, redundant_ty.span(), "")); + } else { + // println!( + // "&ty ({ty}) == default_arg_val ({default_arg_val}) = {}", + // &ty == default_arg_val + // ); + println!("\tIt was **not** there."); + } + } + + return; +} + +type TyPair<'a> = (Ty<'a>, hir::Ty<'a>); + +fn get_tys_fn_sig<'tcx>( + tcx: TyCtxt<'tcx>, + sig: FnSig<'tcx>, + item_owner_id: OwnerId, +) -> impl Iterator> + 'tcx { + // Assumes inputs are in the same order in `rustc_middle` and the hir. + + let poly_fn_sig = tcx.fn_sig(item_owner_id).skip_binder(); + + let output_ty = poly_fn_sig.output().skip_binder(); + let output = if let FnRetTy::Return(output_hir_ty) = sig.decl.output { + vec![(output_ty, *output_hir_ty)] + } else { + Vec::new() + }; + let inputs_ty = poly_fn_sig.inputs().skip_binder(); + let inputs_hir_tys = sig.decl.inputs; + inputs_ty + .iter() + .copied() + .zip(inputs_hir_tys.iter().copied()) + .chain(output) +} +/// Get all types in the the generics. +/// Limitation: this does not look at generic predicates, such as the where clause, due to the added +/// complexity. This could change in the future. +fn get_tys_from_generics<'tcx>( + tcx: TyCtxt<'tcx>, + generics: &Generics<'tcx>, + item_owner_id: OwnerId, +) -> impl Iterator> + 'tcx { + // Assumes the generics are the same order in `rustc_middle` and the hir. + let default_tys = tcx + .generics_of(item_owner_id) + .own_params + .iter() + .filter_map(move |param| { + param + .default_value(tcx) + .and_then(|default_value| default_value.skip_binder().as_type()) + }) + .zip( + generics + .params + .iter() + .filter_map(|GenericParam { kind, .. }| match kind { + GenericParamKind::Type { + default: Some(default), .. + } => Some(**default), + GenericParamKind::Const { ty, .. } => Some(**ty), + _ => None, + }), + ); + // Might be a good idea to look at + // https://doc.rust-lang.org/nightly/nightly-rustc/src/rustc_hir_analysis/collect/predicates_of.rs.html + // if checking predicates is ever implemented. The + // [`ParamEnv`](https://rustc-dev-guide.rust-lang.org/typing_parameter_envs.html) is where the predicates would be + // found in the `rustc_middle` level. + + default_tys +} + +/// Helper function to... +fn match_trait_ref_constraint_idents<'tcx, T: Iterator>>( + refs: T, + ident_map: FxHashMap, impl Iterator> + Clone)>, +) -> Vec> { + refs.filter_map(|trait_ref| { + if let hir::TraitRef { + path: + Path { + segments: + [ + .., + PathSegment { + args: Some(GenericArgs { constraints, .. }), + .. + }, + ], + .. + }, + .. + } = trait_ref + { + Some( + constraints + .iter() + .filter_map( + |AssocItemConstraint { + ident, + kind, + gen_args: hir_gen_args, + .. + }| { + if let AssocItemConstraintKind::Equality { term: Term::Ty(hir_ty) } = kind { + ident_map.get(ident).map(|(ty, gen_args)| { + iter::once((*ty, **hir_ty)).chain(gen_args.clone().into_iter().zip( + hir_gen_args.args.iter().filter_map(|arg| { + if let hir::GenericArg::Type(ty) = arg { + Some(*ty.as_unambig_ty()) + } else { + None + } + }), + )) + }) + } else { + None + } + }, + ) + .flatten(), + ) + } else { + None + } + }) + .flatten() + .collect() +} + +fn walk_ty_recursive<'tcx>( + tcx: TyCtxt<'tcx>, + ty: Ty<'tcx>, + hir_ty: hir::Ty<'tcx>, +) -> Box> + 'tcx> { + let generic_arg_to_tys = |args: &GenericArgs<'tcx>| -> Box>> { + Box::new(args.args.iter().filter_map(|arg| match arg { + rustc_hir::GenericArg::Type(ty) => Some(*ty.as_unambig_ty()), + _ => None, + })) + }; + let trait_ref_args = |trait_ref: &hir::TraitRef<'tcx>| -> Box>> { + if let hir::TraitRef { + path: + Path { + segments: + [ + .., + PathSegment { + args: Some(GenericArgs { args, .. }), + .. + }, + ], + .. + }, + .. + } = trait_ref + { + Box::new(args.iter().filter_map(|arg| { + if let hir::GenericArg::Type(ty) = arg { + Some(*ty.as_unambig_ty()) + } else { + None + } + })) + } else { + Box::new(iter::empty()) + } + }; + let result: Box>> = match (ty.kind(), hir_ty.kind) { + ( + ty::Adt(_, generics), + TyKind::Path(QPath::Resolved( + None, + Path { + segments: + [ + .., + PathSegment { + args: Some(generics_hir), + .. + }, + ], + .. + }, + )), + ) => Box::new( + generics + .iter() + .filter_map(|arg| arg.as_type()) + .zip(generic_arg_to_tys(*generics_hir)), + ), + // FIXME: if check_expr doesn't look at const args, this needs to change. Likely will need to change + // check_item too + (ty::Array(ty, _), TyKind::Array(hir_ty, _)) + | (ty::Pat(ty, _), TyKind::Pat(hir_ty, _)) + | (ty::Slice(ty), TyKind::Slice(hir_ty)) + | (ty::RawPtr(ty, _), TyKind::Ptr(MutTy { ty: hir_ty, .. })) + | (ty::Ref(_, ty, _), TyKind::Ref(_, MutTy { ty: hir_ty, .. })) => Box::new(iter::once((*ty, *hir_ty))), + ( + ty::FnPtr(tys, _), + TyKind::FnPtr(FnPtrTy { + decl: + FnDecl { + inputs: inputs_hir, + output: output_hir, + .. + }, + .. + }), + ) => { + let tys = tys.skip_binder(); + let iter = tys.inputs().iter().copied().zip(inputs_hir.iter().copied()); + if let FnRetTy::Return(hir_ty) = output_hir { + Box::new(iter.chain(iter::once((tys.output(), **hir_ty)))) + } else { + Box::new(iter) + } + }, + (ty::Dynamic(predicates, _, _), TyKind::TraitObject(hir_predicates, _)) => { + // GATs are ignored as they are not object safe. + + // Assumes that generics in `rustc_middle` are in the same order as the hir. + let trait_generics = predicates + .iter() + .filter_map(move |predicate| { + if let ExistentialPredicate::Trait(ExistentialTraitRef { args, .. }) = predicate.skip_binder() { + Some(args.iter().filter_map(|arg| arg.as_type())) + } else { + None + } + }) + .flatten() + .zip( + hir_predicates + .iter() + .map(|poly_trait_ref| &poly_trait_ref.trait_ref) + .flat_map(trait_ref_args), + ); + let ident_map = predicates + .iter() + .filter_map(move |predicate| { + if let ExistentialPredicate::Projection(ExistentialProjection { def_id, term, .. }) = + predicate.skip_binder() + && let Some(ty) = term.as_type() + { + Some((tcx.item_ident(def_id), (ty, [].iter().copied()))) + } else { + None + } + }) + .collect(); + // The equality constaints will not have the same order, so this will match the identifiers of the + // associated item. + let trait_preds = match_trait_ref_constraint_idents( + hir_predicates.iter().map(|poly_trait_ref| &poly_trait_ref.trait_ref), + ident_map, + ); + + Box::new(trait_generics.chain(trait_preds.into_iter())) + }, + ( + ty::Alias(ty::Projection, AliasTy { args, .. }), + TyKind::Path(QPath::TypeRelative(hir_ty, PathSegment { args: hir_gat_args, .. })), + ) => { + let hir_gat_args_iter = hir_gat_args.map_or_else(|| Box::new(iter::empty()), generic_arg_to_tys); + Box::new( + args.iter() + .flat_map(|arg| arg.as_type()) + .zip(iter::once(*hir_ty).chain(hir_gat_args_iter)), + ) + }, + ( + ty::Alias(ty::Projection, AliasTy { args, .. }), + TyKind::Path(QPath::Resolved( + Some(hir_ty), + Path { + segments: + [ + PathSegment { + args: hir_trait_args, .. + }, + .., + PathSegment { args: hir_gat_args, .. }, + ], + .. + }, + )), + ) => { + // Assumes both will have the same order in `rustc_middle` and the hir + let hir_trait_args_iter_ty = hir_trait_args.map_or_else(|| Box::new(iter::empty()), generic_arg_to_tys); + let hir_gat_args_iter_ty = hir_gat_args.map_or_else(|| Box::new(iter::empty()), generic_arg_to_tys); + Box::new( + args.iter().flat_map(|arg| arg.as_type()).zip( + iter::once(*hir_ty) + .chain(hir_trait_args_iter_ty) + .chain(hir_gat_args_iter_ty), + ), + ) + }, + ( + // `args` doesn't seem to have anything useful, not 100% sure. + ty::Alias(ty::Opaque, AliasTy { args: _, def_id, .. }), + TyKind::OpaqueDef(OpaqueTy { + bounds: [hir_bounds @ ..], + .. + }), + ) + | (ty::Alias(ty::Opaque, AliasTy { args: _, def_id, .. }), TyKind::TraitAscription(hir_bounds)) => { + let bounds = tcx.explicit_item_bounds(def_id).skip_binder(); + // Assumes that the order of the traits are as written and the generic args as well + let trait_bounds_args = bounds + .iter() + .filter_map(move |bound| { + if let Some(TraitPredicate { + trait_ref: TraitRef { def_id, args, .. }, + .. + }) = bound.0.as_trait_clause().map(|binder| binder.skip_binder()) + { + // If predicates are ever checked, this part could use some love. + Some( + tcx.generics_of(def_id) + .own_args_no_defaults(tcx, args) + .iter() + .filter_map(|arg| arg.as_type()), + ) + } else { + None + } + }) + .flatten(); + + let ident_map = bounds + .iter() + .filter_map(|(clause, _)| clause.as_projection_clause()) + .filter_map(|predicate| { + if let Some(ty) = predicate.term().skip_binder().as_type() { + println!("AliasTerm term: {:?}", ty,); + Some(( + tcx.item_ident(predicate.skip_binder().def_id()), + ( + ty, + predicate + .skip_binder() + .projection_term + .own_args(tcx) + .iter() + .filter_map(|arg| arg.as_type()), + ), + )) + } else { + None + } + }) + .collect(); + + let trait_preds = + match_trait_ref_constraint_idents(hir_bounds.iter().filter_map(|bound| bound.trait_ref()), ident_map); + Box::new( + trait_bounds_args + .zip( + hir_bounds + .iter() + .filter_map(|bound| bound.trait_ref()) + .flat_map(trait_ref_args), + ) + .chain(trait_preds), + ) + }, + + (ty::Tuple(tys), TyKind::Tup(hir_tys)) => Box::new(tys.iter().zip(hir_tys.iter().copied())), + + _ => Box::new(iter::empty()), + }; + Box::new( + result + .flat_map(move |(ty, hir_ty)| walk_ty_recursive(tcx, ty, hir_ty)) + .chain(iter::once((ty, hir_ty))), + ) +} + +#[allow(unused)] +impl<'tcx> LateLintPass<'tcx> for ExplicitDefaultArguments { + // TODO: check expressions for turbofish, casts, constructor params and type qualified paths + // TODO: check let statements + // TODO: walk through types recursively, `Ty` and `hir::Ty` in lockstep. Check generic args and + // inner types if it's a tuple or something like that + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + let tys_to_check: Vec> = { + let mut tys = Vec::new(); + if let Some(ident) = item.kind.ident() { + println!("\nchecking item `{ident}`"); + } else { + println!("\nchecking item "); + } + let other_tys: &mut dyn Iterator> = match item.kind { + ItemKind::Const(_, _, ty, _) | ItemKind::TyAlias(_, _, ty) => { + &mut iter::once((cx.tcx.type_of(item.owner_id).skip_binder(), *ty)) + }, + ItemKind::Fn { sig, .. } => &mut get_tys_fn_sig(cx.tcx, sig, item.owner_id), + ItemKind::Enum(_, _, EnumDef { variants }) => { + &mut variants.iter().flat_map(|Variant { data: variant_data, .. }| { + variant_data + .fields() + .iter() + .map(|field| cx.tcx.type_of(field.def_id).skip_binder()) + .zip(variant_data.fields().iter().map(|field| *field.ty)) + }) + }, + ItemKind::Struct(_, _, variant_data) | ItemKind::Union(_, _, variant_data) => &mut variant_data + .fields() + .iter() + .map(|field| cx.tcx.type_of(field.def_id).skip_binder()) + .zip(variant_data.fields().iter().map(|field| *field.ty)), + ItemKind::Trait(_, _, _, _, _, _, trait_items) => &mut trait_items + .iter() + .map(|item| cx.tcx.hir_trait_item(*item)) + .flat_map(|trait_item| { + let tys: Option>>> = match trait_item.kind { + TraitItemKind::Fn(sig, _) => { + Some(Box::new(get_tys_fn_sig(cx.tcx, sig, trait_item.owner_id).chain( + get_tys_from_generics(cx.tcx, trait_item.generics, trait_item.owner_id), + ))) + }, + TraitItemKind::Const(ty, _) | TraitItemKind::Type(_, Some(ty)) => Some(Box::new( + iter::once((cx.tcx.type_of(trait_item.owner_id).skip_binder(), *ty)), + )), + _ => None, + }; + tys + }) + .flatten(), + // TODO: ItemKind::TraitAlias when it stabilizes + ItemKind::Impl(Impl { items, self_ty, .. }) => &mut items + .iter() + .map(|item| cx.tcx.hir_impl_item(*item)) + .flat_map(|impl_item| { + let tys: Option>>> = match impl_item.kind { + ImplItemKind::Fn(sig, _) => { + Some(Box::new(get_tys_fn_sig(cx.tcx, sig, impl_item.owner_id).chain( + get_tys_from_generics(cx.tcx, impl_item.generics, impl_item.owner_id), + ))) + }, + ImplItemKind::Const(ty, _) | ImplItemKind::Type(ty) => Some(Box::new(iter::once(( + cx.tcx.type_of(impl_item.owner_id).skip_binder(), + *ty, + )))), + _ => None, + }; + tys + }) + .flatten() + .chain(iter::once((cx.tcx.type_of(item.owner_id).skip_binder(), *self_ty))), + _ => return, + }; + + if let Some(generics) = item.kind.generics() { + tys.extend(get_tys_from_generics(cx.tcx, generics, item.owner_id)); + } + tys.extend(other_tys); + tys + }; + + for (resolved_ty, hir_ty) in tys_to_check + .iter() + .flat_map(|(ty, hir_ty)| walk_ty_recursive(cx.tcx, *ty, *hir_ty)) + { + println!("CHECKING `{}`/`{}`", resolved_ty, snippet(cx, hir_ty.span, "")); + check_alias_args(cx, resolved_ty, hir_ty); + } + } +} diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index c56fa257b068..70233f85632c 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -138,6 +138,7 @@ mod excessive_bools; mod excessive_nesting; mod exhaustive_items; mod exit; +mod explicit_default_arguments; mod explicit_write; mod extra_unused_type_parameters; mod fallible_impl_from; @@ -831,5 +832,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(infallible_try_from::InfallibleTryFrom)); store.register_late_pass(|_| Box::new(coerce_container_to_any::CoerceContainerToAny)); store.register_late_pass(|_| Box::new(toplevel_ref_arg::ToplevelRefArg)); + store.register_late_pass(|_| Box::new(explicit_default_arguments::ExplicitDefaultArguments)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/tests/ui/explicit_default_arguments.fixed b/tests/ui/explicit_default_arguments.fixed new file mode 100644 index 000000000000..3fa84e9cc871 --- /dev/null +++ b/tests/ui/explicit_default_arguments.fixed @@ -0,0 +1,143 @@ +#![warn(clippy::explicit_default_arguments)] + +use std::marker::PhantomData; + +// Test types +struct DatabaseError; +struct NetworkError; +struct ComplexStruct(PhantomData<(A, B, C, D)>); + +// Type aliases with defaults +type DbResult = Result; +type NetResult = Result; +type Optional = Option; +type ComplexThing = ComplexStruct; +type BoxedDefault = Box; + +// Module to test scoping +mod outer { + pub type NestedResult = Result; +} + +// Const declarations +const DB_CONST: DbResult = Ok(()); +//~^ explicit_default_arguments +const DB_OK: DbResult = Ok(()); +const NET_CONST: NetResult = Ok(""); +//~^ explicit_default_arguments +const NET_OK: NetResult = Ok(""); + +// Static declarations +static STATIC_DB: DbResult = Ok(()); +//~^ explicit_default_arguments +static STATIC_NET: NetResult = Ok(""); +static OPTIONAL: Optional = Some(42); +//~^ explicit_default_arguments +static CUSTOM_OPT: Optional = Some(1.5); + +// Associated types in traits +trait ExampleTrait { + type AssocDb; + type AssocNet; + + fn method() -> DbResult; + //~^ explicit_default_arguments +} + +impl ExampleTrait for () { + type AssocDb = DbResult; + //~^ explicit_default_arguments + type AssocNet = NetResult; + + fn method() -> DbResult { + //~^ explicit_default_arguments + Ok(()) + } +} + +// Function signatures +fn db_function(arg: DbResult) -> DbResult { + //~^ explicit_default_arguments + //~| explicit_default_arguments + arg +} + +fn net_function(arg: NetResult) -> NetResult { + arg +} + +// Struct fields +struct User { + db_field: DbResult, + //~^ explicit_default_arguments + net_field: NetResult, +} + +// Tuple struct +struct Response( + DbResult, + //~^ explicit_default_arguments + NetResult, +); + +// Enum variants +enum ApiResponse { + Success(DbResult), + //~^ explicit_default_arguments + Failure(NetResult), +} + +// Union fields +union DataHolder { + db: std::mem::ManuallyDrop, + //~^ explicit_default_arguments + net: std::mem::ManuallyDrop, +} + +// Type aliases +type DbAlias = DbResult; +//~^ explicit_default_arguments +type NetAlias = NetResult; + +// Complex type scenarios +static COMPLEX_FULL: ComplexThing = ComplexStruct(PhantomData); +//~^ explicit_default_arguments +static COMPLEX_PARTIAL: ComplexThing = ComplexStruct(PhantomData); + +// Nested module type +static NESTED_RESULT: outer::NestedResult = Ok(42); +//~^ explicit_default_arguments + +// Trait implementation with generics +impl ExampleTrait for ComplexThing { + type AssocDb = DbResult; + //~^ explicit_default_arguments + type AssocNet = NetResult; + + fn method() -> DbResult { + //~^ explicit_default_arguments + Ok(()) + } +} + +fn main() { + // Local variables + let a: DbResult = Ok(()); + //~^ explicit_default_arguments + let b: NetResult = Ok(""); + + // Function pointers + let f: fn(DbResult) -> DbResult = db_function; + //~^ explicit_default_arguments + //~| explicit_default_arguments + + // Expressions with std types + let s = String::new(); + let v: Vec = vec![s.clone()]; + let _o: Option> = Some(v); + + // Box with default + let boxed_int: BoxedDefault = Box::new(0); + //~^ explicit_default_arguments + let boxed_float: BoxedDefault = Box::new(0.0); +} diff --git a/tests/ui/explicit_default_arguments.rs b/tests/ui/explicit_default_arguments.rs new file mode 100644 index 000000000000..f670ed248225 --- /dev/null +++ b/tests/ui/explicit_default_arguments.rs @@ -0,0 +1,179 @@ +// These names are confusing and unnecessary, I should probably change them +#![warn(clippy::explicit_default_arguments)] + +use std::marker::PhantomData; + +// Test types +struct DatabaseError; +struct NetworkError; +struct ComplexStruct(PhantomData<(A, B, X, C, D)>); + +// Type aliases with defaults +type DbResult = Result; +type NetResult = Result; +type Optional = Option; +type ComplexThing = ComplexStruct; +type BoxedDefault = Box; + +// Module to test scoping +mod outer { + pub type NestedResult = Result; +} + +// Const declarations +const DB_CONST: DbResult<()> = Ok(()); +const DB_OK: DbResult = Ok(()); +const NET_CONST: NetResult<&str> = Ok(""); +const NET_OK: NetResult = Ok(""); + +// Static declarations +static STATIC_DB: DbResult<()> = Ok(()); +static STATIC_NET: NetResult = Ok(""); +static OPTIONAL: Optional = Some(42); +static CUSTOM_OPT: Optional = Some(1.5); + +// Associated types in traits +trait ExampleTrait1 { + type AssocTy; + type AssocNet; + + fn method(&self) -> DbResult<()>; +} + +trait ExampleTrait2 { + type AssocTy1; + type AssocTy2; +} + +impl ExampleTrait1 for () { + type AssocTy = DbResult<()>; + type AssocNet = NetResult; + + fn method(&self) -> DbResult<()> { + Ok(()) + } +} + +impl ExampleTrait2> for () { + type AssocTy1 = DbResult>; + type AssocTy2 = DbResult<()>; +} + +// Function signatures +fn db_function(arg: DbResult<()>) -> DbResult<()> +where + DbResult<()>: Send, +{ + arg +} + +fn net_function(arg: NetResult) -> NetResult { + arg +} + +trait ObjSafe { + type AssocTy1; + type AssocTy2; + type AssocTy3; + type AssocTy4; +} +// fn foo() -> ComplexThing { +// todo!() +// } +fn foo>( + _hello: Box> + >, +) -> impl ExampleTrait2, AssocTy1 = DbResult>, AssocTy2> = DbResult<()>> +where + i32: Send +{ +} + +fn bar(val: T) -> T::AssocTy { + todo!() +} +impl ComplexThing, ()> { + const HELLO: usize = 5; +} +fn baz(val: T) -> [i32; , ()>>::HELLO] { + todo!() +} + +fn quz() -> impl ExampleTrait2> +{ + +} + +fn qux>>() -> >>::AssocTy2>> { + todo!() +} + +// Struct fields +struct User { + db_field: DbResult<()>, + net_field: NetResult, +} + +// Tuple struct +struct Response(DbResult<()>, NetResult); + +// Enum variants +enum ApiResponse { + Success(DbResult<()>), + Failure(NetResult), +} + +// Union fields +union DataHolder { + db: std::mem::ManuallyDrop>, + net: std::mem::ManuallyDrop, +} + +struct Random> { + val: T, +} + +// Type aliases + +// Complex type scenarios +static COMPLEX_FULL: ComplexThing = ComplexStruct(PhantomData); +static COMPLEX_PARTIAL: ComplexThing = ComplexStruct(PhantomData); + +// Nested module type +static NESTED_RESULT: outer::NestedResult = Ok(42); + +// Trait implementation with generics +impl ExampleTrait1 for ComplexThing, T> { + type AssocTy = DbResult<()>; + type AssocNet = NetResult; + + fn method(&self) -> DbResult<()> { + Ok(()) + } +} + +impl ExampleTrait1 for DbResult<()> { + type AssocTy = DbResult<()>; + type AssocNet = NetResult; + + fn method(&self) -> DbResult<()> { + Ok(()) + } +} + +fn main() { + // Local variables + let a: DbResult<()> = Ok(()); + let b: NetResult = Ok(""); + + // Function pointers + let f: fn(DbResult<()>) -> DbResult<()> = db_function; + + // Expressions with std types + let s = String::new(); + let v: Vec = vec![s.clone()]; + let _o: Option> = Some(v); + + // Box with default + let boxed_int: BoxedDefault = Box::new(0); + let boxed_float: BoxedDefault = Box::new(0.0); +} diff --git a/tests/ui/explicit_default_arguments.stderr b/tests/ui/explicit_default_arguments.stderr new file mode 100644 index 000000000000..68a9e59875c9 --- /dev/null +++ b/tests/ui/explicit_default_arguments.stderr @@ -0,0 +1,137 @@ +error: use + --> tests/ui/explicit_default_arguments.rs:23:17 + | +LL | const DB_CONST: DbResult<()> = Ok(()); + | ^^^^^^^^^^^^ help: unnecessary generics, `()` already is the default: `DbResult` + | + = note: `-D clippy::explicit-default-arguments` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::explicit_default_arguments)]` + +error: use + --> tests/ui/explicit_default_arguments.rs:26:18 + | +LL | const NET_CONST: NetResult<&str> = Ok(""); + | ^^^^^^^^^^^^^^^ help: unnecessary generics, `&str` already is the default: `NetResult` + +error: use + --> tests/ui/explicit_default_arguments.rs:31:19 + | +LL | static STATIC_DB: DbResult<()> = Ok(()); + | ^^^^^^^^^^^^ help: unnecessary generics, `()` already is the default: `DbResult` + +error: use + --> tests/ui/explicit_default_arguments.rs:34:18 + | +LL | static OPTIONAL: Optional = Some(42); + | ^^^^^^^^^^^^^ help: unnecessary generics, `i64` already is the default: `Optional` + +error: use + --> tests/ui/explicit_default_arguments.rs:43:20 + | +LL | fn method() -> DbResult<()>; + | ^^^^^^^^^^^^ help: unnecessary generics, `()` already is the default: `DbResult` + +error: use + --> tests/ui/explicit_default_arguments.rs:48:20 + | +LL | type AssocDb = DbResult<()>; + | ^^^^^^^^^^^^ help: unnecessary generics, `()` already is the default: `DbResult` + +error: use + --> tests/ui/explicit_default_arguments.rs:52:20 + | +LL | fn method() -> DbResult<()> { + | ^^^^^^^^^^^^ help: unnecessary generics, `()` already is the default: `DbResult` + +error: use + --> tests/ui/explicit_default_arguments.rs:59:21 + | +LL | fn db_function(arg: DbResult<()>) -> DbResult<()> { + | ^^^^^^^^^^^^ help: unnecessary generics, `()` already is the default: `DbResult` + +error: use + --> tests/ui/explicit_default_arguments.rs:59:38 + | +LL | fn db_function(arg: DbResult<()>) -> DbResult<()> { + | ^^^^^^^^^^^^ help: unnecessary generics, `()` already is the default: `DbResult` + +error: use + --> tests/ui/explicit_default_arguments.rs:71:15 + | +LL | db_field: DbResult<()>, + | ^^^^^^^^^^^^ help: unnecessary generics, `()` already is the default: `DbResult` + +error: use + --> tests/ui/explicit_default_arguments.rs:78:5 + | +LL | DbResult<()>, + | ^^^^^^^^^^^^ help: unnecessary generics, `()` already is the default: `DbResult` + +error: use + --> tests/ui/explicit_default_arguments.rs:85:13 + | +LL | Success(DbResult<()>), + | ^^^^^^^^^^^^ help: unnecessary generics, `()` already is the default: `DbResult` + +error: use + --> tests/ui/explicit_default_arguments.rs:92:32 + | +LL | db: std::mem::ManuallyDrop>, + | ^^^^^^^^^^^^ help: unnecessary generics, `()` already is the default: `DbResult` + +error: use + --> tests/ui/explicit_default_arguments.rs:98:16 + | +LL | type DbAlias = DbResult<()>; + | ^^^^^^^^^^^^ help: unnecessary generics, `()` already is the default: `DbResult` + +error: use + --> tests/ui/explicit_default_arguments.rs:103:22 + | +LL | static COMPLEX_FULL: ComplexThing = ComplexStruct(PhantomData); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: unnecessary generics, [`u32`, `f64`] already are already the defaults: `ComplexThing` + +error: use + --> tests/ui/explicit_default_arguments.rs:108:23 + | +LL | static NESTED_RESULT: outer::NestedResult = Ok(42); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: unnecessary generics, `usize` already is the default: `outer::NestedResult` + +error: use + --> tests/ui/explicit_default_arguments.rs:113:20 + | +LL | type AssocDb = DbResult<()>; + | ^^^^^^^^^^^^ help: unnecessary generics, `()` already is the default: `DbResult` + +error: use + --> tests/ui/explicit_default_arguments.rs:117:20 + | +LL | fn method() -> DbResult<()> { + | ^^^^^^^^^^^^ help: unnecessary generics, `()` already is the default: `DbResult` + +error: use + --> tests/ui/explicit_default_arguments.rs:125:12 + | +LL | let a: DbResult<()> = Ok(()); + | ^^^^^^^^^^^^ help: unnecessary generics, `()` already is the default: `DbResult` + +error: use + --> tests/ui/explicit_default_arguments.rs:130:15 + | +LL | let f: fn(DbResult<()>) -> DbResult<()> = db_function; + | ^^^^^^^^^^^^ help: unnecessary generics, `()` already is the default: `DbResult` + +error: use + --> tests/ui/explicit_default_arguments.rs:130:32 + | +LL | let f: fn(DbResult<()>) -> DbResult<()> = db_function; + | ^^^^^^^^^^^^ help: unnecessary generics, `()` already is the default: `DbResult` + +error: use + --> tests/ui/explicit_default_arguments.rs:140:20 + | +LL | let boxed_int: BoxedDefault = Box::new(0); + | ^^^^^^^^^^^^^^^^^^ help: unnecessary generics, `i128` already is the default: `BoxedDefault` + +error: aborting due to 22 previous errors + diff --git a/tests/ui/explicit_default_arguments.stdout b/tests/ui/explicit_default_arguments.stdout new file mode 100644 index 000000000000..107c9f07c672 --- /dev/null +++ b/tests/ui/explicit_default_arguments.stdout @@ -0,0 +1,24 @@ +ty span: DbResult<()> +ty span: NetResult<&str> +ty span: DbResult<()> +ty span: Optional +ty span: DbResult<()> +ty span: DbResult<()> +ty span: DbResult<()> +ty span: DbResult<()> +ty span: DbResult<()> +ty span: DbResult<()> +ty span: DbResult<()> +ty span: DbResult<()> +ty span: DbResult<()> +ty span: DbResult<()> +ty span: ComplexThing +seg: outer#0 +seg: NestedResult#0 +ty span: outer::NestedResult +ty span: DbResult<()> +ty span: DbResult<()> +ty span: DbResult<()> +ty span: DbResult<()> +ty span: DbResult<()> +ty span: BoxedDefault