KotlinIsland / basedmypy

Based Python static type checker with baseline, sane default settings and based typing features
https://kotlinisland.github.io/basedmypy/
Other
142 stars 4 forks source link

sound reach-ability on context managers #522

Open KotlinIsland opened 1 year ago

KotlinIsland commented 1 year ago

The current solution is heuristic based and highly unsound.

This will require reworking AbstractContextManager to know about the types of Exception and return type of __exit__

class Base:
    def __enter__(self): ...
class A(Base):
    def __exit__(self, exc_type: object, exc_val: object, exc_tb: object) -> True: ...
class B(Base):
    def __exit__(self, exc_type: object, exc_val: object, exc_tb: object) -> False | None:

with A():
    raise Exception
print("hi")  # should not see a reach-ability error

with B():
    pass
print("hi")  # should not see a reach-ability error

with B():
    raise Exception
print("hi")  # should see a reach-ability error

But return should always report unreachable.

KotlinIsland commented 1 year ago

Will have to update the type of: AbstractContextManager to have three params:

class AbstractContextManager[TEnter, TException: BaseException, TReturn: bool | None](ABC):

    def __enter__(self) -> TEnter: ...
    def __exit__(self, exc: TException) -> TReturn: ...

contextlib.contextmanager to capture the return type of the generator:

@overload
def contextmanager(func: Callable[_P, Generator[_T_co, object, True]]) -> Callable[_P, _GeneratorContextManager[_T_co, BaseException,True]]: ...
@overload
def contextmanager(func: Callable[_P, Iterator[_T_co]]) -> Callable[_P, _GeneratorContextManager[_T_co, object, bool | None]]: ...

And suppress:

class suppress[in_T_BaseException](AbstractContextManager[None, in_T_BaseException, True | None]): 
     def __init__(self, *exceptions: type[in_T_BaseExcaption]) -> None: ... 
     @overload
     def __exit__( 
         self, exctype: type[in_T_BaseException], excinst: in_T_BaseException, exctb: TracebackType
     ) -> True: ...
     @overload
     def __exit__( 
         self, exctype: type[BaseException] | None, excinst: BaseException | None, exctb: TracebackType | None 
     ) -> None: ...
KotlinIsland commented 1 year ago

related #522