Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
8 changes: 6 additions & 2 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -7148,7 +7148,11 @@ def get_isinstance_type(self, expr: Expression) -> list[TypeRange] | None:
elif isinstance(typ, TypeType):
# Type[A] means "any type that is a subtype of A" rather than "precisely type A"
# we indicate this by setting is_upper_bound flag
types.append(TypeRange(typ.item, is_upper_bound=True))
is_upper_bound = True
if isinstance(typ.item, NoneType):
# except for Type[None], because "'NoneType' is not an acceptable base type"
is_upper_bound = False
types.append(TypeRange(typ.item, is_upper_bound=is_upper_bound))
elif isinstance(typ, Instance) and typ.type.fullname == "builtins.type":
object_type = Instance(typ.type.mro[-1], [])
types.append(TypeRange(object_type, is_upper_bound=True))
Expand Down Expand Up @@ -7589,7 +7593,7 @@ def convert_to_typetype(type_map: TypeMap) -> TypeMap:
if isinstance(t, TypeVarType):
t = t.upper_bound
# TODO: should we only allow unions of instances as per PEP 484?
if not isinstance(get_proper_type(t), (UnionType, Instance)):
if not isinstance(get_proper_type(t), (UnionType, Instance, NoneType)):
# unknown type; error was likely reported earlier
return {}
converted_type_map[expr] = TypeType.make_normalized(typ)
Expand Down
16 changes: 9 additions & 7 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,13 +496,13 @@ def visit_call_expr_inner(self, e: CallExpr, allow_none_return: bool = False) ->
if is_expr_literal_type(typ):
self.msg.cannot_use_function_with_type(e.callee.name, "Literal", e)
continue
if (
node
and isinstance(node.node, TypeAlias)
and isinstance(get_proper_type(node.node.target), AnyType)
):
self.msg.cannot_use_function_with_type(e.callee.name, "Any", e)
continue
if node and isinstance(node.node, TypeAlias):
target = get_proper_type(node.node.target)
if isinstance(target, AnyType):
self.msg.cannot_use_function_with_type(e.callee.name, "Any", e)
continue
if isinstance(target, NoneType):
continue
if (
isinstance(typ, IndexExpr)
and isinstance(typ.analyzed, (TypeApplication, TypeAliasExpr))
Expand Down Expand Up @@ -4670,6 +4670,8 @@ class LongName(Generic[T]): ...
return type_object_type(tuple_fallback(item).type, self.named_type)
elif isinstance(item, TypedDictType):
return self.typeddict_callable_from_context(item)
elif isinstance(item, NoneType):
return TypeType(item, line=item.line, column=item.column)
elif isinstance(item, AnyType):
return AnyType(TypeOfAny.from_another_any, source_any=item)
else:
Expand Down
35 changes: 35 additions & 0 deletions test-data/unit/check-narrowing.test
Original file line number Diff line number Diff line change
Expand Up @@ -1910,3 +1910,38 @@ if len(x) == a:
else:
reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int, builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]"
[builtins fixtures/len.pyi]


[case testNarrowingIsSubclassNoneType1]
from typing import Type, Union

def f(cls: Type[Union[None, int]]) -> None:
if issubclass(cls, int):
reveal_type(cls) # N: Revealed type is "Type[builtins.int]"
else:
reveal_type(cls) # N: Revealed type is "Type[None]"
[builtins fixtures/isinstance.pyi]


[case testNarrowingIsSubclassNoneType2]
from typing import Type, Union

def f(cls: Type[Union[None, int]]) -> None:
if issubclass(cls, type(None)):
reveal_type(cls) # N: Revealed type is "Type[None]"
else:
reveal_type(cls) # N: Revealed type is "Type[builtins.int]"
[builtins fixtures/isinstance.pyi]


[case testNarrowingIsSubclassNoneType3]
from typing import Type, Union

NoneType_ = type(None)

def f(cls: Type[Union[None, int]]) -> None:
if issubclass(cls, NoneType_):
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

One case is still broken, I believe: (python3.10+)

if issubclass(cls, types.Nonetype)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

You are right. The definition in types.pyi is not helpful. I assumed something similar to mentioned test:

NoneType = type(None)

But it is:

    @final
    class NoneType:
        def __bool__(self) -> Literal[False]: ...

My suggestion is to let ExpressionChecker.analyze_ref_expr directly return TypeType(NoneType()) when it encounters the full name "types.NoneType". (already pushed)

reveal_type(cls) # N: Revealed type is "Type[None]"
else:
reveal_type(cls) # N: Revealed type is "Type[builtins.int]"
[builtins fixtures/isinstance.pyi]