Skip to content

Commit

Permalink
hacks
Browse files Browse the repository at this point in the history
  • Loading branch information
hauntsaninja committed Sep 4, 2023
1 parent 2bd91d5 commit 25c9ef0
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 12 deletions.
10 changes: 5 additions & 5 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -3182,11 +3182,11 @@ def analyze_ordinary_member_access(self, e: MemberExpr, is_lvalue: bool) -> Type
member_type = analyze_member_access(
e.name,
original_type,
e,
is_lvalue,
False,
False,
self.msg,
context=e,
is_lvalue=is_lvalue,
is_super=False,
is_operator=False,
msg=self.msg,
original_type=original_type,
chk=self.chk,
in_literal_context=self.is_literal_context(),
Expand Down
27 changes: 25 additions & 2 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,26 @@ def validate_super_call(node: FuncBase, mx: MemberContext) -> None:
def analyze_type_callable_member_access(name: str, typ: FunctionLike, mx: MemberContext) -> Type:
# Class attribute.
# TODO super?
ret_type = typ.items[0].ret_type
item = typ.items[0]
ret_type = item.ret_type

# The following is a hack. mypy often represents types as CallableType, where the signature of
# CallableType is determined by __new__ or __init__ of the type (this logic is in
# type_object_type). Then if we ever need the TypeInfo or an instance of the type, we fish
# around for the return type in CallableType.type_object. Unfortunately, this is incorrect if
# __new__ returns an unrelated type, but we can kind of salvage things by fishing around in
# CallableType.bound_args
self_type: Type = typ
if len(item.bound_args) == 1 and item.bound_args[0]:
bound_arg = get_proper_type(item.bound_args[0])
if isinstance(bound_arg, Instance) and isinstance(ret_type, Instance):
# Unfortunately, generic arguments have already been determined for us. We need these,
# see e.g. testGenericClassMethodUnboundOnClass. So just copy them over to our type.
# This does the wrong thing with custom __new__, see testNewReturnType15, but is
# a lesser evil.
ret_type = bound_arg.copy_modified(args=ret_type.args)
self_type = TypeType(ret_type)

assert isinstance(ret_type, ProperType)
if isinstance(ret_type, TupleType):
ret_type = tuple_fallback(ret_type)
Expand All @@ -394,7 +413,11 @@ def analyze_type_callable_member_access(name: str, typ: FunctionLike, mx: Member
# See https://github.com/python/mypy/pull/1787 for more info.
# TODO: do not rely on same type variables being present in all constructor overloads.
result = analyze_class_attribute_access(
ret_type, name, mx, original_vars=typ.items[0].variables, mcs_fallback=typ.fallback
ret_type,
name,
mx.copy_modified(self_type=self_type),
original_vars=typ.items[0].variables,
mcs_fallback=typ.fallback,
)
if result:
return result
Expand Down
7 changes: 3 additions & 4 deletions mypy/typeops.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,9 @@ def class_callable(
# this case (although we should probably change it at some point)
# See testValueTypeWithNewInParentClass
# Also see testSelfTypeInGenericClassUsedFromAnotherGenericClass1
if (
not is_subtype(default_ret_type, explicit_type, ignore_type_params=True)
or is_subtype(explicit_type, default_ret_type, ignore_type_params=True)
):
if not is_subtype(
default_ret_type, explicit_type, ignore_type_params=True
) or is_subtype(explicit_type, default_ret_type, ignore_type_params=True):
ret_type = explicit_type
elif (
# We have to skip protocols, because it can be a subtype of a return type
Expand Down
2 changes: 1 addition & 1 deletion mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1466,7 +1466,7 @@ def deserialize(cls, data: JsonDict | str) -> Instance:
def copy_modified(
self,
*,
args: Bogus[list[Type]] = _dummy,
args: Bogus[Sequence[Type]] = _dummy,
last_known_value: Bogus[LiteralType | None] = _dummy,
) -> Instance:
new = Instance(
Expand Down
35 changes: 35 additions & 0 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -7007,6 +7007,41 @@ reveal_type(A()) # E: Cannot instantiate protocol class "Foo" \
reveal_type(A().foo()) # E: Cannot instantiate protocol class "Foo" \
# N: Revealed type is "builtins.str"

[case testNewReturnType14]
from __future__ import annotations

class A:
def __new__(cls) -> int: raise # E: Incompatible return type for "__new__" (returns "int", but must return a subtype of "A")

class B(A):
@classmethod
def foo(cls) -> int: raise

reveal_type(B.foo()) # N: Revealed type is "builtins.int"
[builtins fixtures/classmethod.pyi]

[case testNewReturnType15]
from typing import Generic, Type, TypeVar

T = TypeVar("T")

class A(Generic[T]):
def __new__(cls) -> B[int]: ...
@classmethod
def foo(cls: Type[T]) -> T: ...

class B(A[T]): ...

# These are incorrect, should be Any and str
# See https://github.com/python/mypy/pull/16020
reveal_type(B.foo()) # N: Revealed type is "builtins.int"
reveal_type(B[str].foo()) # N: Revealed type is "builtins.int"

class C(A[str]): ...

reveal_type(C.foo()) # N: Revealed type is "builtins.str"
[builtins fixtures/classmethod.pyi]

[case testMetaclassPlaceholderNode]
from sympy.assumptions import ManagedProperties
from sympy.ops import AssocOp
Expand Down

0 comments on commit 25c9ef0

Please sign in to comment.