From 6d5308731aca35419523f25140bd1cd45235f955 Mon Sep 17 00:00:00 2001 From: momohatt Date: Tue, 6 Oct 2020 13:34:14 +0900 Subject: [PATCH 01/11] Fail when first arg of namedtuple doesn't match the variable name --- mypy/semanal_namedtuple.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mypy/semanal_namedtuple.py b/mypy/semanal_namedtuple.py index ce82cb84348b..da81d617f9b7 100644 --- a/mypy/semanal_namedtuple.py +++ b/mypy/semanal_namedtuple.py @@ -180,6 +180,11 @@ def check_namedtuple(self, self.store_namedtuple_info(info, name, call, is_typed) return True, info + typename = cast(Union[StrExpr, BytesExpr, UnicodeExpr], call.args[0]).value + if var_name and var_name != typename: + self.fail("First argument to namedtuple() should be '{}', not '{}'".format( + var_name, typename), call) + # We use the variable name as the class name if it exists. If # it doesn't, we use the name passed as an argument. We prefer # the variable name because it should be unique inside a @@ -188,7 +193,7 @@ def check_namedtuple(self, if var_name: name = var_name else: - name = cast(Union[StrExpr, BytesExpr, UnicodeExpr], call.args[0]).value + name = typename if var_name is None or is_func_scope: # There are two special cases where need to give it a unique name derived From d61619f937e4d7858835e787b86e76e1638555fa Mon Sep 17 00:00:00 2001 From: momohatt Date: Sun, 11 Oct 2020 22:25:03 +0900 Subject: [PATCH 02/11] Fix --- mypy/semanal.py | 16 ++++++++++------ mypy/semanal_namedtuple.py | 23 ++++++++++------------- test-data/unit/check-incremental.test | 4 ++++ test-data/unit/check-namedtuple.test | 8 ++++++++ test-data/unit/fine-grained.test | 4 ++-- 5 files changed, 34 insertions(+), 21 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 2ea444bf4ab2..aecdec4463c6 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2177,13 +2177,17 @@ def analyze_namedtuple_assign(self, s: AssignmentStmt) -> bool: return False lvalue = s.lvalues[0] name = lvalue.name - is_named_tuple, info = self.named_tuple_analyzer.check_namedtuple(s.rvalue, name, - self.is_func_scope()) - if not is_named_tuple: + internal_name, info = 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) + 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) @@ -4819,9 +4823,9 @@ def expr_to_analyzed_type(self, allow_placeholder: bool = False) -> Optional[Type]: if isinstance(expr, CallExpr): expr.accept(self) - is_named_tuple, info = self.named_tuple_analyzer.check_namedtuple(expr, None, - self.is_func_scope()) - if not is_named_tuple: + internal_name, info = self.named_tuple_analyzer.check_namedtuple(expr, None, + self.is_func_scope()) + if internal_name is None: # Some form of namedtuple is the only valid type that looks like a call # expression. This isn't a valid type. raise TypeTranslationError() diff --git a/mypy/semanal_namedtuple.py b/mypy/semanal_namedtuple.py index da81d617f9b7..c42ade500a1e 100644 --- a/mypy/semanal_namedtuple.py +++ b/mypy/semanal_namedtuple.py @@ -138,38 +138,40 @@ def check_namedtuple_classdef(self, defn: ClassDef, is_stub_file: bool def check_namedtuple(self, node: Expression, var_name: Optional[str], - is_func_scope: bool) -> Tuple[bool, Optional[TypeInfo]]: + is_func_scope: bool) -> Tuple[Optional[str], Optional[TypeInfo]]: """Check if a call defines a namedtuple. The optional var_name argument is the name of the variable to which this is assigned, if any. Return a tuple of two items: - * Can it be a valid named tuple? + * Name of the named tuple, which is passed as the first argument to namedtuple, + or None if it is not a valid named tuple * Corresponding TypeInfo, or None if not ready. If the definition is invalid but looks like a namedtuple, report errors but return (some) TypeInfo. """ if not isinstance(node, CallExpr): - return False, None + return None, None call = node callee = call.callee if not isinstance(callee, RefExpr): - return False, None + return None, None fullname = callee.fullname if fullname == 'collections.namedtuple': is_typed = False elif fullname == 'typing.NamedTuple': is_typed = True else: - return False, None + return None, None + typename = cast(Union[StrExpr, BytesExpr, UnicodeExpr], call.args[0]).value result = self.parse_namedtuple_args(call, fullname) if result: items, types, defaults, ok = result else: # This is a valid named tuple but some types are not ready. - return True, None + return typename, None if not ok: # Error. Construct dummy return value. if var_name: @@ -178,12 +180,7 @@ def check_namedtuple(self, name = 'namedtuple@' + str(call.line) info = self.build_namedtuple_typeinfo(name, [], [], {}, node.line) self.store_namedtuple_info(info, name, call, is_typed) - return True, info - - typename = cast(Union[StrExpr, BytesExpr, UnicodeExpr], call.args[0]).value - if var_name and var_name != typename: - self.fail("First argument to namedtuple() should be '{}', not '{}'".format( - var_name, typename), call) + return typename, info # We use the variable name as the class name if it exists. If # it doesn't, we use the name passed as an argument. We prefer @@ -233,7 +230,7 @@ def check_namedtuple(self, if name != var_name or is_func_scope: # NOTE: we skip local namespaces since they are not serialized. self.api.add_symbol_skip_local(name, info) - return True, info + return typename, info def store_namedtuple_info(self, info: TypeInfo, name: str, call: CallExpr, is_typed: bool) -> None: diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index da06cb8eb9c9..a4c53578e826 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -5056,7 +5056,9 @@ from typing import NamedTuple NT = NamedTuple('BadName', [('x', int)]) [builtins fixtures/tuple.pyi] [out] +tmp/b.py:2: error: First argument to namedtuple() should be 'NT', not 'BadName' [out2] +tmp/b.py:2: error: First argument to namedtuple() should be 'NT', not 'BadName' tmp/a.py:3: note: Revealed type is 'Tuple[builtins.int, fallback=b.NT]' [case testNewAnalyzerIncrementalBrokenNamedTupleNested] @@ -5076,7 +5078,9 @@ def test() -> None: NT = namedtuple('BadName', ['x', 'y']) [builtins fixtures/list.pyi] [out] +tmp/b.py:4: error: First argument to namedtuple() should be 'NT', not 'BadName' [out2] +tmp/b.py:4: error: First argument to namedtuple() should be 'NT', not 'BadName' [case testNewAnalyzerIncrementalMethodNamedTuple] diff --git a/test-data/unit/check-namedtuple.test b/test-data/unit/check-namedtuple.test index 48d4bc3df355..ea27bb6ff983 100644 --- a/test-data/unit/check-namedtuple.test +++ b/test-data/unit/check-namedtuple.test @@ -962,3 +962,11 @@ def foo(): Type1 = NamedTuple('Type1', [('foo', foo)]) # E: Function "b.foo" is not valid as a type # N: Perhaps you need "Callable[...]" or a callback protocol? [builtins fixtures/tuple.pyi] + +[case testNamedTupleTypeNameMatchesVariableName] +from typing import NamedTuple +from collections import namedtuple + +A = NamedTuple('X', [('a', int)]) # E: First argument to namedtuple() should be 'A', not 'X' +B = namedtuple('X', ['a']) # E: First argument to namedtuple() should be 'B', not 'X' +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 5761f6cb337c..4d1cd99a32dc 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -9635,12 +9635,12 @@ from n import M [file n.py] from typing import NamedTuple -M = NamedTuple('_N', [('x', int)]) +M = NamedTuple('M', [('x', int)]) [file n.py.2] # change the line numbers from typing import NamedTuple -M = NamedTuple('_N', [('x', int)]) +M = NamedTuple('M', [('x', int)]) [builtins fixtures/tuple.pyi] [out] From 15297b06d6e592a339f2d0920d3ab1d01dc379b1 Mon Sep 17 00:00:00 2001 From: momohatt Date: Sun, 11 Oct 2020 22:41:04 +0900 Subject: [PATCH 03/11] Update comment in parse_namedtuple_args --- mypy/semanal_namedtuple.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/semanal_namedtuple.py b/mypy/semanal_namedtuple.py index c42ade500a1e..694636f8205c 100644 --- a/mypy/semanal_namedtuple.py +++ b/mypy/semanal_namedtuple.py @@ -245,7 +245,7 @@ def parse_namedtuple_args(self, call: CallExpr, fullname: str Returns a 4-tuple: - List of argument names - List of argument types - - Number of arguments that have a default value + - List of default values - Whether the definition typechecked. Return None if at least one of the types is not ready. From 1d9a69b31f673cd4531f0500e5bf30a2c77c5e4a Mon Sep 17 00:00:00 2001 From: momohatt Date: Sun, 11 Oct 2020 22:50:27 +0900 Subject: [PATCH 04/11] Return dummy name as the internal namedtuple name --- mypy/semanal_namedtuple.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/semanal_namedtuple.py b/mypy/semanal_namedtuple.py index 694636f8205c..6c20f489ed9b 100644 --- a/mypy/semanal_namedtuple.py +++ b/mypy/semanal_namedtuple.py @@ -180,7 +180,7 @@ def check_namedtuple(self, name = 'namedtuple@' + str(call.line) info = self.build_namedtuple_typeinfo(name, [], [], {}, node.line) self.store_namedtuple_info(info, name, call, is_typed) - return typename, info + return name, info # We use the variable name as the class name if it exists. If # it doesn't, we use the name passed as an argument. We prefer From 98b4ebf66860adf4c3d85da8803b43c5a368a6a6 Mon Sep 17 00:00:00 2001 From: momohatt Date: Sun, 11 Oct 2020 22:54:24 +0900 Subject: [PATCH 05/11] Refine comment --- mypy/semanal_namedtuple.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/semanal_namedtuple.py b/mypy/semanal_namedtuple.py index 6c20f489ed9b..95dafe539293 100644 --- a/mypy/semanal_namedtuple.py +++ b/mypy/semanal_namedtuple.py @@ -145,7 +145,7 @@ def check_namedtuple(self, which this is assigned, if any. Return a tuple of two items: - * Name of the named tuple, which is passed as the first argument to namedtuple, + * Internal name of the named tuple (e.g. the name passed as an argument to namedtuple) or None if it is not a valid named tuple * Corresponding TypeInfo, or None if not ready. From 9d2bf288d65287c07d96936ed77251911769d63f Mon Sep 17 00:00:00 2001 From: momohatt Date: Sun, 11 Oct 2020 22:59:03 +0900 Subject: [PATCH 06/11] Change context of error --- mypy/semanal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index aecdec4463c6..f586bf8426cb 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2186,7 +2186,7 @@ def analyze_namedtuple_assign(self, s: AssignmentStmt) -> bool: return False if internal_name != name: self.fail("First argument to namedtuple() should be '{}', not '{}'".format( - name, internal_name), s) + name, internal_name), s.rvalue) return True # Yes, it's a valid namedtuple, but defer if it is not ready. if not info: From 273f0ed07d20ac7ab06cc5828c3bd4319b920bb1 Mon Sep 17 00:00:00 2001 From: momohatt Date: Sun, 11 Oct 2020 23:01:37 +0900 Subject: [PATCH 07/11] Fix 1st argument of namedtuple --- mypy/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/plugin.py b/mypy/plugin.py index eb31878b62a7..fcc372dacf8c 100644 --- a/mypy/plugin.py +++ b/mypy/plugin.py @@ -359,7 +359,7 @@ def final_iteration(self) -> bool: # A context for querying for configuration data about a module for # cache invalidation purposes. ReportConfigContext = NamedTuple( - 'DynamicClassDefContext', [ + 'ReportConfigContext', [ ('id', str), # Module name ('path', str), # Module file path ('is_check', bool) # Is this invocation for checking whether the config matches From 5ede060ec45c1dfd80cb148433d104a0e694d926 Mon Sep 17 00:00:00 2001 From: momohatt Date: Sun, 11 Oct 2020 23:40:29 +0900 Subject: [PATCH 08/11] Remove testReexportNamedTupleChange --- test-data/unit/fine-grained.test | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 4d1cd99a32dc..e098bc760f37 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -9622,26 +9622,3 @@ class C: [out] == main:5: error: Unsupported left operand type for + ("str") - -[case testReexportNamedTupleChange] -from m import M - -def f(x: M) -> None: ... - -f(M(0)) - -[file m.py] -from n import M - -[file n.py] -from typing import NamedTuple -M = NamedTuple('M', [('x', int)]) - -[file n.py.2] -# change the line numbers -from typing import NamedTuple -M = NamedTuple('M', [('x', int)]) - -[builtins fixtures/tuple.pyi] -[out] -== From 7f79fe84ac8768123da1b2b022b238abbc63e79c Mon Sep 17 00:00:00 2001 From: momohatt Date: Mon, 12 Oct 2020 18:21:20 +0900 Subject: [PATCH 09/11] Add a test case where semanal for namedtuple is deferred --- test-data/unit/check-namedtuple.test | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test-data/unit/check-namedtuple.test b/test-data/unit/check-namedtuple.test index ea27bb6ff983..a12db8fa92ca 100644 --- a/test-data/unit/check-namedtuple.test +++ b/test-data/unit/check-namedtuple.test @@ -969,4 +969,7 @@ from collections import namedtuple A = NamedTuple('X', [('a', int)]) # E: First argument to namedtuple() should be 'A', not 'X' B = namedtuple('X', ['a']) # E: First argument to namedtuple() should be 'B', not 'X' + +C = NamedTuple('X', [('a', 'Y')]) # E: First argument to namedtuple() should be 'C', not 'X' +class Y: ... [builtins fixtures/tuple.pyi] From 2e8982e68e8db4f966a590ac0b9e9305b9e9453d Mon Sep 17 00:00:00 2001 From: momohatt Date: Mon, 12 Oct 2020 18:28:11 +0900 Subject: [PATCH 10/11] Extract first argument only when namedtuple typechecked --- mypy/semanal_namedtuple.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy/semanal_namedtuple.py b/mypy/semanal_namedtuple.py index 95dafe539293..c043d9fe57d3 100644 --- a/mypy/semanal_namedtuple.py +++ b/mypy/semanal_namedtuple.py @@ -165,12 +165,12 @@ def check_namedtuple(self, is_typed = True else: return None, None - typename = cast(Union[StrExpr, BytesExpr, UnicodeExpr], call.args[0]).value result = self.parse_namedtuple_args(call, fullname) if result: items, types, defaults, ok = result else: # This is a valid named tuple but some types are not ready. + typename = cast(Union[StrExpr, BytesExpr, UnicodeExpr], call.args[0]).value return typename, None if not ok: # Error. Construct dummy return value. @@ -182,6 +182,7 @@ def check_namedtuple(self, self.store_namedtuple_info(info, name, call, is_typed) return name, info + typename = cast(Union[StrExpr, BytesExpr, UnicodeExpr], call.args[0]).value # We use the variable name as the class name if it exists. If # it doesn't, we use the name passed as an argument. We prefer # the variable name because it should be unique inside a From fc43fdd15e34f9f149383809c19a92bd447b6ab1 Mon Sep 17 00:00:00 2001 From: momohatt Date: Mon, 12 Oct 2020 19:44:23 +0900 Subject: [PATCH 11/11] Make parse_namedtuple_args optionally return typename --- mypy/semanal_namedtuple.py | 76 +++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/mypy/semanal_namedtuple.py b/mypy/semanal_namedtuple.py index c043d9fe57d3..0067fba22322 100644 --- a/mypy/semanal_namedtuple.py +++ b/mypy/semanal_namedtuple.py @@ -167,12 +167,8 @@ def check_namedtuple(self, return None, None result = self.parse_namedtuple_args(call, fullname) if result: - items, types, defaults, ok = result + items, types, defaults, typename, ok = result else: - # This is a valid named tuple but some types are not ready. - typename = cast(Union[StrExpr, BytesExpr, UnicodeExpr], call.args[0]).value - return typename, None - if not ok: # Error. Construct dummy return value. if var_name: name = var_name @@ -181,8 +177,10 @@ def check_namedtuple(self, info = self.build_namedtuple_typeinfo(name, [], [], {}, node.line) self.store_namedtuple_info(info, name, call, is_typed) return name, info + if not ok: + # This is a valid named tuple but some types are not ready. + return typename, None - typename = cast(Union[StrExpr, BytesExpr, UnicodeExpr], call.args[0]).value # We use the variable name as the class name if it exists. If # it doesn't, we use the name passed as an argument. We prefer # the variable name because it should be unique inside a @@ -240,26 +238,30 @@ def store_namedtuple_info(self, info: TypeInfo, name: str, call.analyzed.set_line(call.line, call.column) def parse_namedtuple_args(self, call: CallExpr, fullname: str - ) -> Optional[Tuple[List[str], List[Type], List[Expression], bool]]: + ) -> Optional[Tuple[List[str], List[Type], List[Expression], + str, bool]]: """Parse a namedtuple() call into data needed to construct a type. - Returns a 4-tuple: + Returns a 5-tuple: - List of argument names - List of argument types - List of default values - - Whether the definition typechecked. + - First argument of namedtuple + - Whether all types are ready. - Return None if at least one of the types is not ready. + Return None if the definition didn't typecheck. """ # TODO: Share code with check_argument_count in checkexpr.py? args = call.args if len(args) < 2: - return self.fail_namedtuple_arg("Too few arguments for namedtuple()", call) + self.fail("Too few arguments for namedtuple()", call) + return None defaults = [] # type: List[Expression] if len(args) > 2: # Typed namedtuple doesn't support additional arguments. if fullname == 'typing.NamedTuple': - return self.fail_namedtuple_arg("Too many arguments for NamedTuple()", call) + self.fail("Too many arguments for NamedTuple()", call) + return None for i, arg_name in enumerate(call.arg_names[2:], 2): if arg_name == 'defaults': arg = args[i] @@ -275,38 +277,42 @@ def parse_namedtuple_args(self, call: CallExpr, fullname: str ) break if call.arg_kinds[:2] != [ARG_POS, ARG_POS]: - return self.fail_namedtuple_arg("Unexpected arguments to namedtuple()", call) + self.fail("Unexpected arguments to namedtuple()", call) + return None if not isinstance(args[0], (StrExpr, BytesExpr, UnicodeExpr)): - return self.fail_namedtuple_arg( + self.fail( "namedtuple() expects a string literal as the first argument", call) + return None + typename = cast(Union[StrExpr, BytesExpr, UnicodeExpr], call.args[0]).value types = [] # type: List[Type] - ok = True if not isinstance(args[1], (ListExpr, TupleExpr)): if (fullname == 'collections.namedtuple' and isinstance(args[1], (StrExpr, BytesExpr, UnicodeExpr))): str_expr = args[1] items = str_expr.value.replace(',', ' ').split() else: - return self.fail_namedtuple_arg( + self.fail( "List or tuple literal expected as the second argument to namedtuple()", call) + return None else: listexpr = args[1] if fullname == 'collections.namedtuple': # The fields argument contains just names, with implicit Any types. if any(not isinstance(item, (StrExpr, BytesExpr, UnicodeExpr)) for item in listexpr.items): - return self.fail_namedtuple_arg("String literal expected as namedtuple() item", - call) + self.fail("String literal expected as namedtuple() item", call) + return None items = [cast(Union[StrExpr, BytesExpr, UnicodeExpr], item).value for item in listexpr.items] else: # The fields argument contains (name, type) tuples. result = self.parse_namedtuple_fields_with_types(listexpr.items, call) - if result: - items, types, _, ok = result - else: + if result is None: # One of the types is not ready, defer. return None + items, types, _, ok = result + if not ok: + return [], [], [], typename, False if not types: types = [AnyType(TypeOfAny.unannotated) for _ in items] underscore = [item for item in items if item.startswith('_')] @@ -316,50 +322,46 @@ def parse_namedtuple_args(self, call: CallExpr, fullname: str if len(defaults) > len(items): self.fail("Too many defaults given in call to namedtuple()", call) defaults = defaults[:len(items)] - return items, types, defaults, ok + return items, types, defaults, typename, True def parse_namedtuple_fields_with_types(self, nodes: List[Expression], context: Context ) -> Optional[Tuple[List[str], List[Type], - List[Expression], - bool]]: + List[Expression], bool]]: """Parse typed named tuple fields. - Return (names, types, defaults, error occurred), or None if at least one of - the types is not ready. + Return (names, types, defaults, whether types are all ready), or None if error occurred. """ items = [] # type: List[str] types = [] # type: List[Type] for item in nodes: if isinstance(item, TupleExpr): if len(item.items) != 2: - return self.fail_namedtuple_arg("Invalid NamedTuple field definition", - item) + self.fail("Invalid NamedTuple field definition", item) + return None name, type_node = item.items if isinstance(name, (StrExpr, BytesExpr, UnicodeExpr)): items.append(name.value) else: - return self.fail_namedtuple_arg("Invalid NamedTuple() field name", item) + self.fail("Invalid NamedTuple() field name", item) + return None try: type = expr_to_unanalyzed_type(type_node) except TypeTranslationError: - return self.fail_namedtuple_arg('Invalid field type', type_node) + self.fail('Invalid field type', type_node) + return None analyzed = self.api.anal_type(type) # Workaround #4987 and avoid introducing a bogus UnboundType if isinstance(analyzed, UnboundType): analyzed = AnyType(TypeOfAny.from_error) # These should be all known, otherwise we would defer in visit_assignment_stmt(). if analyzed is None: - return None + return [], [], [], False types.append(analyzed) else: - return self.fail_namedtuple_arg("Tuple expected as NamedTuple() field", item) + self.fail("Tuple expected as NamedTuple() field", item) + return None return items, types, [], True - def fail_namedtuple_arg(self, message: str, context: Context - ) -> Tuple[List[str], List[Type], List[Expression], bool]: - self.fail(message, context) - return [], [], [], False - def build_namedtuple_typeinfo(self, name: str, items: List[str], pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy