Move nested quantification check to ast_validation

This commit is contained in:
Jack Huey 2021-04-21 03:12:04 -04:00
parent 9891582897
commit 4568e7d62e
2 changed files with 52 additions and 75 deletions

View file

@ -81,6 +81,13 @@ struct AstValidator<'a> {
is_assoc_ty_bound_banned: bool,
lint_buffer: &'a mut LintBuffer,
/// This is slightly complicated. Our representation for poly-trait-refs contains a single
/// binder and thus we only allow a single level of quantification. However,
/// the syntax of Rust permits quantification in two places in where clauses,
/// e.g., `T: for <'a> Foo<'a>` and `for <'a, 'b> &'b T: Foo<'a>`. If both are
/// defined, then error.
trait_ref_hack: bool,
}
impl<'a> AstValidator<'a> {
@ -1213,8 +1220,25 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
deny_equality_constraints(self, predicate, generics);
}
}
walk_list!(self, visit_generic_param, &generics.params);
for predicate in &generics.where_clause.predicates {
match predicate {
WherePredicate::BoundPredicate(bound_pred) => {
// A type binding, eg `for<'c> Foo: Send+Clone+'c`
self.check_late_bound_lifetime_defs(&bound_pred.bound_generic_params);
visit::walk_generics(self, generics)
self.visit_ty(&bound_pred.bounded_ty);
self.trait_ref_hack = !bound_pred.bound_generic_params.is_empty();
walk_list!(self, visit_param_bound, &bound_pred.bounds);
walk_list!(self, visit_generic_param, &bound_pred.bound_generic_params);
self.trait_ref_hack = false;
}
_ => {
self.visit_where_predicate(predicate);
}
}
}
}
fn visit_generic_param(&mut self, param: &'a GenericParam) {
@ -1263,17 +1287,21 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
visit::walk_pat(self, pat)
}
fn visit_where_predicate(&mut self, p: &'a WherePredicate) {
if let &WherePredicate::BoundPredicate(ref bound_predicate) = p {
// A type binding, eg `for<'c> Foo: Send+Clone+'c`
self.check_late_bound_lifetime_defs(&bound_predicate.bound_generic_params);
}
visit::walk_where_predicate(self, p);
}
fn visit_poly_trait_ref(&mut self, t: &'a PolyTraitRef, m: &'a TraitBoundModifier) {
self.check_late_bound_lifetime_defs(&t.bound_generic_params);
if self.trait_ref_hack && !t.bound_generic_params.is_empty() {
struct_span_err!(
self.err_handler(),
t.span,
E0316,
"nested quantification of lifetimes"
)
.emit();
}
let trait_ref_hack = self.trait_ref_hack;
self.trait_ref_hack = false;
visit::walk_poly_trait_ref(self, t, m);
self.trait_ref_hack = trait_ref_hack;
}
fn visit_variant_data(&mut self, s: &'a VariantData) {
@ -1492,6 +1520,7 @@ pub fn check_crate(session: &Session, krate: &Crate, lints: &mut LintBuffer) ->
is_impl_trait_banned: false,
is_assoc_ty_bound_banned: false,
lint_buffer: lints,
trait_ref_hack: false,
};
visit::walk_crate(&mut validator, krate);

View file

@ -278,29 +278,6 @@ enum BinderScopeType {
/// you had `T: for<'a> Foo<Bar: for<'b> Baz<'a, 'b>>`, then the `for<'a>`
/// scope uses `PolyTraitRef`.
PolyTraitRef,
/// This is slightly complicated. Our representation for poly-trait-refs contains a single
/// binder and thus we only allow a single level of quantification. However,
/// the syntax of Rust permits quantification in two places in where clauses,
/// e.g., `T: for <'a> Foo<'a>` and `for <'a, 'b> &'b T: Foo<'a>`. In order
/// to get the De Bruijn indices correct when representing these constraints,
/// we should only introduce one scope. However, we want to support both
/// locations for the quantifier and during lifetime resolution we want
/// precise information (so we can't desugar in an earlier phase). Moreso,
/// an error here doesn't cause a bail from type checking, so we need to be
/// extra careful that we don't lose any bound var information for *either*
/// syntactic binder and that we track all lifetimes defined in both binders.
///
/// This mechanism is similar to the concatenation done in nested poly trait
/// refs, i.e. the inner syntactic binder extends upon the lifetimes on the
/// outer syntactic binder. However, we require a separate variant here to
/// distinguish `for<'a> T: for<'b> Foo<'a, 'b>` from
/// `T: for<'a> Bar<Baz: for<'b> Foo<'a, 'b>>`. In this case, the innermost
/// `: for<'b> Foo<'a, 'b>` both have a `for<'a>` scope above it. However,
/// in the former case, we must emit an error because this is invalid syntax.
/// Put another way: `PolyTraitRef` and `BoundedTy` behave identically except
/// that `BoundedTy` is used to signal that an error should be emitted if
/// another syntactic binder is found.
BoundedTy,
/// Within a syntactic trait ref, there may be multiple poly trait refs that
/// are nested (under the `associcated_type_bounds` feature). The binders of
/// the innner poly trait refs are extended from the outer poly trait refs
@ -309,8 +286,7 @@ enum BinderScopeType {
/// would be `Concatenating`. This also used in trait refs in where clauses
/// where we have two binders `for<> T: for<> Foo` (I've intentionally left
/// out any lifetimes because they aren't needed to show the two scopes).
/// See `BoundedTy` for a bit more details, but the inner `for<>` has a scope
/// of `Concatenating`.
/// The inner `for<>` has a scope of `Concatenating`.
Concatenating,
/// Any other binder scopes. These are "normal" in that they increase the binder
/// depth, are fully syntactic, don't concatenate, and don't have special syntactical
@ -1311,7 +1287,7 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
next_early_index,
track_lifetime_uses: true,
opaque_type_parent: false,
scope_type: BinderScopeType::BoundedTy,
scope_type: BinderScopeType::PolyTraitRef,
};
this.with(scope, |old_scope, this| {
this.check_lifetime_params(old_scope, &bound_generic_params);
@ -1344,30 +1320,24 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
// FIXME(jackh726): This is pretty weird. `LangItemTrait` doesn't go
// through the regular poly trait ref code, so we don't get another
// chance to introduce a binder. For now, I'm keeping the existing logic
// of "if there isn't a `BoundedTy` scope above us, add one", but I
// of "if there isn't a Binder scope above us, add one", but I
// imagine there's a better way to go about this.
let mut scope = self.scope;
let trait_ref_hack = loop {
match scope {
Scope::Body { .. } | Scope::Root => {
Scope::TraitRefBoundary { .. } | Scope::Body { .. } | Scope::Root => {
break false;
}
Scope::Binder { .. } => {
break true;
}
Scope::Elision { s, .. }
| Scope::ObjectLifetimeDefault { s, .. }
| Scope::Supertrait { s, .. } => {
scope = s;
}
Scope::TraitRefBoundary { .. } => {
break false;
}
Scope::Binder { scope_type, lifetimes, .. } => {
let trait_ref_hack =
matches!(scope_type, BinderScopeType::BoundedTy) && !lifetimes.is_empty();
break trait_ref_hack;
}
}
};
match bound {
@ -1402,10 +1372,10 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
let next_early_index = self.next_early_index();
let mut scope = self.scope;
let mut supertrait_lifetimes = vec![];
let (mut binders, trait_ref_hack, scope_type) = loop {
let (mut binders, scope_type) = loop {
match scope {
Scope::Body { .. } | Scope::Root => {
break (vec![], false, BinderScopeType::PolyTraitRef);
break (vec![], BinderScopeType::PolyTraitRef);
}
Scope::Elision { s, .. } | Scope::ObjectLifetimeDefault { s, .. } => {
@ -1420,10 +1390,10 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
Scope::TraitRefBoundary { .. } => {
// We should only see super trait lifetimes if there is a `Binder` above
assert!(supertrait_lifetimes.is_empty());
break (vec![], false, BinderScopeType::PolyTraitRef);
break (vec![], BinderScopeType::PolyTraitRef);
}
Scope::Binder { hir_id, scope_type, lifetimes, .. } => {
Scope::Binder { hir_id, scope_type, .. } => {
if let BinderScopeType::Other = scope_type {
bug!(
"Expected all syntacic poly trait refs to be surrounded by a `TraitRefBoundary`"
@ -1434,30 +1404,11 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
let mut full_binders =
self.map.late_bound_vars.entry(*hir_id).or_default().clone();
full_binders.extend(supertrait_lifetimes.into_iter());
let trait_ref_hack =
matches!(scope_type, BinderScopeType::BoundedTy) && !lifetimes.is_empty();
break (full_binders, trait_ref_hack, BinderScopeType::Concatenating);
break (full_binders, BinderScopeType::Concatenating);
}
}
};
// See note on `BinderScopeType::BoundedTy`. If `for<..>`
// has been defined in both the outer and inner part of the
// trait ref, emit an error.
let has_lifetimes = trait_ref.bound_generic_params.iter().any(|param| match param.kind {
GenericParamKind::Lifetime { .. } => true,
_ => false,
});
if trait_ref_hack && has_lifetimes {
struct_span_err!(
self.tcx.sess,
trait_ref.span,
E0316,
"nested quantification of lifetimes"
)
.emit();
}
let initial_bound_vars = binders.len() as u32;
let mut lifetimes: FxHashMap<hir::ParamName, Region> = FxHashMap::default();
let binders_iter = trait_ref
@ -1486,7 +1437,7 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
// Always introduce a scope here, even if this is in a where clause and
// we introduced the binders around the bounded Ty. In that case, we
// just reuse the concatenation functionality also present in nested trait
// refs. See `BinderScopeType::BoundedTy` for more details on that case.
// refs.
let scope = Scope::Binder {
hir_id: trait_ref.trait_ref.hir_ref_id,
lifetimes,
@ -2319,7 +2270,6 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
}
match scope_type {
BinderScopeType::Other => late_depth += 1,
BinderScopeType::BoundedTy => late_depth += 1,
BinderScopeType::PolyTraitRef => late_depth += 1,
BinderScopeType::Concatenating => {}
}
@ -3051,7 +3001,6 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
}
match scope_type {
BinderScopeType::Other => late_depth += 1,
BinderScopeType::BoundedTy => late_depth += 1,
BinderScopeType::PolyTraitRef => late_depth += 1,
BinderScopeType::Concatenating => {}
}
@ -3216,7 +3165,6 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
Scope::Binder { s, scope_type, .. } => {
match scope_type {
BinderScopeType::Other => late_depth += 1,
BinderScopeType::BoundedTy => late_depth += 1,
BinderScopeType::PolyTraitRef => late_depth += 1,
BinderScopeType::Concatenating => {}
}