"""Overlay for the enum standard library.

For InterpreterClass enums, i.e. ones in the file being analyzed, the overlay
is accessed by:
1. abstract.BuildClass sees a class with enum.Enum as its parent, and calls
EnumBuilder.make_class.
2. EnumBuilder.make_class does some validation, then passes along the actual
creation to vm.make_class. Notably, EnumBuilder passes in EnumInstance to
vm.make_class, which provides enum-specific behavior.
3. vm.make_class does its usual, then calls call_metaclass_init on the newly
created EnumInstance. This bounces back into the overlay, namely EnumMetaInit.
4. EnumMetaInit does the actual transformation of members into proper enum
members.

The transformation into an enum happens so late because enum members are
instances of the enums, which is easier to accomplish when the enum class has
already been created.

PytdClass enums, i.e. those loaded from type stubs, enter the overlay when the
pytd.Class is wrapped with an abstract.PyTDClass in convert.py. After wrapping,
call_metaclass_init is called, allowing EnumMetaInit to transform the PyTDClass
into a proper enum.
"""

import logging

from pytype import abstract
from pytype import abstract_utils
from pytype import function
from pytype import overlay
from pytype import overlay_utils
from pytype.overlays import classgen
from pytype.pytd import pytd
from pytype.pytd import pytd_utils
from pytype.pytd import visitors

log = logging.getLogger(__name__)


class EnumOverlay(overlay.Overlay):
  """An overlay for the enum std lib module."""

  def __init__(self, vm):
    ast = vm.loader.import_name("enum")
    if vm.options.use_enum_overlay:
      member_map = {
          "Enum": EnumBuilder,
          "EnumMeta": EnumMeta,
          "IntEnum": IntEnumBuilder,
      }
      ast = ast.Visit(visitors.RemoveMethods())
    else:
      member_map = {}

    super().__init__(vm, "enum", member_map, ast)


