diff --git a/mypy/checker.py b/mypy/checker.py index 04a286beef5e..0c9300813b18 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -211,7 +211,7 @@ from mypy.types_utils import is_overlapping_none, remove_optional, store_argument_type, strip_type from mypy.typetraverser import TypeTraverserVisitor from mypy.typevars import fill_typevars, fill_typevars_with_any, has_no_typevars -from mypy.util import is_dunder, is_sunder +from mypy.util import is_dunder, is_sunder, maybe_mangled from mypy.visitor import NodeVisitor T = TypeVar("T") @@ -1350,7 +1350,7 @@ def check_func_def( if ( arg_type.variance == COVARIANT and defn.name not in ("__init__", "__new__", "__post_init__") - and not is_private(defn.name) # private methods are not inherited + and "mypy-" not in defn.name # skip internally added methods ): ctx: Context = arg_type if ctx.line < 0: @@ -1993,7 +1993,6 @@ def check_explicit_override_decorator( and found_method_base_classes and not defn.is_explicit_override and defn.name not in ("__init__", "__new__") - and not is_private(defn.name) ): self.msg.explicit_override_decorator_missing( defn.name, found_method_base_classes[0].fullname, context or defn @@ -2050,7 +2049,7 @@ def check_method_or_accessor_override_for_base( base_attr = base.names.get(name) if base_attr: # First, check if we override a final (always an error, even with Any types). - if is_final_node(base_attr.node) and not is_private(name): + if is_final_node(base_attr.node): self.msg.cant_override_final(name, base.name, defn) # Second, final can't override anything writeable independently of types. if defn.is_final: @@ -2415,9 +2414,6 @@ def check_override( if original.type_is is not None and override.type_is is None: fail = True - if is_private(name): - fail = False - if fail: emitted_msg = False @@ -2720,25 +2716,26 @@ def check_enum(self, defn: ClassDef) -> None: def check_final_enum(self, defn: ClassDef, base: TypeInfo) -> None: for sym in base.names.values(): - if self.is_final_enum_value(sym): + if self.is_final_enum_value(sym, base): self.fail(f'Cannot extend enum with existing members: "{base.name}"', defn) break - def is_final_enum_value(self, sym: SymbolTableNode) -> bool: + def is_final_enum_value(self, sym: SymbolTableNode, base: TypeInfo) -> bool: if isinstance(sym.node, (FuncBase, Decorator)): return False # A method is fine if not isinstance(sym.node, Var): return True # Can be a class or anything else # Now, only `Var` is left, we need to check: - # 1. Private name like in `__prop = 1` + # 1. Mangled name like in `_class__prop = 1` # 2. Dunder name like `__hash__ = some_hasher` # 3. Sunder name like `_order_ = 'a, b, c'` # 4. If it is a method / descriptor like in `method = classmethod(func)` + name = sym.node.name if ( - is_private(sym.node.name) - or is_dunder(sym.node.name) - or is_sunder(sym.node.name) + maybe_mangled(name, base.name) + or is_dunder(name) + or is_sunder(name) # TODO: make sure that `x = @class/staticmethod(func)` # and `x = property(prop)` both work correctly. # Now they are incorrectly counted as enum members. @@ -2849,12 +2846,8 @@ def check_multiple_inheritance(self, typ: TypeInfo) -> None: # Verify that inherited attributes are compatible. mro = typ.mro[1:] all_names = {name for base in mro for name in base.names} + # Sort for reproducible message order. for name in sorted(all_names - typ.names.keys()): - # Sort for reproducible message order. - # Attributes defined in both the type and base are skipped. - # Normal checks for attribute compatibility should catch any problems elsewhere. - if is_private(name): - continue # Compare the first base defining a name with the rest. # Remaining bases may not be pairwise compatible as the first base provides # the used definition. @@ -2966,7 +2959,7 @@ class C(B, A[int]): ... # this is unsafe because... ok = True # Final attributes can never be overridden, but can override # non-final read-only attributes. - if is_final_node(second.node) and not is_private(name): + if is_final_node(second.node): self.msg.cant_override_final(name, base2.name, ctx) if is_final_node(first.node): self.check_if_final_var_override_writable(name, second.node, ctx) @@ -3436,9 +3429,6 @@ def check_compatibility_all_supers( ): continue - if is_private(lvalue_node.name): - continue - base_type, base_node = self.lvalue_type_from_base(lvalue_node, base) custom_setter = is_custom_settable_property(base_node) if isinstance(base_type, PartialType): @@ -3642,8 +3632,6 @@ def check_compatibility_final_super( """ if not isinstance(base_node, (Var, FuncBase, Decorator)): return True - if is_private(node.name): - return True if base_node.is_final and (node.is_final or not isinstance(base_node, Var)): # Give this error only for explicit override attempt with `Final`, or # if we are overriding a final method with variable. @@ -8942,11 +8930,6 @@ def is_overlapping_types_for_overload(left: Type, right: Type) -> bool: ) -def is_private(node_name: str) -> bool: - """Check if node is private to class definition.""" - return node_name.startswith("__") and not node_name.endswith("__") - - def is_string_literal(typ: Type) -> bool: strs = try_getting_str_literals_from_type(typ) return strs is not None and len(strs) == 1 diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 0994d0df400b..30719468beb2 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -74,6 +74,7 @@ get_proper_type, ) from mypy.typetraverser import TypeTraverserVisitor +from mypy.util import is_dunder, maybe_mangled if TYPE_CHECKING: # import for forward declaration only import mypy.checker @@ -1177,7 +1178,7 @@ def analyze_enum_class_attribute_access( if name in EXCLUDED_ENUM_ATTRIBUTES: return report_missing_attribute(mx.original_type, itype, name, mx) # Dunders and private names are not Enum members - if name.startswith("__") and name.replace("_", "") != "": + if is_dunder(name) or maybe_mangled(name, itype.type.name): return None node = itype.type.get(name) diff --git a/mypy/fastparse.py b/mypy/fastparse.py index b9a55613ec16..eacb69df249f 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -3,7 +3,7 @@ import re import sys import warnings -from collections.abc import Sequence +from collections.abc import Iterable, Sequence from typing import Any, Callable, Final, Literal, Optional, TypeVar, Union, cast, overload from mypy import defaults, errorcodes as codes, message_registry @@ -353,6 +353,103 @@ def find_disallowed_expression_in_annotation_scope(expr: ast3.expr | None) -> as return None +_T_FuncDef = TypeVar("_T_FuncDef", ast3.FunctionDef, ast3.AsyncFunctionDef) + + +class NameMangler(ast3.NodeTransformer): + """Mangle (nearly) all private identifiers within a class body (including nested classes).""" + + mangled2unmangled: dict[str, str] + _classname_complete: str + _classname_trimmed: str + _mangle_annotations: bool + _unmangled_args: set[str] + + def __init__(self, classname: str, mangle_annotations: bool) -> None: + self.mangled2unmangled = {} + self._classname_complete = classname + self._classname_trimmed = classname.lstrip("_") + self._mangle_annotations = mangle_annotations + self._unmangled_args = set() + + def _mangle_name(self, name: str) -> str: + if name.startswith("__") and not name.endswith("__"): + mangled = f"_{self._classname_trimmed}{name}" + self.mangled2unmangled[mangled] = name + return mangled + return name + + def _mangle_slots(self, node: ast3.ClassDef) -> None: + for assign in node.body: + if isinstance(assign, ast3.Assign): + for target in assign.targets: + if isinstance(target, ast3.Name) and (target.id == "__slots__"): + constants: Iterable[ast3.expr] = () + if isinstance(values := assign.value, ast3.Constant): + constants = (values,) + elif isinstance(values, (ast3.Tuple, ast3.List)): + constants = values.elts + elif isinstance(values, ast3.Dict): + constants = (key for key in values.keys if key is not None) + for value in constants: + if isinstance(value, ast3.Constant) and isinstance(value.value, str): + value.value = self._mangle_name(value.value) + + def visit_ClassDef(self, node: ast3.ClassDef) -> ast3.ClassDef: + if self._classname_complete == node.name: + for stmt in node.body: + self.visit(stmt) + self._mangle_slots(node) + else: + for dec in node.decorator_list: + self.visit(dec) + NameMangler(node.name, self._mangle_annotations).visit(node) + node.name = self._mangle_name(node.name) + return node + + def _visit_funcdef(self, node: _T_FuncDef) -> _T_FuncDef: + node.name = self._mangle_name(node.name) + self = NameMangler(self._classname_complete, self._mangle_annotations) + self.visit(node.args) + for dec in node.decorator_list: + self.visit(dec) + if self._mangle_annotations and (node.returns is not None): + self.visit(node.returns) + for stmt in node.body: + self.visit(stmt) + return node + + def visit_FunctionDef(self, node: ast3.FunctionDef) -> ast3.FunctionDef: + return self._visit_funcdef(node) + + def visit_AsyncFunctionDef(self, node: ast3.AsyncFunctionDef) -> ast3.AsyncFunctionDef: + return self._visit_funcdef(node) + + def visit_arg(self, node: ast3.arg) -> ast3.arg: + self._unmangled_args.add(node.arg) + if self._mangle_annotations and (node.annotation is not None): + self.visit(node.annotation) + return node + + def visit_AnnAssign(self, node: ast3.AnnAssign) -> ast3.AnnAssign: + self.visit(node.target) + if node.value is not None: + self.visit(node.value) + if self._mangle_annotations: + self.visit(node.annotation) + return node + + def visit_Attribute(self, node: ast3.Attribute) -> ast3.Attribute: + node.attr = self._mangle_name(node.attr) + self.generic_visit(node) + return node + + def visit_Name(self, node: Name) -> Name: + if node.id not in self._unmangled_args: + node.id = self._mangle_name(node.id) + return node + + class ASTConverter: def __init__( self, @@ -1135,6 +1232,15 @@ def visit_ClassDef(self, n: ast3.ClassDef) -> ClassDef: if sys.version_info >= (3, 12) and n.type_params: explicit_type_params = self.translate_type_params(n.type_params) + mangle_annotations = not self.is_stub and not any( + isinstance(i, ImportFrom) + and (i.id == "__future__") + and any(j[0] == "annotations" for j in i.names) + for i in self.imports + ) + mangler = NameMangler(n.name, mangle_annotations) + mangler.visit(n) + cdef = ClassDef( n.name, self.as_required_block(n.body), @@ -1143,6 +1249,7 @@ def visit_ClassDef(self, n: ast3.ClassDef) -> ClassDef: metaclass=dict(keywords).get("metaclass"), keywords=keywords, type_args=explicit_type_params, + mangled2unmangled=mangler.mangled2unmangled, ) cdef.decorators = self.translate_expr_list(n.decorator_list) self.set_line(cdef, n) diff --git a/mypy/messages.py b/mypy/messages.py index 25c4ed68ccb5..2e362ab35d23 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -490,7 +490,9 @@ def has_no_attr( ) failed = True else: - alternatives = set(original_type.type.names.keys()) + alternatives: set[str] = set() + for type_ in original_type.type.mro: + alternatives.update(type_.names.keys()) if module_symbol_table is not None: alternatives |= { k for k, v in module_symbol_table.items() if v.module_public diff --git a/mypy/nodes.py b/mypy/nodes.py index 6487ee4b745c..6658a2405cae 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -14,7 +14,7 @@ import mypy.strconv from mypy.options import Options -from mypy.util import is_typeshed_file, short_type +from mypy.util import is_dunder, is_typeshed_file, maybe_mangled, short_type from mypy.visitor import ExpressionVisitor, NodeVisitor, StatementVisitor if TYPE_CHECKING: @@ -1136,6 +1136,7 @@ class ClassDef(Statement): "has_incompatible_baseclass", "docstring", "removed_statements", + "mangled2unmangled", ) __match_args__ = ("name", "defs") @@ -1159,6 +1160,7 @@ class ClassDef(Statement): has_incompatible_baseclass: bool # Used by special forms like NamedTuple and TypedDict to store invalid statements removed_statements: list[Statement] + mangled2unmangled: Final[dict[str, str]] def __init__( self, @@ -1169,6 +1171,7 @@ def __init__( metaclass: Expression | None = None, keywords: list[tuple[str, Expression]] | None = None, type_args: list[TypeParam] | None = None, + mangled2unmangled: dict[str, str] | None = None, ) -> None: super().__init__() self.name = name @@ -1186,6 +1189,7 @@ def __init__( self.has_incompatible_baseclass = False self.docstring: str | None = None self.removed_statements = [] + self.mangled2unmangled = mangled2unmangled or {} @property def fullname(self) -> str: @@ -3250,7 +3254,7 @@ def enum_members(self) -> list[str]: ( isinstance(sym.node, Var) and name not in EXCLUDED_ENUM_ATTRIBUTES - and not name.startswith("__") + and not (is_dunder(name) or maybe_mangled(name, self.name)) and sym.node.has_explicit_value and not ( isinstance( diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index 90c983b0bacd..080ff206b430 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -91,8 +91,8 @@ frozen_default=False, field_specifiers=DATACLASS_FIELD_SPECIFIERS, ) -_INTERNAL_REPLACE_SYM_NAME: Final = "__mypy-replace" -_INTERNAL_POST_INIT_SYM_NAME: Final = "__mypy-post_init" +_INTERNAL_REPLACE_SYM_NAME: Final = "mypy-replace" +_INTERNAL_POST_INIT_SYM_NAME: Final = "mypy-post_init" class DataclassAttribute: @@ -422,20 +422,22 @@ def _add_internal_replace_method(self, attributes: list[DataclassAttribute]) -> Stashes the signature of 'dataclasses.replace(...)' for this specific dataclass to be used later whenever 'dataclasses.replace' is called for this dataclass. """ + mangled_name = _mangle_internal_sym_name(self._cls.fullname, _INTERNAL_REPLACE_SYM_NAME) add_method_to_class( self._api, self._cls, - _INTERNAL_REPLACE_SYM_NAME, + mangled_name, args=[attr.to_argument(self._cls.info, of="replace") for attr in attributes], return_type=NoneType(), is_staticmethod=True, ) def _add_internal_post_init_method(self, attributes: list[DataclassAttribute]) -> None: + mangled_name = _mangle_internal_sym_name(self._cls.fullname, _INTERNAL_POST_INIT_SYM_NAME) add_method_to_class( self._api, self._cls, - _INTERNAL_POST_INIT_SYM_NAME, + mangled_name, args=[ attr.to_argument(self._cls.info, of="__post_init__") for attr in attributes @@ -974,6 +976,16 @@ def dataclass_class_maker_callback(ctx: ClassDefContext) -> bool: return transformer.transform() +def _mangle_internal_sym_name(type_fullname: str, member_name: str) -> str: + """Create an internal symbol name with the class name mangled in as usual, but that also + contains the class module path to avoid false positives when subclassing a dataclass with + same name.""" + module_name, _, type_name = type_fullname.rpartition(".") + module_name = module_name.replace(".", "-") + type_name = type_name.lstrip("_") + return f"_{type_name}__{module_name}__{member_name}" + + def _get_transform_spec(reason: Expression) -> DataclassTransformSpec: """Find the relevant transform parameters from the decorator/parent class/metaclass that triggered the dataclasses plugin. @@ -1032,7 +1044,8 @@ def _get_expanded_dataclasses_fields( ctx, get_proper_type(typ.upper_bound), display_typ, parent_typ ) elif isinstance(typ, Instance): - replace_sym = typ.type.get_method(_INTERNAL_REPLACE_SYM_NAME) + mangled_name = _mangle_internal_sym_name(typ.type.fullname, _INTERNAL_REPLACE_SYM_NAME) + replace_sym = typ.type.get_method(mangled_name) if replace_sym is None: return None replace_sig = replace_sym.type @@ -1116,7 +1129,8 @@ def check_post_init(api: TypeChecker, defn: FuncItem, info: TypeInfo) -> None: return assert isinstance(defn.type, FunctionLike) - ideal_sig_method = info.get_method(_INTERNAL_POST_INIT_SYM_NAME) + mangled_name = _mangle_internal_sym_name(info.fullname, _INTERNAL_POST_INIT_SYM_NAME) + ideal_sig_method = info.get_method(mangled_name) assert ideal_sig_method is not None and ideal_sig_method.type is not None ideal_sig = ideal_sig_method.type assert isinstance(ideal_sig, ProperType) # we set it ourselves diff --git a/mypy/semanal.py b/mypy/semanal.py index 1a64731057e2..5f96df8ea703 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -304,7 +304,14 @@ ) from mypy.types_utils import is_invalid_recursive_alias, store_argument_type from mypy.typevars import fill_typevars -from mypy.util import correct_relative_import, is_dunder, module_prefix, unmangle, unnamed_function +from mypy.util import ( + correct_relative_import, + is_dunder, + maybe_mangled, + module_prefix, + unmangle, + unnamed_function, +) from mypy.visitor import NodeVisitor T = TypeVar("T") @@ -4300,7 +4307,7 @@ def analyze_name_lvalue( kind == MDEF and isinstance(self.type, TypeInfo) and self.type.is_enum - and not name.startswith("__") + and not (is_dunder(name) or maybe_mangled(name, self.type.name)) ): # Special case: we need to be sure that `Enum` keys are unique. if existing is not None and not isinstance(existing.node, PlaceholderNode): diff --git a/mypy/semanal_typeddict.py b/mypy/semanal_typeddict.py index 0d6a0b7ff87f..7ba787c111d5 100644 --- a/mypy/semanal_typeddict.py +++ b/mypy/semanal_typeddict.py @@ -325,6 +325,7 @@ def analyze_typeddict_classdef_fields( self.fail(TPDICT_CLASS_ERROR, stmt) else: name = stmt.lvalues[0].name + name = defn.mangled2unmangled.get(name, name) if name in (oldfields or []): self.fail(f'Overwriting TypedDict field "{name}" while extending', stmt) if name in fields: diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 41bb4601e23f..4e9c1942c675 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -2071,9 +2071,10 @@ def infer_variance(info: TypeInfo, i: int) -> bool: tvar = info.defn.type_vars[i] self_type = fill_typevars(info) for member in all_non_object_members(info): - # __mypy-replace is an implementation detail of the dataclass plugin - if member in ("__init__", "__new__", "__mypy-replace"): + if member in ("__init__", "__new__"): continue + if "mypy-" in member: + continue # skip internally added methods if isinstance(self_type, TupleType): self_type = mypy.typeops.tuple_fallback(self_type) diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index f3199dae7f73..94a36a6279a5 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -1833,7 +1833,7 @@ class X: def __mangle_good(self, text): pass def __mangle_bad(self, text): pass """, - error="X.__mangle_bad", + error="X._X__mangle_bad", ) yield Case( stub=""" @@ -1850,7 +1850,7 @@ class __Mangled2: def __mangle_good(self, text): pass def __mangle_bad(self, text): pass """, - error="Klass.__Mangled1.__Mangled2.__mangle_bad", + error="Klass._Klass__Mangled1._Mangled1__Mangled2._Mangled2__mangle_bad", ) yield Case( stub=""" @@ -1863,7 +1863,7 @@ class __Dunder__: def __mangle_good(self, text): pass def __mangle_bad(self, text): pass """, - error="__Dunder__.__mangle_bad", + error="__Dunder__._Dunder____mangle_bad", ) yield Case( stub=""" @@ -1876,7 +1876,7 @@ class _Private: def __mangle_good(self, text): pass def __mangle_bad(self, text): pass """, - error="_Private.__mangle_bad", + error="_Private._Private__mangle_bad", ) @collect_cases diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 9208630937e7..152b5d04c53f 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -114,6 +114,7 @@ ) from mypy.types_utils import get_bad_type_type_item from mypy.typevars import fill_typevars +from mypy.util import maybe_mangled T = TypeVar("T") @@ -984,7 +985,7 @@ def analyze_unbound_type_without_type_info( isinstance(sym.node, Var) and sym.node.info and sym.node.info.is_enum - and not sym.node.name.startswith("__") + and not maybe_mangled(sym.node.name, sym.node.info.name) ): value = sym.node.name base_enum_short_name = sym.node.info.name @@ -2020,7 +2021,7 @@ def tuple_type(self, items: list[Type], line: int, column: int) -> TupleType: class MsgCallback(Protocol): - def __call__(self, __msg: str, __ctx: Context, *, code: ErrorCode | None = None) -> None: ... + def __call__(self, msg: str, ctx: Context, /, *, code: ErrorCode | None = None) -> None: ... def get_omitted_any( diff --git a/mypy/util.py b/mypy/util.py index d3f49f74bbae..f2984fa8f81c 100644 --- a/mypy/util.py +++ b/mypy/util.py @@ -62,13 +62,17 @@ def is_dunder(name: str, exclude_special: bool = False) -> bool: """ if exclude_special and name in SPECIAL_DUNDERS: return False - return name.startswith("__") and name.endswith("__") + return name.startswith("__") and name.endswith("__") and bool(name.replace("_", "")) def is_sunder(name: str) -> bool: return not is_dunder(name) and name.startswith("_") and name.endswith("_") +def maybe_mangled(member_name: str, type_name: str) -> bool: + return member_name.startswith(f"_{type_name}__") and not member_name.endswith("__") + + def split_module_names(mod_name: str) -> list[str]: """Return the module and all parent module names. diff --git a/test-data/unit/check-async-await.test b/test-data/unit/check-async-await.test index 0ef08e5a0775..c8ce09605195 100644 --- a/test-data/unit/check-async-await.test +++ b/test-data/unit/check-async-await.test @@ -177,7 +177,7 @@ async def f() -> None: [builtins fixtures/async_await.pyi] [typing fixtures/typing-async.pyi] [out] -main:7: error: "Coroutine[Any, Any, AsyncGenerator[str, None]]" has no attribute "__aiter__" (not async iterable) +main:7: error: "Coroutine[Any, Any, AsyncGenerator[str, None]]" has no attribute "__aiter__"; maybe "__await__"? (not async iterable) main:7: note: Maybe you forgot to use "await"? [case testAsyncForErrorCanBeIgnored] diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index 887a9052d0b9..673a1a18d219 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -226,6 +226,25 @@ reveal_type(d.foo) # N: Revealed type is "builtins.str" reveal_type(d.bar) # N: Revealed type is "builtins.int" [builtins fixtures/dataclasses.pyi] +[case testDataclassCompatibleOverrideEqualName] +from dataclasses import dataclass, InitVar +import m + +@dataclass +class D(m.D): + x: InitVar[int] + +[file m.py] +from dataclasses import dataclass + +@dataclass +class D: + y: int + +[out] + +[builtins fixtures/dataclasses.pyi] + [case testDataclassIncompatibleFrozenOverride] from dataclasses import dataclass diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index a3abf53e29ac..b293c1769557 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -2267,8 +2267,7 @@ class MyEnum(Enum): B = 2 __my_dict = {A: "ham", B: "spam"} -# TODO: change the next line to use MyEnum._MyEnum__my_dict when mypy implements name mangling -x: MyEnum = MyEnum.__my_dict # E: Incompatible types in assignment (expression has type "Dict[int, str]", variable has type "MyEnum") +x: MyEnum = MyEnum._MyEnum__my_dict # E: Incompatible types in assignment (expression has type "Dict[int, str]", variable has type "MyEnum") [builtins fixtures/enum.pyi] [case testEnumWithPrivateAttributeReachability] diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 767b55efcac2..45559a9f7c8a 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -2995,7 +2995,7 @@ from typing import TypeVar, Protocol, Generic, Optional T = TypeVar('T') class F(Protocol[T]): - def __call__(self, __x: T) -> T: ... + def __call__(self, x: T, /) -> T: ... def lift(f: F[T]) -> F[Optional[T]]: ... def g(x: T) -> T: diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index cb0b11bf013c..7d23be968daf 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -3616,7 +3616,7 @@ T = TypeVar('T', contravariant=True) S = TypeVar('S', contravariant=True) class Call(Protocol[T, S]): - def __call__(self, __x: T, *args: S) -> None: ... + def __call__(self, x: T, /, *args: S) -> None: ... def f(x: Call[T, S]) -> Tuple[T, S]: ... def g(*x: int) -> None: ... diff --git a/test-data/unit/check-literal.test b/test-data/unit/check-literal.test index 0b2721e77624..94fba8eef5c7 100644 --- a/test-data/unit/check-literal.test +++ b/test-data/unit/check-literal.test @@ -2468,8 +2468,7 @@ b: Literal[Color.BLUE] bad1: Literal[Color] # E: Parameter 1 of Literal[...] is invalid bad2: Literal[Color.func] # E: Parameter 1 of Literal[...] is invalid bad3: Literal[Color.func()] # E: Invalid type: Literal[...] cannot contain arbitrary expressions -# TODO: change the next line to use Color._Color__ROUGE when mypy implements name mangling -bad4: Literal[Color.__ROUGE] # E: Parameter 1 of Literal[...] is invalid +bad4: Literal[Color._Color__ROUGE] # E: Parameter 1 of Literal[...] is invalid def expects_color(x: Color) -> None: pass def expects_red(x: Literal[Color.RED]) -> None: pass diff --git a/test-data/unit/check-mangling.test b/test-data/unit/check-mangling.test new file mode 100644 index 000000000000..03c718f3e1cf --- /dev/null +++ b/test-data/unit/check-mangling.test @@ -0,0 +1,348 @@ +[case testNameManglingAttribute] + +class A: + __x: int + + def __init__(self) -> None: + self.__y: int + self._A__z: int + + def get(self) -> None: + reveal_type(self.__x) # N: Revealed type is "builtins.int" + reveal_type(self._A__x) # N: Revealed type is "builtins.int" + reveal_type(self.__y) # N: Revealed type is "builtins.int" + reveal_type(self._A__y) # N: Revealed type is "builtins.int" + reveal_type(self.__z) # N: Revealed type is "builtins.int" + reveal_type(self._A__z) # N: Revealed type is "builtins.int" + + def set(self) -> None: + self.__x = "" # E: Incompatible types in assignment (expression has type "str", variable has type "int") + self._A__x = "" # E: Incompatible types in assignment (expression has type "str", variable has type "int") + self.__y += "" # E: Unsupported operand types for + ("int" and "str") + self._A__y += "" # E: Unsupported operand types for + ("int" and "str") + self.__z = "" # E: Incompatible types in assignment (expression has type "str", variable has type "int") + self._A__z = "" # E: Incompatible types in assignment (expression has type "str", variable has type "int") + +class B(A): + __x: str + + def __init__(self) -> None: + self.__y: str + self._A__z: str # E: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int") + + def get(self) -> None: + reveal_type(self.__x) # N: Revealed type is "builtins.str" + reveal_type(self._A__x) # N: Revealed type is "builtins.int" + reveal_type(self._B__x) # N: Revealed type is "builtins.str" + reveal_type(self.__y) # N: Revealed type is "builtins.str" + reveal_type(self._A__y) # N: Revealed type is "builtins.int" + reveal_type(self._B__y) # N: Revealed type is "builtins.str" + self.__z # E: "B" has no attribute "_B__z"; maybe "_A__z", "_B__x", or "_B__y"? + reveal_type(self._A__z) # N: Revealed type is "builtins.str" + self._B__z # E: "B" has no attribute "_B__z"; maybe "_A__z", "_B__x", or "_B__y"? + +[case testNameManglingMethod] + +class A: + + def __f(self) -> int: + ... + + def g(self) -> None: + reveal_type(self.__f()) # N: Revealed type is "builtins.int" + reveal_type(self._A__f()) # N: Revealed type is "builtins.int" + + async def __h(self) -> int: + ... + + async def i(self) -> None: + await reveal_type(self.__h()) # N: Revealed type is "typing.Coroutine[Any, Any, builtins.int]" + await reveal_type(self._A__h()) # N: Revealed type is "typing.Coroutine[Any, Any, builtins.int]" + +class B(A): + + def j(self) -> None: + reveal_type(self._A__f()) # N: Revealed type is "builtins.int" + self.__f() # E: "B" has no attribute "_B__f"; maybe "_A__f"? + self._B__f() # E: "B" has no attribute "_B__f"; maybe "_A__f"? + + async def k(self) -> None: + await reveal_type(self._A__h()) # N: Revealed type is "typing.Coroutine[Any, Any, builtins.int]" + self.__h() # E: "B" has no attribute "_B__h"; maybe "_A__h"? + self._B__h() # E: "B" has no attribute "_B__h"; maybe "_A__h"? + +class C(B): + + def __f(self) -> str: + ... + + def _A__f(self) -> str: # E: Return type "str" of "_A__f" incompatible with return type "int" in supertype "A" + ... + + async def __h(self) -> str: + ... + + async def _A__h(self) -> str: # E: Return type "Coroutine[Any, Any, str]" of "_A__h" incompatible with return type "Coroutine[Any, Any, int]" in supertype "A" + ... + +[case testNameManglingDecorator] +from typing import Callable, Protocol, Type + +class FuncWrapper(Protocol): + def __call__(__self, f: Callable[[A], int]) -> Callable[[A], str]: ... +def decorator_func(string: str) -> FuncWrapper: ... + +class ClassModifier(Protocol): + def __call__(__self, c: Type[A._A__B]) -> Type[A._A__B]: ... +def decorator_class(string: str) -> ClassModifier: ... + +class A: + __x: str = "test" + + @decorator_func(__x) + def __wrapped(self) -> int: ... + + @decorator_class(__x) + class __B: ... + +a: A +a.__wrapped() # E: "A" has no attribute "__wrapped"; maybe "_A__wrapped"? +reveal_type(a._A__wrapped()) # N: Revealed type is "builtins.str" +a.__B # E: "A" has no attribute "__B" +reveal_type(a._A__B) # N: Revealed type is "def () -> __main__.A._A__B" + +[case testNameManglingNestedClass] + +class A: + class __B: + __y: int + + def __g(self) -> str: + reveal_type(self.__y) # N: Revealed type is "builtins.int" + reveal_type(self._B__y) # N: Revealed type is "builtins.int" + return "x" + + reveal_type(__g) # N: Revealed type is "def (self: __main__.A._A__B) -> builtins.str" + reveal_type(_B__g) # N: Revealed type is "def (self: __main__.A._A__B) -> builtins.str" + + __x: int + + def __f(self) -> float: + reveal_type(self.__x) # N: Revealed type is "builtins.int" + reveal_type(self._A__x) # N: Revealed type is "builtins.int" + b = self.__B() + b.__y # E: "_A__B" has no attribute "_A__y"; maybe "_B__y"? + b._A__y # E: "_A__B" has no attribute "_A__y"; maybe "_B__y"? + reveal_type(b._B__y) # N: Revealed type is "builtins.int" + return 1.0 + + reveal_type(__f) # N: Revealed type is "def (self: __main__.A) -> builtins.float" + reveal_type(_A__f) # N: Revealed type is "def (self: __main__.A) -> builtins.float" + +[case testNameManglingInheritance] + +class __A: ... +class B(__A): ... + +import enum as __enum +class C(__enum.Flag): ... +[builtins fixtures/tuple.pyi] + +[case testNameManglingAnnotationsPast] +from typing import Generic, TypeVar + +__T = TypeVar("__T") + +class A(Generic[__T]): + __x: __T # E: Name "_A__T" is not defined + +__y = int +class __B: + def f1(self, a: A[int]) -> __y: # E: Name "_B__y" is not defined + a.__x # E: "A[int]" has no attribute "_B__x"; maybe "_A__x"? + + def f2(self, a: A) -> "__y": + a.__x # E: "A[Any]" has no attribute "_B__x"; maybe "_A__x"? + return 1 + +class C: + b1: __B # E: Name "_C__B" is not defined + b2: "__B" + + def f1(self, __x: __y, __z: int) -> None: # E: Name "_C__y" is not defined + reveal_type(__z) # N: Revealed type is "builtins.int" + + def f2(self, __x: "__y", __z: "int") -> None: + reveal_type(__x) # N: Revealed type is "builtins.int" + reveal_type(__z) # N: Revealed type is "builtins.int" + +reveal_type(C().b2) # N: Revealed type is "__main__.__B" + +[case testNameManglingAnnotationsFuture] +from __future__ import annotations +from typing import Generic, TypeVar + +__T = TypeVar("__T") +class A(Generic[__T]): + __x: __T + +__y = int +class __B: + def f1(self, a: A[int]) -> __y: + a.__x # E: "A[int]" has no attribute "_B__x"; maybe "_A__x"? + return 1 + + def f2(self, a: A) -> "__y": + a.__x # E: "A[Any]" has no attribute "_B__x"; maybe "_A__x"? + return 1 + +class C: + b1: __B + b2: "__B" + + def f1(self, __x: __y, __z: int) -> None: + reveal_type(__x) # N: Revealed type is "builtins.int" + reveal_type(__z) # N: Revealed type is "builtins.int" + + def f2(self, __x: "__y", __z: "int") -> None: + reveal_type(__x) # N: Revealed type is "builtins.int" + reveal_type(__z) # N: Revealed type is "builtins.int" + +reveal_type(C().b1) # N: Revealed type is "__main__.__B" +reveal_type(C().b2) # N: Revealed type is "__main__.__B" +[builtins fixtures/tuple.pyi] + +[case testNameManglingSlots] + +class A: + __slots__ = ["__x"] + + def __init__(self) -> None: + self.__x = 1 + +A().__x = 1 # E: "A" has no attribute "__x" +A()._A__x = 1 + +class B(A): + def __init__(self) -> None: + self.__x = 1 + +B().__x = 1 # E: "B" has no attribute "__x" +B()._B__x = 1 + +class C(A): + __slots__ = [] + + def __init__(self) -> None: + self.__x = 1 # E: Trying to assign name "_C__x" that is not in "__slots__" of type "__main__.C" + +C().__x = 1 # E: "C" has no attribute "__x" +C()._C__x = 1 # E: Trying to assign name "_C__x" that is not in "__slots__" of type "__main__.C" + +class D(A): + __slots__ = ["__x"] + + def __init__(self) -> None: + self.__x = 1 + +D().__x = 1 # E: "D" has no attribute "__x" +D()._D__x = 1 + +class E(A): + __slots__ = ("__x",) + + def __init__(self) -> None: + self.__x = 1 + +E().__x = 1 # E: "E" has no attribute "__x" +E()._E__x = 1 + +class F(A): + __slots__ = "__x" + + def __init__(self) -> None: + self.__x = 1 + +F().__x = 1 # E: "F" has no attribute "__x" +F()._F__x = 1 + +class G(A): + __slots__ = {"__x": "docstring"} + + def __init__(self) -> None: + self.__x = 1 + +G().__x = 1 # E: "G" has no attribute "__x" +G()._G__x = 1 +[builtins fixtures/dict.pyi] + +[case testNameManglingTypedDictBasics] +from typing import TypedDict + +class A(TypedDict): + __a: int + +a: A +reveal_type(a) # N: Revealed type is "TypedDict('__main__.A', {'__a': builtins.int})" +reveal_type(a["__a"]) # N: Revealed type is "builtins.int" +a = {"__a": 1} +a = {"_A__a": 1} # E: Missing key "__a" for TypedDict "A" \ + # E: Extra key "_A__a" for TypedDict "A" + +class B(A): + __b: int + +b: B +reveal_type(b) # N: Revealed type is "TypedDict('__main__.B', {'__a': builtins.int, '__b': builtins.int})" +reveal_type(b["__a"] + b["__b"]) # N: Revealed type is "builtins.int" +b = {"__a": 1, "__b": 2} +b = {"_A__a": 1, "_B__b": 2} # E: Missing keys ("__a", "__b") for TypedDict "B" \ + # E: Extra keys ("_A__a", "_B__b") for TypedDict "B" + +class C(TypedDict): + __c: int + +class D(B, C): + pass + +d: D +reveal_type(d) # N: Revealed type is "TypedDict('__main__.D', {'__c': builtins.int, '__a': builtins.int, '__b': builtins.int})" +reveal_type(d["__a"] + d["__b"] + d["__c"]) # N: Revealed type is "builtins.int" +d = {"__a": 1, "__b": 2, "__c": 3} +d = {"_A__a": 1, "_B__b": 2, "_C__c": 3} # E: Missing keys ("__c", "__a", "__b") for TypedDict "D" \ + # E: Extra keys ("_A__a", "_B__b", "_C__c") for TypedDict "D" + +class E(D): + __a: int # E: Overwriting TypedDict field "__a" while extending + __b: int # E: Overwriting TypedDict field "__b" while extending + __c: int # E: Overwriting TypedDict field "__c" while extending + +[typing fixtures/typing-typeddict.pyi] + +[case testNameManglingTypedDictNotTotal] +from typing import TypedDict + +class A(TypedDict, total=False): + __a: int + +a: A +reveal_type(a) # N: Revealed type is "TypedDict('__main__.A', {'__a'?: builtins.int})" +reveal_type(a["__a"]) # N: Revealed type is "builtins.int" +a = {"__a": 1} +a = {"_A__a": 1} # E: Extra key "_A__a" for TypedDict "A" + +[typing fixtures/typing-typeddict.pyi] + +[case testNameManglingTypedDictAlternativeSyntax] +from typing import TypedDict + +A = TypedDict("A", {"__a": int}) + +a: A +reveal_type(a) # N: Revealed type is "TypedDict('__main__.A', {'__a': builtins.int})" +reveal_type(a["__a"]) # N: Revealed type is "builtins.int" +a = {"__a": 1} +a = {"_A__a": 1} # E: Missing key "__a" for TypedDict "A" \ + # E: Extra key "_A__a" for TypedDict "A" + +[typing fixtures/typing-typeddict.pyi] diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index a7124b7a83d3..e334af965197 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -2584,7 +2584,7 @@ class Caller(Protocol): def __call__(self, x: str) -> None: ... class CallerAnon(Protocol): - def __call__(self, __x: str) -> None: ... + def __call__(self, x: str, /) -> None: ... def call(x: str) -> None: pass @@ -2656,7 +2656,7 @@ class C(B): ... class D(B): ... class Call(Protocol): - def __call__(self, __x: C) -> B: ... + def __call__(self, x: C, /) -> B: ... Normal = Callable[[D], A] def a(x: Call) -> None: ... diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index f90baed0eb16..0c1de300a289 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -633,7 +633,7 @@ from typing import overload class Some: @overload - def foo(self, __a: int): ... + def foo(self, a: int, /): ... @overload def foo(self, a: float, /): ... @overload @@ -654,7 +654,7 @@ from typing import overload class Some: @overload @classmethod - def foo(cls, __a: int): ... + def foo(cls, a: int, /): ... @overload @classmethod def foo(cls, a: float, /): ... diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 4c49bd7093cd..d9ca6d75ccd3 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -951,17 +951,17 @@ In = TypeVar('In') Out = TypeVar('Out') Other = TypeVar('Other') -_1 = TypeVar('_1') -_2 = TypeVar('_2') -__1 = TypeVar('__1') -__2 = TypeVar('__2') +A1 = TypeVar('A1') +A2 = TypeVar('A2') +B1 = TypeVar('B1') +B2 = TypeVar('B2') class Lnk(Generic[In, Out]): @overload def __rshift__(self, other: Lnk[Out, Other]) -> Lnk[In,Other]: ... @overload - def __rshift__(self: Lnk[In, Tuple[_1, _2]], - other: Tuple[Lnk[_1, __1], Lnk[_2, __2]]) -> Lnk[In, Tuple[__1, __2]]: ... + def __rshift__(self: Lnk[In, Tuple[A1, A2]], + other: Tuple[Lnk[A1, B1], Lnk[A2, B2]]) -> Lnk[In, Tuple[B1, B2]]: ... def __rshift__(self: Any, other: Any) -> Any: ... diff --git a/test-data/unit/deps.test b/test-data/unit/deps.test index 2c231c9afff6..8947062e0999 100644 --- a/test-data/unit/deps.test +++ b/test-data/unit/deps.test @@ -1389,15 +1389,16 @@ class B(A): [out] -> , m + -> , m + -> m.B._B__m__mypy-replace -> -> , m.B.__init__ - -> , m, m.B.__mypy-replace -> -> -> -> m, m.A, m.B -> m - -> m + -> m -> m -> m.B -> m @@ -1421,16 +1422,17 @@ class B(A): [out] -> , m + -> , m + -> m.B._B__m__mypy-replace -> -> , m.B.__init__ -> - -> , m, m.B.__mypy-replace -> -> -> -> m, m.A, m.B -> m - -> m + -> m -> m -> m.B -> m diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index 0e0e2b1f344d..044a4487e4dc 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -1963,17 +1963,33 @@ from dataclasses import dataclass, replace class A: x: int +@dataclass +class B(A): + y: str + a = A(x=42) a2 = replace(a, x=42) reveal_type(a2) a2 = replace() -a2 = replace(a, x='spam') +a2 = replace(a, x="spam") a2 = replace(a, x=42, q=42) -[out] -_testDataclassReplace.py:9: note: Revealed type is "_testDataclassReplace.A" -_testDataclassReplace.py:10: error: Too few arguments for "replace" -_testDataclassReplace.py:11: error: Argument "x" to "replace" of "A" has incompatible type "str"; expected "int" -_testDataclassReplace.py:12: error: Unexpected keyword argument "q" for "replace" of "A" + +b = B(x=42, y="egg") +b2 = replace(b, x=42, y="egg") +reveal_type(b2) +replace() +replace(b, x="egg", y=42) +replace(b, x=42, y="egg", q=42) +[out] +_testDataclassReplace.py:13: note: Revealed type is "_testDataclassReplace.A" +_testDataclassReplace.py:14: error: Too few arguments for "replace" +_testDataclassReplace.py:15: error: Argument "x" to "replace" of "A" has incompatible type "str"; expected "int" +_testDataclassReplace.py:16: error: Unexpected keyword argument "q" for "replace" of "A" +_testDataclassReplace.py:20: note: Revealed type is "_testDataclassReplace.B" +_testDataclassReplace.py:21: error: Too few arguments for "replace" +_testDataclassReplace.py:22: error: Argument "x" to "replace" of "B" has incompatible type "str"; expected "int" +_testDataclassReplace.py:22: error: Argument "y" to "replace" of "B" has incompatible type "int"; expected "str" +_testDataclassReplace.py:23: error: Unexpected keyword argument "q" for "replace" of "B" [case testGenericInferenceWithTuple] # flags: --new-type-inference