-
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
The Great Generics Generalisation: HIR Edition #48149
Conversation
src/librustc/hir/intravisit.rs
Outdated
@@ -718,11 +718,11 @@ pub fn walk_ty_param_bound<'v, V: Visitor<'v>>(visitor: &mut V, bound: &'v TyPar | |||
|
|||
pub fn walk_generic_param<'v, V: Visitor<'v>>(visitor: &mut V, param: &'v GenericParam) { | |||
match *param { | |||
GenericParam::Lifetime(ref ld) => { | |||
GenericParameter::Lifetime(ref ld) => { |
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.
General comment: we prefer the shorter Param
to Parameter
(changes should be from the latter to the former, not the other way around).
src/libsyntax/ast.rs
Outdated
/// The type parameters for this path segment, if present. | ||
pub types: Vec<P<Ty>>, | ||
/// The parameters for this path segment. | ||
pub parameters: Vec<GenericAngleBracketedParam>, |
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.
Similarly to #45930, I don't see how this makes us closer to const generics.
Okay, maybe with merging lifetime and type arguments into an enum and using exhaustive matching everywhere we will less likely forget to add const generics somewhere, but that's all.
On the other hand, we get methods fn lifetimes
and fn types
below that have to allocate just to get the separate lists of lifetime and type arguments.
In addition, binding arguments are still separate, and we can't unify with fn-like arguments as well because they use totally different structure.
Seems like a net negative to me.
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.
Doing the same transformation in HIR (and in ty
) may be a better idea because we can unify with fn-like generic arguments, but from what I see it still makes things more complex, less convenient and we have to allocate just to visit something.
Can we maybe implement AST/HIR support for const generics first and only then exercise in questionable refactoring?
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'm also not a fan of generic enum GenericParameter
used both in HIR and in ty
.
There's plenty of opportunities to do this generalization in various places in HIR and ty, but this is not generally done, because... that's not useful, there's no reason to generalize here, let's keep HIR and ty separate.
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.
Okay, I guess without enum GenericParameter
the HIR refactoring is not that bad if we avoid allocation and use the same iterator-based approach to fn lifetimes
/fn types
as #45930 does.
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.
Also the things in this PR are GenericArgs
, not GenericParameters
(these already exist) or especially PathParameters
(it's hard to come up with worse name, they are neither parameters, nor apply to a path), good opportunity to rename!
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'm going to do a more thorough review but 1. I'm against abstracting enum
definitions when it doesn't have a drastically positive impact (I've debated this on IRC already) 2. I have high hopes for getting rid of lifetimes
/ types
methods in most places, and the few remaining places should just do it by hand with exhaustive matches.
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.
Oh, also, one of the reasons I'm against over-abstracting enum
s is because in some places there are actual common fields (like IDs & names) and it'd be useful to extract those out.
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.
In addition to not doing this refactoring on AST, I'd actually prefer to revert the AST part of #45930, at least until all kinds of generic parameters are allowed to intermingle (which may never happen). AST is supposed to be close to the source and I don't think it's desirable to make things impossible in source (like <'a, T, U, 'b>
) representable in it.
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.
That might be a worthwhile goal, but didn't the const
generics RFC impose no order between type and const
parameters? cc @withoutboats @nikomatsakis
Can we maybe implement AST/HIR support for const generics first and only then exercise in questionable refactoring?
FWIW I've been asking (on IRC) for the refactoring before landing const
generics, because I'm really worried that certain parts of the compiler which hackily use separate vectors for lifetimes and types will get even worse and make the refactoring more difficult.
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.
@petrochenkov: It doesn't really make const generics closer — it's intended to reduce the amount of boilerplate when they're introduced, as well as trigger errors if we forget to handle them in a particular spot.
I dislike the types()
/lifetimes()
usages too. I'd like to remove them wherever possible, as @eddyb suggests. I've removed the obvious instances where they were unnecessary, but hopefully there's a lot more clean up that can be done.
The names are effectively all placeholders at this stage — my main aim was to keep them separate, so they were easy to identity. It's very easy to rename them all!
@eddyb: if lifetimes()
/types()
can be mostly removed, then the common enum is less useful. But if no-one else likes it, I'll remove it now...
In addition to not doing this refactoring on AST, I'd actually prefer to revert the AST part of #45930, at least until all kinds of generic parameters are allowed to intermingle (which may never happen).
That might be a worthwhile goal, but didn't the const generics RFC impose no order between type and const parameters?
Ordering of type and const parameters was left as an open question in the RFC. However, I think it really makes sense to restrict const parameters (at least to begin with) to come after type parameters because of the dependencies it implies:
Types can depend on lifetimes; consts can depend on types (possible in theory, though not part of RFC 2000).
If consts can come before types, to me that implies that types dependent on consts can be specified, and Rust definitely isn't ready for those yet.
src/librustc/hir/mod.rs
Outdated
@@ -340,12 +341,12 @@ impl PathSegment { | |||
} | |||
} | |||
|
|||
pub type GenericPathParam = GenericParameter<Lifetime, P<Ty>>; |
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.
IMO this should be called just PathParam
(and PathParameters
below just PathParams
). Same for parameters
vs params
, but avoid changing too much existing code unless you want a separate PR.
3e8baf0
to
f9e0747
Compare
src/librustc/ty/subst.rs
Outdated
@@ -39,6 +39,20 @@ const TAG_MASK: usize = 0b11; | |||
const TYPE_TAG: usize = 0b00; | |||
const REGION_TAG: usize = 0b01; | |||
|
|||
pub enum GenericParameterKind<'tcx> { |
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.
Do we really need this? I know Kind
is a bit awkward to work with... but if you an UnpackedKind
it should maybe be a separate PR? Because you should remove as_{lifetime,type}
and force everything to go through UnpackedKind
.
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.
It simplifies one loop so far. I'll rename it now and think about whether it's worth committing to or not.
src/librustc/hir/intravisit.rs
Outdated
walk_list!(visitor, visit_lifetime, &path_parameters.lifetimes); | ||
walk_list!(visitor, visit_ty, &path_parameters.types); | ||
walk_list!(visitor, visit_lifetime, path_parameters.lifetimes()); | ||
walk_list!(visitor, visit_ty, path_parameters.types()); |
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 reason methods like lifetimes
/ types
should be avoided is that this code needs to look much different (it should iterate and match over path_parameters
- maybe with a visit_path_param
method in Visitor
).
src/librustc/hir/lowering.rs
Outdated
@@ -871,6 +872,20 @@ impl<'a> LoweringContext<'a> { | |||
} | |||
} | |||
|
|||
fn lower_param(&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.
lower_path_param
@@ -1680,17 +1680,27 @@ impl<'a> State<'a> { | |||
} | |||
}; | |||
|
|||
if !parameters.lifetimes.iter().all(|lt| lt.is_elided()) { | |||
for lifetime in ¶meters.lifetimes { | |||
start_or_comma(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.
Did you forget to include start_or_comma
in the final code?
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.
Yes, I forgot one of the edge cases! Thanks!
27bf7a3
to
5aeb66b
Compare
I agree with placing constant parameters/arguments between type parameters/arguments and type bindings (when applicable) for now, but I don't think dependencies you've described are the right reason. Generic parameters don't form a stack, they are introduced "simultaneously", you can refer to later parameters from earlier parameters, the only restriction is absence of cycles. struct S<T: PartialEq<U>, U>(T, U); // OK I'm mostly concerned about matching generic parameters with provided generic arguments, especially when some arguments are omitted. fn f<'a, T>(...) { ... }
f::<'lifetimes, Types>(...)
// is effectively
f::<'lifetimes><Types>(...)
// so if you omit lifetimes
f::<Types>(...)
// then it's still okay, even if `Types` is in the "wrong" position (first, not second)
// because this is equivalent to
f::<><Types>(...)
// i.e. inferred lifetime arguments, provided type arguments If intermingled generic parameters/arguments are allowed, they will have to support these kind of rules somehow. |
src/librustc/hir/mod.rs
Outdated
@@ -340,12 +340,16 @@ impl PathSegment { | |||
} | |||
} | |||
|
|||
#[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug)] | |||
pub enum PathParam { |
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.
See #48149 (comment)
Also the things in this PR are GenericArgs, not GenericParameters (these already exist) or especially PathParameters (it's hard to come up with worse name, they are neither parameters, nor apply to a path), good opportunity to rename!
If you keep this naming, I'll rename it later anyway, but it would be nice to avoid double cruft.
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.
Oh, sorry, I forgot you mentioned this! What do you suggest renaming PathParameters
to?
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.
PathParam
-> GenericArg
, PathParameters
-> GenericArgs
.
src/librustc/hir/mod.rs
Outdated
/// The type parameters for this path segment, if present. | ||
pub types: HirVec<P<Ty>>, | ||
/// The generic parameters for this path segment. | ||
pub parameters: HirVec<PathParam>, | ||
/// Bindings (equality constraints) on associated types, if present. | ||
/// E.g., `Foo<A=Bar>`. | ||
pub bindings: HirVec<TypeBinding>, |
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.
@eddyb
I wonder if bindings
should be included to the list as well.
On one hand, if the goal is to employ exhaustive matching to avoid errors, then they should be included. I would certainly expect a visitor method like fn visit_generic_arg
(aka fn visit_path_param
) to visit them as well.
On the other hand they are not quite like other generic arguments and don't directly mirror anything from GenericParams
.
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've seen ICEs caused by associated type bindings being forgotten about (fixed some of them in #43532).
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.
Yeah, I was considering this too. I think @eddyb's opinion was that they were different enough that it was better keeping them separate, but it'd be nice to have some way to ensure they were always handled too.
6c11160
to
48b7730
Compare
src/librustc/traits/select.rs
Outdated
@@ -564,7 +564,7 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> { | |||
let trait_ref = &mut trait_pred.trait_ref; | |||
let unit_substs = trait_ref.substs; | |||
let mut never_substs = Vec::with_capacity(unit_substs.len()); | |||
never_substs.push(UnpackedKind::Type(tcx.types.never).pack()); | |||
never_substs.push(From::from(tcx.types.never)); |
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.
This should be Kind::from
- or preferably .into()
.
Is there anything left to do here or any more concerns that need to be voiced? If not, is this ready to be moved along or should it be dropped? |
|
b53a2bb
to
5940768
Compare
I've addressed the latest comments — I can include type bindings in the refactoring if the decision is made. I think it's also reasonably something that could be done in a later PR if a conclusive decision is not made now. |
9bbcbed
to
4a919c9
Compare
☔ The latest upstream changes (presumably #48399) made this pull request unmergeable. Please resolve the merge conflicts. |
src/librustc/hir/mod.rs
Outdated
#[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug)] | ||
pub struct GenericArgs { | ||
/// The generic parameters for this path segment. | ||
pub parameters: HirVec<GenericArg>, |
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.
Shouldn't this be called args
?
@@ -355,30 +359,48 @@ pub struct PathParameters { | |||
pub parenthesized: 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.
Does this field make sense without this struct being called PathArgs
or something?
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.
Yes, the "parenthesized" property still applies to a single segment, not the whole path, e.g. Fn(Args1)::Output<Args2>
.
@@ -150,7 +149,7 @@ impl Region { | |||
} | |||
} | |||
|
|||
fn subst(self, params: &[hir::Lifetime], map: &NamedRegionMap) -> Option<Region> { | |||
fn subst(self, params: Vec<&hir::Lifetime>, map: &NamedRegionMap) -> Option<Region> { |
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.
This seems potentially expensive, why not use an 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.
Ah, I thought I had replaced these all with iterators; must have overlooked that one.
src/librustc/ty/mod.rs
Outdated
} | ||
|
||
#[derive(Clone, Debug, RustcEncodable, RustcDecodable)] | ||
pub struct KindIndexed<L, T> { |
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.
Do we really want this? Also I'd keep such a refactoring separate from AST/HIR IMO.
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.
Yeah, it's not used in very many places... I did it simply to avoid magic constants, but I was debating whether it was worth the extra declaration. I can separate it out regardless, though.
👏 👏 Thanks so much!! |
So, as far as I'm aware, there was one more small piece of refactoring to do — but I'm quite happy to make a follow-up PR for it to end the constant rebasing with this one. (I'm going to try to get to it by the weekend.) |
@varkor Sounds fair. Really good work on this. I think we all can’t wait to see some form of const generics support in nightly! |
The Great Generics Generalisation: HIR Edition This is essentially a followup to #45930, consolidating the use of separate lifetime and type vectors into single kinds vectors wherever possible. This is intended to provide more of the groundwork for const generics (#44580). r? @eddyb cc @yodaldevoid
🎉 |
☀️ Test successful - status-appveyor, status-travis |
@petrochenkov My bad (going through my GitHub notifications right now), and thanks for pushing this through! IIRC everything should've been addressed, but I'll take another look now to be sure. |
@@ -37,21 +37,30 @@ use hir::intravisit; | |||
|
|||
// Returns true if the given set of generics implies that the item it's | |||
// associated with must be inlined. | |||
fn generics_require_inlining(generics: &hir::Generics) -> bool { | |||
generics.params.iter().any(|param| param.is_type_param()) | |||
fn generics_require_inlining(generics: &ty::Generics) -> 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.
Doesn't this usecase already have a method on ty::Generics
?
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 so. At least, nothing with a similar name.
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 meant the behavior, not name. I'm pretty sure it's requires_monomorphization
.
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.
Okay, I'll change it.
ty::GenericParamDefKind::Lifetime { .. } => {}, | ||
ty::GenericParamDefKind::Type { .. } => return, | ||
} | ||
} |
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.
You already have Err(ty::layout::LayoutError::Unknown(_)) => return,
below, so you don't need this check anymore.
let has_types = generics.params.iter().any(|param| match param.kind { | ||
ty::GenericParamDefKind::Type { .. } => true, | ||
_ => false, | ||
}); |
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.
Again, isn't there a method on ty::Generics
for this check?
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.
There's a way to count the number of types, but that won't return early. I could add a method if it's frequent enough.
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.
Again, I think it's requires_monomorphization
.
let has_default = Untracked(ty_param.default.is_some()); | ||
self.record(def_id, IsolatedEncoder::encode_info_for_ty_param, (def_id, has_default)); | ||
} | ||
generics.params.iter().for_each(|param| match param.kind { |
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.
Unnecessary for_each
.
data.args.iter().for_each(|arg| match arg { | ||
GenericArg::Type(ty) => self.visit_ty(ty), | ||
_ => {} | ||
}); |
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.
Why doesn't this use visit_generic_arg
? Or is there no such method?
GenericParamKind::Type { .. } => { | ||
for bound in ¶m.bounds { | ||
self.check_generic_bound(bound); | ||
} |
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.
This can avoid looking at param.kind
.
GenericParamKind::Type { ref default, .. } => { | ||
if found_default || default.is_some() { | ||
found_default = true; | ||
return Some((Ident::with_empty_ctxt(param.ident.name), Def::Err)); |
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.
return
in a closure can be confusing - if
-else
works here. Also, an unconditional found_default |= default.is_some()
followed by if found_default
may be clearer.
GenericParamDefKind::Type { .. } => (false, true), | ||
}; | ||
provided.as_ref().and_then(|data| { | ||
for arg in &data.args { |
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.
Still quadratic - is fixing this part of the cleanup you mentioned?
I've left some comments. There are 11 uses of |
Addresses the errors produced by (re)moving, merging or renaming structs, fields and methods by rust-lang/rust#48149 and rust-lang/rust#51580
…=eddyb The Great Generics Generalisation: HIR Followup Addresses the final comments in #48149. r? @eddyb, but there are a few things I have yet to clean up. Making the PR now to more easily see when things break. cc @yodaldevoid
This is essentially a followup to #45930, consolidating the use of separate lifetime and type vectors into single kinds vectors wherever possible. This is intended to provide more of the groundwork for const generics (#44580).
r? @eddyb
cc @yodaldevoid