-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Conversation
src/librustc_typeck/diagnostics.rs
Outdated
@@ -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. |
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.
Nits: "Declaration" should probably be lowercase, "s/it's/its"
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.
Thanks!
src/librustc_typeck/diagnostics.rs
Outdated
```compile_fail,E0642 | ||
#![feature(conservative_impl_trait)] | ||
trait Foo | ||
fn foo(&self, &impl Iterator) |
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.
Unnamed parameters are deprecated. Consider changing this to fn foo(&self, iter: &impl Iterator)
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 just noticed that I need to change the feature as well.
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.
@cramertj It is sufficient to go from &impl Iterator
to _: &impl Iterator
, correct?
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.
Sure, I suppose you could do that.
use std::fmt::Debug; | ||
|
||
trait Foo { | ||
fn foo(&self, &impl Debug); |
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.
DItto RE unnamed arguments
Try this again, |
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. |
@chrisvittal sorry didn't get a chance to review this weekend, will shortly though I promise! |
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.
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.
src/librustc/hir/lowering.rs
Outdated
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) { |
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.
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.
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 second is good. I forgot you could do that.
src/librustc/hir/lowering.rs
Outdated
@@ -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), |
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 don't think we want to allow impl trait in foreign items. That would be something like this:
extern fn foo() -> impl 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.
I agree. I overlooked this.
src/librustc/hir/lowering.rs
Outdated
} | ||
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, |
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.
Nit: each param on its own line.
src/librustc/hir/lowering.rs
Outdated
@@ -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, |
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.
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, |
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 commit message says is_in_impl_trait
, but the field is is_in_trait_impl
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.
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?
src/librustc_typeck/collect.rs
Outdated
} | ||
|
||
let mut visitor = ImplTraitUniversalVisitor { items: Vec::new() }; | ||
opt_inputs.map(|inputs| for t in inputs.iter() { |
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.
Nit: if let Some(inputs) = opt_inputs { ... }
would be clearer
Here are the things I think we need to fix before landing:
|
Big reason to deny fn in_generics_Fn<F: Fn() -> impl Debug>(_: F) {} |
@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. |
📌 Commit 53dceb4 has been approved by |
⌛ Testing commit 53dceb489e31daef08cd71b0cfba212223a24f87 with merge bf53d9c666f1f99e77ba994172a24091f23f0377... |
💔 Test failed - status-travis |
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.
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.
74aeb66
to
4ce61b7
Compare
@bors r+ |
📌 Commit 4ce61b7 has been approved by |
cc @Manishearth @oli-obk -- note that clippy is affected here (not sure if you were cc'd before). |
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
☀️ Test successful - status-appveyor, status-travis |
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. |
This PR allows using fn foo<T: Bar<impl Clone>>(_x: T) {
} (despite being prohibited in 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 |
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
tohir::TyImplTraitExistential
and addhir::TyImplTraitUniversal(DefId, TyParamBounds)
. TheDefId
is needed to extract the index of the parameter inast_ty_to_ty
.Introduce an
ImplTraitContext
enum to lowering to keep track of the kind and allowedness ofimpl Trait
in that position. This new argument is passed through many places, all ending up inlower_ty
.Modify
generics_of
andexplicit_predicates_of
to collect theimpl 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.
And vice versa.
Incedentally, supress
unused type parameter
errors if the type being compared is already aTyError
.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 newcompare_synthetic_generics
method.impl Trait
parameters arekeywords::Invalid.name()
. I would like them to beimpl ...
if possible, but I haven't figured out a way to do that yet.compare_synthetic_generics
I have tried to outline the open questions in the function itselfr? @nikomatsakis