Skip to content

Commit d695a49

Browse files
committed
Auto merge of rust-lang#96482 - willcrichton:fix-trait-suggestion-for-binops, r=estebank
Add Output = expected type trait obligation for known binary operators This PR is a follow-on to rust-lang#94034 that addresses rust-lang#96442. That is, after replacing the trait-suggestion logic in `op.rs` with a more generic path that analyzes a general set of `Obligation`s, then we lost some specificity in the suggestions where the bounds on the associated type `Output=` would not get suggested. This PR fixes this issue by changing `FnCtxt::construct_obligation_for_trait` to include a new `ProjectionPredicate` obligation for binary operators that obliges that `Output` is the same as the expected type of the expression. Additionally, to get the expected type of the expression, this PR threads the `Expectation<'tcx>` structure throughout several functions. See src/test/ui/generic-associated-types/missing-bounds.stderr for an example of how this works. One side effect of this change is it causes type-check failures with binops to include additional information. Specifically, many now say ``` error: type mismatch resolving `<Lhs as TheBinop>::Output == ExpectedTy` ``` It's up for discussion whether this added context is worth it to the user. r? `@estebank`
2 parents e6c43cf + 2f15dfa commit d695a49

File tree

14 files changed

+218
-44
lines changed

14 files changed

+218
-44
lines changed

compiler/rustc_middle/src/traits/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ pub mod util;
1212
use crate::infer::canonical::Canonical;
1313
use crate::ty::abstract_const::NotConstEvaluatable;
1414
use crate::ty::subst::SubstsRef;
15-
use crate::ty::{self, AdtKind, Ty, TyCtxt};
15+
use crate::ty::{self, AdtKind, Predicate, Ty, TyCtxt};
1616

1717
use rustc_data_structures::sync::Lrc;
1818
use rustc_errors::{Applicability, Diagnostic};
@@ -414,6 +414,7 @@ pub enum ObligationCauseCode<'tcx> {
414414
BinOp {
415415
rhs_span: Option<Span>,
416416
is_lit: bool,
417+
output_pred: Option<Predicate<'tcx>>,
417418
},
418419
}
419420

compiler/rustc_middle/src/ty/mod.rs

+18
Original file line numberDiff line numberDiff line change
@@ -1033,6 +1033,24 @@ impl<'tcx> Predicate<'tcx> {
10331033
}
10341034
}
10351035

1036+
pub fn to_opt_poly_projection_pred(self) -> Option<PolyProjectionPredicate<'tcx>> {
1037+
let predicate = self.kind();
1038+
match predicate.skip_binder() {
1039+
PredicateKind::Projection(t) => Some(predicate.rebind(t)),
1040+
PredicateKind::Trait(..)
1041+
| PredicateKind::Subtype(..)
1042+
| PredicateKind::Coerce(..)
1043+
| PredicateKind::RegionOutlives(..)
1044+
| PredicateKind::WellFormed(..)
1045+
| PredicateKind::ObjectSafe(..)
1046+
| PredicateKind::ClosureKind(..)
1047+
| PredicateKind::TypeOutlives(..)
1048+
| PredicateKind::ConstEvaluatable(..)
1049+
| PredicateKind::ConstEquate(..)
1050+
| PredicateKind::TypeWellFormedFromEnv(..) => None,
1051+
}
1052+
}
1053+
10361054
pub fn to_opt_type_outlives(self) -> Option<PolyTypeOutlivesPredicate<'tcx>> {
10371055
let predicate = self.kind();
10381056
match predicate.skip_binder() {

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

+1
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,7 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
665665
self.suggest_restricting_param_bound(
666666
&mut err,
667667
trait_predicate,
668+
None,
668669
obligation.cause.body_id,
669670
);
670671
} else if !suggested {

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

+25-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ use rustc_middle::hir::map;
2424
use rustc_middle::ty::{
2525
self, suggest_arbitrary_trait_bound, suggest_constraining_type_param, AdtKind, DefIdTree,
2626
GeneratorDiagnosticData, GeneratorInteriorTypeCause, Infer, InferTy, IsSuggestable,
27-
ToPredicate, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeVisitable,
27+
ProjectionPredicate, ToPredicate, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable,
28+
TypeVisitable,
2829
};
2930
use rustc_middle::ty::{TypeAndMut, TypeckResults};
3031
use rustc_session::Limit;
@@ -172,6 +173,7 @@ pub trait InferCtxtExt<'tcx> {
172173
&self,
173174
err: &mut Diagnostic,
174175
trait_pred: ty::PolyTraitPredicate<'tcx>,
176+
proj_pred: Option<ty::PolyProjectionPredicate<'tcx>>,
175177
body_id: hir::HirId,
176178
);
177179

