-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
refactor impl trait
to model abstract type
a bit better
#44727
Comments
I think @cramertj is probably going to be hacking on this. |
cc @eddyb -- do you think this rough idea makes any sense? |
It makes sense to me. Not sure what's the best way to handle generics, but we'll discover that while implementing. |
@cramertj -- just checking in, how goes? |
@nikomatsakis It goes! I've got most of the generics_of/predicates_of stuff sorted out. I'm trying to figure out the best way to handle the privacy and access visitors, but it's just taking time-- I'm not blocked. I'll try to have something up for review by Wednesday evening (PST). |
So there's been a fair amount of discussion on the best way to do this. I am becoming wary of trying to do this desugaring at the HIR level -- I think it's a bit of an open question just how much we should desugar at HIR. I think I like the idea of trying to hew fairly closely to the user's AST, but do "light desugaring" -- e.g., the way we "embed" symbol tables paths now. But that too is a bit challenging, since e.g. doing name resolution on the AST implies that we have to kind of "re-use" those results later on, even if the desugared version might look quite different. In fact, I think integration with name resolution is a key point here. In particular, if we have something like This is a bit of a complication because if we wanted to "faithfully" desugar to something like this: abstract type Foo<U>: impl Iterator<Item = U>;
fn foo<T>() -> Foo<T> { ... } As you can see, the bound Interestingly, right now, this problem is specific to type parameters. We don't have this problem with lifetimes yet, since those are resolved after HIR construction. But there's talk of moving lifetime resolution to occur earlier, and then it will encounter a similar tension. In addition, the early- vs late-bound distinction for lifetimes makes this more complex, as I'll get to in a bit. There are three ways to address this that I can see:
The first option seems like a bad idea to me. The more we can contain knowledge of how the desugaring works into a narrow band of the compiler, the better. The first option forces everything between the parser and HIR lowering to "coordinate" on how the desugaring works. The second choice feels the closest to the current implementation. I think it would work like this. We already have a The challenge here is that the fn foo<'a,'b,T>(...) -> impl Iterator<Item = &'a T> { .. }
// for now imagine 'a and 'b are both early bound then we would effectively wind up desugaring to this: abstract type Foo<'a, 'b, T, 'c>: impl Iterator<Item = &'c T>;
fn foo<'a,'b,T>(...) -> Foo<'static, 'static, T, 'a> That is, the
This means of course that the lifetime resolution has to be aware of this: in particular, when it resolves the On the other hand, we also need to generate the I imagine extending This basically means that the code which has to know about how the desugaring works is:
I think the main downside here is that the "clever trick" to keep the lifetimes of The upside is that I do have this nagging feeling that HIR lowering is not the right place to do 'large-scale' desugarings where we synthesize new items and things. To be honest, I'm not even sure if the desugarings we do now (e.g., I think that there is a "slightly tweaked" variant of the previous plan in which we do a bit more desugaring during HIR lowering. In particular, we would introduce def-ids for the type and lifetime parameters that the abstract type we will need (rather than piggybacking on the existing def-ids, as I described in the previous section). Perhaps we wind up with TyImplTrait {
// If `-> impl Iterator<Item = &'a T>` becomes `-> Foo<'a, T>`, then these vectors
// store the `'a` and `T`
lifetime_parameters: Vec<Lifetime>, // `['a]` in the above example
type_parameters: Vec<DefId>, // `[D]` where `D` is the def-id of `T`, in the above example
exist_ty: ExistTy,
}
struct ExistTy {
// In above example, this would include `'a` and `T`, each with fresh def-ids
generics: hir::Generics,
// Paths in here are "rewritten" to refer to the `T` in our generics above
bounds: hir::TyParamBounds,
} The challenges here:
But after doing it:
So I'm not sure where I land, but I think both of these plans seem reasonably viable. |
It's probably worth noting just for posterity that I had started on a version of the HIR-desugaring approach here, although it handles IDs incorrectly. I'll continue to pursue this-- I'm going to start on the second choice mentioned in @nikomatsakis's comment above, and I'll update with my progress. |
impl Trait Lifetime Handling This PR implements the updated strategy for handling `impl Trait` lifetimes, as described in [RFC 1951](https://github.com/rust-lang/rfcs/blob/master/text/1951-expand-impl-trait.md) (cc #42183). With this PR, the `impl Trait` desugaring works as follows: ```rust fn foo<T, 'a, 'b, 'c>(...) -> impl Foo<'a, 'b> { ... } // desugars to exists type MyFoo<ParentT, 'parent_a, 'parent_b, 'parent_c, 'a, 'b>: Foo<'a, 'b>; fn foo<T, 'a, 'b, 'c>(...) -> MyFoo<T, 'static, 'static, 'static, 'a, 'b> { ... } ``` All of the in-scope (parent) generics are listed as parent generics of the anonymous type, with parent regions being replaced by `'static`. Parent regions referenced in the `impl Trait` return type are duplicated into the anonymous type's generics and mapped appropriately. One case came up that wasn't specified in the RFC: it's possible to write a return type that contains multiple regions, neither of which outlives the other. In that case, it's not clear what the required lifetime of the output type should be, so we generate an error. There's one remaining FIXME in one of the tests: `-> impl Foo<'a, 'b> + 'c` should be able to outlive both `'a` and `'b`, but not `'c`. Currently, it can't outlive any of them. @nikomatsakis and I have discussed this, and there are some complex interactions here if we ever allow `impl<'a, 'b> SomeTrait for AnonType<'a, 'b> { ... }`, so the plan is to hold off on this until we've got a better idea of what the interactions are here. cc #34511. Fixes #44727.
After some discussion with @cramertj, I wanted to write up a rough idea for how to represent
impl Trait
in the HIR etc. The key idea is to move towards a place where we represent theabstract type
that animpl Trait
conceptually desugars to as a distinct "item" in the HIR.Today, for each usage of
impl Trait
, we create a def-id, which basically represents theabstract type
behind theimpl Trait
. However, in the HIR itself, we continue to mirror the syntax, so for example the variant forImplTrait
includes the bounds listed inline. This is not I think what we really want.The refactoring then is to do the following:
hir::Crate
a "abstract type" vector sort of like the list of bodies.hir::AbstractType
struct that contains the following:ImplTrait
variant ofhir::Ty
with to include a "list of substs" in some form (and not have bounds)impl trait
, we create and push ahir::AbstractType
:ast::Ty
into thehir::AbstractType
hir::ImplTrait(DefId, [T, 'a])
where the "substs" are references to all the lifetimse/types in scope (I'm not entirely sure how best to represent and synthesize those?)The text was updated successfully, but these errors were encountered: