Skip to content

Commit 5e442fb

Browse files
committed
Point at specific field in struct literal when trait fulfillment fails
1 parent cc47b06 commit 5e442fb

File tree

3 files changed

+288
-23
lines changed

3 files changed

+288
-23
lines changed

compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs

Lines changed: 229 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ use rustc_infer::infer::error_reporting::{FailureCode, ObligationCauseExt};
2424
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
2525
use rustc_infer::infer::InferOk;
2626
use rustc_infer::infer::TypeTrace;
27+
use rustc_infer::traits::PredicateObligation;
2728
use rustc_middle::ty::adjustment::AllowTwoPhase;
2829
use rustc_middle::ty::visit::TypeVisitable;
30+
use rustc_middle::ty::walk::TypeWalker;
2931
use rustc_middle::ty::{self, DefIdTree, IsSuggestable, Ty, TypeSuperVisitable, TypeVisitor};
3032
use rustc_session::Session;
3133
use rustc_span::symbol::{kw, Ident};
@@ -523,7 +525,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
523525
// Sometimes macros mess up the spans, so do not normalize the
524526
// arg span to equal the error span, because that's less useful
525527
// than pointing out the arg expr in the wrong context.
526-
if normalized_span.source_equal(error_span) { span } else { normalized_span }
528+
if normalized_span.source_equal(error_span) {
529+
span
530+
} else {
531+
normalized_span
532+
}
527533
};
528534

529535
// Precompute the provided types and spans, since that's all we typically need for below
@@ -776,9 +782,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
776782
// can be collated pretty easily if needed.
777783

