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

Implement impl Trait in argument position (RFC1951, Universal quantification) #45918

Merged
merged 28 commits into from
Nov 16, 2017

Conversation

chrisvittal
Copy link
Contributor

@chrisvittal chrisvittal commented Nov 10, 2017

Implements the remainder of #44721, part of #34511.

Note: This PR currently allows argument position impl Trait in trait functions. The machinery is there to prevent this if we want to, but it currently does not.

Rename hir::TyImplTrait to hir::TyImplTraitExistential and add hir::TyImplTraitUniversal(DefId, TyParamBounds). The DefId is needed to extract the index of the parameter in ast_ty_to_ty.

Introduce an ImplTraitContext enum to lowering to keep track of the kind and allowedness of impl Trait in that position. This new argument is passed through many places, all ending up in lower_ty.

Modify generics_of and explicit_predicates_of to collect the impl Trait args into anonymous synthetic generic parameters and to extend the predicates with the appropriate bounds.

Add a comparison of the 'syntheticness' of type parameters, that is, prevent the following.

trait Foo {
    fn foo(&self, &impl Debug);
}
impl Foo for Bar {
    fn foo<U: Debug>(&self, x: &U) { ... }
}

And vice versa.

Incedentally, supress unused type parameter errors if the type being compared is already a TyError.

TODO: I have tried to annotate open questions with FIXMEs. The most notable ones that haven't been resolved are the names of the impl Trait types and the questions surrounding the new compare_synthetic_generics method.

  1. For now, the names used for impl Trait parameters are keywords::Invalid.name(). I would like them to be impl ... if possible, but I haven't figured out a way to do that yet.
  2. For compare_synthetic_generics I have tried to outline the open questions in the function itself

r? @nikomatsakis

@@ -4665,6 +4625,22 @@ It is recommended that you look for a `new` function or equivalent in the
crate's documentation.
"##,

E0642: r##"
This error indicates that there is a mismatch between generic parameters and
impl Trait parameters in a trait Declaration versus it's impl.
Copy link
Member

Choose a reason for hiding this comment

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

Nits: "Declaration" should probably be lowercase, "s/it's/its"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks!

