Skip to content

Commit 5051a48

Browse files
authored
Do not use dictionary in CallableType (#19580)
This is mypyc-specific optimization: `dict` creation is quite slow, so we should not create a dict in the `CallableType` constructor. Instead, I store the required info on the relevant `FuncDef` object (and restore the link to definition in `fixup.py`). Quite surprisingly, this gives 2% speedup on my desktop.
1 parent 5b03024 commit 5051a48

File tree

7 files changed

+30
-38
lines changed

7 files changed

+30
-38
lines changed

mypy/checker.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,7 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
730730
assert isinstance(item, Decorator)
731731
item_type = self.extract_callable_type(item.var.type, item)
732732
if item_type is not None:
733-
item_type.definition = item
733+
item_type.definition = item.func
734734
item_types.append(item_type)
735735
if item_types:
736736
defn.type = Overloaded(item_types)

mypy/checkexpr.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2934,7 +2934,7 @@ def infer_overload_return_type(
29342934
if not args_contain_any:
29352935
self.chk.store_types(m)
29362936
if isinstance(infer_type, ProperType) and isinstance(infer_type, CallableType):
2937-
self.chk.check_deprecated(infer_type.definition, context)
2937+
self.chk.warn_deprecated(infer_type.definition, context)
29382938
return ret_type, infer_type
29392939
p_infer_type = get_proper_type(infer_type)
29402940
if isinstance(p_infer_type, CallableType):
@@ -2975,7 +2975,7 @@ def infer_overload_return_type(
29752975
if isinstance(inferred_callable, ProperType) and isinstance(
29762976
inferred_callable, CallableType
29772977
):
2978-
self.chk.check_deprecated(inferred_callable.definition, context)
2978+
self.chk.warn_deprecated(inferred_callable.definition, context)
29792979
return return_types[0], inferred_types[0]
29802980

29812981
def overload_erased_call_targets(

mypy/fixup.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
Overloaded,
3030
Parameters,
3131
ParamSpecType,
32+
ProperType,
3233
TupleType,
3334
TypeAliasType,
3435
TypedDictType,
@@ -177,6 +178,11 @@ def visit_overloaded_func_def(self, o: OverloadedFuncDef) -> None:
177178
item.accept(self)
178179
if o.impl:
179180
o.impl.accept(self)
181+
if isinstance(o.type, Overloaded):
182+
# For error messages we link the original definition for each item.
183+
for typ, item in zip(o.type.items, o.items):
184+
if isinstance(item, Decorator):
185+
typ.definition = item.func
180186

181187
def visit_decorator(self, d: Decorator) -> None:
182188
if self.current_info is not None:
@@ -187,6 +193,8 @@ def visit_decorator(self, d: Decorator) -> None:
187193
d.var.accept(self)
188194
for node in d.decorators:
189195
node.accept(self)
196+
if isinstance(d.var.type, ProperType) and isinstance(d.var.type, CallableType):
197+
d.var.type.definition = d.func
190198

191199
def visit_class_def(self, c: ClassDef) -> None:
192200
for v in c.type_vars:

mypy/messages.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1003,7 +1003,7 @@ def maybe_note_about_special_args(self, callee: CallableType, context: Context)
10031003
if self.prefer_simple_messages():
10041004
return
10051005
# https://github.com/python/mypy/issues/11309
1006-
first_arg = callee.def_extras.get("first_arg")
1006+
first_arg = get_first_arg(callee)
10071007
if first_arg and first_arg not in {"self", "cls", "mcs"}:
10081008
self.note(
10091009
"Looks like the first special argument in a method "
@@ -3007,7 +3007,7 @@ def [T <: int] f(self, x: int, y: T) -> None
30073007
s = definition_arg_names[0] + s
30083008
s = f"{tp.definition.name}({s})"
30093009
elif tp.name:
3010-
first_arg = tp.def_extras.get("first_arg")
3010+
first_arg = get_first_arg(tp)
30113011
if first_arg:
30123012
if s:
30133013
s = ", " + s
@@ -3050,6 +3050,12 @@ def [T <: int] f(self, x: int, y: T) -> None
30503050
return f"def {s}"
30513051

30523052

3053+
def get_first_arg(tp: CallableType) -> str | None:
3054+
if not isinstance(tp.definition, FuncDef) or not tp.definition.info or tp.definition.is_static:
3055+
return None
3056+
return tp.definition.original_first_arg
3057+
3058+
30533059
def variance_string(variance: int) -> str:
30543060
if variance == COVARIANT:
30553061
return "covariant"

mypy/nodes.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -823,6 +823,7 @@ class FuncDef(FuncItem, SymbolNode, Statement):
823823
"dataclass_transform_spec",
824824
"docstring",
825825
"deprecated",
826+
"original_first_arg",
826827
)
827828

828829
__match_args__ = ("name", "arguments", "type", "body")
@@ -855,6 +856,12 @@ def __init__(
855856
# the majority). In cases where self is not annotated and there are no Self
856857
# in the signature we can simply drop the first argument.
857858
self.is_trivial_self = False
859+
# This is needed because for positional-only arguments the name is set to None,
860+
# but we sometimes still want to show it in error messages.
861+
if arguments:
862+
self.original_first_arg: str | None = arguments[0].variable.name
863+
else:
864+
self.original_first_arg = None
858865

859866
@property
860867
def name(self) -> str:
@@ -886,6 +893,7 @@ def serialize(self) -> JsonDict:
886893
else self.dataclass_transform_spec.serialize()
887894
),
888895
"deprecated": self.deprecated,
896+
"original_first_arg": self.original_first_arg,
889897
}
890898

891899
@classmethod
@@ -906,6 +914,7 @@ def deserialize(cls, data: JsonDict) -> FuncDef:
906914
set_flags(ret, data["flags"])
907915
# NOTE: ret.info is set in the fixup phase.
908916
ret.arg_names = data["arg_names"]
917+
ret.original_first_arg = data.get("original_first_arg")
909918
ret.arg_kinds = [ArgKind(x) for x in data["arg_kinds"]]
910919
ret.abstract_status = data["abstract_status"]
911920
ret.dataclass_transform_spec = (

mypy/types.py

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,7 @@
2121

2222
import mypy.nodes
2323
from mypy.bogus_type import Bogus
24-
from mypy.nodes import (
25-
ARG_POS,
26-
ARG_STAR,
27-
ARG_STAR2,
28-
INVARIANT,
29-
ArgKind,
30-
FakeInfo,
31-
FuncDef,
32-
SymbolNode,
33-
)
24+
from mypy.nodes import ARG_POS, ARG_STAR, ARG_STAR2, INVARIANT, ArgKind, FakeInfo, SymbolNode
3425
from mypy.options import Options
3526
from mypy.state import state
3627
from mypy.util import IdMapper
@@ -1841,8 +1832,6 @@ class CallableType(FunctionLike):
18411832
"from_type_type", # Was this callable generated by analyzing Type[...]
18421833
# instantiation?
18431834
"is_bound", # Is this a bound method?
1844-
"def_extras", # Information about original definition we want to serialize.
1845-
# This is used for more detailed error messages.
18461835
"type_guard", # T, if -> TypeGuard[T] (ret_type is bool in this case).
18471836
"type_is", # T, if -> TypeIs[T] (ret_type is bool in this case).
18481837
"from_concatenate", # whether this callable is from a concatenate object
@@ -1869,7 +1858,6 @@ def __init__(
18691858
special_sig: str | None = None,
18701859
from_type_type: bool = False,
18711860
is_bound: bool = False,
1872-
def_extras: dict[str, Any] | None = None,
18731861
type_guard: Type | None = None,
18741862
type_is: Type | None = None,
18751863
from_concatenate: bool = False,
@@ -1902,22 +1890,6 @@ def __init__(
19021890
self.from_concatenate = from_concatenate
19031891
self.imprecise_arg_kinds = imprecise_arg_kinds
19041892
self.is_bound = is_bound
1905-
if def_extras:
1906-
self.def_extras = def_extras
1907-
elif isinstance(definition, FuncDef):
1908-
# This information would be lost if we don't have definition
1909-
# after serialization, but it is useful in error messages.
1910-
# TODO: decide how to add more info here (file, line, column)
1911-
# without changing interface hash.
1912-
first_arg: str | None = None
1913-
if definition.arg_names and definition.info and not definition.is_static:
1914-
if getattr(definition, "arguments", None):
1915-
first_arg = definition.arguments[0].variable.name
1916-
else:
1917-
first_arg = definition.arg_names[0]
1918-
self.def_extras = {"first_arg": first_arg}
1919-
else:
1920-
self.def_extras = {}
19211893
self.type_guard = type_guard
19221894
self.type_is = type_is
19231895
self.unpack_kwargs = unpack_kwargs
@@ -1939,7 +1911,6 @@ def copy_modified(
19391911
special_sig: Bogus[str | None] = _dummy,
19401912
from_type_type: Bogus[bool] = _dummy,
19411913
is_bound: Bogus[bool] = _dummy,
1942-
def_extras: Bogus[dict[str, Any]] = _dummy,
19431914
type_guard: Bogus[Type | None] = _dummy,
19441915
type_is: Bogus[Type | None] = _dummy,
19451916
from_concatenate: Bogus[bool] = _dummy,
@@ -1964,7 +1935,6 @@ def copy_modified(
19641935
special_sig=special_sig if special_sig is not _dummy else self.special_sig,
19651936
from_type_type=from_type_type if from_type_type is not _dummy else self.from_type_type,
19661937
is_bound=is_bound if is_bound is not _dummy else self.is_bound,
1967-
def_extras=def_extras if def_extras is not _dummy else dict(self.def_extras),
19681938
type_guard=type_guard if type_guard is not _dummy else self.type_guard,
19691939
type_is=type_is if type_is is not _dummy else self.type_is,
19701940
from_concatenate=(
@@ -2291,7 +2261,6 @@ def serialize(self) -> JsonDict:
22912261
"is_ellipsis_args": self.is_ellipsis_args,
22922262
"implicit": self.implicit,
22932263
"is_bound": self.is_bound,
2294-
"def_extras": dict(self.def_extras),
22952264
"type_guard": self.type_guard.serialize() if self.type_guard is not None else None,
22962265
"type_is": (self.type_is.serialize() if self.type_is is not None else None),
22972266
"from_concatenate": self.from_concatenate,
@@ -2314,7 +2283,6 @@ def deserialize(cls, data: JsonDict) -> CallableType:
23142283
is_ellipsis_args=data["is_ellipsis_args"],
23152284
implicit=data["implicit"],
23162285
is_bound=data["is_bound"],
2317-
def_extras=data["def_extras"],
23182286
type_guard=(
23192287
deserialize_type(data["type_guard"]) if data["type_guard"] is not None else None
23202288
),

test-data/unit/check-serialize.test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ def f(x: int) -> int: pass
224224
[out2]
225225
tmp/a.py:2: note: Revealed type is "builtins.str"
226226
tmp/a.py:3: error: Unexpected keyword argument "x" for "f"
227+
tmp/b.py: note: "f" defined here
227228

228229
[case testSerializeTypeGuardFunction]
229230
import a

0 commit comments

Comments
 (0)