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

RFC: Return Type Notation #3654

Merged
merged 13 commits into from
Jul 12, 2024
Merged

RFC: Return Type Notation #3654

merged 13 commits into from
Jul 12, 2024

Conversation

nikomatsakis
Copy link
Contributor

@nikomatsakis nikomatsakis commented Jun 5, 2024

Return type notation (RTN) gives a way to reference or bound the type returned by a trait method. The new bounds look like T: Trait<method(..): Send> or T::method(..): Send. The primary use case is to add bounds such as Send to the futures returned by async fns in traits and -> impl Future functions, but they work for any trait function defined with return-position impl trait (e.g., where T: Factory<widgets(..): DoubleEndedIterator> would also be valid).

This RFC proposes a new kind of type written <T as Trait>::method(..) (or T::method(..) for short). RTN refers to "the type returned by invoking method on T".

To keep this RFC focused, it only covers usage of RTN as the Self type of a bound or where-clause. The expectation is that, after accepting this RFC, we will gradually expand RTN usage to other places as covered under Future Possibilities. As a notable example, supporting RTN in struct field types would allow constructing types that store the results of a call to a trait -> impl Trait method, making them more suitable for use in public APIs.

Examples of RTN usage allowed by this RFC include:

  • where <T as Trait>::method(..): Send
    • (the base syntax)
  • where T: Trait<method(..): Send>
  • where T::method(..): Send
    • (sugar where Trait is inferred from the compiler)
  • dyn Trait<method(..): Send>
    • (dyn types take lists of bounds)
  • impl Trait<method(..): Send>
    • (...as do impl types)

Rendered

Tracking:

@nikomatsakis nikomatsakis added the T-lang Relevant to the language team, which will review and decide on the RFC. label Jun 5, 2024
@traviscross traviscross added the I-lang-nominated Indicates that an issue has been nominated for prioritizing at the next lang team meeting. label Jun 5, 2024
text/0000-return-type-notation.md Outdated Show resolved Hide resolved
There are multiple ways we could write this where-clause, varying in their specificity...

* `where C::capture(..): Send` -- this indicates that `C::capture()` will return a `Send` value for any possible set of parameters
* `where C::capture(&mut C, i32): Send` -- this indicates that `C::capture()` will return a `Send` value when invoked specifically on a `&mut C` (for the `self` parameter) and an `i32`
Copy link
Member

Choose a reason for hiding this comment

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

wouldn't this bring back the ambiguity with Fn()? for example when we have fn capture() -> impl Future

Copy link
Member

Choose a reason for hiding this comment

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

Indeed, it would. fn f() where C::capture(&mut C, i32): Send {} is already syntactically legal today. The parentheses denote parenthesized type arguments which may be attached to arbitary type paths (and it also comes with turbofish for expression and pattern paths: C::capture::(&mut C, i32)).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is syntactically legal, yes, but not meaningful semantically.

Copy link
Member

@fmease fmease Jun 7, 2024

Choose a reason for hiding this comment

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

Semantically as well if C is a module but yeah, I don't think it can actually meaningfully clash if C is a type parameter since types and modules live in the same namespace.

//@ edition: 2015
//@ check-pass
mod C { pub use std::ops::Fn as capture; }
fn f() where C::capture(): 'static {}

text/0000-return-type-notation.md Outdated Show resolved Hide resolved
text/0000-return-type-notation.md Outdated Show resolved Hide resolved
text/0000-return-type-notation.md Outdated Show resolved Hide resolved
text/0000-return-type-notation.md Outdated Show resolved Hide resolved

We encountered a number of problems with this design.

### If the name is implicit, what name should we use?
Copy link
Member

Choose a reason for hiding this comment

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

you could also use one of those # namespace such as fn#widgets or return#widgets or result_of#widgets

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, I'll add that as a possibility

@rpjohnst
Copy link

rpjohnst commented Jun 5, 2024

I would like to propose a tweak to the desguaring of RTN, which simplifies the limitation to "only in the Self of bounds" in the short term, and opens up some nicer future possibilities.

The RFC describes the T::method(..) syntax as projecting a type, and then using this type in a bound as a separate step. This approach avoids the implementation complexity of typeof, but still shares an expressiveness and ergonomics papercut with it: there is no way to name its result or unify it with another type, so it must be spelled out in full at each occurrence.

By comparison, while normal associated types do support a projection syntax, they also crucially support a syntax for naming and unification. For example, given the bound I: IntoIterator you can write I::IntoIter, but you cannot write I::IntoIter = i32. Fortunately, you can instead write I: IntoIterator<IntoIter = J>, or I: IntoIterator<IntoIter = i32>, or even something like I: IntoIterator<IntoIter = Foo<I, J, K>>.

This is sort of the type level analog to let bindings. Without it you can still express some things, but a lot of them are awkward, and other things are impossible. I understand that the RFC is focused on something minimal to be expanded on later, but I think this is important enough to consider from the start, and fortunately the "only Self of bounds" limitation lines up with this neatly.

So the tweak I propose is this: T::method(..): Send should be shorthand for the associated bound T: Trait<$Method: Send>, rather than the projection bound T::$Method: Send or T::method::Output: Send. The associated bound T: Trait<method(..): Send> becomes T: Trait<$Method: Send> directly. (Or, using the method ZST, T::method: FnStatic<_, Output: Send> and T: Trait<method: FnStatic<_, Output: Send>> respectively.)

This opens the door for T::method(..) -> U to be a full bound of its own, shorthand for T: Trait<$Method = U>, which enables multiple and/or unifying uses of U without re-specifying T::method(..). (Or, again using the method ZST, T::method: FnStatic<_, Output = U>.) This in turn brings the RTN sugar closer to the Fn trait sugar, making for a smoother transition between the sugar and whatever fully-general form(s) we get in the future, such as the const auto trait idea. We might also extend Fn trait sugar with the equivalent parameter elision and associated bound syntax, as in F: Fn(..): Send.

nikomatsakis

This comment was marked as off-topic.

Thanks kennytm!

Co-authored-by: kennytm <kennytm@gmail.com>
@nikomatsakis
Copy link
Contributor Author

@rpjohnst

I like the suggestion of T: Trait<method() -> X> as a possible future syntax (and I will add it into the list of options). That said, does the suggestion to "change the desugaring" have any practical impact in terms of the set of Rust programs that are accepted?

It seems to me to be more of a change in how we think about things, is that correct?

@rpjohnst
Copy link

rpjohnst commented Jun 5, 2024

I believe that is correct, because of the limitation to Self types of bounds, but I haven't tried to break it. (For all I know, though, the existing prototype implementation already works like this.)

It does also change how we might relax that limitation in the future, though- if T::method(..) is not a self-contained type projection, but part of two bound shorthands (T::method(..): Send, T::method(..) -> U), then we might prefer to write the examples from this section like this instead:

trait DataFactory {
    async fn load(&self) -> Data;
}

fn load_data<D: DataFactory<load(..) -> L>, L>(data_factory: D) {
    let load_future: L = data_factory.load();
    await_future(load_future);
}
    
fn await_future<D: DataFactory<load(..) -> L>, L>(argument: L) -> Data {
    argument.await
}

struct Wrap<'a, D: DataFactory<load(&'a D) -> L>, L> {
    load_future: L, // the future returned by `D::load`.
}

This does look like it would subtly change some quantifier scopes, but maybe they would wind up being closer to what you would get if you wrote this out by hand with generic associated types?

@nikomatsakis
Copy link
Contributor Author

nikomatsakis commented Jun 6, 2024

@rpjohnst

The RFC is written with RTNs acting as a kind of "pseudo-associated type"1. The idea is that you can think of traits as having an internal $Method<...> GAT with some undefined set of parameters and method(..) as the user-facing syntax for accessing it. So I would like to see method(..) be usable in the same way that GATs are -- that means being able to do T::method(..): Send but also T: Trait<method(..): Send>.

I agree with your suggestion that should also mean being able to do T: Trait<method(..) -> X> in the same way that you can do T: Trait<$Method = X> (as you suggested). I see this missing point as a gap in the RFC as written.

But I don't quite follow why we would want to rewrite the DataFactory example. Continuing with my analogy above, I think that just as one can write T::$Method<..> as a standalone type, you should be able to do T::method(..).

I guess my point of confusion is this paragraph that you wrote:

This opens the door for T::method(..) -> U to be a full bound of its own, shorthand for T: Trait<$Method = U>, which enables multiple and/or unifying uses of U without re-specifying T::method(..). (Or, again using the method ZST, T::method: FnStatic<_, Output = U>.) This in turn brings the RTN sugar closer to the Fn trait sugar, making for a smoother transition between the sugar and whatever fully-general form(s) we get in the future, such as the const auto trait idea. We might also extend Fn trait sugar with the equivalent parameter elision and associated bound syntax, as in F: Fn(..): Send.

Again here I think that making the Fn sugar closer seems very good, but I don't quite know how it is blocked by also allowing T::method(..) to be usable as an associated type.

Footnotes

  1. In earlier drafts I tried to introduce the term "associated return type" but it didn't "stick". I still kind of like it except that it doesn't scale to e.g. top-level functions.

@rpjohnst
Copy link

rpjohnst commented Jun 6, 2024

Ah, I could have spelled that out more clearly- the issue I see is that if T::method(..) -> X is sugar for the bound T: Trait<$Method = X>, then for consistency T::method(..) would be sugar for T: Trait<$Method = ()>, with the implicit -> () that we have everywhere else.

It's probably a good idea to have a projection syntax as well, but since the RFC limits the T::method(..) syntax to bounds anyway, I think that can be worked out as a future extension, e.g. whether we make T::method(..) do double duty, or come up with an alternative syntax to avoid the ambiguity.

@nikomatsakis
Copy link
Contributor Author

It's probably a good idea to have a projection syntax as well, but since the RFC limits the T::method(..) syntax to bounds anyway, I think that can be worked out as a future extension, e.g. whether we make T::method(..) do double duty, or come up with an alternative syntax to avoid the ambiguity.

The RFC doesn't limit it to bounds, it limits it to "self position" -- i.e., the RFC allows for T::method(..): Send. I don't expect that to be widely used though and of course (as you say) it's not strictly necessary, it's more to set the precedent and just be a bit less surprising.

I see your point about consistency, but there are other kinds of consistency to consider, e.g., T: Trait<Foo: Send> allows to T::Foo.

I think I will add this as an "unresolved question". I expect we will stabilize the bound form first anyway for implementation reasons.

@rpjohnst
Copy link

rpjohnst commented Jun 7, 2024

The RFC doesn't limit it to bounds, it limits it to "self position" -- i.e., the RFC allows for T::method(..): Send. I don't expect that to be widely used though and of course (as you say) it's not strictly necessary, it's more to set the precedent and just be a bit less surprising.

Right, this is all I meant by "limited to bounds." I was thinking of T::method(..): Send as sugar for T: Trait<$Method: Send> or maybe T::method: FnStatic<_, Output: Send>- sort of an extension of associated bound syntax, which doesn't quite have the same conflict with T::method(..) -> (), because it wouldn't make sense to write T::method(..) -> X: Send anyway.

I guess if we consider that form to be rare enough (i.e. we expect everyone to use T: Trait<method(..): Send> most of the time instead) it could be worth delaying its stabilization for this unresolved question, but it seems reasonable either way.

@bluebear94
Copy link

