rustc_trait_selection/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
12use rustc_hir::def::DefKind;
13use rustc_infer::infer::InferCtxt;
14use rustc_middle::bug;
15use rustc_middle::traits::ObligationCause;
16use rustc_middle::ty::abstract_const::NotConstEvaluatable;
17use rustc_middle::ty::{self, TyCtxt, TypeVisitable, TypeVisitableExt, TypeVisitor};
18use rustc_span::{DUMMY_SP, Span};
19use tracing::{debug, instrument};
20
21use super::EvaluateConstErr;
22use crate::traits::ObligationCtxt;
23
24/// Check if a given constant can be evaluated.
25#[instrument(skip(infcx), level = "debug")]
26pub 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) == 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 fully concrete `ConstKind::Expr`, but
66                // haven't implemented evaluating `ConstKind::Expr` yet, so we are unable to tell
67                // if it is evaluatable or not. As this is unreachable for now, we can simple ICE
68                // here.
69                tcx.dcx().span_bug(span, "evaluating `ConstKind::Expr` is not currently supported");
70            }
71            ty::ConstKind::Unevaluated(_) => {
72                match crate::traits::try_evaluate_const(infcx, unexpanded_ct, param_env) {
73                    Err(EvaluateConstErr::HasGenericsOrInfers) => {
74                        Err(NotConstEvaluatable::Error(infcx.dcx().span_delayed_bug(
75                            span,
76                            "Missing value for constant, but no error reported?",
77                        )))
78                    }
79                    Err(
80                        EvaluateConstErr::EvaluationFailure(e)
81                        | EvaluateConstErr::InvalidConstParamTy(e),
82                    ) => 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        match crate::traits::try_evaluate_const(infcx, unexpanded_ct, param_env) {
98            // If we're evaluating a generic foreign constant, under a nightly compiler while
99            // the current crate does not enable `feature(generic_const_exprs)`, abort
100            // compilation with a useful error.
101            Err(_)
102                if tcx.sess.is_nightly_build()
103                    && satisfied_from_param_env(
104                        tcx,
105                        infcx,
106                        tcx.expand_abstract_consts(unexpanded_ct),
107                        param_env,
108                    ) =>
109            {
110                tcx.dcx()
111                    .struct_span_fatal(
112                        // Slightly better span than just using `span` alone
113                        if span == DUMMY_SP { tcx.def_span(uv.def) } else { span },
114                        "failed to evaluate generic const expression",
115                    )
116                    .with_note("the crate this constant originates from uses `#![feature(generic_const_exprs)]`")
117                    .with_span_suggestion_verbose(
118                        DUMMY_SP,
119                        "consider enabling this feature",
120                        "#![feature(generic_const_exprs)]\n",
121                        rustc_errors::Applicability::MaybeIncorrect,
122                    )
123                    .emit()
124            }
125
126            Err(EvaluateConstErr::HasGenericsOrInfers) => {
127                let err = if uv.has_non_region_infer() {
128                    NotConstEvaluatable::MentionsInfer
129                } else if uv.has_non_region_param() {
130                    NotConstEvaluatable::MentionsParam
131                } else {
132                    let guar = infcx.dcx().span_delayed_bug(
133                        span,
134                        "Missing value for constant, but no error reported?",
135                    );
136                    NotConstEvaluatable::Error(guar)
137                };
138
139                Err(err)
140            }
141            Err(
142                EvaluateConstErr::EvaluationFailure(e) | EvaluateConstErr::InvalidConstParamTy(e),
143            ) => Err(NotConstEvaluatable::Error(e)),
144            Ok(_) => Ok(()),
145        }
146    }
147}
148
149#[instrument(skip(infcx, tcx), level = "debug")]
150fn satisfied_from_param_env<'tcx>(
151    tcx: TyCtxt<'tcx>,
152    infcx: &InferCtxt<'tcx>,
153    ct: ty::Const<'tcx>,
154    param_env: ty::ParamEnv<'tcx>,
155) -> bool {
156    // Try to unify with each subtree in the AbstractConst to allow for
157    // `N + 1` being const evaluatable even if theres only a `ConstEvaluatable`
158    // predicate for `(N + 1) * 2`
159    struct Visitor<'a, 'tcx> {
160        ct: ty::Const<'tcx>,
161        param_env: ty::ParamEnv<'tcx>,
162
163        infcx: &'a InferCtxt<'tcx>,
164        single_match: Option<Result<ty::Const<'tcx>, ()>>,
165    }
166
167    impl<'a, 'tcx> TypeVisitor<TyCtxt<'tcx>> for Visitor<'a, 'tcx> {
168        fn visit_const(&mut self, c: ty::Const<'tcx>) {
169            debug!("is_const_evaluatable: candidate={:?}", c);
170            if self.infcx.probe(|_| {
171                let ocx = ObligationCtxt::new(self.infcx);
172                ocx.eq(&ObligationCause::dummy(), self.param_env, c, self.ct).is_ok()
173                    && ocx.select_all_or_error().is_empty()
174            }) {
175                self.single_match = match self.single_match {
176                    None => Some(Ok(c)),
177                    Some(Ok(o)) if o == c => Some(Ok(c)),
178                    Some(_) => Some(Err(())),
179                };
180            }
181
182            if let ty::ConstKind::Expr(e) = c.kind() {
183                e.visit_with(self);
184            } else {
185                // FIXME(generic_const_exprs): This doesn't recurse into `<T as Trait<U>>::ASSOC`'s args.
186                // This is currently unobservable as `<T as Trait<{ U + 1 }>>::ASSOC` creates an anon const
187                // with its own `ConstEvaluatable` bound in the param env which we will visit separately.
188                //
189                // If we start allowing directly writing `ConstKind::Expr` without an intermediate anon const
190                // this will be incorrect. It might be worth investigating making `predicates_of` elaborate
191                // all of the `ConstEvaluatable` bounds rather than having a visitor here.
192            }
193        }
194    }
195
196    let mut single_match: Option<Result<ty::Const<'tcx>, ()>> = None;
197
198    for pred in param_env.caller_bounds() {
199        match pred.kind().skip_binder() {
200            ty::ClauseKind::ConstEvaluatable(ce) => {
201                let b_ct = tcx.expand_abstract_consts(ce);
202                let mut v = Visitor { ct, infcx, param_env, single_match };
203                let _ = b_ct.visit_with(&mut v);
204
205                single_match = v.single_match;
206            }
207            _ => {} // don't care
208        }
209    }
210
211    if let Some(Ok(c)) = single_match {
212        let ocx = ObligationCtxt::new(infcx);
213        assert!(ocx.eq(&ObligationCause::dummy(), param_env, c, ct).is_ok());
214        assert!(ocx.select_all_or_error().is_empty());
215        return true;
216    }
217
218    debug!("is_const_evaluatable: no");
219    false
220}