mypy icon indicating copy to clipboard operation
mypy copied to clipboard

Fix checking multiple assignments based on tuple unpacking involving partially initialised variables (Fixes #12915).

Open tyralla opened this issue 3 years ago • 11 comments

Fix checking multiple assignments based on tuple unpacking involving partially initialised variables (Fixes #12915).

This proposal is an alternative to #14423. Similar to #14423, the main idea is to convert unions of tuples to tuples of (simplified) unions during multi-assignment checks. In addition, it extends this idea to other iterable types, which allows removing the undefined_rvalue logic and the no_partial_types logic. Hence, the problem reported in #12915 with partially initialised variables should be fixed for unions that combine, for example, tuples and lists, as well.

Besides the new test case also provided by #14423 (testDefinePartiallyInitialisedVariableDuringTupleUnpacking), this commit also adds the test cases testUnionUnpackingIncludingListPackingSameItemTypes, testUnionUnpackingIncludingListPackingDifferentItemTypes, and testUnionUnpackingIncludingListPackingForVariousItemTypes.

tyralla avatar Jan 12 '23 15:01 tyralla

Diff from mypy_primer, showing the effect of this PR on open source code:

werkzeug (https://github.com/pallets/werkzeug)
+ src/werkzeug/test.py:1274: error: Cannot determine type of "headers"  [has-type]
+ src/werkzeug/routing/map.py:735: error: Cannot determine type of "path"  [has-type]
+ src/werkzeug/routing/map.py:735: error: Cannot determine type of "domain_part"  [has-type]

python-chess (https://github.com/niklasf/python-chess)
+ chess/pgn.py:481: error: Cannot determine type of "tail"  [has-type]
+ chess/pgn.py:481: error: Cannot determine type of "head"  [has-type]

pylint (https://github.com/pycqa/pylint)
+ pylint/checkers/base_checker.py:228: error: Cannot determine type of "msg"  [has-type]
+ pylint/checkers/base_checker.py:228: error: Cannot determine type of "descr"  [has-type]
+ pylint/checkers/base_checker.py:228: error: Cannot determine type of "symbol"  [has-type]

steam.py (https://github.com/Gobot1234/steam.py)
- steam/game_server.py:285: error: "None" not callable  [misc]
+ steam/game_server.py:283: error: Cannot determine type of "op"  [has-type]
+ steam/game_server.py:284: error: Cannot determine type of "query_1"  [has-type]
+ steam/game_server.py:285: error: Cannot determine type of "query_2"  [has-type]
+ steam/game_server.py:285: error: Cannot determine type of "query_1"  [has-type]

aiohttp (https://github.com/aio-libs/aiohttp)
+ aiohttp/client_reqrep.py:348: error: Cannot determine type of "key"  [has-type]
+ aiohttp/client_reqrep.py:349: error: Cannot determine type of "key"  [has-type]
+ aiohttp/client_reqrep.py:349: error: Cannot determine type of "value"  [has-type]
+ aiohttp/client_reqrep.py:351: error: Cannot determine type of "key"  [has-type]
+ aiohttp/client_reqrep.py:351: error: Cannot determine type of "value"  [has-type]

bokeh (https://github.com/bokeh/bokeh)
+ src/bokeh/colors/color.py: note: In member "from_tuple" of class "RGB":
+ src/bokeh/colors/color.py:302:24: error: Cannot determine type of "r"  [has-type]
+ src/bokeh/colors/color.py:302:27: error: Cannot determine type of "g"  [has-type]
+ src/bokeh/colors/color.py:302:30: error: Cannot determine type of "b"  [has-type]
+ src/bokeh/colors/color.py:305:24: error: Cannot determine type of "r"  [has-type]
+ src/bokeh/colors/color.py:305:27: error: Cannot determine type of "g"  [has-type]
+ src/bokeh/colors/color.py:305:30: error: Cannot determine type of "b"  [has-type]
+ src/bokeh/colors/color.py:305:33: error: Cannot determine type of "a"  [has-type]

pydantic (https://github.com/samuelcolvin/pydantic)
+ pydantic/color.py:159: error: Cannot determine type of "s"  [has-type]
+ pydantic/color.py:159: error: Cannot determine type of "li"  [has-type]
+ pydantic/color.py:159: error: Cannot determine type of "h"  [has-type]
+ pydantic/color.py:162: error: Cannot determine type of "s"  [has-type]
+ pydantic/color.py:162: error: Cannot determine type of "li"  [has-type]
+ pydantic/color.py:162: error: Cannot determine type of "h"  [has-type]
+ pydantic/color.py:162: error: Cannot determine type of "a"  [has-type]

github-actions[bot] avatar Jan 12 '23 15:01 github-actions[bot]

I checked the primer diff for pylint and simplified the relevant code. The error happens in the last line:

MessageDefinitionTuple = Union[
    Tuple[str, str, str],
    Tuple[str, str, str, ExtraMessageOptions],
]

options: ExtraMessageOptions = {}
if len(msg_tuple) == 4:
    (msg, symbol, descr, options) = msg_tuple  # type: ignore[misc]
elif len(msg_tuple) == 3:
    (msg, symbol, descr) = msg_tuple  # type: ignore[misc]
else:
    raise InvalidMessageError("invalid error message")
return MessageDefinition(self, msgid, msg, descr, symbol, **options)

So, the new error message seems valid.

tyralla avatar Jan 12 '23 16:01 tyralla

The differences reported for steam are also related to an ignored tuple error:

_raw: tuple[Query[Any], Operator, T_co] | tuple[str]

self._raw = raw  # type: ignore  # can't tell if this is a pyright bug

query_1, op, query_2 = self._raw

return op.format(
    query_1.query,
    query_2.query if isinstance(query_2, Query) else query_1._callback(query_2),
)

However, this reminds me that I forgot to consider ellipsis cases, e.g. Tuple[int, ...].

tyralla avatar Jan 12 '23 16:01 tyralla

However, this reminds me that I forgot to consider ellipsis cases, e.g. Tuple[int, ...].

It's okay. Mypy seems to handle Tuple[int, ...] as Instance, so this case is already covered by isinstance(item, Instance) and self.type_is_iterable(item) for handling iterables like List[int]. Only fixed-size tuples seem to be translated to TupleType. I will adjust a test case nevertheless.

tyralla avatar Jan 12 '23 19:01 tyralla

Last example pydantic:


HslColorTuple = Union[Tuple[float, float, float], Tuple[float, float, float, float]]

def as_hsl_tuple(self, *, alpha: Optional[bool] = None) -> HslColorTuple:
    ...

def as_hsl(self) -> str:
    if self._rgba.alpha is None:
        h, s, li = self.as_hsl_tuple(alpha=False)  # type: ignore
        return f'hsl({h * 360:0.0f}, {s:0.0%}, {li:0.0%})'
    else:
        h, s, li, a = self.as_hsl_tuple(alpha=True)  # type: ignore
        return f'hsl({h * 360:0.0f}, {s:0.0%}, {li:0.0%}, {round(a, 2)})'

Still the same reason.

tyralla avatar Jan 12 '23 20:01 tyralla

Diff from mypy_primer, showing the effect of this PR on open source code:

werkzeug (https://github.com/pallets/werkzeug)
+ src/werkzeug/test.py:1274: error: Cannot determine type of "headers"  [has-type]
+ src/werkzeug/routing/map.py:735: error: Cannot determine type of "path"  [has-type]
+ src/werkzeug/routing/map.py:735: error: Cannot determine type of "domain_part"  [has-type]

python-chess (https://github.com/niklasf/python-chess)
+ chess/pgn.py:481: error: Cannot determine type of "tail"  [has-type]
+ chess/pgn.py:481: error: Cannot determine type of "head"  [has-type]

aiohttp (https://github.com/aio-libs/aiohttp)
+ aiohttp/client_reqrep.py:348: error: Cannot determine type of "key"  [has-type]
+ aiohttp/client_reqrep.py:349: error: Cannot determine type of "key"  [has-type]
+ aiohttp/client_reqrep.py:349: error: Cannot determine type of "value"  [has-type]
+ aiohttp/client_reqrep.py:351: error: Cannot determine type of "key"  [has-type]
+ aiohttp/client_reqrep.py:351: error: Cannot determine type of "value"  [has-type]

steam.py (https://github.com/Gobot1234/steam.py)
- steam/game_server.py:285: error: "None" not callable  [misc]
+ steam/game_server.py:283: error: Cannot determine type of "op"  [has-type]
+ steam/game_server.py:284: error: Cannot determine type of "query_1"  [has-type]
+ steam/game_server.py:285: error: Cannot determine type of "query_2"  [has-type]
+ steam/game_server.py:285: error: Cannot determine type of "query_1"  [has-type]

pylint (https://github.com/pycqa/pylint)
+ pylint/checkers/base_checker.py:228: error: Cannot determine type of "msg"  [has-type]
+ pylint/checkers/base_checker.py:228: error: Cannot determine type of "descr"  [has-type]
+ pylint/checkers/base_checker.py:228: error: Cannot determine type of "symbol"  [has-type]

bokeh (https://github.com/bokeh/bokeh)
+ src/bokeh/colors/color.py: note: In member "from_tuple" of class "RGB":
+ src/bokeh/colors/color.py:302:24: error: Cannot determine type of "r"  [has-type]
+ src/bokeh/colors/color.py:302:27: error: Cannot determine type of "g"  [has-type]
+ src/bokeh/colors/color.py:302:30: error: Cannot determine type of "b"  [has-type]
+ src/bokeh/colors/color.py:305:24: error: Cannot determine type of "r"  [has-type]
+ src/bokeh/colors/color.py:305:27: error: Cannot determine type of "g"  [has-type]
+ src/bokeh/colors/color.py:305:30: error: Cannot determine type of "b"  [has-type]
+ src/bokeh/colors/color.py:305:33: error: Cannot determine type of "a"  [has-type]

pydantic (https://github.com/samuelcolvin/pydantic)
+ pydantic/color.py:159: error: Cannot determine type of "s"  [has-type]
+ pydantic/color.py:159: error: Cannot determine type of "li"  [has-type]
+ pydantic/color.py:159: error: Cannot determine type of "h"  [has-type]
+ pydantic/color.py:162: error: Cannot determine type of "s"  [has-type]
+ pydantic/color.py:162: error: Cannot determine type of "li"  [has-type]
+ pydantic/color.py:162: error: Cannot determine type of "h"  [has-type]
+ pydantic/color.py:162: error: Cannot determine type of "a"  [has-type]

github-actions[bot] avatar Jan 12 '23 20:01 github-actions[bot]

Maybe it's better to prevent such follow-up error reports by analysing all possible union members instead of quitting when a single union member is problematic.

The intermediate construction of tuples is unnecessary. Checking each lvalue directly against the union of its relevant rvalue types is sufficient and avoids the hard-to-understand convert_star_rvalue_type argument.

I will change both aspects in the next commit.

tyralla avatar Jan 14 '23 18:01 tyralla

Diff from mypy_primer, showing the effect of this PR on open source code:

werkzeug (https://github.com/pallets/werkzeug)
+ src/werkzeug/test.py:1273: error: Unused "type: ignore" comment
+ src/werkzeug/routing/map.py:734: error: Unused "type: ignore" comment

porcupine (https://github.com/Akuli/porcupine)
+ porcupine/plugins/directory_tree.py:412: error: Unused "type: ignore" comment

github-actions[bot] avatar Jan 14 '23 19:01 github-actions[bot]

Seems, Mypy primer tells me I forgot to handle other kinds of not iterable types like None and Literal.

tyralla avatar Jan 15 '23 20:01 tyralla

According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉

github-actions[bot] avatar Jan 15 '23 21:01 github-actions[bot]

According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉

github-actions[bot] avatar Jan 16 '23 05:01 github-actions[bot]

@ilevkivskyi: Would you like to review this one? I am asking you because it changes code you worked on. (And because this pull request grows old. So far, resolving conflicts should be simple.)

tyralla avatar Apr 20 '23 16:04 tyralla