Skip to content
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

Reject unconstrained lifetimes in type_of(assoc_ty) instead of during wfcheck of the impl item #127973

Closed
wants to merge 4 commits into from

Conversation

oli-obk
Copy link
Contributor

@oli-obk oli-obk commented Jul 19, 2024

Opened to discuss as part of https://rust-lang.zulipchat.com/#narrow/stream/144729-t-types/topic/unconstrained.20lifetimes.20in.20impls

Lifetimes are allowed to be unconstrained on impls because it made some libcore macros simpler and it was a breaking change to fix it. This becomes a problem when the lifetime is used in a nonconstraining way (e.g. in an associated item), but in a way that it can be used (e.g. via associated types). Associated functions are not an issue, because they must already have a compatible signature to the trait's assoc function decls, which means unconstrained lifetimes can't be a part of them (except via assoc types).

While the current system is sound, it also doesn't mention in the diagnostic why the lifetime is not permitted when it is permitted in other situations.

Instead of rejecting those lifetimes in wfcheck, we now reject them in type_of of associated types. Thus, during hir ty lowering of assoc types, we report any types with unconstrained lifetimes. since this is happening on the hir, we have a fairly good span to report to the user.

We could instead walk the hir and look for the offending lifetime (just for improving the span), but I have a deeper reason for this change:

We can now move the remainder of the check from wfcheck to explicit_predicates_of, which already computes the necessary information, and is on a regular query code path, so we can taint it and suppress follow-up errors and ICEs

fixes #123141
fixes #124350
fixes #125874
fixes #126942

@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 Jul 19, 2024
@oli-obk oli-obk force-pushed the uplift_wf_checks branch from aaaed98 to 403b050 Compare July 20, 2024 13:43
@rust-log-analyzer

This comment has been minimized.

@oli-obk oli-obk force-pushed the uplift_wf_checks branch from 403b050 to dbbcbf9 Compare July 22, 2024 09:56
MyFrom::my_from(self.0).ok().unwrap()
}
}

fn main() {
let _pos: Phantom1<DummyT<()>> = Scope::new().my_index();
//~^ ERROR: no method named `my_index` found for struct `Scope`
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This one is funny and should probably have some more taint tracking to get eliminated (same with the TAIT cycle above)

pub struct GenericPredicates<'tcx> {
pub parent: Option<DefId>,
pub predicates: &'tcx [(Clause<'tcx>, Span)],
pub effects_min_tys: &'tcx ty::List<Ty<'tcx>>,
pub tainted_by_errors: Result<(), ErrorGuaranteed>,
Copy link
Member

Choose a reason for hiding this comment

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

Explain what it means to be tainted here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. If preferred I can also register a dummy predicate {error}: Sized or sth, that would probably result in similar behaviour

@oli-obk
Copy link
Contributor Author

oli-obk commented Jul 23, 2024

FWIW I tried an alternative design that would taint impl_trait_header instead of predicates_of, but that does not work at this time, because if generic parameters are used in bounds before their own bounds a processed, we get bogus ambiguity errors. I hope that's a thing the next solver will also fix, as it's quite funky to rely on that. See https://github.com/oli-obk/rust/pull/new/uplift_wf_checks2 for the branch with the other approach.

