Skip to content

fix list.__add__ to use the expected type#14801

Open
KotlinIsland wants to merge 2 commits intopython:mainfrom
KotlinIsland:list.__add__
Open

fix list.__add__ to use the expected type#14801
KotlinIsland wants to merge 2 commits intopython:mainfrom
KotlinIsland:list.__add__

Conversation

@KotlinIsland
Copy link
Copy Markdown
Contributor

@KotlinIsland KotlinIsland commented Sep 28, 2025

originally: #14756

now broken (mypy)

_: list[int | str] = list[int]() + list[str]()

this break seems extremely minor (two cases in corpus)

now fixed (mypy)

_: list[int | str] = list[str]() + list[str | int]()

now fixed (pyright):

_: list[object] = list[int]() + list[str]()

@github-actions

This comment has been minimized.

@srittau
Copy link
Copy Markdown
Collaborator

srittau commented Sep 30, 2025

This has the same problem as #14755: _T1 has the same meaning as Any, so this is what we should use (with an appropriate comment). Also, the tests are not successful.

@KotlinIsland
Copy link
Copy Markdown
Contributor Author

KotlinIsland commented Sep 30, 2025

that's just not true, a type var and Any are different. if we make this Any then it will include Any, we don't want that

could you explain why you think it's Any? or respond to my example where I explain how they are different

@JelleZijlstra
Copy link
Copy Markdown
Member

One of your examples relied on an incorrect behavior in your type checker (assignability involving list[B] and list[A | Any]). Another involved a TypeVar with a default, which this one doesn't have.

@KotlinIsland
Copy link
Copy Markdown
Contributor Author

One of your examples relied on an incorrect behavior in your type checker (assignability involving list[B] and list[A | Any]). Another involved a TypeVar with a default, which this one doesn't have.

yes, i already said the first example wasn't right, i've hidden it for clarity. the case with the deafult, is to explain the situation, but i can understand that it's not 1-to-1, so consider this:

def list_unsafe(*t: object) -> list[Any | int]:
    if bool():
        return ["oops", 1]  # no error
    else:
        return [*t, 1]
        
unsafe1 = list_unsafe(None)  # `unsafe1` is `list[Any | int]`
unsafe2 = list_unsafe()  # `unsafe2` is `list[Any | int]`

def list_safe[T](*t: T) -> list[T | int]:
    if bool():
        return [1, "oops"]  # error
    else:
        return [1, *t]

safe1 = list_safe(None)  # `safe1` is `list[None | int]`
safe2 = list_safe()  # `safe2` is `list[int]`

then we just remove the redundant unused *t and we get

def list_safe[T]() -> list[T | int]:
    if bool():
        return ["oops", 1]  # error
    else:
        return [1]

safe1 = list_safe()  # `safe1` is `list[int]`
safe2: list[object] = list_safe()  # `safe2` is `list[object]`, with no error

Code sample in basedpyright playground

effectivly, T is inferred as Never in these cases and is collapsed against the int

