@@ -10,8 +10,10 @@ use rustc_hir::{BinOp, BinOpKind, Block, ConstBlock, Expr, ExprKind, HirId, Item
1010use rustc_lexer:: tokenize;
1111use rustc_lint:: LateContext ;
1212use rustc_middle:: mir:: interpret:: { alloc_range, Scalar } ;
13+ use rustc_middle:: mir:: ConstValue ;
1314use rustc_middle:: ty:: { self , EarlyBinder , FloatTy , GenericArgsRef , IntTy , List , ScalarInt , Ty , TyCtxt , UintTy } ;
1415use rustc_middle:: { bug, mir, span_bug} ;
16+ use rustc_span:: def_id:: DefId ;
1517use rustc_span:: symbol:: { Ident , Symbol } ;
1618use rustc_span:: SyntaxContext ;
1719use rustc_target:: abi:: Size ;
@@ -307,6 +309,12 @@ impl ConstantSource {
307309 }
308310}
309311
312+ /// Attempts to check whether the expression is a constant representing an empty slice, str, array,
313+ /// etc…
314+ pub fn constant_is_empty ( lcx : & LateContext < ' _ > , e : & Expr < ' _ > ) -> Option < bool > {
315+ ConstEvalLateContext :: new ( lcx, lcx. typeck_results ( ) ) . expr_is_empty ( e)
316+ }
317+
310318/// Attempts to evaluate the expression as a constant.
311319pub fn constant < ' tcx > (
312320 lcx : & LateContext < ' tcx > ,
@@ -406,7 +414,13 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
406414 match e. kind {
407415 ExprKind :: ConstBlock ( ConstBlock { body, .. } ) => self . expr ( self . lcx . tcx . hir ( ) . body ( body) . value ) ,
408416 ExprKind :: DropTemps ( e) => self . expr ( e) ,
409- ExprKind :: Path ( ref qpath) => self . fetch_path ( qpath, e. hir_id , self . typeck_results . expr_ty ( e) ) ,
417+ ExprKind :: Path ( ref qpath) => {
418+ self . fetch_path_and_apply ( qpath, e. hir_id , self . typeck_results . expr_ty ( e) , |this, result| {
419+ let result = mir_to_const ( this. lcx , result) ?;
420+ this. source = ConstantSource :: Constant ;
421+ Some ( result)
422+ } )
423+ } ,
410424 ExprKind :: Block ( block, _) => self . block ( block) ,
411425 ExprKind :: Lit ( lit) => {
412426 if is_direct_expn_of ( e. span , "cfg" ) . is_some ( ) {
@@ -472,6 +486,49 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
472486 }
473487 }
474488
489+ /// Simple constant folding to determine if an expression is an empty slice, str, array, …
490+ /// `None` will be returned if the constness cannot be determined, or if the resolution
491+ /// leaves the local crate.
492+ pub fn expr_is_empty ( & mut self , e : & Expr < ' _ > ) -> Option < bool > {
493+ match e. kind {
494+ ExprKind :: ConstBlock ( ConstBlock { body, .. } ) => self . expr_is_empty ( self . lcx . tcx . hir ( ) . body ( body) . value ) ,
495+ ExprKind :: DropTemps ( e) => self . expr_is_empty ( e) ,
496+ ExprKind :: Path ( ref qpath) => {
497+ if !self
498+ . typeck_results
499+ . qpath_res ( qpath, e. hir_id )
500+ . opt_def_id ( )
501+ . is_some_and ( DefId :: is_local)
502+ {
503+ return None ;
504+ }
505+ self . fetch_path_and_apply ( qpath, e. hir_id , self . typeck_results . expr_ty ( e) , |this, result| {
506+ mir_is_empty ( this. lcx , result)
507+ } )
508+ } ,
509+ ExprKind :: Lit ( lit) => {
510+ if is_direct_expn_of ( e. span , "cfg" ) . is_some ( ) {
511+ None
512+ } else {
513+ match & lit. node {
514+ LitKind :: Str ( is, _) => Some ( is. is_empty ( ) ) ,
515+ LitKind :: ByteStr ( s, _) | LitKind :: CStr ( s, _) => Some ( s. is_empty ( ) ) ,
516+ _ => None ,
517+ }
518+ }
519+ } ,
520+ ExprKind :: Array ( vec) => self . multi ( vec) . map ( |v| v. is_empty ( ) ) ,
521+ ExprKind :: Repeat ( ..) => {
522+ if let ty:: Array ( _, n) = self . typeck_results . expr_ty ( e) . kind ( ) {
523+ Some ( n. try_eval_target_usize ( self . lcx . tcx , self . lcx . param_env ) ? == 0 )
524+ } else {
525+ span_bug ! ( e. span, "typeck error" ) ;
526+ }
527+ } ,
528+ _ => None ,
529+ }
530+ }
531+
475532 #[ expect( clippy:: cast_possible_wrap) ]
476533 fn constant_not ( & self , o : & Constant < ' tcx > , ty : Ty < ' _ > ) -> Option < Constant < ' tcx > > {
477534 use self :: Constant :: { Bool , Int } ;
@@ -519,8 +576,11 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
519576 vec. iter ( ) . map ( |elem| self . expr ( elem) ) . collect :: < Option < _ > > ( )
520577 }
521578
522- /// Lookup a possibly constant expression from an `ExprKind::Path`.
523- fn fetch_path ( & mut self , qpath : & QPath < ' _ > , id : HirId , ty : Ty < ' tcx > ) -> Option < Constant < ' tcx > > {
579+ /// Lookup a possibly constant expression from an `ExprKind::Path` and apply a function on it.
580+ fn fetch_path_and_apply < T , F > ( & mut self , qpath : & QPath < ' _ > , id : HirId , ty : Ty < ' tcx > , f : F ) -> Option < T >
581+ where
582+ F : FnOnce ( & mut Self , rustc_middle:: mir:: Const < ' tcx > ) -> Option < T > ,
583+ {
524584 let res = self . typeck_results . qpath_res ( qpath, id) ;
525585 match res {
526586 Res :: Def ( DefKind :: Const | DefKind :: AssocConst , def_id) => {
@@ -553,9 +613,7 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
553613 . const_eval_resolve ( self . param_env , mir:: UnevaluatedConst :: new ( def_id, args) , None )
554614 . ok ( )
555615 . map ( |val| rustc_middle:: mir:: Const :: from_value ( val, ty) ) ?;
556- let result = mir_to_const ( self . lcx , result) ?;
557- self . source = ConstantSource :: Constant ;
558- Some ( result)
616+ f ( self , result)
559617 } ,
560618 _ => None ,
561619 }
@@ -746,7 +804,6 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
746804}
747805
748806pub fn mir_to_const < ' tcx > ( lcx : & LateContext < ' tcx > , result : mir:: Const < ' tcx > ) -> Option < Constant < ' tcx > > {
749- use rustc_middle:: mir:: ConstValue ;
750807 let mir:: Const :: Val ( val, _) = result else {
751808 // We only work on evaluated consts.
752809 return None ;
@@ -794,6 +851,42 @@ pub fn mir_to_const<'tcx>(lcx: &LateContext<'tcx>, result: mir::Const<'tcx>) ->
794851 }
795852}
796853
854+ fn mir_is_empty < ' tcx > ( lcx : & LateContext < ' tcx > , result : mir:: Const < ' tcx > ) -> Option < bool > {
855+ let mir:: Const :: Val ( val, _) = result else {
856+ // We only work on evaluated consts.
857+ return None ;
858+ } ;
859+ match ( val, result. ty ( ) . kind ( ) ) {
860+ ( _, ty:: Ref ( _, inner_ty, _) ) => match inner_ty. kind ( ) {
861+ ty:: Str | ty:: Slice ( _) => {
862+ if let ConstValue :: Indirect { alloc_id, offset } = val {
863+ // Get the length from the slice, using the same formula as
864+ // [`ConstValue::try_get_slice_bytes_for_diagnostics`].
865+ let a = lcx. tcx . global_alloc ( alloc_id) . unwrap_memory ( ) . inner ( ) ;
866+ let ptr_size = lcx. tcx . data_layout . pointer_size ;
867+ if a. size ( ) < offset + 2 * ptr_size {
868+ // (partially) dangling reference
869+ return None ;
870+ }
871+ let len = a
872+ . read_scalar ( & lcx. tcx , alloc_range ( offset + ptr_size, ptr_size) , false )
873+ . ok ( ) ?
874+ . to_target_usize ( & lcx. tcx )
875+ . ok ( ) ?;
876+ Some ( len == 0 )
877+ } else {
878+ None
879+ }
880+ } ,
881+ ty:: Array ( _, len) => Some ( len. try_to_target_usize ( lcx. tcx ) ? == 0 ) ,
882+ _ => None ,
883+ } ,
884+ ( ConstValue :: Indirect { .. } , ty:: Array ( _, len) ) => Some ( len. try_to_target_usize ( lcx. tcx ) ? == 0 ) ,
885+ ( ConstValue :: ZeroSized , _) => Some ( true ) ,
886+ _ => None ,
887+ }
888+ }
889+
797890fn field_of_struct < ' tcx > (
798891 adt_def : ty:: AdtDef < ' tcx > ,
799892 lcx : & LateContext < ' tcx > ,
0 commit comments