@@ -311,6 +311,28 @@ pub(crate) fn infer_expression_type<'db>(
311311 infer_same_file_expression_type ( db, expression, & module)
312312}
313313
314+ /// TODO: Just created to verify the idea.
315+ #[ salsa:: tracked( returns( ref) , cycle_fn=expression_cycle_recover, cycle_initial=expression_cycle_initial, heap_size=get_size2:: GetSize :: get_heap_size) ]
316+ pub ( crate ) fn infer_expression < ' db > (
317+ db : & ' db dyn Db ,
318+ expression : Expression < ' db > ,
319+ ) -> ExpressionInference < ' db > {
320+ let file = expression. file ( db) ;
321+ let module = parsed_module ( db, file) . load ( db) ;
322+ let _span = tracing:: trace_span!(
323+ "infer_expression" ,
324+ expression = ?expression. as_id( ) ,
325+ range = ?expression. node_ref( db, & module) . range( ) ,
326+ ?file
327+ )
328+ . entered ( ) ;
329+
330+ let index = semantic_index ( db, file) ;
331+
332+ TypeInferenceBuilder :: new ( db, InferenceRegion :: Expression ( expression) , index, & module)
333+ . finish_expression ( )
334+ }
335+
314336fn single_expression_cycle_recover < ' db > (
315337 _db : & ' db dyn Db ,
316338 _value : & Type < ' db > ,
@@ -640,6 +662,9 @@ struct ExpressionInferenceExtra<'db> {
640662 ///
641663 /// Falls back to `Type::Never` if an expression is missing.
642664 cycle_fallback : bool ,
665+
666+ /// `true` if all expressions in this expression are definitely bound
667+ all_definitely_bound : bool ,
643668}
644669
645670impl < ' db > ExpressionInference < ' db > {
@@ -648,6 +673,7 @@ impl<'db> ExpressionInference<'db> {
648673 Self {
649674 extra : Some ( Box :: new ( ExpressionInferenceExtra {
650675 cycle_fallback : true ,
676+ all_definitely_bound : true ,
651677 ..ExpressionInferenceExtra :: default ( )
652678 } ) ) ,
653679 expressions : FxHashMap :: default ( ) ,
@@ -681,6 +707,13 @@ impl<'db> ExpressionInference<'db> {
681707 fn fallback_type ( & self ) -> Option < Type < ' db > > {
682708 self . is_cycle_callback ( ) . then_some ( Type :: Never )
683709 }
710+
711+ pub ( crate ) fn definitely_bound ( & self ) -> bool {
712+ match self . extra . as_ref ( ) {
713+ Some ( e) => e. all_definitely_bound ,
714+ None => true ,
715+ }
716+ }
684717}
685718
686719/// Whether the intersection type is on the left or right side of the comparison.
@@ -818,6 +851,8 @@ pub(super) struct TypeInferenceBuilder<'db, 'ast> {
818851 ///
819852 /// This is used only when constructing a cycle-recovery `TypeInference`.
820853 cycle_fallback : bool ,
854+
855+ all_definitely_bound : bool ,
821856}
822857
823858impl < ' db , ' ast > TypeInferenceBuilder < ' db , ' ast > {
@@ -849,6 +884,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
849884 declarations : VecMap :: default ( ) ,
850885 deferred : VecSet :: default ( ) ,
851886 cycle_fallback : false ,
887+ all_definitely_bound : true ,
852888 }
853889 }
854890
@@ -6260,7 +6296,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
62606296 let db = self . db ( ) ;
62616297
62626298 let ( resolved, constraint_keys) =
6263- self . infer_place_load ( PlaceExprRef :: from ( & expr) , ast:: ExprRef :: Name ( name_node) ) ;
6299+ self . record_place_load ( PlaceExprRef :: from ( & expr) , ast:: ExprRef :: Name ( name_node) ) ;
62646300
62656301 resolved
62666302 // Not found in the module's explicitly declared global symbols?
@@ -6352,6 +6388,22 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
63526388 }
63536389 }
63546390
6391+ /// Infer the type of a place expression from definitions, assuming a load context.
6392+ /// This method also returns the [`ConstraintKey`]s for each scope associated with `expr`,
6393+ /// which is used to narrow by condition rather than by assignment.
6394+ fn record_place_load (
6395+ & mut self ,
6396+ place_expr : PlaceExprRef ,
6397+ expr_ref : ast:: ExprRef ,
6398+ ) -> ( PlaceAndQualifiers < ' db > , Vec < ( FileScopeId , ConstraintKey ) > ) {
6399+ let place = self . infer_place_load ( place_expr, expr_ref) ;
6400+ if !place. 0 . place . is_definitely_bound ( ) {
6401+ self . all_definitely_bound = false ;
6402+ }
6403+
6404+ place
6405+ }
6406+
63556407 /// Infer the type of a place expression from definitions, assuming a load context.
63566408 /// This method also returns the [`ConstraintKey`]s for each scope associated with `expr`,
63576409 /// which is used to narrow by condition rather than by assignment.
@@ -6788,7 +6840,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
67886840
67896841 let mut assigned_type = None ;
67906842 if let Some ( place_expr) = PlaceExpr :: try_from_expr ( attribute) {
6791- let ( resolved, keys) = self . infer_place_load (
6843+ let ( resolved, keys) = self . record_place_load (
67926844 PlaceExprRef :: from ( & place_expr) ,
67936845 ast:: ExprRef :: Attribute ( attribute) ,
67946846 ) ;
@@ -8338,7 +8390,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
83388390 // If `value` is a valid reference, we attempt type narrowing by assignment.
83398391 if !value_ty. is_unknown ( ) {
83408392 if let Some ( expr) = PlaceExpr :: try_from_expr ( subscript) {
8341- let ( place, keys) = self . infer_place_load (
8393+ let ( place, keys) = self . record_place_load (
83428394 PlaceExprRef :: from ( & expr) ,
83438395 ast:: ExprRef :: Subscript ( subscript) ,
83448396 ) ;
@@ -8877,6 +8929,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
88778929 declarations,
88788930 deferred,
88798931 cycle_fallback,
8932+ all_definitely_bound : all_bound,
88808933
88818934 // builder only state
88828935 deferred_state : _,
@@ -8912,6 +8965,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
89128965 bindings : bindings. into_boxed_slice ( ) ,
89138966 diagnostics,
89148967 cycle_fallback,
8968+ all_definitely_bound : all_bound,
89158969 } )
89168970 } ) ;
89178971
@@ -8936,6 +8990,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
89368990 declarations,
89378991 deferred,
89388992 cycle_fallback,
8993+ all_definitely_bound : _,
89398994
89408995 // builder only state
89418996 deferred_state : _,
@@ -8998,6 +9053,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
89989053 deferred : _,
89999054 bindings : _,
90009055 declarations : _,
9056+ all_definitely_bound : _,
90019057
90029058 // Builder only state
90039059 deferred_state : _,
@@ -11092,6 +11148,80 @@ mod tests {
1109211148 assert_diagnostic_messages ( & diagnostics, expected) ;
1109311149 }
1109411150
11151+ #[ test]
11152+ fn analyze_cycles_gridout ( ) {
11153+ let mut db = setup_db ( ) ;
11154+ let filename = "src/gridout.py" ;
11155+ db. write_dedented (
11156+ filename,
11157+ r#"
11158+ EMPTY = b""
11159+
11160+
11161+ class GridOut:
11162+ def __init__(self: "GridOut") -> None:
11163+ self._buffer_pos = 0
11164+ self._buffer = b""
11165+
11166+ def readchunk(self: "GridOut") -> bytes:
11167+ if not len(self._buffer) - self._buffer_pos:
11168+ raise Exception("truncated chunk")
11169+ self._buffer_pos = 0
11170+ return EMPTY
11171+
11172+ def _read_size_or_line(self: "GridOut", size: int = -1) -> bytes:
11173+ if size > self._position:
11174+ size = self._position
11175+ if size == 0:
11176+ return bytes()
11177+
11178+ received = 0
11179+ needed = size - received
11180+ while received < size:
11181+ if self._buffer:
11182+ buf = self._buffer
11183+ chunk_start = self._buffer_pos
11184+ chunk_data = buf[self._buffer_pos :]
11185+ self._buffer = EMPTY
11186+ else:
11187+ buf = self.readchunk()
11188+ chunk_start = 0
11189+ chunk_data = buf
11190+
11191+ needed = buf.find(EMPTY, chunk_start, chunk_start + needed)
11192+ if len(chunk_data) > needed:
11193+ self._buffer = buf
11194+ self._buffer_pos = chunk_start + needed
11195+ self._position -= len(self._buffer) - self._buffer_pos
11196+
11197+ return b""
11198+ "# ,
11199+ )
11200+ . unwrap ( ) ;
11201+
11202+ db. clear_salsa_events ( ) ;
11203+ assert_file_diagnostics ( & db, filename, & [ ] ) ;
11204+ let events = db. take_salsa_events ( ) ;
11205+ let cycles = salsa:: attach ( & db, || {
11206+ events
11207+ . iter ( )
11208+ . filter_map ( |event| {
11209+ if let salsa:: EventKind :: WillIterateCycle {
11210+ database_key,
11211+ iteration_count,
11212+ fell_back : _,
11213+ } = event. kind
11214+ {
11215+ Some ( format ! ( "{database_key:?}, {iteration_count:?}" ) )
11216+ } else {
11217+ None
11218+ }
11219+ } )
11220+ . collect :: < Vec < _ > > ( )
11221+ } ) ;
11222+ assert_eq ! ( cycles. len( ) , 651 ) ;
11223+ }
11224+
1109511225 #[ test]
1109611226 fn not_literal_string ( ) -> anyhow:: Result < ( ) > {
1109711227 let mut db = setup_db ( ) ;
0 commit comments