1515import operator
1616import types
1717from collections .abc import Callable
18- from dataclasses import InitVar , dataclass , replace
18+ from dataclasses import InitVar , dataclass , field , replace
1919from inspect import Parameter , Signature
2020from typing import TYPE_CHECKING , Any , TypeVar , Union , assert_never , cast , get_args , get_origin
2121
@@ -238,14 +238,7 @@ class RuntimeClassDescriptor:
238238 def __get__ (self , obj : object , owner : RuntimeClass | None = None ) -> Callable :
239239 if owner is None :
240240 raise AttributeError (f"Can only access { self .name } on the class, not an instance" )
241- cls_decl = owner .__egg_decls__ ._classes [owner .__egg_tp__ .ident ]
242- if self .name in cls_decl .class_methods :
243- return RuntimeFunction (
244- owner .__egg_decls_thunk__ , Thunk .value (ClassMethodRef (owner .__egg_tp__ .ident , self .name )), None
245- )
246- if self .name in cls_decl .preserved_methods :
247- return cls_decl .preserved_methods [self .name ]
248- raise AttributeError (f"Class { owner .__egg_tp__ .ident } has no method { self .name } " ) from None
241+ return RuntimeClass .__getattr__ (owner , self .name )
249242
250243
251244RUNTIME_CLASS_DESCRIPTORS : dict [str , RuntimeClassDescriptor ] = {
@@ -258,6 +251,9 @@ class RuntimeClass(DelayedDeclarations, metaclass=ClassFactory):
258251 __egg_tp__ : TypeRefWithVars
259252 # True if we want `__parameters__` to be recognized by `Union`, which means we can't inherit from `type` directly.
260253 _egg_has_params : InitVar [bool ] = False
254+ __egg_attr_cache__ : dict [str , RuntimeFunction | RuntimeExpr | Callable ] = field (
255+ init = False , repr = False , default_factory = dict
256+ )
261257
262258 def __post_init__ (self , _egg_has_params : bool ) -> None :
263259 global _PY_OBJECT_CLASS , _UNSTABLE_FN_CLASS
@@ -362,7 +358,7 @@ def __getitem__(self, args: object) -> RuntimeClass:
362358 tp = TypeRefWithVars (self .__egg_tp__ .ident , final_args )
363359 return RuntimeClass (Thunk .fn (Declarations .create , self , * decls_like ), tp , _egg_has_params = True )
364360
365- def __getattr__ (self , name : str ) -> RuntimeFunction | RuntimeExpr | Callable :
361+ def __getattr__ (self , name : str ) -> RuntimeFunction | RuntimeExpr | Callable : # noqa: C901
366362 if not isinstance (name , str ):
367363 raise TypeError (f"Attribute name must be a string, got { name !r} " )
368364 if name == "__origin__" and self .__egg_tp__ .args :
@@ -380,6 +376,11 @@ def __getattr__(self, name: str) -> RuntimeFunction | RuntimeExpr | Callable:
380376 }:
381377 raise AttributeError
382378
379+ try :
380+ return self .__egg_attr_cache__ [name ]
381+ except KeyError :
382+ pass
383+
383384 try :
384385 cls_decl = self .__egg_decls__ ._classes [self .__egg_tp__ .ident ]
385386 except Exception as e :
@@ -388,29 +389,33 @@ def __getattr__(self, name: str) -> RuntimeFunction | RuntimeExpr | Callable:
388389
389390 preserved_methods = cls_decl .preserved_methods
390391 if name in preserved_methods :
391- return preserved_methods [name ]
392-
392+ res = preserved_methods [name ]
393393 # if this is a class variable, return an expr for it, otherwise, assume it's a method
394- if name in cls_decl .class_variables :
394+ elif name in cls_decl .class_variables :
395395 return_tp = cls_decl .class_variables [name ]
396- return RuntimeExpr (
396+ res = RuntimeExpr (
397397 self .__egg_decls_thunk__ ,
398398 Thunk .value (TypedExprDecl (return_tp .type_ref , CallDecl (ClassVariableRef (self .__egg_tp__ .ident , name )))),
399399 )
400- bound = self .__egg_tp__ .to_just () if self .__egg_tp__ .args else None
401- if name in cls_decl .class_methods :
402- return RuntimeFunction (
403- self .__egg_decls_thunk__ , Thunk .value (ClassMethodRef (self .__egg_tp__ .ident , name )), bound
404- )
405- # allow referencing properties and methods as class variables as well
406- if name in cls_decl .properties :
407- return RuntimeFunction (
408- self .__egg_decls_thunk__ , Thunk .value (PropertyRef (self .__egg_tp__ .ident , name )), bound
400+ else :
401+ if name in cls_decl .class_methods :
402+ callable_ref : CallableRef = ClassMethodRef (self .__egg_tp__ .ident , name )
403+ # allow referencing properties and methods as class variables as well
404+ elif name in cls_decl .properties :
405+ callable_ref = PropertyRef (self .__egg_tp__ .ident , name )
406+ elif name in cls_decl .methods :
407+ callable_ref = MethodRef (self .__egg_tp__ .ident , name )
408+ else :
409+ msg = f"Class { self .__egg_tp__ .ident } has no method { name } "
410+ raise AttributeError (msg ) from None
411+ res = RuntimeFunction (
412+ self .__egg_decls_thunk__ ,
413+ Thunk .value (callable_ref ),
414+ self .__egg_tp__ .to_just () if self .__egg_tp__ .args else None ,
409415 )
410- if name in cls_decl .methods :
411- return RuntimeFunction (self .__egg_decls_thunk__ , Thunk .value (MethodRef (self .__egg_tp__ .ident , name )), bound )
412- msg = f"Class { self .__egg_tp__ .ident } has no method { name } "
413- raise AttributeError (msg ) from None
416+
417+ self .__egg_attr_cache__ [name ] = res
418+ return res
414419
415420 def __str__ (self ) -> str :
416421 return str (self .__egg_tp__ )
0 commit comments