diff --git a/mypy/checker.py b/mypy/checker.py index 8fc000127635..8e831f659786 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -174,7 +174,7 @@ class TypeChecker(NodeVisitor[None], CheckerPluginInterface): # Type checking pass number (0 = first pass) pass_num = 0 # Last pass number to take - last_pass = DEFAULT_LAST_PASS # type: int + last_pass = DEFAULT_LAST_PASS # Have we deferred the current function? If yes, don't infer additional # types during this pass within the function. current_node_deferred = False @@ -1809,7 +1809,10 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type self.check_indexed_assignment(index_lvalue, rvalue, lvalue) if inferred: - rvalue_type = self.expr_checker.accept(rvalue, infer_literal=inferred.is_final) + rvalue_type = self.expr_checker.accept( + rvalue, + in_final_declaration=inferred.is_final, + ) self.infer_variable_type(inferred, lvalue, rvalue_type, rvalue) def check_compatibility_all_supers(self, lvalue: RefExpr, lvalue_type: Optional[Type], diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index a450d3a8a062..e69fcc696815 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -18,7 +18,7 @@ from mypy.types import ( Type, AnyType, CallableType, Overloaded, NoneTyp, TypeVarDef, TupleType, TypedDictType, Instance, TypeVarType, ErasedType, UnionType, - PartialType, DeletedType, UninhabitedType, TypeType, TypeOfAny, LiteralType, + PartialType, DeletedType, UninhabitedType, TypeType, TypeOfAny, LiteralType, LiteralValue, true_only, false_only, is_named_instance, function_type, callable_type, FunctionLike, StarType, is_optional, remove_optional, is_generic_instance ) @@ -139,7 +139,7 @@ def __init__(self, self.msg = msg self.plugin = plugin self.type_context = [None] - self.infer_literal = False + self.in_final_declaration = False # Temporary overrides for expression types. This is currently # used by the union math in overloads. # TODO: refactor this to use a pattern similar to one in @@ -211,10 +211,12 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type: def analyze_var_ref(self, var: Var, context: Context) -> Type: if var.type: - if self.is_literal_context() and var.name() in {'True', 'False'}: - return LiteralType(var.name() == 'True', self.named_type('builtins.bool')) - else: - return var.type + if isinstance(var.type, Instance): + if self._is_literal_context() and var.type.final_value is not None: + return var.type.final_value + if var.name() in {'True', 'False'}: + return self._handle_literal_expr(var.name() == 'True', 'builtins.bool') + return var.type else: if not var.is_ready and self.chk.in_checked_function(): self.chk.handle_cannot_determine_type(var.name(), context) @@ -693,7 +695,8 @@ def check_call(self, elif isinstance(callee, Instance): call_function = analyze_member_access('__call__', callee, context, False, False, False, self.msg, - original_type=callee, chk=self.chk) + original_type=callee, chk=self.chk, + in_literal_context=self._is_literal_context()) return self.check_call(call_function, args, arg_kinds, context, arg_names, callable_node, arg_messages) elif isinstance(callee, TypeVarType): @@ -1757,7 +1760,8 @@ def analyze_ordinary_member_access(self, e: MemberExpr, original_type = self.accept(e.expr) member_type = analyze_member_access( e.name, original_type, e, is_lvalue, False, False, - self.msg, original_type=original_type, chk=self.chk) + self.msg, original_type=original_type, chk=self.chk, + in_literal_context=self._is_literal_context()) return member_type def analyze_external_member_access(self, member: str, base_type: Type, @@ -1767,35 +1771,36 @@ def analyze_external_member_access(self, member: str, base_type: Type, """ # TODO remove; no private definitions in mypy return analyze_member_access(member, base_type, context, False, False, False, - self.msg, original_type=base_type, chk=self.chk) + self.msg, original_type=base_type, chk=self.chk, + in_literal_context=self._is_literal_context()) + + def _is_literal_context(self) -> bool: + return is_literal_type_like(self.type_context[-1]) + + def _handle_literal_expr(self, value: LiteralValue, fallback_name: str) -> Type: + typ = self.named_type(fallback_name) + if self._is_literal_context(): + return LiteralType(value=value, fallback=typ) + elif self.in_final_declaration: + return typ.copy_with_final_value(value) + else: + return typ def visit_int_expr(self, e: IntExpr) -> Type: """Type check an integer literal (trivial).""" - typ = self.named_type('builtins.int') - if self.is_literal_context(): - return LiteralType(value=e.value, fallback=typ) - return typ + return self._handle_literal_expr(e.value, 'builtins.int') def visit_str_expr(self, e: StrExpr) -> Type: """Type check a string literal (trivial).""" - typ = self.named_type('builtins.str') - if self.is_literal_context(): - return LiteralType(value=e.value, fallback=typ) - return typ + return self._handle_literal_expr(e.value, 'builtins.str') def visit_bytes_expr(self, e: BytesExpr) -> Type: """Type check a bytes literal (trivial).""" - typ = self.named_type('builtins.bytes') - if is_literal_type_like(self.type_context[-1]): - return LiteralType(value=e.value, fallback=typ) - return typ + return self._handle_literal_expr(e.value, 'builtins.bytes') def visit_unicode_expr(self, e: UnicodeExpr) -> Type: """Type check a unicode literal (trivial).""" - typ = self.named_type('builtins.unicode') - if is_literal_type_like(self.type_context[-1]): - return LiteralType(value=e.value, fallback=typ) - return typ + return self._handle_literal_expr(e.value, 'builtins.unicode') def visit_float_expr(self, e: FloatExpr) -> Type: """Type check a float literal (trivial).""" @@ -1932,7 +1937,8 @@ def check_method_call_by_name(self, """ local_errors = local_errors or self.msg method_type = analyze_member_access(method, base_type, context, False, False, True, - local_errors, original_type=base_type, chk=self.chk) + local_errors, original_type=base_type, chk=self.chk, + in_literal_context=self._is_literal_context()) return self.check_method_call( method, base_type, method_type, args, arg_kinds, context, local_errors) @@ -1996,6 +2002,7 @@ def lookup_operator(op_name: str, base_type: Type) -> Optional[Type]: context=context, msg=local_errors, chk=self.chk, + in_literal_context=self._is_literal_context() ) if local_errors.is_errors(): return None @@ -2946,7 +2953,8 @@ def analyze_super(self, e: SuperExpr, is_lvalue: bool) -> Type: override_info=base, context=e, msg=self.msg, - chk=self.chk) + chk=self.chk, + in_literal_context=self._is_literal_context()) assert False, 'unreachable' else: # Invalid super. This has been reported by the semantic analyzer. @@ -3113,7 +3121,7 @@ def accept(self, type_context: Optional[Type] = None, allow_none_return: bool = False, always_allow_any: bool = False, - infer_literal: bool = False, + in_final_declaration: bool = False, ) -> Type: """Type check a node in the given type context. If allow_none_return is True and this expression is a call, allow it to return None. This @@ -3121,8 +3129,8 @@ def accept(self, """ if node in self.type_overrides: return self.type_overrides[node] - old_infer_literal = self.infer_literal - self.infer_literal = infer_literal + old_in_final_declaration = self.in_final_declaration + self.in_final_declaration = in_final_declaration self.type_context.append(type_context) try: if allow_none_return and isinstance(node, CallExpr): @@ -3135,7 +3143,7 @@ def accept(self, report_internal_error(err, self.chk.errors.file, node.line, self.chk.errors, self.chk.options) self.type_context.pop() - self.infer_literal = old_infer_literal + self.in_final_declaration = old_in_final_declaration assert typ is not None self.chk.store_type(node, typ) @@ -3381,9 +3389,6 @@ def narrow_type_from_binder(self, expr: Expression, known_type: Type) -> Type: return ans return known_type - def is_literal_context(self) -> bool: - return self.infer_literal or is_literal_type_like(self.type_context[-1]) - def has_any_type(t: Type) -> bool: """Whether t contains an Any type""" diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 2829777eb848..b8826695f9f5 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -71,7 +71,8 @@ def analyze_member_access(name: str, msg: MessageBuilder, *, original_type: Type, chk: 'mypy.checker.TypeChecker', - override_info: Optional[TypeInfo] = None) -> Type: + override_info: Optional[TypeInfo] = None, + in_literal_context: bool = False) -> Type: """Return the type of attribute 'name' of 'typ'. The actual implementation is in '_analyze_member_access' and this docstring @@ -96,7 +97,11 @@ def analyze_member_access(name: str, context, msg, chk=chk) - return _analyze_member_access(name, typ, mx, override_info) + result = _analyze_member_access(name, typ, mx, override_info) + if in_literal_context and isinstance(result, Instance) and result.final_value is not None: + return result.final_value + else: + return result def _analyze_member_access(name: str, diff --git a/mypy/defaults.py b/mypy/defaults.py index af1f68e44627..3169ec540e8d 100644 --- a/mypy/defaults.py +++ b/mypy/defaults.py @@ -5,8 +5,8 @@ PYTHON2_VERSION = (2, 7) # type: Final PYTHON3_VERSION = (3, 6) # type: Final PYTHON3_VERSION_MIN = (3, 4) # type: Final -CACHE_DIR = '.mypy_cache' # type: Final[str] -CONFIG_FILE = 'mypy.ini' # type: Final[str] +CACHE_DIR = '.mypy_cache' # type: Final +CONFIG_FILE = 'mypy.ini' # type: Final SHARED_CONFIG_FILES = ('setup.cfg',) # type: Final USER_CONFIG_FILES = ('~/.mypy.ini',) # type: Final CONFIG_FILES = (CONFIG_FILE,) + SHARED_CONFIG_FILES + USER_CONFIG_FILES # type: Final diff --git a/mypy/erasetype.py b/mypy/erasetype.py index fa3e4abf79b6..2f05892894c5 100644 --- a/mypy/erasetype.py +++ b/mypy/erasetype.py @@ -51,7 +51,7 @@ def visit_deleted_type(self, t: DeletedType) -> Type: return t def visit_instance(self, t: Instance) -> Type: - return Instance(t.type, [AnyType(TypeOfAny.special_form)] * len(t.args), t.line) + return t.copy_modified(args=[AnyType(TypeOfAny.special_form)] * len(t.args)) def visit_type_var(self, t: TypeVarType) -> Type: return AnyType(TypeOfAny.special_form) diff --git a/mypy/expandtype.py b/mypy/expandtype.py index bf51dc2aa9a9..afe51a636b25 100644 --- a/mypy/expandtype.py +++ b/mypy/expandtype.py @@ -80,16 +80,14 @@ def visit_erased_type(self, t: ErasedType) -> Type: raise RuntimeError() def visit_instance(self, t: Instance) -> Type: - args = self.expand_types(t.args) - return Instance(t.type, args, t.line, t.column) + return t.copy_modified(args=self.expand_types(t.args)) def visit_type_var(self, t: TypeVarType) -> Type: repl = self.variables.get(t.id, t) if isinstance(repl, Instance): inst = repl # Return copy of instance with type erasure flag on. - return Instance(inst.type, inst.args, line=inst.line, - column=inst.column, erased=True) + return inst.copy_modified(erased=True) else: return repl diff --git a/mypy/reachability.py b/mypy/reachability.py index f871680f3fb8..4585c08c861a 100644 --- a/mypy/reachability.py +++ b/mypy/reachability.py @@ -77,7 +77,7 @@ def infer_condition_value(expr: Expression, options: Options) -> int: if alias.op == 'not': expr = alias.expr negated = True - result = TRUTH_VALUE_UNKNOWN # type: int + result = TRUTH_VALUE_UNKNOWN if isinstance(expr, NameExpr): name = expr.name elif isinstance(expr, MemberExpr): diff --git a/mypy/sametypes.py b/mypy/sametypes.py index 1cb826a5ec4f..1fec6d572fd4 100644 --- a/mypy/sametypes.py +++ b/mypy/sametypes.py @@ -77,7 +77,8 @@ def visit_deleted_type(self, left: DeletedType) -> bool: def visit_instance(self, left: Instance) -> bool: return (isinstance(self.right, Instance) and left.type == self.right.type and - is_same_types(left.args, self.right.args)) + is_same_types(left.args, self.right.args) and + left.final_value == self.right.final_value) def visit_type_var(self, left: TypeVarType) -> bool: return (isinstance(self.right, TypeVarType) and diff --git a/mypy/semanal.py b/mypy/semanal.py index 1c358ee8858b..4266ed5606bd 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -65,7 +65,7 @@ from mypy.messages import CANNOT_ASSIGN_TO_TYPE, MessageBuilder from mypy.types import ( FunctionLike, UnboundType, TypeVarDef, TupleType, UnionType, StarType, function_type, - CallableType, Overloaded, Instance, Type, AnyType, LiteralType, + CallableType, Overloaded, Instance, Type, AnyType, TypeTranslator, TypeOfAny, TypeType, NoneTyp, ) from mypy.nodes import implicit_module_attrs @@ -1908,22 +1908,29 @@ def analyze_simple_literal_type(self, rvalue: Expression, is_final: bool) -> Opt # inside type variables with value restrictions (like # AnyStr). return None + if isinstance(rvalue, FloatExpr): + return self.named_type_or_none('builtins.float') + if isinstance(rvalue, IntExpr): typ = self.named_type_or_none('builtins.int') if typ and is_final: - return LiteralType(rvalue.value, typ, rvalue.line, rvalue.column) + return typ.copy_with_final_value(rvalue.value) return typ - if isinstance(rvalue, FloatExpr): - return self.named_type_or_none('builtins.float') if isinstance(rvalue, StrExpr): typ = self.named_type_or_none('builtins.str') if typ and is_final: - return LiteralType(rvalue.value, typ, rvalue.line, rvalue.column) + return typ.copy_with_final_value(rvalue.value) return typ if isinstance(rvalue, BytesExpr): - return self.named_type_or_none('builtins.bytes') + typ = self.named_type_or_none('builtins.bytes') + if typ and is_final: + return typ.copy_with_final_value(rvalue.value) + return typ if isinstance(rvalue, UnicodeExpr): - return self.named_type_or_none('builtins.unicode') + typ = self.named_type_or_none('builtins.unicode') + if typ and is_final: + return typ.copy_with_final_value(rvalue.value) + return typ return None diff --git a/mypy/server/astdiff.py b/mypy/server/astdiff.py index 8697358a4205..53dd2489e985 100644 --- a/mypy/server/astdiff.py +++ b/mypy/server/astdiff.py @@ -284,7 +284,8 @@ def visit_deleted_type(self, typ: DeletedType) -> SnapshotItem: def visit_instance(self, typ: Instance) -> SnapshotItem: return ('Instance', typ.type.fullname(), - snapshot_types(typ.args)) + snapshot_types(typ.args), + None if typ.final_value is None else snapshot_type(typ.final_value)) def visit_type_var(self, typ: TypeVarType) -> SnapshotItem: return ('TypeVar', diff --git a/mypy/server/astmerge.py b/mypy/server/astmerge.py index edfdc076e7d7..97cf2fdf50cc 100644 --- a/mypy/server/astmerge.py +++ b/mypy/server/astmerge.py @@ -337,6 +337,8 @@ def visit_instance(self, typ: Instance) -> None: typ.type = self.fixup(typ.type) for arg in typ.args: arg.accept(self) + if typ.final_value: + typ.final_value.accept(self) def visit_any(self, typ: AnyType) -> None: pass diff --git a/mypy/server/deps.py b/mypy/server/deps.py index ae2075d72fe7..43fb464fda5e 100644 --- a/mypy/server/deps.py +++ b/mypy/server/deps.py @@ -882,6 +882,8 @@ def visit_instance(self, typ: Instance) -> List[str]: triggers = [trigger] for arg in typ.args: triggers.extend(self.get_type_triggers(arg)) + if typ.final_value: + triggers.extend(self.get_type_triggers(typ.final_value)) return triggers def visit_any(self, typ: AnyType) -> List[str]: diff --git a/mypy/stubgen.py b/mypy/stubgen.py index a79dd6a8ff00..a97ded77cf6b 100755 --- a/mypy/stubgen.py +++ b/mypy/stubgen.py @@ -420,7 +420,7 @@ def __init__(self, _all_: Optional[List[str]], pyversion: Tuple[int, int], self._import_lines = [] # type: List[str] self._indent = '' self._vars = [[]] # type: List[List[str]] - self._state = EMPTY # type: str + self._state = EMPTY self._toplevel_names = [] # type: List[str] self._pyversion = pyversion self._include_private = include_private diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 300295db74a6..227b3ba10d27 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -589,7 +589,7 @@ def get_member_flags(name: str, info: TypeInfo) -> Set[int]: return {IS_CLASS_OR_STATIC} # just a variable if isinstance(v, Var) and not v.is_property: - flags = {IS_SETTABLE} # type: Set[int] + flags = {IS_SETTABLE} if v.is_classvar: flags.add(IS_CLASSVAR) return flags diff --git a/mypy/type_visitor.py b/mypy/type_visitor.py index 64ae75e174ee..0b8c5ca10d62 100644 --- a/mypy/type_visitor.py +++ b/mypy/type_visitor.py @@ -159,7 +159,7 @@ def visit_deleted_type(self, t: DeletedType) -> Type: return t def visit_instance(self, t: Instance) -> Type: - return Instance(t.type, self.translate_types(t.args), t.line, t.column) + return t.copy_modified(args=self.translate_types(t.args)) def visit_type_var(self, t: TypeVarType) -> Type: return t diff --git a/mypy/typeanal.py b/mypy/typeanal.py index a870273ebcfd..bfeffb8aa8df 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -678,6 +678,9 @@ def analyze_literal_param(self, idx: int, arg: Type, ctx: Context) -> Optional[L elif isinstance(arg, (NoneTyp, LiteralType)): # Types that we can just add directly to the literal/potential union of literals. return [arg] + elif isinstance(arg, Instance) and arg.final_value is not None: + # Types generated from declarations like "var: Final = 4". + return [arg.final_value] elif isinstance(arg, UnionType): out = [] for union_arg in arg.items: @@ -1073,7 +1076,7 @@ def replace_alias_tvars(tp: Type, vars: List[str], subs: List[Type], def set_any_tvars(tp: Type, vars: List[str], newline: int, newcolumn: int, implicit: bool = True) -> Type: if implicit: - type_of_any = TypeOfAny.from_omitted_generics # type: int + type_of_any = TypeOfAny.from_omitted_generics else: type_of_any = TypeOfAny.special_form any_type = AnyType(type_of_any, line=newline, column=newcolumn) diff --git a/mypy/types.py b/mypy/types.py index d21eabdd77f5..0d65afb8f5fb 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -561,37 +561,43 @@ class Instance(Type): The list of type variables may be empty. """ - __slots__ = ('type', 'args', 'erased', 'invalid', 'type_ref') + __slots__ = ('type', 'args', 'erased', 'invalid', 'type_ref', 'final_value') def __init__(self, typ: mypy.nodes.TypeInfo, args: List[Type], - line: int = -1, column: int = -1, erased: bool = False) -> None: + line: int = -1, column: int = -1, erased: bool = False, + final_value: Optional['LiteralType'] = None) -> None: super().__init__(line, column) self.type = typ self.args = args self.erased = erased # True if result of type variable substitution self.invalid = False # True if recovered after incorrect number of type arguments error self.type_ref = None # type: Optional[str] + self.final_value = final_value def accept(self, visitor: 'TypeVisitor[T]') -> T: return visitor.visit_instance(self) def __hash__(self) -> int: - return hash((self.type, tuple(self.args))) + return hash((self.type, tuple(self.args), self.final_value)) def __eq__(self, other: object) -> bool: if not isinstance(other, Instance): return NotImplemented - return self.type == other.type and self.args == other.args + return (self.type == other.type + and self.args == other.args + and self.final_value == other.final_value) def serialize(self) -> Union[JsonDict, str]: assert self.type is not None type_ref = self.type.fullname() - if not self.args: + if not self.args and not self.final_value: return type_ref data = {'.class': 'Instance', } # type: JsonDict data['type_ref'] = type_ref data['args'] = [arg.serialize() for arg in self.args] + if self.final_value is not None: + data['final_value'] = self.final_value.serialize() return data @classmethod @@ -608,10 +614,32 @@ def deserialize(cls, data: Union[JsonDict, str]) -> 'Instance': args = [deserialize_type(arg) for arg in args_list] inst = Instance(NOT_READY, args) inst.type_ref = data['type_ref'] # Will be fixed up by fixup.py later. + if 'final_value' in data: + inst.final_value = LiteralType.deserialize(data['final_value']) return inst - def copy_modified(self, *, args: List[Type]) -> 'Instance': - return Instance(self.type, args, self.line, self.column, self.erased) + def copy_modified(self, *, + args: Bogus[List[Type]] = _dummy, + erased: Bogus[bool] = _dummy) -> 'Instance': + return Instance( + self.type, + args if args is not _dummy else self.args, + self.line, + self.column, + erased if erased is not _dummy else self.erased, + self.final_value, + ) + + def copy_with_final_value(self, value: LiteralValue) -> 'Instance': + # Note: the fallback for this LiteralType is the *original* type, not the newly + # generated one. This helps prevent infinite loops when we traverse the type tree. + final_value = LiteralType( + value=value, + fallback=self, + line=self.line, + column=self.column, + ) + return Instance(self.type, self.args, self.line, self.column, self.erased, final_value) def has_readable_member(self, name: str) -> bool: return self.type.has_readable_member(name) @@ -2042,7 +2070,7 @@ def get_typ_args(tp: Type) -> List[Type]: def set_typ_args(tp: Type, new_args: List[Type], line: int = -1, column: int = -1) -> Type: """Return a copy of a parametrizable Type with arguments set to new_args.""" if isinstance(tp, Instance): - return Instance(tp.type, new_args, line, column) + return tp.copy_modified(args=new_args) if isinstance(tp, TupleType): return tp.copy_modified(items=new_args) if isinstance(tp, UnionType): diff --git a/test-data/unit/check-final.test b/test-data/unit/check-final.test index ab9054c51bdb..7f895b97d277 100644 --- a/test-data/unit/check-final.test +++ b/test-data/unit/check-final.test @@ -338,7 +338,7 @@ class C: [case testFinalReassignModuleVar] from typing import Final -x: Final[int] = 1 +x: Final = 1 x # Dummy reference to allow renaming once implemented x = 2 # E: Cannot assign to final name "x" def f() -> int: @@ -358,7 +358,7 @@ y: Final = 2 # E: Name 'y' already defined on line 17 \ # E: Cannot redefine an existing name as final y = 3 # No error here, first definition wins -z: Final[int] = 1 +z: Final = 1 z: Final = 2 # E: Name 'z' already defined on line 22 \ # E: Cannot redefine an existing name as final z = 3 # E: Cannot assign to final name "z" @@ -395,11 +395,11 @@ main:8: error: Cannot assign to final name "ID" from typing import Final def f() -> None: - nl: Final[int] = 0 + nl: Final = 0 x: Final = 1 x = 1 # E: Cannot assign to final name "x" - y: Final[int] = 1 + y: Final = 1 y: Final = 2 # E: Cannot redefine an existing name as final def nested() -> None: nonlocal nl @@ -418,7 +418,7 @@ x: Final[int] from typing import Final class C: - x: Final[int] = 1 + x: Final = 1 x = 2 # E: Cannot assign to final name "x" y = 1 @@ -430,9 +430,9 @@ from typing import Final class C: def __init__(self) -> None: - self.x: Final[int] = 1 + self.x: Final = 1 self.y = 1 - self.y: Final[int] = 2 # E: Cannot redefine an existing name as final + self.y: Final = 2 # E: Cannot redefine an existing name as final def meth(self) -> None: self.x = 2 # E: Cannot assign to final attribute "x" [out] @@ -441,9 +441,9 @@ class C: from typing import Final class C: - y: Final[int] = 1 + y: Final = 1 def __init__(self) -> None: - self.x: Final[int] = 1 + self.x: Final = 1 self.y = 2 # E: Cannot assign to final attribute "y" x = 2 # E: Cannot assign to final name "x" [out] @@ -452,9 +452,9 @@ class C: from typing import Final class C: - x: Final[int] = 1 + x: Final = 1 def __init__(self) -> None: - self.y: Final[int] = 1 + self.y: Final = 1 def meth(self) -> None: self.x = 2 # E: Cannot assign to final attribute "x" self.y = 2 # E: Cannot assign to final attribute "y" @@ -472,9 +472,9 @@ class C: from typing import Final class C: - x: Final[int] = 1 + x: Final = 1 def __init__(self) -> None: - self.y: Final[int] = 1 + self.y: Final = 1 class D(C): pass @@ -494,14 +494,9 @@ class C: class D(C): pass -C().x = 2 # E: Cannot assign to final attribute "x" \ - # E: Incompatible types in assignment (expression has type "Literal[2]", variable has type "Literal[1]") - -D().x = 2 # E: Cannot assign to final attribute "x" \ - # E: Incompatible types in assignment (expression has type "Literal[2]", variable has type "Literal[1]") - -D().y = 2 # E: Cannot assign to final attribute "y" \ - # E: Incompatible types in assignment (expression has type "Literal[2]", variable has type "Literal[1]") +C().x = 2 # E: Cannot assign to final attribute "x" +D().x = 2 # E: Cannot assign to final attribute "x" +D().y = 2 # E: Cannot assign to final attribute "y" [out] [case testFinalWorksWithComplexTargets] @@ -551,9 +546,9 @@ class A: def y(self) -> int: ... class B(A): - x: Final[int] = 1 + x: Final = 1 def __init__(self) -> None: - self.y: Final[int] = 1 + self.y: Final = 1 class C(B): x: int = 2 # E: Cannot assign to final name "x" y: int = 2 # E: Cannot assign to final name "y" @@ -575,9 +570,9 @@ class A: @property def y(self) -> int: ... class B(A): - x: Final[int] = 1 + x: Final = 1 def __init__(self) -> None: - self.y: Final[int] = 1 + self.y: Final = 1 class C(B): x: Final = 2 # E: Cannot override final attribute "x" (previously declared in base class "B") y: Final = 2 # E: Cannot override final attribute "y" (previously declared in base class "B") @@ -598,15 +593,11 @@ class B(A): self.y: Final = 1 class C(B): def __init__(self) -> None: - self.x = 2 # E: Cannot assign to final attribute "x" \ - # E: Incompatible types in assignment (expression has type "Literal[2]", variable has type "Literal[1]") - self.y = 2 # E: Cannot assign to final attribute "y" \ - # E: Incompatible types in assignment (expression has type "Literal[2]", variable has type "Literal[1]") + self.x = 2 # E: Cannot assign to final attribute "x" + self.y = 2 # E: Cannot assign to final attribute "y" def meth(self) -> None: - self.x = 3 # E: Cannot assign to final attribute "x" \ - # E: Incompatible types in assignment (expression has type "Literal[3]", variable has type "Literal[1]") - self.y = 3 # E: Cannot assign to final attribute "y" \ - # E: Incompatible types in assignment (expression has type "Literal[3]", variable has type "Literal[1]") + self.x = 3 # E: Cannot assign to final attribute "x" + self.y = 3 # E: Cannot assign to final attribute "y" [builtins fixtures/property.pyi] [out] @@ -624,10 +615,8 @@ class B(A): self.y: Final = 1 class C(B): def __init__(self) -> None: - self.x: Final = 2 # E: Cannot override final attribute "x" (previously declared in base class "B") \ - # E: Incompatible types in assignment (expression has type "Literal[2]", base class "B" defined the type as "Literal[1]") - self.y: Final = 2 # E: Cannot override final attribute "y" (previously declared in base class "B") \ - # E: Incompatible types in assignment (expression has type "Literal[2]", base class "B" defined the type as "Literal[1]") + self.x: Final = 2 # E: Cannot override final attribute "x" (previously declared in base class "B") + self.y: Final = 2 # E: Cannot override final attribute "y" (previously declared in base class "B") [builtins fixtures/property.pyi] [out] @@ -645,10 +634,8 @@ class B(A): self.y: Final = 1 class C(B): def meth(self) -> None: - self.x: int = 2 # E: Cannot assign to final attribute "x" \ - # E: Incompatible types in assignment (expression has type "int", base class "B" defined the type as "Literal[1]") - self.y: int = 2 # E: Cannot assign to final attribute "y" \ - # E: Incompatible types in assignment (expression has type "int", base class "B" defined the type as "Literal[1]") + self.x: int = 2 # E: Cannot assign to final attribute "x" + self.y: int = 2 # E: Cannot assign to final attribute "y" self.x = 3 # E: Cannot assign to final attribute "x" self.y = 3 # E: Cannot assign to final attribute "y" @@ -693,7 +680,7 @@ C().x = 4 # E: Cannot assign to final attribute "x" from typing import Final class A: - x: Final[int] = 1 + x: Final = 1 class B: def __init__(self) -> None: self.x = 2 @@ -975,11 +962,11 @@ class D(B): [case testFinalCanUseTypingExtensions] from typing_extensions import final, Final -x: Final[int] = 1 +x: Final = 1 x = 2 # E: Cannot assign to final name "x" class S: - x: Final[int] = 1 + x: Final = 1 S.x = 2 # E: Cannot assign to final attribute "x" class B: @@ -997,13 +984,11 @@ class E(F): ... # E: Cannot inherit from final class "F" from typing_extensions import final as f, Final as F x: F = 1 -x = 2 # E: Incompatible types in assignment (expression has type "Literal[2]", variable has type "Literal[1]") \ - # E: Cannot assign to final name "x" +x = 2 # E: Cannot assign to final name "x" class S: x: F = 1 -S.x = 2 # E: Cannot assign to final attribute "x" \ - # E: Incompatible types in assignment (expression has type "Literal[2]", variable has type "Literal[1]") +S.x = 2 # E: Cannot assign to final attribute "x" class B: @f diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 7233c320b4fb..738f0b6242aa 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -4239,11 +4239,11 @@ class C: [file mod.py.2] from typing import Final -x: Final[int] = 1 +x: Final = 1 class C: - y: Final[int] = 1 + y: Final = 1 def __init__(self) -> None: - self.z: Final[int] = 1 + self.z: Final = 1 [out] [out2] main:5: error: Cannot assign to final name "x" diff --git a/test-data/unit/check-literal.test b/test-data/unit/check-literal.test index 8e7aa79a8f71..d00d7c4cbb67 100644 --- a/test-data/unit/check-literal.test +++ b/test-data/unit/check-literal.test @@ -2087,7 +2087,7 @@ reveal_type(unify(func)) # E: Revealed type is '' -- [case testLiteralFinalInferredAsLiteral] -from typing_extensions import Final +from typing_extensions import Final, Literal var1: Final = 1 var2: Final = "foo" @@ -2106,26 +2106,43 @@ class Foo: self.instancevar3: Final = True self.instancevar4: Final = None -reveal_type(var1) # E: Revealed type is 'Literal[1]' -reveal_type(var2) # E: Revealed type is 'Literal['foo']' -reveal_type(var3) # E: Revealed type is 'Literal[True]' -reveal_type(var4) # E: Revealed type is 'None' - -reveal_type(Foo.classvar1) # E: Revealed type is 'Literal[1]' -reveal_type(Foo.classvar2) # E: Revealed type is 'Literal['foo']' -reveal_type(Foo.classvar3) # E: Revealed type is 'Literal[True]' -reveal_type(Foo.classvar4) # E: Revealed type is 'None' +def force1(x: Literal[1]) -> None: pass +def force2(x: Literal["foo"]) -> None: pass +def force3(x: Literal[True]) -> None: pass +def force4(x: Literal[None]) -> None: pass + +reveal_type(var1) # E: Revealed type is 'builtins.int' +reveal_type(var2) # E: Revealed type is 'builtins.str' +reveal_type(var3) # E: Revealed type is 'builtins.bool' +reveal_type(var4) # E: Revealed type is 'None' +force1(reveal_type(var1)) # E: Revealed type is 'Literal[1]' +force2(reveal_type(var2)) # E: Revealed type is 'Literal['foo']' +force3(reveal_type(var3)) # E: Revealed type is 'Literal[True]' +force4(reveal_type(var4)) # E: Revealed type is 'None' + +reveal_type(Foo.classvar1) # E: Revealed type is 'builtins.int' +reveal_type(Foo.classvar2) # E: Revealed type is 'builtins.str' +reveal_type(Foo.classvar3) # E: Revealed type is 'builtins.bool' +reveal_type(Foo.classvar4) # E: Revealed type is 'None' +force1(reveal_type(Foo.classvar1)) # E: Revealed type is 'Literal[1]' +force2(reveal_type(Foo.classvar2)) # E: Revealed type is 'Literal['foo']' +force3(reveal_type(Foo.classvar3)) # E: Revealed type is 'Literal[True]' +force4(reveal_type(Foo.classvar4)) # E: Revealed type is 'None' f = Foo() -reveal_type(f.instancevar1) # E: Revealed type is 'Literal[1]' -reveal_type(f.instancevar2) # E: Revealed type is 'Literal['foo']' -reveal_type(f.instancevar3) # E: Revealed type is 'Literal[True]' -reveal_type(f.instancevar4) # E: Revealed type is 'None' +reveal_type(f.instancevar1) # E: Revealed type is 'builtins.int' +reveal_type(f.instancevar2) # E: Revealed type is 'builtins.str' +reveal_type(f.instancevar3) # E: Revealed type is 'builtins.bool' +reveal_type(f.instancevar4) # E: Revealed type is 'None' +force1(reveal_type(f.instancevar1)) # E: Revealed type is 'Literal[1]' +force2(reveal_type(f.instancevar2)) # E: Revealed type is 'Literal['foo']' +force3(reveal_type(f.instancevar3)) # E: Revealed type is 'Literal[True]' +force4(reveal_type(f.instancevar4)) # E: Revealed type is 'None' [builtins fixtures/primitives.pyi] [out] -[case testLiteralFinalDirectTypesSupercededInferredLiteral] -from typing_extensions import Final +[case testLiteralFinalDirectInstanceTypesSupercedeInferredLiteral] +from typing_extensions import Final, Literal var1: Final[int] = 1 var2: Final[str] = "foo" @@ -2144,21 +2161,93 @@ class Foo: self.instancevar3: Final[bool] = True self.instancevar4: Final[None] = None +def force1(x: Literal[1]) -> None: pass +def force2(x: Literal["foo"]) -> None: pass +def force3(x: Literal[True]) -> None: pass +def force4(x: Literal[None]) -> None: pass + reveal_type(var1) # E: Revealed type is 'builtins.int' reveal_type(var2) # E: Revealed type is 'builtins.str' reveal_type(var3) # E: Revealed type is 'builtins.bool' reveal_type(var4) # E: Revealed type is 'None' +force1(var1) # E: Argument 1 to "force1" has incompatible type "int"; expected "Literal[1]" +force2(var2) # E: Argument 1 to "force2" has incompatible type "str"; expected "Literal['foo']" +force3(var3) # E: Argument 1 to "force3" has incompatible type "bool"; expected "Literal[True]" +force4(var4) reveal_type(Foo.classvar1) # E: Revealed type is 'builtins.int' reveal_type(Foo.classvar2) # E: Revealed type is 'builtins.str' reveal_type(Foo.classvar3) # E: Revealed type is 'builtins.bool' reveal_type(Foo.classvar4) # E: Revealed type is 'None' +force1(Foo.classvar1) # E: Argument 1 to "force1" has incompatible type "int"; expected "Literal[1]" +force2(Foo.classvar2) # E: Argument 1 to "force2" has incompatible type "str"; expected "Literal['foo']" +force3(Foo.classvar3) # E: Argument 1 to "force3" has incompatible type "bool"; expected "Literal[True]" +force4(Foo.classvar4) f = Foo() reveal_type(f.instancevar1) # E: Revealed type is 'builtins.int' reveal_type(f.instancevar2) # E: Revealed type is 'builtins.str' reveal_type(f.instancevar3) # E: Revealed type is 'builtins.bool' reveal_type(f.instancevar4) # E: Revealed type is 'None' +force1(f.instancevar1) # E: Argument 1 to "force1" has incompatible type "int"; expected "Literal[1]" +force2(f.instancevar2) # E: Argument 1 to "force2" has incompatible type "str"; expected "Literal['foo']" +force3(f.instancevar3) # E: Argument 1 to "force3" has incompatible type "bool"; expected "Literal[True]" +force4(f.instancevar4) +[builtins fixtures/primitives.pyi] +[out] + +[case testLiteralFinalDirectLiteralTypesForceLiteral] +from typing_extensions import Final, Literal + +var1: Final[Literal[1]] = 1 +var2: Final[Literal["foo"]] = "foo" +var3: Final[Literal[True]] = True +var4: Final[Literal[None]] = None + +class Foo: + classvar1: Final[Literal[1]] = 1 + classvar2: Final[Literal["foo"]] = "foo" + classvar3: Final[Literal[True]] = True + classvar4: Final[Literal[None]] = None + + def __init__(self) -> None: + self.instancevar1: Final[Literal[1]] = 1 + self.instancevar2: Final[Literal["foo"]] = "foo" + self.instancevar3: Final[Literal[True]] = True + self.instancevar4: Final[Literal[None]] = None + +def force1(x: Literal[1]) -> None: pass +def force2(x: Literal["foo"]) -> None: pass +def force3(x: Literal[True]) -> None: pass +def force4(x: Literal[None]) -> None: pass + +reveal_type(var1) # E: Revealed type is 'Literal[1]' +reveal_type(var2) # E: Revealed type is 'Literal['foo']' +reveal_type(var3) # E: Revealed type is 'Literal[True]' +reveal_type(var4) # E: Revealed type is 'None' +force1(reveal_type(var1)) # E: Revealed type is 'Literal[1]' +force2(reveal_type(var2)) # E: Revealed type is 'Literal['foo']' +force3(reveal_type(var3)) # E: Revealed type is 'Literal[True]' +force4(reveal_type(var4)) # E: Revealed type is 'None' + +reveal_type(Foo.classvar1) # E: Revealed type is 'Literal[1]' +reveal_type(Foo.classvar2) # E: Revealed type is 'Literal['foo']' +reveal_type(Foo.classvar3) # E: Revealed type is 'Literal[True]' +reveal_type(Foo.classvar4) # E: Revealed type is 'None' +force1(reveal_type(Foo.classvar1)) # E: Revealed type is 'Literal[1]' +force2(reveal_type(Foo.classvar2)) # E: Revealed type is 'Literal['foo']' +force3(reveal_type(Foo.classvar3)) # E: Revealed type is 'Literal[True]' +force4(reveal_type(Foo.classvar4)) # E: Revealed type is 'None' + +f = Foo() +reveal_type(f.instancevar1) # E: Revealed type is 'Literal[1]' +reveal_type(f.instancevar2) # E: Revealed type is 'Literal['foo']' +reveal_type(f.instancevar3) # E: Revealed type is 'Literal[True]' +reveal_type(f.instancevar4) # E: Revealed type is 'None' +force1(reveal_type(f.instancevar1)) # E: Revealed type is 'Literal[1]' +force2(reveal_type(f.instancevar2)) # E: Revealed type is 'Literal['foo']' +force3(reveal_type(f.instancevar3)) # E: Revealed type is 'Literal[True]' +force4(reveal_type(f.instancevar4)) # E: Revealed type is 'None' [builtins fixtures/primitives.pyi] [out] @@ -2193,12 +2282,119 @@ Foo().instancevar1 = 10 # E: Cannot assign to final attribute "instancevar1" \ [case testLiteralFinalGoesOnlyOneLevelDown] from typing import Tuple -from typing_extensions import Final +from typing_extensions import Final, Literal a: Final = 1 b: Final = (1, 2) -reveal_type(a) # E: Revealed type is 'Literal[1]' -reveal_type(b) # E: Revealed type is 'Tuple[builtins.int, builtins.int]' +def force1(x: Literal[1]) -> None: pass +def force2(x: Tuple[Literal[1], Literal[2]]) -> None: pass + +reveal_type(a) # E: Revealed type is 'builtins.int' +reveal_type(b) # E: Revealed type is 'Tuple[builtins.int, builtins.int]' + +force1(reveal_type(a)) # E: Revealed type is 'Literal[1]' +force2(reveal_type(b)) # E: Argument 1 to "force2" has incompatible type "Tuple[int, int]"; expected "Tuple[Literal[1], Literal[2]]" \ + # E: Revealed type is 'Tuple[builtins.int, builtins.int]' [builtins fixtures/tuple.pyi] [out] + +[case testLiteralFinalCollectionPropagation] +from typing import List +from typing_extensions import Final, Literal + +a: Final = 1 +implicit = [a] +explicit: List[Literal[1]] = [a] + +def force1(x: List[Literal[1]]) -> None: pass +def force2(x: Literal[1]) -> None: pass + +reveal_type(implicit) # E: Revealed type is 'builtins.list[builtins.int*]' +force1(reveal_type(implicit)) # E: Argument 1 to "force1" has incompatible type "List[int]"; expected "List[Literal[1]]" \ + # E: Revealed type is 'builtins.list[builtins.int*]' +force2(reveal_type(implicit[0])) # E: Argument 1 to "force2" has incompatible type "int"; expected "Literal[1]" \ + # E: Revealed type is 'builtins.int*' + +reveal_type(explicit) # E: Revealed type is 'builtins.list[Literal[1]]' +force1(reveal_type(explicit)) # E: Revealed type is 'builtins.list[Literal[1]]' +force2(reveal_type(explicit[0])) # E: Revealed type is 'Literal[1]' +[builtins fixtures/list.pyi] +[out] + +[case testLiteralFinalStringTypesPython3] +from typing_extensions import Final, Literal + +a: Final = u"foo" +b: Final = "foo" +c: Final = b"foo" + +def force_unicode(x: Literal[u"foo"]) -> None: pass +def force_bytes(x: Literal[b"foo"]) -> None: pass + +force_unicode(reveal_type(a)) # E: Revealed type is 'Literal['foo']' +force_unicode(reveal_type(b)) # E: Revealed type is 'Literal['foo']' +force_unicode(reveal_type(c)) # E: Argument 1 to "force_unicode" has incompatible type "Literal[b'foo']"; expected "Literal['foo']" \ + # E: Revealed type is 'Literal[b'foo']' + +force_bytes(reveal_type(a)) # E: Argument 1 to "force_bytes" has incompatible type "Literal['foo']"; expected "Literal[b'foo']" \ + # E: Revealed type is 'Literal['foo']' +force_bytes(reveal_type(b)) # E: Argument 1 to "force_bytes" has incompatible type "Literal['foo']"; expected "Literal[b'foo']" \ + # E: Revealed type is 'Literal['foo']' +force_bytes(reveal_type(c)) # E: Revealed type is 'Literal[b'foo']' +[out] + +[case testLiteralFinalStringTypesPython2UnicodeLiterals] +# flags: --python-version 2.7 +from __future__ import unicode_literals +from typing_extensions import Final, Literal + +a = u"foo" # type: Final +b = "foo" # type: Final +c = b"foo" # type: Final + +def force_unicode(x): + # type: (Literal[u"foo"]) -> None + pass +def force_bytes(x): + # type: (Literal[b"foo"]) -> None + pass + +force_unicode(reveal_type(a)) # E: Revealed type is 'Literal[u'foo']' +force_unicode(reveal_type(b)) # E: Revealed type is 'Literal[u'foo']' +force_unicode(reveal_type(c)) # E: Argument 1 to "force_unicode" has incompatible type "Literal['foo']"; expected "Literal[u'foo']" \ + # E: Revealed type is 'Literal['foo']' + +force_bytes(reveal_type(a)) # E: Argument 1 to "force_bytes" has incompatible type "Literal[u'foo']"; expected "Literal['foo']" \ + # E: Revealed type is 'Literal[u'foo']' +force_bytes(reveal_type(b)) # E: Argument 1 to "force_bytes" has incompatible type "Literal[u'foo']"; expected "Literal['foo']" \ + # E: Revealed type is 'Literal[u'foo']' +force_bytes(reveal_type(c)) # E: Revealed type is 'Literal['foo']' +[out] + +[case testLiteralFinalStringTypesPython2] +# flags: --python-version 2.7 +from typing_extensions import Final, Literal + +a = u"foo" # type: Final +b = "foo" # type: Final +c = b"foo" # type: Final + +def force_unicode(x): + # type: (Literal[u"foo"]) -> None + pass +def force_bytes(x): + # type: (Literal[b"foo"]) -> None + pass + +force_unicode(reveal_type(a)) # E: Revealed type is 'Literal[u'foo']' +force_unicode(reveal_type(b)) # E: Argument 1 to "force_unicode" has incompatible type "Literal['foo']"; expected "Literal[u'foo']" \ + # E: Revealed type is 'Literal['foo']' +force_unicode(reveal_type(c)) # E: Argument 1 to "force_unicode" has incompatible type "Literal['foo']"; expected "Literal[u'foo']" \ + # E: Revealed type is 'Literal['foo']' + +force_bytes(reveal_type(a)) # E: Argument 1 to "force_bytes" has incompatible type "Literal[u'foo']"; expected "Literal['foo']" \ + # E: Revealed type is 'Literal[u'foo']' +force_bytes(reveal_type(b)) # E: Revealed type is 'Literal['foo']' +force_bytes(reveal_type(c)) # E: Revealed type is 'Literal['foo']' +[out] diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 13d857ccd177..8da12a200df9 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -8007,11 +8007,11 @@ class C: [file mod.py.2] from typing import Final -x: Final[int] = 1 +x: Final = 1 class C: - y: Final[int] = 1 + y: Final = 1 def __init__(self) -> None: - self.z: Final[int] = 1 + self.z: Final = 1 [out] == main:5: error: Cannot assign to final name "x" @@ -8042,9 +8042,9 @@ class C: from typing import Final class C: - x: Final[int] = 1 + x: Final = 1 def __init__(self) -> None: - self.y: Final[int] = 1 + self.y: Final = 1 [out] == main:4: error: Cannot assign to final name "x"