]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_trait_selection/src/traits/const_evaluatable.rs
Make `expand_abstract_consts` infallible
[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     ct: ty::Const<'tcx>,
29     param_env: ty::ParamEnv<'tcx>,
30     span: Span,
31 ) -> Result<(), NotConstEvaluatable> {
32     let tcx = infcx.tcx;
33     let uv = match ct.kind() {
34         ty::ConstKind::Unevaluated(uv) => uv,
35         // FIXME(generic_const_exprs): this seems wrong but I couldn't find a way to get this to trigger
36         ty::ConstKind::Expr(_) => bug!("unexpected expr in `is_const_evaluatable: {ct:?}"),
37         ty::ConstKind::Param(_)
38         | ty::ConstKind::Bound(_, _)
39         | ty::ConstKind::Placeholder(_)
40         | ty::ConstKind::Value(_)
41         | ty::ConstKind::Error(_) => return Ok(()),
42         ty::ConstKind::Infer(_) => return Err(NotConstEvaluatable::MentionsInfer),
43     };
44
45     if tcx.features().generic_const_exprs {
46         let ct = tcx.expand_abstract_consts(ct);
47
48         let is_anon_ct = if let ty::ConstKind::Unevaluated(uv) = ct.kind() {
49             tcx.def_kind(uv.def.did) == DefKind::AnonConst
50         } else {
51             false
52         };
53
54         if !is_anon_ct {
55             if satisfied_from_param_env(tcx, infcx, ct, param_env)? {
56                 return Ok(());
57             }
58             if ct.has_non_region_infer() {
59                 return Err(NotConstEvaluatable::MentionsInfer);
60             } else if ct.has_non_region_param() {
61                 return Err(NotConstEvaluatable::MentionsParam);
62             }
63         }
64
65         let concrete = infcx.const_eval_resolve(param_env, uv, Some(span));
66         match concrete {
67             Err(ErrorHandled::TooGeneric) => Err(NotConstEvaluatable::Error(
68                 infcx
69                     .tcx
70                     .sess
71                     .delay_span_bug(span, "Missing value for constant, but no error reported?"),
72             )),
73             Err(ErrorHandled::Reported(e)) => Err(NotConstEvaluatable::Error(e)),
74             Ok(_) => Ok(()),
75         }
76     } else {
77         // FIXME: We should only try to evaluate a given constant here if it is fully concrete
78         // as we don't want to allow things like `[u8; std::mem::size_of::<*mut T>()]`.
79         //
80         // We previously did not check this, so we only emit a future compat warning if
81         // const evaluation succeeds and the given constant is still polymorphic for now
82         // and hopefully soon change this to an error.
83         //
84         // See #74595 for more details about this.
85         let concrete = infcx.const_eval_resolve(param_env, uv, Some(span));
86         match concrete {
87             // If we're evaluating a generic foreign constant, under a nightly compiler while
88             // the current crate does not enable `feature(generic_const_exprs)`, abort
89             // compilation with a useful error.
90             Err(_) if tcx.sess.is_nightly_build()
91                 && let ty::ConstKind::Expr(_) = tcx.expand_abstract_consts(ct).kind() =>
92             {
93                 tcx.sess
94                     .struct_span_fatal(
95                         // Slightly better span than just using `span` alone
96                         if span == rustc_span::DUMMY_SP { tcx.def_span(uv.def.did) } else { span },
97                         "failed to evaluate generic const expression",
98                     )
99                     .note("the crate this constant originates from uses `#![feature(generic_const_exprs)]`")
100                     .span_suggestion_verbose(
101                         rustc_span::DUMMY_SP,
102                         "consider enabling this feature",
103                         "#![feature(generic_const_exprs)]\n",
104                         rustc_errors::Applicability::MaybeIncorrect,
105                     )
106                     .emit()
107             }
108
109             Err(ErrorHandled::TooGeneric) => {
110                 let err = if uv.has_non_region_infer() {
111                     NotConstEvaluatable::MentionsInfer
112                 } else if uv.has_non_region_param() {
113                     NotConstEvaluatable::MentionsParam
114                 } else {
115                     let guar = infcx.tcx.sess.delay_span_bug(span, format!("Missing value for constant, but no error reported?"));
116                     NotConstEvaluatable::Error(guar)
117                 };
118
119                 Err(err)
120             },
121             Err(ErrorHandled::Reported(e)) => Err(NotConstEvaluatable::Error(e)),
122             Ok(_) => Ok(()),
123         }
124     }
125 }
126
127 #[instrument(skip(infcx, tcx), level = "debug")]
128 fn satisfied_from_param_env<'tcx>(
129     tcx: TyCtxt<'tcx>,
130     infcx: &InferCtxt<'tcx>,
131     ct: ty::Const<'tcx>,
132     param_env: ty::ParamEnv<'tcx>,
133 ) -> Result<bool, NotConstEvaluatable> {
134     // Try to unify with each subtree in the AbstractConst to allow for
135     // `N + 1` being const evaluatable even if theres only a `ConstEvaluatable`
136     // predicate for `(N + 1) * 2`
137     struct Visitor<'a, 'tcx> {
138         ct: ty::Const<'tcx>,
139         param_env: ty::ParamEnv<'tcx>,
140
141         infcx: &'a InferCtxt<'tcx>,
142     }
143     impl<'a, 'tcx> TypeVisitor<'tcx> for Visitor<'a, 'tcx> {
144         type BreakTy = ();
145         fn visit_const(&mut self, c: ty::Const<'tcx>) -> ControlFlow<Self::BreakTy> {
146             if let Ok(()) = self.infcx.commit_if_ok(|_| {
147                 let ocx = ObligationCtxt::new_in_snapshot(self.infcx);
148                 if let Ok(()) = ocx.eq(&ObligationCause::dummy(), self.param_env, c.ty(), self.ct.ty())
149                     && let Ok(()) = ocx.eq(&ObligationCause::dummy(), self.param_env, c, self.ct)
150                     && ocx.select_all_or_error().is_empty()
151                 {
152                     Ok(())
153                 } else {
154                     Err(())
155                 }
156             }) {
157                 ControlFlow::BREAK
158             } else if let ty::ConstKind::Expr(e) = c.kind() {
159                 e.visit_with(self)
160             } else {
161                 // FIXME(generic_const_exprs): This doesn't recurse into `<T as Trait<U>>::ASSOC`'s substs.
162                 // This is currently unobservable as `<T as Trait<{ U + 1 }>>::ASSOC` creates an anon const
163                 // with its own `ConstEvaluatable` bound in the param env which we will visit separately.
164                 //
165                 // If we start allowing directly writing `ConstKind::Expr` without an intermediate anon const
166                 // this will be incorrect. It might be worth investigating making `predicates_of` elaborate
167                 // all of the `ConstEvaluatable` bounds rather than having a visitor here.
168                 ControlFlow::CONTINUE
169             }
170         }
171     }
172
173     for pred in param_env.caller_bounds() {
174         match pred.kind().skip_binder() {
175             ty::PredicateKind::ConstEvaluatable(ce) => {
176                 let b_ct = tcx.expand_abstract_consts(ce);
177                 let mut v = Visitor { ct, infcx, param_env };
178                 let result = b_ct.visit_with(&mut v);
179
180                 if let ControlFlow::Break(()) = result {
181                     debug!("is_const_evaluatable: abstract_const ~~> ok");
182                     return Ok(true);
183                 }
184             }
185             _ => {} // don't care
186         }
187     }
188
189     Ok(false)
190 }