@@ -457,6 +459,7 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
457459
&self,
458460
mut err: &mut Diagnostic,
459461
trait_pred: ty::PolyTraitPredicate<'tcx>,
462+
proj_pred: Option<ty::PolyProjectionPredicate<'tcx>>,
460463
body_id: hir::HirId,
461464
) {
462465
let trait_pred = self.resolve_numeric_literals_with_default(trait_pred);
@@ -589,9 +592,28 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
589592
}
590593
// Missing generic type parameter bound.
591594
let param_name = self_ty.to_string();
592-
let constraint = with_no_trimmed_paths!(
595+
let mut constraint = with_no_trimmed_paths!(
593596
trait_pred.print_modifiers_and_trait_path().to_string()
594597
);
598+
599+
if let Some(proj_pred) = proj_pred {
600+
let ProjectionPredicate { projection_ty, term } = proj_pred.skip_binder();
601+
let item = self.tcx.associated_item(projection_ty.item_def_id);
602+
603+
// FIXME: this case overlaps with code in TyCtxt::note_and_explain_type_err.
604+
// That should be extracted into a helper function.
605+
if constraint.ends_with('>') {
606+
constraint = format!(
607+
"{}, {}={}>",
608+
&constraint[..constraint.len() - 1],
609+
item.name,
610+
term.to_string()
611+
);
612+
} else {
613+
constraint.push_str(&format!("<{}={}>", item.name, term.to_string()));
614+
}
615+
}
616+
595617
if suggest_constraining_type_param(
596618
self.tcx,
597619
generics,
@@ -2825,7 +2847,7 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
28252847
trait_ref: &ty::PolyTraitRef<'tcx>,
28262848
) {
28272849
let rhs_span = match obligation.cause.code() {
2828-
ObligationCauseCode::BinOp { rhs_span: Some(span), is_lit } if *is_lit => span,
2850+
ObligationCauseCode::BinOp { rhs_span: Some(span), is_lit, .. } if *is_lit => span,
28292851
_ => return,
28302852
};
28312853
match (

compiler/rustc_typeck/src/check/expr.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -282,11 +282,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
282282
match expr.kind {
283283
ExprKind::Box(subexpr) => self.check_expr_box(subexpr, expected),
284284
ExprKind::Lit(ref lit) => self.check_lit(&lit, expected),
285-
ExprKind::Binary(op, lhs, rhs) => self.check_binop(expr, op, lhs, rhs),
285+
ExprKind::Binary(op, lhs, rhs) => self.check_binop(expr, op, lhs, rhs, expected),
286286
ExprKind::Assign(lhs, rhs, span) => {
287287
self.check_expr_assign(expr, expected, lhs, rhs, span)
288288
}
289-
ExprKind::AssignOp(op, lhs, rhs) => self.check_binop_assign(expr, op, lhs, rhs),
289+
ExprKind::AssignOp(op, lhs, rhs) => {
290+
self.check_binop_assign(expr, op, lhs, rhs, expected)
291+
}
290292
ExprKind::Unary(unop, oprnd) => self.check_expr_unary(unop, oprnd, expected, expr),
291293
ExprKind::AddrOf(kind, mutbl, oprnd) => {
292294
self.check_expr_addr_of(kind, mutbl, oprnd, expected, expr)
@@ -404,14 +406,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
404406
}
405407
}
406408
hir::UnOp::Not => {
407-
let result = self.check_user_unop(expr, oprnd_t, unop);
409+
let result = self.check_user_unop(expr, oprnd_t, unop, expected_inner);
408410
// If it's builtin, we can reuse the type, this helps inference.
409411
if !(oprnd_t.is_integral() || *oprnd_t.kind() == ty::Bool) {
410412
oprnd_t = result;
411413
}
412414
}
413415
hir::UnOp::Neg => {
414-
let result = self.check_user_unop(expr, oprnd_t, unop);
416+
let result = self.check_user_unop(expr, oprnd_t, unop, expected_inner);
415417
// If it's builtin, we can reuse the type, this helps inference.
416418
if !oprnd_t.is_numeric() {
417419
oprnd_t = result;

compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs

+1
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
413413
rhs_span: opt_input_expr.map(|expr| expr.span),
414414
is_lit: opt_input_expr
415415
.map_or(false, |expr| matches!(expr.kind, ExprKind::Lit(_))),
416+
output_pred: None,
416417
},
417418
),
418419
self.param_env,

compiler/rustc_typeck/src/check/method/mod.rs

+27-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ mod suggest;
1010
pub use self::suggest::SelfSource;
1111
pub use self::MethodError::*;
1212

13-
use crate::check::FnCtxt;
13+
use crate::check::{Expectation, FnCtxt};
1414
use crate::ObligationCause;
1515
use rustc_data_structures::sync::Lrc;
1616
use rustc_errors::{Applicability, Diagnostic};
@@ -20,8 +20,10 @@ use rustc_hir::def_id::DefId;
2020
use rustc_infer::infer::{self, InferOk};
2121
use rustc_middle::ty::subst::Subst;
2222
use rustc_middle::ty::subst::{InternalSubsts, SubstsRef};
23-
use rustc_middle::ty::{self, ToPredicate, Ty, TypeVisitable};
24-
use rustc_middle::ty::{DefIdTree, GenericParamDefKind};
23+
use rustc_middle::ty::{
24+
self, AssocKind, DefIdTree, GenericParamDefKind, ProjectionPredicate, ProjectionTy, Term,
25+
ToPredicate, Ty, TypeVisitable,
26+
};
2527
use rustc_span::symbol::Ident;
2628
use rustc_span::Span;
2729
use rustc_trait_selection::traits;
@@ -318,6 +320,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
318320
self_ty: Ty<'tcx>,
319321
opt_input_type: Option<Ty<'tcx>>,
320322
opt_input_expr: Option<&'tcx hir::Expr<'tcx>>,
323+
expected: Expectation<'tcx>,
321324
) -> (traits::Obligation<'tcx, ty::Predicate<'tcx>>, &'tcx ty::List<ty::subst::GenericArg<'tcx>>)
322325
{
323326
// Construct a trait-reference `self_ty : Trait<input_tys>`
@@ -339,6 +342,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
339342

340343
// Construct an obligation
341344
let poly_trait_ref = ty::Binder::dummy(trait_ref);
345+
let opt_output_ty =
346+
expected.only_has_type(self).and_then(|ty| (!ty.needs_infer()).then(|| ty));
347+
let opt_output_assoc_item = self.tcx.associated_items(trait_def_id).find_by_name_and_kind(
348+
self.tcx,
349+
Ident::from_str("Output"),
350+
AssocKind::Type,
351+
trait_def_id,
352+
);
353+
let output_pred =
354+
opt_output_ty.zip(opt_output_assoc_item).map(|(output_ty, output_assoc_item)| {
355+
ty::Binder::dummy(ty::PredicateKind::Projection(ProjectionPredicate {
356+
projection_ty: ProjectionTy { substs, item_def_id: output_assoc_item.def_id },
357+
term: Term::Ty(output_ty),
358+
}))
359+
.to_predicate(self.tcx)
360+
});
361+
342362
(
343363
traits::Obligation::new(
344364
traits::ObligationCause::new(
@@ -348,6 +368,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
348368
rhs_span: opt_input_expr.map(|expr| expr.span),
349369
is_lit: opt_input_expr
350370
.map_or(false, |expr| matches!(expr.kind, hir::ExprKind::Lit(_))),
371+
output_pred,
351372
},
352373
),
353374
self.param_env,
@@ -397,13 +418,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
397418
self_ty: Ty<'tcx>,
398419
opt_input_type: Option<Ty<'tcx>>,
399420
opt_input_expr: Option<&'tcx hir::Expr<'tcx>>,
421+
expected: Expectation<'tcx>,
400422
) -> Option<InferOk<'tcx, MethodCallee<'tcx>>> {
401423
let (obligation, substs) = self.obligation_for_op_method(
402424
span,
403425
trait_def_id,
404426
self_ty,
405427
opt_input_type,
406428
opt_input_expr,
429+
expected,
407430
);
408431
self.construct_obligation_for_trait(
409432
span,
@@ -505,6 +528,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
505528
rhs_span: opt_input_expr.map(|expr| expr.span),
506529
is_lit: opt_input_expr
507530
.map_or(false, |expr| matches!(expr.kind, hir::ExprKind::Lit(_))),
531+
output_pred: None,
508532
},
509533
)
510534
} else {

0 commit comments

Comments
 (0)