class EnumBuilder(abstract.PyTDClass):
  """Overlays enum.Enum."""

  def __init__(self, vm, name="Enum"):
    enum_ast = vm.loaded_overlays["enum"].ast
    pyval = enum_ast.Lookup(f"enum.{name}")
    super().__init__(name, pyval, vm)

  def make_class(self, node, name_var, bases, class_dict_var, cls_var,
                 new_class_var=None, is_decorated=False):
    """Check the members for errors, then create the enum class."""
    # TODO(tsudol): Handle is_decorated: @enum.unique, for example.
    del is_decorated
    # make_class intercepts the class creation for enums in order to check for
    # errors. EnumMeta turns the class into a full enum, but that's too late for
    # proper error checking.
    # TODO(tsudol): Check enum validity.

    # Enums have a specific ordering for base classes:
    # https://docs.python.org/3/library/enum.html#restricted-enum-subclassing
    # Mostly, we just care that the last base is some kind of Enum.
    if not bases:
      # This should be impossible.
      bases = [self.to_variable(node)]
    elif not any(b.is_enum for b in bases[-1].data):
      msg = ("The last base class for an enum must be enum.Enum or a subclass "
             "of enum.Enum")
      self.vm.errorlog.base_class_error(self.vm.frames, bases[-1], details=msg)
      return node, self.vm.new_unsolvable(node)
    cls_var = cls_var or self.vm.loaded_overlays["enum"].members["EnumMeta"]
    return self.vm.make_class(node, name_var, bases, class_dict_var, cls_var,
                              new_class_var, class_type=EnumInstance)

  def call(self, node, func, args, alias_map=None):
    """Implements the behavior of the enum functional API."""
    # Because of how this is called, we supply our own "self" argument.
    # See class_mixin.Class._call_new_and_init.
    args = args.simplify(node, self.vm)
    args = args.replace(posargs=(self.vm.new_unsolvable(node),) + args.posargs)
    # It's possible that this class has been called in order to look up an enum
    # member, e.g. on something annotated as Type[Enum].
    # First, check the lookup API. If that succeeds, return the result.
    # If not, check against the functional API.
    # Note that super().call or _call_new_and_init won't work here, because
    # they don't raise FailedFunctionCall.
    node, pytd_new_var = self.vm.attribute_handler.get_attribute(
        node, self, "__new__", self.to_binding(node))
    pytd_new = abstract_utils.get_atomic_value(pytd_new_var)
    # There are 2 signatures for Enum.__new__. The one with fewer arguments is
    # for looking up values, and the other is for the functional API.
    # I don't think we have a guarantee of ordering for signatures, so choose
    # them based on parameter count.
    lookup_sig, api_sig = sorted([s.signature for s in pytd_new.signatures],
                                 key=lambda s: s.maximum_param_count())
    lookup_new = abstract.SimpleFunction.from_signature(lookup_sig, self.vm)
    try:
      return lookup_new.call(node, None, args, alias_map)
    except function.FailedFunctionCall as e:
      log.info("Called Enum.__new__ as lookup, but failed:\n%s", e)
    api_new = abstract.SimpleFunction.from_signature(api_sig, self.vm)
    api_new.call(node, None, args, alias_map)

    # At this point, we know this is a functional API call.
    argmap = {name: var for name, var, _ in api_sig.iter_args(args)}
    cls_name_var = argmap["value"]
    try:
      names = abstract_utils.get_atomic_python_constant(argmap["names"])
    except abstract_utils.ConversionError as e:
      log.info("Failed to unwrap values in enum functional interface:\n%s", e)
      return node, self.vm.new_unsolvable(node)

    if isinstance(names, str):
      names = names.replace(",", " ").split()
      fields = {name: self.vm.convert.build_int(node) for name in names}
    elif isinstance(names, dict):
      # Dict keys are strings, not strings in variables. The values are
      # variables, they don't need to be changed.
      fields = names
    else:
      # List of names, or list of (name, value) pairs.
      try:
        possible_pairs = [abstract_utils.get_atomic_python_constant(p)
                          for p in names]
      except abstract_utils.ConversionError as e:
        log.debug("Failed to unwrap possible enum field pairs:\n  %s", e)
        return node, self.vm.new_unsolvable(node)
      if not possible_pairs:
        fields = {}
      elif isinstance(possible_pairs[0], str):
        fields = {name: self.vm.convert.build_int(node)
                  for name in possible_pairs}
      else:
        # List of (name_var, value_var) pairs.
        # The earlier get_atomic_python_constant call only unwrapped the tuple,
        # so the values in the tuple still need to be unwrapped.
        try:
          fields = {
              abstract_utils.get_atomic_python_constant(name):
                  value
              for name, value in possible_pairs
          }
        except abstract_utils.ConversionError as e:
          log.debug("Failed to unwrap field names for enum:\n  %s", e)
          return node, self.vm.new_unsolvable(node)

    cls_dict = abstract.Dict(self.vm)
    cls_dict.update(node, fields)

    metaclass = self.vm.loaded_overlays["enum"].members["EnumMeta"]

    return self.vm.make_class(
        node=node,
        name_var=cls_name_var,
        bases=[self.to_variable(node)],
        class_dict_var=cls_dict.to_variable(node),
        cls_var=metaclass,
        class_type=EnumInstance)


class IntEnumBuilder(EnumBuilder):
  """Overlays enum.IntEnum using EnumBuilder."""

  def __init__(self, vm):
    super().__init__(vm, name="IntEnum")


class EnumInstance(abstract.InterpreterClass):
  """A wrapper for classes that subclass enum.Enum."""

  def __init__(self, name, bases, members, cls, vm):
    super().__init__(name, bases, members, cls, vm)
    # This is set by EnumMetaInit.setup_interpreterclass.
    self.member_type = None

  def instantiate(self, node, container=None):
    # Instantiate creates a canonical enum member. This intended for when no
    # particular enum member is needed, e.g. during analysis. Real members have
    # these fields set during class creation.
    # TODO(tsudol): Use the types of other members to set `value`.
    del container
    instance = abstract.Instance(self, self.vm)
    instance.members["name"] = self.vm.convert.build_string(node, "")
    if self.member_type:
      value = self.member_type.instantiate(node)
    else:
      # instantiate() should never be called before setup_interpreterclass sets
      # self.member_type, because pytype will complain about recursive types.
      # But there's no reason not to make sure this function is safe.
      value = self.vm.new_unsolvable(node)
    instance.members["value"] = value
    return instance.to_variable(node)

  def is_empty_enum(self):
    for member in self.members.values():
      for b in member.data:
        if b.cls == self:
          return False
    return True


