Skip to content

WIP: Check uninhabitedness through the trait solver #116247

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

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
@@ -517,6 +517,7 @@ fn trait_predicate_kind<'tcx>(
| ty::PredicateKind::ClosureKind(..)
| ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(..))
| ty::PredicateKind::ConstEquate(..)
| ty::PredicateKind::Uninhabited(..)
| ty::PredicateKind::Ambiguous => None,
}
}
1 change: 1 addition & 0 deletions compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs
Original file line number Diff line number Diff line change
@@ -657,6 +657,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// code is looking for a self type of an unresolved
// inference variable.
| ty::PredicateKind::ClosureKind(..)
| ty::PredicateKind::Uninhabited(..)
| ty::PredicateKind::Ambiguous
=> None,
},
2 changes: 1 addition & 1 deletion compiler/rustc_infer/src/infer/mod.rs
Original file line number Diff line number Diff line change
@@ -1794,7 +1794,7 @@ impl<'tcx> TyOrConstInferVar<'tcx> {

/// Tries to extract an inference variable from a type, returns `None`
/// for types other than `ty::Infer(_)` (or `InferTy::Fresh*`).
fn maybe_from_ty(ty: Ty<'tcx>) -> Option<Self> {
pub fn maybe_from_ty(ty: Ty<'tcx>) -> Option<Self> {
match *ty.kind() {
ty::Infer(ty::TyVar(v)) => Some(TyOrConstInferVar::Ty(v)),
ty::Infer(ty::IntVar(v)) => Some(TyOrConstInferVar::TyInt(v)),
3 changes: 3 additions & 0 deletions compiler/rustc_infer/src/traits/util.rs
Original file line number Diff line number Diff line change
@@ -380,6 +380,9 @@ impl<'tcx, O: Elaboratable<'tcx>> Elaborator<'tcx, O> {
ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(..)) => {
// Nothing to elaborate
}
ty::PredicateKind::Uninhabited(..) => {
// Nothing to elaborate
}
}
}
}
33 changes: 10 additions & 23 deletions compiler/rustc_lint/src/builtin.rs
Original file line number Diff line number Diff line change
@@ -2511,18 +2511,11 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
// And now, enums.
let span = cx.tcx.def_span(adt_def.did());
let mut potential_variants = adt_def.variants().iter().filter_map(|variant| {
let definitely_inhabited = match variant
.inhabited_predicate(cx.tcx, *adt_def)
.instantiate(cx.tcx, args)
.apply_any_module(cx.tcx, cx.param_env)
{
if variant.is_privately_uninhabited(cx.tcx, *adt_def, args, cx.param_env) {
// Entirely skip uninhabited variants.
Some(false) => return None,
// Forward the others, but remember which ones are definitely inhabited.
Some(true) => true,
None => false,
};
Some((variant, definitely_inhabited))
return None;
}
Some(variant)
});
let Some(first_variant) = potential_variants.next() else {
return Some(
@@ -2531,12 +2524,12 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
);
};
// So we have at least one potentially inhabited variant. Might we have two?
let Some(second_variant) = potential_variants.next() else {
let Some(_) = potential_variants.next() else {
// There is only one potentially inhabited variant. So we can recursively check that variant!
return variant_find_init_error(
cx,
ty,
&first_variant.0,
&first_variant,
args,
"field of the only potentially inhabited enum variant",
init,
@@ -2547,16 +2540,9 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
// then we have a tag and hence leaving this uninit is definitely disallowed.
// (Leaving it zeroed could be okay, depending on which variant is encoded as zero tag.)
if init == InitKind::Uninit {
let definitely_inhabited = (first_variant.1 as usize)
+ (second_variant.1 as usize)
+ potential_variants
.filter(|(_variant, definitely_inhabited)| *definitely_inhabited)
.count();
if definitely_inhabited > 1 {
return Some(InitError::from(
"enums with multiple inhabited variants have to be initialized to a variant",
).spanned(span));
}
return Some(InitError::from(
"enums with multiple inhabited variants have to be initialized to a variant",
).spanned(span));
}
// We couldn't find anything wrong here.
None
@@ -2599,6 +2585,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
label: expr.span,
sub,
tcx: cx.tcx,
param_env: cx.param_env,
},
);
}
7 changes: 3 additions & 4 deletions compiler/rustc_lint/src/lints.rs
Original file line number Diff line number Diff line change
@@ -10,9 +10,7 @@ use rustc_errors::{
};
use rustc_hir::def_id::DefId;
use rustc_macros::{LintDiagnostic, Subdiagnostic};
use rustc_middle::ty::{
inhabitedness::InhabitedPredicate, Clause, PolyExistentialTraitRef, Ty, TyCtxt,
};
use rustc_middle::ty::{Clause, ParamEnv, PolyExistentialTraitRef, Ty, TyCtxt};
use rustc_session::parse::ParseSess;
use rustc_span::{edition::Edition, sym, symbol::Ident, Span, Symbol};

@@ -432,6 +430,7 @@ pub struct BuiltinUnpermittedTypeInit<'a> {
pub label: Span,
pub sub: BuiltinUnpermittedTypeInitSub,
pub tcx: TyCtxt<'a>,
pub param_env: ParamEnv<'a>,
}

impl<'a> DecorateLint<'a, ()> for BuiltinUnpermittedTypeInit<'_> {
@@ -441,7 +440,7 @@ impl<'a> DecorateLint<'a, ()> for BuiltinUnpermittedTypeInit<'_> {
) -> &'b mut rustc_errors::DiagnosticBuilder<'a, ()> {
diag.set_arg("ty", self.ty);
diag.span_label(self.label, fluent::lint_builtin_unpermitted_type_init_label);
if let InhabitedPredicate::True = self.ty.inhabited_predicate(self.tcx) {
if !self.ty.is_privately_uninhabited(self.tcx, self.param_env) {
// Only suggest late `MaybeUninit::assume_init` initialization if the type is inhabited.
diag.span_label(
self.label,
4 changes: 2 additions & 2 deletions compiler/rustc_lint/src/unused.rs
Original file line number Diff line number Diff line change
@@ -269,10 +269,10 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults {
span: Span,
) -> Option<MustUsePath> {
if ty.is_unit()
|| !ty.is_inhabited_from(
|| ty.is_uninhabited_from(
cx.tcx,
cx.tcx.parent_module(expr.hir_id).to_def_id(),
cx.param_env,
cx.tcx.parent_module(expr.hir_id).to_def_id(),
)
{
return Some(MustUsePath::Suppressed);
1 change: 0 additions & 1 deletion compiler/rustc_middle/src/query/erase.rs
Original file line number Diff line number Diff line change
@@ -332,7 +332,6 @@ tcx_lifetime! {
rustc_middle::ty::FnSig,
rustc_middle::ty::GenericArg,
rustc_middle::ty::GenericPredicates,
rustc_middle::ty::inhabitedness::InhabitedPredicate,
rustc_middle::ty::Instance,
rustc_middle::ty::InstanceDef,
rustc_middle::ty::layout::FnAbiError,
8 changes: 8 additions & 0 deletions compiler/rustc_middle/src/query/keys.rs
Original file line number Diff line number Diff line change
@@ -487,6 +487,14 @@ impl<'tcx, T: Key> Key for ty::ParamEnvAnd<'tcx, T> {
}
}

impl<'tcx> Key for ty::ParamEnvAnd<'tcx, (Ty<'tcx>, Option<DefId>)> {
type CacheSelector = DefaultCacheSelector<Self>;

fn default_span(&self, tcx: TyCtxt<'_>) -> Span {
self.value.0.default_span(tcx)
}
}

impl Key for Symbol {
type CacheSelector = DefaultCacheSelector<Self>;

17 changes: 8 additions & 9 deletions compiler/rustc_middle/src/query/mod.rs
Original file line number Diff line number Diff line change
@@ -1362,6 +1362,14 @@ rustc_queries! {
query has_significant_drop_raw(env: ty::ParamEnvAnd<'tcx, Ty<'tcx>>) -> bool {
desc { "computing whether `{}` has a significant drop", env.value }
}
/// Query backing `Ty::is_uninhabited*`.
query type_is_uninhabited_from_raw(key: ty::ParamEnvAnd<'tcx, (Ty<'tcx>, Option<DefId>)>) -> bool {
desc { |tcx|
"computing whether `{}` is uninhabited{}",
key.value.0,
key.value.1.map_or(String::new(), |module| tcx.def_path_str(module))
}
}

/// Query backing `Ty::is_structural_eq_shallow`.
///
@@ -1712,15 +1720,6 @@ rustc_queries! {
feedable
}

query inhabited_predicate_adt(key: DefId) -> ty::inhabitedness::InhabitedPredicate<'tcx> {
desc { "computing the uninhabited predicate of `{:?}`", key }
}

/// Do not call this query directly: invoke `Ty::inhabited_predicate` instead.
query inhabited_predicate_type(key: Ty<'tcx>) -> ty::inhabitedness::InhabitedPredicate<'tcx> {
desc { "computing the uninhabited predicate of `{}`", key }
}

query dep_kind(_: CrateNum) -> CrateDepKind {
eval_always
desc { "fetching what a dependency looks like" }
2 changes: 1 addition & 1 deletion compiler/rustc_middle/src/traits/select.rs
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@ pub type SelectionCache<'tcx> = Cache<
pub type EvaluationCache<'tcx> = Cache<
// See above: this cache does not use `ParamEnvAnd` in its keys due to sometimes incorrectly
// caching with the wrong `ParamEnv`.
(ty::ParamEnv<'tcx>, ty::PolyTraitPredicate<'tcx>),
(ty::ParamEnv<'tcx>, ty::Predicate<'tcx>),
EvaluationResult,
>;

3 changes: 3 additions & 0 deletions compiler/rustc_middle/src/ty/flags.rs
Original file line number Diff line number Diff line change
@@ -294,6 +294,9 @@ impl FlagComputation {
self.add_term(t1);
self.add_term(t2);
}
ty::PredicateKind::Uninhabited(ty, _) => {
self.add_ty(ty);
}
}
}

146 changes: 146 additions & 0 deletions compiler/rustc_middle/src/ty/inhabitedness.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
use crate::ty::context::TyCtxt;
use crate::ty::{self, DefId, Ty, VariantDef, Visibility};

use rustc_type_ir::sty::TyKind::*;

impl<'tcx> VariantDef {
fn is_uninhabited_module(
&self,
tcx: TyCtxt<'tcx>,
adt: ty::AdtDef<'_>,
args: ty::GenericArgsRef<'tcx>,
param_env: ty::ParamEnv<'tcx>,
module: Option<DefId>,
) -> bool {
debug_assert!(!adt.is_union());
if self.is_field_list_non_exhaustive() && !self.def_id.is_local() {
// Non-exhaustive variants from other crates are always considered inhabited.
return false;
}
self.fields.iter().any(|field| {
let fty = tcx.type_of(field.did).instantiate(tcx, args);
if adt.is_struct()
&& let Visibility::Restricted(from) = field.vis
&& let Some(module) = module
&& !tcx.is_descendant_of(module, from)
{
// The field may be uninhabited, this is not visible from `module`, so we return.
return false;
}
fty.is_uninhabited_module(tcx, param_env, module)
})
}

pub fn is_uninhabited_from(
&self,
tcx: TyCtxt<'tcx>,
adt: ty::AdtDef<'_>,
args: ty::GenericArgsRef<'tcx>,
param_env: ty::ParamEnv<'tcx>,
module: DefId,
) -> bool {
self.is_uninhabited_module(tcx, adt, args, param_env, Some(module))
}

pub fn is_privately_uninhabited(
&self,
tcx: TyCtxt<'tcx>,
adt: ty::AdtDef<'_>,
args: ty::GenericArgsRef<'tcx>,
param_env: ty::ParamEnv<'tcx>,
) -> bool {
self.is_uninhabited_module(tcx, adt, args, param_env, None)
}
}

impl<'tcx> Ty<'tcx> {
pub fn is_trivially_uninhabited(self) -> Option<bool> {
match self.kind() {
// For now, unions are always considered inhabited
Adt(adt, _) if adt.is_union() => Some(false),
// Non-exhaustive ADTs from other crates are always considered inhabited
Adt(adt, _) if adt.is_variant_list_non_exhaustive() && !adt.did().is_local() => {
Some(false)
}
Never => Some(true),
Tuple(tys) if tys.is_empty() => Some(false),
// use a query for more complex cases
Param(_) | Alias(..) | Adt(..) | Array(..) | Tuple(_) => None,
// references and other types are inhabited
_ => Some(false),
}
}

fn is_uninhabited_module(
self,
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
module: Option<DefId>,
) -> bool {
if let Some(trivial) = self.is_trivially_uninhabited() {
trivial
} else {
tcx.type_is_uninhabited_from_raw(param_env.and((self, module)))
}
}

/// Checks whether a type is visibly uninhabited from a particular module.
///
/// # Example
/// ```
/// #![feature(never_type)]
/// # fn main() {}
/// enum Void {}
/// mod a {
/// pub mod b {
/// pub struct SecretlyUninhabited {
/// _priv: !,
/// }
/// }
/// }
///
/// mod c {
/// use super::Void;
/// pub struct AlsoSecretlyUninhabited {
/// _priv: Void,
/// }
/// mod d {
/// }
/// }
///
/// struct Foo {
/// x: a::b::SecretlyUninhabited,
/// y: c::AlsoSecretlyUninhabited,
/// }
/// ```
/// In this code, the type `Foo` will only be visibly uninhabited inside the
/// modules b, c and d. This effects pattern-matching on `Foo` or types that
/// contain `Foo`.
///
/// # Example
/// ```ignore (illustrative)
/// let foo_result: Result<T, Foo> = ... ;
/// let Ok(t) = foo_result;
/// ```
/// This code should only compile in modules where the uninhabitedness of Foo is
/// visible.
#[inline]
pub fn is_uninhabited_from(
self,
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
module: DefId,
) -> bool {
self.is_uninhabited_module(tcx, param_env, Some(module))
}

/// Returns true if the type is uninhabited without regard to visibility
#[inline]
pub fn is_privately_uninhabited(
self,
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
) -> bool {
self.is_uninhabited_module(tcx, param_env, None)
}
}
Loading