Example #1
0
  def value_instance_to_pytd_type(self, node, v, instance, seen, view):
    """Get the PyTD type an instance of this object would have.

    Args:
      node: The node.
      v: The object.
      instance: The instance.
      seen: Already seen instances.
      view: A Variable -> binding map.

    Returns:
      A PyTD type.
    """
    if isinstance(v, abstract.Union):
      return pytd.UnionType(tuple(
          self.value_instance_to_pytd_type(node, t, instance, seen, view)
          for t in v.options))
    elif isinstance(v, abstract.AnnotationContainer):
      return self.value_instance_to_pytd_type(
          node, v.base_cls, instance, seen, view)
    elif isinstance(v, mixin.Class):
      if not self._detailed and v.official_name is None:
        return pytd.AnythingType()
      if seen is None:
        # We make the set immutable to ensure that the seen instances for
        # different parameter values don't interfere with one another.
        seen = frozenset()
      if instance in seen:
        # We have a circular dependency in our types (e.g., lst[0] == lst). Stop
        # descending into the type parameters.
        type_params = ()
      else:
        type_params = tuple(t.name for t in v.template)
      if instance is not None:
        seen |= {instance}
      type_arguments = self._value_to_parameter_types(
          node, v, instance, type_params, seen, view)
      base = pytd_utils.NamedTypeWithModule(v.official_name or v.name, v.module)
      if self._is_tuple(v, instance):
        if type_arguments:
          homogeneous = False
        else:
          homogeneous = True
          type_arguments = [pytd.NothingType()]
      elif v.full_name == "typing.Callable":
        homogeneous = not isinstance(v, abstract.CallableClass)
      else:
        homogeneous = len(type_arguments) == 1
      return pytd_utils.MakeClassOrContainerType(
          base, type_arguments, homogeneous)
    elif isinstance(v, abstract.TypeParameter):
      # We generate the full definition because, if this type parameter is
      # imported, we will need the definition in order to declare it later.
      return self._typeparam_to_def(node, v, v.name)
    elif isinstance(v, typing_overlay.NoReturn):
      return pytd.NothingType()
    else:
      log.info("Using ? for instance of %s", v.name)
      return pytd.AnythingType()
Example #2
0
def JoinTypes(types):
  """Combine a list of types into a union type, if needed.

  Leaves singular return values alone, or wraps a UnionType around them if there
  are multiple ones, or if there are no elements in the list (or only
  NothingType) return NothingType.

  Arguments:
    types: A list of types. This list might contain other UnionTypes. If
    so, they are flattened.

  Returns:
    A type that represents the union of the types passed in. Order is preserved.
  """
  queue = collections.deque(types)
  seen = set()
  new_types = []
  while queue:
    t = queue.popleft()
    if isinstance(t, pytd.UnionType):
      queue.extendleft(reversed(t.type_list))
    elif isinstance(t, pytd.NothingType):
      pass
    elif t not in seen:
      new_types.append(t)
      seen.add(t)

  if len(new_types) == 1:
    return new_types.pop()
  elif any(isinstance(t, pytd.AnythingType) for t in new_types):
    return pytd.AnythingType()
  elif new_types:
    return pytd.UnionType(tuple(new_types))  # tuple() to make unions hashable
  else:
    return pytd.NothingType()
Example #3
0
def SplitParents(parser, p, parents):
    """Strip the special Generic[...] class out of the base classes."""
    template = ()
    other_parents = []
    for parent in parents:
        if (isinstance(parent, pytd.GenericType) and parent.base_type
                == pytd.ExternalType('Generic', 'typing')):
            if not all(
                    isinstance(param, pytd.NamedType)
                    for param in parent.parameters):
                make_syntax_error(
                    parser,
                    'Illegal template parameter %s' % pytd.Print(parent), p)
            if template:
                make_syntax_error(parser, 'Duplicate Template base class', p)
            template = tuple(
                pytd.TemplateItem(pytd.TypeParameter(param.name))
                for param in parent.parameters)
            all_names = [t.name for t in template]
            duplicates = [
                name for name, count in collections.Counter(all_names).items()
                if count >= 2
            ]
            if duplicates:
                make_syntax_error(
                    parser,
                    'Duplicate template parameters' + ', '.join(duplicates), p)
        else:
            if parent != pytd.NothingType():
                other_parents.append(parent)
    return template, tuple(other_parents)
Example #4
0
  def value_to_pytd_type(self, node, v, seen, view):
    """Get a PyTD type representing this object, as seen at a node.

    Args:
      node: The node from which we want to observe this object.
      v: The object.
      seen: The set of values seen before while computing the type.
      view: A Variable -> binding map.

    Returns:
      A PyTD type.
    """
    if isinstance(v, (abstract.Empty, abstract.Nothing)):
      return pytd.NothingType()
    elif isinstance(v, abstract.TypeParameterInstance):
      if (v.name in v.instance.type_parameters and
          v.instance.type_parameters[v.name].bindings):
        return pytd_utils.JoinTypes(
            self.value_to_pytd_type(node, p, seen, view)
            for p in v.instance.type_parameters[v.name].data)
      else:
        # The type parameter was never initialized
        return pytd.AnythingType()
    elif isinstance(v, (abstract.Function, abstract.IsInstance,
                        abstract.BoundFunction)):
      return pytd.NamedType("typing.Callable")
    elif isinstance(v, abstract.Class):
      param = self.value_instance_to_pytd_type(node, v, None, seen, view)
      return pytd.GenericType(base_type=pytd.NamedType("__builtin__.type"),
                              parameters=(param,))
    elif isinstance(v, abstract.Module):
      return pytd.NamedType("__builtin__.module")
    elif isinstance(v, abstract.SimpleAbstractValue):
      if v.cls:
        classvalues = self._get_values(node, v.cls, view)
        cls_types = []
        for cls in classvalues:
          cls_types.append(self.value_instance_to_pytd_type(
              node, cls, v, seen=seen, view=view))
        ret = pytd_utils.JoinTypes(cls_types)
        visitors.InPlaceFillInClasses(ret, v.vm.loader.builtins)
        return ret
      else:
        # We don't know this type's __class__, so return AnythingType to
        # indicate that we don't know anything about what this is.
        # This happens e.g. for locals / globals, which are returned from the
        # code in class declarations.
        log.info("Using ? for %s", v.name)
        return pytd.AnythingType()
    elif isinstance(v, abstract.Union):
      return pytd.UnionType(tuple(self.value_to_pytd_type(node, o, seen, view)
                                  for o in v.options))
    elif isinstance(v, abstract.SuperInstance):
      return pytd.NamedType("__builtin__.super")
    elif isinstance(v, abstract.Unsolvable):
      return pytd.AnythingType()
    elif isinstance(v, abstract.Unknown):
      return pytd.NamedType(v.class_name)
    else:
      raise NotImplementedError(v.__class__.__name__)
Example #5
0
def pytd_callable(
    base_type: pytd.NamedType,
    parameters: Parameters
) -> pytd_node.Node:
  """Create a pytd.CallableType."""
  if isinstance(parameters[0], list):
    if len(parameters) > 2:
      raise ParseError(
          "Expected 2 parameters to Callable, got %d" % len(parameters))
    if len(parameters) == 1:
      # We're usually happy to treat omitted parameters as "Any", but we
      # need a return type for CallableType, or we wouldn't know whether the
      # last parameter is an argument or return type.
      parameters += (pytd.AnythingType(),)
    if not parameters[0] or parameters[0] == [pytd.NothingType()]:
      # Callable[[], ret] -> pytd.CallableType(ret)
      parameters = parameters[1:]
    else:
      # Callable[[x, ...], ret] -> pytd.CallableType(x, ..., ret)
      parameters = tuple(parameters[0]) + parameters[1:]
    return pytd.CallableType(base_type=base_type, parameters=parameters)
  else:
    # Fall back to a generic Callable if first param is Any
    assert parameters
    if not is_any(parameters[0]):
      msg = ("First argument to Callable must be a list of argument types "
             "(got %r)" % parameters[0])
      raise ParseError(msg)
    return pytd.GenericType(base_type=base_type, parameters=parameters)
Example #6
0
 def test_join_nothing_type(self):
     """Test that JoinTypes() removes or collapses 'nothing'."""
     a = pytd.NamedType("a")
     nothing = pytd.NothingType()
     self.assertEqual(pytd_utils.JoinTypes([a, nothing]), a)
     self.assertEqual(pytd_utils.JoinTypes([nothing]), nothing)
     self.assertEqual(pytd_utils.JoinTypes([nothing, nothing]), nothing)
Example #7
0
 def testToTypeWithViewAndEmptyParam(self):
     instance = abstract.List([], self._vm)
     view = {instance.cls: instance.cls.bindings[0]}
     pytd_type = instance.to_type(self._vm.root_cfg_node,
                                  seen=None,
                                  view=view)
     self.assertEqual("__builtin__.list", pytd_type.base_type.name)
     self.assertSequenceEqual((pytd.NothingType(), ), pytd_type.parameters)
Example #8
0
    def setUp(self):
        self.options = config.Options.create(
            python_version=self.PYTHON_VERSION, python_exe=self.PYTHON_EXE)

        def t(name):  # pylint: disable=invalid-name
            return pytd.ClassType("__builtin__." + name)

        self.bool = t("bool")
        self.dict = t("dict")
        self.float = t("float")
        self.complex = t("complex")
        self.int = t("int")
        self.list = t("list")
        self.none_type = t("NoneType")
        self.object = t("object")
        self.set = t("set")
        self.frozenset = t("frozenset")
        self.str = t("str")
        self.bytearray = t("bytearray")
        self.tuple = t("tuple")
        self.unicode = t("unicode")
        self.generator = t("generator")
        self.function = pytd.ClassType("typing.Callable")
        self.anything = pytd.AnythingType()
        self.nothing = pytd.NothingType()
        self.module = t("module")
        self.file = t("file")

        # The various union types use pytd_utils.CanonicalOrdering()'s ordering:
        self.intorstr = pytd.UnionType((self.int, self.str))
        self.strorunicode = pytd.UnionType((self.str, self.unicode))
        self.intorfloat = pytd.UnionType((self.float, self.int))
        self.intorfloatorstr = pytd.UnionType((self.float, self.int, self.str))
        self.complexorstr = pytd.UnionType((self.complex, self.str))
        self.intorfloatorcomplex = pytd.UnionType(
            (self.int, self.float, self.complex))
        self.int_tuple = pytd.GenericType(self.tuple, (self.int, ))
        self.nothing_tuple = pytd.GenericType(self.tuple, (self.nothing, ))
        self.intorfloat_tuple = pytd.GenericType(self.tuple,
                                                 (self.intorfloat, ))
        self.int_set = pytd.GenericType(self.set, (self.int, ))
        self.intorfloat_set = pytd.GenericType(self.set, (self.intorfloat, ))
        self.unknown_frozenset = pytd.GenericType(self.frozenset,
                                                  (self.anything, ))
        self.float_frozenset = pytd.GenericType(self.frozenset, (self.float, ))
        self.empty_frozenset = pytd.GenericType(self.frozenset,
                                                (self.nothing, ))
        self.int_list = pytd.GenericType(self.list, (self.int, ))
        self.str_list = pytd.GenericType(self.list, (self.str, ))
        self.intorfloat_list = pytd.GenericType(self.list, (self.intorfloat, ))
        self.intorstr_list = pytd.GenericType(self.list, (self.intorstr, ))
        self.anything_list = pytd.GenericType(self.list, (self.anything, ))
        self.nothing_list = pytd.GenericType(self.list, (self.nothing, ))
        self.int_int_dict = pytd.GenericType(self.dict, (self.int, self.int))
        self.int_str_dict = pytd.GenericType(self.dict, (self.int, self.str))
        self.str_int_dict = pytd.GenericType(self.dict, (self.str, self.int))
        self.nothing_nothing_dict = pytd.GenericType(
            self.dict, (self.nothing, self.nothing))
Example #9
0
    def setUpClass(cls):
        super().setUpClass()
        # We use class-wide loader to avoid creating a new loader for every test
        # method if not required.
        cls._loader = None

        def t(name):  # pylint: disable=invalid-name
            return pytd.ClassType("builtins." + name)

        cls.bool = t("bool")
        cls.dict = t("dict")
        cls.float = t("float")
        cls.complex = t("complex")
        cls.int = t("int")
        cls.list = t("list")
        cls.none_type = t("NoneType")
        cls.object = t("object")
        cls.set = t("set")
        cls.frozenset = t("frozenset")
        cls.str = t("str")
        cls.bytearray = t("bytearray")
        cls.tuple = t("tuple")
        cls.unicode = t("unicode")
        cls.generator = t("generator")
        cls.function = pytd.ClassType("typing.Callable")
        cls.anything = pytd.AnythingType()
        cls.nothing = pytd.NothingType()
        cls.module = t("module")
        cls.file = t("file")

        # The various union types use pytd_utils.CanonicalOrdering()'s ordering:
        cls.intorstr = pytd.UnionType((cls.int, cls.str))
        cls.strorunicode = pytd.UnionType((cls.str, cls.unicode))
        cls.intorfloat = pytd.UnionType((cls.float, cls.int))
        cls.intorfloatorstr = pytd.UnionType((cls.float, cls.int, cls.str))
        cls.complexorstr = pytd.UnionType((cls.complex, cls.str))
        cls.intorfloatorcomplex = pytd.UnionType(
            (cls.int, cls.float, cls.complex))
        cls.int_tuple = pytd.GenericType(cls.tuple, (cls.int, ))
        cls.nothing_tuple = pytd.TupleType(cls.tuple, ())
        cls.intorfloat_tuple = pytd.GenericType(cls.tuple, (cls.intorfloat, ))
        cls.int_set = pytd.GenericType(cls.set, (cls.int, ))
        cls.intorfloat_set = pytd.GenericType(cls.set, (cls.intorfloat, ))
        cls.unknown_frozenset = pytd.GenericType(cls.frozenset,
                                                 (cls.anything, ))
        cls.float_frozenset = pytd.GenericType(cls.frozenset, (cls.float, ))
        cls.empty_frozenset = pytd.GenericType(cls.frozenset, (cls.nothing, ))
        cls.int_list = pytd.GenericType(cls.list, (cls.int, ))
        cls.str_list = pytd.GenericType(cls.list, (cls.str, ))
        cls.intorfloat_list = pytd.GenericType(cls.list, (cls.intorfloat, ))
        cls.intorstr_list = pytd.GenericType(cls.list, (cls.intorstr, ))
        cls.anything_list = pytd.GenericType(cls.list, (cls.anything, ))
        cls.nothing_list = pytd.GenericType(cls.list, (cls.nothing, ))
        cls.int_int_dict = pytd.GenericType(cls.dict, (cls.int, cls.int))
        cls.int_str_dict = pytd.GenericType(cls.dict, (cls.int, cls.str))
        cls.str_int_dict = pytd.GenericType(cls.dict, (cls.str, cls.int))
        cls.nothing_nothing_dict = pytd.GenericType(cls.dict,
                                                    (cls.nothing, cls.nothing))
Example #10
0
 def test_empty_tuple(self):
   ty = self.Infer("""
     def f():
       return ()
     f()
   """, deep=False, show_library_calls=True)
   self.assertHasOnlySignatures(
       ty.Lookup("f"),
       ((), pytd.GenericType(self.tuple, (pytd.NothingType(),))))
Example #11
0
 def testEmptyTuple(self):
   ty = self.Infer("""
     def f():
       return ()
     f()
   """, deep=False, solve_unknowns=False, show_library_calls=True)
   self.assertHasOnlySignatures(
       ty.Lookup("f"),
       ((), pytd.HomogeneousContainerType(self.tuple, (pytd.NothingType(),))))
Example #12
0
 def testToTypeWithViewAndEmptyParam(self):
     instance = abstract.Instance(self._vm.convert.list_type, self._vm,
                                  self._vm.root_cfg_node)
     instance.type_parameters["T"] = self._vm.program.NewVariable()
     view = {instance.cls: instance.cls.bindings[0]}
     pytd_type = instance.to_type(self._vm.root_cfg_node,
                                  seen=None,
                                  view=view)
     self.assertEquals("__builtin__.list", pytd_type.base_type.name)
     self.assertSequenceEqual((pytd.NothingType(), ), pytd_type.parameters)
Example #13
0
 def p_funcdef_code(self, p):
     """funcdef : DEF NAME PYTHONCODE"""
     # NAME (not: module_name) because PYTHONCODE is always local.
     _, _, name, _ = p
     p[0] = NameAndSig(
         name=name,
         # signature is for completeness - it's ignored
         signature=pytd.Signature(params=(),
                                  return_type=pytd.NothingType(),
                                  exceptions=(),
                                  template=(),
                                  has_optional=False),
         external_code=True)
Example #14
0
  def value_instance_to_pytd_type(self, node, v, instance, seen, view):
    """Get the PyTD type an instance of this object would have.

    Args:
      node: The node.
      v: The object.
      instance: The instance.
      seen: Already seen instances.
      view: A Variable -> binding map.

    Returns:
      A PyTD type.
    """
    if isinstance(v, abstract.Union):
      return pytd.UnionType(tuple(
          self.value_instance_to_pytd_type(node, t, instance, seen, view)
          for t in v.options))
    elif isinstance(v, abstract.AnnotationContainer):
      return self.value_instance_to_pytd_type(
          node, v.base_cls, instance, seen, view)
    elif isinstance(v, abstract.Class):
      if v.official_name is None:
        return pytd.AnythingType()
      if seen is None:
        seen = set()
      if instance in seen:
        # We have a circular dependency in our types (e.g., lst[0] == lst). Stop
        # descending into the type parameters.
        type_params = ()
      else:
        type_params = tuple(t.name for t in v.template)
      if instance is not None:
        seen.add(instance)
      type_arguments = self._value_to_parameter_types(
          node, v, instance, type_params, seen, view)
      base = pytd_utils.NamedTypeWithModule(v.official_name, v.module)
      if self._is_tuple(v, instance):
        if type_arguments:
          homogeneous = False
        else:
          homogeneous = True
          type_arguments = [pytd.NothingType()]
      else:
        homogeneous = len(type_arguments) == 1
      return pytd_utils.MakeClassOrContainerType(
          base, type_arguments, homogeneous)
    elif isinstance(v, abstract.TypeVariable):
      return pytd.TypeParameter(v.name, None)
    else:
      log.info("Using ? for instance of %s", v.name)
      return pytd.AnythingType()
Example #15
0
 def testEmptyTuple(self):
     with self.Infer("""
   def f():
     return ()
   f()
 """,
                     deep=False,
                     solve_unknowns=False,
                     extract_locals=False) as ty:
         self.assertHasOnlySignatures(
             ty.Lookup("f"),
             ((),
              pytd.HomogeneousContainerType(self.tuple,
                                            (pytd.NothingType(), ))))
Example #16
0
 def new_external_function(self, decorators, name):
     """Return a _NameAndSig for an external code function."""
     del decorators
     return _NameAndSig(
         name=name,
         # signature is for completeness - it's ignored
         signature=pytd.Signature(params=(),
                                  starargs=None,
                                  starstarargs=None,
                                  return_type=pytd.NothingType(),
                                  exceptions=(),
                                  template=()),
         decorators=(),
         external_code=True)
Example #17
0
 def testOrder(self):
   # pytd types' primary sort key is the class name, second sort key is
   # the contents when interpreted as a (named)tuple.
   nodes = [pytd.AnythingType(),
            pytd.GenericType(self.list, (self.int,)),
            pytd.NamedType("int"),
            pytd.NothingType(),
            pytd.UnionType((self.float,)),
            pytd.UnionType((self.int,))]
   for n1, n2 in zip(nodes[:-1], nodes[1:]):
     self.assertLess(n1, n2)
     self.assertLessEqual(n1, n2)
     self.assertGreater(n2, n1)
     self.assertGreaterEqual(n2, n1)
   for p in itertools.permutations(nodes):
     self.assertEquals(list(sorted(p)), nodes)
Example #18
0
    def VisitUnionType(self, union):
        """Push unions down into containers.

    This collects similar container types in unions and merges them into
    single instances with the union type pushed down to the element_type level.

    Arguments:
      union: A pytd.Union instance. Might appear in a parameter, a return type,
        a constant type, etc.

    Returns:
      A simplified pytd.Union.
    """
        if not any(isinstance(t, pytd.GenericType) for t in union.type_list):
            # Optimization: If we're not going to change anything, return original.
            return union
        union = utils.JoinTypes(union.type_list)  # flatten
        if not isinstance(union, pytd.UnionType):
            union = pytd.UnionType((union, ))
        collect = {}
        has_redundant_base_types = False
        for t in union.type_list:
            if isinstance(t, pytd.GenericType):
                if t.base_type in collect:
                    has_redundant_base_types = True
                    collect[t.base_type] = tuple(
                        utils.JoinTypes([p1, p2])
                        for p1, p2 in zip(collect[t.base_type], t.parameters))
                else:
                    collect[t.base_type] = t.parameters
        if not has_redundant_base_types:
            return union
        result = pytd.NothingType()
        done = set()
        for t in union.type_list:
            if isinstance(t, pytd.GenericType):
                if t.base_type in done:
                    continue  # already added
                parameters = collect[t.base_type]
                add = t.Replace(parameters=tuple(
                    p.Visit(CombineContainers()) for p in parameters))
                done.add(t.base_type)
            else:
                add = t
            result = utils.JoinTypes([result, add])
        return result
Example #19
0
    def testTypeMatcher(self):
        """Test for the TypeMatcher class."""
        class MyTypeMatcher(utils.TypeMatcher):
            def default_match(self, t1, t2, mykeyword):
                assert mykeyword == "foobar"
                return t1 == t2

            def match_Function_against_Function(self, f1, f2, mykeyword):
                assert mykeyword == "foobar"
                return all(
                    self.match(sig1, sig2, mykeyword)
                    for sig1, sig2 in zip(f1.signatures, f2.signatures))

        s1 = pytd.Signature((), pytd.NothingType(), (), (), False)
        s2 = pytd.Signature((), pytd.AnythingType(), (), (), False)
        self.assertTrue(MyTypeMatcher().match(pytd.Function("f1", (s1, s2)),
                                              pytd.Function("f2", (s1, s2)),
                                              mykeyword="foobar"))
        self.assertFalse(MyTypeMatcher().match(pytd.Function("f1", (s1, s2)),
                                               pytd.Function("f2", (s2, s2)),
                                               mykeyword="foobar"))
Example #20
0
    def resolve_type(self, name: Union[str, pytd_node.Node]) -> pytd_node.Node:
        """Return the fully resolved name for an alias.

    Args:
      name: The name of the type or alias.

    Returns:
      A pytd.NamedType with the fully resolved and qualified name.
    """
        if isinstance(name, (pytd.GenericType, pytd.AnythingType)):
            return name
        if isinstance(name, pytd.NamedType):
            name = name.name
        if name == "nothing":
            return pytd.NothingType()
        base_type = self.type_map.get(name)
        if base_type is None:
            module, dot, tail = name.partition(".")
            full_name = self.module_path_map.get(module, module) + dot + tail
            base_type = pytd.NamedType(full_name)
        return base_type
Example #21
0
 def testAnythingNothing(self):
   m = type_match.TypeMatch({})
   eq = m.match_type_against_type(pytd.AnythingType(), pytd.NothingType(), {})
   self.assertEqual(eq, booleq.TRUE)
Example #22
0
 def testNothingRight(self):
   m = type_match.TypeMatch({})
   eq = m.match_type_against_type(pytd.NamedType("A"), pytd.NothingType(), {})
   self.assertEqual(eq, booleq.FALSE)
Example #23
0
 def testEmptyNodesAreTrue(self):
     self.assertTrue(pytd.AnythingType())
     self.assertTrue(pytd.NothingType())
Example #24
0
 def test_empty_nodes_are_true(self):
     self.assertTrue(pytd.AnythingType())
     self.assertTrue(pytd.NothingType())
Example #25
0
 def p_type_nothing(self, p):
     """type : NOTHING"""
     p[0] = pytd.NothingType()
Example #26
0
    def value_instance_to_pytd_type(self, node, v, instance, seen, view):
        """Get the PyTD type an instance of this object would have.

    Args:
      node: The node.
      v: The object.
      instance: The instance.
      seen: Already seen instances.
      view: A Variable -> binding map.

    Returns:
      A PyTD type.
    """
        if isinstance(v, abstract.Union):
            return pytd.UnionType(
                tuple(
                    self.value_instance_to_pytd_type(node, t, instance, seen,
                                                     view) for t in v.options))
        elif isinstance(v, abstract.AnnotationContainer):
            return self.value_instance_to_pytd_type(node, v.base_cls, instance,
                                                    seen, view)
        elif isinstance(v, abstract.LiteralClass):
            if not v.value:
                # TODO(b/173742489): Remove this workaround once we support literal
                # enums.
                return pytd.AnythingType()
            if isinstance(v.value.pyval, (str, bytes)):
                # Strings are stored as strings of their representations, prefix and
                # quotes and all.
                value = repr(v.value.pyval)
            elif isinstance(v.value.pyval, bool):
                # True and False are stored as pytd constants.
                value = self.vm.lookup_builtin(f"builtins.{v.value.pyval}")
            else:
                # Ints are stored as their literal values. Note that Literal[None] or a
                # nested literal will never appear here, since we simplified it to None
                # or unnested it, respectively, in typing_overlay. Literal[<enum>] does
                # not appear here yet because it is unsupported.
                assert isinstance(v.value.pyval, int), v.value.pyval
                value = v.value.pyval
            return pytd.Literal(value)
        elif isinstance(v, class_mixin.Class):
            if not self._detailed and v.official_name is None:
                return pytd.AnythingType()
            if seen is None:
                # We make the set immutable to ensure that the seen instances for
                # different parameter values don't interfere with one another.
                seen = frozenset()
            if instance in seen:
                # We have a circular dependency in our types (e.g., lst[0] == lst). Stop
                # descending into the type parameters.
                type_params = ()
            else:
                type_params = tuple(t.name for t in v.template)
            if instance is not None:
                seen |= {instance}
            type_arguments = self._value_to_parameter_types(
                node, v, instance, type_params, seen, view)
            base = pytd_utils.NamedTypeWithModule(v.official_name or v.name,
                                                  v.module)
            if self._is_tuple(v, instance):
                homogeneous = False
            elif v.full_name == "typing.Callable":
                homogeneous = not isinstance(v, abstract.CallableClass)
            else:
                homogeneous = len(type_arguments) == 1
            return pytd_utils.MakeClassOrContainerType(base, type_arguments,
                                                       homogeneous)
        elif isinstance(v, abstract.TypeParameter):
            # We generate the full definition because, if this type parameter is
            # imported, we will need the definition in order to declare it later.
            return self._typeparam_to_def(node, v, v.name)
        elif isinstance(v, typing_overlay.NoReturn):
            return pytd.NothingType()
        else:
            log.info("Using Any for instance of %s", v.name)
            return pytd.AnythingType()