while these can be resolved (see https://github.com/oli-obk/rust/pull/new/uplift_wf_checks3), we still have several new cycles:

  • type_of(assoc_ty) checks whether impl_trait_header is None in order to do inherent assoc ty feature gating (easily resolved by checking the hir for of_trait)
  • fn_sig(assoc_fn) if the signature contains Self::Foo, we'll need the trait and impl self type to produce all assoc items with the right name (possibly from super traits).

@oli-obk oli-obk force-pushed the uplift_wf_checks branch from 61cec80 to 719d6e8 Compare July 23, 2024 09:25
@rust-log-analyzer

This comment has been minimized.

@oli-obk oli-obk force-pushed the uplift_wf_checks branch from 719d6e8 to 5229b23 Compare July 23, 2024 10:40
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.

Regarding SelectionError -- I wonder if we should instead treat the goal as successful but constrain all the variables with Error instead.

@oli-obk oli-obk force-pushed the uplift_wf_checks branch from 5229b23 to ecab78c Compare July 24, 2024 08:55
@rust-log-analyzer

This comment has been minimized.

Comment on lines +637 to +604
if let Err(guar) = unsatisfied_predicates.error_reported() {
return guar;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is necessary to avoid bogus diagnostics like


error[E0599]: the method `my_index` exists for struct `Scope<_>`, but its trait bounds were not satisfied
  --> $DIR/ice-failed-to-resolve-instance-for-110696.rs:50:51
   |
LL | struct Scope<T>(Phantom2<DummyT<T>>);
   | --------------- method `my_index` not found for this struct because it doesn't satisfy `Scope<{type error}>: MyIndex<DummyT<{type error}>>`
...
LL |     let _pos: Phantom1<DummyT<()>> = Scope::new().my_index();
   |                                                   ^^^^^^^^ method cannot be called on `Scope<_>` due to unsatisfied trait bounds
   |
note: trait bound `{type error}: Sized` was not satisfied
  --> $DIR/ice-failed-to-resolve-instance-for-110696.rs:41:6
   |
LL | impl<T: MyFrom<Phantom2<DummyT<U>>>, U> MyIndex<DummyT<T>> for Scope<U> {
   |      ^                               ^  ------------------     --------
   |      |                               |
   |      |                               unsatisfied trait bound introduced here
   |      unsatisfied trait bound introduced here
   = help: items from traits can only be used if the trait is implemented and in scope
note: `MyIndex` defines an item `my_index`, perhaps you need to implement it
  --> $DIR/ice-failed-to-resolve-instance-for-110696.rs:9:1
   |
LL | trait MyIndex<T> {
   | ^^^^^^^^^^^^^^^^
help: consider relaxing the type parameter's implicit `Sized` bound
   |
LL | impl<T: ?Sized + MyFrom<Phantom2<DummyT<U>>>, U> MyIndex<DummyT<T>> for Scope<U> {
   |         ++++++++
help: consider relaxing the type parameter's implicit `Sized` bound
   |
LL | impl<T: MyFrom<Phantom2<DummyT<U>>>, U: ?Sized> MyIndex<DummyT<T>> for Scope<U> {
   |                                       ++++++++

Not sure yet how to generally avoid these (I don't want to just hide the predicate, I want to hide the entire error), but this change doesn't make things worse really, so 🤷

@oli-obk oli-obk force-pushed the uplift_wf_checks branch from ecab78c to a1ad9fd Compare July 24, 2024 10:55
@bors
Copy link
Contributor

bors commented Jul 29, 2024

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

@oli-obk oli-obk force-pushed the uplift_wf_checks branch from a1ad9fd to 4938b6f Compare July 29, 2024 08:17
@rust-log-analyzer

This comment has been minimized.

@oli-obk oli-obk force-pushed the uplift_wf_checks branch from 4938b6f to ed019dc Compare July 29, 2024 08:58
@rust-log-analyzer

This comment has been minimized.

@oli-obk oli-obk force-pushed the uplift_wf_checks branch from ed019dc to 525f049 Compare July 29, 2024 09:35
@rust-log-analyzer

This comment has been minimized.

@oli-obk oli-obk force-pushed the uplift_wf_checks branch from 525f049 to 12a5681 Compare July 31, 2024 15:52
@rust-log-analyzer

This comment has been minimized.

@Dylan-DPC Dylan-DPC 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 Aug 21, 2024
@alex-semenyuk alex-semenyuk removed the S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. label Oct 8, 2024
@alex-semenyuk alex-semenyuk added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Oct 8, 2024
@Dylan-DPC Dylan-DPC 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 Nov 3, 2024
@rust-log-analyzer

This comment has been minimized.

@compiler-errors
Copy link
Member

compiler-errors commented Dec 3, 2024

Of all of the tests you've linked, as far as I can tell only only one of them continues to ICE (#125874). Am I testing them wrong, or is this true?

I think we should probably reformulate this PR; it seems a bit too invasive for me to be comfortable with landing it, and I think we can find an approach that requires tracking less state than what this PR does.

@oli-obk
Copy link
Contributor Author

oli-obk commented Dec 4, 2024

I think we should probably reformulate this PR; it seems a bit too invasive for me to be comfortable with landing it, and I think we can find an approach that requires tracking less state than what this PR does.

I wonder if we can get most of the way just by stripping lifetimes that are unmentioned in the signature in lowering or generics_of, and then making the error reporting on "unknown lifetime" figure out that it's an unconstrained one

@bors
Copy link
Contributor

bors commented Dec 8, 2024

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

bors added a commit to rust-lang-ci/rust that referenced this pull request Jan 3, 2025
…d, r=<try>

Project to `TyKind::Error` when there are unconstrained non-lifetime (ty/const) impl params

I think this is a bit less invasive of an approach compared to rust-lang#127973.

It splits the `enforce_impl_params_are_constrained` function into lifetime/non-lifetime, and queryfies the latter. We can then use the result of the latter query (`Result<(), ErrorGuaranteed>`) to intercept projection and constrain the projected type to `TyKind::Error`, which ensures that we leak no ty or const vars to places that don't expect them, like `normalize_erasing_regions`.

The reason we split `enforce_impl_params_are_constrained` into two parts is because we only error for *lifetimes* if the lifetime ends up showing up in any of the associated types of the impl (e.g. we allow `impl<'a> Foo { type Assoc = (); }`). However, in order to compute the `type_of` query for the anonymous associated type of an RPITIT, we need to do trait solving (in `query collect_return_position_impl_trait_in_trait_tys`). That would induce cycles. Luckily, it turns out for lifetimes we don't even care about if they're unconstrained, since they're erased in all contexts that we are trying to fix ICEs. So it's sufficient to keep this check separated out of the query.

Fixes rust-lang#123141
Fixes rust-lang#125874
Fixes rust-lang#126942
Fixes rust-lang#127804
Fixes rust-lang#130967

r? oli-obk
bors added a commit to rust-lang-ci/rust that referenced this pull request Jan 3, 2025
…d, r=<try>

Project to `TyKind::Error` when there are unconstrained non-lifetime (ty/const) impl params

It splits the `enforce_impl_params_are_constrained` function into lifetime/non-lifetime, and queryfies the latter. We can then use the result of the latter query (`Result<(), ErrorGuaranteed>`) to intercept projection and constrain the projected type to `TyKind::Error`, which ensures that we leak no ty or const vars to places that don't expect them, like `normalize_erasing_regions`.

The reason we split `enforce_impl_params_are_constrained` into two parts is because we only error for *lifetimes* if the lifetime ends up showing up in any of the associated types of the impl (e.g. we allow `impl<'a> Foo { type Assoc = (); }`). However, in order to compute the `type_of` query for the anonymous associated type of an RPITIT, we need to do trait solving (in `query collect_return_position_impl_trait_in_trait_tys`). That would induce cycles. Luckily, it turns out for lifetimes we don't even care about if they're unconstrained, since they're erased in all contexts that we are trying to fix ICEs. So it's sufficient to keep this check separated out of the query.

I think this is a bit less invasive of an approach compared to rust-lang#127973. The major difference between this PR and that PR is that we queryify the check instead of merging it into the `explicit_predicates_of` query, and we use the result to taint just projection goals, rather than trait goals too. This doesn't require a lot of new tracking in `ItemCtxt` and `GenericPredicates`, and it also seems to not require any other changes to typeck like that PR did.

Fixes rust-lang#123141
Fixes rust-lang#125874
Fixes rust-lang#126942
Fixes rust-lang#127804
Fixes rust-lang#130967

r? oli-obk
@oli-obk oli-obk closed this Jan 3, 2025
bors added a commit to rust-lang-ci/rust that referenced this pull request Jan 3, 2025
…d, r=oli-obk

Project to `TyKind::Error` when there are unconstrained non-lifetime (ty/const) impl params

It splits the `enforce_impl_params_are_constrained` function into lifetime/non-lifetime, and queryfies the latter. We can then use the result of the latter query (`Result<(), ErrorGuaranteed>`) to intercept projection and constrain the projected type to `TyKind::Error`, which ensures that we leak no ty or const vars to places that don't expect them, like `normalize_erasing_regions`.

The reason we split `enforce_impl_params_are_constrained` into two parts is because we only error for *lifetimes* if the lifetime ends up showing up in any of the associated types of the impl (e.g. we allow `impl<'a> Foo { type Assoc = (); }`). However, in order to compute the `type_of` query for the anonymous associated type of an RPITIT, we need to do trait solving (in `query collect_return_position_impl_trait_in_trait_tys`). That would induce cycles. Luckily, it turns out for lifetimes we don't even care about if they're unconstrained, since they're erased in all contexts that we are trying to fix ICEs. So it's sufficient to keep this check separated out of the query.

I think this is a bit less invasive of an approach compared to rust-lang#127973. The major difference between this PR and that PR is that we queryify the check instead of merging it into the `explicit_predicates_of` query, and we use the result to taint just projection goals, rather than trait goals too. This doesn't require a lot of new tracking in `ItemCtxt` and `GenericPredicates`, and it also seems to not require any other changes to typeck like that PR did.

Fixes rust-lang#123141
Fixes rust-lang#125874
Fixes rust-lang#126942
Fixes rust-lang#127804
Fixes rust-lang#130967

r? oli-obk
bors added a commit to rust-lang-ci/rust that referenced this pull request Jan 4, 2025
…d, r=oli-obk

Project to `TyKind::Error` when there are unconstrained non-lifetime (ty/const) impl params

It splits the `enforce_impl_params_are_constrained` function into lifetime/non-lifetime, and queryfies the latter. We can then use the result of the latter query (`Result<(), ErrorGuaranteed>`) to intercept projection and constrain the projected type to `TyKind::Error`, which ensures that we leak no ty or const vars to places that don't expect them, like `normalize_erasing_regions`.

The reason we split `enforce_impl_params_are_constrained` into two parts is because we only error for *lifetimes* if the lifetime ends up showing up in any of the associated types of the impl (e.g. we allow `impl<'a> Foo { type Assoc = (); }`). However, in order to compute the `type_of` query for the anonymous associated type of an RPITIT, we need to do trait solving (in `query collect_return_position_impl_trait_in_trait_tys`). That would induce cycles. Luckily, it turns out for lifetimes we don't even care about if they're unconstrained, since they're erased in all contexts that we are trying to fix ICEs. So it's sufficient to keep this check separated out of the query.

I think this is a bit less invasive of an approach compared to rust-lang#127973. The major difference between this PR and that PR is that we queryify the check instead of merging it into the `explicit_predicates_of` query, and we use the result to taint just projection goals, rather than trait goals too. This doesn't require a lot of new tracking in `ItemCtxt` and `GenericPredicates`, and it also seems to not require any other changes to typeck like that PR did.

Fixes rust-lang#123141
Fixes rust-lang#125874
Fixes rust-lang#126942
Fixes rust-lang#127804
Fixes rust-lang#130967

r? oli-obk
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
7 participants