agronholm / typeguard

Run-time type checker for Python
Other
1.5k stars 112 forks source link

False positive when checking `<class 'types.GenericAlias'>`(e.g.`dict[str, str]`) against `type` #432

Closed fefe982 closed 5 months ago

fefe982 commented 7 months ago

Things to check first

Typeguard version

4.1.5

Python version

3.12.0

What happened?

With this file:

import typeguard

@typeguard.typechecked
def foo(t: type):
    pass

foo(dict[str,str])

typeguard reports:

Traceback (most recent call last):
  File "/****/test.py", line 7, in <module>
    foo(dict[str,str])
  File "/****/test.py", line 4, in foo
    def foo(t: type):
  File "/****/lib/python3.12/site-packages/typeguard/_functions.py", line 138, in check_argument_types
    check_type_internal(value, annotation, memo)
  File "/****/lib/python3.12/site-packages/typeguard/_checkers.py", line 759, in check_type_internal
    checker(value, origin_type, args, memo)
  File "/****/lib/python3.12/site-packages/typeguard/_checkers.py", line 438, in check_class
    raise TypeCheckError("is not a class")
typeguard.TypeCheckError: argument "t" (types.GenericAlias) is not a class

It should pass the test

How can we reproduce the bug?

With this file:

import typeguard

@typeguard.typechecked
def foo(t: type):
    pass

foo(dict[str,str])

typeguard reports:

Traceback (most recent call last):
  File "/****/test.py", line 7, in <module>
    foo(dict[str,str])
  File "/****/test.py", line 4, in foo
    def foo(t: type):
  File "/****/lib/python3.12/site-packages/typeguard/_functions.py", line 138, in check_argument_types
    check_type_internal(value, annotation, memo)
  File "/****/lib/python3.12/site-packages/typeguard/_checkers.py", line 759, in check_type_internal
    checker(value, origin_type, args, memo)
  File "/****/lib/python3.12/site-packages/typeguard/_checkers.py", line 438, in check_class
    raise TypeCheckError("is not a class")
typeguard.TypeCheckError: argument "t" (types.GenericAlias) is not a class
fefe982 commented 7 months ago

Typeguard uses check_class in _checkers.py to check for type annotation.

It first uses

    if not isclass(value):
        raise TypeCheckError("is not a class")

to check whether the type passed in is a class.

According to Python doc, python introduced generic aliases since 3.9, and dict[str, str] is an instance of types.GenericAlias:

>>> from types import GenericAlias

>>> list[int] == GenericAlias(list, (int,))
True

>>> dict[str, int] == GenericAlias(dict, (str, int))
True

Through some test, I found that in python 3.9, inspect.isclass(dict[str, str] returns True in 3.9 and 3.10, but returns False in 3.11 and 3.12. So dict[str,str] cannot pass the check in 3.11 or 3.12.

I tried to change the check into if not isclass(value) and not isinstance(value, types.GenericAlias): and it fixed the problem.