Skip to content

Commit 32c447e

Browse files
committed
Auto merge of #83898 - Aaron1011:feature/hir-wf, r=estebank
Add initial implementation of HIR-based WF checking for diagnostics During well-formed checking, we walk through all types 'nested' in generic arguments. For example, WF-checking `Option<MyStruct<u8>>` will cause us to check `MyStruct<u8>` and `u8`. However, this is done on a `rustc_middle::ty::Ty`, which has no span information. As a result, any errors that occur will have a very general span (e.g. the definintion of an associated item). This becomes a problem when macros are involved. In general, an associated type like `type MyType = Option<MyStruct<u8>>;` may have completely different spans for each nested type in the HIR. Using the span of the entire associated item might end up pointing to a macro invocation, even though a user-provided span is available in one of the nested types. This PR adds a framework for HIR-based well formed checking. This check is only run during error reporting, and is used to obtain a more precise span for an existing error. This is accomplished by individually checking each 'nested' type in the HIR for the type, allowing us to find the most-specific type (and span) that produces a given error. The majority of the changes are to the error-reporting code. However, some of the general trait code is modified to pass through more information. Since this has no soundness implications, I've implemented a minimal version to begin with, which can be extended over time. In particular, this only works for HIR items with a corresponding `DefId` (e.g. it will not work for WF-checking performed within function bodies).
2 parents 74ef0c3 + a765333 commit 32c447e

File tree

23 files changed

+350
-66
lines changed

23 files changed

+350
-66
lines changed

compiler/rustc_infer/src/traits/mod.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ pub struct FulfillmentError<'tcx> {
7070
/// obligation error caused by a call argument. When this is the case, we also signal that in
7171
/// this field to ensure accuracy of suggestions.
7272
pub points_at_arg_span: bool,
73+
/// Diagnostics only: the 'root' obligation which resulted in
74+
/// the failure to process `obligation`. This is the obligation
75+
/// that was initially passed to `register_predicate_obligation`
76+
pub root_obligation: PredicateObligation<'tcx>,
7377
}
7478

7579
#[derive(Clone)]
@@ -122,8 +126,9 @@ impl<'tcx> FulfillmentError<'tcx> {
122126
pub fn new(
123127
obligation: PredicateObligation<'tcx>,
124128
code: FulfillmentErrorCode<'tcx>,
129+
root_obligation: PredicateObligation<'tcx>,
125130
) -> FulfillmentError<'tcx> {
126-
FulfillmentError { obligation, code, points_at_arg_span: false }
131+
FulfillmentError { obligation, code, points_at_arg_span: false, root_obligation }
127132
}
128133
}
129134

compiler/rustc_middle/src/query/mod.rs

