Skip to content

Commit 083a5cd

Browse files
authored
Rollup merge of #78214 - estebank:match-semicolon, r=oli-obk
Tweak match arm semicolon removal suggestion to account for futures * Tweak and extend "use `.await`" suggestions * Suggest removal of semicolon on prior match arm * Account for `impl Future` when suggesting semicolon removal * Silence some errors when encountering `await foo()?` as can't be certain what the intent was *Thanks to https://twitter.com/a_hoverbear/status/1318960787105353728 for pointing this out!*
2 parents 9e907d4 + f5d7443 commit 083a5cd

24 files changed

+567
-318
lines changed

compiler/rustc_infer/src/infer/error_reporting/mod.rs

+171-52
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ use super::region_constraints::GenericKind;
5050
use super::{InferCtxt, RegionVariableOrigin, SubregionOrigin, TypeTrace, ValuePairs};
5151

5252
use crate::infer;
53-
use crate::infer::OriginalQueryValues;
5453
use crate::traits::error_reporting::report_object_safety_error;
5554
use crate::traits::{
5655
IfExpressionCause, MatchExpressionArmCause, ObligationCause, ObligationCauseCode,
56+
StatementAsExpression,
5757
};
5858

5959
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
@@ -64,7 +64,6 @@ use rustc_hir::def_id::DefId;
6464
use rustc_hir::lang_items::LangItem;
6565
use rustc_hir::{Item, ItemKind, Node};
6666
use rustc_middle::ty::error::TypeError;
67-
use rustc_middle::ty::ParamEnvAnd;
6867
use rustc_middle::ty::{
6968
self,
7069
subst::{Subst, SubstsRef},
@@ -688,13 +687,36 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
688687
};
689688
let msg = "`match` arms have incompatible types";
690689
err.span_label(outer_error_span, msg);
691-
if let Some(sp) = semi_span {
692-
err.span_suggestion_short(
693-
sp,
694-
"consider removing this semicolon",
695-
String::new(),
696-
Applicability::MachineApplicable,
697-
);
690+
if let Some((sp, boxed)) = semi_span {
691+
if let (StatementAsExpression::NeedsBoxing, [.., prior_arm]) =
692+
(boxed, &prior_arms[..])
693+
{
694+
err.multipart_suggestion(
695+
"consider removing this semicolon and boxing the expressions",
696+
vec![
697+
(prior_arm.shrink_to_lo(), "Box::new(".to_string()),
698+
(prior_arm.shrink_to_hi(), ")".to_string()),
699+
(arm_span.shrink_to_lo(), "Box::new(".to_string()),
700+
(arm_span.shrink_to_hi(), ")".to_string()),
701+
(sp, String::new()),
702+
],
703+
Applicability::HasPlaceholders,
704+
);
705+
} else if matches!(boxed, StatementAsExpression::NeedsBoxing) {
706+
err.span_suggestion_short(
707+
sp,
708+
"consider removing this semicolon and boxing the expressions",
709+
String::new(),
710+
Applicability::MachineApplicable,
711+
);
712+
} else {
713+
err.span_suggestion_short(
714+
sp,
715+
"consider removing this semicolon",
716+
String::new(),
717+
Applicability::MachineApplicable,
718+
);
719+
}
698720
}
699721
if let Some(ret_sp) = opt_suggest_box_span {
700722
// Get return type span and point to it.
@@ -717,13 +739,27 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
717739
if let Some(sp) = outer {
718740
err.span_label(sp, "`if` and `else` have incompatible types");
719741
}
720-
if let Some(sp) = semicolon {
721-
err.span_suggestion_short(
722-
sp,
723-
"consider removing this semicolon",
724-
String::new(),
725-
Applicability::MachineApplicable,
726-
);
742+
if let Some((sp, boxed)) = semicolon {
743+
if matches!(boxed, StatementAsExpression::NeedsBoxing) {
744+
err.multipart_suggestion(
745+
"consider removing this semicolon and boxing the expression",
746+
vec![
747+
(then.shrink_to_lo(), "Box::new(".to_string()),
748+
(then.shrink_to_hi(), ")".to_string()),
749+
(else_sp.shrink_to_lo(), "Box::new(".to_string()),
750+
(else_sp.shrink_to_hi(), ")".to_string()),
751+
(sp, String::new()),
752+
],
753+
Applicability::MachineApplicable,
754+
);
755+
} else {
756+
err.span_suggestion_short(
757+
sp,
758+
"consider removing this semicolon",
759+
String::new(),
760+
Applicability::MachineApplicable,
761+
);
762+
}
727763
}
728764
if let Some(ret_sp) = opt_suggest_box_span {
729765
self.suggest_boxing_for_return_impl_trait(
@@ -1602,6 +1638,16 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
16021638
Mismatch::Variable(exp_found) => Some(exp_found),
16031639
Mismatch::Fixed(_) => None,
16041640
};
1641+
let exp_found = match terr {
1642+
// `terr` has more accurate type information than `exp_found` in match expressions.
1643+
ty::error::TypeError::Sorts(terr)
1644+
if exp_found.map_or(false, |ef| terr.found == ef.found) =>
1645+
{
1646+
Some(*terr)
1647+
}
1648+
_ => exp_found,
1649+
};
1650+
debug!("exp_found {:?} terr {:?}", exp_found, terr);
16051651
if let Some(exp_found) = exp_found {
16061652
self.suggest_as_ref_where_appropriate(span, &exp_found, diag);
16071653
self.suggest_await_on_expect_found(cause, span, &exp_found, diag);
@@ -1623,6 +1669,53 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
16231669
self.note_error_origin(diag, cause, exp_found);
16241670
}
16251671

1672+
fn get_impl_future_output_ty(&self, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
1673+
if let ty::Opaque(def_id, substs) = ty.kind() {
1674+
let future_trait = self.tcx.require_lang_item(LangItem::Future, None);
1675+
// Future::Output
1676+
let item_def_id = self
1677+
.tcx
1678+
.associated_items(future_trait)
1679+
.in_definition_order()
1680+
.next()
1681+
.unwrap()
1682+
.def_id;
1683+
1684+
let bounds = self.tcx.explicit_item_bounds(*def_id);
1685+
1686+
for (predicate, _) in bounds {
1687+
let predicate = predicate.subst(self.tcx, substs);
1688+
if let ty::PredicateAtom::Projection(projection_predicate) =
1689+
predicate.skip_binders()
1690+
{
1691+
if projection_predicate.projection_ty.item_def_id == item_def_id {
1692+
// We don't account for multiple `Future::Output = Ty` contraints.
1693+
return Some(projection_predicate.ty);
1694+
}
1695+
}
1696+
}
1697+
}
1698+
None
1699+
}
1700+
1701+
/// A possible error is to forget to add `.await` when using futures:
1702+
///
1703+
/// ```
1704+
/// async fn make_u32() -> u32 {
1705+
/// 22
1706+
/// }
1707+
///
1708+
/// fn take_u32(x: u32) {}
1709+
///
1710+
/// async fn foo() {
1711+
/// let x = make_u32();
1712+
/// take_u32(x);
1713+
/// }
1714+
/// ```
1715+
///
1716+
/// This routine checks if the found type `T` implements `Future<Output=U>` where `U` is the
1717+
/// expected type. If this is the case, and we are inside of an async body, it suggests adding
1718+
/// `.await` to the tail of the expression.
16261719
fn suggest_await_on_expect_found(
16271720
&self,
16281721
cause: &ObligationCause<'tcx>,
@@ -1632,50 +1725,76 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
16321725
) {
16331726
debug!(
16341727
"suggest_await_on_expect_found: exp_span={:?}, expected_ty={:?}, found_ty={:?}",
1635-
exp_span, exp_found.expected, exp_found.found
1728+
exp_span, exp_found.expected, exp_found.found,
16361729
);
16371730

1638-
if let ty::Opaque(def_id, _) = *exp_found.expected.kind() {
1639-
let future_trait = self.tcx.require_lang_item(LangItem::Future, None);
1640-
// Future::Output
1641-
let item_def_id = self
1642-
.tcx
1643-
.associated_items(future_trait)
1644-
.in_definition_order()
1645-
.next()
1646-
.unwrap()
1647-
.def_id;
1731+
if let ObligationCauseCode::CompareImplMethodObligation { .. } = &cause.code {
1732+
return;
1733+
}
16481734

1649-
let projection_ty = self.tcx.projection_ty_from_predicates((def_id, item_def_id));
1650-
if let Some(projection_ty) = projection_ty {
1651-
let projection_query = self.canonicalize_query(
1652-
&ParamEnvAnd { param_env: self.tcx.param_env(def_id), value: projection_ty },
1653-
&mut OriginalQueryValues::default(),
1654-
);
1655-
if let Ok(resp) = self.tcx.normalize_projection_ty(projection_query) {
1656-
let normalized_ty = resp.value.value.normalized_ty;
1657-
debug!("suggest_await_on_expect_found: normalized={:?}", normalized_ty);
1658-
if ty::TyS::same_type(normalized_ty, exp_found.found) {
1659-
let span = if let ObligationCauseCode::Pattern {
1660-
span,
1661-
origin_expr: _,
1662-
root_ty: _,
1663-
} = cause.code
1664-
{
1665-
// scrutinee's span
1666-
span.unwrap_or(exp_span)
1667-
} else {
1668-
exp_span
1669-
};
1670-
diag.span_suggestion_verbose(
1671-
span.shrink_to_hi(),
1672-
"consider awaiting on the future",
1673-
".await".to_string(),
1735+
match (
1736+
self.get_impl_future_output_ty(exp_found.expected),
1737+
self.get_impl_future_output_ty(exp_found.found),
1738+
) {
1739+
(Some(exp), Some(found)) if ty::TyS::same_type(exp, found) => match &cause.code {
1740+
ObligationCauseCode::IfExpression(box IfExpressionCause { then, .. }) => {
1741+
diag.multipart_suggestion(
1742+
"consider `await`ing on both `Future`s",
1743+
vec![
1744+
(then.shrink_to_hi(), ".await".to_string()),
1745+
(exp_span.shrink_to_hi(), ".await".to_string()),
1746+
],
1747+
Applicability::MaybeIncorrect,
1748+
);
1749+
}
1750+
ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause {
1751+
prior_arms,
1752+
..
1753+
}) => {
1754+
if let [.., arm_span] = &prior_arms[..] {
1755+
diag.multipart_suggestion(
1756+
"consider `await`ing on both `Future`s",
1757+
vec![
1758+
(arm_span.shrink_to_hi(), ".await".to_string()),
1759+
(exp_span.shrink_to_hi(), ".await".to_string()),
1760+
],
16741761
Applicability::MaybeIncorrect,
16751762
);
1763+
} else {
1764+
diag.help("consider `await`ing on both `Future`s");
16761765
}
16771766
}
1767+
_ => {
1768+
diag.help("consider `await`ing on both `Future`s");
1769+
}
1770+
},
1771+
(_, Some(ty)) if ty::TyS::same_type(exp_found.expected, ty) => {
1772+
let span = match cause.code {
1773+
// scrutinee's span
1774+
ObligationCauseCode::Pattern { span: Some(span), .. } => span,
1775+
_ => exp_span,
1776+
};
1777+
diag.span_suggestion_verbose(
1778+
span.shrink_to_hi(),
1779+
"consider `await`ing on the `Future`",
1780+
".await".to_string(),
1781+
Applicability::MaybeIncorrect,
1782+
);
1783+
}
1784+
(Some(ty), _) if ty::TyS::same_type(ty, exp_found.found) => {
1785+
let span = match cause.code {
1786+
// scrutinee's span
1787+
ObligationCauseCode::Pattern { span: Some(span), .. } => span,
1788+
_ => exp_span,
1789+
};
1790+
diag.span_suggestion_verbose(
1791+
span.shrink_to_hi(),
1792+
"consider `await`ing on the `Future`",
1793+
".await".to_string(),
1794+
Applicability::MaybeIncorrect,
1795+
);
16781796
}
1797+
_ => {}
16791798
}
16801799
}
16811800

compiler/rustc_middle/src/traits/mod.rs

+15-2
Original file line numberDiff line numberDiff line change
@@ -340,11 +340,24 @@ impl ObligationCauseCode<'_> {
340340
#[cfg(target_arch = "x86_64")]
341341
static_assert_size!(ObligationCauseCode<'_>, 32);
342342

343+
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
344+
pub enum StatementAsExpression {
345+
CorrectType,
346+
NeedsBoxing,
347+
}
348+
349+
impl<'tcx> ty::Lift<'tcx> for StatementAsExpression {
350+
type Lifted = StatementAsExpression;
351+
fn lift_to_tcx(self, _tcx: TyCtxt<'tcx>) -> Option<StatementAsExpression> {
352+
Some(self)
353+
}
354+
}
355+
343356
#[derive(Clone, Debug, PartialEq, Eq, Hash, Lift)]
344357
pub struct MatchExpressionArmCause<'tcx> {
345358
pub arm_span: Span,
346359
pub scrut_span: Span,
347-
pub semi_span: Option<Span>,
360+
pub semi_span: Option<(Span, StatementAsExpression)>,
348361
pub source: hir::MatchSource,
349362
pub prior_arms: Vec<Span>,
350363
pub last_ty: Ty<'tcx>,
@@ -357,7 +370,7 @@ pub struct IfExpressionCause {
357370
pub then: Span,
358371
pub else_sp: Span,
359372
pub outer: Option<Span>,
360-
pub semicolon: Option<Span>,
373+
pub semicolon: Option<(Span, StatementAsExpression)>,
361374
pub opt_suggest_box_span: Option<Span>,
362375
}
363376

compiler/rustc_middle/src/ty/error.rs

+13-24
Original file line numberDiff line numberDiff line change
@@ -334,26 +334,15 @@ impl<'tcx> TyCtxt<'tcx> {
334334
debug!("note_and_explain_type_err err={:?} cause={:?}", err, cause);
335335
match err {
336336
Sorts(values) => {
337-
let expected_str = values.expected.sort_string(self);
338-
let found_str = values.found.sort_string(self);
339-
if expected_str == found_str && expected_str == "closure" {
340-
db.note("no two closures, even if identical, have the same type");
341-
db.help("consider boxing your closure and/or using it as a trait object");
342-
}
343-
if expected_str == found_str && expected_str == "opaque type" {
344-
// Issue #63167
345-
db.note("distinct uses of `impl Trait` result in different opaque types");
346-
let e_str = values.expected.to_string();
347-
let f_str = values.found.to_string();
348-
if e_str == f_str && &e_str == "impl std::future::Future" {
349-
// FIXME: use non-string based check.
350-
db.help(
351-
"if both `Future`s have the same `Output` type, consider \
352-
`.await`ing on both of them",
353-
);
354-
}
355-
}
356337
match (values.expected.kind(), values.found.kind()) {
338+
(ty::Closure(..), ty::Closure(..)) => {
339+
db.note("no two closures, even if identical, have the same type");
340+
db.help("consider boxing your closure and/or using it as a trait object");
341+
}
342+
(ty::Opaque(..), ty::Opaque(..)) => {
343+
// Issue #63167
344+
db.note("distinct uses of `impl Trait` result in different opaque types");
345+
}
357346
(ty::Float(_), ty::Infer(ty::IntVar(_))) => {
358347
if let Ok(
359348
// Issue #53280
@@ -382,12 +371,12 @@ impl<'tcx> TyCtxt<'tcx> {
382371
}
383372
db.note(
384373
"a type parameter was expected, but a different one was found; \
385-
you might be missing a type parameter or trait bound",
374+
you might be missing a type parameter or trait bound",
386375
);
387376
db.note(
388377
"for more information, visit \
389-
https://doc.rust-lang.org/book/ch10-02-traits.html\
390-
#traits-as-parameters",
378+
https://doc.rust-lang.org/book/ch10-02-traits.html\
379+
#traits-as-parameters",
391380
);
392381
}
393382
(ty::Projection(_), ty::Projection(_)) => {
@@ -471,8 +460,8 @@ impl<T> Trait<T> for X {
471460
}
472461
db.note(
473462
"for more information, visit \
474-
https://doc.rust-lang.org/book/ch10-02-traits.html\
475-
#traits-as-parameters",
463+
https://doc.rust-lang.org/book/ch10-02-traits.html\
464+
#traits-as-parameters",
476465
);
477466
}
478467
(ty::Param(p), ty::Closure(..) | ty::Generator(..)) => {

compiler/rustc_parse/src/parser/diagnostics.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -1207,7 +1207,13 @@ impl<'a> Parser<'a> {
12071207
self.recover_await_prefix(await_sp)?
12081208
};
12091209
let sp = self.error_on_incorrect_await(lo, hi, &expr, is_question);
1210-
let expr = self.mk_expr(lo.to(sp), ExprKind::Await(expr), attrs);
1210+
let kind = match expr.kind {
1211+
// Avoid knock-down errors as we don't know whether to interpret this as `foo().await?`
1212+
// or `foo()?.await` (the very reason we went with postfix syntax 😅).
1213+
ExprKind::Try(_) => ExprKind::Err,
1214+
_ => ExprKind::Await(expr),
1215+
};
1216+
let expr = self.mk_expr(lo.to(sp), kind, attrs);
12111217
self.maybe_recover_from_bad_qpath(expr, true)
12121218
}
12131219

0 commit comments

Comments
 (0)