FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Add the metaclass-calling framework needed for EnumMeta.__new__. · ag-python/pytype@bf876f8 · GitHub

forked from google/pytype

Commit bf876f8

Browse files
committed
Add the metaclass-calling framework needed for EnumMeta.__new__.
Adds the code needed to detect and call a custom implementation of type.__new__. An implementation of EnumMeta.__new__ will come in a later CL. Also fixes a minor issue where convert.py was failing to add modules and class prefixes to method names. PiperOrigin-RevId: 626545872
1 parent d9c1435 commit bf876f8

8 files changed

Lines changed: 88 additions & 20 deletions

File tree

‎pytype/rewrite/abstract/base.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ def _attrs(self):
137137
def instantiate(self) -> 'Singleton':
138138
return self
139139

140+
def get_attribute(self, name: str) -> 'Singleton':
141+
return self
142+
140143

141144
class Union(BaseValue):
142145
"""Union of values."""

‎pytype/rewrite/abstract/classes.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def instantiate(self) -> 'FrozenInstance':
110110
if isinstance(setup_method, functions_lib.InterpreterFunction):
111111
_ = setup_method.bind_to(self).analyze()
112112
constructor = self.get_attribute(self.constructor)
113-
if constructor:
113+
if constructor and constructor.full_name != 'builtins.object.__new__':
114114
log.error('Custom __new__ not yet implemented')
115115
instance = MutableInstance(self._ctx, self)
116116
for initializer_name in self.initializers:

‎pytype/rewrite/abstract/functions.py‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -596,8 +596,8 @@ def _attrs(self):
596596
return (self.name, self.code)
597597

598598
def call_with_mapped_args(self, mapped_args: MappedArgs[_FrameT]) -> _FrameT:
599-
log.info('Calling function:\n Sig: %s\n Args: %s',
600-
mapped_args.signature, mapped_args.argdict)
599+
log.info('Calling function %s:\n Sig: %s\n Args: %s',
600+
self.full_name, mapped_args.signature, mapped_args.argdict)
601601
parent_frame = mapped_args.frame or self._parent_frame
602602
if parent_frame.final_locals is None:
603603
k = None
@@ -622,6 +622,8 @@ class PytdFunction(SimpleFunction[SimpleReturn]):
622622

623623
def call_with_mapped_args(
624624
self, mapped_args: MappedArgs[FrameType]) -> SimpleReturn:
625+
log.info('Calling function %s:\n Sig: %s\n Args: %s',
626+
self.full_name, mapped_args.signature, mapped_args.argdict)
625627
ret = mapped_args.signature.annotations['return'].instantiate()
626628
return SimpleReturn(ret)
627629

‎pytype/rewrite/convert.py‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""Conversion from pytd to abstract representations of Python values."""
22

3+
from typing import Optional, Tuple
4+
35
from pytype.pytd import pytd
46
from pytype.rewrite.abstract import abstract
57

@@ -38,8 +40,10 @@ def pytd_class_to_value(self, cls: pytd.Class) -> abstract.SimpleClass:
3840
# don't cause infinite recursion.
3941
self._cache.classes[cls] = abstract_class
4042
for method in cls.methods:
41-
abstract_class.members[method.name] = (
42-
self.pytd_function_to_value(method))
43+
# For consistency with InterpreterFunction, prepend the class name.
44+
full_name = f'{name}.{method.name}'
45+
method_value = self.pytd_function_to_value(method, (module, full_name))
46+
abstract_class.members[method.name] = method_value
4347
for constant in cls.constants:
4448
constant_type = self.pytd_type_to_value(constant.type)
4549
abstract_class.members[constant.name] = constant_type.instantiate()
@@ -61,11 +65,15 @@ def pytd_class_to_value(self, cls: pytd.Class) -> abstract.SimpleClass:
6165
return abstract_class
6266

6367
def pytd_function_to_value(
64-
self, func: pytd.Function) -> abstract.PytdFunction:
68+
self, func: pytd.Function, func_name: Optional[Tuple[str, str]] = None,
69+
) -> abstract.PytdFunction:
6570
"""Converts a pytd function to an abstract function."""
6671
if func in self._cache.funcs:
6772
return self._cache.funcs[func]
68-
module, _, name = func.name.rpartition('.')
73+
if func_name:
74+
module, name = func_name
75+
else:
76+
module, _, name = func.name.rpartition('.')
6977
signatures = tuple(
7078
abstract.Signature.from_pytd(self._ctx, name, pytd_sig)
7179
for pytd_sig in func.signatures)

‎pytype/rewrite/convert_test.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ def f(self, x) -> None: ...
6565
self.assertEqual(set(cls.members), {'f'})
6666
f = cls.members['f']
6767
self.assertIsInstance(f, abstract.PytdFunction)
68-
self.assertEqual(repr(f.signatures[0]), 'def f(self: C, x: Any) -> None')
68+
self.assertEqual(f.module, '<test>')
69+
self.assertEqual(repr(f.signatures[0]), 'def C.f(self: C, x: Any) -> None')
6970

7071
def test_constant(self):
7172
pytd_cls = self.build_pytd("""

‎pytype/rewrite/frame.py‎

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""A frame of an abstract VM for type analysis of python bytecode."""
22

3+
import itertools
34
import logging
45
from typing import Any, FrozenSet, List, Mapping, Optional, Sequence, Set, Type
56

@@ -310,7 +311,8 @@ def _merge_nonlocals_into(self, frame: Optional['Frame']) -> None:
310311

311312
def _build_class(self, args: abstract.Args) -> abstract.InterpreterClass:
312313
builder = args.posargs[0].get_atomic_value(_FrameFunction)
313-
name = abstract.get_atomic_constant(args.posargs[1], str)
314+
name_var = args.posargs[1]
315+
name = abstract.get_atomic_constant(name_var, str)
314316

315317
base_vars = args.posargs[2:]
316318
bases = []
@@ -330,16 +332,41 @@ def _build_class(self, args: abstract.Args) -> abstract.InterpreterClass:
330332
keywords[kw] = val
331333

332334
frame = builder.call(abstract.Args(frame=self))
333-
cls = abstract.InterpreterClass(
334-
ctx=self._ctx,
335-
name=name,
336-
members=dict(frame.final_locals),
337-
bases=bases,
338-
keywords=keywords,
339-
functions=frame.functions,
340-
classes=frame.classes,
341-
)
342-
log.info('Created class: %s', cls.name)
335+
members = dict(frame.final_locals)
336+
metaclass_instance = None
337+
for metaclass in itertools.chain([keywords.get('metaclass')],
338+
(base.metaclass for base in bases)):
339+
if not metaclass:
340+
continue
341+
metaclass_new = metaclass.get_attribute('__new__')
342+
if metaclass_new.full_name == 'builtins.type.__new__':
343+
continue
344+
# The metaclass has overridden type.__new__. Invoke the custom __new__
345+
# method to construct the class.
346+
metaclass_var = metaclass.to_variable()
347+
bases_var = abstract.Tuple(self._ctx, tuple(base_vars)).to_variable()
348+
members_var = abstract.Dict(
349+
self._ctx, {self._ctx.consts[k].to_variable(): v.to_variable()
350+
for k, v in members.items()}
351+
).to_variable()
352+
args = abstract.Args(
353+
posargs=(metaclass_var, name_var, bases_var, members_var),
354+
frame=self)
355+
metaclass_instance = metaclass_new.call(args).get_return_value()
356+
break
357+
if metaclass_instance and metaclass_instance.full_name == name:
358+
cls = metaclass_instance
359+
else:
360+
cls = abstract.InterpreterClass(
361+
ctx=self._ctx,
362+
name=name,
363+
members=members,
364+
bases=bases,
365+
keywords=keywords,
366+
functions=frame.functions,
367+
classes=frame.classes,
368+
)
369+
log.info('Created class: %r', cls)
343370
return cls
344371

345372
def _call_function(

‎pytype/rewrite/tests/test_basic.py‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,5 +142,29 @@ def test_aliases(self):
142142
""")
143143

144144

145+
@test_base.skip('Under construction')
146+
class EnumTest(RewriteTest):
147+
"""Enum tests."""
148+
149+
def test_member(self):
150+
self.Check("""
151+
import enum
152+
class E(enum.Enum):
153+
X = 42
154+
assert_type(E.X, E)
155+
""")
156+
157+
def test_member_pyi(self):
158+
with self.DepTree([('foo.pyi', """
159+
import enum
160+
class E(enum.Enum):
161+
X = 42
162+
""")]):
163+
self.Check("""
164+
import foo
165+
assert_type(foo.E.X, foo.E)
166+
""")
167+
168+
145169
if __name__ == '__main__':
146170
test_base.main()

‎pytype/stubs/stdlib/enum.pytd‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, Dict, Iterable, Iterator, Tuple, Type, TypeVar, Union
1+
from typing import Any, Dict, Iterable, Iterator, Self, Tuple, Type, TypeVar, Union
22

33
_T = TypeVar('_T')
44
_EnumType = TypeVar('_EnumType', bound=Type[Enum])
@@ -8,6 +8,9 @@ class EnumMeta(type, Iterable):
88
def __getitem__(cls: EnumMeta, name: str) -> Any: ...
99
def __contains__(self, member: Enum) -> bool: ...
1010
def __len__(self) -> int: ...
11+
def __new__(
12+
metacls: type[Self], cls: str, bases: tuple[type, ...], classdict: dict[str, Any], **kwds: Any
13+
) -> Self: ...
1114

1215
class Enum(metaclass=EnumMeta):
1316
__members__: collections.OrderedDict[str, Enum]

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL