Esempio n. 1
0
 def convert_function_annotations(self, node, raw_annotations):
     if raw_annotations:
         # {"i": int, "return": str} is stored as (int, str, ("i", "return"))
         names = abstract.get_atomic_python_constant(raw_annotations[-1])
         type_list = raw_annotations[:-1]
         annotations = {}
         late_annotations = {}
         for name, t in zip(names, type_list):
             name = abstract.get_atomic_python_constant(name)
             visible = t.Data(node)
             if len(visible) > 1:
                 self.vm.errorlog.ambiguous_annotation(
                     self.vm.frames, visible, name)
             else:
                 try:
                     annot = self._process_one_annotation(
                         visible[0], name, self.vm.frames)
                 except self.LateAnnotationError:
                     late_annotations[name] = LateAnnotation(
                         visible[0], name, self.vm.simple_stack())
                 else:
                     if annot is not None:
                         annotations[name] = annot
         return annotations, late_annotations
     else:
         return {}, {}
Esempio n. 2
0
 def _getargs(self, node, args):
     self._match_args(node, args)
     # Normally we would use typing.NamedTuple.__new__ to match args to
     # parameters, but we can't import typing.
     # TODO(tsudol): Replace with typing.NamedTuple.__new__.
     f = function.Signature.from_param_names("typing.NamedTuple",
                                             ["typename", "fields"])
     callargs = {
         arg_name: arg_var
         for arg_name, arg_var, _ in f.iter_args(args)
     }
     # typing.NamedTuple doesn't support rename or verbose
     name_var = callargs["typename"]
     fields_var = callargs["fields"]
     fields = abstract.get_atomic_python_constant(fields_var)
     # The fields is a list of tuples, so we need to deeply unwrap them.
     fields = [abstract.get_atomic_python_constant(t) for t in fields]
     # We need the actual string for the field names and the AtomicAbstractValue
     # for the field types.
     names = []
     types = []
     for field in fields:
         if (len(field) != 2 or any(not self._is_str_instance(v)
                                    for v in field[0].data)):
             # Note that we don't need to check field[1] because both 'str'
             # (forward reference) and 'type' are valid for it.
             sig, = self.signatures
             bad_param = abstract.BadParam(name="fields",
                                           expected=self._fields_type)
             raise abstract.WrongArgTypes(sig.signature, args, self.vm,
                                          bad_param)
         name, typ = field
         names.append(abstract.get_atomic_python_constant(name))
         types.append(abstract.get_atomic_value(typ))
     return name_var, names, types
Esempio n. 3
0
 def _getargs(self, node, args):
     self.match_args(node, args)
     sig, = self.signatures
     callargs = {
         name: var
         for name, var, _ in sig.signature.iter_args(args)
     }
     # typing.NamedTuple doesn't support rename or verbose
     name_var = callargs["typename"]
     fields_var = callargs["fields"]
     fields = abstract.get_atomic_python_constant(fields_var)
     # The fields is a list of tuples, so we need to deeply unwrap them.
     fields = [abstract.get_atomic_python_constant(t) for t in fields]
     # We need the actual string for the field names and the AtomicAbstractValue
     # for the field types.
     names = []
     types = []
     for field in fields:
         if (len(field) != 2 or any(not self._is_str_instance(v)
                                    for v in field[0].data)):
             # Note that we don't need to check field[1] because both 'str'
             # (forward reference) and 'type' are valid for it.
             sig, = self.signatures
             bad_param = abstract.BadParam(name="fields",
                                           expected=self._fields_type)
             raise abstract.WrongArgTypes(sig.signature, args, self.vm,
                                          bad_param)
         name, typ = field
         names.append(abstract.get_atomic_python_constant(name))
         types.append(abstract.get_atomic_value(typ))
     return name_var, names, types
Esempio n. 4
0
    def _getargs(self, node, args):
        """Extracts the typename, field_names and rename arguments.

    collections.namedtuple takes potentially 4 arguments, but we only care about
    three of them. This function checks the argument count and ensures multiple
    values aren't passed for 'verbose' and 'rename'.

    Args:
      node: The current CFG node. Used by _match_args.
      args: An abstract.FunctionArgs object

    Returns:
      A tuple containing the typename, field_names and rename arguments passed
      to this call to collections.namedtuple.

    Raises:
      abstract.FailedFunctionCall: The arguments do not match those needed by
        the function call. See also: abstract.PyTDFunction._match_args().
      abstract.ConversionError: One of the arguments could not be extracted.
        Typically occurs if typename or one of the field names is in unicode.
    """

        # abstract.PyTDFunction._match_args checks the args for this call.
        self._match_args(node, args)

        # inspect.callargs returns a dictionary mapping the argument names to
        # the values in args.posargs and args.namedargs (or False if there is no
        # value given).
        callargs = inspect.getcallargs(collections.namedtuple, *args.posargs,
                                       **args.namedargs)

        # The name of the namedtuple class is the first arg (a Variable)
        # We need the actual Variable later, so we'll just return name_var and
        # extract the name itself later.
        name_var = callargs["typename"]

        # The fields are also a Variable, which stores the field names as Variables.
        # Extract the list itself, we don't need the wrapper.
        fields_var = callargs["field_names"]
        fields = abstract.get_atomic_python_constant(fields_var)
        # namedtuple fields can be given as a single string, e.g. "a, b, c" or as a
        # list [Variable('a'), Variable('b'), Variable('c')].
        # We just want a list of strings.
        if isinstance(fields, (str, unicode)):
            field_names = fields.replace(",", " ").split()
        else:
            field_names = [
                abstract.get_atomic_python_constant(f) for f in fields
            ]

        # namedtuple also takes a "verbose" argument, but we don't care about that.

        # rename will take any problematic field names and give them a new name.
        # Like the other args, it's stored as a Variable, but we want just a bool.
        if callargs["rename"]:
            rename = abstract.get_atomic_python_constant(callargs["rename"])
        else:
            rename = False

        return name_var, field_names, rename
Esempio n. 5
0
 def _getargs(self, node, args):
     self._match_args(node, args)
     # Normally we would use typing.NamedTuple.__new__ to match args to
     # parameters, but we can't import typing.
     # TODO(tsudol): Replace with typing.NamedTuple.__new__.
     f = function.Signature.from_param_names("typing.NamedTuple",
                                             ["typename", "fields"])
     callargs = {
         arg_name: arg_var
         for arg_name, arg_var, _ in f.iter_args(args)
     }
     # typing.NamedTuple doesn't support rename or verbose
     name_var = callargs["typename"]
     fields_var = callargs["fields"]
     fields = abstract.get_atomic_python_constant(fields_var)
     # The fields is a list of tuples, so we need to deeply unwrap them.
     fields = [abstract.get_atomic_python_constant(t) for t in fields]
     # We need the actual string for the field names and the AtomicAbstractValue
     # for the field types.
     names = []
     types = []
     for (name, typ) in fields:
         names.append(abstract.get_atomic_python_constant(name))
         types.append(abstract.get_atomic_value(typ))
     return name_var, names, types
Esempio n. 6
0
    def make_class(self, node, f_locals):
        f_locals = abstract.get_atomic_python_constant(f_locals)

        # retrieve __qualname__ to get the name of class
        name = f_locals["__qualname__"]
        # retrieve __annotations__ to get the dict
        # with key-value pair of (variable, type)
        anno = f_locals.get("__annotations__", {})
        if anno:
            anno = abstract.get_atomic_value(anno)

        # assemble the arguments that are compatible with NamedTupleFuncBuilder.call
        field_list = []
        defaults = []
        for k, v in anno.items():
            if k in f_locals:
                defaults.append(f_locals.get(k))
                # TODO(ahxun): check if the value matches the declared type
            k = self.vm.convert.constant_to_var(k, node=node)
            field_list.append(self.vm.convert.build_tuple(node, (k, v)))
        anno = self.vm.convert.build_list(node, field_list)
        posargs = (name, anno)
        args = abstract.FunctionArgs(posargs=posargs)
        node, cls_var = self.namedtuple.call(node, None, args)
        cls_val = abstract.get_atomic_value(cls_var)

        if not isinstance(cls_val, abstract.Unsolvable):
            # set __new__.__defaults__
            defaults = abstract.Tuple(tuple(defaults),
                                      self.vm).to_variable(node)
            node, new_attr = self.vm.attribute_handler.get_attribute(
                node, cls_val, "__new__")
            new_attr = abstract.get_atomic_value(new_attr)
            node = self.vm.attribute_handler.set_attribute(
                node, new_attr, "__defaults__", defaults)

            # set the attribute without overriding special namedtuple attributes
            node, fields = self.vm.attribute_handler.get_attribute(
                node, cls_val, "_fields")
            fields = abstract.get_atomic_python_constant(fields, tuple)
            fields = [
                abstract.get_atomic_python_constant(field, str)
                for field in fields
            ]
            for key in f_locals:
                if key in self._prohibited:
                    self.vm.errorlog.not_writable(self.vm.frames, cls_val, key)
                if key not in self._special and key not in fields:
                    node = self.vm.attribute_handler.set_attribute(
                        node, cls_val, key, f_locals[key])

        return node, cls_var
Esempio n. 7
0
 def convert_function_annotations(self, node, raw_annotations):
     if raw_annotations:
         # {"i": int, "return": str} is stored as (int, str, ("i", "return"))
         names = abstract.get_atomic_python_constant(raw_annotations[-1])
         type_list = raw_annotations[:-1]
         annotations_list = []
         for name, t in zip(names, type_list):
             name = abstract.get_atomic_python_constant(name)
             t = self.convert_function_type_annotation(node, name, t)
             annotations_list.append((name, t))
         return self.convert_annotations_list(annotations_list)
     else:
         return {}, {}
Esempio n. 8
0
    def call(self, node, _, args):
        try:
            name_var, field_names, field_types = self._getargs(node, args)
        except abstract.ConversionError:
            return node, self.vm.convert.unsolvable.to_variable(node)

        try:
            name = abstract.get_atomic_python_constant(name_var)
        except abstract.ConversionError:
            return node, self.vm.convert.unsolvable.to_variable(node)

        try:
            field_names = self._validate_and_rename_args(
                name, field_names, False)
        except ValueError as e:
            self.vm.errorlog.invalid_namedtuple_arg(self.vm.frames,
                                                    utils.message(e))
            return node, self.vm.convert.unsolvable.to_variable(node)

        annots, late_annots = self.vm.annotations_util.convert_annotations_list(
            moves.zip(field_names, field_types))
        if late_annots:
            # We currently don't support forward references. Report if we find any,
            # then continue by using Unsolvable instead.
            self.vm.errorlog.not_supported_yet(
                self.vm.frames, "Forward references in typing.NamedTuple")
        field_types = [
            annots.get(field_name, self.vm.convert.unsolvable)
            for field_name in field_names
        ]

        cls_var = self._build_namedtuple(name, field_names, field_types, node)
        self.vm.trace_classdef(cls_var)
        return node, cls_var
Esempio n. 9
0
 def call(self, node, func, args):
   args = args.simplify(node)
   self._match_args(node, args, match_all_views=True)
   # As long as the types match we do not really care about the actual
   # class name. But, if we have a string literal value as the name arg,
   # we will use it.
   name_arg = args.namedargs.get(self._name_arg_name) or args.posargs[0]
   try:
     _ = abstract.get_atomic_python_constant(name_arg, str)
   except abstract.ConversionError:
     name_arg = self.vm.convert.constant_to_var(
         "_NewType_Internal_Class_Name_%d_" % self.internal_name_counter)
   type_arg = args.namedargs.get(self._type_arg_name) or args.posargs[1]
   try:
     type_value = abstract.get_atomic_value(type_arg)
   except abstract.ConversionError:
     # We need the type arg to be an atomic value. If not, we just
     # silently return unsolvable.
     return node, self.vm.convert.create_new_unsolvable(node)
   value_arg_name = "val"
   constructor = abstract.SimpleFunction(
       name="__init__",
       param_names=("self", value_arg_name),
       varargs_name=None,
       kwonly_params=(),
       kwargs_name=None,
       defaults={},
       annotations={value_arg_name: type_value},
       late_annotations={},
       vm=self.vm,
   ).to_variable(node)
   members = abstract.Dict(self.vm)
   members.set_str_item(node, "__init__", constructor)
   return node, self.vm.make_class(node, name_arg, (type_arg,),
                                   members.to_variable(node), None)
Esempio n. 10
0
 def call(self, node, func, args):
     if len(args.posargs) == 4:
         self.match_args(node, args)  # May raise FailedFunctionCall.
         cls, name_var, bases_var, class_dict_var = args.posargs
         try:
             bases = list(abstract.get_atomic_python_constant(bases_var))
             if not bases:
                 bases = [
                     self.vm.convert.object_type.to_variable(
                         self.vm.root_cfg_node)
                 ]
             variable = self.vm.make_class(node, name_var, bases,
                                           class_dict_var, cls)
         except abstract.ConversionError:
             pass
         else:
             return node, variable
     elif (args.posargs and self.vm.callself_stack
           and args.posargs[-1].data == self.vm.callself_stack[-1].data):
         # We're calling type(self) in an __init__ method. A common pattern for
         # making a class non-instantiable is:
         #   class Foo:
         #     def __init__(self):
         #       if type(self) is Foo:
         #         raise ...
         # If we were to return 'Foo', pytype would think that this constructor
         # can never return. The correct return type is something like
         # TypeVar(bound=Foo), but we can't introduce a type parameter that isn't
         # bound to a class or function, so we'll go with Any.
         self.match_args(node, args)  # May raise FailedFunctionCall.
         return node, self.vm.convert.unsolvable.to_variable(node)
     return super(TypeNew, self).call(node, func, args)
Esempio n. 11
0
 def convert_function_annotations(self, node, raw_annotations):
     if raw_annotations:
         # {"i": int, "return": str} is stored as (int, str, ("i", "return"))
         names = abstract.get_atomic_python_constant(raw_annotations[-1])
         type_list = raw_annotations[:-1]
         annotations_list = []
         for name, t in zip(names, type_list):
             name = abstract.get_atomic_python_constant(name)
             visible = t.Data(node)
             if len(visible) > 1:
                 self.vm.errorlog.ambiguous_annotation(
                     self.vm.frames, visible, name)
                 t = None
             else:
                 t = visible[0]
             annotations_list.append((name, t))
         return self.convert_annotations_list(annotations_list)
     else:
         return {}, {}
Esempio n. 12
0
 def call(self, node, func, args):
     if len(args.posargs) == 4:
         self._match_args(node, args)  # May raise FailedFunctionCall.
         cls, name_var, bases_var, class_dict_var = args.posargs
         try:
             bases = list(abstract.get_atomic_python_constant(bases_var))
             if not bases:
                 bases = [
                     self.vm.convert.object_type.to_variable(
                         self.vm.root_cfg_node)
                 ]
             variable = self.vm.make_class(node, name_var, bases,
                                           class_dict_var, cls)
         except abstract.ConversionError:
             pass
         else:
             return node, variable
     return super(TypeNew, self).call(node, func, args)
Esempio n. 13
0
 def call(self, node, *args, **kwargs):
     """Call typing.TypeVar()."""
     if len(args) < 1:
         self.vm.errorlog.invalid_typevar(self.vm.frame.current_opcode,
                                          "Need name as first parameter")
         return node, self.vm.convert.unsolvable.to_variable(node)
     try:
         typevar_name = abstract.get_atomic_python_constant(args[0])
     except abstract.ConversionError:
         self.vm.errorlog.invalid_typevar(self.vm.frame.current_opcode,
                                          "Name must be a constant string")
         return node, self.vm.convert.unsolvable.to_variable(node)
     constraints = args[1:]
     bound = kwargs.get("bound")
     # TODO(kramm): These are variables. We should convert them to booleans.
     covariant = kwargs.get("covariant")
     contravariant = kwargs.get("contravariant")
     typevar = abstract.TypeVariable(typevar_name, self.vm, constraints,
                                     bound, covariant, contravariant)
     self.vm.trace_typevar(typevar_name, typevar)
     return node, typevar.to_variable(node)
Esempio n. 14
0
 def call(self, node, func, args):
     if self.vm.PY3:
         # In Python 3, the type of IO object returned depends on the mode.
         self.match_args(node, args)  # May raise FailedFunctionCall.
         sig, = self.signatures
         callargs = {
             name: var
             for name, var, _ in sig.signature.iter_args(args)
         }
         try:
             if "mode" not in callargs:
                 io_type = "Text"  # The default mode is 'r'.
             else:
                 mode = abstract.get_atomic_python_constant(
                     callargs["mode"])
                 io_type = "Binary" if "b" in mode else "Text"
         except abstract.ConversionError:
             pass
         else:
             return node, self.vm.convert.constant_to_var(
                 abstract.AsInstance(
                     self.vm.lookup_builtin("typing.%sIO" % io_type)), {},
                 node)
     return super(Open, self).call(node, func, args)
Esempio n. 15
0
 def getitem_slot(self, node, slice_var):
     slice_tuple = abstract.get_atomic_python_constant(slice_var)
     values = tuple(s.Data(node)[0] for s in slice_tuple)
     new_union = Union(self.name, self.vm, node, self.elements + values)
     return node, new_union.to_variable(node, "Union")
Esempio n. 16
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 abstract.FailedFunctionCall
    will be raised. Other cases may raise abstract.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: an abstract.FunctionArgs instance

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

    Raises:
      abstract.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, rename = self._getargs(node, args)
    except abstract.ConversionError:
      return node, self.vm.convert.unsolvable.to_variable(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.get_atomic_python_constant(name_var)
    except abstract.ConversionError:
      return node, self.vm.convert.unsolvable.to_variable(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.vm.errorlog.invalid_namedtuple_arg(self.vm.frames, e.message)
      return node, self.vm.convert.unsolvable.to_variable(node)

    name = namedtuple_name(name, field_names)
    ast = namedtuple_ast(name, field_names,
                         python_version=self.vm.python_version)
    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.vm.convert.constant_to_value(cls, {}, self.vm.root_cfg_node)
    self.vm.trace_namedtuple(instance)
    return node, instance.to_variable(node)
Esempio n. 17
0
 def getitem_slot(self, node, slice_var):
   slice_tuple = abstract.get_atomic_python_constant(slice_var)
   values = tuple(s.Data(node)[0] for s in slice_tuple)
   new_union = Union(self.name, self.vm, self.elements + values)
   return node, new_union.to_variable(node, "Union")