778784
// Next special case: if there is only one "Incompatible" error, just emit that
779-
if let [
780-
Error::Invalid(provided_idx, expected_idx, Compatibility::Incompatible(Some(err))),
781-
] = &errors[..]
785+
if let [Error::Invalid(provided_idx, expected_idx, Compatibility::Incompatible(Some(err)))] =
786+
&errors[..]
782787
{
783788
let (formal_ty, expected_ty) = formal_and_expected_inputs[*expected_idx];
784789
let (provided_ty, provided_arg_span) = provided_arg_tys[*provided_idx];
@@ -1520,25 +1525,21 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
15201525
// Our block must be a `assign desugar local; assignment`
15211526
if let Some(hir::Node::Block(hir::Block {
15221527
stmts:
1523-
[
1524-
hir::Stmt {
1525-
kind:
1526-
hir::StmtKind::Local(hir::Local {
1527-
source:
1528-
hir::LocalSource::AssignDesugar(_),
1529-
..
1530-
}),
1531-
..
1532-
},
1533-
hir::Stmt {
1534-
kind:
1535-
hir::StmtKind::Expr(hir::Expr {
1536-
kind: hir::ExprKind::Assign(..),
1537-
..
1538-
}),
1539-
..
1540-
},
1541-
],
1528+
[hir::Stmt {
1529+
kind:
1530+
hir::StmtKind::Local(hir::Local {
1531+
source: hir::LocalSource::AssignDesugar(_),
1532+
..
1533+
}),
1534+
..
1535+
}, hir::Stmt {
1536+
kind:
1537+
hir::StmtKind::Expr(hir::Expr {
1538+
kind: hir::ExprKind::Assign(..),
1539+
..
1540+
}),
1541+
..
1542+
}],
15421543
..
15431544
})) = self.tcx.hir().find(blk.hir_id)
15441545
{
@@ -1946,7 +1947,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
19461947
if let [(idx, _)] = args_referencing_param.as_slice()
19471948
&& let Some(arg) = receiver
19481949
.map_or(args.get(*idx), |rcvr| if *idx == 0 { Some(rcvr) } else { args.get(*idx - 1) }) {
1950+
19491951
error.obligation.cause.span = arg.span.find_ancestor_in_same_ctxt(error.obligation.cause.span).unwrap_or(arg.span);
1952+
1953+
if let hir::Node::Expr(arg_expr) = self.tcx.hir().get(arg.hir_id) {
1954+
// This is more specific than pointing at the entire argument.
1955+
self.point_at_specific_expr_if_possible(error, arg_expr)
1956+
}
1957+
19501958
error.obligation.cause.map_code(|parent_code| {
19511959
ObligationCauseCode::FunctionArgumentObligation {
19521960
arg_hir_id: arg.hir_id,
@@ -1964,6 +1972,183 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
19641972
false
19651973
}
19661974

1975+
fn point_at_specific_expr_if_possible_for_obligation_cause_code(
1976+
&self,
1977+
obligation_cause_code: &traits::ObligationCauseCode<'tcx>,
1978+
expr: &'tcx hir::Expr<'tcx>,
1979+
) -> Result<&'tcx hir::Expr<'tcx>, &'tcx hir::Expr<'tcx>> {
1980+
match obligation_cause_code {
1981+
traits::ObligationCauseCode::ImplDerivedObligation(impl_derived) => self
1982+
.point_at_specific_expr_if_possible_for_derived_predicate_obligation(
1983+
impl_derived,
1984+
expr,
1985+
),
1986+
traits::ObligationCauseCode::ExprBindingObligation(_, _, _, _) => Ok(expr),
1987+
_ => {
1988+
// Err(expr) indicates that we cannot process any further.
1989+
Err(expr)
1990+
}
1991+
}
1992+
}
1993+
1994+
fn point_at_specific_expr_if_possible_for_derived_predicate_obligation(
1995+
&self,
1996+
obligation: &traits::ImplDerivedObligationCause<'tcx>,
1997+
expr: &'tcx hir::Expr<'tcx>,
1998+
) -> Result<&'tcx hir::Expr<'tcx>, &'tcx hir::Expr<'tcx>> {
1999+
if obligation.derived.parent_trait_pred.skip_binder().polarity != ty::ImplPolarity::Positive
2000+
{
2001+
// For now, ignore non-positive trait bounds.
2002+
return Err(expr);
2003+
}
2004+
2005+
let expr = self.point_at_specific_expr_if_possible_for_obligation_cause_code(
2006+
&*obligation.derived.parent_code,
2007+
expr,
2008+
)?;
2009+
2010+
match expr.kind {
2011+
hir::ExprKind::Struct(
2012+
hir::QPath::Resolved(
2013+
None,
2014+
hir::Path {
2015+
res: hir::def::Res::Def(hir::def::DefKind::Struct, struct_def_id),
2016+
..
2017+
},
2018+
),
2019+
struct_fields,
2020+
_, // NOTE: "Rest" fields in structs are currently ignored by this function.
2021+
) => {
2022+
let impl_generics: &ty::Generics = self.tcx.generics_of(obligation.impl_def_id);
2023+
// The relevant generics occur in this predicate.
2024+
2025+
// Now we look up the Self type for the trait impl.
2026+
let impl_trait_ref: Option<ty::TraitRef<'tcx>> =
2027+
self.tcx.impl_trait_ref(obligation.impl_def_id);
2028+
2029+
let Some(impl_trait_ref) = impl_trait_ref else {
2030+
return Err(expr); // We must have a valid impl ref.
2031+
};
2032+
let impl_self_ty: Ty<'tcx> = impl_trait_ref.self_ty();
2033+
2034+
let ty::Adt(impl_self_ty_path, impl_self_ty_args) = impl_self_ty.kind() else {
2035+
return Err(expr);
2036+
};
2037+
2038+
if &impl_self_ty_path.did() != struct_def_id {
2039+
// If the struct in the impl is different from the struct constructor
2040+
// we're using, then we cannot make progress.
2041+
return Err(expr);
2042+
}
2043+
2044+
// The struct being constructed is the same as the struct in the impl.
2045+
2046+
// Which of the self's type arguments use variables used in the predicate?
2047+
let mut relevant_impl_generics: Vec<&ty::GenericParamDef> = Vec::new();
2048+
for param in impl_generics.params.iter() {
2049+
if find_param_def_in_ty_walker(impl_self_ty.walk(), param) {
2050+
relevant_impl_generics.push(param);
2051+
}
2052+
}
2053+
let mut relevant_ty_args_indices: Vec<usize> = Vec::new();
2054+
for (index, ty_arg) in impl_self_ty_args.as_slice().iter().enumerate() {
2055+
let mut found = false;
2056+
for param in relevant_impl_generics.iter() {
2057+
if find_param_def_in_ty_walker(ty_arg.walk(), param) {
2058+
found = true;
2059+
}
2060+
}
2061+
if found {
2062+
relevant_ty_args_indices.push(index);
2063+
}
2064+
// in ty_arg, at least one of the type parameters mentioned above must be used.
2065+
}
2066+
2067+
let struct_def_generics: &ty::Generics = self.tcx.generics_of(struct_def_id);
2068+
2069+
let struct_variant_defs: Vec<&ty::VariantDef> =
2070+
impl_self_ty_path.variants().iter().collect::<Vec<_>>();
2071+
2072+
if struct_variant_defs.len() != 1 {
2073+
// We expect exactly one variant to exist, since it's a struct type.
2074+
return Err(expr);
2075+
}
2076+
2077+
let struct_variant_def: &ty::VariantDef = struct_variant_defs[0];
2078+
2079+
let mut blameable_field_defs: Vec<&ty::FieldDef> = Vec::new();
2080+
for field_def in struct_variant_def.fields.iter() {
2081+
let field_type: Ty<'tcx> = self.tcx.type_of(field_def.did);
2082+
let mut is_blameable = false;
2083+
2084+
for param in struct_def_generics.params.iter() {
2085+
if find_param_def_in_ty_walker(field_type.walk(), param) {
2086+
is_blameable = true;
2087+
break;
2088+
}
2089+
}
2090+
2091+
if is_blameable {
2092+
blameable_field_defs.push(field_def);
2093+
}
2094+
}
2095+
2096+
if blameable_field_defs.len() != 1 {
2097+
// We must have a single blameable field, in order to be able to blame it.
2098+
return Err(expr);
2099+
}
2100+
2101+
let blameable_field_def: &ty::FieldDef = blameable_field_defs[0];
2102+
2103+
for field in struct_fields.iter() {
2104+
if field.ident.as_str() == blameable_field_def.ident(self.tcx).as_str() {
2105+
// Blame this field!
2106+
return Ok(field.expr);
2107+
}
2108+
}
2109+
2110+
// We failed to find a matching field in the original struct expression.
2111+
Err(expr)
2112+
}
2113+
_ => Err(expr), // Stop propagating.
2114+
}
2115+
}
2116+
2117+
fn point_at_specific_expr_if_possible_for_predicate_obligation(
2118+
&self,
2119+
obligation: &PredicateObligation<'tcx>,
2120+
expr: &'tcx hir::Expr<'tcx>,
2121+
) -> Result<&'tcx hir::Expr<'tcx>, &'tcx hir::Expr<'tcx>> {
2122+
self.point_at_specific_expr_if_possible_for_obligation_cause_code(
2123+
obligation.cause.code(),
2124+
expr,
2125+
)
2126+
}
2127+
2128+
/**
2129+
* Recursively searches for the most-specific blamable expression.
2130+
* Specifically, looks for constructor expressions, e.g. `(false, 5)` constructs a tuple
2131+
* or `Some(5)` constructs an `Option<i32>` and pairs them with their derived impl predicates.
2132+
*/
2133+
fn point_at_specific_expr_if_possible(
2134+
&self,
2135+
error: &mut traits::FulfillmentError<'tcx>,
2136+
expr: &'tcx hir::Expr<'tcx>,
2137+
) {
2138+
let expr = match self
2139+
.point_at_specific_expr_if_possible_for_predicate_obligation(&error.obligation, expr)
2140+
{
2141+
Ok(expr) => expr,
2142+
Err(expr) => expr,
2143+
};
2144+
2145+
// update the error span.
2146+
error.obligation.cause.span = expr
2147+
.span
2148+
.find_ancestor_in_same_ctxt(error.obligation.cause.span)
2149+
.unwrap_or(error.obligation.cause.span);
2150+
}
2151+
19672152
fn point_at_field_if_possible(
19682153
&self,
19692154
error: &mut traits::FulfillmentError<'tcx>,
@@ -2242,3 +2427,24 @@ fn find_param_in_ty<'tcx>(ty: Ty<'tcx>, param_to_point_at: ty::GenericArg<'tcx>)
22422427
}
22432428
false
22442429
}
2430+
2431+
fn find_param_def_in_ty_walker<'tcx>(
2432+
mut walk: TypeWalker<'tcx>,
2433+
param_to_point_at: &'tcx ty::GenericParamDef,
2434+
) -> bool {
2435+
let param_to_point_at_param_ty = ty::ParamTy::for_def(param_to_point_at);
2436+
while let Some(arg) = walk.next() {
2437+
match arg.unpack() {
2438+
ty::GenericArgKind::Type(arg_ty) => match arg_ty.kind() {
2439+
ty::Param(arg_param_ty) => {
2440+
if arg_param_ty == &param_to_point_at_param_ty {
2441+
return true;
2442+
}
2443+
}
2444+
_ => {}
2445+
},
2446+
_ => {}
2447+
}
2448+
}
2449+
false
2450+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
trait T1 {}
2+
trait T2 {}
3+
trait T3 {}
4+
trait T4 {}
5+
6+
impl<B: T2> T1 for Wrapper<B> {}
7+
8+
impl T2 for i32 {}
9+
impl T3 for i32 {}
10+
11+
impl<A: T3> T2 for Burrito<A> {}
12+
13+
struct Wrapper<W> {
14+
value: W,
15+
}
16+
17+
struct Burrito<F> {
18+
filling: F,
19+
}
20+
21+
fn want<V: T1>(_x: V) {}
22+
23+
fn example<Q>(q: Q) {
24+
want(Wrapper { value: Burrito { filling: q } });
25+
//~^ ERROR the trait bound `Q: T3` is not satisfied [E0277]
26+
}
27+
28+
fn main() {}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
error[E0277]: the trait bound `Q: T3` is not satisfied
2+
--> $DIR/blame-trait-error.rs:24:46
3+
|
4+
LL | want(Wrapper { value: Burrito { filling: q } });
5+
| ---- ^ the trait `T3` is not implemented for `Q`
6+
| |
7+
| required by a bound introduced by this call
8+
|
9+
note: required for `Burrito<Q>` to implement `T2`
10+
--> $DIR/blame-trait-error.rs:11:13
11+
|
12+
LL | impl<A: T3> T2 for Burrito<A> {}
13+
| ^^ ^^^^^^^^^^
14+
note: required for `Wrapper<Burrito<Q>>` to implement `T1`
15+
--> $DIR/blame-trait-error.rs:6:13
16+
|
17+
LL | impl<B: T2> T1 for Wrapper<B> {}
18+
| ^^ ^^^^^^^^^^
19+
note: required by a bound in `want`
20+
--> $DIR/blame-trait-error.rs:21:12
21+
|
22+
LL | fn want<V: T1>(_x: V) {}
23+
| ^^ required by this bound in `want`
24+
help: consider restricting type parameter `Q`
25+
|
26+
LL | fn example<Q: T3>(q: Q) {
27+
| ++++
28+
29+
error: aborting due to previous error
30+
31+
For more information about this error, try `rustc --explain E0277`.

0 commit comments

Comments
 (0)