]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_trait_selection/src/traits/const_evaluatable.rs
Auto merge of #103808 - cjgillot:vec-cache, r=TaKO8Ki
[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_errors::ErrorGuaranteed;
12 use rustc_infer::infer::InferCtxt;
13 use rustc_middle::mir::interpret::ErrorHandled;
14 use rustc_middle::ty::abstract_const::{
15     walk_abstract_const, AbstractConst, FailureKind, Node, NotConstEvaluatable,
16 };
17 use rustc_middle::ty::{self, TyCtxt, TypeVisitable};
18 use rustc_span::Span;
19
20 use std::iter;
21 use std::ops::ControlFlow;
22
23 pub struct ConstUnifyCtxt<'tcx> {
24     pub tcx: TyCtxt<'tcx>,
25     pub param_env: ty::ParamEnv<'tcx>,
26 }
27
28 impl<'tcx> ConstUnifyCtxt<'tcx> {
29     // Substitutes generics repeatedly to allow AbstractConsts to unify where a
30     // ConstKind::Unevaluated could be turned into an AbstractConst that would unify e.g.
31     // Param(N) should unify with Param(T), substs: [Unevaluated("T2", [Unevaluated("T3", [Param(N)])])]
32     #[inline]
33     #[instrument(skip(self), level = "debug")]
34     fn try_replace_substs_in_root(
35         &self,
36         mut abstr_const: AbstractConst<'tcx>,
37     ) -> Option<AbstractConst<'tcx>> {
38         while let Node::Leaf(ct) = abstr_const.root(self.tcx) {
39             match AbstractConst::from_const(self.tcx, ct) {
40                 Ok(Some(act)) => abstr_const = act,
41                 Ok(None) => break,
42                 Err(_) => return None,
43             }
44         }
45
46         Some(abstr_const)
47     }
48
49     /// Tries to unify two abstract constants using structural equality.
50     #[instrument(skip(self), level = "debug")]
51     pub fn try_unify(&self, a: AbstractConst<'tcx>, b: AbstractConst<'tcx>) -> bool {
52         let a = if let Some(a) = self.try_replace_substs_in_root(a) {
53             a
54         } else {
55             return true;
56         };
57
58         let b = if let Some(b) = self.try_replace_substs_in_root(b) {
59             b
60         } else {
61             return true;
62         };
63
64         let a_root = a.root(self.tcx);
65         let b_root = b.root(self.tcx);
66         debug!(?a_root, ?b_root);
67
68         match (a_root, b_root) {
69             (Node::Leaf(a_ct), Node::Leaf(b_ct)) => {
70                 let a_ct = a_ct.eval(self.tcx, self.param_env);
71                 debug!("a_ct evaluated: {:?}", a_ct);
72                 let b_ct = b_ct.eval(self.tcx, self.param_env);
73                 debug!("b_ct evaluated: {:?}", b_ct);
74
75                 if a_ct.ty() != b_ct.ty() {
76                     return false;
77                 }
78
79                 match (a_ct.kind(), b_ct.kind()) {
80                     // We can just unify errors with everything to reduce the amount of
81                     // emitted errors here.
82                     (ty::ConstKind::Error(_), _) | (_, ty::ConstKind::Error(_)) => true,
83                     (ty::ConstKind::Param(a_param), ty::ConstKind::Param(b_param)) => {
84                         a_param == b_param
85                     }
86                     (ty::ConstKind::Value(a_val), ty::ConstKind::Value(b_val)) => a_val == b_val,
87                     // If we have `fn a<const N: usize>() -> [u8; N + 1]` and `fn b<const M: usize>() -> [u8; 1 + M]`
88                     // we do not want to use `assert_eq!(a(), b())` to infer that `N` and `M` have to be `1`. This
89                     // means that we only allow inference variables if they are equal.
90                     (ty::ConstKind::Infer(a_val), ty::ConstKind::Infer(b_val)) => a_val == b_val,
91                     // We expand generic anonymous constants at the start of this function, so this
92                     // branch should only be taking when dealing with associated constants, at
93                     // which point directly comparing them seems like the desired behavior.
94                     //
95                     // FIXME(generic_const_exprs): This isn't actually the case.
96                     // We also take this branch for concrete anonymous constants and
97                     // expand generic anonymous constants with concrete substs.
98                     (ty::ConstKind::Unevaluated(a_uv), ty::ConstKind::Unevaluated(b_uv)) => {
99                         a_uv == b_uv
100                     }
101                     // FIXME(generic_const_exprs): We may want to either actually try
102                     // to evaluate `a_ct` and `b_ct` if they are fully concrete or something like
103                     // this, for now we just return false here.
104                     _ => false,
105                 }
106             }
107             (Node::Binop(a_op, al, ar), Node::Binop(b_op, bl, br)) if a_op == b_op => {
108                 self.try_unify(a.subtree(al), b.subtree(bl))
109                     && self.try_unify(a.subtree(ar), b.subtree(br))
110             }
111             (Node::UnaryOp(a_op, av), Node::UnaryOp(b_op, bv)) if a_op == b_op => {
112                 self.try_unify(a.subtree(av), b.subtree(bv))
113             }
114             (Node::FunctionCall(a_f, a_args), Node::FunctionCall(b_f, b_args))
115                 if a_args.len() == b_args.len() =>
116             {
117                 self.try_unify(a.subtree(a_f), b.subtree(b_f))
118                     && iter::zip(a_args, b_args)
119                         .all(|(&an, &bn)| self.try_unify(a.subtree(an), b.subtree(bn)))
120             }
121             (Node::Cast(a_kind, a_operand, a_ty), Node::Cast(b_kind, b_operand, b_ty))
122                 if (a_ty == b_ty) && (a_kind == b_kind) =>
123             {
124                 self.try_unify(a.subtree(a_operand), b.subtree(b_operand))
125             }
126             // use this over `_ => false` to make adding variants to `Node` less error prone
127             (Node::Cast(..), _)
128             | (Node::FunctionCall(..), _)
129             | (Node::UnaryOp(..), _)
130             | (Node::Binop(..), _)
131             | (Node::Leaf(..), _) => false,
132         }
133     }
134 }
135
136 #[instrument(skip(tcx), level = "debug")]
137 pub fn try_unify_abstract_consts<'tcx>(
138     tcx: TyCtxt<'tcx>,
139     (a, b): (ty::UnevaluatedConst<'tcx>, ty::UnevaluatedConst<'tcx>),
140     param_env: ty::ParamEnv<'tcx>,
141 ) -> bool {
142     (|| {
143         if let Some(a) = AbstractConst::new(tcx, a)? {
144             if let Some(b) = AbstractConst::new(tcx, b)? {
145                 let const_unify_ctxt = ConstUnifyCtxt { tcx, param_env };
146                 return Ok(const_unify_ctxt.try_unify(a, b));
147             }
148         }
149
150         Ok(false)
151     })()
152     .unwrap_or_else(|_: ErrorGuaranteed| true)
153     // FIXME(generic_const_exprs): We should instead have this
154     // method return the resulting `ty::Const` and return `ConstKind::Error`
155     // on `ErrorGuaranteed`.
156 }
157
158 /// Check if a given constant can be evaluated.
159 #[instrument(skip(infcx), level = "debug")]
160 pub fn is_const_evaluatable<'tcx>(
161     infcx: &InferCtxt<'tcx>,
162     ct: ty::Const<'tcx>,
163     param_env: ty::ParamEnv<'tcx>,
164     span: Span,
165 ) -> Result<(), NotConstEvaluatable> {
166     let tcx = infcx.tcx;
167     let uv = match ct.kind() {
168         ty::ConstKind::Unevaluated(uv) => uv,
169         ty::ConstKind::Param(_)
170         | ty::ConstKind::Bound(_, _)
171         | ty::ConstKind::Placeholder(_)
172         | ty::ConstKind::Value(_)
173         | ty::ConstKind::Error(_) => return Ok(()),
174         ty::ConstKind::Infer(_) => return Err(NotConstEvaluatable::MentionsInfer),
175     };
176
177     if tcx.features().generic_const_exprs {
178         if let Some(ct) = AbstractConst::new(tcx, uv)? {
179             if satisfied_from_param_env(tcx, ct, param_env)? {
180                 return Ok(());
181             }
182             match ct.unify_failure_kind(tcx) {
183                 FailureKind::MentionsInfer => {
184                     return Err(NotConstEvaluatable::MentionsInfer);
185                 }
186                 FailureKind::MentionsParam => {
187                     return Err(NotConstEvaluatable::MentionsParam);
188                 }
189                 // returned below
190                 FailureKind::Concrete => {}
191             }
192         }
193         let concrete = infcx.const_eval_resolve(param_env, uv, Some(span));
194         match concrete {
195             Err(ErrorHandled::TooGeneric) => Err(NotConstEvaluatable::Error(
196                 infcx
197                     .tcx
198                     .sess
199                     .delay_span_bug(span, "Missing value for constant, but no error reported?"),
200             )),
201             Err(ErrorHandled::Reported(e)) => Err(NotConstEvaluatable::Error(e)),
202             Ok(_) => Ok(()),
203         }
204     } else {
205         // FIXME: We should only try to evaluate a given constant here if it is fully concrete
206         // as we don't want to allow things like `[u8; std::mem::size_of::<*mut T>()]`.
207         //
208         // We previously did not check this, so we only emit a future compat warning if
209         // const evaluation succeeds and the given constant is still polymorphic for now
210         // and hopefully soon change this to an error.
211         //
212         // See #74595 for more details about this.
213         let concrete = infcx.const_eval_resolve(param_env, uv, Some(span));
214
215         match concrete {
216           // If we're evaluating a foreign constant, under a nightly compiler without generic
217           // const exprs, AND it would've passed if that expression had been evaluated with
218           // generic const exprs, then suggest using generic const exprs.
219           Err(_) if tcx.sess.is_nightly_build()
220             && let Ok(Some(ct)) = AbstractConst::new(tcx, uv)
221             && satisfied_from_param_env(tcx, ct, param_env) == Ok(true) => {
222               tcx.sess
223                   .struct_span_fatal(
224                       // Slightly better span than just using `span` alone
225                       if span == rustc_span::DUMMY_SP { tcx.def_span(uv.def.did) } else { span },
226                       "failed to evaluate generic const expression",
227                   )
228                   .note("the crate this constant originates from uses `#![feature(generic_const_exprs)]`")
229                   .span_suggestion_verbose(
230                       rustc_span::DUMMY_SP,
231                       "consider enabling this feature",
232                       "#![feature(generic_const_exprs)]\n",
233                       rustc_errors::Applicability::MaybeIncorrect,
234                   )
235                   .emit()
236             }
237
238             Err(ErrorHandled::TooGeneric) => {
239                 let err = if uv.has_non_region_infer() {
240                     NotConstEvaluatable::MentionsInfer
241                 } else if uv.has_non_region_param() {
242                     NotConstEvaluatable::MentionsParam
243                 } else {
244                     let guar = infcx.tcx.sess.delay_span_bug(span, format!("Missing value for constant, but no error reported?"));
245                     NotConstEvaluatable::Error(guar)
246                 };
247
248                 Err(err)
249             },
250             Err(ErrorHandled::Reported(e)) => Err(NotConstEvaluatable::Error(e)),
251             Ok(_) => Ok(()),
252         }
253     }
254 }
255
256 #[instrument(skip(tcx), level = "debug")]
257 fn satisfied_from_param_env<'tcx>(
258     tcx: TyCtxt<'tcx>,
259     ct: AbstractConst<'tcx>,
260     param_env: ty::ParamEnv<'tcx>,
261 ) -> Result<bool, NotConstEvaluatable> {
262     for pred in param_env.caller_bounds() {
263         match pred.kind().skip_binder() {
264             ty::PredicateKind::ConstEvaluatable(uv) => {
265                 if let Some(b_ct) = AbstractConst::from_const(tcx, uv)? {
266                     let const_unify_ctxt = ConstUnifyCtxt { tcx, param_env };
267
268                     // Try to unify with each subtree in the AbstractConst to allow for
269                     // `N + 1` being const evaluatable even if theres only a `ConstEvaluatable`
270                     // predicate for `(N + 1) * 2`
271                     let result = walk_abstract_const(tcx, b_ct, |b_ct| {
272                         match const_unify_ctxt.try_unify(ct, b_ct) {
273                             true => ControlFlow::BREAK,
274                             false => ControlFlow::CONTINUE,
275                         }
276                     });
277
278                     if let ControlFlow::Break(()) = result {
279                         debug!("is_const_evaluatable: abstract_const ~~> ok");
280                         return Ok(true);
281                     }
282                 }
283             }
284             _ => {} // don't care
285         }
286     }
287
288     Ok(false)
289 }