The typeof syntax could be extended to deduce a type given variables with certain types. For the HealthCheck example, it might look like the following:

fn start_health_check<H>(health_check: H, server: Server)
where
    H: HealthCheck + Send + 'static,
    for<'a> typeof for (health_check: &'a mut H, server: Server) H::check(health_check, server): Send,

or even

fn start_health_check<H>(health_check: H, server: Server)
where
    H: HealthCheck + Send + 'static,
    for<'a> typeof for (health_check: &'a mut H, server: Server) health_check.check(send): Send,

This avoids the need for a dummy function but is still considerably more verbose than RTN.

nikomatsakis and others added 4 commits June 10, 2024 14:06
Co-authored-by: zachs18 <8355914+zachs18@users.noreply.github.com>
github markdown makes newlines significant
@nikomatsakis
Copy link
Contributor Author

@bluebear94 I pushed a variant of your proposal using let as a comment. I also added an unresolved question regarding @rpjohnst's point about T::foo(..) as a standalone type.

@nikomatsakis

This comment was marked as outdated.

@kennytm

This comment was marked as outdated.

@nikomatsakis

This comment was marked as off-topic.

@nikomatsakis
Copy link
Contributor Author

nikomatsakis commented Jun 12, 2024

@rfcbot fcp merge

This proposal has been under discussion for a long time and, in this first week, all the feedback has been incorporated. I'm going to go ahead and kick off the checkboxes towards final-comment-period.

@rfcbot
Copy link
Collaborator

rfcbot commented Jun 12, 2024

Team member @nikomatsakis has proposed to merge this. The next step is review by the rest of the tagged team members:

No concerns currently listed.

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns.
See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added proposed-final-comment-period Currently awaiting signoff of all team members in order to enter the final comment period. disposition-merge This RFC is in PFCP or FCP with a disposition to merge it. labels Jun 12, 2024
@matklad
Copy link
Member

matklad commented Jun 14, 2024

I'd love to summaries here discussion around these couple of posts:

While I don't think this summary should affect the specific decision on this RFC, I think it helps
to illuminate the surrounding context.

The main motivation for RTN is to be able to say that a particular future is Send. We care about
Sendness of futures because, roughly, only Send futures work with work-stealing executors.

Peeling Rust specific abstractions, the picture is this:

We have an asynchronous computation. The computation is suspended, meaning that it's not actually
executing, and instead is just a bunch of memory waiting for a wake up. Specifically, this memory is
roughly "local variables live across a yield point". It seems that the safety claim here is:

To safely migrate this computation to a different OS-thread, all suspended state must be Send.

It is important to realize that this claim is actually wrong. It is totally fine to migrate async
tasks across threads even if they hold onto non-Send data across the yield point. Two proofs by
example here are:

  • OS-threads, which can hold onto all kinds of non-Send data before a blocking syscall, and end up
    being woken up on a different CPU core after syscall returns.
  • Goroutines, which similarly can use local non-thread safe data and migrate between OS threads.

Non-the-less, it would be wrong to just remove Send bounds from work-stealing executors --- while
they are not needed to protect us from general data races, they end up outlawing one very specific
scenario where unsoundly-bad things can happen --- smuggling non-thread-safe data between threads
using thread locals. The following example breaks if we just remove Send bounds:

async fn sneaky() {
  thread_local! { static TL: Rc<()> = Rc::new(()); }
  let rc = TL.with(|it| it.clone());
  async {}.await;
  rc.clone();
}

One can imagine alternative world, where:

  • Send and Sync traits are not definitionaly dependent on OS threads.
  • There's only unsafe API for accessing thread locals, with one part of the contract being "make
    sure that the data you get from a thread local doesn't end up in a different OS thread".

In that world, both work-stealing and thread-per-core executors would work fine with non-Send
futures. So, there wouldn't be any need to specify or constrain the sendness of async functions, and
moreover there wouldn't be a need to be generic over sendness. As such, that world would have much
less motivation for either RTN or trait transformers.

In other words, this is an instance of a classic Rust problem of composing unsafe abstractions.

The two unsafe abstractions here are:

  • Thread locals
  • Work-stealing async tasks

Each can be made safe in isolation, but the combination of the two end up being unsound.

From where I stand, I am 0.85 sure that we've picked the wrong one of the two in this case: I'll
gladly use a context object in lieu of a thread local, or write some unsafe inside my global
allocator, if that means that crates like may are sound, and if I don't
need to further paint async part of the ecosystem into Send, !Send and ~Send colors.


That being said, I don't see a reasonable way we can rectify the mistake while maintaining backwards
compatibility. Given that the issues were somewhat widely discussed and no-one else seemed to
suggested a workable approach either, I am skeptical that this is at all possible.

So it seems reasonable to accept this part of Rust as is, and instead try to address the
repercussions, where something like RTN does seem necessary.

@pnkfelix
Copy link
Member

@rfcbot reviewed

@pnkfelix
Copy link
Member

@rfcbot fcp reviewed

@rfcbot rfcbot added finished-final-comment-period The final comment period is finished for this RFC. to-announce and removed final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. labels Jun 27, 2024
@rfcbot
Copy link
Collaborator

rfcbot commented Jun 27, 2024

The final comment period, with a disposition to merge, as per the review above, is now complete.

As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.

This will be merged soon.

@kennytm kennytm mentioned this pull request Jul 2, 2024
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Jul 3, 2024
…estebank

Change return-type-notation to use `(..)`

Aligns the syntax with the current wording of [RFC 3654](rust-lang/rfcs#3654). Also implements rustfmt support (along with making a match exhaustive).

Tracking:
* rust-lang#109417
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Jul 4, 2024
Rollup merge of rust-lang#127092 - compiler-errors:rtn-dots-redux, r=estebank

Change return-type-notation to use `(..)`

Aligns the syntax with the current wording of [RFC 3654](rust-lang/rfcs#3654). Also implements rustfmt support (along with making a match exhaustive).

Tracking:
* rust-lang#109417
@traviscross traviscross force-pushed the rtn branch 2 times, most recently from 4ddd80d to 3504c71 Compare July 12, 2024 05:39
We've been using "tracking issue" rather than "rust issue" recently,
so let's adjust that.  Let's also use sentence case for the title and
remove some remaining bits left over from the RFC template.  And let's
replace some Unicode characters that don't have ubiquitous font
support with ASCII equivalents.
@traviscross traviscross merged commit 4a0401d into rust-lang:master Jul 12, 2024
@traviscross
Copy link
Contributor

The team has accepted this RFC, and it's now been merged.

Thanks to @nikomatsakis for pushing forward on this important work, to @compiler-errors for the experimental implementation, and to all those who reviewed the RFC and provided useful feedback.

For further updates on this work, follow the tracking issue:

matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Sep 21, 2024
…kh726

Implement Return Type Notation (RTN)'s path form in where clauses

Implement return type notation (RTN) in path position for where clauses. We already had RTN in associated type position ([e.g.](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=627a4fb8e2cb334863fbd08ed3722c09)), but per [the RFC](https://rust-lang.github.io/rfcs/3654-return-type-notation.html#where-rtn-can-be-used-for-now):

> As a standalone type, RTN can only be used as the Self type of a where-clause [...]

Specifically, in order to enable code like:

```rust
trait Foo {
    fn bar() -> impl Sized;
}

fn is_send(_: impl Send) {}

fn test<T>()
where
    T: Foo,
    T::bar(..): Send,
{
    is_send(T::bar());
}
```

* In the resolver, when we see a `TyKind::Path` whose final segment is `GenericArgs::ParenthesizedElided` (i.e. `(..)`), resolve that path in the *value* namespace, since we're looking for a method.
* When lowering where clauses in HIR lowering, we first try to intercept an RTN self type via `lower_ty_maybe_return_type_notation`. If we find an RTN type, we lower it manually in a way that respects its higher-ranked-ness (see below) and resolves to the corresponding RPITIT. Anywhere else, we'll emit the same "return type notation not allowed in this position yet" error we do when writing RTN in every other position.
* In `resolve_bound_vars`, we add some special treatment for RTN types in where clauses. Specifically, we need to add new lifetime variables to our binders for the early- and late-bound vars we encounter on the method. This implements the higher-ranked desugaring [laid out in the RFC](https://rust-lang.github.io/rfcs/3654-return-type-notation.html#converting-to-higher-ranked-trait-bounds).

This PR also adds a bunch of tests, mostly negative ones (testing error messages).

In a follow-up PR, I'm going to mark RTN as no longer incomplete, since this PR basically finishes the impl surface that we should initially stabilize, and the RFC was accepted.

cc [RFC 3654](rust-lang/rfcs#3654) and rust-lang#109417
compiler-errors added a commit to compiler-errors/rust that referenced this pull request Sep 21, 2024
…kh726

Implement Return Type Notation (RTN)'s path form in where clauses

Implement return type notation (RTN) in path position for where clauses. We already had RTN in associated type position ([e.g.](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=627a4fb8e2cb334863fbd08ed3722c09)), but per [the RFC](https://rust-lang.github.io/rfcs/3654-return-type-notation.html#where-rtn-can-be-used-for-now):

> As a standalone type, RTN can only be used as the Self type of a where-clause [...]

Specifically, in order to enable code like:

```rust
trait Foo {
    fn bar() -> impl Sized;
}

fn is_send(_: impl Send) {}

fn test<T>()
where
    T: Foo,
    T::bar(..): Send,
{
    is_send(T::bar());
}
```

* In the resolver, when we see a `TyKind::Path` whose final segment is `GenericArgs::ParenthesizedElided` (i.e. `(..)`), resolve that path in the *value* namespace, since we're looking for a method.
* When lowering where clauses in HIR lowering, we first try to intercept an RTN self type via `lower_ty_maybe_return_type_notation`. If we find an RTN type, we lower it manually in a way that respects its higher-ranked-ness (see below) and resolves to the corresponding RPITIT. Anywhere else, we'll emit the same "return type notation not allowed in this position yet" error we do when writing RTN in every other position.
* In `resolve_bound_vars`, we add some special treatment for RTN types in where clauses. Specifically, we need to add new lifetime variables to our binders for the early- and late-bound vars we encounter on the method. This implements the higher-ranked desugaring [laid out in the RFC](https://rust-lang.github.io/rfcs/3654-return-type-notation.html#converting-to-higher-ranked-trait-bounds).

This PR also adds a bunch of tests, mostly negative ones (testing error messages).

In a follow-up PR, I'm going to mark RTN as no longer incomplete, since this PR basically finishes the impl surface that we should initially stabilize, and the RFC was accepted.

cc [RFC 3654](rust-lang/rfcs#3654) and rust-lang#109417
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Sep 22, 2024
Rollup merge of rust-lang#129629 - compiler-errors:rtn-in-path, r=jackh726

Implement Return Type Notation (RTN)'s path form in where clauses

Implement return type notation (RTN) in path position for where clauses. We already had RTN in associated type position ([e.g.](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=627a4fb8e2cb334863fbd08ed3722c09)), but per [the RFC](https://rust-lang.github.io/rfcs/3654-return-type-notation.html#where-rtn-can-be-used-for-now):

> As a standalone type, RTN can only be used as the Self type of a where-clause [...]

Specifically, in order to enable code like:

```rust
trait Foo {
    fn bar() -> impl Sized;
}

fn is_send(_: impl Send) {}

fn test<T>()
where
    T: Foo,
    T::bar(..): Send,
{
    is_send(T::bar());
}
```

* In the resolver, when we see a `TyKind::Path` whose final segment is `GenericArgs::ParenthesizedElided` (i.e. `(..)`), resolve that path in the *value* namespace, since we're looking for a method.
* When lowering where clauses in HIR lowering, we first try to intercept an RTN self type via `lower_ty_maybe_return_type_notation`. If we find an RTN type, we lower it manually in a way that respects its higher-ranked-ness (see below) and resolves to the corresponding RPITIT. Anywhere else, we'll emit the same "return type notation not allowed in this position yet" error we do when writing RTN in every other position.
* In `resolve_bound_vars`, we add some special treatment for RTN types in where clauses. Specifically, we need to add new lifetime variables to our binders for the early- and late-bound vars we encounter on the method. This implements the higher-ranked desugaring [laid out in the RFC](https://rust-lang.github.io/rfcs/3654-return-type-notation.html#converting-to-higher-ranked-trait-bounds).

This PR also adds a bunch of tests, mostly negative ones (testing error messages).

In a follow-up PR, I'm going to mark RTN as no longer incomplete, since this PR basically finishes the impl surface that we should initially stabilize, and the RFC was accepted.

cc [RFC 3654](rust-lang/rfcs#3654) and rust-lang#109417
@PoignardAzur
Copy link

@nikomatsakis

That said, I have noticed thread-locals cropping up more and more often as an annoying soundness hole (most recently in duchess and salsa),

I'm very curious what you mean wrt salsa, and a quick search hasn't turned up any discussions. Do you have a link on hand?

lnicola pushed a commit to lnicola/rust-analyzer that referenced this pull request Sep 25, 2024
Implement Return Type Notation (RTN)'s path form in where clauses

Implement return type notation (RTN) in path position for where clauses. We already had RTN in associated type position ([e.g.](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=627a4fb8e2cb334863fbd08ed3722c09)), but per [the RFC](https://rust-lang.github.io/rfcs/3654-return-type-notation.html#where-rtn-can-be-used-for-now):

> As a standalone type, RTN can only be used as the Self type of a where-clause [...]

Specifically, in order to enable code like:

```rust
trait Foo {
    fn bar() -> impl Sized;
}

fn is_send(_: impl Send) {}

fn test<T>()
where
    T: Foo,
    T::bar(..): Send,
{
    is_send(T::bar());
}
```

* In the resolver, when we see a `TyKind::Path` whose final segment is `GenericArgs::ParenthesizedElided` (i.e. `(..)`), resolve that path in the *value* namespace, since we're looking for a method.
* When lowering where clauses in HIR lowering, we first try to intercept an RTN self type via `lower_ty_maybe_return_type_notation`. If we find an RTN type, we lower it manually in a way that respects its higher-ranked-ness (see below) and resolves to the corresponding RPITIT. Anywhere else, we'll emit the same "return type notation not allowed in this position yet" error we do when writing RTN in every other position.
* In `resolve_bound_vars`, we add some special treatment for RTN types in where clauses. Specifically, we need to add new lifetime variables to our binders for the early- and late-bound vars we encounter on the method. This implements the higher-ranked desugaring [laid out in the RFC](https://rust-lang.github.io/rfcs/3654-return-type-notation.html#converting-to-higher-ranked-trait-bounds).

This PR also adds a bunch of tests, mostly negative ones (testing error messages).

In a follow-up PR, I'm going to mark RTN as no longer incomplete, since this PR basically finishes the impl surface that we should initially stabilize, and the RFC was accepted.

cc [RFC 3654](rust-lang/rfcs#3654) and rust-lang/rust#109417
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
disposition-merge This RFC is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this RFC. I-lang-nominated Indicates that an issue has been nominated for prioritizing at the next lang team meeting. T-lang Relevant to the language team, which will review and decide on the RFC. to-announce
Projects
None yet
Development

Successfully merging this pull request may close these issues.