Skip to content

Commit 7875524

Browse files
authored
Unrolled build for #154853
Rollup merge of #154853 - lapla-cogito:normlize_projecttion_const, r=BoxyUwU mgca: Register `ConstArgHasType` when normalizing projection consts Fixes #152962 Fixes #154748 Fixes #154750 When running CTFE on a MIR body, normalizing a type const within that body can change the type of the resulting value, causing the MIR to become ill-formed. Since no prior error has been reported at that point, MIR validation fires a `span_bug!`. The existing `ConstArgHasType` check in `wfcheck::check_type_const` does catch this at the definition site, but due to query evaluation ordering, the normalization path can reach MIR validation before that check has run. Fix this by registering a `ConstArgHasType(ct, expected_ty)` obligation/goal when normalizing projection consts (both trait and inherent), in both the old and new trait solvers. This ensures the type mismatch is reported as an error during normalization itself, which taints the MIR before validation runs. The first commit fixes the original case reported in the issue. The second commit fixes a different ICE pattern reported within the issue (see #152962 (comment)). r? BoxyUwU
2 parents 485ec3f + 277dccc commit 7875524

20 files changed

Lines changed: 409 additions & 8 deletions

compiler/rustc_next_trait_solver/src/solve/normalizes_to/inherent.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ where
5454
.map(|pred| goal.with(cx, pred)),
5555
);
5656

57-
let normalized = match inherent.kind {
57+
let normalized: I::Term = match inherent.kind {
5858
ty::AliasTermKind::InherentTy { def_id } => {
5959
cx.type_of(def_id.into()).instantiate(cx, inherent_args).skip_norm_wip().into()
6060
}
@@ -74,6 +74,7 @@ where
7474
}
7575
kind => panic!("expected inherent alias, found {kind:?}"),
7676
};
77+
7778
self.instantiate_normalizes_to_term(goal, normalized);
7879
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
7980
}

compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,42 @@ where
121121
/// We know `term` to always be a fully unconstrained inference variable, so
122122
/// `eq` should never fail here. However, in case `term` contains aliases, we
123123
/// emit nested `AliasRelate` goals to structurally normalize the alias.
124+
///
125+
/// Additionally, when `term` is a const, this registers a `ConstArgHasType`
126+
/// goal to ensure that the const value's type matches the declared type of
127+
/// the alias it was normalized from.
128+
///
129+
/// You may reasonably wonder: shouldn't `wfcheck::check_type_const` already
130+
/// catch any such type mismatch at the definition site, so that the
131+
/// definition is tainted and we never even attempt to normalize a reference
132+
/// to it? In principle that's exactly what should happen. However, we cannot
133+
/// simply force the defining item's wfcheck to run before all uses are
134+
/// normalized: wfcheck itself may depend on typeck, trait solving, and
135+
/// normalization, so enforcing such a strict ordering would easily create
136+
/// query cycles.
137+
///
138+
/// However, when CTFE runs on a MIR body, normalizing a type const within
139+
/// that body can change the type of the resulting value, causing the MIR
140+
/// to become ill-formed. If `check_type_const` for that alias has not yet
141+
/// reported its error, no prior error has been recorded and MIR validation
142+
/// fires a `span_bug!`. Registering the obligation here ensures the type
143+
/// mismatch is reported during normalization itself, tainting the MIR
144+
/// before validation runs.
124145
pub fn instantiate_normalizes_to_term(
125146
&mut self,
126147
goal: Goal<I, NormalizesTo<I>>,
127148
term: I::Term,
128149
) {
150+
if let Some(ct) = term.as_const() {
151+
let cx = self.cx();
152+
let alias = goal.predicate.alias;
153+
let expected_ty =
154+
cx.type_of(alias.def_id()).instantiate(cx, alias.args).skip_norm_wip();
155+
self.add_goal(
156+
GoalSource::Misc,
157+
goal.with(cx, ty::ClauseKind::ConstArgHasType(ct, expected_ty)),
158+
);
159+
}
129160
self.eq(goal.param_env, goal.predicate.term, term)
130161
.expect("expected goal term to be fully unconstrained");
131162
}

