76
76
//! is not just used to generate a new value. For example, `x += 1` is
77
77
//! a read but not a use. This is used to generate better warnings.
78
78
//!
79
- //! ## Special Variables
79
+ //! ## Special nodes and variables
80
80
//!
81
- //! We generate various special variables for various, well, special purposes.
82
- //! These are described in the `specials` struct:
83
- //!
84
- //! - `exit_ln`: a live node that is generated to represent every 'exit' from
85
- //! the function, whether it be by explicit return, panic, or other means.
81
+ //! We generate various special nodes for various, well, special purposes.
82
+ //! These are described in the `Specials` struct.
86
83
87
84
use self :: LiveNodeKind :: * ;
88
85
use self :: VarKind :: * ;
@@ -131,6 +128,7 @@ enum LiveNodeKind {
131
128
UpvarNode ( Span ) ,
132
129
ExprNode ( Span ) ,
133
130
VarDefNode ( Span ) ,
131
+ ClosureNode ,
134
132
ExitNode ,
135
133
}
136
134
@@ -140,6 +138,7 @@ fn live_node_kind_to_string(lnk: LiveNodeKind, tcx: TyCtxt<'_>) -> String {
140
138
UpvarNode ( s) => format ! ( "Upvar node [{}]" , sm. span_to_string( s) ) ,
141
139
ExprNode ( s) => format ! ( "Expr node [{}]" , sm. span_to_string( s) ) ,
142
140
VarDefNode ( s) => format ! ( "Var def node [{}]" , sm. span_to_string( s) ) ,
141
+ ClosureNode => "Closure node" . to_owned ( ) ,
143
142
ExitNode => "Exit node" . to_owned ( ) ,
144
143
}
145
144
}
@@ -396,10 +395,12 @@ fn visit_fn<'tcx>(
396
395
397
396
// compute liveness
398
397
let mut lsets = Liveness :: new ( & mut fn_maps, def_id) ;
399
- let entry_ln = lsets. compute ( & body. value ) ;
398
+ let entry_ln = lsets. compute ( fk, & body, sp, id) ;
399
+ lsets. log_liveness ( entry_ln, id) ;
400
400
401
401
// check for various error conditions
402
402
lsets. visit_body ( body) ;
403
+ lsets. warn_about_unused_upvars ( entry_ln) ;
403
404
lsets. warn_about_unused_args ( body, entry_ln) ;
404
405
}
405
406
@@ -634,6 +635,12 @@ impl RWUTable {
634
635
635
636
#[ derive( Copy , Clone ) ]
636
637
struct Specials {
638
+ /// A live node representing a point of execution before closure entry &
639
+ /// after closure exit. Used to calculate liveness of captured variables
640
+ /// through calls to the same closure. Used for Fn & FnMut closures only.
641
+ closure_ln : LiveNode ,
642
+ /// A live node representing every 'exit' from the function, whether it be
643
+ /// by explicit return, panic, or other means.
637
644
exit_ln : LiveNode ,
638
645
}
639
646
@@ -658,11 +665,8 @@ struct Liveness<'a, 'tcx> {
658
665
659
666
impl < ' a , ' tcx > Liveness < ' a , ' tcx > {
660
667
fn new ( ir : & ' a mut IrMaps < ' tcx > , def_id : LocalDefId ) -> Liveness < ' a , ' tcx > {
661
- // Special nodes and variables:
662
- // - exit_ln represents the end of the fn, either by return or panic
663
- // - implicit_ret_var is a pseudo-variable that represents
664
- // an implicit return
665
668
let specials = Specials {
669
+ closure_ln : ir. add_live_node ( ClosureNode ) ,
666
670
exit_ln : ir. add_live_node ( ExitNode ) ,
667
671
} ;
668
672
@@ -789,6 +793,20 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
789
793
String :: from_utf8 ( wr) . unwrap ( )
790
794
}
791
795
796
+ fn log_liveness ( & self , entry_ln : LiveNode , hir_id : hir:: HirId ) {
797
+ // hack to skip the loop unless debug! is enabled:
798
+ debug ! (
799
+ "^^ liveness computation results for body {} (entry={:?})" ,
800
+ {
801
+ for ln_idx in 0 ..self . ir. num_live_nodes {
802
+ debug!( "{:?}" , self . ln_str( LiveNode ( ln_idx as u32 ) ) ) ;
803
+ }
804
+ hir_id
805
+ } ,
806
+ entry_ln
807
+ ) ;
808
+ }
809
+
792
810
fn init_empty ( & mut self , ln : LiveNode , succ_ln : LiveNode ) {
793
811
self . successors [ ln. get ( ) ] = succ_ln;
794
812
@@ -889,33 +907,87 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
889
907
self . rwu_table . assign_unpacked ( idx, rwu) ;
890
908
}
891
909
892
- fn compute ( & mut self , body : & hir:: Expr < ' _ > ) -> LiveNode {
893
- debug ! ( "compute: using id for body, {:?}" , body) ;
910
+ fn compute (
911
+ & mut self ,
912
+ fk : FnKind < ' _ > ,
913
+ body : & hir:: Body < ' _ > ,
914
+ span : Span ,
915
+ id : hir:: HirId ,
916
+ ) -> LiveNode {
917
+ debug ! ( "compute: using id for body, {:?}" , body. value) ;
894
918
895
- let s = self . s ;
919
+ // # Liveness of captured variables
920
+ //
921
+ // When computing the liveness for captured variables we take into
922
+ // account how variable is captured (ByRef vs ByValue) and what is the
923
+ // closure kind (Generator / FnOnce vs Fn / FnMut).
924
+ //
925
+ // Variables captured by reference are assumed to be used on the exit
926
+ // from the closure.
927
+ //
928
+ // In FnOnce closures, variables captured by value are known to be dead
929
+ // on exit since it is impossible to call the closure again.
930
+ //
931
+ // In Fn / FnMut closures, variables captured by value are live on exit
932
+ // if they are live on the entry to the closure, since only the closure
933
+ // itself can access them on subsequent calls.
896
934
897
935
if let Some ( upvars) = self . ir . tcx . upvars_mentioned ( self . ir . body_owner ) {
936
+ // Mark upvars captured by reference as used after closure exits.
898
937
for ( & var_hir_id, upvar) in upvars. iter ( ) . rev ( ) {
899
- let var = self . variable ( var_hir_id, upvar. span ) ;
900
- self . acc ( s. exit_ln , var, ACC_READ | ACC_USE ) ;
938
+ let upvar_id = ty:: UpvarId {
939
+ var_path : ty:: UpvarPath { hir_id : var_hir_id } ,
940
+ closure_expr_id : self . ir . body_owner . expect_local ( ) ,
941
+ } ;
942
+ match self . tables . upvar_capture ( upvar_id) {
943
+ ty:: UpvarCapture :: ByRef ( _) => {
944
+ let var = self . variable ( var_hir_id, upvar. span ) ;
945
+ self . acc ( self . s . exit_ln , var, ACC_READ | ACC_USE ) ;
946
+ }
947
+ ty:: UpvarCapture :: ByValue => { }
948
+ }
901
949
}
902
950
}
903
951
904
- let entry_ln = self . propagate_through_expr ( body, s. exit_ln ) ;
952
+ let succ = self . propagate_through_expr ( & body. value , self . s . exit_ln ) ;
905
953
906
- // hack to skip the loop unless debug! is enabled:
907
- debug ! (
908
- "^^ liveness computation results for body {} (entry={:?})" ,
909
- {
910
- for ln_idx in 0 ..self . ir. num_live_nodes {
911
- debug!( "{:?}" , self . ln_str( LiveNode ( ln_idx as u32 ) ) ) ;
912
- }
913
- body. hir_id
954
+ match fk {
955
+ FnKind :: Method ( ..) | FnKind :: ItemFn ( ..) => return succ,
956
+ FnKind :: Closure ( ..) => { }
957
+ }
958
+
959
+ let ty = self . tables . node_type ( id) ;
960
+ match ty. kind {
961
+ ty:: Closure ( _def_id, substs) => match substs. as_closure ( ) . kind ( ) {
962
+ ty:: ClosureKind :: Fn => { }
963
+ ty:: ClosureKind :: FnMut => { }
964
+ ty:: ClosureKind :: FnOnce => return succ,
914
965
} ,
915
- entry_ln
916
- ) ;
966
+ ty:: Generator ( ..) => return succ,
967
+ _ => {
968
+ span_bug ! ( span, "type of closure expr {:?} is not a closure {:?}" , id, ty, ) ;
969
+ }
970
+ } ;
917
971
918
- entry_ln
972
+ // Propagate through calls to the closure.
973
+ let mut first_merge = true ;
974
+ loop {
975
+ self . init_from_succ ( self . s . closure_ln , succ) ;
976
+ for param in body. params {
977
+ param. pat . each_binding ( |_bm, hir_id, _x, ident| {
978
+ let var = self . variable ( hir_id, ident. span ) ;
979
+ self . define ( self . s . closure_ln , var) ;
980
+ } )
981
+ }
982
+
983
+ if !self . merge_from_succ ( self . s . exit_ln , self . s . closure_ln , first_merge) {
984
+ break ;
985
+ }
986
+ first_merge = false ;
987
+ assert_eq ! ( succ, self . propagate_through_expr( & body. value, self . s. exit_ln) ) ;
988
+ }
989
+
990
+ succ
919
991
}
920
992
921
993
fn propagate_through_block ( & mut self , blk : & hir:: Block < ' _ > , succ : LiveNode ) -> LiveNode {
@@ -1533,11 +1605,60 @@ impl<'tcx> Liveness<'_, 'tcx> {
1533
1605
if name. is_empty ( ) || name. as_bytes ( ) [ 0 ] == b'_' { None } else { Some ( name) }
1534
1606
}
1535
1607
1608
+ fn warn_about_unused_upvars ( & self , entry_ln : LiveNode ) {
1609
+ let upvars = match self . ir . tcx . upvars_mentioned ( self . ir . body_owner ) {
1610
+ None => return ,
1611
+ Some ( upvars) => upvars,
1612
+ } ;
1613
+ for ( & var_hir_id, upvar) in upvars. iter ( ) {
1614
+ let var = self . variable ( var_hir_id, upvar. span ) ;
1615
+ let upvar_id = ty:: UpvarId {
1616
+ var_path : ty:: UpvarPath { hir_id : var_hir_id } ,
1617
+ closure_expr_id : self . ir . body_owner . expect_local ( ) ,
1618
+ } ;
1619
+ match self . tables . upvar_capture ( upvar_id) {
1620
+ ty:: UpvarCapture :: ByValue => { }
1621
+ ty:: UpvarCapture :: ByRef ( ..) => continue ,
1622
+ } ;
1623
+ if self . used_on_entry ( entry_ln, var) {
1624
+ if self . live_on_entry ( entry_ln, var) . is_none ( ) {
1625
+ if let Some ( name) = self . should_warn ( var) {
1626
+ self . ir . tcx . struct_span_lint_hir (
1627
+ lint:: builtin:: UNUSED_ASSIGNMENTS ,
1628
+ var_hir_id,
1629
+ vec ! [ upvar. span] ,
1630
+ |lint| {
1631
+ lint. build ( & format ! ( "value captured by `{}` is never read" , name) )
1632
+ . help ( "did you mean to capture by reference instead?" )
1633
+ . emit ( ) ;
1634
+ } ,
1635
+ ) ;
1636
+ }
1637
+ }
1638
+ } else {
1639
+ if let Some ( name) = self . should_warn ( var) {
1640
+ self . ir . tcx . struct_span_lint_hir (
1641
+ lint:: builtin:: UNUSED_VARIABLES ,
1642
+ var_hir_id,
1643
+ vec ! [ upvar. span] ,
1644
+ |lint| {
1645
+ lint. build ( & format ! ( "unused variable: `{}`" , name) )
1646
+ . help ( "did you mean to capture by reference instead?" )
1647
+ . emit ( ) ;
1648
+ } ,
1649
+ ) ;
1650
+ }
1651
+ }
1652
+ }
1653
+ }
1654
+
1536
1655
fn warn_about_unused_args ( & self , body : & hir:: Body < ' _ > , entry_ln : LiveNode ) {
1537
1656
for p in body. params {
1538
1657
self . check_unused_vars_in_pat ( & p. pat , Some ( entry_ln) , |spans, hir_id, ln, var| {
1539
1658
if self . live_on_entry ( ln, var) . is_none ( ) {
1540
- self . report_dead_assign ( hir_id, spans, var, true ) ;
1659
+ self . report_unsed_assign ( hir_id, spans, var, |name| {
1660
+ format ! ( "value passed to `{}` is never read" , name)
1661
+ } ) ;
1541
1662
}
1542
1663
} ) ;
1543
1664
}
@@ -1651,35 +1772,30 @@ impl<'tcx> Liveness<'_, 'tcx> {
1651
1772
1652
1773
fn warn_about_dead_assign ( & self , spans : Vec < Span > , hir_id : HirId , ln : LiveNode , var : Variable ) {
1653
1774
if self . live_on_exit ( ln, var) . is_none ( ) {
1654
- self . report_dead_assign ( hir_id, spans, var, false ) ;
1775
+ self . report_unsed_assign ( hir_id, spans, var, |name| {
1776
+ format ! ( "value assigned to `{}` is never read" , name)
1777
+ } ) ;
1655
1778
}
1656
1779
}
1657
1780
1658
- fn report_dead_assign ( & self , hir_id : HirId , spans : Vec < Span > , var : Variable , is_param : bool ) {
1781
+ fn report_unsed_assign (
1782
+ & self ,
1783
+ hir_id : HirId ,
1784
+ spans : Vec < Span > ,
1785
+ var : Variable ,
1786
+ message : impl Fn ( & str ) -> String ,
1787
+ ) {
1659
1788
if let Some ( name) = self . should_warn ( var) {
1660
- if is_param {
1661
- self . ir . tcx . struct_span_lint_hir (
1662
- lint:: builtin:: UNUSED_ASSIGNMENTS ,
1663
- hir_id,
1664
- spans,
1665
- |lint| {
1666
- lint. build ( & format ! ( "value passed to `{}` is never read" , name) )
1667
- . help ( "maybe it is overwritten before being read?" )
1668
- . emit ( ) ;
1669
- } ,
1670
- )
1671
- } else {
1672
- self . ir . tcx . struct_span_lint_hir (
1673
- lint:: builtin:: UNUSED_ASSIGNMENTS ,
1674
- hir_id,
1675
- spans,
1676
- |lint| {
1677
- lint. build ( & format ! ( "value assigned to `{}` is never read" , name) )
1678
- . help ( "maybe it is overwritten before being read?" )
1679
- . emit ( ) ;
1680
- } ,
1681
- )
1682
- }
1789
+ self . ir . tcx . struct_span_lint_hir (
1790
+ lint:: builtin:: UNUSED_ASSIGNMENTS ,
1791
+ hir_id,
1792
+ spans,
1793
+ |lint| {
1794
+ lint. build ( & message ( & name) )
1795
+ . help ( "maybe it is overwritten before being read?" )
1796
+ . emit ( ) ;
1797
+ } ,
1798
+ )
1683
1799
}
1684
1800
}
1685
1801
}
0 commit comments