class EnumCmpEQ(abstract.SimpleFunction):
  """Implements the functionality of __eq__ for an enum."""
  # b/195136939: Enum equality checks could be made more exact/precise by
  # comparing the members' names. However, this causes issues when enums are
  # used in an if statement; see the bug for examples.

  def __init__(self, vm):
    super().__init__(
        name="__eq__",
        param_names=("self", "other"),
        varargs_name=None,
        kwonly_params=(),
        kwargs_name=None,
        defaults={},
        annotations={
            "return": vm.convert.bool_type,
        },
        vm=vm)

  def call(self, node, unused_f, args, alias_map=None):
    _, argmap = self.match_and_map_args(node, args, alias_map)
    this_var = argmap["self"]
    other_var = argmap["other"]
    # This is called by vm._call_binop_on_bindings, so both should have
    # exactly 1 possibility.
    try:
      this = abstract_utils.get_atomic_value(this_var)
      other = abstract_utils.get_atomic_value(other_var)
    except abstract_utils.ConversionError:
      return node, self.vm.convert.build_bool(node)
    return node, self.vm.convert.build_bool(node, this.cls == other.cls)


class EnumMeta(abstract.PyTDClass):
  """Wrapper for enum.EnumMeta.

  EnumMeta is essentially a container for the functions that drive a lot of the
  enum behavior: EnumMetaInit for modifying enum classes, for example.
  """

  def __init__(self, vm):
    enum_ast = vm.loaded_overlays["enum"].ast
    pytd_cls = enum_ast.Lookup("enum.EnumMeta")
    super().__init__("EnumMeta", pytd_cls, vm)
    init = EnumMetaInit(vm)
    self._member_map["__init__"] = init
    self.members["__init__"] = init.to_variable(vm.root_node)
    getitem = EnumMetaGetItem(vm)
    self._member_map["__getitem__"] = getitem
    self.members["__getitem__"] = getitem.to_variable(vm.root_node)


class EnumMetaInit(abstract.SimpleFunction):
  """Implements the functionality of EnumMeta.__init__.

  Overlaying this function is necessary in order to hook into pytype's metaclass
  handling and set up the Enum classes correctly.
  """

  def __init__(self, vm):
    super().__init__(
        name="__init__",
        param_names=("cls", "name", "bases", "namespace"),
        varargs_name=None,
        kwonly_params=(),
        kwargs_name=None,
        defaults={},
        annotations={},
        vm=vm)
    self._str_pytd = vm.lookup_builtin("builtins.str")

  def _get_class_locals(self, node, cls_name, cls_dict):
    # First, check if get_class_locals works for this class.
    if cls_name in self.vm.local_ops:
      ret = classgen.get_class_locals(
          cls_name, False, classgen.Ordering.LAST_ASSIGN, self.vm).items()
      return ret

    # If it doesn't work, then it's likely this class was created using the
    # functional API. Grab members from the cls_dict instead.
    ret = {name: abstract_utils.Local(node, None, None, value, self.vm)
           for name, value in cls_dict.items()}
    return ret.items()

  def _make_new(self, node, member_type, cls):
    # Note that setup_interpreterclass and setup_pytdclass both set member_type
    # to `unsolvable` if the enum has no members. Technically, `__new__` should
    # not accept any arguments, because it will always fail if the enum has no
    # members. But `unsolvable` is much simpler to implement and use.
    return overlay_utils.make_method(
        vm=self.vm,
        node=node,
        name="__new__",
        params=[
            overlay_utils.Param("value",
                                abstract.Union([member_type, cls], self.vm))
        ],
        return_type=cls)

  def _get_base_type(self, bases):
    # Enums may have a data class as one of their bases: class F(str, Enum) will
    # make all of F's members strings, even if they're assigned a value of
    # a different type.
    # The enum library searches through cls's bases' MRO to find all possible
    # base types. For simplicity, we just grab the second-to-last base.
    if len(bases) > 1:
      base_type_var = bases[-2]
      return abstract_utils.get_atomic_value(base_type_var, default=None)
    elif bases and len(bases[0].data) == 1:
      base_type_cls = abstract_utils.get_atomic_value(bases[0])
      if isinstance(base_type_cls, EnumInstance):
        # Enums with no members and no explicit base type have `unsolvable` as
        # their member type. Their subclasses use the default base type, int.
        # Some enums may have members with actually unsolvable member types, so
        # check if the enum is empty.
        if (base_type_cls.member_type == self.vm.convert.unsolvable and
            base_type_cls.is_empty_enum()):
          return None
        else:
          return base_type_cls.member_type
      elif base_type_cls.is_enum:
        return self._get_base_type(base_type_cls.bases())
    return None

  def _get_member_new(self, node, cls, base_type):
    # Get the __new__ that's used to create enum members.
    # If the enum defines its own __new__, use that.
    if "__new__" in cls:
      return cls.get_own_new(node, cls.to_binding(node))
    # The first fallback is the base type, e.g. str in `class M(str, Enum)`.
    if base_type and "__new__" in base_type:
      return base_type.get_own_new(node, base_type.to_binding(node))
    # The last fallback is the base enum type, as long as it isn't Enum.
    enum_base = abstract_utils.get_atomic_value(cls.bases()[-1])
    # enum_base.__new__ is saved as __new_member__, if it has a custom __new__.
    if enum_base.full_name != "enum.Enum" and "__new_member__" in enum_base:
      node, new = self.vm.attribute_handler.get_attribute(
          node, enum_base, "__new_member__")
      new = abstract_utils.get_atomic_value(new)
      return node, new.underlying.to_variable(node)
    return node, None

  def _is_orig_auto(self, orig):
    try:
      data = abstract_utils.get_atomic_value(orig)
    except abstract_utils.ConversionError as e:
      log.info("Failed to extract atomic enum value for auto() check: %s", e)
      return False
    return data.isinstance_Instance() and data.cls.full_name == "enum.auto"

  def _call_generate_next_value(self, node, cls, name):
    node, method = self.vm.attribute_handler.get_attribute(
        node, cls, "_generate_next_value_", cls.to_binding(node))
    # It's possible we'll get a unsolvable (due to __getattr__, say) for method.
    # We treat that as if the method is undefined instead.
    if method and all(abstract_utils.is_callable(m) for m in method.data):
      args = function.Args(posargs=(
          self.vm.convert.build_string(node, name),
          self.vm.convert.build_int(node),
          self.vm.convert.build_int(node),
          self.vm.convert.build_list(node, [])))
      return self.vm.call_function(node, method, args)
    else:
      return node, self.vm.convert.build_int(node)

  def _wrap_value(self, node, value, base_type):
    # Process an enum member's value for use as an argument. Returns a tuple
    # that can be used for Args.posargs.
    # If value is not a tuple already, make it one.
    # Then, if the base type is tuple, wrap it again.
    arg = abstract_utils.maybe_extract_tuple(value)
    if base_type.full_name == "builtins.tuple":
      arg = (self.vm.convert.build_tuple(node, arg),)
    return arg

  def _mark_dynamic_enum(self, cls):
    # Checks if the enum should be marked as having dynamic attributes.
    # Of course, if it's already marked dynamic, don't accidentally unmark it.
    if cls.maybe_missing_members:
      return
    # The most typical use of custom subclasses of EnumMeta is to add more
    # members to the enum, or to (for example) make attribute access
    # case-insensitive. Treat such enums as having dynamic attributes.
    if cls.cls and cls.cls.full_name != "enum.EnumMeta":
      cls.maybe_missing_members = True
      return
    for base_var in cls.bases():
      for base in base_var.data:
        if not base.is_enum:
          continue
        # Interpreter classes don't have "maybe_missing_members" set even if
        # they have _HAS_DYNAMIC_ATTRIBUTES. But for enums, those markers should
        # apply to the whole class.
        if ((base.cls and base.cls.full_name != "enum.EnumMeta") or
            base.maybe_missing_members or base.has_dynamic_attributes()):
          cls.maybe_missing_members = True
          return

  def _setup_interpreterclass(self, node, cls):
    member_types = []
    base_type = self._get_base_type(cls.bases())
    for name, local in self._get_class_locals(node, cls.name, cls.members):
      if name in abstract_utils.DYNAMIC_ATTRIBUTE_MARKERS:
        continue
      assert local.orig, ("A local with no assigned value was passed to the "
                          "enum overlay.")
      value = local.orig
      if self._is_orig_auto(value):
        node, value = self._call_generate_next_value(node, cls, name)
      # Enum members are created by calling __new__ (of either the base type or
      # the first enum in MRO that defines its own __new__, or else object if
      # neither of those applies) and then calling __init__ on the member.
      node, enum_new = self._get_member_new(node, cls, base_type)
      if enum_new:
        new_args = function.Args(
            posargs=((cls.to_variable(node),) +
                     abstract_utils.maybe_extract_tuple(value)))
        node, member_var = self.vm.call_function(
            node, enum_new, new_args, fallback_to_unsolvable=False)
        member = abstract_utils.get_atomic_value(member_var)
      else:
        # Build instances directly, because you can't call instantiate() when
        # creating the class -- pytype complains about recursive types.
        member = abstract.Instance(cls, self.vm)
        member_var = member.to_variable(node)
      if "_value_" not in member.members:
        if base_type:
          args = function.Args(posargs=self._wrap_value(node, value, base_type))
          node, value = base_type.call(node, base_type.to_binding(node), args)
        member.members["_value_"] = value
      if "__init__" in cls:
        init_args = function.Args(
            posargs=(member_var,) + abstract_utils.maybe_extract_tuple(value))
        node = cls.call_init(node, cls.to_binding(node), init_args)
      member.members["value"] = member.members["_value_"]
      member.members["name"] = self.vm.convert.build_string(node, name)
      cls.members[name] = member.to_variable(node)
      member_types.extend(value.data)
    if base_type:
      member_type = base_type
    elif member_types:
      member_type = self.vm.convert.merge_classes(member_types)
    else:
      member_type = self.vm.convert.unsolvable
    cls.member_type = member_type
    # If cls has a __new__, save it for later. (See _get_member_new above.)
    # It needs to be marked as a classmethod, or else pytype will try to
    # pass an instance of cls instead of cls when analyzing it.
    if "__new__" in cls:
      saved_new = cls.members["__new__"]
      if not any(x.isinstance_ClassMethodInstance() for x in saved_new.data):
        args = function.Args(posargs=(saved_new,))
        node, saved_new = self.vm.load_special_builtin("classmethod").call(
            node, None, args)
      cls.members["__new_member__"] = saved_new
    self._mark_dynamic_enum(cls)
    cls.members["__new__"] = self._make_new(node, member_type, cls)
    cls.members["__eq__"] = EnumCmpEQ(self.vm).to_variable(node)
    # _generate_next_value_ is used as a static method of the enum, not a class
    # method. We need to rebind it here to make pytype analyze it correctly.
    # However, we skip this if it's already a staticmethod.
    if "_generate_next_value_" in cls.members:
      gnv = cls.members["_generate_next_value_"]
      if not any(x.isinstance_StaticMethodInstance() for x in gnv.data):
        args = function.Args(posargs=(gnv,))
        node, new_gnv = self.vm.load_special_builtin("staticmethod").call(
            node, None, args)
        cls.members["_generate_next_value_"] = new_gnv
    return node

  def _setup_pytdclass(self, node, cls):
    # Only constants need to be transformed. We assume that enums in type
    # stubs are fully realized, i.e. there are no auto() calls and the members
    # already have values of the base type.
    # Instance attributes are stored as properties, which pytype marks using
    # typing.Annotated(<base_type>, 'property').
    # TODO(tsudol): Ensure only valid enum members are transformed.
    instance_attrs = {}
    possible_members = {}
    for pytd_val in cls.pytd_cls.constants:
      if pytd_val.name in abstract_utils.DYNAMIC_ATTRIBUTE_MARKERS:
        continue
      assert isinstance(pytd_val, pytd.Constant)
      if isinstance(pytd_val.type, pytd.Annotated):
        if "'property'" in pytd_val.type.annotations:
          instance_attrs[pytd_val.name] = pytd_val.Replace(
              type=pytd_val.type.base_type)
          # Properties must be deleted from the class's member map, otherwise
          # pytype will not complain when you try to access them on the class.
          del cls._member_map[pytd_val.name]  # pylint: disable=protected-access
          # Note that we can't remove the entry from cls.members, because
          # datatypes.MonitorDict does not support __delitem__. This shouldn't
          # be an issue, because cls shouldn't have had any reason to load
          # members by this point.
        else:
          possible_members[pytd_val.name] = pytd_val.Replace(
              type=pytd_val.type.base_type)
      else:
        possible_members[pytd_val.name] = pytd_val

    member_types = []
    for name, pytd_val in possible_members.items():
      # Build instances directly, because you can't call instantiate() when
      # creating the class -- pytype complains about recursive types.
      member = abstract.Instance(cls, self.vm)
      member.members["name"] = self.vm.convert.constant_to_var(
          pyval=pytd.Constant(name="name", type=self._str_pytd),
          node=node)
      # Some type stubs may use the class type for enum member values, instead
      # of the actual value type. Detect that and use Any.
      if pytd_val.type.name == cls.pytd_cls.name:
        value_type = pytd.AnythingType()
      else:
        value_type = pytd_val.type
      member.members["value"] = self.vm.convert.constant_to_var(
          pyval=pytd.Constant(name="value", type=value_type),
          node=node)
      member.members["_value_"] = member.members["value"]
      for attr_name, attr_val in instance_attrs.items():
        member.members[attr_name] = self.vm.convert.constant_to_var(
            pyval=attr_val, node=node)
      cls._member_map[name] = member  # pylint: disable=protected-access
      cls.members[name] = member.to_variable(node)
      member_types.append(value_type)
    # Because we overwrite __new__, we need to mark dynamic enums here.
    # Of course, this can be moved later once custom __init__ is supported.
    self._mark_dynamic_enum(cls)
    if not member_types:
      member_types.append(pytd.AnythingType())
    member_type = self.vm.convert.constant_to_value(
        pytd_utils.JoinTypes(member_types))
    cls.members["__new__"] = self._make_new(node, member_type, cls)
    cls.members["__eq__"] = EnumCmpEQ(self.vm).to_variable(node)
    return node

  def call(self, node, func, args, alias_map=None):
    # Use super.call to check args and get a return value.
    node, ret = super().call(node, func, args, alias_map)
    argmap = self._map_args(node, args)

    # Args: cls, name, bases, namespace_dict.
    # cls is the EnumInstance created by EnumBuilder.make_class, or an
    # abstract.PyTDClass created by convert.py.
    cls_var = argmap["cls"]
    cls, = cls_var.data

    # This function will get called for every class that has enum.EnumMeta as
    # its metaclass, including enum.Enum and other enum module members.
    # We don't have anything to do for those, so return early.
    if cls.isinstance_PyTDClass() and cls.full_name.startswith("enum."):
      return node, ret

    if cls.isinstance_InterpreterClass():
      node = self._setup_interpreterclass(node, cls)
    elif cls.isinstance_PyTDClass():
      node = self._setup_pytdclass(node, cls)
    else:
      raise ValueError(
          f"Expected an InterpreterClass or PyTDClass, but got {type(cls)}")

    return node, ret


