From 3dcb7523c06c9810e03a04083bf0b4726a22a286 Mon Sep 17 00:00:00 2001 From: David Fisher Date: Wed, 4 May 2016 16:22:58 -0700 Subject: [PATCH 01/27] Basic strict Optional checking --- mypy/checker.py | 23 ++++++++++++++------- mypy/erasetype.py | 4 +++- mypy/experimental.py | 1 + mypy/main.py | 6 ++++++ mypy/meet.py | 11 +++++----- mypy/messages.py | 2 +- mypy/parse.py | 12 +++++++++++ mypy/subtypes.py | 13 ++++++++++-- mypy/test/data/check-optional.test | 4 ++++ mypy/test/testcheck.py | 1 + mypy/typeanal.py | 18 +++++++++++++--- mypy/types.py | 33 ++++++++++++++++++++++++++---- typeshed | 2 +- 13 files changed, 106 insertions(+), 24 deletions(-) create mode 100644 mypy/experimental.py create mode 100644 mypy/test/data/check-optional.test diff --git a/mypy/checker.py b/mypy/checker.py index 5d872ec5cf8b..e5c5a94bcb6c 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -50,6 +50,7 @@ from mypy.join import join_simple, join_types from mypy.treetransform import TransformVisitor from mypy.meet import meet_simple, nearest_builtin_ancestor, is_overlapping_types +from mypy import experimental T = TypeVar('T') @@ -376,10 +377,12 @@ class TypeChecker(NodeVisitor[Type]): # Should we check untyped function defs? check_untyped_defs = False - def __init__(self, errors: Errors, modules: Dict[str, MypyFile], - pyversion: Tuple[int, int] = defaults.PYTHON3_VERSION, - disallow_untyped_calls=False, disallow_untyped_defs=False, - check_untyped_defs=False) -> None: + def __init__(self, errors: Errors, + modules: Dict[str, MypyFile], + pyversion: Tuple[int, int], + disallow_untyped_calls: bool, + disallow_untyped_defs: bool, + check_untyped_defs: bool) -> None: """Construct a type checker. Use errors to report type check errors. @@ -547,7 +550,10 @@ def get_generator_receive_type(self, return_type: Type) -> Type: else: # `return_type` is a supertype of Generator, so callers won't be able to send it # values. - return Void() + if experimental.STRICT_OPTIONAL: + return NoneTyp() + else: + return Void() def get_generator_return_type(self, return_type: Type) -> Type: if isinstance(return_type, AnyType): @@ -661,7 +667,7 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: str) -> None: if fdef: # Check if __init__ has an invalid, non-None return type. if (fdef.info and fdef.name() == '__init__' and - not isinstance(typ.ret_type, Void) and + not isinstance(typ.ret_type, (Void, NoneTyp)) and not self.dynamic_funcs[-1]): self.fail(messages.INIT_MUST_HAVE_NONE_RETURN_TYPE, item.type) @@ -2018,7 +2024,10 @@ def visit_yield_from_expr(self, e: YieldFromExpr) -> Type: return self.get_generator_return_type(iter_type) else: # Non-Generators don't return anything from `yield from` expressions. - return Void() + if experimental.STRICT_OPTIONAL: + return NoneTyp() + else: + return Void() def visit_member_expr(self, e: MemberExpr) -> Type: return self.expr_checker.visit_member_expr(e) diff --git a/mypy/erasetype.py b/mypy/erasetype.py index 15ccd4f9bb7e..7f49a34a1d88 100644 --- a/mypy/erasetype.py +++ b/mypy/erasetype.py @@ -3,6 +3,7 @@ Instance, TypeVarType, CallableType, TupleType, UnionType, Overloaded, ErasedType, PartialType, DeletedType, TypeTranslator, TypeList ) +from mypy import experimental def erase_type(typ: Type) -> Type: @@ -59,7 +60,8 @@ def visit_type_var(self, t: TypeVarType) -> Type: def visit_callable_type(self, t: CallableType) -> Type: # We must preserve the fallback type for overload resolution to work. - return CallableType([], [], [], Void(), t.fallback) + none_type = NoneTyp() if experimental.STRICT_OPTIONAL else Void() + return CallableType([], [], [], none_type, t.fallback) def visit_overloaded(self, t: Overloaded) -> Type: return t.items()[0].accept(self) diff --git a/mypy/experimental.py b/mypy/experimental.py new file mode 100644 index 000000000000..a4684cc55ad1 --- /dev/null +++ b/mypy/experimental.py @@ -0,0 +1 @@ +STRICT_OPTIONAL = False diff --git a/mypy/main.py b/mypy/main.py index 7579f6260235..81d513b10b17 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -10,11 +10,13 @@ from mypy import build from mypy import defaults from mypy import git +from mypy import experimental from mypy.build import BuildSource, BuildResult, PYTHON_EXTENSIONS from mypy.errors import CompileError, set_drop_into_pdb from mypy.version import __version__ + PY_EXTENSIONS = tuple(PYTHON_EXTENSIONS) @@ -153,6 +155,8 @@ def parse_version(v): help="enable experimental fast parser") parser.add_argument('-i', '--incremental', action='store_true', help="enable experimental module cache") + parser.add_argument('--strict-optional', action='store_true', + help="enable experimental strict Optional checks") parser.add_argument('-f', '--dirty-stubs', action='store_true', help="don't warn if typeshed is out of sync") parser.add_argument('--pdb', action='store_true', help="invoke pdb on fatal error") @@ -248,6 +252,8 @@ def parse_version(v): options.build_flags.append(build.FAST_PARSER) if args.incremental: options.build_flags.append(build.INCREMENTAL) + if args.strict_optional: + experimental.STRICT_OPTIONAL = True # Set reports. for flag, val in vars(args).items(): diff --git a/mypy/meet.py b/mypy/meet.py index cbffed5b24a9..464caf13b6c5 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -3,7 +3,8 @@ from mypy.join import is_similar_callables, combine_similar_callables from mypy.types import ( Type, AnyType, TypeVisitor, UnboundType, Void, ErrorType, NoneTyp, TypeVarType, - Instance, CallableType, TupleType, ErasedType, TypeList, UnionType, PartialType, DeletedType + Instance, CallableType, TupleType, ErasedType, TypeList, UnionType, PartialType, DeletedType, + UninhabitedType ) from mypy.subtypes import is_subtype from mypy.nodes import TypeInfo @@ -28,7 +29,7 @@ def meet_simple(s: Type, t: Type, default_right: bool = True) -> Type: if isinstance(s, UnionType): return UnionType.make_simplified_union([meet_types(x, t) for x in s.items]) elif not is_overlapping_types(s, t, use_promotions=True): - return NoneTyp() + return UninhabitedType() else: if default_right: return t @@ -175,7 +176,7 @@ def visit_instance(self, t: Instance) -> Type: args.append(self.meet(t.args[i], si.args[i])) return Instance(t.type, args) else: - return NoneTyp() + return UninhabitedType() else: if is_subtype(t, self.s): return t @@ -183,7 +184,7 @@ def visit_instance(self, t: Instance) -> Type: # See also above comment. return self.s else: - return NoneTyp() + return UninhabitedType() else: return self.default(self.s) @@ -216,4 +217,4 @@ def default(self, typ): elif isinstance(typ, Void) or isinstance(typ, ErrorType): return ErrorType() else: - return NoneTyp() + return UninhabitedType() diff --git a/mypy/messages.py b/mypy/messages.py index 90be97fc2e8d..958abc5e5da1 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -258,7 +258,7 @@ def format_simple(self, typ: Type, verbosity: int = 0) -> str: elif isinstance(typ, Void): return 'None' elif isinstance(typ, NoneTyp): - return 'None' + return 'builtins.None' elif isinstance(typ, AnyType): return '"Any"' elif isinstance(typ, DeletedType): diff --git a/mypy/parse.py b/mypy/parse.py index 4cd3389dd5bb..ea45379c6515 100644 --- a/mypy/parse.py +++ b/mypy/parse.py @@ -37,6 +37,8 @@ parse_type, parse_types, parse_signature, TypeParseError, parse_str_as_signature ) +from mypy import experimental + precedence = { '**': 16, @@ -782,8 +784,18 @@ def parse_normal_arg(self, require_named: bool, else: kind = nodes.ARG_POS + self.set_type_optional(type, initializer) + return Argument(variable, type, initializer, kind), require_named + def set_type_optional(self, type: Type, initializer: Node) -> None: + if not experimental.STRICT_OPTIONAL: + return + # Indicate that type should be wrapped in an Optional if arg is initialized to None. + optional = isinstance(initializer, NameExpr) and initializer.name == 'None' + if isinstance(type, UnboundType): + type.optional = optional + def parse_parameter_annotation(self) -> Node: if self.current_str() == ':': self.skip() diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 46db522173d9..879b7e1e121c 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -3,7 +3,7 @@ from mypy.types import ( Type, AnyType, UnboundType, TypeVisitor, ErrorType, Void, NoneTyp, Instance, TypeVarType, CallableType, TupleType, UnionType, Overloaded, ErasedType, TypeList, - PartialType, DeletedType, is_named_instance + PartialType, DeletedType, is_named_instance, UninhabitedType ) import mypy.applytype import mypy.constraints @@ -13,6 +13,8 @@ from mypy.nodes import CONTRAVARIANT, COVARIANT from mypy.maptype import map_instance_to_supertype +from mypy import experimental + TypeParameterChecker = Callable[[Type, Type, int], bool] @@ -98,9 +100,16 @@ def visit_any(self, left: AnyType) -> bool: def visit_void(self, left: Void) -> bool: return isinstance(self.right, Void) - def visit_none_type(self, left: NoneTyp) -> bool: + def visit_uninhabited_type(self, left: UninhabitedType) -> bool: return not isinstance(self.right, Void) + def visit_none_type(self, left: NoneTyp) -> bool: + if experimental.STRICT_OPTIONAL: + # TODO(ddfisher): what about Unions? + return isinstance(self.right, NoneTyp) + else: + return not isinstance(self.right, Void) + def visit_erased_type(self, left: ErasedType) -> bool: return True diff --git a/mypy/test/data/check-optional.test b/mypy/test/data/check-optional.test new file mode 100644 index 000000000000..e35233af8241 --- /dev/null +++ b/mypy/test/data/check-optional.test @@ -0,0 +1,4 @@ +-- Tests for strict Optional behavior + +[case testNoneMemberOfOptional] +1 + "str" diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index 16f60648a41a..e98738f56602 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -57,6 +57,7 @@ 'check-flags.test', 'check-incremental.test', 'check-bound.test', + 'check-optional.test', ] diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 345adbba8013..620fbe1091f4 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -16,6 +16,7 @@ from mypy.exprtotype import expr_to_unanalyzed_type, TypeTranslationError from mypy.subtypes import satisfies_upper_bound from mypy import nodes +from mypy import experimental type_constructors = ['typing.Tuple', 'typing.Union', 'typing.Callable'] @@ -73,6 +74,11 @@ def __init__(self, self.fail = fail_func def visit_unbound_type(self, t: UnboundType) -> Type: + if t.optional: + t.optional = False + # We don't need to worry about double-wrapping Optionals or + # wrapping Anys: Union simplification will take care of that. + return UnionType.make_simplified_union([self.visit_unbound_type(t), NoneTyp()]) sym = self.lookup(t.name, t) if sym is not None: if sym.node is None: @@ -91,7 +97,10 @@ def visit_unbound_type(self, t: UnboundType) -> Type: tvar_expr.variance, t.line) elif fullname == 'builtins.None': - return Void() + if experimental.STRICT_OPTIONAL: + return NoneTyp() + else: + return Void() elif fullname == 'typing.Any': return AnyType() elif fullname == 'typing.Tuple': @@ -110,8 +119,11 @@ def visit_unbound_type(self, t: UnboundType) -> Type: if len(t.args) != 1: self.fail('Optional[...] must have exactly one type argument', t) items = self.anal_array(t.args) - # Currently Optional[t] is just an alias for t. - return items[0] + if experimental.STRICT_OPTIONAL: + return UnionType.make_simplified_union([items[0], NoneTyp()]) + else: + # Without strict Optional checking Optional[t] is just an alias for t. + return items[0] elif fullname == 'typing.Callable': return self.analyze_callable_type(t) elif sym.kind == TYPE_ALIAS: diff --git a/mypy/types.py b/mypy/types.py index 1d2c231abc6e..8db315bb9225 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -99,12 +99,19 @@ class UnboundType(Type): name = '' args = None # type: List[Type] + # should this type be wrapped in an Optional? + optional = False - def __init__(self, name: str, args: List[Type] = None, line: int = -1) -> None: + def __init__(self, + name: str, + args: List[Type] = None, + line: int = -1, + optional: bool = False) -> None: if not args: args = [] self.name = name self.args = args + self.optional = optional super().__init__(line) def accept(self, visitor: 'TypeVisitor[T]') -> T: @@ -204,6 +211,24 @@ def deserialize(cls, data: JsonDict) -> 'Void': assert data['.class'] == 'Void' return Void() +class UninhabitedType(Type): + """This type has no members. + """ + + def __init__(self, line: int = -1) -> None: + super().__init__(line) + + def accept(self, visitor: 'TypeVisitor[T]') -> T: + return visitor.visit_uninhabited_type(self) + + def serialize(self) -> JsonDict: + return {'.class': 'UninhabitedType'} + + @classmethod + def deserialize(cls, data: JsonDict) -> 'UninhabitedType': + assert data['.class'] == 'UninhabitedType' + return UninhabitedType() + class NoneTyp(Type): """The type of 'None'. @@ -683,7 +708,7 @@ def make_union(items: List[Type], line: int = -1) -> Type: elif len(items) == 1: return items[0] else: - return Void() + return UninhabitedType() @staticmethod def make_simplified_union(items: List[Type], line: int = -1) -> Type: @@ -971,8 +996,8 @@ def visit_void(self, t): return 'void' def visit_none_type(self, t): - # Include quotes to make this distinct from the None value. - return "'None'" + # Fully qualify to make this distinct from the None value. + return "builtins.None" def visit_erased_type(self, t): return "" diff --git a/typeshed b/typeshed index 292447bd6270..c21d8a41d58d 160000 --- a/typeshed +++ b/typeshed @@ -1 +1 @@ -Subproject commit 292447bd627041ec055b633cac1f5895790b73f1 +Subproject commit c21d8a41d58d3899f5042b76704ee5b183cf59b0 From 30f3a0fbd9d97403227f1b962f1ac22e026b213b Mon Sep 17 00:00:00 2001 From: David Fisher Date: Thu, 12 May 2016 14:16:16 -0700 Subject: [PATCH 02/27] WIP --- mypy/constraints.py | 5 +++- mypy/erasetype.py | 5 +++- mypy/expandtype.py | 5 +++- mypy/fixup.py | 6 ++++- mypy/join.py | 9 ++++++- mypy/meet.py | 7 +++++ mypy/messages.py | 2 +- mypy/sametypes.py | 6 ++++- mypy/subtypes.py | 7 ++--- mypy/test/data/check-tuples.test | 4 +-- mypy/test/testtypes.py | 46 ++++++++++++++++---------------- mypy/typeanal.py | 8 +++++- mypy/typefixture.py | 4 ++- mypy/types.py | 13 +++++++++ 14 files changed, 90 insertions(+), 37 deletions(-) diff --git a/mypy/constraints.py b/mypy/constraints.py index 4ea64f43bb65..68d12ecae872 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -5,7 +5,7 @@ from mypy.types import ( CallableType, Type, TypeVisitor, UnboundType, AnyType, Void, NoneTyp, TypeVarType, Instance, TupleType, UnionType, Overloaded, ErasedType, PartialType, DeletedType, - is_named_instance + is_named_instance, UninhabitedType ) from mypy.maptype import map_instance_to_supertype from mypy import nodes @@ -222,6 +222,9 @@ def visit_void(self, template: Void) -> List[Constraint]: def visit_none_type(self, template: NoneTyp) -> List[Constraint]: return [] + def visit_uninhabited_type(self, template: UninhabitedType) -> List[Constraint]: + return [] + def visit_erased_type(self, template: ErasedType) -> List[Constraint]: return [] diff --git a/mypy/erasetype.py b/mypy/erasetype.py index 7f49a34a1d88..8056e2e01b07 100644 --- a/mypy/erasetype.py +++ b/mypy/erasetype.py @@ -1,7 +1,7 @@ from mypy.types import ( Type, TypeVisitor, UnboundType, ErrorType, AnyType, Void, NoneTyp, Instance, TypeVarType, CallableType, TupleType, UnionType, Overloaded, ErasedType, - PartialType, DeletedType, TypeTranslator, TypeList + PartialType, DeletedType, TypeTranslator, TypeList, UninhabitedType ) from mypy import experimental @@ -41,6 +41,9 @@ def visit_void(self, t: Void) -> Type: def visit_none_type(self, t: NoneTyp) -> Type: return t + def visit_uninhabited_type(self, t: UninhabitedType) -> Type: + return t + def visit_erased_type(self, t: ErasedType) -> Type: # Should not get here. raise RuntimeError() diff --git a/mypy/expandtype.py b/mypy/expandtype.py index 60730b563bd6..45b84fe407a7 100644 --- a/mypy/expandtype.py +++ b/mypy/expandtype.py @@ -3,7 +3,7 @@ from mypy.types import ( Type, Instance, CallableType, TypeVisitor, UnboundType, ErrorType, AnyType, Void, NoneTyp, TypeVarType, Overloaded, TupleType, UnionType, ErasedType, TypeList, - PartialType, DeletedType + PartialType, DeletedType, UninhabitedType ) @@ -53,6 +53,9 @@ def visit_void(self, t: Void) -> Type: def visit_none_type(self, t: NoneTyp) -> Type: return t + def visit_uninhabited_type(self, t: UninhabitedType) -> Type: + return t + def visit_deleted_type(self, t: DeletedType) -> Type: return t diff --git a/mypy/fixup.py b/mypy/fixup.py index 134e611fe85c..3a1c07a6b8fe 100644 --- a/mypy/fixup.py +++ b/mypy/fixup.py @@ -7,7 +7,8 @@ TypeVarExpr, ClassDef, LDEF, MDEF, GDEF, MODULE_REF) from mypy.types import (CallableType, EllipsisType, Instance, Overloaded, TupleType, - TypeList, TypeVarType, UnboundType, UnionType, TypeVisitor) + TypeList, TypeVarType, UnboundType, UnionType, TypeVisitor, + UninhabitedType) from mypy.visitor import NodeVisitor @@ -180,6 +181,9 @@ def visit_deleted_type(self, o: Any) -> None: def visit_none_type(self, o: Any) -> None: pass # Nothing to descend into. + def visit_uninhabited_type(self, o: Any) -> None: + pass # Nothing to descend into. + def visit_partial_type(self, o: Any) -> None: raise RuntimeError("Shouldn't get here", o) diff --git a/mypy/join.py b/mypy/join.py index 2ae03602120b..0ec4e7880d07 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -5,7 +5,7 @@ from mypy.types import ( Type, AnyType, NoneTyp, Void, TypeVisitor, Instance, UnboundType, ErrorType, TypeVarType, CallableType, TupleType, ErasedType, TypeList, - UnionType, FunctionLike, Overloaded, PartialType, DeletedType + UnionType, FunctionLike, Overloaded, PartialType, DeletedType, UninhabitedType ) from mypy.maptype import map_instance_to_supertype from mypy.subtypes import is_subtype, is_equivalent, is_subtype_ignoring_tvars @@ -105,6 +105,13 @@ def visit_void(self, t: Void) -> Type: return ErrorType() def visit_none_type(self, t: NoneTyp) -> Type: + # TODO(ddfisher): should behave differently with strict optional + if not isinstance(self.s, Void): + return self.s + else: + return self.default(self.s) + + def visit_uninhabited_type(self, t: UninhabitedType) -> bool: if not isinstance(self.s, Void): return self.s else: diff --git a/mypy/meet.py b/mypy/meet.py index 464caf13b6c5..4c2089e4f5c7 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -141,6 +141,13 @@ def visit_void(self, t: Void) -> Type: return ErrorType() def visit_none_type(self, t: NoneTyp) -> Type: + # TODO(ddfisher): should behave different with strict optional + if not isinstance(self.s, Void) and not isinstance(self.s, ErrorType): + return t + else: + return ErrorType() + + def visit_uninhabited_type(self, t: UninhabitedType) -> bool: if not isinstance(self.s, Void) and not isinstance(self.s, ErrorType): return t else: diff --git a/mypy/messages.py b/mypy/messages.py index 958abc5e5da1..90be97fc2e8d 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -258,7 +258,7 @@ def format_simple(self, typ: Type, verbosity: int = 0) -> str: elif isinstance(typ, Void): return 'None' elif isinstance(typ, NoneTyp): - return 'builtins.None' + return 'None' elif isinstance(typ, AnyType): return '"Any"' elif isinstance(typ, DeletedType): diff --git a/mypy/sametypes.py b/mypy/sametypes.py index bfe37a6a1394..87b81dac508a 100644 --- a/mypy/sametypes.py +++ b/mypy/sametypes.py @@ -2,7 +2,8 @@ from mypy.types import ( Type, UnboundType, ErrorType, AnyType, NoneTyp, Void, TupleType, UnionType, CallableType, - TypeVarType, Instance, TypeVisitor, ErasedType, TypeList, Overloaded, PartialType, DeletedType + TypeVarType, Instance, TypeVisitor, ErasedType, TypeList, Overloaded, PartialType, DeletedType, + UninhabitedType ) @@ -69,6 +70,9 @@ def visit_void(self, left: Void) -> bool: def visit_none_type(self, left: NoneTyp) -> bool: return isinstance(self.right, NoneTyp) + def visit_uninhabited_type(self, t: UninhabitedType) -> bool: + return isinstance(self.right, UninhabitedType) + def visit_erased_type(self, left: ErasedType) -> bool: # Should not get here. raise RuntimeError() diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 879b7e1e121c..562229575767 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -100,16 +100,17 @@ def visit_any(self, left: AnyType) -> bool: def visit_void(self, left: Void) -> bool: return isinstance(self.right, Void) - def visit_uninhabited_type(self, left: UninhabitedType) -> bool: - return not isinstance(self.right, Void) - def visit_none_type(self, left: NoneTyp) -> bool: if experimental.STRICT_OPTIONAL: # TODO(ddfisher): what about Unions? + print("OMG") return isinstance(self.right, NoneTyp) else: return not isinstance(self.right, Void) + def visit_uninhabited_type(self, left: UninhabitedType) -> bool: + return not isinstance(self.right, Void) + def visit_erased_type(self, left: ErasedType) -> bool: return True diff --git a/mypy/test/data/check-tuples.test b/mypy/test/data/check-tuples.test index e57d6532186d..2c32b0faa6fb 100644 --- a/mypy/test/data/check-tuples.test +++ b/mypy/test/data/check-tuples.test @@ -287,7 +287,7 @@ a, b = None, None # type: (A, B) a1, b1 = a, a # type: (A, B) # E: Incompatible types in assignment (expression has type "A", variable has type "B") a2, b2 = b, b # type: (A, B) # E: Incompatible types in assignment (expression has type "B", variable has type "A") a3, b3 = a # type: (A, B) # E: '__main__.A' object is not iterable -a4, b4 = None # type: (A, B) # E: ''None'' object is not iterable +a4, b4 = None # type: (A, B) # E: 'builtins.None' object is not iterable a5, b5 = a, b, a # type: (A, B) # E: Too many values to unpack (2 expected, 3 provided) ax, bx = a, b # type: (A, B) @@ -301,7 +301,7 @@ class B: pass a, b = None, None # type: (A, B) def f(): pass -a, b = None # E: ''None'' object is not iterable +a, b = None # E: 'builtins.None' object is not iterable a, b = a # E: '__main__.A' object is not iterable a, b = f # E: 'def () -> Any' object is not iterable diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index 754ed7271a71..08586eefb162 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -11,7 +11,7 @@ from mypy.meet import meet_types from mypy.types import ( UnboundType, AnyType, Void, CallableType, TupleType, TypeVarDef, Type, - Instance, NoneTyp, ErrorType, Overloaded + Instance, NoneTyp, ErrorType, Overloaded, UninhabitedType ) from mypy.nodes import ARG_POS, ARG_OPT, ARG_STAR, CONTRAVARIANT, INVARIANT, COVARIANT from mypy.replacetvars import replace_type_vars @@ -103,7 +103,7 @@ def set_up(self): # expand_type def test_trivial_expand(self): - for t in (self.fx.a, self.fx.o, self.fx.t, self.fx.void, self.fx.nonet, + for t in (self.fx.a, self.fx.o, self.fx.t, self.fx.void, self.fx.uninhab, self.tuple(self.fx.a), self.callable([], self.fx.a, self.fx.a), self.fx.anyt): self.assert_expand(t, [], t) @@ -135,7 +135,7 @@ def assert_expand(self, orig, map_items, result): # replace_type_vars def test_trivial_replace(self): - for t in (self.fx.a, self.fx.o, self.fx.void, self.fx.nonet, + for t in (self.fx.a, self.fx.o, self.fx.void, self.fx.uninhab, self.tuple(self.fx.a), self.callable([], self.fx.a, self.fx.a), self.fx.anyt, self.fx.err): @@ -154,7 +154,7 @@ def assert_replace(self, orig, result): # erase_type def test_trivial_erase(self): - for t in (self.fx.a, self.fx.o, self.fx.void, self.fx.nonet, + for t in (self.fx.a, self.fx.o, self.fx.void, self.fx.uninhab, self.fx.anyt, self.fx.err): self.assert_erase(t, t) @@ -545,8 +545,8 @@ def test_class_subtyping(self): self.assert_meet(self.fx.a, self.fx.o, self.fx.a) self.assert_meet(self.fx.a, self.fx.b, self.fx.b) self.assert_meet(self.fx.b, self.fx.o, self.fx.b) - self.assert_meet(self.fx.a, self.fx.d, NoneTyp()) - self.assert_meet(self.fx.b, self.fx.c, NoneTyp()) + self.assert_meet(self.fx.a, self.fx.d, UninhabitedType()) + self.assert_meet(self.fx.b, self.fx.c, UninhabitedType()) def test_tuples(self): self.assert_meet(self.tuple(), self.tuple(), self.tuple()) @@ -559,10 +559,10 @@ def test_tuples(self): self.assert_meet(self.tuple(self.fx.a, self.fx.a), self.fx.std_tuple, - NoneTyp()) + UninhabitedType()) self.assert_meet(self.tuple(self.fx.a), self.tuple(self.fx.a, self.fx.a), - NoneTyp()) + UninhabitedType()) def test_function_types(self): self.assert_meet(self.callable(self.fx.a, self.fx.b), @@ -571,15 +571,15 @@ def test_function_types(self): self.assert_meet(self.callable(self.fx.a, self.fx.b), self.callable(self.fx.b, self.fx.b), - NoneTyp()) + UninhabitedType()) self.assert_meet(self.callable(self.fx.a, self.fx.b), self.callable(self.fx.a, self.fx.a), - NoneTyp()) + UninhabitedType()) def test_type_vars(self): self.assert_meet(self.fx.t, self.fx.t, self.fx.t) self.assert_meet(self.fx.s, self.fx.s, self.fx.s) - self.assert_meet(self.fx.t, self.fx.s, NoneTyp()) + self.assert_meet(self.fx.t, self.fx.s, UninhabitedType()) def test_void(self): self.assert_meet(self.fx.void, self.fx.void, self.fx.void) @@ -638,29 +638,29 @@ def test_simple_generics(self): self.assert_meet(self.fx.ga, self.fx.ga, self.fx.ga) self.assert_meet(self.fx.ga, self.fx.o, self.fx.ga) self.assert_meet(self.fx.ga, self.fx.gb, self.fx.gb) - self.assert_meet(self.fx.ga, self.fx.gd, self.fx.nonet) - self.assert_meet(self.fx.ga, self.fx.g2a, self.fx.nonet) + self.assert_meet(self.fx.ga, self.fx.gd, self.fx.uninhab) + self.assert_meet(self.fx.ga, self.fx.g2a, self.fx.uninhab) self.assert_meet(self.fx.ga, self.fx.nonet, self.fx.nonet) self.assert_meet(self.fx.ga, self.fx.anyt, self.fx.ga) for t in [self.fx.a, self.fx.t, self.tuple(), self.callable(self.fx.a, self.fx.b)]: - self.assert_meet(t, self.fx.ga, self.fx.nonet) + self.assert_meet(t, self.fx.ga, self.fx.uninhab) def test_generics_with_multiple_args(self): self.assert_meet(self.fx.hab, self.fx.hab, self.fx.hab) self.assert_meet(self.fx.hab, self.fx.haa, self.fx.hab) - self.assert_meet(self.fx.hab, self.fx.had, self.fx.nonet) + self.assert_meet(self.fx.hab, self.fx.had, self.fx.uninhab) self.assert_meet(self.fx.hab, self.fx.hbb, self.fx.hbb) def test_generics_with_inheritance(self): self.assert_meet(self.fx.gsab, self.fx.gb, self.fx.gsab) - self.assert_meet(self.fx.gsba, self.fx.gb, self.fx.nonet) + self.assert_meet(self.fx.gsba, self.fx.gb, self.fx.uninhab) def test_generics_with_inheritance_and_shared_supertype(self): - self.assert_meet(self.fx.gsba, self.fx.gs2a, self.fx.nonet) - self.assert_meet(self.fx.gsab, self.fx.gs2a, self.fx.nonet) + self.assert_meet(self.fx.gsba, self.fx.gs2a, self.fx.uninhab) + self.assert_meet(self.fx.gsab, self.fx.gs2a, self.fx.uninhab) def test_generic_types_and_dynamic(self): self.assert_meet(self.fx.gdyn, self.fx.ga, self.fx.ga) @@ -675,20 +675,20 @@ def test_callables_with_dynamic(self): def test_meet_interface_types(self): self.assert_meet(self.fx.f, self.fx.f, self.fx.f) - self.assert_meet(self.fx.f, self.fx.f2, self.fx.nonet) + self.assert_meet(self.fx.f, self.fx.f2, self.fx.uninhab) self.assert_meet(self.fx.f, self.fx.f3, self.fx.f3) def test_join_interface_and_class_types(self): self.assert_meet(self.fx.o, self.fx.f, self.fx.f) - self.assert_meet(self.fx.a, self.fx.f, self.fx.nonet) + self.assert_meet(self.fx.a, self.fx.f, self.fx.uninhab) self.assert_meet(self.fx.e, self.fx.f, self.fx.e) def test_join_class_types_with_shared_interfaces(self): # These have nothing special with respect to meets, unlike joins. These # are for completeness only. - self.assert_meet(self.fx.e, self.fx.e2, self.fx.nonet) - self.assert_meet(self.fx.e2, self.fx.e3, self.fx.nonet) + self.assert_meet(self.fx.e, self.fx.e2, self.fx.uninhab) + self.assert_meet(self.fx.e2, self.fx.e3, self.fx.uninhab) def test_meet_with_generic_interfaces(self): # TODO fix @@ -697,7 +697,7 @@ def test_meet_with_generic_interfaces(self): fx = InterfaceTypeFixture() self.assert_meet(fx.gfa, fx.m1, fx.m1) self.assert_meet(fx.gfa, fx.gfa, fx.gfa) - self.assert_meet(fx.gfb, fx.m1, fx.nonet) + self.assert_meet(fx.gfb, fx.m1, fx.uninhab) # FIX generic interfaces + ranges diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 620fbe1091f4..e877a363ecfd 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -5,7 +5,7 @@ from mypy.types import ( Type, UnboundType, TypeVarType, TupleType, UnionType, Instance, AnyType, CallableType, Void, NoneTyp, DeletedType, TypeList, TypeVarDef, TypeVisitor, StarType, PartialType, - EllipsisType + EllipsisType, UninhabitedType ) from mypy.nodes import ( BOUND_TVAR, TYPE_ALIAS, UNBOUND_IMPORTED, @@ -176,6 +176,9 @@ def visit_void(self, t: Void) -> Type: def visit_none_type(self, t: NoneTyp) -> Type: return t + def visit_uninhabited_type(self, t: UninhabitedType) -> Type: + return t + def visit_deleted_type(self, t: DeletedType) -> Type: return t @@ -381,6 +384,9 @@ def visit_void(self, t: Void) -> None: def visit_none_type(self, t: NoneTyp) -> None: pass + def visit_uninhabited_type(self, t: UninhabitedType) -> None: + pass + def visit_deleted_type(self, t: DeletedType) -> None: pass diff --git a/mypy/typefixture.py b/mypy/typefixture.py index ffd1e84d90bd..0e68047c8ded 100644 --- a/mypy/typefixture.py +++ b/mypy/typefixture.py @@ -6,7 +6,8 @@ from typing import List from mypy.types import ( - TypeVarType, AnyType, Void, ErrorType, NoneTyp, Instance, CallableType, TypeVarDef + TypeVarType, AnyType, Void, ErrorType, NoneTyp, Instance, CallableType, + TypeVarDef, UninhabitedType ) from mypy.nodes import ( TypeInfo, ClassDef, Block, ARG_POS, ARG_OPT, ARG_STAR, SymbolTable, @@ -38,6 +39,7 @@ def __init__(self, variance: int=COVARIANT) -> None: self.void = Void() self.err = ErrorType() self.nonet = NoneTyp() + self.uninhab = UninhabitedType() # Abstract class TypeInfos diff --git a/mypy/types.py b/mypy/types.py index 8db315bb9225..b1dd0bed3372 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -847,6 +847,10 @@ def visit_void(self, t: Void) -> T: def visit_none_type(self, t: NoneTyp) -> T: pass + @abstractmethod + def visit_uninhabited_type(self, t: UninhabitedType) -> T: + pass + def visit_erased_type(self, t: ErasedType) -> T: raise self._notimplemented_helper() @@ -913,6 +917,9 @@ def visit_void(self, t: Void) -> Type: def visit_none_type(self, t: NoneTyp) -> Type: return t + def visit_uninhabited_type(self, t: UninhabitedType) -> Type: + return t + def visit_erased_type(self, t: ErasedType) -> Type: return t @@ -999,6 +1006,9 @@ def visit_none_type(self, t): # Fully qualify to make this distinct from the None value. return "builtins.None" + def visit_uninhabited_type(self, t): + return "" + def visit_erased_type(self, t): return "" @@ -1140,6 +1150,9 @@ def visit_any(self, t: AnyType) -> bool: def visit_void(self, t: Void) -> bool: return self.default + def visit_uninhabited_type(self, t: UninhabitedType) -> bool: + return self.default + def visit_none_type(self, t: NoneTyp) -> bool: return self.default From b5e843341a2207fa140cc85b0a267cf32a16910f Mon Sep 17 00:00:00 2001 From: David Fisher Date: Wed, 18 May 2016 19:20:51 -0700 Subject: [PATCH 03/27] cleanup --- mypy/checker.py | 23 +++++--------- mypy/constraints.py | 5 +--- mypy/erasetype.py | 9 ++---- mypy/expandtype.py | 5 +--- mypy/fixup.py | 6 +--- mypy/join.py | 34 ++++++++++----------- mypy/main.py | 1 - mypy/meet.py | 48 +++++++++++++++++++----------- mypy/sametypes.py | 6 +--- mypy/subtypes.py | 10 +++---- mypy/test/data/check-optional.test | 2 +- mypy/test/testtypes.py | 46 ++++++++++++++-------------- mypy/typeanal.py | 9 ++---- mypy/typefixture.py | 4 +-- mypy/types.py | 38 ++++------------------- 15 files changed, 96 insertions(+), 150 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index e5c5a94bcb6c..5d872ec5cf8b 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -50,7 +50,6 @@ from mypy.join import join_simple, join_types from mypy.treetransform import TransformVisitor from mypy.meet import meet_simple, nearest_builtin_ancestor, is_overlapping_types -from mypy import experimental T = TypeVar('T') @@ -377,12 +376,10 @@ class TypeChecker(NodeVisitor[Type]): # Should we check untyped function defs? check_untyped_defs = False - def __init__(self, errors: Errors, - modules: Dict[str, MypyFile], - pyversion: Tuple[int, int], - disallow_untyped_calls: bool, - disallow_untyped_defs: bool, - check_untyped_defs: bool) -> None: + def __init__(self, errors: Errors, modules: Dict[str, MypyFile], + pyversion: Tuple[int, int] = defaults.PYTHON3_VERSION, + disallow_untyped_calls=False, disallow_untyped_defs=False, + check_untyped_defs=False) -> None: """Construct a type checker. Use errors to report type check errors. @@ -550,10 +547,7 @@ def get_generator_receive_type(self, return_type: Type) -> Type: else: # `return_type` is a supertype of Generator, so callers won't be able to send it # values. - if experimental.STRICT_OPTIONAL: - return NoneTyp() - else: - return Void() + return Void() def get_generator_return_type(self, return_type: Type) -> Type: if isinstance(return_type, AnyType): @@ -667,7 +661,7 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: str) -> None: if fdef: # Check if __init__ has an invalid, non-None return type. if (fdef.info and fdef.name() == '__init__' and - not isinstance(typ.ret_type, (Void, NoneTyp)) and + not isinstance(typ.ret_type, Void) and not self.dynamic_funcs[-1]): self.fail(messages.INIT_MUST_HAVE_NONE_RETURN_TYPE, item.type) @@ -2024,10 +2018,7 @@ def visit_yield_from_expr(self, e: YieldFromExpr) -> Type: return self.get_generator_return_type(iter_type) else: # Non-Generators don't return anything from `yield from` expressions. - if experimental.STRICT_OPTIONAL: - return NoneTyp() - else: - return Void() + return Void() def visit_member_expr(self, e: MemberExpr) -> Type: return self.expr_checker.visit_member_expr(e) diff --git a/mypy/constraints.py b/mypy/constraints.py index 68d12ecae872..4ea64f43bb65 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -5,7 +5,7 @@ from mypy.types import ( CallableType, Type, TypeVisitor, UnboundType, AnyType, Void, NoneTyp, TypeVarType, Instance, TupleType, UnionType, Overloaded, ErasedType, PartialType, DeletedType, - is_named_instance, UninhabitedType + is_named_instance ) from mypy.maptype import map_instance_to_supertype from mypy import nodes @@ -222,9 +222,6 @@ def visit_void(self, template: Void) -> List[Constraint]: def visit_none_type(self, template: NoneTyp) -> List[Constraint]: return [] - def visit_uninhabited_type(self, template: UninhabitedType) -> List[Constraint]: - return [] - def visit_erased_type(self, template: ErasedType) -> List[Constraint]: return [] diff --git a/mypy/erasetype.py b/mypy/erasetype.py index 8056e2e01b07..15ccd4f9bb7e 100644 --- a/mypy/erasetype.py +++ b/mypy/erasetype.py @@ -1,9 +1,8 @@ from mypy.types import ( Type, TypeVisitor, UnboundType, ErrorType, AnyType, Void, NoneTyp, Instance, TypeVarType, CallableType, TupleType, UnionType, Overloaded, ErasedType, - PartialType, DeletedType, TypeTranslator, TypeList, UninhabitedType + PartialType, DeletedType, TypeTranslator, TypeList ) -from mypy import experimental def erase_type(typ: Type) -> Type: @@ -41,9 +40,6 @@ def visit_void(self, t: Void) -> Type: def visit_none_type(self, t: NoneTyp) -> Type: return t - def visit_uninhabited_type(self, t: UninhabitedType) -> Type: - return t - def visit_erased_type(self, t: ErasedType) -> Type: # Should not get here. raise RuntimeError() @@ -63,8 +59,7 @@ def visit_type_var(self, t: TypeVarType) -> Type: def visit_callable_type(self, t: CallableType) -> Type: # We must preserve the fallback type for overload resolution to work. - none_type = NoneTyp() if experimental.STRICT_OPTIONAL else Void() - return CallableType([], [], [], none_type, t.fallback) + return CallableType([], [], [], Void(), t.fallback) def visit_overloaded(self, t: Overloaded) -> Type: return t.items()[0].accept(self) diff --git a/mypy/expandtype.py b/mypy/expandtype.py index 45b84fe407a7..60730b563bd6 100644 --- a/mypy/expandtype.py +++ b/mypy/expandtype.py @@ -3,7 +3,7 @@ from mypy.types import ( Type, Instance, CallableType, TypeVisitor, UnboundType, ErrorType, AnyType, Void, NoneTyp, TypeVarType, Overloaded, TupleType, UnionType, ErasedType, TypeList, - PartialType, DeletedType, UninhabitedType + PartialType, DeletedType ) @@ -53,9 +53,6 @@ def visit_void(self, t: Void) -> Type: def visit_none_type(self, t: NoneTyp) -> Type: return t - def visit_uninhabited_type(self, t: UninhabitedType) -> Type: - return t - def visit_deleted_type(self, t: DeletedType) -> Type: return t diff --git a/mypy/fixup.py b/mypy/fixup.py index 3a1c07a6b8fe..134e611fe85c 100644 --- a/mypy/fixup.py +++ b/mypy/fixup.py @@ -7,8 +7,7 @@ TypeVarExpr, ClassDef, LDEF, MDEF, GDEF, MODULE_REF) from mypy.types import (CallableType, EllipsisType, Instance, Overloaded, TupleType, - TypeList, TypeVarType, UnboundType, UnionType, TypeVisitor, - UninhabitedType) + TypeList, TypeVarType, UnboundType, UnionType, TypeVisitor) from mypy.visitor import NodeVisitor @@ -181,9 +180,6 @@ def visit_deleted_type(self, o: Any) -> None: def visit_none_type(self, o: Any) -> None: pass # Nothing to descend into. - def visit_uninhabited_type(self, o: Any) -> None: - pass # Nothing to descend into. - def visit_partial_type(self, o: Any) -> None: raise RuntimeError("Shouldn't get here", o) diff --git a/mypy/join.py b/mypy/join.py index 0ec4e7880d07..e4ffc3283f83 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -5,11 +5,13 @@ from mypy.types import ( Type, AnyType, NoneTyp, Void, TypeVisitor, Instance, UnboundType, ErrorType, TypeVarType, CallableType, TupleType, ErasedType, TypeList, - UnionType, FunctionLike, Overloaded, PartialType, DeletedType, UninhabitedType + UnionType, FunctionLike, Overloaded, PartialType, DeletedType ) from mypy.maptype import map_instance_to_supertype from mypy.subtypes import is_subtype, is_equivalent, is_subtype_ignoring_tvars +from mypy import experimental + def join_simple(declaration: Type, s: Type, t: Type) -> Type: """Return a simple least upper bound given the declared type.""" @@ -17,9 +19,6 @@ def join_simple(declaration: Type, s: Type, t: Type) -> Type: if isinstance(s, AnyType): return s - if isinstance(s, NoneTyp) and not isinstance(t, Void): - return t - if isinstance(s, ErasedType): return t @@ -57,9 +56,6 @@ def join_types(s: Type, t: Type) -> Type: if isinstance(s, AnyType): return s - if isinstance(s, NoneTyp) and not isinstance(t, Void): - return t - if isinstance(s, ErasedType): return t @@ -105,17 +101,16 @@ def visit_void(self, t: Void) -> Type: return ErrorType() def visit_none_type(self, t: NoneTyp) -> Type: - # TODO(ddfisher): should behave differently with strict optional - if not isinstance(self.s, Void): - return self.s + if experimental.STRICT_OPTIONAL: + if isinstance(self.s, NoneTyp): + return t + else: + return self.default(self.s) else: - return self.default(self.s) - - def visit_uninhabited_type(self, t: UninhabitedType) -> bool: - if not isinstance(self.s, Void): - return self.s - else: - return self.default(self.s) + if not isinstance(self.s, Void): + return self.s + else: + return self.default(self.s) def visit_deleted_type(self, t: DeletedType) -> Type: if not isinstance(self.s, Void): @@ -330,7 +325,10 @@ def join_type_list(types: List[Type]) -> Type: # This is a little arbitrary but reasonable. Any empty tuple should be compatible # with all variable length tuples, and this makes it possible. A better approach # would be to use a special bottom type. - return NoneTyp() + if experimental.STRICT_OPTIONAL: + return Void() + else: + return NoneTyp() joined = types[0] for t in types[1:]: joined = join_types(joined, t) diff --git a/mypy/main.py b/mypy/main.py index 81d513b10b17..c1888e37577f 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -16,7 +16,6 @@ from mypy.version import __version__ - PY_EXTENSIONS = tuple(PYTHON_EXTENSIONS) diff --git a/mypy/meet.py b/mypy/meet.py index 4c2089e4f5c7..9d367b314242 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -3,12 +3,13 @@ from mypy.join import is_similar_callables, combine_similar_callables from mypy.types import ( Type, AnyType, TypeVisitor, UnboundType, Void, ErrorType, NoneTyp, TypeVarType, - Instance, CallableType, TupleType, ErasedType, TypeList, UnionType, PartialType, DeletedType, - UninhabitedType + Instance, CallableType, TupleType, ErasedType, TypeList, UnionType, PartialType, DeletedType ) from mypy.subtypes import is_subtype from mypy.nodes import TypeInfo +from mypy import experimental + # TODO Describe this module. @@ -29,7 +30,10 @@ def meet_simple(s: Type, t: Type, default_right: bool = True) -> Type: if isinstance(s, UnionType): return UnionType.make_simplified_union([meet_types(x, t) for x in s.items]) elif not is_overlapping_types(s, t, use_promotions=True): - return UninhabitedType() + if experimental.STRICT_OPTIONAL: + return Void() + else: + return NoneTyp() else: if default_right: return t @@ -109,7 +113,7 @@ def __init__(self, s: Type) -> None: def visit_unbound_type(self, t: UnboundType) -> Type: if isinstance(self.s, Void) or isinstance(self.s, ErrorType): return ErrorType() - elif isinstance(self.s, NoneTyp): + elif isinstance(self.s, NoneTyp) and not experimental.STRICT_OPTIONAL: return self.s else: return AnyType() @@ -141,21 +145,22 @@ def visit_void(self, t: Void) -> Type: return ErrorType() def visit_none_type(self, t: NoneTyp) -> Type: - # TODO(ddfisher): should behave different with strict optional - if not isinstance(self.s, Void) and not isinstance(self.s, ErrorType): - return t - else: - return ErrorType() - - def visit_uninhabited_type(self, t: UninhabitedType) -> bool: - if not isinstance(self.s, Void) and not isinstance(self.s, ErrorType): - return t + if experimental.STRICT_OPTIONAL: + if isinstance(self.s, NoneTyp) or (isinstance(self.s, Instance) and + self.s.type.fullname() == 'builtins.object'): + return t + else: + return ErrorType() else: - return ErrorType() + if not isinstance(self.s, Void) and not isinstance(self.s, ErrorType): + return t + else: + return ErrorType() def visit_deleted_type(self, t: DeletedType) -> Type: if not isinstance(self.s, Void) and not isinstance(self.s, ErrorType): if isinstance(self.s, NoneTyp): + # TODO(ddfisher): is this correct? return self.s else: return t @@ -183,7 +188,10 @@ def visit_instance(self, t: Instance) -> Type: args.append(self.meet(t.args[i], si.args[i])) return Instance(t.type, args) else: - return UninhabitedType() + if experimental.STRICT_OPTIONAL: + return Void() + else: + return NoneTyp() else: if is_subtype(t, self.s): return t @@ -191,7 +199,10 @@ def visit_instance(self, t: Instance) -> Type: # See also above comment. return self.s else: - return UninhabitedType() + if experimental.STRICT_OPTIONAL: + return Void() + else: + return NoneTyp() else: return self.default(self.s) @@ -224,4 +235,7 @@ def default(self, typ): elif isinstance(typ, Void) or isinstance(typ, ErrorType): return ErrorType() else: - return UninhabitedType() + if experimental.STRICT_OPTIONAL: + return Void() + else: + return NoneTyp() diff --git a/mypy/sametypes.py b/mypy/sametypes.py index 87b81dac508a..bfe37a6a1394 100644 --- a/mypy/sametypes.py +++ b/mypy/sametypes.py @@ -2,8 +2,7 @@ from mypy.types import ( Type, UnboundType, ErrorType, AnyType, NoneTyp, Void, TupleType, UnionType, CallableType, - TypeVarType, Instance, TypeVisitor, ErasedType, TypeList, Overloaded, PartialType, DeletedType, - UninhabitedType + TypeVarType, Instance, TypeVisitor, ErasedType, TypeList, Overloaded, PartialType, DeletedType ) @@ -70,9 +69,6 @@ def visit_void(self, left: Void) -> bool: def visit_none_type(self, left: NoneTyp) -> bool: return isinstance(self.right, NoneTyp) - def visit_uninhabited_type(self, t: UninhabitedType) -> bool: - return isinstance(self.right, UninhabitedType) - def visit_erased_type(self, left: ErasedType) -> bool: # Should not get here. raise RuntimeError() diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 562229575767..c3f543e3cb63 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -3,7 +3,7 @@ from mypy.types import ( Type, AnyType, UnboundType, TypeVisitor, ErrorType, Void, NoneTyp, Instance, TypeVarType, CallableType, TupleType, UnionType, Overloaded, ErasedType, TypeList, - PartialType, DeletedType, is_named_instance, UninhabitedType + PartialType, DeletedType, is_named_instance ) import mypy.applytype import mypy.constraints @@ -102,14 +102,12 @@ def visit_void(self, left: Void) -> bool: def visit_none_type(self, left: NoneTyp) -> bool: if experimental.STRICT_OPTIONAL: - # TODO(ddfisher): what about Unions? - print("OMG") - return isinstance(self.right, NoneTyp) + return (isinstance(self.right, NoneTyp) or + (isinstance(self.right, Instance) and + self.right.type.fullname() == 'builtins.object')) else: return not isinstance(self.right, Void) - def visit_uninhabited_type(self, left: UninhabitedType) -> bool: - return not isinstance(self.right, Void) def visit_erased_type(self, left: ErasedType) -> bool: return True diff --git a/mypy/test/data/check-optional.test b/mypy/test/data/check-optional.test index e35233af8241..b36653bab6fa 100644 --- a/mypy/test/data/check-optional.test +++ b/mypy/test/data/check-optional.test @@ -1,4 +1,4 @@ -- Tests for strict Optional behavior [case testNoneMemberOfOptional] -1 + "str" +1 + 1 diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index 08586eefb162..754ed7271a71 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -11,7 +11,7 @@ from mypy.meet import meet_types from mypy.types import ( UnboundType, AnyType, Void, CallableType, TupleType, TypeVarDef, Type, - Instance, NoneTyp, ErrorType, Overloaded, UninhabitedType + Instance, NoneTyp, ErrorType, Overloaded ) from mypy.nodes import ARG_POS, ARG_OPT, ARG_STAR, CONTRAVARIANT, INVARIANT, COVARIANT from mypy.replacetvars import replace_type_vars @@ -103,7 +103,7 @@ def set_up(self): # expand_type def test_trivial_expand(self): - for t in (self.fx.a, self.fx.o, self.fx.t, self.fx.void, self.fx.uninhab, + for t in (self.fx.a, self.fx.o, self.fx.t, self.fx.void, self.fx.nonet, self.tuple(self.fx.a), self.callable([], self.fx.a, self.fx.a), self.fx.anyt): self.assert_expand(t, [], t) @@ -135,7 +135,7 @@ def assert_expand(self, orig, map_items, result): # replace_type_vars def test_trivial_replace(self): - for t in (self.fx.a, self.fx.o, self.fx.void, self.fx.uninhab, + for t in (self.fx.a, self.fx.o, self.fx.void, self.fx.nonet, self.tuple(self.fx.a), self.callable([], self.fx.a, self.fx.a), self.fx.anyt, self.fx.err): @@ -154,7 +154,7 @@ def assert_replace(self, orig, result): # erase_type def test_trivial_erase(self): - for t in (self.fx.a, self.fx.o, self.fx.void, self.fx.uninhab, + for t in (self.fx.a, self.fx.o, self.fx.void, self.fx.nonet, self.fx.anyt, self.fx.err): self.assert_erase(t, t) @@ -545,8 +545,8 @@ def test_class_subtyping(self): self.assert_meet(self.fx.a, self.fx.o, self.fx.a) self.assert_meet(self.fx.a, self.fx.b, self.fx.b) self.assert_meet(self.fx.b, self.fx.o, self.fx.b) - self.assert_meet(self.fx.a, self.fx.d, UninhabitedType()) - self.assert_meet(self.fx.b, self.fx.c, UninhabitedType()) + self.assert_meet(self.fx.a, self.fx.d, NoneTyp()) + self.assert_meet(self.fx.b, self.fx.c, NoneTyp()) def test_tuples(self): self.assert_meet(self.tuple(), self.tuple(), self.tuple()) @@ -559,10 +559,10 @@ def test_tuples(self): self.assert_meet(self.tuple(self.fx.a, self.fx.a), self.fx.std_tuple, - UninhabitedType()) + NoneTyp()) self.assert_meet(self.tuple(self.fx.a), self.tuple(self.fx.a, self.fx.a), - UninhabitedType()) + NoneTyp()) def test_function_types(self): self.assert_meet(self.callable(self.fx.a, self.fx.b), @@ -571,15 +571,15 @@ def test_function_types(self): self.assert_meet(self.callable(self.fx.a, self.fx.b), self.callable(self.fx.b, self.fx.b), - UninhabitedType()) + NoneTyp()) self.assert_meet(self.callable(self.fx.a, self.fx.b), self.callable(self.fx.a, self.fx.a), - UninhabitedType()) + NoneTyp()) def test_type_vars(self): self.assert_meet(self.fx.t, self.fx.t, self.fx.t) self.assert_meet(self.fx.s, self.fx.s, self.fx.s) - self.assert_meet(self.fx.t, self.fx.s, UninhabitedType()) + self.assert_meet(self.fx.t, self.fx.s, NoneTyp()) def test_void(self): self.assert_meet(self.fx.void, self.fx.void, self.fx.void) @@ -638,29 +638,29 @@ def test_simple_generics(self): self.assert_meet(self.fx.ga, self.fx.ga, self.fx.ga) self.assert_meet(self.fx.ga, self.fx.o, self.fx.ga) self.assert_meet(self.fx.ga, self.fx.gb, self.fx.gb) - self.assert_meet(self.fx.ga, self.fx.gd, self.fx.uninhab) - self.assert_meet(self.fx.ga, self.fx.g2a, self.fx.uninhab) + self.assert_meet(self.fx.ga, self.fx.gd, self.fx.nonet) + self.assert_meet(self.fx.ga, self.fx.g2a, self.fx.nonet) self.assert_meet(self.fx.ga, self.fx.nonet, self.fx.nonet) self.assert_meet(self.fx.ga, self.fx.anyt, self.fx.ga) for t in [self.fx.a, self.fx.t, self.tuple(), self.callable(self.fx.a, self.fx.b)]: - self.assert_meet(t, self.fx.ga, self.fx.uninhab) + self.assert_meet(t, self.fx.ga, self.fx.nonet) def test_generics_with_multiple_args(self): self.assert_meet(self.fx.hab, self.fx.hab, self.fx.hab) self.assert_meet(self.fx.hab, self.fx.haa, self.fx.hab) - self.assert_meet(self.fx.hab, self.fx.had, self.fx.uninhab) + self.assert_meet(self.fx.hab, self.fx.had, self.fx.nonet) self.assert_meet(self.fx.hab, self.fx.hbb, self.fx.hbb) def test_generics_with_inheritance(self): self.assert_meet(self.fx.gsab, self.fx.gb, self.fx.gsab) - self.assert_meet(self.fx.gsba, self.fx.gb, self.fx.uninhab) + self.assert_meet(self.fx.gsba, self.fx.gb, self.fx.nonet) def test_generics_with_inheritance_and_shared_supertype(self): - self.assert_meet(self.fx.gsba, self.fx.gs2a, self.fx.uninhab) - self.assert_meet(self.fx.gsab, self.fx.gs2a, self.fx.uninhab) + self.assert_meet(self.fx.gsba, self.fx.gs2a, self.fx.nonet) + self.assert_meet(self.fx.gsab, self.fx.gs2a, self.fx.nonet) def test_generic_types_and_dynamic(self): self.assert_meet(self.fx.gdyn, self.fx.ga, self.fx.ga) @@ -675,20 +675,20 @@ def test_callables_with_dynamic(self): def test_meet_interface_types(self): self.assert_meet(self.fx.f, self.fx.f, self.fx.f) - self.assert_meet(self.fx.f, self.fx.f2, self.fx.uninhab) + self.assert_meet(self.fx.f, self.fx.f2, self.fx.nonet) self.assert_meet(self.fx.f, self.fx.f3, self.fx.f3) def test_join_interface_and_class_types(self): self.assert_meet(self.fx.o, self.fx.f, self.fx.f) - self.assert_meet(self.fx.a, self.fx.f, self.fx.uninhab) + self.assert_meet(self.fx.a, self.fx.f, self.fx.nonet) self.assert_meet(self.fx.e, self.fx.f, self.fx.e) def test_join_class_types_with_shared_interfaces(self): # These have nothing special with respect to meets, unlike joins. These # are for completeness only. - self.assert_meet(self.fx.e, self.fx.e2, self.fx.uninhab) - self.assert_meet(self.fx.e2, self.fx.e3, self.fx.uninhab) + self.assert_meet(self.fx.e, self.fx.e2, self.fx.nonet) + self.assert_meet(self.fx.e2, self.fx.e3, self.fx.nonet) def test_meet_with_generic_interfaces(self): # TODO fix @@ -697,7 +697,7 @@ def test_meet_with_generic_interfaces(self): fx = InterfaceTypeFixture() self.assert_meet(fx.gfa, fx.m1, fx.m1) self.assert_meet(fx.gfa, fx.gfa, fx.gfa) - self.assert_meet(fx.gfb, fx.m1, fx.uninhab) + self.assert_meet(fx.gfb, fx.m1, fx.nonet) # FIX generic interfaces + ranges diff --git a/mypy/typeanal.py b/mypy/typeanal.py index e877a363ecfd..28223dbb6302 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -5,7 +5,7 @@ from mypy.types import ( Type, UnboundType, TypeVarType, TupleType, UnionType, Instance, AnyType, CallableType, Void, NoneTyp, DeletedType, TypeList, TypeVarDef, TypeVisitor, StarType, PartialType, - EllipsisType, UninhabitedType + EllipsisType ) from mypy.nodes import ( BOUND_TVAR, TYPE_ALIAS, UNBOUND_IMPORTED, @@ -98,6 +98,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: t.line) elif fullname == 'builtins.None': if experimental.STRICT_OPTIONAL: + # TODO(ddfisher): make Void if it's a function return type return NoneTyp() else: return Void() @@ -176,9 +177,6 @@ def visit_void(self, t: Void) -> Type: def visit_none_type(self, t: NoneTyp) -> Type: return t - def visit_uninhabited_type(self, t: UninhabitedType) -> Type: - return t - def visit_deleted_type(self, t: DeletedType) -> Type: return t @@ -384,9 +382,6 @@ def visit_void(self, t: Void) -> None: def visit_none_type(self, t: NoneTyp) -> None: pass - def visit_uninhabited_type(self, t: UninhabitedType) -> None: - pass - def visit_deleted_type(self, t: DeletedType) -> None: pass diff --git a/mypy/typefixture.py b/mypy/typefixture.py index 0e68047c8ded..ffd1e84d90bd 100644 --- a/mypy/typefixture.py +++ b/mypy/typefixture.py @@ -6,8 +6,7 @@ from typing import List from mypy.types import ( - TypeVarType, AnyType, Void, ErrorType, NoneTyp, Instance, CallableType, - TypeVarDef, UninhabitedType + TypeVarType, AnyType, Void, ErrorType, NoneTyp, Instance, CallableType, TypeVarDef ) from mypy.nodes import ( TypeInfo, ClassDef, Block, ARG_POS, ARG_OPT, ARG_STAR, SymbolTable, @@ -39,7 +38,6 @@ def __init__(self, variance: int=COVARIANT) -> None: self.void = Void() self.err = ErrorType() self.nonet = NoneTyp() - self.uninhab = UninhabitedType() # Abstract class TypeInfos diff --git a/mypy/types.py b/mypy/types.py index b1dd0bed3372..e9eb1e82d795 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -211,24 +211,6 @@ def deserialize(cls, data: JsonDict) -> 'Void': assert data['.class'] == 'Void' return Void() -class UninhabitedType(Type): - """This type has no members. - """ - - def __init__(self, line: int = -1) -> None: - super().__init__(line) - - def accept(self, visitor: 'TypeVisitor[T]') -> T: - return visitor.visit_uninhabited_type(self) - - def serialize(self) -> JsonDict: - return {'.class': 'UninhabitedType'} - - @classmethod - def deserialize(cls, data: JsonDict) -> 'UninhabitedType': - assert data['.class'] == 'UninhabitedType' - return UninhabitedType() - class NoneTyp(Type): """The type of 'None'. @@ -464,7 +446,10 @@ def __init__(self, self.arg_names = arg_names self.min_args = arg_kinds.count(mypy.nodes.ARG_POS) self.is_var_arg = mypy.nodes.ARG_STAR in arg_kinds - self.ret_type = ret_type + if isinstance(ret_type, NoneTyp): + self.ret_type = Void(line=ret_type.get_line()) + else: + self.ret_type = ret_type self.fallback = fallback assert not name or ' Type: elif len(items) == 1: return items[0] else: - return UninhabitedType() + return Void() @staticmethod def make_simplified_union(items: List[Type], line: int = -1) -> Type: @@ -847,10 +832,6 @@ def visit_void(self, t: Void) -> T: def visit_none_type(self, t: NoneTyp) -> T: pass - @abstractmethod - def visit_uninhabited_type(self, t: UninhabitedType) -> T: - pass - def visit_erased_type(self, t: ErasedType) -> T: raise self._notimplemented_helper() @@ -917,9 +898,6 @@ def visit_void(self, t: Void) -> Type: def visit_none_type(self, t: NoneTyp) -> Type: return t - def visit_uninhabited_type(self, t: UninhabitedType) -> Type: - return t - def visit_erased_type(self, t: ErasedType) -> Type: return t @@ -1006,9 +984,6 @@ def visit_none_type(self, t): # Fully qualify to make this distinct from the None value. return "builtins.None" - def visit_uninhabited_type(self, t): - return "" - def visit_erased_type(self, t): return "" @@ -1150,9 +1125,6 @@ def visit_any(self, t: AnyType) -> bool: def visit_void(self, t: Void) -> bool: return self.default - def visit_uninhabited_type(self, t: UninhabitedType) -> bool: - return self.default - def visit_none_type(self, t: NoneTyp) -> bool: return self.default From 8f8fb49528c6a9882c9f76b24d97bee9c43896e9 Mon Sep 17 00:00:00 2001 From: David Fisher Date: Wed, 18 May 2016 19:47:45 -0700 Subject: [PATCH 04/27] the return of uninhabitedtype --- mypy/constraints.py | 5 ++++- mypy/erasetype.py | 5 ++++- mypy/expandtype.py | 5 ++++- mypy/fixup.py | 6 +++++- mypy/join.py | 10 +++++++--- mypy/meet.py | 19 ++++++++++++------- mypy/messages.py | 2 +- mypy/sametypes.py | 6 +++++- mypy/subtypes.py | 4 +++- mypy/typeanal.py | 8 +++++++- mypy/types.py | 33 ++++++++++++++++++++++++++++++++- 11 files changed, 84 insertions(+), 19 deletions(-) diff --git a/mypy/constraints.py b/mypy/constraints.py index 4ea64f43bb65..68d12ecae872 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -5,7 +5,7 @@ from mypy.types import ( CallableType, Type, TypeVisitor, UnboundType, AnyType, Void, NoneTyp, TypeVarType, Instance, TupleType, UnionType, Overloaded, ErasedType, PartialType, DeletedType, - is_named_instance + is_named_instance, UninhabitedType ) from mypy.maptype import map_instance_to_supertype from mypy import nodes @@ -222,6 +222,9 @@ def visit_void(self, template: Void) -> List[Constraint]: def visit_none_type(self, template: NoneTyp) -> List[Constraint]: return [] + def visit_uninhabited_type(self, template: UninhabitedType) -> List[Constraint]: + return [] + def visit_erased_type(self, template: ErasedType) -> List[Constraint]: return [] diff --git a/mypy/erasetype.py b/mypy/erasetype.py index 15ccd4f9bb7e..015346b2d820 100644 --- a/mypy/erasetype.py +++ b/mypy/erasetype.py @@ -1,7 +1,7 @@ from mypy.types import ( Type, TypeVisitor, UnboundType, ErrorType, AnyType, Void, NoneTyp, Instance, TypeVarType, CallableType, TupleType, UnionType, Overloaded, ErasedType, - PartialType, DeletedType, TypeTranslator, TypeList + PartialType, DeletedType, TypeTranslator, TypeList, UninhabitedType ) @@ -40,6 +40,9 @@ def visit_void(self, t: Void) -> Type: def visit_none_type(self, t: NoneTyp) -> Type: return t + def visit_uninhabited_type(self, t: UninhabitedType) -> Type: + return t + def visit_erased_type(self, t: ErasedType) -> Type: # Should not get here. raise RuntimeError() diff --git a/mypy/expandtype.py b/mypy/expandtype.py index 60730b563bd6..45b84fe407a7 100644 --- a/mypy/expandtype.py +++ b/mypy/expandtype.py @@ -3,7 +3,7 @@ from mypy.types import ( Type, Instance, CallableType, TypeVisitor, UnboundType, ErrorType, AnyType, Void, NoneTyp, TypeVarType, Overloaded, TupleType, UnionType, ErasedType, TypeList, - PartialType, DeletedType + PartialType, DeletedType, UninhabitedType ) @@ -53,6 +53,9 @@ def visit_void(self, t: Void) -> Type: def visit_none_type(self, t: NoneTyp) -> Type: return t + def visit_uninhabited_type(self, t: UninhabitedType) -> Type: + return t + def visit_deleted_type(self, t: DeletedType) -> Type: return t diff --git a/mypy/fixup.py b/mypy/fixup.py index 134e611fe85c..3a1c07a6b8fe 100644 --- a/mypy/fixup.py +++ b/mypy/fixup.py @@ -7,7 +7,8 @@ TypeVarExpr, ClassDef, LDEF, MDEF, GDEF, MODULE_REF) from mypy.types import (CallableType, EllipsisType, Instance, Overloaded, TupleType, - TypeList, TypeVarType, UnboundType, UnionType, TypeVisitor) + TypeList, TypeVarType, UnboundType, UnionType, TypeVisitor, + UninhabitedType) from mypy.visitor import NodeVisitor @@ -180,6 +181,9 @@ def visit_deleted_type(self, o: Any) -> None: def visit_none_type(self, o: Any) -> None: pass # Nothing to descend into. + def visit_uninhabited_type(self, o: Any) -> None: + pass # Nothing to descend into. + def visit_partial_type(self, o: Any) -> None: raise RuntimeError("Shouldn't get here", o) diff --git a/mypy/join.py b/mypy/join.py index e4ffc3283f83..2e1fed7e2db0 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -5,7 +5,7 @@ from mypy.types import ( Type, AnyType, NoneTyp, Void, TypeVisitor, Instance, UnboundType, ErrorType, TypeVarType, CallableType, TupleType, ErasedType, TypeList, - UnionType, FunctionLike, Overloaded, PartialType, DeletedType + UnionType, FunctionLike, Overloaded, PartialType, DeletedType, UninhabitedType ) from mypy.maptype import map_instance_to_supertype from mypy.subtypes import is_subtype, is_equivalent, is_subtype_ignoring_tvars @@ -112,6 +112,9 @@ def visit_none_type(self, t: NoneTyp) -> Type: else: return self.default(self.s) + def visit_uninhabited_type(self, t: UninhabitedType) -> Type: + return self.s + def visit_deleted_type(self, t: DeletedType) -> Type: if not isinstance(self.s, Void): return self.s @@ -324,9 +327,10 @@ def join_type_list(types: List[Type]) -> Type: if not types: # This is a little arbitrary but reasonable. Any empty tuple should be compatible # with all variable length tuples, and this makes it possible. A better approach - # would be to use a special bottom type. + # would be to use a special bottom type, which we do when strict Optional + # checking is enabled. if experimental.STRICT_OPTIONAL: - return Void() + return UninhabitedType() else: return NoneTyp() joined = types[0] diff --git a/mypy/meet.py b/mypy/meet.py index 9d367b314242..6d57be7e3419 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -3,7 +3,8 @@ from mypy.join import is_similar_callables, combine_similar_callables from mypy.types import ( Type, AnyType, TypeVisitor, UnboundType, Void, ErrorType, NoneTyp, TypeVarType, - Instance, CallableType, TupleType, ErasedType, TypeList, UnionType, PartialType, DeletedType + Instance, CallableType, TupleType, ErasedType, TypeList, UnionType, PartialType, DeletedType, + UninhabitedType ) from mypy.subtypes import is_subtype from mypy.nodes import TypeInfo @@ -31,7 +32,7 @@ def meet_simple(s: Type, t: Type, default_right: bool = True) -> Type: return UnionType.make_simplified_union([meet_types(x, t) for x in s.items]) elif not is_overlapping_types(s, t, use_promotions=True): if experimental.STRICT_OPTIONAL: - return Void() + return UninhabitedType() else: return NoneTyp() else: @@ -111,9 +112,10 @@ def __init__(self, s: Type) -> None: self.s = s def visit_unbound_type(self, t: UnboundType) -> Type: + # TODO(ddfisher): understand what's going on here if isinstance(self.s, Void) or isinstance(self.s, ErrorType): return ErrorType() - elif isinstance(self.s, NoneTyp) and not experimental.STRICT_OPTIONAL: + elif isinstance(self.s, NoneTyp): return self.s else: return AnyType() @@ -157,10 +159,13 @@ def visit_none_type(self, t: NoneTyp) -> Type: else: return ErrorType() + def visit_uninhabited_type(self, t: UninhabitedType) -> Type: + return t + def visit_deleted_type(self, t: DeletedType) -> Type: + # TODO(ddfisher): understand what's going on here if not isinstance(self.s, Void) and not isinstance(self.s, ErrorType): if isinstance(self.s, NoneTyp): - # TODO(ddfisher): is this correct? return self.s else: return t @@ -189,7 +194,7 @@ def visit_instance(self, t: Instance) -> Type: return Instance(t.type, args) else: if experimental.STRICT_OPTIONAL: - return Void() + return UninhabitedType() else: return NoneTyp() else: @@ -200,7 +205,7 @@ def visit_instance(self, t: Instance) -> Type: return self.s else: if experimental.STRICT_OPTIONAL: - return Void() + return UninhabitedType() else: return NoneTyp() else: @@ -236,6 +241,6 @@ def default(self, typ): return ErrorType() else: if experimental.STRICT_OPTIONAL: - return Void() + return UninhabitedType() else: return NoneTyp() diff --git a/mypy/messages.py b/mypy/messages.py index 90be97fc2e8d..c956945d733b 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -256,7 +256,7 @@ def format_simple(self, typ: Type, verbosity: int = 0) -> str: else: return 'union type ({} items)'.format(len(items)) elif isinstance(typ, Void): - return 'None' + return 'Void' elif isinstance(typ, NoneTyp): return 'None' elif isinstance(typ, AnyType): diff --git a/mypy/sametypes.py b/mypy/sametypes.py index bfe37a6a1394..87b81dac508a 100644 --- a/mypy/sametypes.py +++ b/mypy/sametypes.py @@ -2,7 +2,8 @@ from mypy.types import ( Type, UnboundType, ErrorType, AnyType, NoneTyp, Void, TupleType, UnionType, CallableType, - TypeVarType, Instance, TypeVisitor, ErasedType, TypeList, Overloaded, PartialType, DeletedType + TypeVarType, Instance, TypeVisitor, ErasedType, TypeList, Overloaded, PartialType, DeletedType, + UninhabitedType ) @@ -69,6 +70,9 @@ def visit_void(self, left: Void) -> bool: def visit_none_type(self, left: NoneTyp) -> bool: return isinstance(self.right, NoneTyp) + def visit_uninhabited_type(self, t: UninhabitedType) -> bool: + return isinstance(self.right, UninhabitedType) + def visit_erased_type(self, left: ErasedType) -> bool: # Should not get here. raise RuntimeError() diff --git a/mypy/subtypes.py b/mypy/subtypes.py index c3f543e3cb63..1e851d5136d9 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -3,7 +3,7 @@ from mypy.types import ( Type, AnyType, UnboundType, TypeVisitor, ErrorType, Void, NoneTyp, Instance, TypeVarType, CallableType, TupleType, UnionType, Overloaded, ErasedType, TypeList, - PartialType, DeletedType, is_named_instance + PartialType, DeletedType, is_named_instance, UninhabitedType ) import mypy.applytype import mypy.constraints @@ -108,6 +108,8 @@ def visit_none_type(self, left: NoneTyp) -> bool: else: return not isinstance(self.right, Void) + def visit_uninhabited_type(self, left: UninhabitedType) -> bool: + return True def visit_erased_type(self, left: ErasedType) -> bool: return True diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 28223dbb6302..cb81fbe9ff26 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -5,7 +5,7 @@ from mypy.types import ( Type, UnboundType, TypeVarType, TupleType, UnionType, Instance, AnyType, CallableType, Void, NoneTyp, DeletedType, TypeList, TypeVarDef, TypeVisitor, StarType, PartialType, - EllipsisType + EllipsisType, UninhabitedType ) from mypy.nodes import ( BOUND_TVAR, TYPE_ALIAS, UNBOUND_IMPORTED, @@ -177,6 +177,9 @@ def visit_void(self, t: Void) -> Type: def visit_none_type(self, t: NoneTyp) -> Type: return t + def visit_uninhabited_type(self, t: UninhabitedType) -> Type: + return t + def visit_deleted_type(self, t: DeletedType) -> Type: return t @@ -382,6 +385,9 @@ def visit_void(self, t: Void) -> None: def visit_none_type(self, t: NoneTyp) -> None: pass + def visit_uninhabited_type(self, t: UninhabitedType) -> None: + pass + def visit_deleted_type(self, t: DeletedType) -> None: pass diff --git a/mypy/types.py b/mypy/types.py index e9eb1e82d795..aef9196d395b 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -211,6 +211,24 @@ def deserialize(cls, data: JsonDict) -> 'Void': assert data['.class'] == 'Void' return Void() +class UninhabitedType(Type): + """This type has no members. + """ + + def __init__(self, line: int = -1) -> None: + super().__init__(line) + + def accept(self, visitor: 'TypeVisitor[T]') -> T: + return visitor.visit_uninhabited_type(self) + + def serialize(self) -> JsonDict: + return {'.class': 'UninhabitedType'} + + @classmethod + def deserialize(cls, data: JsonDict) -> 'UninhabitedType': + assert data['.class'] == 'UninhabitedType' + return UninhabitedType() + class NoneTyp(Type): """The type of 'None'. @@ -693,7 +711,7 @@ def make_union(items: List[Type], line: int = -1) -> Type: elif len(items) == 1: return items[0] else: - return Void() + return UninhabitedType() @staticmethod def make_simplified_union(items: List[Type], line: int = -1) -> Type: @@ -832,6 +850,10 @@ def visit_void(self, t: Void) -> T: def visit_none_type(self, t: NoneTyp) -> T: pass + @abstractmethod + def visit_uninhabited_type(self, t: UninhabitedType) -> T: + pass + def visit_erased_type(self, t: ErasedType) -> T: raise self._notimplemented_helper() @@ -898,6 +920,9 @@ def visit_void(self, t: Void) -> Type: def visit_none_type(self, t: NoneTyp) -> Type: return t + def visit_uninhabited_type(self, t: UninhabitedType) -> Type: + return t + def visit_erased_type(self, t: ErasedType) -> Type: return t @@ -984,6 +1009,9 @@ def visit_none_type(self, t): # Fully qualify to make this distinct from the None value. return "builtins.None" + def visit_uninhabited_type(self, t): + return "" + def visit_erased_type(self, t): return "" @@ -1125,6 +1153,9 @@ def visit_any(self, t: AnyType) -> bool: def visit_void(self, t: Void) -> bool: return self.default + def visit_uninhabited_type(self, t: UninhabitedType) -> bool: + return self.default + def visit_none_type(self, t: NoneTyp) -> bool: return self.default From 8c0577a5c5ba77ddfcef2e38a6b32f91a0446022 Mon Sep 17 00:00:00 2001 From: David Fisher Date: Wed, 18 May 2016 20:07:21 -0700 Subject: [PATCH 05/27] fix tests WIP --- mypy/join.py | 6 ++++++ mypy/messages.py | 2 +- mypy/parse.py | 3 +++ mypy/test/data/check-optional.test | 2 +- mypy/typeanal.py | 6 ++++-- mypy/types.py | 18 ++++++++++++------ 6 files changed, 27 insertions(+), 10 deletions(-) diff --git a/mypy/join.py b/mypy/join.py index 2e1fed7e2db0..30ebd28f1760 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -31,6 +31,9 @@ def join_simple(declaration: Type, s: Type, t: Type) -> Type: if isinstance(declaration, UnionType): return UnionType.make_simplified_union([s, t]) + if isinstance(s, NoneTyp) and not isinstance(t, NoneTyp): + s, t = t, s + value = t.accept(TypeJoinVisitor(s)) if value is None: @@ -59,6 +62,9 @@ def join_types(s: Type, t: Type) -> Type: if isinstance(s, ErasedType): return t + if isinstance(s, NoneTyp) and not isinstance(t, NoneTyp): + s, t = t, s + # Use a visitor to handle non-trivial cases. return t.accept(TypeJoinVisitor(s)) diff --git a/mypy/messages.py b/mypy/messages.py index c956945d733b..90be97fc2e8d 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -256,7 +256,7 @@ def format_simple(self, typ: Type, verbosity: int = 0) -> str: else: return 'union type ({} items)'.format(len(items)) elif isinstance(typ, Void): - return 'Void' + return 'None' elif isinstance(typ, NoneTyp): return 'None' elif isinstance(typ, AnyType): diff --git a/mypy/parse.py b/mypy/parse.py index ea45379c6515..8f2a7d738078 100644 --- a/mypy/parse.py +++ b/mypy/parse.py @@ -484,6 +484,9 @@ def parse_function(self, no_type_checks: bool=False) -> FuncDef: if is_error: return None + if typ and isinstance(typ.ret_type, UnboundType): + typ.ret_type.ret_type = True + node = FuncDef(name, args, body, typ) node.set_line(def_tok) if typ is not None: diff --git a/mypy/test/data/check-optional.test b/mypy/test/data/check-optional.test index b36653bab6fa..a1b09036257d 100644 --- a/mypy/test/data/check-optional.test +++ b/mypy/test/data/check-optional.test @@ -1,4 +1,4 @@ -- Tests for strict Optional behavior [case testNoneMemberOfOptional] -1 + 1 +"foo" diff --git a/mypy/typeanal.py b/mypy/typeanal.py index cb81fbe9ff26..82c064cd7be2 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -98,8 +98,10 @@ def visit_unbound_type(self, t: UnboundType) -> Type: t.line) elif fullname == 'builtins.None': if experimental.STRICT_OPTIONAL: - # TODO(ddfisher): make Void if it's a function return type - return NoneTyp() + if t.ret_type: + return Void() + else: + return NoneTyp() else: return Void() elif fullname == 'typing.Any': diff --git a/mypy/types.py b/mypy/types.py index aef9196d395b..8ede523ba1bb 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -6,6 +6,8 @@ import mypy.nodes from mypy.nodes import INVARIANT, SymbolNode +from mypy import experimental + T = TypeVar('T') @@ -101,17 +103,21 @@ class UnboundType(Type): args = None # type: List[Type] # should this type be wrapped in an Optional? optional = False + # is this type a return type? + ret_type = False def __init__(self, name: str, args: List[Type] = None, line: int = -1, - optional: bool = False) -> None: + optional: bool = False, + ret_type: bool = False) -> None: if not args: args = [] self.name = name self.args = args self.optional = optional + self.ret_type = ret_type super().__init__(line) def accept(self, visitor: 'TypeVisitor[T]') -> T: @@ -464,10 +470,7 @@ def __init__(self, self.arg_names = arg_names self.min_args = arg_kinds.count(mypy.nodes.ARG_POS) self.is_var_arg = mypy.nodes.ARG_STAR in arg_kinds - if isinstance(ret_type, NoneTyp): - self.ret_type = Void(line=ret_type.get_line()) - else: - self.ret_type = ret_type + self.ret_type = ret_type self.fallback = fallback assert not name or ' Type: elif len(items) == 1: return items[0] else: - return UninhabitedType() + if experimental.STRICT_OPTIONAL: + return UninhabitedType() + else: + return Void() @staticmethod def make_simplified_union(items: List[Type], line: int = -1) -> Type: From b136ef5319e2a9a1f9d0db956b47d79ceaa175a4 Mon Sep 17 00:00:00 2001 From: David Fisher Date: Fri, 20 May 2016 12:26:10 -0700 Subject: [PATCH 06/27] tests, bug fixes, partial types --- mypy/checker.py | 50 ++++++++++----- mypy/checkexpr.py | 17 +++--- mypy/meet.py | 2 +- mypy/messages.py | 21 ++++--- mypy/solve.py | 9 ++- mypy/test/data/check-optional.test | 98 +++++++++++++++++++++++++++++- mypy/test/testcheck.py | 8 +++ mypy/types.py | 7 ++- 8 files changed, 177 insertions(+), 35 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 5d872ec5cf8b..b140db06b0ee 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -30,7 +30,7 @@ from mypy.types import ( Type, AnyType, CallableType, Void, FunctionLike, Overloaded, TupleType, Instance, NoneTyp, ErrorType, strip_type, - UnionType, TypeVarType, PartialType, DeletedType + UnionType, TypeVarType, PartialType, DeletedType, UninhabitedType ) from mypy.sametypes import is_same_type from mypy.messages import MessageBuilder @@ -51,6 +51,8 @@ from mypy.treetransform import TransformVisitor from mypy.meet import meet_simple, nearest_builtin_ancestor, is_overlapping_types +from mypy import experimental + T = TypeVar('T') @@ -1171,7 +1173,10 @@ def check_assignment(self, lvalue: Node, rvalue: Node, infer_lvalue_type: bool = partial_types = self.find_partial_types(var) if partial_types is not None: if not self.current_node_deferred: - var.type = rvalue_type + if experimental.STRICT_OPTIONAL: + var.type = UnionType.make_simplified_union([rvalue_type, NoneTyp()]) + else: + var.type = rvalue_type else: var.type = None del partial_types[var] @@ -1439,15 +1444,16 @@ def infer_variable_type(self, name: Var, lvalue: Node, def infer_partial_type(self, name: Var, lvalue: Node, init_type: Type) -> bool: if isinstance(init_type, NoneTyp): - partial_type = PartialType(None, name) + # TODO(ddfisher): make pass UnboundType to third arg if class variable + partial_type = PartialType(None, name, [init_type]) elif isinstance(init_type, Instance): fullname = init_type.type.fullname() - if ((fullname == 'builtins.list' or fullname == 'builtins.set' or - fullname == 'builtins.dict') - and isinstance(init_type.args[0], NoneTyp) - and (fullname != 'builtins.dict' or isinstance(init_type.args[1], NoneTyp)) - and isinstance(lvalue, NameExpr)): - partial_type = PartialType(init_type.type, name) + if (isinstance(lvalue, NameExpr) and + (fullname == 'builtins.list' or + fullname == 'builtins.set' or + fullname == 'builtins.dict') and + all(isinstance(t, (NoneTyp, UninhabitedType)) for t in init_type.args)): + partial_type = PartialType(init_type.type, name, init_type.args) else: return False else: @@ -1530,8 +1536,8 @@ def try_infer_partial_type_from_indexed_assignment( self, lvalue: IndexExpr, rvalue: Node) -> None: # TODO: Should we share some of this with try_infer_partial_type? if isinstance(lvalue.base, RefExpr) and isinstance(lvalue.base.node, Var): - var = cast(Var, lvalue.base.node) - if var is not None and isinstance(var.type, PartialType): + var = lvalue.base.node + if isinstance(var.type, PartialType): type_type = var.type.type if type_type is None: return # The partial type is None. @@ -1541,12 +1547,15 @@ def try_infer_partial_type_from_indexed_assignment( typename = type_type.fullname() if typename == 'builtins.dict': # TODO: Don't infer things twice. + # TODO(ddfisher): fixup strict optional stuff key_type = self.accept(lvalue.index) value_type = self.accept(rvalue) - if is_valid_inferred_type(key_type) and is_valid_inferred_type(value_type): + full_key_type = UnionType.make_simplified_union([key_type, var.type.inner_types[0]]) + full_value_type = UnionType.make_simplified_union([value_type, var.type.inner_types[0]]) + if is_valid_inferred_type(full_key_type) and is_valid_inferred_type(full_value_type): if not self.current_node_deferred: var.type = self.named_generic_type('builtins.dict', - [key_type, value_type]) + [full_key_type, full_value_type]) del partial_types[var] def visit_expression_stmt(self, s: ExpressionStmt) -> Type: @@ -1856,7 +1865,10 @@ def analyze_iterable_item_type(self, expr: Node) -> Type: self.check_not_void(iterable, expr) if isinstance(iterable, TupleType): - joined = NoneTyp() # type: Type + if experimental.STRICT_OPTIONAL: + joined = UninhabitedType() # type: Type + else: + joined = NoneTyp() for item in iterable.items: joined = join_types(joined, item) if isinstance(joined, ErrorType): @@ -2282,8 +2294,12 @@ def leave_partial_types(self) -> None: partial_types = self.partial_types.pop() if not self.current_node_deferred: for var, context in partial_types.items(): - self.msg.fail(messages.NEED_ANNOTATION_FOR_VAR, context) - var.type = AnyType() + if experimental.STRICT_OPTIONAL and cast(PartialType, var.type).type is None: + # None partial type: assume variable is intended to have type None + var.type = NoneTyp() + else: + self.msg.fail(messages.NEED_ANNOTATION_FOR_VAR, context) + var.type = AnyType() def find_partial_types(self, var: Var) -> Optional[Dict[Var, Context]]: for partial_types in reversed(self.partial_types): @@ -2543,6 +2559,8 @@ def is_valid_inferred_type(typ: Type) -> bool: """ if is_same_type(typ, NoneTyp()): return False + if is_same_type(typ, UninhabitedType()): + return False elif isinstance(typ, Instance): for arg in typ.args: if not is_valid_inferred_type(arg): diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 0f59c2d21072..c2861a17092e 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -154,19 +154,20 @@ def try_infer_partial_type(self, e: CallExpr) -> None: var = cast(Var, e.callee.expr.node) partial_types = self.chk.find_partial_types(var) if partial_types is not None and not self.chk.current_node_deferred: - partial_type_type = cast(PartialType, var.type).type - if partial_type_type is None: + partial_type = cast(PartialType, var.type) + if partial_type.type is None: # A partial None type -> can't infer anything. return - typename = partial_type_type.fullname() + typename = partial_type.type.fullname() methodname = e.callee.name # Sometimes we can infer a full type for a partial List, Dict or Set type. # TODO: Don't infer argument expression twice. if (typename in self.item_args and methodname in self.item_args[typename] and e.arg_kinds == [ARG_POS]): item_type = self.accept(e.args[0]) - if mypy.checker.is_valid_inferred_type(item_type): - var.type = self.chk.named_generic_type(typename, [item_type]) + full_item_type = UnionType.make_simplified_union([item_type, partial_type.inner_types[0]]) + if mypy.checker.is_valid_inferred_type(full_item_type): + var.type = self.chk.named_generic_type(typename, [full_item_type]) del partial_types[var] elif (typename in self.container_args and methodname in self.container_args[typename] @@ -175,10 +176,12 @@ def try_infer_partial_type(self, e: CallExpr) -> None: if isinstance(arg_type, Instance): arg_typename = arg_type.type.fullname() if arg_typename in self.container_args[typename][methodname]: + full_item_types = [UnionType.make_simplified_union([item_type, prev_type]) + for item_type, prev_type in zip(arg_type.args, partial_type.inner_types)] if all(mypy.checker.is_valid_inferred_type(item_type) - for item_type in arg_type.args): + for item_type in full_item_types): var.type = self.chk.named_generic_type(typename, - list(arg_type.args)) + list(full_item_types)) del partial_types[var] def check_call_expr_with_callee_type(self, callee_type: Type, diff --git a/mypy/meet.py b/mypy/meet.py index 6d57be7e3419..8f1e68fa71d2 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -152,7 +152,7 @@ def visit_none_type(self, t: NoneTyp) -> Type: self.s.type.fullname() == 'builtins.object'): return t else: - return ErrorType() + return UninhabitedType() else: if not isinstance(self.s, Void) and not isinstance(self.s, ErrorType): return t diff --git a/mypy/messages.py b/mypy/messages.py index 90be97fc2e8d..a791c90ce41d 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -247,14 +247,21 @@ def format_simple(self, typ: Type, verbosity: int = 0) -> str: else: return 'tuple(length {})'.format(len(items)) elif isinstance(typ, UnionType): - items = [] - for t in typ.items: - items.append(strip_quotes(self.format(t))) - s = '"Union[{}]"'.format(', '.join(items)) - if len(s) < 40: - return s + # Only print Unions as Optionals if the Optional wouldn't have to contain another Union + print_as_optional = (len(typ.items) - + sum(isinstance(t, NoneTyp) for t in typ.items) == 1) + if print_as_optional: + rest = [t for t in typ.items if not isinstance(t, NoneTyp)] + return '"Optional[{}]"'.format(strip_quotes(self.format(rest[0]))) else: - return 'union type ({} items)'.format(len(items)) + items = [] + for t in typ.items: + items.append(strip_quotes(self.format(t))) + s = '"Union[{}]"'.format(', '.join(items)) + if len(s) < 40: + return s + else: + return 'union type ({} items)'.format(len(items)) elif isinstance(typ, Void): return 'None' elif isinstance(typ, NoneTyp): diff --git a/mypy/solve.py b/mypy/solve.py index 7fbd07c8f6c2..8199d1bea6c6 100644 --- a/mypy/solve.py +++ b/mypy/solve.py @@ -2,12 +2,14 @@ from typing import List, Dict -from mypy.types import Type, Void, NoneTyp, AnyType, ErrorType +from mypy.types import Type, Void, NoneTyp, AnyType, ErrorType, UninhabitedType from mypy.constraints import Constraint, SUPERTYPE_OF from mypy.join import join_types from mypy.meet import meet_types from mypy.subtypes import is_subtype +from mypy import experimental + def solve_constraints(vars: List[int], constraints: List[Constraint], strict=True) -> List[Type]: @@ -58,7 +60,10 @@ def solve_constraints(vars: List[int], constraints: List[Constraint], else: # No constraints for type variable -- type 'None' is the most specific type. if strict: - candidate = NoneTyp() + if experimental.STRICT_OPTIONAL: + candidate = UninhabitedType() + else: + candidate = NoneTyp() else: candidate = AnyType() elif top is None: diff --git a/mypy/test/data/check-optional.test b/mypy/test/data/check-optional.test index a1b09036257d..ff4046186dbd 100644 --- a/mypy/test/data/check-optional.test +++ b/mypy/test/data/check-optional.test @@ -1,4 +1,100 @@ -- Tests for strict Optional behavior +[case testImplicitNoneType] +x = None +x() # E: None not callable + +[case testExplicitNoneType] +x = None # type: None +x() # E: None not callable + [case testNoneMemberOfOptional] -"foo" +from typing import Optional +x = None # type: Optional[int] + +[case testTypeMemberOfOptional] +from typing import Optional +x = 0 # type: Optional[int] + +[case testNoneNotMemberOfType] +x = None # type: int +[out] +main:1: error: Incompatible types in assignment (expression has type None, variable has type "int") + +[case testTypeNotMemberOfNone] +x = 0 # type: None +[out] +main:1: error: Incompatible types in assignment (expression has type "int", variable has type None) + +[case testOptionalNotMemberOfType] +from typing import Optional +def f(a: int) -> None: pass +x = None # type: Optional[int] +f(x) # E: Argument 1 to "f" has incompatible type "Optional[int]"; expected "int" + +[case testIsinstanceCases] +from typing import Optional +def f(a: int) -> None: pass +def g(a: None) -> None: pass +x = None # type: Optional[int] +if isinstance(x, int): + f(x) +else: + g(x) +[builtins fixtures/isinstance.py] + +[case testLambdaReturningNone] +f = lambda: None +x = f() + +[case testFunctionStillVoid] +def f() -> None: pass +f() +x = f() # E: "f" does not return a value + +[case testNoneArgumentType] +def f(x: None) -> None: pass +f(None) + +[case testInferOptionalFromDefaultNone] +def f(x: int = None) -> None: + x + 1 # E: Unsupported left operand type for + (some union) +f(None) +[out] +main: note: In function "f": + +[case testInferOptionalType] +x = None +x = 1 +reveal_type(x) # E: Revealed type is 'Union[builtins.int, builtins.None]' + +[case testInferOptionalTypeFromOptional] +from typing import Optional +y = None # type: Optional[int] +x = None +x = y +reveal_type(x) # E: Revealed type is 'Union[builtins.int, builtins.None]' + +[case testInferOptionalListType] +x = [None] +x.append(1) +reveal_type(x) # E: Revealed type is 'builtins.list[Union[builtins.int, builtins.None]]' +[builtins fixtures/list.py] + +[case testInferNonOptionalListType] +x = [] +x.append(1) +x() # E: List[int] not callable +[builtins fixtures/list.py] + +[case testInferOptionalDictKeyValueTypes] +x = {None: None} +x["bar"] = 1 +reveal_type(x) # E: Revealed type is 'builtins.dict[Union[builtins.str, builtins.None], Union[builtins.int, builtins.None]]' +[builtins fixtures/dict.py] + +[case testInferNonOptionalDictType] +x = {} +x["bar"] = 1 +x() # E: Dict[str, int] not callable +[builtins fixtures/dict.py] diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index e98738f56602..512a4bfc3e67 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -19,6 +19,7 @@ ) from mypy.errors import CompileError +from mypy import experimental # List of files that contain test case descriptions. files = [ @@ -72,12 +73,19 @@ def cases(self) -> List[DataDrivenTestCase]: def run_test(self, testcase: DataDrivenTestCase) -> None: incremental = 'Incremental' in testcase.name.lower() or 'incremental' in testcase.file + optional = 'optional' in testcase.file if incremental: # Incremental tests are run once with a cold cache, once with a warm cache. # Expect success on first run, errors from testcase.output (if any) on second run. self.clear_cache() self.run_test_once(testcase, 1) self.run_test_once(testcase, 2) + elif optional: + try: + experimental.STRICT_OPTIONAL = True + self.run_test_once(testcase) + finally: + experimental.STRICT_OPTIONAL = False else: self.run_test_once(testcase) diff --git a/mypy/types.py b/mypy/types.py index 8ede523ba1bb..4aa87feda134 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -788,10 +788,15 @@ class PartialType(Type): # None for the 'None' partial type; otherwise a generic class type = None # type: Optional[mypy.nodes.TypeInfo] var = None # type: mypy.nodes.Var + inner_types = None # type: List[Type] - def __init__(self, type: Optional['mypy.nodes.TypeInfo'], var: 'mypy.nodes.Var') -> None: + def __init__(self, + type: Optional['mypy.nodes.TypeInfo'], + var: 'mypy.nodes.Var', + inner_types: List[Type]) -> None: self.type = type self.var = var + self.inner_types = inner_types def accept(self, visitor: 'TypeVisitor[T]') -> T: return visitor.visit_partial_type(self) From e274c13648291f47b9036d28f67bb339c81950e4 Mon Sep 17 00:00:00 2001 From: David Fisher Date: Fri, 20 May 2016 14:45:25 -0700 Subject: [PATCH 07/27] isinstance checks --- mypy/checker.py | 64 +++++++++++++++++++++++------- mypy/test/data/check-modules.test | 2 +- mypy/test/data/check-optional.test | 44 ++++++++++++++++++++ 3 files changed, 94 insertions(+), 16 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index b140db06b0ee..229595fd9d65 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2347,7 +2347,8 @@ def find_isinstance_check(node: Node, type_map: Dict[Node, Type], weak: bool=False) \ -> Tuple[Optional[Dict[Node, Type]], Optional[Dict[Node, Type]]]: - """Find any isinstance checks (within a chain of ands). + """Find any isinstance checks (within a chain of ands). Includes + implicit and explicit checks for None. Return value is a map of variables to their types if the condition is true and a map of variables to their types if the condition is false. @@ -2357,26 +2358,59 @@ def find_isinstance_check(node: Node, Guaranteed to not return None, None. (But may return {}, {}) """ + def split_types(current_type: Optional[Type], type_if_true: Optional[Type], expr: Node) \ + -> Tuple[Optional[Dict[Node, Type]], Optional[Dict[Node, Type]]]: + if type_if_true: + if current_type: + if is_proper_subtype(current_type, type_if_true): + return {expr: type_if_true}, None + elif not is_overlapping_types(current_type, type_if_true): + return None, {expr: current_type} + else: + type_if_false = restrict_subtype_away(current_type, type_if_true) + return {expr: type_if_true}, {expr: type_if_false} + else: + return {expr: type_if_true}, {} + else: + # An isinstance check, but we don't understand the type + if weak: + return {expr: AnyType()}, {expr: vartype} + else: + return {}, {} + + def is_none(n: Node) -> bool: + return isinstance(n, NameExpr) and n.fullname == 'builtins.None' + if isinstance(node, CallExpr): if refers_to_fullname(node.callee, 'builtins.isinstance'): expr = node.args[0] if expr.literal == LITERAL_TYPE: vartype = type_map[expr] type = get_isinstance_type(node.args[1], type_map) - if type: - elsetype = vartype - if vartype: - if is_proper_subtype(vartype, type): - return {expr: type}, None - elif not is_overlapping_types(vartype, type): - return None, {expr: elsetype} - else: - elsetype = restrict_subtype_away(vartype, type) - return {expr: type}, {expr: elsetype} - else: - # An isinstance check, but we don't understand the type - if weak: - return {expr: AnyType()}, {expr: vartype} + return split_types(vartype, type, expr) + elif (isinstance(node, ComparisonExpr) and any(is_none(n) for n in node.operands)): + # Check for `x is None` and `x is not None`. + is_not = node.operators == ['is not'] + if is_not or node.operators == ['is']: + if_vars = {} + else_vars = {} + for expr in node.operands: + if expr.literal == LITERAL_TYPE and not is_none(expr): + # This should only be true at most once: there should be + # two elements in node.operands, and at least one of them + # should represent a None. + vartype = type_map[expr] + if_vars, else_vars = split_types(vartype, NoneTyp(), expr) + break + + if is_not: + if_vars, else_vars = else_vars, if_vars + return if_vars, else_vars + elif (isinstance(node, RefExpr)): + # The type could be falsy, so we can't deduce anything new about the else branch + vartype = type_map[node] + _, if_vars = split_types(vartype, NoneTyp(), node) + return if_vars, {} elif isinstance(node, OpExpr) and node.op == 'and': left_if_vars, right_else_vars = find_isinstance_check( node.left, diff --git a/mypy/test/data/check-modules.test b/mypy/test/data/check-modules.test index 742b3465cd80..33e14ea9d9d4 100644 --- a/mypy/test/data/check-modules.test +++ b/mypy/test/data/check-modules.test @@ -61,7 +61,7 @@ class Bad: pass [case testImportWithinBlock] import typing -if None: +if 0: import m m.a = m.b # E: Incompatible types in assignment (expression has type "B", variable has type "A") m.a = m.a diff --git a/mypy/test/data/check-optional.test b/mypy/test/data/check-optional.test index ff4046186dbd..f358067f2528 100644 --- a/mypy/test/data/check-optional.test +++ b/mypy/test/data/check-optional.test @@ -43,6 +43,50 @@ else: g(x) [builtins fixtures/isinstance.py] +[case testIfCases] +from typing import Optional +def f(a: int) -> None: pass +def g(a: None) -> None: pass +x = None # type: Optional[int] +if x: + f(x) +else: + g(x) # E: Argument 1 to "g" has incompatible type "Optional[int]"; expected None +[builtins fixtures/bool.py] + +[case testIfNotCases] +from typing import Optional +def f(a: int) -> None: pass +def g(a: None) -> None: pass +x = None # type: Optional[int] +if not x: + g(x) # E: Argument 1 to "g" has incompatible type "Optional[int]"; expected None +else: + f(x) +[builtins fixtures/bool.py] + +[case testIsNotNoneCases] +from typing import Optional +def f(a: int) -> None: pass +def g(a: None) -> None: pass +x = None # type: Optional[int] +if x is not None: + f(x) +else: + g(x) +[builtins fixtures/bool.py] + +[case testIsNoneCases] +from typing import Optional +def f(a: int) -> None: pass +def g(a: None) -> None: pass +x = None # type: Optional[int] +if x is None: + g(x) +else: + f(x) +[builtins fixtures/bool.py] + [case testLambdaReturningNone] f = lambda: None x = f() From 60478602c8eeb612b42940fd31162be088912cb7 Mon Sep 17 00:00:00 2001 From: David Fisher Date: Fri, 20 May 2016 15:20:50 -0700 Subject: [PATCH 08/27] fixup todos --- mypy/checker.py | 4 +--- mypy/meet.py | 12 ++++++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 229595fd9d65..3020a1869c41 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1443,8 +1443,7 @@ def infer_variable_type(self, name: Var, lvalue: Node, self.set_inferred_type(name, lvalue, init_type) def infer_partial_type(self, name: Var, lvalue: Node, init_type: Type) -> bool: - if isinstance(init_type, NoneTyp): - # TODO(ddfisher): make pass UnboundType to third arg if class variable + if isinstance(init_type, (NoneTyp, UninhabitedType)): partial_type = PartialType(None, name, [init_type]) elif isinstance(init_type, Instance): fullname = init_type.type.fullname() @@ -1547,7 +1546,6 @@ def try_infer_partial_type_from_indexed_assignment( typename = type_type.fullname() if typename == 'builtins.dict': # TODO: Don't infer things twice. - # TODO(ddfisher): fixup strict optional stuff key_type = self.accept(lvalue.index) value_type = self.accept(rvalue) full_key_type = UnionType.make_simplified_union([key_type, var.type.inner_types[0]]) diff --git a/mypy/meet.py b/mypy/meet.py index 8f1e68fa71d2..224d15c839c8 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -112,11 +112,13 @@ def __init__(self, s: Type) -> None: self.s = s def visit_unbound_type(self, t: UnboundType) -> Type: - # TODO(ddfisher): understand what's going on here if isinstance(self.s, Void) or isinstance(self.s, ErrorType): return ErrorType() elif isinstance(self.s, NoneTyp): - return self.s + if experimental.STRICT_OPTIONAL: + return AnyType() + else: + return self.s else: return AnyType() @@ -163,10 +165,12 @@ def visit_uninhabited_type(self, t: UninhabitedType) -> Type: return t def visit_deleted_type(self, t: DeletedType) -> Type: - # TODO(ddfisher): understand what's going on here if not isinstance(self.s, Void) and not isinstance(self.s, ErrorType): if isinstance(self.s, NoneTyp): - return self.s + if experimental.STRICT_OPTIONAL: + return t + else: + return self.s else: return t else: From d60c93f8cebbec1d531636d554f72ff46ee8ee71 Mon Sep 17 00:00:00 2001 From: David Fisher Date: Fri, 20 May 2016 16:35:05 -0700 Subject: [PATCH 09/27] allow None class variable declarations --- mypy/checker.py | 9 ++++++++- mypy/test/data/check-optional.test | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 3020a1869c41..69c92ee795e6 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1184,7 +1184,14 @@ def check_assignment(self, lvalue: Node, rvalue: Node, infer_lvalue_type: bool = # an error will be reported elsewhere. self.infer_partial_type(lvalue_type.var, lvalue, rvalue_type) return - rvalue_type = self.check_simple_assignment(lvalue_type, rvalue, lvalue) + if (isinstance(rvalue, NameExpr) and rvalue.fullname == 'builtins.None' and + isinstance(lvalue, NameExpr) and + isinstance(lvalue.node, Var) and + lvalue.node.is_initialized_in_class): + # Allow None's to be assigned to class variables with non-Optional types. + rvalue_type = lvalue_type + else: + rvalue_type = self.check_simple_assignment(lvalue_type, rvalue, lvalue) if rvalue_type and infer_lvalue_type: self.binder.assign_type(lvalue, rvalue_type, diff --git a/mypy/test/data/check-optional.test b/mypy/test/data/check-optional.test index f358067f2528..190ded0a85f0 100644 --- a/mypy/test/data/check-optional.test +++ b/mypy/test/data/check-optional.test @@ -142,3 +142,19 @@ x = {} x["bar"] = 1 x() # E: Dict[str, int] not callable [builtins fixtures/dict.py] + +[case testNoneClassVariable] +from typing import Optional +class C: + x = None # type: int + def __init__(self) -> None: + self.x = 0 + +[case testNoneClassVariableInInit] +from typing import Optional +class C: + x = None # type: int + def __init__(self) -> None: + self.x = None # E: Incompatible types in assignment (expression has type None, variable has type "int") +[out] +main: note: In member "__init__" of class "C": From 07cce60b4c3dd8267ec7d97071123d4b41cb2d03 Mon Sep 17 00:00:00 2001 From: David Fisher Date: Fri, 20 May 2016 16:43:53 -0700 Subject: [PATCH 10/27] Rename experimental -> experiments --- mypy/checker.py | 8 ++++---- mypy/{experimental.py => experiments.py} | 0 mypy/join.py | 6 +++--- mypy/main.py | 4 ++-- mypy/meet.py | 16 ++++++++-------- mypy/parse.py | 4 ++-- mypy/solve.py | 4 ++-- mypy/subtypes.py | 4 ++-- mypy/test/data/check-lists.test | 2 +- mypy/test/testcheck.py | 6 +++--- mypy/typeanal.py | 6 +++--- mypy/types.py | 4 ++-- 12 files changed, 32 insertions(+), 32 deletions(-) rename mypy/{experimental.py => experiments.py} (100%) diff --git a/mypy/checker.py b/mypy/checker.py index ecd831e4006a..b24491cb4340 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -53,7 +53,7 @@ from mypy.treetransform import TransformVisitor from mypy.meet import meet_simple, nearest_builtin_ancestor, is_overlapping_types -from mypy import experimental +from mypy import experiments T = TypeVar('T') @@ -1190,7 +1190,7 @@ def check_assignment(self, lvalue: Node, rvalue: Node, infer_lvalue_type: bool = partial_types = self.find_partial_types(var) if partial_types is not None: if not self.current_node_deferred: - if experimental.STRICT_OPTIONAL: + if experiments.STRICT_OPTIONAL: var.type = UnionType.make_simplified_union([rvalue_type, NoneTyp()]) else: var.type = rvalue_type @@ -1887,7 +1887,7 @@ def analyze_iterable_item_type(self, expr: Node) -> Type: self.check_not_void(iterable, expr) if isinstance(iterable, TupleType): - if experimental.STRICT_OPTIONAL: + if experiments.STRICT_OPTIONAL: joined = UninhabitedType() # type: Type else: joined = NoneTyp() @@ -2316,7 +2316,7 @@ def leave_partial_types(self) -> None: partial_types = self.partial_types.pop() if not self.current_node_deferred: for var, context in partial_types.items(): - if experimental.STRICT_OPTIONAL and cast(PartialType, var.type).type is None: + if experiments.STRICT_OPTIONAL and cast(PartialType, var.type).type is None: # None partial type: assume variable is intended to have type None var.type = NoneTyp() else: diff --git a/mypy/experimental.py b/mypy/experiments.py similarity index 100% rename from mypy/experimental.py rename to mypy/experiments.py diff --git a/mypy/join.py b/mypy/join.py index 30ebd28f1760..6d3a63f2173f 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -10,7 +10,7 @@ from mypy.maptype import map_instance_to_supertype from mypy.subtypes import is_subtype, is_equivalent, is_subtype_ignoring_tvars -from mypy import experimental +from mypy import experiments def join_simple(declaration: Type, s: Type, t: Type) -> Type: @@ -107,7 +107,7 @@ def visit_void(self, t: Void) -> Type: return ErrorType() def visit_none_type(self, t: NoneTyp) -> Type: - if experimental.STRICT_OPTIONAL: + if experiments.STRICT_OPTIONAL: if isinstance(self.s, NoneTyp): return t else: @@ -335,7 +335,7 @@ def join_type_list(types: List[Type]) -> Type: # with all variable length tuples, and this makes it possible. A better approach # would be to use a special bottom type, which we do when strict Optional # checking is enabled. - if experimental.STRICT_OPTIONAL: + if experiments.STRICT_OPTIONAL: return UninhabitedType() else: return NoneTyp() diff --git a/mypy/main.py b/mypy/main.py index c1888e37577f..c9ac136f7653 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -10,7 +10,7 @@ from mypy import build from mypy import defaults from mypy import git -from mypy import experimental +from mypy import experiments from mypy.build import BuildSource, BuildResult, PYTHON_EXTENSIONS from mypy.errors import CompileError, set_drop_into_pdb @@ -252,7 +252,7 @@ def parse_version(v): if args.incremental: options.build_flags.append(build.INCREMENTAL) if args.strict_optional: - experimental.STRICT_OPTIONAL = True + experiments.STRICT_OPTIONAL = True # Set reports. for flag, val in vars(args).items(): diff --git a/mypy/meet.py b/mypy/meet.py index 224d15c839c8..1cd916849b86 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -9,7 +9,7 @@ from mypy.subtypes import is_subtype from mypy.nodes import TypeInfo -from mypy import experimental +from mypy import experiments # TODO Describe this module. @@ -31,7 +31,7 @@ def meet_simple(s: Type, t: Type, default_right: bool = True) -> Type: if isinstance(s, UnionType): return UnionType.make_simplified_union([meet_types(x, t) for x in s.items]) elif not is_overlapping_types(s, t, use_promotions=True): - if experimental.STRICT_OPTIONAL: + if experiments.STRICT_OPTIONAL: return UninhabitedType() else: return NoneTyp() @@ -115,7 +115,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: if isinstance(self.s, Void) or isinstance(self.s, ErrorType): return ErrorType() elif isinstance(self.s, NoneTyp): - if experimental.STRICT_OPTIONAL: + if experiments.STRICT_OPTIONAL: return AnyType() else: return self.s @@ -149,7 +149,7 @@ def visit_void(self, t: Void) -> Type: return ErrorType() def visit_none_type(self, t: NoneTyp) -> Type: - if experimental.STRICT_OPTIONAL: + if experiments.STRICT_OPTIONAL: if isinstance(self.s, NoneTyp) or (isinstance(self.s, Instance) and self.s.type.fullname() == 'builtins.object'): return t @@ -167,7 +167,7 @@ def visit_uninhabited_type(self, t: UninhabitedType) -> Type: def visit_deleted_type(self, t: DeletedType) -> Type: if not isinstance(self.s, Void) and not isinstance(self.s, ErrorType): if isinstance(self.s, NoneTyp): - if experimental.STRICT_OPTIONAL: + if experiments.STRICT_OPTIONAL: return t else: return self.s @@ -197,7 +197,7 @@ def visit_instance(self, t: Instance) -> Type: args.append(self.meet(t.args[i], si.args[i])) return Instance(t.type, args) else: - if experimental.STRICT_OPTIONAL: + if experiments.STRICT_OPTIONAL: return UninhabitedType() else: return NoneTyp() @@ -208,7 +208,7 @@ def visit_instance(self, t: Instance) -> Type: # See also above comment. return self.s else: - if experimental.STRICT_OPTIONAL: + if experiments.STRICT_OPTIONAL: return UninhabitedType() else: return NoneTyp() @@ -244,7 +244,7 @@ def default(self, typ): elif isinstance(typ, Void) or isinstance(typ, ErrorType): return ErrorType() else: - if experimental.STRICT_OPTIONAL: + if experiments.STRICT_OPTIONAL: return UninhabitedType() else: return NoneTyp() diff --git a/mypy/parse.py b/mypy/parse.py index 8f2a7d738078..7f2da2b38c6e 100644 --- a/mypy/parse.py +++ b/mypy/parse.py @@ -37,7 +37,7 @@ parse_type, parse_types, parse_signature, TypeParseError, parse_str_as_signature ) -from mypy import experimental +from mypy import experiments precedence = { @@ -792,7 +792,7 @@ def parse_normal_arg(self, require_named: bool, return Argument(variable, type, initializer, kind), require_named def set_type_optional(self, type: Type, initializer: Node) -> None: - if not experimental.STRICT_OPTIONAL: + if not experiments.STRICT_OPTIONAL: return # Indicate that type should be wrapped in an Optional if arg is initialized to None. optional = isinstance(initializer, NameExpr) and initializer.name == 'None' diff --git a/mypy/solve.py b/mypy/solve.py index 8199d1bea6c6..9e70e6361e2e 100644 --- a/mypy/solve.py +++ b/mypy/solve.py @@ -8,7 +8,7 @@ from mypy.meet import meet_types from mypy.subtypes import is_subtype -from mypy import experimental +from mypy import experiments def solve_constraints(vars: List[int], constraints: List[Constraint], @@ -60,7 +60,7 @@ def solve_constraints(vars: List[int], constraints: List[Constraint], else: # No constraints for type variable -- type 'None' is the most specific type. if strict: - if experimental.STRICT_OPTIONAL: + if experiments.STRICT_OPTIONAL: candidate = UninhabitedType() else: candidate = NoneTyp() diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 1e851d5136d9..e7980f710083 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -13,7 +13,7 @@ from mypy.nodes import CONTRAVARIANT, COVARIANT from mypy.maptype import map_instance_to_supertype -from mypy import experimental +from mypy import experiments TypeParameterChecker = Callable[[Type, Type, int], bool] @@ -101,7 +101,7 @@ def visit_void(self, left: Void) -> bool: return isinstance(self.right, Void) def visit_none_type(self, left: NoneTyp) -> bool: - if experimental.STRICT_OPTIONAL: + if experiments.STRICT_OPTIONAL: return (isinstance(self.right, NoneTyp) or (isinstance(self.right, Instance) and self.right.type.fullname() == 'builtins.object')) diff --git a/mypy/test/data/check-lists.test b/mypy/test/data/check-lists.test index 62c3bf6a6694..3bafa99245d1 100644 --- a/mypy/test/data/check-lists.test +++ b/mypy/test/data/check-lists.test @@ -61,4 +61,4 @@ class B: pass class C: pass [builtins fixtures/list.py] [out] -main: note: In function "f": \ No newline at end of file +main: note: In function "f": diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index 36778586350c..14ba91c77b5f 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -19,7 +19,7 @@ ) from mypy.errors import CompileError -from mypy import experimental +from mypy import experiments # List of files that contain test case descriptions. files = [ @@ -82,10 +82,10 @@ def run_test(self, testcase: DataDrivenTestCase) -> None: self.run_test_once(testcase, 2) elif optional: try: - experimental.STRICT_OPTIONAL = True + experiments.STRICT_OPTIONAL = True self.run_test_once(testcase) finally: - experimental.STRICT_OPTIONAL = False + experiments.STRICT_OPTIONAL = False else: self.run_test_once(testcase) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 0c3818d381dc..811486307686 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -16,7 +16,7 @@ from mypy.exprtotype import expr_to_unanalyzed_type, TypeTranslationError from mypy.subtypes import satisfies_upper_bound from mypy import nodes -from mypy import experimental +from mypy import experiments type_constructors = ['typing.Tuple', 'typing.Union', 'typing.Callable'] @@ -97,7 +97,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: tvar_expr.variance, t.line) elif fullname == 'builtins.None': - if experimental.STRICT_OPTIONAL: + if experiments.STRICT_OPTIONAL: if t.ret_type: return Void() else: @@ -122,7 +122,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: if len(t.args) != 1: self.fail('Optional[...] must have exactly one type argument', t) items = self.anal_array(t.args) - if experimental.STRICT_OPTIONAL: + if experiments.STRICT_OPTIONAL: return UnionType.make_simplified_union([items[0], NoneTyp()]) else: # Without strict Optional checking Optional[t] is just an alias for t. diff --git a/mypy/types.py b/mypy/types.py index 4aa87feda134..e2ca4d52cfca 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -6,7 +6,7 @@ import mypy.nodes from mypy.nodes import INVARIANT, SymbolNode -from mypy import experimental +from mypy import experiments T = TypeVar('T') @@ -714,7 +714,7 @@ def make_union(items: List[Type], line: int = -1) -> Type: elif len(items) == 1: return items[0] else: - if experimental.STRICT_OPTIONAL: + if experiments.STRICT_OPTIONAL: return UninhabitedType() else: return Void() From 15cad446f8100dcb8e529173f07bf623347df744 Mon Sep 17 00:00:00 2001 From: David Fisher Date: Fri, 20 May 2016 16:46:34 -0700 Subject: [PATCH 11/27] add missing type annotations --- mypy/checker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index b24491cb4340..6b6287e840fe 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2414,8 +2414,8 @@ def is_none(n: Node) -> bool: # Check for `x is None` and `x is not None`. is_not = node.operators == ['is not'] if is_not or node.operators == ['is']: - if_vars = {} - else_vars = {} + if_vars = {} # type: Dict[Node, Type] + else_vars = {} # type: Dict[Node, Type] for expr in node.operands: if expr.literal == LITERAL_TYPE and not is_none(expr): # This should only be true at most once: there should be From 53ebea076119ba2596222389d62f86516bdcc949 Mon Sep 17 00:00:00 2001 From: David Fisher Date: Sat, 21 May 2016 12:48:00 -0700 Subject: [PATCH 12/27] fix lint errors really not convinced that the line length is set right --- mypy/checker.py | 12 ++++++++---- mypy/checkexpr.py | 10 +++++++--- mypy/types.py | 1 + 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 6b6287e840fe..0bb060dde1b4 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1191,7 +1191,8 @@ def check_assignment(self, lvalue: Node, rvalue: Node, infer_lvalue_type: bool = if partial_types is not None: if not self.current_node_deferred: if experiments.STRICT_OPTIONAL: - var.type = UnionType.make_simplified_union([rvalue_type, NoneTyp()]) + var.type = UnionType.make_simplified_union( + [rvalue_type, NoneTyp()]) else: var.type = rvalue_type else: @@ -1572,9 +1573,12 @@ def try_infer_partial_type_from_indexed_assignment( # TODO: Don't infer things twice. key_type = self.accept(lvalue.index) value_type = self.accept(rvalue) - full_key_type = UnionType.make_simplified_union([key_type, var.type.inner_types[0]]) - full_value_type = UnionType.make_simplified_union([value_type, var.type.inner_types[0]]) - if is_valid_inferred_type(full_key_type) and is_valid_inferred_type(full_value_type): + full_key_type = UnionType.make_simplified_union( + [key_type, var.type.inner_types[0]]) + full_value_type = UnionType.make_simplified_union( + [value_type, var.type.inner_types[0]]) + if (is_valid_inferred_type(full_key_type) and + is_valid_inferred_type(full_value_type)): if not self.current_node_deferred: var.type = self.named_generic_type('builtins.dict', [full_key_type, full_value_type]) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index d6e9f14cbedf..c228abae6d38 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -165,7 +165,8 @@ def try_infer_partial_type(self, e: CallExpr) -> None: if (typename in self.item_args and methodname in self.item_args[typename] and e.arg_kinds == [ARG_POS]): item_type = self.accept(e.args[0]) - full_item_type = UnionType.make_simplified_union([item_type, partial_type.inner_types[0]]) + full_item_type = UnionType.make_simplified_union( + [item_type, partial_type.inner_types[0]]) if mypy.checker.is_valid_inferred_type(full_item_type): var.type = self.chk.named_generic_type(typename, [full_item_type]) del partial_types[var] @@ -176,8 +177,11 @@ def try_infer_partial_type(self, e: CallExpr) -> None: if isinstance(arg_type, Instance): arg_typename = arg_type.type.fullname() if arg_typename in self.container_args[typename][methodname]: - full_item_types = [UnionType.make_simplified_union([item_type, prev_type]) - for item_type, prev_type in zip(arg_type.args, partial_type.inner_types)] + full_item_types = [ + UnionType.make_simplified_union([item_type, prev_type]) + for item_type, prev_type + in zip(arg_type.args, partial_type.inner_types) + ] if all(mypy.checker.is_valid_inferred_type(item_type) for item_type in full_item_types): var.type = self.chk.named_generic_type(typename, diff --git a/mypy/types.py b/mypy/types.py index e2ca4d52cfca..9ab3fac59875 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -217,6 +217,7 @@ def deserialize(cls, data: JsonDict) -> 'Void': assert data['.class'] == 'Void' return Void() + class UninhabitedType(Type): """This type has no members. """ From 5625c751c5cdd5e27120a0d71eceb5c3433307ae Mon Sep 17 00:00:00 2001 From: David Fisher Date: Mon, 23 May 2016 11:46:33 -0700 Subject: [PATCH 13/27] rename UnboundType.ret_type -> is_ret_type --- mypy/parse.py | 2 +- mypy/typeanal.py | 2 +- mypy/types.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mypy/parse.py b/mypy/parse.py index 7f2da2b38c6e..b55fe7956954 100644 --- a/mypy/parse.py +++ b/mypy/parse.py @@ -485,7 +485,7 @@ def parse_function(self, no_type_checks: bool=False) -> FuncDef: return None if typ and isinstance(typ.ret_type, UnboundType): - typ.ret_type.ret_type = True + typ.ret_type.is_ret_type = True node = FuncDef(name, args, body, typ) node.set_line(def_tok) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 811486307686..2a700cbbf287 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -98,7 +98,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: t.line) elif fullname == 'builtins.None': if experiments.STRICT_OPTIONAL: - if t.ret_type: + if t.is_ret_type: return Void() else: return NoneTyp() diff --git a/mypy/types.py b/mypy/types.py index 9ab3fac59875..6704793f514a 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -104,20 +104,20 @@ class UnboundType(Type): # should this type be wrapped in an Optional? optional = False # is this type a return type? - ret_type = False + is_ret_type = False def __init__(self, name: str, args: List[Type] = None, line: int = -1, optional: bool = False, - ret_type: bool = False) -> None: + is_ret_type: bool = False) -> None: if not args: args = [] self.name = name self.args = args self.optional = optional - self.ret_type = ret_type + self.is_ret_type = is_ret_type super().__init__(line) def accept(self, visitor: 'TypeVisitor[T]') -> T: From 157f57d20f086adc3c62f907bd58f79ebc32f634 Mon Sep 17 00:00:00 2001 From: David Fisher Date: Mon, 23 May 2016 13:52:54 -0700 Subject: [PATCH 14/27] fix lack of type narrowing for member variables --- mypy/checker.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 0bb060dde1b4..c46b0cfd15fd 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -225,14 +225,14 @@ def get_declaration(self, expr: Any) -> Type: else: return self.frames[0].get(expr.literal_hash) - def assign_type(self, expr: Node, type: Type, + def assign_type(self, expr: Node, + type: Type, + declared_type: Type, restrict_any: bool = False) -> None: if not expr.literal: return self.invalidate_dependencies(expr) - declared_type = self.get_declaration(expr) - if declared_type is None: # Not sure why this happens. It seems to mainly happen in # member initialization. @@ -1212,7 +1212,9 @@ def check_assignment(self, lvalue: Node, rvalue: Node, infer_lvalue_type: bool = rvalue_type = self.check_simple_assignment(lvalue_type, rvalue, lvalue) if rvalue_type and infer_lvalue_type: - self.binder.assign_type(lvalue, rvalue_type, + self.binder.assign_type(lvalue, + rvalue_type, + lvalue_type, self.typing_mode_weak()) elif index_lvalue: self.check_indexed_assignment(index_lvalue, rvalue, rvalue) @@ -1445,7 +1447,7 @@ def infer_variable_type(self, name: Var, lvalue: Node, """Infer the type of initialized variables from initializer type.""" if self.typing_mode_weak(): self.set_inferred_type(name, lvalue, AnyType()) - self.binder.assign_type(lvalue, init_type, True) + self.binder.assign_type(lvalue, init_type, self.binder.get_declaration(lvalue), True) elif isinstance(init_type, Void): self.check_not_void(init_type, context) self.set_inference_error_fallback_type(name, lvalue, init_type, context) @@ -1945,7 +1947,9 @@ def flatten(t: Node) -> List[Node]: s.expr.accept(self) for elt in flatten(s.expr): if isinstance(elt, NameExpr): - self.binder.assign_type(elt, DeletedType(source=elt.name), + self.binder.assign_type(elt, + DeletedType(source=elt.name), + self.binder.get_declaration(elt), self.typing_mode_weak()) return None From 0a2c4e11b4a0e47299757d1a4ec09265d9b342c0 Mon Sep 17 00:00:00 2001 From: David Fisher Date: Tue, 24 May 2016 13:18:55 -0700 Subject: [PATCH 15/27] gate new isinstance checks behind experiments flag --- mypy/checker.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index c46b0cfd15fd..934c9db6a6fa 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2418,7 +2418,8 @@ def is_none(n: Node) -> bool: vartype = type_map[expr] type = get_isinstance_type(node.args[1], type_map) return split_types(vartype, type, expr) - elif (isinstance(node, ComparisonExpr) and any(is_none(n) for n in node.operands)): + elif (isinstance(node, ComparisonExpr) and any(is_none(n) for n in node.operands) and + experiments.STRICT_OPTIONAL): # Check for `x is None` and `x is not None`. is_not = node.operators == ['is not'] if is_not or node.operators == ['is']: @@ -2436,7 +2437,7 @@ def is_none(n: Node) -> bool: if is_not: if_vars, else_vars = else_vars, if_vars return if_vars, else_vars - elif (isinstance(node, RefExpr)): + elif isinstance(node, RefExpr) and experiments.STRICT_OPTIONAL: # The type could be falsy, so we can't deduce anything new about the else branch vartype = type_map[node] _, if_vars = split_types(vartype, NoneTyp(), node) From b6f60324ba084612a744a1fdafb16eed6253e3f7 Mon Sep 17 00:00:00 2001 From: David Fisher Date: Tue, 24 May 2016 13:24:49 -0700 Subject: [PATCH 16/27] fix tests broken by member type variable narrowing --- mypy/test/data/check-dynamic-typing.test | 2 +- mypy/test/data/check-inference.test | 5 ++++- mypy/test/data/typexport-basic.test | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/mypy/test/data/check-dynamic-typing.test b/mypy/test/data/check-dynamic-typing.test index c58e1d57306c..8f41e2ed6260 100644 --- a/mypy/test/data/check-dynamic-typing.test +++ b/mypy/test/data/check-dynamic-typing.test @@ -191,7 +191,7 @@ a = d.foo(a()) # E: "A" not callable a = d.x a = d.foo(a, a) d.x = a -d.x.y.z +d.x.y.z # E: "A" has no attribute "y" class A: pass [out] diff --git a/mypy/test/data/check-inference.test b/mypy/test/data/check-inference.test index afd390d1154a..a05aef2d239c 100644 --- a/mypy/test/data/check-inference.test +++ b/mypy/test/data/check-inference.test @@ -1400,13 +1400,16 @@ main: note: In member "f" of class "A": [case testAttributePartiallyInitializedToNoneWithMissingAnnotation] class A: def f(self) -> None: - self.x = None # E: Need type annotation for variable + self.x = None def g(self) -> None: self.x = 1 self.x() [out] main: note: In member "f" of class "A": +main:3: error: Need type annotation for variable +main: note: In member "g" of class "A": +main:7: error: "int" not callable [case testGlobalInitializedToNoneSetFromFunction] a = None diff --git a/mypy/test/data/typexport-basic.test b/mypy/test/data/typexport-basic.test index b284a35cdc1e..0264bc981fc1 100644 --- a/mypy/test/data/typexport-basic.test +++ b/mypy/test/data/typexport-basic.test @@ -232,7 +232,7 @@ NameExpr(6) : A NameExpr(6) : A MemberExpr(7) : A MemberExpr(7) : A -MemberExpr(7) : Any +MemberExpr(7) : A NameExpr(7) : A NameExpr(7) : A From 35a6b2e745a9436f119f826e159f28daa3a4107b Mon Sep 17 00:00:00 2001 From: David Fisher Date: Tue, 24 May 2016 16:55:32 -0700 Subject: [PATCH 17/27] strengthen tests per Jukka's suggestions --- mypy/test/data/check-optional.test | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mypy/test/data/check-optional.test b/mypy/test/data/check-optional.test index 190ded0a85f0..718160d70388 100644 --- a/mypy/test/data/check-optional.test +++ b/mypy/test/data/check-optional.test @@ -63,6 +63,7 @@ if not x: g(x) # E: Argument 1 to "g" has incompatible type "Optional[int]"; expected None else: f(x) + x() # E: "int" not callable [builtins fixtures/bool.py] [case testIsNotNoneCases] @@ -72,8 +73,10 @@ def g(a: None) -> None: pass x = None # type: Optional[int] if x is not None: f(x) + x() # E: "int" not callable else: g(x) + x() # E: None not callable [builtins fixtures/bool.py] [case testIsNoneCases] From 8b49b8f70aff0e37d81a5905f60537d219f1201a Mon Sep 17 00:00:00 2001 From: David Fisher Date: Wed, 25 May 2016 18:16:48 -0700 Subject: [PATCH 18/27] Fixup type lattice per Reid's comments --- mypy/join.py | 10 ++++++++-- mypy/meet.py | 9 ++++++++- mypy/subtypes.py | 5 ++--- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/mypy/join.py b/mypy/join.py index 6d3a63f2173f..24a23d1548d7 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -34,6 +34,9 @@ def join_simple(declaration: Type, s: Type, t: Type) -> Type: if isinstance(s, NoneTyp) and not isinstance(t, NoneTyp): s, t = t, s + if isinstance(s, UninhabitedType) and not isinstance(t, UninhabitedType): + s, t = t, s + value = t.accept(TypeJoinVisitor(s)) if value is None: @@ -108,7 +111,7 @@ def visit_void(self, t: Void) -> Type: def visit_none_type(self, t: NoneTyp) -> Type: if experiments.STRICT_OPTIONAL: - if isinstance(self.s, NoneTyp): + if isinstance(self.s, (NoneTyp, UninhabitedType)): return t else: return self.default(self.s) @@ -119,7 +122,10 @@ def visit_none_type(self, t: NoneTyp) -> Type: return self.default(self.s) def visit_uninhabited_type(self, t: UninhabitedType) -> Type: - return self.s + if not isinstance(self.s, Void): + return self.s + else: + return self.default(self.s) def visit_deleted_type(self, t: DeletedType) -> Type: if not isinstance(self.s, Void): diff --git a/mypy/meet.py b/mypy/meet.py index 1cd916849b86..779c8d4894f0 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -119,6 +119,8 @@ def visit_unbound_type(self, t: UnboundType) -> Type: return AnyType() else: return self.s + elif isinstance(self.s, UninhabitedType): + return self.s else: return AnyType() @@ -162,7 +164,10 @@ def visit_none_type(self, t: NoneTyp) -> Type: return ErrorType() def visit_uninhabited_type(self, t: UninhabitedType) -> Type: - return t + if not isinstance(self.s, Void) and not isinstance(self.s, ErrorType): + return t + else: + return ErrorType() def visit_deleted_type(self, t: DeletedType) -> Type: if not isinstance(self.s, Void) and not isinstance(self.s, ErrorType): @@ -171,6 +176,8 @@ def visit_deleted_type(self, t: DeletedType) -> Type: return t else: return self.s + elif isinstance(self.s, UninhabitedType): + return self.s else: return t else: diff --git a/mypy/subtypes.py b/mypy/subtypes.py index e7980f710083..e88d150fcafd 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -103,13 +103,12 @@ def visit_void(self, left: Void) -> bool: def visit_none_type(self, left: NoneTyp) -> bool: if experiments.STRICT_OPTIONAL: return (isinstance(self.right, NoneTyp) or - (isinstance(self.right, Instance) and - self.right.type.fullname() == 'builtins.object')) + is_named_instance(self.right, 'builtins.object')) else: return not isinstance(self.right, Void) def visit_uninhabited_type(self, left: UninhabitedType) -> bool: - return True + return not isinstance(self.right, Void) def visit_erased_type(self, left: ErasedType) -> bool: return True From d9996da8b8ad36dad6cbee7f41b2ec26371e9a19 Mon Sep 17 00:00:00 2001 From: David Fisher Date: Wed, 25 May 2016 18:45:56 -0700 Subject: [PATCH 19/27] Address Reid's remaining comments --- mypy/checker.py | 45 +++++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 934c9db6a6fa..93a5c3048aab 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1578,7 +1578,7 @@ def try_infer_partial_type_from_indexed_assignment( full_key_type = UnionType.make_simplified_union( [key_type, var.type.inner_types[0]]) full_value_type = UnionType.make_simplified_union( - [value_type, var.type.inner_types[0]]) + [value_type, var.type.inner_types[1]]) if (is_valid_inferred_type(full_key_type) and is_valid_inferred_type(full_value_type)): if not self.current_node_deferred: @@ -2375,8 +2375,8 @@ def method_type(self, func: FuncBase) -> FunctionLike: def find_isinstance_check(node: Node, type_map: Dict[Node, Type], - weak: bool=False) \ - -> Tuple[Optional[Dict[Node, Type]], Optional[Dict[Node, Type]]]: + weak: bool=False + ) -> Tuple[Optional[Dict[Node, Type]], Optional[Dict[Node, Type]]]: """Find any isinstance checks (within a chain of ands). Includes implicit and explicit checks for None. @@ -2388,23 +2388,32 @@ def find_isinstance_check(node: Node, Guaranteed to not return None, None. (But may return {}, {}) """ - def split_types(current_type: Optional[Type], type_if_true: Optional[Type], expr: Node) \ - -> Tuple[Optional[Dict[Node, Type]], Optional[Dict[Node, Type]]]: - if type_if_true: + def conditional_type_map(expr: Node, + current_type: Optional[Type], + proposed_type: Optional[Type] + ) -> Tuple[Optional[Dict[Node, Type]], Optional[Dict[Node, Type]]]: + """Takes in an expression, the current type of the expression, and a + proposed type of that expression. + + Returns a 2-tuple: The first element is a map from the expression to + the proposed type, if the expression can be the proposed type. The + second element is a map from the expression to the type it would hold + if it was not the proposed type, if any.""" + if proposed_type: if current_type: - if is_proper_subtype(current_type, type_if_true): - return {expr: type_if_true}, None - elif not is_overlapping_types(current_type, type_if_true): + if is_proper_subtype(current_type, proposed_type): + return {expr: proposed_type}, None + elif not is_overlapping_types(current_type, proposed_type): return None, {expr: current_type} else: - type_if_false = restrict_subtype_away(current_type, type_if_true) - return {expr: type_if_true}, {expr: type_if_false} + remaining_type = restrict_subtype_away(current_type, proposed_type) + return {expr: proposed_type}, {expr: remaining_type} else: - return {expr: type_if_true}, {} + return {expr: proposed_type}, {} else: # An isinstance check, but we don't understand the type if weak: - return {expr: AnyType()}, {expr: vartype} + return {expr: AnyType()}, {expr: current_type} else: return {}, {} @@ -2417,7 +2426,7 @@ def is_none(n: Node) -> bool: if expr.literal == LITERAL_TYPE: vartype = type_map[expr] type = get_isinstance_type(node.args[1], type_map) - return split_types(vartype, type, expr) + return conditional_type_map(expr, vartype, type) elif (isinstance(node, ComparisonExpr) and any(is_none(n) for n in node.operands) and experiments.STRICT_OPTIONAL): # Check for `x is None` and `x is not None`. @@ -2431,7 +2440,7 @@ def is_none(n: Node) -> bool: # two elements in node.operands, and at least one of them # should represent a None. vartype = type_map[expr] - if_vars, else_vars = split_types(vartype, NoneTyp(), expr) + if_vars, else_vars = conditional_type_map(expr, vartype, NoneTyp()) break if is_not: @@ -2440,7 +2449,7 @@ def is_none(n: Node) -> bool: elif isinstance(node, RefExpr) and experiments.STRICT_OPTIONAL: # The type could be falsy, so we can't deduce anything new about the else branch vartype = type_map[node] - _, if_vars = split_types(vartype, NoneTyp(), node) + _, if_vars = conditional_type_map(node, vartype, NoneTyp()) return if_vars, {} elif isinstance(node, OpExpr) and node.op == 'and': left_if_vars, right_else_vars = find_isinstance_check( @@ -2623,6 +2632,10 @@ def is_valid_inferred_type(typ: Type) -> bool: Examples of invalid types include the None type or a type with a None component. """ if is_same_type(typ, NoneTyp()): + # With strict Optional checking, we *may* eventually infer NoneTyp, but + # we only do that if we can't infer a specific Optional type. This + # resolution happens in leave_partial_types when we pop a partial types + # scope. return False if is_same_type(typ, UninhabitedType()): return False From ea46191b516a52063b0a24006189fb9f28c5863f Mon Sep 17 00:00:00 2001 From: David Fisher Date: Thu, 26 May 2016 16:52:22 -0700 Subject: [PATCH 20/27] Generalize some NoneTyp isinstance checks found by Reid --- mypy/checkexpr.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index c228abae6d38..4b17cd65604d 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -5,7 +5,7 @@ from mypy.types import ( Type, AnyType, CallableType, Overloaded, NoneTyp, Void, TypeVarDef, TupleType, Instance, TypeVarType, ErasedType, UnionType, - PartialType, DeletedType, UnboundType + PartialType, DeletedType, UnboundType, UninhabitedType ) from mypy.nodes import ( NameExpr, RefExpr, Var, FuncDef, OverloadedFuncDef, TypeInfo, CallExpr, @@ -378,7 +378,7 @@ def infer_function_type_arguments_using_context( # Only substitute non-None and non-erased types. new_args = [] # type: List[Type] for arg in args: - if isinstance(arg, NoneTyp) or has_erased_component(arg): + if isinstance(arg, (NoneTyp, UninhabitedType)) or has_erased_component(arg): new_args.append(None) else: new_args.append(arg) @@ -437,7 +437,7 @@ def infer_function_type_arguments(self, callee_type: CallableType, # if they shuffle type variables around, as we assume that there is a 1-1 # correspondence with dict type variables. This is a marginal issue and # a little tricky to fix so it's left unfixed for now. - if isinstance(inferred_args[0], NoneTyp): + if isinstance(inferred_args[0], (NoneTyp, UninhabitedType)): inferred_args[0] = self.named_type('builtins.str') elif not is_subtype(self.named_type('builtins.str'), inferred_args[0]): self.msg.fail(messages.KEYWORD_ARGUMENT_REQUIRES_STR_KEY_TYPE, @@ -471,7 +471,7 @@ def infer_function_type_arguments_pass2( # information to infer the argument. Replace them with None values so # that they are not applied yet below. for i, arg in enumerate(inferred_args): - if isinstance(arg, NoneTyp) or isinstance(arg, ErasedType): + if isinstance(arg, (NoneTyp, UninhabitedType)) or isinstance(arg, ErasedType): inferred_args[i] = None callee_type = cast(CallableType, self.apply_generic_arguments( @@ -1678,7 +1678,7 @@ def overload_arg_similarity(actual: Type, formal: Type) -> int: actual = actual.erase_to_union_or_bound() if isinstance(formal, TypeVarType): formal = formal.erase_to_union_or_bound() - if (isinstance(actual, NoneTyp) or isinstance(actual, AnyType) or + if (isinstance(actual, (NoneTyp, UninhabitedType)) or isinstance(actual, AnyType) or isinstance(formal, AnyType) or isinstance(formal, CallableType) or (isinstance(actual, Instance) and actual.type.fallback_to_any)): # These could match anything at runtime. From 161a2bcc36a5eacec62ee773acae73c74fa0453b Mon Sep 17 00:00:00 2001 From: David Fisher Date: Fri, 27 May 2016 14:10:46 -0700 Subject: [PATCH 21/27] Fix other crash as per Reid --- mypy/checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 93a5c3048aab..e47c1ccade73 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2435,7 +2435,7 @@ def is_none(n: Node) -> bool: if_vars = {} # type: Dict[Node, Type] else_vars = {} # type: Dict[Node, Type] for expr in node.operands: - if expr.literal == LITERAL_TYPE and not is_none(expr): + if expr.literal == LITERAL_TYPE and not is_none(expr) and expr in type_map: # This should only be true at most once: there should be # two elements in node.operands, and at least one of them # should represent a None. From a40636e93dcbb14f49b34bc8f338da520d493004 Mon Sep 17 00:00:00 2001 From: David Fisher Date: Mon, 6 Jun 2016 15:55:08 -0700 Subject: [PATCH 22/27] unnest functions --- mypy/checker.py | 78 +++++++++++++++++++++++++------------------------ 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 4b034098d464..16e58a6e8c36 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1214,7 +1214,7 @@ def check_assignment(self, lvalue: Node, rvalue: Node, infer_lvalue_type: bool = # an error will be reported elsewhere. self.infer_partial_type(lvalue_type.var, lvalue, rvalue_type) return - if (isinstance(rvalue, NameExpr) and rvalue.fullname == 'builtins.None' and + if (is_literal_none(rvalue) and isinstance(lvalue, NameExpr) and isinstance(lvalue.node, Var) and lvalue.node.is_initialized_in_class): @@ -2385,6 +2385,40 @@ def method_type(self, func: FuncBase) -> FunctionLike: return method_type_with_fallback(func, self.named_type('builtins.function')) +def conditional_type_map(expr: Node, + current_type: Optional[Type], + proposed_type: Optional[Type], + *, + weak: bool = False + ) -> Tuple[Optional[Dict[Node, Type]], Optional[Dict[Node, Type]]]: + """Takes in an expression, the current type of the expression, and a + proposed type of that expression. + + Returns a 2-tuple: The first element is a map from the expression to + the proposed type, if the expression can be the proposed type. The + second element is a map from the expression to the type it would hold + if it was not the proposed type, if any.""" + if proposed_type: + if current_type: + if is_proper_subtype(current_type, proposed_type): + return {expr: proposed_type}, None + elif not is_overlapping_types(current_type, proposed_type): + return None, {expr: current_type} + else: + remaining_type = restrict_subtype_away(current_type, proposed_type) + return {expr: proposed_type}, {expr: remaining_type} + else: + return {expr: proposed_type}, {} + else: + # An isinstance check, but we don't understand the type + if weak: + return {expr: AnyType()}, {expr: current_type} + else: + return {}, {} + +def is_literal_none(n: Node) -> bool: + return isinstance(n, NameExpr) and n.fullname == 'builtins.None' + def find_isinstance_check(node: Node, type_map: Dict[Node, Type], weak: bool=False @@ -2400,46 +2434,14 @@ def find_isinstance_check(node: Node, Guaranteed to not return None, None. (But may return {}, {}) """ - def conditional_type_map(expr: Node, - current_type: Optional[Type], - proposed_type: Optional[Type] - ) -> Tuple[Optional[Dict[Node, Type]], Optional[Dict[Node, Type]]]: - """Takes in an expression, the current type of the expression, and a - proposed type of that expression. - - Returns a 2-tuple: The first element is a map from the expression to - the proposed type, if the expression can be the proposed type. The - second element is a map from the expression to the type it would hold - if it was not the proposed type, if any.""" - if proposed_type: - if current_type: - if is_proper_subtype(current_type, proposed_type): - return {expr: proposed_type}, None - elif not is_overlapping_types(current_type, proposed_type): - return None, {expr: current_type} - else: - remaining_type = restrict_subtype_away(current_type, proposed_type) - return {expr: proposed_type}, {expr: remaining_type} - else: - return {expr: proposed_type}, {} - else: - # An isinstance check, but we don't understand the type - if weak: - return {expr: AnyType()}, {expr: current_type} - else: - return {}, {} - - def is_none(n: Node) -> bool: - return isinstance(n, NameExpr) and n.fullname == 'builtins.None' - if isinstance(node, CallExpr): if refers_to_fullname(node.callee, 'builtins.isinstance'): expr = node.args[0] if expr.literal == LITERAL_TYPE: vartype = type_map[expr] type = get_isinstance_type(node.args[1], type_map) - return conditional_type_map(expr, vartype, type) - elif (isinstance(node, ComparisonExpr) and any(is_none(n) for n in node.operands) and + return conditional_type_map(expr, vartype, type, weak=weak) + elif (isinstance(node, ComparisonExpr) and any(is_literal_none(n) for n in node.operands) and experiments.STRICT_OPTIONAL): # Check for `x is None` and `x is not None`. is_not = node.operators == ['is not'] @@ -2447,12 +2449,12 @@ def is_none(n: Node) -> bool: if_vars = {} # type: Dict[Node, Type] else_vars = {} # type: Dict[Node, Type] for expr in node.operands: - if expr.literal == LITERAL_TYPE and not is_none(expr) and expr in type_map: + if expr.literal == LITERAL_TYPE and not is_literal_none(expr) and expr in type_map: # This should only be true at most once: there should be # two elements in node.operands, and at least one of them # should represent a None. vartype = type_map[expr] - if_vars, else_vars = conditional_type_map(expr, vartype, NoneTyp()) + if_vars, else_vars = conditional_type_map(expr, vartype, NoneTyp(), weak=weak) break if is_not: @@ -2461,7 +2463,7 @@ def is_none(n: Node) -> bool: elif isinstance(node, RefExpr) and experiments.STRICT_OPTIONAL: # The type could be falsy, so we can't deduce anything new about the else branch vartype = type_map[node] - _, if_vars = conditional_type_map(node, vartype, NoneTyp()) + _, if_vars = conditional_type_map(node, vartype, NoneTyp(), weak=weak) return if_vars, {} elif isinstance(node, OpExpr) and node.op == 'and': left_if_vars, right_else_vars = find_isinstance_check( From 6661f93993b6946ff33e462decbee71d37a0a9b7 Mon Sep 17 00:00:00 2001 From: David Fisher Date: Mon, 6 Jun 2016 16:08:43 -0700 Subject: [PATCH 23/27] Fixup tests; add new test --- mypy/test/data/check-optional.test | 43 ++++++++++++++---------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/mypy/test/data/check-optional.test b/mypy/test/data/check-optional.test index 718160d70388..41b01aa82e3a 100644 --- a/mypy/test/data/check-optional.test +++ b/mypy/test/data/check-optional.test @@ -34,60 +34,47 @@ f(x) # E: Argument 1 to "f" has incompatible type "Optional[int]"; expected "in [case testIsinstanceCases] from typing import Optional -def f(a: int) -> None: pass -def g(a: None) -> None: pass x = None # type: Optional[int] if isinstance(x, int): - f(x) + reveal_type(x) # E: Revealed type is 'builtins.int' else: - g(x) + reveal_type(x) # E: Revealed type is 'builtins.None' [builtins fixtures/isinstance.py] [case testIfCases] from typing import Optional -def f(a: int) -> None: pass -def g(a: None) -> None: pass x = None # type: Optional[int] if x: - f(x) + reveal_type(x) # E: Revealed type is 'builtins.int' else: - g(x) # E: Argument 1 to "g" has incompatible type "Optional[int]"; expected None + reveal_type(x) # E: Revealed type is 'Union[builtins.int, builtins.None]' [builtins fixtures/bool.py] [case testIfNotCases] from typing import Optional -def f(a: int) -> None: pass -def g(a: None) -> None: pass x = None # type: Optional[int] if not x: - g(x) # E: Argument 1 to "g" has incompatible type "Optional[int]"; expected None + reveal_type(x) # E: Revealed type is 'Union[builtins.int, builtins.None]' else: - f(x) - x() # E: "int" not callable + reveal_type(x) # E: Revealed type is 'builtins.int' [builtins fixtures/bool.py] [case testIsNotNoneCases] from typing import Optional -def f(a: int) -> None: pass -def g(a: None) -> None: pass x = None # type: Optional[int] if x is not None: - f(x) - x() # E: "int" not callable + reveal_type(x) # E: Revealed type is 'builtins.int' else: - g(x) - x() # E: None not callable + reveal_type(x) # E: Revealed type is 'builtins.None' [builtins fixtures/bool.py] [case testIsNoneCases] from typing import Optional -def f(a: int) -> None: pass -def g(a: None) -> None: pass x = None # type: Optional[int] if x is None: - g(x) + reveal_type(x) # E: Revealed type is 'builtins.None' else: - f(x) + reveal_type(x) # E: Revealed type is 'builtins.int' [builtins fixtures/bool.py] [case testLambdaReturningNone] @@ -161,3 +148,13 @@ class C: self.x = None # E: Incompatible types in assignment (expression has type None, variable has type "int") [out] main: note: In member "__init__" of class "C": + +[case testMultipleAssignmentNoneClassVariableInInit] +from typing import Optional +class C: + x, y = None, None # type: int, str + def __init__(self) -> None: + self.x = None # E: Incompatible types in assignment (expression has type None, variable has type "int") + self.y = None # E: Incompatible types in assignment (expression has type None, variable has type "str") +[out] +main: note: In member "__init__" of class "C": From fbbab64db0cde913f2cae4202a3bd9f01acd2b5f Mon Sep 17 00:00:00 2001 From: David Fisher Date: Mon, 6 Jun 2016 16:18:27 -0700 Subject: [PATCH 24/27] update docstrings --- mypy/types.py | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/mypy/types.py b/mypy/types.py index 6704793f514a..4920a525194f 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -220,6 +220,16 @@ def deserialize(cls, data: JsonDict) -> 'Void': class UninhabitedType(Type): """This type has no members. + + This type is almost the bottom type, except it is not a subtype of Void. + With strict Optional checking, it is the only common subtype between all + other types, which allows `meet` to be well defined. Without strict + Optional checking, NoneTyp fills this role. + + In general, for any type T that isn't Void: + join(UninhabitedType, T) = T + meet(UninhabitedType, T) = UninhabitedType + is_subtype(UninhabitedType, T) = True """ def __init__(self, line: int = -1) -> None: @@ -240,14 +250,19 @@ def deserialize(cls, data: JsonDict) -> 'UninhabitedType': class NoneTyp(Type): """The type of 'None'. - This is only used internally during type inference. Programs - cannot declare a variable of this type, and the type checker - refuses to infer this type for a variable. However, subexpressions - often have this type. Note that this is not used as the result - type when calling a function with a void type, even though - semantically such a function returns a None value; the void type - is used instead so that we can report an error if the caller tries - to do anything with the return value. + Without strict Optional checking: + This is only used internally during type inference. Programs + cannot declare a variable of this type, and the type checker + refuses to infer this type for a variable. However, subexpressions + often have this type. Note that this is not used as the result + type when calling a function with a void type, even though + semantically such a function returns a None value; the void type + is used instead so that we can report an error if the caller tries + to do anything with the return value. + + With strict Optional checking: + This type can be written by users as 'None', except as the return value + of a function, where 'None' means Void. """ def __init__(self, line: int = -1) -> None: From b9e309f93d0929cd4537eb5ca1dfb6270d0aabff Mon Sep 17 00:00:00 2001 From: David Fisher Date: Mon, 6 Jun 2016 16:58:00 -0700 Subject: [PATCH 25/27] fix lint errors --- mypy/checker.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 16e58a6e8c36..d0a51dac70d4 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2386,11 +2386,11 @@ def method_type(self, func: FuncBase) -> FunctionLike: def conditional_type_map(expr: Node, - current_type: Optional[Type], - proposed_type: Optional[Type], - *, - weak: bool = False - ) -> Tuple[Optional[Dict[Node, Type]], Optional[Dict[Node, Type]]]: + current_type: Optional[Type], + proposed_type: Optional[Type], + *, + weak: bool = False + ) -> Tuple[Optional[Dict[Node, Type]], Optional[Dict[Node, Type]]]: """Takes in an expression, the current type of the expression, and a proposed type of that expression. @@ -2416,9 +2416,11 @@ def conditional_type_map(expr: Node, else: return {}, {} + def is_literal_none(n: Node) -> bool: return isinstance(n, NameExpr) and n.fullname == 'builtins.None' + def find_isinstance_check(node: Node, type_map: Dict[Node, Type], weak: bool=False From 87f46a0ac07fadb26fddaec5cb6c4530af4771fb Mon Sep 17 00:00:00 2001 From: David Fisher Date: Tue, 7 Jun 2016 16:05:06 -0700 Subject: [PATCH 26/27] add test for overloads and fix overloading --- mypy/checkexpr.py | 6 +++++- mypy/meet.py | 4 ++++ mypy/test/data/check-optional.test | 9 +++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 87bd04b7ce59..e82e0bf0fea8 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -35,6 +35,7 @@ from mypy.constraints import get_actual_type from mypy.checkstrformat import StringFormatterChecker +from mypy import experiments # Type of callback user for checking individual function arguments. See # check_args() below for details. @@ -1697,11 +1698,14 @@ def overload_arg_similarity(actual: Type, formal: Type) -> int: actual = actual.erase_to_union_or_bound() if isinstance(formal, TypeVarType): formal = formal.erase_to_union_or_bound() - if (isinstance(actual, (NoneTyp, UninhabitedType)) or isinstance(actual, AnyType) or + if (isinstance(actual, UninhabitedType) or isinstance(actual, AnyType) or isinstance(formal, AnyType) or isinstance(formal, CallableType) or (isinstance(actual, Instance) and actual.type.fallback_to_any)): # These could match anything at runtime. return 2 + if not experiments.STRICT_OPTIONAL and isinstance(actual, NoneTyp): + # NoneTyp matches anything if we're not doing strict Optional checking + return 2 if isinstance(actual, UnionType): return max(overload_arg_similarity(item, formal) for item in actual.items) diff --git a/mypy/meet.py b/mypy/meet.py index 779c8d4894f0..9c9ea43a1b54 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -94,6 +94,10 @@ class C(A, B): ... if isinstance(s, UnionType): return any(is_overlapping_types(t, item) for item in s.items) + if experiments.STRICT_OPTIONAL: + if isinstance(t, NoneTyp) != isinstance(s, NoneTyp): + # NoneTyp does not overlap with other non-Union types under strict Optional checking + return False # We conservatively assume that non-instance, non-union types can overlap any other # types. return True diff --git a/mypy/test/data/check-optional.test b/mypy/test/data/check-optional.test index 41b01aa82e3a..7dad0933cf24 100644 --- a/mypy/test/data/check-optional.test +++ b/mypy/test/data/check-optional.test @@ -158,3 +158,12 @@ class C: self.y = None # E: Incompatible types in assignment (expression has type None, variable has type "str") [out] main: note: In member "__init__" of class "C": + +[case testOverloadWithNone] +from typing import overload +@overload +def f(x: None) -> str: pass +@overload +def f(x: int) -> int: pass +reveal_type(f(None)) # E: Revealed type is 'builtins.str' +reveal_type(f(0)) # E: Revealed type is 'builtins.int' From 7247430bf2238d9841c832376a82488913fb0fa6 Mon Sep 17 00:00:00 2001 From: David Fisher Date: Fri, 10 Jun 2016 11:07:29 -0700 Subject: [PATCH 27/27] minor test fixup --- mypy/test/data/check-modules.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/test/data/check-modules.test b/mypy/test/data/check-modules.test index 14f789775431..d2f863e6205d 100644 --- a/mypy/test/data/check-modules.test +++ b/mypy/test/data/check-modules.test @@ -61,7 +61,7 @@ class Bad: pass [case testImportWithinBlock] import typing -if 0: +if None: import m m.a = m.b # E: Incompatible types in assignment (expression has type "B", variable has type "A") m.a = m.a