Beispiel #1
0
 def test_name(self):
     # The generated name has to be different from the official name, or we'll
     # end up with nonsense like X = X.
     self.assertNotEqual("X", escape.pack_namedtuple("X", []))
     # Two namedtuple instances should have the same name iff the instances are
     # the same.
     self.assertNotEqual(escape.pack_namedtuple("X", []),
                         escape.pack_namedtuple("X", ["a"]))
     self.assertNotEqual(escape.pack_namedtuple("X", ["a"]),
                         escape.pack_namedtuple("X", ["b"]))
Beispiel #2
0
 def test_name_conflict(self):
   ty = self.Infer("""
     import collections
     X = collections.namedtuple("_", [])
     Y = collections.namedtuple("_", [])
     Z = collections.namedtuple("_", "a")
   """, deep=False)
   name_x = escape.pack_namedtuple("_", [])
   name_z = escape.pack_namedtuple("_", ["a"])
   ast_x = self._namedtuple_ast(name_x, [])
   ast_z = self._namedtuple_ast(name_z, ["a"])
   ast = pytd_utils.Concat(ast_x, ast_z)
   expected = pytd_utils.Print(ast) + textwrap.dedent("""
     collections = ...  # type: module
     X = {name_x}
     Y = {name_x}
     Z = {name_z}""").format(name_x=name_x, name_z=name_z)
   self.assertTypesMatchPytd(ty, expected)
Beispiel #3
0
 def test_subclass(self):
     ty = self.Infer("""
   import collections
   class X(collections.namedtuple("X", [])):
     def __new__(cls, _):
       return super(X, cls).__new__(cls)
 """)
     name = escape.pack_namedtuple("X", [])
     ast = self._namedtuple_ast(name, [])
     expected = pytd_utils.Print(ast) + textwrap.dedent("""
   _TX = TypeVar("_TX", bound=X)
   class X({name}):
     def __new__(cls: Type[_TX], _) -> _TX: ...""").format(name=name)
     self.assertTypesMatchPytd(ty, expected)
Beispiel #4
0
    def test_inherit_from_namedtuple(self):
        ty = self.Infer("""
      import collections

      class Foo(
          collections.namedtuple('_Foo', 'x y z')):
        pass
    """)
        name = escape.pack_namedtuple("_Foo", ["x", "y", "z"])
        ast = collections_overlay.namedtuple_ast(name, ["x", "y", "z"],
                                                 self.python_version)
        expected = pytd_utils.Print(ast) + textwrap.dedent("""
      collections = ...  # type: module
      class Foo({name}): ...""").format(name=name)
        self.assertTypesMatchPytd(ty, expected)
Beispiel #5
0
  def test_inherit_from_namedtuple(self):
    ty = self.Infer("""
      import collections

      class Foo(
          collections.namedtuple('_Foo', 'x y z')):
        pass
    """)
    name = escape.pack_namedtuple("_Foo", ["x", "y", "z"])
    ast = named_tuple.namedtuple_ast(
        name, ["x", "y", "z"], [False] * 3, self.options)
    expected = pytd_utils.Print(ast) + textwrap.dedent("""
      import collections
      class Foo({name}): ...""").format(name=name)
    self.assertTypesMatchPytd(ty, expected)
Beispiel #6
0
    def _namedtuple_def(self, suffix="", **kws):
        """Generate the expected pyi for a simple namedtuple definition.

    Args:
      suffix: Optionally, extra text to append to the pyi.
      **kws: Must contain exactly one argument of the form
        alias=(name, [<fields>]). For example, to generate a definition for
        X = namedtuple("_X", "y z"), the method call should be
        _namedtuple_def(X=("_X", ["y", "z"])).

    Returns:
      The expected pyi for the namedtuple instance.
    """
        (alias, (name, fields)), = kws.items()  # pylint: disable=unbalanced-tuple-unpacking
        name = escape.pack_namedtuple(name, fields)
        suffix += f"\n{alias} = {name}"
        return pytd_utils.Print(self._namedtuple_ast(name,
                                                     fields)) + "\n" + suffix
