@@ -24,14 +24,14 @@ def isEmptySignature(sig: inspect.Signature) -> bool:
2424 return False
2525 return isEmptyAnnotation (sig .return_annotation )
2626
27- def handleMatchesTyResult (res : MatchesTyResult , tyLoc : Optional [location .Loc ]) -> bool :
27+ def handleMatchesTyResult (res : MatchesTyResult , getTyLoc : Callable [[], Optional [location .Loc ] ]) -> bool :
2828 match res :
2929 case MatchesTyFailure (exc , ty ):
3030 if isDebug ():
3131 debug (f'Exception occurred while calling matchesTy with type { ty } , re-raising' )
3232 raise exc
3333 else :
34- raise errors .WyppTypeError .invalidType (ty , tyLoc )
34+ raise errors .WyppTypeError .invalidType (ty , getTyLoc () )
3535 case b :
3636 return b
3737
@@ -63,11 +63,17 @@ def checkSignature(sig: inspect.Signature, info: location.CallableInfo, cfg: Che
6363 locDecl = info .getParamSourceLocation (name )
6464 raise errors .WyppTypeError .partialAnnotationError (location .CallableName .mk (info ), name , locDecl )
6565 if p .default is not inspect .Parameter .empty :
66- locDecl = info .getParamSourceLocation (name )
66+ locDecl = lambda : info .getParamSourceLocation (name )
6767 if not handleMatchesTyResult (matchesTy (p .default , ty , cfg .ns ), locDecl ):
68- raise errors .WyppTypeError .defaultError (location .CallableName .mk (info ), name , locDecl , ty , p .default )
68+ raise errors .WyppTypeError .defaultError (location .CallableName .mk (info ), name ,
69+ locDecl (), ty , p .default )
70+
71+ _argCountCache = {}
6972
7073def mandatoryArgCount (sig : inspect .Signature ) -> int :
74+ x = _argCountCache .get (sig )
75+ if x is not None :
76+ return x
7177 required_kinds = {
7278 inspect .Parameter .POSITIONAL_ONLY ,
7379 inspect .Parameter .POSITIONAL_OR_KEYWORD ,
@@ -77,10 +83,12 @@ def mandatoryArgCount(sig: inspect.Signature) -> int:
7783 for p in sig .parameters .values ():
7884 if p .kind in required_kinds and p .default is inspect ._empty :
7985 res = res + 1
86+ _argCountCache [sig ] = res
8087 return res
8188
8289def checkArgument (p : inspect .Parameter , name : str , idx : Optional [int ], a : Any ,
83- locArg : Optional [location .Loc ], info : location .CallableInfo , cfg : CheckCfg ):
90+ getLocArg : Callable [[], Optional [location .Loc ]],
91+ info : location .CallableInfo , cfg : CheckCfg ):
8492 t = p .annotation
8593 if not isEmptyAnnotation (t ):
8694 if p .kind == inspect .Parameter .VAR_POSITIONAL :
@@ -110,29 +118,38 @@ def checkArgument(p: inspect.Parameter, name: str, idx: Optional[int], a: Any,
110118 else :
111119 raise ValueError (f'Invalid type for keyword argument: { t } ' )
112120 t = valT
113- locDecl = info .getParamSourceLocation (name )
121+ locDecl = lambda : info .getParamSourceLocation (name )
114122 if not handleMatchesTyResult (matchesTy (a , t , cfg .ns ), locDecl ):
115123 cn = location .CallableName .mk (info )
116124 raise errors .WyppTypeError .argumentError (cn ,
117125 name ,
118126 idx ,
119- locDecl ,
127+ locDecl () ,
120128 t ,
121129 a ,
122- locArg )
130+ getLocArg ())
131+
123132def checkArguments (sig : inspect .Signature , args : tuple , kwargs : dict ,
124133 info : location .CallableInfo , cfg : CheckCfg ) -> None :
125- debug (f'Checking arguments when calling { info } ' )
134+ if isDebug ():
135+ debug (f'Checking arguments when calling { info } ' )
126136 paramNames = list (sig .parameters )
127137 mandatory = mandatoryArgCount (sig )
128138 kind = getKind (cfg , paramNames )
129139 offset = 1 if kind == 'method' else 0
130- fi = stacktrace .callerOutsideWypp ()
131- callLoc = None if not fi else location .Loc .fromFrameInfo (fi )
132140 cn = location .CallableName .mk (info )
141+ # stacktrace.callerOutsideWypp() is expensive, only access it lazily
142+ def getCallLoc () -> Optional [location .Loc ]:
143+ fi = stacktrace .callerOutsideWypp ()
144+ return None if not fi else location .Loc .fromFrameInfo (fi )
145+ def getLocArg (idxOrName : int | str ) -> Callable [[], Optional [location .Loc ]]:
146+ def f ():
147+ fi = stacktrace .callerOutsideWypp ()
148+ return None if fi is None else location .locationOfArgument (fi , i )
149+ return f
133150 def raiseArgMismatch ():
134151 raise errors .WyppTypeError .argCountMismatch (cn ,
135- callLoc ,
152+ getCallLoc () ,
136153 len (paramNames ) - offset ,
137154 mandatory - offset ,
138155 len (args ) - offset )
@@ -152,46 +169,46 @@ def raiseArgMismatch():
152169 raiseArgMismatch ()
153170 # Check positional args
154171 for i in range (len (args )):
155- locArg = None if fi is None else location .locationOfArgument (fi , i )
156172 if i < len (positionalNames ):
157173 name = positionalNames [i ]
158174 p = sig .parameters [name ]
159- checkArgument (p , name , i - offset , args [i ], locArg , info , cfg )
175+ checkArgument (p , name , i - offset , args [i ], getLocArg ( i ) , info , cfg )
160176 elif varPositionalParam is not None :
161- checkArgument (varPositionalParam , varPositionalParam .name , i - offset , args [i ], locArg , info , cfg )
177+ checkArgument (varPositionalParam , varPositionalParam .name , i - offset , args [i ], getLocArg ( i ) , info , cfg )
162178 else :
163179 raiseArgMismatch ()
164180 # Check keyword args
165181 for name in kwargs :
166- locArg = None if fi is None else location .locationOfArgument (fi , name )
167182 if name in sig .parameters and sig .parameters [name ].kind not in (
168183 inspect .Parameter .VAR_POSITIONAL , inspect .Parameter .VAR_KEYWORD
169184 ):
170- checkArgument (sig .parameters [name ], name , None , kwargs [name ], locArg , info , cfg )
185+ checkArgument (sig .parameters [name ], name , None , kwargs [name ], getLocArg ( name ) , info , cfg )
171186 elif varKeywordParam is not None :
172- checkArgument (varKeywordParam , name , None , kwargs [name ], locArg , info , cfg )
187+ checkArgument (varKeywordParam , name , None , kwargs [name ], getLocArg ( name ) , info , cfg )
173188 else :
174- raise errors .WyppTypeError .unknownKeywordArgument (cn , callLoc , name )
189+ raise errors .WyppTypeError .unknownKeywordArgument (cn , getCallLoc () , name )
175190
176- def checkReturn (sig : inspect .Signature , returnFrame : Optional [inspect .FrameInfo ],
191+ def checkReturn (sig : inspect .Signature , getReturnFrame : Callable [[], Optional [inspect .FrameInfo ] ],
177192 result : Any , info : location .CallableInfo , cfg : CheckCfg ) -> None :
178193 if info .isAsync :
179194 return
180195 t = sig .return_annotation
181196 if isEmptyAnnotation (t ):
182197 t = None
183- debug (f'Checking return value when calling { info } , return type: { t } ' )
184- locDecl = info .getResultTypeLocation ()
198+ if isDebug ():
199+ debug (f'Checking return value when calling { info } , return type: { t } ' )
200+ locDecl = lambda : info .getResultTypeLocation ()
185201 if not handleMatchesTyResult (matchesTy (result , t , cfg .ns ), locDecl ):
186202 fi = stacktrace .callerOutsideWypp ()
187203 if fi is not None :
188204 locRes = location .Loc .fromFrameInfo (fi )
189205 returnLoc = None
190206 extraFrames = []
207+ returnFrame = getReturnFrame ()
191208 if returnFrame :
192209 returnLoc = location .Loc .fromFrameInfo (returnFrame )
193210 extraFrames = [returnFrame ]
194- raise errors .WyppTypeError .resultError (location .CallableName .mk (info ), locDecl , t , returnLoc , result ,
211+ raise errors .WyppTypeError .resultError (location .CallableName .mk (info ), locDecl () , t , returnLoc , result ,
195212 locRes , extraFrames )
196213
197214
@@ -232,9 +249,9 @@ def wrapped(*args, **kwargs) -> T:
232249 utils ._call_with_frames_removed (checkArguments , sig , args , kwargs , info , checkCfg )
233250 returnTracker = stacktrace .getReturnTracker ()
234251 result = utils ._call_with_next_frame_removed (f , * args , ** kwargs )
235- retFrame = returnTracker .getReturnFrame (0 )
252+ getRetFrame = lambda : returnTracker .getReturnFrame (0 )
236253 utils ._call_with_frames_removed (
237- checkReturn , sig , retFrame , result , info , checkCfg
254+ checkReturn , sig , getRetFrame , result , info , checkCfg
238255 )
239256 return result
240257 return wrapped
0 commit comments