Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit

Permalink
remove method is_private by mangling "__mypy-replace" and "__mypy-p…
Browse files Browse the repository at this point in the history
…ost_init".
  • Loading branch information
tyralla committed Jan 5, 2024
1 parent 01d5c5a commit e37398b
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 36 deletions.
26 changes: 3 additions & 23 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1249,7 +1249,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:
Expand Down Expand Up @@ -1881,7 +1881,6 @@ def check_explicit_override_decorator(
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
Expand Down Expand Up @@ -1924,7 +1923,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:
Expand Down Expand Up @@ -2175,9 +2174,6 @@ def check_override(
if original.type_guard is not None and override.type_guard is None:
fail = True

if is_private(name):
fail = False

if fail:
emitted_msg = False

Expand Down Expand Up @@ -2575,8 +2571,6 @@ def check_multiple_inheritance(self, typ: TypeInfo) -> None:
# Normal checks for attribute compatibility should catch any problems elsewhere.
non_overridden_attrs = base.names.keys() - typ.names.keys()
for name in non_overridden_attrs:
if is_private(name):
continue
for base2 in mro[i + 1 :]:
# We only need to check compatibility of attributes from classes not
# in a subclass relationship. For subclasses, normal (single inheritance)
Expand Down Expand Up @@ -2684,7 +2678,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)
Expand Down Expand Up @@ -3140,9 +3134,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)
if isinstance(base_type, PartialType):
base_type = None
Expand Down Expand Up @@ -3312,8 +3303,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.
Expand Down Expand Up @@ -8259,15 +8248,6 @@ def is_overlapping_types_no_promote_no_uninhabited_no_none(left: Type, right: Ty
)


def is_private(node_name: str) -> bool:
"""Check if node is private to class definition.
Since Mypy supports name mangling, `is_private` is likely only required for
internally introduced names like `__mypy-replace` and `__mypy-post_init`.
"""
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
Expand Down
12 changes: 8 additions & 4 deletions mypy/plugins/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,20 +392,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 = f"_{self._cls.name.lstrip('_')}{_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 = f"_{self._cls.name.lstrip('_')}{_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
Expand Down Expand Up @@ -998,7 +1000,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 = f"_{typ.type.name.lstrip('_')}{_INTERNAL_REPLACE_SYM_NAME}"
replace_sym = typ.type.get_method(mangled_name)
if replace_sym is None:
return None
replace_sig = replace_sym.type
Expand Down Expand Up @@ -1082,7 +1085,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 = f"_{info.name.lstrip('_')}{_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
Expand Down
8 changes: 5 additions & 3 deletions test-data/unit/deps.test
Original file line number Diff line number Diff line change
Expand Up @@ -1385,15 +1385,16 @@ class B(A):

[out]
<m.A.(abstract)> -> <m.B.__init__>, m
<m.A._A__mypy-replace> -> <m.B._A__mypy-replace>, m
<m.A._B__mypy-replace> -> m.B._B__mypy-replace
<m.A.__dataclass_fields__> -> <m.B.__dataclass_fields__>
<m.A.__init__> -> <m.B.__init__>, m.B.__init__
<m.A.__mypy-replace> -> <m.B.__mypy-replace>, m, m.B.__mypy-replace
<m.A.__new__> -> <m.B.__new__>
<m.A.x> -> <m.B.x>
<m.A.y> -> <m.B.y>
<m.A> -> m, m.A, m.B
<m.A[wildcard]> -> m
<m.B.__mypy-replace> -> m
<m.B._B__mypy-replace> -> m
<m.B.y> -> m
<m.B> -> m.B
<m.Z> -> m
Expand All @@ -1417,10 +1418,11 @@ class B(A):

[out]
<m.A.(abstract)> -> <m.B.__init__>, m
<m.A._A__mypy-replace> -> <m.B._A__mypy-replace>, m
<m.A._B__mypy-replace> -> m.B._B__mypy-replace
<m.A.__dataclass_fields__> -> <m.B.__dataclass_fields__>
<m.A.__init__> -> <m.B.__init__>, m.B.__init__
<m.A.__match_args__> -> <m.B.__match_args__>
<m.A.__mypy-replace> -> <m.B.__mypy-replace>, m, m.B.__mypy-replace
<m.A.__new__> -> <m.B.__new__>
<m.A.x> -> <m.B.x>
<m.A.y> -> <m.B.y>
Expand Down
28 changes: 22 additions & 6 deletions test-data/unit/pythoneval.test
Original file line number Diff line number Diff line change
Expand Up @@ -2037,17 +2037,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
Expand Down

0 comments on commit e37398b

Please sign in to comment.