@@ -23,6 +23,9 @@ use rustc_session::lint;
2323use rustc_span:: def_id:: { DefId , LocalDefId } ;
2424use rustc_span:: Span ;
2525
26+ use std:: cmp;
27+
28+ /// Check if a given constant can be evaluated.
2629pub fn is_const_evaluatable < ' cx , ' tcx > (
2730 infcx : & InferCtxt < ' cx , ' tcx > ,
2831 def : ty:: WithOptConstParam < DefId > ,
@@ -32,23 +35,87 @@ pub fn is_const_evaluatable<'cx, 'tcx>(
3235) -> Result < ( ) , ErrorHandled > {
3336 debug ! ( "is_const_evaluatable({:?}, {:?})" , def, substs) ;
3437 if infcx. tcx . features ( ) . const_evaluatable_checked {
35- if let Some ( ct) = AbstractConst :: new ( infcx. tcx , def, substs) ? {
36- for pred in param_env. caller_bounds ( ) {
37- match pred. skip_binders ( ) {
38- ty:: PredicateAtom :: ConstEvaluatable ( b_def, b_substs) => {
39- debug ! ( "is_const_evaluatable: caller_bound={:?}, {:?}" , b_def, b_substs) ;
40- if b_def == def && b_substs == substs {
41- debug ! ( "is_const_evaluatable: caller_bound ~~> ok" ) ;
42- return Ok ( ( ) ) ;
43- } else if AbstractConst :: new ( infcx. tcx , b_def, b_substs) ?
44- . map_or ( false , |b_ct| try_unify ( infcx. tcx , ct, b_ct) )
45- {
46- debug ! ( "is_const_evaluatable: abstract_const ~~> ok" ) ;
47- return Ok ( ( ) ) ;
38+ let tcx = infcx. tcx ;
39+ match AbstractConst :: new ( tcx, def, substs) ? {
40+ // We are looking at a generic abstract constant.
41+ Some ( ct) => {
42+ for pred in param_env. caller_bounds ( ) {
43+ match pred. skip_binders ( ) {
44+ ty:: PredicateAtom :: ConstEvaluatable ( b_def, b_substs) => {
45+ debug ! (
46+ "is_const_evaluatable: caller_bound={:?}, {:?}" ,
47+ b_def, b_substs
48+ ) ;
49+ if b_def == def && b_substs == substs {
50+ debug ! ( "is_const_evaluatable: caller_bound ~~> ok" ) ;
51+ return Ok ( ( ) ) ;
52+ } else if AbstractConst :: new ( tcx, b_def, b_substs) ?
53+ . map_or ( false , |b_ct| try_unify ( tcx, ct, b_ct) )
54+ {
55+ debug ! ( "is_const_evaluatable: abstract_const ~~> ok" ) ;
56+ return Ok ( ( ) ) ;
57+ }
4858 }
59+ _ => { } // don't care
4960 }
50- _ => { } // don't care
5161 }
62+
63+ // We were unable to unify the abstract constant with
64+ // a constant found in the caller bounds, there are
65+ // now three possible cases here.
66+ //
67+ // - The substs are concrete enough that we can simply
68+ // try and evaluate the given constant.
69+ // - The abstract const still references an inference
70+ // variable, in this case we return `TooGeneric`.
71+ // - The abstract const references a generic parameter,
72+ // this means that we emit an error here.
73+ #[ derive( Debug , Copy , Clone , PartialEq , Eq , PartialOrd , Ord ) ]
74+ enum FailureKind {
75+ MentionsInfer ,
76+ MentionsParam ,
77+ Concrete ,
78+ }
79+ let mut failure_kind = FailureKind :: Concrete ;
80+ walk_abstract_const ( tcx, ct, |node| match node {
81+ Node :: Leaf ( leaf) => {
82+ let leaf = leaf. subst ( tcx, ct. substs ) ;
83+ if leaf. has_infer_types_or_consts ( ) {
84+ failure_kind = FailureKind :: MentionsInfer ;
85+ } else if leaf. has_param_types_or_consts ( ) {
86+ failure_kind = cmp:: min ( failure_kind, FailureKind :: MentionsParam ) ;
87+ }
88+ }
89+ Node :: Binop ( _, _, _) | Node :: UnaryOp ( _, _) | Node :: FunctionCall ( _, _) => ( ) ,
90+ } ) ;
91+
92+ match failure_kind {
93+ FailureKind :: MentionsInfer => {
94+ return Err ( ErrorHandled :: TooGeneric ) ;
95+ }
96+ FailureKind :: MentionsParam => {
97+ // FIXME(const_evaluatable_checked): Better error message.
98+ infcx
99+ . tcx
100+ . sess
101+ . struct_span_err ( span, "unconstrained generic constant" )
102+ . span_help (
103+ tcx. def_span ( def. did ) ,
104+ "consider adding a `where` bound for this expression" ,
105+ )
106+ . emit ( ) ;
107+ return Err ( ErrorHandled :: Reported ( ErrorReported ) ) ;
108+ }
109+ FailureKind :: Concrete => {
110+ // Dealt with below by the same code which handles this
111+ // without the feature gate.
112+ }
113+ }
114+ }
115+ None => {
116+ // If we are dealing with a concrete constant, we can
117+ // reuse the old code path and try to evaluate
118+ // the constant.
52119 }
53120 }
54121 }
@@ -95,7 +162,36 @@ pub fn is_const_evaluatable<'cx, 'tcx>(
95162 }
96163
97164 debug ! ( ?concrete, "is_const_evaluatable" ) ;
98- concrete. map ( drop)
165+ match concrete {
166+ Err ( ErrorHandled :: TooGeneric ) if !substs. has_infer_types_or_consts ( ) => {
167+ // FIXME(const_evaluatable_checked): We really should move
168+ // emitting this error message to fulfill instead. For
169+ // now this is easier.
170+ //
171+ // This is not a problem without `const_evaluatable_checked` as
172+ // all `ConstEvaluatable` predicates have to be fulfilled for compilation
173+ // to succeed.
174+ //
175+ // @lcnr: We already emit an error for things like
176+ // `fn test<const N: usize>() -> [0 - N]` eagerly here,
177+ // so until we fix this I don't really care.
178+
179+ let mut err = infcx
180+ . tcx
181+ . sess
182+ . struct_span_err ( span, "constant expression depends on a generic parameter" ) ;
183+ // FIXME(const_generics): we should suggest to the user how they can resolve this
184+ // issue. However, this is currently not actually possible
185+ // (see https://github.com/rust-lang/rust/issues/66962#issuecomment-575907083).
186+ //
187+ // Note that with `feature(const_evaluatable_checked)` this case should not
188+ // be reachable.
189+ err. note ( "this may fail depending on what value the parameter takes" ) ;
190+ err. emit ( ) ;
191+ Err ( ErrorHandled :: Reported ( ErrorReported ) )
192+ }
193+ c => c. map ( drop) ,
194+ }
99195}
100196
101197/// A tree representing an anonymous constant.
@@ -421,6 +517,33 @@ pub(super) fn try_unify_abstract_consts<'tcx>(
421517 // on `ErrorReported`.
422518}
423519
520+ fn walk_abstract_const < ' tcx , F > ( tcx : TyCtxt < ' tcx > , ct : AbstractConst < ' tcx > , mut f : F )
521+ where
522+ F : FnMut ( Node < ' tcx > ) ,
523+ {
524+ recurse ( tcx, ct, & mut f) ;
525+ fn recurse < ' tcx > ( tcx : TyCtxt < ' tcx > , ct : AbstractConst < ' tcx > , f : & mut dyn FnMut ( Node < ' tcx > ) ) {
526+ let root = ct. root ( ) ;
527+ f ( root) ;
528+ match root {
529+ Node :: Leaf ( _) => ( ) ,
530+ Node :: Binop ( _, l, r) => {
531+ recurse ( tcx, ct. subtree ( l) , f) ;
532+ recurse ( tcx, ct. subtree ( r) , f) ;
533+ }
534+ Node :: UnaryOp ( _, v) => {
535+ recurse ( tcx, ct. subtree ( v) , f) ;
536+ }
537+ Node :: FunctionCall ( func, args) => {
538+ recurse ( tcx, ct. subtree ( func) , f) ;
539+ for & arg in args {
540+ recurse ( tcx, ct. subtree ( arg) , f) ;
541+ }
542+ }
543+ }
544+ }
545+ }
546+
424547/// Tries to unify two abstract constants using structural equality.
425548pub ( super ) fn try_unify < ' tcx > (
426549 tcx : TyCtxt < ' tcx > ,
0 commit comments