11from __future__ import annotations
22
3+ import enum
34from collections import defaultdict
45from collections .abc import Iterator
56from contextlib import contextmanager
@@ -36,6 +37,11 @@ class CurrentType(NamedTuple):
3637 from_assignment : bool
3738
3839
40+ class UnreachableType (enum .Enum ):
41+ BINDER_UNREACHABLE = enum .auto ()
42+ SEMANAL_UNREACHABLE = enum .auto ()
43+
44+
3945class Frame :
4046 """A Frame represents a specific point in the execution of a program.
4147 It carries information about the current types of expressions at
@@ -51,7 +57,7 @@ class Frame:
5157 def __init__ (self , id : int , conditional_frame : bool = False ) -> None :
5258 self .id = id
5359 self .types : dict [Key , CurrentType ] = {}
54- self .unreachable = False
60+ self .unreachable : UnreachableType | None = None
5561 self .conditional_frame = conditional_frame
5662 self .suppress_unreachable_warnings = False
5763
@@ -161,8 +167,11 @@ def put(self, expr: Expression, typ: Type, *, from_assignment: bool = True) -> N
161167 self ._add_dependencies (key )
162168 self ._put (key , typ , from_assignment )
163169
164- def unreachable (self ) -> None :
165- self .frames [- 1 ].unreachable = True
170+ def unreachable (self , from_semanal : bool = False ) -> None :
171+ unreachable_type = UnreachableType .BINDER_UNREACHABLE
172+ if from_semanal :
173+ unreachable_type = UnreachableType .SEMANAL_UNREACHABLE
174+ self .frames [- 1 ].unreachable = unreachable_type
166175
167176 def suppress_unreachable_warnings (self ) -> None :
168177 self .frames [- 1 ].suppress_unreachable_warnings = True
@@ -175,12 +184,22 @@ def get(self, expr: Expression) -> Type | None:
175184 return None
176185 return found .type
177186
178- def is_unreachable (self ) -> bool :
187+ def is_unreachable (self ) -> UnreachableType | None :
179188 # TODO: Copy the value of unreachable into new frames to avoid
180189 # this traversal on every statement?
181- return any (f .unreachable for f in self .frames )
190+ unreachable_type = None
191+ for f in self .frames :
192+ if f .unreachable and not unreachable_type :
193+ unreachable_type = f .unreachable
194+ elif f .unreachable == UnreachableType .SEMANAL_UNREACHABLE :
195+ unreachable_type = f .unreachable
196+ return unreachable_type
182197
183198 def is_unreachable_warning_suppressed (self ) -> bool :
199+ # Do not report unreachable warnings from frames that were marked
200+ # unreachable by the semanal_pass1.
201+ if self .is_unreachable () == UnreachableType .SEMANAL_UNREACHABLE :
202+ return True
184203 return any (f .suppress_unreachable_warnings for f in self .frames )
185204
186205 def cleanse (self , expr : Expression ) -> None :
@@ -202,6 +221,12 @@ def update_from_options(self, frames: list[Frame]) -> bool:
202221 If a key is declared as AnyType, only update it if all the
203222 options are the same.
204223 """
224+ if all (f .unreachable for f in frames ):
225+ semanal_unreachable = any (
226+ f .unreachable == UnreachableType .SEMANAL_UNREACHABLE for f in frames
227+ )
228+ self .unreachable (from_semanal = semanal_unreachable )
229+
205230 all_reachable = all (not f .unreachable for f in frames )
206231 frames = [f for f in frames if not f .unreachable ]
207232 changed = False
@@ -262,8 +287,6 @@ def update_from_options(self, frames: list[Frame]) -> bool:
262287 self ._put (key , type , from_assignment = True )
263288 changed = True
264289
265- self .frames [- 1 ].unreachable = not frames
266-
267290 return changed
268291
269292 def pop_frame (self , can_skip : bool , fall_through : int ) -> Frame :
@@ -411,7 +434,7 @@ def allow_jump(self, index: int) -> None:
411434 for f in self .frames [index + 1 :]:
412435 frame .types .update (f .types )
413436 if f .unreachable :
414- frame .unreachable = True
437+ frame .unreachable = f . unreachable
415438 self .options_on_return [index ].append (frame )
416439
417440 def handle_break (self ) -> None :
0 commit comments