Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix crash with generic class definition in function #13678

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
80 changes: 42 additions & 38 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -2738,30 +2738,32 @@ def analyze_namedtuple_assign(self, s: AssignmentStmt) -> bool:
return False
lvalue = s.lvalues[0]
name = lvalue.name
internal_name, info, tvar_defs = self.named_tuple_analyzer.check_namedtuple(
s.rvalue, name, self.is_func_scope()
)
if internal_name is None:
return False
if isinstance(lvalue, MemberExpr):
self.fail("NamedTuple type as an attribute is not supported", lvalue)
return False
if internal_name != name:
self.fail(
'First argument to namedtuple() should be "{}", not "{}"'.format(
name, internal_name
),
s.rvalue,
code=codes.NAME_MATCH,
namespace = self.qualified_name(name)
with self.tvar_scope_frame(self.tvar_scope.class_frame(namespace)):
internal_name, info, tvar_defs = self.named_tuple_analyzer.check_namedtuple(
s.rvalue, name, self.is_func_scope()
)
if internal_name is None:
return False
if isinstance(lvalue, MemberExpr):
self.fail("NamedTuple type as an attribute is not supported", lvalue)
return False
if internal_name != name:
self.fail(
'First argument to namedtuple() should be "{}", not "{}"'.format(
name, internal_name
),
s.rvalue,
code=codes.NAME_MATCH,
)
return True
# Yes, it's a valid namedtuple, but defer if it is not ready.
if not info:
self.mark_incomplete(name, lvalue, becomes_typeinfo=True)
else:
self.setup_type_vars(info.defn, tvar_defs)
self.setup_alias_type_vars(info.defn)
return True
# Yes, it's a valid namedtuple, but defer if it is not ready.
if not info:
self.mark_incomplete(name, lvalue, becomes_typeinfo=True)
else:
self.setup_type_vars(info.defn, tvar_defs)
self.setup_alias_type_vars(info.defn)
return True

def analyze_typeddict_assign(self, s: AssignmentStmt) -> bool:
"""Check if s defines a typed dict."""
Expand All @@ -2775,22 +2777,24 @@ def analyze_typeddict_assign(self, s: AssignmentStmt) -> bool:
return False
lvalue = s.lvalues[0]
name = lvalue.name
is_typed_dict, info, tvar_defs = self.typed_dict_analyzer.check_typeddict(
s.rvalue, name, self.is_func_scope()
)
if not is_typed_dict:
return False
if isinstance(lvalue, MemberExpr):
self.fail("TypedDict type as attribute is not supported", lvalue)
return False
# Yes, it's a valid typed dict, but defer if it is not ready.
if not info:
self.mark_incomplete(name, lvalue, becomes_typeinfo=True)
else:
defn = info.defn
self.setup_type_vars(defn, tvar_defs)
self.setup_alias_type_vars(defn)
return True
namespace = self.qualified_name(name)
with self.tvar_scope_frame(self.tvar_scope.class_frame(namespace)):
is_typed_dict, info, tvar_defs = self.typed_dict_analyzer.check_typeddict(
s.rvalue, name, self.is_func_scope()
)
if not is_typed_dict:
return False
if isinstance(lvalue, MemberExpr):
self.fail("TypedDict type as attribute is not supported", lvalue)
return False
# Yes, it's a valid typed dict, but defer if it is not ready.
if not info:
self.mark_incomplete(name, lvalue, becomes_typeinfo=True)
else:
defn = info.defn
self.setup_type_vars(defn, tvar_defs)
self.setup_alias_type_vars(defn)
return True

def analyze_lvalues(self, s: AssignmentStmt) -> None:
# We cannot use s.type, because analyze_simple_literal_type() will set it.
Expand Down
3 changes: 2 additions & 1 deletion mypy/semanal_namedtuple.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,11 +321,12 @@ def parse_namedtuple_args(
) -> None | (tuple[list[str], list[Type], list[Expression], str, list[TypeVarLikeType], bool]):
"""Parse a namedtuple() call into data needed to construct a type.

Returns a 5-tuple:
Returns a 6-tuple:
- List of argument names
- List of argument types
- List of default values
- First argument of namedtuple
- All typevars found in the field definition
- Whether all types are ready.

Return None if the definition didn't typecheck.
Expand Down
12 changes: 6 additions & 6 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1331,29 +1331,29 @@ def bind_function_type_variables(
) -> Sequence[TypeVarLikeType]:
"""Find the type variables of the function type and bind them in our tvar_scope"""
if fun_type.variables:
defs = []
for var in fun_type.variables:
var_node = self.lookup_qualified(var.name, defn)
assert var_node, "Binding for function type variable not found within function"
var_expr = var_node.node
assert isinstance(var_expr, TypeVarLikeExpr)
self.tvar_scope.bind_new(var.name, var_expr)
return fun_type.variables
binding = self.tvar_scope.bind_new(var.name, var_expr)
defs.append(binding)
return defs
typevars = self.infer_type_variables(fun_type)
# Do not define a new type variable if already defined in scope.
typevars = [
(name, tvar) for name, tvar in typevars if not self.is_defined_type_var(name, defn)
]
defs: list[TypeVarLikeType] = []
defs = []
for name, tvar in typevars:
if not self.tvar_scope.allow_binding(tvar.fullname):
self.fail(
f'Type variable "{name}" is bound by an outer class',
defn,
code=codes.VALID_TYPE,
)
self.tvar_scope.bind_new(name, tvar)
binding = self.tvar_scope.get_binding(tvar.fullname)
assert binding is not None
binding = self.tvar_scope.bind_new(name, tvar)
defs.append(binding)

return defs
Expand Down
10 changes: 10 additions & 0 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,16 @@ def f() -> None:
a.g(a) # E: Too many arguments for "g" of "A"
[targets __main__, __main__.f]

[case testGenericClassWithinFunction]
from typing import TypeVar

def test() -> None:
T = TypeVar('T', bound='Foo')
class Foo:
def bar(self, foo: T) -> T:
pass
Copy link
Collaborator

Choose a reason for hiding this comment

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

What about using T in the function body (e.g. x: T = foo, reveal_type(x)), just in case?

Would it make sense to test using T in the class body -- this should generate an error, since the binding is out of scope. This should probably be a separate test case (or at least a separate function in this test case) to avoid interfering with the current test case.

reveal_type(Foo.bar) # N: Revealed type is "def [T <: __main__.Foo@5] (self: __main__.Foo@5, foo: T`-1) -> T`-1"

[case testConstructNestedClass]
import typing
class A:
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-namedtuple.test
Original file line number Diff line number Diff line change
Expand Up @@ -1284,7 +1284,7 @@ from typing import NamedTuple, TypeVar

T = TypeVar("T")
NT = NamedTuple("NT", [("key", int), ("value", T)])
reveal_type(NT) # N: Revealed type is "def [T] (key: builtins.int, value: T`-1) -> Tuple[builtins.int, T`-1, fallback=__main__.NT[T`-1]]"
reveal_type(NT) # N: Revealed type is "def [T] (key: builtins.int, value: T`1) -> Tuple[builtins.int, T`1, fallback=__main__.NT[T`1]]"

nts: NT[str]
reveal_type(nts) # N: Revealed type is "Tuple[builtins.int, builtins.str, fallback=__main__.NT[builtins.str]]"
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-typeddict.test
Original file line number Diff line number Diff line change
Expand Up @@ -2550,7 +2550,7 @@ from typing import TypedDict, TypeVar

T = TypeVar("T")
TD = TypedDict("TD", {"key": int, "value": T})
reveal_type(TD) # N: Revealed type is "def [T] (*, key: builtins.int, value: T`-1) -> TypedDict('__main__.TD', {'key': builtins.int, 'value': T`-1})"
reveal_type(TD) # N: Revealed type is "def [T] (*, key: builtins.int, value: T`1) -> TypedDict('__main__.TD', {'key': builtins.int, 'value': T`1})"

tds: TD[str]
reveal_type(tds) # N: Revealed type is "TypedDict('__main__.TD', {'key': builtins.int, 'value': builtins.str})"
Expand Down