Rollup merge of #138001 - meithecatte:privately-uninhabited, r=Nadrieril

mir_build: consider privacy when checking for irrefutable patterns

This PR fixes #137999.

Note that, since this makes the compiler reject code that was previously accepted, it will probably need a crater run.

I include a commit that factors out a common code pattern into a helper function, purely because the fact that this was repeated all over the place was bothering me. Let me know if I should split that into a separate PR instead.
This commit is contained in:
Matthias Krüger 2025-03-19 16:52:54 +01:00 committed by GitHub
commit 2ab69b898a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 121 additions and 50 deletions

View file

@ -43,7 +43,6 @@ use rustc_middle::ty::error::TypeError;
use rustc_middle::ty::{self, Ty, TyCtxt, TypeAndMut, TypeVisitableExt, VariantDef, elaborate};
use rustc_middle::{bug, span_bug};
use rustc_session::lint;
use rustc_span::def_id::LOCAL_CRATE;
use rustc_span::{DUMMY_SP, Span, sym};
use rustc_trait_selection::infer::InferCtxtExt;
use tracing::{debug, instrument};
@ -805,7 +804,7 @@ impl<'a, 'tcx> CastCheck<'tcx> {
_ => return Err(CastError::NonScalar),
};
if let ty::Adt(adt_def, _) = *self.expr_ty.kind()
&& adt_def.did().krate != LOCAL_CRATE
&& !adt_def.did().is_local()
&& adt_def.variants().iter().any(VariantDef::is_field_list_non_exhaustive)
{
return Err(CastError::ForeignNonExhaustiveAdt);

View file

@ -1977,7 +1977,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// Prohibit struct expressions when non-exhaustive flag is set.
let adt = adt_ty.ty_adt_def().expect("`check_struct_path` returned non-ADT type");
if !adt.did().is_local() && variant.is_field_list_non_exhaustive() {
if variant.field_list_has_applicable_non_exhaustive() {
self.dcx()
.emit_err(StructExprNonExhaustive { span: expr.span, what: adt.variant_descr() });
}

View file

@ -20,7 +20,7 @@ use rustc_middle::hir::place::ProjectionKind;
pub use rustc_middle::hir::place::{Place, PlaceBase, PlaceWithHirId, Projection};
use rustc_middle::mir::FakeReadCause;
use rustc_middle::ty::{
self, AdtKind, BorrowKind, Ty, TyCtxt, TypeFoldable, TypeVisitableExt as _, adjustment,
self, BorrowKind, Ty, TyCtxt, TypeFoldable, TypeVisitableExt as _, adjustment,
};
use rustc_middle::{bug, span_bug};
use rustc_span::{ErrorGuaranteed, Span};
@ -1899,14 +1899,7 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
// to assume that more cases will be added to the variant in the future. This mean
// that we should handle non-exhaustive SingleVariant the same way we would handle
// a MultiVariant.
// If the variant is not local it must be defined in another crate.
let is_non_exhaustive = match def.adt_kind() {
AdtKind::Struct | AdtKind::Union => {
def.non_enum_variant().is_field_list_non_exhaustive()
}
AdtKind::Enum => def.is_variant_list_non_exhaustive(),
};
def.variants().len() > 1 || (!def.did().is_local() && is_non_exhaustive)
def.variants().len() > 1 || def.variant_list_has_applicable_non_exhaustive()
} else {
false
}

View file

@ -1724,7 +1724,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
};
// Require `..` if struct has non_exhaustive attribute.
let non_exhaustive = variant.is_field_list_non_exhaustive() && !adt.did().is_local();
let non_exhaustive = variant.field_list_has_applicable_non_exhaustive();
if non_exhaustive && !has_rest_pat {
self.error_foreign_non_exhaustive_spat(pat, adt.variant_descr(), fields.is_empty());
}

View file

