Skip to content

The never type and diverging type variables #85021

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

Closed
wants to merge 2 commits into from
Closed
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
7 changes: 7 additions & 0 deletions compiler/rustc_infer/src/infer/mod.rs
Original file line number Diff line number Diff line change
@@ -1275,6 +1275,13 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
}
}

/// Returns `false` if all non-auxiliary type variables unified with
/// `vid` is diverging. Returns `true` otherwise.
pub fn probe_ty_diverging(&self, vid: TyVid) -> bool {
let mut inner = self.inner.borrow_mut();
inner.type_variables().var_diverges_with_unification(vid)
}

/// Resolve any type variables found in `value` -- but only one
/// level. So, if the variable `?X` is bound to some type
/// `Foo<?Y>`, then this would return `Foo<?Y>` (but `?Y` may
108 changes: 100 additions & 8 deletions compiler/rustc_infer/src/infer/type_variable.rs
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ use rustc_data_structures::undo_log::{Rollback, UndoLogs};
/// Represents a single undo-able action that affects a type inference variable.
pub(crate) enum UndoLog<'tcx> {
EqRelation(sv::UndoLog<ut::Delegate<TyVidEqKey<'tcx>>>),
SubRelation(sv::UndoLog<ut::Delegate<ty::TyVid>>),
SubRelation(sv::UndoLog<ut::Delegate<TyVidSubKey>>),
Values(sv::UndoLog<Delegate>),
}

@@ -28,8 +28,8 @@ impl<'tcx> From<sv::UndoLog<ut::Delegate<TyVidEqKey<'tcx>>>> for UndoLog<'tcx> {
}

/// Convert from a specific kind of undo to the more general UndoLog
impl<'tcx> From<sv::UndoLog<ut::Delegate<ty::TyVid>>> for UndoLog<'tcx> {
fn from(l: sv::UndoLog<ut::Delegate<ty::TyVid>>) -> Self {
impl<'tcx> From<sv::UndoLog<ut::Delegate<TyVidSubKey>>> for UndoLog<'tcx> {
fn from(l: sv::UndoLog<ut::Delegate<TyVidSubKey>>) -> Self {
UndoLog::SubRelation(l)
}
}
@@ -83,7 +83,7 @@ pub struct TypeVariableStorage<'tcx> {
/// This is reasonable because, in Rust, subtypes have the same
/// "skeleton" and hence there is no possible type such that
/// (e.g.) `Box<?3> <: ?3` for any `?3`.
sub_relations: ut::UnificationTableStorage<ty::TyVid>,
sub_relations: ut::UnificationTableStorage<TyVidSubKey>,
}

pub struct TypeVariableTable<'a, 'tcx> {
@@ -169,6 +169,16 @@ impl<'tcx> TypeVariableStorage<'tcx> {
}

impl<'tcx> TypeVariableTable<'_, 'tcx> {
/// Returns `false` if all non-auxiliary type variables unified with
/// `vid` is diverging. Returns `true` otherwise.
///
/// Precondition: `vid` should be unknown.
pub fn var_diverges_with_unification(&mut self, vid: ty::TyVid) -> bool {
debug_assert!(self.probe(vid).is_unknown());
let kind = self.sub_relations().inlined_probe_value(vid);
matches!(kind, TyVarUnifiedDiverging::Yes)
}

/// Returns the diverges flag given when `vid` was created.
///
/// Note that this function does not return care whether
@@ -243,8 +253,9 @@ impl<'tcx> TypeVariableTable<'_, 'tcx> {
) -> ty::TyVid {
let eq_key = self.eq_relations().new_key(TypeVariableValue::Unknown { universe });

let sub_key = self.sub_relations().new_key(());
assert_eq!(eq_key.vid, sub_key);
let diverging_kind = TyVarUnifiedDiverging::from(diverging, origin.kind);
let sub_key = self.sub_relations().new_key(diverging_kind);
assert_eq!(eq_key.vid, sub_key.vid);

let index = self.values().push(TypeVariableData { origin, diverging });
assert_eq!(eq_key.vid.index, index as u32);
@@ -279,7 +290,7 @@ impl<'tcx> TypeVariableTable<'_, 'tcx> {
///
/// exists X. (a <: X || X <: a) && (b <: X || X <: b)
pub fn sub_root_var(&mut self, vid: ty::TyVid) -> ty::TyVid {
self.sub_relations().find(vid)
self.sub_relations().find(vid).vid
}

/// Returns `true` if `a` and `b` have same "sub-root" (i.e., exists some
@@ -326,7 +337,7 @@ impl<'tcx> TypeVariableTable<'_, 'tcx> {
}

#[inline]
fn sub_relations(&mut self) -> super::UnificationTable<'_, 'tcx, ty::TyVid> {
fn sub_relations(&mut self) -> super::UnificationTable<'_, 'tcx, TyVidSubKey> {
self.storage.sub_relations.with_log(self.undo_log)
}

@@ -443,3 +454,84 @@ impl<'tcx> ut::UnifyValue for TypeVariableValue<'tcx> {
}
}
}

///////////////////////////////////////////////////////////////////////////

/// These structs (a newtyped TyVid) are used as the unification key
/// for the `sub_relations`; they carry a `TyVarUnifiedDiverging`
/// along with them.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) struct TyVidSubKey {
vid: ty::TyVid,
}

/// This enum denotes whether unified type variables are all diverging
/// variables. Note auxiliary type variables (guessed with the help of
/// `TypeVariableOriginKind`) should be ignored.
#[derive(Copy, Clone, Debug)]
pub enum TyVarUnifiedDiverging {
/// All unified type variables are diverging.
Yes,
/// Some unified type variable are not diverging.
No,
/// We don't know the final result at all because we haven't seen
/// any non-auxiliary type variables yet.
Maybe,
}

impl From<ty::TyVid> for TyVidSubKey {
fn from(vid: ty::TyVid) -> Self {
TyVidSubKey { vid }
}
}

impl ut::UnifyKey for TyVidSubKey {
type Value = TyVarUnifiedDiverging;
fn index(&self) -> u32 {
self.vid.index
}
fn from_index(i: u32) -> Self {
TyVidSubKey::from(ty::TyVid { index: i })
}
fn tag() -> &'static str {
"TyVidSubKey"
}
}

impl ut::UnifyValue for TyVarUnifiedDiverging {
type Error = ut::NoError;

fn unify_values(value1: &Self, value2: &Self) -> Result<Self, ut::NoError> {
match (*value1, *value2) {
// Auxiliary type variables should be ignored.
(TyVarUnifiedDiverging::Maybe, other) => Ok(other),
(other, TyVarUnifiedDiverging::Maybe) => Ok(other),

// We've found some non-diverging type variables.
(TyVarUnifiedDiverging::No, _) => Ok(TyVarUnifiedDiverging::No),
(_, TyVarUnifiedDiverging::No) => Ok(TyVarUnifiedDiverging::No),

// All type variables are diverging yet.
(TyVarUnifiedDiverging::Yes, TyVarUnifiedDiverging::Yes) => {
Ok(TyVarUnifiedDiverging::Yes)
}
}
}
}

impl TyVarUnifiedDiverging {
#[inline]
fn from(diverging: bool, origin: TypeVariableOriginKind) -> Self {
if diverging {
return TyVarUnifiedDiverging::Yes;
}

// FIXME: Is it a complete list? Probably not.
match origin {
TypeVariableOriginKind::MiscVariable | TypeVariableOriginKind::LatticeVariable => {
TyVarUnifiedDiverging::Maybe
}
_ => TyVarUnifiedDiverging::No,
}
}
}
2 changes: 1 addition & 1 deletion compiler/rustc_infer/src/infer/undo_log.rs
Original file line number Diff line number Diff line change
@@ -45,7 +45,7 @@ impl_from! {
TypeVariables(type_variable::UndoLog<'tcx>),

TypeVariables(sv::UndoLog<ut::Delegate<type_variable::TyVidEqKey<'tcx>>>),
TypeVariables(sv::UndoLog<ut::Delegate<ty::TyVid>>),
TypeVariables(sv::UndoLog<ut::Delegate<type_variable::TyVidSubKey>>),
TypeVariables(sv::UndoLog<type_variable::Delegate>),
TypeVariables(type_variable::Instantiate),

6 changes: 5 additions & 1 deletion compiler/rustc_typeck/src/check/_match.rs
Original file line number Diff line number Diff line change
@@ -555,7 +555,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
if let Some(m) = contains_ref_bindings {
self.check_expr_with_needs(scrut, Needs::maybe_mut_place(m))
} else if no_arms {
self.check_expr(scrut)
// The hint for never type is a little hacky, but it will make
// `match never {}` work even without `never_type_fallback`.
// We can remove it once the feature `never_type_fallback` gets
// stabilized.
self.check_expr_with_hint(scrut, self.tcx.types.never)
} else {
// ...but otherwise we want to use any supertype of the
// scrutinee. This is sort of a workaround, see note (*) in
13 changes: 9 additions & 4 deletions compiler/rustc_typeck/src/check/coercion.rs
Original file line number Diff line number Diff line change
@@ -1022,8 +1022,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {

// First try to coerce the new expression to the type of the previous ones,
// but only if the new expression has no coercion already applied to it.
let mut first_error = None;
if !self.typeck_results.borrow().adjustments().contains_key(new.hir_id) {
let first_try = match self.typeck_results.borrow().expr_adjustments(new) {
&[] | &[Adjustment { kind: Adjust::NeverToAny, .. }] => true,
_ => false,
};
let first_error = if first_try {
let result = self.commit_if_ok(|_| coerce.coerce(new_ty, prev_ty));
match result {
Ok(ok) => {
@@ -1035,9 +1038,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
);
return Ok(target);
}
Err(e) => first_error = Some(e),
Err(e) => Some(e),
}
}
} else {
None
};

// Then try to coerce the previous expressions to the type of the new one.
// This requires ensuring there are no coercions applied to *any* of the
58 changes: 38 additions & 20 deletions compiler/rustc_typeck/src/check/expr.rs
Original file line number Diff line number Diff line change
@@ -68,25 +68,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
extend_err: impl Fn(&mut DiagnosticBuilder<'_>),
) -> Ty<'tcx> {
let expected_ty = expected.to_option(&self).unwrap_or(self.tcx.types.bool);
let mut ty = self.check_expr_with_expectation(expr, expected);

// While we don't allow *arbitrary* coercions here, we *do* allow
// coercions from ! to `expected`.
if ty.is_never() {
assert!(
!self.typeck_results.borrow().adjustments().contains_key(expr.hir_id),
"expression with never type wound up being adjusted"
);
let adj_ty = self.next_diverging_ty_var(TypeVariableOrigin {
kind: TypeVariableOriginKind::AdjustmentType,
span: expr.span,
});
self.apply_adjustments(
expr,
vec![Adjustment { kind: Adjust::NeverToAny, target: adj_ty }],
);
ty = adj_ty;
}
let ty = self.check_expr_with_expectation(expr, expected);

if let Some(mut err) = self.demand_suptype_diag(expr.span, expected_ty, ty) {
let expr = expr.peel_drop_temps();
@@ -216,7 +198,43 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
debug!("type of {} is...", self.tcx.hir().node_to_string(expr.hir_id));
debug!("... {:?}, expected is {:?}", ty, expected);

ty
// Convert the never type to a diverging type variable.
// Overall it helps to improve the consistency. We expect that we can
// have the same behaviour for `return.foo()` and `{ return }.foo()`.
if ty.is_never() {
assert!(
!self.typeck_results.borrow().adjustments().contains_key(expr.hir_id),
"expression with never type wound up being adjusted"
);

let expected_ty = match expected {
ExpectHasType(target_ty) => Some(target_ty),
ExpectCastableToType(target_ty) => Some(target_ty),
_ => None,
};

// Mirco-optimization: No need to create a diverging type variable
// if the target type is known.
let target_ty = expected_ty
.map(|target_ty| self.infcx.shallow_resolve(target_ty))
.filter(|target_ty| !target_ty.is_ty_var())
.unwrap_or_else(|| {
self.next_diverging_ty_var(TypeVariableOrigin {
kind: TypeVariableOriginKind::AdjustmentType,
span: expr.span,
})
});
if !target_ty.is_never() {
self.apply_adjustments(
expr,
vec![Adjustment { kind: Adjust::NeverToAny, target: target_ty }],
);
}

target_ty
} else {
ty
}
}

fn check_expr_kind(
47 changes: 37 additions & 10 deletions compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs
Original file line number Diff line number Diff line change
@@ -1567,17 +1567,44 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
/// Numeric inference variables may be left unresolved.
pub fn structurally_resolved_type(&self, sp: Span, ty: Ty<'tcx>) -> Ty<'tcx> {
let ty = self.resolve_vars_with_obligations(ty);
if !ty.is_ty_var() {
ty
if let ty::Infer(ty::TyVar(vid)) = ty.kind() {
// If we get a type variable here, we may not want to issue "type
// annotations needed". For example, the code can be something like
// `panic!().foo()` or `{ return }.foo()`.
//
// However, we must issue the error message if we found the type
// variable is related to some non-auxiliary non-diverging ones.
//
// We'll issue the error message for this
// ```
// let a = return;
// { if true { a } else { return } }.foo();
// ```
// but we won't for this
// ```
// let a: ! = return;
// { if true { a } else { return } }.foo();
// ```

let new_ty = if self.infcx.probe_ty_diverging(*vid) {
if self.tcx.features().never_type_fallback {
self.tcx.types.never
} else {
self.tcx.types.unit
}
} else {
if !self.is_tainted_by_errors() {
self.emit_inference_failure_err((**self).body_id, sp, ty.into(), vec![], E0282)
.note("type must be known at this point")
.emit();
}
self.tcx.ty_error()
};

self.demand_suptype(sp, new_ty, ty);
new_ty
} else {
if !self.is_tainted_by_errors() {
self.emit_inference_failure_err((**self).body_id, sp, ty.into(), vec![], E0282)
.note("type must be known at this point")
.emit();
}
let err = self.tcx.ty_error();
self.demand_suptype(sp, err, ty);
err
ty
}
}

8 changes: 5 additions & 3 deletions src/test/mir-opt/inline/inline_diverging.f.Inline.diff
Original file line number Diff line number Diff line change
@@ -4,18 +4,20 @@
fn f() -> () {
let mut _0: (); // return place in scope 0 at $DIR/inline-diverging.rs:7:12: 7:12
let mut _1: !; // in scope 0 at $DIR/inline-diverging.rs:7:12: 9:2
let _2: !; // in scope 0 at $DIR/inline-diverging.rs:8:5: 8:12
+ let mut _3: !; // in scope 0 at $DIR/inline-diverging.rs:8:5: 8:12
let _2: (); // in scope 0 at $DIR/inline-diverging.rs:8:5: 8:12
let mut _3: !; // in scope 0 at $DIR/inline-diverging.rs:8:5: 8:12
+ let mut _4: !; // in scope 0 at $DIR/inline-diverging.rs:8:5: 8:12
+ scope 1 (inlined sleep) { // at $DIR/inline-diverging.rs:8:5: 8:12
+ }

bb0: {
StorageLive(_2); // scope 0 at $DIR/inline-diverging.rs:8:5: 8:12
StorageLive(_3); // scope 0 at $DIR/inline-diverging.rs:8:5: 8:12
- sleep(); // scope 0 at $DIR/inline-diverging.rs:8:5: 8:12
- // mir::Constant
- // + span: $DIR/inline-diverging.rs:8:5: 8:10
- // + literal: Const { ty: fn() -> ! {sleep}, val: Value(Scalar(<ZST>)) }
+ StorageLive(_3); // scope 0 at $DIR/inline-diverging.rs:8:5: 8:12
+ StorageLive(_4); // scope 0 at $DIR/inline-diverging.rs:8:5: 8:12
+ goto -> bb1; // scope 0 at $DIR/inline-diverging.rs:8:5: 8:12
+ }
+
8 changes: 5 additions & 3 deletions src/test/mir-opt/inline/inline_diverging.g.Inline.diff
Original file line number Diff line number Diff line change
@@ -8,8 +8,9 @@
let mut _3: i32; // in scope 0 at $DIR/inline-diverging.rs:13:8: 13:9
let mut _4: i32; // in scope 0 at $DIR/inline-diverging.rs:14:9: 14:10
let mut _5: !; // in scope 0 at $DIR/inline-diverging.rs:15:12: 17:6
let _6: !; // in scope 0 at $DIR/inline-diverging.rs:16:9: 16:16
+ let mut _7: !; // in scope 0 at $DIR/inline-diverging.rs:16:9: 16:16
let _6: (); // in scope 0 at $DIR/inline-diverging.rs:16:9: 16:16
let mut _7: !; // in scope 0 at $DIR/inline-diverging.rs:16:9: 16:16
+ let mut _8: !; // in scope 0 at $DIR/inline-diverging.rs:16:9: 16:16
+ scope 1 (inlined panic) { // at $DIR/inline-diverging.rs:16:9: 16:16
+ }

@@ -33,8 +34,9 @@

bb2: {
StorageLive(_6); // scope 0 at $DIR/inline-diverging.rs:16:9: 16:16
StorageLive(_7); // scope 0 at $DIR/inline-diverging.rs:16:9: 16:16
- panic(); // scope 0 at $DIR/inline-diverging.rs:16:9: 16:16
+ StorageLive(_7); // scope 0 at $DIR/inline-diverging.rs:16:9: 16:16
+ StorageLive(_8); // scope 0 at $DIR/inline-diverging.rs:16:9: 16:16
+ begin_panic::<&str>(const "explicit panic"); // scope 1 at $DIR/inline-diverging.rs:16:9: 16:16
// mir::Constant
- // + span: $DIR/inline-diverging.rs:16:9: 16:14
Loading