Skip to content

Fix isinstance on namedtuple type aliases#21103

Open
paulorochaoliveira wants to merge 2 commits intopython:masterfrom
paulorochaoliveira:bug/namedtuple-typealias-isinstance
Open

Fix isinstance on namedtuple type aliases#21103
paulorochaoliveira wants to merge 2 commits intopython:masterfrom
paulorochaoliveira:bug/namedtuple-typealias-isinstance

Conversation

@paulorochaoliveira
Copy link
Copy Markdown

Summary

Fix #21100
Fix a false positive where isinstance(x, Alias) reports
Parameterized generics cannot be used with class or instance checks
when Alias is a TypeAlias to a class inheriting from NamedTuple.

Changes

  • Exempt namedtuple-backed tuple aliases from the generic-alias isinstance rejection path
  • Add a regression test for TypeAlias = Foo where Foo is a NamedTuple

Testing

  • ./venv/bin/pytest -n0 mypy/test/testcheck.py -k "testIsinstanceTypeAliasToNamedTuple or testIsinstanceTypeArgsAliases or testAliasNonGeneric"
  • ./venv/bin/pytest -n0 mypy/test/testcheck.py -k check-isinstance.test

@github-actions

This comment has been minimized.

@github-actions
Copy link
Copy Markdown
Contributor

According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅

@@ -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!)

Copy link
Copy Markdown
Collaborator

@A5rocks A5rocks left a comment

Choose a reason for hiding this comment

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

Actually I read the context and this is already what we do for unions, so I'm happy with this.

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.

isinstance doesn't work with TypeAlias for classes inherited from NamedTuple

3 participants