@@ -4,7 +4,8 @@ use std::collections::{HashMap, HashSet, VecDeque};
44use crate :: diagnostic:: Span ;
55use crate :: hir:: {
66 CallResolution , HirBinding , HirBindingKind , HirBlock , HirCallArg , HirEffectEvent ,
7- HirEffectEventKind , HirExpr , HirFunctionBody , HirReturnProof , HirStmt , ParamEffect ,
7+ HirEffectEventKind , HirExpr , HirFunctionBody , HirReturnProof , HirStmt , HirTypeKind , ParamEffect ,
8+ ResolvedCalleeKind ,
89} ;
910use crate :: syntax:: ast:: { Callee , Expr } ;
1011
@@ -115,6 +116,11 @@ struct LocalFlowBinding {
115116 value_handle_field : Option < ( String , Span ) > ,
116117 fresh_from_local_source : Option < String > ,
117118 fresh_from_scrutinee : bool ,
119+ /// The binding's initializer is itself a fresh value (a fresh-returning call,
120+ /// a struct/variant constructor, or a literal). Such a binding holds a fresh,
121+ /// unaliased value, so returning it directly preserves freshness — until it is
122+ /// moved, retained, or captured (which clears its fresh-returnable status).
123+ fresh_from_fresh_value : bool ,
118124}
119125
120126#[ derive( Debug , Clone , PartialEq , Eq ) ]
@@ -520,20 +526,24 @@ fn collect_fresh_return_issue(
520526 . unwrap_or ( return_span)
521527 . clone ( ) ;
522528 if let Some ( state) = entry_states. get ( return_span) {
523- if state. is_managed ( name)
524- || ( state. is_local ( name)
525- && ( !state. is_clean_local ( name) || !state. is_fresh_returnable_local ( name) ) )
526- {
529+ // A binding bound from a fresh value (a `let s = <fresh source>`)
530+ // stays returnable-as-fresh until it is moved, retained, or
531+ // captured — including a *managed* `let`, not only an exclusive
532+ // `local`. Those invalidations clear the fresh-returnable flag, so
533+ // an aliased binding falls back to NotClean.
534+ let returns_fresh =
535+ state. is_clean_local ( name) && state. is_fresh_returnable_local ( name) ;
536+ if state. is_managed ( name) || state. is_local ( name) {
537+ if returns_fresh {
538+ return ;
539+ }
527540 push_fresh_return_issue (
528541 issues,
529542 FreshReturnIssueKind :: NotClean { name : name. clone ( ) } ,
530543 span,
531544 ) ;
532545 return ;
533546 }
534- if state. is_local ( name) {
535- return ;
536- }
537547 }
538548 push_fresh_return_issue (
539549 issues,
@@ -2598,6 +2608,7 @@ fn collect_select_local_flow(
25982608 value_handle_field : None ,
25992609 fresh_from_local_source : None ,
26002610 fresh_from_scrutinee : false ,
2611+ fresh_from_fresh_value : false ,
26012612 } ;
26022613 let binding_node = push_pattern_binding_flow_step ( steps, & arm. span , binding) ;
26032614 if let Some ( body_entry) = arm_flow. entry {
@@ -2674,6 +2685,7 @@ fn fresh_match_pattern_binding(
26742685 value_handle_field : None ,
26752686 fresh_from_local_source : source. map ( str:: to_string) ,
26762687 fresh_from_scrutinee,
2688+ fresh_from_fresh_value : false ,
26772689 } )
26782690}
26792691
@@ -3011,7 +3023,14 @@ fn collect_flow_entry_states(
30113023fn transfer_flow_step ( step : & LocalFlowStep , mut state : BodyState ) -> BodyState {
30123024 if let Some ( binding) = & step. binding {
30133025 match binding. kind {
3014- HirBindingKind :: ManagedLet => state. bind_managed ( binding. name . clone ( ) ) ,
3026+ HirBindingKind :: ManagedLet => {
3027+ state. bind_managed ( binding. name . clone ( ) ) ;
3028+ // A managed binding initialized from a fresh value is returnable
3029+ // as fresh until aliased (move/retain/capture clear this).
3030+ if binding. fresh_from_fresh_value {
3031+ state. mark_fresh_returnable ( binding. name . clone ( ) ) ;
3032+ }
3033+ }
30153034 HirBindingKind :: LocalLet => {
30163035 if binding. fresh_from_scrutinee {
30173036 state. bind_local ( binding. name . clone ( ) ) ;
@@ -3039,7 +3058,9 @@ fn transfer_flow_step(step: &LocalFlowStep, mut state: BodyState) -> BodyState {
30393058 }
30403059
30413060 for capture in & step. managed_closure_captures {
3042- if state. is_local ( capture) {
3061+ // Capturing a local, or a managed binding that is currently returnable as
3062+ // fresh, into a managed closure aliases it — clear its fresh/clean status.
3063+ if state. is_local ( capture) || state. is_fresh_returnable_local ( capture) {
30433064 state. mark_retained ( capture) ;
30443065 }
30453066 }
@@ -3176,6 +3197,7 @@ fn local_flow_step_binding(statement: &HirStmt) -> Option<LocalFlowBinding> {
31763197 value_handle_field : value. as_ref ( ) . and_then ( local_binding_handle_field_source) ,
31773198 fresh_from_local_source : None ,
31783199 fresh_from_scrutinee : false ,
3200+ fresh_from_fresh_value : value. as_ref ( ) . is_some_and ( hir_expr_is_fresh_value) ,
31793201 } ) ,
31803202 HirStmt :: Return { .. }
31813203 | HirStmt :: With { .. }
@@ -3192,6 +3214,35 @@ fn local_flow_step_binding(statement: &HirStmt) -> Option<LocalFlowBinding> {
31923214 }
31933215}
31943216
3217+ /// True if `value` is itself a fresh, unaliased value: a literal, a collection
3218+ /// literal, a struct/variant constructor, or a fresh-returning call (seen through
3219+ /// `?` and effect wrappers). A managed `let` bound to such a value can be returned
3220+ /// directly as `fresh` until it is moved, retained, or captured — exactly the
3221+ /// invalidations the flow analysis already applies to clean locals.
3222+ fn hir_expr_is_fresh_value ( value : & HirExpr ) -> bool {
3223+ match value {
3224+ HirExpr :: Number { .. }
3225+ | HirExpr :: String { .. }
3226+ | HirExpr :: ObjectLiteral { .. }
3227+ | HirExpr :: MapLiteral { .. }
3228+ | HirExpr :: ArrayLiteral { .. } => true ,
3229+ HirExpr :: Call { resolution, .. } => match resolution {
3230+ CallResolution :: EnumVariant => true ,
3231+ CallResolution :: Resolved {
3232+ kind :
3233+ ResolvedCalleeKind :: Constructor {
3234+ type_kind : HirTypeKind :: Struct ,
3235+ } ,
3236+ ..
3237+ } => true ,
3238+ CallResolution :: Resolved { signature, .. } => signature. returns_fresh ,
3239+ _ => false ,
3240+ } ,
3241+ HirExpr :: Try { value, .. } | HirExpr :: Effect { value, .. } => hir_expr_is_fresh_value ( value) ,
3242+ _ => false ,
3243+ }
3244+ }
3245+
31953246fn local_binding_source_ident ( value : & HirExpr ) -> Option < ( String , Span ) > {
31963247 match value {
31973248 HirExpr :: Ident { name, span, .. } => Some ( ( name. clone ( ) , span. clone ( ) ) ) ,
@@ -3357,6 +3408,15 @@ impl BodyState {
33573408 }
33583409 }
33593410
3411+ /// Mark a (managed) binding as returnable-as-fresh: it currently holds a
3412+ /// fresh, unaliased value. Cleared by `mark_moved`/`mark_retained` when the
3413+ /// binding is aliased.
3414+ pub ( crate ) fn mark_fresh_returnable ( & mut self , name : impl Into < String > ) {
3415+ let name = name. into ( ) ;
3416+ self . clean_locals . insert ( name. clone ( ) ) ;
3417+ self . fresh_returnable_locals . insert ( name) ;
3418+ }
3419+
33603420 pub ( crate ) fn bind_managed ( & mut self , name : impl Into < String > ) {
33613421 self . managed . insert ( name. into ( ) ) ;
33623422 }
@@ -3480,7 +3540,9 @@ impl BodyState {
34803540 if path_root ( & event. binding_name ) . is_some_and ( |root| self . locals . contains ( root) ) {
34813541 self . mark_moved ( & event. binding_name , event. span . clone ( ) ) ;
34823542 }
3483- } else if self . locals . contains ( & event. binding_name ) {
3543+ } else if self . locals . contains ( & event. binding_name )
3544+ || self . fresh_returnable_locals . contains ( & event. binding_name )
3545+ {
34843546 self . mark_moved ( & event. binding_name , event. span . clone ( ) ) ;
34853547 }
34863548 }
@@ -3491,7 +3553,12 @@ impl BodyState {
34913553 if !matches ! ( event. kind, HirEffectEventKind :: Retain { .. } ) {
34923554 continue ;
34933555 }
3494- if self . locals . contains ( & event. binding_name ) {
3556+ // Retaining a local, or a managed binding that is currently returnable
3557+ // as fresh, aliases it into retained state — clear its fresh status so
3558+ // it can no longer be returned as `fresh`.
3559+ if self . locals . contains ( & event. binding_name )
3560+ || self . fresh_returnable_locals . contains ( & event. binding_name )
3561+ {
34953562 self . mark_retained ( & event. binding_name ) ;
34963563 }
34973564 }
0 commit comments