Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,10 @@ def visit_call_expr_inner(self, e: CallExpr, allow_none_return: bool = False) ->
and node
and isinstance(node.node, TypeAlias)
and not node.node.no_args
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like no_args should be True in this case? In which case this if statement is fine.

I'd rather that, unless there's a good reason for existing behavior...

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@A5rocks, I considered that, but no_args currently seems to mean more than “this alias was written without subscripting”.

In particular, it appears to assume an Instance target in a few places:

  • mypy/types.py (TypeAliasType._expand_once)
  • mypy/typeanal.py (instantiate_type_alias)
  • the alias creation logic in mypy/semanal.py

For aliases to classes inheriting from NamedTuple, the target is a TupleType with a namedtuple fallback rather than an Instance, so making no_args=True here would likely require broadening that invariant first.

Because of that, I went with the narrower fix in the isinstance check path. If you’d prefer, I can rework this to make no_args support namedtuple-backed aliases more generally, but that seemed like a larger behavioral change than needed for this bug.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's interesting! I had assumed this already worked for tuples but actually they don't work at runtime! I guess I agree that no_args is the wrong approach then. This still feels a bit hacky though so I'll think a bit more... But as a first comment, could you add a test to show that you didn't modify the following output?:

from typing import Any, TypeAlias

Alias: TypeAlias = tuple[int, int]

def is_foo(x: Any) -> bool:
    return isinstance(x, Alias)

(your code shouldn't have changed that, but unless that's already a test it's better to prevent people like me from modifying this without knowing the runtime behavior!)

and not (
isinstance(target := get_proper_type(node.node.target), TupleType)
and tuple_fallback(target).type.fullname != "builtins.tuple"
)
and not (
isinstance(union_target := get_proper_type(node.node.target), UnionType)
and (
Expand Down
15 changes: 15 additions & 0 deletions test-data/unit/check-isinstance.test
Original file line number Diff line number Diff line change
Expand Up @@ -1900,6 +1900,21 @@ isinstance(x, It2) # E: Parameterized generics cannot be used with class or ins
[builtins fixtures/isinstance.pyi]
[typing fixtures/typing-full.pyi]

[case testIsinstanceTypeAliasToNamedTuple]
# flags: --warn-unreachable
from typing import Any, NamedTuple
from typing_extensions import TypeAlias

class Foo(NamedTuple):
pass

Alias: TypeAlias = Foo

def is_foo(x: Any) -> bool:
return isinstance(x, Alias)
[builtins fixtures/tuple.pyi]
[typing fixtures/typing-full.pyi]

[case testIssubclassTypeArgs]
# flags: --warn-unreachable
from typing import Iterable, TypeVar
Expand Down
Loading