+14
Original file line numberDiff line numberDiff line change
@@ -1713,4 +1713,18 @@ rustc_queries! {
17131713
query limits(key: ()) -> Limits {
17141714
desc { "looking up limits" }
17151715
}
1716+
1717+
/// Performs an HIR-based well-formed check on the item with the given `HirId`. If
1718+
/// we get an `Umimplemented` error that matches the provided `Predicate`, return
1719+
/// the cause of the newly created obligation.
1720+
///
1721+
/// This is only used by error-reporting code to get a better cause (in particular, a better
1722+
/// span) for an *existing* error. Therefore, it is best-effort, and may never handle
1723+
/// all of the cases that the normal `ty::Ty`-based wfcheck does. This is fine,
1724+
/// because the `ty::Ty`-based wfcheck is always run.
1725+
query diagnostic_hir_wf_check(key: (ty::Predicate<'tcx>, hir::HirId)) -> Option<traits::ObligationCause<'tcx>> {
1726+
eval_always
1727+
no_hash
1728+
desc { "performing HIR wf-checking for predicate {:?} at item {:?}", key.0, key.1 }
1729+
}
17161730
}

compiler/rustc_middle/src/traits/mod.rs

+12-5
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use crate::mir::abstract_const::NotConstEvaluatable;
1313
use crate::ty::subst::SubstsRef;
1414
use crate::ty::{self, AdtKind, Ty, TyCtxt};
1515

16+
use rustc_data_structures::sync::Lrc;
1617
use rustc_errors::{Applicability, DiagnosticBuilder};
1718
use rustc_hir as hir;
1819
use rustc_hir::def_id::DefId;
@@ -24,7 +25,6 @@ use smallvec::SmallVec;
2425
use std::borrow::Cow;
2526
use std::fmt;
2627
use std::ops::Deref;
27-
use std::rc::Rc;
2828

2929
pub use self::select::{EvaluationCache, EvaluationResult, OverflowError, SelectionCache};
3030

@@ -87,7 +87,7 @@ pub enum Reveal {
8787
#[derive(Clone, PartialEq, Eq, Hash, Lift)]
8888
pub struct ObligationCause<'tcx> {
8989
/// `None` for `ObligationCause::dummy`, `Some` otherwise.
90-
data: Option<Rc<ObligationCauseData<'tcx>>>,
90+
data: Option<Lrc<ObligationCauseData<'tcx>>>,
9191
}
9292

9393
const DUMMY_OBLIGATION_CAUSE_DATA: ObligationCauseData<'static> =
@@ -131,7 +131,7 @@ impl<'tcx> ObligationCause<'tcx> {
131131
body_id: hir::HirId,
132132
code: ObligationCauseCode<'tcx>,
133133
) -> ObligationCause<'tcx> {
134-
ObligationCause { data: Some(Rc::new(ObligationCauseData { span, body_id, code })) }
134+
ObligationCause { data: Some(Lrc::new(ObligationCauseData { span, body_id, code })) }
135135
}
136136

137137
pub fn misc(span: Span, body_id: hir::HirId) -> ObligationCause<'tcx> {
@@ -148,7 +148,7 @@ impl<'tcx> ObligationCause<'tcx> {
148148
}
149149

150150
pub fn make_mut(&mut self) -> &mut ObligationCauseData<'tcx> {
151-
Rc::make_mut(self.data.get_or_insert_with(|| Rc::new(DUMMY_OBLIGATION_CAUSE_DATA)))
151+
Lrc::make_mut(self.data.get_or_insert_with(|| Lrc::new(DUMMY_OBLIGATION_CAUSE_DATA)))
152152
}
153153

154154
pub fn span(&self, tcx: TyCtxt<'tcx>) -> Span {
@@ -326,6 +326,13 @@ pub enum ObligationCauseCode<'tcx> {
326326

327327
/// If `X` is the concrete type of an opaque type `impl Y`, then `X` must implement `Y`
328328
OpaqueType,
329+
330+
/// Well-formed checking. If a `HirId` is provided,
331+
/// it is used to perform HIR-based wf checking if an error
332+
/// occurs, in order to generate a more precise error message.
333+
/// This is purely for diagnostic purposes - it is always
334+
/// correct to use `MiscObligation` instead
335+
WellFormed(Option<hir::HirId>),
329336
}
330337

331338
impl ObligationCauseCode<'_> {
@@ -389,7 +396,7 @@ pub struct DerivedObligationCause<'tcx> {
389396
pub parent_trait_ref: ty::PolyTraitRef<'tcx>,
390397

391398
/// The parent trait had this cause.
392-
pub parent_code: Rc<ObligationCauseCode<'tcx>>,
399+
pub parent_code: Lrc<ObligationCauseCode<'tcx>>,
393400
}
394401

395402
#[derive(Clone, Debug, TypeFoldable, Lift)]

compiler/rustc_mir/src/borrow_check/type_check/mod.rs

+19-17
Original file line numberDiff line numberDiff line change
@@ -2070,24 +2070,26 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
20702070
debug!("check_rvalue: is_const_fn={:?}", is_const_fn);
20712071

20722072
let def_id = body.source.def_id().expect_local();
2073-
self.infcx.report_selection_error(
2074-
&traits::Obligation::new(
2075-
ObligationCause::new(
2076-
span,
2077-
self.tcx().hir().local_def_id_to_hir_id(def_id),
2078-
traits::ObligationCauseCode::RepeatVec(is_const_fn),
2079-
),
2080-
self.param_env,
2081-
ty::Binder::dummy(ty::TraitRef::new(
2082-
self.tcx().require_lang_item(
2083-
LangItem::Copy,
2084-
Some(self.last_span),
2085-
),
2086-
tcx.mk_substs_trait(ty, &[]),
2087-
))
2088-
.without_const()
2089-
.to_predicate(self.tcx()),
2073+
let obligation = traits::Obligation::new(
2074+
ObligationCause::new(
2075+
span,
2076+
self.tcx().hir().local_def_id_to_hir_id(def_id),
2077+
traits::ObligationCauseCode::RepeatVec(is_const_fn),
20902078
),
2079+
self.param_env,
2080+
ty::Binder::dummy(ty::TraitRef::new(
2081+
self.tcx().require_lang_item(
2082+
LangItem::Copy,
2083+
Some(self.last_span),
2084+
),
2085+
tcx.mk_substs_trait(ty, &[]),
2086+
))
2087+
.without_const()
2088+
.to_predicate(self.tcx()),
2089+
);
2090+
self.infcx.report_selection_error(
2091+
obligation.clone(),
2092+
&obligation,
20912093
&traits::SelectionError::Unimplemented,
20922094
false,
20932095
false,

compiler/rustc_query_impl/src/keys.rs

+12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Defines the set of legal keys that can be used in queries.
22
33
use rustc_hir::def_id::{CrateNum, DefId, LocalDefId, LOCAL_CRATE};
4+
use rustc_hir::HirId;
45
use rustc_middle::infer::canonical::Canonical;
56
use rustc_middle::mir;
67
use rustc_middle::ty::fast_reject::SimplifiedType;
@@ -395,3 +396,14 @@ impl<'tcx> Key for (DefId, Ty<'tcx>, SubstsRef<'tcx>, ty::ParamEnv<'tcx>) {
395396
DUMMY_SP
396397
}
397398
}
399+
400+
impl<'tcx> Key for (ty::Predicate<'tcx>, HirId) {
401+
#[inline(always)]
402+
fn query_crate_is_local(&self) -> bool {
403+
true
404+
}
405+
406+
fn default_span(&self, _tcx: TyCtxt<'_>) -> Span {
407+
DUMMY_SP
408+
}
409+
}

compiler/rustc_trait_selection/src/infer.rs

+2-5
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ pub trait InferCtxtExt<'tcx> {
3030

3131
fn partially_normalize_associated_types_in<T>(
3232
&self,
33-
span: Span,
34-
body_id: hir::HirId,
33+
cause: ObligationCause<'tcx>,
3534
param_env: ty::ParamEnv<'tcx>,
3635
value: T,
3736
) -> InferOk<'tcx, T>
@@ -79,8 +78,7 @@ impl<'cx, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'cx, 'tcx> {
7978
/// new obligations that must further be processed.
8079
fn partially_normalize_associated_types_in<T>(
8180
&self,
82-
span: Span,
83-
body_id: hir::HirId,
81+
cause: ObligationCause<'tcx>,
8482
param_env: ty::ParamEnv<'tcx>,
8583
value: T,
8684
) -> InferOk<'tcx, T>
@@ -89,7 +87,6 @@ impl<'cx, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'cx, 'tcx> {
8987
{
9088
debug!("partially_normalize_associated_types_in(value={:?})", value);
9189
let mut selcx = traits::SelectionContext::new(self);
92-
let cause = ObligationCause::misc(span, body_id);
9390
let traits::Normalized { value, obligations } =
9491
traits::normalize(&mut selcx, param_env, cause, value);
9592
debug!(

compiler/rustc_trait_selection/src/opaque_types.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::infer::InferCtxtExt as _;
2-
use crate::traits::{self, PredicateObligation};
2+
use crate::traits::{self, ObligationCause, PredicateObligation};
33
use rustc_data_structures::fx::FxHashMap;
44
use rustc_data_structures::sync::Lrc;
55
use rustc_data_structures::vec_map::VecMap;
@@ -1049,8 +1049,11 @@ impl<'a, 'tcx> Instantiator<'a, 'tcx> {
10491049
item_bounds.iter().map(|(bound, _)| bound.subst(tcx, substs)).collect();
10501050

10511051
let param_env = tcx.param_env(def_id);
1052-
let InferOk { value: bounds, obligations } =
1053-
infcx.partially_normalize_associated_types_in(span, self.body_id, param_env, bounds);
1052+
let InferOk { value: bounds, obligations } = infcx.partially_normalize_associated_types_in(
1053+
ObligationCause::misc(span, self.body_id),
1054+
param_env,
1055+
bounds,
1056+
);
10541057
self.obligations.extend(obligations);
10551058

10561059
debug!("instantiate_opaque_types: bounds={:?}", bounds);

compiler/rustc_trait_selection/src/traits/chalk_fulfill.rs

+11-2
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ impl TraitEngine<'tcx> for FulfillmentContext<'tcx> {
5858
obligation: obligation.clone(),
5959
code: FulfillmentErrorCode::CodeAmbiguity,
6060
points_at_arg_span: false,
61+
// FIXME - does Chalk have a notation of 'root obligation'?
62+
// This is just for diagnostics, so it's okay if this is wrong
63+
root_obligation: obligation.clone(),
6164
})
6265
.collect();
6366
Err(errors)
@@ -105,11 +108,14 @@ impl TraitEngine<'tcx> for FulfillmentContext<'tcx> {
105108
),
106109

107110
Err(_err) => errors.push(FulfillmentError {
108-
obligation,
111+
obligation: obligation.clone(),
109112
code: FulfillmentErrorCode::CodeSelectionError(
110113
SelectionError::Unimplemented,
111114
),
112115
points_at_arg_span: false,
116+
// FIXME - does Chalk have a notation of 'root obligation'?
117+
// This is just for diagnostics, so it's okay if this is wrong
118+
root_obligation: obligation,
113119
}),
114120
}
115121
} else {
@@ -119,11 +125,14 @@ impl TraitEngine<'tcx> for FulfillmentContext<'tcx> {
119125
}
120126

121127
Err(NoSolution) => errors.push(FulfillmentError {
122-
obligation,
128+
obligation: obligation.clone(),
123129
code: FulfillmentErrorCode::CodeSelectionError(
124130
SelectionError::Unimplemented,
125131
),
126132
points_at_arg_span: false,
133+
// FIXME - does Chalk have a notation of 'root obligation'?
134+
// This is just for diagnostics, so it's okay if this is wrong
135+
root_obligation: obligation,
127136
}),
128137
}
129138
}

compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs

+26-8
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,13 @@ pub trait InferCtxtExt<'tcx> {
5555

5656
fn report_overflow_error_cycle(&self, cycle: &[PredicateObligation<'tcx>]) -> !;
5757

58+
/// The `root_obligation` parameter should be the `root_obligation` field
59+
/// from a `FulfillmentError`. If no `FulfillmentError` is available,
60+
/// then it should be the same as `obligation`.
5861
fn report_selection_error(
5962
&self,
60-
obligation: &PredicateObligation<'tcx>,
63+
obligation: PredicateObligation<'tcx>,
64+
root_obligation: &PredicateObligation<'tcx>,
6165
error: &SelectionError<'tcx>,
6266
fallback_has_occurred: bool,
6367
points_at_arg: bool,
@@ -225,16 +229,29 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
225229

226230
fn report_selection_error(
227231
&self,
228-
obligation: &PredicateObligation<'tcx>,
232+
mut obligation: PredicateObligation<'tcx>,
233+
root_obligation: &PredicateObligation<'tcx>,
229234
error: &SelectionError<'tcx>,
230235
fallback_has_occurred: bool,
231236
points_at_arg: bool,
232237
) {
233238
let tcx = self.tcx;
234-
let span = obligation.cause.span;
239+
let mut span = obligation.cause.span;
235240

236241
let mut err = match *error {
237242
SelectionError::Unimplemented => {
243+
// If this obligation was generated as a result of well-formed checking, see if we
244+
// can get a better error message by performing HIR-based well formed checking.
245+
if let ObligationCauseCode::WellFormed(Some(wf_hir_id)) =
246+
root_obligation.cause.code.peel_derives()
247+
{
248+
if let Some(cause) =
249+
self.tcx.diagnostic_hir_wf_check((obligation.predicate, *wf_hir_id))
250+
{
251+
obligation.cause = cause;
252+
span = obligation.cause.span;
253+
}
254+
}
238255
if let ObligationCauseCode::CompareImplMethodObligation {
239256
item_name,
240257
impl_item_def_id,
@@ -279,7 +296,7 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
279296
.unwrap_or_default();
280297

281298
let OnUnimplementedNote { message, label, note, enclosing_scope } =
282-
self.on_unimplemented_note(trait_ref, obligation);
299+
self.on_unimplemented_note(trait_ref, &obligation);
283300
let have_alt_message = message.is_some() || label.is_some();
284301
let is_try_conversion = self.is_try_conversion(span, trait_ref.def_id());
285302
let is_unsize =
@@ -338,7 +355,7 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
338355
Applicability::MachineApplicable,
339356
);
340357
}
341-
if let Some(ret_span) = self.return_type_span(obligation) {
358+
if let Some(ret_span) = self.return_type_span(&obligation) {
342359
err.span_label(
343360
ret_span,
344361
&format!(
@@ -368,7 +385,7 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
368385
points_at_arg,
369386
have_alt_message,
370387
) {
371-
self.note_obligation_cause(&mut err, obligation);
388+
self.note_obligation_cause(&mut err, &obligation);
372389
err.emit();
373390
return;
374391
}
@@ -821,7 +838,7 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
821838
}
822839
};
823840

824-
self.note_obligation_cause(&mut err, obligation);
841+
self.note_obligation_cause(&mut err, &obligation);
825842
self.point_at_returns_when_relevant(&mut err, &obligation);
826843

827844
err.emit();
@@ -1168,7 +1185,8 @@ impl<'a, 'tcx> InferCtxtPrivExt<'tcx> for InferCtxt<'a, 'tcx> {
11681185
match error.code {
11691186
FulfillmentErrorCode::CodeSelectionError(ref selection_error) => {
11701187
self.report_selection_error(
1171-
&error.obligation,
1188+
error.obligation.clone(),
1189+
&error.root_obligation,
11721190
selection_error,
11731191
fallback_has_occurred,
11741192
error.points_at_arg_span,

compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1902,7 +1902,8 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
19021902
| ObligationCauseCode::ReturnNoExpression
19031903
| ObligationCauseCode::UnifyReceiver(..)
19041904
| ObligationCauseCode::OpaqueType
1905-
| ObligationCauseCode::MiscObligation => {}
1905+
| ObligationCauseCode::MiscObligation
1906+
| ObligationCauseCode::WellFormed(..) => {}
19061907
ObligationCauseCode::SliceOrArrayElem => {
19071908
err.note("slice and array elements must have `Sized` type");
19081909
}

compiler/rustc_trait_selection/src/traits/fulfill.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,10 @@ fn substs_infer_vars<'a, 'tcx>(
717717
fn to_fulfillment_error<'tcx>(
718718
error: Error<PendingPredicateObligation<'tcx>, FulfillmentErrorCode<'tcx>>,
719719
) -> FulfillmentError<'tcx> {
720-
let obligation = error.backtrace.into_iter().next().unwrap().obligation;
721-
FulfillmentError::new(obligation, error.error)
720+
let mut iter = error.backtrace.into_iter();
721+
let obligation = iter.next().unwrap().obligation;
722+
// The root obligation is the last item in the backtrace - if there's only
723+
// one item, then it's the same as the main obligation
724+
let root_obligation = iter.next_back().map_or_else(|| obligation.clone(), |e| e.obligation);
725+
FulfillmentError::new(obligation, error.error, root_obligation)
722726
}

0 commit comments

Comments
 (0)