class EnumMetaGetItem(abstract.SimpleFunction):
  """Implements the functionality of __getitem__ for enums."""

  def __init__(self, vm):
    super().__init__(
        name="__getitem__",
        param_names=("cls", "name"),
        varargs_name=None,
        kwonly_params=(),
        kwargs_name=None,
        defaults={},
        annotations={"name": vm.convert.str_type},
        vm=vm)

  def _get_member_by_name(self, enum, name):
    if isinstance(enum, EnumInstance):
      return enum.members.get(name)
    else:
      assert isinstance(enum, abstract.PyTDClass)
      if name in enum:
        enum.load_lazy_attribute(name)
        return enum.members[name]

  def call(self, node, _, args, alias_map=None):
    _, argmap = self.match_and_map_args(node, args, alias_map)
    cls_var = argmap["cls"]
    name_var = argmap["name"]
    try:
      cls = abstract_utils.get_atomic_value(cls_var)
    except abstract_utils.ConversionError:
      return node, self.vm.new_unsolvable(node)
    # We may have been given an instance of the class, such as if pytype is
    # analyzing this method due to a super() call in a subclass.
    if cls.isinstance_Instance():
      cls = cls.cls
    # If we can't get a concrete name, treat it like it matches and return a
    # canonical enum member.
    try:
      name = abstract_utils.get_atomic_python_constant(name_var, str)
    except abstract_utils.ConversionError:
      return node, cls.instantiate(node)
    inst = self._get_member_by_name(cls, name)
    if inst:
      return node, inst
    else:
      self.vm.errorlog.attribute_error(
          self.vm.frames, cls_var.bindings[0], name)
      return node, self.vm.new_unsolvable(node)