```compile_fail,E0642
#![feature(conservative_impl_trait)]
trait Foo
fn foo(&self, &impl Iterator)
Copy link
Member

Choose a reason for hiding this comment

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

Unnamed parameters are deprecated. Consider changing this to fn foo(&self, iter: &impl Iterator)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I just noticed that I need to change the feature as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@cramertj It is sufficient to go from &impl Iterator to _: &impl Iterator, correct?

Copy link
Member

Choose a reason for hiding this comment

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

Sure, I suppose you could do that.

use std::fmt::Debug;

trait Foo {
fn foo(&self, &impl Debug);
Copy link
Member

Choose a reason for hiding this comment

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

DItto RE unnamed arguments

@kennytm kennytm added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Nov 10, 2017
@chrisvittal
Copy link
Contributor Author

Try this again,

r? @nikomatsakis

@chrisvittal
Copy link
Contributor Author

Oh! I just thought of one more thing to do. I need to clean up E0562 as that is the error we use for both positions now.

@nikomatsakis
Copy link
Contributor

@chrisvittal sorry didn't get a chance to review this weekend, will shortly though I promise!

Copy link
Contributor

@nikomatsakis nikomatsakis left a comment

Choose a reason for hiding this comment

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

OK, I did a reasonably thorough review. The code is mostly excellent. I left some nits. However, there are a few discrepancies that I think we should correct before landing -- or at least note for later follow-up. I'm also going to push a few commits with various tests and edits.

output: match decl.output {
FunctionRetTy::Ty(ref ty) => hir::Return(self.lower_ty(ty)),
FunctionRetTy::Ty(ref ty) => match (impl_trait_return_allow, fn_def_id) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: It's a bit hard to decipher this match. I think I'd rather see this as

if impl_trait_return_allow && fn_def_id.is_some() {
    let fn_def_if = fn_def_id.unwrap();
    hir::Return(self.lower_ty(ty, ImplTraitContext::Existential(fn_def_id)))
} else {
    hir::Return(self.lower_ty(ty, ImplTraitContext::Disallowed))
}

or perhaps

match fn_def_id {
    Some(d) if impl_trait_return_allow => hir::Return(self.lower_ty(ty, ImplTraitContext::Existential(d))),
    _ => hir::Return(self.lower_ty(ty, ImplTraitContext::Disallowed))
}

since that emphasizes that there are really just two outcomes 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.

The second is good. I forgot you could do that.

@@ -1719,12 +1798,13 @@ impl<'a> LoweringContext<'a> {
attrs: this.lower_attrs(&i.attrs),
node: match i.node {
ForeignItemKind::Fn(ref fdec, ref generics) => {
hir::ForeignItemFn(this.lower_fn_decl(fdec),
let fn_def_id = this.resolver.definitions().opt_local_def_id(i.id);
hir::ForeignItemFn(this.lower_fn_decl(fdec, fn_def_id, true),
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we want to allow impl trait in foreign items. That would be something like this:

extern fn foo() -> impl Trait;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree. I overlooked this.

}
ImplItemKind::Method(ref sig, ref body) => {
let body_id = this.lower_body(Some(&sig.decl), |this| {
let body = this.lower_block(body, false);
this.expr_block(body, ThinVec::new())
});
hir::ImplItemKind::Method(this.lower_method_sig(sig), body_id)
let impl_trait_return_allow = !this.is_in_trait_impl;
hir::ImplItemKind::Method(this.lower_method_sig(sig, fn_def_id,
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: each param on its own line.

@@ -1055,11 +1095,24 @@ impl<'a> LoweringContext<'a> {
}).collect()
}

fn lower_fn_decl(&mut self, decl: &FnDecl) -> P<hir::FnDecl> {
fn lower_fn_decl(&mut self,
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we leave a comment about the interaction between fn_def_id and impl_trait_return_allow? Something like:

If fn_def_id is Some, then impl traits will be allowed in argument position, and they will be translated into universal arguments bound on the given fn-def-id. If, further, impl_trait_return_allow is true, then impl trait will also be allowed in return position.

It might also be nice to explain why we have it this way. For example:

We wish to support impl trait in argument position in traits and trait impls, but permit return position in inherent functions and methods. In yet other cases, such as extern functions, we wish to support neither.

@@ -103,6 +103,7 @@ pub struct LoweringContext<'a> {
catch_scopes: Vec<NodeId>,
loop_scopes: Vec<NodeId>,
is_in_loop_condition: bool,
is_in_trait_impl: bool,
Copy link
Contributor

Choose a reason for hiding this comment

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

the commit message says is_in_impl_trait, but the field is is_in_trait_impl

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately, file is correct and I don't want to invalidate the history. Especially since you've also added new commits. What should I do?

}

let mut visitor = ImplTraitUniversalVisitor { items: Vec::new() };
opt_inputs.map(|inputs| for t in inputs.iter() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: if let Some(inputs) = opt_inputs { ... } would be clearer

@nikomatsakis
Copy link
Contributor

Here are the things I think we need to fix before landing:

  • Most importantly, the name field being empty string seems like a problem. See the ui test in my most recent commit. That said, we could conceivably leave this for followup, and I'd be fine with that I think -- but it seems like we should at least use a hardcoded name like "impl Trait".
  • There are a few discrepancies in terms of where impl trait is allowed. Things don't seem quite right. These are reflected in my where-allowed.rs test, with FIXME comments.
    • It should not be allowed in foreign items.
    • We should either consistently allow in return position of a type (e.g., fn() -> impl Debug) or not. Right now we allow it for closure types like dyn Fn() -> impl Debug but disallow it for fn pointer types.
    • The RFC is sort of ambiguous: it's clear we do no want to allow in parameter position, but return position seems harmless enough. Still, I'm fine with just making it an error for now (which is how I wrote the test) if we want to go slow. Easy to expand later.
  • In my ideal world, we'd have a distinct test covering each of the cases in where-allowed where (universal) impl Trait is accepted. We don't. I added some, along with some other trickier cases that leapt immediately to mind.

@chrisvittal
Copy link
Contributor Author

chrisvittal commented Nov 14, 2017

Big reason to deny Fn() -> impl Trait, as of right now, the following is an ICE.

fn in_generics_Fn<F: Fn() -> impl Debug>(_: F) {}

@nikomatsakis
Copy link
Contributor

@bors r+ p=1

This is an awesome change. I am gratuitously giving it p=1 because it'd be really nice to see it land before the end of the week.

@bors
Copy link
Contributor

bors commented Nov 15, 2017

📌 Commit 53dceb4 has been approved by nikomatsakis

@bors
Copy link
Contributor

bors commented Nov 15, 2017

⌛ Testing commit 53dceb489e31daef08cd71b0cfba212223a24f87 with merge bf53d9c666f1f99e77ba994172a24091f23f0377...

@bors
Copy link
Contributor

bors commented Nov 15, 2017

💔 Test failed - status-travis

chrisvittal and others added 13 commits November 15, 2017 15:46
With the check for impl trait moving from type checking to HIR lowering
the error needs to move too.
Replace hir::TyImplTrait with TyImplTraitUniversal and
TyImplTraitExistential.

Add an ImplTraitContext enum to rustc::hir::lowering to track the kind
and allowedness of an impl Trait.

Significantly alter lowering to thread ImplTraitContext and one other
boolean parameter described below throughought much of lowering.

The other parameter is for tracking if lowering a function is in a trait
impl, as there is not enough information to otherwise know this
information during lowering otherwise.

This change also removes the checks from ast_ty_to_ty for impl trait
allowedness as they are now all taking place in HIR lowering.
This is for tracking if an ImplItem is part of a trait impl. Add
a with_trait_impl_ref method to ItemLowerer to appropriately save the
state to allow appropriate nesting of trait and non-trait impls.
In ast_generics extraction in generics_of and explicit_predicates_of,
also collect inputs if there are any.

Then use a Visitor to extract the necessary information from the
TyImplTraitUniversal types before extending generics and predicates with
the new information.
First some background:
To the compiler, the following two signatures in the trait vs the impl
are the same.

```rust
trait Foo {
    fn foo(&self, &impl Debug);
}
impl Foo for () {
    fn foo<U: Debug>(&self, x: &U) { ... }
}
```

We do not want to allow this, and so we add a new error and check.

The check just tests that all paramters 'syntheticness' match up. As
during collection, the impl Trait parameters are transformed into
anonymous synthetic generics.

Furthermore, causes a check for unused type parameters to be skipped in
check_bounds_are_used if there is already a TyError. Thus, an unused
input will not trigger `type parameter unused` errors.

Update the one test that checked for this error in the case of
a TyError.
Move feature gate check to inside HIR lowering. Change error messages
and update tests.
nikomatsakis and others added 10 commits November 15, 2017 15:46
Uses Symbol::intern and hir.node_to_pretty_string to create a name for
the impl Trait parameter that is just impl and then a ' + ' separated
list of bounds that the user typed.
Add requested comments, restructure some small bits of code. Fix extern
declarations allowing impl Trait.
We already disallowed them to be in the arg list, such as
Fn(impl Debug), but now we disallow Fn() -> impl Debug.

Also remove the ImplTraitContext argument from the function
lower_parenthesized_parameter_data as it is now unused.

Comment out part of test run-pass/impl-trait/xcrate.rs that now fails.
@nikomatsakis
Copy link
Contributor

@bors r+

@bors
Copy link
Contributor

bors commented Nov 15, 2017

📌 Commit 4ce61b7 has been approved by nikomatsakis

@nikomatsakis
Copy link
Contributor

cc @Manishearth @oli-obk -- note that clippy is affected here (not sure if you were cc'd before).

@kennytm kennytm added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Nov 15, 2017
@bors
Copy link
Contributor

bors commented Nov 15, 2017

⌛ Testing commit 4ce61b7 with merge b8c70b0...

bors added a commit that referenced this pull request Nov 15, 2017
Implement `impl Trait` in argument position (RFC1951, Universal quantification)

Implements the remainder of #44721, part of #34511.

**Note**: This PR currently allows argument position `impl Trait` in trait functions. The machinery is there to prevent this if we want to, but it currently does not.

Rename `hir::TyImplTrait` to `hir::TyImplTraitExistential` and add `hir::TyImplTraitUniversal(DefId, TyParamBounds)`. The `DefId` is needed to extract the index of the parameter in `ast_ty_to_ty`.

Introduce an `ImplTraitContext` enum to lowering to keep track of the kind and allowedness of `impl Trait` in that position. This new argument is passed through many places, all ending up in `lower_ty`.

Modify `generics_of` and `explicit_predicates_of` to collect the `impl Trait` args into anonymous synthetic generic parameters and to extend the predicates with the appropriate bounds.

Add a comparison of the 'syntheticness' of type parameters, that is, prevent the following.
```rust
trait Foo {
    fn foo(&self, &impl Debug);
}
impl Foo for Bar {
    fn foo<U: Debug>(&self, x: &U) { ... }
}
```
And vice versa.

Incedentally, supress `unused type parameter` errors if the type being compared is already a `TyError`.

**TODO**: I have tried to annotate open questions with **FIXME**s. The most notable ones that haven't been resolved are the names of the `impl Trait` types and the questions surrounding the new `compare_synthetic_generics` method.
1. For now, the names used for `impl Trait` parameters are `keywords::Invalid.name()`. I would like them to be `impl ...` if possible, but I haven't figured out a way to do that yet.
2. For `compare_synthetic_generics` I have tried to outline the open questions in the [function itself](https://github.com/chrisvittal/rust/blob/3fc9e3705f7bd01f3cb0ea470cf2892f17a92350/src/librustc_typeck/check/compare_method.rs#L714-L725)

r? @nikomatsakis
@bors
Copy link
Contributor

bors commented Nov 16, 2017

☀️ Test successful - status-appveyor, status-travis
Approved by: nikomatsakis
Pushing b8c70b0 to master...

@spease
Copy link

spease commented Nov 24, 2017

There's something I read that discussed this change, but I couldn't find it again.

The particular essay pointed out that once someone is exposed to generics, it becomes intuitive to try to use a generic for the return type in order to effectively do an impl Trait.

I just wanted to note that I concur with that statement. That's what I tried.

It was then argued that allowing impl Trait in argument position would allow people to get exposed to this first, and hence be conducive to them trying impl Trait in the return position. I think this too is accurate.

That being said, I would assert that specifying a type should be consistent - if impl Trait is allowed to specify a type, then generics should be too, and vice versa. This makes it simpler to understand what tools you have available to you, because then when you are familiar with all the tools for specifying a type name, you know exactly which tools you can use whenever you see a typename - otherwise you have to remember every case you can use those tools in.

This would also imply impl Trait in other areas - eg on a struct.

I don't know if this would be massively difficult to implement, I'm purely suggesting that from a user developer perspective this would help simplify learning by making the language more consistent.

@sinkuu
Copy link
Contributor

sinkuu commented Apr 10, 2018

@chrisvittal

This PR allows using impl Trait in type parameter:

fn foo<T: Bar<impl Clone>>(_x: T) {
}

(despite being prohibited in where clause:

fn foo<T>(_x: T) where T: Bar<impl Clone> {
//^ `impl Trait` not allowed outside of function and inherent method return types
}

)

Is this intentional? I had a grance through RFC, and I think it does not state impl Trait can be used in type bounds.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants