Propagate Expectation around binop typeck code to construct more precise trait obligations for binops.

This commit is contained in:
Will Crichton 2022-04-26 13:59:08 -07:00
parent 8c1cc82a82
commit e5bb7d80d6
12 changed files with 194 additions and 42 deletions

View File

@ -12,7 +12,7 @@ pub mod util;
use crate::infer::canonical::Canonical;
use crate::ty::abstract_const::NotConstEvaluatable;
use crate::ty::subst::SubstsRef;
use crate::ty::{self, AdtKind, Ty, TyCtxt};
use crate::ty::{self, AdtKind, Predicate, Ty, TyCtxt};
use rustc_data_structures::sync::Lrc;
use rustc_errors::{Applicability, Diagnostic};
@ -414,6 +414,7 @@ pub enum ObligationCauseCode<'tcx> {
BinOp {
rhs_span: Option<Span>,
is_lit: bool,
output_pred: Option<Predicate<'tcx>>,
},
}

View File

@ -1033,6 +1033,24 @@ impl<'tcx> Predicate<'tcx> {
}
}
pub fn to_opt_poly_projection_pred(self) -> Option<PolyProjectionPredicate<'tcx>> {
let predicate = self.kind();
match predicate.skip_binder() {
PredicateKind::Projection(t) => Some(predicate.rebind(t)),
PredicateKind::Trait(..)
| PredicateKind::Subtype(..)
| PredicateKind::Coerce(..)
| PredicateKind::RegionOutlives(..)
| PredicateKind::WellFormed(..)
| PredicateKind::ObjectSafe(..)
| PredicateKind::ClosureKind(..)
| PredicateKind::TypeOutlives(..)
| PredicateKind::ConstEvaluatable(..)
| PredicateKind::ConstEquate(..)
| PredicateKind::TypeWellFormedFromEnv(..) => None,
}
}
pub fn to_opt_type_outlives(self) -> Option<PolyTypeOutlivesPredicate<'tcx>> {
let predicate = self.kind();
match predicate.skip_binder() {

View File

@ -665,6 +665,7 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
self.suggest_restricting_param_bound(
&mut err,
trait_predicate,
None,
obligation.cause.body_id,
);
} else if !suggested {

View File

@ -24,7 +24,8 @@ use rustc_middle::hir::map;
use rustc_middle::ty::{
self, suggest_arbitrary_trait_bound, suggest_constraining_type_param, AdtKind, DefIdTree,
GeneratorDiagnosticData, GeneratorInteriorTypeCause, Infer, InferTy, IsSuggestable,
ToPredicate, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeVisitable,
ProjectionPredicate, ToPredicate, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable,
TypeVisitable,
};
use rustc_middle::ty::{TypeAndMut, TypeckResults};
use rustc_session::Limit;
@ -172,6 +173,7 @@ pub trait InferCtxtExt<'tcx> {
&self,
err: &mut Diagnostic,
trait_pred: ty::PolyTraitPredicate<'tcx>,
proj_pred: Option<ty::PolyProjectionPredicate<'tcx>>,
body_id: hir::HirId,
);
@ -457,6 +459,7 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
&self,
mut err: &mut Diagnostic,
trait_pred: ty::PolyTraitPredicate<'tcx>,
proj_pred: Option<ty::PolyProjectionPredicate<'tcx>>,
body_id: hir::HirId,
) {
let trait_pred = self.resolve_numeric_literals_with_default(trait_pred);
@ -589,9 +592,16 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
}
// Missing generic type parameter bound.
let param_name = self_ty.to_string();
let constraint = with_no_trimmed_paths!(
let mut constraint = with_no_trimmed_paths!(
trait_pred.print_modifiers_and_trait_path().to_string()
);
if let Some(proj_pred) = proj_pred {
let ProjectionPredicate { projection_ty, term } = proj_pred.skip_binder();
let item = self.tcx.associated_item(projection_ty.item_def_id);
constraint.push_str(&format!("<{}={}>", item.name, term.to_string()));
}
if suggest_constraining_type_param(
self.tcx,
generics,
@ -2825,7 +2835,7 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
trait_ref: &ty::PolyTraitRef<'tcx>,
) {
let rhs_span = match obligation.cause.code() {
ObligationCauseCode::BinOp { rhs_span: Some(span), is_lit } if *is_lit => span,
ObligationCauseCode::BinOp { rhs_span: Some(span), is_lit, .. } if *is_lit => span,
_ => return,
};
match (

View File

@ -282,11 +282,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
match expr.kind {
ExprKind::Box(subexpr) => self.check_expr_box(subexpr, expected),
ExprKind::Lit(ref lit) => self.check_lit(&lit, expected),
ExprKind::Binary(op, lhs, rhs) => self.check_binop(expr, op, lhs, rhs),
ExprKind::Binary(op, lhs, rhs) => self.check_binop(expr, op, lhs, rhs, expected),
ExprKind::Assign(lhs, rhs, span) => {
self.check_expr_assign(expr, expected, lhs, rhs, span)
}
ExprKind::AssignOp(op, lhs, rhs) => self.check_binop_assign(expr, op, lhs, rhs),
ExprKind::AssignOp(op, lhs, rhs) => {
self.check_binop_assign(expr, op, lhs, rhs, expected)
}
ExprKind::Unary(unop, oprnd) => self.check_expr_unary(unop, oprnd, expected, expr),
ExprKind::AddrOf(kind, mutbl, oprnd) => {
self.check_expr_addr_of(kind, mutbl, oprnd, expected, expr)
@ -404,14 +406,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}
hir::UnOp::Not => {
let result = self.check_user_unop(expr, oprnd_t, unop);
let result = self.check_user_unop(expr, oprnd_t, unop, expected_inner);
// If it's builtin, we can reuse the type, this helps inference.
if !(oprnd_t.is_integral() || *oprnd_t.kind() == ty::Bool) {
oprnd_t = result;
}
}
hir::UnOp::Neg => {
let result = self.check_user_unop(expr, oprnd_t, unop);
let result = self.check_user_unop(expr, oprnd_t, unop, expected_inner);
// If it's builtin, we can reuse the type, this helps inference.
if !oprnd_t.is_numeric() {
oprnd_t = result;

View File

@ -413,6 +413,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
rhs_span: opt_input_expr.map(|expr| expr.span),
is_lit: opt_input_expr
.map_or(false, |expr| matches!(expr.kind, ExprKind::Lit(_))),
output_pred: None,
},
),
self.param_env,

View File

@ -10,7 +10,7 @@ mod suggest;
pub use self::suggest::SelfSource;
pub use self::MethodError::*;
use crate::check::FnCtxt;
use crate::check::{Expectation, FnCtxt};
use crate::ObligationCause;
use rustc_data_structures::sync::Lrc;
use rustc_errors::{Applicability, Diagnostic};
@ -20,8 +20,10 @@ use rustc_hir::def_id::DefId;
use rustc_infer::infer::{self, InferOk};
use rustc_middle::ty::subst::Subst;
use rustc_middle::ty::subst::{InternalSubsts, SubstsRef};
use rustc_middle::ty::{self, ToPredicate, Ty, TypeVisitable};
use rustc_middle::ty::{DefIdTree, GenericParamDefKind};
use rustc_middle::ty::{
self, AssocKind, DefIdTree, GenericParamDefKind, ProjectionPredicate, ProjectionTy, Term,
ToPredicate, Ty, TypeVisitable,
};
use rustc_span::symbol::Ident;
use rustc_span::Span;
use rustc_trait_selection::traits;
@ -318,6 +320,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self_ty: Ty<'tcx>,
opt_input_type: Option<Ty<'tcx>>,
opt_input_expr: Option<&'tcx hir::Expr<'tcx>>,
expected: Expectation<'tcx>,
) -> (traits::Obligation<'tcx, ty::Predicate<'tcx>>, &'tcx ty::List<ty::subst::GenericArg<'tcx>>)
{
// Construct a trait-reference `self_ty : Trait<input_tys>`
@ -339,6 +342,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// Construct an obligation
let poly_trait_ref = ty::Binder::dummy(trait_ref);
let opt_output_ty =
expected.only_has_type(self).and_then(|ty| (!ty.needs_infer()).then(|| ty));
let opt_output_assoc_item = self.tcx.associated_items(trait_def_id).find_by_name_and_kind(
self.tcx,
Ident::from_str("Output"),
AssocKind::Type,
trait_def_id,
);
let output_pred =
opt_output_ty.zip(opt_output_assoc_item).map(|(output_ty, output_assoc_item)| {
ty::Binder::dummy(ty::PredicateKind::Projection(ProjectionPredicate {
projection_ty: ProjectionTy { substs, item_def_id: output_assoc_item.def_id },
term: Term::Ty(output_ty),
}))
.to_predicate(self.tcx)
});
(
traits::Obligation::new(
traits::ObligationCause::new(
@ -348,6 +368,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
rhs_span: opt_input_expr.map(|expr| expr.span),
is_lit: opt_input_expr
.map_or(false, |expr| matches!(expr.kind, hir::ExprKind::Lit(_))),
output_pred,
},
),
self.param_env,
@ -397,6 +418,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self_ty: Ty<'tcx>,
opt_input_type: Option<Ty<'tcx>>,
opt_input_expr: Option<&'tcx hir::Expr<'tcx>>,
expected: Expectation<'tcx>,
) -> Option<InferOk<'tcx, MethodCallee<'tcx>>> {
let (obligation, substs) = self.obligation_for_op_method(
span,
@ -404,6 +426,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self_ty,
opt_input_type,
opt_input_expr,
expected,
);
self.construct_obligation_for_trait(
span,
@ -505,6 +528,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
rhs_span: opt_input_expr.map(|expr| expr.span),
is_lit: opt_input_expr
.map_or(false, |expr| matches!(expr.kind, hir::ExprKind::Lit(_))),
output_pred: None,
},
)
} else {

View File

@ -2,10 +2,12 @@
use super::method::MethodCallee;
use super::{has_expected_num_generic_args, FnCtxt};
use crate::check::Expectation;
use rustc_ast as ast;
use rustc_errors::{self, struct_span_err, Applicability, Diagnostic};
use rustc_hir as hir;
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
use rustc_infer::traits::ObligationCauseCode;
use rustc_middle::ty::adjustment::{
Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability,
};
@ -30,9 +32,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
op: hir::BinOp,
lhs: &'tcx hir::Expr<'tcx>,
rhs: &'tcx hir::Expr<'tcx>,
expected: Expectation<'tcx>,
) -> Ty<'tcx> {
let (lhs_ty, rhs_ty, return_ty) =
self.check_overloaded_binop(expr, lhs, rhs, op, IsAssign::Yes);
self.check_overloaded_binop(expr, lhs, rhs, op, IsAssign::Yes, expected);
let ty =
if !lhs_ty.is_ty_var() && !rhs_ty.is_ty_var() && is_builtin_binop(lhs_ty, rhs_ty, op) {
@ -50,6 +53,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Some(rhs_ty),
Some(rhs),
Op::Binary(op, IsAssign::Yes),
expected,
)
.is_ok()
{
@ -70,6 +74,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
op: hir::BinOp,
lhs_expr: &'tcx hir::Expr<'tcx>,
rhs_expr: &'tcx hir::Expr<'tcx>,
expected: Expectation<'tcx>,
) -> Ty<'tcx> {
let tcx = self.tcx;
@ -94,8 +99,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// Otherwise, we always treat operators as if they are
// overloaded. This is the way to be most flexible w/r/t
// types that get inferred.
let (lhs_ty, rhs_ty, return_ty) =
self.check_overloaded_binop(expr, lhs_expr, rhs_expr, op, IsAssign::No);
let (lhs_ty, rhs_ty, return_ty) = self.check_overloaded_binop(
expr,
lhs_expr,
rhs_expr,
op,
IsAssign::No,
expected,
);
// Supply type inference hints if relevant. Probably these
// hints should be enforced during select as part of the
@ -176,6 +187,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
rhs_expr: &'tcx hir::Expr<'tcx>,
op: hir::BinOp,
is_assign: IsAssign,
expected: Expectation<'tcx>,
) -> (Ty<'tcx>, Ty<'tcx>, Ty<'tcx>) {
debug!(
"check_overloaded_binop(expr.hir_id={}, op={:?}, is_assign={:?})",
@ -222,6 +234,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Some(rhs_ty_var),
Some(rhs_expr),
Op::Binary(op, is_assign),
expected,
);
// see `NB` above
@ -282,7 +295,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Err(_) if lhs_ty.references_error() || rhs_ty.references_error() => self.tcx.ty_error(),
Err(errors) => {
let source_map = self.tcx.sess.source_map();
let (mut err, missing_trait, _use_output) = match is_assign {
let (mut err, missing_trait, use_output) = match is_assign {
IsAssign::Yes => {
let mut err = struct_span_err!(
self.tcx.sess,
@ -406,6 +419,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
rhs_expr,
op,
is_assign,
expected,
);
self.add_type_neq_err_label(
&mut err,
@ -415,6 +429,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
lhs_expr,
op,
is_assign,
expected,
);
}
self.note_unmet_impls_on_type(&mut err, errors);
@ -429,6 +444,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Some(rhs_ty),
Some(rhs_expr),
Op::Binary(op, is_assign),
expected,
)
.is_ok()
{
@ -490,19 +506,31 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Some(rhs_ty),
Some(rhs_expr),
Op::Binary(op, is_assign),
expected,
)
.unwrap_err();
let predicates = errors
.into_iter()
.filter_map(|error| error.obligation.predicate.to_opt_poly_trait_pred())
.collect::<Vec<_>>();
if !predicates.is_empty() {
for pred in predicates {
self.infcx.suggest_restricting_param_bound(
&mut err,
pred,
self.body_id,
);
if !errors.is_empty() {
for error in errors {
if let Some(trait_pred) =
error.obligation.predicate.to_opt_poly_trait_pred()
{
let proj_pred = match error.obligation.cause.code() {
ObligationCauseCode::BinOp {
output_pred: Some(output_pred),
..
} if use_output => {
output_pred.to_opt_poly_projection_pred()
}
_ => None,
};
self.infcx.suggest_restricting_param_bound(
&mut err,
trait_pred,
proj_pred,
self.body_id,
);
}
}
} else if *ty != lhs_ty {
// When we know that a missing bound is responsible, we don't show
@ -532,6 +560,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
other_expr: &'tcx hir::Expr<'tcx>,
op: hir::BinOp,
is_assign: IsAssign,
expected: Expectation<'tcx>,
) -> bool /* did we suggest to call a function because of missing parentheses? */ {
err.span_label(span, ty.to_string());
if let FnDef(def_id, _) = *ty.kind() {
@ -561,6 +590,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Some(other_ty),
Some(other_expr),
Op::Binary(op, is_assign),
expected,
)
.is_ok()
{
@ -677,9 +707,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
ex: &'tcx hir::Expr<'tcx>,
operand_ty: Ty<'tcx>,
op: hir::UnOp,
expected: Expectation<'tcx>,
) -> Ty<'tcx> {
assert!(op.is_by_value());
match self.lookup_op_method(operand_ty, None, None, Op::Unary(op, ex.span)) {
match self.lookup_op_method(operand_ty, None, None, Op::Unary(op, ex.span), expected) {
Ok(method) => {
self.write_method_call(ex.hir_id, method);
method.sig.output()
@ -712,6 +743,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self.infcx.suggest_restricting_param_bound(
&mut err,
pred,
None,
self.body_id,
);
}
@ -772,6 +804,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
other_ty: Option<Ty<'tcx>>,
other_ty_expr: Option<&'tcx hir::Expr<'tcx>>,
op: Op,
expected: Expectation<'tcx>,
) -> Result<MethodCallee<'tcx>, Vec<FulfillmentError<'tcx>>> {
let lang = self.tcx.lang_items();
@ -856,7 +889,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let opname = Ident::with_dummy_span(opname);
let method = trait_did.and_then(|trait_did| {
self.lookup_op_method_in_trait(span, opname, trait_did, lhs_ty, other_ty, other_ty_expr)
self.lookup_op_method_in_trait(
span,
opname,
trait_did,
lhs_ty,
other_ty,
other_ty_expr,
expected,
)
});
match (method, trait_did) {
@ -867,8 +908,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
(None, None) => Err(vec![]),
(None, Some(trait_did)) => {
let (obligation, _) =
self.obligation_for_op_method(span, trait_did, lhs_ty, other_ty, other_ty_expr);
let (obligation, _) = self.obligation_for_op_method(
span,
trait_did,
lhs_ty,
other_ty,
other_ty_expr,
expected,
);
let mut fulfill = <dyn TraitEngine<'_>>::new(self.tcx);
fulfill.register_predicate_obligation(self, obligation);
Err(fulfill.select_where_possible(&self.infcx))

View File

@ -0,0 +1,46 @@
// run-rustfix
use std::ops::Add;
struct A<B>(B);
impl<B> Add for A<B> where B: Add + Add<Output = B> {
type Output = Self;
fn add(self, rhs: Self) -> Self {
A(self.0 + rhs.0) //~ ERROR mismatched types
}
}
struct C<B>(B);
impl<B: Add + Add<Output = B>> Add for C<B> {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self(self.0 + rhs.0) //~ ERROR mismatched types
}
}
struct D<B>(B);
impl<B: std::ops::Add<Output=B>> Add for D<B> {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self(self.0 + rhs.0) //~ ERROR cannot add `B` to `B`
}
}
struct E<B>(B);
impl<B: Add + Add<Output = B>> Add for E<B> where B: Add<Output = B> {
//~^ ERROR equality constraints are not yet supported in `where` clauses
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self(self.0 + rhs.0) //~ ERROR mismatched types
}
}
fn main() {}

View File

@ -1,3 +1,5 @@
// run-rustfix
use std::ops::Add;
struct A<B>(B);

View File

@ -1,5 +1,5 @@
error: equality constraints are not yet supported in `where` clauses
--> $DIR/missing-bounds.rs:35:33
--> $DIR/missing-bounds.rs:37:33
|
LL | impl<B: Add> Add for E<B> where <B as Add>::Output = B {
| ^^^^^^^^^^^^^^^^^^^^^^ not supported
@ -11,7 +11,7 @@ LL | impl<B: Add> Add for E<B> where B: Add<Output = B> {
| ~~~~~~~~~~~~~~~~~~
error[E0308]: mismatched types
--> $DIR/missing-bounds.rs:9:11
--> $DIR/missing-bounds.rs:11:11
|
LL | impl<B> Add for A<B> where B: Add {
| - this type parameter
@ -24,7 +24,7 @@ LL | A(self.0 + rhs.0)
= note: expected type parameter `B`
found associated type `<B as Add>::Output`
note: tuple struct defined here
--> $DIR/missing-bounds.rs:3:8
--> $DIR/missing-bounds.rs:5:8
|
LL | struct A<B>(B);
| ^
@ -34,7 +34,7 @@ LL | impl<B> Add for A<B> where B: Add + Add<Output = B> {
| +++++++++++++++++
error[E0308]: mismatched types
--> $DIR/missing-bounds.rs:19:14
--> $DIR/missing-bounds.rs:21:14
|
LL | impl<B: Add> Add for C<B> {
| - this type parameter
@ -47,7 +47,7 @@ LL | Self(self.0 + rhs.0)
= note: expected type parameter `B`
found associated type `<B as Add>::Output`
note: tuple struct defined here
--> $DIR/missing-bounds.rs:13:8
--> $DIR/missing-bounds.rs:15:8
|
LL | struct C<B>(B);
| ^
@ -57,7 +57,7 @@ LL | impl<B: Add + Add<Output = B>> Add for C<B> {
| +++++++++++++++++
error[E0369]: cannot add `B` to `B`
--> $DIR/missing-bounds.rs:29:21
--> $DIR/missing-bounds.rs:31:21
|
LL | Self(self.0 + rhs.0)
| ------ ^ ----- B
@ -66,11 +66,11 @@ LL | Self(self.0 + rhs.0)
|
help: consider restricting type parameter `B`
|
LL | impl<B: std::ops::Add> Add for D<B> {
| +++++++++++++++
LL | impl<B: std::ops::Add<Output=B>> Add for D<B> {
| +++++++++++++++++++++++++
error[E0308]: mismatched types
--> $DIR/missing-bounds.rs:40:14
--> $DIR/missing-bounds.rs:42:14
|
LL | impl<B: Add> Add for E<B> where <B as Add>::Output = B {
| - this type parameter
@ -83,7 +83,7 @@ LL | Self(self.0 + rhs.0)
= note: expected type parameter `B`
found associated type `<B as Add>::Output`
note: tuple struct defined here
--> $DIR/missing-bounds.rs:33:8
--> $DIR/missing-bounds.rs:35:8
|
LL | struct E<B>(B);
| ^

View File

@ -8,8 +8,8 @@ LL | n + 10
|
help: consider restricting type parameter `N`
|
LL | fn add_ten<N: std::ops::Add<i32>>(n: N) -> N {
| ++++++++++++++++++++
LL | fn add_ten<N: std::ops::Add<i32><Output=N>>(n: N) -> N {
| ++++++++++++++++++++++++++++++
error: aborting due to previous error