@ -1193,9 +1193,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
};
}
let is_non_exhaustive =
def.non_enum_variant().is_field_list_non_exhaustive();
if is_non_exhaustive && !def.did().is_local() {
if def.non_enum_variant().field_list_has_applicable_non_exhaustive() {
return FfiUnsafe {
ty,
reason: if def.is_struct() {
@ -1248,14 +1246,12 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
};
}
use improper_ctypes::{
check_non_exhaustive_variant, non_local_and_non_exhaustive,
};
use improper_ctypes::check_non_exhaustive_variant;
let non_local_def = non_local_and_non_exhaustive(def);
let non_exhaustive = def.variant_list_has_applicable_non_exhaustive();
// Check the contained variants.
let ret = def.variants().iter().try_for_each(|variant| {
check_non_exhaustive_variant(non_local_def, variant)
check_non_exhaustive_variant(non_exhaustive, variant)
.map_break(|reason| FfiUnsafe { ty, reason, help: None })?;
match self.check_variant_for_ffi(acc, ty, def, variant, args) {

View file

@ -15,13 +15,13 @@ use crate::fluent_generated as fluent;
/// so we don't need the lint to account for it.
/// e.g. going from enum Foo { A, B, C } to enum Foo { A, B, C, D(u32) }.
pub(crate) fn check_non_exhaustive_variant(
non_local_def: bool,
non_exhaustive_variant_list: bool,
variant: &ty::VariantDef,
) -> ControlFlow<DiagMessage, ()> {
// non_exhaustive suggests it is possible that someone might break ABI
// see: https://github.com/rust-lang/rust/issues/44109#issuecomment-537583344
// so warn on complex enums being used outside their crate
if non_local_def {
if non_exhaustive_variant_list {
// which is why we only warn about really_tagged_union reprs from https://rust.tf/rfc2195
// with an enum like `#[repr(u8)] enum Enum { A(DataA), B(DataB), }`
// but exempt enums with unit ctors like C's (e.g. from rust-bindgen)
@ -30,8 +30,7 @@ pub(crate) fn check_non_exhaustive_variant(
}
}
let non_exhaustive_variant_fields = variant.is_field_list_non_exhaustive();
if non_exhaustive_variant_fields && !variant.def_id.is_local() {
if variant.field_list_has_applicable_non_exhaustive() {
return ControlFlow::Break(fluent::lint_improper_ctypes_non_exhaustive_variant);
}
@ -42,10 +41,3 @@ fn variant_has_complex_ctor(variant: &ty::VariantDef) -> bool {
// CtorKind::Const means a "unit" ctor
!matches!(variant.ctor_kind(), Some(CtorKind::Const))
}
// non_exhaustive suggests it is possible that someone might break ABI
// see: https://github.com/rust-lang/rust/issues/44109#issuecomment-537583344
// so warn on complex enums being used outside their crate
pub(crate) fn non_local_and_non_exhaustive(def: ty::AdtDef<'_>) -> bool {
def.is_variant_list_non_exhaustive() && !def.did().is_local()
}

View file

@ -327,11 +327,22 @@ impl<'tcx> AdtDef<'tcx> {
}
/// Returns `true` if the variant list of this ADT is `#[non_exhaustive]`.
///
/// Note that this function will return `true` even if the ADT has been
/// defined in the crate currently being compiled. If that's not what you
/// want, see [`Self::variant_list_has_applicable_non_exhaustive`].
#[inline]
pub fn is_variant_list_non_exhaustive(self) -> bool {
self.flags().contains(AdtFlags::IS_VARIANT_LIST_NON_EXHAUSTIVE)
}
/// Returns `true` if the variant list of this ADT is `#[non_exhaustive]`
/// and has been defined in another crate.
#[inline]
pub fn variant_list_has_applicable_non_exhaustive(self) -> bool {
self.is_variant_list_non_exhaustive() && !self.did().is_local()
}
/// Returns the kind of the ADT.
#[inline]
pub fn adt_kind(self) -> AdtKind {

View file

@ -107,7 +107,7 @@ impl<'tcx> Ty<'tcx> {
// For now, unions are always considered inhabited
Adt(adt, _) if adt.is_union() => InhabitedPredicate::True,
// Non-exhaustive ADTs from other crates are always considered inhabited
Adt(adt, _) if adt.is_variant_list_non_exhaustive() && !adt.did().is_local() => {
Adt(adt, _) if adt.variant_list_has_applicable_non_exhaustive() => {
InhabitedPredicate::True
}
Never => InhabitedPredicate::False,

View file

@ -1208,12 +1208,23 @@ impl VariantDef {
}
}
/// Is this field list non-exhaustive?
/// Returns `true` if the field list of this variant is `#[non_exhaustive]`.
///
/// Note that this function will return `true` even if the type has been
/// defined in the crate currently being compiled. If that's not what you
/// want, see [`Self::field_list_has_applicable_non_exhaustive`].
#[inline]
pub fn is_field_list_non_exhaustive(&self) -> bool {
self.flags.intersects(VariantFlags::IS_FIELD_LIST_NON_EXHAUSTIVE)
}
/// Returns `true` if the field list of this variant is `#[non_exhaustive]`
/// and the type has been defined in another crate.
#[inline]
pub fn field_list_has_applicable_non_exhaustive(&self) -> bool {
self.is_field_list_non_exhaustive() && !self.def_id.is_local()
}
/// Computes the `Ident` of this variant by looking up the `Span`
pub fn ident(&self, tcx: TyCtxt<'_>) -> Ident {
Ident::new(self.name, tcx.def_ident_span(self.def_id).unwrap())

View file

@ -273,12 +273,12 @@ impl<'tcx> MatchPairTree<'tcx> {
let irrefutable = adt_def.variants().iter_enumerated().all(|(i, v)| {
i == variant_index
|| !v
.inhabited_predicate(cx.tcx, adt_def)
.instantiate(cx.tcx, args)
.apply_ignore_module(cx.tcx, cx.infcx.typing_env(cx.param_env))
}) && (adt_def.did().is_local()
|| !adt_def.is_variant_list_non_exhaustive());
|| !v.inhabited_predicate(cx.tcx, adt_def).instantiate(cx.tcx, args).apply(
cx.tcx,
cx.infcx.typing_env(cx.param_env),
cx.def_id.into(),
)
}) && !adt_def.variant_list_has_applicable_non_exhaustive();
if irrefutable { None } else { Some(TestCase::Variant { adt_def, variant_index }) }
}

View file

@ -613,9 +613,9 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for NonExhaustivePatternsTypeNo
diag.span_note(span, fluent::mir_build_def_note);
}
let is_variant_list_non_exhaustive = matches!(self.ty.kind(),
ty::Adt(def, _) if def.is_variant_list_non_exhaustive() && !def.did().is_local());
if is_variant_list_non_exhaustive {
let is_non_exhaustive = matches!(self.ty.kind(),
ty::Adt(def, _) if def.variant_list_has_applicable_non_exhaustive());
if is_non_exhaustive {
diag.note(fluent::mir_build_non_exhaustive_type_note);
} else {
diag.note(fluent::mir_build_type_note);

View file

@ -150,9 +150,7 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> {
/// Returns whether the given type is an enum from another crate declared `#[non_exhaustive]`.
pub fn is_foreign_non_exhaustive_enum(&self, ty: RevealedTy<'tcx>) -> bool {
match ty.kind() {
ty::Adt(def, ..) => {
def.is_enum() && def.is_variant_list_non_exhaustive() && !def.did().is_local()
}
ty::Adt(def, ..) => def.variant_list_has_applicable_non_exhaustive(),
_ => false,
}
}

View file

@ -134,7 +134,7 @@ impl<'tcx> LateLintPass<'tcx> for Default {
&& let ty::Adt(adt, args) = *binding_type.kind()
&& adt.is_struct()
&& let variant = adt.non_enum_variant()
&& (adt.did().is_local() || !variant.is_field_list_non_exhaustive())
&& !variant.field_list_has_applicable_non_exhaustive()
&& let module_did = cx.tcx.parent_module(stmt.hir_id)
&& variant
.fields

View file

@ -54,8 +54,9 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessUpdate {
if let ExprKind::Struct(_, fields, StructTailExpr::Base(base)) = expr.kind {
let ty = cx.typeck_results().expr_ty(expr);
if let ty::Adt(def, _) = ty.kind() {
if fields.len() == def.non_enum_variant().fields.len()
&& !def.variant(0_usize.into()).is_field_list_non_exhaustive()
let variant = def.non_enum_variant();
if fields.len() == variant.fields.len()
&& !variant.is_field_list_non_exhaustive()
{
span_lint(
cx,

View file

@ -51,7 +51,7 @@ impl LateLintPass<'_> for UnneededStructPattern {
let variant = cx.tcx.adt_def(enum_did).variant_with_id(did);
let has_only_fields_brackets = variant.ctor.is_some() && variant.fields.is_empty();
let non_exhaustive_activated = !variant.def_id.is_local() && variant.is_field_list_non_exhaustive();
let non_exhaustive_activated = variant.field_list_has_applicable_non_exhaustive();
if !has_only_fields_brackets || non_exhaustive_activated {
return;
}

View file

@ -0,0 +1,44 @@
//@ edition:2024
//@ check-fail
mod m {
enum Void {}
pub struct Internal {
_v: Void,
}
pub enum Test {
A(u32, u32),
B(Internal),
}
}
use m::Test;
pub fn f1(x: &mut Test) {
let r1: &mut u32 = match x {
Test::A(a, _) => a,
_ => todo!(),
};
let r2: &mut u32 = match x { //~ ERROR cannot use `*x` because it was mutably borrowed
Test::A(_, b) => b,
_ => todo!(),
};
let _ = *r1;
let _ = *r2;
}
pub fn f2(x: &mut Test) {
let r = &mut *x;
match x { //~ ERROR cannot use `*x` because it was mutably borrowed
Test::A(_, _) => {}
_ => {}
}
let _ = r;
}
fn main() {}

View file

@ -0,0 +1,26 @@
error[E0503]: cannot use `*x` because it was mutably borrowed
--> $DIR/privately-uninhabited-issue-137999.rs:25:30
|
LL | Test::A(a, _) => a,
| - `x.0` is borrowed here
...
LL | let r2: &mut u32 = match x {
| ^ use of borrowed `x.0`
...
LL | let _ = *r1;
| --- borrow later used here
error[E0503]: cannot use `*x` because it was mutably borrowed
--> $DIR/privately-uninhabited-issue-137999.rs:36:11
|
LL | let r = &mut *x;
| ------- `*x` is borrowed here
LL | match x {
| ^ use of borrowed `*x`
...
LL | let _ = r;
| - borrow later used here
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0503`.