void Analyzer::visitClassDef()

in StrictModules/analyzer.cpp [674:863]


void Analyzer::visitClassDef(const stmt_ty stmt) {
  auto classDef = stmt->v.ClassDef;
  // Step 1, identify metaclass
  std::shared_ptr<BaseStrictObject> metaclass;
  std::vector<AnalysisResult> bases = visitListLikeHelper(classDef.bases);
  // register metaclass if found in keyword args
  // find if any base class has metaclass
  int kwSize = asdl_seq_LEN(classDef.keywords);
  std::vector<std::string> kwargKeys;
  std::vector<AnalysisResult> kwargValues;
  kwargKeys.reserve(kwSize);
  kwargValues.reserve(kwSize);
  for (int i = 0; i < kwSize; ++i) {
    keyword_ty kw =
        reinterpret_cast<keyword_ty>(asdl_seq_GET(classDef.keywords, i));
    AnalysisResult kwVal = visitExpr(kw->value);
    if (PyUnicode_CompareWithASCIIString(kw->arg, "metaclass") == 0) {
      metaclass = std::move(kwVal);
    } else {
      kwargKeys.push_back(PyUnicode_AsUTF8(kw->arg));
      kwargValues.push_back(std::move(kwVal));
    }
  }

  bool replacedBases = false;
  AnalysisResult origBases;

  if (metaclass == nullptr && !bases.empty()) {
    // check if __mro_entries__ is defined for any bases
    std::vector<AnalysisResult> newBases;
    newBases.reserve(bases.size());
    auto baseTuple =
        std::make_shared<StrictTuple>(TupleType(), context_.caller, bases);

    for (auto& base : bases) {
      auto baseType = base->getType();
      if (baseType == UnknownType() ||
          std::dynamic_pointer_cast<StrictType>(base) != nullptr) {
        newBases.push_back(base);
        continue;
      }
      auto mroEntriesFunc = baseType->typeLookup("__mro_entries__", context_);
      if (mroEntriesFunc != nullptr) {
        auto newBaseEntries =
            iCall(mroEntriesFunc, {base, baseTuple}, kEmptyArgNames, context_);
        if (newBaseEntries != nullptr) {
          auto newBaseVec =
              iGetElementsVec(std::move(newBaseEntries), context_);
          newBases.insert(
              newBases.end(),
              std::move_iterator(newBaseVec.begin()),
              std::move_iterator(newBaseVec.end()));
          replacedBases = true;
        }
      }
    }

    if (replacedBases) {
      origBases = baseTuple;
    }
    std::swap(bases, newBases);

    // look for most common metaclass for all bases, skipping over
    // unknowns. Identify metaclass conflict
    if (metaclass == nullptr || !metaclass->isUnknown()) {
      auto metaclassType = std::dynamic_pointer_cast<StrictType>(metaclass);
      for (auto& base : bases) {
        auto baseTyp = std::dynamic_pointer_cast<StrictType>(base);
        if (baseTyp == nullptr) {
          continue;
        }
        auto baseTypMeta = baseTyp->getType();
        if (metaclassType == nullptr) {
          metaclassType = std::move(baseTypMeta);
        } else if (metaclassType->isSubType(baseTypMeta)) {
          continue;
        } else if (baseTypMeta->isSubType(metaclassType)) {
          metaclassType = std::move(baseTypMeta);
          continue;
        } else {
          context_.raiseTypeError("metaclass conflict");
        }
      }
      metaclass = metaclassType;
    }
  }
  if (metaclass == nullptr) {
    metaclass = TypeType();
  }

  // Step 2, run __prepare__ if exists, creating namespace ns
  std::shared_ptr<DictType> ns = std::make_shared<DictType>();
  AnalysisResult nsObj;
  std::string className = PyUnicode_AsUTF8(classDef.name);
  auto classNameObj = context_.makeStr(className);
  auto baseTupleObj =
      std::make_shared<StrictTuple>(TupleType(), context_.caller, bases);
  if (metaclass->getType() == UnknownType()) {
    context_.error<UnknownValueCallException>(metaclass->getDisplayName());
  } else {
    auto prepareFunc = iLoadAttr(metaclass, "__prepare__", nullptr, context_);
    if (prepareFunc != nullptr) {
      nsObj = iCall(
          prepareFunc, {classNameObj, baseTupleObj}, kEmptyArgNames, context_);
    }
  }

  // Step 3, create a hidden scope containing __class__
  const ScopeT& currentScope = stack_.getCurrentScope();
  std::shared_ptr<DictType> hiddenDunderClassScopeDict =
      std::make_shared<DictType>();
  std::unique_ptr<ScopeT> hiddenDunderClassScope = std::make_unique<ScopeT>(
      currentScope.getSTEntry(),
      hiddenDunderClassScopeDict,
      currentScope.getScopeData(),
      true);

  // Step 4, visit statements in class body with __class__ scope
  // Then ns scope
  {
    auto hiddenDunderClassManager =
        stack_.enterScope(std::move(hiddenDunderClassScope));
    auto classContextManager = stack_.enterScopeByAst(stmt, ns);
    classContextManager.getScope()->setScopeData(
        AnalysisScopeData(context_, nullptr, nsObj));
    visitStmtSeq(classDef.body);
  }

  // Step 5, extract the resulting ns scope, add __orig_bases__
  // if mro entries is used in step 1
  if (origBases != nullptr) {
    if (nsObj == nullptr) {
      (*ns)["__orig_bases__"] = std::move(origBases);
    } else {
      iSetElement(
          nsObj,
          context_.makeStr("__orig_bases__"),
          std::move(origBases),
          context_);
    }
  }
  AnalysisResult classDict = nsObj;
  if (classDict == nullptr) {
    classDict = strDictToObjHelper(std::move(ns), context_);
  }
  // Additional step, add __module__ to the class dict
  if (!iContainsElement(classDict, context_.makeStr("__module__"), context_)) {
    // getting __name__ from creator may not be accurate.
    // But we probably don't care about __module__ being accurate
    iSetElement(
      classDict,
      context_.makeStr("__module__"),
      context_.makeStr(modName_),
      context_);
  }

  // Step 6, call metaclass with class name, bases, ns, and kwargs
  std::vector<AnalysisResult> classCallArg{
      std::move(classNameObj), std::move(baseTupleObj), classDict};
  classCallArg.insert(
      classCallArg.end(),
      std::move_iterator(kwargValues.begin()),
      std::move_iterator(kwargValues.end()));
  auto classObj =
      iCall(metaclass, std::move(classCallArg), std::move(kwargKeys), context_);
  // Step 7, apply decorators
  int decoratorSize = asdl_seq_LEN(classDef.decorator_list);
  // decorators should be applied in reverse order
  for (int i = decoratorSize - 1; i >= 0; --i) {
    expr_ty dec =
        reinterpret_cast<expr_ty>(asdl_seq_GET(classDef.decorator_list, i));
    AnalysisResult decObj = visitExpr(dec);
    // call decorators, fix lineno
    {
      auto contextManager = updateContextHelper(dec->lineno, dec->col_offset);
      AnalysisResult tempClass =
          iCall(decObj, {classObj}, kEmptyArgNames, context_);
      std::swap(classObj, tempClass);
    }
  }
  // Additional step, add rewriter attrs of the class definition
  if (isNoSlotBuiltinType(classObj)) {
    classObj->ensureRewriterAttrs().setSlotsEnabled(false);
  }
  (*astToResults_)[stmt] = classObj;

  // Step 8, populate __class__ in hidden scope defined in step 3
  (*hiddenDunderClassScopeDict)[kDunderClass] = classObj;
  stack_.set(className, std::move(classObj));
}