Open MyKo101 opened 4 months ago
I believe the following fix to the check_uniontype()
function would cover this issue:
def check_uniontype(
value: Any,
origin_type: Any,
args: tuple[Any, ...],
memo: TypeCheckMemo,
) -> None:
# NEW CODE HERE VVVVVV
if len(args) == 0:
if isinstance(value,types.UnionType):
return
else:
raise TypeCheckError("is not a UnionType")
# ^^^^^^^^^^^^^^^^^
errors: dict[str, TypeCheckError] = {}
for type_ in args:
try:
check_type_internal(value, type_, memo)
return
except TypeCheckError as exc:
errors[get_type_name(type_)] = exc
formatted_errors = indent(
"\n".join(f"{key}: {error}" for key, error in errors.items()), " "
)
raise TypeCheckError(f"did not match any element in the union:\n{formatted_errors}")
Things to check first
[X] I have searched the existing issues and didn't find my bug already reported there
[X] I have checked that my bug is still present in the latest release
Typeguard version
4.3.0
Python version
3.10
What happened?
When trying to check if an object is of the type
UnionType
, typeguard rejects regardless of whether the type matches. It is important to note that I am not checking if an object is one of multiple types (using, for example does"hello"
matchint | str
), I am looking at whether an object is aUnionType
(for example, ifint | str
is aUnionType
). See the MWE for more information and to make clearer what my code is doing.When using the
|
operator when defining type-hints, it will either generate aUnion
orUnionType
type object. If the two objects you are combining are standardtypes
, then it will createUnionType
but if they are complex constructs (such as aLiteral
) then it will create aUnion
object. I am not sure if it is as cut-and-dry as this, but this seems to be the general behaviour.These are handled in
typeguard
by thecheck_union()
andcheck_uniontype()
functions which are currently identical. These function looks through all of the arguments of the provided type and if one of them matches (dispatched tocheck_type_internal()
) then it returns and is considered to have passed. However in my code, I am usingUnionType
on its own without any arguments and so this loop is never entered and thecheck_uniontype()
function always raises aTypeCheckError
.From my understanding the only time a
UnionType
object will be in use without arguments is when it is being used explicitly, and otherwise it will have been created via a|
operation between twotypes
. I also believe that aUnion
would always have arguments as this aSpecialForm
and so other checkers such asmypy
would already fail in this instance and if your function is to take a generalUnion
construct, then it would have to take aSpecialForm
and check the subclassing because otherwise you would need to useUnion[X,Y]
.For this reason, I think a solution would be to include an early escape within the
check_uniontype()
function if theargs
is an empty list and if thevalue
is explicitly aUnionType
. I appreciate if further consideration is needed since the difference betweenUnion
andUnionType
is probably more nuanced than I am assuming.How can we reproduce the bug?
The following code demonstrates the issue (and hopefully will provide some insight into what I am trying to check).
A smaller example would be the following which calls the
check_type()
function directly: