Auto merge of #122150 - ShoyuVanilla:replace-typewalker, r=lcnr

Replace `TypeWalker` usage with `TypeVisitor` in `wf.rs`

Resolves #121693
This commit is contained in:
bors 2024-03-09 12:02:25 +00:00
commit b054da8155
3 changed files with 311 additions and 301 deletions

View file

@ -2,7 +2,9 @@ use crate::infer::InferCtxt;
use crate::traits;
use rustc_hir as hir;
use rustc_hir::lang_items::LangItem;
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt};
use rustc_middle::ty::{
self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor,
};
use rustc_middle::ty::{GenericArg, GenericArgKind, GenericArgsRef};
use rustc_span::def_id::{DefId, LocalDefId, CRATE_DEF_ID};
use rustc_span::{Span, DUMMY_SP};
@ -535,305 +537,8 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> {
/// Pushes all the predicates needed to validate that `ty` is WF into `out`.
#[instrument(level = "debug", skip(self))]
fn compute(&mut self, arg: GenericArg<'tcx>) {
let mut walker = arg.walk();
let param_env = self.param_env;
let depth = self.recursion_depth;
while let Some(arg) = walker.next() {
debug!(?arg, ?self.out);
let ty = match arg.unpack() {
GenericArgKind::Type(ty) => ty,
// No WF constraints for lifetimes being present, any outlives
// obligations are handled by the parent (e.g. `ty::Ref`).
GenericArgKind::Lifetime(_) => continue,
GenericArgKind::Const(ct) => {
match ct.kind() {
ty::ConstKind::Unevaluated(uv) => {
if !ct.has_escaping_bound_vars() {
let obligations = self.nominal_obligations(uv.def, uv.args);
self.out.extend(obligations);
let predicate = ty::Binder::dummy(ty::PredicateKind::Clause(
ty::ClauseKind::ConstEvaluatable(ct),
));
let cause = self.cause(traits::WellFormed(None));
self.out.push(traits::Obligation::with_depth(
self.tcx(),
cause,
self.recursion_depth,
self.param_env,
predicate,
));
}
}
ty::ConstKind::Infer(_) => {
let cause = self.cause(traits::WellFormed(None));
self.out.push(traits::Obligation::with_depth(
self.tcx(),
cause,
self.recursion_depth,
self.param_env,
ty::Binder::dummy(ty::PredicateKind::Clause(
ty::ClauseKind::WellFormed(ct.into()),
)),
));
}
ty::ConstKind::Expr(_) => {
// FIXME(generic_const_exprs): this doesn't verify that given `Expr(N + 1)` the
// trait bound `typeof(N): Add<typeof(1)>` holds. This is currently unnecessary
// as `ConstKind::Expr` is only produced via normalization of `ConstKind::Unevaluated`
// which means that the `DefId` would have been typeck'd elsewhere. However in
// the future we may allow directly lowering to `ConstKind::Expr` in which case
// we would not be proving bounds we should.
let predicate = ty::Binder::dummy(ty::PredicateKind::Clause(
ty::ClauseKind::ConstEvaluatable(ct),
));
let cause = self.cause(traits::WellFormed(None));
self.out.push(traits::Obligation::with_depth(
self.tcx(),
cause,
self.recursion_depth,
self.param_env,
predicate,
));
}
ty::ConstKind::Error(_)
| ty::ConstKind::Param(_)
| ty::ConstKind::Bound(..)
| ty::ConstKind::Placeholder(..) => {
// These variants are trivially WF, so nothing to do here.
}
ty::ConstKind::Value(..) => {
// FIXME: Enforce that values are structurally-matchable.
}
}
continue;
}
};
debug!("wf bounds for ty={:?} ty.kind={:#?}", ty, ty.kind());
match *ty.kind() {
ty::Bool
| ty::Char
| ty::Int(..)
| ty::Uint(..)
| ty::Float(..)
| ty::Error(_)
| ty::Str
| ty::CoroutineWitness(..)
| ty::Never
| ty::Param(_)
| ty::Bound(..)
| ty::Placeholder(..)
| ty::Foreign(..) => {
// WfScalar, WfParameter, etc
}
// Can only infer to `ty::Int(_) | ty::Uint(_)`.
ty::Infer(ty::IntVar(_)) => {}
// Can only infer to `ty::Float(_)`.
ty::Infer(ty::FloatVar(_)) => {}
ty::Slice(subty) => {
self.require_sized(subty, traits::SliceOrArrayElem);
}
ty::Array(subty, _) => {
self.require_sized(subty, traits::SliceOrArrayElem);
// Note that we handle the len is implicitly checked while walking `arg`.
}
ty::Tuple(tys) => {
if let Some((_last, rest)) = tys.split_last() {
for &elem in rest {
self.require_sized(elem, traits::TupleElem);
}
}
}
ty::RawPtr(_) => {
// Simple cases that are WF if their type args are WF.
}
ty::Alias(ty::Projection, data) => {
walker.skip_current_subtree(); // Subtree handled by compute_projection.
self.compute_projection(data);
}
ty::Alias(ty::Inherent, data) => {
walker.skip_current_subtree(); // Subtree handled by compute_inherent_projection.
self.compute_inherent_projection(data);
}
ty::Adt(def, args) => {
// WfNominalType
let obligations = self.nominal_obligations(def.did(), args);
self.out.extend(obligations);
}
ty::FnDef(did, args) => {
let obligations = self.nominal_obligations(did, args);
self.out.extend(obligations);
}
ty::Ref(r, rty, _) => {
// WfReference
if !r.has_escaping_bound_vars() && !rty.has_escaping_bound_vars() {
let cause = self.cause(traits::ReferenceOutlivesReferent(ty));
self.out.push(traits::Obligation::with_depth(
self.tcx(),
cause,
depth,
param_env,
ty::Binder::dummy(ty::PredicateKind::Clause(
ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(rty, r)),
)),
));
}
}
ty::Coroutine(did, args, ..) => {
// Walk ALL the types in the coroutine: this will
// include the upvar types as well as the yield
// type. Note that this is mildly distinct from
// the closure case, where we have to be careful
// about the signature of the closure. We don't
// have the problem of implied bounds here since
// coroutines don't take arguments.
let obligations = self.nominal_obligations(did, args);
self.out.extend(obligations);
}
ty::Closure(did, args) => {
// Only check the upvar types for WF, not the rest
// of the types within. This is needed because we
// capture the signature and it may not be WF
// without the implied bounds. Consider a closure
// like `|x: &'a T|` -- it may be that `T: 'a` is
// not known to hold in the creator's context (and
// indeed the closure may not be invoked by its
// creator, but rather turned to someone who *can*
// verify that).
//
// The special treatment of closures here really
// ought not to be necessary either; the problem
// is related to #25860 -- there is no way for us
// to express a fn type complete with the implied
// bounds that it is assuming. I think in reality
// the WF rules around fn are a bit messed up, and
// that is the rot problem: `fn(&'a T)` should
// probably always be WF, because it should be
// shorthand for something like `where(T: 'a) {
// fn(&'a T) }`, as discussed in #25860.
walker.skip_current_subtree(); // subtree handled below
// FIXME(eddyb) add the type to `walker` instead of recursing.
self.compute(args.as_closure().tupled_upvars_ty().into());
// Note that we cannot skip the generic types
// types. Normally, within the fn
// body where they are created, the generics will
// always be WF, and outside of that fn body we
// are not directly inspecting closure types
// anyway, except via auto trait matching (which
// only inspects the upvar types).
// But when a closure is part of a type-alias-impl-trait
// then the function that created the defining site may
// have had more bounds available than the type alias
// specifies. This may cause us to have a closure in the
// hidden type that is not actually well formed and
// can cause compiler crashes when the user abuses unsafe
// code to procure such a closure.
// See tests/ui/type-alias-impl-trait/wf_check_closures.rs
let obligations = self.nominal_obligations(did, args);
self.out.extend(obligations);
}
ty::CoroutineClosure(did, args) => {
// See the above comments. The same apply to coroutine-closures.
walker.skip_current_subtree();
self.compute(args.as_coroutine_closure().tupled_upvars_ty().into());
let obligations = self.nominal_obligations(did, args);
self.out.extend(obligations);
}
ty::FnPtr(_) => {
// let the loop iterate into the argument/return
// types appearing in the fn signature
}
ty::Alias(ty::Opaque, ty::AliasTy { def_id, args, .. }) => {
// All of the requirements on type parameters
// have already been checked for `impl Trait` in
// return position. We do need to check type-alias-impl-trait though.
if self.tcx().is_type_alias_impl_trait(def_id) {
let obligations = self.nominal_obligations(def_id, args);
self.out.extend(obligations);
}
}
ty::Alias(ty::Weak, ty::AliasTy { def_id, args, .. }) => {
let obligations = self.nominal_obligations(def_id, args);
self.out.extend(obligations);
}
ty::Dynamic(data, r, _) => {
// WfObject
//
// Here, we defer WF checking due to higher-ranked
// regions. This is perhaps not ideal.
self.from_object_ty(ty, data, r);
// FIXME(#27579) RFC also considers adding trait
// obligations that don't refer to Self and
// checking those
let defer_to_coercion = self.tcx().features().object_safe_for_dispatch;
if !defer_to_coercion {
if let Some(principal) = data.principal_def_id() {
self.out.push(traits::Obligation::with_depth(
self.tcx(),
self.cause(traits::WellFormed(None)),
depth,
param_env,
ty::Binder::dummy(ty::PredicateKind::ObjectSafe(principal)),
));
}
}
}
// Inference variables are the complicated case, since we don't
// know what type they are. We do two things:
//
// 1. Check if they have been resolved, and if so proceed with
// THAT type.
// 2. If not, we've at least simplified things (e.g., we went
// from `Vec<$0>: WF` to `$0: WF`), so we can
// register a pending obligation and keep
// moving. (Goal is that an "inductive hypothesis"
// is satisfied to ensure termination.)
// See also the comment on `fn obligations`, describing "livelock"
// prevention, which happens before this can be reached.
ty::Infer(_) => {
let cause = self.cause(traits::WellFormed(None));
self.out.push(traits::Obligation::with_depth(
self.tcx(),
cause,
self.recursion_depth,
param_env,
ty::Binder::dummy(ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(
ty.into(),
))),
));
}
}
debug!(?self.out);
}
arg.visit_with(self);
debug!(?self.out);
}
#[instrument(level = "debug", skip(self))]
@ -933,6 +638,302 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> {
}
}
impl<'a, 'tcx> TypeVisitor<TyCtxt<'tcx>> for WfPredicates<'a, 'tcx> {
type Result = ();
fn visit_ty(&mut self, t: <TyCtxt<'tcx> as ty::Interner>::Ty) -> Self::Result {
debug!("wf bounds for t={:?} t.kind={:#?}", t, t.kind());
match *t.kind() {
ty::Bool
| ty::Char
| ty::Int(..)
| ty::Uint(..)
| ty::Float(..)
| ty::Error(_)
| ty::Str
| ty::CoroutineWitness(..)
| ty::Never
| ty::Param(_)
| ty::Bound(..)
| ty::Placeholder(..)
| ty::Foreign(..) => {
// WfScalar, WfParameter, etc
}
// Can only infer to `ty::Int(_) | ty::Uint(_)`.
ty::Infer(ty::IntVar(_)) => {}
// Can only infer to `ty::Float(_)`.
ty::Infer(ty::FloatVar(_)) => {}
ty::Slice(subty) => {
self.require_sized(subty, traits::SliceOrArrayElem);
}
ty::Array(subty, _) => {
self.require_sized(subty, traits::SliceOrArrayElem);
// Note that we handle the len is implicitly checked while walking `arg`.
}
ty::Tuple(tys) => {
if let Some((_last, rest)) = tys.split_last() {
for &elem in rest {
self.require_sized(elem, traits::TupleElem);
}
}
}
ty::RawPtr(_) => {
// Simple cases that are WF if their type args are WF.
}
ty::Alias(ty::Projection, data) => {
self.compute_projection(data);
return; // Subtree handled by compute_projection.
}
ty::Alias(ty::Inherent, data) => {
self.compute_inherent_projection(data);
return; // Subtree handled by compute_inherent_projection.
}
ty::Adt(def, args) => {
// WfNominalType
let obligations = self.nominal_obligations(def.did(), args);
self.out.extend(obligations);
}
ty::FnDef(did, args) => {
let obligations = self.nominal_obligations(did, args);
self.out.extend(obligations);
}
ty::Ref(r, rty, _) => {
// WfReference
if !r.has_escaping_bound_vars() && !rty.has_escaping_bound_vars() {
let cause = self.cause(traits::ReferenceOutlivesReferent(t));
self.out.push(traits::Obligation::with_depth(
self.tcx(),
cause,
self.recursion_depth,
self.param_env,
ty::Binder::dummy(ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(
ty::OutlivesPredicate(rty, r),
))),
));
}
}
ty::Coroutine(did, args, ..) => {
// Walk ALL the types in the coroutine: this will
// include the upvar types as well as the yield
// type. Note that this is mildly distinct from
// the closure case, where we have to be careful
// about the signature of the closure. We don't
// have the problem of implied bounds here since
// coroutines don't take arguments.
let obligations = self.nominal_obligations(did, args);
self.out.extend(obligations);
}
ty::Closure(did, args) => {
// Note that we cannot skip the generic types
// types. Normally, within the fn
// body where they are created, the generics will
// always be WF, and outside of that fn body we
// are not directly inspecting closure types
// anyway, except via auto trait matching (which
// only inspects the upvar types).
// But when a closure is part of a type-alias-impl-trait
// then the function that created the defining site may
// have had more bounds available than the type alias
// specifies. This may cause us to have a closure in the
// hidden type that is not actually well formed and
// can cause compiler crashes when the user abuses unsafe
// code to procure such a closure.
// See tests/ui/type-alias-impl-trait/wf_check_closures.rs
let obligations = self.nominal_obligations(did, args);
self.out.extend(obligations);
// Only check the upvar types for WF, not the rest
// of the types within. This is needed because we
// capture the signature and it may not be WF
// without the implied bounds. Consider a closure
// like `|x: &'a T|` -- it may be that `T: 'a` is
// not known to hold in the creator's context (and
// indeed the closure may not be invoked by its
// creator, but rather turned to someone who *can*
// verify that).
//
// The special treatment of closures here really
// ought not to be necessary either; the problem
// is related to #25860 -- there is no way for us
// to express a fn type complete with the implied
// bounds that it is assuming. I think in reality
// the WF rules around fn are a bit messed up, and
// that is the rot problem: `fn(&'a T)` should
// probably always be WF, because it should be
// shorthand for something like `where(T: 'a) {
// fn(&'a T) }`, as discussed in #25860.
let upvars = args.as_closure().tupled_upvars_ty();
return upvars.visit_with(self);
}
ty::CoroutineClosure(did, args) => {
// See the above comments. The same apply to coroutine-closures.
let obligations = self.nominal_obligations(did, args);
self.out.extend(obligations);
let upvars = args.as_coroutine_closure().tupled_upvars_ty();
return upvars.visit_with(self);
}
ty::FnPtr(_) => {
// Let the visitor iterate into the argument/return
// types appearing in the fn signature.
}
ty::Alias(ty::Opaque, ty::AliasTy { def_id, args, .. }) => {
// All of the requirements on type parameters
// have already been checked for `impl Trait` in
// return position. We do need to check type-alias-impl-trait though.
if self.tcx().is_type_alias_impl_trait(def_id) {
let obligations = self.nominal_obligations(def_id, args);
self.out.extend(obligations);
}
}
ty::Alias(ty::Weak, ty::AliasTy { def_id, args, .. }) => {
let obligations = self.nominal_obligations(def_id, args);
self.out.extend(obligations);
}
ty::Dynamic(data, r, _) => {
// WfObject
//
// Here, we defer WF checking due to higher-ranked
// regions. This is perhaps not ideal.
self.from_object_ty(t, data, r);
// FIXME(#27579) RFC also considers adding trait
// obligations that don't refer to Self and
// checking those
let defer_to_coercion = self.tcx().features().object_safe_for_dispatch;
if !defer_to_coercion {
if let Some(principal) = data.principal_def_id() {
self.out.push(traits::Obligation::with_depth(
self.tcx(),
self.cause(traits::WellFormed(None)),
self.recursion_depth,
self.param_env,
ty::Binder::dummy(ty::PredicateKind::ObjectSafe(principal)),
));
}
}
}
// Inference variables are the complicated case, since we don't
// know what type they are. We do two things:
//
// 1. Check if they have been resolved, and if so proceed with
// THAT type.
// 2. If not, we've at least simplified things (e.g., we went
// from `Vec<$0>: WF` to `$0: WF`), so we can
// register a pending obligation and keep
// moving. (Goal is that an "inductive hypothesis"
// is satisfied to ensure termination.)
// See also the comment on `fn obligations`, describing "livelock"
// prevention, which happens before this can be reached.
ty::Infer(_) => {
let cause = self.cause(traits::WellFormed(None));
self.out.push(traits::Obligation::with_depth(
self.tcx(),
cause,
self.recursion_depth,
self.param_env,
ty::Binder::dummy(ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(
t.into(),
))),
));
}
}
t.super_visit_with(self)
}
fn visit_const(&mut self, c: <TyCtxt<'tcx> as ty::Interner>::Const) -> Self::Result {
match c.kind() {
ty::ConstKind::Unevaluated(uv) => {
if !c.has_escaping_bound_vars() {
let obligations = self.nominal_obligations(uv.def, uv.args);
self.out.extend(obligations);
let predicate = ty::Binder::dummy(ty::PredicateKind::Clause(
ty::ClauseKind::ConstEvaluatable(c),
));
let cause = self.cause(traits::WellFormed(None));
self.out.push(traits::Obligation::with_depth(
self.tcx(),
cause,
self.recursion_depth,
self.param_env,
predicate,
));
}
}
ty::ConstKind::Infer(_) => {
let cause = self.cause(traits::WellFormed(None));
self.out.push(traits::Obligation::with_depth(
self.tcx(),
cause,
self.recursion_depth,
self.param_env,
ty::Binder::dummy(ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(
c.into(),
))),
));
}
ty::ConstKind::Expr(_) => {
// FIXME(generic_const_exprs): this doesn't verify that given `Expr(N + 1)` the
// trait bound `typeof(N): Add<typeof(1)>` holds. This is currently unnecessary
// as `ConstKind::Expr` is only produced via normalization of `ConstKind::Unevaluated`
// which means that the `DefId` would have been typeck'd elsewhere. However in
// the future we may allow directly lowering to `ConstKind::Expr` in which case
// we would not be proving bounds we should.
let predicate = ty::Binder::dummy(ty::PredicateKind::Clause(
ty::ClauseKind::ConstEvaluatable(c),
));
let cause = self.cause(traits::WellFormed(None));
self.out.push(traits::Obligation::with_depth(
self.tcx(),
cause,
self.recursion_depth,
self.param_env,
predicate,
));
}
ty::ConstKind::Error(_)
| ty::ConstKind::Param(_)
| ty::ConstKind::Bound(..)
| ty::ConstKind::Placeholder(..) => {
// These variants are trivially WF, so nothing to do here.
}
ty::ConstKind::Value(..) => {
// FIXME: Enforce that values are structurally-matchable.
}
}
c.super_visit_with(self)
}
fn visit_predicate(&mut self, _p: <TyCtxt<'tcx> as ty::Interner>::Predicate) -> Self::Result {
bug!("predicate should not be checked for well-formedness");
}
}
/// Given an object type like `SomeTrait + Send`, computes the lifetime
/// bounds that must hold on the elided self type. These are derived
/// from the declarations of `SomeTrait`, `Send`, and friends -- if

View file

@ -20,6 +20,7 @@ where
//~^^^ ERROR: function takes 1 generic argument but 2 generic arguments were supplied
//~^^^^ ERROR: unconstrained generic constant
//~^^^^^ ERROR: unconstrained generic constant `{const expr}`
//~^^^^^^ ERROR: unconstrained generic constant `{const expr}`
}
fn main() {}

View file

@ -58,7 +58,15 @@ error: unconstrained generic constant `{const expr}`
LL | foo::<_, L>([(); L + 1 + L]);
| ^^^^^^^^^^^
error: aborting due to 5 previous errors
error: unconstrained generic constant `{const expr}`
--> $DIR/issue_114151.rs:17:5
|
LL | foo::<_, L>([(); L + 1 + L]);
| ^^^^^^^^^^^
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
error: aborting due to 6 previous errors
Some errors have detailed explanations: E0107, E0308.
For more information about an error, try `rustc --explain E0107`.