]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_trait_selection/src/traits/const_evaluatable.rs
Rollup merge of #105882 - compiler-errors:issue-105832, r=jackh726
[rust.git] / compiler / rustc_trait_selection / src / traits / const_evaluatable.rs
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
9 //! `thir_abstract_const` which can then be checked for structural equality with other
10 //! generic constants mentioned in the `caller_bounds` of the current environment.
11 use rustc_hir::def::DefKind;
12 use rustc_infer::infer::InferCtxt;
13 use rustc_middle::mir::interpret::ErrorHandled;
14
15 use rustc_middle::traits::ObligationCause;
16 use rustc_middle::ty::abstract_const::NotConstEvaluatable;
17 use rustc_middle::ty::{self, TyCtxt, TypeVisitable, TypeVisitor};
18
19 use rustc_span::Span;
20 use std::ops::ControlFlow;
21
22 use crate::traits::ObligationCtxt;
23
24 /// Check if a given constant can be evaluated.
25 #[instrument(skip(infcx), level = "debug")]
26 pub fn is_const_evaluatable<'tcx>(
27     infcx: &InferCtxt<'tcx>,
28     unexpanded_ct: ty::Const<'tcx>,
29     param_env: ty::ParamEnv<'tcx>,
30     span: Span,
31 ) -> Result<(), NotConstEvaluatable> {
32     let tcx = infcx.tcx;
33     match tcx.expand_abstract_consts(unexpanded_ct).kind() {
34         ty::ConstKind::Unevaluated(_) | ty::ConstKind::Expr(_) => (),
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     };
42
43     if tcx.features().generic_const_exprs {
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) {
54                 return Ok(());
55             }
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);
60             }
61         }
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(
69                     span,
70                     "evaluating `ConstKind::Expr` is not currently supported",
71                 )))
72             }
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                 }
85             }
86             _ => bug!("unexpected constkind in `is_const_evalautable: {unexpanded_ct:?}`"),
87         }
88     } else {
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
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.
105         let concrete = infcx.const_eval_resolve(param_env, uv, Some(span));
106         match concrete {
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()
133             }
134
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 {
141                     let guar = infcx
142                         .tcx
143                         .sess
144                         .delay_span_bug(span, "Missing value for constant, but no error reported?");
145                     NotConstEvaluatable::Error(guar)
146                 };
147
148                 Err(err)
149             }
150             Err(ErrorHandled::Reported(e)) => Err(NotConstEvaluatable::Error(e)),
151             Ok(_) => Ok(()),
152         }
153     }
154 }
155
156 #[instrument(skip(infcx, tcx), level = "debug")]
157 fn satisfied_from_param_env<'tcx>(
158     tcx: TyCtxt<'tcx>,
159     infcx: &InferCtxt<'tcx>,
160     ct: ty::Const<'tcx>,
161     param_env: ty::ParamEnv<'tcx>,
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>,
171     }
172     impl<'a, 'tcx> TypeVisitor<'tcx> for Visitor<'a, 'tcx> {
173         type BreakTy = ();
174         fn visit_const(&mut self, c: ty::Const<'tcx>) -> ControlFlow<Self::BreakTy> {
175             debug!("is_const_evaluatable: candidate={:?}", c);
176             if let Ok(()) = self.infcx.commit_if_ok(|_| {
177                 let ocx = ObligationCtxt::new_in_snapshot(self.infcx);
178                 if let Ok(()) = ocx.eq(&ObligationCause::dummy(), self.param_env, c.ty(), self.ct.ty())
179                     && let Ok(()) = ocx.eq(&ObligationCause::dummy(), self.param_env, c, self.ct)
180                     && ocx.select_all_or_error().is_empty()
181                 {
182                     Ok(())
183                 } else {
184                     Err(())
185                 }
186             }) {
187                 ControlFlow::BREAK
188             } else if let ty::ConstKind::Expr(e) = c.kind() {
189                 e.visit_with(self)
190             } else {
191                 // FIXME(generic_const_exprs): This doesn't recurse into `<T as Trait<U>>::ASSOC`'s substs.
192                 // This is currently unobservable as `<T as Trait<{ U + 1 }>>::ASSOC` creates an anon const
193                 // with its own `ConstEvaluatable` bound in the param env which we will visit separately.
194                 //
195                 // If we start allowing directly writing `ConstKind::Expr` without an intermediate anon const
196                 // this will be incorrect. It might be worth investigating making `predicates_of` elaborate
197                 // all of the `ConstEvaluatable` bounds rather than having a visitor here.
198                 ControlFlow::CONTINUE
199             }
200         }
201     }
202
203     for pred in param_env.caller_bounds() {
204         match pred.kind().skip_binder() {
205             ty::PredicateKind::ConstEvaluatable(ce) => {
206                 let b_ct = tcx.expand_abstract_consts(ce);
207                 let mut v = Visitor { ct, infcx, param_env };
208                 let result = b_ct.visit_with(&mut v);
209
210                 if let ControlFlow::Break(()) = result {
211                     debug!("is_const_evaluatable: yes");
212                     return true;
213                 }
214             }
215             _ => {} // don't care
216         }
217     }
218
219     debug!("is_const_evaluatable: no");
220     false
221 }