text: //     Copyright 2016, Kay Hayen, mailto:kay.hayen@gmail.com
  //
  //     Part of "Nuitka", an optimizing Python compiler that is compatible and
  //     integrates with CPython, but also works on its own.
  //
  //     Licensed under the Apache License, Version 2.0 (the "License");
  //     you may not use this file except in compliance with the License.
  //     You may obtain a copy of the License at
  //
  //        http://www.apache.org/licenses/LICENSE-2.0
  //
  //     Unless required by applicable law or agreed to in writing, software
  //     distributed under the License is distributed on an "AS IS" BASIS,
  //     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  //     See the License for the specific language governing permissions and
  //     limitations under the License.
  //
  
  #include "nuitka/prelude.h"
  
  #include "nuitka/compiled_method.h"
  
  #include "structmember.h"
  
  static PyObject *Nuitka_Method_get__doc__( struct Nuitka_MethodObject *method, void *closure )
  {
      return INCREASE_REFCOUNT( method->m_function->m_doc );
  }
  
  static PyGetSetDef Nuitka_Method_getsets[] =
  {
      { (char *)"__doc__", (getter)Nuitka_Method_get__doc__, NULL, NULL },
      { NULL }
  };
  
  #define OFF( x ) offsetof( struct Nuitka_MethodObject, x )
  
  static PyMemberDef Nuitka_Method_members[] =
  {
      { (char *)"im_class", T_OBJECT, OFF( m_class ), READONLY | RESTRICTED, (char *)"the class associated with a method"},
      { (char *)"im_func", T_OBJECT, OFF( m_function ), READONLY | RESTRICTED, (char *)"the function (or other callable) implementing a method" },
      { (char *)"__func__", T_OBJECT, OFF( m_function ), READONLY | RESTRICTED, (char *)"the function (or other callable) implementing a method" },
      { (char *)"im_self",  T_OBJECT, OFF( m_object ), READONLY | RESTRICTED, (char *)"the instance to which a method is bound; None for unbound method" },
      { (char *)"__self__", T_OBJECT, OFF( m_object ), READONLY | RESTRICTED, (char *)"the instance to which a method is bound; None for unbound method" },
      { NULL }
  };
  
  static PyObject *Nuitka_Method_reduce( struct Nuitka_MethodObject *method )
  {
      PyErr_Format(
          PyExc_TypeError,
          "Can't pickle instancemethod objects"
      );
  
      return NULL;
  }
  
  static PyObject *Nuitka_Method_reduce_ex( struct Nuitka_MethodObject *method, PyObject *args )
  {
      int proto;
  
      if ( !PyArg_ParseTuple(args, "|i:__reduce_ex__", &proto) )
      {
          return NULL;
      }
  
      PyErr_Format(
          PyExc_TypeError,
          "Can't pickle instancemethod objects"
      );
  
      return NULL;
  }
  
  static PyObject *Nuitka_Method_deepcopy( struct Nuitka_MethodObject *method, PyObject *memo )
  {
      assert( Nuitka_Method_Check( (PyObject *)method ));
  
      static PyObject *module_copy = NULL;
      static PyObject *deepcopy_function = NULL;
  
      if ( module_copy == NULL  )
      {
          module_copy = PyImport_ImportModule( "copy" );
          CHECK_OBJECT( module_copy );
  
          deepcopy_function = PyObject_GetAttrString( module_copy, "deepcopy" );
          CHECK_OBJECT( deepcopy_function );
      }
  
      PyObject *object = PyObject_CallFunctionObjArgs( deepcopy_function, method->m_object, memo, NULL );
  
      if (unlikely( object == NULL ))
      {
          return NULL;
      }
  
      return Nuitka_Method_New( method->m_function, object, method->m_class );
  }
  
  
  
  static PyMethodDef Nuitka_Method_methods[] =
  {
      { "__reduce__",    (PyCFunction)Nuitka_Method_reduce,    METH_NOARGS,  NULL },
      { "__reduce_ex__", (PyCFunction)Nuitka_Method_reduce_ex, METH_VARARGS, NULL },
      { "__deepcopy__",  (PyCFunction)Nuitka_Method_deepcopy,  METH_O,       NULL },
      { NULL }
  };
  
  extern PyObject *const_str_plain___name__;
  
  static char const *GET_CLASS_NAME( PyObject *klass )
  {
      if ( klass == NULL )
      {
          return "?";
      }
      else
      {
  #if PYTHON_VERSION < 300
          if ( PyClass_Check( klass ) )
          {
              return Nuitka_String_AsString( ((PyClassObject *)klass)->cl_name );
          }
  #endif
  
          if ( !PyType_Check( klass ) )
          {
              klass = (PyObject *)Py_TYPE( klass );
          }
  
          return ((PyTypeObject *)klass)->tp_name;
      }
  }
  
  static char const *GET_INSTANCE_CLASS_NAME( PyObject *instance )
  {
      // TODO: We have a constant for that already.
      PyObject *klass = PyObject_GetAttrString( instance, "__class__" );
  
      // Fallback to type as this cannot fail.
      if ( klass == NULL )
      {
          CLEAR_ERROR_OCCURRED();
          klass = INCREASE_REFCOUNT( (PyObject *)Py_TYPE( instance ) );
      }
  
      char const *result = GET_CLASS_NAME( klass );
  
      Py_DECREF( klass );
  
      return result;
  }
  
  static char const *GET_CALLABLE_DESC( PyObject *object )
  {
      if ( Nuitka_Function_Check( object ) || Nuitka_Generator_Check( object ) || PyMethod_Check( object ) || PyFunction_Check( object ) || PyCFunction_Check( object ) )
      {
          return "()";
      }
  #if PYTHON_VERSION < 300
      else if ( PyClass_Check( object ) )
      {
          return " constructor";
      }
      else if ( PyInstance_Check( object ))
      {
          return " instance";
      }
  #endif
      else
      {
          return " object";
      }
  }
  
  static char const *GET_CALLABLE_NAME( PyObject *object )
  {
      if ( Nuitka_Function_Check( object ) )
      {
          return Nuitka_String_AsString( Nuitka_Function_GetName( object ) );
      }
      else if ( Nuitka_Generator_Check( object ) )
      {
          return Nuitka_String_AsString( Nuitka_Generator_GetName( object ) );
      }
      else if ( PyMethod_Check( object ) )
      {
          return PyEval_GetFuncName( PyMethod_GET_FUNCTION( object ) );
      }
      else if ( PyFunction_Check( object ) )
      {
          return Nuitka_String_AsString( ((PyFunctionObject*)object)->func_name );
      }
  #if PYTHON_VERSION < 300
      else if ( PyInstance_Check( object ) )
      {
          return Nuitka_String_AsString( ((PyInstanceObject*)object)->in_class->cl_name );
      }
      else if ( PyClass_Check( object ) )
      {
          return Nuitka_String_AsString( ((PyClassObject*)object)->cl_name );
      }
  #endif
      else if ( PyCFunction_Check( object ) )
      {
          return ((PyCFunctionObject*)object)->m_ml->ml_name;
      }
      else
      {
          return Py_TYPE( object )->tp_name;
      }
  }
  
  static PyObject *Nuitka_Method_tp_call( struct Nuitka_MethodObject *method, PyObject *args, PyObject *kw )
  {
      Py_ssize_t arg_count = PyTuple_Size( args );
  
      if ( method->m_object == NULL )
      {
          if (unlikely( arg_count < 1 ))
          {
              PyErr_Format(
                  PyExc_TypeError,
                  "unbound compiled_method %s%s must be called with %s instance as first argument (got nothing instead)",
                  GET_CALLABLE_NAME( (PyObject *)method->m_function ),
                  GET_CALLABLE_DESC( (PyObject *)method->m_function ),
                  GET_CLASS_NAME( method->m_class )
              );
              return NULL;
          }
          else
          {
              PyObject *self = PyTuple_GET_ITEM( args, 0 );
              CHECK_OBJECT( self );
  
              int result = PyObject_IsInstance( self, method->m_class );
  
              if (unlikely( result < 0 ))
              {
                  return NULL;
              }
              else if (unlikely( result == 0 ))
              {
                  PyErr_Format(
                      PyExc_TypeError,
                      "unbound compiled_method %s%s must be called with %s instance as first argument (got %s instance instead)",
                      GET_CALLABLE_NAME( (PyObject *)method->m_function ),
                      GET_CALLABLE_DESC( (PyObject *)method->m_function ),
                      GET_CLASS_NAME( method->m_class ),
                      GET_INSTANCE_CLASS_NAME( (PyObject *)self )
                  );
  
                  return NULL;
              }
          }
  
          return Py_TYPE( method->m_function )->tp_call(
              (PyObject *)method->m_function, args, kw
          );
      }
      else
      {
          return Nuitka_CallMethodFunctionPosArgsKwArgs(
              method->m_function,
              method->m_object,
              &PyTuple_GET_ITEM( args, 0 ),
              arg_count,
              kw
          );
      }
  }
  
  
  static PyObject *Nuitka_Method_tp_descr_get( struct Nuitka_MethodObject *method, PyObject *object, PyObject *klass )
  {
      // Don't rebind already bound methods.
      if ( method->m_object != NULL )
      {
          return INCREASE_REFCOUNT( (PyObject *)method );
      }
  
      if ( method->m_class != NULL && klass != NULL )
      {
          // Quick subclass test, bound methods remain the same if the class is a sub class
          int result = PyObject_IsSubclass( klass, method->m_class );
  
          if (unlikely( result < 0 ))
          {
              return NULL;
          }
          else if ( result == 0 )
          {
              return INCREASE_REFCOUNT( (PyObject *)method );
          }
      }
  
      return Nuitka_Method_New( method->m_function, object, klass );
  }
  
  static PyObject *Nuitka_Method_tp_getattro( struct Nuitka_MethodObject *method, PyObject *name )
  {
      PyObject *descr = _PyType_Lookup( &Nuitka_Method_Type, name );
  
      if ( descr != NULL )
      {
          if (
  #if PYTHON_VERSION < 300
              PyType_HasFeature( Py_TYPE( descr ), Py_TPFLAGS_HAVE_CLASS ) &&
  #endif
              ( Py_TYPE( descr )->tp_descr_get != NULL )
             )
          {
              return Py_TYPE( descr )->tp_descr_get(
                  descr,
                  (PyObject *)method,
                  (PyObject *)Py_TYPE( method )
              );
          }
          else
          {
              return INCREASE_REFCOUNT( descr );
          }
      }
  
      return PyObject_GetAttr( (PyObject *)method->m_function, name );
  }
  
  
  static long Nuitka_Method_tp_traverse( struct Nuitka_MethodObject *method, visitproc visit, void *arg )
  {
      Py_VISIT( method->m_function );
      Py_VISIT( method->m_object );
      Py_VISIT( method->m_class );
  
      return 0;
  }
  
   // tp_repr slot, decide how a function shall be output
  static PyObject *Nuitka_Method_tp_repr( struct Nuitka_MethodObject *method )
  {
      if ( method->m_object == NULL )
      {
  #if PYTHON_VERSION < 300
          return PyString_FromFormat(
              "<unbound compiled_method %s.%s>",
              GET_CLASS_NAME( method->m_class ),
              Nuitka_String_AsString( method->m_function->m_name )
          );
  #else
          return PyUnicode_FromFormat(
              "<compiled_function %s at %p>",
              Nuitka_String_AsString( method->m_function->m_name ),
              method->m_function
          );
  #endif
      }
      else
      {
          // Note: CPython uses repr ob the object, although a comment despises
          // it, we do it for compatibility.
          PyObject *object_repr = PyObject_Repr( method->m_object );
  
          if ( object_repr == NULL )
          {
              return NULL;
          }
  #if PYTHON_VERSION < 300
          else if ( !PyString_Check( object_repr ) )
          {
              Py_DECREF( object_repr );
              return NULL;
          }
  #else
          else if ( !PyUnicode_Check( object_repr ) )
          {
              Py_DECREF( object_repr );
              return NULL;
          }
  #endif
  
  #if PYTHON_VERSION < 300
          PyObject *result = PyString_FromFormat(
              "<bound compiled_method %s.%s of %s>",
              GET_CLASS_NAME( method->m_class ),
              Nuitka_String_AsString( method->m_function->m_name ),
              Nuitka_String_AsString_Unchecked( object_repr )
          );
  #elif PYTHON_VERSION < 350
          PyObject *result = PyUnicode_FromFormat(
              "<bound compiled_method %s.%s of %s>",
              GET_CLASS_NAME( method->m_class ),
              Nuitka_String_AsString( method->m_function->m_name ),
              Nuitka_String_AsString_Unchecked( object_repr )
          );
  #else
          PyObject *result = PyUnicode_FromFormat(
              "<bound compiled_method %s of %s>",
              Nuitka_String_AsString( method->m_function->m_qualname ),
              Nuitka_String_AsString_Unchecked( object_repr )
          );
  #endif
  
          Py_DECREF( object_repr );
  
          return result;
      }
  }
  
  #if PYTHON_VERSION < 300
  static int Nuitka_Method_tp_compare( struct Nuitka_MethodObject *a, struct Nuitka_MethodObject *b )
  {
      if ( a->m_function->m_counter < b->m_function->m_counter )
      {
          return -1;
      }
      else if ( a->m_function->m_counter > b->m_function->m_counter )
      {
          return 1;
      }
      else if ( a->m_object == b->m_object )
      {
          return 0;
      }
      else if ( a->m_object == NULL )
      {
          return -1;
      }
      else if ( b->m_object == NULL )
      {
          return 1;
      }
      else
      {
          return PyObject_Compare( a->m_object, b->m_object );
      }
  }
  #endif
  
  static PyObject *Nuitka_Method_tp_richcompare( struct Nuitka_MethodObject *a, struct Nuitka_MethodObject *b, int op )
  {
      if ( op != Py_EQ && op != Py_NE )
      {
          return INCREASE_REFCOUNT( Py_NotImplemented );
      }
  
  
      if ( Nuitka_Method_Check( (PyObject *)a ) == false || Nuitka_Method_Check( (PyObject *)b ) == false )
      {
          return INCREASE_REFCOUNT( Py_NotImplemented );
      }
  
      bool result = a->m_function->m_counter == b->m_function->m_counter;
  
      // If the underlying function objects are the same, check the objects, which
      // may be NULL in case of unbound methods, which would be the same again.
      if ( result )
      {
          if ( a->m_object == NULL )
          {
              result = b->m_object == NULL;
          }
          else if ( b->m_object == NULL )
          {
              result = 0;
          }
          else
          {
              int res = PyObject_RichCompareBool(
                  a->m_object,
                  b->m_object,
                  Py_EQ
              );
  
              result = res != 0;
          }
      }
  
      if ( op == Py_EQ )
      {
          return INCREASE_REFCOUNT( BOOL_FROM( result ) );
      }
      else
      {
          return INCREASE_REFCOUNT( BOOL_FROM( !result ) );
      }
  }
  
  
  static long Nuitka_Method_tp_hash( struct Nuitka_MethodObject *method )
  {
      // Just give the hash of the method function, that ought to be good enough.
      return method->m_function->m_counter;
  }
  
  // Cache for method object, try to avoid malloc overhead.
  static struct Nuitka_MethodObject *method_cache_head = NULL;
  static int method_cache_size = 0;
  static const int max_method_cache_size = 4096;
  
  static void Nuitka_Method_tp_dealloc( struct Nuitka_MethodObject *method )
  {
  #ifndef __NUITKA_NO_ASSERT__
      // Save the current exception, if any, we must to not corrupt it.
      PyObject *save_exception_type, *save_exception_value;
      PyTracebackObject *save_exception_tb;
      FETCH_ERROR_OCCURRED( &save_exception_type, &save_exception_value, &save_exception_tb );
      RESTORE_ERROR_OCCURRED( save_exception_type, save_exception_value, save_exception_tb );
  #endif
  
      Nuitka_GC_UnTrack( method );
  
      if ( method->m_weakrefs != NULL )
      {
          PyObject_ClearWeakRefs( (PyObject *)method );
      }
  
      Py_XDECREF( method->m_object );
      Py_XDECREF( method->m_class );
  
      Py_DECREF( (PyObject *)method->m_function );
  
      if (likely( method_cache_size < max_method_cache_size ))
      {
          method->m_object = (PyObject *)method_cache_head;
          method_cache_head = method;
          method_cache_size += 1;
      }
      else {
          PyObject_GC_Del( method );
      }
  
  #ifndef __NUITKA_NO_ASSERT__
      PyThreadState *tstate = PyThreadState_GET();
  
      assert( tstate->curexc_type == save_exception_type );
      assert( tstate->curexc_value == save_exception_value );
      assert( (PyTracebackObject *)tstate->curexc_traceback == save_exception_tb );
  #endif
  }
  
  static PyObject *Nuitka_Method_tp_new( PyTypeObject* type, PyObject* args, PyObject *kw )
  {
      PyObject *func;
      PyObject *self;
      PyObject *klass = NULL;
  
      if ( !_PyArg_NoKeywords( "instancemethod", kw ) )
      {
          return NULL;
      }
      else if ( !PyArg_UnpackTuple( args, "compiled_method", 2, 3, &func, &self, &klass ) )
      {
          return NULL;
      }
      else if ( !PyCallable_Check( func ) )
      {
          PyErr_Format( PyExc_TypeError, "first argument must be callable" );
          return NULL;
      }
      else
      {
          if ( self == Py_None )
          {
              self = NULL;
          }
  
          if ( self == NULL && klass == NULL )
          {
              PyErr_Format( PyExc_TypeError, "unbound methods must have non-NULL im_class" );
              return NULL;
          }
      }
  
      assert( Nuitka_Function_Check( func ) );
  
      return Nuitka_Method_New( (struct Nuitka_FunctionObject *)func, self, klass );
  }
  
  PyTypeObject Nuitka_Method_Type =
  {
      PyVarObject_HEAD_INIT(NULL, 0)
      "compiled_method",
      sizeof(struct Nuitka_MethodObject),
      0,
      (destructor)Nuitka_Method_tp_dealloc,        // tp_dealloc
      0,                                           /* tp_print */
      0,                                           /* tp_getattr */
      0,                                           /* tp_setattr */
  #if PYTHON_VERSION < 300
      (cmpfunc)Nuitka_Method_tp_compare,           /* tp_compare */
  #else
      0,
  #endif
      (reprfunc)Nuitka_Method_tp_repr,             /* tp_repr */
      0,                                           /* tp_as_number */
      0,                                           /* tp_as_sequence */
      0,                                           /* tp_as_mapping */
      (hashfunc)Nuitka_Method_tp_hash,             /* tp_hash */
      (ternaryfunc)Nuitka_Method_tp_call,          /* tp_call */
      0,                                           /* tp_str */
      (getattrofunc)Nuitka_Method_tp_getattro,     /* tp_getattro */
      PyObject_GenericSetAttr,                     /* tp_setattro */
      0,                                           /* tp_as_buffer */
      Py_TPFLAGS_DEFAULT       |
  #if PYTHON_VERSION < 300
      Py_TPFLAGS_HAVE_WEAKREFS |
  #endif
      Py_TPFLAGS_HAVE_GC,                          /* tp_flags */
      0,                                           /* tp_doc */
      (traverseproc)Nuitka_Method_tp_traverse,     /* tp_traverse */
      0,                                           /* tp_clear */
      (richcmpfunc)Nuitka_Method_tp_richcompare,   /* tp_richcompare */
      offsetof(struct Nuitka_MethodObject, m_weakrefs), /* tp_weaklistoffset */
      0,                                           /* tp_iter */
      0,                                           /* tp_iternext */
      Nuitka_Method_methods,                       /* tp_methods */
      Nuitka_Method_members,                       /* tp_members */
      Nuitka_Method_getsets,                       /* tp_getset */
      0,                                           /* tp_base */
      0,                                           /* tp_dict */
      (descrgetfunc)Nuitka_Method_tp_descr_get,    /* tp_descr_get */
      0,                                           /* tp_descr_set */
      0,                                           /* tp_dictoffset */
      0,                                           /* tp_init */
      0,                                           /* tp_alloc */
      Nuitka_Method_tp_new,                        /* tp_new */
      0,                                           /* tp_free */
      0,                                           /* tp_is_gc */
      0,                                           /* tp_bases */
      0,                                           /* tp_mro */
      0,                                           /* tp_cache */
      0,                                           /* tp_subclasses */
      0,                                           /* tp_weaklist */
      0,                                           /* tp_del */
      0                                            /* tp_version_tag */
  #if PYTHON_VERSION >= 340
      ,0                                           /* tp_finalizer */
  #endif
  };
  
  void _initCompiledMethodType( void )
  {
      PyType_Ready( &Nuitka_Method_Type );
  }
  
  
  PyObject *Nuitka_Method_New( struct Nuitka_FunctionObject *function, PyObject *object, PyObject *klass )
  {
      struct Nuitka_MethodObject *result = method_cache_head;
  
      if ( result != NULL )
      {
          method_cache_head = (struct Nuitka_MethodObject *)method_cache_head->m_object;
          method_cache_size -= 1;
  
          Py_TYPE( result ) = &Nuitka_Method_Type;
          _Py_NewReference( (PyObject *)result );
      }
      else
      {
          result = PyObject_GC_New(struct Nuitka_MethodObject, &Nuitka_Method_Type);
      }
  
      if (unlikely( result == NULL ))
      {
          PyErr_Format(
              PyExc_RuntimeError,
              "cannot create method %s",
              Nuitka_String_AsString( function->m_name )
          );
  
          return NULL;
      }
  
      result->m_function = (struct Nuitka_FunctionObject *)INCREASE_REFCOUNT( (PyObject *)function );
  
      result->m_object = object;
      Py_XINCREF( object );
      result->m_class = klass;
      Py_XINCREF( klass );
  
      result->m_weakrefs = NULL;
  
      Nuitka_GC_Track( result );
      return (PyObject *)result;
  }
  