-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat: Implement Return Position Impl Trait In Traits correctly #19394
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
base: master
Are you sure you want to change the base?
Conversation
($id:ident, $loc:ident) => { | ||
#[salsa::interned(no_debug, no_lifetime)] | ||
pub struct $id { | ||
#[return_ref] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Next salsa will hopefully flip this to be the default mode (once someone implements it salsa-rs/salsa#719)
b86adf9
to
1922299
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not the most thorough review, but i focused mostly on the docs and explanations as to how this works
(note: i don't really understand show this works)
// In this situation, we don't know even that the trait and impl generics match, therefore | ||
// the only binders we can give to comply with the trait's binders are the trait's binders. | ||
// However, for impl associated types chalk wants only their own generics, excluding | ||
// those of the impl (unlike in traits), therefore we filter them here. | ||
// Completely unlike the docs, Chalk requires both the impl generics and the associated type | ||
// generics in the binder. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Slight rewrite for clarity and emphasis of some key points. Note that I don't really understand much in hir-ty
, so my rewrite might not be correct, but one of my goals in rewriting this was to better understand hir-ty
!
// In this situation, we don't know even that the trait and impl generics match, therefore | |
// the only binders we can give to comply with the trait's binders are the trait's binders. | |
// However, for impl associated types chalk wants only their own generics, excluding | |
// those of the impl (unlike in traits), therefore we filter them here. | |
// Completely unlike the docs, Chalk requires both the impl generics and the associated type | |
// generics in the binder. | |
// In this situation, we don't know that the trait and impl generics even match. Therefore, | |
// the only binders we can offer to comply with the trait's binders are the trait's binders. | |
// This is tautological. | |
// | |
// However, for impl-associated types, Chalk wants only the type's *own* generics, excluding | |
// those of the `impl`. This is unlike the behavior in traits, making it necessary to filter | |
// out all other generics. | |
// | |
// Note that unlike Chalk's documentation, Chalk, in reality, requires both the | |
// impl generics and the associated type generics to be in the binder. |
Two clarifying questions:
- What are "impl associated types"?
- What does Chalk's documentation actually say that you're doing differently here?
match from_assoc_type_id(db, id) { | ||
AnyTraitAssocType::Normal(type_alias) => match type_alias.lookup(db.upcast()).container { | ||
ItemContainerId::TraitId(trait_id) => trait_id, | ||
_ => panic!("`AssocTypeId` without parent trait"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_ => panic!("`AssocTypeId` without parent trait"), | |
_ => panic!("`AssocTypeId` without parent trait; this is a bug."), |
(The "this is a bug" suffix is a thing we do in tracing
; I've found it makes people more proactive in reporting bugs)
// FIXME: This isn't entirely correct, the generics of the RPITIT assoc type may differ from its method | ||
// wrt. lifetimes, but we don't handle that currently. See https://rustc-dev-guide.rust-lang.org/return-position-impl-trait-in-trait.html. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// FIXME: This isn't entirely correct, the generics of the RPITIT assoc type may differ from its method | |
// wrt. lifetimes, but we don't handle that currently. See https://rustc-dev-guide.rust-lang.org/return-position-impl-trait-in-trait.html. | |
// FIXME: This isn't entirely correct, the generics of the RPITIT assoc type may differ from its method | |
// with regard to lifetimes, but rust-analyzer doesn't really handle lifetimes in the first place, so whatever. | |
// For details how on how to handle this properly (e.g., as rustc does), see | |
// https://rustc-dev-guide.rust-lang.org/return-position-impl-trait-in-trait.html. |
AnyTraitAssocType::Normal(it) => it, | ||
AnyTraitAssocType::Rpitit(_) => { | ||
unreachable!( | ||
"Rust does not currently have a way to specify alias equation on RPITIT" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I'd... use "equation". It's correct, I guess, but might be a bit too impose if a user sees this error message (can they see this error message)?
"Rust does not currently have a way to specify alias equation on RPITIT" | |
"Rust does not currently have a way to specify aliases on RPITIT" |
AnyTraitAssocType::Normal(it) => it, | ||
AnyTraitAssocType::Rpitit(_) => { | ||
unreachable!( | ||
"Rust does not currently have a way to specify alias equation on RPITIT" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same nit about "equation" as above.
/// fn foo(&self) -> Option<impl Debug>; | ||
/// } | ||
/// ``` | ||
/// The equation will tell us that the hidden associated type has value `Option<impl Debug>` (note: this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// The equation will tell us that the hidden associated type has value `Option<impl Debug>` (note: this | |
/// The above process will tell us that the hidden associated type is `Option<impl Debug>` (note: this |
/// An associated type synthesized from a Return Position Impl Trait In Trait | ||
/// of the trait (not the impls). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// An associated type synthesized from a Return Position Impl Trait In Trait | |
/// of the trait (not the impls). | |
/// An associated type synthesized from a Return Position Impl Trait In Trait. | |
/// of the *trait*. | |
/// | |
/// This is not come from the the impls; see [`RpititImplAssocTy`] instead. |
/// An associated type synthesized from a Return Position Impl Trait In Trait | ||
/// of the impl (not the trait). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// An associated type synthesized from a Return Position Impl Trait In Trait | |
/// of the impl (not the trait). | |
/// An associated type synthesized from a Return Position Impl Trait In Trait | |
/// of the *impl*. | |
/// | |
/// This is not come from the the trait; see [`RpititTraitAssocTy`] instead. |
/// The generics are the generics of the method (with some modifications that we | ||
/// don't currently implement, see https://rustc-dev-guide.rust-lang.org/return-position-impl-trait-in-trait.html). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The things we don't implement is the lifetime lowering, right?
crates/hir-ty/src/rpitit.rs
Outdated
impl_id: ImplId, | ||
trait_method_id: FunctionId, | ||
) -> Box<[Arc<AssociatedTyValue>]> { | ||
tracing::error!("impl_method_rpitit_values()"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you want this?
tracing::error!("impl_method_rpitit_values()"); | |
tracing::span!("impl_method_rpitit_values", Level::INFO); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was a debugging aid, I pushed the PR in a not-ready-for-review state because @compiler-errors wanted to debug the Chalk issue.
This was blocked on rust-lang/chalk#826, which I think still requires a release. |
Wondering if this also fixes #19439 |
0c7e786
to
98fb8c6
Compare
They don't exist in hir-def, only hir-ty. The idea is that the trait/impl datum query will compute and intern them, then other queries can treat them as (partially) normal associated types. (Lowering RPITIT to synthesized associated types, like rustc does, is required to properly support Return Type Notation).
98fb8c6
to
4966781
Compare
Instead of lowering them as opaque type, in the trait they should get lowered into a synthesized associated type. Opaques "mostly worked" here, but they break when trying to implement Return Type Notation, which is essentially a way to refer in code to this synthesized associated type. So we need to do the correct thing.
It broke due to the RPITIT changes. And also make it more robust, by relying on semantic instead of textual match.
The previous setup led to a cycle in scenario like the following: ```rust trait Trait { type Assoc; fn foo() -> impl Sized; } impl Trait for () { type Assoc = (); fn foo() -> Self::Assoc; } ``` Because we asked Chalk to normalize the return type of the method, and for that it asked us the datum of the impl, which again causes us to evaluate the RPITITs. Instead, we only put the associated type ID in `impl_datum()`, and we compute it on `associated_ty_value()`. This still causes a cycle because Chalk needlessly evaluates all associated types for an impl, but at least it'll work for the new trait solver, and compiler-errors will try to fix that for Chalk too.
Chalk apparently requires associated type values to contain both the impl and the associated ty binders, completely unlike the docs (which say they should only contain those of the associated type). So fix that.
Here, we lower the method's return type twice: once with RPITITs as assoc types, and once with the RPITITs as opaques, as if it was repeated on the impl.
Cycles can occur in malformed code (this is only a conjecture) or from Chalk bugs (for this I have a test).
Inside the method, we should insert an environment clause `RpititAssoc = Ty` so we know the value of the associated type. This matters mostly for defaulted trait methods, because for them without this calling the same method from the same trait will return the RPITIT and not the opaque type, but it also matters for impl methods, and will matter even more once we start reporting trait errors, as then RTN bounds could cause errors on the methods without this. Unfortunately that means we have to separate `trait_environment()` from `trait_environment_for_body()` (i.e. make the latter no longer a transparent wrapper around the former but a real query), because the RPITIT infra needs to call `trait_environment()` but `trait_environment_for_body()` needs to call it for the `AliasEq` clauses. This means another query, more memory usage etc.. But looks like it's unavoidable.
4966781
to
2b201a1
Compare
Okay, I think this is ready for review. I sincerely request: this PR is not small, and even less simple, and went through a rebase over a commit that changes a fundamental fact about Chalk - in which order we store generic parameters. I had many bugs, and I am sure there are more. I will really appreciate a throughout review, even more than one. If you feel like you know the ty area of rust-analyzer well enough and agree to do so, please review! Okay, now to the boring facts: This PR is a prerequisite to supporting Return Type Notation (and also correctly support Return Position Impl Trait In Trait in general). So it may be surprising that this PR, aimed to improve the correctness of our types, actually regresses unknown types on self (by a bit). This is due to bugs in Chalk that cause cycles, and it will be fixed by the switch to the new trait solver. Speed and memory usage also regress (although not by much). The main reason is that there are now two queries for trait environment: |
Having to do with how we rebase the impl params onto the trait params.
This is a requirement to landing Return Type Notation.
Phew! That is not a lot of code, but it was pretty complicated. I'm still not 100% sure I did everything correctly, so I will appreciate a thorough review.