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

Diverging tyvars #85558

Closed
wants to merge 2 commits into from
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
7 changes: 7 additions & 0 deletions compiler/rustc_infer/src/infer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
108 changes: 100 additions & 8 deletions compiler/rustc_infer/src/infer/type_variable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>),
}

Expand All @@ -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)
}
}
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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
Expand Up @@ -46,7 +46,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),

Expand Down
6 changes: 5 additions & 1 deletion compiler/rustc_typeck/src/check/_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 9 additions & 4 deletions compiler/rustc_typeck/src/check/coercion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -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
Expand Down
58 changes: 38 additions & 20 deletions compiler/rustc_typeck/src/check/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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(
Expand Down
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
Expand Up @@ -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
}
}

Expand Down
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
Expand Up @@ -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
+ }
+
Expand Down
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
Expand Up @@ -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
+ }

Expand All @@ -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
Expand Down
Loading