python / python/mypy

Add support for control flow analysis of aliased conditions

Open
#13,807 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

feature
Dominant language
Python
Stars
20.6k
Forks
3.3k
PR merge metrics
PR metrics pending

Description

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)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the isinstance/reveal_type examples in the issue and the linked TypeScript control-flow-analysis reference. Trace how mypy currently handles the aliased condition; done means narrowing value to str and Sequence[str] in the two branches and supporting the rewritten copier example.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
compilers
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.