diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index 0ecf9b338974..991a1f4729df 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -870,6 +870,9 @@ def analyze_class(self, defn: ClassDef) -> None: self.prepare_class_def(defn, info) return + if self.analyze_namedtuple_classdef(defn): + return + # Create TypeInfo for class now that base classes and the MRO can be calculated. self.prepare_class_def(defn) @@ -880,10 +883,6 @@ def analyze_class(self, defn: ClassDef) -> None: with self.scope.class_scope(defn.info): self.configure_base_classes(defn, base_types) - - if self.analyze_namedtuple_classdef(defn): - return - defn.info.is_protocol = is_protocol self.analyze_metaclass(defn) defn.info.runtime_protocol = False @@ -904,40 +903,23 @@ def analyze_class_body_common(self, defn: ClassDef) -> None: self.leave_class() def analyze_namedtuple_classdef(self, defn: ClassDef) -> bool: - """Analyze class-based named tuple if the NamedTuple base class is present. - - TODO: Move this to NamedTupleAnalyzer? - - Return True only if the class is a NamedTuple class. - """ - named_tuple_info = self.named_tuple_analyzer.analyze_namedtuple_classdef(defn) - if named_tuple_info is None: - return False - # Temporarily clear the names dict so we don't get errors about duplicate names - # that were already set in build_namedtuple_typeinfo. - nt_names = named_tuple_info.names - named_tuple_info.names = SymbolTable() - - self.analyze_class_body_common(defn) - - # Make sure we didn't use illegal names, then reset the names in the typeinfo. - for prohibited in NAMEDTUPLE_PROHIBITED_NAMES: - if prohibited in named_tuple_info.names: - if nt_names.get(prohibited) is named_tuple_info.names[prohibited]: - continue - ctx = named_tuple_info.names[prohibited].node - assert ctx is not None - self.fail('Cannot overwrite NamedTuple attribute "{}"'.format(prohibited), - ctx) - - # Restore the names in the original symbol table. This ensures that the symbol - # table contains the field objects created by build_namedtuple_typeinfo. Exclude - # __doc__, which can legally be overwritten by the class. - named_tuple_info.names.update({ - key: value for key, value in nt_names.items() - if key not in named_tuple_info.names or key != '__doc__' - }) - return True + """Check if this class can define a named tuple.""" + if defn.info and defn.info.tuple_type: + # Don't reprocess everything. We just need to process methods defined + # in the named tuple class body. + is_named_tuple, info = True, defn.info # type: bool, Optional[TypeInfo] + else: + is_named_tuple, info = self.named_tuple_analyzer.analyze_namedtuple_classdef(defn) + if is_named_tuple: + if info is None: + self.mark_incomplete(defn.name, defn) + else: + self.prepare_class_def(defn, info) + with self.scope.class_scope(defn.info): + with self.named_tuple_analyzer.save_namedtuple_body(info): + self.analyze_class_body_common(defn) + return True + return False def apply_class_plugin_hooks(self, defn: ClassDef) -> None: """Apply a plugin hook that may infer a more precise definition for a class.""" @@ -1229,10 +1211,15 @@ def prepare_class_def(self, defn: ClassDef, info: Optional[TypeInfo] = None) -> # in globals under mangled unique names # # TODO: Putting local classes into globals breaks assumptions in fine-grained - # incremental mode and we should avoid it. + # incremental mode and we should avoid it. In general, this logic is too + # ad-hoc and needs to be removed/refactored. if '@' not in defn.info._fullname: local_name = defn.info._fullname + '@' + str(defn.line) - defn.info._fullname = self.cur_mod_id + '.' + local_name + if defn.info.is_named_tuple: + # Module is already correctly set in _fullname for named tuples. + defn.info._fullname += '@' + str(defn.line) + else: + defn.info._fullname = self.cur_mod_id + '.' + local_name else: # Preserve name from previous fine-grained incremental run. local_name = defn.info._fullname @@ -1298,7 +1285,9 @@ def configure_base_classes(self, for base, base_expr in bases: if isinstance(base, TupleType): - if info.tuple_type: + # There may be an existing valid tuple type from previous semanal iterations. + # Use equality to check if it is the case. + if info.tuple_type and info.tuple_type != base: self.fail("Class has two incompatible bases derived from tuple", defn) defn.has_incompatible_baseclass = True info.tuple_type = base @@ -1426,11 +1415,15 @@ def expr_to_analyzed_type(self, allow_placeholder: bool = False) -> Optional[Type]: if isinstance(expr, CallExpr): expr.accept(self) - info = self.named_tuple_analyzer.check_namedtuple(expr, None, self.is_func_scope()) - if info is None: + is_named_tuple, info = self.named_tuple_analyzer.check_namedtuple(expr, None, + self.is_func_scope()) + if not is_named_tuple: # Some form of namedtuple is the only valid type that looks like a call # expression. This isn't a valid type. raise TypeTranslationError() + elif not info: + self.defer() + return None assert info.tuple_type, "NamedTuple without tuple type" fallback = Instance(info, []) return TupleType(info.tuple_type.items, fallback=fallback) @@ -1888,6 +1881,8 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: for expr in names_modified_by_assignment(s): self.mark_incomplete(expr.name, expr) return + if self.analyze_namedtuple_assign(s): + return self.analyze_lvalues(s) self.check_final_implicit_def(s) self.check_classvar(s) @@ -1896,7 +1891,6 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: self.check_and_set_up_type_alias(s) self.newtype_analyzer.process_newtype_declaration(s) self.process_typevar_declaration(s) - self.named_tuple_analyzer.process_namedtuple_definition(s, self.is_func_scope()) self.typed_dict_analyzer.process_typeddict_definition(s, self.is_func_scope()) self.enum_call_analyzer.process_enum_call(s, self.is_func_scope()) self.store_final_status(s) @@ -1904,6 +1898,23 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: self.process_module_assignment(s.lvalues, s.rvalue, s) self.process__all__(s) + def analyze_namedtuple_assign(self, s: AssignmentStmt) -> bool: + """Check if s defines a namedtuple.""" + if isinstance(s.rvalue, CallExpr) and isinstance(s.rvalue.analyzed, NamedTupleExpr): + return True # This is a valid and analyzed named tuple definition, nothing to do here. + if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], NameExpr): + 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: + return False + # Yes, it's a valid namedtuple, but defer if it is not ready. + if not info: + self.mark_incomplete(name, lvalue, becomes_typeinfo=True) + return True + def analyze_lvalues(self, s: AssignmentStmt) -> None: for lval in s.lvalues: self.analyze_lvalue(lval, @@ -3674,7 +3685,8 @@ def record_incomplete_ref(self) -> None: self.defer() self.num_incomplete_refs += 1 - def mark_incomplete(self, name: str, node: Node) -> None: + def mark_incomplete(self, name: str, node: Node, + becomes_typeinfo: bool = False) -> None: """Mark a definition as incomplete (and defer current analysis target). Also potentially mark the current namespace as incomplete. @@ -3682,13 +3694,15 @@ def mark_incomplete(self, name: str, node: Node) -> None: Args: name: The name that we weren't able to define (or '*' if the name is unknown) node: The node that refers to the name (definition or lvalue) + becomes_typeinfo: Pass this to PlaceholderNode (used by special forms like + named tuples that will create TypeInfos). """ self.defer() if name == '*': self.incomplete = True elif name not in self.current_symbol_table() and not self.is_global_or_nonlocal(name): fullname = self.qualified_name(name) - self.add_symbol(name, PlaceholderNode(fullname, node, False), context=None) + self.add_symbol(name, PlaceholderNode(fullname, node, becomes_typeinfo), context=None) self.missing_names.add(name) def is_incomplete_namespace(self, fullname: str) -> bool: diff --git a/mypy/newsemanal/semanal_main.py b/mypy/newsemanal/semanal_main.py index aca219814d70..fcd8bf722e24 100644 --- a/mypy/newsemanal/semanal_main.py +++ b/mypy/newsemanal/semanal_main.py @@ -123,7 +123,7 @@ def process_top_level_function(analyzer: 'NewSemanticAnalyzer', analyzer.incomplete_namespaces.add(module) while deferred and more_iterations: iteration += 1 - if not incomplete or iteration == MAX_ITERATIONS: + if not (deferred or incomplete) or iteration == MAX_ITERATIONS: # OK, this is one last pass, now missing names will be reported. more_iterations = False analyzer.incomplete_namespaces.discard(module) diff --git a/mypy/newsemanal/semanal_namedtuple.py b/mypy/newsemanal/semanal_namedtuple.py index e3f495e6d674..f6a0788f1ab2 100644 --- a/mypy/newsemanal/semanal_namedtuple.py +++ b/mypy/newsemanal/semanal_namedtuple.py @@ -2,11 +2,11 @@ This is conceptually part of mypy.semanal (semantic analyzer pass 2). """ - -from typing import Tuple, List, Dict, Mapping, Optional, Union, cast +from contextlib import contextmanager +from typing import Tuple, List, Dict, Mapping, Optional, Union, cast, Iterator from mypy.types import ( - Type, TupleType, NoneTyp, AnyType, TypeOfAny, TypeVarType, TypeVarDef, CallableType, TypeType + Type, TupleType, AnyType, TypeOfAny, TypeVarDef, CallableType, TypeType, TypeVarType ) from mypy.newsemanal.semanal_shared import ( SemanticAnalyzerInterface, set_callable_name, PRIORITY_FALLBACKS @@ -15,7 +15,7 @@ Var, EllipsisExpr, Argument, StrExpr, BytesExpr, UnicodeExpr, ExpressionStmt, NameExpr, AssignmentStmt, PassStmt, Decorator, FuncBase, ClassDef, Expression, RefExpr, TypeInfo, NamedTupleExpr, CallExpr, Context, TupleExpr, ListExpr, SymbolTableNode, FuncDef, Block, - TempNode, ARG_POS, ARG_NAMED_OPT, ARG_OPT, MDEF, GDEF + TempNode, SymbolTable, TypeVarExpr, ARG_POS, ARG_NAMED_OPT, ARG_OPT, MDEF, GDEF ) from mypy.options import Options from mypy.exprtotype import expr_to_unanalyzed_type, TypeTranslationError @@ -35,35 +35,51 @@ NAMEDTUP_CLASS_ERROR = ('Invalid statement in NamedTuple definition; ' 'expected "field_name: field_type [= default]"') # type: Final +SELF_TVAR_NAME = '_NT' # type: Final + class NamedTupleAnalyzer: def __init__(self, options: Options, api: SemanticAnalyzerInterface) -> None: self.options = options self.api = api - def analyze_namedtuple_classdef(self, defn: ClassDef) -> Optional[TypeInfo]: - # special case for NamedTuple + def analyze_namedtuple_classdef(self, defn: ClassDef) -> Tuple[bool, Optional[TypeInfo]]: + """Analyze if given class definition can be a named tuple definition. + + Return a tuple where first item indicates whether this can possibly be a named tuple, + and the second item is the corresponding TypeInfo (may be None if not ready and should be + deferred). + """ for base_expr in defn.base_type_exprs: if isinstance(base_expr, RefExpr): self.api.accept(base_expr) if base_expr.fullname == 'typing.NamedTuple': - node = self.api.lookup(defn.name, defn) - if node is not None: - node.kind = GDEF # TODO in process_namedtuple_definition also applies here - items, types, default_items = self.check_namedtuple_classdef(defn) - info = self.build_namedtuple_typeinfo( - defn.name, items, types, default_items) - node.node = info - defn.info.replaced = info - defn.info = info - defn.analyzed = NamedTupleExpr(info, is_typed=True) - defn.analyzed.line = defn.line - defn.analyzed.column = defn.column - return info - return None + result = self.check_namedtuple_classdef(defn) + if result is None: + # This is a valid named tuple, but some types are incomplete. + return True, None + items, types, default_items = result + info = self.build_namedtuple_typeinfo( + defn.name, items, types, default_items) + defn.info = info + defn.analyzed = NamedTupleExpr(info, is_typed=True) + defn.analyzed.line = defn.line + defn.analyzed.column = defn.column + # All done: this is a valid named tuple with all types known. + return True, info + # This can't be a valid named tuple. + return False, None def check_namedtuple_classdef( - self, defn: ClassDef) -> Tuple[List[str], List[Type], Dict[str, Expression]]: + self, defn: ClassDef) -> Optional[Tuple[List[str], List[Type], Dict[str, Expression]]]: + """Parse and validate fields in named tuple class definition. + + Return a three tuple: + * field names + * field types + * field default values + or None, if any of the types are not ready. + """ if self.options.python_version < (3, 6): self.fail('NamedTuple class syntax is only supported in Python 3.6', defn) return [], [], {} @@ -98,7 +114,9 @@ def check_namedtuple_classdef( types.append(AnyType(TypeOfAny.unannotated)) else: analyzed = self.api.anal_type(stmt.type) - assert analyzed is not None # TODO: Handle None values + if analyzed is None: + # Something is incomplete. We need to defer this named tuple. + return None types.append(analyzed) # ...despite possible minor failures that allow further analyzis. if name.startswith('_'): @@ -115,52 +133,41 @@ def check_namedtuple_classdef( default_items[name] = stmt.rvalue return items, types, default_items - def process_namedtuple_definition(self, s: AssignmentStmt, is_func_scope: bool) -> None: - """Check if s defines a namedtuple; if yes, store the definition in symbol table. - - Assume that s has already been successfully analyzed. - """ - if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], NameExpr): - return - lvalue = s.lvalues[0] - name = lvalue.name - named_tuple = self.check_namedtuple(s.rvalue, name, is_func_scope) - if named_tuple is None: - return - # Yes, it's a valid namedtuple definition. Add it to the symbol table. - node = self.api.lookup(name, s) - assert node is not None - node.kind = GDEF # TODO locally defined namedtuple - node.node = named_tuple - def check_namedtuple(self, node: Expression, var_name: Optional[str], - is_func_scope: bool) -> Optional[TypeInfo]: + is_func_scope: bool) -> Tuple[bool, 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. - If it does, return the corresponding TypeInfo. Return None otherwise. + Return a tuple of two items: + * Can it be 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 None + return False, None call = node callee = call.callee if not isinstance(callee, RefExpr): - return None + return False, None fullname = callee.fullname if fullname == 'collections.namedtuple': is_typed = False elif fullname == 'typing.NamedTuple': is_typed = True else: - return None - items, types, defaults, ok = self.parse_namedtuple_args(call, fullname) + return False, None + 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 if not ok: # Error. Construct dummy return value. if var_name: @@ -169,7 +176,7 @@ def check_namedtuple(self, name = 'namedtuple@' + str(call.line) info = self.build_namedtuple_typeinfo(name, [], [], {}) self.store_namedtuple_info(info, name, call, is_typed) - return info + return True, info name = cast(Union[StrExpr, BytesExpr, UnicodeExpr], call.args[0]).value if name != var_name or is_func_scope: # Give it a unique name derived from the line number. @@ -184,8 +191,8 @@ def check_namedtuple(self, info = self.build_namedtuple_typeinfo(name, items, types, default_items) # Store it as a global just in case it would remain anonymous. # (Or in the nearest class if there is one.) - self.store_namedtuple_info(info, name, call, is_typed) - return info + self.store_namedtuple_info(info, var_name or name, call, is_typed) + return True, info def store_namedtuple_info(self, info: TypeInfo, name: str, call: CallExpr, is_typed: bool) -> None: @@ -195,7 +202,7 @@ 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 - ) -> Tuple[List[str], List[Type], List[Expression], bool]: + ) -> Optional[Tuple[List[str], List[Type], List[Expression], bool]]: """Parse a namedtuple() call into data needed to construct a type. Returns a 4-tuple: @@ -204,6 +211,7 @@ def parse_namedtuple_args(self, call: CallExpr, fullname: str - Number of arguments that have a default value - Whether the definition typechecked. + Return None if at least one of the types is not ready. """ # TODO: Share code with check_argument_count in checkexpr.py? args = call.args @@ -255,7 +263,12 @@ def parse_namedtuple_args(self, call: CallExpr, fullname: str for item in listexpr.items] else: # The fields argument contains (name, type) tuples. - items, types, _, ok = self.parse_namedtuple_fields_with_types(listexpr.items, call) + result = self.parse_namedtuple_fields_with_types(listexpr.items, call) + if result: + items, types, _, ok = result + else: + # One of the types is not ready, defer. + return None if not types: types = [AnyType(TypeOfAny.unannotated) for _ in items] underscore = [item for item in items if item.startswith('_')] @@ -268,8 +281,14 @@ def parse_namedtuple_args(self, call: CallExpr, fullname: str return items, types, defaults, ok def parse_namedtuple_fields_with_types(self, nodes: List[Expression], context: Context - ) -> Tuple[List[str], List[Type], List[Expression], - bool]: + ) -> Optional[Tuple[List[str], List[Type], + List[Expression], + bool]]: + """Parse typed named tuple fields. + + Return (names, types, defaults, error ocurred), or None if at least one of + the types is not ready. + """ items = [] # type: List[str] types = [] # type: List[Type] for item in nodes: @@ -287,7 +306,9 @@ def parse_namedtuple_fields_with_types(self, nodes: List[Expression], context: C except TypeTranslationError: return self.fail_namedtuple_arg('Invalid field type', type_node) analyzed = self.api.anal_type(type) - assert analyzed is not None # TODO: Handle None values + # These should be all known, otherwise we would defer in visit_assignment_stmt(). + if analyzed is None: + return None types.append(analyzed) else: return self.fail_namedtuple_arg("Tuple expected as NamedTuple() field", item) @@ -348,13 +369,13 @@ def add_field(var: Var, is_initialized_in_class: bool = False, add_field(Var('__annotations__', ordereddictype), is_initialized_in_class=True) add_field(Var('__doc__', strtype), is_initialized_in_class=True) - tvd = TypeVarDef('NT', 'NT', -1, [], info.tuple_type) + tvd = TypeVarDef(SELF_TVAR_NAME, info.fullname() + '.' + SELF_TVAR_NAME, + -1, [], info.tuple_type) selftype = TypeVarType(tvd) def add_method(funcname: str, ret: Type, args: List[Argument], - name: Optional[str] = None, is_classmethod: bool = False, is_new: bool = False, ) -> None: @@ -381,6 +402,7 @@ def add_method(funcname: str, v.is_classmethod = True v.info = info v._fullname = func._fullname + func.is_decorated = True dec = Decorator(func, [NameExpr('classmethod')], v) info.names[funcname] = SymbolTableNode(MDEF, dec) else: @@ -394,7 +416,7 @@ def make_init_arg(var: Var) -> Argument: kind = ARG_POS if default is None else ARG_OPT return Argument(var, var.type, default, kind) - add_method('__new__', ret=selftype, name=info.name(), + add_method('__new__', ret=selftype, args=[make_init_arg(var) for var in vars], is_new=True) add_method('_asdict', args=[], ret=ordereddictype) @@ -403,8 +425,44 @@ def make_init_arg(var: Var) -> Argument: args=[Argument(Var('iterable', iterable_type), iterable_type, None, ARG_POS), Argument(Var('new'), special_form_any, EllipsisExpr(), ARG_NAMED_OPT), Argument(Var('len'), special_form_any, EllipsisExpr(), ARG_NAMED_OPT)]) + + self_tvar_expr = TypeVarExpr(SELF_TVAR_NAME, info.fullname() + '.' + SELF_TVAR_NAME, + [], info.tuple_type) + info.names[SELF_TVAR_NAME] = SymbolTableNode(MDEF, self_tvar_expr) return info + @contextmanager + def save_namedtuple_body(self, named_tuple_info: TypeInfo) -> Iterator[None]: + """Preserve the generated body of class-based named tuple and then restore it. + + Temporarily clear the names dict so we don't get errors about duplicate names + that were already set in build_namedtuple_typeinfo (we already added the tuple + field names while generating the TypeInfo, and actual duplicates are + already reported). + """ + nt_names = named_tuple_info.names + named_tuple_info.names = SymbolTable() + + yield + + # Make sure we didn't use illegal names, then reset the names in the typeinfo. + for prohibited in NAMEDTUPLE_PROHIBITED_NAMES: + if prohibited in named_tuple_info.names: + if nt_names.get(prohibited) is named_tuple_info.names[prohibited]: + continue + ctx = named_tuple_info.names[prohibited].node + assert ctx is not None + self.fail('Cannot overwrite NamedTuple attribute "{}"'.format(prohibited), + ctx) + + # Restore the names in the original symbol table. This ensures that the symbol + # table contains the field objects created by build_namedtuple_typeinfo. Exclude + # __doc__, which can legally be overwritten by the class. + named_tuple_info.names.update({ + key: value for key, value in nt_names.items() + if key not in named_tuple_info.names or key != '__doc__' + }) + # Helpers def fail(self, msg: str, ctx: Context) -> None: diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index 2d3eb5ed9df4..7145f77c07a1 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -685,6 +685,163 @@ import a reveal_type(a.C()) # E: Revealed type is 'a.C' def f(): pass +[case testNewAnalyzerNamedTupleCall] +from typing import NamedTuple + +o: Out +i: In + +Out = NamedTuple('Out', [('x', In), ('y', Other)]) + +reveal_type(o) # E: Revealed type is 'Tuple[Tuple[builtins.str, __main__.Other, fallback=__main__.In], __main__.Other, fallback=__main__.Out]' +reveal_type(o.x) # E: Revealed type is 'Tuple[builtins.str, __main__.Other, fallback=__main__.In]' +reveal_type(o.y) # E: Revealed type is '__main__.Other' +reveal_type(o.x.t) # E: Revealed type is '__main__.Other' +reveal_type(i.t) # E: Revealed type is '__main__.Other' + +In = NamedTuple('In', [('s', str), ('t', Other)]) +class Other: pass + +[case testNewAnalyzerNamedTupleClass] +from typing import NamedTuple + +o: Out +i: In + +class Out(NamedTuple): + x: In + y: Other + +reveal_type(o) # E: Revealed type is 'Tuple[Tuple[builtins.str, __main__.Other, fallback=__main__.In], __main__.Other, fallback=__main__.Out]' +reveal_type(o.x) # E: Revealed type is 'Tuple[builtins.str, __main__.Other, fallback=__main__.In]' +reveal_type(o.y) # E: Revealed type is '__main__.Other' +reveal_type(o.x.t) # E: Revealed type is '__main__.Other' +reveal_type(i.t) # E: Revealed type is '__main__.Other' + +class In(NamedTuple): + s: str + t: Other +class Other: pass + +[case testNewAnalyzerNamedTupleCallNested] +from typing import NamedTuple + +o: C.Out +i: C.In + +reveal_type(o) # E: Revealed type is 'Tuple[Tuple[builtins.str, __main__.C.Other, fallback=__main__.C.In], __main__.C.Other, fallback=__main__.C.Out]' +reveal_type(o.x) # E: Revealed type is 'Tuple[builtins.str, __main__.C.Other, fallback=__main__.C.In]' +reveal_type(o.y) # E: Revealed type is '__main__.C.Other' +reveal_type(o.x.t) # E: Revealed type is '__main__.C.Other' +reveal_type(i.t) # E: Revealed type is '__main__.C.Other' + +class C: + Out = NamedTuple('Out', [('x', In), ('y', Other)]) + In = NamedTuple('In', [('s', str), ('t', Other)]) + class Other: pass + + +[case testNewAnalyzerNamedTupleClassNested] +from typing import NamedTuple + +o: C.Out +i: C.In + +reveal_type(o) # E: Revealed type is 'Tuple[Tuple[builtins.str, __main__.C.Other, fallback=__main__.C.In], __main__.C.Other, fallback=__main__.C.Out]' +reveal_type(o.x) # E: Revealed type is 'Tuple[builtins.str, __main__.C.Other, fallback=__main__.C.In]' +reveal_type(o.y) # E: Revealed type is '__main__.C.Other' +reveal_type(o.x.t) # E: Revealed type is '__main__.C.Other' +reveal_type(i.t) # E: Revealed type is '__main__.C.Other' + +class C: + class Out(NamedTuple): + x: C.In + y: C.Other + class In(NamedTuple): + s: str + t: C.Other + class Other: pass + +[case testNewAnalyzerNamedTupleCallNestedMethod] +from typing import NamedTuple + +c = C() +reveal_type(c.o) # E: Revealed type is 'Tuple[Tuple[builtins.str, __main__.Other@12, fallback=__main__.C.In@11], __main__.Other@12, fallback=__main__.C.Out@10]' +reveal_type(c.o.x) # E: Revealed type is 'Tuple[builtins.str, __main__.Other@12, fallback=__main__.C.In@11]' + +class C: + def get_tuple(self) -> None: + self.o: Out + Out = NamedTuple('Out', [('x', In), ('y', Other)]) + In = NamedTuple('In', [('s', str), ('t', Other)]) + class Other: pass + +[case testNewAnalyzerNamedTupleClassNestedMethod] +from typing import NamedTuple + +c = C() +reveal_type(c.o) # E: Revealed type is 'Tuple[Tuple[builtins.str, __main__.Other@18, fallback=__main__.C.In@15], __main__.Other@18, fallback=__main__.C.Out@11]' +reveal_type(c.o.x) # E: Revealed type is 'Tuple[builtins.str, __main__.Other@18, fallback=__main__.C.In@15]' +reveal_type(c.o.method()) # E: Revealed type is 'Tuple[builtins.str, __main__.Other@18, fallback=__main__.C.In@15]' + +class C: + def get_tuple(self) -> None: + self.o: Out + class Out(NamedTuple): + x: In + y: Other + def method(self) -> In: ... + class In(NamedTuple): + s: str + t: Other + class Other: pass + +[case testNewAnalyzerNamedTupleClassForwardMethod] +from typing import NamedTuple + +n: NT +reveal_type(n.get_other()) # E: Revealed type is 'Tuple[builtins.str, fallback=__main__.Other]' +reveal_type(n.get_other().s) # E: Revealed type is 'builtins.str' + +class NT(NamedTuple): + x: int + y: int + def get_other(self) -> Other: pass + +class Other(NamedTuple): + s: str + +[case testNewAnalyzerNamedTupleSpecialMethods] +from typing import NamedTuple + +o: SubO + +reveal_type(SubO._make) # E: Revealed type is 'def (iterable: typing.Iterable[Any], *, new: Any =, len: Any =) -> Tuple[Tuple[builtins.str, __main__.Other, fallback=__main__.In], __main__.Other, fallback=__main__.SubO]' +reveal_type(o._replace(y=Other())) # E: Revealed type is 'Tuple[Tuple[builtins.str, __main__.Other, fallback=__main__.In], __main__.Other, fallback=__main__.SubO]' + +class SubO(Out): pass + +Out = NamedTuple('Out', [('x', In), ('y', Other)]) +In = NamedTuple('In', [('s', str), ('t', Other)]) +class Other: pass + +[case testNewAnalyzerNamedTupleBaseClass] +from typing import NamedTuple + +o: Out +reveal_type(o) # E: Revealed type is 'Tuple[Tuple[builtins.str, __main__.Other, fallback=__main__.In], __main__.Other, fallback=__main__.Out]' +reveal_type(o.x) # E: Revealed type is 'Tuple[builtins.str, __main__.Other, fallback=__main__.In]' +reveal_type(o.x.t) # E: Revealed type is '__main__.Other' +reveal_type(Out._make) # E: Revealed type is 'def (iterable: typing.Iterable[Any], *, new: Any =, len: Any =) -> Tuple[Tuple[builtins.str, __main__.Other, fallback=__main__.In], __main__.Other, fallback=__main__.Out]' + +class Out(NamedTuple('Out', [('x', In), ('y', Other)])): + pass + +class In(NamedTuple): + s: str + t: Other +class Other: pass + [case testNewAnalyzerIncompleteRefShadowsBuiltin1] import a