RawObject FUNC()

in runtime/builtins-module.cpp [488:660]


RawObject FUNC(builtins, __build_class__)(Thread* thread, Arguments args) {
  Runtime* runtime = thread->runtime();
  HandleScope scope(thread);
  Object body_obj(&scope, args.get(0));
  if (!body_obj.isFunction()) {
    return thread->raiseWithFmt(LayoutId::kTypeError,
                                "__build_class__: func must be a function");
  }
  Function body(&scope, *body_obj);
  Object name(&scope, args.get(1));
  if (!runtime->isInstanceOfStr(*name)) {
    return thread->raiseWithFmt(LayoutId::kTypeError,
                                "__build_class__: name is not a string");
  }
  Object metaclass(&scope, args.get(2));
  Object bootstrap(&scope, args.get(3));
  Tuple orig_bases(&scope, args.get(4));
  Tuple bases(&scope, *orig_bases);
  Dict kwargs(&scope, args.get(5));

  if (bootstrap == Bool::trueObj()) {
    CHECK(name.isStr(), "bootstrap class names must not be str subclass");
    name = Runtime::internStr(thread, name);
    Object type_obj(&scope, findBuiltinTypeWithName(thread, name));
    CHECK(!type_obj.isErrorNotFound(), "Unknown builtin type");
    Type type(&scope, *type_obj);

    if (bases.length() == 0 && name != runtime->symbols()->at(ID(object))) {
      bases = runtime->implicitBases();
    }
    Tuple builtin_bases(&scope, type.bases());
    word bases_length = bases.length();
    CHECK(builtin_bases.length() == bases_length, "mismatching bases for '%s'",
          Str::cast(*name).toCStr());
    for (word i = 0; i < bases_length; i++) {
      CHECK(builtin_bases.at(i) == bases.at(i), "mismatching bases for '%s'",
            Str::cast(*name).toCStr());
    }

    if (type.mro().isNoneType()) {
      Type superclass(&scope, bases.at(0));
      DCHECK(!superclass.mro().isNoneType(), "superclass not initialized yet");
      Tuple superclass_mro(&scope, superclass.mro());
      word mro_length = superclass_mro.length() + 1;
      MutableTuple mro(&scope, runtime->newMutableTuple(mro_length));
      mro.atPut(0, *type);
      mro.replaceFromWith(1, *superclass_mro, mro_length - 1);
      type.setMro(mro.becomeImmutable());
    }

    Dict type_dict(&scope, runtime->newDict());
    Object result(&scope,
                  thread->callFunctionWithImplicitGlobals(body, type_dict));
    if (result.isError()) return *result;
    CHECK(!typeAssignFromDict(thread, type, type_dict).isErrorException(),
          "error while assigning bootstrap type dict");
    // TODO(T53997177): Centralize type initialization
    Object module_name(&scope, typeAtById(thread, type, ID(__module__)));
    // non-heap-types in CPython have no `__module__` unless there is a
    // "." in `tp_name`. Remove the attribute when it equals "builtins".
    if (module_name.isStr() &&
        Str::cast(*module_name).equals(runtime->symbols()->at(ID(builtins)))) {
      typeRemoveById(thread, type, ID(__module__));
    }

    Object qualname(&scope, NoneType::object());
    if (type.instanceLayoutId() == LayoutId::kType) {
      qualname = *name;
      // Note: `type` is the only type allowed to have a descriptor instead of
      // a string for `__qualname__`.
    } else {
      qualname = typeRemoveById(thread, type, ID(__qualname__));
      DCHECK(qualname.isStr() && Str::cast(*qualname).equals(Str::cast(*name)),
             "unexpected __qualname__ attribute");
    }
    type.setQualname(*qualname);
    typeAddDocstring(thread, type);

    if (Layout::cast(type.instanceLayout()).hasTupleOverflow() &&
        typeAtById(thread, type, ID(__dict__)).isErrorNotFound()) {
      typeAddInstanceDict(thread, type);
    }

    if (DCHECK_IS_ON()) {
      Object dunder_new(&scope, typeAtById(thread, type, ID(__new__)));
      if (!dunder_new.isStaticMethod()) {
        if (!(dunder_new.isNoneType() || dunder_new.isErrorNotFound())) {
          DCHECK(false, "__new__ for %s should be a staticmethod",
                 Str::cast(*name).toCStr());
        }
      }
    }

    pickBuiltinTypeCtorFunction(thread, type);
    runtime->builtinTypeCreated(thread, type);
    return *type;
  }

  Object updated_bases(&scope, replaceNonTypeBases(thread, bases));
  if (updated_bases.isErrorException()) {
    return *updated_bases;
  }
  bases = *updated_bases;
  bool metaclass_is_class;
  if (metaclass.isUnbound()) {
    metaclass_is_class = true;
    if (bases.length() == 0) {
      metaclass = runtime->typeAt(LayoutId::kType);
    } else {
      metaclass = runtime->typeOf(bases.at(0));
    }
  } else {
    metaclass_is_class = runtime->isInstanceOfType(*metaclass);
  }

  if (metaclass_is_class) {
    Type metaclass_type(&scope, *metaclass);
    metaclass = calculateMetaclass(thread, metaclass_type, bases);
    if (metaclass.isError()) return *metaclass;
  }

  Object dict_obj(&scope, NoneType::object());
  Object prepare_method(
      &scope, runtime->attributeAtById(thread, metaclass, ID(__prepare__)));
  if (prepare_method.isError()) {
    Object given(&scope, thread->pendingExceptionType());
    Object exc(&scope, runtime->typeAt(LayoutId::kAttributeError));
    if (!givenExceptionMatches(thread, given, exc)) {
      return *prepare_method;
    }
    thread->clearPendingException();
    dict_obj = runtime->newDict();
  } else {
    thread->stackPush(*prepare_method);
    Tuple pargs(&scope, runtime->newTupleWith2(name, bases));
    thread->stackPush(*pargs);
    thread->stackPush(*kwargs);
    dict_obj = Interpreter::callEx(thread, CallFunctionExFlag::VAR_KEYWORDS);
    if (dict_obj.isError()) return *dict_obj;
  }
  if (!runtime->isMapping(thread, dict_obj)) {
    if (metaclass_is_class) {
      Type metaclass_type(&scope, *metaclass);
      Str metaclass_type_name(&scope, metaclass_type.name());
      return thread->raiseWithFmt(
          LayoutId::kTypeError,
          "%S.__prepare__() must return a mapping, not %T",
          &metaclass_type_name, &dict_obj);
    }
    return thread->raiseWithFmt(
        LayoutId::kTypeError,
        "<metaclass>.__prepare__() must return a mapping, not %T", &dict_obj);
  }
  Dict type_dict(&scope, *dict_obj);

  // TODO(cshapiro): might need to do some kind of callback here and we want
  // backtraces to work correctly.  The key to doing that would be to put some
  // state on the stack in between the the incoming arguments from the builtin
  // caller and the on-stack state for the class body function call.
  Object body_result(&scope,
                     thread->callFunctionWithImplicitGlobals(body, type_dict));
  if (body_result.isError()) return *body_result;

  if (bases != orig_bases) {
    dictAtPutById(thread, type_dict, ID(__orig_bases__), orig_bases);
  }

  thread->stackPush(*metaclass);
  Tuple pargs(&scope, runtime->newTupleWith3(name, bases, type_dict));
  thread->stackPush(*pargs);
  thread->stackPush(*kwargs);
  return Interpreter::callEx(thread, CallFunctionExFlag::VAR_KEYWORDS);
}