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

WIP: Check uninhabitedness through the trait solver #116247

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Up @@ -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,
},
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_infer/src/infer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_infer/src/traits/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
}
Expand Down
33 changes: 10 additions & 23 deletions compiler/rustc_lint/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -2599,6 +2585,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
label: expr.span,
sub,
tcx: cx.tcx,
param_env: cx.param_env,
},
);
}
Expand Down
7 changes: 3 additions & 4 deletions compiler/rustc_lint/src/lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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<'_> {
Expand All @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_lint/src/unused.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 0 additions & 1 deletion compiler/rustc_middle/src/query/erase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 8 additions & 0 deletions compiler/rustc_middle/src/query/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>;

Expand Down
17 changes: 8 additions & 9 deletions compiler/rustc_middle/src/query/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
///
Expand Down Expand Up @@ -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" }
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_middle/src/traits/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
>;

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

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