Rollup merge of #155047 - jdonszelmann:lint-against-eq-typing-mode, r=lcnr

Always exhaustively match on typing mode

r? @lcnr

Unimplements Eq/PartialEq for TypingMode, adds TypingModeEqWrapper for the few cases where we need it (mainly in the query system), and adds a new rustc internal lint to detect cases where we non-exhaustively match on typing mode.
This commit is contained in:
Jonathan Brouwer
2026-04-10 15:33:10 +02:00
committed by GitHub
39 changed files with 465 additions and 78 deletions
@@ -1349,3 +1349,12 @@ impl<S: Stage> NoArgsAttributeParser<S> for RustcIntrinsicConstStableIndirectPar
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn)]);
const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcIntrinsicConstStableIndirect;
}
pub(crate) struct RustcExhaustiveParser;
impl<S: Stage> NoArgsAttributeParser<S> for RustcExhaustiveParser {
const PATH: &'static [Symbol] = &[sym::rustc_must_match_exhaustively];
const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Enum)]);
const CREATE: fn(Span) -> AttributeKind = AttributeKind::RustcMustMatchExhaustively;
}
@@ -297,6 +297,7 @@ mod late {
Single<WithoutArgs<RustcEffectiveVisibilityParser>>,
Single<WithoutArgs<RustcEiiForeignItemParser>>,
Single<WithoutArgs<RustcEvaluateWhereClausesParser>>,
Single<WithoutArgs<RustcExhaustiveParser>>,
Single<WithoutArgs<RustcHasIncoherentInherentImplsParser>>,
Single<WithoutArgs<RustcHiddenTypeOfOpaquesParser>>,
Single<WithoutArgs<RustcInheritOverflowChecksParser>>,
@@ -103,13 +103,10 @@ fn in_any_value_of_ty<'tcx>(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> bool {
// FIXME(#132279): Once we've got a typing mode which reveals opaque types using the HIR
// typeck results without causing query cycles, we should use this here instead of defining
// opaque types.
let typing_env = ty::TypingEnv {
typing_mode: ty::TypingMode::analysis_in_body(
cx.tcx,
cx.body.source.def_id().expect_local(),
),
param_env: cx.typing_env.param_env,
};
let typing_env = ty::TypingEnv::new(
cx.typing_env.param_env,
ty::TypingMode::analysis_in_body(cx.tcx, cx.body.source.def_id().expect_local()),
);
let (infcx, param_env) = cx.tcx.infer_ctxt().build_with_typing_env(typing_env);
let ocx = ObligationCtxt::new(&infcx);
let obligation = Obligation::new(
@@ -371,10 +371,20 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
// This shouldn't be used for statics, since statics are conceptually places,
// not values -- so what we do here could break pointer identity.
assert!(key.value.promoted.is_some() || !tcx.is_static(key.value.instance.def_id()));
// Const eval always happens in PostAnalysis mode . See the comment in
// `InterpCx::new` for more details.
debug_assert_eq!(key.typing_env.typing_mode, ty::TypingMode::PostAnalysis);
if cfg!(debug_assertions) {
match key.typing_env.typing_mode() {
ty::TypingMode::PostAnalysis => {}
ty::TypingMode::Coherence
| ty::TypingMode::Analysis { .. }
| ty::TypingMode::Borrowck { .. }
| ty::TypingMode::PostBorrowckAnalysis { .. } => {
bug!(
"Const eval should always happens in PostAnalysis mode. See the comment in `InterpCx::new` for more details."
)
}
}
// Make sure we format the instance even if we do not print it.
// This serves as a regression test against an ICE on printing.
// The next two lines concatenated contain some discussion:
@@ -236,9 +236,19 @@ pub(crate) fn eval_to_valtree<'tcx>(
typing_env: ty::TypingEnv<'tcx>,
cid: GlobalId<'tcx>,
) -> EvalToValTreeResult<'tcx> {
// Const eval always happens in PostAnalysis mode . See the comment in
// `InterpCx::new` for more details.
debug_assert_eq!(typing_env.typing_mode, ty::TypingMode::PostAnalysis);
if cfg!(debug_assertions) {
match typing_env.typing_mode() {
ty::TypingMode::PostAnalysis => {}
ty::TypingMode::Coherence
| ty::TypingMode::Analysis { .. }
| ty::TypingMode::Borrowck { .. }
| ty::TypingMode::PostBorrowckAnalysis { .. } => {
bug!(
"Const eval should always happens in PostAnalysis mode. See the comment in `InterpCx::new` for more details."
)
}
}
}
let const_alloc = tcx.eval_to_allocation_raw(typing_env.as_query_input(cid))?;
// FIXME Need to provide a span to `eval_to_valtree`
@@ -1,5 +1,3 @@
use std::debug_assert_matches;
use either::{Left, Right};
use rustc_abi::{Align, HasDataLayout, Size, TargetDataLayout};
use rustc_hir::def_id::DefId;
@@ -11,9 +9,10 @@
LayoutOfHelpers, TyAndLayout,
};
use rustc_middle::ty::{
self, GenericArgsRef, Ty, TyCtxt, TypeFoldable, TypeVisitableExt, TypingEnv, Variance,
self, GenericArgsRef, Ty, TyCtxt, TypeFoldable, TypeVisitableExt, TypingEnv, TypingMode,
Variance,
};
use rustc_middle::{mir, span_bug};
use rustc_middle::{bug, mir, span_bug};
use rustc_span::Span;
use rustc_target::callconv::FnAbi;
use tracing::{debug, trace};
@@ -243,7 +242,18 @@ pub fn new(
// opaque types. This is needed for trivial things like `size_of`, but also for using associated
// types that are not specified in the opaque type. We also use MIR bodies whose opaque types have
// already been revealed, so we'd be able to at least partially observe the hidden types anyways.
debug_assert_matches!(typing_env.typing_mode, ty::TypingMode::PostAnalysis);
if cfg!(debug_assertions) {
match typing_env.typing_mode() {
TypingMode::PostAnalysis => {}
TypingMode::Coherence
| TypingMode::Analysis { .. }
| TypingMode::Borrowck { .. }
| TypingMode::PostBorrowckAnalysis { .. } => {
bug!("Const eval should always happens in PostAnalysis mode.");
}
}
}
InterpCx {
machine,
tcx: tcx.at(root_span),
@@ -1414,6 +1414,10 @@ pub struct BuiltinAttribute {
rustc_scalable_vector, Normal, template!(List: &["count"]), WarnFollowing, EncodeCrossCrate::Yes,
"`#[rustc_scalable_vector]` defines a scalable vector type"
),
rustc_attr!(
rustc_must_match_exhaustively, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::Yes,
"enums with `#[rustc_must_match_exhaustively]` must be matched on with a match block that mentions all variants explicitly"
),
// ==========================================================================
// Internal attributes, Testing:
@@ -1471,6 +1471,9 @@ pub enum AttributeKind {
fn_names: ThinVec<Ident>,
},
/// Represents `#[rustc_must_match_exhaustively]`
RustcMustMatchExhaustively(Span),
/// Represents `#[rustc_never_returns_null_ptr]`
RustcNeverReturnsNullPtr,
@@ -156,6 +156,7 @@ pub fn encode_cross_crate(&self) -> EncodeCrossCrate {
RustcMain => No,
RustcMir(..) => Yes,
RustcMustImplementOneOf { .. } => No,
RustcMustMatchExhaustively(..) => Yes,
RustcNeverReturnsNullPtr => Yes,
RustcNeverTypeOptions { .. } => No,
RustcNoImplicitAutorefs => Yes,
@@ -13,6 +13,7 @@
self, BoundVar, GenericArg, InferConst, List, Ty, TyCtxt, TypeFlags, TypeFoldable, TypeFolder,
TypeSuperFoldable, TypeVisitableExt,
};
use rustc_type_ir::TypingModeEqWrapper;
use smallvec::SmallVec;
use tracing::debug;
@@ -72,7 +73,7 @@ pub fn canonicalize_query<V>(
query_state,
)
.unchecked_map(|(param_env, value)| param_env.and(value));
CanonicalQueryInput { canonical, typing_mode: self.typing_mode() }
CanonicalQueryInput { canonical, typing_mode: TypingModeEqWrapper(self.typing_mode()) }
}
/// Canonicalizes a query *response* `V`. When we canonicalize a
+4 -4
View File
@@ -564,16 +564,16 @@ pub fn build_with_canonical<T>(
where
T: TypeFoldable<TyCtxt<'tcx>>,
{
let infcx = self.build(input.typing_mode);
let infcx = self.build(input.typing_mode.0);
let (value, args) = infcx.instantiate_canonical(span, &input.canonical);
(infcx, value, args)
}
pub fn build_with_typing_env(
mut self,
TypingEnv { typing_mode, param_env }: TypingEnv<'tcx>,
typing_env: TypingEnv<'tcx>,
) -> (InferCtxt<'tcx>, ty::ParamEnv<'tcx>) {
(self.build(typing_mode), param_env)
(self.build(typing_env.typing_mode()), typing_env.param_env)
}
pub fn build(&mut self, typing_mode: TypingMode<'tcx>) -> InferCtxt<'tcx> {
@@ -1376,7 +1376,7 @@ pub fn typing_env(&self, param_env: ty::ParamEnv<'tcx>) -> ty::TypingEnv<'tcx> {
| ty::TypingMode::PostBorrowckAnalysis { .. }
| ty::TypingMode::PostAnalysis) => mode,
};
ty::TypingEnv { typing_mode, param_env }
ty::TypingEnv::new(param_env, typing_mode)
}
/// Similar to [`Self::canonicalize_query`], except that it returns
@@ -89,7 +89,7 @@ pub fn handle_opaque_type(
if def_id.is_local() =>
{
let def_id = def_id.expect_local();
if let ty::TypingMode::Coherence = self.typing_mode() {
if self.typing_mode().is_coherence() {
// See comment on `insert_hidden_type` for why this is sufficient in coherence
return Some(self.register_hidden_type(
OpaqueTypeKey { def_id, args },
@@ -7,7 +7,7 @@
use rustc_middle::ty::error::TypeError;
use rustc_middle::ty::{
self, AliasRelationDirection, InferConst, Term, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable,
TypeVisitableExt, TypeVisitor, TypingMode,
TypeVisitableExt, TypeVisitor,
};
use rustc_span::Span;
use tracing::{debug, instrument, warn};
@@ -603,7 +603,7 @@ fn tys(&mut self, t: Ty<'tcx>, t2: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> {
//
// cc trait-system-refactor-initiative#108
if self.infcx.next_trait_solver()
&& !matches!(self.infcx.typing_mode(), TypingMode::Coherence)
&& !self.infcx.typing_mode().is_coherence()
&& self.in_alias
{
inner.type_variables().equate(vid, new_var_id);
@@ -735,7 +735,7 @@ fn consts(
// See the comment for type inference variables
// for more details.
if self.infcx.next_trait_solver()
&& !matches!(self.infcx.typing_mode(), TypingMode::Coherence)
&& !self.infcx.typing_mode().is_coherence()
&& self.in_alias
{
variable_table.union(vid, new_var_id);
+1 -1
View File
@@ -637,7 +637,7 @@ pub fn typing_mode(&self) -> TypingMode<'tcx> {
}
pub fn typing_env(&self) -> TypingEnv<'tcx> {
TypingEnv { typing_mode: self.typing_mode(), param_env: self.param_env }
TypingEnv::new(self.param_env, self.typing_mode())
}
pub fn type_is_copy_modulo_regions(&self, ty: Ty<'tcx>) -> bool {
+89 -2
View File
@@ -15,8 +15,9 @@
use crate::lints::{
AttributeKindInFindAttr, BadOptAccessDiag, DefaultHashTypesDiag,
ImplicitSysrootCrateImportDiag, LintPassByHand, NonGlobImportTypeIrInherent, QueryInstability,
QueryUntracked, SpanUseEqCtxtDiag, SymbolInternStringLiteralDiag, TyQualified, TykindDiag,
TykindKind, TypeIrDirectUse, TypeIrInherentUsage, TypeIrTraitUsage,
QueryUntracked, RustcMustMatchExhaustivelyNotExhaustive, SpanUseEqCtxtDiag,
SymbolInternStringLiteralDiag, TyQualified, TykindDiag, TykindKind, TypeIrDirectUse,
TypeIrInherentUsage, TypeIrTraitUsage,
};
use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
@@ -713,3 +714,89 @@ fn find_attr_kind_in_pat(cx: &EarlyContext<'_>, pat: &Pat) {
}
}
}
declare_tool_lint! {
pub rustc::RUSTC_MUST_MATCH_EXHAUSTIVELY,
Allow,
"Forbids matches with wildcards, or if-let matching on enums marked with `#[rustc_must_match_exhaustively]`",
report_in_external_macro: true
}
declare_lint_pass!(RustcMustMatchExhaustively => [RUSTC_MUST_MATCH_EXHAUSTIVELY]);
fn is_rustc_must_match_exhaustively(cx: &LateContext<'_>, id: HirId) -> Option<Span> {
let res = cx.typeck_results();
let ty = res.node_type(id);
let ty = if let ty::Ref(_, ty, _) = ty.kind() { *ty } else { ty };
if let Some(adt_def) = ty.ty_adt_def()
&& adt_def.is_enum()
{
find_attr!(cx.tcx, adt_def.did(), RustcMustMatchExhaustively(span) => *span)
} else {
None
}
}
fn pat_is_not_exhaustive_heuristic(pat: &hir::Pat<'_>) -> Option<(Span, &'static str)> {
match pat.kind {
hir::PatKind::Missing => None,
hir::PatKind::Wild => Some((pat.span, "because of this wildcard pattern")),
hir::PatKind::Binding(_, _, _, Some(pat)) => pat_is_not_exhaustive_heuristic(pat),
hir::PatKind::Binding(..) => Some((pat.span, "because of this variable binding")),
hir::PatKind::Struct(..) => None,
hir::PatKind::TupleStruct(..) => None,
hir::PatKind::Or(..) => None,
hir::PatKind::Never => None,
hir::PatKind::Tuple(..) => None,
hir::PatKind::Box(pat) => pat_is_not_exhaustive_heuristic(&*pat),
hir::PatKind::Deref(pat) => pat_is_not_exhaustive_heuristic(&*pat),
hir::PatKind::Ref(pat, _, _) => pat_is_not_exhaustive_heuristic(&*pat),
hir::PatKind::Expr(..) => None,
hir::PatKind::Guard(..) => None,
hir::PatKind::Range(..) => None,
hir::PatKind::Slice(..) => None,
hir::PatKind::Err(..) => None,
}
}
impl<'tcx> LateLintPass<'tcx> for RustcMustMatchExhaustively {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) {
match expr.kind {
// This is not perfect exhaustiveness checking, that's why this is just a rustc internal
// attribute. But it catches most reasonable cases
hir::ExprKind::Match(expr, arms, _) => {
if let Some(attr_span) = is_rustc_must_match_exhaustively(cx, expr.hir_id) {
for arm in arms {
if let Some((span, message)) = pat_is_not_exhaustive_heuristic(arm.pat) {
cx.emit_span_lint(
RUSTC_MUST_MATCH_EXHAUSTIVELY,
expr.span,
RustcMustMatchExhaustivelyNotExhaustive {
attr_span,
pat_span: span,
message,
},
);
}
}
}
}
hir::ExprKind::If(expr, ..) if let ExprKind::Let(expr) = expr.kind => {
if let Some(attr_span) = is_rustc_must_match_exhaustively(cx, expr.init.hir_id) {
cx.emit_span_lint(
RUSTC_MUST_MATCH_EXHAUSTIVELY,
expr.span,
RustcMustMatchExhaustivelyNotExhaustive {
attr_span,
pat_span: expr.span,
message: "using if let only matches on one variant (try using `match`)",
},
);
}
}
_ => {}
}
}
}
+3
View File
@@ -668,6 +668,8 @@ fn register_internals(store: &mut LintStore) {
store.register_early_pass(|| Box::new(ImplicitSysrootCrateImport));
store.register_lints(&BadUseOfFindAttr::lint_vec());
store.register_early_pass(|| Box::new(BadUseOfFindAttr));
store.register_lints(&RustcMustMatchExhaustively::lint_vec());
store.register_late_pass(|_| Box::new(RustcMustMatchExhaustively));
store.register_group(
false,
"rustc::internal",
@@ -688,6 +690,7 @@ fn register_internals(store: &mut LintStore) {
LintId::of(DIRECT_USE_OF_RUSTC_TYPE_IR),
LintId::of(IMPLICIT_SYSROOT_CRATE_IMPORT),
LintId::of(BAD_USE_OF_FIND_ATTR),
LintId::of(RUSTC_MUST_MATCH_EXHAUSTIVELY),
],
);
}
+12
View File
@@ -1162,6 +1162,18 @@ pub(crate) struct ImplicitSysrootCrateImportDiag<'a> {
#[help("remove `AttributeKind`")]
pub(crate) struct AttributeKindInFindAttr;
#[derive(Diagnostic)]
#[diag("match is not exhaustive")]
#[help("explicitly list all variants of the enum in a `match`")]
pub(crate) struct RustcMustMatchExhaustivelyNotExhaustive {
#[label("required because of this attribute")]
pub attr_span: Span,
#[note("{$message}")]
pub pat_span: Span,
pub message: &'static str,
}
// let_underscore.rs
#[derive(Diagnostic)]
pub(crate) enum NonBindingLet {
+4 -4
View File
@@ -415,10 +415,10 @@ pub fn basic_blocks_mut(&mut self) -> &mut IndexVec<BasicBlock, BasicBlockData<'
pub fn typing_env(&self, tcx: TyCtxt<'tcx>) -> TypingEnv<'tcx> {
match self.phase {
// FIXME(#132279): we should reveal the opaques defined in the body during analysis.
MirPhase::Built | MirPhase::Analysis(_) => TypingEnv {
typing_mode: ty::TypingMode::non_body_analysis(),
param_env: tcx.param_env(self.source.def_id()),
},
MirPhase::Built | MirPhase::Analysis(_) => TypingEnv::new(
tcx.param_env(self.source.def_id()),
ty::TypingMode::non_body_analysis(),
),
MirPhase::Runtime(_) => TypingEnv::post_analysis(tcx, self.source.def_id()),
}
}
+20 -7
View File
@@ -105,7 +105,8 @@
AliasTy, AliasTyKind, Article, Binder, BoundConst, BoundRegion, BoundRegionKind, BoundTy,
BoundTyKind, BoundVariableKind, CanonicalPolyFnSig, CoroutineArgsExt, EarlyBinder, FnSig,
InlineConstArgs, InlineConstArgsParts, ParamConst, ParamTy, PlaceholderConst,
PlaceholderRegion, PlaceholderType, PolyFnSig, TyKind, TypeAndMut, TypingMode, UpvarArgs,
PlaceholderRegion, PlaceholderType, PolyFnSig, TyKind, TypeAndMut, TypingMode,
TypingModeEqWrapper, UpvarArgs,
};
pub use self::trait_def::TraitDef;
pub use self::typeck_results::{
@@ -980,11 +981,19 @@ pub struct ParamEnvAnd<'tcx, T> {
pub struct TypingEnv<'tcx> {
#[type_foldable(identity)]
#[type_visitable(ignore)]
pub typing_mode: TypingMode<'tcx>,
typing_mode: TypingModeEqWrapper<'tcx>,
pub param_env: ParamEnv<'tcx>,
}
impl<'tcx> TypingEnv<'tcx> {
pub fn new(param_env: ParamEnv<'tcx>, typing_mode: TypingMode<'tcx>) -> Self {
Self { typing_mode: TypingModeEqWrapper(typing_mode), param_env }
}
pub fn typing_mode(&self) -> TypingMode<'tcx> {
self.typing_mode.0
}
/// Create a typing environment with no where-clauses in scope
/// where all opaque types and default associated items are revealed.
///
@@ -993,7 +1002,7 @@ impl<'tcx> TypingEnv<'tcx> {
/// use `TypingMode::PostAnalysis`, they may still have where-clauses
/// in scope.
pub fn fully_monomorphized() -> TypingEnv<'tcx> {
TypingEnv { typing_mode: TypingMode::PostAnalysis, param_env: ParamEnv::empty() }
Self::new(ParamEnv::empty(), TypingMode::PostAnalysis)
}
/// Create a typing environment for use during analysis outside of a body.
@@ -1006,7 +1015,7 @@ pub fn non_body_analysis(
def_id: impl IntoQueryKey<DefId>,
) -> TypingEnv<'tcx> {
let def_id = def_id.into_query_key();
TypingEnv { typing_mode: TypingMode::non_body_analysis(), param_env: tcx.param_env(def_id) }
Self::new(tcx.param_env(def_id), TypingMode::non_body_analysis())
}
pub fn post_analysis(tcx: TyCtxt<'tcx>, def_id: impl IntoQueryKey<DefId>) -> TypingEnv<'tcx> {
@@ -1018,8 +1027,12 @@ pub fn post_analysis(tcx: TyCtxt<'tcx>, def_id: impl IntoQueryKey<DefId>) -> Typ
/// opaque types in the `param_env`.
pub fn with_post_analysis_normalized(self, tcx: TyCtxt<'tcx>) -> TypingEnv<'tcx> {
let TypingEnv { typing_mode, param_env } = self;
if let TypingMode::PostAnalysis = typing_mode {
return self;
match typing_mode.0 {
TypingMode::Coherence
| TypingMode::Analysis { .. }
| TypingMode::Borrowck { .. }
| TypingMode::PostBorrowckAnalysis { .. } => {}
TypingMode::PostAnalysis => return self,
}
// No need to reveal opaques with the new solver enabled,
@@ -1029,7 +1042,7 @@ pub fn with_post_analysis_normalized(self, tcx: TyCtxt<'tcx>) -> TypingEnv<'tcx>
} else {
ParamEnv::new(tcx.reveal_opaque_types_in_bounds(param_env.caller_bounds()))
};
TypingEnv { typing_mode: TypingMode::PostAnalysis, param_env }
TypingEnv { typing_mode: TypingModeEqWrapper(TypingMode::PostAnalysis), param_env }
}
/// Combine this typing environment with the given `value` to be used by
+1
View File
@@ -40,6 +40,7 @@
pub type Binder<'tcx, T> = ir::Binder<TyCtxt<'tcx>, T>;
pub type EarlyBinder<'tcx, T> = ir::EarlyBinder<TyCtxt<'tcx>, T>;
pub type TypingMode<'tcx> = ir::TypingMode<TyCtxt<'tcx>>;
pub type TypingModeEqWrapper<'tcx> = ir::TypingModeEqWrapper<TyCtxt<'tcx>>;
pub type Placeholder<'tcx, T> = ir::Placeholder<TyCtxt<'tcx>, T>;
pub type PlaceholderRegion<'tcx> = ir::PlaceholderRegion<TyCtxt<'tcx>>;
pub type PlaceholderType<'tcx> = ir::PlaceholderType<TyCtxt<'tcx>>;
@@ -548,7 +548,16 @@ fn move_paths_for_fields(
let subpath = self.elaborator.field_subpath(variant_path, field_idx);
let tcx = self.tcx();
assert_eq!(self.elaborator.typing_env().typing_mode, ty::TypingMode::PostAnalysis);
match self.elaborator.typing_env().typing_mode() {
ty::TypingMode::PostAnalysis => {}
ty::TypingMode::Coherence
| ty::TypingMode::Analysis { .. }
| ty::TypingMode::Borrowck { .. }
| ty::TypingMode::PostBorrowckAnalysis { .. } => {
bug!()
}
}
let field_ty = field.ty(tcx, args);
// We silently leave an unnormalized type here to support polymorphic drop
// elaboration for users of rustc internal APIs
@@ -17,7 +17,7 @@
use rustc_type_ir::relate::solver_relating::RelateExt;
use rustc_type_ir::{
self as ty, Canonical, CanonicalVarKind, CanonicalVarValues, InferCtxtLike, Interner,
TypeFoldable,
TypeFoldable, TypingModeEqWrapper,
};
use tracing::instrument;
@@ -66,7 +66,10 @@ pub(super) fn canonicalize_goal<D, I>(
predefined_opaques_in_body: delegate.cx().mk_predefined_opaques_in_body(opaque_types),
},
);
let query_input = ty::CanonicalQueryInput { canonical, typing_mode: delegate.typing_mode() };
let query_input = ty::CanonicalQueryInput {
canonical,
typing_mode: TypingModeEqWrapper(delegate.typing_mode()),
};
(orig_values, query_input)
}
@@ -466,15 +466,20 @@ pub(super) fn assemble_and_evaluate_candidates<G: GoalKind<D>>(
// as we may want to weaken inference guidance in the future and don't want
// to worry about causing major performance regressions when doing so.
// See trait-system-refactor-initiative#226 for some ideas here.
if TypingMode::Coherence == self.typing_mode()
|| !candidates.iter().any(|c| {
let assemble_impls = match self.typing_mode() {
TypingMode::Coherence => true,
TypingMode::Analysis { .. }
| TypingMode::Borrowck { .. }
| TypingMode::PostBorrowckAnalysis { .. }
| TypingMode::PostAnalysis => !candidates.iter().any(|c| {
matches!(
c.source,
CandidateSource::ParamEnv(ParamEnvSource::NonGlobal)
| CandidateSource::AliasBound(_)
) && has_no_inference_or_external_constraints(c.result)
})
{
}),
};
if assemble_impls {
self.assemble_impl_candidates(goal, &mut candidates);
self.assemble_object_bound_candidates(goal, &mut candidates);
}
@@ -93,7 +93,9 @@ pub(super) fn normalize_opaque_type(
});
self.eq(goal.param_env, expected, actual)?;
}
_ => unreachable!(),
TypingMode::Coherence
| TypingMode::PostBorrowckAnalysis { .. }
| TypingMode::PostAnalysis => unreachable!(),
}
}
@@ -62,7 +62,7 @@ fn initial_provisional_result(
// See `tests/ui/traits/next-solver/cycles/unproductive-in-coherence.rs` for an
// example where this would matter. We likely should change these cycles to `NoSolution`
// even in coherence once this is a bit more settled.
PathKind::Inductive => match input.typing_mode {
PathKind::Inductive => match input.typing_mode.0 {
TypingMode::Coherence => {
response_no_constraints(cx, input, Certainty::overflow(false))
}
@@ -1421,7 +1421,7 @@ pub(super) fn merge_trait_candidates(
mut candidates: Vec<Candidate<I>>,
failed_candidate_info: FailedCandidateInfo,
) -> Result<(CanonicalResponse<I>, Option<TraitGoalProvenVia>), NoSolution> {
if let TypingMode::Coherence = self.typing_mode() {
if self.typing_mode().is_coherence() {
return if let Some((response, _)) = self.try_merge_candidates(&candidates) {
Ok((response, Some(TraitGoalProvenVia::Misc)))
} else {
+1
View File
@@ -339,6 +339,7 @@ fn check_attributes(
| AttributeKind::RustcMacroTransparency(_)
| AttributeKind::RustcMain
| AttributeKind::RustcMir(_)
| AttributeKind::RustcMustMatchExhaustively(..)
| AttributeKind::RustcNeverReturnsNullPtr
| AttributeKind::RustcNeverTypeOptions {..}
| AttributeKind::RustcNoImplicitAutorefs
+1
View File
@@ -1753,6 +1753,7 @@
rustc_main,
rustc_mir,
rustc_must_implement_one_of,
rustc_must_match_exhaustively,
rustc_never_returns_null_ptr,
rustc_never_type_options,
rustc_no_implicit_autorefs,
@@ -8,7 +8,7 @@
use rustc_middle::traits::query::NoSolution;
use rustc_middle::ty::elaborate::elaborate;
use rustc_middle::ty::fast_reject::DeepRejectCtxt;
use rustc_middle::ty::{self, Ty, TypingMode};
use rustc_middle::ty::{self, Ty};
use thin_vec::{ThinVec, thin_vec};
use super::SelectionContext;
@@ -25,7 +25,7 @@ pub fn evaluate_host_effect_obligation<'tcx>(
selcx: &mut SelectionContext<'_, 'tcx>,
obligation: &HostEffectObligation<'tcx>,
) -> Result<ThinVec<PredicateObligation<'tcx>>, EvaluationFailure> {
if selcx.infcx.typing_mode() == TypingMode::Coherence {
if selcx.infcx.typing_mode().is_coherence() {
span_bug!(
obligation.cause.span,
"should not select host obligation in old solver in intercrate mode"
@@ -841,8 +841,7 @@ fn process_trait_obligation(
stalled_on: &mut Vec<TyOrConstInferVar>,
) -> ProcessResult<PendingPredicateObligation<'tcx>, FulfillmentErrorCode<'tcx>> {
let infcx = self.selcx.infcx;
if obligation.predicate.is_global() && !matches!(infcx.typing_mode(), TypingMode::Coherence)
{
if obligation.predicate.is_global() && !infcx.typing_mode().is_coherence() {
// no type variables present, can use evaluation for better caching.
// FIXME: consider caching errors too.
if infcx.predicate_must_hold_considering_regions(obligation) {
@@ -896,8 +895,7 @@ fn process_projection_obligation(
) -> ProcessResult<PendingPredicateObligation<'tcx>, FulfillmentErrorCode<'tcx>> {
let tcx = self.selcx.tcx();
let infcx = self.selcx.infcx;
if obligation.predicate.is_global() && !matches!(infcx.typing_mode(), TypingMode::Coherence)
{
if obligation.predicate.is_global() && !infcx.typing_mode().is_coherence() {
// no type variables present, can use evaluation for better caching.
// FIXME: consider caching errors too.
if infcx.predicate_must_hold_considering_regions(obligation) {
@@ -15,7 +15,7 @@
use rustc_infer::traits::{Obligation, PolyTraitObligation, PredicateObligation, SelectionError};
use rustc_middle::ty::fast_reject::DeepRejectCtxt;
use rustc_middle::ty::{
self, FieldInfo, SizedTraitKind, TraitRef, Ty, TypeVisitableExt, TypingMode, elaborate,
self, FieldInfo, SizedTraitKind, TraitRef, Ty, TypeVisitableExt, elaborate,
};
use rustc_middle::{bug, span_bug};
use rustc_span::DUMMY_SP;
@@ -849,7 +849,7 @@ fn assemble_candidates_from_auto_impls(
//
// Note that this is only sound as projection candidates of opaque types
// are always applicable for auto traits.
} else if let TypingMode::Coherence = self.infcx.typing_mode() {
} else if self.infcx.typing_mode().is_coherence() {
// We do not emit auto trait candidates for opaque types in coherence.
// Doing so can result in weird dependency cycles.
candidates.ambiguous = true;
@@ -3,9 +3,9 @@
//! [rustc dev guide]: https://rustc-dev-guide.rust-lang.org/traits/resolution.html#selection
use std::cell::{Cell, RefCell};
use std::cmp;
use std::fmt::{self, Display};
use std::ops::ControlFlow;
use std::{assert_matches, cmp};
use hir::def::DefKind;
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
@@ -210,8 +210,9 @@ pub fn with_query_mode(
/// Enables tracking of intercrate ambiguity causes. See
/// the documentation of [`Self::intercrate_ambiguity_causes`] for more.
pub fn enable_tracking_intercrate_ambiguity_causes(&mut self) {
assert_matches!(self.infcx.typing_mode(), TypingMode::Coherence);
assert!(self.infcx.typing_mode().is_coherence());
assert!(self.intercrate_ambiguity_causes.is_none());
self.intercrate_ambiguity_causes = Some(FxIndexSet::default());
debug!("selcx: enable_tracking_intercrate_ambiguity_causes");
}
@@ -222,7 +223,8 @@ pub fn enable_tracking_intercrate_ambiguity_causes(&mut self) {
pub fn take_intercrate_ambiguity_causes(
&mut self,
) -> FxIndexSet<IntercrateAmbiguityCause<'tcx>> {
assert_matches!(self.infcx.typing_mode(), TypingMode::Coherence);
assert!(self.infcx.typing_mode().is_coherence());
self.intercrate_ambiguity_causes.take().unwrap_or_default()
}
@@ -1016,7 +1018,7 @@ fn evaluate_trait_predicate_recursively<'o>(
previous_stack: TraitObligationStackList<'o, 'tcx>,
mut obligation: PolyTraitObligation<'tcx>,
) -> Result<EvaluationResult, OverflowError> {
if !matches!(self.infcx.typing_mode(), TypingMode::Coherence)
if !self.infcx.typing_mode().is_coherence()
&& obligation.is_global()
&& obligation.param_env.caller_bounds().iter().all(|bound| bound.has_param())
{
@@ -2548,7 +2550,7 @@ fn match_impl(
nested_obligations.extend(obligations);
if impl_trait_header.polarity == ty::ImplPolarity::Reservation
&& !matches!(self.infcx.typing_mode(), TypingMode::Coherence)
&& !self.infcx.typing_mode().is_coherence()
{
debug!("reservation impls only apply in intercrate mode");
return Err(());
+1 -1
View File
@@ -154,7 +154,7 @@ fn resolve_associated_item<'tcx>(
// and the obligation is monomorphic, otherwise passes such as
// transmute checking and polymorphic MIR optimizations could
// get a result which isn't correct for all monomorphizations.
match typing_env.typing_mode {
match typing_env.typing_mode() {
ty::TypingMode::Coherence
| ty::TypingMode::Analysis { .. }
| ty::TypingMode::Borrowck { .. }
+2 -2
View File
@@ -11,7 +11,7 @@
use crate::data_structures::HashMap;
use crate::inherent::*;
use crate::{self as ty, Interner, TypingMode, UniverseIndex};
use crate::{self as ty, Interner, TypingModeEqWrapper, UniverseIndex};
#[derive_where(Clone, Hash, PartialEq, Debug; I: Interner, V)]
#[derive_where(Copy; I: Interner, V: Copy)]
@@ -21,7 +21,7 @@
)]
pub struct CanonicalQueryInput<I: Interner, V> {
pub canonical: Canonical<I, V>,
pub typing_mode: TypingMode<I>,
pub typing_mode: TypingModeEqWrapper<I>,
}
impl<I: Interner, V: Eq> Eq for CanonicalQueryInput<I, V> {}
+92 -4
View File
@@ -1,3 +1,5 @@
use std::hash::{Hash, Hasher};
use derive_where::derive_where;
#[cfg(feature = "nightly")]
use rustc_macros::{Decodable_NoContext, Encodable_NoContext, HashStable_NoContext};
@@ -18,11 +20,28 @@
///
/// If neither of these functions are available, feel free to reach out to
/// t-types for help.
#[derive_where(Clone, Copy, Hash, PartialEq, Debug; I: Interner)]
///
/// Because typing rules get subtly different based on what typing mode we're in,
/// subtle enough that changing the behavior of typing modes can sometimes cause
/// changes that we don't even have tests for, we'd like to enforce the rule that
/// any place where we specialize behavior based on the typing mode, we match
/// *exhaustively* on the typing mode. That way, it's easy to determine all the
/// places that must change when anything about typing modes changes.
///
/// Hence, `TypingMode` does not implement `Eq`, though [`TypingModeEqWrapper`] is available
/// in the rare case that you do need this. Most cases where this currently matters is
/// where we pass typing modes through the query system and want to cache based on it.
/// See also `#[rustc_must_match_exhaustively]`, which tries to detect non-exhaustive
/// matches.
///
/// Since matching on typing mode to single out `Coherence` is so common, and `Coherence`
/// is so different from the other modes: see also [`is_coherence`](TypingMode::is_coherence)
#[derive_where(Clone, Copy, Hash, Debug; I: Interner)]
#[cfg_attr(
feature = "nightly",
derive(Encodable_NoContext, Decodable_NoContext, HashStable_NoContext)
)]
#[cfg_attr(feature = "nightly", cfg_attr(not(bootstrap), rustc_must_match_exhaustively))]
pub enum TypingMode<I: Interner> {
/// When checking whether impls overlap, we check whether any obligations
/// are guaranteed to never hold when unifying the impls. This requires us
@@ -90,9 +109,71 @@ pub enum TypingMode<I: Interner> {
PostAnalysis,
}
impl<I: Interner> Eq for TypingMode<I> {}
/// We want to highly discourage using equality checks on typing modes.
/// Instead you should match, **exhaustively**, so when we ever modify the enum we get a compile
/// error. Only use `TypingModeEqWrapper` when you really really really have to.
/// Prefer unwrapping `TypingModeEqWrapper` in apis that should return a `TypingMode` whenever
/// possible, and if you ever get an `TypingModeEqWrapper`, prefer unwrapping it and matching on it **exhaustively**.
#[derive_where(Clone, Copy, Debug; I: Interner)]
#[cfg_attr(
feature = "nightly",
derive(Encodable_NoContext, Decodable_NoContext, HashStable_NoContext)
)]
pub struct TypingModeEqWrapper<I: Interner>(pub TypingMode<I>);
impl<I: Interner> Hash for TypingModeEqWrapper<I> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl<I: Interner> PartialEq for TypingModeEqWrapper<I> {
fn eq(&self, other: &Self) -> bool {
match (self.0, other.0) {
(TypingMode::Coherence, TypingMode::Coherence) => true,
(
TypingMode::Analysis { defining_opaque_types_and_generators: l },
TypingMode::Analysis { defining_opaque_types_and_generators: r },
) => l == r,
(
TypingMode::Borrowck { defining_opaque_types: l },
TypingMode::Borrowck { defining_opaque_types: r },
) => l == r,
(
TypingMode::PostBorrowckAnalysis { defined_opaque_types: l },
TypingMode::PostBorrowckAnalysis { defined_opaque_types: r },
) => l == r,
(TypingMode::PostAnalysis, TypingMode::PostAnalysis) => true,
(
TypingMode::Coherence
| TypingMode::Analysis { .. }
| TypingMode::Borrowck { .. }
| TypingMode::PostBorrowckAnalysis { .. }
| TypingMode::PostAnalysis,
_,
) => false,
}
}
}
impl<I: Interner> Eq for TypingModeEqWrapper<I> {}
impl<I: Interner> TypingMode<I> {
/// There are a bunch of places in the compiler where we single out `Coherence`,
/// and alter behavior. We'd like to *always* match on `TypingMode` exhaustively,
/// but not having this method leads to a bunch of noisy code.
///
/// See also the documentation on [`TypingMode`] about exhaustive matching.
pub fn is_coherence(&self) -> bool {
match self {
TypingMode::Coherence => true,
TypingMode::Analysis { .. }
| TypingMode::Borrowck { .. }
| TypingMode::PostBorrowckAnalysis { .. }
| TypingMode::PostAnalysis => false,
}
}
/// Analysis outside of a body does not define any opaque types.
pub fn non_body_analysis() -> TypingMode<I> {
TypingMode::Analysis { defining_opaque_types_and_generators: Default::default() }
@@ -322,6 +403,13 @@ pub fn may_use_unstable_feature<'a, I: Interner, Infcx>(
// Note: `feature_bound_holds_in_crate` does not consider a feature to be enabled
// if we are in std/core even if there is a corresponding `feature` attribute on the crate.
(infcx.typing_mode() == TypingMode::PostAnalysis)
|| infcx.cx().features().feature_bound_holds_in_crate(symbol)
match infcx.typing_mode() {
TypingMode::Coherence
| TypingMode::Analysis { .. }
| TypingMode::Borrowck { .. }
| TypingMode::PostBorrowckAnalysis { .. } => {
infcx.cx().features().feature_bound_holds_in_crate(symbol)
}
TypingMode::PostAnalysis => true,
}
}
+1 -4
View File
@@ -87,10 +87,7 @@ pub(crate) fn with_param_env<T, F: FnOnce(&mut Self) -> T>(
}
pub(crate) fn typing_env(&self) -> ty::TypingEnv<'tcx> {
ty::TypingEnv {
typing_mode: ty::TypingMode::non_body_analysis(),
param_env: self.param_env,
}
ty::TypingEnv::new(self.param_env, ty::TypingMode::non_body_analysis())
}
/// Call the closure with the given parameters set as
@@ -85,8 +85,8 @@ fn typing_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) ->
.upcast(tcx)
}),
)));
ty::TypingEnv {
typing_mode: ty::TypingMode::non_body_analysis(),
ty::TypingEnv::new(
param_env,
}
ty::TypingMode::non_body_analysis(),
)
}
@@ -0,0 +1,48 @@
//@ compile-flags: -Z unstable-options
//@ ignore-stage1
#![feature(rustc_private)]
#![feature(rustc_attrs)]
#![deny(rustc::rustc_must_match_exhaustively)]
#[rustc_must_match_exhaustively]
#[derive(Copy, Clone)]
enum Foo {
A { field: u32 },
B,
}
fn foo(f: Foo) {
match f {
Foo::A { .. } => {}
Foo::B => {}
}
match f {
//~^ ERROR match is not exhaustive
Foo::A { .. } => {}
_ => {}
}
match f {
//~^ ERROR match is not exhaustive
Foo::A { .. } => {}
a => {}
}
match &f {
//~^ ERROR match is not exhaustive
Foo::A { .. } => {}
a => {}
}
match f {
Foo::A { .. } => {}
a @ Foo::B => {}
}
if let Foo::A { .. } = f {}
//~^ ERROR match is not exhaustive
}
fn main() {}
@@ -0,0 +1,71 @@
error: match is not exhaustive
--> $DIR/must_match_exhaustively.rs:21:11
|
LL | #[rustc_must_match_exhaustively]
| -------------------------------- required because of this attribute
...
LL | match f {
| ^
|
= help: explicitly list all variants of the enum in a `match`
note: because of this wildcard pattern
--> $DIR/must_match_exhaustively.rs:24:9
|
LL | _ => {}
| ^
note: the lint level is defined here
--> $DIR/must_match_exhaustively.rs:6:9
|
LL | #![deny(rustc::rustc_must_match_exhaustively)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: match is not exhaustive
--> $DIR/must_match_exhaustively.rs:27:11
|
LL | #[rustc_must_match_exhaustively]
| -------------------------------- required because of this attribute
...
LL | match f {
| ^
|
= help: explicitly list all variants of the enum in a `match`
note: because of this variable binding
--> $DIR/must_match_exhaustively.rs:30:9
|
LL | a => {}
| ^
error: match is not exhaustive
--> $DIR/must_match_exhaustively.rs:33:11
|
LL | #[rustc_must_match_exhaustively]
| -------------------------------- required because of this attribute
...
LL | match &f {
| ^^
|
= help: explicitly list all variants of the enum in a `match`
note: because of this variable binding
--> $DIR/must_match_exhaustively.rs:36:9
|
LL | a => {}
| ^
error: match is not exhaustive
--> $DIR/must_match_exhaustively.rs:44:8
|
LL | #[rustc_must_match_exhaustively]
| -------------------------------- required because of this attribute
...
LL | if let Foo::A { .. } = f {}
| ^^^^^^^^^^^^^^^^^^^^^
|
= help: explicitly list all variants of the enum in a `match`
note: using if let only matches on one variant (try using `match`)
--> $DIR/must_match_exhaustively.rs:44:8
|
LL | if let Foo::A { .. } = f {}
| ^^^^^^^^^^^^^^^^^^^^^
error: aborting due to 4 previous errors