From 158c88961865c3c24653ddb85704897702781975 Mon Sep 17 00:00:00 2001 From: Max Moroz Date: Tue, 14 Mar 2017 04:25:51 -0700 Subject: [PATCH 01/13] Make no inference when variables present in second argument to isinstance --- mypy/semanal.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index f3b1eaec8568..2b44f85c0157 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2696,8 +2696,9 @@ def visit_member_expr(self, expr: MemberExpr) -> None: # one type checker run. If we reported errors here, # the build would terminate after semantic analysis # and we wouldn't be able to report any type errors. - full_name = '%s.%s' % (file.fullname() if file is not None else None, expr.name) - mod_name = " '%s'" % file.fullname() if file is not None else '' + full_name = '%s.%s' % (file.fullname() if file is not None # type: ignore + else None, expr.name) + mod_name = " '%s'" % file.fullname() if file is not None else '' # type: ignore if full_name in obsolete_name_mapping: self.fail("Module%s has no attribute %r (it's now called %r)" % ( mod_name, expr.name, obsolete_name_mapping[full_name]), expr) From c5525bd2b6f89c79d20c37b386d2031b190eed3e Mon Sep 17 00:00:00 2001 From: Max Moroz Date: Tue, 14 Mar 2017 22:06:56 -0700 Subject: [PATCH 02/13] Add failing unit tests --- test-data/unit/check-isinstance.test | 120 +++++++++++++++++++++ test-data/unit/fixtures/isinstancelist.pyi | 1 + 2 files changed, 121 insertions(+) diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index 973d463d6085..24031084a29d 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -1412,3 +1412,123 @@ def f(x: Union[int, A], a: Type[A]) -> None: [builtins fixtures/isinstancelist.pyi] +[case testIssubclass] +from typing import Type, ClassVar + + +class Mob: + pass + +class Goblin(Mob): + aggressive: bool = True + level: int + +class GoblinAmbusher(Goblin): + job: ClassVar[str] = 'Ranger' + +class GoblinDigger(Goblin): + job: ClassVar[str] = 'Thief' + + +def test_issubclass1(cls: Type[Mob]) -> None: + if issubclass(cls, Goblin): + cls.aggressive + cls.level + cls.job # E: Type[Goblin] has no attribute "job" + g = cls() + g.level = 15 + g.job # E: "Goblin" has no attribute "job" + if issubclass(cls, GoblinAmbusher): + cls.aggressive + cls.level + cls.job + g = cls() + g.level = 15 + g.job + g.job = 'Warrior' # E: Cannot assign to class variable "job" via instance + else: + cls.aggressive # E: Type[Mob] has no attribute "aggressive" + cls.job # E: Type[Mob] has no attribute "job" + cls.level # E: Type[Mob] has no attribute "level" + m = cls() + m.level = 15 # E: "Mob" has no attribute "level" + m.job # E: "Mob" has no attribute "job" + if issubclass(cls, GoblinAmbusher): + cls.aggressive + cls.job + cls.level + ga = cls() + ga.level = 15 + ga.job + ga.job = 'Warrior' # E: Cannot assign to class variable "job" via instance + + if issubclass(cls, GoblinAmbusher): + cls.aggressive + cls.level + cls.job + ga = cls() + ga.level = 15 + ga.job + ga.job = "Warrior" # E: Cannot assign to class variable "job" via instance + + if issubclass(cls, (Goblin, GoblinAmbusher)): + cls.aggressive + cls.level + cls.job # E: Some element of union has no attribute "job" + g = cls() + g.level = 15 + g.job # E: "Goblin" has no attribute "job" + if issubclass(cls, GoblinAmbusher): + cls.aggressive + cls.level + reveal_type(cls) # E: Revealed type is 'Type[__main__.GoblinAmbusher]' + cls.job + ga = cls() + ga.level = 15 + ga.job + ga.job = "Warrior" # E: Cannot assign to class variable "job" via instance + else: + cls.aggressive # E: Type[Mob] has no attribute "aggressive" + cls.job # E: Type[Mob] has no attribute "job" + cls.level # E: Type[Mob] has no attribute "level" + m = cls() + m.level = 15 # E: "Mob" has no attribute "level" + m.job # E: "Mob" has no attribute "job" + if issubclass(cls, GoblinAmbusher): + cls.aggressive + cls.job + cls.level + ga = cls() + ga.level = 15 + ga.job + ga.job = "Warrior" # E: Cannot assign to class variable "job" via instance + + if issubclass(cls, (GoblinDigger, GoblinAmbusher)): + cls.aggressive + cls.level + cls.job + g = cls() + g.level = 15 + g.job + g.job = "Warrior" # E: Cannot assign to class variable "job" via instance + + +def test_issubclass2(cls: Type[Goblin]) -> None: + if issubclass(cls, GoblinAmbusher): + cls.aggressive + cls.level + cls.job + ga = cls() + ga.level = 15 + ga.job + ga.job = "Warrior" # E: Cannot assign to class variable "job" via instance + else: + cls.aggressive + cls.level + cls.job # E: Type[Goblin] has no attribute "job" + g = cls() + g.level = 15 + g.job # E: "Goblin" has no attribute "job" + + +[builtins fixtures/isinstancelist.pyi] \ No newline at end of file diff --git a/test-data/unit/fixtures/isinstancelist.pyi b/test-data/unit/fixtures/isinstancelist.pyi index 9096961b06ab..7d0c8a02b1af 100644 --- a/test-data/unit/fixtures/isinstancelist.pyi +++ b/test-data/unit/fixtures/isinstancelist.pyi @@ -12,6 +12,7 @@ class tuple: pass class function: pass def isinstance(x: object, t: Union[type, Tuple]) -> bool: pass +def issubclass(x: object, t: Union[type, Tuple]) -> bool: pass @builtinclass class int: From 1776e908aa2b5ba84550fb373173aa6ac159b53c Mon Sep 17 00:00:00 2001 From: Max Moroz Date: Tue, 14 Mar 2017 22:07:20 -0700 Subject: [PATCH 03/13] Infer type from issubclass --- mypy/checker.py | 60 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 72fbc371ac4f..756991ebca7d 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -6,7 +6,7 @@ import sys from typing import ( - Dict, Set, List, cast, Tuple, TypeVar, Union, Optional, NamedTuple, Iterator + Dict, Set, List, cast, Tuple, TypeVar, Union, Optional, NamedTuple, Iterator, MutableMapping ) from mypy.errors import Errors, report_internal_error @@ -2616,6 +2616,20 @@ def or_conditional_maps(m1: TypeMap, m2: TypeMap) -> TypeMap: return result +def convert_to_types(m: MutableMapping[Expression, Type]) -> None: + for k in m: + x = m[k] + if isinstance(x, UnionType): + m[k] = UnionType([TypeType(t) for t in x.items]) + elif isinstance(x, Instance): + m[k] = TypeType(m[k]) + else: + # TODO: verify this is ok + # unknown object, don't know how to convert + m.clear() + return + + def find_isinstance_check(node: Expression, type_map: Dict[Expression, Type], ) -> Tuple[TypeMap, TypeMap]: @@ -2639,8 +2653,24 @@ def find_isinstance_check(node: Expression, expr = node.args[0] if expr.literal == LITERAL_TYPE: vartype = type_map[expr] - types = get_isinstance_type(node.args[1], type_map) - return conditional_type_map(expr, vartype, types) + type = get_isinstance_type(node.args[1], type_map) + return conditional_type_map(expr, vartype, type) + elif refers_to_fullname(node.callee, 'builtins.issubclass'): + expr = node.args[0] + if expr.literal == LITERAL_TYPE: + vartype = type_map[expr] + type = get_issubclass_type(node.args[1], type_map) + if isinstance(vartype, UnionType): + vartype = UnionType([t.item for t in vartype.items]) # type: ignore + elif isinstance(vartype, TypeType): + vartype = vartype.item + else: + # TODO: verify this is ok + return {}, {} # unknown type + yes_map, no_map = conditional_type_map(expr, vartype, type) + convert_to_types(yes_map) + convert_to_types(no_map) + return yes_map, no_map elif refers_to_fullname(node.callee, 'builtins.callable'): expr = node.args[0] if expr.literal == LITERAL_TYPE: @@ -2747,6 +2777,30 @@ def get_isinstance_type(expr: Expression, type_map: Dict[Expression, Type]) -> L return types +def get_issubclass_type(expr: Expression, type_map: Dict[Expression, Type]) -> Type: + all_types = [type_map[e] for e in flatten(expr)] + + types = [] # type: List[Type] + + for type in all_types: + if isinstance(type, FunctionLike): + if type.is_type_obj(): + # Type variables may be present -- erase them, which is the best + # we can do (outside disallowing them here). + type = erase_typevars(type.items()[0].ret_type) + types.append(type) + elif isinstance(type, TypeType): + types.append(type.item) + else: # we didn't see an actual type, but rather a variable whose value is unknown to us + return None + + assert len(types) != 0 + if len(types) == 1: + return types[0] + else: + return UnionType(types) + + def expand_func(defn: FuncItem, map: Dict[TypeVarId, Type]) -> FuncItem: visitor = TypeTransformVisitor(map) ret = defn.accept(visitor) From 3334b8e67151f80cc62cdad31d4408bc9b01c32d Mon Sep 17 00:00:00 2001 From: Max Moroz Date: Sat, 18 Mar 2017 11:28:56 -0700 Subject: [PATCH 04/13] Apply CR comments --- mypy/checker.py | 58 +++++++----------- test-data/unit/check-isinstance.test | 90 +++++++++++++++++++++------- typeshed | 2 +- 3 files changed, 90 insertions(+), 60 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 756991ebca7d..1bc05b712dbd 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -6,7 +6,7 @@ import sys from typing import ( - Dict, Set, List, cast, Tuple, TypeVar, Union, Optional, NamedTuple, Iterator, MutableMapping + Dict, Set, List, cast, Tuple, TypeVar, Union, Optional, NamedTuple, Iterator ) from mypy.errors import Errors, report_internal_error @@ -2616,18 +2616,18 @@ def or_conditional_maps(m1: TypeMap, m2: TypeMap) -> TypeMap: return result -def convert_to_types(m: MutableMapping[Expression, Type]) -> None: - for k in m: - x = m[k] - if isinstance(x, UnionType): - m[k] = UnionType([TypeType(t) for t in x.items]) - elif isinstance(x, Instance): - m[k] = TypeType(m[k]) +def convert_to_typetype(type_map: TypeMap) -> TypeMap: + converted_type_map: TypeMap = {} + for expr, typ in type_map.items(): + if isinstance(typ, UnionType): + converted_type_map[expr] = UnionType([TypeType(t) for t in typ.items]) + elif isinstance(typ, Instance): + converted_type_map[expr] = TypeType(typ) else: # TODO: verify this is ok # unknown object, don't know how to convert - m.clear() - return + return {} + return converted_type_map def find_isinstance_check(node: Expression, @@ -2659,17 +2659,23 @@ def find_isinstance_check(node: Expression, expr = node.args[0] if expr.literal == LITERAL_TYPE: vartype = type_map[expr] - type = get_issubclass_type(node.args[1], type_map) + type = get_isinstance_type(node.args[1], type_map) if isinstance(vartype, UnionType): - vartype = UnionType([t.item for t in vartype.items]) # type: ignore + union_list = [] + for t in vartype.items: + if isinstance(t, TypeType): + union_list.append(t.item) + else: + # this an error; should be caught earlier, so we should never be here + return {}, {} + vartype = UnionType(union_list) elif isinstance(vartype, TypeType): vartype = vartype.item else: # TODO: verify this is ok return {}, {} # unknown type yes_map, no_map = conditional_type_map(expr, vartype, type) - convert_to_types(yes_map) - convert_to_types(no_map) + yes_map, no_map = map(convert_to_typetype, (yes_map, no_map)) return yes_map, no_map elif refers_to_fullname(node.callee, 'builtins.callable'): expr = node.args[0] @@ -2777,30 +2783,6 @@ def get_isinstance_type(expr: Expression, type_map: Dict[Expression, Type]) -> L return types -def get_issubclass_type(expr: Expression, type_map: Dict[Expression, Type]) -> Type: - all_types = [type_map[e] for e in flatten(expr)] - - types = [] # type: List[Type] - - for type in all_types: - if isinstance(type, FunctionLike): - if type.is_type_obj(): - # Type variables may be present -- erase them, which is the best - # we can do (outside disallowing them here). - type = erase_typevars(type.items()[0].ret_type) - types.append(type) - elif isinstance(type, TypeType): - types.append(type.item) - else: # we didn't see an actual type, but rather a variable whose value is unknown to us - return None - - assert len(types) != 0 - if len(types) == 1: - return types[0] - else: - return UnionType(types) - - def expand_func(defn: FuncItem, map: Dict[TypeVarId, Type]) -> FuncItem: visitor = TypeTransformVisitor(map) ret = defn.accept(visitor) diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index 24031084a29d..80fe0417d878 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -1412,9 +1412,41 @@ def f(x: Union[int, A], a: Type[A]) -> None: [builtins fixtures/isinstancelist.pyi] + [case testIssubclass] from typing import Type, ClassVar +class Goblin: + aggressive: bool = True + level: int + +class GoblinAmbusher(Goblin): + job: ClassVar[str] = 'Ranger' + + +def test_issubclass(cls: Type[Goblin]) -> None: + if issubclass(cls, GoblinAmbusher): + cls.aggressive + cls.level + cls.job + ga = cls() + ga.level = 15 + ga.job + ga.job = "Warrior" # E: Cannot assign to class variable "job" via instance + else: + cls.aggressive + cls.level + cls.job # E: Type[Goblin] has no attribute "job" + g = cls() + g.level = 15 + g.job # E: "Goblin" has no attribute "job" + + +[builtins fixtures/isinstancelist.pyi] + + +[case testIssubclassDeepHierarchy] +from typing import Type, ClassVar class Mob: pass @@ -1426,11 +1458,7 @@ class Goblin(Mob): class GoblinAmbusher(Goblin): job: ClassVar[str] = 'Ranger' -class GoblinDigger(Goblin): - job: ClassVar[str] = 'Thief' - - -def test_issubclass1(cls: Type[Mob]) -> None: +def test_issubclass(cls: Type[Mob]) -> None: if issubclass(cls, Goblin): cls.aggressive cls.level @@ -1471,6 +1499,27 @@ def test_issubclass1(cls: Type[Mob]) -> None: ga.job ga.job = "Warrior" # E: Cannot assign to class variable "job" via instance +[builtins fixtures/isinstancelist.pyi] + + +[case testIssubclassTuple] +from typing import Type, ClassVar + +class Mob: + pass + +class Goblin(Mob): + aggressive: bool = True + level: int + +class GoblinAmbusher(Goblin): + job: ClassVar[str] = 'Ranger' + +class GoblinDigger(Goblin): + job: ClassVar[str] = 'Thief' + + +def test_issubclass(cls: Type[Mob]) -> None: if issubclass(cls, (Goblin, GoblinAmbusher)): cls.aggressive cls.level @@ -1512,23 +1561,22 @@ def test_issubclass1(cls: Type[Mob]) -> None: g.job g.job = "Warrior" # E: Cannot assign to class variable "job" via instance +[builtins fixtures/isinstancelist.pyi] -def test_issubclass2(cls: Type[Goblin]) -> None: - if issubclass(cls, GoblinAmbusher): - cls.aggressive - cls.level - cls.job - ga = cls() - ga.level = 15 - ga.job - ga.job = "Warrior" # E: Cannot assign to class variable "job" via instance + +[case testIssubclassBuiltins] +from typing import List, Type + +class MyList(List): pass +class MyIntList(List[int]): pass + +def f(cls: Type): + if issubclass(cls, MyList): + cls()[0] else: - cls.aggressive - cls.level - cls.job # E: Type[Goblin] has no attribute "job" - g = cls() - g.level = 15 - g.job # E: "Goblin" has no attribute "job" + cls()[0] # E: + if issubclass(cls, MyIntList): + cls()[0] + 1 -[builtins fixtures/isinstancelist.pyi] \ No newline at end of file +[builtins fixtures/isinstancelist.pyi] diff --git a/typeshed b/typeshed index bd5b33f3b18f..48b196295176 160000 --- a/typeshed +++ b/typeshed @@ -1 +1 @@ -Subproject commit bd5b33f3b18fc8811ad2403d35f21ad6aae94b62 +Subproject commit 48b19629517677de98cb46b1f52fcbe03f0fa2eb From 3587549c4d3f5174738234df011a68049e3d663a Mon Sep 17 00:00:00 2001 From: Max Moroz Date: Mon, 20 Mar 2017 19:40:41 -0700 Subject: [PATCH 05/13] Fix 3.3 syntax, updated typeshed --- mypy/checker.py | 2 +- typeshed | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 1bc05b712dbd..489f8bd4c41e 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2617,7 +2617,7 @@ def or_conditional_maps(m1: TypeMap, m2: TypeMap) -> TypeMap: def convert_to_typetype(type_map: TypeMap) -> TypeMap: - converted_type_map: TypeMap = {} + converted_type_map = {} # type: TypeMap for expr, typ in type_map.items(): if isinstance(typ, UnionType): converted_type_map[expr] = UnionType([TypeType(t) for t in typ.items]) diff --git a/typeshed b/typeshed index 48b196295176..48920fea7ee6 160000 --- a/typeshed +++ b/typeshed @@ -1 +1 @@ -Subproject commit 48b19629517677de98cb46b1f52fcbe03f0fa2eb +Subproject commit 48920fea7ee6c1d413fba12fe709c652da065393 From 81e19234191860985359b01040b3d536731bb76a Mon Sep 17 00:00:00 2001 From: Max Moroz Date: Tue, 21 Mar 2017 12:20:14 -0700 Subject: [PATCH 06/13] More CR fixes --- mypy/checker.py | 3 ++- mypy/semanal.py | 4 ++-- test-data/unit/check-isinstance.test | 19 +------------------ typeshed | 2 +- 4 files changed, 6 insertions(+), 22 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 489f8bd4c41e..b1ad32a10abe 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2666,7 +2666,8 @@ def find_isinstance_check(node: Expression, if isinstance(t, TypeType): union_list.append(t.item) else: - # this an error; should be caught earlier, so we should never be here + # this is an error that should be reported earlier + # if we reach here, we refuse to do any type inference return {}, {} vartype = UnionType(union_list) elif isinstance(vartype, TypeType): diff --git a/mypy/semanal.py b/mypy/semanal.py index 2b44f85c0157..922a508910b3 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2696,9 +2696,9 @@ def visit_member_expr(self, expr: MemberExpr) -> None: # one type checker run. If we reported errors here, # the build would terminate after semantic analysis # and we wouldn't be able to report any type errors. - full_name = '%s.%s' % (file.fullname() if file is not None # type: ignore + full_name = '%s.%s' % (file.fullname() if file is not None else None, expr.name) - mod_name = " '%s'" % file.fullname() if file is not None else '' # type: ignore + mod_name = " '%s'" % file.fullname() if file is not None else '' if full_name in obsolete_name_mapping: self.fail("Module%s has no attribute %r (it's now called %r)" % ( mod_name, expr.name, obsolete_name_mapping[full_name]), expr) diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index 80fe0417d878..9c63563daf17 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -1417,16 +1417,13 @@ def f(x: Union[int, A], a: Type[A]) -> None: from typing import Type, ClassVar class Goblin: - aggressive: bool = True level: int class GoblinAmbusher(Goblin): job: ClassVar[str] = 'Ranger' - def test_issubclass(cls: Type[Goblin]) -> None: if issubclass(cls, GoblinAmbusher): - cls.aggressive cls.level cls.job ga = cls() @@ -1434,7 +1431,6 @@ def test_issubclass(cls: Type[Goblin]) -> None: ga.job ga.job = "Warrior" # E: Cannot assign to class variable "job" via instance else: - cls.aggressive cls.level cls.job # E: Type[Goblin] has no attribute "job" g = cls() @@ -1452,7 +1448,6 @@ class Mob: pass class Goblin(Mob): - aggressive: bool = True level: int class GoblinAmbusher(Goblin): @@ -1460,14 +1455,12 @@ class GoblinAmbusher(Goblin): def test_issubclass(cls: Type[Mob]) -> None: if issubclass(cls, Goblin): - cls.aggressive cls.level cls.job # E: Type[Goblin] has no attribute "job" g = cls() g.level = 15 g.job # E: "Goblin" has no attribute "job" if issubclass(cls, GoblinAmbusher): - cls.aggressive cls.level cls.job g = cls() @@ -1475,14 +1468,12 @@ def test_issubclass(cls: Type[Mob]) -> None: g.job g.job = 'Warrior' # E: Cannot assign to class variable "job" via instance else: - cls.aggressive # E: Type[Mob] has no attribute "aggressive" cls.job # E: Type[Mob] has no attribute "job" cls.level # E: Type[Mob] has no attribute "level" m = cls() m.level = 15 # E: "Mob" has no attribute "level" m.job # E: "Mob" has no attribute "job" if issubclass(cls, GoblinAmbusher): - cls.aggressive cls.job cls.level ga = cls() @@ -1491,7 +1482,6 @@ def test_issubclass(cls: Type[Mob]) -> None: ga.job = 'Warrior' # E: Cannot assign to class variable "job" via instance if issubclass(cls, GoblinAmbusher): - cls.aggressive cls.level cls.job ga = cls() @@ -1509,7 +1499,6 @@ class Mob: pass class Goblin(Mob): - aggressive: bool = True level: int class GoblinAmbusher(Goblin): @@ -1518,17 +1507,14 @@ class GoblinAmbusher(Goblin): class GoblinDigger(Goblin): job: ClassVar[str] = 'Thief' - def test_issubclass(cls: Type[Mob]) -> None: if issubclass(cls, (Goblin, GoblinAmbusher)): - cls.aggressive cls.level cls.job # E: Some element of union has no attribute "job" g = cls() g.level = 15 g.job # E: "Goblin" has no attribute "job" if issubclass(cls, GoblinAmbusher): - cls.aggressive cls.level reveal_type(cls) # E: Revealed type is 'Type[__main__.GoblinAmbusher]' cls.job @@ -1537,14 +1523,12 @@ def test_issubclass(cls: Type[Mob]) -> None: ga.job ga.job = "Warrior" # E: Cannot assign to class variable "job" via instance else: - cls.aggressive # E: Type[Mob] has no attribute "aggressive" cls.job # E: Type[Mob] has no attribute "job" cls.level # E: Type[Mob] has no attribute "level" m = cls() m.level = 15 # E: "Mob" has no attribute "level" m.job # E: "Mob" has no attribute "job" if issubclass(cls, GoblinAmbusher): - cls.aggressive cls.job cls.level ga = cls() @@ -1553,7 +1537,6 @@ def test_issubclass(cls: Type[Mob]) -> None: ga.job = "Warrior" # E: Cannot assign to class variable "job" via instance if issubclass(cls, (GoblinDigger, GoblinAmbusher)): - cls.aggressive cls.level cls.job g = cls() @@ -1574,7 +1557,7 @@ def f(cls: Type): if issubclass(cls, MyList): cls()[0] else: - cls()[0] # E: + cls()[0] if issubclass(cls, MyIntList): cls()[0] + 1 diff --git a/typeshed b/typeshed index 48920fea7ee6..bd5b33f3b18f 160000 --- a/typeshed +++ b/typeshed @@ -1 +1 @@ -Subproject commit 48920fea7ee6c1d413fba12fe709c652da065393 +Subproject commit bd5b33f3b18fc8811ad2403d35f21ad6aae94b62 From e4ffdc5a0121d3e103b6ddd9a80504b96a7d7779 Mon Sep 17 00:00:00 2001 From: Max Moroz Date: Tue, 21 Mar 2017 12:43:11 -0700 Subject: [PATCH 07/13] Fix testr --- test-data/unit/check-isinstance.test | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index 9c63563daf17..f16f1be2318d 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -1553,11 +1553,11 @@ from typing import List, Type class MyList(List): pass class MyIntList(List[int]): pass -def f(cls: Type): +def f(cls: Type[object]) -> None: if issubclass(cls, MyList): cls()[0] else: - cls()[0] + cls()[0] # E: Value of type "object" is not indexable if issubclass(cls, MyIntList): cls()[0] + 1 From 9440a528753713c97eab4e862b9eb1f56460ddbe Mon Sep 17 00:00:00 2001 From: Max Moroz Date: Tue, 21 Mar 2017 13:04:25 -0700 Subject: [PATCH 08/13] Revert accidental format change --- mypy/semanal.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 922a508910b3..f3b1eaec8568 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2696,8 +2696,7 @@ def visit_member_expr(self, expr: MemberExpr) -> None: # one type checker run. If we reported errors here, # the build would terminate after semantic analysis # and we wouldn't be able to report any type errors. - full_name = '%s.%s' % (file.fullname() if file is not None - else None, expr.name) + full_name = '%s.%s' % (file.fullname() if file is not None else None, expr.name) mod_name = " '%s'" % file.fullname() if file is not None else '' if full_name in obsolete_name_mapping: self.fail("Module%s has no attribute %r (it's now called %r)" % ( From f94ca45878ada874de15d362dd261acbc775277c Mon Sep 17 00:00:00 2001 From: Max Moroz Date: Thu, 13 Apr 2017 07:38:53 -0700 Subject: [PATCH 09/13] Address CR --- mypy/checker.py | 6 +- test-data/unit/check-isinstance.test | 83 +++++++--------------------- 2 files changed, 23 insertions(+), 66 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 149783583db6..2c9b51eb95e2 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2683,8 +2683,7 @@ def convert_to_typetype(type_map: TypeMap) -> TypeMap: elif isinstance(typ, Instance): converted_type_map[expr] = TypeType(typ) else: - # TODO: verify this is ok - # unknown object, don't know how to convert + # unknown type; error was likely reported earlier return {} return converted_type_map @@ -2734,7 +2733,8 @@ def find_isinstance_check(node: Expression, elif isinstance(vartype, TypeType): vartype = vartype.item else: - # TODO: verify this is ok + # any other object whose type we don't know precisely + # for example, Any or Instance of type type return {}, {} # unknown type yes_map, no_map = conditional_type_map(expr, vartype, type) yes_map, no_map = map(convert_to_typetype, (yes_map, no_map)) diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index 3a875836f884..34ea756ad089 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -1434,6 +1434,7 @@ class GoblinAmbusher(Goblin): def test_issubclass(cls: Type[Goblin]) -> None: if issubclass(cls, GoblinAmbusher): + reveal_type(cls) # E: Revealed type is 'Type[__main__.GoblinAmbusher]' cls.level cls.job ga = cls() @@ -1441,6 +1442,7 @@ def test_issubclass(cls: Type[Goblin]) -> None: ga.job ga.job = "Warrior" # E: Cannot assign to class variable "job" via instance else: + reveal_type(cls) # E: Revealed type is 'Type[__main__.Goblin]' cls.level cls.job # E: Type[Goblin] has no attribute "job" g = cls() @@ -1452,52 +1454,29 @@ def test_issubclass(cls: Type[Goblin]) -> None: [case testIssubclassDeepHierarchy] -from typing import Type, ClassVar +from typing import Type class Mob: pass class Goblin(Mob): - level: int + pass class GoblinAmbusher(Goblin): - job: ClassVar[str] = 'Ranger' + pass def test_issubclass(cls: Type[Mob]) -> None: if issubclass(cls, Goblin): - cls.level - cls.job # E: Type[Goblin] has no attribute "job" - g = cls() - g.level = 15 - g.job # E: "Goblin" has no attribute "job" + reveal_type(cls) # E: Revealed type is 'Type[__main__.Goblin]' if issubclass(cls, GoblinAmbusher): - cls.level - cls.job - g = cls() - g.level = 15 - g.job - g.job = 'Warrior' # E: Cannot assign to class variable "job" via instance + reveal_type(cls) # E: Revealed type is 'Type[__main__.GoblinAmbusher]' else: - cls.job # E: Type[Mob] has no attribute "job" - cls.level # E: Type[Mob] has no attribute "level" - m = cls() - m.level = 15 # E: "Mob" has no attribute "level" - m.job # E: "Mob" has no attribute "job" + reveal_type(cls) # E: Revealed type is 'Type[__main__.Mob]' if issubclass(cls, GoblinAmbusher): - cls.job - cls.level - ga = cls() - ga.level = 15 - ga.job - ga.job = 'Warrior' # E: Cannot assign to class variable "job" via instance + reveal_type(cls) # E: Revealed type is 'Type[__main__.GoblinAmbusher]' if issubclass(cls, GoblinAmbusher): - cls.level - cls.job - ga = cls() - ga.level = 15 - ga.job - ga.job = "Warrior" # E: Cannot assign to class variable "job" via instance + reveal_type(cls) # E: Revealed type is 'Type[__main__.GoblinAmbusher]' [builtins fixtures/isinstancelist.pyi] @@ -1509,50 +1488,25 @@ class Mob: pass class Goblin(Mob): - level: int + pass class GoblinAmbusher(Goblin): - job: ClassVar[str] = 'Ranger' + pass class GoblinDigger(Goblin): - job: ClassVar[str] = 'Thief' + pass def test_issubclass(cls: Type[Mob]) -> None: if issubclass(cls, (Goblin, GoblinAmbusher)): - cls.level - cls.job # E: Type[Goblin] has no attribute "job" - g = cls() - g.level = 15 - g.job # E: "Goblin" has no attribute "job" + reveal_type(cls) # E: Revealed type is 'Type[__main__.Goblin]' if issubclass(cls, GoblinAmbusher): - cls.level reveal_type(cls) # E: Revealed type is 'Type[__main__.GoblinAmbusher]' - cls.job - ga = cls() - ga.level = 15 - ga.job - ga.job = "Warrior" # E: Cannot assign to class variable "job" via instance else: - cls.job # E: Type[Mob] has no attribute "job" - cls.level # E: Type[Mob] has no attribute "level" - m = cls() - m.level = 15 # E: "Mob" has no attribute "level" - m.job # E: "Mob" has no attribute "job" + reveal_type(cls) # E: Revealed type is 'Type[__main__.Mob]' if issubclass(cls, GoblinAmbusher): - cls.job - cls.level - ga = cls() - ga.level = 15 - ga.job - ga.job = "Warrior" # E: Cannot assign to class variable "job" via instance - + reveal_type(cls) # E: Revealed type is 'Type[__main__.GoblinAmbusher]' if issubclass(cls, (GoblinDigger, GoblinAmbusher)): - cls.level - cls.job - g = cls() - g.level = 15 - g.job - g.job = "Warrior" # E: Cannot assign to class variable "job" via instance + reveal_type(cls) # E: Revealed type is 'Union[Type[__main__.GoblinDigger], Type[__main__.GoblinAmbusher]]' [builtins fixtures/isinstancelist.pyi] @@ -1565,11 +1519,14 @@ class MyIntList(List[int]): pass def f(cls: Type[object]) -> None: if issubclass(cls, MyList): + reveal_type(cls) # E: Revealed type is 'Type[__main__.MyList]' cls()[0] else: + reveal_type(cls) # E: Revealed type is 'Type[builtins.object]' cls()[0] # E: Value of type "object" is not indexable if issubclass(cls, MyIntList): + reveal_type(cls) # E: Revealed type is 'Type[__main__.MyIntList]' cls()[0] + 1 [builtins fixtures/isinstancelist.pyi] From a260b791445c2e421c731fa31c5c61ff6d25d077 Mon Sep 17 00:00:00 2001 From: Max Moroz Date: Thu, 13 Apr 2017 07:44:34 -0700 Subject: [PATCH 10/13] Rename type into typ --- mypy/checker.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 2c9b51eb95e2..102d4582388c 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2830,18 +2830,18 @@ def flatten_types(t: Type) -> List[Type]: def get_isinstance_type(expr: Expression, type_map: Dict[Expression, Type]) -> List[TypeRange]: all_types = flatten_types(type_map[expr]) types = [] # type: List[TypeRange] - for type in all_types: - if isinstance(type, FunctionLike) and type.is_type_obj(): + for typ in all_types: + if isinstance(typ, FunctionLike) and typ.is_type_obj(): # Type variables may be present -- erase them, which is the best # we can do (outside disallowing them here). - type = erase_typevars(type.items()[0].ret_type) - types.append(TypeRange(type, is_upper_bound=False)) - elif isinstance(type, TypeType): + typ = erase_typevars(typ.items()[0].ret_type) + types.append(TypeRange(typ, is_upper_bound=False)) + elif isinstance(typ, TypeType): # Type[A] means "any type that is a subtype of A" rather than "precisely type A" # we indicate this by setting is_upper_bound flag - types.append(TypeRange(type.item, is_upper_bound=True)) - elif isinstance(type, Instance) and type.type.fullname() == 'builtins.type': - object_type = Instance(type.type.mro[-1], []) + types.append(TypeRange(typ.item, is_upper_bound=True)) + elif isinstance(typ, Instance) and typ.type.fullname() == 'builtins.type': + object_type = Instance(typ.type.mro[-1], []) types.append(TypeRange(object_type, is_upper_bound=True)) else: # we didn't see an actual type, but rather a variable whose value is unknown to us return None @@ -2992,17 +2992,17 @@ def is_more_precise_signature(t: CallableType, s: CallableType) -> bool: return is_more_precise(t.ret_type, s.ret_type) -def infer_operator_assignment_method(type: Type, operator: str) -> Tuple[bool, str]: +def infer_operator_assignment_method(typ: Type, operator: str) -> Tuple[bool, str]: """Determine if operator assignment on given value type is in-place, and the method name. For example, if operator is '+', return (True, '__iadd__') or (False, '__add__') depending on which method is supported by the type. """ method = nodes.op_methods[operator] - if isinstance(type, Instance): + if isinstance(typ, Instance): if operator in nodes.ops_with_inplace_method: inplace_method = '__i' + method[2:] - if type.type.has_readable_member(inplace_method): + if typ.type.has_readable_member(inplace_method): return True, inplace_method return False, method From e48d983ddaf24fbce7a0d40bf4c7dd7716767dc6 Mon Sep 17 00:00:00 2001 From: Max Moroz Date: Thu, 13 Apr 2017 17:18:17 -0700 Subject: [PATCH 11/13] Fix unreachable block crash --- mypy/checker.py | 2 ++ test-data/unit/check-isinstance.test | 39 +++++++++++++++++----------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 102d4582388c..488f3185e897 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2677,6 +2677,8 @@ def or_conditional_maps(m1: TypeMap, m2: TypeMap) -> TypeMap: def convert_to_typetype(type_map: TypeMap) -> TypeMap: converted_type_map = {} # type: TypeMap + if type_map is None: + return None for expr, typ in type_map.items(): if isinstance(typ, UnionType): converted_type_map[expr] = UnionType([TypeType(t) for t in typ.items]) diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index dbf8c68c374b..d41833745569 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -1423,31 +1423,40 @@ def f(x: Union[int, A], a: Type[A]) -> None: [builtins fixtures/isinstancelist.pyi] +[case testIssubclassUnreachable] +from typing import Type, Sequence, Union +x: Type[str] +if issubclass(x, int): + reveal_type(x) # unreachable block + + +class X: pass +class Y(X): pass +class Z(X): pass + +a: Union[Type[Y], Type[Z]] +if issubclass(a, X): + reveal_type(a) # E: Revealed type is 'Union[Type[__main__.Y], Type[__main__.Z]]' +else: + reveal_type(a) # unreachable block + +[builtins fixtures/isinstancelist.pyi] + + [case testIssubclass] -from typing import Type, ClassVar +from typing import Type class Goblin: - level: int + pass class GoblinAmbusher(Goblin): - job: ClassVar[str] = 'Ranger' + pass def test_issubclass(cls: Type[Goblin]) -> None: if issubclass(cls, GoblinAmbusher): reveal_type(cls) # E: Revealed type is 'Type[__main__.GoblinAmbusher]' - cls.level - cls.job - ga = cls() - ga.level = 15 - ga.job - ga.job = "Warrior" # E: Cannot assign to class variable "job" via instance else: reveal_type(cls) # E: Revealed type is 'Type[__main__.Goblin]' - cls.level - cls.job # E: Type[Goblin] has no attribute "job" - g = cls() - g.level = 15 - g.job # E: "Goblin" has no attribute "job" [builtins fixtures/isinstancelist.pyi] @@ -1482,7 +1491,7 @@ def test_issubclass(cls: Type[Mob]) -> None: [case testIssubclassTuple] -from typing import Type, ClassVar +from typing import Type class Mob: pass From b0ce6838199549955f5decd8bc932e96e683339c Mon Sep 17 00:00:00 2001 From: Max Moroz Date: Wed, 19 Apr 2017 12:38:24 -0700 Subject: [PATCH 12/13] Add back integration tests --- test-data/unit/check-isinstance.test | 88 ++++++++++++++++++++++++---- 1 file changed, 78 insertions(+), 10 deletions(-) diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index d41833745569..541c2eb1bf71 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -1444,78 +1444,146 @@ else: [case testIssubclass] -from typing import Type +from typing import Type, ClassVar class Goblin: - pass + level: int class GoblinAmbusher(Goblin): - pass + job: ClassVar[str] = 'Ranger' def test_issubclass(cls: Type[Goblin]) -> None: if issubclass(cls, GoblinAmbusher): reveal_type(cls) # E: Revealed type is 'Type[__main__.GoblinAmbusher]' + cls.level + cls.job + ga = cls() + ga.level = 15 + ga.job + ga.job = "Warrior" # E: Cannot assign to class variable "job" via instance else: reveal_type(cls) # E: Revealed type is 'Type[__main__.Goblin]' + cls.level + cls.job # E: Type[Goblin] has no attribute "job" + g = cls() + g.level = 15 + g.job # E: "Goblin" has no attribute "job" [builtins fixtures/isinstancelist.pyi] [case testIssubclassDeepHierarchy] -from typing import Type +from typing import Type, ClassVar class Mob: pass class Goblin(Mob): - pass + level: int class GoblinAmbusher(Goblin): - pass + job: ClassVar[str] = 'Ranger' def test_issubclass(cls: Type[Mob]) -> None: if issubclass(cls, Goblin): reveal_type(cls) # E: Revealed type is 'Type[__main__.Goblin]' + cls.level + cls.job # E: Type[Goblin] has no attribute "job" + g = cls() + g.level = 15 + g.job # E: "Goblin" has no attribute "job" if issubclass(cls, GoblinAmbusher): reveal_type(cls) # E: Revealed type is 'Type[__main__.GoblinAmbusher]' + cls.level + cls.job + g = cls() + g.level = 15 + g.job + g.job = 'Warrior' # E: Cannot assign to class variable "job" via instance else: reveal_type(cls) # E: Revealed type is 'Type[__main__.Mob]' + cls.job # E: Type[Mob] has no attribute "job" + cls.level # E: Type[Mob] has no attribute "level" + m = cls() + m.level = 15 # E: "Mob" has no attribute "level" + m.job # E: "Mob" has no attribute "job" if issubclass(cls, GoblinAmbusher): reveal_type(cls) # E: Revealed type is 'Type[__main__.GoblinAmbusher]' + cls.job + cls.level + ga = cls() + ga.level = 15 + ga.job + ga.job = 'Warrior' # E: Cannot assign to class variable "job" via instance if issubclass(cls, GoblinAmbusher): reveal_type(cls) # E: Revealed type is 'Type[__main__.GoblinAmbusher]' + cls.level + cls.job + ga = cls() + ga.level = 15 + ga.job + ga.job = "Warrior" # E: Cannot assign to class variable "job" via instance [builtins fixtures/isinstancelist.pyi] [case testIssubclassTuple] -from typing import Type +from typing import Type, ClassVar class Mob: pass class Goblin(Mob): - pass + level: int class GoblinAmbusher(Goblin): - pass + job: ClassVar[str] = 'Ranger' class GoblinDigger(Goblin): - pass + job: ClassVar[str] = 'Thief' def test_issubclass(cls: Type[Mob]) -> None: if issubclass(cls, (Goblin, GoblinAmbusher)): reveal_type(cls) # E: Revealed type is 'Type[__main__.Goblin]' + cls.level + cls.job # E: Type[Goblin] has no attribute "job" + g = cls() + g.level = 15 + g.job # E: "Goblin" has no attribute "job" if issubclass(cls, GoblinAmbusher): + cls.level reveal_type(cls) # E: Revealed type is 'Type[__main__.GoblinAmbusher]' + cls.job + ga = cls() + ga.level = 15 + ga.job + ga.job = "Warrior" # E: Cannot assign to class variable "job" via instance else: reveal_type(cls) # E: Revealed type is 'Type[__main__.Mob]' + cls.job # E: Type[Mob] has no attribute "job" + cls.level # E: Type[Mob] has no attribute "level" + m = cls() + m.level = 15 # E: "Mob" has no attribute "level" + m.job # E: "Mob" has no attribute "job" if issubclass(cls, GoblinAmbusher): reveal_type(cls) # E: Revealed type is 'Type[__main__.GoblinAmbusher]' + cls.job + cls.level + ga = cls() + ga.level = 15 + ga.job + ga.job = "Warrior" # E: Cannot assign to class variable "job" via instance + if issubclass(cls, (GoblinDigger, GoblinAmbusher)): reveal_type(cls) # E: Revealed type is 'Union[Type[__main__.GoblinDigger], Type[__main__.GoblinAmbusher]]' + cls.level + cls.job + g = cls() + g.level = 15 + g.job + g.job = "Warrior" # E: Cannot assign to class variable "job" via instance [builtins fixtures/isinstancelist.pyi] From aca05c7d4e271686f11335a9157a8db18e525587 Mon Sep 17 00:00:00 2001 From: Max Moroz Date: Thu, 20 Apr 2017 00:53:18 -0700 Subject: [PATCH 13/13] Add union destructuring test --- test-data/unit/check-isinstance.test | 22 ++++++++++++++++++++++ typeshed | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index 541c2eb1bf71..f3f4ea429f56 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -1443,6 +1443,28 @@ else: [builtins fixtures/isinstancelist.pyi] +[case testIssubclasDestructuringUnions] +from typing import Union, List, Tuple, Dict, Type +def f(x: Union[Type[int], Type[str], Type[List]]) -> None: + if issubclass(x, (str, (int,))): + reveal_type(x) # E: Revealed type is 'Union[Type[builtins.int], Type[builtins.str]]' + reveal_type(x()) # E: Revealed type is 'Union[builtins.int, builtins.str]' + x()[1] # E: Value of type "Union[int, str]" is not indexable + else: + reveal_type(x) # E: Revealed type is 'Type[builtins.list]' + reveal_type(x()) # E: Revealed type is 'builtins.list[]' + x()[1] + reveal_type(x) # E: Revealed type is 'Union[Type[builtins.int], Type[builtins.str], Type[builtins.list]]' + reveal_type(x()) # E: Revealed type is 'Union[builtins.int, builtins.str, builtins.list[]]' + if issubclass(x, (str, (list,))): + reveal_type(x) # E: Revealed type is 'Union[Type[builtins.str], Type[builtins.list[Any]]]' + reveal_type(x()) # E: Revealed type is 'Union[builtins.str, builtins.list[]]' + x()[1] + reveal_type(x) # E: Revealed type is 'Union[Type[builtins.int], Type[builtins.str], Type[builtins.list[Any]]]' + reveal_type(x()) # E: Revealed type is 'Union[builtins.int, builtins.str, builtins.list[]]' +[builtins fixtures/isinstancelist.pyi] + + [case testIssubclass] from typing import Type, ClassVar diff --git a/typeshed b/typeshed index 26360e821b42..8b835f95001b 160000 --- a/typeshed +++ b/typeshed @@ -1 +1 @@ -Subproject commit 26360e821b427c29ca27117061a3ba806f63e12e +Subproject commit 8b835f95001b61734c6b147d3aa6eb4fbe7bce03