python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.19k stars 2.78k forks source link

Add support for control flow analysis of aliased conditions #13807

Open sisp opened 1 year ago

sisp commented 1 year ago

Feature

I think it would be great if mypy supported control flow analysis of aliased conditions like, e.g., introduced in TypeScript v4.4.

Currently, mypy works as follows:

from typing import Sequence, Union

def f(value: Union[str, Sequence[str]]):
    is_string = isinstance(value, str)
    if is_string:
        reveal_type(value)  # Revealed type is "Union[builtins.str, typing.Sequence[builtins.str]]"
    else:
        reveal_type(value)  # Revealed type is "Union[builtins.str, typing.Sequence[builtins.str]]"

But it would be nice if it could infer the type of value correctly in the condition:

from typing import Sequence, Union

def f(value: Union[str, Sequence[str]]):
    is_string = isinstance(value, str)
    if is_string:
        reveal_type(value)  # Revealed type is "builtins.str"
    else:
        reveal_type(value)  # Revealed type is "typing.Sequence[builtins.str]"

Pitch

See, e.g., https://github.com/copier-org/copier/pull/812:

def _execute_tasks(self, tasks: Sequence[Task]) -> None:
    # ...
    for i, task in enumerate(tasks):
        task_cmd = task.cmd
        if isinstance(task_cmd, str):
            task_cmd = self._render_string(task_cmd)
            use_shell = True
        else:
            task_cmd = [self._render_string(str(part)) for part in task_cmd]
            use_shell = False
        # ...
        with local.cwd(self.subproject.local_abspath), local.env(**task.extra_env):
            subprocess.run(task_cmd, shell=use_shell, check=True, env=local.env)

Source: https://github.com/copier-org/copier/blob/1f40ddaac2cc1b1d90ab08c681a3109e329c0206/copier/main.py#L193-L214

It would be nice to rewrite the above snippet as follows:

 def _execute_tasks(self, tasks: Sequence[Task]) -> None:
     # ...
     for i, task in enumerate(tasks):
         task_cmd = task.cmd
+        use_shell = isinstance(task_cmd, str)
+        if use_shell:
-        if isinstance(task_cmd, str):
             task_cmd = self._render_string(task_cmd)
-            use_shell = True
         else:
             task_cmd = [self._render_string(str(part)) for part in task_cmd]
-            use_shell = False
         # ...
         with local.cwd(self.subproject.local_abspath), local.env(**task.extra_env):
             subprocess.run(task_cmd, shell=use_shell, check=True, env=local.env)
ikonst commented 1 year ago

For the record, pyright appears to do it.