but even if Any and a type var did have the same semantics here, it would still be a mistake to use Any. Any means "not typed", just because something has the same outcome, it should still be typed correctly. print is def print(*values: object, ... why not use Any here if it has the same meaning as object?

@github-actions

This comment has been minimized.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Oct 1, 2025

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

core (https://github.com/home-assistant/core)
- ...typeshed_to_test/stdlib/builtins.pyi:139: note: "__init_subclass__" of "object" defined here
+ ...typeshed_to_test/stdlib/builtins.pyi:140: note: "__init_subclass__" of "object" defined here
- ...typeshed_to_test/stdlib/builtins.pyi:139: note: "__init_subclass__" of "object" defined here
+ ...typeshed_to_test/stdlib/builtins.pyi:140: note: "__init_subclass__" of "object" defined here

zulip (https://github.com/zulip/zulip)
+ zerver/lib/onboarding_steps.py:65: error: Unsupported operand types for + ("list[OneTimeNotice]" and "list[OneTimeAction]")  [operator]
- ...typeshed_to_test/stdlib/builtins.pyi:117: note: "SubTest" defined here
+ ...typeshed_to_test/stdlib/builtins.pyi:118: note: "SubTest" defined here

ibis (https://github.com/ibis-project/ibis)
- ...typeshed_to_test/stdlib/builtins.pyi:117: note: "Any" defined here
+ ...typeshed_to_test/stdlib/builtins.pyi:118: note: "Any" defined here
- ...typeshed_to_test/stdlib/builtins.pyi:117: note: "__init__" of "object" defined here
+ ...typeshed_to_test/stdlib/builtins.pyi:118: note: "__init__" of "object" defined here

prefect (https://github.com/PrefectHQ/prefect)
- src/prefect/utilities/callables.py:579: error: Incompatible types in assignment (expression has type "list[None]", variable has type "list[Optional[expr]]")  [assignment]
- src/prefect/utilities/callables.py:579: note: "list" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance
- src/prefect/utilities/callables.py:579: note: Consider using "Sequence" instead, which is covariant
- src/prefect/utilities/callables.py:581: error: Unsupported operand types for + ("list[None]" and "list[Optional[expr]]")  [operator]
- ...typeshed_to_test/stdlib/builtins.pyi:139: note: "__init_subclass__" of "object" defined here
+ ...typeshed_to_test/stdlib/builtins.pyi:140: note: "__init_subclass__" of "object" defined here
- ...typeshed_to_test/stdlib/builtins.pyi:139: note: "__init_subclass__" of "object" defined here
+ ...typeshed_to_test/stdlib/builtins.pyi:140: note: "__init_subclass__" of "object" defined here
- ...typeshed_to_test/stdlib/builtins.pyi:139: note: "__init_subclass__" of "object" defined here
+ ...typeshed_to_test/stdlib/builtins.pyi:140: note: "__init_subclass__" of "object" defined here
- ...typeshed_to_test/stdlib/builtins.pyi:139: note: "__init_subclass__" of "object" defined here
+ ...typeshed_to_test/stdlib/builtins.pyi:140: note: "__init_subclass__" of "object" defined here
- ...typeshed_to_test/stdlib/builtins.pyi:139: note: "__init_subclass__" of "object" defined here
+ ...typeshed_to_test/stdlib/builtins.pyi:140: note: "__init_subclass__" of "object" defined here

pydantic (https://github.com/pydantic/pydantic)
- pydantic/aliases.py:29: error: Incompatible types in assignment (expression has type "list[str]", variable has type "list[int | str]")  [assignment]
- pydantic/aliases.py:29: note: "list" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance
- pydantic/aliases.py:29: note: Consider using "Sequence" instead, which is covariant
- pydantic/aliases.py:29: error: Argument 1 to "list" has incompatible type "tuple[str | int, ...]"; expected "Iterable[str]"  [arg-type]

strawberry (https://github.com/strawberry-graphql/strawberry)
- ...typeshed_to_test/stdlib/builtins.pyi:139: note: "__init_subclass__" of "object" defined here
+ ...typeshed_to_test/stdlib/builtins.pyi:140: note: "__init_subclass__" of "object" defined here
- ...typeshed_to_test/stdlib/builtins.pyi:139: note: "__init_subclass__" of "object" defined here
+ ...typeshed_to_test/stdlib/builtins.pyi:140: note: "__init_subclass__" of "object" defined here

meson (https://github.com/mesonbuild/meson)
+ mesonbuild/modules/i18n.py:165:43: error: Unsupported operand types for + ("list[File]" and "list[CustomTarget]")  [operator]

@KotlinIsland
Copy link
Copy Markdown
Contributor Author

@srittau @JelleZijlstra any update on this?

@jorenham
Copy link
Copy Markdown
Contributor

jorenham commented Mar 6, 2026

This is a smart solution that relies on the subtle difference between def f() -> Any and def g[T]() -> T. In the latter case, a free type variable is returned, that can resolve to anything it gets assigned to, much like Any. However, the difference is that the free typevar returned by f can only resolve to a single type; i.e. it "becomes" that type. This is different for Any, which is assignable to anything, and can also be reassigned to anything else. So the difference is that the free type variable returned by f can not be reassigned. I suppose you could see it as a more intelligent Any in that sense.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants