@@ -10,6 +10,7 @@ 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} ;
1516use rustc_span:: symbol:: { Ident , Symbol } ;
@@ -303,6 +304,12 @@ impl ConstantSource {
303304 }
304305}
305306
307+ /// Attempts to check whether the expression is a constant representing an empty slice, str, array,
308+ /// etc…
309+ pub fn constant_is_empty ( lcx : & LateContext < ' _ > , e : & Expr < ' _ > ) -> Option < bool > {
310+ ConstEvalLateContext :: new ( lcx, lcx. typeck_results ( ) ) . expr_is_empty ( e)
311+ }
312+
306313/// Attempts to evaluate the expression as a constant.
307314pub fn constant < ' tcx > (
308315 lcx : & LateContext < ' tcx > ,
@@ -402,7 +409,13 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
402409 match e. kind {
403410 ExprKind :: ConstBlock ( ConstBlock { body, .. } ) => self . expr ( self . lcx . tcx . hir ( ) . body ( body) . value ) ,
404411 ExprKind :: DropTemps ( e) => self . expr ( e) ,
405- ExprKind :: Path ( ref qpath) => self . fetch_path ( qpath, e. hir_id , self . typeck_results . expr_ty ( e) ) ,
412+ ExprKind :: Path ( ref qpath) => {
413+ self . fetch_path_and_apply ( qpath, e. hir_id , self . typeck_results . expr_ty ( e) , |this, result| {
414+ let result = mir_to_const ( this. lcx , result) ?;
415+ this. source = ConstantSource :: Constant ;
416+ Some ( result)
417+ } )
418+ } ,
406419 ExprKind :: Block ( block, _) => self . block ( block) ,
407420 ExprKind :: Lit ( lit) => {
408421 if is_direct_expn_of ( e. span , "cfg" ) . is_some ( ) {
@@ -468,6 +481,39 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
468481 }
469482 }
470483
484+ /// Simple constant folding to determine if an expression is an empty slice, str, array, …
485+ pub fn expr_is_empty ( & mut self , e : & Expr < ' _ > ) -> Option < bool > {
486+ match e. kind {
487+ ExprKind :: ConstBlock ( ConstBlock { body, .. } ) => self . expr_is_empty ( self . lcx . tcx . hir ( ) . body ( body) . value ) ,
488+ ExprKind :: DropTemps ( e) => self . expr_is_empty ( e) ,
489+ ExprKind :: Path ( ref qpath) => {
490+ self . fetch_path_and_apply ( qpath, e. hir_id , self . typeck_results . expr_ty ( e) , |this, result| {
491+ mir_is_empty ( this. lcx , result)
492+ } )
493+ } ,
494+ ExprKind :: Lit ( lit) => {
495+ if is_direct_expn_of ( e. span , "cfg" ) . is_some ( ) {
496+ None
497+ } else {
498+ match & lit. node {
499+ LitKind :: Str ( is, _) => Some ( is. is_empty ( ) ) ,
500+ LitKind :: ByteStr ( s, _) | LitKind :: CStr ( s, _) => Some ( s. is_empty ( ) ) ,
501+ _ => None ,
502+ }
503+ }
504+ } ,
505+ ExprKind :: Array ( vec) => self . multi ( vec) . map ( |v| v. is_empty ( ) ) ,
506+ ExprKind :: Repeat ( ..) => {
507+ if let ty:: Array ( _, n) = self . typeck_results . expr_ty ( e) . kind ( ) {
508+ Some ( n. try_eval_target_usize ( self . lcx . tcx , self . lcx . param_env ) ? == 0 )
509+ } else {
510+ span_bug ! ( e. span, "typeck error" ) ;
511+ }
512+ } ,
513+ _ => None ,
514+ }
515+ }
516+
471517 #[ expect( clippy:: cast_possible_wrap) ]
472518 fn constant_not ( & self , o : & Constant < ' tcx > , ty : Ty < ' _ > ) -> Option < Constant < ' tcx > > {
473519 use self :: Constant :: { Bool , Int } ;
@@ -515,8 +561,11 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
515561 vec. iter ( ) . map ( |elem| self . expr ( elem) ) . collect :: < Option < _ > > ( )
516562 }
517563
518- /// Lookup a possibly constant expression from an `ExprKind::Path`.
519- fn fetch_path ( & mut self , qpath : & QPath < ' _ > , id : HirId , ty : Ty < ' tcx > ) -> Option < Constant < ' tcx > > {
564+ /// Lookup a possibly constant expression from an `ExprKind::Path` and apply a function on it.
565+ fn fetch_path_and_apply < T , F > ( & mut self , qpath : & QPath < ' _ > , id : HirId , ty : Ty < ' tcx > , f : F ) -> Option < T >
566+ where
567+ F : FnOnce ( & mut Self , rustc_middle:: mir:: Const < ' tcx > ) -> Option < T > ,
568+ {
520569 let res = self . typeck_results . qpath_res ( qpath, id) ;
521570 match res {
522571 Res :: Def ( DefKind :: Const | DefKind :: AssocConst , def_id) => {
@@ -549,9 +598,7 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
549598 . const_eval_resolve ( self . param_env , mir:: UnevaluatedConst :: new ( def_id, args) , None )
550599 . ok ( )
551600 . map ( |val| rustc_middle:: mir:: Const :: from_value ( val, ty) ) ?;
552- let result = mir_to_const ( self . lcx , result) ?;
553- self . source = ConstantSource :: Constant ;
554- Some ( result)
601+ f ( self , result)
555602 } ,
556603 _ => None ,
557604 }
@@ -742,7 +789,6 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
742789}
743790
744791pub fn mir_to_const < ' tcx > ( lcx : & LateContext < ' tcx > , result : mir:: Const < ' tcx > ) -> Option < Constant < ' tcx > > {
745- use rustc_middle:: mir:: ConstValue ;
746792 let mir:: Const :: Val ( val, _) = result else {
747793 // We only work on evaluated consts.
748794 return None ;
@@ -788,6 +834,42 @@ pub fn mir_to_const<'tcx>(lcx: &LateContext<'tcx>, result: mir::Const<'tcx>) ->
788834 }
789835}
790836
837+ fn mir_is_empty < ' tcx > ( lcx : & LateContext < ' tcx > , result : mir:: Const < ' tcx > ) -> Option < bool > {
838+ let mir:: Const :: Val ( val, _) = result else {
839+ // We only work on evaluated consts.
840+ return None ;
841+ } ;
842+ match ( val, result. ty ( ) . kind ( ) ) {
843+ ( _, ty:: Ref ( _, inner_ty, _) ) => match inner_ty. kind ( ) {
844+ ty:: Str | ty:: Slice ( _) => {
845+ if let ConstValue :: Indirect { alloc_id, offset } = val {
846+ // Get the length from the slice, using the same formula as
847+ // [`ConstValue::try_get_slice_bytes_for_diagnostics`].
848+ let a = lcx. tcx . global_alloc ( alloc_id) . unwrap_memory ( ) . inner ( ) ;
849+ let ptr_size = lcx. tcx . data_layout . pointer_size ;
850+ if a. size ( ) < offset + 2 * ptr_size {
851+ // (partially) dangling reference
852+ return None ;
853+ }
854+ let len = a
855+ . read_scalar ( & lcx. tcx , alloc_range ( offset + ptr_size, ptr_size) , false )
856+ . ok ( ) ?
857+ . to_target_usize ( & lcx. tcx )
858+ . ok ( ) ?;
859+ Some ( len == 0 )
860+ } else {
861+ None
862+ }
863+ } ,
864+ ty:: Array ( _, len) => Some ( len. try_to_target_usize ( lcx. tcx ) ? == 0 ) ,
865+ _ => None ,
866+ } ,
867+ ( ConstValue :: Indirect { .. } , ty:: Array ( _, len) ) => Some ( len. try_to_target_usize ( lcx. tcx ) ? == 0 ) ,
868+ ( ConstValue :: ZeroSized , _) => Some ( true ) ,
869+ _ => None ,
870+ }
871+ }
872+
791873fn field_of_struct < ' tcx > (
792874 adt_def : ty:: AdtDef < ' tcx > ,
793875 lcx : & LateContext < ' tcx > ,
0 commit comments