Skip to content
Closed
Show file tree
Hide file tree
Changes from 8 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
6 changes: 6 additions & 0 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ def check_first_pass(self) -> None:
Deferred functions will be processed by check_second_pass().
"""
self.recurse_into_functions = True
self.prepare_types()
with state.strict_optional_set(self.options.strict_optional):
self.errors.set_file(self.path, self.tree.fullname, scope=self.tscope)
with self.tscope.module_scope(self.tree.fullname):
Expand Down Expand Up @@ -366,6 +367,11 @@ def check_second_pass(self,
self.check_partial(node)
return True

def prepare_types(self) -> None:
"""Additional preparations before actual type checking."""
# We need to set `TypeType` fallback, since it is now unset:
TypeType.fallback = self.type_type()

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.

This is inconsistent with how fallbacks are set everywhere else. I don't think that we should use a global (or class) variable for this. Instead, we should look up the fallback when constructing an instance.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, exactly. This is a single variable, because TypeType has always a fallback to builtins.type. But, since TypeType instances are created quite commonly, I've decided to make it a class-variable, so we can skip addding TypeType(inst, fallback=self.named_type('builtins.type')) everywhere.

At some places in the code, we can't even do that: because chk instance is not available there.


def check_partial(self, node: Union[DeferredNodeType, FineGrainedDeferredNodeType]) -> None:
if isinstance(node, MypyFile):
self.check_top_level(node)
Expand Down
5 changes: 4 additions & 1 deletion mypy/subtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,10 @@ def visit_type_type(self, left: TypeType) -> bool:
item = get_proper_type(item.upper_bound)
if isinstance(item, Instance):
metaclass = item.type.metaclass_type
return metaclass is not None and self._is_subtype(metaclass, right)
if metaclass is not None and self._is_subtype(metaclass, right):
return True
if isinstance(TypeType.fallback, Instance):
return self._is_subtype(TypeType.fallback, right)
return False

def visit_type_alias_type(self, left: TypeAliasType) -> bool:
Expand Down
7 changes: 4 additions & 3 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,9 +321,10 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt
return make_optional_type(item)
elif fullname == 'typing.Callable':
return self.analyze_callable_type(t)
elif (fullname == 'typing.Type' or
(fullname == 'builtins.type' and (self.options.python_version >= (3, 9) or
self.api.is_future_flag_set('annotations')))):
elif (fullname == 'typing.Type'
or (fullname == 'builtins.type'
and (self.options.python_version >= (3, 9)
or self.api.is_future_flag_set('annotations')))):
if len(t.args) == 0:
if fullname == 'typing.Type':
any_type = self.get_omitted_any(t)
Expand Down
3 changes: 3 additions & 0 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1915,6 +1915,9 @@ class TypeType(ProperType):
# This can't be everything, but it can be a class reference,
# a generic class instance, a union, Any, a type variable...
item: ProperType
# Fallback to `builtins.type`, used for better type checking.
# Can be unset untill `typechecker` phase.
Comment thread
sobolevn marked this conversation as resolved.
Outdated
fallback: ClassVar[Optional[Instance]] = None

def __init__(self, item: Bogus[Union[Instance, AnyType, TypeVarType, TupleType, NoneType,
CallableType]], *,
Expand Down
51 changes: 51 additions & 0 deletions test-data/unit/check-generic-subtyping.test
Original file line number Diff line number Diff line change
Expand Up @@ -1033,3 +1033,54 @@ x2: X2[str, int]
reveal_type(iter(x2)) # N: Revealed type is "typing.Iterator[builtins.int*]"
reveal_type([*x2]) # N: Revealed type is "builtins.list[builtins.int*]"
[builtins fixtures/dict.pyi]

[case testTypeSubtypingWithDifferentGenericVars]
# flags: --python-version 3.10
from typing import Any, Protocol, Iterable

class Hashable(Protocol):
def __hash__(self) -> int: pass

t1: type = type
h1: Hashable = t1

t2: type[type] = type
h2: Hashable = t2

t3: type[Any] = type
h3: Hashable = t3

# Metaclass magic:

class NotHashable:
__hash__: None

h_i: Hashable = NotHashable

class NotHashableMeta(type):
__hash__: None # E: Incompatible types in assignment (expression has type "None", base class "type" defined the type as "Callable[[type[Any]], int]")

class NotHashableType(metaclass=NotHashableMeta):
pass

h_t: Hashable = NotHashableType # E: Incompatible types in assignment (expression has type "Type[NotHashableType]", variable has type "Hashable")

# Exact types:

o1: type[object] = object
o2: type[int] = int
o3 = object

h4: Hashable = o1
h5: Hashable = o2
h6: Hashable = o3

# Errors:

err1: Iterable = t1 # E: Incompatible types in assignment (expression has type "type[Any]", variable has type "Iterable[Any]")
err2: Iterable = t2 # E: Incompatible types in assignment (expression has type "Type[type[Any]]", variable has type "Iterable[Any]")
err3: Iterable = t3 # E: Incompatible types in assignment (expression has type "Type[Any]", variable has type "Iterable[Any]")
err4: int = t1 # E: Incompatible types in assignment (expression has type "type[Any]", variable has type "int")
err5: int = t2 # E: Incompatible types in assignment (expression has type "Type[type[Any]]", variable has type "int")
err6: int = t3 # E: Incompatible types in assignment (expression has type "Type[Any]", variable has type "int")
[builtins fixtures/type.pyi]
1 change: 1 addition & 0 deletions test-data/unit/fixtures/type.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class type(Generic[T]):
__name__: str
def __or__(self, other: Union[type, None]) -> type: pass
def mro(self) -> List['type']: pass
def __hash__(self) -> int: pass

class tuple(Generic[T]): pass
class function: pass
Expand Down