compiler/rustc_trait_selection/src/traits/normalize.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ impl<'a, 'b, 'tcx> AssocTypeNormalizer<'a, 'b, 'tcx> {
339339
}),
340340
);
341341
self.depth += 1;
342-
let res = if free.kind.is_type() {
342+
let res: ty::Term<'tcx> = if free.kind.is_type() {
343343
infcx
344344
.tcx
345345
.type_of(free.def_id())
@@ -356,6 +356,19 @@ impl<'a, 'b, 'tcx> AssocTypeNormalizer<'a, 'b, 'tcx> {
356356
.fold_with(self)
357357
.into()
358358
};
359+
// When normalizing a free const alias, register a `ConstArgHasType`
360+
// obligation to ensure the const value's type matches the declared type.
361+
if let Some(ct) = res.as_const() {
362+
let expected_ty =
363+
infcx.tcx.type_of(free.def_id()).instantiate(infcx.tcx, free.args).skip_norm_wip();
364+
self.obligations.push(Obligation::with_depth(
365+
infcx.tcx,
366+
self.cause.clone(),
367+
self.depth,
368+
self.param_env,
369+
ty::ClauseKind::ConstArgHasType(ct, expected_ty),
370+
));
371+
}
359372
self.depth -= 1;
360373
res
361374
}

compiler/rustc_trait_selection/src/traits/project.rs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::ops::ControlFlow;
55
use rustc_data_structures::sso::SsoHashSet;
66
use rustc_data_structures::stack::ensure_sufficient_stack;
77
use rustc_errors::ErrorGuaranteed;
8+
use rustc_hir::def_id::DefId;
89
use rustc_hir::lang_items::LangItem;
910
use rustc_infer::infer::DefineOpaqueTypes;
1011
use rustc_infer::infer::resolve::OpportunisticRegionResolver;
@@ -487,6 +488,30 @@ fn normalize_to_error<'a, 'tcx>(
487488
Normalized { value: new_value, obligations }
488489
}
489490

491+
/// When normalizing a const alias, register a `ConstArgHasType` obligation
492+
/// to ensure the const value's type matches the declared type.
493+
fn push_const_arg_has_type_obligation<'tcx>(
494+
tcx: TyCtxt<'tcx>,
495+
obligations: &mut PredicateObligations<'tcx>,
496+
cause: &ObligationCause<'tcx>,
497+
depth: usize,
498+
param_env: ty::ParamEnv<'tcx>,
499+
term: Term<'tcx>,
500+
def_id: DefId,
501+
args: ty::GenericArgsRef<'tcx>,
502+
) {
503+
if let Some(ct) = term.as_const() {
504+
let expected_ty = tcx.type_of(def_id).instantiate(tcx, args).skip_norm_wip();
505+
obligations.push(Obligation::with_depth(
506+
tcx,
507+
cause.clone(),
508+
depth,
509+
param_env,
510+
ty::ClauseKind::ConstArgHasType(ct, expected_ty),
511+
));
512+
}
513+
}
514+
490515
/// Confirm and normalize the given inherent projection.
491516
// FIXME(mgca): While this supports constants, it is only used for types by default right now
492517
#[instrument(level = "debug", skip(selcx, param_env, cause, obligations))]
@@ -554,6 +579,17 @@ pub fn normalize_inherent_projection<'a, 'b, 'tcx>(
554579
tcx.const_of_item(alias_term.def_id()).instantiate(tcx, args).skip_norm_wip().into()
555580
};
556581

582+
push_const_arg_has_type_obligation(
583+
tcx,
584+
obligations,
585+
&cause,
586+
depth + 1,
587+
param_env,
588+
term,
589+
alias_term.def_id(),
590+
args,
591+
);
592+
557593
let mut term = selcx.infcx.resolve_vars_if_possible(term);
558594
if term.has_aliases() {
559595
term =
@@ -2049,7 +2085,18 @@ fn confirm_impl_candidate<'cx, 'tcx>(
20492085
Progress { term: err, obligations: nested }
20502086
} else {
20512087
assoc_term_own_obligations(selcx, obligation, &mut nested);
2052-
Progress { term: term.instantiate(tcx, args).skip_norm_wip(), obligations: nested }
2088+
let instantiated_term: Term<'tcx> = term.instantiate(tcx, args).skip_norm_wip();
2089+
push_const_arg_has_type_obligation(
2090+
tcx,
2091+
&mut nested,
2092+
&obligation.cause,
2093+
obligation.recursion_depth + 1,
2094+
obligation.param_env,
2095+
instantiated_term,
2096+
assoc_term.item.def_id,
2097+
args,
2098+
);
2099+
Progress { term: instantiated_term, obligations: nested }
20532100
};
20542101
Ok(Projected::Progress(progress))
20552102
}

compiler/rustc_traits/src/normalize_projection_ty.rs

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use rustc_infer::infer::TyCtxtInferExt;
22
use rustc_infer::infer::canonical::{Canonical, QueryResponse};
33
use rustc_infer::traits::PredicateObligations;
44
use rustc_middle::query::Providers;
5-
use rustc_middle::ty::{ParamEnvAnd, TyCtxt};
5+
use rustc_middle::ty::{self, ParamEnvAnd, TyCtxt};
66
use rustc_trait_selection::error_reporting::InferCtxtErrorExt;
77
use rustc_trait_selection::infer::InferCtxtBuilderExt;
88
use rustc_trait_selection::traits::query::normalize::NormalizationResult;
@@ -19,6 +19,25 @@ pub(crate) fn provide(p: &mut Providers) {
1919
};
2020
}
2121

22+
/// If `normalized_term` is a const, returns a `ConstArgHasType` obligation
23+
/// to verify that the const value's type matches the alias's declared type.
24+
/// Returns `None` if the term is a type rather than a const.
25+
fn const_arg_has_type_obligation<'tcx>(
26+
tcx: TyCtxt<'tcx>,
27+
param_env: ty::ParamEnv<'tcx>,
28+
normalized_term: ty::Term<'tcx>,
29+
goal: ty::AliasTerm<'tcx>,
30+
) -> Option<traits::PredicateObligation<'tcx>> {
31+
let ct = normalized_term.as_const()?;
32+
let expected_ty = tcx.type_of(goal.def_id()).instantiate(tcx, goal.args).skip_norm_wip();
33+
Some(traits::Obligation::new(
34+
tcx,
35+
ObligationCause::dummy(),
36+
param_env,
37+
ty::ClauseKind::ConstArgHasType(ct, expected_ty),
38+
))
39+
}
40+
2241
fn normalize_canonicalized_projection<'tcx>(
2342
tcx: TyCtxt<'tcx>,
2443
goal: CanonicalAliasGoal<'tcx>,
@@ -40,6 +59,12 @@ fn normalize_canonicalized_projection<'tcx>(
4059
0,
4160
&mut obligations,
4261
);
62+
obligations.extend(const_arg_has_type_obligation(
63+
tcx,
64+
param_env,
65+
normalized_term,
66+
goal.into(),
67+
));
4368
ocx.register_obligations(obligations);
4469
// #112047: With projections and opaques, we are able to create opaques that
4570
// are recursive (given some generic parameters of the opaque's type variables).
@@ -86,11 +111,17 @@ fn normalize_canonicalized_free_alias<'tcx>(
86111
},
87112
);
88113
ocx.register_obligations(obligations);
89-
let normalized_term = if goal.kind.is_type() {
114+
let normalized_term: ty::Term<'tcx> = if goal.kind.is_type() {
90115
tcx.type_of(goal.def_id()).instantiate(tcx, goal.args).skip_norm_wip().into()
91116
} else {
92117
tcx.const_of_item(goal.def_id()).instantiate(tcx, goal.args).skip_norm_wip().into()
93118
};
119+
ocx.register_obligations(const_arg_has_type_obligation(
120+
tcx,
121+
param_env,
122+
normalized_term,
123+
goal.into(),
124+
));
94125
Ok(NormalizationResult { normalized_term })
95126
},
96127
)
@@ -116,6 +147,12 @@ fn normalize_canonicalized_inherent_projection<'tcx>(
116147
0,
117148
&mut obligations,
118149
);
150+
obligations.extend(const_arg_has_type_obligation(
151+
tcx,
152+
param_env,
153+
normalized_term,
154+
goal.into(),
155+
));
119156
ocx.register_obligations(obligations);
120157

121158
Ok(NormalizationResult { normalized_term })
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//@ compile-flags: -Zvalidate-mir -Znext-solver
2+
3+
#![feature(min_generic_const_args)]
4+
5+
type const X: usize = const { N };
6+
//~^ ERROR type annotations needed
7+
8+
type const N: usize = "this isn't a usize";
9+
//~^ ERROR the constant `"this isn't a usize"` is not of type `usize`
10+
11+
fn main() {}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error[E0284]: type annotations needed: cannot normalize `X::{constant#0}`
2+
--> $DIR/type-const-free-anon-const-mismatch.rs:5:1
3+
|
4+
LL | type const X: usize = const { N };
5+
| ^^^^^^^^^^^^^^^^^^^ cannot normalize `X::{constant#0}`
6+
7+
error: the constant `"this isn't a usize"` is not of type `usize`
8+
--> $DIR/type-const-free-anon-const-mismatch.rs:8:1
9+
|
10+
LL | type const N: usize = "this isn't a usize";
11+
| ^^^^^^^^^^^^^^^^^^^ expected `usize`, found `&'static str`
12+
13+
error: aborting due to 2 previous errors
14+
15+
For more information about this error, try `rustc --explain E0284`.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
error: the constant `"this isn't a usize"` is not of type `usize`
2+
--> $DIR/type-const-free-value-type-mismatch.rs:8:1
3+
|
4+
LL | type const N: usize = "this isn't a usize";
5+
| ^^^^^^^^^^^^^^^^^^^ expected `usize`, found `&'static str`
6+
7+
error[E0308]: mismatched types
8+
--> $DIR/type-const-free-value-type-mismatch.rs:11:11
9+
|
10+
LL | fn f() -> [u8; const { N }] {}
11+
| - ^^^^^^^^^^^^^^^^^ expected `[u8; const { N }]`, found `()`
12+
| |
13+
| implicitly returns `()` as its body has no tail or `return` expression
14+
15+
error: aborting due to 2 previous errors
16+
17+
For more information about this error, try `rustc --explain E0308`.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
error: the constant `"this isn't a usize"` is not of type `usize`
2+
--> $DIR/type-const-free-value-type-mismatch.rs:8:1
3+
|
4+
LL | type const N: usize = "this isn't a usize";
5+
| ^^^^^^^^^^^^^^^^^^^ expected `usize`, found `&'static str`
6+
7+
error[E0284]: type annotations needed: cannot normalize `f::{constant#0}`
8+
--> $DIR/type-const-free-value-type-mismatch.rs:11:11
9+
|
10+
LL | fn f() -> [u8; const { N }] {}
11+
| ^^^^^^^^^^^^^^^^^ cannot normalize `f::{constant#0}`
12+
13+
error[E0308]: mismatched types
14+
--> $DIR/type-const-free-value-type-mismatch.rs:11:11
15+
|
16+
LL | fn f() -> [u8; const { N }] {}
17+
| - ^^^^^^^^^^^^^^^^^ expected `[u8; _]`, found `()`
18+
| |
19+
| implicitly returns `()` as its body has no tail or `return` expression
20+
21+
error: aborting due to 3 previous errors
22+
23+
Some errors have detailed explanations: E0284, E0308.
24+
For more information about an error, try `rustc --explain E0284`.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#![feature(min_generic_const_args)]
2+
3+
//@ revisions: current next
4+
//@ ignore-compare-mode-next-solver (explicit revisions)
5+
//@[next] compile-flags: -Znext-solver
6+
//@ compile-flags: -Zvalidate-mir
7+
8+
type const N: usize = "this isn't a usize";
9+
//~^ ERROR the constant `"this isn't a usize"` is not of type `usize`
10+
11+
fn f() -> [u8; const { N }] {}
12+
//[current]~^ ERROR mismatched types [E0308]
13+
//[next]~^^ ERROR type annotations needed
14+
//[next]~| ERROR mismatched types [E0308]
15+
16+
fn main() {}

0 commit comments

Comments
 (0)