Skip to content

Commit 5f3ab5c

Browse files
committed
improve performance
1 parent 45d5d1e commit 5f3ab5c

17 files changed

Lines changed: 108 additions & 21 deletions

File tree

python/code/typeguard/_checkers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -915,7 +915,7 @@ def check_type_internal(
915915
looking up forward references
916916
"""
917917
ty = type(value)
918-
if ty == annotation or (value is None and ty is type(None)):
918+
if ty == annotation or (value is None and annotation is type(None)):
919919
# some early exits for better performance
920920
return
921921
annotation = resolve_alias_chains(annotation)

python/code/wypp/errors.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,30 @@ def invalidType(ty: Any, loc: Optional[location.Loc]) -> WyppTypeError:
7878
lines.append(renderLoc(loc))
7979
raise WyppTypeError('\n'.join(lines))
8080

81+
@staticmethod
82+
def invalidRestArgType(ty: Any, loc: Optional[location.Loc]) -> WyppTypeError:
83+
lines = []
84+
tyStr = renderTy(ty)
85+
lines.append(i18n.invalidRestArgTy(tyStr))
86+
lines.append('')
87+
if loc is not None:
88+
lines.append(f'## {i18n.tr("File")} {loc.filename}')
89+
lines.append(f'## {i18n.tr("Type declared in line")} {loc.startLine}:\n')
90+
lines.append(renderLoc(loc))
91+
raise WyppTypeError('\n'.join(lines))
92+
93+
@staticmethod
94+
def invalidKwArgType(ty: Any, loc: Optional[location.Loc]) -> WyppTypeError:
95+
lines = []
96+
tyStr = renderTy(ty)
97+
lines.append(i18n.invalidKwArgTy(tyStr))
98+
lines.append('')
99+
if loc is not None:
100+
lines.append(f'## {i18n.tr("File")} {loc.filename}')
101+
lines.append(f'## {i18n.tr("Type declared in line")} {loc.startLine}:\n')
102+
lines.append(renderLoc(loc))
103+
raise WyppTypeError('\n'.join(lines))
104+
81105
@staticmethod
82106
def unknownKeywordArgument(callableName: location.CallableName, callLoc: Optional[location.Loc], name: str) -> WyppTypeError:
83107
lines = []

python/code/wypp/i18n.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ def tr(key: str, **kws) -> str:
102102

103103
'invalid type `{ty}`':
104104
'ungültiger Typ `{ty}`',
105+
'invalid type for rest argument: `{ty}`':
106+
'ungültiger Typ für Restargument: `{ty}`',
107+
'invalid type for keyword argument: `{ty}`':
108+
'ungültiger Typ für Schlüsselwort-Argument: `{ty}`',
105109
'Cannot set attribute to value of type `{ty}`.':
106110
'Das Attribut kann nicht auf einen Wert vom Typ `{ty}` gesetzt werden.',
107111
'Problematic assignment in line': 'Fehlerhafte Zuweisung in Zeile',
@@ -342,6 +346,12 @@ def noTypeAnnotationForAttribute(attrName: str, recordName: str) -> str:
342346
def invalidTy(ty: Any) -> str:
343347
return tr('invalid type `{ty}`', ty=ty)
344348

349+
def invalidRestArgTy(ty: Any) -> str:
350+
return tr('invalid type for rest argument: `{ty}`', ty=ty)
351+
352+
def invalidKwArgTy(ty: Any) -> str:
353+
return tr('invalid type for keyword argument: `{ty}`', ty=ty)
354+
345355
def didYouMean(ty: str) -> str:
346356
return tr('Did you mean `{ty}`?', ty=ty)
347357

python/code/wypp/stacktrace.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ def __call__(self, frame: types.FrameType, event: str, arg: Any):
8989
pass
9090
case 'c_exception':
9191
pass
92+
def getReturnFrameType(self, idx: int) -> Optional[types.FrameType]:
93+
try:
94+
return self.__returnFrames[idx]
95+
except IndexError:
96+
return None
9297
def getReturnFrame(self, idx: int) -> Optional[inspect.FrameInfo]:
9398
try:
9499
f = self.__returnFrames[idx]
@@ -102,6 +107,15 @@ def getReturnFrame(self, idx: int) -> Optional[inspect.FrameInfo]:
102107
else:
103108
return None
104109

110+
def frameTypeToFrameInfo(f: Optional[types.FrameType]) -> Optional[inspect.FrameInfo]:
111+
if f:
112+
tb = inspect.getframeinfo(f, context=1)
113+
fi = inspect.FrameInfo(f, tb.filename, tb.lineno, tb.function, tb.code_context, tb.index)
114+
del f
115+
return fi
116+
else:
117+
return None
118+
105119
# when using _call_with_next_frame_removed, we have to take the second-to-last
106120
# return. Hence, we keep the two most recent returns byn setting entriesToKeep = 2.
107121
def installProfileHook(entriesToKeep: int=2) -> ReturnTracker:

python/code/wypp/typecheck.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22
from collections.abc import Callable
33
from typing import ParamSpec, TypeVar, Any, Optional, Literal
4+
import types
45
import inspect
56
from dataclasses import dataclass
67
import utils
@@ -68,12 +69,8 @@ def checkSignature(sig: inspect.Signature, info: location.CallableInfo, cfg: Che
6869
raise errors.WyppTypeError.defaultError(location.CallableName.mk(info), name,
6970
locDecl(), ty, p.default)
7071

71-
_argCountCache = {}
7272

7373
def mandatoryArgCount(sig: inspect.Signature) -> int:
74-
x = _argCountCache.get(sig)
75-
if x is not None:
76-
return x
7774
required_kinds = {
7875
inspect.Parameter.POSITIONAL_ONLY,
7976
inspect.Parameter.POSITIONAL_OR_KEYWORD,
@@ -83,29 +80,37 @@ def mandatoryArgCount(sig: inspect.Signature) -> int:
8380
for p in sig.parameters.values():
8481
if p.kind in required_kinds and p.default is inspect._empty:
8582
res = res + 1
86-
_argCountCache[sig] = res
8783
return res
8884

8985
def checkArgument(p: inspect.Parameter, name: str, idx: Optional[int], a: Any,
9086
getLocArg: Callable[[], Optional[location.Loc]],
9187
info: location.CallableInfo, cfg: CheckCfg):
9288
t = p.annotation
9389
if not isEmptyAnnotation(t):
90+
locDecl = lambda: info.getParamSourceLocation(name)
9491
if p.kind == inspect.Parameter.VAR_POSITIONAL:
92+
if type(t) == str:
93+
t = eval(t)
9594
argT = None
9695
# For *args annotated as tuple[X, ...], extract the element type X
9796
origin = getattr(t, '__origin__', None)
9897
if origin is tuple:
9998
args = getattr(t, '__args__', None)
100-
if args:
99+
if args and len(args) == 2 and args[1] is Ellipsis:
100+
# tuple[X, ...] — homogeneous variadic
101101
argT = args[0]
102+
elif args:
103+
# tuple[X, Y, ...] — fixed-length tuple, no single element type to extract
104+
raise errors.WyppTypeError.invalidRestArgType(t, locDecl())
102105
elif t is tuple:
103106
# bare `tuple` without type parameters, nothing to check
104107
return
105108
else:
106-
raise ValueError(f'Invalid type for rest argument: {t}')
109+
raise errors.WyppTypeError.invalidRestArgType(t, locDecl())
107110
t = argT
108111
elif p.kind == inspect.Parameter.VAR_KEYWORD:
112+
if type(t) == str:
113+
t = eval(t)
109114
valT = None
110115
# For **kwargs annotated as dict[str, X], extract the value type X
111116
origin = getattr(t, '__origin__', None)
@@ -116,9 +121,8 @@ def checkArgument(p: inspect.Parameter, name: str, idx: Optional[int], a: Any,
116121
elif t is dict:
117122
return
118123
else:
119-
raise ValueError(f'Invalid type for keyword argument: {t}')
124+
raise errors.WyppTypeError.invalidKwArgType(t, info.getParamSourceLocation(p.name))
120125
t = valT
121-
locDecl = lambda: info.getParamSourceLocation(name)
122126
if not handleMatchesTyResult(matchesTy(a, t, cfg.ns), locDecl):
123127
cn = location.CallableName.mk(info)
124128
raise errors.WyppTypeError.argumentError(cn,
@@ -188,7 +192,7 @@ def raiseArgMismatch():
188192
else:
189193
raise errors.WyppTypeError.unknownKeywordArgument(cn, getCallLoc(), name)
190194

191-
def checkReturn(sig: inspect.Signature, getReturnFrame: Callable[[], Optional[inspect.FrameInfo]],
195+
def checkReturn(sig: inspect.Signature, returnFrameType: Optional[types.FrameType],
192196
result: Any, info: location.CallableInfo, cfg: CheckCfg) -> None:
193197
if info.isAsync:
194198
return
@@ -204,7 +208,7 @@ def checkReturn(sig: inspect.Signature, getReturnFrame: Callable[[], Optional[in
204208
locRes = location.Loc.fromFrameInfo(fi)
205209
returnLoc = None
206210
extraFrames = []
207-
returnFrame = getReturnFrame()
211+
returnFrame = stacktrace.frameTypeToFrameInfo(returnFrameType)
208212
if returnFrame:
209213
returnLoc = location.Loc.fromFrameInfo(returnFrame)
210214
extraFrames = [returnFrame]
@@ -249,9 +253,9 @@ def wrapped(*args, **kwargs) -> T:
249253
utils._call_with_frames_removed(checkArguments, sig, args, kwargs, info, checkCfg)
250254
returnTracker = stacktrace.getReturnTracker()
251255
result = utils._call_with_next_frame_removed(f, *args, **kwargs)
252-
getRetFrame = lambda: returnTracker.getReturnFrame(0)
256+
ft = returnTracker.getReturnFrameType(0)
253257
utils._call_with_frames_removed(
254-
checkReturn, sig, getRetFrame, result, info, checkCfg
258+
checkReturn, sig, ft, result, info, checkCfg
255259
)
256260
return result
257261
return wrapped

python/file-test-data/extras/args3_ok.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from wypp import *
22

3-
def f(x: int, *rest: tuple[int], **kw: dict[str, int]):
3+
def f(x: int, *rest: tuple[int, ...], **kw: dict[str, int]):
44
print(f'x={x}, kw={kw}')
55

66
f(1)

python/file-test-data/extras/args5.err

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ f(1, 10, '11', y=2, z=3)
1414

1515
## Typ deklariert in Zeile 3:
1616

17-
def f(x: int, *rest: tuple[int], **kw: dict[str, int]):
17+
def f(x: int, *rest: tuple[int, ...], **kw: dict[str, int]):

python/file-test-data/extras/args5.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from wypp import *
22

3-
def f(x: int, *rest: tuple[int], **kw: dict[str, int]):
3+
def f(x: int, *rest: tuple[int, ...], **kw: dict[str, int]):
44
print(f'x={x}, kw={kw}')
55

66
f(1)

python/file-test-data/extras/args6.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from wypp import *
22

3-
def f(x: int, *rest: tuple[int], **kw: dict[str, int]):
3+
def f(x: int, *rest: tuple[int, ...], **kw: dict[str, int]):
44
print(f'x={x}, kw={kw}')
55

66
f(1)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Traceback (most recent call last):
2+
File "file-test-data/extras/args7.py", line 6, in <module>
3+
f(1, 2)
4+
5+
WyppTypeError: ungültiger Typ für Restargument: `tuple[int]`
6+
7+
## Datei file-test-data/extras/args7.py
8+
## Typ deklariert in Zeile 3:
9+
10+
def f(x: int, *rest: tuple[int]):

0 commit comments

Comments
 (0)