Classify outlives constraints when type checking

The MIR/NLL type checker is in a much better position to classify
constraints and already has to classify into boring and interesting.
Adds spans to Locations::All for error reporting
Adds more constraint categories
This commit is contained in:
Matthew Jasper 2018-09-15 18:30:14 +01:00
parent 6e425219f1
commit 994dc4bd1e
12 changed files with 379 additions and 306 deletions

View file

@ -284,7 +284,7 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
}
}
fn constrain_opaque_type<FRR: FreeRegionRelations<'tcx>>(
pub fn constrain_opaque_type<FRR: FreeRegionRelations<'tcx>>(
&self,
def_id: DefId,
opaque_defn: &OpaqueTypeDecl<'tcx>,

View file

@ -9,10 +9,12 @@
// except according to those terms.
use borrow_check::nll::type_check::Locations;
use borrow_check::nll::constraints::{ConstraintIndex, ConstraintSet, OutlivesConstraint};
use borrow_check::nll::constraints::{ConstraintCategory, ConstraintIndex};
use borrow_check::nll::constraints::{ConstraintSet, OutlivesConstraint};
use rustc::ty::RegionVid;
use rustc_data_structures::graph;
use rustc_data_structures::indexed_vec::IndexVec;
use syntax_pos::DUMMY_SP;
/// The construct graph organizes the constraints by their end-points.
/// It can be used to view a `R1: R2` constraint as either an edge `R1
@ -174,7 +176,8 @@ impl<'s, D: ConstraintGraphDirecton> Iterator for Edges<'s, D> {
Some(OutlivesConstraint {
sup: self.static_region,
sub: next_static_idx.into(),
locations: Locations::All,
locations: Locations::All(DUMMY_SP),
category: ConstraintCategory::Internal,
})
} else {
None

View file

@ -23,6 +23,42 @@ crate struct ConstraintSet {
constraints: IndexVec<ConstraintIndex, OutlivesConstraint>,
}
/// Constraints can be categorized to determine whether and why they are
/// interesting. Order of variants indicates sort order of the category,
/// thereby influencing diagnostic output.
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub enum ConstraintCategory {
Return,
TypeAnnotation,
Cast,
CallArgument,
/// A constraint that came from checking the body of a closure.
///
/// Ideally we would give an explanation that points to the relevant part
/// of the closure's body.
ClosureBounds,
CopyBound,
SizedBound,
Assignment,
OpaqueType,
/// A "boring" constraint (caused by the given location) is one that
/// the user probably doesn't want to see described in diagnostics,
/// because it is kind of an artifact of the type system setup.
/// Example: `x = Foo { field: y }` technically creates
/// intermediate regions representing the "type of `Foo { field: y
/// }`", and data flows from `y` into those variables, but they
/// are not very interesting. The assignment into `x` on the other
/// hand might be.
Boring,
// Boring and applicable everywhere.
BoringNoLocation,
/// A constraint that doesn't correspond to anything the user sees.
Internal,
}
impl ConstraintSet {
crate fn push(&mut self, constraint: OutlivesConstraint) {
debug!(
@ -87,6 +123,9 @@ pub struct OutlivesConstraint {
/// Where did this constraint arise?
pub locations: Locations,
/// What caused this constraint?
pub category: ConstraintCategory,
}
impl fmt::Debug for OutlivesConstraint {

View file

@ -88,11 +88,13 @@ impl<'tcx> RegionInferenceContext<'tcx> {
sup,
sub,
locations,
category,
} = constraint;
with_msg(&format!(
"{:?}: {:?} due to {:?}",
"{:?}: {:?} due to {:?} at {:?}",
sup,
sub,
category,
locations,
))?;
}

View file

@ -8,14 +8,13 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use borrow_check::nll::constraints::OutlivesConstraint;
use borrow_check::nll::constraints::{OutlivesConstraint, ConstraintCategory};
use borrow_check::nll::region_infer::RegionInferenceContext;
use borrow_check::nll::type_check::Locations;
use rustc::hir::def_id::DefId;
use rustc::infer::error_reporting::nice_region_error::NiceRegionError;
use rustc::infer::InferCtxt;
use rustc::mir::{self, Location, Mir, Place, Rvalue, StatementKind, TerminatorKind};
use rustc::ty::{self, TyCtxt, RegionVid};
use rustc::mir::{Location, Mir};
use rustc::ty::{self, RegionVid};
use rustc_data_structures::indexed_vec::IndexVec;
use rustc_errors::{Diagnostic, DiagnosticBuilder};
use std::collections::VecDeque;
@ -28,19 +27,6 @@ mod var_name;
use self::region_name::RegionName;
/// Constraints that are considered interesting can be categorized to
/// determine why they are interesting. Order of variants indicates
/// sort order of the category, thereby influencing diagnostic output.
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
enum ConstraintCategory {
Cast,
Assignment,
Return,
CallArgument,
Other,
Boring,
}
impl fmt::Display for ConstraintCategory {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Must end with a space. Allows for empty names to be provided.
@ -49,7 +35,14 @@ impl fmt::Display for ConstraintCategory {
ConstraintCategory::Return => write!(f, "returning this value "),
ConstraintCategory::Cast => write!(f, "cast "),
ConstraintCategory::CallArgument => write!(f, "argument "),
_ => write!(f, ""),
ConstraintCategory::TypeAnnotation => write!(f, "type annotation "),
ConstraintCategory::ClosureBounds => write!(f, "closure body "),
ConstraintCategory::SizedBound => write!(f, "proving this value is `Sized` "),
ConstraintCategory::CopyBound => write!(f, "copying this value "),
ConstraintCategory::OpaqueType => write!(f, "opaque type "),
ConstraintCategory::Boring
| ConstraintCategory::BoringNoLocation
| ConstraintCategory::Internal => write!(f, ""),
}
}
}
@ -71,7 +64,6 @@ impl<'tcx> RegionInferenceContext<'tcx> {
fn best_blame_constraint(
&self,
mir: &Mir<'tcx>,
tcx: TyCtxt<'_, '_, 'tcx>,
from_region: RegionVid,
target_test: impl Fn(RegionVid) -> bool,
) -> (ConstraintCategory, Span, RegionVid) {
@ -96,7 +88,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
// Classify each of the constraints along the path.
let mut categorized_path: Vec<(ConstraintCategory, Span)> = path
.iter()
.map(|&index| self.classify_constraint(index, mir, tcx))
.map(|constraint| (constraint.category, constraint.locations.span(mir)))
.collect();
debug!(
"best_blame_constraint: categorized_path={:#?}",
@ -129,12 +121,10 @@ impl<'tcx> RegionInferenceContext<'tcx> {
let constraint_sup_scc = self.constraint_sccs.scc(constraint.sup);
match categorized_path[i].0 {
ConstraintCategory::Boring => false,
ConstraintCategory::Other => {
// other isn't interesting when the two lifetimes
// are unified.
constraint_sup_scc != self.constraint_sccs.scc(constraint.sub)
}
ConstraintCategory::OpaqueType
| ConstraintCategory::Boring
| ConstraintCategory::BoringNoLocation
| ConstraintCategory::Internal => false,
_ => constraint_sup_scc != target_scc,
}
});
@ -220,106 +210,6 @@ impl<'tcx> RegionInferenceContext<'tcx> {
None
}
/// This function will return true if a constraint is interesting and false if a constraint
/// is not. It is useful in filtering constraint paths to only interesting points.
fn constraint_is_interesting(&self, constraint: OutlivesConstraint) -> bool {
debug!(
"constraint_is_interesting: locations={:?} constraint={:?}",
constraint.locations, constraint
);
match constraint.locations {
Locations::Interesting(_) | Locations::All => true,
_ => false,
}
}
/// This function classifies a constraint from a location.
fn classify_constraint(
&self,
constraint: OutlivesConstraint,
mir: &Mir<'tcx>,
tcx: TyCtxt<'_, '_, 'tcx>,
) -> (ConstraintCategory, Span) {
debug!("classify_constraint: constraint={:?}", constraint);
let span = constraint.locations.span(mir);
let location = constraint
.locations
.from_location()
.unwrap_or(Location::START);
if !self.constraint_is_interesting(constraint) {
return (ConstraintCategory::Boring, span);
}
let data = &mir[location.block];
debug!(
"classify_constraint: location={:?} data={:?}",
location, data
);
let category = if location.statement_index == data.statements.len() {
if let Some(ref terminator) = data.terminator {
debug!("classify_constraint: terminator.kind={:?}", terminator.kind);
match terminator.kind {
TerminatorKind::DropAndReplace { .. } => ConstraintCategory::Assignment,
// Classify calls differently depending on whether or not
// the sub region appears in the destination type (so the
// sup region is in the return type). If the return type
// contains the sub-region, then this is either an
// assignment or a return, depending on whether we are
// writing to the RETURN_PLACE or not.
//
// The idea here is that the region is being propagated
// from an input into the output place, so it's a kind of
// assignment. Otherwise, if the sub-region only appears in
// the argument types, then use the CallArgument
// classification.
TerminatorKind::Call { destination: Some((ref place, _)), .. } => {
if tcx.any_free_region_meets(
&place.ty(mir, tcx).to_ty(tcx),
|region| self.to_region_vid(region) == constraint.sub,
) {
match place {
Place::Local(mir::RETURN_PLACE) => ConstraintCategory::Return,
_ => ConstraintCategory::Assignment,
}
} else {
ConstraintCategory::CallArgument
}
}
TerminatorKind::Call { destination: None, .. } => {
ConstraintCategory::CallArgument
}
_ => ConstraintCategory::Other,
}
} else {
ConstraintCategory::Other
}
} else {
let statement = &data.statements[location.statement_index];
debug!("classify_constraint: statement.kind={:?}", statement.kind);
match statement.kind {
StatementKind::Assign(ref place, ref rvalue) => {
debug!("classify_constraint: place={:?} rvalue={:?}", place, rvalue);
if *place == Place::Local(mir::RETURN_PLACE) {
ConstraintCategory::Return
} else {
match rvalue {
Rvalue::Cast(..) => ConstraintCategory::Cast,
Rvalue::Use(..) | Rvalue::Aggregate(..) => {
ConstraintCategory::Assignment
}
_ => ConstraintCategory::Other,
}
}
}
_ => ConstraintCategory::Other,
}
};
(category, span)
}
/// Report an error because the universal region `fr` was required to outlive
/// `outlived_fr` but it is not known to do so. For example:
///
@ -341,7 +231,6 @@ impl<'tcx> RegionInferenceContext<'tcx> {
let (category, span, _) = self.best_blame_constraint(
mir,
infcx.tcx,
fr,
|r| r == outlived_fr
);
@ -574,11 +463,10 @@ impl<'tcx> RegionInferenceContext<'tcx> {
crate fn find_outlives_blame_span(
&self,
mir: &Mir<'tcx>,
tcx: TyCtxt<'_, '_, 'tcx>,
fr1: RegionVid,
fr2: RegionVid,
) -> Span {
let (_, span, _) = self.best_blame_constraint(mir, tcx, fr1, |r| r == fr2);
let (_, span, _) = self.best_blame_constraint(mir, fr1, |r| r == fr2);
span
}
}

View file

@ -1062,7 +1062,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
longer_fr, shorter_fr,
);
let blame_span = self.find_outlives_blame_span(mir, infcx.tcx, longer_fr, shorter_fr);
let blame_span = self.find_outlives_blame_span(mir, longer_fr, shorter_fr);
if let Some(propagated_outlives_requirements) = propagated_outlives_requirements {
// Shrink `fr` until we find a non-local region (if we do).
@ -1147,7 +1147,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
};
// Find the code to blame for the fact that `longer_fr` outlives `error_fr`.
let span = self.find_outlives_blame_span(mir, infcx.tcx, longer_fr, error_region);
let span = self.find_outlives_blame_span(mir, longer_fr, error_region);
// Obviously, this error message is far from satisfactory.
// At present, though, it only appears in unit tests --

View file

@ -9,7 +9,7 @@
// except according to those terms.
use borrow_check::location::LocationTable;
use borrow_check::nll::constraints::{ConstraintSet, OutlivesConstraint};
use borrow_check::nll::constraints::{ConstraintCategory, ConstraintSet, OutlivesConstraint};
use borrow_check::nll::facts::AllFacts;
use borrow_check::nll::region_infer::{RegionTest, TypeTest};
use borrow_check::nll::type_check::Locations;
@ -30,6 +30,7 @@ crate struct ConstraintConversion<'a, 'gcx: 'tcx, 'tcx: 'a> {
implicit_region_bound: Option<ty::Region<'tcx>>,
param_env: ty::ParamEnv<'tcx>,
locations: Locations,
category: ConstraintCategory,
outlives_constraints: &'a mut ConstraintSet,
type_tests: &'a mut Vec<TypeTest<'tcx>>,
all_facts: &'a mut Option<AllFacts>,
@ -44,6 +45,7 @@ impl<'a, 'gcx, 'tcx> ConstraintConversion<'a, 'gcx, 'tcx> {
implicit_region_bound: Option<ty::Region<'tcx>>,
param_env: ty::ParamEnv<'tcx>,
locations: Locations,
category: ConstraintCategory,
outlives_constraints: &'a mut ConstraintSet,
type_tests: &'a mut Vec<TypeTest<'tcx>>,
all_facts: &'a mut Option<AllFacts>,
@ -56,6 +58,7 @@ impl<'a, 'gcx, 'tcx> ConstraintConversion<'a, 'gcx, 'tcx> {
implicit_region_bound,
param_env,
locations,
category,
outlives_constraints,
type_tests,
all_facts,
@ -183,6 +186,7 @@ impl<'a, 'gcx, 'tcx> ConstraintConversion<'a, 'gcx, 'tcx> {
fn add_outlives(&mut self, sup: ty::RegionVid, sub: ty::RegionVid) {
self.outlives_constraints.push(OutlivesConstraint {
locations: self.locations,
category: self.category,
sub,
sup,
});

View file

@ -14,6 +14,7 @@ use borrow_check::nll::type_check::constraint_conversion;
use borrow_check::nll::type_check::{Locations, MirTypeckRegionConstraints};
use borrow_check::nll::universal_regions::UniversalRegions;
use borrow_check::nll::ToRegionVid;
use borrow_check::nll::constraints::ConstraintCategory;
use rustc::infer::canonical::QueryRegionConstraint;
use rustc::infer::outlives::free_region_map::FreeRegionRelations;
use rustc::infer::region_constraints::GenericKind;
@ -23,6 +24,7 @@ use rustc::traits::query::type_op::{self, TypeOp};
use rustc::ty::{self, RegionVid, Ty};
use rustc_data_structures::transitive_relation::TransitiveRelation;
use std::rc::Rc;
use syntax_pos::DUMMY_SP;
#[derive(Debug)]
crate struct UniversalRegionRelations<'tcx> {
@ -283,7 +285,8 @@ impl UniversalRegionRelationsBuilder<'cx, 'gcx, 'tcx> {
&self.region_bound_pairs,
self.implicit_region_bound,
self.param_env,
Locations::All,
Locations::All(DUMMY_SP),
ConstraintCategory::Internal,
&mut self.constraints.outlives_constraints,
&mut self.constraints.type_tests,
&mut self.all_facts,

View file

@ -29,8 +29,9 @@ use rustc::ty::subst::Subst;
use rustc::ty::Ty;
use rustc_data_structures::indexed_vec::Idx;
use syntax_pos::Span;
use super::{Locations, TypeChecker};
use super::{ConstraintCategory, Locations, TypeChecker};
impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
pub(super) fn equate_inputs_and_outputs(
@ -56,7 +57,12 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
);
let mir_input_ty = mir.local_decls[local].ty;
self.equate_normalized_input_or_output(normalized_input_ty, mir_input_ty);
let mir_input_span = mir.local_decls[local].source_info.span;
self.equate_normalized_input_or_output(
normalized_input_ty,
mir_input_ty,
mir_input_span,
);
}
assert!(
@ -65,16 +71,19 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
);
if let Some(mir_yield_ty) = mir.yield_ty {
let ur_yield_ty = universal_regions.yield_ty.unwrap();
self.equate_normalized_input_or_output(ur_yield_ty, mir_yield_ty);
let yield_span = mir.local_decls[RETURN_PLACE].source_info.span;
self.equate_normalized_input_or_output(ur_yield_ty, mir_yield_ty, yield_span);
}
// Return types are a bit more complex. They may contain existential `impl Trait`
// types.
let param_env = self.param_env;
let mir_output_ty = mir.local_decls[RETURN_PLACE].ty;
let output_span = mir.local_decls[RETURN_PLACE].source_info.span;
let opaque_type_map =
self.fully_perform_op(
Locations::All,
Locations::All(output_span),
ConstraintCategory::BoringNoLocation,
CustomTypeOp::new(
|infcx| {
let mut obligations = ObligationAccumulator::default();
@ -152,26 +161,38 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
// prove that `T: Iterator` where `T` is the type we
// instantiated it with).
if let Some(opaque_type_map) = opaque_type_map {
self.fully_perform_op(
Locations::All,
CustomTypeOp::new(
|_cx| {
infcx.constrain_opaque_types(&opaque_type_map, universal_region_relations);
Ok(InferOk {
value: (),
obligations: vec![],
})
},
|| "opaque_type_map".to_string(),
),
).unwrap();
for (opaque_def_id, opaque_decl) in opaque_type_map {
self.fully_perform_op(
Locations::All(infcx.tcx.def_span(opaque_def_id)),
ConstraintCategory::OpaqueType,
CustomTypeOp::new(
|_cx| {
infcx.constrain_opaque_type(
opaque_def_id,
&opaque_decl,
universal_region_relations
);
Ok(InferOk {
value: (),
obligations: vec![],
})
},
|| "opaque_type_map".to_string(),
),
).unwrap();
}
}
}
fn equate_normalized_input_or_output(&mut self, a: Ty<'tcx>, b: Ty<'tcx>) {
fn equate_normalized_input_or_output(&mut self, a: Ty<'tcx>, b: Ty<'tcx>, span: Span) {
debug!("equate_normalized_input_or_output(a={:?}, b={:?})", a, b);
if let Err(terr) = self.eq_types(a, b, Locations::All) {
if let Err(terr) = self.eq_types(
a,
b,
Locations::All(span),
ConstraintCategory::BoringNoLocation,
) {
span_mirbug!(
self,
Location::START,

View file

@ -8,10 +8,11 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use borrow_check::nll::constraints::ConstraintCategory;
use borrow_check::nll::region_infer::values::{self, PointIndex, RegionValueElements};
use borrow_check::nll::type_check::liveness::liveness_map::{LiveVar, NllLivenessMap};
use borrow_check::nll::type_check::liveness::local_use_map::LocalUseMap;
use borrow_check::nll::type_check::AtLocation;
use borrow_check::nll::type_check::NormalizeLocation;
use borrow_check::nll::type_check::TypeChecker;
use dataflow::move_paths::indexes::MovePathIndex;
use dataflow::move_paths::MoveData;
@ -487,7 +488,11 @@ impl LivenessContext<'_, '_, '_, '_, 'tcx> {
if let Some(data) = &drop_data.region_constraint_data {
for &drop_location in drop_locations {
self.typeck
.push_region_constraints(drop_location.boring(), data);
.push_region_constraints(
drop_location.to_locations(),
ConstraintCategory::Boring,
data,
);
}
}

View file

@ -13,7 +13,7 @@
use borrow_check::borrow_set::BorrowSet;
use borrow_check::location::LocationTable;
use borrow_check::nll::constraints::{ConstraintSet, OutlivesConstraint};
use borrow_check::nll::constraints::{ConstraintCategory, ConstraintSet, OutlivesConstraint};
use borrow_check::nll::facts::AllFacts;
use borrow_check::nll::region_infer::values::{LivenessValues, RegionValueElements};
use borrow_check::nll::region_infer::{ClosureRegionRequirementsExt, TypeTest};
@ -252,7 +252,8 @@ impl<'a, 'b, 'gcx, 'tcx> Visitor<'tcx> for TypeVerifier<'a, 'b, 'gcx, 'tcx> {
constant.ty,
ty::Variance::Invariant,
user_ty,
location.boring(),
location.to_locations(),
ConstraintCategory::Boring,
) {
span_mirbug!(
self,
@ -281,7 +282,8 @@ impl<'a, 'b, 'gcx, 'tcx> Visitor<'tcx> for TypeVerifier<'a, 'b, 'gcx, 'tcx> {
local_decl.ty,
ty::Variance::Invariant,
user_ty,
Locations::All,
Locations::All(local_decl.source_info.span),
ConstraintCategory::TypeAnnotation,
) {
span_mirbug!(
self,
@ -364,14 +366,19 @@ impl<'a, 'b, 'gcx, 'tcx> TypeVerifier<'a, 'b, 'gcx, 'tcx> {
let instantiated_predicates = tcx.predicates_of(def_id).instantiate(tcx, substs);
type_checker.normalize_and_prove_instantiated_predicates(
instantiated_predicates,
location.boring(),
location.to_locations(),
);
}
debug!("sanitize_constant: expected_ty={:?}", constant.literal.ty);
if let Err(terr) = self.cx
.eq_types(constant.literal.ty, constant.ty, location.boring())
.eq_types(
constant.literal.ty,
constant.ty,
location.to_locations(),
ConstraintCategory::Boring,
)
{
span_mirbug!(
self,
@ -417,7 +424,12 @@ impl<'a, 'b, 'gcx, 'tcx> TypeVerifier<'a, 'b, 'gcx, 'tcx> {
let sty = self.sanitize_type(place, sty);
let ty = self.tcx().type_of(def_id);
let ty = self.cx.normalize(ty, location);
if let Err(terr) = self.cx.eq_types(ty, sty, location.boring()) {
if let Err(terr) = self.cx.eq_types(
ty,
sty,
location.to_locations(),
ConstraintCategory::Boring,
) {
span_mirbug!(
self,
place,
@ -461,7 +473,11 @@ impl<'a, 'b, 'gcx, 'tcx> TypeVerifier<'a, 'b, 'gcx, 'tcx> {
// (e.g., #29149). Note that we decide to use Copy before knowing whether the bounds
// fully apply: in effect, the rule is that if a value of some type could implement
// Copy, then it must.
self.cx.prove_trait_ref(trait_ref, location.interesting());
self.cx.prove_trait_ref(
trait_ref,
location.to_locations(),
ConstraintCategory::CopyBound,
);
}
place_ty
}
@ -560,7 +576,12 @@ impl<'a, 'b, 'gcx, 'tcx> TypeVerifier<'a, 'b, 'gcx, 'tcx> {
ProjectionElem::Field(field, fty) => {
let fty = self.sanitize_type(place, fty);
match self.field_ty(place, base, field, location) {
Ok(ty) => if let Err(terr) = self.cx.eq_types(ty, fty, location.boring()) {
Ok(ty) => if let Err(terr) = self.cx.eq_types(
ty,
fty,
location.to_locations(),
ConstraintCategory::Boring,
) {
span_mirbug!(
self,
place,
@ -740,43 +761,32 @@ pub enum Locations {
/// user-given type annotations; e.g., if the user wrote `let mut
/// x: &'static u32 = ...`, we would ensure that all values
/// assigned to `x` are of `'static` lifetime.
All,
/// A "boring" constraint (caused by the given location) is one that
/// the user probably doesn't want to see described in diagnostics,
/// because it is kind of an artifact of the type system setup.
///
/// Example: `x = Foo { field: y }` technically creates
/// intermediate regions representing the "type of `Foo { field: y
/// }`", and data flows from `y` into those variables, but they
/// are not very interesting. The assignment into `x` on the other
/// hand might be.
Boring(Location),
/// The span points to the place the constraint arose. For example,
/// it points to the type in a user-given type annotation. If
/// there's no sensible span then it's DUMMY_SP.
All(Span),
/// An *important* outlives constraint (caused by the given
/// location) is one that would be useful to highlight in
/// diagnostics, because it represents a point where references
/// flow from one spot to another (e.g., `x = y`)
Interesting(Location),
/// An outlives constraint that only has to hold at a single location,
/// usually it represents a point where references flow from one spot to
/// another (e.g., `x = y`)
Single(Location),
}
impl Locations {
pub fn from_location(&self) -> Option<Location> {
match self {
Locations::All => None,
Locations::Boring(from_location) | Locations::Interesting(from_location) => {
Some(*from_location)
}
Locations::All(_) => None,
Locations::Single(from_location) => Some(*from_location),
}
}
/// Gets a span representing the location.
pub fn span(&self, mir: &Mir<'_>) -> Span {
let span_location = match self {
Locations::All => Location::START,
Locations::Boring(l) | Locations::Interesting(l) => *l,
};
mir.source_info(span_location).span
match self {
Locations::All(span) => *span,
Locations::Single(l) => mir.source_info(*l).span,
}
}
}
@ -816,12 +826,13 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
fn fully_perform_op<R>(
&mut self,
locations: Locations,
category: ConstraintCategory,
op: impl type_op::TypeOp<'gcx, 'tcx, Output = R>,
) -> Fallible<R> {
let (r, opt_data) = op.fully_perform(self.infcx)?;
if let Some(data) = &opt_data {
self.push_region_constraints(locations, data);
self.push_region_constraints(locations, category, data);
}
Ok(r)
@ -830,6 +841,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
fn push_region_constraints(
&mut self,
locations: Locations,
category: ConstraintCategory,
data: &[QueryRegionConstraint<'tcx>],
) {
debug!(
@ -846,6 +858,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
self.implicit_region_bound,
self.param_env,
locations,
category,
&mut borrowck_context.constraints.outlives_constraints,
&mut borrowck_context.constraints.type_tests,
&mut borrowck_context.all_facts,
@ -853,22 +866,36 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
}
}
fn sub_types(&mut self, sub: Ty<'tcx>, sup: Ty<'tcx>, locations: Locations) -> Fallible<()> {
fn sub_types(
&mut self,
sub: Ty<'tcx>,
sup: Ty<'tcx>,
locations: Locations,
category: ConstraintCategory,
) -> Fallible<()> {
relate_tys::sub_types(
self.infcx,
sub,
sup,
locations,
category,
self.borrowck_context.as_mut().map(|x| &mut **x),
)
}
fn eq_types(&mut self, a: Ty<'tcx>, b: Ty<'tcx>, locations: Locations) -> Fallible<()> {
fn eq_types(
&mut self,
a: Ty<'tcx>,
b: Ty<'tcx>,
locations: Locations,
category: ConstraintCategory,
) -> Fallible<()> {
relate_tys::eq_types(
self.infcx,
a,
b,
locations,
category,
self.borrowck_context.as_mut().map(|x| &mut **x),
)
}
@ -879,6 +906,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
v: ty::Variance,
b: CanonicalTy<'tcx>,
locations: Locations,
category: ConstraintCategory,
) -> Fallible<()> {
relate_tys::relate_type_and_user_type(
self.infcx,
@ -886,6 +914,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
v,
b,
locations,
category,
self.borrowck_context.as_mut().map(|x| &mut **x),
)
}
@ -903,21 +932,22 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
// they are not caused by the user, but rather artifacts
// of lowering. Assignments to other sorts of places *are* interesting
// though.
let is_temp = if let Place::Local(l) = *place {
l != RETURN_PLACE && !mir.local_decls[l].is_user_variable.is_some()
} else {
false
};
let locations = if is_temp {
location.boring()
} else {
location.interesting()
let category = match *place {
Place::Local(RETURN_PLACE) => ConstraintCategory::Return,
Place::Local(l) if !mir.local_decls[l].is_user_variable.is_some() => {
ConstraintCategory::Boring
}
_ => ConstraintCategory::Assignment,
};
let place_ty = place.ty(mir, tcx).to_ty(tcx);
let rv_ty = rv.ty(mir, tcx);
if let Err(terr) = self.sub_types(rv_ty, place_ty, locations) {
if let Err(terr) = self.sub_types(
rv_ty,
place_ty,
location.to_locations(),
category,
) {
span_mirbug!(
self,
stmt,
@ -933,7 +963,8 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
rv_ty,
ty::Variance::Invariant,
user_ty,
location.boring(),
location.to_locations(),
ConstraintCategory::Boring,
) {
span_mirbug!(
self,
@ -952,7 +983,11 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
def_id: tcx.lang_items().sized_trait().unwrap(),
substs: tcx.mk_substs_trait(place_ty, &[]),
};
self.prove_trait_ref(trait_ref, location.interesting());
self.prove_trait_ref(
trait_ref,
location.to_locations(),
ConstraintCategory::SizedBound,
);
}
}
StatementKind::SetDiscriminant {
@ -983,7 +1018,13 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
StatementKind::AscribeUserType(ref place, variance, c_ty) => {
let place_ty = place.ty(mir, tcx).to_ty(tcx);
if let Err(terr) =
self.relate_type_and_user_type(place_ty, variance, c_ty, Locations::All)
self.relate_type_and_user_type(
place_ty,
variance,
c_ty,
Locations::All(stmt.source_info.span),
ConstraintCategory::TypeAnnotation,
)
{
span_mirbug!(
self,
@ -1035,8 +1076,13 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
let place_ty = location.ty(mir, tcx).to_ty(tcx);
let rv_ty = value.ty(mir, tcx);
let locations = term_location.interesting();
if let Err(terr) = self.sub_types(rv_ty, place_ty, locations) {
let locations = term_location.to_locations();
if let Err(terr) = self.sub_types(
rv_ty,
place_ty,
locations,
ConstraintCategory::Assignment,
) {
span_mirbug!(
self,
term,
@ -1053,7 +1099,12 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
..
} => {
let discr_ty = discr.ty(mir, tcx);
if let Err(terr) = self.sub_types(discr_ty, switch_ty, term_location.boring()) {
if let Err(terr) = self.sub_types(
discr_ty,
switch_ty,
term_location.to_locations(),
ConstraintCategory::Assignment,
) {
span_mirbug!(
self,
term,
@ -1093,7 +1144,8 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
self.prove_predicates(
sig.inputs().iter().map(|ty| ty::Predicate::WellFormed(ty)),
term_location.boring(),
term_location.to_locations(),
ConstraintCategory::Boring,
);
// The ordinary liveness rules will ensure that all
@ -1139,7 +1191,12 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
match mir.yield_ty {
None => span_mirbug!(self, term, "yield in non-generator"),
Some(ty) => {
if let Err(terr) = self.sub_types(value_ty, ty, term_location.interesting())
if let Err(terr) = self.sub_types(
value_ty,
ty,
term_location.to_locations(),
ConstraintCategory::Return,
)
{
span_mirbug!(
self,
@ -1168,19 +1225,22 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
match *destination {
Some((ref dest, _target_block)) => {
let dest_ty = dest.ty(mir, tcx).to_ty(tcx);
let is_temp = if let Place::Local(l) = *dest {
l != RETURN_PLACE && !mir.local_decls[l].is_user_variable.is_some()
} else {
false
let category = match *dest {
Place::Local(RETURN_PLACE) => ConstraintCategory::Return,
Place::Local(l) if !mir.local_decls[l].is_user_variable.is_some() => {
ConstraintCategory::Boring
}
_ => ConstraintCategory::Assignment,
};
let locations = if is_temp {
term_location.boring()
} else {
term_location.interesting()
};
let locations = term_location.to_locations();
if let Err(terr) = self.sub_types(sig.output(), dest_ty, locations) {
if let Err(terr) = self.sub_types(
sig.output(),
dest_ty,
locations,
category,
) {
span_mirbug!(
self,
term,
@ -1221,7 +1281,12 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
}
for (n, (fn_arg, op_arg)) in sig.inputs().iter().zip(args).enumerate() {
let op_arg_ty = op_arg.ty(mir, self.tcx());
if let Err(terr) = self.sub_types(op_arg_ty, fn_arg, term_location.interesting()) {
if let Err(terr) = self.sub_types(
op_arg_ty,
fn_arg,
term_location.to_locations(),
ConstraintCategory::CallArgument,
) {
span_mirbug!(
self,
term,
@ -1470,7 +1535,11 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
substs: tcx.mk_substs_trait(operand_ty, &[]),
};
self.prove_trait_ref(trait_ref, location.interesting());
self.prove_trait_ref(
trait_ref,
location.to_locations(),
ConstraintCategory::CopyBound,
);
},
Rvalue::NullaryOp(_, ty) => {
@ -1485,24 +1554,34 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
substs: tcx.mk_substs_trait(ty, &[]),
};
self.prove_trait_ref(trait_ref, location.interesting());
self.prove_trait_ref(
trait_ref,
location.to_locations(),
ConstraintCategory::SizedBound,
);
}
Rvalue::Cast(cast_kind, op, ty) => match cast_kind {
CastKind::ReifyFnPointer => {
let fn_sig = op.ty(mir, tcx).fn_sig(tcx);
Rvalue::Cast(cast_kind, op, ty) => {
match cast_kind {
CastKind::ReifyFnPointer => {
let fn_sig = op.ty(mir, tcx).fn_sig(tcx);
// The type that we see in the fcx is like
// `foo::<'a, 'b>`, where `foo` is the path to a
// function definition. When we extract the
// signature, it comes from the `fn_sig` query,
// and hence may contain unnormalized results.
let fn_sig = self.normalize(fn_sig, location);
// The type that we see in the fcx is like
// `foo::<'a, 'b>`, where `foo` is the path to a
// function definition. When we extract the
// signature, it comes from the `fn_sig` query,
// and hence may contain unnormalized results.
let fn_sig = self.normalize(fn_sig, location);
let ty_fn_ptr_from = tcx.mk_fn_ptr(fn_sig);
let ty_fn_ptr_from = tcx.mk_fn_ptr(fn_sig);
if let Err(terr) = self.eq_types(ty_fn_ptr_from, ty, location.interesting()) {
span_mirbug!(
if let Err(terr) = self.eq_types(
ty_fn_ptr_from,
ty,
location.to_locations(),
ConstraintCategory::Cast,
) {
span_mirbug!(
self,
rvalue,
"equating {:?} with {:?} yields {:?}",
@ -1510,20 +1589,25 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
ty,
terr
);
}
}
CastKind::ClosureFnPointer => {
let sig = match op.ty(mir, tcx).sty {
ty::Closure(def_id, substs) => {
substs.closure_sig_ty(def_id, tcx).fn_sig(tcx)
}
_ => bug!(),
};
let ty_fn_ptr_from = tcx.coerce_closure_fn_ty(sig);
}
if let Err(terr) = self.eq_types(ty_fn_ptr_from, ty, location.interesting()) {
span_mirbug!(
CastKind::ClosureFnPointer => {
let sig = match op.ty(mir, tcx).sty {
ty::Closure(def_id, substs) => {
substs.closure_sig_ty(def_id, tcx).fn_sig(tcx)
}
_ => bug!(),
};
let ty_fn_ptr_from = tcx.coerce_closure_fn_ty(sig);
if let Err(terr) = self.eq_types(
ty_fn_ptr_from,
ty,
location.to_locations(),
ConstraintCategory::Cast,
) {
span_mirbug!(
self,
rvalue,
"equating {:?} with {:?} yields {:?}",
@ -1531,23 +1615,28 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
ty,
terr
);
}
}
}
CastKind::UnsafeFnPointer => {
let fn_sig = op.ty(mir, tcx).fn_sig(tcx);
CastKind::UnsafeFnPointer => {
let fn_sig = op.ty(mir, tcx).fn_sig(tcx);
// The type that we see in the fcx is like
// `foo::<'a, 'b>`, where `foo` is the path to a
// function definition. When we extract the
// signature, it comes from the `fn_sig` query,
// and hence may contain unnormalized results.
let fn_sig = self.normalize(fn_sig, location);
// The type that we see in the fcx is like
// `foo::<'a, 'b>`, where `foo` is the path to a
// function definition. When we extract the
// signature, it comes from the `fn_sig` query,
// and hence may contain unnormalized results.
let fn_sig = self.normalize(fn_sig, location);
let ty_fn_ptr_from = tcx.safe_to_unsafe_fn_ty(fn_sig);
let ty_fn_ptr_from = tcx.safe_to_unsafe_fn_ty(fn_sig);
if let Err(terr) = self.eq_types(ty_fn_ptr_from, ty, location.interesting()) {
span_mirbug!(
if let Err(terr) = self.eq_types(
ty_fn_ptr_from,
ty,
location.to_locations(),
ConstraintCategory::Cast,
) {
span_mirbug!(
self,
rvalue,
"equating {:?} with {:?} yields {:?}",
@ -1555,21 +1644,26 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
ty,
terr
);
}
}
CastKind::Unsize => {
let &ty = ty;
let trait_ref = ty::TraitRef {
def_id: tcx.lang_items().coerce_unsized_trait().unwrap(),
substs: tcx.mk_substs_trait(op.ty(mir, tcx), &[ty.into()]),
};
self.prove_trait_ref(
trait_ref,
location.to_locations(),
ConstraintCategory::Cast,
);
}
CastKind::Misc => {}
}
CastKind::Unsize => {
let &ty = ty;
let trait_ref = ty::TraitRef {
def_id: tcx.lang_items().coerce_unsized_trait().unwrap(),
substs: tcx.mk_substs_trait(op.ty(mir, tcx), &[ty.into()]),
};
self.prove_trait_ref(trait_ref, location.interesting());
}
CastKind::Misc => {}
},
}
Rvalue::Ref(region, _borrow_kind, borrowed_place) => {
self.add_reborrow_constraint(location, region, borrowed_place);
@ -1644,7 +1738,12 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
};
let operand_ty = operand.ty(mir, tcx);
if let Err(terr) = self.sub_types(operand_ty, field_ty, location.boring()) {
if let Err(terr) = self.sub_types(
operand_ty,
field_ty,
location.to_locations(),
ConstraintCategory::Boring,
) {
span_mirbug!(
self,
rvalue,
@ -1723,7 +1822,8 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
constraints.outlives_constraints.push(OutlivesConstraint {
sup: ref_region.to_region_vid(),
sub: borrow_region.to_region_vid(),
locations: location.boring(),
locations: location.to_locations(),
category: ConstraintCategory::Boring,
});
if let Some(all_facts) = all_facts {
@ -1839,8 +1939,11 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
*substs,
);
// Hmm, are these constraints *really* boring?
self.push_region_constraints(location.boring(), &closure_constraints);
self.push_region_constraints(
location.to_locations(),
ConstraintCategory::ClosureBounds,
&closure_constraints,
);
}
tcx.predicates_of(*def_id).instantiate(tcx, substs.substs)
@ -1855,16 +1958,22 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
self.normalize_and_prove_instantiated_predicates(
instantiated_predicates,
location.boring(),
location.to_locations(),
);
}
fn prove_trait_ref(&mut self, trait_ref: ty::TraitRef<'tcx>, locations: Locations) {
fn prove_trait_ref(
&mut self,
trait_ref: ty::TraitRef<'tcx>,
locations: Locations,
category: ConstraintCategory,
) {
self.prove_predicates(
Some(ty::Predicate::Trait(
trait_ref.to_poly_trait_ref().to_poly_trait_predicate(),
)),
locations,
category,
);
}
@ -1875,7 +1984,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
) {
for predicate in instantiated_predicates.predicates {
let predicate = self.normalize(predicate, locations);
self.prove_predicate(predicate, locations);
self.prove_predicate(predicate, locations, ConstraintCategory::Boring);
}
}
@ -1883,6 +1992,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
&mut self,
predicates: impl IntoIterator<Item = ty::Predicate<'tcx>>,
locations: Locations,
category: ConstraintCategory,
) {
for predicate in predicates {
debug!(
@ -1890,11 +2000,16 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
predicate, locations,
);
self.prove_predicate(predicate, locations);
self.prove_predicate(predicate, locations, category);
}
}
fn prove_predicate(&mut self, predicate: ty::Predicate<'tcx>, locations: Locations) {
fn prove_predicate(
&mut self,
predicate: ty::Predicate<'tcx>,
locations: Locations,
category: ConstraintCategory,
) {
debug!(
"prove_predicate(predicate={:?}, location={:?})",
predicate, locations,
@ -1903,6 +2018,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
let param_env = self.param_env;
self.fully_perform_op(
locations,
category,
param_env.and(type_op::prove_predicate::ProvePredicate::new(predicate)),
).unwrap_or_else(|NoSolution| {
span_mirbug!(self, NoSolution, "could not prove {:?}", predicate);
@ -1943,6 +2059,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
let param_env = self.param_env;
self.fully_perform_op(
location.to_locations(),
ConstraintCategory::Boring,
param_env.and(type_op::normalize::Normalize::new(value)),
).unwrap_or_else(|NoSolution| {
span_mirbug!(self, NoSolution, "failed to normalize `{:?}`", value);
@ -1996,26 +2113,6 @@ impl MirPass for TypeckMir {
}
}
pub trait AtLocation {
/// Indicates a "boring" constraint that the user probably
/// woudln't want to see highlights.
fn boring(self) -> Locations;
/// Indicates an "interesting" edge, which is of significance only
/// for diagnostics.
fn interesting(self) -> Locations;
}
impl AtLocation for Location {
fn boring(self) -> Locations {
Locations::Boring(self)
}
fn interesting(self) -> Locations {
Locations::Interesting(self)
}
}
trait NormalizeLocation: fmt::Debug + Copy {
fn to_locations(self) -> Locations;
}
@ -2028,6 +2125,6 @@ impl NormalizeLocation for Locations {
impl NormalizeLocation for Location {
fn to_locations(self) -> Locations {
self.boring()
Locations::Single(self)
}
}

View file

@ -8,7 +8,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use borrow_check::nll::constraints::OutlivesConstraint;
use borrow_check::nll::constraints::{ConstraintCategory, OutlivesConstraint};
use borrow_check::nll::type_check::{BorrowCheckContext, Locations};
use borrow_check::nll::universal_regions::UniversalRegions;
use borrow_check::nll::ToRegionVid;
@ -28,6 +28,7 @@ pub(super) fn sub_types<'tcx>(
a: Ty<'tcx>,
b: Ty<'tcx>,
locations: Locations,
category: ConstraintCategory,
borrowck_context: Option<&mut BorrowCheckContext<'_, 'tcx>>,
) -> Fallible<()> {
debug!("sub_types(a={:?}, b={:?}, locations={:?})", a, b, locations);
@ -35,6 +36,7 @@ pub(super) fn sub_types<'tcx>(
infcx,
ty::Variance::Covariant,
locations,
category,
borrowck_context,
ty::List::empty(),
).relate(&a, &b)?;
@ -47,6 +49,7 @@ pub(super) fn eq_types<'tcx>(
a: Ty<'tcx>,
b: Ty<'tcx>,
locations: Locations,
category: ConstraintCategory,
borrowck_context: Option<&mut BorrowCheckContext<'_, 'tcx>>,
) -> Fallible<()> {
debug!("eq_types(a={:?}, b={:?}, locations={:?})", a, b, locations);
@ -54,6 +57,7 @@ pub(super) fn eq_types<'tcx>(
infcx,
ty::Variance::Invariant,
locations,
category,
borrowck_context,
ty::List::empty(),
).relate(&a, &b)?;
@ -69,6 +73,7 @@ pub(super) fn relate_type_and_user_type<'tcx>(
v: ty::Variance,
b: CanonicalTy<'tcx>,
locations: Locations,
category: ConstraintCategory,
borrowck_context: Option<&mut BorrowCheckContext<'_, 'tcx>>,
) -> Fallible<()> {
debug!(
@ -89,6 +94,7 @@ pub(super) fn relate_type_and_user_type<'tcx>(
infcx,
v1,
locations,
category,
borrowck_context,
b_variables,
).relate(&b_value, &a)?;
@ -124,6 +130,8 @@ struct TypeRelating<'cx, 'bccx: 'cx, 'gcx: 'tcx, 'tcx: 'bccx> {
/// Where (and why) is this relation taking place?
locations: Locations,
category: ConstraintCategory,
/// This will be `Some` when we are running the type check as part
/// of NLL, and `None` if we are running a "sanity check".
borrowck_context: Option<&'cx mut BorrowCheckContext<'bccx, 'tcx>>,
@ -161,6 +169,7 @@ impl<'cx, 'bccx, 'gcx, 'tcx> TypeRelating<'cx, 'bccx, 'gcx, 'tcx> {
infcx: &'cx InferCtxt<'cx, 'gcx, 'tcx>,
ambient_variance: ty::Variance,
locations: Locations,
category: ConstraintCategory,
borrowck_context: Option<&'cx mut BorrowCheckContext<'bccx, 'tcx>>,
canonical_var_infos: CanonicalVarInfos<'tcx>,
) -> Self {
@ -171,6 +180,7 @@ impl<'cx, 'bccx, 'gcx, 'tcx> TypeRelating<'cx, 'bccx, 'gcx, 'tcx> {
borrowck_context,
locations,
canonical_var_values,
category,
a_scopes: vec![],
b_scopes: vec![],
}
@ -264,6 +274,7 @@ impl<'cx, 'bccx, 'gcx, 'tcx> TypeRelating<'cx, 'bccx, 'gcx, 'tcx> {
sup,
sub,
locations: self.locations,
category: self.category,
});
// FIXME all facts!