Beispiel #7
0
 def test_store_and_load_from_namedtuple(self):
     ty = self.Infer("""
   import collections
   t = collections.namedtuple('t', ['x', 'y', 'z'])
   t.x = 3
   t.y = "foo"
   t.z = 1j
   x = t.x
   y = t.y
   z = t.z
 """)
     name = escape.pack_namedtuple("t", ["x", "y", "z"])
     ast = collections_overlay.namedtuple_ast(name, ["x", "y", "z"],
                                              self.python_version)
     expected = pytd_utils.Print(ast) + textwrap.dedent("""
   collections = ...  # type: module
   t = {name}
   x = ...  # type: int
   y = ...  # type: str
   z = ...  # type: complex""").format(name=name)
     self.assertTypesMatchPytd(ty, expected)
Beispiel #8
0
  def call(self, node, _, args):
    """Creates a namedtuple class definition.

    Performs the same argument checking as collections.namedtuple, e.g. making
    sure field names don't start with _ or digits, making sure no keywords are
    used for the typename or field names, and so on. Because the methods of the
    class have to be changed to match the number and names of the fields, we
    construct pytd.Function and pytd.Constant instances for each member of the
    class. Finally, the pytd.Class is wrapped in an abstract.PyTDClass.

    If incorrect arguments are passed, a subclass of function.FailedFunctionCall
    will be raised. Other cases may raise abstract_utils.ConversionError
    exceptions, such as when the arguments are in unicode or any of the
    arguments have multiple bindings, but these are caught and return Any. This
    also occurs if an argument to namedtuple is invalid, e.g. a keyword is used
    as a field name and rename is False.

    Arguments:
      node: the current CFG node
      _: the func binding, ignored.
      args: a function.Args instance

    Returns:
      a tuple of the given CFG node and an abstract.PyTDClass instance (wrapped
      in a Variable) representing the constructed namedtuple class.
      If an abstract_utils.ConversionError occurs or if field names are invalid,
      this function returns Unsolvable (in a Variable) instead of a PyTDClass.

    Raises:
      function.FailedFunctionCall: Raised by _getargs if any of the arguments
        do not match the function signature.
    """
    # If we can't extract the arguments, we take the easy way out and return Any
    try:
      name_var, field_names, defaults, rename = self._getargs(node, args)
    except abstract_utils.ConversionError:
      return node, self.ctx.new_unsolvable(node)

    # We need the bare name for a few things, so pull that out now.
    # The same unicode issue can strike here, so again return Any.
    try:
      name = abstract_utils.get_atomic_python_constant(name_var)
    except abstract_utils.ConversionError:
      return node, self.ctx.new_unsolvable(node)

    # namedtuple does some checking and optionally renaming of field names,
    # so we do too.
    try:
      field_names = self._validate_and_rename_args(name, field_names, rename)
    except ValueError as e:
      self.ctx.errorlog.invalid_namedtuple_arg(self.ctx.vm.frames,
                                               utils.message(e))
      return node, self.ctx.new_unsolvable(node)

    name = escape.pack_namedtuple(name, field_names)
    ast = namedtuple_ast(name, field_names, defaults, options=self.ctx.options)
    mapping = self._get_known_types_mapping()

    # A truly well-formed pyi for the namedtuple will have references to the new
    # namedtuple class itself for all `self` params and as the return type for
    # methods like __new__, _replace and _make. In order to do that, we need a
    # ClassType.
    cls_type = pytd.ClassType(name)
    mapping[name] = cls_type

    cls = ast.Lookup(name).Visit(visitors.ReplaceTypes(mapping))
    cls_type.cls = cls

    # Make sure all NamedType nodes have been replaced by ClassType nodes with
    # filled cls pointers.
    cls.Visit(visitors.VerifyLookup())

    # We can't build the PyTDClass directly, and instead must run it through
    # convert.constant_to_value first, for caching.
    instance = self.ctx.convert.constant_to_value(cls, {}, self.ctx.root_node)
    self.ctx.vm.trace_namedtuple(instance)
    return node, instance.to_variable(node)