Skip to content

Commit bae241b

Browse files
committed
fix edge case with namedtuple
1 parent 8fd88ee commit bae241b

File tree

4 files changed

+102
-60
lines changed

4 files changed

+102
-60
lines changed

docs/source/error_code_list3.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,4 +147,13 @@ basedmypy has lint rules regarding their validity as ``Callable``\s:
147147
148148
A().a(1) # runtime TypeError: <lambda>() takes 1 positional argument but 2 were given
149149
150+
151+
.. _code-unhandled-scenario:
152+
153+
A scenario isn't being handled correctly by basedmypy itself
154+
------------------------------------------------------------
155+
156+
Sometimes, the implementation of a the type system is not completely sound
157+
and basedmypy needs to be updated.
158+
150159
.. _code-reveal:

mypy/checker.py

Lines changed: 76 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3152,6 +3152,80 @@ def check_type_alias_rvalue(self, s: AssignmentStmt) -> None:
31523152
alias_type = self.expr_checker.accept(s.rvalue)
31533153
self.store_type(s.lvalues[-1], alias_type)
31543154

3155+
def check_function_on_class(
3156+
self,
3157+
lvalue: Lvalue,
3158+
rvalue: Expression,
3159+
p_lvalue_type: ProperType | None,
3160+
p_rvalue_type: ProperType | None,
3161+
):
3162+
"""Special case: function assigned to an instance var via the class will be methodised"""
3163+
type_type = None
3164+
expr_type = None
3165+
if isinstance(lvalue, MemberExpr):
3166+
expr_type = self.expr_checker.accept(lvalue.expr)
3167+
proper = get_proper_type(expr_type)
3168+
if isinstance(proper, CallableType) and proper.is_type_obj():
3169+
type_type = expr_type
3170+
if not (
3171+
(type_type or self.scope.active_class())
3172+
and isinstance(p_rvalue_type, CallableType)
3173+
and isinstance(p_lvalue_type, CallableType)
3174+
):
3175+
return
3176+
node = None
3177+
if type_type:
3178+
assert expr_type
3179+
proper = get_proper_type(expr_type)
3180+
assert isinstance(proper, CallableType)
3181+
proper = get_proper_type(proper.ret_type)
3182+
if isinstance(proper, TupleType):
3183+
proper = proper.partial_fallback
3184+
if not isinstance(proper, Instance):
3185+
self.msg.fail(
3186+
"This value is some special cased thing, needs to be handled separately",
3187+
rvalue,
3188+
code=codes.UNHANDLED_SCENARIO,
3189+
)
3190+
return
3191+
assert isinstance(lvalue, (NameExpr, MemberExpr))
3192+
table_node = proper.type.get(lvalue.name)
3193+
if table_node is None:
3194+
# work around https://github.com/python/mypy/issues/17316
3195+
return
3196+
node = table_node.node
3197+
class_var = (isinstance(node, Var) and node.is_classvar) or (
3198+
isinstance(lvalue, NameExpr)
3199+
and isinstance(lvalue.node, Var)
3200+
and lvalue.node.is_classvar
3201+
)
3202+
if p_rvalue_type.is_function:
3203+
if codes.CALLABLE_FUNCTIONTYPE in self.options.enabled_error_codes:
3204+
if not (class_var and p_lvalue_type.is_function):
3205+
self.msg.fail(
3206+
'Assigning a "FunctionType" on the class will become a "MethodType"',
3207+
rvalue,
3208+
code=codes.CALLABLE_FUNCTIONTYPE,
3209+
)
3210+
if not class_var:
3211+
self.msg.note(
3212+
'Consider setting it on the instance, or using "ClassVar"', rvalue
3213+
)
3214+
elif (
3215+
codes.POSSIBLE_FUNCTION in self.options.enabled_error_codes
3216+
and type(p_rvalue_type) is CallableType
3217+
and p_rvalue_type.is_callable
3218+
):
3219+
self.msg.fail(
3220+
'This "CallableType" could be a "FunctionType", which when assigned via the class, would produce a "MethodType"',
3221+
rvalue,
3222+
code=codes.POSSIBLE_FUNCTION,
3223+
)
3224+
if class_var:
3225+
self.msg.note('Consider changing the type to "FunctionType"', rvalue)
3226+
elif not p_lvalue_type.is_function:
3227+
self.msg.note('Consider setting it on the instance, or using "ClassVar"', rvalue)
3228+
31553229
def check_assignment(
31563230
self,
31573231
lvalue: Lvalue,
@@ -3306,66 +3380,8 @@ def check_assignment(
33063380
self.msg.concrete_only_assign(p_lvalue_type, rvalue)
33073381
return
33083382

3309-
# Special case: function assigned to an instance var via the class will be methodised
3310-
type_type = None
3311-
expr_type = None
3312-
if isinstance(lvalue, MemberExpr):
3313-
expr_type = self.expr_checker.accept(lvalue.expr)
3314-
proper = get_proper_type(expr_type)
3315-
if isinstance(proper, CallableType) and proper.is_type_obj():
3316-
type_type = expr_type
3317-
if (
3318-
(type_type or self.scope.active_class())
3319-
and isinstance(p_rvalue_type, CallableType)
3320-
and isinstance(p_lvalue_type, CallableType)
3321-
):
3322-
node = None
3323-
if type_type:
3324-
assert expr_type
3325-
proper = get_proper_type(expr_type)
3326-
assert isinstance(proper, CallableType)
3327-
proper = get_proper_type(proper.ret_type)
3328-
assert isinstance(proper, Instance)
3329-
assert isinstance(lvalue, (NameExpr, MemberExpr))
3330-
table_node = proper.type.get(lvalue.name)
3331-
if table_node is None:
3332-
# work around https://github.com/python/mypy/issues/17316
3333-
return
3334-
node = table_node.node
3335-
class_var = (isinstance(node, Var) and node.is_classvar) or (
3336-
isinstance(lvalue, NameExpr)
3337-
and isinstance(lvalue.node, Var)
3338-
and lvalue.node.is_classvar
3339-
)
3340-
if p_rvalue_type.is_function:
3341-
if codes.CALLABLE_FUNCTIONTYPE in self.options.enabled_error_codes:
3342-
if not (class_var and p_lvalue_type.is_function):
3343-
self.msg.fail(
3344-
'Assigning a "FunctionType" on the class will become a "MethodType"',
3345-
rvalue,
3346-
code=codes.CALLABLE_FUNCTIONTYPE,
3347-
)
3348-
if not class_var:
3349-
self.msg.note(
3350-
'Consider setting it on the instance, or using "ClassVar"',
3351-
rvalue,
3352-
)
3353-
elif (
3354-
codes.POSSIBLE_FUNCTION in self.options.enabled_error_codes
3355-
and type(p_rvalue_type) is CallableType
3356-
and p_rvalue_type.is_callable
3357-
):
3358-
self.msg.fail(
3359-
'This "CallableType" could be a "FunctionType", which when assigned via the class, would produce a "MethodType"',
3360-
rvalue,
3361-
code=codes.POSSIBLE_FUNCTION,
3362-
)
3363-
if class_var:
3364-
self.msg.note('Consider changing the type to "FunctionType"', rvalue)
3365-
elif not p_lvalue_type.is_function:
3366-
self.msg.note(
3367-
'Consider setting it on the instance, or using "ClassVar"', rvalue
3368-
)
3383+
self.check_function_on_class(lvalue, rvalue, p_lvalue_type, p_rvalue_type)
3384+
33693385
proper_right = get_proper_type(rvalue_type)
33703386
if (
33713387
isinstance(lvalue, RefExpr)

mypy/errorcodes.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,12 @@ def __hash__(self) -> int:
301301
POSSIBLE_FUNCTION: Final[ErrorCode] = ErrorCode(
302302
"possible-function", "possible FunctionType on class", "General", default_enabled=False
303303
)
304+
UNHANDLED_SCENARIO: Final[ErrorCode] = ErrorCode(
305+
"unhandled-scenario",
306+
"an unknown error occurred. raise an issue",
307+
"General",
308+
default_enabled=False,
309+
)
304310

305311
REGEX: Final = ErrorCode("regex", "Regex related errors", "General")
306312
REVEAL: Final = ErrorCode("reveal", "Reveal types at check time", "General")

test-data/unit/check-based-callable.test

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,3 +271,14 @@ reveal_type(A().f) # N: Revealed type is "_NamedCallable & () -> None"
271271
reveal_type(A().c) # N: Revealed type is "_NamedCallable & () -> None"
272272
reveal_type(A().s) # N: Revealed type is "_NamedCallable & () -> None"
273273
[builtins fixtures/callable.pyi]
274+
275+
276+
[case testNonInstanceThing]
277+
# test a crash when the subject isn't an instance
278+
from collections import namedtuple
279+
280+
f = namedtuple("f", "")
281+
f.__repr__ = lambda _: "" # E: "type[f]" has no attribute "__repr__" [attr-defined] \
282+
# E: Expression type contains "Any" (has type "def (_: Untyped) -> str") [no-any-expr]
283+
reveal_type(f) # N: Revealed type is "() -> tuple[(), fallback=__main__.f]"
284+
[builtins fixtures/tuple.pyi]

0 commit comments

Comments
 (0)