Support HIR wf checking for function signatures
During function type-checking, we normalize any associated types in the function signature (argument types + return type), and then create WF obligations for each of the normalized types. The HIR wf code does not currently support this case, so any errors that we get have imprecise spans. This commit extends `ObligationCauseCode::WellFormed` to support recording a function parameter, allowing us to get the corresponding HIR type if an error occurs. Function typechecking is modified to pass this information during signature normalization and WF checking. The resulting code is fairly verbose, due to the fact that we can no longer normalize the entire signature with a single function call. As part of the refactoring, we now perform HIR-based WF checking for several other 'typed items' (statics, consts, and inherent impls). As a result, WF and projection errors in a function signature now have a precise span, which points directly at the responsible type. If a function signature is constructed via a macro, this will allow the error message to point at the code 'most responsible' for the error (e.g. a user-supplied macro argument).
This commit is contained in:
parent
da7d405357
commit
db0324ebb2
28 changed files with 240 additions and 91 deletions
|
@ -40,6 +40,7 @@ use rustc_trait_selection::opaque_types::InferCtxtExt as _;
|
|||
use rustc_trait_selection::traits::error_reporting::InferCtxtExt as _;
|
||||
use rustc_trait_selection::traits::{
|
||||
self, ObligationCause, ObligationCauseCode, StatementAsExpression, TraitEngine, TraitEngineExt,
|
||||
WellFormedLoc,
|
||||
};
|
||||
|
||||
use std::collections::hash_map::Entry;
|
||||
|
@ -419,13 +420,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
&self,
|
||||
span: Span,
|
||||
value: T,
|
||||
hir_id: hir::HirId,
|
||||
loc: WellFormedLoc,
|
||||
) -> T
|
||||
where
|
||||
T: TypeFoldable<'tcx>,
|
||||
{
|
||||
self.inh.normalize_associated_types_in_with_cause(
|
||||
ObligationCause::new(span, self.body_id, ObligationCauseCode::WellFormed(Some(hir_id))),
|
||||
ObligationCause::new(span, self.body_id, ObligationCauseCode::WellFormed(Some(loc))),
|
||||
self.param_env,
|
||||
value,
|
||||
)
|
||||
|
|
|
@ -22,8 +22,9 @@ use rustc_span::symbol::{sym, Ident, Symbol};
|
|||
use rustc_span::Span;
|
||||
use rustc_trait_selection::opaque_types::may_define_opaque_type;
|
||||
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
|
||||
use rustc_trait_selection::traits::{self, ObligationCause, ObligationCauseCode};
|
||||
use rustc_trait_selection::traits::{self, ObligationCause, ObligationCauseCode, WellFormedLoc};
|
||||
|
||||
use std::convert::TryInto;
|
||||
use std::iter;
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
|
@ -386,7 +387,7 @@ fn check_associated_item(
|
|||
span: Span,
|
||||
sig_if_method: Option<&hir::FnSig<'_>>,
|
||||
) {
|
||||
let code = ObligationCauseCode::WellFormed(Some(item_id));
|
||||
let code = ObligationCauseCode::WellFormed(Some(WellFormedLoc::Ty(item_id.expect_owner())));
|
||||
for_id(tcx, item_id, span).with_fcx(|fcx| {
|
||||
let item = fcx.tcx.associated_item(fcx.tcx.hir().local_def_id(item_id));
|
||||
|
||||
|
@ -400,7 +401,11 @@ fn check_associated_item(
|
|||
match item.kind {
|
||||
ty::AssocKind::Const => {
|
||||
let ty = fcx.tcx.type_of(item.def_id);
|
||||
let ty = fcx.normalize_associated_types_in_wf(span, ty, item_id);
|
||||
let ty = fcx.normalize_associated_types_in_wf(
|
||||
span,
|
||||
ty,
|
||||
WellFormedLoc::Ty(item_id.expect_owner()),
|
||||
);
|
||||
fcx.register_wf_obligation(ty.into(), span, code.clone());
|
||||
}
|
||||
ty::AssocKind::Fn => {
|
||||
|
@ -422,7 +427,11 @@ fn check_associated_item(
|
|||
}
|
||||
if item.defaultness.has_value() {
|
||||
let ty = fcx.tcx.type_of(item.def_id);
|
||||
let ty = fcx.normalize_associated_types_in_wf(span, ty, item_id);
|
||||
let ty = fcx.normalize_associated_types_in_wf(
|
||||
span,
|
||||
ty,
|
||||
WellFormedLoc::Ty(item_id.expect_owner()),
|
||||
);
|
||||
fcx.register_wf_obligation(ty.into(), span, code.clone());
|
||||
}
|
||||
}
|
||||
|
@ -621,7 +630,11 @@ fn check_item_type(tcx: TyCtxt<'_>, item_id: hir::HirId, ty_span: Span, allow_fo
|
|||
|
||||
for_id(tcx, item_id, ty_span).with_fcx(|fcx| {
|
||||
let ty = tcx.type_of(tcx.hir().local_def_id(item_id));
|
||||
let item_ty = fcx.normalize_associated_types_in_wf(ty_span, ty, item_id);
|
||||
let item_ty = fcx.normalize_associated_types_in_wf(
|
||||
ty_span,
|
||||
ty,
|
||||
WellFormedLoc::Ty(item_id.expect_owner()),
|
||||
);
|
||||
|
||||
let mut forbid_unsized = true;
|
||||
if allow_foreign_ty {
|
||||
|
@ -634,7 +647,7 @@ fn check_item_type(tcx: TyCtxt<'_>, item_id: hir::HirId, ty_span: Span, allow_fo
|
|||
fcx.register_wf_obligation(
|
||||
item_ty.into(),
|
||||
ty_span,
|
||||
ObligationCauseCode::WellFormed(Some(item_id)),
|
||||
ObligationCauseCode::WellFormed(Some(WellFormedLoc::Ty(item_id.expect_owner()))),
|
||||
);
|
||||
if forbid_unsized {
|
||||
fcx.register_bound(
|
||||
|
@ -684,7 +697,9 @@ fn check_impl<'tcx>(
|
|||
fcx.register_wf_obligation(
|
||||
self_ty.into(),
|
||||
ast_self_ty.span,
|
||||
ObligationCauseCode::WellFormed(Some(item.hir_id())),
|
||||
ObligationCauseCode::WellFormed(Some(WellFormedLoc::Ty(
|
||||
item.hir_id().expect_owner(),
|
||||
))),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -901,11 +916,48 @@ fn check_fn_or_method<'fcx, 'tcx>(
|
|||
implied_bounds: &mut Vec<Ty<'tcx>>,
|
||||
) {
|
||||
let sig = fcx.tcx.liberate_late_bound_regions(def_id, sig);
|
||||
let sig = fcx.normalize_associated_types_in(span, sig);
|
||||
|
||||
for (&input_ty, ty) in iter::zip(sig.inputs(), hir_decl.inputs) {
|
||||
fcx.register_wf_obligation(input_ty.into(), ty.span, ObligationCauseCode::WellFormed(None));
|
||||
// Normalize the input and output types one at a time, using a different
|
||||
// `WellFormedLoc` for each. We cannot call `normalize_associated_types`
|
||||
// on the entire `FnSig`, since this would use the same `WellFormedLoc`
|
||||
// for each type, preventing the HIR wf check from generating
|
||||
// a nice error message.
|
||||
let ty::FnSig { mut inputs_and_output, c_variadic, unsafety, abi } = sig;
|
||||
inputs_and_output =
|
||||
fcx.tcx.mk_type_list(inputs_and_output.iter().enumerate().map(|(i, ty)| {
|
||||
fcx.normalize_associated_types_in_wf(
|
||||
span,
|
||||
ty,
|
||||
WellFormedLoc::Param {
|
||||
function: def_id.expect_local(),
|
||||
// Note that the `param_idx` of the output type is
|
||||
// one greater than the index of the last input type.
|
||||
param_idx: i.try_into().unwrap(),
|
||||
},
|
||||
)
|
||||
}));
|
||||
// Manually call `normalize_assocaited_types_in` on the other types
|
||||
// in `FnSig`. This ensures that if the types of these fields
|
||||
// ever change to include projections, we will start normalizing
|
||||
// them automatically.
|
||||
let sig = ty::FnSig {
|
||||
inputs_and_output,
|
||||
c_variadic: fcx.normalize_associated_types_in(span, c_variadic),
|
||||
unsafety: fcx.normalize_associated_types_in(span, unsafety),
|
||||
abi: fcx.normalize_associated_types_in(span, abi),
|
||||
};
|
||||
|
||||
for (i, (&input_ty, ty)) in iter::zip(sig.inputs(), hir_decl.inputs).enumerate() {
|
||||
fcx.register_wf_obligation(
|
||||
input_ty.into(),
|
||||
ty.span,
|
||||
ObligationCauseCode::WellFormed(Some(WellFormedLoc::Param {
|
||||
function: def_id.expect_local(),
|
||||
param_idx: i.try_into().unwrap(),
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
implied_bounds.extend(sig.inputs());
|
||||
|
||||
fcx.register_wf_obligation(
|
||||
|
|
|
@ -3,10 +3,10 @@ use rustc_hir as hir;
|
|||
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
|
||||
use rustc_hir::HirId;
|
||||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
use rustc_infer::traits::ObligationCause;
|
||||
use rustc_infer::traits::TraitEngine;
|
||||
use rustc_infer::traits::{ObligationCause, WellFormedLoc};
|
||||
use rustc_middle::ty::query::Providers;
|
||||
use rustc_middle::ty::{self, ToPredicate, TyCtxt};
|
||||
use rustc_middle::ty::{self, Region, ToPredicate, TyCtxt, TypeFoldable, TypeFolder};
|
||||
use rustc_trait_selection::traits;
|
||||
|
||||
pub fn provide(providers: &mut Providers) {
|
||||
|
@ -17,21 +17,20 @@ pub fn provide(providers: &mut Providers) {
|
|||
// need access to `ItemCtxt`
|
||||
fn diagnostic_hir_wf_check<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
(predicate, hir_id): (ty::Predicate<'tcx>, HirId),
|
||||
(predicate, loc): (ty::Predicate<'tcx>, WellFormedLoc),
|
||||
) -> Option<ObligationCause<'tcx>> {
|
||||
let hir = tcx.hir();
|
||||
// HIR wfcheck should only ever happen as part of improving an existing error
|
||||
tcx.sess.delay_span_bug(hir.span(hir_id), "Performed HIR wfcheck without an existing error!");
|
||||
|
||||
// Currently, we only handle WF checking for items (e.g. associated items).
|
||||
// It would be nice to extend this to handle wf checks inside functions.
|
||||
let def_id = match tcx.hir().opt_local_def_id(hir_id) {
|
||||
Some(def_id) => def_id,
|
||||
None => return None,
|
||||
let def_id = match loc {
|
||||
WellFormedLoc::Ty(def_id) => def_id,
|
||||
WellFormedLoc::Param { function, param_idx: _ } => function,
|
||||
};
|
||||
let hir_id = HirId::make_owner(def_id);
|
||||
|
||||
// HIR wfcheck should only ever happen as part of improving an existing error
|
||||
tcx.sess
|
||||
.delay_span_bug(tcx.def_span(def_id), "Performed HIR wfcheck without an existing error!");
|
||||
|
||||
// FIXME - figure out how we want to handle wf-checking for
|
||||
// things inside a function body.
|
||||
let icx = ItemCtxt::new(tcx, def_id.to_def_id());
|
||||
|
||||
// To perform HIR-based WF checking, we iterate over all HIR types
|
||||
|
@ -72,7 +71,8 @@ fn diagnostic_hir_wf_check<'tcx>(
|
|||
fn visit_ty(&mut self, ty: &'tcx hir::Ty<'tcx>) {
|
||||
self.tcx.infer_ctxt().enter(|infcx| {
|
||||
let mut fulfill = traits::FulfillmentContext::new();
|
||||
let tcx_ty = self.icx.to_ty(ty);
|
||||
let tcx_ty =
|
||||
self.icx.to_ty(ty).fold_with(&mut EraseAllBoundRegions { tcx: self.tcx });
|
||||
let cause = traits::ObligationCause::new(
|
||||
ty.span,
|
||||
self.hir_id,
|
||||
|
@ -119,19 +119,66 @@ fn diagnostic_hir_wf_check<'tcx>(
|
|||
depth: 0,
|
||||
};
|
||||
|
||||
let ty = match tcx.hir().get(hir_id) {
|
||||
hir::Node::ImplItem(item) => match item.kind {
|
||||
hir::ImplItemKind::TyAlias(ty) => Some(ty),
|
||||
_ => None,
|
||||
// Get the starting `hir::Ty` using our `WellFormedLoc`.
|
||||
// We will walk 'into' this type to try to find
|
||||
// a more precise span for our predicate.
|
||||
let ty = match loc {
|
||||
WellFormedLoc::Ty(_) => match hir.get(hir_id) {
|
||||
hir::Node::ImplItem(item) => match item.kind {
|
||||
hir::ImplItemKind::TyAlias(ty) => Some(ty),
|
||||
ref item => bug!("Unexpected ImplItem {:?}", item),
|
||||
},
|
||||
hir::Node::TraitItem(item) => match item.kind {
|
||||
hir::TraitItemKind::Type(_, ty) => ty,
|
||||
ref item => bug!("Unexpected TraitItem {:?}", item),
|
||||
},
|
||||
hir::Node::Item(item) => match item.kind {
|
||||
hir::ItemKind::Static(ty, _, _) | hir::ItemKind::Const(ty, _) => Some(ty),
|
||||
hir::ItemKind::Impl(ref impl_) => {
|
||||
assert!(impl_.of_trait.is_none(), "Unexpected trait impl: {:?}", impl_);
|
||||
Some(impl_.self_ty)
|
||||
}
|
||||
ref item => bug!("Unexpected item {:?}", item),
|
||||
},
|
||||
ref node => bug!("Unexpected node {:?}", node),
|
||||
},
|
||||
hir::Node::TraitItem(item) => match item.kind {
|
||||
hir::TraitItemKind::Type(_, ty) => ty,
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
WellFormedLoc::Param { function: _, param_idx } => {
|
||||
let fn_decl = hir.fn_decl_by_hir_id(hir_id).unwrap();
|
||||
// Get return type
|
||||
if param_idx as usize == fn_decl.inputs.len() {
|
||||
match fn_decl.output {
|
||||
hir::FnRetTy::Return(ty) => Some(ty),
|
||||
// The unit type `()` is always well-formed
|
||||
hir::FnRetTy::DefaultReturn(_span) => None,
|
||||
}
|
||||
} else {
|
||||
Some(&fn_decl.inputs[param_idx as usize])
|
||||
}
|
||||
}
|
||||
};
|
||||
if let Some(ty) = ty {
|
||||
visitor.visit_ty(ty);
|
||||
}
|
||||
visitor.cause
|
||||
}
|
||||
|
||||
struct EraseAllBoundRegions<'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
}
|
||||
|
||||
// Higher ranked regions are complicated.
|
||||
// To make matters worse, the HIR WF check can instantiate them
|
||||
// outside of a `Binder`, due to the way we (ab)use
|
||||
// `ItemCtxt::to_ty`. To make things simpler, we just erase all
|
||||
// of them, regardless of depth. At worse, this will give
|
||||
// us an inaccurate span for an error message, but cannot
|
||||
// lead to unsoundess (we call `delay_span_bug` at the start
|
||||
// of `diagnostic_hir_wf_check`).
|
||||
impl<'tcx> TypeFolder<'tcx> for EraseAllBoundRegions<'tcx> {
|
||||
fn tcx<'a>(&'a self) -> TyCtxt<'tcx> {
|
||||
self.tcx
|
||||
}
|
||||
fn fold_region(&mut self, r: Region<'tcx>) -> Region<'tcx> {
|
||||
if let ty::ReLateBound(..) = r { &ty::ReErased } else { r }
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue