diff --git a/mypy/checker.py b/mypy/checker.py index 267355cdf12de..2fb94e5f3abd2 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1715,6 +1715,11 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: with self.enter_final_context(s.is_final_def): self.check_assignment(s.lvalues[-1], s.rvalue, s.type is None, s.new_syntax) + if s.is_alias_def: + # We do this mostly for compatibility with old semantic analyzer. + # TODO: should we get rid of this? + self.store_type(s.lvalues[-1], self.expr_checker.accept(s.rvalue)) + if (s.type is not None and self.options.disallow_any_unimported and has_any_from_unimported_type(s.type)): @@ -1824,7 +1829,9 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type self.msg.concrete_only_assign(lvalue_type, rvalue) return if rvalue_type and infer_lvalue_type and not isinstance(lvalue_type, PartialType): - self.binder.assign_type(lvalue, rvalue_type, lvalue_type, False) + # Don't use type binder for definitions of special forms, like named tuples. + if not (isinstance(lvalue, NameExpr) and lvalue.is_special_form): + self.binder.assign_type(lvalue, rvalue_type, lvalue_type, False) elif index_lvalue: self.check_indexed_assignment(index_lvalue, rvalue, lvalue) diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index 655285d001efb..2ed7756636857 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -71,7 +71,8 @@ IntExpr, FloatExpr, UnicodeExpr, TempNode, OverloadPart, PlaceholderNode, COVARIANT, CONTRAVARIANT, INVARIANT, nongen_builtins, get_member_expr_fullname, REVEAL_TYPE, - REVEAL_LOCALS, is_final_node, TypedDictExpr, type_aliases_target_versions + REVEAL_LOCALS, is_final_node, TypedDictExpr, type_aliases_target_versions, + EnumCallExpr ) from mypy.tvar_scope import TypeVarScope from mypy.typevars import fill_typevars @@ -84,11 +85,12 @@ CallableType, Overloaded, Instance, Type, AnyType, LiteralType, LiteralValue, TypeTranslator, TypeOfAny, TypeType, NoneTyp, PlaceholderType ) +from mypy.type_visitor import TypeQuery from mypy.nodes import implicit_module_attrs from mypy.newsemanal.typeanal import ( TypeAnalyser, analyze_type_alias, no_subscript_builtin_alias, TypeVariableQuery, TypeVarList, remove_dups, has_any_from_unimported_type, - check_for_explicit_any, fix_instance_types + check_for_explicit_any, type_constructors, fix_instance_types ) from mypy.exprtotype import expr_to_unanalyzed_type, TypeTranslationError from mypy.options import Options @@ -558,7 +560,9 @@ def _visit_func_def(self, defn: FuncDef) -> None: analyzer = self.type_analyzer() tag = self.track_incomplete_refs() result = analyzer.visit_callable_type(defn.type, nested=False) - if self.found_incomplete_ref(tag): + # Don't store not ready types (including placeholders). + if self.found_incomplete_ref(tag) or has_placeholder(result): + self.defer() return defn.type = result self.add_type_alias_deps(analyzer.aliases_used) @@ -1877,14 +1881,62 @@ def add_type_alias_deps(self, aliases_used: Iterable[str], target = self.scope.current_target() self.cur_mod_node.alias_deps[target].update(aliases_used) - def is_not_ready_type_ref(self, rv: Expression) -> bool: - """Does this expression refers to a not-ready class? + def is_none_alias(self, node: Expression) -> bool: + """Is this a r.h.s. for a None alias? - This includes 'Ref' and 'Ref[Arg1, Arg2, ...]', where 'Ref' - refers to a PlaceholderNode with becomes_typeinfo=True. + We special case the assignments like Void = type(None), to allow using + Void in type annotations. """ - if isinstance(rv, IndexExpr) and isinstance(rv.base, RefExpr): - return self.is_not_ready_type_ref(rv.base) + if isinstance(node, CallExpr): + if (isinstance(node.callee, NameExpr) and len(node.args) == 1 and + isinstance(node.args[0], NameExpr)): + call = self.lookup_qualified(node.callee.name, node.callee) + arg = self.lookup_qualified(node.args[0].name, node.args[0]) + if (call is not None and call.node and call.node.fullname() == 'builtins.type' and + arg is not None and arg.node and arg.node.fullname() == 'builtins.None'): + return True + return False + + def is_type_ref(self, rv: Expression, bare: bool = False) -> bool: + """Does this expression refer to a type? + + This includes: + * Special forms, like Any or Union + * Classes (except subscripted enums) + * Other type aliases + * PlaceholderNodes with becomes_typeinfo=True (these can be not ready class + definitions, and not ready aliases). + + If bare is True, this is not a base of an index expression, so some special + forms are not valid (like a bare Union). + + Note: This method should be only used in context of a type alias definition. + This method can only return True for RefExprs, to check if C[int] is a valid + target for type alias call this method on expr.base (i.e. on C in C[int]). + See also can_be_type_alias(). + """ + if not isinstance(rv, RefExpr): + return False + if isinstance(rv.node, TypeVarExpr): + self.fail('Type variable "{}" is invalid as target for type alias'.format( + rv.fullname), rv) + return False + + if bare: + # These three are valid even if bare, for example + # A = Tuple is just equivalent to A = Tuple[Any, ...]. + valid_refs = {'typing.Any', 'typing.Tuple', 'typing.Callable'} + else: + valid_refs = type_constructors + + if isinstance(rv.node, TypeAlias) or rv.fullname in valid_refs: + return True + if isinstance(rv.node, TypeInfo): + if bare: + return True + # Assignment color = Color['RED'] defines a variable, not an alias. + return not rv.node.is_enum + if isinstance(rv, NameExpr): n = self.lookup(rv.name, rv) if n and isinstance(n.node, PlaceholderNode) and n.node.becomes_typeinfo: @@ -1899,48 +1951,113 @@ def is_not_ready_type_ref(self, rv: Expression) -> bool: return True return False + def should_wait_rhs(self, rv: Expression) -> bool: + """Can we already classify this r.h.s. of an assignment or should we wait? + + This returns True if we don't have enough information to decide whether + an assignment is just a normal variable definition or a special form. + Always return False if this is a final iteration. This will typically cause + the lvalue to be classified as a variable plus emit an error. + """ + if self.final_iteration: + # No chance, nothing has changed. + return False + if isinstance(rv, NameExpr): + n = self.lookup(rv.name, rv) + if n and isinstance(n.node, PlaceholderNode) and not n.node.becomes_typeinfo: + return True + elif isinstance(rv, MemberExpr): + fname = get_member_expr_fullname(rv) + if fname: + n = self.lookup_qualified(fname, rv, suppress_errors=True) + if n and isinstance(n.node, PlaceholderNode) and not n.node.becomes_typeinfo: + return True + elif isinstance(rv, IndexExpr) and isinstance(rv.base, RefExpr): + return self.should_wait_rhs(rv.base) + elif isinstance(rv, CallExpr) and isinstance(rv.callee, RefExpr): + # This is only relevant for builtin SCC where things like 'TypeVar' + # may be not ready. + return self.should_wait_rhs(rv.callee) + return False + + def can_be_type_alias(self, rv: Expression) -> bool: + """Is this a valid r.h.s. for an alias definition? + + Note: this function should be only called for expressions where self.should_wait_rhs() + returns False. + """ + if isinstance(rv, RefExpr) and self.is_type_ref(rv, bare=True): + return True + if isinstance(rv, IndexExpr) and self.is_type_ref(rv.base, bare=False): + return True + if self.is_none_alias(rv): + return True + return False + + def record_special_form_lvalue(self, s: AssignmentStmt) -> None: + """Record minimal necessary information about l.h.s. of a special form. + + This exists mostly for compatibility with the old semantic analyzer. + """ + lvalue = s.lvalues[0] + assert isinstance(lvalue, NameExpr) + lvalue.is_special_form = True + if self.current_symbol_kind() == GDEF: + lvalue.fullname = self.qualified_name(lvalue.name) + lvalue.kind = self.current_symbol_kind() + def visit_assignment_stmt(self, s: AssignmentStmt) -> None: - s.is_final_def = self.unwrap_final(s) tag = self.track_incomplete_refs() s.rvalue.accept(self) - if isinstance(s.rvalue, IndexExpr) and isinstance(s.rvalue.base, RefExpr): - # Special case: analyze index expression _as a type_ to trigger - # incomplete refs for string forward references, for example - # Union['ClassA', 'ClassB']. - # We throw away the results of the analysis and we only care about - # the detection of incomplete references (this doesn't change the expression - # in place). - self.analyze_alias(s.rvalue, allow_placeholder=True) - top_level_not_ready = self.is_not_ready_type_ref(s.rvalue) - # NOTE: the first check is insufficient. We want to defer creation of a Var. - if self.found_incomplete_ref(tag) or top_level_not_ready: + if self.found_incomplete_ref(tag) or self.should_wait_rhs(s.rvalue): # Initializer couldn't be fully analyzed. Defer the current node and give up. # Make sure that if we skip the definition of some local names, they can't be # added later in this scope, since an earlier definition should take precedence. for expr in names_modified_by_assignment(s): - # NOTE: Currently for aliases like 'X = List[Y]', where 'Y' is not ready - # we proceed forward and create a Var. The latter will be replaced with - # a type alias it r.h.s. is a valid alias. - self.mark_incomplete(expr.name, expr, becomes_typeinfo=top_level_not_ready) + self.mark_incomplete(expr.name, expr) return - if self.analyze_namedtuple_assign(s): - return - if self.analyze_typeddict_assign(s): + + # The r.h.s. is now ready to be classified, first check if it is a special form: + special_form = False + # * type alias + if self.check_and_set_up_type_alias(s): + s.is_alias_def = True + special_form = True + # * type variable definition + elif self.process_typevar_declaration(s): + special_form = True + # * type constructors + elif self.analyze_namedtuple_assign(s): + special_form = True + elif self.analyze_typeddict_assign(s): + special_form = True + elif self.newtype_analyzer.process_newtype_declaration(s): + special_form = True + elif self.analyze_enum_assign(s): + special_form = True + if special_form: + self.record_special_form_lvalue(s) return + + # OK, this is a regular assignment, perform the necessary analysis steps. + s.is_final_def = self.unwrap_final(s) self.analyze_lvalues(s) self.check_final_implicit_def(s) self.check_classvar(s) self.process_type_annotation(s) self.apply_dynamic_class_hook(s) - self.check_and_set_up_type_alias(s) - self.newtype_analyzer.process_newtype_declaration(s) - self.process_typevar_declaration(s) - self.enum_call_analyzer.process_enum_call(s, self.is_func_scope()) self.store_final_status(s) if not s.type: self.process_module_assignment(s.lvalues, s.rvalue, s) self.process__all__(s) + def analyze_enum_assign(self, s: AssignmentStmt) -> bool: + """Check if s defines an Enum.""" + if isinstance(s.rvalue, CallExpr) and isinstance(s.rvalue.analyzed, EnumCallExpr): + # Already analyzed enum -- nothing to do here. + return True + return self.enum_call_analyzer.process_enum_call(s, self.is_func_scope()) + 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): @@ -1956,16 +2073,6 @@ def analyze_namedtuple_assign(self, s: AssignmentStmt) -> bool: # 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: - # TODO: This is needed for one-to-one compatibility with old analyzer, otherwise - # type checker will try to infer Any for the l.h.s. causing named tuple class - # object to have type Any when it appears in runtime context. - # Remove this and update the checker after new analyzer is the default one! - # See also #6458. - lvalue.fullname = self.qualified_name(name) - lvalue.is_inferred_def = True - lvalue.kind = kind = self.current_symbol_kind() - lvalue.node = self.make_name_lvalue_var(lvalue, kind, inferred=True) return True def analyze_typeddict_assign(self, s: AssignmentStmt) -> bool: @@ -1983,14 +2090,6 @@ def analyze_typeddict_assign(self, s: AssignmentStmt) -> bool: # 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: - # TODO: This is needed for one-to-one compatibility with old analyzer, otherwise - # type checker will try to infer Any for the l.h.s. - # Remove this after new analyzer is the default one! - lvalue.fullname = self.qualified_name(name) - lvalue.is_inferred_def = True - lvalue.kind = kind = self.current_symbol_kind() - lvalue.node = self.make_name_lvalue_var(lvalue, kind, inferred=True) return True def analyze_lvalues(self, s: AssignmentStmt) -> None: @@ -2132,7 +2231,9 @@ def process_type_annotation(self, s: AssignmentStmt) -> None: lvalue = s.lvalues[-1] allow_tuple_literal = isinstance(lvalue, TupleExpr) analyzed = self.anal_type(s.type, allow_tuple_literal=allow_tuple_literal) - if analyzed is None: + # Don't store not ready types (including placeholders). + if analyzed is None or has_placeholder(analyzed): + self.defer() return s.type = analyzed if (self.type and self.type.is_protocol and isinstance(lvalue, NameExpr) and @@ -2229,23 +2330,40 @@ def analyze_alias(self, rvalue: Expression, qualified_tvars = [] return typ, alias_tvars, depends_on, qualified_tvars - def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: + def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool: """Check if assignment creates a type alias and set it up as needed. - For simple aliases like L = List we use a simpler mechanism, just copying TypeInfo. - For subscripted (including generic) aliases the resulting types are stored - in rvalue.analyzed. + Return True if it is a type alias (even if the target is not ready), + or False otherwise. + Note: the resulting types for subscripted (including generic) aliases + are also stored in rvalue.analyzed. """ lvalue = s.lvalues[0] if len(s.lvalues) > 1 or not isinstance(lvalue, NameExpr): # First rule: Only simple assignments like Alias = ... create aliases. - return - if s.type: + return False + if s.unanalyzed_type is not None: # Second rule: Explicit type (cls: Type[A] = A) always creates variable, not alias. - return + return False + + existing = self.current_symbol_table().get(lvalue.name) + # Third rule: type aliases can't be re-defined. For example: + # A: Type[float] = int + # A = float # OK, but this doesn't define an alias + # B = int + # B = float # Error! + if existing and (isinstance(existing.node, Var) or + isinstance(existing.node, TypeAlias) and not s.is_alias_def): + # Note: if is_alias_def=True, this is just a node from previous iteration. + if isinstance(existing.node, TypeAlias) and not s.is_alias_def: + self.fail('Cannot assign multiple types to name "{}"' + ' without an explicit "Type[...]" annotation' + .format(lvalue.name), lvalue) + return False + non_global_scope = self.type or self.is_func_scope() - if isinstance(s.rvalue, RefExpr) and non_global_scope and lvalue.is_inferred_def: - # Third rule: Non-subscripted right hand side creates a variable + if isinstance(s.rvalue, RefExpr) and non_global_scope: + # Fourth rule (special case): Non-subscripted right hand side creates a variable # at class and function scopes. For example: # # class Model: @@ -2255,38 +2373,39 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: # # without this rule, this typical use case will require a lot of explicit # annotations (see the second rule). - return + return False rvalue = s.rvalue - tag = self.track_incomplete_refs() - res, alias_tvars, depends_on, qualified_tvars = self.analyze_alias(rvalue) - if not res or self.found_incomplete_ref(tag): - return - if (isinstance(res, Instance) and res.type.name() == lvalue.name and - res.type.module_name == self.cur_mod_id): - # Aliases like C = C is a no-op. - return - s.is_alias_def = True - node = self.lookup(lvalue.name, lvalue) - assert node is not None - assert node.node is not None + if not self.can_be_type_alias(rvalue): + return False + + res = None # type: Optional[Type] + if self.is_none_alias(rvalue): + res = NoneTyp() + alias_tvars, depends_on, qualified_tvars = \ + [], set(), [] # type: List[str], Set[str], List[str] + else: + tag = self.track_incomplete_refs() + res, alias_tvars, depends_on, qualified_tvars = \ + self.analyze_alias(rvalue, allow_placeholder=True) + if not res: + return False + if self.found_incomplete_ref(tag) or isinstance(res, PlaceholderType): + # Since we have got here, we know this must be a type alias (incomplete refs + # may appear in nested positions), therefore use becomes_typeinfo=True. + self.add_symbol(lvalue.name, PlaceholderNode(self.qualified_name(lvalue.name), + rvalue, becomes_typeinfo=True), s) + return True self.add_type_alias_deps(depends_on) # In addition to the aliases used, we add deps on unbound # type variables, since they are erased from target type. self.add_type_alias_deps(qualified_tvars) # The above are only direct deps on other aliases. # For subscripted aliases, type deps from expansion are added in deps.py - # (because the type is stored) - if not lvalue.is_inferred_def: - # Type aliases can't be re-defined. - if isinstance(node.node, (TypeAlias, TypeInfo)): - self.fail('Cannot assign multiple types to name "{}"' - ' without an explicit "Type[...]" annotation' - .format(lvalue.name), lvalue) - return + # (because the type is stored). check_for_explicit_any(res, self.options, self.is_typeshed_stub_file, self.msg, context=s) - # when this type alias gets "inlined", the Any is not explicit anymore, - # so we need to replace it with non-explicit Anys + # When this type alias gets "inlined", the Any is not explicit anymore, + # so we need to replace it with non-explicit Anys. res = make_any_non_explicit(res) no_args = isinstance(res, Instance) and not res.args fix_instance_types(res, self.fail) @@ -2297,10 +2416,21 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: s.rvalue.analyzed.column = res.column elif isinstance(s.rvalue, RefExpr): s.rvalue.is_alias_rvalue = True - node.node = TypeAlias(res, self.qualified_name(lvalue.name), s.line, s.column, - alias_tvars=alias_tvars, no_args=no_args) + alias_node = TypeAlias(res, self.qualified_name(lvalue.name), s.line, s.column, + alias_tvars=alias_tvars, no_args=no_args) + if existing: + # Did alias get updated? + if (isinstance(existing.node, PlaceholderNode) or + isinstance(existing.node, TypeAlias) and existing.node.target != res): + self.progress = True + # We need to defer so that this change can get propagated to base classes. + self.defer() + existing.node = alias_node + else: + self.add_symbol(lvalue.name, alias_node, s) if isinstance(rvalue, RefExpr) and isinstance(rvalue.node, TypeAlias): - node.node.normalized = rvalue.node.normalized + alias_node.normalized = rvalue.node.normalized + return True def analyze_lvalue(self, lval: Lvalue, nested: bool = False, explicit_type: bool = False, @@ -2571,24 +2701,30 @@ def store_declared_types(self, lvalue: Lvalue, typ: Type) -> None: # This has been flagged elsewhere as an error, so just ignore here. pass - def process_typevar_declaration(self, s: AssignmentStmt) -> None: - """Check if s declares a TypeVar; it yes, store it in symbol table.""" + def process_typevar_declaration(self, s: AssignmentStmt) -> bool: + """Check if s declares a TypeVar; it yes, store it in symbol table. + + Return True if this looks like a type variable declaration (but maybe + with errors), otherwise return False. + """ call = self.get_typevar_declaration(s) if not call: - return + return False lvalue = s.lvalues[0] assert isinstance(lvalue, NameExpr) + if s.type: + self.fail("Cannot declare the type of a type variable", s) + return False name = lvalue.name - if not lvalue.is_inferred_def: - if s.type: - self.fail("Cannot declare the type of a type variable", s) - else: - self.fail("Cannot redefine '%s' as a type variable" % name, s) - return + names = self.current_symbol_table() + existing = names.get(name) + if existing and not isinstance(existing.node, (TypeVarExpr, PlaceholderNode)): + self.fail("Cannot redefine '%s' as a type variable" % name, s) + return False if not self.check_typevar_name(call, name, s): - return + return False # Constraining types n_values = call.arg_kinds[1:].count(ARG_POS) @@ -2600,7 +2736,7 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> None: n_values, s) if res is None: - return + return False variance, upper_bound = res if self.options.disallow_any_unimported: @@ -2624,21 +2760,21 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> None: upper_bound = AnyType(TypeOfAny.implementation_artifact) # Yes, it's a valid type variable definition! Add it to the symbol table. - node = self.lookup(name, s) - assert node is not None - assert node.fullname is not None - node.kind = self.current_symbol_kind() - if isinstance(node.node, TypeVarExpr): + if existing and isinstance(existing.node, TypeVarExpr): # Existing definition from previous semanal iteration, use it. - type_var = node.node + # TODO: This may be confused with a duplicate TypeVar definition. + # Fix this and add corresponding tests. + type_var = existing.node type_var.values = values type_var.upper_bound = upper_bound type_var.variance = variance else: - type_var = TypeVarExpr(name, node.fullname, values, upper_bound, variance) + type_var = TypeVarExpr(name, self.qualified_name(name), + values, upper_bound, variance) type_var.line = call.line call.analyzed = type_var - node.node = type_var + self.add_symbol(name, type_var, s) + return True def check_typevar_name(self, call: CallExpr, name: str, context: Context) -> bool: name = unmangle(name) @@ -3166,7 +3302,7 @@ def visit_name_expr(self, expr: NameExpr) -> None: self.fail("'{}' is a type variable and only valid in type " "context".format(expr.name), expr) elif isinstance(n.node, PlaceholderNode): - self.defer() + self.process_placeholder(expr.name, 'name', expr) else: expr.kind = n.kind expr.node = n.node @@ -3379,7 +3515,7 @@ def visit_member_expr(self, expr: MemberExpr) -> None: n = self.rebind_symbol_table_node(n) if n: if isinstance(n.node, PlaceholderNode): - self.defer() + self.process_placeholder(expr.name, 'attribute', expr) return # TODO: What if None? expr.kind = n.kind @@ -3450,6 +3586,21 @@ def visit_member_expr(self, expr: MemberExpr) -> None: expr.fullname = n.fullname expr.node = n.node + def process_placeholder(self, name: str, kind: str, ctx: Context) -> None: + """Process a reference targeting placeholder node. + + If this is not a final iteration, defer current node, + otherwise report an error. + + The 'kind' argument indicates if this a name or attribute expression + (used for better error message). + """ + if self.final_iteration: + self.fail('Cannot resolve {} "{}" (possible cyclic definition)'.format(kind, name), + ctx) + else: + self.defer() + def visit_op_expr(self, expr: OpExpr) -> None: expr.left.accept(self) @@ -4065,7 +4216,10 @@ def add_symbol_table_node(self, name: str, symbol: SymbolTableNode, if (existing is not None and context is not None and (not isinstance(existing.node, PlaceholderNode) - or isinstance(symbol.node, PlaceholderNode))): + or isinstance(symbol.node, PlaceholderNode) and + # Allow replacing becomes_typeinfo=False with becomes_typeinfo=True. + # This can happen for type aliases and NewTypes. + not symbol.node.becomes_typeinfo)): # There is an existing node, so this may be a redefinition. # If the new node points to the same node as the old one, # or if both old and new nodes are placeholders, we don't @@ -4287,6 +4441,19 @@ def report_hang(self) -> None: blocker=True) +class HasPlaceholders(TypeQuery[bool]): + def __init__(self) -> None: + super().__init__(any) + + def visit_placeholder_type(self, t: PlaceholderType) -> bool: + return True + + +def has_placeholder(typ: Type) -> bool: + """Check if a type contains any placeholder types (recursively).""" + return typ.accept(HasPlaceholders()) + + def replace_implicit_first_type(sig: FunctionLike, new: Type) -> FunctionLike: if isinstance(sig, CallableType): if len(sig.arg_types) == 0: diff --git a/mypy/newsemanal/semanal_enum.py b/mypy/newsemanal/semanal_enum.py index 06999b60cbfaf..3a6c33ab077b3 100644 --- a/mypy/newsemanal/semanal_enum.py +++ b/mypy/newsemanal/semanal_enum.py @@ -19,20 +19,22 @@ def __init__(self, options: Options, api: SemanticAnalyzerInterface) -> None: self.options = options self.api = api - def process_enum_call(self, s: AssignmentStmt, is_func_scope: bool) -> None: - """Check if s defines an Enum; if yes, store the definition in symbol table.""" + def process_enum_call(self, s: AssignmentStmt, is_func_scope: bool) -> bool: + """Check if s defines an Enum; if yes, store the definition in symbol table. + + Return True if this looks like an Enum definition (but maybe with errors), + otherwise return False. + """ if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], NameExpr): - return + return False lvalue = s.lvalues[0] name = lvalue.name enum_call = self.check_enum_call(s.rvalue, name, is_func_scope) if enum_call is None: - return + return False # Yes, it's a valid Enum definition. Add it to the symbol table. - node = self.api.lookup(name, s) - if node: - node.kind = GDEF # TODO locally defined Enum - node.node = enum_call + self.api.add_symbol(name, enum_call, s) + return True def check_enum_call(self, node: Expression, @@ -62,18 +64,19 @@ class A(enum.Enum): items, values, ok = self.parse_enum_call_args(call, fullname.split('.')[-1]) if not ok: # Error. Construct dummy return value. - return self.build_enum_call_typeinfo(var_name, [], fullname) - name = cast(Union[StrExpr, UnicodeExpr], call.args[0]).value - if name != var_name or is_func_scope: - # Give it a unique name derived from the line number. - name += '@' + str(call.line) - info = self.build_enum_call_typeinfo(name, items, fullname) - # Store it as a global just in case it would remain anonymous. - # (Or in the nearest class if there is one.) - stnode = SymbolTableNode(GDEF, info) - self.api.add_symbol_table_node(name, stnode) + info = self.build_enum_call_typeinfo(var_name, [], fullname) + else: + name = cast(Union[StrExpr, UnicodeExpr], call.args[0]).value + if name != var_name or is_func_scope: + # Give it a unique name derived from the line number. + name += '@' + str(call.line) + info = self.build_enum_call_typeinfo(name, items, fullname) + # Store generated TypeInfo under both names, see semanal_namedtuple for more details. + if name != var_name or is_func_scope: + self.api.add_symbol_skip_local(name, info) call.analyzed = EnumCallExpr(info, items, values) call.analyzed.set_line(call.line, call.column) + info.line = node.line return info def build_enum_call_typeinfo(self, name: str, items: List[str], fullname: str) -> TypeInfo: @@ -93,6 +96,10 @@ def build_enum_call_typeinfo(self, name: str, items: List[str], fullname: str) - def parse_enum_call_args(self, call: CallExpr, class_name: str) -> Tuple[List[str], List[Optional[Expression]], bool]: + """Parse arguments of an Enum call. + + Return a tuple of fields, values, was there an error. + """ args = call.args if len(args) < 2: return self.fail_enum_call_arg("Too few arguments for %s()" % class_name, call) diff --git a/mypy/newsemanal/semanal_newtype.py b/mypy/newsemanal/semanal_newtype.py index d68c7c5107d71..cc295d040e304 100644 --- a/mypy/newsemanal/semanal_newtype.py +++ b/mypy/newsemanal/semanal_newtype.py @@ -5,10 +5,14 @@ from typing import Tuple, Optional -from mypy.types import Type, Instance, CallableType, NoneTyp, TupleType, AnyType, TypeOfAny +from mypy.types import ( + Type, Instance, CallableType, NoneTyp, TupleType, AnyType, PlaceholderType, + TypeOfAny +) from mypy.nodes import ( AssignmentStmt, NewTypeExpr, CallExpr, NameExpr, RefExpr, Context, StrExpr, BytesExpr, - UnicodeExpr, Block, FuncDef, Argument, TypeInfo, Var, SymbolTableNode, GDEF, MDEF, ARG_POS + UnicodeExpr, Block, FuncDef, Argument, TypeInfo, Var, SymbolTableNode, MDEF, ARG_POS, + PlaceholderNode ) from mypy.newsemanal.semanal_shared import SemanticAnalyzerInterface from mypy.options import Options @@ -26,17 +30,36 @@ def __init__(self, self.api = api self.msg = msg - def process_newtype_declaration(self, s: AssignmentStmt) -> None: - """Check if s declares a NewType; if yes, store it in symbol table.""" - # Extract and check all information from newtype declaration + def process_newtype_declaration(self, s: AssignmentStmt) -> bool: + """Check if s declares a NewType; if yes, store it in symbol table. + + Return True if it's a NewType declaration. The current target may be + deferred as a side effect if the base type is not ready, even if + the return value is True. + + The logic in this function mostly copies the logic for visit_class_def() + with a single (non-Generic) base. + """ name, call = self.analyze_newtype_declaration(s) if name is None or call is None: - return - - old_type = self.check_newtype_args(name, call, s) - call.analyzed = NewTypeExpr(name, old_type, line=call.line) + return False + # OK, now we know this is a NewType. But the base type may be not ready yet, + # add placeholder as we do for ClassDef. + + fullname = self.api.qualified_name(name) + if (not call.analyzed or + isinstance(call.analyzed, NewTypeExpr) and not call.analyzed.info): + # Start from labeling this as a future class, as we do for normal ClassDefs. + self.api.add_symbol(name, PlaceholderNode(fullname, s, becomes_typeinfo=True), s) + + old_type, should_defer = self.check_newtype_args(name, call, s) + if not call.analyzed: + call.analyzed = NewTypeExpr(name, old_type, line=call.line) if old_type is None: - return + if should_defer: + # Base type is not ready. + self.api.defer() + return True # Create the corresponding class definition if the aliased type is subtypeable if isinstance(old_type, TupleType): @@ -48,9 +71,14 @@ def process_newtype_declaration(self, s: AssignmentStmt) -> None: self.fail("NewType cannot be used with protocol classes", s) newtype_class_info = self.build_newtype_typeinfo(name, old_type, old_type) else: - message = "Argument 2 to NewType(...) must be subclassable (got {})" - self.fail(message.format(self.msg.format(old_type)), s) - return + if old_type is not None: + message = "Argument 2 to NewType(...) must be subclassable (got {})" + self.fail(message.format(self.msg.format(old_type)), s) + # Otherwise the error was already reported. + old_type = AnyType(TypeOfAny.from_error) + object_type = self.api.named_type('__builtins__.object') + newtype_class_info = self.build_newtype_typeinfo(name, old_type, object_type) + newtype_class_info.fallback_to_any = True check_for_explicit_any(old_type, self.options, self.api.is_typeshed_stub_file, self.msg, context=s) @@ -59,13 +87,16 @@ def process_newtype_declaration(self, s: AssignmentStmt) -> None: self.msg.unimported_type_becomes_any("Argument 2 to NewType(...)", old_type, s) # If so, add it to the symbol table. - node = self.api.lookup(name, s) - if node is None: - self.fail("Could not find {} in current namespace".format(name), s) - return - # TODO: why does NewType work in local scopes despite always being of kind GDEF? - node.kind = GDEF - call.analyzed.info = node.node = newtype_class_info + assert isinstance(call.analyzed, NewTypeExpr) + # As we do for normal classes, create the TypeInfo only once, then just + # update base classes on next iterations (to get rid of placeholders there). + if not call.analyzed.info: + call.analyzed.info = newtype_class_info + else: + call.analyzed.info.bases = newtype_class_info.bases + self.api.add_symbol(name, call.analyzed.info, s) + newtype_class_info.line = s.line + return True def analyze_newtype_declaration(self, s: AssignmentStmt) -> Tuple[Optional[str], Optional[CallExpr]]: @@ -76,13 +107,18 @@ def analyze_newtype_declaration(self, and isinstance(s.rvalue, CallExpr) and isinstance(s.rvalue.callee, RefExpr) and s.rvalue.callee.fullname == 'typing.NewType'): - lvalue = s.lvalues[0] name = s.lvalues[0].name - if not lvalue.is_inferred_def: - if s.type: - self.fail("Cannot declare the type of a NewType declaration", s) - else: - self.fail("Cannot redefine '%s' as a NewType" % name, s) + + if s.type: + self.fail("Cannot declare the type of a NewType declaration", s) + + names = self.api.current_symbol_table() + existing = names.get(name) + # Give a better error message than generic "Name already defined", + # like the old semantic analyzer does. + if (existing and + not isinstance(existing.node, PlaceholderNode) and not s.rvalue.analyzed): + self.fail("Cannot redefine '%s' as a NewType" % name, s) # This dummy NewTypeExpr marks the call as sufficiently analyzed; it will be # overwritten later with a fully complete NewTypeExpr if there are no other @@ -91,12 +127,17 @@ def analyze_newtype_declaration(self, return name, call - def check_newtype_args(self, name: str, call: CallExpr, context: Context) -> Optional[Type]: + def check_newtype_args(self, name: str, call: CallExpr, + context: Context) -> Tuple[Optional[Type], bool]: + """Ananlyze base type in NewType call. + + Return a tuple (type, should defer). + """ has_failed = False args, arg_kinds = call.args, call.arg_kinds if len(args) != 2 or arg_kinds[0] != ARG_POS or arg_kinds[1] != ARG_POS: self.fail("NewType(...) expects exactly two positional arguments", context) - return None + return None, False # Check first argument if not isinstance(args[0], (StrExpr, BytesExpr, UnicodeExpr)): @@ -113,19 +154,22 @@ def check_newtype_args(self, name: str, call: CallExpr, context: Context) -> Opt unanalyzed_type = expr_to_unanalyzed_type(args[1]) except TypeTranslationError: self.fail(msg, context) - return None + return None, False # We want to use our custom error message (see above), so we suppress # the default error message for invalid types here. old_type = self.api.anal_type(unanalyzed_type, report_invalid_types=False) + should_defer = False + if old_type is None or isinstance(old_type, PlaceholderType): + should_defer = True # The caller of this function assumes that if we return a Type, it's always # a valid one. So, we translate AnyTypes created from errors into None. if isinstance(old_type, AnyType) and old_type.is_from_error: self.fail(msg, context) - return None + return None, False - return None if has_failed else old_type + return None if has_failed else old_type, should_defer def build_newtype_typeinfo(self, name: str, old_type: Type, base_type: Instance) -> TypeInfo: info = self.api.basic_new_typeinfo(name, base_type) diff --git a/mypy/newsemanal/semanal_shared.py b/mypy/newsemanal/semanal_shared.py index 82d5d1889a6a2..75cbc1ccf0508 100644 --- a/mypy/newsemanal/semanal_shared.py +++ b/mypy/newsemanal/semanal_shared.py @@ -6,7 +6,7 @@ from mypy.nodes import ( Context, SymbolTableNode, MypyFile, ImportedName, FuncDef, Node, TypeInfo, Expression, GDEF, - SymbolNode + SymbolNode, SymbolTable ) from mypy.util import correct_relative_import from mypy.types import Type, FunctionLike, Instance, TupleType @@ -123,6 +123,14 @@ def add_symbol_table_node(self, name: str, stnode: SymbolTableNode) -> bool: """Add node to the current symbol table.""" raise NotImplementedError + @abstractmethod + def current_symbol_table(self) -> SymbolTable: + """Get currently active symbol table. + + May be module, class, or local namespace. + """ + raise NotImplementedError + @abstractmethod def add_symbol(self, name: str, node: SymbolNode, context: Optional[Context], module_public: bool = True, module_hidden: bool = False) -> bool: diff --git a/mypy/newsemanal/typeanal.py b/mypy/newsemanal/typeanal.py index 3624b6fa848b3..becacaa0487a0 100644 --- a/mypy/newsemanal/typeanal.py +++ b/mypy/newsemanal/typeanal.py @@ -75,48 +75,6 @@ def analyze_type_alias(node: Expression, full names of type aliases it depends on (directly or indirectly). Return None otherwise. 'node' must have been semantically analyzed. """ - # Quickly return None if the expression doesn't look like a type. Note - # that we don't support straight string literals as type aliases - # (only string literals within index expressions). - if isinstance(node, RefExpr): - # Note that this misses the case where someone tried to use a - # class-referenced type variable as a type alias. It's easier to catch - # that one in checkmember.py - if isinstance(node.node, TypeVarExpr): - api.fail('Type variable "{}" is invalid as target for type alias'.format( - node.fullname), node) - return None - if not (isinstance(node.node, TypeInfo) or - node.fullname in ('typing.Any', 'typing.Tuple', 'typing.Callable') or - isinstance(node.node, TypeAlias)): - return None - elif isinstance(node, IndexExpr): - base = node.base - if isinstance(base, RefExpr): - if not (isinstance(base.node, TypeInfo) or - base.fullname in type_constructors or - isinstance(base.node, TypeAlias)): - return None - # Enums can't be generic, and without this check we may incorrectly interpret indexing - # an Enum class as creating a type alias. - if isinstance(base.node, TypeInfo) and base.node.is_enum: - return None - else: - return None - elif isinstance(node, CallExpr): - if (isinstance(node.callee, NameExpr) and len(node.args) == 1 and - isinstance(node.args[0], NameExpr)): - call = api.lookup_qualified(node.callee.name, node.callee) - arg = api.lookup_qualified(node.args[0].name, node.args[0]) - if (call is not None and call.node and call.node.fullname() == 'builtins.type' and - arg is not None and arg.node and arg.node.fullname() == 'builtins.None'): - return NoneTyp(), set() - return None - return None - else: - return None - - # It's a type alias (though it may be an invalid one). try: type = expr_to_unanalyzed_type(node) except TypeTranslationError: @@ -414,14 +372,10 @@ def analyze_unbound_type_without_type_info(self, t: UnboundType, sym: SymbolTabl (not self.tvar_scope or self.tvar_scope.get_binding(sym) is None)) if self.allow_unbound_tvars and unbound_tvar and not self.third_pass: return t + # None of the above options worked, we give up. - # NOTE: 'final_iteration' is iteration when we hit the maximum number of iterations limit. - if unbound_tvar or self.api.final_iteration: - # TODO: This is problematic, since we will have to wait until the maximum number - # of iterations to report an invalid type. - self.fail('Invalid type "{}"'.format(name), t) - else: - self.api.defer() + self.fail('Invalid type "{}"'.format(name), t) + if self.third_pass and isinstance(sym.node, TypeVarExpr): self.note_func("Forward references to type variables are prohibited", t) return AnyType(TypeOfAny.from_error) diff --git a/mypy/nodes.py b/mypy/nodes.py index 6c17d6d3b1f14..b08cda22d5de7 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1418,11 +1418,13 @@ class NameExpr(RefExpr): This refers to a local name, global name or a module. """ - __slots__ = ('name',) + __slots__ = ('name', 'is_special_form') def __init__(self, name: str) -> None: super().__init__() self.name = name # Name referred to (may be qualified) + # Is this a l.h.s. of a special form assignment like typed dict or type variable? + self.is_special_form = False def accept(self, visitor: ExpressionVisitor[T]) -> T: return visitor.visit_name_expr(self) diff --git a/mypy/strconv.py b/mypy/strconv.py index de9548910ab7e..21a25876f21f4 100644 --- a/mypy/strconv.py +++ b/mypy/strconv.py @@ -345,7 +345,9 @@ def visit_star_expr(self, o: 'mypy.nodes.StarExpr') -> str: return self.dump([o.expr], o) def visit_name_expr(self, o: 'mypy.nodes.NameExpr') -> str: - pretty = self.pretty_name(o.name, o.kind, o.fullname, o.is_inferred_def, o.node) + pretty = self.pretty_name(o.name, o.kind, o.fullname, + o.is_inferred_def or o.is_special_form, + o.node) if isinstance(o.node, mypy.nodes.Var) and o.node.is_final: pretty += ' = {}'.format(o.node.final_value) return short_type(o) + '(' + pretty + ')' diff --git a/mypy/test/hacks.py b/mypy/test/hacks.py index 833657066d19f..09a4d5e0ffacf 100644 --- a/mypy/test/hacks.py +++ b/mypy/test/hacks.py @@ -15,10 +15,8 @@ 'check-incremental.test', 'check-literal.test', 'check-modules.test', - 'check-newtype.test', 'check-overloading.test', 'check-python2.test', - 'check-semanal-error.test', 'check-statements.test', 'check-unions.test', 'check-unreachable-code.test', diff --git a/mypy/treetransform.py b/mypy/treetransform.py index 64345e59060b9..f68794d8907b4 100644 --- a/mypy/treetransform.py +++ b/mypy/treetransform.py @@ -329,6 +329,7 @@ def duplicate_name(self, node: NameExpr) -> NameExpr: # visit_name_expr() is used when there is no such restriction. new = NameExpr(node.name) self.copy_ref(new, node) + new.is_special_form = node.is_special_form return new def visit_member_expr(self, node: MemberExpr) -> MemberExpr: diff --git a/test-data/unit/check-custom-plugin.test b/test-data/unit/check-custom-plugin.test index 1b033874fc038..b810b8ba46b21 100644 --- a/test-data/unit/check-custom-plugin.test +++ b/test-data/unit/check-custom-plugin.test @@ -497,12 +497,12 @@ from mod import declarative_base, Column, Instr, non_declarative_base Bad1 = non_declarative_base() Bad2 = Bad3 = declarative_base() -class C1(Bad1): ... # E: Invalid base class \ - # E: Invalid type "__main__.Bad1" -class C2(Bad2): ... # E: Invalid base class \ - # E: Invalid type "__main__.Bad2" -class C3(Bad3): ... # E: Invalid base class \ - # E: Invalid type "__main__.Bad3" +class C1(Bad1): ... # E: Invalid type "__main__.Bad1" \ + # E: Invalid base class +class C2(Bad2): ... # E: Invalid type "__main__.Bad2" \ + # E: Invalid base class +class C3(Bad3): ... # E: Invalid type "__main__.Bad3" \ + # E: Invalid base class [file mod.py] from typing import Generic, TypeVar def declarative_base(): ... diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index 78094b90e6663..5733fd0580ee7 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -339,14 +339,25 @@ def main() -> None: x # E: Name 'x' is not defined [case testNewAnalyzerCyclicDefinitions] -gx = gy # E: Cannot determine type of 'gy' +gx = gy # E: Cannot resolve name "gy" (possible cyclic definition) gy = gx def main() -> None: class C: def meth(self) -> None: - lx = ly # E: Cannot determine type of 'ly' + lx = ly # E: Cannot resolve name "ly" (possible cyclic definition) ly = lx +[case testNewAnalyzerCyclicDefinitionCrossModule] +import b +[file a.py] +import b +x = b.x # E: Cannot determine type of 'x' +[file b.py] +import a +x = a.x # E: Cannot resolve attribute "x" (possible cyclic definition) \ + # E: Module has no attribute "x" +[builtins fixtures/module.pyi] + [case testNewAnalyzerMutuallyRecursiveOverloadedFunctions] from typing import overload, Union @@ -1241,7 +1252,7 @@ class C: y: int [builtins fixtures/list.pyi] -[case testNewAnalyzerAliasToNotReadyTwoDeferrals-skip] +[case testNewAnalyzerAliasToNotReadyTwoDeferrals] from typing import List x: B @@ -1249,10 +1260,11 @@ B = List[C] A = C class C(List[A]): pass -reveal_type(x) +reveal_type(x) # E: Revealed type is 'builtins.list[__main__.C]' +reveal_type(x[0][0]) # E: Revealed type is '__main__.C*' [builtins fixtures/list.pyi] -[case testNewAnalyzerAliasToNotReadyDirectBase-skip] +[case testNewAnalyzerAliasToNotReadyDirectBase] from typing import List x: B @@ -1260,7 +1272,39 @@ B = List[C] class C(B): pass reveal_type(x) # E: Revealed type is 'builtins.list[__main__.C]' -reveal_type(x[0][0]) # E: Revealed type is '__main__.C' +reveal_type(x[0][0]) # E: Revealed type is '__main__.C*' +[builtins fixtures/list.pyi] + +[case testNewAnalyzerAliasToNotReadyTwoDeferralsFunction] +import a +[file a.py] +from typing import List +from b import D + +def f(x: B) -> List[B]: ... +B = List[C] +A = C +class C(List[A]): pass +[file b.py] +from a import f +class D: ... +reveal_type(f) # E: Revealed type is 'def (x: builtins.list[a.C]) -> builtins.list[builtins.list[a.C]]' +[builtins fixtures/list.pyi] + +[case testNewAnalyzerAliasToNotReadyDirectBaseFunction] +import a +[file a.py] +from typing import List +from b import D + +def f(x: B) -> List[B]: ... +B = List[C] +class C(B): pass + +[file b.py] +from a import f +class D: ... +reveal_type(f) # E: Revealed type is 'def (x: builtins.list[a.C]) -> builtins.list[builtins.list[a.C]]' [builtins fixtures/list.pyi] [case testNewAnalyzerAliasToNotReadyMixed] @@ -1716,3 +1760,99 @@ class C(metaclass=Meta): [case testNewAnalyzerFunctionError] def f(x: asdf) -> None: # E: Name 'asdf' is not defined pass + +[case testNewAnalyzerEnumRedefinition] +from enum import Enum + +A = Enum('A', ['x', 'y']) +A = Enum('A', ['z', 't']) # E: Name 'A' already defined on line 3 + +[case testNewAnalyzerNewTypeRedefinition] +from typing import NewType + +A = NewType('A', int) +A = NewType('A', str) # E: Cannot redefine 'A' as a NewType \ + # E: Name 'A' already defined on line 3 + +[case testNewAnalyzerNewTypeForwardClass] +from typing import NewType, List + +x: C +reveal_type(x[0]) # E: Revealed type is '__main__.C*' + +C = NewType('C', B) + +class B(List[C]): + pass +[builtins fixtures/list.pyi] + +[case testNewAnalyzerNewTypeForwardClassAlias] +from typing import NewType, List + +x: D +reveal_type(x[0]) # E: Revealed type is '__main__.C*' + +D = C +C = NewType('C', B) + +class B(List[D]): + pass +[builtins fixtures/list.pyi] + +[case testNewAnalyzerNewTypeForwardClassAliasReversed] +from typing import NewType, List + +x: D +reveal_type(x[0][0]) # E: Revealed type is '__main__.C*' + +D = C +C = NewType('C', List[B]) + +class B(List[C]): + pass +[builtins fixtures/list.pyi] + +[case testNewAnalyzerNewTypeForwardClassAliasDirect] +from typing import NewType, List + +x: D +reveal_type(x[0][0]) # E: Revealed type is '__main__.C*' + +D = List[C] +C = NewType('C', B) + +class B(D): + pass +[builtins fixtures/list.pyi] + +-- Copied from check-classes.test (tricky corner cases). +[case testNewAnalyzerNoCrashForwardRefToBrokenDoubleNewTypeClass] +from typing import Any, Dict, List, NewType + +Foo = NewType('NotFoo', int) # type: ignore +Foos = NewType('Foos', List[Foo]) + +x: C +class C: + def frob(self, foos: Dict[Any, Foos]) -> None: + foo = foos.get(1) + dict(foo) +[builtins fixtures/dict.pyi] + +[case testNewAnalyzerForwardTypeAliasInBase] +from typing import List, Generic, TypeVar, NamedTuple +T = TypeVar('T') + +class C(A, B): + pass +class G(Generic[T]): pass +A = G[C] +class B(NamedTuple): + x: int + +y: C +reveal_type(y.x) # E: Revealed type is 'builtins.int' +reveal_type(y[0]) # E: Revealed type is 'builtins.int' +x: A +reveal_type(x) # E: Revealed type is '__main__.G[Tuple[builtins.int, fallback=__main__.C]]' +[builtins fixtures/list.pyi] diff --git a/test-data/unit/check-newtype.test b/test-data/unit/check-newtype.test index 076821fb9b194..ea1a1c9a988a3 100644 --- a/test-data/unit/check-newtype.test +++ b/test-data/unit/check-newtype.test @@ -306,16 +306,18 @@ main:3: error: Invalid type "__main__.T" main:4: error: Invalid type "__main__.T" [case testNewTypeRedefiningVariablesFails] +# flags: --new-semantic-analyzer from typing import NewType a = 3 def f(): a -a = NewType('a', int) # E: Cannot redefine 'a' as a NewType +a = NewType('a', int) # E: Cannot redefine 'a' as a NewType \ + # E: Name 'a' already defined on line 4 b = NewType('b', int) def g(): b -b = NewType('b', float) # E: Cannot assign to a type \ - # E: Cannot redefine 'b' as a NewType +b = NewType('b', float) # E: Cannot redefine 'b' as a NewType \ + # E: Name 'b' already defined on line 8 c = NewType('c', str) # type: str # E: Cannot declare the type of a NewType declaration @@ -337,7 +339,7 @@ class C(B): pass # E: Cannot subclass NewType from typing import Protocol, NewType class P(Protocol): - attr: int + attr: int = 0 class D: attr: int @@ -364,8 +366,9 @@ issubclass(object, T) # E: Cannot use issubclass() with a NewType type [builtins fixtures/isinstancelist.pyi] [case testInvalidNewTypeCrash] +# flags: --new-semantic-analyzer from typing import List, NewType, Union N = NewType('N', XXX) # E: Argument 2 to NewType(...) must be subclassable (got "Any") \ # E: Name 'XXX' is not defined -x: List[Union[N, int]] # E: Invalid type "__main__.N" +x: List[Union[N, int]] [builtins fixtures/list.pyi] diff --git a/test-data/unit/check-semanal-error.test b/test-data/unit/check-semanal-error.test index d7aa02b070b9a..3be47bade3863 100644 --- a/test-data/unit/check-semanal-error.test +++ b/test-data/unit/check-semanal-error.test @@ -49,16 +49,15 @@ A().foo(1) A().x = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") [case testInvalidBaseClass2] -# flags: --new-semantic-analyzer X = 1 class A(X): # E x = 1 A().foo(1) A().x = '' # E [out] -main:3: error: Invalid base class -main:3: error: Invalid type "__main__.X" -main:6: error: Incompatible types in assignment (expression has type "str", variable has type "int") +main:2: error: Invalid type "__main__.X" +main:2: error: Invalid base class +main:5: error: Incompatible types in assignment (expression has type "str", variable has type "int") [case testInvalidNumberOfTypeArgs] @@ -90,9 +89,10 @@ def m() -> None: ... # E: Name 'm' already defined (by an import) [out] [case testIgnoredImportDup] +# flags: --new-semantic-analyzer import m # type: ignore from m import f # type: ignore -def m() -> None: ... # ok -def f() -> None: ... # ok +def m() -> None: ... # E: Name 'm' already defined (possibly by an import) +def f() -> None: ... # E: Name 'f' already defined (possibly by an import) [out] diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 6cdb45ce115ba..232bafc52e593 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -5334,8 +5334,8 @@ main:4: error: "C" expects no type arguments, but 1 given main:4: error: Invalid type "a.T" main:6: error: Free type variable expected in Generic[...] main:7: error: Invalid type "a.T" -main:10: error: Bad number of arguments for type alias, expected: 0, given: 1 main:10: error: Invalid type "a.T" +main:10: error: Bad number of arguments for type alias, expected: 0, given: 1 [case testChangeTypeVarToModule] # flags: --new-semantic-analyzer @@ -5363,8 +5363,8 @@ main:4: error: "C" expects no type arguments, but 1 given main:4: error: Invalid type "T" main:6: error: Free type variable expected in Generic[...] main:7: error: Invalid type "T" -main:10: error: Bad number of arguments for type alias, expected: 0, given: 1 main:10: error: Invalid type "T" +main:10: error: Bad number of arguments for type alias, expected: 0, given: 1 [case testChangeClassToModule] import a