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

feat: Implement Return Position Impl Trait In Traits correctly #19394

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

ChayimFriedman2
Copy link
Contributor

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.

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).
@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Mar 18, 2025
@ChayimFriedman2 ChayimFriedman2 marked this pull request as draft March 18, 2025 20:13
($id:ident, $loc:ident) => {
#[salsa::interned(no_debug, no_lifetime)]
pub struct $id {
#[return_ref]
Copy link
Member

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)

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.
@ChayimFriedman2 ChayimFriedman2 changed the title Implement Return Position Impl Trait In Traits correctly feat: Implement Return Position Impl Trait In Traits correctly Mar 19, 2025
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.
Copy link
Contributor

@davidbarsky davidbarsky left a 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)

Comment on lines +1020 to +1025
// 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.
Copy link
Contributor

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!

Suggested change
// 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:

  1. What are "impl associated types"?
  2. 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"),
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
_ => 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)

Comment on lines +412 to +413
// 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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// 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"
Copy link
Contributor

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)?

Suggested change
"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"
Copy link
Contributor

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
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// 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

Comment on lines +34 to +35
/// An associated type synthesized from a Return Position Impl Trait In Trait
/// of the trait (not the impls).
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// 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.

Comment on lines +50 to +51
/// An associated type synthesized from a Return Position Impl Trait In Trait
/// of the impl (not the trait).
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// 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.

Comment on lines +43 to +44
/// 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).
Copy link
Contributor

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?

impl_id: ImplId,
trait_method_id: FunctionId,
) -> Box<[Arc<AssociatedTyValue>]> {
tracing::error!("impl_method_rpitit_values()");
Copy link
Contributor

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?

Suggested change
tracing::error!("impl_method_rpitit_values()");
tracing::span!("impl_method_rpitit_values", Level::INFO);

Copy link
Contributor Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-review Status: Awaiting review from the assignee but also interested parties.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants