diff --git a/pytype/convert.py b/pytype/convert.py index 976f8dfb1..7a68b7e75 100644 --- a/pytype/convert.py +++ b/pytype/convert.py @@ -981,7 +981,7 @@ def _constant_to_value(self, pyval, subst, get_node): underlying = self.constant_to_value(param, subst, node) subclass_name = fiddle_overlay.get_fiddle_buildable_subclass(pyval) try: - return fiddle_overlay.BuildableType( + return fiddle_overlay.BuildableType.make( subclass_name, underlying, self.ctx) except KeyError: # We are in the middle of constructing the fiddle ast so diff --git a/pytype/overlays/fiddle_overlay.py b/pytype/overlays/fiddle_overlay.py index e38415266..051f8e8ce 100644 --- a/pytype/overlays/fiddle_overlay.py +++ b/pytype/overlays/fiddle_overlay.py @@ -73,7 +73,7 @@ def __init__(self, name, ctx, module): self.module = module def __repr__(self): - return f"Fiddle{self.name}" + return f"FiddleBuildableBuilder[{self.name}]" def _match_pytd_init(self, node, init_var, args): init = init_var.data[0] @@ -119,7 +119,16 @@ def unwrap(arg_var): # If the underlying type is a function, do not try to instantiate it return self.ctx.new_unsolvable(node) else: - return d.underlying.instantiate(node) + # Match either Config[A] or A + # TODO(mdemello): This is to prevent issues when a dataclass field + # has type Config[A] rather than A, in which case blindly unwrapping + # an arg of type Config[A] is wrong. We should ideally do arg-by-arg + # matching here instead of trying to construct function args without + # reference to the signature we are matching. + return self.ctx.join_variables(node, [ + arg_var, + d.underlying.instantiate(node) + ]) return arg_var new_args = (underlying.instantiate(node),) new_args += tuple(unwrap(arg) for arg in args[1:]) @@ -158,7 +167,7 @@ def getitem_slot(self, node, index_var) -> Tuple[Node, abstract.Instance]: """Specialize the generic class with the value of index_var.""" underlying = index_var.data[0] - ret = BuildableType( + ret = BuildableType.make( self.fiddle_type_name, underlying, self.ctx, module=self.module ) return node, ret.to_variable(node) @@ -172,28 +181,37 @@ class BuildableType(abstract.ParameterizedClass): """Base generic class for fiddle.Config and fiddle.Partial.""" def __init__( - self, fiddle_type_name, underlying, ctx, template=None, module="fiddle" + self, base_cls, underlying, ctx, template=None, module="fiddle" ): - base_cls = BuildableBuilder(fiddle_type_name, ctx, module) - if isinstance(underlying, abstract.Function): # We don't support functions for now, but falling back to Any here gets us # as much of the functionality as possible. formal_type_parameters = {abstract_utils.T: ctx.convert.unsolvable} + elif isinstance(underlying, abstract.ConcreteValue): + # We should not hit this case but there are some complex cases where we + # handle __getitem__ wrong. + formal_type_parameters = {abstract_utils.T: ctx.convert.unsolvable} else: # Classes and TypeVars formal_type_parameters = {abstract_utils.T: underlying} super().__init__(base_cls, formal_type_parameters, ctx, template) # pytype: disable=wrong-arg-types - self.fiddle_type_name = fiddle_type_name + self.fiddle_type_name = base_cls.fiddle_type_name self.underlying = underlying self.module = module + @classmethod + def make( + cls, fiddle_type_name, underlying, ctx, template=None, module="fiddle" + ): + base_cls = BuildableBuilder(fiddle_type_name, ctx, module) + return cls(base_cls, underlying, ctx, template, module) + def replace(self, inner_types): inner_types = dict(inner_types) new_underlying = inner_types[abstract_utils.T] typ = self.__class__ - return typ( + return typ.make( self.fiddle_type_name, new_underlying, self.ctx, self.template, self.module ) @@ -207,11 +225,23 @@ def __repr__(self): return f"{self.fiddle_type_name}Type[{self.underlying}]" -class Buildable(abstract.Instance): +class Buildable(abstract.Instance, mixin.HasSlots): + """Base class for Config and Partial instances.""" + def __init__(self, fiddle_type_name, cls, ctx, container=None): super().__init__(cls, ctx, container) self.fiddle_type_name = fiddle_type_name self.underlying = None + mixin.HasSlots.init_mixin(self) + self.set_native_slot("__getitem__", self.getitem_slot) + + def getitem_slot(self, node, slice_var) -> Tuple[Node, abstract.Instance]: + # We need to set this here otherwise we walk up the chain and call + # getitem_slot on BuildableBuilder, which tries to create an + # AnnotationContainer. + # TODO(mdemello): This probably needs to delegate to + # vm_utils._call_binop_on_bindings with the lhs set to self.underlying. + return node, self.ctx.new_unsolvable(node) class Config(Buildable): @@ -233,7 +263,7 @@ def _convert_type(typ, subst, ctx): if isinstance(typ, abstract.TypeParameter) and typ.name in subst: # TODO(mdemello): Handle typevars in unions. typ = subst[typ.name] - new_typ = BuildableType("Config", typ, ctx, module="fiddle") + new_typ = BuildableType.make("Config", typ, ctx, module="fiddle") return abstract.Union([new_typ, typ], ctx) @@ -271,7 +301,7 @@ def make_instance( instance_class = {"Config": Config, "Partial": Partial}[subclass_name] # Create the specialized class Config[underlying] or Partial[underlying] try: - cls = BuildableType(subclass_name, underlying, ctx, module="fiddle") + cls = BuildableType.make(subclass_name, underlying, ctx, module="fiddle") except KeyError: # We are in the middle of constructing the fiddle ast; fiddle.Config doesn't # exist yet diff --git a/pytype/stubs/builtins/builtins.pytd b/pytype/stubs/builtins/builtins.pytd index 0d0012b30..750390192 100644 --- a/pytype/stubs/builtins/builtins.pytd +++ b/pytype/stubs/builtins/builtins.pytd @@ -995,6 +995,8 @@ class memoryview(Sequence[int]): def __setitem__(self, index: int, o: int) -> None: ... @overload def __setitem__(self, index: tuple[int, ...], o: int) -> None: ... + def __buffer__(self, __flags: int) -> memoryview: ... + def __release_buffer__(self, __buffer: memoryview) -> None: ... if sys.version_info >= (3, 10): def tobytes(self, order: Literal["C", "F", "A"] | None = "C") -> bytes: ... else: diff --git a/pytype/tests/test_fiddle_overlay.py b/pytype/tests/test_fiddle_overlay.py index 1e6d3de59..58a44630a 100644 --- a/pytype/tests/test_fiddle_overlay.py +++ b/pytype/tests/test_fiddle_overlay.py @@ -149,6 +149,26 @@ class Parent: c = fiddle.{self.buildable_type_name}(Parent, child_data, child_regular) """) + def test_dataclass_with_fiddle_member(self): + with self.DepTree([("fiddle.pyi", _FIDDLE_PYI)]): + self.Check(f""" + import dataclasses + import fiddle + + @dataclasses.dataclass + class DataClass: + x: int + y: str + + @dataclasses.dataclass + class Parent: + child_data: DataClass + child_config: fiddle.Config[DataClass] + + child_data = fiddle.Config(DataClass, x=1, y='y') + c = fiddle.{self.buildable_type_name}(Parent, child_data, child_data) + """) + def test_nested_object_assignment(self): with self.DepTree([("fiddle.pyi", _FIDDLE_PYI)]): self.Check(f"""