]>
Commit | Line | Data |
---|---|---|
1b1a35ee XL |
1 | //! Checking that constant values used in types can be successfully evaluated. |
2 | //! | |
3 | //! For concrete constants, this is fairly simple as we can just try and evaluate it. | |
4 | //! | |
5 | //! When dealing with polymorphic constants, for example `std::mem::size_of::<T>() - 1`, | |
6 | //! this is not as easy. | |
7 | //! | |
8 | //! In this case we try to build an abstract representation of this constant using | |
c295e0f8 | 9 | //! `thir_abstract_const` which can then be checked for structural equality with other |
1b1a35ee | 10 | //! generic constants mentioned in the `caller_bounds` of the current environment. |
487cf647 | 11 | use rustc_hir::def::DefKind; |
1b1a35ee | 12 | use rustc_infer::infer::InferCtxt; |
064997fb | 13 | use rustc_middle::mir::interpret::ErrorHandled; |
064997fb | 14 | |
487cf647 FG |
15 | use rustc_middle::traits::ObligationCause; |
16 | use rustc_middle::ty::abstract_const::NotConstEvaluatable; | |
9ffffee4 | 17 | use rustc_middle::ty::{self, TyCtxt, TypeVisitable, TypeVisitableExt, TypeVisitor}; |
064997fb | 18 | |
487cf647 FG |
19 | use rustc_span::Span; |
20 | use std::ops::ControlFlow; | |
064997fb | 21 | |
487cf647 | 22 | use crate::traits::ObligationCtxt; |
064997fb | 23 | |
1b1a35ee | 24 | /// Check if a given constant can be evaluated. |
5e7ed085 | 25 | #[instrument(skip(infcx), level = "debug")] |
2b03887a FG |
26 | pub fn is_const_evaluatable<'tcx>( |
27 | infcx: &InferCtxt<'tcx>, | |
487cf647 | 28 | unexpanded_ct: ty::Const<'tcx>, |
1b1a35ee XL |
29 | param_env: ty::ParamEnv<'tcx>, |
30 | span: Span, | |
cdc7bbd5 | 31 | ) -> Result<(), NotConstEvaluatable> { |
5e7ed085 | 32 | let tcx = infcx.tcx; |
487cf647 FG |
33 | match tcx.expand_abstract_consts(unexpanded_ct).kind() { |
34 | ty::ConstKind::Unevaluated(_) | ty::ConstKind::Expr(_) => (), | |
2b03887a FG |
35 | ty::ConstKind::Param(_) |
36 | | ty::ConstKind::Bound(_, _) | |
37 | | ty::ConstKind::Placeholder(_) | |
38 | | ty::ConstKind::Value(_) | |
39 | | ty::ConstKind::Error(_) => return Ok(()), | |
40 | ty::ConstKind::Infer(_) => return Err(NotConstEvaluatable::MentionsInfer), | |
41 | }; | |
5e7ed085 FG |
42 | |
43 | if tcx.features().generic_const_exprs { | |
487cf647 FG |
44 | let ct = tcx.expand_abstract_consts(unexpanded_ct); |
45 | ||
46 | let is_anon_ct = if let ty::ConstKind::Unevaluated(uv) = ct.kind() { | |
47 | tcx.def_kind(uv.def.did) == DefKind::AnonConst | |
48 | } else { | |
49 | false | |
50 | }; | |
51 | ||
52 | if !is_anon_ct { | |
53 | if satisfied_from_param_env(tcx, infcx, ct, param_env) { | |
923072b8 FG |
54 | return Ok(()); |
55 | } | |
487cf647 FG |
56 | if ct.has_non_region_infer() { |
57 | return Err(NotConstEvaluatable::MentionsInfer); | |
58 | } else if ct.has_non_region_param() { | |
59 | return Err(NotConstEvaluatable::MentionsParam); | |
1b1a35ee | 60 | } |
1b1a35ee | 61 | } |
487cf647 FG |
62 | |
63 | match unexpanded_ct.kind() { | |
64 | ty::ConstKind::Expr(_) => { | |
65 | // FIXME(generic_const_exprs): we have a `ConstKind::Expr` which is fully concrete, but | |
66 | // currently it is not possible to evaluate `ConstKind::Expr` so we are unable to tell if it | |
67 | // is evaluatable or not. For now we just ICE until this is implemented. | |
68 | Err(NotConstEvaluatable::Error(tcx.sess.delay_span_bug( | |
064997fb | 69 | span, |
487cf647 | 70 | "evaluating `ConstKind::Expr` is not currently supported", |
064997fb FG |
71 | ))) |
72 | } | |
487cf647 FG |
73 | ty::ConstKind::Unevaluated(uv) => { |
74 | let concrete = infcx.const_eval_resolve(param_env, uv, Some(span)); | |
75 | match concrete { | |
76 | Err(ErrorHandled::TooGeneric) => { | |
77 | Err(NotConstEvaluatable::Error(infcx.tcx.sess.delay_span_bug( | |
78 | span, | |
79 | "Missing value for constant, but no error reported?", | |
80 | ))) | |
81 | } | |
82 | Err(ErrorHandled::Reported(e)) => Err(NotConstEvaluatable::Error(e)), | |
83 | Ok(_) => Ok(()), | |
84 | } | |
923072b8 | 85 | } |
487cf647 | 86 | _ => bug!("unexpected constkind in `is_const_evalautable: {unexpanded_ct:?}`"), |
923072b8 FG |
87 | } |
88 | } else { | |
487cf647 FG |
89 | let uv = match unexpanded_ct.kind() { |
90 | ty::ConstKind::Unevaluated(uv) => uv, | |
91 | ty::ConstKind::Expr(_) => { | |
92 | bug!("`ConstKind::Expr` without `feature(generic_const_exprs)` enabled") | |
93 | } | |
94 | _ => bug!("unexpected constkind in `is_const_evalautable: {unexpanded_ct:?}`"), | |
95 | }; | |
96 | ||
923072b8 FG |
97 | // FIXME: We should only try to evaluate a given constant here if it is fully concrete |
98 | // as we don't want to allow things like `[u8; std::mem::size_of::<*mut T>()]`. | |
99 | // | |
100 | // We previously did not check this, so we only emit a future compat warning if | |
101 | // const evaluation succeeds and the given constant is still polymorphic for now | |
102 | // and hopefully soon change this to an error. | |
103 | // | |
104 | // See #74595 for more details about this. | |
f2b60f7d | 105 | let concrete = infcx.const_eval_resolve(param_env, uv, Some(span)); |
923072b8 | 106 | match concrete { |
487cf647 FG |
107 | // If we're evaluating a generic foreign constant, under a nightly compiler while |
108 | // the current crate does not enable `feature(generic_const_exprs)`, abort | |
109 | // compilation with a useful error. | |
110 | Err(_) | |
111 | if tcx.sess.is_nightly_build() | |
112 | && satisfied_from_param_env( | |
113 | tcx, | |
114 | infcx, | |
115 | tcx.expand_abstract_consts(unexpanded_ct), | |
116 | param_env, | |
117 | ) => | |
118 | { | |
119 | tcx.sess | |
120 | .struct_span_fatal( | |
121 | // Slightly better span than just using `span` alone | |
122 | if span == rustc_span::DUMMY_SP { tcx.def_span(uv.def.did) } else { span }, | |
123 | "failed to evaluate generic const expression", | |
124 | ) | |
125 | .note("the crate this constant originates from uses `#![feature(generic_const_exprs)]`") | |
126 | .span_suggestion_verbose( | |
127 | rustc_span::DUMMY_SP, | |
128 | "consider enabling this feature", | |
129 | "#![feature(generic_const_exprs)]\n", | |
130 | rustc_errors::Applicability::MaybeIncorrect, | |
131 | ) | |
132 | .emit() | |
923072b8 | 133 | } |
5e7ed085 | 134 | |
2b03887a FG |
135 | Err(ErrorHandled::TooGeneric) => { |
136 | let err = if uv.has_non_region_infer() { | |
137 | NotConstEvaluatable::MentionsInfer | |
138 | } else if uv.has_non_region_param() { | |
139 | NotConstEvaluatable::MentionsParam | |
140 | } else { | |
9c376795 FG |
141 | let guar = infcx |
142 | .tcx | |
143 | .sess | |
144 | .delay_span_bug(span, "Missing value for constant, but no error reported?"); | |
2b03887a FG |
145 | NotConstEvaluatable::Error(guar) |
146 | }; | |
147 | ||
148 | Err(err) | |
923072b8 FG |
149 | } |
150 | Err(ErrorHandled::Reported(e)) => Err(NotConstEvaluatable::Error(e)), | |
2b03887a | 151 | Ok(_) => Ok(()), |
1b1a35ee | 152 | } |
1b1a35ee XL |
153 | } |
154 | } | |
155 | ||
487cf647 | 156 | #[instrument(skip(infcx, tcx), level = "debug")] |
5e7ed085 FG |
157 | fn satisfied_from_param_env<'tcx>( |
158 | tcx: TyCtxt<'tcx>, | |
487cf647 FG |
159 | infcx: &InferCtxt<'tcx>, |
160 | ct: ty::Const<'tcx>, | |
5e7ed085 | 161 | param_env: ty::ParamEnv<'tcx>, |
487cf647 FG |
162 | ) -> bool { |
163 | // Try to unify with each subtree in the AbstractConst to allow for | |
164 | // `N + 1` being const evaluatable even if theres only a `ConstEvaluatable` | |
165 | // predicate for `(N + 1) * 2` | |
166 | struct Visitor<'a, 'tcx> { | |
167 | ct: ty::Const<'tcx>, | |
168 | param_env: ty::ParamEnv<'tcx>, | |
169 | ||
170 | infcx: &'a InferCtxt<'tcx>, | |
9c376795 | 171 | single_match: Option<Result<ty::Const<'tcx>, ()>>, |
487cf647 | 172 | } |
9c376795 | 173 | |
9ffffee4 | 174 | impl<'a, 'tcx> TypeVisitor<TyCtxt<'tcx>> for Visitor<'a, 'tcx> { |
487cf647 FG |
175 | type BreakTy = (); |
176 | fn visit_const(&mut self, c: ty::Const<'tcx>) -> ControlFlow<Self::BreakTy> { | |
177 | debug!("is_const_evaluatable: candidate={:?}", c); | |
9c376795 | 178 | if self.infcx.probe(|_| { |
487cf647 | 179 | let ocx = ObligationCtxt::new_in_snapshot(self.infcx); |
9c376795 FG |
180 | ocx.eq(&ObligationCause::dummy(), self.param_env, c.ty(), self.ct.ty()).is_ok() |
181 | && ocx.eq(&ObligationCause::dummy(), self.param_env, c, self.ct).is_ok() | |
487cf647 | 182 | && ocx.select_all_or_error().is_empty() |
487cf647 | 183 | }) { |
9c376795 FG |
184 | self.single_match = match self.single_match { |
185 | None => Some(Ok(c)), | |
186 | Some(Ok(o)) if o == c => Some(Ok(c)), | |
187 | Some(_) => Some(Err(())), | |
188 | }; | |
189 | } | |
190 | ||
191 | if let ty::ConstKind::Expr(e) = c.kind() { | |
487cf647 FG |
192 | e.visit_with(self) |
193 | } else { | |
194 | // FIXME(generic_const_exprs): This doesn't recurse into `<T as Trait<U>>::ASSOC`'s substs. | |
195 | // This is currently unobservable as `<T as Trait<{ U + 1 }>>::ASSOC` creates an anon const | |
196 | // with its own `ConstEvaluatable` bound in the param env which we will visit separately. | |
197 | // | |
198 | // If we start allowing directly writing `ConstKind::Expr` without an intermediate anon const | |
199 | // this will be incorrect. It might be worth investigating making `predicates_of` elaborate | |
200 | // all of the `ConstEvaluatable` bounds rather than having a visitor here. | |
9c376795 | 201 | ControlFlow::Continue(()) |
487cf647 FG |
202 | } |
203 | } | |
204 | } | |
205 | ||
9c376795 FG |
206 | let mut single_match: Option<Result<ty::Const<'tcx>, ()>> = None; |
207 | ||
5e7ed085 FG |
208 | for pred in param_env.caller_bounds() { |
209 | match pred.kind().skip_binder() { | |
487cf647 FG |
210 | ty::PredicateKind::ConstEvaluatable(ce) => { |
211 | let b_ct = tcx.expand_abstract_consts(ce); | |
9c376795 FG |
212 | let mut v = Visitor { ct, infcx, param_env, single_match }; |
213 | let _ = b_ct.visit_with(&mut v); | |
487cf647 | 214 | |
9c376795 | 215 | single_match = v.single_match; |
5e7ed085 FG |
216 | } |
217 | _ => {} // don't care | |
218 | } | |
219 | } | |
220 | ||
9c376795 | 221 | if let Some(Ok(c)) = single_match { |
9ffffee4 | 222 | let ocx = ObligationCtxt::new_in_snapshot(infcx); |
9c376795 FG |
223 | assert!(ocx.eq(&ObligationCause::dummy(), param_env, c.ty(), ct.ty()).is_ok()); |
224 | assert!(ocx.eq(&ObligationCause::dummy(), param_env, c, ct).is_ok()); | |
225 | assert!(ocx.select_all_or_error().is_empty()); | |
226 | return true; | |
227 | } | |
228 | ||
487cf647 FG |
229 | debug!("is_const_evaluatable: no"); |
230 | false | |
5e7ed085 | 231 | } |