From e84bf4f9f3332fa2529f67980e028865c5c40e3e Mon Sep 17 00:00:00 2001 From: "Matheus L. P." Date: Thu, 25 Sep 2025 17:42:33 -0500 Subject: [PATCH 1/7] Implement explicit_default_arguments lint --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + .../src/explicit_default_arguments.rs | 165 ++++++++++++++++++ clippy_lints/src/lib.rs | 2 + tests/ui/explicit_default_arguments.fixed | 143 +++++++++++++++ tests/ui/explicit_default_arguments.rs | 143 +++++++++++++++ tests/ui/explicit_default_arguments.stderr | 137 +++++++++++++++ tests/ui/explicit_default_arguments.stdout | 24 +++ 8 files changed, 616 insertions(+) create mode 100644 clippy_lints/src/explicit_default_arguments.rs create mode 100644 tests/ui/explicit_default_arguments.fixed create mode 100644 tests/ui/explicit_default_arguments.rs create mode 100644 tests/ui/explicit_default_arguments.stderr create mode 100644 tests/ui/explicit_default_arguments.stdout 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..342a5fa84d47 --- /dev/null +++ b/clippy_lints/src/explicit_default_arguments.rs @@ -0,0 +1,165 @@ +use std::borrow::Cow; + +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::{GenericArg, QPath, TyKind}; +use rustc_hir_analysis::lower_ty; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{EarlyBinder, GenericParamDefKind}; +use rustc_session::declare_lint_pass; + +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]); + +// TODO: Refactor and improve naming. Make it right. +// TODO: Will type inference be an issue? +impl<'tcx> LateLintPass<'tcx> for ExplicitDefaultArguments { + fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx rustc_hir::Ty<'_, rustc_hir::AmbigArg>) { + // TODO: Double-check for ICEs and other issues + if let TyKind::Path(qpath) = ty.kind + && let QPath::Resolved(_, path) = qpath + && let Some(last_path_segment) = path.segments.last() + && let Some(generic_args) = last_path_segment.args + && let Res::Def(DefKind::TyAlias, def_id) = cx.qpath_res(&qpath, ty.hir_id) + && !generic_args.args.is_empty() + { + // FIXME: Use a simpler comparision instead of type lowering to avoid issues + let default_generic_arg_types: Vec<_> = cx + .tcx + .generics_of(def_id) + .own_params + .iter() + .map(|generic_param| { + if let GenericParamDefKind::Type { has_default, .. } = generic_param.kind + && has_default + { + Some(cx.tcx.type_of(generic_param.def_id)) + } else { + None + } + }) + .collect(); + let generic_arg_types: Vec<_> = generic_args + .args + .iter() + .map(|arg| { + if let GenericArg::Type(ty) = arg { + Some((ty, lower_ty(cx.tcx, ty.as_unambig_ty()))) + } else { + None + } + }) + .collect(); + + // This method works because generic parameters will always be trailing, as it is enforced by the + // compiler and syntactically impossble either way. + let mut first_default: Option = None; + let mut defaults: Vec> = vec![]; + for (i, (actual, default)) in generic_arg_types.iter().zip(default_generic_arg_types).enumerate() { + let Some(actual) = actual else { + continue; + }; + let Some(default) = default else { + continue; + }; + if EarlyBinder::bind(actual.1) != default { + continue; + } + first_default.get_or_insert(i); + + defaults.push(snippet(cx, actual.0.span, "")); + } + let path_str = { + let mut string = String::new(); + for (i, segment) in path.segments.iter().enumerate() { + string.push_str(&segment.ident.to_string()); + if i < path.segments.len() - 1 { + string.push_str("::"); + } + } + string + }; + let sugg = if let Some(first_default) = first_default + && first_default > 0 + { + let mut string = path_str; + let mut iter = generic_arg_types.iter().enumerate(); + string.push('<'); + while let Some((i, Some((ty, _)))) = iter.next() { + if i >= first_default { + break; + } + string.push_str(&snippet(cx, ty.span, "")); + if i + 1 < first_default { + string.push_str(", "); + } + } + string.push('>'); + string + } else { + if snippet(cx, ty.span, "") == "outer::NestedResult" { + for segment in path.segments { + println!("seg: {:?}", segment.ident); + } + } + path_str + }; + if defaults.is_empty() { + return; + } + // TODO: Use constants for strings when possible + let msg = if defaults.len() == 1 { + format!("unnecessary generics, `{}` already is the default", defaults[0]) + } else { + let mut defaults_str = String::new(); + for (i, default) in defaults.iter().enumerate() { + defaults_str.push('`'); + defaults_str.push_str(default); + defaults_str.push('`'); + + if i < defaults.len() - 1 { + defaults_str.push_str(", "); + } + } + format!("unnecessary generics, [{defaults_str}] already are already the defaults",) + }; + println!("ty span: {}", snippet(cx, ty.span, "")); + span_lint_and_sugg( + cx, + EXPLICIT_DEFAULT_ARGUMENTS, + ty.span, + "use", + msg, + sugg, + rustc_errors::Applicability::MachineApplicable, + ); + } + } +} 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..ceaa9c042a96 --- /dev/null +++ b/tests/ui/explicit_default_arguments.rs @@ -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<&str> = 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.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 From 4e4dd8f5dc69c539d6e0f03a1e95ed7af7f86767 Mon Sep 17 00:00:00 2001 From: "Matheus L. P." Date: Sun, 9 Nov 2025 11:49:55 -0600 Subject: [PATCH 2/7] feat: working with fn arguments --- .../src/explicit_default_arguments.rs | 222 +++++++++--------- tests/ui/explicit_default_arguments.rs | 38 +-- 2 files changed, 115 insertions(+), 145 deletions(-) diff --git a/clippy_lints/src/explicit_default_arguments.rs b/clippy_lints/src/explicit_default_arguments.rs index 342a5fa84d47..67991be01af6 100644 --- a/clippy_lints/src/explicit_default_arguments.rs +++ b/clippy_lints/src/explicit_default_arguments.rs @@ -1,12 +1,14 @@ use std::borrow::Cow; +use clippy_utils::MaybePath; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet; +use rustc_data_structures::fx::FxHashMap; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{GenericArg, QPath, TyKind}; +use rustc_hir::{FnRetTy, GenericArg, ItemKind, QPath, TyKind}; use rustc_hir_analysis::lower_ty; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{EarlyBinder, GenericParamDefKind}; +use rustc_middle::ty::{self, EarlyBinder, GenericParamDefKind}; use rustc_session::declare_lint_pass; declare_clippy_lint! { @@ -38,128 +40,118 @@ declare_clippy_lint! { declare_lint_pass!(ExplicitDefaultArguments => [EXPLICIT_DEFAULT_ARGUMENTS]); -// TODO: Refactor and improve naming. Make it right. -// TODO: Will type inference be an issue? +#[allow(unused)] impl<'tcx> LateLintPass<'tcx> for ExplicitDefaultArguments { - fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx rustc_hir::Ty<'_, rustc_hir::AmbigArg>) { - // TODO: Double-check for ICEs and other issues - if let TyKind::Path(qpath) = ty.kind - && let QPath::Resolved(_, path) = qpath - && let Some(last_path_segment) = path.segments.last() - && let Some(generic_args) = last_path_segment.args - && let Res::Def(DefKind::TyAlias, def_id) = cx.qpath_res(&qpath, ty.hir_id) - && !generic_args.args.is_empty() - { - // FIXME: Use a simpler comparision instead of type lowering to avoid issues - let default_generic_arg_types: Vec<_> = cx - .tcx - .generics_of(def_id) - .own_params - .iter() - .map(|generic_param| { - if let GenericParamDefKind::Type { has_default, .. } = generic_param.kind - && has_default - { - Some(cx.tcx.type_of(generic_param.def_id)) - } else { - None - } - }) - .collect(); - let generic_arg_types: Vec<_> = generic_args - .args - .iter() - .map(|arg| { - if let GenericArg::Type(ty) = arg { - Some((ty, lower_ty(cx.tcx, ty.as_unambig_ty()))) - } else { - None - } - }) - .collect(); + // Also check expressions for turbofish, casts, constructor params and type qualified paths + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx rustc_hir::Item<'tcx>) { + let _/*(generics, hir_ty, ty)*/ = match item.kind { + // ItemKind::Static(_, _, ty, _) => ty, + // ItemKind::Const(_, _, ty, _) => ty, + ItemKind::Fn { sig, ident, .. } => { + println!("checking func `{ident}`"); + // TODO: check inputs too + let resolved_ty = { + let poly_fn_sig = cx.tcx.fn_sig(item.owner_id).skip_binder(); + poly_fn_sig.output().skip_binder() + }; - // This method works because generic parameters will always be trailing, as it is enforced by the - // compiler and syntactically impossble either way. - let mut first_default: Option = None; - let mut defaults: Vec> = vec![]; - for (i, (actual, default)) in generic_arg_types.iter().zip(default_generic_arg_types).enumerate() { - let Some(actual) = actual else { - continue; + // `None` = unit type + let FnRetTy::Return(instantiated_alias_hir) = sig.decl.output else { + return; }; - let Some(default) = default else { - continue; + // TODO: check `TyKind::Path(qpath @ QPath::TypeRelative(_, path))` too + let (aliased_ty, alias_ty_generic_params) = if let TyKind::Path(qpath @ QPath::Resolved(_, path)) = instantiated_alias_hir.kind { + let Res::Def(DefKind::TyAlias, def_id) = cx.qpath_res(&qpath, instantiated_alias_hir.hir_id()) else { + return; + }; + // TODO: fill-in generic args and stuff + (cx.tcx.type_of(def_id).skip_binder(), &cx.tcx.generics_of(def_id).own_params) + } else { + return + }; + let alias_ty_generic_defaults: Vec<_> = + alias_ty_generic_params + .iter() + .filter_map(|param| matches!(param.kind, GenericParamDefKind::Type { has_default: true, .. }).then(|| cx.tcx.type_of(param.def_id))) + .collect(); + println!("resolved alias (ty::Ty): {resolved_ty}"); + println!("instantiated alias (hir::Ty): {}", snippet(&cx.tcx, instantiated_alias_hir.span, "")); + // Defaults to the alias type + println!("aliased ty: {aliased_ty}"); + let ty::Adt(_, aliased_ty_generic_args) = aliased_ty.kind() else { + return; + }; + let ty::Adt(_, resolved_ty_generic_args) = resolved_ty.kind() else { + return; }; - if EarlyBinder::bind(actual.1) != default { - continue; - } - first_default.get_or_insert(i); - defaults.push(snippet(cx, actual.0.span, "")); - } - let path_str = { - let mut string = String::new(); - for (i, segment) in path.segments.iter().enumerate() { - string.push_str(&segment.ident.to_string()); - if i < path.segments.len() - 1 { - string.push_str("::"); + /// Index of a resolved ty generic param to its default + let mut map1 = FxHashMap::default(); + /// Index of a resolved ty generic param to its index in the alias type generics + let mut map2 = FxHashMap::default(); + for (i, generic_arg) in aliased_ty_generic_args.iter().enumerate() { + if let Some(generic_arg_ty) = generic_arg.as_type() + && let ty::Param(param) = generic_arg_ty.kind() + && let Some(param_def) = alias_ty_generic_params.iter().find(|param_def| param_def.name == param.name) + // Does it have a default defined in the type alias? + && let GenericParamDefKind::Type { has_default: true, .. } = param_def.kind { + // Was the default explicitly written, or was it there because just because it got resolved? + map1.insert(i, cx.tcx.type_of(param_def.def_id).skip_binder()); + map2.insert(i, param_def.index); } } - string - }; - let sugg = if let Some(first_default) = first_default - && first_default > 0 - { - let mut string = path_str; - let mut iter = generic_arg_types.iter().enumerate(); - string.push('<'); - while let Some((i, Some((ty, _)))) = iter.next() { - if i >= first_default { - break; - } - string.push_str(&snippet(cx, ty.span, "")); - if i + 1 < first_default { - string.push_str(", "); - } - } - string.push('>'); - string - } else { - if snippet(cx, ty.span, "") == "outer::NestedResult" { - for segment in path.segments { - println!("seg: {:?}", segment.ident); - } - } - path_str - }; - if defaults.is_empty() { - return; - } - // TODO: Use constants for strings when possible - let msg = if defaults.len() == 1 { - format!("unnecessary generics, `{}` already is the default", defaults[0]) - } else { - let mut defaults_str = String::new(); - for (i, default) in defaults.iter().enumerate() { - defaults_str.push('`'); - defaults_str.push_str(default); - defaults_str.push('`'); + println!("map1: {map1:?}"); + println!("map2: {map2:?}"); + + for (i, generic_arg) in resolved_ty_generic_args.iter().enumerate() { + if let Some(ty) = generic_arg.as_type() + && let Some(default_arg_val) = map1.get(&i) + { + println!("&ty ({ty}) == default_arg_val ({default_arg_val}) = {}", &ty == default_arg_val); + if let TyKind::Path(QPath::Resolved(_, path)) = instantiated_alias_hir.kind + && let Some(last_seg) = path.segments.last() + && let Some(generic_args) = last_seg.args + && let Some(i) = map2.get(&i) + // If something was specified and the resolved form of the type alias had the default, + // then it is redundant + && generic_args.args.get(*i as usize).is_some() { + println!("\tIt was there!"); + // generic_args.args.iter().position(|arg| arg) + } else { + println!("\tIt was **not** there."); + } - if i < defaults.len() - 1 { - defaults_str.push_str(", "); } } - format!("unnecessary generics, [{defaults_str}] already are already the defaults",) - }; - println!("ty span: {}", snippet(cx, ty.span, "")); - span_lint_and_sugg( - cx, - EXPLICIT_DEFAULT_ARGUMENTS, - ty.span, - "use", - msg, - sugg, - rustc_errors::Applicability::MachineApplicable, - ); - } + // println!("alias type generics: {:#?}", alias_ty_generic_params); + println!("alias type generic param defaults: {alias_ty_generic_defaults:?}"); + println!(); + }, + // rustc_hir::ItemKind::Macro(ident, macro_def, macro_kinds) => todo!(), + // rustc_hir::ItemKind::Mod(ident, _) => todo!(), + // rustc_hir::ItemKind::ForeignMod { abi, items } => todo!(), + // rustc_hir::ItemKind::GlobalAsm { asm, fake_body } => todo!(), + // rustc_hir::ItemKind::TyAlias(ident, generics, ty) => todo!(), + // rustc_hir::ItemKind::Enum(ident, generics, enum_def) => todo!(), + // rustc_hir::ItemKind::Struct(ident, generics, variant_data) => todo!(), + // rustc_hir::ItemKind::Union(ident, generics, variant_data) => todo!(), + // rustc_hir::ItemKind::Trait(constness, is_auto, safety, ident, generics, generic_bounds, trait_item_ids) + // => todo!(), rustc_hir::ItemKind::TraitAlias(ident, generics, generic_bounds) => todo!(), + // NOTE: consider parent generics + // rustc_hir::ItemKind::Impl(_) => todo!(), + _ => return, + }; } + // fn check_path(&mut self, cx: &LateContext<'tcx>, path: &rustc_hir::Path<'tcx>, _: + // rustc_hir::HirId) { println!("`{}` defaults:", snippet(cx, path.span, "")); + // if let Res::Def(DefKind::TyAlias, id) = path { + // for generic_param in cx.tcx.generics_of(id).own_params { + // if let Some(generic_arg) = generic_param.default_value(cx.tcx) { + // generic_arg.skip_binder().kind + // } + // println!("\t- {}", ) + // } + // } + // // TODO: use expect type alias + // } } diff --git a/tests/ui/explicit_default_arguments.rs b/tests/ui/explicit_default_arguments.rs index ceaa9c042a96..5cd80cd2db55 100644 --- a/tests/ui/explicit_default_arguments.rs +++ b/tests/ui/explicit_default_arguments.rs @@ -1,3 +1,4 @@ +// These names are confusing, I should probably change them in the main branch. #![warn(clippy::explicit_default_arguments)] use std::marker::PhantomData; @@ -5,13 +6,13 @@ use std::marker::PhantomData; // Test types struct DatabaseError; struct NetworkError; -struct ComplexStruct(PhantomData<(A, B, C, D)>); +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 ComplexThing = ComplexStruct; type BoxedDefault = Box; // Module to test scoping @@ -21,18 +22,14 @@ mod outer { // Const declarations const DB_CONST: DbResult<()> = Ok(()); -//~^ explicit_default_arguments const DB_OK: DbResult = Ok(()); const NET_CONST: NetResult<&str> = 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 @@ -41,24 +38,19 @@ trait ExampleTrait { 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 } @@ -66,56 +58,46 @@ fn net_function(arg: NetResult) -> NetResult { arg } +fn foo() -> ComplexThing { + todo!() +} + // Struct fields struct User { db_field: DbResult<()>, - //~^ explicit_default_arguments net_field: NetResult, } // Tuple struct -struct Response( - DbResult<()>, - //~^ explicit_default_arguments - NetResult, -); +struct Response(DbResult<()>, 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(()) } } @@ -123,13 +105,10 @@ impl ExampleTrait for ComplexThing { 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(); @@ -138,6 +117,5 @@ fn main() { // Box with default let boxed_int: BoxedDefault = Box::new(0); - //~^ explicit_default_arguments let boxed_float: BoxedDefault = Box::new(0.0); } From 20c0e0685f4f211fe2acf974fc15c842c5a6e3e4 Mon Sep 17 00:00:00 2001 From: "Matheus L. P." Date: Sat, 15 Nov 2025 23:24:42 -0600 Subject: [PATCH 3/7] feat: lint structs & refactor more --- .../src/explicit_default_arguments.rs | 240 ++++++++++++------ 1 file changed, 159 insertions(+), 81 deletions(-) diff --git a/clippy_lints/src/explicit_default_arguments.rs b/clippy_lints/src/explicit_default_arguments.rs index 67991be01af6..ddb408c0ae87 100644 --- a/clippy_lints/src/explicit_default_arguments.rs +++ b/clippy_lints/src/explicit_default_arguments.rs @@ -1,14 +1,11 @@ -use std::borrow::Cow; - use clippy_utils::MaybePath; -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_hir}; use clippy_utils::source::snippet; use rustc_data_structures::fx::FxHashMap; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{FnRetTy, GenericArg, ItemKind, QPath, TyKind}; -use rustc_hir_analysis::lower_ty; +use rustc_hir::{self as hir, FnRetTy, ItemKind, Path, PathSegment, QPath, TyKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self, EarlyBinder, GenericParamDefKind}; +use rustc_middle::ty::{self, GenericArg, GenericParamDef, GenericParamDefKind, ParamTy, Ty, TyCtxt}; use rustc_session::declare_lint_pass; declare_clippy_lint! { @@ -40,92 +37,165 @@ declare_clippy_lint! { declare_lint_pass!(ExplicitDefaultArguments => [EXPLICIT_DEFAULT_ARGUMENTS]); +// TODO: walk through types recursively, this is where the walking in lockstep thing comes in + +/// 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() + .map(|ty| { + if let ty::Param(param) = ty.kind() { + Some((i, param)) + } else { + None + } + }) + .flatten() + }) + .fold( + (FxHashMap::default(), FxHashMap::default()), + |(mut map1, mut map2), (i, param)| { + if let Some( + alias_ty_param @ GenericParamDef { + kind: GenericParamDefKind::Type { has_default: true, .. }, + .. + }, + ) = alias_ty_params.iter().find(|param_def| param_def.name == param.name) + { + map1.insert(i, cx.tcx.type_of(alias_ty_param.def_id).skip_binder()); + 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 { + // We aren't parsing a path or it doesn't have generics + 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; + }; + // TODO: fill-in generic args and stuff, maybe not here + 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 (map1, map2) = match_generics(cx, aliased_ty_args.as_slice(), alias_ty_params.as_slice()); + + println!("map1: {map1:?}"); + println!("map2: {map2:?}"); + + for (i, generic_arg) in resolved_ty_args.iter().enumerate() { + let is_arg_default = map1.get(&i).copied() == generic_arg.as_type(); + // Was the default explicitly written, or was it there because just because it got resolved? + if let Some(ty) = generic_arg.as_type() + && let Some(default_arg_val) = map1.get(&i) + && let Some(j) = map2.get(&i) + // If something was specified and the resolved form of the type alias had the default, + // then it is redundant + && hir_ty_args.args.get(*j as usize).is_some() + { + println!( + "&ty ({ty}) == default_arg_val ({default_arg_val}) = {}", + &ty == default_arg_val + ); + let redudant_ty = hir_ty_args.args.get(*j as usize).unwrap(); + span_lint_hir( + &cx, + EXPLICIT_DEFAULT_ARGUMENTS, + redudant_ty.hir_id(), + redudant_ty.span(), + "redudant usage of default argument", + ); + // println!( + // "\tIt was there! `{}`", + // snippet(&cx.tcx, hir_ty_args.args.get(*j as usize).unwrap().span(), "") + // ); + } else { + // println!( + // "&ty ({ty}) == default_arg_val ({default_arg_val}) = {}", + // &ty == default_arg_val + // ); + println!("\tIt was **not** there."); + } + } + + return; +} + #[allow(unused)] impl<'tcx> LateLintPass<'tcx> for ExplicitDefaultArguments { // Also check expressions for turbofish, casts, constructor params and type qualified paths fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx rustc_hir::Item<'tcx>) { - let _/*(generics, hir_ty, ty)*/ = match item.kind { + let tys_to_check: Vec<(Ty<'_>, &hir::Ty<'_>)> = match item.kind { // ItemKind::Static(_, _, ty, _) => ty, // ItemKind::Const(_, _, ty, _) => ty, ItemKind::Fn { sig, ident, .. } => { - println!("checking func `{ident}`"); + println!("\nchecking func `{ident}`"); // TODO: check inputs too - let resolved_ty = { - let poly_fn_sig = cx.tcx.fn_sig(item.owner_id).skip_binder(); - poly_fn_sig.output().skip_binder() - }; + let poly_fn_sig = cx.tcx.fn_sig(item.owner_id).skip_binder(); + let output_ty = poly_fn_sig.output().skip_binder(); // `None` = unit type - let FnRetTy::Return(instantiated_alias_hir) = sig.decl.output else { - return; - }; - // TODO: check `TyKind::Path(qpath @ QPath::TypeRelative(_, path))` too - let (aliased_ty, alias_ty_generic_params) = if let TyKind::Path(qpath @ QPath::Resolved(_, path)) = instantiated_alias_hir.kind { - let Res::Def(DefKind::TyAlias, def_id) = cx.qpath_res(&qpath, instantiated_alias_hir.hir_id()) else { - return; - }; - // TODO: fill-in generic args and stuff - (cx.tcx.type_of(def_id).skip_binder(), &cx.tcx.generics_of(def_id).own_params) - } else { - return - }; - let alias_ty_generic_defaults: Vec<_> = - alias_ty_generic_params - .iter() - .filter_map(|param| matches!(param.kind, GenericParamDefKind::Type { has_default: true, .. }).then(|| cx.tcx.type_of(param.def_id))) - .collect(); - println!("resolved alias (ty::Ty): {resolved_ty}"); - println!("instantiated alias (hir::Ty): {}", snippet(&cx.tcx, instantiated_alias_hir.span, "")); - // Defaults to the alias type - println!("aliased ty: {aliased_ty}"); - let ty::Adt(_, aliased_ty_generic_args) = aliased_ty.kind() else { - return; - }; - let ty::Adt(_, resolved_ty_generic_args) = resolved_ty.kind() else { + let FnRetTy::Return(output_hir_ty) = sig.decl.output else { return; }; - - /// Index of a resolved ty generic param to its default - let mut map1 = FxHashMap::default(); - /// Index of a resolved ty generic param to its index in the alias type generics - let mut map2 = FxHashMap::default(); - for (i, generic_arg) in aliased_ty_generic_args.iter().enumerate() { - if let Some(generic_arg_ty) = generic_arg.as_type() - && let ty::Param(param) = generic_arg_ty.kind() - && let Some(param_def) = alias_ty_generic_params.iter().find(|param_def| param_def.name == param.name) - // Does it have a default defined in the type alias? - && let GenericParamDefKind::Type { has_default: true, .. } = param_def.kind { - // Was the default explicitly written, or was it there because just because it got resolved? - map1.insert(i, cx.tcx.type_of(param_def.def_id).skip_binder()); - map2.insert(i, param_def.index); - } - } - println!("map1: {map1:?}"); - println!("map2: {map2:?}"); - - for (i, generic_arg) in resolved_ty_generic_args.iter().enumerate() { - if let Some(ty) = generic_arg.as_type() - && let Some(default_arg_val) = map1.get(&i) - { - println!("&ty ({ty}) == default_arg_val ({default_arg_val}) = {}", &ty == default_arg_val); - if let TyKind::Path(QPath::Resolved(_, path)) = instantiated_alias_hir.kind - && let Some(last_seg) = path.segments.last() - && let Some(generic_args) = last_seg.args - && let Some(i) = map2.get(&i) - // If something was specified and the resolved form of the type alias had the default, - // then it is redundant - && generic_args.args.get(*i as usize).is_some() { - println!("\tIt was there!"); - // generic_args.args.iter().position(|arg| arg) - } else { - println!("\tIt was **not** there."); - } - - } - } - // println!("alias type generics: {:#?}", alias_ty_generic_params); - println!("alias type generic param defaults: {alias_ty_generic_defaults:?}"); - println!(); + let inputs_ty = poly_fn_sig.inputs().skip_binder(); + let inputs_hir_tys = sig.decl.inputs; + let mut result = vec![(output_ty, output_hir_ty)]; + result.extend(inputs_ty.iter().copied().zip(inputs_hir_tys.iter()).collect::>()); + result }, // rustc_hir::ItemKind::Macro(ident, macro_def, macro_kinds) => todo!(), // rustc_hir::ItemKind::Mod(ident, _) => todo!(), @@ -133,7 +203,12 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitDefaultArguments { // rustc_hir::ItemKind::GlobalAsm { asm, fake_body } => todo!(), // rustc_hir::ItemKind::TyAlias(ident, generics, ty) => todo!(), // rustc_hir::ItemKind::Enum(ident, generics, enum_def) => todo!(), - // rustc_hir::ItemKind::Struct(ident, generics, variant_data) => todo!(), + ItemKind::Struct(_, _, 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)) + .collect(), // rustc_hir::ItemKind::Union(ident, generics, variant_data) => todo!(), // rustc_hir::ItemKind::Trait(constness, is_auto, safety, ident, generics, generic_bounds, trait_item_ids) // => todo!(), rustc_hir::ItemKind::TraitAlias(ident, generics, generic_bounds) => todo!(), @@ -141,6 +216,9 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitDefaultArguments { // rustc_hir::ItemKind::Impl(_) => todo!(), _ => return, }; + for (resolved_ty, hir_ty) in tys_to_check { + check_alias_args(cx, resolved_ty, hir_ty); + } } // fn check_path(&mut self, cx: &LateContext<'tcx>, path: &rustc_hir::Path<'tcx>, _: // rustc_hir::HirId) { println!("`{}` defaults:", snippet(cx, path.span, "")); From 429e4c238bc43fc9e975849272ea723b633cb8d1 Mon Sep 17 00:00:00 2001 From: "Matheus L. P." Date: Thu, 20 Nov 2025 19:29:02 -0600 Subject: [PATCH 4/7] feat: lint all kind of items --- .../src/explicit_default_arguments.rs | 288 ++++++++++++------ tests/ui/explicit_default_arguments.rs | 18 +- 2 files changed, 207 insertions(+), 99 deletions(-) diff --git a/clippy_lints/src/explicit_default_arguments.rs b/clippy_lints/src/explicit_default_arguments.rs index ddb408c0ae87..4a9ddff48930 100644 --- a/clippy_lints/src/explicit_default_arguments.rs +++ b/clippy_lints/src/explicit_default_arguments.rs @@ -1,11 +1,18 @@ +use std::iter; + use clippy_utils::MaybePath; -use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_hir}; +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, FnRetTy, ItemKind, Path, PathSegment, QPath, TyKind}; +use rustc_hir::{ + self as hir, EnumDef, FnRetTy, FnSig, GenericParam, GenericParamKind, Generics, Impl, ImplItem, ImplItemKind, Item, + ItemKind, OwnerId, Path, PathSegment, QPath, TraitItem, TraitItemKind, TyKind, Variant, WhereBoundPredicate, + WherePredicateKind, +}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self, GenericArg, GenericParamDef, GenericParamDefKind, ParamTy, Ty, TyCtxt}; +use rustc_middle::ty::{self, GenericArg, GenericParamDef, Ty}; + use rustc_session::declare_lint_pass; declare_clippy_lint! { @@ -37,8 +44,6 @@ declare_clippy_lint! { declare_lint_pass!(ExplicitDefaultArguments => [EXPLICIT_DEFAULT_ARGUMENTS]); -// TODO: walk through types recursively, this is where the walking in lockstep thing comes in - /// 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. @@ -55,28 +60,23 @@ fn match_generics<'tcx>( .iter() .enumerate() .filter_map(|(i, generic_arg)| { - generic_arg - .as_type() - .map(|ty| { - if let ty::Param(param) = ty.kind() { - Some((i, param)) - } else { - None - } - }) - .flatten() + 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 @ GenericParamDef { - kind: GenericParamDefKind::Type { has_default: true, .. }, - .. - }, - ) = alias_ty_params.iter().find(|param_def| param_def.name == param.name) + 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, cx.tcx.type_of(alias_ty_param.def_id).skip_binder()); + map1.insert(i, default_value); map2.insert(i, alias_ty_param.index); } (map1, map2) @@ -84,7 +84,7 @@ fn match_generics<'tcx>( ) } // 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>) { +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): {}", @@ -108,14 +108,12 @@ fn check_alias_args<'tcx>(cx: &LateContext<'tcx>, resolved_ty: Ty<'tcx>, hir_ty: ), ) = hir_ty.kind else { - // We aren't parsing a path or it doesn't have generics 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; }; - // TODO: fill-in generic args and stuff, maybe not here 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 { @@ -131,37 +129,28 @@ fn check_alias_args<'tcx>(cx: &LateContext<'tcx>, resolved_ty: Ty<'tcx>, hir_ty: let ty::Adt(_, resolved_ty_args) = resolved_ty.kind() else { return; }; - let (map1, map2) = match_generics(cx, aliased_ty_args.as_slice(), alias_ty_params.as_slice()); + let (defaults, aliased_to_alias) = match_generics(cx, aliased_ty_args.as_slice(), alias_ty_params.as_slice()); - println!("map1: {map1:?}"); - println!("map2: {map2:?}"); + 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() { - let is_arg_default = map1.get(&i).copied() == generic_arg.as_type(); // Was the default explicitly written, or was it there because just because it got resolved? - if let Some(ty) = generic_arg.as_type() - && let Some(default_arg_val) = map1.get(&i) - && let Some(j) = map2.get(&i) - // If something was specified and the resolved form of the type alias had the default, - // then it is redundant - && hir_ty_args.args.get(*j as usize).is_some() + // 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() { - println!( - "&ty ({ty}) == default_arg_val ({default_arg_val}) = {}", - &ty == default_arg_val - ); - let redudant_ty = hir_ty_args.args.get(*j as usize).unwrap(); + // TODO: show a hint span_lint_hir( &cx, EXPLICIT_DEFAULT_ARGUMENTS, - redudant_ty.hir_id(), - redudant_ty.span(), + redundant_ty.hir_id(), + redundant_ty.span(), "redudant usage of default argument", ); - // println!( - // "\tIt was there! `{}`", - // snippet(&cx.tcx, hir_ty_args.args.get(*j as usize).unwrap().span(), "") - // ); + println!("\tIt was there! `{}`", snippet(&cx.tcx, redundant_ty.span(), "")); } else { // println!( // "&ty ({ty}) == default_arg_val ({default_arg_val}) = {}", @@ -174,62 +163,169 @@ fn check_alias_args<'tcx>(cx: &LateContext<'tcx>, resolved_ty: Ty<'tcx>, hir_ty: return; } +type TyPair<'a> = (Ty<'a>, hir::Ty<'a>); + +fn get_tys_fn_sig<'tcx>( + cx: &LateContext<'tcx>, + sig: FnSig<'tcx>, + item_owner_id: OwnerId, +) -> Box> + 'tcx> { + let poly_fn_sig = cx.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; + Box::new( + inputs_ty + .iter() + .copied() + .zip(inputs_hir_tys.iter().copied()) + .chain(output), + ) +} +fn get_tys_generics_predicates<'tcx>( + cx: &LateContext<'tcx>, + generics: &Generics<'tcx>, + item_owner_id: OwnerId, +) -> Box> + 'tcx> { + // Binding for filter map + let tcx = cx.tcx; + + let params = cx + .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, + }), + ); + let predicates = cx + .tcx + .explicit_predicates_of(item_owner_id) + .predicates + .iter() + .filter_map(|predicate| { + predicate + .0 + .as_trait_clause() + .map(|clause| clause.self_ty().skip_binder()) + .or(predicate + .0 + .as_type_outlives_clause() + .map(|clause| clause.skip_binder().0)) + }) + .zip(generics.predicates.iter().filter_map(|predicate| { + if let WherePredicateKind::BoundPredicate(WhereBoundPredicate { bounded_ty, .. }) = predicate.kind { + Some(**bounded_ty) + } else { + None + } + })); + Box::new(params.chain(predicates)) +} + #[allow(unused)] impl<'tcx> LateLintPass<'tcx> for ExplicitDefaultArguments { - // Also check expressions for turbofish, casts, constructor params and type qualified paths - fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx rustc_hir::Item<'tcx>) { - let tys_to_check: Vec<(Ty<'_>, &hir::Ty<'_>)> = match item.kind { - // ItemKind::Static(_, _, ty, _) => ty, - // ItemKind::Const(_, _, ty, _) => ty, - ItemKind::Fn { sig, ident, .. } => { - println!("\nchecking func `{ident}`"); - // TODO: check inputs too - let poly_fn_sig = cx.tcx.fn_sig(item.owner_id).skip_binder(); + // 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, 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, sig, trait_item.owner_id).chain( + get_tys_generics_predicates(cx, 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, sig, impl_item.owner_id).chain( + get_tys_generics_predicates(cx, 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, + }; - let output_ty = poly_fn_sig.output().skip_binder(); - // `None` = unit type - let FnRetTy::Return(output_hir_ty) = sig.decl.output else { - return; - }; - let inputs_ty = poly_fn_sig.inputs().skip_binder(); - let inputs_hir_tys = sig.decl.inputs; - let mut result = vec![(output_ty, output_hir_ty)]; - result.extend(inputs_ty.iter().copied().zip(inputs_hir_tys.iter()).collect::>()); - result - }, - // rustc_hir::ItemKind::Macro(ident, macro_def, macro_kinds) => todo!(), - // rustc_hir::ItemKind::Mod(ident, _) => todo!(), - // rustc_hir::ItemKind::ForeignMod { abi, items } => todo!(), - // rustc_hir::ItemKind::GlobalAsm { asm, fake_body } => todo!(), - // rustc_hir::ItemKind::TyAlias(ident, generics, ty) => todo!(), - // rustc_hir::ItemKind::Enum(ident, generics, enum_def) => todo!(), - ItemKind::Struct(_, _, 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)) - .collect(), - // rustc_hir::ItemKind::Union(ident, generics, variant_data) => todo!(), - // rustc_hir::ItemKind::Trait(constness, is_auto, safety, ident, generics, generic_bounds, trait_item_ids) - // => todo!(), rustc_hir::ItemKind::TraitAlias(ident, generics, generic_bounds) => todo!(), - // NOTE: consider parent generics - // rustc_hir::ItemKind::Impl(_) => todo!(), - _ => return, + if let Some(generics) = item.kind.generics() { + tys.extend(get_tys_generics_predicates(cx, generics, item.owner_id)); + } + tys.extend(other_tys); + tys }; for (resolved_ty, hir_ty) in tys_to_check { check_alias_args(cx, resolved_ty, hir_ty); } } - // fn check_path(&mut self, cx: &LateContext<'tcx>, path: &rustc_hir::Path<'tcx>, _: - // rustc_hir::HirId) { println!("`{}` defaults:", snippet(cx, path.span, "")); - // if let Res::Def(DefKind::TyAlias, id) = path { - // for generic_param in cx.tcx.generics_of(id).own_params { - // if let Some(generic_arg) = generic_param.default_value(cx.tcx) { - // generic_arg.skip_binder().kind - // } - // println!("\t- {}", ) - // } - // } - // // TODO: use expect type alias - // } } diff --git a/tests/ui/explicit_default_arguments.rs b/tests/ui/explicit_default_arguments.rs index 5cd80cd2db55..accc16fc44a2 100644 --- a/tests/ui/explicit_default_arguments.rs +++ b/tests/ui/explicit_default_arguments.rs @@ -50,7 +50,10 @@ impl ExampleTrait for () { } // Function signatures -fn db_function(arg: DbResult<()>) -> DbResult<()> { +fn db_function(arg: DbResult<()>) -> DbResult<()> +where + DbResult<()>: Send, +{ arg } @@ -58,7 +61,7 @@ fn net_function(arg: NetResult) -> NetResult { arg } -fn foo() -> ComplexThing { +fn foo() -> ComplexThing { todo!() } @@ -93,7 +96,16 @@ static COMPLEX_PARTIAL: ComplexThing = ComplexStruct(PhantomData); static NESTED_RESULT: outer::NestedResult = Ok(42); // Trait implementation with generics -impl ExampleTrait for ComplexThing { +impl ExampleTrait for ComplexThing, T> { + type AssocDb = DbResult<()>; + type AssocNet = NetResult; + + fn method() -> DbResult<()> { + Ok(()) + } +} + +impl ExampleTrait for DbResult<()> { type AssocDb = DbResult<()>; type AssocNet = NetResult; From cc5a7c8d2b20b45a13fb85409d9270d9145c59c2 Mon Sep 17 00:00:00 2001 From: "Matheus L. P." Date: Sun, 23 Nov 2025 14:58:33 -0600 Subject: [PATCH 5/7] feat: walk through some types recursively --- .../src/explicit_default_arguments.rs | 116 +++++++++++++++++- tests/ui/explicit_default_arguments.rs | 33 ++++- 2 files changed, 139 insertions(+), 10 deletions(-) diff --git a/clippy_lints/src/explicit_default_arguments.rs b/clippy_lints/src/explicit_default_arguments.rs index 4a9ddff48930..2546f1ce10e1 100644 --- a/clippy_lints/src/explicit_default_arguments.rs +++ b/clippy_lints/src/explicit_default_arguments.rs @@ -6,12 +6,12 @@ use clippy_utils::source::snippet; use rustc_data_structures::fx::FxHashMap; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{ - self as hir, EnumDef, FnRetTy, FnSig, GenericParam, GenericParamKind, Generics, Impl, ImplItem, ImplItemKind, Item, - ItemKind, OwnerId, Path, PathSegment, QPath, TraitItem, TraitItemKind, TyKind, Variant, WhereBoundPredicate, - WherePredicateKind, + self as hir, AmbigArg, EnumDef, FnRetTy, FnSig, GenericParam, GenericParamKind, Generics, Impl, ImplItem, + ImplItemKind, Item, ItemKind, MutTy, OwnerId, Path, PathSegment, QPath, TraitItem, TraitItemKind, TyKind, Variant, + WhereBoundPredicate, WherePredicateKind, }; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self, GenericArg, GenericParamDef, Ty}; +use rustc_middle::ty::{self, AliasTy, ConstKind, GenericArg, GenericParamDef, Ty, UnevaluatedConst}; use rustc_session::declare_lint_pass; @@ -83,6 +83,7 @@ fn match_generics<'tcx>( }, ) } + // 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}"); @@ -188,6 +189,7 @@ fn get_tys_fn_sig<'tcx>( .chain(output), ) } +// FIXME: check trait bounds in predicates because they can have generic args too fn get_tys_generics_predicates<'tcx>( cx: &LateContext<'tcx>, generics: &Generics<'tcx>, @@ -243,6 +245,104 @@ fn get_tys_generics_predicates<'tcx>( Box::new(params.chain(predicates)) } +fn walk_ty_recursive<'tcx>( + // cx: &LateContext<'tcx>, + ty: Ty<'tcx>, + hir_ty: hir::Ty<'tcx>, +) -> Box> + 'tcx> { + let generic_arg_to_ty = |args: &rustc_hir::GenericArgs<'tcx>| -> Box>> { + Box::new(args.args.iter().flat_map(|arg| match arg { + rustc_hir::GenericArg::Type(ty) => Some(*ty.as_unambig_ty()), + _ => None, + })) + }; + let result: Box>> = match (ty.kind(), hir_ty.kind) { + // 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::Slice(ty), TyKind::Slice(hir_ty)) => { + Box::new(iter::once((*ty, *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::Adt(_, generics), + TyKind::Path(QPath::Resolved( + None, + Path { + segments: + [ + .., + PathSegment { + args: Some(generics_hir), + .. + }, + ], + .. + }, + )), + ) => Box::new( + generics + .iter() + .flat_map(|arg| arg.as_type()) + .zip(generic_arg_to_ty(*generics_hir)), + ), + ( + ty::Alias(ty::Projection, AliasTy { args, .. }), + TyKind::Path(QPath::TypeRelative(hir_ty, PathSegment { args: hir_gat_args, .. })), + ) => { + println!( + "FOUND TYPE RELATIVE: `{:#?}`, gat args: `{:?}`", + // snippet(cx, hir_ty.span, ""), + args.as_slice(), + hir_gat_args + ); + let hir_gat_args_iter = hir_gat_args.map_or_else(|| Box::new(iter::empty()), generic_arg_to_ty); + 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, .. }, + ], + .. + }, + )), + ) => { + println!( + "FOUND TYPE RELATIVE: `{:#?}`, trait args: `{:?}`, \n gat args: `{:?}`", + args.as_slice(), + hir_trait_args, + hir_gat_args + ); + let hir_trait_args_iter = hir_trait_args.map_or_else(|| Box::new(iter::empty()), generic_arg_to_ty); + let hir_gat_args_iter = hir_gat_args.map_or_else(|| Box::new(iter::empty()), generic_arg_to_ty); + Box::new( + args.iter() + .flat_map(|arg| arg.as_type()) + .zip(iter::once(*hir_ty).chain(hir_trait_args_iter).chain(hir_gat_args_iter)), + ) + }, + _ => Box::new(iter::empty()), + }; + Box::new( + result + .flat_map(|(ty, hir_ty)| walk_ty_recursive(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 @@ -324,7 +424,13 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitDefaultArguments { tys.extend(other_tys); tys }; - for (resolved_ty, hir_ty) in tys_to_check { + + for (resolved_ty, hir_ty) in tys_to_check + .iter() + .flat_map(|(ty, hir_ty)| walk_ty_recursive(*ty, *hir_ty)) + .collect::>() + { + println!("CHECKING `{}`/`{}`", resolved_ty, snippet(cx, hir_ty.span, "")); check_alias_args(cx, resolved_ty, hir_ty); } } diff --git a/tests/ui/explicit_default_arguments.rs b/tests/ui/explicit_default_arguments.rs index accc16fc44a2..939dee830523 100644 --- a/tests/ui/explicit_default_arguments.rs +++ b/tests/ui/explicit_default_arguments.rs @@ -1,4 +1,4 @@ -// These names are confusing, I should probably change them in the main branch. +// These names are confusing and unnecessary, I should probably change them #![warn(clippy::explicit_default_arguments)] use std::marker::PhantomData; @@ -33,14 +33,19 @@ static OPTIONAL: Optional = Some(42); static CUSTOM_OPT: Optional = Some(1.5); // Associated types in traits -trait ExampleTrait { +trait ExampleTrait1 { type AssocDb; type AssocNet; fn method() -> DbResult<()>; } -impl ExampleTrait for () { +trait ExampleTrait2 { + type AssocTy1; + type AssocTy2; +} + +impl ExampleTrait1 for () { type AssocDb = DbResult<()>; type AssocNet = NetResult; @@ -65,6 +70,24 @@ fn foo() -> ComplexThing { todo!() } +fn bar(val: T) -> T::AssocDb { + todo!() +} +impl ComplexThing, ()> { + const HELLO: usize = 5; +} +fn baz(val: T) -> [i32; , ()>>::HELLO] { + todo!() +} + +fn quz>() -> ::AssocTy1 { + todo!() +} + +fn qux>() -> >::AssocTy2>> { + todo!() +} + // Struct fields struct User { db_field: DbResult<()>, @@ -96,7 +119,7 @@ static COMPLEX_PARTIAL: ComplexThing = ComplexStruct(PhantomData); static NESTED_RESULT: outer::NestedResult = Ok(42); // Trait implementation with generics -impl ExampleTrait for ComplexThing, T> { +impl ExampleTrait1 for ComplexThing, T> { type AssocDb = DbResult<()>; type AssocNet = NetResult; @@ -105,7 +128,7 @@ impl ExampleTrait for ComplexThing, T> { } } -impl ExampleTrait for DbResult<()> { +impl ExampleTrait1 for DbResult<()> { type AssocDb = DbResult<()>; type AssocNet = NetResult; From 20ea5577739b9251c8639958cb222ecd2b0f97f3 Mon Sep 17 00:00:00 2001 From: "Matheus L. P." Date: Wed, 26 Nov 2025 12:21:03 -0600 Subject: [PATCH 6/7] feat: finish recusive type walker --- .../src/explicit_default_arguments.rs | 337 +++++++++++++----- tests/ui/explicit_default_arguments.rs | 52 ++- 2 files changed, 290 insertions(+), 99 deletions(-) diff --git a/clippy_lints/src/explicit_default_arguments.rs b/clippy_lints/src/explicit_default_arguments.rs index 2546f1ce10e1..e85d145c8f8c 100644 --- a/clippy_lints/src/explicit_default_arguments.rs +++ b/clippy_lints/src/explicit_default_arguments.rs @@ -6,14 +6,18 @@ use clippy_utils::source::snippet; use rustc_data_structures::fx::FxHashMap; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{ - self as hir, AmbigArg, EnumDef, FnRetTy, FnSig, GenericParam, GenericParamKind, Generics, Impl, ImplItem, - ImplItemKind, Item, ItemKind, MutTy, OwnerId, Path, PathSegment, QPath, TraitItem, TraitItemKind, TyKind, Variant, - WhereBoundPredicate, WherePredicateKind, + 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, ConstKind, GenericArg, GenericParamDef, Ty, UnevaluatedConst}; +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 @@ -167,11 +171,13 @@ fn check_alias_args<'tcx>(cx: &LateContext<'tcx>, resolved_ty: Ty<'tcx>, hir_ty: type TyPair<'a> = (Ty<'a>, hir::Ty<'a>); fn get_tys_fn_sig<'tcx>( - cx: &LateContext<'tcx>, + tcx: TyCtxt<'tcx>, sig: FnSig<'tcx>, item_owner_id: OwnerId, -) -> Box> + 'tcx> { - let poly_fn_sig = cx.tcx.fn_sig(item_owner_id).skip_binder(); +) -> 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 { @@ -181,25 +187,22 @@ fn get_tys_fn_sig<'tcx>( }; let inputs_ty = poly_fn_sig.inputs().skip_binder(); let inputs_hir_tys = sig.decl.inputs; - Box::new( - inputs_ty - .iter() - .copied() - .zip(inputs_hir_tys.iter().copied()) - .chain(output), - ) + inputs_ty + .iter() + .copied() + .zip(inputs_hir_tys.iter().copied()) + .chain(output) } -// FIXME: check trait bounds in predicates because they can have generic args too -fn get_tys_generics_predicates<'tcx>( - cx: &LateContext<'tcx>, +/// 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, -) -> Box> + 'tcx> { - // Binding for filter map - let tcx = cx.tcx; - - let params = cx - .tcx +) -> 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() @@ -220,50 +223,95 @@ fn get_tys_generics_predicates<'tcx>( _ => None, }), ); - let predicates = cx - .tcx - .explicit_predicates_of(item_owner_id) - .predicates - .iter() - .filter_map(|predicate| { - predicate - .0 - .as_trait_clause() - .map(|clause| clause.self_ty().skip_binder()) - .or(predicate - .0 - .as_type_outlives_clause() - .map(|clause| clause.skip_binder().0)) - }) - .zip(generics.predicates.iter().filter_map(|predicate| { - if let WherePredicateKind::BoundPredicate(WhereBoundPredicate { bounded_ty, .. }) = predicate.kind { - Some(**bounded_ty) - } else { - None - } - })); - Box::new(params.chain(predicates)) + // 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 +} + +fn match_trait_ref_constraint_idents<'tcx, T: Iterator>>( + refs: T, + ident_map: FxHashMap>, +) -> 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, .. }| { + if let AssocItemConstraintKind::Equality { term: Term::Ty(hir_ty) } = kind { + ident_map.get(ident).map(|&ty| (ty, **hir_ty)) + } else { + None + } + }), + ) + } else { + None + } + }) + .flatten() + .collect() } fn walk_ty_recursive<'tcx>( - // cx: &LateContext<'tcx>, + tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, hir_ty: hir::Ty<'tcx>, ) -> Box> + 'tcx> { - let generic_arg_to_ty = |args: &rustc_hir::GenericArgs<'tcx>| -> Box>> { - Box::new(args.args.iter().flat_map(|arg| match arg { + 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) { - // 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::Slice(ty), TyKind::Slice(hir_ty)) => { - Box::new(iter::once((*ty, *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::Adt(_, generics), TyKind::Path(QPath::Resolved( @@ -283,20 +331,83 @@ fn walk_ty_recursive<'tcx>( ) => Box::new( generics .iter() - .flat_map(|arg| arg.as_type()) - .zip(generic_arg_to_ty(*generics_hir)), + .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)) + } 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, .. })), ) => { - println!( - "FOUND TYPE RELATIVE: `{:#?}`, gat args: `{:?}`", - // snippet(cx, hir_ty.span, ""), - args.as_slice(), - hir_gat_args - ); - let hir_gat_args_iter = hir_gat_args.map_or_else(|| Box::new(iter::empty()), generic_arg_to_ty); + 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()) @@ -320,25 +431,82 @@ fn walk_ty_recursive<'tcx>( }, )), ) => { - println!( - "FOUND TYPE RELATIVE: `{:#?}`, trait args: `{:?}`, \n gat args: `{:?}`", - args.as_slice(), - hir_trait_args, - hir_gat_args - ); - let hir_trait_args_iter = hir_trait_args.map_or_else(|| Box::new(iter::empty()), generic_arg_to_ty); - let hir_gat_args_iter = hir_gat_args.map_or_else(|| Box::new(iter::empty()), generic_arg_to_ty); + // 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).chain(hir_gat_args_iter)), + 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() { + Some((tcx.item_ident(predicate.skip_binder().def_id()), ty)) + } 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(|(ty, hir_ty)| walk_ty_recursive(ty, hir_ty)) + .flat_map(move |(ty, hir_ty)| walk_ty_recursive(tcx, ty, hir_ty)) .chain(iter::once((ty, hir_ty))), ) } @@ -361,7 +529,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitDefaultArguments { 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, sig, item.owner_id), + 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 @@ -382,8 +550,8 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitDefaultArguments { .flat_map(|trait_item| { let tys: Option>>> = match trait_item.kind { TraitItemKind::Fn(sig, _) => { - Some(Box::new(get_tys_fn_sig(cx, sig, trait_item.owner_id).chain( - get_tys_generics_predicates(cx, trait_item.generics, trait_item.owner_id), + 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( @@ -401,8 +569,8 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitDefaultArguments { .flat_map(|impl_item| { let tys: Option>>> = match impl_item.kind { ImplItemKind::Fn(sig, _) => { - Some(Box::new(get_tys_fn_sig(cx, sig, impl_item.owner_id).chain( - get_tys_generics_predicates(cx, impl_item.generics, impl_item.owner_id), + 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(( @@ -419,7 +587,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitDefaultArguments { }; if let Some(generics) = item.kind.generics() { - tys.extend(get_tys_generics_predicates(cx, generics, item.owner_id)); + tys.extend(get_tys_from_generics(cx.tcx, generics, item.owner_id)); } tys.extend(other_tys); tys @@ -427,8 +595,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitDefaultArguments { for (resolved_ty, hir_ty) in tys_to_check .iter() - .flat_map(|(ty, hir_ty)| walk_ty_recursive(*ty, *hir_ty)) - .collect::>() + .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/tests/ui/explicit_default_arguments.rs b/tests/ui/explicit_default_arguments.rs index 939dee830523..0d67c44202f1 100644 --- a/tests/ui/explicit_default_arguments.rs +++ b/tests/ui/explicit_default_arguments.rs @@ -34,10 +34,10 @@ static CUSTOM_OPT: Optional = Some(1.5); // Associated types in traits trait ExampleTrait1 { - type AssocDb; + type AssocTy; type AssocNet; - fn method() -> DbResult<()>; + fn method(&self) -> DbResult<()>; } trait ExampleTrait2 { @@ -46,14 +46,19 @@ trait ExampleTrait2 { } impl ExampleTrait1 for () { - type AssocDb = DbResult<()>; + type AssocTy = DbResult<()>; type AssocNet = NetResult; - fn method() -> DbResult<()> { + fn method(&self) -> DbResult<()> { Ok(()) } } +impl ExampleTrait2> for () { + type AssocTy1 = DbResult>; + type AssocTy2 = NetResult; +} + // Function signatures fn db_function(arg: DbResult<()>) -> DbResult<()> where @@ -66,11 +71,25 @@ fn net_function(arg: NetResult) -> NetResult { arg } -fn foo() -> ComplexThing { - todo!() +trait ObjSafe { + type AssocTy1; + type AssocTy2; + type AssocTy3; + type AssocTy4; +} +// fn foo() -> ComplexThing { +// todo!() +// } +fn foo>( + _hello: Box> + >, + // _world: Box, AssocTy1 = DbResult<()>, AssocTy2 = i16, AssocTy3 = i32, AssocTy4 = i64>>, +) -> impl ExampleTrait2, AssocTy1 = DbResult>> +where + i32: Send +{ } -fn bar(val: T) -> T::AssocDb { +fn bar(val: T) -> T::AssocTy { todo!() } impl ComplexThing, ()> { @@ -80,11 +99,12 @@ fn baz(val: T) -> [i32; , ()>>::HELL todo!() } -fn quz>() -> ::AssocTy1 { - todo!() +fn quz() -> impl ExampleTrait2> +{ + } -fn qux>() -> >::AssocTy2>> { +fn qux>>() -> >>::AssocTy2>> { todo!() } @@ -109,6 +129,10 @@ union DataHolder { net: std::mem::ManuallyDrop, } +struct Random> { + val: T, +} + // Type aliases // Complex type scenarios @@ -120,19 +144,19 @@ static NESTED_RESULT: outer::NestedResult = Ok(42); // Trait implementation with generics impl ExampleTrait1 for ComplexThing, T> { - type AssocDb = DbResult<()>; + type AssocTy = DbResult<()>; type AssocNet = NetResult; - fn method() -> DbResult<()> { + fn method(&self) -> DbResult<()> { Ok(()) } } impl ExampleTrait1 for DbResult<()> { - type AssocDb = DbResult<()>; + type AssocTy = DbResult<()>; type AssocNet = NetResult; - fn method() -> DbResult<()> { + fn method(&self) -> DbResult<()> { Ok(()) } } From 843b788048dde9a9d683e03e99486b645173a727 Mon Sep 17 00:00:00 2001 From: "Matheus L. P." Date: Fri, 5 Dec 2025 21:50:41 -0600 Subject: [PATCH 7/7] fix: check GATs in impl Traits --- .../src/explicit_default_arguments.rs | 51 +++++++++++++++---- tests/ui/explicit_default_arguments.rs | 5 +- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/clippy_lints/src/explicit_default_arguments.rs b/clippy_lints/src/explicit_default_arguments.rs index e85d145c8f8c..da2af9286f5b 100644 --- a/clippy_lints/src/explicit_default_arguments.rs +++ b/clippy_lints/src/explicit_default_arguments.rs @@ -232,9 +232,10 @@ fn get_tys_from_generics<'tcx>( default_tys } +/// Helper function to... fn match_trait_ref_constraint_idents<'tcx, T: Iterator>>( refs: T, - ident_map: FxHashMap>, + ident_map: FxHashMap, impl Iterator> + Clone)>, ) -> Vec> { refs.filter_map(|trait_ref| { if let hir::TraitRef { @@ -256,13 +257,31 @@ fn match_trait_ref_constraint_idents<'tcx, T: Iterator( predicate.skip_binder() && let Some(ty) = term.as_type() { - Some((tcx.item_ident(def_id), ty)) + Some((tcx.item_ident(def_id), (ty, [].iter().copied()))) } else { None } @@ -479,7 +498,19 @@ fn walk_ty_recursive<'tcx>( .filter_map(|(clause, _)| clause.as_projection_clause()) .filter_map(|predicate| { if let Some(ty) = predicate.term().skip_binder().as_type() { - Some((tcx.item_ident(predicate.skip_binder().def_id()), ty)) + 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 } diff --git a/tests/ui/explicit_default_arguments.rs b/tests/ui/explicit_default_arguments.rs index 0d67c44202f1..f670ed248225 100644 --- a/tests/ui/explicit_default_arguments.rs +++ b/tests/ui/explicit_default_arguments.rs @@ -56,7 +56,7 @@ impl ExampleTrait1 for () { impl ExampleTrait2> for () { type AssocTy1 = DbResult>; - type AssocTy2 = NetResult; + type AssocTy2 = DbResult<()>; } // Function signatures @@ -82,8 +82,7 @@ trait ObjSafe { // } fn foo>( _hello: Box> + >, - // _world: Box, AssocTy1 = DbResult<()>, AssocTy2 = i16, AssocTy3 = i32, AssocTy4 = i64>>, -) -> impl ExampleTrait2, AssocTy1 = DbResult>> +) -> impl ExampleTrait2, AssocTy1 = DbResult>, AssocTy2> = DbResult<()>> where i32: Send {