Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Permit Coercions in Type Ascriptions #79730

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions compiler/rustc_typeck/src/check/_match.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::check::coercion::CoerceMany;
use crate::check::{Diverges, Expectation, FnCtxt, Needs};
use crate::check::{Diverges, Expectation, FnCtxt, Needs, TypeAscriptionCtxt};
use rustc_hir::{self as hir, ExprKind};
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
use rustc_infer::traits::Obligation;
Expand Down Expand Up @@ -41,7 +41,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// 2. By expecting `bool` for `expr` we get nice diagnostics for e.g. `if x = y { .. }`.
//
// FIXME(60707): Consider removing hack with principled solution.
self.check_expr_has_type_or_error(scrut, self.tcx.types.bool, |_| {})
self.check_expr_has_type_or_error(
scrut,
self.tcx.types.bool,
|_| {},
orig_expected.get_coercion_ctxt(),
)
} else {
self.demand_scrutinee_type(arms, scrut)
};
Expand Down Expand Up @@ -82,7 +87,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// us to give better error messages (pointing to a usually better
// arm for inconsistent arms or to the whole match when a `()` type
// is required).
Expectation::ExpectHasType(ety) if ety != self.tcx.mk_unit() => ety,
Expectation::ExpectHasType(ety, _) if ety != self.tcx.mk_unit() => ety,
_ => self.next_ty_var(TypeVariableOrigin {
kind: TypeVariableOriginKind::MiscVariable,
span: expr.span,
Expand All @@ -97,9 +102,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
if let Some(g) = &arm.guard {
self.diverges.set(Diverges::Maybe);
match g {
hir::Guard::If(e) => {
self.check_expr_has_type_or_error(e, tcx.types.bool, |_| {})
}
hir::Guard::If(e) => self.check_expr_has_type_or_error(
e,
tcx.types.bool,
|_| {},
expected.get_coercion_ctxt(),
),
};
}

Expand All @@ -124,7 +132,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
orig_expected,
self.ret_coercion_impl_trait.map(|ty| (self.body_id.owner, ty)),
) {
(Expectation::ExpectHasType(expected), Some((id, ty)))
(Expectation::ExpectHasType(expected, _), Some((id, ty)))
if self.in_tail_expr && self.can_coerce(arm_ty, expected) =>
{
let impl_trait_ret_ty = self.infcx.instantiate_opaque_types(
Expand Down Expand Up @@ -525,7 +533,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
kind: TypeVariableOriginKind::TypeInference,
span: scrut.span,
});
self.check_expr_has_type_or_error(scrut, scrut_ty, |_| {});
self.check_expr_has_type_or_error(scrut, scrut_ty, |_| {}, TypeAscriptionCtxt::Normal);
scrut_ty
}
}
Expand Down
83 changes: 64 additions & 19 deletions compiler/rustc_typeck/src/check/expectation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,44 @@ use rustc_span::{self, Span};
use super::Expectation::*;
use super::FnCtxt;

// We permit coercion of type ascriptions in coercion sites and sub-expressions
// that originate from coercion sites. When we encounter a
// coercion site we propagate this expectation of coercions of type ascriptions
// down into sub-expressions by providing the `Expectation` with a
// TypeAscriptionCtxt::Coercion. Whenever we encounter an expression of
// ExprKind::Type in a sub-expression and TypeAscriptionCtxt is set, we coerce
// the type ascription
#[derive(Copy, Clone, Debug)]
pub enum TypeAscriptionCtxt {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Let's rename this to something like CoercionPermitted

Coercion,
Normal,
}

impl TypeAscriptionCtxt {
fn is_coercion_site(self) -> bool {
match self {
TypeAscriptionCtxt::Coercion => true,
TypeAscriptionCtxt::Normal => false,
}
}
}

/// When type-checking an expression, we propagate downward
/// whatever type hint we are able in the form of an `Expectation`.
#[derive(Copy, Clone, Debug)]
pub enum Expectation<'tcx> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I feel like we should structure this like

struct Expectation<'tcx> {
    coercion_site: TypeAscriptionCtxt,
    kind: ExpectationKind<'tcx>
}

enum ExpectationKind<"tcx> {
    NoExpectation,
    ....
}

/// We know nothing about what type this expression should have.
NoExpectation,
NoExpectation(TypeAscriptionCtxt),

/// This expression should have the type given (or some subtype).
ExpectHasType(Ty<'tcx>),
ExpectHasType(Ty<'tcx>, TypeAscriptionCtxt),

/// This expression will be cast to the `Ty`.
ExpectCastableToType(Ty<'tcx>),
ExpectCastableToType(Ty<'tcx>, TypeAscriptionCtxt),

/// This rvalue expression will be wrapped in `&` or `Box` and coerced
/// to `&Ty` or `Box<Ty>`, respectively. `Ty` is `[A]` or `Trait`.
ExpectRvalueLikeUnsized(Ty<'tcx>),
ExpectRvalueLikeUnsized(Ty<'tcx>, TypeAscriptionCtxt),
}

impl<'a, 'tcx> Expectation<'tcx> {
Expand All @@ -42,12 +64,12 @@ impl<'a, 'tcx> Expectation<'tcx> {
// 'else' branch.
pub(super) fn adjust_for_branches(&self, fcx: &FnCtxt<'a, 'tcx>) -> Expectation<'tcx> {
match *self {
ExpectHasType(ety) => {
ExpectHasType(ety, ctxt) => {
let ety = fcx.shallow_resolve(ety);
if !ety.is_ty_var() { ExpectHasType(ety) } else { NoExpectation }
if !ety.is_ty_var() { ExpectHasType(ety, ctxt) } else { NoExpectation(ctxt) }
}
ExpectRvalueLikeUnsized(ety) => ExpectRvalueLikeUnsized(ety),
_ => NoExpectation,
ExpectRvalueLikeUnsized(ety, ctxt) => ExpectRvalueLikeUnsized(ety, ctxt),
_ => NoExpectation(TypeAscriptionCtxt::Normal),
}
}

Expand All @@ -70,10 +92,14 @@ impl<'a, 'tcx> Expectation<'tcx> {
/// which still is useful, because it informs integer literals and the like.
/// See the test case `test/ui/coerce-expect-unsized.rs` and #20169
/// for examples of where this comes up,.
pub(super) fn rvalue_hint(fcx: &FnCtxt<'a, 'tcx>, ty: Ty<'tcx>) -> Expectation<'tcx> {
pub(super) fn rvalue_hint(
fcx: &FnCtxt<'a, 'tcx>,
ty: Ty<'tcx>,
ctxt: TypeAscriptionCtxt,
) -> Expectation<'tcx> {
match fcx.tcx.struct_tail_without_normalization(ty).kind() {
ty::Slice(_) | ty::Str | ty::Dynamic(..) => ExpectRvalueLikeUnsized(ty),
_ => ExpectHasType(ty),
ty::Slice(_) | ty::Str | ty::Dynamic(..) => ExpectRvalueLikeUnsized(ty, ctxt),
_ => ExpectHasType(ty, ctxt),
}
}

Expand All @@ -82,17 +108,23 @@ impl<'a, 'tcx> Expectation<'tcx> {
// no constraints yet present), just returns `None`.
fn resolve(self, fcx: &FnCtxt<'a, 'tcx>) -> Expectation<'tcx> {
match self {
NoExpectation => NoExpectation,
ExpectCastableToType(t) => ExpectCastableToType(fcx.resolve_vars_if_possible(t)),
ExpectHasType(t) => ExpectHasType(fcx.resolve_vars_if_possible(t)),
ExpectRvalueLikeUnsized(t) => ExpectRvalueLikeUnsized(fcx.resolve_vars_if_possible(t)),
NoExpectation(ctxt) => NoExpectation(ctxt),
ExpectCastableToType(t, ctxt) => {
ExpectCastableToType(fcx.resolve_vars_if_possible(t), ctxt)
}
ExpectHasType(t, ctxt) => ExpectHasType(fcx.resolve_vars_if_possible(t), ctxt),
ExpectRvalueLikeUnsized(t, ctxt) => {
ExpectRvalueLikeUnsized(fcx.resolve_vars_if_possible(t), ctxt)
}
}
}

pub(super) fn to_option(self, fcx: &FnCtxt<'a, 'tcx>) -> Option<Ty<'tcx>> {
match self.resolve(fcx) {
NoExpectation => None,
ExpectCastableToType(ty) | ExpectHasType(ty) | ExpectRvalueLikeUnsized(ty) => Some(ty),
NoExpectation(_) => None,
ExpectCastableToType(ty, _) | ExpectHasType(ty, _) | ExpectRvalueLikeUnsized(ty, _) => {
Some(ty)
}
}
}

Expand All @@ -102,8 +134,8 @@ impl<'a, 'tcx> Expectation<'tcx> {
/// such a constraint, if it exists.
pub(super) fn only_has_type(self, fcx: &FnCtxt<'a, 'tcx>) -> Option<Ty<'tcx>> {
match self.resolve(fcx) {
ExpectHasType(ty) => Some(ty),
NoExpectation | ExpectCastableToType(_) | ExpectRvalueLikeUnsized(_) => None,
ExpectHasType(ty, _) => Some(ty),
NoExpectation(_) | ExpectCastableToType(_, _) | ExpectRvalueLikeUnsized(_, _) => None,
}
}

Expand All @@ -114,4 +146,17 @@ impl<'a, 'tcx> Expectation<'tcx> {
fcx.next_ty_var(TypeVariableOrigin { kind: TypeVariableOriginKind::MiscVariable, span })
})
}

pub(super) fn get_coercion_ctxt(self) -> TypeAscriptionCtxt {
match self {
ExpectHasType(_, ctxt) => ctxt,
ExpectCastableToType(_, ctxt) => ctxt,
ExpectRvalueLikeUnsized(_, ctxt) => ctxt,
NoExpectation(ctxt) => ctxt,
}
}

pub(super) fn coerce_type_ascriptions(self) -> bool {
self.get_coercion_ctxt().is_coercion_site()
}
}
Loading