Skip to content

Show pending obligations as unsatisfied constraints in report_similar_impl_candidates #134348

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

soareschen
Copy link

Fixes: #134346

Summary

This PR attempts to fix the issue in #134346, by additional help hints for each unsatisfied indirect constraint inside a blanket implementation.

Details

To provide the extra information, a new variant Select(PredicateObligations<'tcx>) is added to ScrubbedTraitError<'tcx> to extract the pending_obligations returned from select_where_possible. Then inside report_similar_impl_candidates, we iterate through every pending obligation in the errors, and print them out as a help hint.

Potential Issues

This is my first contribution to the Rust compiler project. I'm not sure of the best way to present the error messages, or handle them anywhere else in the codebase. If there are better ways to implement the improvement, I'm happy to modify the PR according to the suggestions.

An unresolved issue here is that the fix is only implemented for the old Rust trait solver. Compared to OldSolverError, I don't see the pending_obligations field to be present inside NextSolverError. So I'm unsure of the best way to keep this forward compatible with the upcoming trait solver.

I'm also not sure if the fix here would produce too noisy errors outside of my specific use case. When I test this fix with more complex projects that I have, the error messages may contain many unresolved constraints. However when scanning through all items, I determined that none of the listed unresolved constraints should be considered uninformative noise. Hence, I do think that it is essential to have all unresolved constraints listed in the error message.

@rustbot
Copy link
Collaborator

rustbot commented Dec 15, 2024

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @wesleywiser (or someone else) some time within the next two weeks.

Please see the contribution instructions for more information. Namely, in order to ensure the minimum review times lag, PR authors and assigned reviewers should ensure that the review label (S-waiting-on-review and S-waiting-on-author) stays updated, invoking these commands when appropriate:

  • @rustbot author: the review is finished, PR author should check the comments and take action accordingly
  • @rustbot review: the author is ready for a review, this PR will be queued again in the reviewer's queue

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Dec 15, 2024
@rust-log-analyzer
Copy link
Collaborator

The job x86_64-gnu-llvm-18 failed! Check out the build log: (web) (plain)

Click to see the possible cause of the failure (guessed by this bot)
#16 exporting to docker image format
#16 sending tarball 27.1s done
#16 DONE 39.9s
##[endgroup]
Setting extra environment values for docker:  --env ENABLE_GCC_CODEGEN=1 --env GCC_EXEC_PREFIX=/usr/lib/gcc/
[CI_JOB_NAME=x86_64-gnu-llvm-18]
debug: `DISABLE_CI_RUSTC_IF_INCOMPATIBLE` configured.
---
sccache: Starting the server...
##[group]Configure the build
configure: processing command line
configure: 
configure: build.configure-args := ['--build=x86_64-unknown-linux-gnu', '--llvm-root=/usr/lib/llvm-18', '--enable-llvm-link-shared', '--set', 'rust.randomize-layout=true', '--set', 'rust.thin-lto-import-instr-limit=10', '--enable-verbose-configure', '--enable-sccache', '--disable-manage-submodules', '--enable-locked-deps', '--enable-cargo-native-static', '--set', 'rust.codegen-units-std=1', '--set', 'dist.compression-profile=balanced', '--dist-compression-formats=xz', '--set', 'rust.lld=false', '--disable-dist-src', '--release-channel=nightly', '--enable-debug-assertions', '--enable-overflow-checks', '--enable-llvm-assertions', '--set', 'rust.verify-llvm-ir', '--set', 'rust.codegen-backends=llvm,cranelift,gcc', '--set', 'llvm.static-libstdcpp', '--enable-new-symbol-mangling']
configure: target.x86_64-unknown-linux-gnu.llvm-config := /usr/lib/llvm-18/bin/llvm-config
configure: llvm.link-shared     := True
configure: rust.randomize-layout := True
configure: rust.thin-lto-import-instr-limit := 10
---
To only update this specific test, also pass `--test-args errors/trait-bound-error-spans/blame-trait-error.rs`

error: 1 errors occurred comparing output.
status: exit status: 1
command: env -u RUSTC_LOG_COLOR RUSTC_ICE="0" RUST_BACKTRACE="short" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/bin/rustc" "/checkout/tests/ui/errors/trait-bound-error-spans/blame-trait-error.rs" "-Zthreads=1" "-Zsimulate-remapped-rust-src-base=/rustc/FAKE_PREFIX" "-Ztranslate-remapped-path-to-local-path=no" "-Z" "ignore-directory-in-diagnostics-source-blocks=/cargo" "-Z" "ignore-directory-in-diagnostics-source-blocks=/checkout/vendor" "--sysroot" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2" "--target=x86_64-unknown-linux-gnu" "--check-cfg" "cfg(FALSE)" "--error-format" "json" "--json" "future-incompat" "-Ccodegen-units=1" "-Zui-testing" "-Zdeduplicate-diagnostics=no" "-Zwrite-long-types-to-disk=no" "-Cstrip=debuginfo" "--emit" "metadata" "-C" "prefer-dynamic" "--out-dir" "/checkout/obj/build/x86_64-unknown-linux-gnu/test/ui/errors/trait-bound-error-spans/blame-trait-error" "-A" "unused" "-A" "internal_features" "-Crpath" "-Cdebuginfo=0" "-Lnative=/checkout/obj/build/x86_64-unknown-linux-gnu/native/rust-test-helpers"
--- stderr -------------------------------
--- stderr -------------------------------
error[E0277]: the trait bound `Q: T3` is not satisfied
   |
   |
LL |     want(Wrapper { value: Burrito { filling: q } });
   |     ----                                     ^ the trait `T3` is not implemented for `Q`
   |     required by a bound introduced by this call
   |
   |
note: required for `Burrito<Q>` to implement `T2`
   |
   |
LL | impl<A: T3> T2 for Burrito<A> {}
   |         |
   |         unsatisfied trait bound introduced here
   |         unsatisfied trait bound introduced here
note: required for `Wrapper<Burrito<Q>>` to implement `T1`
   |
   |
LL | impl<B: T2> T1 for Wrapper<B> {}
   |         |
   |         unsatisfied trait bound introduced here
note: required by a bound in `want`
  --> /checkout/tests/ui/errors/trait-bound-error-spans/blame-trait-error.rs:25:12
  --> /checkout/tests/ui/errors/trait-bound-error-spans/blame-trait-error.rs:25:12
   |
LL | fn want<V: T1>(_x: V) {}
   |            ^^ required by this bound in `want`
help: consider restricting type parameter `Q` with trait `T3`
   |
LL | fn example<Q: T3>(q: Q) {

error[E0277]: `()` is not an iterator
##[error]  --> /checkout/tests/ui/errors/trait-bound-error-spans/blame-trait-error.rs:56:15
   |
---
   = help: the trait `T1` is implemented for `Option<It>`
note: required for `Option<()>` to implement `T1`
  --> /checkout/tests/ui/errors/trait-bound-error-spans/blame-trait-error.rs:21:20
   |
LL | impl<It: Iterator> T1 for Option<It> {}
   |          |
   |          unsatisfied trait bound introduced here
note: required by a bound in `want`
  --> /checkout/tests/ui/errors/trait-bound-error-spans/blame-trait-error.rs:25:12
  --> /checkout/tests/ui/errors/trait-bound-error-spans/blame-trait-error.rs:25:12
   |
LL | fn want<V: T1>(_x: V) {}
   |            ^^ required by this bound in `want`
error[E0277]: `Q` is not an iterator
##[error]  --> /checkout/tests/ui/errors/trait-bound-error-spans/blame-trait-error.rs:59:15
   |
   |
LL |     want(Some(q));
   |     ----      ^ `Q` is not an iterator
   |     required by a bound introduced by this call
   |
   |
note: required for `Option<Q>` to implement `T1`
   |
   |
LL | impl<It: Iterator> T1 for Option<It> {}
   |          |
   |          unsatisfied trait bound introduced here
note: required by a bound in `want`
  --> /checkout/tests/ui/errors/trait-bound-error-spans/blame-trait-error.rs:25:12
  --> /checkout/tests/ui/errors/trait-bound-error-spans/blame-trait-error.rs:25:12
   |
LL | fn want<V: T1>(_x: V) {}
   |            ^^ required by this bound in `want`
help: consider restricting type parameter `Q` with trait `Iterator`
   |
LL | fn example<Q: std::iter::Iterator>(q: Q) {

error[E0277]: `Q` is not an iterator
##[error]  --> /checkout/tests/ui/errors/trait-bound-error-spans/blame-trait-error.rs:62:16
   |
   |
LL |     want(&Some(q));
   |     ----       ^ `Q` is not an iterator
   |     required by a bound introduced by this call
   |
   |
note: required for `Option<Q>` to implement `T1`
   |
   |
LL | impl<It: Iterator> T1 for Option<It> {}
   |          |
   |          unsatisfied trait bound introduced here
   = note: 1 redundant requirement hidden
   = note: 1 redundant requirement hidden
   = note: required for `&Option<Q>` to implement `T1`
note: required by a bound in `want`
   |
   |
LL | fn want<V: T1>(_x: V) {}
   |            ^^ required by this bound in `want`
help: consider restricting type parameter `Q` with trait `Iterator`
   |
LL | fn example<Q: std::iter::Iterator>(q: Q) {


error[E0277]: the trait bound `Q: T3` is not satisfied
   |
   |
LL |     want(&ExampleTuple::ExampleTupleVariant(q));
   |     ----                                    ^ the trait `T3` is not implemented for `Q`
   |     required by a bound introduced by this call
   |
   |
note: required for `ExampleTuple<Q>` to implement `T1`
   |
   |
LL | impl<A> T1 for ExampleTuple<A> where A: T3 {}
   |         ^^     ^^^^^^^^^^^^^^^          -- unsatisfied trait bound introduced here
   = note: 1 redundant requirement hidden
   = note: required for `&ExampleTuple<Q>` to implement `T1`
note: required by a bound in `want`
   |
   |
LL | fn want<V: T1>(_x: V) {}
   |            ^^ required by this bound in `want`
help: consider restricting type parameter `Q` with trait `T3`
   |
LL | fn example<Q: T3>(q: Q) {


error[E0277]: the trait bound `Q: T3` is not satisfied
   |
   |
LL |     want(&ExampleTupleVariant(q));
   |     ----                      ^ the trait `T3` is not implemented for `Q`
   |     required by a bound introduced by this call
   |
   |
note: required for `ExampleTuple<Q>` to implement `T1`
   |
   |
LL | impl<A> T1 for ExampleTuple<A> where A: T3 {}
   |         ^^     ^^^^^^^^^^^^^^^          -- unsatisfied trait bound introduced here
   = note: 1 redundant requirement hidden
   = note: required for `&ExampleTuple<Q>` to implement `T1`
note: required by a bound in `want`
   |
   |
LL | fn want<V: T1>(_x: V) {}
   |            ^^ required by this bound in `want`
help: consider restricting type parameter `Q` with trait `T3`
   |
LL | fn example<Q: T3>(q: Q) {


error[E0277]: the trait bound `Q: T3` is not satisfied
   |
   |
LL |     want(&ExampleOtherTuple::ExampleTupleVariant(q));
   |     ----                                         ^ the trait `T3` is not implemented for `Q`
   |     required by a bound introduced by this call
   |
   |
note: required for `ExampleTuple<Q>` to implement `T1`
   |
   |
LL | impl<A> T1 for ExampleTuple<A> where A: T3 {}
   |         ^^     ^^^^^^^^^^^^^^^          -- unsatisfied trait bound introduced here
   = note: 1 redundant requirement hidden
   = note: required for `&ExampleTuple<Q>` to implement `T1`
note: required by a bound in `want`
   |
   |
LL | fn want<V: T1>(_x: V) {}
   |            ^^ required by this bound in `want`
help: consider restricting type parameter `Q` with trait `T3`
   |
LL | fn example<Q: T3>(q: Q) {


error[E0277]: the trait bound `Q: T3` is not satisfied
   |
   |
LL |     want(&ExampleDifferentTupleVariantName(q));
   |     ----                                   ^ the trait `T3` is not implemented for `Q`
   |     required by a bound introduced by this call
   |
   |
note: required for `ExampleTuple<Q>` to implement `T1`
   |
   |
LL | impl<A> T1 for ExampleTuple<A> where A: T3 {}
   |         ^^     ^^^^^^^^^^^^^^^          -- unsatisfied trait bound introduced here
   = note: 1 redundant requirement hidden
   = note: required for `&ExampleTuple<Q>` to implement `T1`
note: required by a bound in `want`
   |
   |
LL | fn want<V: T1>(_x: V) {}
   |            ^^ required by this bound in `want`
help: consider restricting type parameter `Q` with trait `T3`
   |
LL | fn example<Q: T3>(q: Q) {


error[E0277]: the trait bound `Q: T3` is not satisfied
   |
   |
LL |     want(&ExampleYetAnotherTupleVariantName(q));
   |     ----                                    ^ the trait `T3` is not implemented for `Q`
   |     required by a bound introduced by this call
   |
   |
note: required for `ExampleTuple<Q>` to implement `T1`
   |
   |
LL | impl<A> T1 for ExampleTuple<A> where A: T3 {}
   |         ^^     ^^^^^^^^^^^^^^^          -- unsatisfied trait bound introduced here
   = note: 1 redundant requirement hidden
   = note: required for `&ExampleTuple<Q>` to implement `T1`
note: required by a bound in `want`
   |
   |
LL | fn want<V: T1>(_x: V) {}
   |            ^^ required by this bound in `want`
help: consider restricting type parameter `Q` with trait `T3`
   |
LL | fn example<Q: T3>(q: Q) {


error[E0277]: the trait bound `Q: T3` is not satisfied
   |
   |
LL |     want(&ExampleStruct::ExampleStructVariant { field: q });
   |     ---- required by a bound introduced by this call   ^ the trait `T3` is not implemented for `Q`
   |
note: required for `ExampleStruct<Q>` to implement `T1`
   |
   |
LL | impl<A> T1 for ExampleStruct<A> where A: T3 {}
   |         ^^     ^^^^^^^^^^^^^^^^          -- unsatisfied trait bound introduced here
   = note: 1 redundant requirement hidden
   = note: required for `&ExampleStruct<Q>` to implement `T1`
note: required by a bound in `want`
   |
   |
LL | fn want<V: T1>(_x: V) {}
   |            ^^ required by this bound in `want`
help: consider restricting type parameter `Q` with trait `T3`
   |
LL | fn example<Q: T3>(q: Q) {


error[E0277]: the trait bound `Q: T3` is not satisfied
   |
   |
LL |     want(&ExampleStructVariant { field: q });
   |     ----                                ^ the trait `T3` is not implemented for `Q`
   |     required by a bound introduced by this call
   |
   |
note: required for `ExampleStruct<Q>` to implement `T1`
   |
   |
LL | impl<A> T1 for ExampleStruct<A> where A: T3 {}
   |         ^^     ^^^^^^^^^^^^^^^^          -- unsatisfied trait bound introduced here
   = note: 1 redundant requirement hidden
   = note: required for `&ExampleStruct<Q>` to implement `T1`
note: required by a bound in `want`
   |
   |
LL | fn want<V: T1>(_x: V) {}
   |            ^^ required by this bound in `want`
help: consider restricting type parameter `Q` with trait `T3`
   |
LL | fn example<Q: T3>(q: Q) {


error[E0277]: the trait bound `Q: T3` is not satisfied
   |
   |
LL |     want(&ExampleOtherStruct::ExampleStructVariant { field: q });
   |     ---- required by a bound introduced by this call        ^ the trait `T3` is not implemented for `Q`
   |
note: required for `ExampleStruct<Q>` to implement `T1`
   |
   |
LL | impl<A> T1 for ExampleStruct<A> where A: T3 {}
   |         ^^     ^^^^^^^^^^^^^^^^          -- unsatisfied trait bound introduced here
   = note: 1 redundant requirement hidden
   = note: required for `&ExampleStruct<Q>` to implement `T1`
note: required by a bound in `want`
   |
   |
LL | fn want<V: T1>(_x: V) {}
   |            ^^ required by this bound in `want`
help: consider restricting type parameter `Q` with trait `T3`
   |
LL | fn example<Q: T3>(q: Q) {


error[E0277]: the trait bound `Q: T3` is not satisfied
   |
   |
LL |     want(&ExampleDifferentStructVariantName { field: q });
   |     ---- required by a bound introduced by this call ^ the trait `T3` is not implemented for `Q`
   |
note: required for `ExampleStruct<Q>` to implement `T1`
   |
   |
LL | impl<A> T1 for ExampleStruct<A> where A: T3 {}
   |         ^^     ^^^^^^^^^^^^^^^^          -- unsatisfied trait bound introduced here
   = note: 1 redundant requirement hidden
   = note: required for `&ExampleStruct<Q>` to implement `T1`
note: required by a bound in `want`
   |
   |
LL | fn want<V: T1>(_x: V) {}
   |            ^^ required by this bound in `want`
help: consider restricting type parameter `Q` with trait `T3`
   |
LL | fn example<Q: T3>(q: Q) {


error[E0277]: the trait bound `Q: T3` is not satisfied
   |
   |
LL |     want(&ExampleYetAnotherStructVariantName { field: q });
   |     ---- required by a bound introduced by this call  ^ the trait `T3` is not implemented for `Q`
   |
note: required for `ExampleStruct<Q>` to implement `T1`
   |
   |
LL | impl<A> T1 for ExampleStruct<A> where A: T3 {}
   |         ^^     ^^^^^^^^^^^^^^^^          -- unsatisfied trait bound introduced here
   = note: 1 redundant requirement hidden
   = note: required for `&ExampleStruct<Q>` to implement `T1`
note: required by a bound in `want`
   |
   |
LL | fn want<V: T1>(_x: V) {}
   |            ^^ required by this bound in `want`
help: consider restricting type parameter `Q` with trait `T3`
   |
LL | fn example<Q: T3>(q: Q) {


error[E0277]: the trait bound `Q: T3` is not satisfied
   |
   |
LL |     want(&ExampleActuallyTupleStruct(q, 0));
   |     ----                             ^ the trait `T3` is not implemented for `Q`
   |     required by a bound introduced by this call
   |
   |
note: required for `ExampleActuallyTupleStruct<Q>` to implement `T1`
   |
   |
LL | impl<A> T1 for ExampleActuallyTupleStruct<A> where A: T3 {}
   |         ^^     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^          -- unsatisfied trait bound introduced here
   = note: 1 redundant requirement hidden
   = note: required for `&ExampleActuallyTupleStruct<Q>` to implement `T1`
note: required by a bound in `want`
   |
   |
LL | fn want<V: T1>(_x: V) {}
   |            ^^ required by this bound in `want`
help: consider restricting type parameter `Q` with trait `T3`
   |
LL | fn example<Q: T3>(q: Q) {


error[E0277]: the trait bound `Q: T3` is not satisfied
   |
   |
LL |     want(&ExampleActuallyTupleStructOther(q, 0));
   |     ----                                  ^ the trait `T3` is not implemented for `Q`
   |     required by a bound introduced by this call
   |
   |
note: required for `ExampleActuallyTupleStruct<Q>` to implement `T1`
   |
   |
LL | impl<A> T1 for ExampleActuallyTupleStruct<A> where A: T3 {}
   |         ^^     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^          -- unsatisfied trait bound introduced here
   = note: 1 redundant requirement hidden
   = note: required for `&ExampleActuallyTupleStruct<Q>` to implement `T1`
note: required by a bound in `want`
   |
   |
LL | fn want<V: T1>(_x: V) {}
   |            ^^ required by this bound in `want`
help: consider restricting type parameter `Q` with trait `T3`
   |
LL | fn example<Q: T3>(q: Q) {

error: aborting due to 16 previous errors

For more information about this error, try `rustc --explain E0277`.
---

50    |                                              |
51    |                                              the trait `From<impl Into<u32>>` is not implemented for `impl Debug`
52    |
+    = help: the following constraint is not satisfied: `impl Debug: From<impl Into<u32>>`
53    = help: the trait `Into<U>` is implemented for `T`
54    = note: required for `impl Into<u32>` to implement `Into<impl Debug>`

61    |                                  |
62    |                                  the trait `From<impl Into<u32>>` is not implemented for `impl Debug`
63    |
63    |
+    = help: the following constraint is not satisfied: `impl Debug: From<impl Into<u32>>`
64    = help: the trait `Into<U>` is implemented for `T`
65    = note: required for `impl Into<u32>` to implement `Into<impl Debug>`


The actual stderr differed from the expected stderr.
To update references, rerun the tests and pass the `--bless` flag
To update references, rerun the tests and pass the `--bless` flag
To only update this specific test, also pass `--test-args impl-trait/nested_impl_trait.rs`

error: 1 errors occurred comparing output.
status: exit status: 1
command: env -u RUSTC_LOG_COLOR RUSTC_ICE="0" RUST_BACKTRACE="short" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/bin/rustc" "/checkout/tests/ui/impl-trait/nested_impl_trait.rs" "-Zthreads=1" "-Zsimulate-remapped-rust-src-base=/rustc/FAKE_PREFIX" "-Ztranslate-remapped-path-to-local-path=no" "-Z" "ignore-directory-in-diagnostics-source-blocks=/cargo" "-Z" "ignore-directory-in-diagnostics-source-blocks=/checkout/vendor" "--sysroot" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2" "--target=x86_64-unknown-linux-gnu" "--check-cfg" "cfg(FALSE)" "--error-format" "json" "--json" "future-incompat" "-Ccodegen-units=1" "-Zui-testing" "-Zdeduplicate-diagnostics=no" "-Zwrite-long-types-to-disk=no" "-Cstrip=debuginfo" "--emit" "metadata" "-C" "prefer-dynamic" "--out-dir" "/checkout/obj/build/x86_64-unknown-linux-gnu/test/ui/impl-trait/nested_impl_trait" "-A" "unused" "-A" "internal_features" "-Crpath" "-Cdebuginfo=0" "-Lnative=/checkout/obj/build/x86_64-unknown-linux-gnu/native/rust-test-helpers"
--- stderr -------------------------------
error[E0666]: nested `impl Trait` is not allowed
##[error]  --> /checkout/tests/ui/impl-trait/nested_impl_trait.rs:6:56
   |
   |
LL | fn bad_in_ret_position(x: impl Into<u32>) -> impl Into<impl Debug> { x }
   |                                              |         |
   |                                              |         nested `impl Trait` here
   |                                              outer `impl Trait`


error[E0666]: nested `impl Trait` is not allowed
##[error]  --> /checkout/tests/ui/impl-trait/nested_impl_trait.rs:10:42
   |
LL | fn bad_in_fn_syntax(x: fn() -> impl Into<impl Debug>) {}
   |                                |         |
   |                                |         nested `impl Trait` here
   |                                outer `impl Trait`


error[E0666]: nested `impl Trait` is not allowed
##[error]  --> /checkout/tests/ui/impl-trait/nested_impl_trait.rs:14:37
   |
LL | fn bad_in_arg_position(_: impl Into<impl Debug>) { }
   |                           |         |
   |                           |         nested `impl Trait` here
   |                           outer `impl Trait`


error[E0666]: nested `impl Trait` is not allowed
##[error]  --> /checkout/tests/ui/impl-trait/nested_impl_trait.rs:19:44
   |
LL |     fn bad(x: impl Into<u32>) -> impl Into<impl Debug> { x }
   |                                  |         |
   |                                  |         nested `impl Trait` here
   |                                  outer `impl Trait`


error[E0562]: `impl Trait` is not allowed in `fn` pointer return types
##[error]  --> /checkout/tests/ui/impl-trait/nested_impl_trait.rs:10:32
   |
LL | fn bad_in_fn_syntax(x: fn() -> impl Into<impl Debug>) {}
   |
   = note: `impl Trait` is only allowed in arguments and return types of functions and methods


error[E0277]: the trait bound `impl Debug: From<impl Into<u32>>` is not satisfied
   |
   |
LL | fn bad_in_ret_position(x: impl Into<u32>) -> impl Into<impl Debug> { x }
   |                                              ^^^^^^^^^^^^^^^^^^^^^   - return type was inferred to be `impl Into<u32>` here
   |                                              the trait `From<impl Into<u32>>` is not implemented for `impl Debug`
   |
   = help: the following constraint is not satisfied: `impl Debug: From<impl Into<u32>>`
   = help: the trait `Into<U>` is implemented for `T`
   = help: the trait `Into<U>` is implemented for `T`
   = note: required for `impl Into<u32>` to implement `Into<impl Debug>`

error[E0277]: the trait bound `impl Debug: From<impl Into<u32>>` is not satisfied
   |
   |
LL |     fn bad(x: impl Into<u32>) -> impl Into<impl Debug> { x }
   |                                  ^^^^^^^^^^^^^^^^^^^^^   - return type was inferred to be `impl Into<u32>` here
   |                                  the trait `From<impl Into<u32>>` is not implemented for `impl Debug`
   |
   = help: the following constraint is not satisfied: `impl Debug: From<impl Into<u32>>`
   = help: the trait `Into<U>` is implemented for `T`
   = help: the trait `Into<U>` is implemented for `T`
   = note: required for `impl Into<u32>` to implement `Into<impl Debug>`
error: aborting due to 7 previous errors

Some errors have detailed explanations: E0277, E0562, E0666.
For more information about an error, try `rustc --explain E0277`.
---
diff of stderr:

8    |     --- return type was inferred to be `Bar` here
9    |
10    = help: the trait `PartialEq<(Foo, i32)>` is not implemented for `Bar`
+    = help: the following constraint is not satisfied: `Bar: PartialEq<(Foo, i32)>`
+    = help: the following constraint is not satisfied: `Bar: PartialEq<(Foo, i32)>`
11    = help: the trait `PartialEq<(Bar, i32)>` is implemented for `Bar`
13 error: aborting due to 1 previous error


The actual stderr differed from the expected stderr.
The actual stderr differed from the expected stderr.
To update references, rerun the tests and pass the `--bless` flag
To only update this specific test, also pass `--test-args impl-trait/recursive-type-alias-impl-trait-declaration.rs`

error: 1 errors occurred comparing output.
status: exit status: 1
command: env -u RUSTC_LOG_COLOR RUSTC_ICE="0" RUST_BACKTRACE="short" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/bin/rustc" "/checkout/tests/ui/impl-trait/recursive-type-alias-impl-trait-declaration.rs" "-Zthreads=1" "-Zsimulate-remapped-rust-src-base=/rustc/FAKE_PREFIX" "-Ztranslate-remapped-path-to-local-path=no" "-Z" "ignore-directory-in-diagnostics-source-blocks=/cargo" "-Z" "ignore-directory-in-diagnostics-source-blocks=/checkout/vendor" "--sysroot" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2" "--target=x86_64-unknown-linux-gnu" "--check-cfg" "cfg(FALSE)" "--error-format" "json" "--json" "future-incompat" "-Ccodegen-units=1" "-Zui-testing" "-Zdeduplicate-diagnostics=no" "-Zwrite-long-types-to-disk=no" "-Cstrip=debuginfo" "--emit" "metadata" "-C" "prefer-dynamic" "--out-dir" "/checkout/obj/build/x86_64-unknown-linux-gnu/test/ui/impl-trait/recursive-type-alias-impl-trait-declaration" "-A" "unused" "-A" "internal_features" "-Crpath" "-Cdebuginfo=0" "-Lnative=/checkout/obj/build/x86_64-unknown-linux-gnu/native/rust-test-helpers"
--- stderr -------------------------------
--- stderr -------------------------------
error[E0277]: can't compare `Bar` with `(Foo, i32)`
   |
LL | fn foo() -> Foo {
LL | fn foo() -> Foo {
   |             ^^^ no implementation for `Bar == (Foo, i32)`
LL |     //~^ ERROR can't compare `Bar` with `(Foo, i32)`
   |     --- return type was inferred to be `Bar` here
   |
   |
   = help: the trait `PartialEq<(Foo, i32)>` is not implemented for `Bar`
   = help: the following constraint is not satisfied: `Bar: PartialEq<(Foo, i32)>`
   = help: the following constraint is not satisfied: `Bar: PartialEq<(Foo, i32)>`
   = help: the trait `PartialEq<(Bar, i32)>` is implemented for `Bar`
error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0277`.
------------------------------------------
------------------------------------------


---- [ui] tests/ui/kindck/kindck-impl-type-params.rs stdout ----
Saved the actual stderr to "/checkout/obj/build/x86_64-unknown-linux-gnu/test/ui/kindck/kindck-impl-type-params/kindck-impl-type-params.stderr"
diff of stderr:

80 LL |     let a = t as Box<dyn Gettable<String>>;
82    |
+    = help: the following constraint is not satisfied: `String: Copy`
+    = help: the following constraint is not satisfied: `String: Copy`
83    = help: the trait `Gettable<T>` is implemented for `S<T>`
84 note: required for `S<String>` to implement `Gettable<String>`


96 LL |     let a: Box<dyn Gettable<Foo>> = t;
98    |
+    = help: the following constraint is not satisfied: `Foo: Copy`
+    = help: the following constraint is not satisfied: `Foo: Copy`
99    = help: the trait `Gettable<T>` is implemented for `S<T>`
100 note: required for `S<Foo>` to implement `Gettable<Foo>`


The actual stderr differed from the expected stderr.
To update references, rerun the tests and pass the `--bless` flag
To update references, rerun the tests and pass the `--bless` flag
To only update this specific test, also pass `--test-args kindck/kindck-impl-type-params.rs`

error: 1 errors occurred comparing output.
status: exit status: 1
command: env -u RUSTC_LOG_COLOR RUSTC_ICE="0" RUST_BACKTRACE="short" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/bin/rustc" "/checkout/tests/ui/kindck/kindck-impl-type-params.rs" "-Zthreads=1" "-Zsimulate-remapped-rust-src-base=/rustc/FAKE_PREFIX" "-Ztranslate-remapped-path-to-local-path=no" "-Z" "ignore-directory-in-diagnostics-source-blocks=/cargo" "-Z" "ignore-directory-in-diagnostics-source-blocks=/checkout/vendor" "--sysroot" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2" "--target=x86_64-unknown-linux-gnu" "--check-cfg" "cfg(FALSE)" "--error-format" "json" "--json" "future-incompat" "-Ccodegen-units=1" "-Zui-testing" "-Zdeduplicate-diagnostics=no" "-Zwrite-long-types-to-disk=no" "-Cstrip=debuginfo" "--emit" "metadata" "-C" "prefer-dynamic" "--out-dir" "/checkout/obj/build/x86_64-unknown-linux-gnu/test/ui/kindck/kindck-impl-type-params" "-A" "unused" "-A" "internal_features" "-Crpath" "-Cdebuginfo=0" "-Lnative=/checkout/obj/build/x86_64-unknown-linux-gnu/native/rust-test-helpers"
--- stderr -------------------------------
error[E0277]: `T` cannot be sent between threads safely
##[error]  --> /checkout/tests/ui/kindck/kindck-impl-type-params.rs:16:13
   |
   |
LL |     let a = &t as &dyn Gettable<T>;
   |             ^^ `T` cannot be sent between threads safely
   |
note: required for `S<T>` to implement `Gettable<T>`
   |
   |
LL | impl<T: Send + Copy + 'static> Gettable<T> for S<T> {}
   |         |
   |         unsatisfied trait bound introduced here
   |         unsatisfied trait bound introduced here
   = note: required for the cast from `&S<T>` to `&dyn Gettable<T>`
help: consider restricting type parameter `T` with trait `Send`
   |
LL | fn f<T: std::marker::Send>(val: T) {

error[E0277]: the trait bound `T: Copy` is not satisfied
##[error]  --> /checkout/tests/ui/kindck/kindck-impl-type-params.rs:16:13
   |
   |
LL |     let a = &t as &dyn Gettable<T>;
   |             ^^ the trait `Copy` is not implemented for `T`
   |
note: required for `S<T>` to implement `Gettable<T>`
   |
   |
LL | impl<T: Send + Copy + 'static> Gettable<T> for S<T> {}
   |                |
   |                unsatisfied trait bound introduced here
   |                unsatisfied trait bound introduced here
   = note: required for the cast from `&S<T>` to `&dyn Gettable<T>`
help: consider restricting type parameter `T` with trait `Copy`
   |
LL | fn f<T: std::marker::Copy>(val: T) {

error[E0277]: `T` cannot be sent between threads safely
##[error]  --> /checkout/tests/ui/kindck/kindck-impl-type-params.rs:23:31
   |
   |
LL |     let a: &dyn Gettable<T> = &t;
   |                               ^^ `T` cannot be sent between threads safely
   |
note: required for `S<T>` to implement `Gettable<T>`
   |
   |
LL | impl<T: Send + Copy + 'static> Gettable<T> for S<T> {}
   |         |
   |         unsatisfied trait bound introduced here
   |         unsatisfied trait bound introduced here
   = note: required for the cast from `&S<T>` to `&dyn Gettable<T>`
help: consider restricting type parameter `T` with trait `Send`
   |
LL | fn g<T: std::marker::Send>(val: T) {

error[E0277]: the trait bound `T: Copy` is not satisfied
##[error]  --> /checkout/tests/ui/kindck/kindck-impl-type-params.rs:23:31
   |
   |
LL |     let a: &dyn Gettable<T> = &t;
   |                               ^^ the trait `Copy` is not implemented for `T`
   |
note: required for `S<T>` to implement `Gettable<T>`
   |
   |
LL | impl<T: Send + Copy + 'static> Gettable<T> for S<T> {}
   |                |
   |                unsatisfied trait bound introduced here
   |                unsatisfied trait bound introduced here
   = note: required for the cast from `&S<T>` to `&dyn Gettable<T>`
help: consider restricting type parameter `T` with trait `Copy`
   |
LL | fn g<T: std::marker::Copy>(val: T) {

error[E0277]: the trait bound `String: Copy` is not satisfied
##[error]  --> /checkout/tests/ui/kindck/kindck-impl-type-params.rs:36:13
   |
   |
LL |     let a = t as Box<dyn Gettable<String>>;
   |
   = help: the following constraint is not satisfied: `String: Copy`
   = help: the following constraint is not satisfied: `String: Copy`
   = help: the trait `Gettable<T>` is implemented for `S<T>`
note: required for `S<String>` to implement `Gettable<String>`
   |
   |
LL | impl<T: Send + Copy + 'static> Gettable<T> for S<T> {}
   |                |
   |                unsatisfied trait bound introduced here
   |                unsatisfied trait bound introduced here
   = note: required for the cast from `Box<S<String>>` to `Box<dyn Gettable<String>>`
error[E0277]: the trait bound `Foo: Copy` is not satisfied
##[error]  --> /checkout/tests/ui/kindck/kindck-impl-type-params.rs:44:37
   |
   |
LL |     let a: Box<dyn Gettable<Foo>> = t;
   |
   = help: the following constraint is not satisfied: `Foo: Copy`
   = help: the following constraint is not satisfied: `Foo: Copy`
   = help: the trait `Gettable<T>` is implemented for `S<T>`
note: required for `S<Foo>` to implement `Gettable<Foo>`
   |
   |
LL | impl<T: Send + Copy + 'static> Gettable<T> for S<T> {}
   |                |
   |                unsatisfied trait bound introduced here
   |                unsatisfied trait bound introduced here
   = note: required for the cast from `Box<S<Foo>>` to `Box<dyn Gettable<Foo>>`
help: consider annotating `Foo` with `#[derive(Copy)]`
LL +     #[derive(Copy)]
LL |     struct Foo; // does not impl Copy
   |


error: lifetime may not live long enough
##[error]  --> /checkout/tests/ui/kindck/kindck-impl-type-params.rs:30:19
   |
LL | fn foo<'a>() {
   |        -- lifetime `'a` defined here
LL |     let t: S<&'a isize> = S(marker::PhantomData);
LL |     let a = &t as &dyn Gettable<&'a isize>;
   |                   ^^^^^^^^^^^^^^^^^^^^^^^^ type annotation requires that `'a` must outlive `'static`
error: aborting due to 7 previous errors

For more information about this error, try `rustc --explain E0277`.
------------------------------------------
------------------------------------------


---- [ui] tests/ui/type-alias-impl-trait/self-referential-4.rs stdout ----
Saved the actual stderr to "/checkout/obj/build/x86_64-unknown-linux-gnu/test/ui/type-alias-impl-trait/self-referential-4/self-referential-4.stderr"

7    |     - return type was inferred to be `&i32` here
8    |
8    |
9    = help: the trait `PartialEq<Bar<'b, 'static>>` is not implemented for `&i32`
+    = help: the following constraint is not satisfied: `i32: PartialEq<Bar<'static, 'static>>`
+    = help: the following constraint is not satisfied: `i32: PartialEq<Bar<'static, 'static>>`
11 
11 
12 error[E0277]: can't compare `&i32` with `Foo<'static, 'b>`
18    |     - return type was inferred to be `&i32` here
19    |
19    |
20    = help: the trait `PartialEq<Foo<'static, 'b>>` is not implemented for `&i32`
+    = help: the following constraint is not satisfied: `i32: PartialEq<Foo<'static, 'b>>`
+    = help: the following constraint is not satisfied: `i32: PartialEq<Foo<'static, 'b>>`
22 
22 
23 error[E0277]: can't compare `&i32` with `Moo<'static, 'a>`
29    |     - return type was inferred to be `&i32` here
30    |
30    |
31    = help: the trait `PartialEq<Moo<'static, 'a>>` is not implemented for `&i32`
+    = help: the following constraint is not satisfied: `i32: PartialEq<Moo<'static, 'static>>`
+    = help: the following constraint is not satisfied: `i32: PartialEq<Moo<'static, 'static>>`
33 
34 error: aborting due to 3 previous errors



The actual stderr differed from the expected stderr.
To update references, rerun the tests and pass the `--bless` flag
To only update this specific test, also pass `--test-args type-alias-impl-trait/self-referential-4.rs`

error: 1 errors occurred comparing output.
status: exit status: 1
command: env -u RUSTC_LOG_COLOR RUSTC_ICE="0" RUST_BACKTRACE="short" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/bin/rustc" "/checkout/tests/ui/type-alias-impl-trait/self-referential-4.rs" "-Zthreads=1" "-Zsimulate-remapped-rust-src-base=/rustc/FAKE_PREFIX" "-Ztranslate-remapped-path-to-local-path=no" "-Z" "ignore-directory-in-diagnostics-source-blocks=/cargo" "-Z" "ignore-directory-in-diagnostics-source-blocks=/checkout/vendor" "--sysroot" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2" "--target=x86_64-unknown-linux-gnu" "--check-cfg" "cfg(FALSE)" "--error-format" "json" "--json" "future-incompat" "-Ccodegen-units=1" "-Zui-testing" "-Zdeduplicate-diagnostics=no" "-Zwrite-long-types-to-disk=no" "-Cstrip=debuginfo" "--emit" "metadata" "-C" "prefer-dynamic" "--out-dir" "/checkout/obj/build/x86_64-unknown-linux-gnu/test/ui/type-alias-impl-trait/self-referential-4" "-A" "unused" "-A" "internal_features" "-Crpath" "-Cdebuginfo=0" "-Lnative=/checkout/obj/build/x86_64-unknown-linux-gnu/native/rust-test-helpers"
--- stderr -------------------------------
--- stderr -------------------------------
error[E0277]: can't compare `&i32` with `Bar<'b, 'static>`
   |
   |
LL | fn bar<'a, 'b>(i: &'a i32) -> Bar<'a, 'b> {
   |                               ^^^^^^^^^^^ no implementation for `&i32 == Bar<'b, 'static>`
LL |     i //~^ ERROR can't compare `&i32` with `Bar<'b, 'static>`
   |     - return type was inferred to be `&i32` here
   |
   = help: the trait `PartialEq<Bar<'b, 'static>>` is not implemented for `&i32`
   = help: the following constraint is not satisfied: `i32: PartialEq<Bar<'static, 'static>>`
   = help: the following constraint is not satisfied: `i32: PartialEq<Bar<'static, 'static>>`


error[E0277]: can't compare `&i32` with `Foo<'static, 'b>`
   |
   |
LL | fn foo<'a, 'b>(i: &'a i32) -> Foo<'a, 'b> {
   |                               ^^^^^^^^^^^ no implementation for `&i32 == Foo<'static, 'b>`
LL |     i //~^ ERROR can't compare `&i32` with `Foo<'static, 'b>`
   |     - return type was inferred to be `&i32` here
   |
   = help: the trait `PartialEq<Foo<'static, 'b>>` is not implemented for `&i32`
   = help: the following constraint is not satisfied: `i32: PartialEq<Foo<'static, 'b>>`
   = help: the following constraint is not satisfied: `i32: PartialEq<Foo<'static, 'b>>`


error[E0277]: can't compare `&i32` with `Moo<'static, 'a>`
   |
   |
LL | fn moo<'a, 'b>(i: &'a i32) -> Moo<'a, 'b> {
   |                               ^^^^^^^^^^^ no implementation for `&i32 == Moo<'static, 'a>`
LL |     i //~^ ERROR can't compare `&i32` with `Moo<'static, 'a>`
   |     - return type was inferred to be `&i32` here
   |
   = help: the trait `PartialEq<Moo<'static, 'a>>` is not implemented for `&i32`
   = help: the following constraint is not satisfied: `i32: PartialEq<Moo<'static, 'static>>`
   = help: the following constraint is not satisfied: `i32: PartialEq<Moo<'static, 'static>>`

error: aborting due to 3 previous errors

For more information about this error, try `rustc --explain E0277`.
---
diff of stderr:

8    |     - return type was inferred to be `&i32` here
9    |
10    = help: the trait `PartialEq<Bar<'b, 'a>>` is not implemented for `&i32`
+    = help: the following constraint is not satisfied: `i32: PartialEq<Bar<'a, 'b>>`
+    = help: the following constraint is not satisfied: `i32: PartialEq<Bar<'a, 'b>>`
12 
12 
13 error[E0277]: can't compare `&i32` with `(i32, Foo<'a, 'b>::{opaque#0}<'a, 'b>)`

The actual stderr differed from the expected stderr.
To update references, rerun the tests and pass the `--bless` flag
To only update this specific test, also pass `--test-args type-alias-impl-trait/self-referential.rs`
To only update this specific test, also pass `--test-args type-alias-impl-trait/self-referential.rs`

error: 1 errors occurred comparing output.
status: exit status: 1
command: env -u RUSTC_LOG_COLOR RUSTC_ICE="0" RUST_BACKTRACE="short" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/bin/rustc" "/checkout/tests/ui/type-alias-impl-trait/self-referential.rs" "-Zthreads=1" "-Zsimulate-remapped-rust-src-base=/rustc/FAKE_PREFIX" "-Ztranslate-remapped-path-to-local-path=no" "-Z" "ignore-directory-in-diagnostics-source-blocks=/cargo" "-Z" "ignore-directory-in-diagnostics-source-blocks=/checkout/vendor" "--sysroot" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2" "--target=x86_64-unknown-linux-gnu" "--check-cfg" "cfg(FALSE)" "--error-format" "json" "--json" "future-incompat" "-Ccodegen-units=1" "-Zui-testing" "-Zdeduplicate-diagnostics=no" "-Zwrite-long-types-to-disk=no" "-Cstrip=debuginfo" "--emit" "metadata" "-C" "prefer-dynamic" "--out-dir" "/checkout/obj/build/x86_64-unknown-linux-gnu/test/ui/type-alias-impl-trait/self-referential" "-A" "unused" "-A" "internal_features" "-Crpath" "-Cdebuginfo=0" "-Lnative=/checkout/obj/build/x86_64-unknown-linux-gnu/native/rust-test-helpers"
--- stderr -------------------------------
--- stderr -------------------------------
error[E0277]: can't compare `&i32` with `Bar<'b, 'a>`
   |
   |
LL | fn bar<'a, 'b>(i: &'a i32) -> Bar<'a, 'b> {
   |                               ^^^^^^^^^^^ no implementation for `&i32 == Bar<'b, 'a>`
LL |     //~^ ERROR can't compare `&i32` with `Bar<'b, 'a>`
   |     - return type was inferred to be `&i32` here
   |
   |
   = help: the trait `PartialEq<Bar<'b, 'a>>` is not implemented for `&i32`
   = help: the following constraint is not satisfied: `i32: PartialEq<Bar<'a, 'b>>`
   = help: the following constraint is not satisfied: `i32: PartialEq<Bar<'a, 'b>>`


error[E0277]: can't compare `&i32` with `(i32, Foo<'a, 'b>::{opaque#0}<'a, 'b>)`
   |
   |
LL | fn foo<'a, 'b>(i: &'a i32) -> Foo<'a, 'b> {
   |                               ^^^^^^^^^^^ no implementation for `&i32 == (i32, Foo<'a, 'b>::{opaque#0}<'a, 'b>)`
LL |     //~^ ERROR can't compare `&i32` with `(i32, Foo<'a, 'b>::{opaque#0}<'a, 'b>)`
LL |     (42, i)
   |     ------- return type was inferred to be `(i32, &i32)` here
   |
   = help: the trait `PartialEq<(i32, Foo<'a, 'b>::{opaque#0}<'a, 'b>)>` is not implemented for `&i32`


error[E0277]: can't compare `&i32` with `(i32, Moo<'b, 'a>::{opaque#0}<'b, 'a>)`
   |
   |
LL | fn moo<'a, 'b>(i: &'a i32) -> Moo<'a, 'b> {
   |                               ^^^^^^^^^^^ no implementation for `&i32 == (i32, Moo<'b, 'a>::{opaque#0}<'b, 'a>)`
LL |     //~^ ERROR can't compare `&i32` with `(i32, Moo<'b, 'a>::{opaque#0}<'b, 'a>)`
LL |     (42, i)
   |     ------- return type was inferred to be `(i32, &i32)` here
   |
   = help: the trait `PartialEq<(i32, Moo<'b, 'a>::{opaque#0}<'b, 'a>)>` is not implemented for `&i32`

error: aborting due to 3 previous errors

For more information about this error, try `rustc --explain E0277`.

@soareschen
Copy link
Author

From the CI failure, it looks like the patch introduces a lot of redundant hints with constraints that are basically equivalent to the original constraint.

I can try to work on hiding pending obligations that match the original. But I'm not quite sure how to do that within report_similar_impl_candidates. Given the pending PredicateObligation<'tcx>, I'm not quite sure what other values I should use, and whether I can perform the comparison with a different type.

Copy link
Member

@compiler-errors compiler-errors left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not totally certain what the motivation here is because there's no test and the issue you linked has a pretty large minimization, so I'm not actually sure what the root cause is and whether this is actually the best way to show that information.

Can you try to turn this into a more minimal example to try to explain better what information the compiler is missing in its diagnostics outputs?

Otherwise I'm not totally certain we can give good feedback on the approach in this PR, especially because making a totally new way of reporting unsatisfied predicates on a pretty common codepath (for unsatisfied impls) seems a bit excessive on first glance.

@@ -17,6 +17,8 @@ use crate::traits::Obligation;
pub enum ScrubbedTraitError<'tcx> {
/// A real error. This goal definitely does not hold.
TrueError,
// A select error with pending obligations
Select(PredicateObligations<'tcx>),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that it's not really documented, but this is antithetical to the design of ScrubbedTraitError. That type should be very lightweight.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally understand your concern. I tried this mainly as a quick hack. I did this because through my ad hoc debugging, I uncovered that select_where_possible contains the pending constraints that I needed to display within the error message. But since the returned details are erased via ScrubbedTraitError, I tried to pass on the pending obligations so that I can show it inside the error message.

I'm not sure what is the best way to workaround this, but at least with this quick hack, I'm able to use this fork of the Rust compiler to significantly simplify debugging for my projects. So regardless of which approach is taken, I probably do need to extract the pending obligations in one way or another and then surface it inside my error messages.

Btw, I have also considered alternative approaches, such as writing a compiler plugin to extract the pending obligations for my specific use cases. However, as far as I looked, I couldn't find ways to implement compiler plugins that help recover or better report compile errors. So I do hope that I can find some ways to fix this upstream in Rust, so that I don't need to instruct my users to use my custom fork of Rust compiler to use my project.

@soareschen
Copy link
Author

soareschen commented Dec 16, 2024

@compiler-errors thanks for the quick feedback! Following is my response:

Can you try to turn this into a more minimal example to try to explain better what information the compiler is missing in its diagnostics outputs?

I tried to further simplify the example code, but the original code I given is already the minimal example. This is because the issue only arise when there are at least two levels of indirections via two or more blanket implementations. When there is only one blanket implementation, the compiler actually gives correct diagnostic.

Click to show simplified code with correct error message

Compared to the original, we removed the DelegateComponent trait and the indirect delegation. (Playground)

// The following code demonstrates an incomplete error message returned from
// the Rust compiler, when there are unsatisfied constraints present in
// code that makes use of _context-generic programming_, as described at
// https://patterns.contextgeneric.dev/.
//
// More details about similar error is described here:
// https://patterns.contextgeneric.dev/debugging-techniques.html


// The trait to link a consumer trait with a provide trait, as explained by:
// https://patterns.contextgeneric.dev/consumer-provider-link.html#blanket-consumer-trait-implementation
pub trait HasComponents {
    type Components;
}

// Our example consumer trait, with the context type being the implicit `Self` type
pub trait CanFormatToString {
    fn format_to_string(&self) -> String;
}

// Our example provider trait, with the context type being the explicit `Context` type
pub trait StringFormatter<Context> {
    fn format_to_string(context: &Context) -> String;
}

// A blanket implementation that links the consumer `CanFormatToString` with
// the provider `StringFormatter`, using `HasComponents`
impl<Context> CanFormatToString for Context
where
    Context: HasComponents,
    Context::Components: StringFormatter<Context>,
{
    fn format_to_string(&self) -> String {
        Context::Components::format_to_string(self)
    }
}

// An example provider for `StringFormatter`, which has a generic
// implementation that requires `Context: debug`
pub struct FormatWithDebug;

impl<Context> StringFormatter<Context> for FormatWithDebug
where
    Context: core::fmt::Debug,
{
    fn format_to_string(context: &Context) -> String {
        format!("{:?}", context)
    }
}

// An example concrete context.
// Note: we pretend to forgot to derive `Debug` to cause error to be raised.
// FIXME: Uncomment the line below to fix the error.
// #[derive(Debug)]
pub struct Person {
    pub first_name: String,
    pub last_name: String,
}

// The components tied to the `Person` context
pub struct PersonComponents;

// Implement the consumer traits for `Person` using
// the aggregated provider `PersonComponents`
impl HasComponents for Person {
    type Components = FormatWithDebug;
}

// Checks that `Person` implements `CanFormatToString`
pub trait CanUsePerson: CanFormatToString {}

// This should raise an error, since we didn't implement `Debug` for `Person`
impl CanUsePerson for Person {}

With this, the error message does show that Person doesn't implement Debug:

error[E0277]: the trait bound `Person: CanFormatToString` is not satisfied
  --> crates/example/src/lib.rs:76:23
   |
76 | impl CanUsePerson for Person {}
   |                       ^^^^^^ the trait `Debug` is not implemented for `Person`, which is required by `Person: CanFormatToString`
   |
note: required for `FormatWithDebug` to implement `StringFormatter<Person>`
  --> crates/example/src/lib.rs:45:15
   |
45 | impl<Context> StringFormatter<Context> for FormatWithDebug
   |               ^^^^^^^^^^^^^^^^^^^^^^^^     ^^^^^^^^^^^^^^^
46 | where
47 |     Context: core::fmt::Debug,
   |              ---------------- unsatisfied trait bound introduced here
note: required for `Person` to implement `CanFormatToString`
  --> crates/example/src/lib.rs:31:15
   |
31 | impl<Context> CanFormatToString for Context
   |               ^^^^^^^^^^^^^^^^^     ^^^^^^^
...
34 |     Context::Components: StringFormatter<Context>,
   |                          ------------------------ unsatisfied trait bound introduced here
note: required by a bound in `CanUsePerson`
  --> crates/example/src/lib.rs:73:25
   |
73 | pub trait CanUsePerson: CanFormatToString {}
   |                         ^^^^^^^^^^^^^^^^^ required by this bound in `CanUsePerson`
help: consider annotating `Person` with `#[derive(Debug)]`
   |
58 + #[derive(Debug)]
59 | pub struct Person {
   |

I hope that at least the diff is small enough that you can see how the error message degraded with few lines of changes.

So I guess the issue does not arise if only one level of blanket implementation is involved. However, note that for my use case, the Rust code may contain arbitrary deeply nested use of blanket implementations.

especially because making a totally new way of reporting unsatisfied predicates on a pretty common codepath (for unsatisfied impls) seems a bit excessive on first glance.

Understandable. This is my first attempt in fixing the problem, and I'm sure there are better ways to do it. I just want to use this as a starting point for discussion, to help you better understand what I'm trying to solve. If you can give me hint on where else I should look to better handle my specific use case without affecting general use cases, I'm happy to make another attempt to work on this.

@compiler-errors
Copy link
Member

I tried to further simplify the example code, but the original code I given is already the minimal example.

This is definitely not a minimal example. A minimal example would have no extra details -- names could be simplified, unnecessary fields removed from structs, associated types can be removed if they're not necessary to demonstrate the issue.

Frankly, I think it's going to be pretty difficult to give the suggestion you want it to, because in the eyes of the compiler, both StringFormatter implementations could equally apply to the unsatisfied trait goal FormatWithDebug: StringFormatter<Person>. It doesn't seem clear how to encode the fact that it seems more obvious that we should derive Debug for Person (compared to implementing DelegateComponent for FormatWithDebug, which is in the eyes of the compiler equally as valid of a fix even if it doesn't make sense according to your API).

@soareschen
Copy link
Author

soareschen commented Dec 17, 2024

This is definitely not a minimal example. A minimal example would have no extra details -- names could be simplified, unnecessary fields removed from structs, associated types can be removed if they're not necessary to demonstrate the issue.

I see, thanks for the clarification! So I suppose it is more about minimizing the syntax, not the core semantics.

I have made another attempt to minimize the example based on your suggestion. Following is the code that produces helpful error message: (playground)

trait HasTarget {
    type Target;
}

trait SelfToStr {
    fn self_to_str(&self) -> String;
}

trait CtxToStr<Ctx> {
    fn ctx_to_str(ctx: &Ctx) -> String;
}

impl<Ctx> SelfToStr for Ctx
where
    Ctx: HasTarget,
    Ctx::Target: CtxToStr<Ctx>,
{
    fn self_to_str(&self) -> String {
        Ctx::Target::ctx_to_str(self)
    }
}

struct CtxToDebugStr;

impl<Ctx: core::fmt::Debug> CtxToStr<Ctx> for CtxToDebugStr {
    fn ctx_to_str(ctx: &Ctx) -> String {
        format!("{ctx:?}")
    }
}

// Uncomment to fix
// #[derive(Debug)]
struct Ctx;

impl HasTarget for Ctx {
    type Target = CtxToDebugStr;
}

trait UseCtx: SelfToStr {}

impl UseCtx for Ctx {}

The error message for the example code above is:

error[E0277]: the trait bound `Ctx: SelfToStr` is not satisfied
  --> crates/example/src/lib.rs:51:17
   |
51 | impl UseCtx for Ctx {}
   |                 ^^^ the trait `Debug` is not implemented for `Ctx`, which is required by `Ctx: SelfToStr`
   |
note: required for `CtxToDebugStr` to implement `CtxToStr<Ctx>`
  --> crates/example/src/lib.rs:35:29
   |
35 | impl<Ctx: core::fmt::Debug> CtxToStr<Ctx> for CtxToDebugStr {
   |           ----------------  ^^^^^^^^^^^^^     ^^^^^^^^^^^^^
   |           |
   |           unsatisfied trait bound introduced here
note: required for `Ctx` to implement `SelfToStr`
  --> crates/example/src/lib.rs:13:11
   |
13 | impl<Ctx> SelfToStr for Ctx
   |           ^^^^^^^^^     ^^^
...
16 |     Ctx::Target: CtxToStr<Ctx>,
   |                  ------------- unsatisfied trait bound introduced here
note: required by a bound in `UseCtx`
  --> crates/example/src/lib.rs:49:15
   |
49 | trait UseCtx: SelfToStr {}
   |               ^^^^^^^^^ required by this bound in `UseCtx`
help: consider annotating `Ctx` with `#[derive(Debug)]`
   |
43 + #[derive(Debug)]
44 | struct Ctx;
   |

However, adding just an extra blanket implementation is sufficient to break the error message: (playground)

impl<Ctx, T> CtxToStr<Ctx> for T
where
    T: HasTarget,
    T::Target: CtxToStr<Ctx>,
{
    fn ctx_to_str(ctx: &Ctx) -> String {
        T::Target::ctx_to_str(ctx)
    }
}

Now the error message becomes:

error[E0277]: the trait bound `CtxToDebugStr: CtxToStr<Ctx>` is not satisfied
  --> crates/example/src/lib.rs:40:17
   |
40 | impl UseCtx for Ctx {}
   |                 ^^^ the trait `CtxToStr<Ctx>` is not implemented for `CtxToDebugStr`, which is required by `Ctx: SelfToStr`
   |
   = help: the trait `CtxToStr<Ctx>` is implemented for `CtxToDebugStr`
note: required for `Ctx` to implement `SelfToStr`
  --> crates/example/src/lib.rs:13:11
   |
13 | impl<Ctx> SelfToStr for Ctx
   |           ^^^^^^^^^     ^^^
...
16 |     Ctx::Target: CtxToStr<Ctx>,
   |                  ------------- unsatisfied trait bound introduced here
note: required by a bound in `UseCtx`
  --> crates/example/src/lib.rs:38:15
   |
38 | trait UseCtx: SelfToStr {}
   |               ^^^^^^^^^ required by this bound in `UseCtx`

Frankly, I think it's going to be pretty difficult to give the suggestion you want it to, because in the eyes of the compiler, both StringFormatter implementations could equally apply to the unsatisfied trait goal FormatWithDebug: StringFormatter<Person>. It doesn't seem clear how to encode the fact that it seems more obvious that we should derive Debug for Person (compared to implementing DelegateComponent for FormatWithDebug, which is in the eyes of the compiler equally as valid of a fix even if it doesn't make sense according to your API).

I think that based on Rust's coherence rules, it should be clear that only one of the two potential implementations for StringFormatter (or CtxToStr in the minimized example) is applicable. Rust allows blanket implementations to be overridden in specific circumstances, and once the non-blanket implementation is allowed by the compiler, we can ignore the blanket implementation for that type when processing the error message.

Looking at the pending obigations, we can also see that the trait solver correctly ignores the blanket implementation, and does not list DelegateComponent (or HasTarget in the minimized example) as an unsatisfied goal.

For my fix in this PR, I am also inside the branch if let [single] = &impl_candidates { ... }. So my rough understanding for that is that the compiler does know that there is only one candidate implementation, with the blanket implementation already being ignored.

We can also see that from the current error report, the line the trait `CtxToStr<Ctx>` is not implemented for `CtxToDebugStr` is immediately followed by the line help: the trait `CtxToStr<Ctx>` is implemented for `CtxToDebugStr` , which is both unhelpful and contradictory.

When I have the free time, I plan to make another debugging dive into the compiler and see which other branch was taken when the blanket implementation was added. I'm guessing that perhaps if we can ignore the blanket implementation, then Rust would potentially print back out the helpful error message, both for the simplified example and for more complex cases.

@bors
Copy link
Collaborator

bors commented Apr 4, 2025

☔ The latest upstream changes (presumably #139354) made this pull request unmergeable. Please resolve the merge conflicts.

@wesleywiser
Copy link
Member

It sounds like this is waiting on the outcome of that compiler debugging dive so I'm going to mark this as waiting on author.

Since this currently modifies trait system internals, I'd rather let Michael review and approve when he's comfortable with the changes 🙂

r? @compiler-errors

@wesleywiser wesleywiser added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Apr 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
6 participants