Example #27
0
    def value_to_pytd_type(self, node, v, seen, view):
        """Get a PyTD type representing this object, as seen at a node.

    Args:
      node: The node from which we want to observe this object.
      v: The object.
      seen: The set of values seen before while computing the type.
      view: A Variable -> binding map.

    Returns:
      A PyTD type.
    """
        if isinstance(v, (abstract.Empty, typing_overlay.NoReturn)):
            return pytd.NothingType()
        elif isinstance(v, abstract.TypeParameterInstance):
            if v.module in self._scopes:
                return self._typeparam_to_def(node, v.param, v.param.name)
            elif v.instance.get_instance_type_parameter(v.full_name).bindings:
                # The type parameter was initialized. Set the view to None, since we
                # don't include v.instance in the view.
                return pytd_utils.JoinTypes(
                    self.value_to_pytd_type(node, p, seen, None) for p in
                    v.instance.get_instance_type_parameter(v.full_name).data)
            elif v.param.constraints:
                return pytd_utils.JoinTypes(
                    self.value_instance_to_pytd_type(node, p, None, seen, view)
                    for p in v.param.constraints)
            elif v.param.bound:
                return self.value_instance_to_pytd_type(
                    node, v.param.bound, None, seen, view)
            else:
                return pytd.AnythingType()
        elif isinstance(v, typing_overlay.TypeVar):
            return pytd.NamedType("builtins.type")
        elif isinstance(v, dataclass_overlay.FieldInstance):
            if not v.default:
                return pytd.AnythingType()
            return pytd_utils.JoinTypes(
                self.value_to_pytd_type(node, d, seen, view)
                for d in v.default.data)
        elif isinstance(v, abstract.FUNCTION_TYPES):
            try:
                signatures = abstract_utils.get_signatures(v)
            except NotImplementedError:
                return pytd.NamedType("typing.Callable")
            if len(signatures) == 1:
                val = self.signature_to_callable(signatures[0])
                if not isinstance(
                        v, abstract.PYTD_FUNCTION_TYPES) or not val.formal:
                    # This is a workaround to make sure we don't put unexpected type
                    # parameters in call traces.
                    return self.value_instance_to_pytd_type(
                        node, val, None, seen, view)
            return pytd.NamedType("typing.Callable")
        elif isinstance(v, (abstract.ClassMethod, abstract.StaticMethod)):
            return self.value_to_pytd_type(node, v.method, seen, view)
        elif isinstance(v, (special_builtins.IsInstance,
                            special_builtins.ClassMethodCallable)):
            return pytd.NamedType("typing.Callable")
        elif isinstance(v, class_mixin.Class):
            param = self.value_instance_to_pytd_type(node, v, None, seen, view)
            return pytd.GenericType(base_type=pytd.NamedType("builtins.type"),
                                    parameters=(param, ))
        elif isinstance(v, abstract.Module):
            return pytd.NamedType("builtins.module")
        elif (self._output_mode >= Converter.OutputMode.LITERAL
              and isinstance(v, abstract.ConcreteValue)
              and isinstance(v.pyval, (int, str, bytes))):
            # LITERAL mode is used only for pretty-printing, so we just stringify the
            # inner value rather than properly converting it.
            return pytd.Literal(repr(v.pyval))
        elif isinstance(v, abstract.SimpleValue):
            if v.cls:
                ret = self.value_instance_to_pytd_type(node,
                                                       v.cls,
                                                       v,
                                                       seen=seen,
                                                       view=view)
                ret.Visit(
                    visitors.FillInLocalPointers(
                        {"builtins": self.vm.loader.builtins}))
                return ret
            else:
                # We don't know this type's __class__, so return AnythingType to
                # indicate that we don't know anything about what this is.
                # This happens e.g. for locals / globals, which are returned from the
                # code in class declarations.
                log.info("Using Any for %s", v.name)
                return pytd.AnythingType()
        elif isinstance(v, abstract.Union):
            opts = []
            for o in v.options:
                # NOTE: Guarding printing of type parameters behind _detailed until
                # round-tripping is working properly.
                if self._detailed and isinstance(o, abstract.TypeParameter):
                    opt = self._typeparam_to_def(node, o, o.name)
                else:
                    opt = self.value_to_pytd_type(node, o, seen, view)
                opts.append(opt)
            return pytd.UnionType(tuple(opts))
        elif isinstance(v, special_builtins.SuperInstance):
            return pytd.NamedType("builtins.super")
        elif isinstance(v, abstract.TypeParameter):
            # Arguably, the type of a type parameter is NamedType("typing.TypeVar"),
            # but pytype doesn't know how to handle that, so let's just go with Any
            # unless self._detailed is set.
            if self._detailed:
                return pytd.NamedType("typing.TypeVar")
            else:
                return pytd.AnythingType()
        elif isinstance(v, abstract.Unsolvable):
            return pytd.AnythingType()
        elif isinstance(v, abstract.Unknown):
            return pytd.NamedType(v.class_name)
        elif isinstance(v, abstract.BuildClass):
            return pytd.NamedType("typing.Callable")
        else:
            raise NotImplementedError(v.__class__.__name__)
Example #28
0
 def __init__(self):
     self.union = pytd.NothingType()
Example #29
0
  def value_to_pytd_type(self, node, v, seen, view):
    """Get a PyTD type representing this object, as seen at a node.

    Args:
      node: The node from which we want to observe this object.
      v: The object.
      seen: The set of values seen before while computing the type.
      view: A Variable -> binding map.

    Returns:
      A PyTD type.
    """
    if isinstance(v, (abstract.Empty, typing_overlay.NoReturn)):
      return pytd.NothingType()
    elif isinstance(v, abstract.TypeParameterInstance):
      if v.instance.get_instance_type_parameter(v.full_name).bindings:
        # The type parameter was initialized. Set the view to None, since we
        # don't include v.instance in the view.
        return pytd_utils.JoinTypes(
            self.value_to_pytd_type(node, p, seen, None)
            for p in v.instance.get_instance_type_parameter(v.full_name).data)
      elif v.param.constraints:
        return pytd_utils.JoinTypes(
            self.value_instance_to_pytd_type(node, p, None, seen, view)
            for p in v.param.constraints)
      elif v.param.bound:
        return self.value_instance_to_pytd_type(
            node, v.param.bound, None, seen, view)
      else:
        return pytd.AnythingType()
    elif isinstance(v, typing_overlay.TypeVar):
      return pytd.NamedType("__builtin__.type")
    elif isinstance(v, abstract.FUNCTION_TYPES):
      try:
        signatures = abstract_utils.get_signatures(v)
      except NotImplementedError:
        return pytd.NamedType("typing.Callable")
      if len(signatures) == 1:
        val = self.signature_to_callable(signatures[0], self.vm)
        if (not isinstance(v, abstract.PYTD_FUNCTION_TYPES) or
            not self.vm.annotations_util.get_type_parameters(val)):
          # This is a workaround to make sure we don't put unexpected type
          # parameters in call traces.
          return self.value_instance_to_pytd_type(node, val, None, seen, view)
      return pytd.NamedType("typing.Callable")
    elif isinstance(v, (abstract.ClassMethod, abstract.StaticMethod)):
      return self.value_to_pytd_type(node, v.method, seen, view)
    elif isinstance(v, (special_builtins.IsInstance,
                        special_builtins.ClassMethodCallable)):
      return pytd.NamedType("typing.Callable")
    elif isinstance(v, mixin.Class):
      param = self.value_instance_to_pytd_type(node, v, None, seen, view)
      return pytd.GenericType(base_type=pytd.NamedType("__builtin__.type"),
                              parameters=(param,))
    elif isinstance(v, abstract.Module):
      return pytd.NamedType("__builtin__.module")
    elif isinstance(v, abstract.SimpleAbstractValue):
      if v.cls:
        ret = self.value_instance_to_pytd_type(
            node, v.cls, v, seen=seen, view=view)
        ret.Visit(visitors.FillInLocalPointers(
            {"__builtin__": self.vm.loader.builtins}))
        return ret
      else:
        # We don't know this type's __class__, so return AnythingType to
        # indicate that we don't know anything about what this is.
        # This happens e.g. for locals / globals, which are returned from the
        # code in class declarations.
        log.info("Using ? for %s", v.name)
        return pytd.AnythingType()
    elif isinstance(v, abstract.Union):
      return pytd.UnionType(tuple(self.value_to_pytd_type(node, o, seen, view)
                                  for o in v.options))
    elif isinstance(v, special_builtins.SuperInstance):
      return pytd.NamedType("__builtin__.super")
    elif isinstance(v, (abstract.Unsolvable, abstract.TypeParameter)):
      # Arguably, the type of a type parameter is NamedType("typing.TypeVar"),
      # but pytype doesn't know how to handle that, so let's just go with Any.
      return pytd.AnythingType()
    elif isinstance(v, abstract.Unknown):
      return pytd.NamedType(v.class_name)
    elif isinstance(v, abstract.BuildClass):
      return pytd.NamedType("typing.Callable")
    else:
      raise NotImplementedError(v.__class__.__name__)
Example #30
0
class _Parser(object):
    """A class used to parse a single PYI file.

  The PYI parser is split into two parts: a low level parser implemented in
  in C++, and the high level parser written in Python.

  The low level parser calls the lexer (also in C++) determines which
  reductions to make, and performs simple actions such as building up lists or
  strings.  It relies on a "peer" to perform more complicated actions
  associated with construction of the AST.

  This class is the high level parser, which invokes the low level parser and
  serves as the peer for AST construction.  Thus it is both the caller and
  callee of the low level parser.

  The low level parser expects the following interface in its peer.

  Attributes that return constant objects:
    ELLIPSIS
    PARSE_ERROR
    NOTHING
    ANYTHING

  Methods used in AST construction:
    new_constant()
    add_alias_or_constant()
    add_import()
    new_type()
    new_union_type()
    new_function()
    new_external_function()
    new_named_tuple()
    regiser_class_name()
    add_class()
    add_type_var()
    if_begin()
    if_elif()
    if_else()
    if_end()

  Other methods:
    set_error_location()


  Error handling is a bit tricky because it is important to associate
  location information with errors, but undesireable to move location
  information around for every call between the low level parser and the
  peer.  As a compromise, when errors are detected (either by the low level
  parser or by the peer raising an exception), set_error_location() is called
  with current location information, then the the call to parse_ext.parse()
  raises an exception (either a ParseError or whatever else was raised by
  the peer in the first place).  The high level parser can thus save location
  information from set_error_location(), catch the exception raised by
  parse_ext.parse(), and raise a new exception that includes a location.

  Conditional pyi code (under an "if" statement) is handled similar to a
  preprocessor, discarding any statements under False conditions rather than
  representing the entire "if" tree in the AST.  This approach allows methods
  such as add_alias_or_constant() to have side effects provided that they
  first check to see if the enclosing scope is active.  There are four
  peer calls used to support conditions:

  if_begin(self, condition): This should be invoked after parsing the initial
      condition but before processing any enclosed definitions.  It establishes
      a new _ConditionScope based on the evaluation of condition.  It returns
      a bool indicating if the scope will now be active.

  if_elif(self, condition): This should be invoked after parsing the condition
      following an "elif", but before any subsequent definitions.  It evaluates
      the condition and changes the scope's state appropriately.  It returns
      a bool indicating if the scope will now be active.

  if_else(self): This should be invoked after parsing "else" but before any
      subsequent definitions.  The scope will become active if it hasn't
      triggered on any previous conditions.  It returns a bool indicating
      if the scope will now be active.

  if_end(self, clauses): This should be called at the end of the entire if
      statement where clauses is a list of (active, defs) pairs.  Active is
      the return value of the corresponding if_begin/if_elif/if_else call, and
      defs is a list of definitions within that block.  The function returns
      the list of defs that should be processed (i.e. the defs in the tuple
      where active was True, or [] if no such tuple is present).

  See _eval_condition for a description of conditions.
  """

    # Values for the parsing context.
    ELLIPSIS = object()  # Special object to signal ELLIPSIS as a parameter.
    PARSE_ERROR = ParseError  # The class object (not an instance of it).
    NOTHING = pytd.NothingType()
    ANYTHING = pytd.AnythingType()

    def __init__(self, version, platform):
        """Initialize the parser.

    Args:
      version: A version tuple.
      platform: A platform string.
    """
        self._used = False
        self._error_location = None
        self._version = _three_tuple(version or _DEFAULT_VERSION)
        self._platform = platform or _DEFAULT_PLATFORM
        self._filename = None
        self._ast_name = None
        # The condition stack, start with a default scope that will always be
        # active.
        self._current_condition = _ConditionScope(None)
        # These fields accumulate definitions that are used to build the
        # final TypeDeclUnit.
        self._constants = []
        self._aliases = []
        self._classes = []
        self._type_params = []
        self._generated_classes = collections.defaultdict(list)

    def parse(self, src, name, filename):
        """Parse a PYI file and return the corresponding AST.

    Note that parse() should be called exactly once per _Parser instance.  It
    holds aggregated state during parsing and is not designed to be reused.

    Args:
      src: The source text to parse.
      name: The name of the module to be created.
      filename: The name of the source file.

    Returns:
      A pytd.TypeDeclUnit() representing the parsed pyi.

    Raises:
      ParseError: If the PYI source could not be parsed.
    """
        # Ensure instances do not get reused.
        assert not self._used
        self._used = True

        self._filename = filename
        self._ast_name = name
        self._type_map = {}

        try:
            defs = parser_ext.parse(self, src)
            ast = self._build_type_decl_unit(defs)
        except ParseError as e:
            if self._error_location:
                line = self._error_location[0]
                try:
                    text = src.splitlines()[line - 1]
                except IndexError:
                    text = None
                raise ParseError(e.message,
                                 line=line,
                                 filename=self._filename,
                                 column=self._error_location[1],
                                 text=text)
            else:
                raise e

        ast = ast.Visit(_InsertTypeParameters())
        # TODO(kramm): This is in the wrong place- it should happen after resolving
        # local names, in load_pytd.
        ast = ast.Visit(pep484.ConvertTypingToNative(name))

        if name:
            ast = ast.Replace(name=name)
            ast = ast.Visit(visitors.AddNamePrefix())
        else:
            # If there's no unique name, hash the sourcecode.
            ast = ast.Replace(name=hashlib.md5(src).hexdigest())

        return ast

    def _build_type_decl_unit(self, defs):
        """Return a pytd.TypeDeclUnit for the given defs (plus parser state)."""
        # defs contains both constant and function definitions.
        constants, functions = _split_definitions(defs)
        constants.extend(self._constants)

        generated_classes = [
            x for class_list in self._generated_classes.values()
            for x in class_list
        ]

        classes = generated_classes + self._classes

        all_names = (list(set(f.name for f in functions)) +
                     [c.name for c in constants] +
                     [c.name
                      for c in self._type_params] + [c.name for c in classes] +
                     [c.name for c in self._aliases])
        duplicates = [
            name for name, count in collections.Counter(all_names).items()
            if count >= 2
        ]
        if duplicates:
            raise ParseError("Duplicate top-level identifier(s): " +
                             ", ".join(duplicates))

        functions, properties = _merge_signatures(functions)
        if properties:
            prop_names = ", ".join(p.name for p in properties)
            raise ParseError(
                "Module-level functions with property decorators: " +
                prop_names)

        return pytd.TypeDeclUnit(name=None,
                                 constants=tuple(constants),
                                 type_params=tuple(self._type_params),
                                 functions=tuple(functions),
                                 classes=tuple(classes),
                                 aliases=tuple(self._aliases))

    def set_error_location(self, location):
        """Record the location of the current error.

    Args:
      location: A tuple (first_line, first_column, last_line, last_column).
    """
        self._error_location = location

    def _eval_condition(self, condition):
        """Evaluate a condition and return a bool.

    Args:
      condition: A condition tuple of (left, op, right). If op is "or", then
      left and right are conditions. Otherwise, left is a name, op is one of
      the comparison strings in _COMPARES, and right is the expected value.

    Returns:
      The boolean result of evaluating the condition.

    Raises:
      ParseError: If the condition cannot be evaluated.
    """
        left, op, right = condition
        if op == "or":
            return self._eval_condition(left) or self._eval_condition(right)
        else:
            return self._eval_comparison(left, op, right)

    def _eval_comparison(self, ident, op, value):
        """Evaluate a comparison and return a bool.

    Args:
      ident: A tuple of a dotted name string and an optional __getitem__ key
        (int or slice).
      op: One of the comparison operator strings in _COMPARES.
      value: Either a string, an integer, or a tuple of integers.

    Returns:
      The boolean result of the comparison.

    Raises:
      ParseError: If the comparison cannot be evaluted.
    """
        name, key = ident
        if name == "sys.version_info":
            if key is None:
                key = slice(None, None, None)
            assert isinstance(key, (int, slice))
            if isinstance(key, int) and not isinstance(value, int):
                raise ParseError(
                    "an element of sys.version_info must be compared to an integer"
                )
            if isinstance(key, slice) and not _is_int_tuple(value):
                raise ParseError(
                    "sys.version_info must be compared to a tuple of integers")
            try:
                actual = self._version[key]
            except IndexError as e:
                raise ParseError(e.message)
            if isinstance(key, slice):
                actual = _three_tuple(actual)
                value = _three_tuple(value)
        elif name == "sys.platform":
            if not isinstance(value, str):
                raise ParseError("sys.platform must be compared to a string")
            if op not in ["==", "!="]:
                raise ParseError(
                    "sys.platform must be compared using == or !=")
            actual = self._platform
        else:
            raise ParseError("Unsupported condition: '%s'." % name)
        return _COMPARES[op](actual, value)

    def if_begin(self, condition):
        """Begin an "if" statement using the specified condition."""
        self._current_condition = _ConditionScope(self._current_condition)
        self._current_condition.apply_condition(
            self._eval_condition(condition))
        return self._current_condition.active

    def if_elif(self, condition):
        """Start an "elif" clause using the specified condition."""
        self._current_condition.apply_condition(
            self._eval_condition(condition))
        return self._current_condition.active

    def if_else(self):
        """Start an "else" clause using the specified condition."""
        self._current_condition.apply_condition(True)
        return self._current_condition.active

    def if_end(self, clauses):
        """Finish an "if" statement given a list of (active, defs) clauses."""
        self._current_condition = self._current_condition.parent
        for cond_value, stmts in clauses:
            if cond_value:
                return stmts
        return []

    def new_constant(self, name, value):
        """Return a Constant.

    Args:
      name: The name of the constant.
      value: None, 0, or a  pytd type.

    Returns:
      A Constant object.

    Raises:
      ParseError: if value is an int other than 0.
    """
        if value is None:
            t = pytd.AnythingType()
        elif isinstance(value, int):
            if value != 0:
                raise ParseError("Only '0' allowed as int literal")
            t = pytd.NamedType("int")
        else:
            t = value
        return pytd.Constant(name, t)

    def add_alias_or_constant(self, name, value):
        """Add an alias or constant.

    Args:
      name: The name of the alias or constant.
      value: A pytd type.  If the type is NamedType("True") or
          NamedType("False") the name becomes a constant of type bool,
          otherwise it becomes an alias.
    """
        if not self._current_condition.active:
            return
        # TODO(dbaum): Consider merging this with new_constant().
        if value in [pytd.NamedType("True"), pytd.NamedType("False")]:
            self._constants.append(pytd.Constant(name, pytd.NamedType("bool")))
        else:
            self._type_map[name] = value
            self._aliases.append(pytd.Alias(name, value))

    def add_import(self, from_package, import_list):
        """Add an import.

    Args:
      from_package: A dotted package name if this is a "from" statement, or None
          if it is an "import" statement.
      import_list: A list of imported items, which are either strings or pairs
          of strings.  Pairs are used when items are renamed during import
          using "as".

    Raises:
      ParseError: If an import statement uses a rename.
    """
        if from_package:
            if not self._current_condition.active:
                return
            # from a.b.c import d, ...
            for item in import_list:
                if isinstance(item, tuple):
                    name, new_name = item
                else:
                    name = new_name = item
                if name != "*":
                    t = pytd.NamedType("%s.%s" % (from_package, name))
                    self._type_map[new_name] = t
                    if from_package != "typing":
                        self._aliases.append(pytd.Alias(new_name, t))
                else:
                    pass  # TODO(kramm): Handle '*' imports in pyi
        else:
            # No need to check _current_condition since there are no side effects.
            # import a, b as c, ...
            for item in import_list:
                # simple import, no impact on pyi, but check for unsupported rename.
                if isinstance(item, tuple):
                    raise ParseError(
                        "Renaming of modules not supported. Use 'from' syntax."
                    )

    def new_type(self, name, parameters=None):
        """Return the AST for a type.

    Args:
      name: The name of the type.
      parameters: List of type parameters.

    Returns:
      A pytd type node.

    Raises:
      ParseError: if parameters are not supplied for a base_type that requires
          parameters, such as Union.
    """
        base_type = self._type_map.get(name)
        if base_type is None:
            base_type = pytd.NamedType(name)
        if parameters is not None:
            return self._parameterized_type(base_type, parameters)
        else:
            if (isinstance(base_type, pytd.NamedType)
                    and base_type.name in ["typing.Union", "typing.Optional"]):
                raise ParseError("Missing options to %s" % base_type.name)
            return base_type

    def _is_tuple_base_type(self, t):
        return isinstance(t, pytd.NamedType) and (
            t.name == "tuple" or
            (self._ast_name != "__builtin__" and t.name == "__builtin__.tuple")
            or (self._ast_name == "typing" and t.name == "Tuple") or
            (self._ast_name != "typing" and t.name == "typing.Tuple"))

    def _heterogeneous_tuple(self, base_type, parameters):
        if parameters:
            return pytd.TupleType(base_type=base_type, parameters=parameters)
        else:
            return base_type

    def _parameterized_type(self, base_type, parameters):
        """Return a parameterized type."""
        if base_type == pytd.NamedType("typing.Callable"):
            # TODO(kramm): Support Callable[[params], ret].
            return base_type
        elif len(parameters) == 2 and parameters[-1] is self.ELLIPSIS:
            element_type = parameters[0]
            if element_type is self.ELLIPSIS:
                raise ParseError("[..., ...] not supported")
            return pytd.HomogeneousContainerType(base_type=base_type,
                                                 parameters=(element_type, ))
        else:
            parameters = tuple(pytd.AnythingType() if p is self.ELLIPSIS else p
                               for p in parameters)
            if self._is_tuple_base_type(base_type):
                return self._heterogeneous_tuple(base_type, parameters)
            else:
                assert parameters
                return pytd.GenericType(base_type=base_type,
                                        parameters=parameters)

    def new_union_type(self, types):
        """Return a new UnionType composed of the specified types."""
        # UnionType flattens any contained UnionType's.
        return pytd.UnionType(tuple(types))

    def new_function(self, decorators, name, param_list, return_type, raises,
                     body):
        """Return a _NameAndSig object for the function.

    Args:
      decorators: List of decorator names.
      name: Name of funciton.
      param_list: List of parameters, where a paremeter is either a tuple
        (name, type, default) or the ELLIPSIS special object.  See
        _validate_params for a more detailed description of allowed parameters.
      return_type: A pytd type object.
      raises: ?
      body: ?

    Returns:
      A _NameAndSig object.

    Raises:
      ParseError: if any validity checks fail.
    """
        if name == "__init__" and isinstance(return_type, pytd.AnythingType):
            ret = pytd.NamedType("NoneType")
        else:
            ret = return_type
        params = _validate_params(param_list)
        signature = pytd.Signature(params=tuple(params.required),
                                   return_type=ret,
                                   starargs=params.starargs,
                                   starstarargs=params.starstarargs,
                                   exceptions=tuple(raises),
                                   template=())

        for stmt in body:
            if stmt is None:
                # TODO(kramm) : process raise statement
                continue  # raise stmt
            mutator = _Mutator(stmt[0], stmt[1])
            try:
                signature = signature.Visit(mutator)
            except NotImplementedError as e:
                raise ParseError(e.message)
            if not mutator.successful:
                raise ParseError("No parameter named %s" % mutator.name)

        # Remove ignored decorators, raise ParseError for invalid decorators.
        decorators = [d for d in decorators if _keep_decorator(d)]
        # TODO(acaceres): if not inside a class, any decorator should be an error
        if len(decorators) > 1:
            raise ParseError("Too many decorators for %s" % name)

        return _NameAndSig(name=name,
                           signature=signature,
                           decorators=tuple(sorted(decorators)),
                           external_code=False)

    def new_external_function(self, decorators, name):
        """Return a _NameAndSig for an external code function."""
        del decorators
        return _NameAndSig(
            name=name,
            # signature is for completeness - it's ignored
            signature=pytd.Signature(params=(),
                                     starargs=None,
                                     starstarargs=None,
                                     return_type=pytd.NothingType(),
                                     exceptions=(),
                                     template=()),
            decorators=(),
            external_code=True)

    def new_named_tuple(self, base_name, fields):
        """Return a type for a named tuple (implicitly generates a class).

    Args:
      base_name: The named tuple's name.
      fields: A list of (name, type) tuples.

    Returns:
      A NamedType() for the generated class that describes the named tuple.
    """
        # Handle previously defined NamedTuples with the same name
        prev_list = self._generated_classes[base_name]
        name_dedup = "~%d" % len(prev_list) if prev_list else ""
        class_name = "`%s%s`" % (base_name, name_dedup)
        class_parent = self._heterogeneous_tuple(pytd.NamedType("tuple"),
                                                 tuple(t for _, t in fields))
        class_constants = tuple(pytd.Constant(n, t) for n, t in fields)
        nt_class = pytd.Class(name=class_name,
                              metaclass=None,
                              parents=(class_parent, ),
                              methods=(),
                              constants=class_constants,
                              template=())

        self._generated_classes[base_name].append(nt_class)
        return pytd.NamedType(nt_class.name)

    def register_class_name(self, class_name):
        """Register a class name so that it can shadow aliases."""
        if not self._current_condition.active:
            return
        self._type_map[class_name] = pytd.NamedType(class_name)

    def add_class(self, class_name, parent_args, defs):
        """Add a class to the module.

    Args:
      class_name: The name of the class (a string).
      parent_args: A list of parent types and (keyword, value) tuples.
          Parent types must be instances of pytd.Type.  Keyword tuples must
          appear at the end of the list.  Currently the only supported keyword
          is 'metaclass'.
      defs: A list of constant (pytd.Constant) and function (_NameAndSig)
          definitions.

    Raises:
      ParseError: if defs contains duplicate names (excluding multiple
          definitions of a function, which is allowed).
    """
        # Process parent_args, extracting parents and possibly a metaclass.
        parents = []
        metaclass = None
        for i, p in enumerate(parent_args):
            if isinstance(p, pytd.Type):
                parents.append(p)
            else:
                keyword, value = p
                if i != len(parent_args) - 1:
                    raise ParseError("metaclass must be last argument")
                if keyword != "metaclass":
                    raise ParseError(
                        "Only 'metaclass' allowed as classdef kwarg")
                metaclass = value

        constants, methods = _split_definitions(defs)

        all_names = (list(set(f.name
                              for f in methods)) + [c.name for c in constants])
        duplicates = [
            name for name, count in collections.Counter(all_names).items()
            if count >= 2
        ]
        if duplicates:
            # TODO(kramm): raise a syntax error right when the identifier is defined.
            raise ParseError("Duplicate identifier(s): " +
                             ", ".join(duplicates))

        # This check is performed after the above error checking so that errors
        # will be spotted even in non-active conditional code.
        if not self._current_condition.active:
            return

        # TODO(dbaum): Is NothingType even legal here?  The grammar accepts it but
        # perhaps it should be a ParseError.
        parents = [p for p in parents if not isinstance(p, pytd.NothingType)]
        methods, properties = _merge_signatures(methods)
        # Ensure that old style classes inherit from classobj.
        if not parents and class_name not in ["classobj", "object"]:
            parents = (pytd.NamedType("classobj"), )
        cls = pytd.Class(name=class_name,
                         metaclass=metaclass,
                         parents=tuple(parents),
                         methods=tuple(methods),
                         constants=tuple(constants + properties),
                         template=())
        self._classes.append(cls)

    def add_type_var(self, name, param_list):
        """Add a type variable with the given name and parameter list."""
        params = _validate_params(param_list)
        if (not params.required
                or not isinstance(params.required[0], pytd.Parameter)):
            raise ParseError("TypeVar's first arg should be a string")

        # Allow and ignore any other arguments (types, covariant=..., etc)
        name_param = params.required[0].name
        if name != name_param:
            raise ParseError("TypeVar name needs to be %r (not %r)" %
                             (name_param, name))

        if not self._current_condition.active:
            return

        self._type_params.append(pytd.TypeParameter(name, scope=None))