From c65bc1aec0da9da8ad4897b2be77fdb5127009cb Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 18 Mar 2019 19:51:38 +0000 Subject: [PATCH 1/9] New semantic analyzer: Prohibit duplicate type variable definitions --- mypy/newsemanal/semanal.py | 18 +++++++++++++++--- test-data/unit/check-newsemanal.test | 17 +++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index 4c189ccfb45e7..c6d5dc0614b3e 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -2666,10 +2666,15 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> bool: if s.type: self.fail("Cannot declare the type of a type variable", s) return False + + assert isinstance(s.rvalue, CallExpr) name = lvalue.name names = self.current_symbol_table() existing = names.get(name) - if existing and not isinstance(existing.node, (TypeVarExpr, PlaceholderNode)): + if existing and not (isinstance(existing.node, PlaceholderNode) or + # Also give error for another type variable with the same name. + isinstance(existing.node, TypeVarExpr) and + existing.node is s.rvalue.analyzed): self.fail("Cannot redefine '%s' as a type variable" % name, s) return False @@ -2712,8 +2717,15 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> bool: # Yes, it's a valid type variable definition! Add it to the symbol table. if existing and isinstance(existing.node, TypeVarExpr): # Existing definition from previous semanal iteration, use it. - # TODO: This may be confused with a duplicate TypeVar definition. - # Fix this and add corresponding tests. + # Note that unlike in most other cases, we patch an existing symbol, instead of + # adding the type variable symbol only when the types it depends on are ready. + # This is necessary to avoid a "deadlock" in this typical situation: + # T = TypeVar('T', bound=C[Any]) + # class C(Generic[T]): + # ... + # We can use allow_placeholder=True for upper bound and values to avoid this, + # but placeholder types will get deeply into class definition and it will be + # hard to get rid of them. type_var = existing.node type_var.values = values type_var.upper_bound = upper_bound diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index 5733fd0580ee7..c289229a3b3ba 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -1856,3 +1856,20 @@ 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] + +[case testNewAnalyzerDuplicateTypeVar] +from typing import TypeVar, Generic, Any + +T = TypeVar('T', bound=B[Any]) +# The "int" error is because of typing fixture. +T = TypeVar('T', bound=C) # E: Cannot redefine 'T' as a type variable \ + # E: Invalid assignment target \ + # E: "int" not callable + +class B(Generic[T]): + x: T +class C: ... + +x: B[int] # E: Type argument "builtins.int" of "B" must be a subtype of "__main__.B[Any]" +y: B[B[Any]] +reveal_type(y.x) # E: Revealed type is '__main__.B*[Any]' From fbe0b67e522a2d75097b01c69841b4db624e5903 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 24 Jun 2019 13:57:06 +0100 Subject: [PATCH 2/9] Update test --- test-data/unit/check-newsemanal.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index fa98e786528fe..d1c15c4722380 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -2003,7 +2003,7 @@ class C: ... x: B[int] # E: Type argument "builtins.int" of "B" must be a subtype of "__main__.B[Any]" y: B[B[Any]] -reveal_type(y.x) # E: Revealed type is '__main__.B*[Any]' +reveal_type(y.x) # N: Revealed type is '__main__.B*[Any]' [case testNewAnalyzerCastForward1] from typing import cast From a18ba6f588fbe8787f38966d144b91966f5867d7 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 25 Jun 2019 15:07:57 +0100 Subject: [PATCH 3/9] Try some radical idea --- mypy/newsemanal/semanal.py | 63 ++++++++++------------------ mypy/newsemanal/typeanal.py | 26 ++++-------- test-data/unit/check-newsemanal.test | 44 +++++++++++++++++++ 3 files changed, 76 insertions(+), 57 deletions(-) diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index 074ad3189b77d..fb024157a47be 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -2682,8 +2682,8 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> bool: existing = names.get(name) if existing and not (isinstance(existing.node, PlaceholderNode) or # Also give error for another type variable with the same name. - isinstance(existing.node, TypeVarExpr) and - existing.node is s.rvalue.analyzed): + (isinstance(existing.node, TypeVarExpr) and + existing.node is s.rvalue.analyzed)): self.fail("Cannot redefine '%s' as a type variable" % name, s) return False @@ -2692,7 +2692,7 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> bool: # Constraining types n_values = call.arg_kinds[1:].count(ARG_POS) - values = self.analyze_types(call.args[1:1 + n_values]) + values = self.analyze_value_types(call.args[1:1 + n_values]) res = self.process_typevar_parameters(call.args[1 + n_values:], call.arg_names[1 + n_values:], @@ -2724,27 +2724,17 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> bool: upper_bound = AnyType(TypeOfAny.implementation_artifact) # Yes, it's a valid type variable definition! Add it to the symbol table. - if existing and isinstance(existing.node, TypeVarExpr): - # Existing definition from previous semanal iteration, use it. - # Note that unlike in most other cases, we patch an existing symbol, instead of - # adding the type variable symbol only when the types it depends on are ready. - # This is necessary to avoid a "deadlock" in this typical situation: - # T = TypeVar('T', bound=C[Any]) - # class C(Generic[T]): - # ... - # We can use allow_placeholder=True for upper bound and values to avoid this, - # but placeholder types will get deeply into class definition and it will be - # hard to get rid of them. - type_var = existing.node - type_var.values = values - type_var.upper_bound = upper_bound - type_var.variance = variance - else: + if not call.analyzed: type_var = TypeVarExpr(name, self.qualified_name(name), values, upper_bound, variance) type_var.line = call.line call.analyzed = type_var - self.add_symbol(name, type_var, s) + else: + call.analyzed.upper_bound = upper_bound + call.analyzed.values = values + self.progress = True + + self.add_symbol(name, call.analyzed, s) return True def check_typevar_name(self, call: CallExpr, name: str, context: Context) -> bool: @@ -2819,18 +2809,14 @@ def process_typevar_parameters(self, args: List[Expression], # We want to use our custom error message below, so we suppress # the default error message for invalid types here. analyzed = self.expr_to_analyzed_type(param_value, + allow_placeholder=True, report_invalid_types=False) - if analyzed is None: - # It is fine to simply use a temporary Any because we don't need the bound - # for anything before main pass of semantic analysis is finished. We will - # incrementally populate `TypeVarExpr` if some part is missing during main - # pass iterations. - # NOTE: It is safe to not call self.defer() here, because the only way - # we can get None from self.anal_type() is if self.found_incomplete_refs() - # returned True. In turn, the only way it can happen is if someone called - # self.record_incomplete_ref(), and the latter unconditionally calls - # self.defer(). - analyzed = AnyType(TypeOfAny.special_form) + assert analyzed is not None + # NOTE: It is safe to not call self.defer() here, because the only way + # we can get None from self.anal_type() is if self.found_incomplete_refs() + # returned True. In turn, the only way it can happen is if someone called + # self.record_incomplete_ref(), and the latter unconditionally calls + # self.defer(). upper_bound = analyzed if isinstance(upper_bound, AnyType) and upper_bound.is_from_error: self.fail("TypeVar 'bound' must be a type", param_value) @@ -2861,7 +2847,7 @@ def process_typevar_parameters(self, args: List[Expression], variance = CONTRAVARIANT else: variance = INVARIANT - return (variance, upper_bound) + return variance, upper_bound def basic_new_typeinfo(self, name: str, basetype_or_fallback: Instance) -> TypeInfo: class_def = ClassDef(name, Block([])) @@ -2884,18 +2870,15 @@ def basic_new_typeinfo(self, name: str, basetype_or_fallback: Instance) -> TypeI info.bases = [basetype_or_fallback] return info - def analyze_types(self, items: List[Expression]) -> List[Type]: + def analyze_value_types(self, items: List[Expression]) -> List[Type]: """Analyze types from values expressions in type variable definition.""" result = [] # type: List[Type] for node in items: try: - analyzed = self.anal_type(expr_to_unanalyzed_type(node)) - if analyzed is not None: - result.append(analyzed) - else: - # It is fine to simply use temporary Anys because we don't need values - # for anything before main pass of semantic analysis is finished. - result.append(AnyType(TypeOfAny.special_form)) + analyzed = self.anal_type(expr_to_unanalyzed_type(node), + allow_placeholder=True) + assert analyzed is not None + result.append(analyzed) except TypeTranslationError: self.fail('Type expected', node) result.append(AnyType(TypeOfAny.from_error)) diff --git a/mypy/newsemanal/typeanal.py b/mypy/newsemanal/typeanal.py index b16cce09f2e8b..d2c06f42554da 100644 --- a/mypy/newsemanal/typeanal.py +++ b/mypy/newsemanal/typeanal.py @@ -156,24 +156,16 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) if sym is not None: node = sym.node if isinstance(node, PlaceholderNode): - if node.becomes_typeinfo: - # Reference to placeholder type. - if self.api.final_iteration: - self.cannot_resolve_type(t) - return AnyType(TypeOfAny.from_error) - elif self.allow_placeholder: - self.api.defer() - else: - self.api.record_incomplete_ref() - return PlaceholderType(node.fullname(), self.anal_array(t.args), t.line) + # Reference to placeholder type. + if self.api.final_iteration: + self.cannot_resolve_type(t) + return AnyType(TypeOfAny.from_error) + elif self.allow_placeholder: + self.api.defer() else: - if self.api.final_iteration: - self.cannot_resolve_type(t) - return AnyType(TypeOfAny.from_error) - else: - # Reference to an unknown placeholder node. - self.api.record_incomplete_ref() - return AnyType(TypeOfAny.special_form) + self.api.record_incomplete_ref() + return PlaceholderType(node.fullname(), self.anal_array(t.args), t.line) + if node is None: self.fail('Internal error (node is None, kind={})'.format(sym.kind), t) return AnyType(TypeOfAny.special_form) diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index d1c15c4722380..66e81468766f2 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -2005,6 +2005,50 @@ x: B[int] # E: Type argument "builtins.int" of "B" must be a subtype of "__main y: B[B[Any]] reveal_type(y.x) # N: Revealed type is '__main__.B*[Any]' +[case testNewAnalyzerDuplicateTypeVarImportCycle] +import a +[file a.py] +from typing import TypeVar, Any +from b import B, C + +T = TypeVar('T', bound=B[Any]) +T = TypeVar('T', bound=C) + +[file b.py] +from typing import Generic, Any +from a import T + +class B(Generic[T]): + x: T +class C: ... + +x: B[int] +y: B[B[Any]] +reveal_type(y.x) + +[case testNewAnalyzerDuplicateTypeVarImportCycleWithAliases] +import a +[file a.py] +from typing import TypeVar, Any +from b import BA, C + +T = TypeVar('T', bound=BAA[Any]) +T = TypeVar('T', bound=C) +BAA = BA + +[file b.py] +from typing import Generic, Any +from a import T + +BA = B +class B(Generic[T]): + x: T +class C: ... + +x: B[int] +y: B[B[Any]] +reveal_type(y.x) + [case testNewAnalyzerCastForward1] from typing import cast From 8f411199e7bdc33ccc9b06ff3d6d9c3ca3c9f1de Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 25 Jun 2019 16:58:18 +0100 Subject: [PATCH 4/9] Try much less radical solution --- mypy/newsemanal/semanal.py | 12 +++++------- mypy/newsemanal/typeanal.py | 26 +++++++++++++++++--------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index fb024157a47be..2d8a051e20ec1 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -2811,12 +2811,9 @@ def process_typevar_parameters(self, args: List[Expression], analyzed = self.expr_to_analyzed_type(param_value, allow_placeholder=True, report_invalid_types=False) - assert analyzed is not None - # NOTE: It is safe to not call self.defer() here, because the only way - # we can get None from self.anal_type() is if self.found_incomplete_refs() - # returned True. In turn, the only way it can happen is if someone called - # self.record_incomplete_ref(), and the latter unconditionally calls - # self.defer(). + if analyzed is None: + self.defer() + analyzed = PlaceholderType('', [], context.line) upper_bound = analyzed if isinstance(upper_bound, AnyType) and upper_bound.is_from_error: self.fail("TypeVar 'bound' must be a type", param_value) @@ -2877,7 +2874,8 @@ def analyze_value_types(self, items: List[Expression]) -> List[Type]: try: analyzed = self.anal_type(expr_to_unanalyzed_type(node), allow_placeholder=True) - assert analyzed is not None + if analyzed is None: + analyzed = PlaceholderType('', [], node.line) result.append(analyzed) except TypeTranslationError: self.fail('Type expected', node) diff --git a/mypy/newsemanal/typeanal.py b/mypy/newsemanal/typeanal.py index d2c06f42554da..b16cce09f2e8b 100644 --- a/mypy/newsemanal/typeanal.py +++ b/mypy/newsemanal/typeanal.py @@ -156,16 +156,24 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) if sym is not None: node = sym.node if isinstance(node, PlaceholderNode): - # Reference to placeholder type. - if self.api.final_iteration: - self.cannot_resolve_type(t) - return AnyType(TypeOfAny.from_error) - elif self.allow_placeholder: - self.api.defer() + if node.becomes_typeinfo: + # Reference to placeholder type. + if self.api.final_iteration: + self.cannot_resolve_type(t) + return AnyType(TypeOfAny.from_error) + elif self.allow_placeholder: + self.api.defer() + else: + self.api.record_incomplete_ref() + return PlaceholderType(node.fullname(), self.anal_array(t.args), t.line) else: - self.api.record_incomplete_ref() - return PlaceholderType(node.fullname(), self.anal_array(t.args), t.line) - + if self.api.final_iteration: + self.cannot_resolve_type(t) + return AnyType(TypeOfAny.from_error) + else: + # Reference to an unknown placeholder node. + self.api.record_incomplete_ref() + return AnyType(TypeOfAny.special_form) if node is None: self.fail('Internal error (node is None, kind={})'.format(sym.kind), t) return AnyType(TypeOfAny.special_form) From f73061fbc56f54a1ace6d52ceebb3d37ca8c9f21 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 25 Jun 2019 17:58:42 +0100 Subject: [PATCH 5/9] Go forward with a simple fix; also unrelated query issue --- mypy/newsemanal/semanal.py | 5 ++++ mypy/type_visitor.py | 2 +- test-data/unit/check-newsemanal.test | 42 ++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index 2d8a051e20ec1..2b68715643c7b 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -997,6 +997,11 @@ def analyze_class(self, defn: ClassDef) -> None: bases = defn.base_type_exprs bases, tvar_defs, is_protocol = self.clean_up_bases_and_infer_type_variables(defn, bases, context=defn) + + for tvd in tvar_defs: + if any(has_placeholder(t) for t in [tvd.upper_bound] + tvd.values): + self.defer() + self.analyze_class_keywords(defn) result = self.analyze_base_classes(bases) diff --git a/mypy/type_visitor.py b/mypy/type_visitor.py index fb7e149953f18..5d0a139a873fe 100644 --- a/mypy/type_visitor.py +++ b/mypy/type_visitor.py @@ -282,7 +282,7 @@ def visit_deleted_type(self, t: DeletedType) -> T: return self.strategy([]) def visit_type_var(self, t: TypeVarType) -> T: - return self.strategy([]) + return self.query_types([t.upper_bound] + t.values) def visit_partial_type(self, t: PartialType) -> T: return self.query_types(t.inner_types) diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index 66e81468766f2..1fc79adef0111 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -2025,6 +2025,12 @@ class C: ... x: B[int] y: B[B[Any]] reveal_type(y.x) +[out] +tmp/b.py:8: error: Type argument "builtins.int" of "B" must be a subtype of "b.B[Any]" +tmp/b.py:10: note: Revealed type is 'b.B*[Any]' +tmp/a.py:5: error: Cannot redefine 'T' as a type variable +tmp/a.py:5: error: Invalid assignment target +tmp/a.py:5: error: "int" not callable [case testNewAnalyzerDuplicateTypeVarImportCycleWithAliases] import a @@ -2048,6 +2054,42 @@ class C: ... x: B[int] y: B[B[Any]] reveal_type(y.x) +[out] +tmp/b.py:9: error: Type argument "builtins.int" of "B" must be a subtype of "b.B[Any]" +tmp/b.py:11: note: Revealed type is 'b.B*[Any]' +tmp/a.py:5: error: Cannot redefine 'T' as a type variable +tmp/a.py:5: error: Invalid assignment target + +[case testNewAnalyzerTypeVarBoundInCycle] +import factory, box + +[file factory.py] +from typing import Generic, Type + +from box import BoxT + +class Factory(Generic[BoxT]): + value: int + + def create(self, boxClass: Type[BoxT]) -> BoxT: + reveal_type(boxClass.create(self)) # N: Revealed type is 'BoxT`1' + return boxClass.create(self) + +[file box.py] +from typing import TYPE_CHECKING, Type, TypeVar + +if TYPE_CHECKING: + from factory import Factory + +BoxT = TypeVar('BoxT', bound='Box') + +class Box: + @classmethod + def create(cls: Type[BoxT], f: Factory) -> BoxT: + return cls(f.value) + + def __init__(self, value: int) -> None: ... +[builtins fixtures/classmethod.pyi] [case testNewAnalyzerCastForward1] from typing import cast From 9f0e7f9f130f00ce68916aa3679133839c7f3a3d Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 25 Jun 2019 18:59:27 +0100 Subject: [PATCH 6/9] Add some comments (and a defer just in case) --- mypy/newsemanal/semanal.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index 2b68715643c7b..b9b873a15393d 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -1000,6 +1000,8 @@ def analyze_class(self, defn: ClassDef) -> None: for tvd in tvar_defs: if any(has_placeholder(t) for t in [tvd.upper_bound] + tvd.values): + # Some type variable bounds or values are not ready, we need + # to re-analyze this class. self.defer() self.analyze_class_keywords(defn) @@ -2817,6 +2819,8 @@ def process_typevar_parameters(self, args: List[Expression], allow_placeholder=True, report_invalid_types=False) if analyzed is None: + # Type variables are special: we need to place them in the symbol table + # soon, even if upper bound is not ready yet. self.defer() analyzed = PlaceholderType('', [], context.line) upper_bound = analyzed @@ -2880,6 +2884,9 @@ def analyze_value_types(self, items: List[Expression]) -> List[Type]: analyzed = self.anal_type(expr_to_unanalyzed_type(node), allow_placeholder=True) if analyzed is None: + # Type variables are special: we need to place them in the symbol table + # soon, even if some value is not ready yet. + self.defer() analyzed = PlaceholderType('', [], node.line) result.append(analyzed) except TypeTranslationError: From 15cc18bc3405e6f84d798efaeebfa31246dbc352 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 25 Jun 2019 19:11:52 +0100 Subject: [PATCH 7/9] Fix self-check --- mypy/newsemanal/semanal.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index b9b873a15393d..8f8a835493b90 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -2737,6 +2737,7 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> bool: type_var.line = call.line call.analyzed = type_var else: + assert isinstance(call.analyzed, TypeVarExpr) call.analyzed.upper_bound = upper_bound call.analyzed.values = values self.progress = True From e035042b3320b4411455c9b8998c114d7428846b Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Wed, 26 Jun 2019 15:18:50 +0100 Subject: [PATCH 8/9] Address CR --- mypy/newsemanal/semanal.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index 8f8a835493b90..6996dbf9da79d 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -2738,9 +2738,10 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> bool: call.analyzed = type_var else: assert isinstance(call.analyzed, TypeVarExpr) + if call.analyzed.values != values or call.analyzed.upper_bound != upper_bound: + self.progress = True call.analyzed.upper_bound = upper_bound call.analyzed.values = values - self.progress = True self.add_symbol(name, call.analyzed, s) return True @@ -2821,8 +2822,11 @@ def process_typevar_parameters(self, args: List[Expression], report_invalid_types=False) if analyzed is None: # Type variables are special: we need to place them in the symbol table - # soon, even if upper bound is not ready yet. - self.defer() + # soon, even if upper bound is not ready yet. Otherwise avoiding a "deadlock" + # in this common pattern would be tricky: + # T = TypeVar('T', bound=Custom[Any]) + # class Custom(Generic[T]): + # ... analyzed = PlaceholderType('', [], context.line) upper_bound = analyzed if isinstance(upper_bound, AnyType) and upper_bound.is_from_error: @@ -2886,8 +2890,8 @@ def analyze_value_types(self, items: List[Expression]) -> List[Type]: allow_placeholder=True) if analyzed is None: # Type variables are special: we need to place them in the symbol table - # soon, even if some value is not ready yet. - self.defer() + # soon, even if some value is not ready yet, see process_typevar_parameters() + # for an example. analyzed = PlaceholderType('', [], node.line) result.append(analyzed) except TypeTranslationError: From bf798153cb1fd986594d0c06a474727ca7172ba0 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Wed, 26 Jun 2019 15:39:13 +0100 Subject: [PATCH 9/9] Fix lint --- mypy/newsemanal/semanal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index 6996dbf9da79d..4986b4c53ba4a 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -2822,8 +2822,8 @@ def process_typevar_parameters(self, args: List[Expression], report_invalid_types=False) if analyzed is None: # Type variables are special: we need to place them in the symbol table - # soon, even if upper bound is not ready yet. Otherwise avoiding a "deadlock" - # in this common pattern would be tricky: + # soon, even if upper bound is not ready yet. Otherwise avoiding + # a "deadlock" in this common pattern would be tricky: # T = TypeVar('T', bound=Custom[Any]) # class Custom(Generic[T]): # ...