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));
}