diff --git a/mypy/semanal.py b/mypy/semanal.py index 0c7ec43dd793..0a1f1cb7bde3 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -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.""" @@ -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. diff --git a/mypy/semanal_namedtuple.py b/mypy/semanal_namedtuple.py index 4375602b5076..6cb42d6c3ede 100644 --- a/mypy/semanal_namedtuple.py +++ b/mypy/semanal_namedtuple.py @@ -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. diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 37f00841562f..0c84f2a0ffb5 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -1331,19 +1331,21 @@ 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( @@ -1351,9 +1353,7 @@ def bind_function_type_variables( 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 diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 3b1eddc8a084..008fd99c1675 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -1060,6 +1060,35 @@ 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 returns_int(self) -> int: + return 0 + + def bar(self, foo: T) -> T: + x: T = foo + reveal_type(x) # N: Revealed type is "T`-1" + reveal_type(x.returns_int()) # N: Revealed type is "builtins.int" + return foo + reveal_type(Foo.bar) # N: Revealed type is "def [T <: __main__.Foo@5] (self: __main__.Foo@5, foo: T`-1) -> T`-1" + +[case testGenericClassWithInvalidTypevarUseWithinFunction] +from typing import TypeVar + +def test() -> None: + T = TypeVar('T', bound='Foo') + class Foo: + invalid: T # E: Type variable "T" is unbound \ + # N: (Hint: Use "Generic[T]" or "Protocol[T]" base class to bind "T" inside a class) \ + # N: (Hint: Use "T" in function signature to bind "T" inside a function) + + def bar(self, foo: T) -> T: + pass + [case testConstructNestedClass] import typing class A: diff --git a/test-data/unit/check-namedtuple.test b/test-data/unit/check-namedtuple.test index e4f75f57280c..4552cfb118cc 100644 --- a/test-data/unit/check-namedtuple.test +++ b/test-data/unit/check-namedtuple.test @@ -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]]" diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index 7fba4da071f3..5bfe9f4c5555 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -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})"