class_docstr: A torch.Tensor input or an intermediate value in the FX graph
class_docstr_tok: ["A","torch.Tensor","input","or","an","intermediate","value","in","the","FX","graph"]
code: def call_method(
      self,
      tx,
      name,
      args: "List[VariableTracker]",
      kwargs: "Dict[str, VariableTracker]",
  ) -> "VariableTracker":
      if tx.strict_checks_enabled:
          if name in self._strict_mode_banned_ops():
              unimplemented(
                  f"Illegal method invocation {name} in strict mode"
              )
      from . import (
          ConstantVariable,
          TorchInGraphFunctionVariable,
          TupleVariable,
      )
      from .builder import wrap_fx_proxy
  
      kwargs = dict(kwargs)
  
      if name in ("stride", "size"):
          dim_var = None
          if len(args) == 1:
              dim_var = args[0]
          elif "dim" in kwargs:
              dim_var = kwargs["dim"]
          else:
              assert (
                  not args and not kwargs
              ), f"Tensor.{name}() unhandled args/kwargs"
  
          dim = guard_if_dyn(dim_var)
  
          def make_const_size_variable(x, **options):
              return SizeVariable(
                  [ConstantVariable.create(y, **options) for y in x],
                  **options,
              )
  
          RetVariable = (
              make_const_size_variable
              if name == "size"
              else ConstantVariable.create
          )
  
          # Technically, this should not be necessary, but I'm including it
          # for enhanced BC, in case example_value is sometimes not set
          # (it really should always be set though!)
          if (r := getattr(self, name)) is not None:
              if dim is None:
                  return RetVariable(r)
              else:
                  return ConstantVariable.create(r[dim])
  
          # It might still be constant!  Consult the fake tensor and see
          if (
              fake := self.proxy.node.meta.get("example_value")
          ) is not None:
              if dim is None:
                  fake_r = getattr(fake, name)()
                  if not has_free_symbols(fake_r):
                      # int conversion for safety, in case a SymInt refined
                      # to constant
                      return RetVariable(tuple(int(r) for r in fake_r))
              else:
                  fake_r = getattr(fake, name)(dim)
                  if not has_free_symbols(fake_r):
                      return ConstantVariable.create(int(fake_r))
  
          # Oops, it's not constant.  Do the dynamic shapes path.
          return wrap_fx_proxy(
              tx,
              tx.output.create_proxy(
                  "call_method",
                  name,
                  *proxy_args_kwargs([self] + list(args), kwargs),
              ),
          )
  
      elif name in ("numel", "nelement"):
          if self.size is not None:
              return ConstantVariable.create(product(self.size))
  
          # It might still be constant!  Consult the fake tensor and see
          if (
              fake := self.proxy.node.meta.get("example_value")
          ) is not None:
              fake_r = fake.numel()
              if not has_free_symbols(fake_r):
                  return ConstantVariable.create(int(fake_r))
  
          assert not kwargs, f"Tensor.{name}() unhandled kwargs"
  
          # Oops, it's not constant.  Do the dynamic shapes path.
          return wrap_fx_proxy(
              tx,
              tx.output.create_proxy(
                  "call_method",
                  "numel",
                  *proxy_args_kwargs([self] + list(args), kwargs),
              ),
          )
  
      elif name in ("ndimension", "dim") and self.ndim is not None:
          constant_result = ConstantVariable.create(self.ndim)
      elif name == "is_floating_point" and self.dtype is not None:
          constant_result = ConstantVariable.create(
              self.dtype.is_floating_point
          )
      elif name == "is_contiguous":
          memory_format = (
              kwargs.pop("memory_format").as_python_constant()
              if "memory_format" in kwargs
              else torch.contiguous_format
          )
          if self.is_contiguous is not None:
              constant_result = ConstantVariable.create(
                  memory_format in self.is_contiguous
              )
          elif (
              fake := self.proxy.node.meta.get("example_value")
          ) is not None:
              constant_result = ConstantVariable.create(
                  fake.is_contiguous(memory_format=memory_format)
              )
          else:
              constant_result = None
      elif (
          name == "type"
          and self.dtype is not None
          and len(args) == 0
          and isinstance(self.device, torch.device)
      ):
          tensortype = next(
              k
              for k, v in tensortype_to_dtype.items()
              if self.dtype in v
          )
          if self.device.type == "cuda":
              constant_result = ConstantVariable.create(
                  f"torch.cuda.{tensortype.__name__}"
              )
          else:
              constant_result = ConstantVariable.create(
                  f"torch.{tensortype.__name__}"
              )
      elif (
          name == "type"
          and len(args) == 1
          and fqn(type(args[0].as_python_constant()))
          == "torch.tensortype"
      ):
          # torch.FloatTensor, etc. are all of type "torch.tensortype".
          # torch.fx's tracer fails on these types, because it doesn't support arguments of torch.tensortype type.
          # So, we pass it in as a string (which is also supported, see above implementation for .type() with 0 args)
          tensor_type = args[0].as_python_constant()
          tensor_type_const = ConstantVariable.create(fqn(tensor_type))
          return wrap_fx_proxy(
              tx,
              tx.output.create_proxy(
                  "call_method",
                  name,
                  *proxy_args_kwargs([self, tensor_type_const], kwargs),
              ),
          )
      elif (
          name == "as_subclass"
          and len(args) == 1
          and isinstance(args[0], TensorSubclassVariable)
      ):
          from .builder import VariableBuilder
          from .torch_function import TensorWithTFOverrideVariable
  
          # [Note: __torch_function__] coerce this tensor variable into a TensorWithTFOverrideVariable
          # in eager, this is just a type change. This isn't sound if a __torch_function__ tensor subclass
          # defines a constructor, but if only a __torch_function__ impl is defined, this is okay to call.
          # It is up to the user whether this is correct behavior or not.
          py_cls = args[0].as_python_constant()
          torch_fn = VariableBuilder(
              tx,
              AttrSource(
                  AttrSource(args[0].source, "__torch_function__"),
                  "__func__",
              ),
          )(py_cls.__torch_function__.__func__)
  
          return TensorWithTFOverrideVariable.from_tensor_var(
              tx, self, py_cls, torch_fn
          )
      elif name == "get_device" and isinstance(
          self.device, torch.device
      ):
          index = self.device.index if self.device.type != "cpu" else -1
          constant_result = ConstantVariable.create(index)
      else:
          constant_result = None
  
      if constant_result:
          assert not kwargs, f"Tensor.{name}() unhandled kwargs"
          # TODO: I think this branch is dead
          if len(args) == 1:
              return constant_result.getitem_const(args[0])
          elif args:
              return TupleVariable(
                  [constant_result.getitem_const(a) for a in args]
              )
          return constant_result
      elif name == "numpy":
          if not config.trace_numpy:
              unimplemented(
                  "Tensor.numpy(). config.trace_numpy is False"
              )
          if not np:
              unimplemented("Tensor.numpy(). NumPy is not available")
          assert not args, "Tensor.numpy() doesn't take args."
          if self.layout != torch.strided:
              raise TypeError(
                  f"can't convert {self.layout} layout tensor to numpy. Use Tensor.dense() first"
              )
          # We don't check that the tensor is on CPU when force is False, as this
          # allows us to execute NumPy code on CUDA. Same for requires_grad=True
          force = (
              "force" in kwargs and kwargs["force"].as_python_constant()
          )
          if force:
              # If the user set force=True we try to preserve the semantics (no gradients, move to CPU...)
              t = self.call_method(tx, "detach", [], {})
              proxy = tx.output.create_proxy(
                  "call_method", "cpu", (t.as_proxy(),), {}
              )
          else:
              # Hacky way to create a view of self that will be marked as NumpyNdarrayVariable
              proxy = tx.output.create_proxy(
                  "call_method",
                  "view_as",
                  *proxy_args_kwargs([self, self], {}),
              )
          return NumpyNdarrayVariable.create(tx, proxy)
      elif name == "tolist":
          from .builder import SourcelessBuilder
  
          def tolist(tensor, sub_proxy):
              def wrap(i, sub_proxy):
                  return SymNodeVariable.create(
                      tx,
                      sub_proxy.item(),
                      sym_num=tx.output.shape_env.create_unbacked_symint(),
                  )
  
              if tensor.dtype not in [
                  torch.int8,
                  torch.int16,
                  torch.int32,
                  torch.int64,
              ]:
                  unimplemented(
                      "Input tensor for tolist must be an integer tensor"
                  )
  
              if tensor.dim() == 0:
                  return wrap(tensor, sub_proxy)
  
              if tensor.dim() == 1:
                  return [
                      wrap(val, sub_proxy[i])
                      for i, val in enumerate(tensor)
                  ]
  
              return [
                  tolist(sub_tensor, sub_proxy=sub_proxy[i])
                  for i, sub_tensor in enumerate(tensor)
              ]
  
          tensor = self.as_proxy().node.meta["example_value"]
          out = tolist(tensor, self.as_proxy())
          return SourcelessBuilder()(tx, out)
      elif name in ("backward", "data_ptr"):
          unimplemented(f"Tensor.{name}")
      elif name == "item" and not config.capture_scalar_outputs:
          unimplemented(f"Tensor.{name}")
      elif name == "__len__":
          return self.call_method(
              tx, "size", [ConstantVariable.create(0)], {}
          )
      elif name == "__setitem__":
          key, value = args
  
          def has_bool_key(v):
              if isinstance(v, TensorVariable):
                  return v.dtype in (torch.bool, torch.int8)
              elif isinstance(v, TupleVariable):
                  return any(has_bool_key(item) for item in v.items)
              else:
                  return False
  
          if (
              has_bool_key(key)
              and isinstance(value, TensorVariable)
              and value.requires_grad
          ):
              unimplemented(
                  "boolean masking setitem backwards, see https://github.com/pytorch/pytorch/issues/114123"
              )
          tx.output.create_proxy(
              "call_function",
              operator.setitem,
              *proxy_args_kwargs([self] + list(args), kwargs),
          )
          return ConstantVariable.create(None)
      elif name in ("resize_", "resize_as_"):
          # Handling resizing in its full generality is difficult.
          unimplemented(f"Tensor.{name}")
      elif name == "set_" and len(args) > 1:
          # torch.Tensor.set_() has several overloads.
          # aten::set_.source_Tensor(Tensor) gets special handling
          # in AOTAutograd and functionalization, because it is the most common
          # overload and is used by FSDP.
          # graph-breaking on aten::set_source_Tensor_storage_offset for now,
          # unless we find that we need to make it work.
          unimplemented("Tensor.set_.source_Tensor_storage_offset")
      elif (
          name == "add_"
          and len(args) == 1
          and len(kwargs) == 1
          and "alpha" in kwargs
      ):
          result = TorchInGraphFunctionVariable(
              torch.mul
          ).call_function(tx, args + [kwargs["alpha"]], {})
          return self.call_method(tx, "add_", [result], {})
      elif (
          name == "addcdiv_"
          and len(args) == 2
          and len(kwargs) == 1
          and "value" in kwargs
      ):
          result = TorchInGraphFunctionVariable(
              torch.div
          ).call_function(tx, args, {})
          result = TorchInGraphFunctionVariable(
              torch.mul
          ).call_function(tx, [result, kwargs["value"]], {})
          return self.call_method(tx, "add_", [result], {})
      elif name == "__contains__":
          # Rewrite __contains__ here so that downstream passes can trace through
          # without dealing with unbacked symbool. Roughly the code we translate is:
          # def __contains__(self, x):
          #     return (x == self).any().item()
          result = TorchInGraphFunctionVariable(torch.eq).call_function(
              tx, [self, args[0]], {}
          )
          result = TorchInGraphFunctionVariable(
              torch.any
          ).call_function(tx, [result], {})
          return result.call_method(tx, "item", [], {})
      elif name == "redistribute":
          # rewrite non-primitive args/kwargs to be included in the on-the-fly prim function
          # and rewrite args to have only proxyable args, then insert call_function
          args_as_value = [x.as_python_constant() for x in args]
          kwargs_as_value = {
              k: v.as_python_constant() for k, v in kwargs.items()
          }
  
          def redistribute_fn_with_prim_types(x):
              return x.redistribute(*args_as_value, **kwargs_as_value)
  
          # attach the same function name for better debugging
          redistribute_fn_with_prim_types.__name__ = f"prim_{name}"
  
          return wrap_fx_proxy(
              tx=tx,
              proxy=tx.output.create_proxy(
                  "call_function",
                  redistribute_fn_with_prim_types,
                  *proxy_args_kwargs([self], {}),
              ),
          )
      elif name in {
          "register_hook",
          "register_post_accumulate_grad_hook",
      }:
          # Note - do not arbitrarily add hooks here - make sure they match the same contract
          # see [On tensor.register_hook]
          assert len(args) == 1
          fn_var = args[0]
          if not isinstance(
              fn_var,
              (
                  variables.functions.FunctoolsPartialVariable,
                  variables.UserFunctionVariable,
                  variables.TorchInGraphFunctionVariable,
                  variables.NNModuleVariable,
              ),
          ):
              unimplemented(
                  "Unexpected callable type passed to register_hook"
              )
  
          if isinstance(fn_var, variables.NestedUserFunctionVariable):
              # NestedUserFunctionVariable don't carry their fn, but reconstruction builds it
              # This should not be onerous to support when needed.
              unimplemented("NYI - lambda variables as hooks")
          elif isinstance(
              fn_var, variables.functions.FunctoolsPartialVariable
          ):
              fn = fn_var.as_python_constant()
          else:
              fn = fn_var.fn
  
          handle_variable = (
              variables.user_defined.RemovableHandleVariable(
                  mutable_local=variables.base.MutableLocal(),
              )
          )
  
          if not self.source:
              # Intermediary
              src = fn_var.source
              if (
                  not src
                  and isinstance(
                      fn_var,
                      variables.functions.FunctoolsPartialVariable,
                  )
                  and fn_var.func.source
              ):
                  src = fn_var.func.source
  
              if not src:
                  unimplemented("No source for register_hook target fn")
  
              tx.output.guards.add(
                  src.make_guard(GuardBuilder.ID_MATCH)
              )
  
              if not compiled_autograd.compiled_autograd_enabled:
                  # TODO(voz):
                  # We can relax this by speculating the callable and ensuring that it doesn't modify arbitrary
                  # python state.
                  # We *Must* be in compiled_autograd here because backward hooks can contain anything, and it is unsafe to run
                  # them in a compiled bwd without re-entering dynamo as compiled_autograd does.
                  #
                  # Discussion point 1 - Should we bypass this if nopython/fullgraph = True?
                  #   No. Because this was going to be a graph break anyway - this check does not
                  # introduce new graph breaks where there were none.
                  #
                  # Discussion point 2 - Should we defer this check to backwards?
                  #   No. Because compiled autograd is not yet ready for prime time. As such, if we defer, a user
                  # would have no recourse - their forward traces just fine, but will fail at backwards unless
                  # compiled_autograd is enabled. If compiled_autograd fails (there are a lot of failures today)
                  # then they have nothing they can do except disable compile.
                  unimplemented(
                      "Compilation of intermediate hooks requires compiled autograd"
                  )
  
              # This wraps our user provided fn with a function that intercedes and
              # uses our `invoke` higher order op to record a hook invocation in bwd graph.
              fn = functools.partial(trace_wrapped, fn=fn)
  
              def _register_hook_trampoline(tensor):
                  hook_callable = getattr(tensor, name)
                  hook_callable(fn)
                  return tensor
  
              return wrap_fx_proxy(
                  tx,
                  tx.output.create_proxy(
                      "call_function",
                      _register_hook_trampoline,
                      (self.as_proxy(),),
                      {},
                  ),
              )
  
          tx.output.side_effects.register_hook(
              self, fn_var, handle_variable, name
          )
          return handle_variable
      elif name == "requires_grad_" and self.as_proxy().node.meta[
          "example_value"
      ].requires_grad != (args[0].value if len(args) > 0 else True):
          unimplemented("Tensor.requires_grad_")
  
      else:
          # Convert x.new(torch.Size) into x.new_empty(torch.Size),
          # as Tensor.new acts differently with a Size input versus a tuple input.
          if (
              name == "new"
              and len(args) == 1
              and isinstance(args[0], SizeVariable)
          ):
              name = "new_empty"
          return wrap_fx_proxy(
              tx,
              tx.output.create_proxy(
                  "call_method",
                  name,
                  *proxy_args_kwargs([self] + list(args), kwargs),
              ),
          )
  
imports: import functools
  import inspect
  import operator
  import types
  from typing import Dict, List
  import sympy
  import torch._numpy
  import torch.fx
  import torch.random
  from torch._dynamo import compiled_autograd
  from torch.fx.experimental.symbolic_shapes import guard_scalar, GuardOnDataDependentSymNode, has_free_symbols, is_symbolic, SymTypes
  from ..None import config, variables
  from .._trace_wrapped_higher_order_op import trace_wrapped
  from ..exc import unimplemented, UserError, UserErrorType
  from ..guards import GuardBuilder, install_guard
  from ..source import AttrSource
  from ..utils import fqn, get_custom_getattr, get_fake_value, get_real_value, guard_if_dyn, object_has_getattribute, product, proxy_args_kwargs, tensortype_to_dtype
  from .base import VariableTracker
  from .constant import ConstantVariable
  from .lists import SizeVariable
label_desc: Use image node_id 11 for calling the TensorVariable obj's underlying member method code with example usage: obj.call_method(tx, name, args, kwargs) and returns: wrap_fx_proxy, constant_result, SizeVariable, wrap_fx_proxy, constant_result, NumpyNdarrayVariable, RetVariable, ConstantVariable, ConstantVariable, TupleVariable, SourcelessBuilder, RetVariable, ConstantVariable, ConstantVariable, unknown, SymNodeVariable, wrap, unknown, self, wrap_fx_proxy, ConstantVariable, TensorWithTFOverrideVariable, unknown, any, False, self, self, result, wrap_fx_proxy, x, handle_variable, wrap_fx_proxy, wrap_fx_proxy, tensor