StrictModules/analyzer.cpp (1,766 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
#include "StrictModules/analyzer.h"
#include "StrictModules/Compiler/abstract_module_loader.h"
#include "StrictModules/Objects/object_interface.h"
#include "StrictModules/Objects/objects.h"
#include "StrictModules/exceptions.h"
#include "StrictModules/caller_context.h"
#include "StrictModules/caller_context_impl.h"
#include "asdl.h"
namespace strictmod {
using namespace objects;
// AnalysisContextManager
class LoopContinueException {};
class LoopBreakException {};
class ImportLoaderUnavailableException {};
AnalysisContextManager::AnalysisContextManager(
CallerContext& ctx,
int newLine,
int newCol)
: context_(&ctx), oldLine_(ctx.lineno), oldCol_(ctx.col) {
context_->lineno = newLine;
context_->col = newCol;
}
AnalysisContextManager::~AnalysisContextManager() {
context_->lineno = oldLine_;
context_->col = oldCol_;
}
// Scope data
const AnalysisResult& AnalysisScopeData::getCallFirstArg() const {
return callFirstArg_;
}
void AnalysisScopeData::set(const std::string& key, AnalysisResult value) {
assert(prepareDict_ != nullptr);
assert(caller_.has_value());
iSetElement(prepareDict_, caller_->makeStr(key), value, *caller_);
}
AnalysisResult AnalysisScopeData::at(const std::string& key) {
assert(prepareDict_ != nullptr);
assert(caller_.has_value());
return iGetElement(prepareDict_, caller_->makeStr(key), *caller_);
}
bool AnalysisScopeData::erase(const std::string& key) {
assert(prepareDict_ != nullptr);
assert(caller_.has_value());
iDelElement(prepareDict_, caller_->makeStr(key), *caller_);
return true;
}
bool AnalysisScopeData::contains(const std::string& key) const {
assert(prepareDict_ != nullptr);
assert(caller_.has_value());
return iContainsElement(prepareDict_, caller_->makeStr(key), *caller_);
}
// template specialization for Scope with AnalysisScopeData
template <>
void Scope<AnalysisResult, AnalysisScopeData>::set(
const std::string& key,
AnalysisResult value) {
if (data_.hasAlternativeDict()) {
data_.set(key, std::move(value));
} else {
(*vars_)[key] = std::move(value);
}
}
template <>
AnalysisResult Scope<AnalysisResult, AnalysisScopeData>::at(
const std::string& key) {
return data_.hasAlternativeDict() ? data_.at(key) : vars_->at(key);
}
template <>
bool Scope<AnalysisResult, AnalysisScopeData>::erase(const std::string& key) {
if (data_.hasAlternativeDict()) {
return data_.erase(key);
}
return vars_->erase(key) > 0;
}
template <>
bool Scope<AnalysisResult, AnalysisScopeData>::contains(
const std::string& key) const {
if (data_.hasAlternativeDict()) {
return data_.contains(key);
}
return vars_->find(key) != vars_->map_end();
}
// Analyzer
Analyzer::Analyzer(
mod_ty root,
compiler::ModuleLoader* loader,
Symtable table,
BaseErrorSink* errors,
std::string filename,
std::string modName,
std::string scopeName,
std::shared_ptr<StrictModuleObject> caller,
bool futureAnnotations)
: Analyzer(
root,
loader,
std::move(table),
errors,
std::move(filename),
std::move(modName),
std::move(scopeName),
std::weak_ptr(caller),
futureAnnotations) {}
Analyzer::Analyzer(
mod_ty root,
compiler::ModuleLoader* loader,
Symtable table,
BaseErrorSink* errors,
std::string filename,
std::string modName,
std::string scopeName,
std::weak_ptr<StrictModuleObject> caller,
bool futureAnnotations)
: root_(root),
loader_(loader),
context_(
std::move(caller),
std::move(filename),
std::move(scopeName),
0,
0,
errors,
loader),
stack_(
table,
scopeFactory,
scopeFactory(table.entryFromAst(root_), getBuiltinsDict()),
scopeFactory(
table.entryFromAst(root_),
std::make_shared<DictType>())),
futureAnnotations_(futureAnnotations),
currentExceptionContext_(),
modName_(std::move(modName)),
astToResults_(std::make_unique<astToResultT>()) {}
Analyzer::Analyzer(
mod_ty root,
compiler::ModuleLoader* loader,
Symtable table,
std::shared_ptr<DictType> toplevelNS,
BaseErrorSink* errors,
std::string filename,
std::string modName,
std::string scopeName,
std::shared_ptr<StrictModuleObject> caller,
bool futureAnnotations)
: root_(root),
loader_(loader),
context_(
std::move(caller),
std::move(filename),
std::move(scopeName),
0,
0,
errors,
loader),
stack_(
table,
scopeFactory,
scopeFactory(table.entryFromAst(root_), getBuiltinsDict()),
scopeFactory(table.entryFromAst(root_), std::move(toplevelNS))),
futureAnnotations_(futureAnnotations),
currentExceptionContext_(),
modName_(std::move(modName)),
astToResults_(std::make_unique<astToResultT>()) {}
Analyzer::Analyzer(
compiler::ModuleLoader* loader,
BaseErrorSink* errors,
std::string filename,
std::string modName,
std::string scopeName,
std::weak_ptr<StrictModuleObject> caller,
int lineno,
int col,
const EnvT& closure,
bool futureAnnotations)
: root_(nullptr),
loader_(loader),
context_(
std::move(caller),
std::move(filename),
std::move(scopeName),
lineno,
col,
errors,
loader),
stack_(EnvT(closure)),
futureAnnotations_(futureAnnotations),
currentExceptionContext_(),
modName_(std::move(modName)),
astToResults_(std::make_unique<astToResultT>()) {}
/* if asname is not nullptr, return asname,
* otherwise return base name of the alias (substring before first '.')
*/
std::string importedNameHelper(alias_ty alias) {
if (alias->asname != nullptr) {
return PyUnicode_AsUTF8(alias->asname);
} else {
std::string name = PyUnicode_AsUTF8(alias->name);
std::size_t it = name.find('.');
return name.substr(0, it);
}
}
std::shared_ptr<BaseStrictObject> Analyzer::handleFromListHelper(
std::shared_ptr<BaseStrictObject> fromMod,
const std::string& name) {
auto value = iLoadAttr(fromMod, name, nullptr, context_);
if (value != nullptr) {
return value;
}
auto dunderPath = iLoadAttr(fromMod, "__path__", nullptr, context_);
if (dunderPath == nullptr) {
// not a package
return nullptr;
}
auto modName = iLoadAttr(fromMod, "__name__", nullptr, context_);
auto modNameStr = std::dynamic_pointer_cast<StrictString>(modName);
if (modNameStr == nullptr) {
return nullptr;
}
return loader_->loadModuleValue(modNameStr->getValue() + "." + name);
}
void Analyzer::visitImport(const stmt_ty stmt) {
/*
* If given a name like `foo.bar`, here we try to load the real
* `foo.bar` module first, but counter-intuitively we won't use it.
* If we don't have an asname (i.e. just `import foo.bar`), all we
* actually care about returning is the base module (`foo`). If we
* do have an asname, we will re-import starting from `foo` and
* allow attributes in parent modules to shadow submodules (e.g.
* import `foo` and see if it has a `bar` attribute before falling
* back to importing `foo.bar`). But we import the real submodule
* first here anyway and check if it exists, because if it doesn't
* that should be an import error regardless (i.e. you're not
* allowed to `import foo.bar as x` if `foo` has a `bar` attribute
* but there is no real module `foo.bar`). In normal Python doing
* this import of the real submodule first is also necessary because
* it populates the attribute on the parent module (importing
* "foo.bar" has the side effect of writing a `bar` attribute on
* `foo`). In strict packages we don't allow that side effect, so
* the only purpose of loading the full submodule here is to match
* the runtime in failing the import if it doesn't exist.
*/
if (loader_ == nullptr) {
throw ImportLoaderUnavailableException();
}
auto importNames = stmt->v.Import.names;
int n = asdl_seq_LEN(importNames);
for (int i = 0; i < n; ++i) {
alias_ty alias = reinterpret_cast<alias_ty>(asdl_seq_GET(importNames, i));
std::string modName(PyUnicode_AsUTF8(alias->name));
std::shared_ptr<StrictModuleObject> leafMod =
loader_->loadModuleValue(modName);
std::shared_ptr<BaseStrictObject> mod;
std::string asName = importedNameHelper(alias);
std::size_t split = modName.find('.');
std::string baseName = modName.substr(0, split);
if (leafMod != nullptr) {
mod = loader_->loadModuleValue(baseName);
if (alias->asname != nullptr) {
while (split != std::string::npos) {
if (mod == nullptr) {
break;
}
std::size_t nextSplit = modName.find('.', split + 1);
mod = iImportFrom(
std::move(mod),
modName.substr(split + 1, nextSplit),
context_,
loader_);
split = nextSplit;
}
}
}
if (mod == nullptr && alias->asname == nullptr) {
mod = makeUnknown(context_, "<imported module {}>", baseName);
} else if (mod == nullptr) {
mod =
makeUnknown(context_, "<imported module {} as {}>", modName, asName);
}
stack_.set(std::move(asName), std::move(mod));
}
}
void Analyzer::visitImportFrom(const stmt_ty stmt) {
if (loader_ == nullptr) {
throw ImportLoaderUnavailableException();
}
auto importFrom = stmt->v.ImportFrom;
bool hasFromName = importFrom.module != nullptr;
std::string fromName = hasFromName ? PyUnicode_AsUTF8(importFrom.module) : "";
if (fromName == "__future__") {
return;
}
std::string importedName;
if (importFrom.level > 0) {
// relative import
int level = importFrom.level;
std::size_t endPos = std::string::npos;
for (int i = 0; i < level; ++i) {
endPos = modName_.rfind('.', endPos - 1);
}
std::string relativeName = modName_.substr(0, endPos);
if (hasFromName) {
importedName.reserve(endPos + fromName.size() + 1);
importedName.append(relativeName);
importedName.append(".");
importedName.append(fromName);
} else {
importedName = relativeName;
}
} else if (hasFromName) {
importedName = fromName;
}
AnalysisResult mod = nullptr;
bool useLazy = stack_.isGlobalScope();
if (!useLazy) {
mod = loader_->loadModuleValue(importedName);
}
int namesSize = asdl_seq_LEN(importFrom.names);
for (int i = 0; i < namesSize; ++i) {
alias_ty alias =
reinterpret_cast<alias_ty>(asdl_seq_GET(importFrom.names, i));
std::string aliasName = PyUnicode_AsUTF8(alias->name);
if (aliasName == "*") {
// star import is prohibited
context_.error<StarImportDisallowedException>(importedName);
continue;
}
std::string displayName = std::string(importFrom.level, '.') + fromName;
std::string nameToStore = importedNameHelper(alias);
AnalysisResult modValue;
if (mod != nullptr) {
modValue = iImportFrom(mod, aliasName, context_, loader_);
} else if (useLazy) {
std::string unknownName;
if (alias->asname == nullptr) {
unknownName =
fmt::format("<{} imported from {}>", aliasName, displayName);
} else {
unknownName = fmt::format(
"<{} imported from {} as {}>", aliasName, displayName, nameToStore);
}
modValue = std::make_shared<StrictLazyObject>(
LazyObjectType(),
context_.caller,
loader_,
importedName,
std::move(unknownName),
context_,
aliasName);
loader_->recordLazyModule(importedName);
}
if (modValue != nullptr) {
stack_.set(std::move(nameToStore), std::move(modValue));
} else if (alias->asname == nullptr) {
AnalysisResult unknown = makeUnknown(
context_, "<{} imported from {}>", aliasName, displayName);
stack_.set(std::move(nameToStore), std::move(unknown));
} else {
AnalysisResult unknown = makeUnknown(
context_,
"<{} imported from {} as {}>",
aliasName,
displayName,
nameToStore);
stack_.set(std::move(nameToStore), std::move(unknown));
}
}
}
void Analyzer::visitAssign(const stmt_ty stmt) {
auto assignStmt = stmt->v.Assign;
AnalysisResult value = visitExpr(assignStmt.value);
if (value) {
for (int i = 0; i < asdl_seq_LEN(assignStmt.targets); ++i) {
expr_ty target =
reinterpret_cast<expr_ty>(asdl_seq_GET(assignStmt.targets, i));
assignToTarget(target, value);
}
}
}
void Analyzer::visitExprStmt(const stmt_ty stmt) {
expr_ty expr = stmt->v.Expr.value;
visitExpr(expr);
}
void Analyzer::visitFunctionDef(const stmt_ty stmt) {
auto f = stmt->v.FunctionDef;
std::string funcName = PyUnicode_AsUTF8(f.name);
AnalysisResult func = visitFunctionDefHelper(
funcName,
f.args,
f.body,
f.decorator_list,
f.returns,
f.type_comment,
stmt->lineno,
stmt->col_offset,
stmt,
false);
// put function in scope
stack_.set(std::move(funcName), std::move(func));
}
void Analyzer::visitAsyncFunctionDef(const stmt_ty stmt) {
auto f = stmt->v.AsyncFunctionDef;
std::string funcName = PyUnicode_AsUTF8(f.name);
AnalysisResult func = visitFunctionDefHelper(
funcName,
f.args,
f.body,
f.decorator_list,
f.returns,
f.type_comment,
stmt->lineno,
stmt->col_offset,
stmt,
true);
stack_.set(std::move(funcName), std::move(func));
}
AnalysisResult Analyzer::visitAnnotationHelper(expr_ty annotation) {
if (futureAnnotations_) {
Ref<> annotationStr = Ref<>::steal(_PyAST_ExprAsUnicode(annotation));
return std::make_shared<StrictString>(
StrType(), context_.caller, std::move(annotationStr));
} else {
return visitExpr(annotation);
}
}
void Analyzer::addToDunderAnnotationsHelper(
expr_ty target,
AnalysisResult value) {
if (!stack_.localContains(kDunderAnnotations)) {
auto annotationsDict =
std::make_shared<StrictDict>(DictObjectType(), context_.caller);
stack_.localSet(kDunderAnnotations, std::move(annotationsDict));
}
auto key = std::make_shared<StrictString>(
StrType(), context_.caller, Ref<>(target->v.Name.id));
auto dunderAnnotationsDict = getFromScope(kDunderAnnotations);
assert(dunderAnnotationsDict != std::nullopt);
iSetElement(
std::move(dunderAnnotationsDict.value()),
std::move(key),
std::move(value),
context_);
}
void Analyzer::visitArgHelper(arg_ty arg, DictDataT& annotations) {
if (arg->annotation == nullptr) {
return;
}
AnalysisResult key = std::make_shared<StrictString>(
StrType(), context_.caller, Ref<>(arg->arg));
annotations[std::move(key)] = visitAnnotationHelper(arg->annotation);
}
void Analyzer::visitArgHelper(
std::vector<std::string>& args,
arg_ty arg,
DictDataT& annotations) {
args.emplace_back(PyUnicode_AsUTF8(arg->arg));
if (arg->annotation != nullptr) {
visitArgHelper(arg, annotations);
}
}
AnalysisResult Analyzer::visitFunctionDefHelper(
std::string funcName,
arguments_ty args,
asdl_seq* body,
asdl_seq* decoratorList,
expr_ty returns,
string, // type_comment
int lineno,
int col_offset,
void* node,
bool isAsync) {
// symbol table for function body
SymtableEntry symbols = stack_.getSymtable().entryFromAst(node);
// function name and qualname
std::string qualName = stack_.getQualifiedScopeName();
if (qualName.empty()) {
qualName = funcName;
} else {
qualName.append(".");
qualName.append(funcName);
}
// function body
int bodySize = asdl_seq_LEN(body);
std::vector<stmt_ty> bodyVec;
bodyVec.reserve(bodySize);
for (int i = 0; i < bodySize; ++i) {
bodyVec.push_back(reinterpret_cast<stmt_ty>(asdl_seq_GET(body, i)));
}
// arguments
std::vector<std::string> posonlyArgs;
std::vector<std::string> posArgs;
std::vector<std::string> kwonlyArgs;
std::optional<std::string> varArg;
std::optional<std::string> kwVarArg;
DictDataT annotations;
int posonlySize = asdl_seq_LEN(args->posonlyargs);
posonlyArgs.reserve(posonlySize);
for (int i = 0; i < posonlySize; ++i) {
arg_ty a = reinterpret_cast<arg_ty>(asdl_seq_GET(args->posonlyargs, i));
visitArgHelper(posonlyArgs, a, annotations);
}
int posSize = asdl_seq_LEN(args->args);
posArgs.reserve(posSize);
for (int i = 0; i < posSize; ++i) {
arg_ty a = reinterpret_cast<arg_ty>(asdl_seq_GET(args->args, i));
visitArgHelper(posArgs, a, annotations);
}
if (args->vararg != nullptr) {
varArg.emplace(PyUnicode_AsUTF8(args->vararg->arg));
visitArgHelper(args->vararg, annotations);
}
int kwSize = asdl_seq_LEN(args->kwonlyargs);
kwonlyArgs.reserve(kwSize);
for (int i = 0; i < kwSize; ++i) {
arg_ty a = reinterpret_cast<arg_ty>(asdl_seq_GET(args->kwonlyargs, i));
visitArgHelper(kwonlyArgs, a, annotations);
}
if (args->kwarg != nullptr) {
kwVarArg.emplace(PyUnicode_AsUTF8(args->kwarg->arg));
visitArgHelper(args->kwarg, annotations);
}
// argument defaults
std::vector<AnalysisResult> posDefaults;
std::vector<AnalysisResult> kwDefaults;
int kwDefaultSize = asdl_seq_LEN(args->kw_defaults);
kwDefaults.reserve(kwDefaultSize);
for (int i = 0; i < kwDefaultSize; ++i) {
expr_ty d = reinterpret_cast<expr_ty>(asdl_seq_GET(args->kw_defaults, i));
if (d == nullptr) {
kwDefaults.push_back(nullptr);
} else {
kwDefaults.push_back(visitExpr(d));
}
}
int posDefaultSize = asdl_seq_LEN(args->defaults);
posDefaults.reserve(posDefaultSize);
for (int i = 0; i < posDefaultSize; ++i) {
expr_ty d = reinterpret_cast<expr_ty>(asdl_seq_GET(args->defaults, i));
posDefaults.push_back(visitExpr(d));
}
// annotations object
if (returns != nullptr) {
annotations[context_.makeStr("return")] = visitAnnotationHelper(returns);
}
std::shared_ptr<StrictDict> annotationsObj = std::make_shared<StrictDict>(
DictObjectType(), context_.caller, std::move(annotations));
AnalysisResult func(new StrictFunction(
FunctionType(),
context_.caller,
funcName,
std::move(qualName),
lineno,
col_offset,
std::move(bodyVec),
stack_.getFunctionScope(),
std::move(symbols),
std::move(posonlyArgs),
std::move(posArgs),
std::move(kwonlyArgs),
std::move(varArg),
std::move(kwVarArg),
std::move(posDefaults),
std::move(kwDefaults),
loader_,
context_.filename,
modName_,
std::nullopt,
std::move(annotationsObj),
futureAnnotations_,
isAsync));
// rewriter attrs
(*astToResults_)[node] = func;
// decorators
int decoratorSize = asdl_seq_LEN(decoratorList);
// 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(decoratorList, i));
AnalysisResult decObj = visitExpr(dec);
(*astToResults_)[dec] = decObj;
// call decorators, fix lineno
{
auto contextManager = updateContextHelper(dec->lineno, dec->col_offset);
AnalysisResult obj = iCall(decObj, {func}, kEmptyArgNames, context_);
func.swap(obj);
}
}
return func;
}
void Analyzer::visitReturn(const stmt_ty stmt) {
expr_ty returnV = stmt->v.Return.value;
if (returnV == nullptr) {
throw(FunctionReturnException(NoneObject()));
}
AnalysisResult returnVal = visitExpr(stmt->v.Return.value);
throw(FunctionReturnException(std::move(returnVal)));
}
static AnalysisResult strDictToObjHelper(
std::shared_ptr<DictType> dict,
const CallerContext& caller) {
DictDataT dictObj;
dictObj.reserve(dict->size());
for (auto& item : *dict) {
dictObj[caller.makeStr(item.first)] = item.second.first;
}
return std::make_shared<StrictDict>(
DictObjectType(), caller.caller, std::move(dictObj));
}
static bool isNoSlotBuiltinType(AnalysisResult value) {
static std::vector<AnalysisResult> noSlotTypes{
IntType(), TupleType(), SetType(), TypeType()};
auto typ = std::dynamic_pointer_cast<StrictType>(value);
if (typ) {
for (auto& base : typ->mro()) {
if (std::find(noSlotTypes.begin(), noSlotTypes.end(), base) !=
noSlotTypes.end()) {
return true;
}
}
}
return false;
}
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));
}
void Analyzer::visitPass(const stmt_ty) {}
void Analyzer::visitDelete(const stmt_ty stmt) {
auto delStmt = stmt->v.Delete;
for (int i = 0; i < asdl_seq_LEN(delStmt.targets); ++i) {
expr_ty target =
reinterpret_cast<expr_ty>(asdl_seq_GET(delStmt.targets, i));
assignToTarget(target, nullptr);
}
}
void Analyzer::visitAugAssign(const stmt_ty stmt) {
// TODO: this is not quite accurate, the correct dunder methods
// to use are __i<op>__ methods. But we have been using normal
// bin ops just fine. Add __i<op>__ methods if needed
auto augAssign = stmt->v.AugAssign;
AnalysisResult leftV = visitExpr(augAssign.target);
AnalysisResult rightV = visitExpr(augAssign.value);
AnalysisResult result =
iDoBinOp(std::move(leftV), std::move(rightV), augAssign.op, context_);
assignToTarget(augAssign.target, std::move(result));
}
void Analyzer::visitAnnAssign(const stmt_ty stmt) {
auto annAssign = stmt->v.AnnAssign;
expr_ty target = annAssign.target;
if (annAssign.value != nullptr) {
assignToTarget(target, visitExpr(annAssign.value));
}
bool classScope = stack_.isClassScope();
bool evalAnnotations = classScope || stack_.isGlobalScope();
if (target->kind == Name_kind && evalAnnotations) {
AnalysisResult type = visitAnnotationHelper(annAssign.annotation);
if (classScope) {
addToDunderAnnotationsHelper(target, std::move(type));
}
}
}
void Analyzer::visitFor(const stmt_ty stmt) {
auto forExpr = stmt->v.For;
AnalysisResult iterObj = visitExpr(forExpr.iter);
bool hasBreak = false;
for (auto& element : iGetElementsVec(iterObj, context_)) {
assignToTarget(forExpr.target, element);
try {
visitStmtSeq(forExpr.body);
} catch (const LoopContinueException&) {
} catch (const LoopBreakException&) {
hasBreak = true;
break;
}
}
if (!hasBreak) {
visitStmtSeq(forExpr.orelse);
}
}
void Analyzer::visitWhile(const stmt_ty stmt) {
int iterCount = 0;
bool hasBreak = false;
auto whileStmt = stmt->v.While;
while (true) {
// check too many iterations
if (++iterCount > kIterationLimit) {
context_.error<StrictModuleTooManyIterationsException>();
break;
}
// check guard
AnalysisResult test = iGetTruthValue(visitExpr(whileStmt.test), context_);
if (test != StrictTrue()) {
break;
}
// execute body
try {
visitStmtSeq(whileStmt.body);
} catch (const LoopContinueException&) {
} catch (const LoopBreakException&) {
hasBreak = true;
break;
}
}
// orelse if no break
if (!hasBreak) {
visitStmtSeq(whileStmt.orelse);
}
}
void Analyzer::visitIf(const stmt_ty stmt) {
auto ifStmt = stmt->v.If;
AnalysisResult testBool = iGetTruthValue(visitExpr(ifStmt.test), context_);
if (testBool == StrictTrue()) {
visitStmtSeq(ifStmt.body);
} else if (testBool == StrictFalse()) {
visitStmtSeq(ifStmt.orelse);
}
}
void Analyzer::visitWith(const stmt_ty stmt) {
auto withStmt = stmt->v.With;
int withItemSize = asdl_seq_LEN(withStmt.items);
std::vector<AnalysisResult> contexts;
contexts.reserve(withItemSize);
// enter and record context
for (int i = 0; i < withItemSize; ++i) {
withitem_ty item =
reinterpret_cast<withitem_ty>(asdl_seq_GET(withStmt.items, i));
AnalysisResult context = visitExpr(item->context_expr);
auto enterFunc = iLoadAttrOnType(
context,
"__enter__",
makeUnknown(context_, "{}.__enter__", context),
context_);
auto enterItem =
iCall(std::move(enterFunc), kEmptyArgs, kEmptyArgNames, context_);
if (item->optional_vars != nullptr) {
assignToTarget(item->optional_vars, std::move(enterItem));
}
contexts.push_back(std::move(context));
}
// body
AnalysisResult returnVal;
std::optional<StrictModuleUserException<BaseStrictObject>> userExc;
AnalysisResult excType = NoneObject();
AnalysisResult excVal = NoneObject();
AnalysisResult excTb = NoneObject();
try {
visitStmtSeq(withStmt.body);
} catch (StrictModuleUserException<BaseStrictObject>& e) {
userExc = e;
excType = e.getWrapped()->getType();
excVal = e.getWrapped();
excTb = makeUnknown(context_, "traceback of {}", excVal);
} catch (FunctionReturnException& r) {
returnVal = r.getVal();
}
// exit contexts, in reverse order
bool suppressExc = false;
for (auto i = contexts.rbegin(); i != contexts.rend(); ++i) {
AnalysisResult context = *i;
auto exitFunc = iLoadAttrOnType(
context,
"__exit__",
makeUnknown(context_, "{}.__exit__", context),
context_);
auto exitResult = iCall(
std::move(exitFunc),
{excType, excVal, excTb},
kEmptyArgNames,
context_);
auto exitResultBool = iGetTruthValue(std::move(exitResult), context_);
suppressExc |= exitResultBool == StrictTrue();
}
if (!suppressExc && userExc.has_value()) {
throw userExc.value();
}
if (returnVal != nullptr) {
throw FunctionReturnException(std::move(returnVal));
}
}
void Analyzer::visitRaise(const stmt_ty stmt) {
auto raiseStmt = stmt->v.Raise;
expr_ty excExpr = raiseStmt.exc;
AnalysisResult exc;
if (excExpr != nullptr) {
exc = visitExpr(excExpr);
if (std::dynamic_pointer_cast<StrictType>(exc)) {
// call the exception type
exc = iCall(std::move(exc), kEmptyArgs, kEmptyArgNames, context_);
}
} else {
exc = currentExceptionContext_;
if (exc == nullptr) {
context_.raiseExceptionStr(RuntimeErrorType(), "no active exceptions");
}
}
expr_ty excCause = raiseStmt.cause;
if (excCause != nullptr) {
visitExpr(excCause);
}
context_.raiseExceptionFromObj(std::move(exc));
}
bool Analyzer::visitExceptionHandlerHelper(
asdl_seq* handlers,
AnalysisResult exc) {
int handlersSize = asdl_seq_LEN(handlers);
for (int i = 0; i < handlersSize; ++i) {
excepthandler_ty handler =
reinterpret_cast<excepthandler_ty>(asdl_seq_GET(handlers, i));
{
// set the correct lineno
auto _ = AnalysisContextManager(
context_, handler->lineno, handler->col_offset);
auto handlerV = handler->v.ExceptHandler;
bool matched = false;
if (handlerV.type != nullptr) {
matched =
isinstanceImpl(nullptr, context_, exc, visitExpr(handlerV.type)) ==
StrictTrue();
} else {
// match against everything
matched = true;
}
if (matched) {
currentExceptionContext_ = exc;
std::string excName;
if (handlerV.name != nullptr) {
excName = PyUnicode_AsUTF8(handlerV.name);
stack_.set(excName, std::move(exc));
visitStmtSeq(handlerV.body);
stack_.erase(excName);
} else {
visitStmtSeq(handlerV.body);
}
currentExceptionContext_ = nullptr;
return true;
}
}
}
return false;
}
void Analyzer::visitTry(const stmt_ty stmt) {
auto tryStmt = stmt->v.Try;
bool caughtException = false;
try {
try {
visitStmtSeq(tryStmt.body);
} catch (StrictModuleUserException<BaseStrictObject>& e) {
caughtException = true;
bool handledException =
visitExceptionHandlerHelper(tryStmt.handlers, e.getWrapped());
if (!handledException) {
throw;
}
}
if (!caughtException) {
visitStmtSeq(tryStmt.orelse);
}
} catch (StrictModuleUserException<BaseStrictObject>& e) {
visitStmtSeq(tryStmt.finalbody);
throw;
}
visitStmtSeq(tryStmt.finalbody);
}
void Analyzer::visitAssert(const stmt_ty) {}
void Analyzer::visitBreak(const stmt_ty) {
throw LoopBreakException();
}
void Analyzer::visitContinue(const stmt_ty) {
throw LoopContinueException();
}
void Analyzer::visitGlobal(const stmt_ty) {}
// Expressions
AnalysisResult Analyzer::visitConstant(const expr_ty expr) {
auto constant = expr->v.Constant;
if (PyLong_CheckExact(constant.value)) {
auto value =
std::make_shared<StrictInt>(IntType(), context_.caller, constant.value);
return value;
}
if (PyUnicode_CheckExact(constant.value)) {
auto value = std::make_shared<StrictString>(
StrType(), context_.caller, Ref<>(constant.value));
return value;
}
if (PyFloat_CheckExact(constant.value)) {
auto value = std::make_shared<StrictFloat>(
FloatType(), context_.caller, constant.value);
return value;
}
if (PyBytes_CheckExact(constant.value)) {
auto value = std::make_shared<StrictBytes>(
BytesType(), context_.caller, constant.value);
return value;
}
if (constant.value == Py_None) {
return NoneObject();
}
if (constant.value == Py_True) {
return StrictTrue();
}
if (constant.value == Py_False) {
return StrictFalse();
}
if (constant.value == Py_Ellipsis) {
return EllipsisObject();
}
return defaultVisitExpr();
}
AnalysisResult Analyzer::visitName(const expr_ty expr) {
auto name = expr->v.Name;
const char* nameStr = PyUnicode_AsUTF8(name.id);
auto value = getFromScope(nameStr);
if (!value) {
// TODO? decide whether to raise NameError or UnboundLocalError base on
// declaration
context_.raiseExceptionStr(
NameErrorType(), "name {} is not defined", nameStr);
}
return value.value();
}
AnalysisResult Analyzer::visitAttribute(const expr_ty expr) {
auto attribute = expr->v.Attribute;
AnalysisResult value = visitExpr(attribute.value);
assert(value != nullptr);
const char* attrName = PyUnicode_AsUTF8(attribute.attr);
assert(attribute.ctx != Del);
auto result = iLoadAttr(value, attrName, nullptr, context_);
if (!result) {
context_.raiseExceptionStr(
AttributeErrorType(),
"{} object has no attribute {}",
value->getTypeRef().getName(),
attrName);
}
return result;
}
AnalysisResult Analyzer::visitCall(const expr_ty expr) {
auto call = expr->v.Call;
AnalysisResult func = visitExpr(call.func);
assert(func != nullptr);
auto argsSeq = call.args;
auto kwargsSeq = call.keywords;
int argsLen = asdl_seq_LEN(argsSeq);
int kwargsLen = asdl_seq_LEN(kwargsSeq);
// Special handling of super() with no arguments
if (argsLen == 0 && kwargsLen == 0 && func == SuperType()) {
return callMagicalSuperHelper(std::move(func));
}
if (argsLen == 1 && kwargsLen == 0 && func == StrictTryImport()) {
expr_ty argExpr = reinterpret_cast<expr_ty>(asdl_seq_GET(argsSeq, 0));
auto argV = visitExpr(argExpr);
auto argStr = std::dynamic_pointer_cast<StrictString>(argV);
if (argStr) {
auto mod = loader_->tryGetModuleValue(argStr->getValue());
return mod ? mod : NoneObject();
}
}
std::vector<AnalysisResult> args;
std::vector<std::string> argNames;
args.reserve(argsLen + kwargsLen);
argNames.reserve(kwargsLen);
for (int i = 0; i < argsLen; ++i) {
expr_ty argExpr = reinterpret_cast<expr_ty>(asdl_seq_GET(argsSeq, i));
if (argExpr->kind == Starred_kind) {
auto starredElts = visitExpr(argExpr);
auto starredEltsVec = iGetElementsVec(std::move(starredElts), context_);
args.insert(
args.end(),
std::move_iterator(starredEltsVec.begin()),
std::move_iterator(starredEltsVec.end()));
} else {
args.push_back(visitExpr(argExpr));
}
}
for (int i = 0; i < kwargsLen; ++i) {
keyword_ty kw = reinterpret_cast<keyword_ty>(asdl_seq_GET(kwargsSeq, i));
if (kw->arg != nullptr) {
args.push_back(visitExpr(kw->value));
argNames.emplace_back(PyUnicode_AsUTF8(kw->arg));
} else {
// double star arg
auto doubleStarArg = visitExpr(kw->value);
auto doubleStarDict =
std::dynamic_pointer_cast<StrictDict>(doubleStarArg);
if (doubleStarDict) {
doubleStarDict->getData().const_iter(
[&args, &argNames](AnalysisResult k, AnalysisResult v) {
auto keyStr =
std::dynamic_pointer_cast<StrictString>(std::move(k));
if (keyStr) {
args.push_back(std::move(v));
argNames.push_back(keyStr->getValue());
}
return true;
});
}
}
}
return iCall(func, std::move(args), std::move(argNames), context_);
}
AnalysisResult Analyzer::callMagicalSuperHelper(AnalysisResult func) {
// rule out cases where user explicitly define __class__ on module level
auto dunderClass = getFromScope(kDunderClass);
if (dunderClass == std::nullopt || stack_.isGlobal(kDunderClass)) {
context_.raiseExceptionStr(
RuntimeErrorType(), "super(): __class__ not found");
}
const AnalysisResult& firstArg =
stack_.getCurrentScope().getScopeData().getCallFirstArg();
if (firstArg == nullptr) {
context_.raiseExceptionStr(RuntimeErrorType(), "super(): no arguments");
}
return iCall(
std::move(func),
{dunderClass.value(), firstArg},
kEmptyArgNames,
context_);
}
std::vector<AnalysisResult> Analyzer::visitListLikeHelper(asdl_seq* elts) {
int eltsLen = asdl_seq_LEN(elts);
std::vector<AnalysisResult> data;
data.reserve(eltsLen);
for (int i = 0; i < eltsLen; ++i) {
expr_ty argExpr = reinterpret_cast<expr_ty>(asdl_seq_GET(elts, i));
if (argExpr->kind == Starred_kind) {
AnalysisResult starredVal = visitStarred(argExpr);
std::vector<AnalysisResult> starredVec =
iGetElementsVec(starredVal, context_);
data.insert(
data.end(),
std::move_iterator(starredVec.begin()),
std::move_iterator(starredVec.end()));
} else {
data.push_back(visitExpr(argExpr));
}
}
return data;
}
AnalysisResult Analyzer::visitSet(const expr_ty expr) {
auto v = visitListLikeHelper(expr->v.Set.elts);
SetDataT data(std::move_iterator(v.begin()), std::move_iterator(v.end()));
AnalysisResult obj =
std::make_shared<StrictSet>(SetType(), context_.caller, std::move(data));
return obj;
}
AnalysisResult Analyzer::visitList(const expr_ty expr) {
auto v = visitListLikeHelper(expr->v.List.elts);
AnalysisResult obj =
std::make_shared<StrictList>(ListType(), context_.caller, std::move(v));
return obj;
}
AnalysisResult Analyzer::visitTuple(const expr_ty expr) {
auto v = visitListLikeHelper(expr->v.Tuple.elts);
AnalysisResult obj =
std::make_shared<StrictTuple>(TupleType(), context_.caller, std::move(v));
return obj;
}
DictDataT Analyzer::visitDictUnpackHelper(expr_ty valueExpr) {
AnalysisResult unpacked = visitExpr(valueExpr);
auto keys = iGetElementsVec(unpacked, context_);
DictDataT map;
map.reserve(keys.size());
for (auto& k : keys) {
auto value = iGetElement(unpacked, k, context_);
map[k] = std::move(value);
}
return map;
}
AnalysisResult Analyzer::visitDict(const expr_ty expr) {
auto dict = expr->v.Dict;
int keysLen = asdl_seq_LEN(dict.keys);
assert(keysLen == asdl_seq_LEN(dict.values));
DictDataT map;
map.reserve(keysLen);
for (int i = 0; i < keysLen; ++i) {
expr_ty keyExpr = reinterpret_cast<expr_ty>(asdl_seq_GET(dict.keys, i));
expr_ty valueExpr = reinterpret_cast<expr_ty>(asdl_seq_GET(dict.values, i));
if (keyExpr == nullptr) {
// handle unpacking
DictDataT unpackedMap = visitDictUnpackHelper(valueExpr);
for (auto& item : unpackedMap) {
map[std::move(item.first)] = std::move(item.second.first);
}
} else {
AnalysisResult kResult = visitExpr(keyExpr);
AnalysisResult vResult = visitExpr(valueExpr);
map[kResult] = vResult;
}
}
return std::make_shared<StrictDict>(
DictObjectType(), context_.caller, std::move(map));
}
AnalysisResult Analyzer::visitBinOp(const expr_ty expr) {
AnalysisResult left = visitExpr(expr->v.BinOp.left);
AnalysisResult right = visitExpr(expr->v.BinOp.right);
return iDoBinOp(
std::move(left), std::move(right), expr->v.BinOp.op, context_);
}
AnalysisResult Analyzer::visitUnaryOp(const expr_ty expr) {
AnalysisResult value = visitExpr(expr->v.UnaryOp.operand);
unaryop_ty op = expr->v.UnaryOp.op;
if (op == Not) {
auto result = iGetTruthValue(std::move(value), context_);
if (result->getType() == UnknownType()) {
return result;
}
return context_.makeBool(result == StrictFalse());
}
return iUnaryOp(std::move(value), op, context_);
}
AnalysisResult Analyzer::visitCompare(const expr_ty expr) {
auto compare = expr->v.Compare;
AnalysisResult leftObj = visitExpr(compare.left);
int cmpSize = asdl_seq_LEN(compare.ops);
AnalysisResult compareValue;
for (int i = 0; i < cmpSize; ++i) {
cmpop_ty op = static_cast<cmpop_ty>(asdl_seq_GET(compare.ops, i));
expr_ty rightExpr =
reinterpret_cast<expr_ty>(asdl_seq_GET(compare.comparators, i));
AnalysisResult rightObj = visitExpr(rightExpr);
compareValue = iBinCmpOp(leftObj, rightObj, op, context_);
AnalysisResult compareBool = iGetTruthValue(compareValue, context_);
if (compareBool == StrictFalse()) {
// short circuit
return compareValue;
}
if (compareBool->getType() == UnknownType()) {
// unknown result
return compareValue;
}
leftObj = std::move(rightObj);
}
assert(compareValue != nullptr);
return compareValue;
}
AnalysisResult Analyzer::visitBoolOp(const expr_ty expr) {
auto boolOpExpr = expr->v.BoolOp;
AnalysisResult result;
int valuesSize = asdl_seq_LEN(boolOpExpr.values);
for (int i = 0; i < valuesSize; ++i) {
expr_ty e = reinterpret_cast<expr_ty>(asdl_seq_GET(boolOpExpr.values, i));
result = visitExpr(e);
auto resultBool = iGetTruthValue(result, context_);
if (boolOpExpr.op == And && resultBool == StrictFalse()) {
break;
} else if (boolOpExpr.op == Or && resultBool == StrictTrue()) {
break;
} else if (resultBool->getType() == UnknownType()) {
break;
}
}
return result;
}
AnalysisResult Analyzer::visitNamedExpr(const expr_ty expr) {
auto namedExpr = expr->v.NamedExpr;
AnalysisResult value = visitExpr(namedExpr.value);
assignToTarget(namedExpr.target, value);
return value;
}
AnalysisResult Analyzer::visitSubscript(const expr_ty expr) {
auto subscrExpr = expr->v.Subscript;
AnalysisResult base = visitExpr(subscrExpr.value);
AnalysisResult idx = visitSliceHelper(subscrExpr.slice);
return iGetElement(std::move(base), std::move(idx), context_);
}
AnalysisResult Analyzer::visitStarred(const expr_ty expr) {
return visitExpr(expr->v.Starred.value);
}
AnalysisResult Analyzer::visitSliceHelper(slice_ty slice) {
switch (slice->kind) {
case Slice_kind: {
auto sliceExp = slice->v.Slice;
AnalysisResult start =
sliceExp.lower ? visitExpr(sliceExp.lower) : NoneObject();
AnalysisResult stop =
sliceExp.upper ? visitExpr(sliceExp.upper) : NoneObject();
AnalysisResult step =
sliceExp.step ? visitExpr(sliceExp.step) : NoneObject();
return std::make_shared<StrictSlice>(
SliceType(),
context_.caller,
std::move(start),
std::move(stop),
std::move(step));
}
case ExtSlice_kind: {
auto extSliceExp = slice->v.ExtSlice;
int dimSize = asdl_seq_LEN(extSliceExp.dims);
std::vector<AnalysisResult> extTuple;
extTuple.reserve(dimSize);
for (int i = 0; i < dimSize; ++i) {
slice_ty dim =
reinterpret_cast<slice_ty>(asdl_seq_GET(extSliceExp.dims, i));
extTuple.push_back(visitSliceHelper(dim));
}
return std::make_shared<StrictTuple>(
TupleType(), context_.caller, std::move(extTuple));
}
case Index_kind:
return visitExpr(slice->v.Index.value);
}
Py_UNREACHABLE();
}
AnalysisResult Analyzer::visitLambda(const expr_ty expr) {
auto lambdaExp = expr->v.Lambda;
stmt_ty returnStmt = Return(
lambdaExp.body,
expr->lineno,
expr->col_offset,
expr->end_lineno,
expr->end_col_offset,
loader_->getArena());
asdl_seq* body = _Py_asdl_seq_new(1, loader_->getArena());
asdl_seq_SET(body, 0, returnStmt);
return visitFunctionDefHelper(
"<lambda>",
lambdaExp.args,
body,
nullptr,
nullptr,
nullptr,
expr->lineno,
expr->col_offset,
expr,
false);
}
AnalysisResult Analyzer::visitIfExp(const expr_ty expr) {
auto ifExp = expr->v.IfExp;
AnalysisResult test = iGetTruthValue(visitExpr(ifExp.test), context_);
if (test == StrictTrue()) {
return visitExpr(ifExp.body);
}
if (test == StrictFalse()) {
return visitExpr(ifExp.orelse);
}
AnalysisResult body = visitExpr(ifExp.body);
AnalysisResult orelse = visitExpr(ifExp.orelse);
return makeUnknown(context_, "({} if {} else {})", body, test, orelse);
}
AnalysisResult Analyzer::visitListComp(const expr_ty expr) {
auto comp = expr->v.ListComp;
std::vector<AnalysisResult> result;
visitGeneratorHelper(
expr,
comp.generators,
[&result](AnalysisResult v) { result.push_back(std::move(v)); },
comp.elt);
return std::make_shared<StrictList>(
ListType(), context_.caller, std::move(result));
}
AnalysisResult Analyzer::visitSetComp(const expr_ty expr) {
auto comp = expr->v.SetComp;
SetDataT result;
visitGeneratorHelper(
expr,
comp.generators,
[&result](AnalysisResult v) { result.insert(std::move(v)); },
comp.elt);
return std::make_shared<StrictSet>(
SetType(), context_.caller, std::move(result));
}
AnalysisResult Analyzer::visitDictComp(const expr_ty expr) {
auto comp = expr->v.DictComp;
DictDataT result;
visitGeneratorHelper(
expr,
comp.generators,
[&result](AnalysisResult k, AnalysisResult v) {
result[std::move(k)] = std::move(v);
},
comp.key,
comp.value);
return std::make_shared<StrictDict>(
DictObjectType(), context_.caller, std::move(result));
}
AnalysisResult Analyzer::visitGeneratorExp(const expr_ty expr) {
auto comp = expr->v.GeneratorExp;
std::vector<AnalysisResult> result;
visitGeneratorHelper(
expr,
comp.generators,
[&result](AnalysisResult v) { result.push_back(std::move(v)); },
comp.elt);
return std::make_shared<StrictGeneratorExp>(
GeneratorExpType(), context_.caller, std::move(result));
}
template <typename CB, typename... Args>
void Analyzer::visitGeneratorHelper(
expr_ty node,
asdl_seq* generators,
CB callback,
Args... targets) {
int numComps = asdl_seq_LEN(generators);
assert(numComps > 0);
// the first comprehension does not have a separate inner scope
comprehension_ty comp =
reinterpret_cast<comprehension_ty>(asdl_seq_GET(generators, 0));
AnalysisResult compValue = visitExpr(comp->iter);
std::vector<comprehension_ty> comps;
comps.reserve(numComps - 1);
for (int i = 1; i < numComps; ++i) {
comps.push_back(
reinterpret_cast<comprehension_ty>(asdl_seq_GET(generators, i)));
}
{
auto manager = stack_.enterScopeByAst(node);
visitGeneratorHelperInner(
std::move(compValue),
comp->target,
comp->ifs,
comps,
0,
callback,
targets...);
}
}
template <typename CB, typename... Args>
void Analyzer::visitGeneratorHelperInner(
AnalysisResult iter,
expr_ty iterTarget,
asdl_seq* ifs,
const std::vector<comprehension_ty>& comps,
std::size_t idx,
CB callback,
Args... targets) {
std::vector<AnalysisResult> elements =
iGetElementsVec(std::move(iter), context_);
if (idx == comps.size()) {
// base case, produce value for targets
for (AnalysisResult elem : elements) {
assignToTarget(iterTarget, std::move(elem));
if (!checkGeneratorIfHelper(ifs)) {
continue;
}
callback(visitExpr(targets)...);
}
return;
}
comprehension_ty nextComp = comps[idx];
for (AnalysisResult elem : elements) {
assignToTarget(iterTarget, std::move(elem));
if (!checkGeneratorIfHelper(ifs)) {
continue;
}
visitGeneratorHelperInner(
visitExpr(nextComp->iter),
nextComp->target,
nextComp->ifs,
comps,
idx + 1,
callback,
targets...);
}
}
bool Analyzer::checkGeneratorIfHelper(asdl_seq* ifs) {
int size = asdl_seq_LEN(ifs);
for (int i = 0; i < size; ++i) {
expr_ty cond = reinterpret_cast<expr_ty>(asdl_seq_GET(ifs, i));
AnalysisResult v = iGetTruthValue(visitExpr(cond), context_);
if (v != StrictTrue()) {
return false;
}
}
return true;
}
AnalysisResult Analyzer::visitAwait(const expr_ty expr) {
auto await = expr->v.Await;
AnalysisResult awaitExpr = visitExpr(await.value);
context_.error<UnsupportedException>("await", awaitExpr->getDisplayName());
return makeUnknown(context_, "await {}", awaitExpr);
}
AnalysisResult Analyzer::visitYield(const expr_ty) {
throw YieldReachedException();
}
AnalysisResult Analyzer::visitYieldFrom(const expr_ty) {
throw YieldReachedException();
}
static AnalysisResult formatHelper(
AnalysisResult str,
AnalysisResult formatSpec,
const CallerContext& caller) {
auto formatFunc = iLoadAttrOnType(str, "__format__", nullptr, caller);
if (formatFunc == nullptr) {
return makeUnknown(caller, "{}.__format__{}", str, formatSpec);
}
AnalysisResult result = iCall(
std::move(formatFunc), {std::move(formatSpec)}, kEmptyArgNames, caller);
return result;
}
AnalysisResult Analyzer::visitFormattedValue(const expr_ty expr) {
auto fv = expr->v.FormattedValue;
AnalysisResult value = visitExpr(fv.value);
switch (fv.conversion) {
case -1:
break;
case 's': {
// call str()
value = iCall(StrType(), {value}, kEmptyArgNames, context_);
break;
}
case 'r': {
// call repr()
value = reprImpl(nullptr, context_, value);
break;
}
case 'a': {
context_.error<UnsupportedException>(
"'joined str to ascii'", value->getDisplayName());
}
}
AnalysisResult formatSpec;
if (fv.format_spec == nullptr) {
formatSpec = context_.makeStr("");
} else {
formatSpec = visitExpr(fv.format_spec);
}
AnalysisResult result =
formatHelper(std::move(value), std::move(formatSpec), context_);
const BaseStrictObject& resultRef = *result;
if (!result->isUnknown() && typeid(resultRef) != typeid(StrictString)) {
context_.raiseTypeError(
"result of format should be string, not {}",
result->getTypeRef().getName());
}
return result;
}
AnalysisResult Analyzer::visitJoinedStr(const expr_ty expr) {
auto joinedStr = expr->v.JoinedStr;
int size = asdl_seq_LEN(joinedStr.values);
std::string result;
bool isUnknown = false;
for (int i = 0; i < size; ++i) {
expr_ty sectionExpr =
reinterpret_cast<expr_ty>(asdl_seq_GET(joinedStr.values, i));
AnalysisResult section = visitExpr(sectionExpr);
auto sectionStr = std::dynamic_pointer_cast<StrictString>(section);
if (sectionStr) {
result += sectionStr->getValue();
} else {
isUnknown = true;
result += section->getDisplayName();
}
}
if (isUnknown) {
return makeUnknown(context_, "{}", std::move(result));
}
return context_.makeStr(std::move(result));
}
void Analyzer::visitStmtSeq(const asdl_seq* seq) {
for (int i = 0; i < asdl_seq_LEN(seq); i++) {
stmt_ty elt = reinterpret_cast<stmt_ty>(asdl_seq_GET(seq, i));
visitStmt(elt);
}
}
void Analyzer::visitStmtSeq(std::vector<stmt_ty> seq) {
for (const stmt_ty s : seq) {
visitStmt(s);
}
}
void Analyzer::defaultVisitMod() {
raiseUnimplemented();
}
void Analyzer::defaultVisitStmt() {
raiseUnimplemented();
}
AnalysisResult Analyzer::defaultVisitExpr() {
raiseUnimplemented();
return makeUnknown(context_, "<unimplemented expr>");
}
AnalysisContextManager Analyzer::updateContext(stmt_ty stmt) {
return updateContextHelper(stmt->lineno, stmt->col_offset);
}
AnalysisContextManager Analyzer::updateContext(expr_ty expr) {
return updateContextHelper(expr->lineno, expr->col_offset);
}
AnalysisContextManager Analyzer::updateContext(mod_ty) {
return updateContextHelper(0, 0);
}
void Analyzer::assignToTarget(const expr_ty target, AnalysisResult value) {
switch (target->kind) {
case Name_kind:
assignToName(target, std::move(value));
break;
case Tuple_kind:
assignToListLike(target->v.Tuple.elts, std::move(value));
break;
case List_kind:
assignToListLike(target->v.List.elts, std::move(value));
break;
case Attribute_kind:
assignToAttribute(target, std::move(value));
break;
case Subscript_kind:
assignToSubscript(target, std::move(value));
break;
case Starred_kind:
assignToStarred(target, std::move(value));
break;
default:
return;
}
}
void Analyzer::assignToName(const expr_ty target, AnalysisResult value) {
PyObject* id = target->v.Name.id;
const char* name = PyUnicode_AsUTF8(id);
if (value == nullptr) {
stack_.erase(name);
} else {
stack_.set(name, std::move(value));
}
}
void Analyzer::assignToListLike(asdl_seq* elts, AnalysisResult value) {
int eltsSize = asdl_seq_LEN(elts);
// delete case
if (value == nullptr) {
for (int i = 0; i < eltsSize; ++i) {
expr_ty elt = reinterpret_cast<expr_ty>(asdl_seq_GET(elts, i));
assignToTarget(elt, nullptr);
}
return;
}
std::vector<AnalysisResult> rData =
iGetElementsVec(std::move(value), context_);
// check of there is star
int starIdx = -1;
for (int i = 0; i < eltsSize; ++i) {
expr_ty elt = reinterpret_cast<expr_ty>(asdl_seq_GET(elts, i));
if (elt->kind == Starred_kind) {
starIdx = i;
break;
}
}
// assignment with star on the lhs, starred exp cannot be used in delete
if (starIdx >= 0 && eltsSize <= int(rData.size() + 1)) {
// process part before star
for (int i = 0; i < starIdx; ++i) {
expr_ty elt = reinterpret_cast<expr_ty>(asdl_seq_GET(elts, i));
assignToTarget(elt, rData[i]);
}
// process the star part
int restSize = eltsSize - starIdx - 1;
int tailBound = rData.size() - restSize;
std::vector<AnalysisResult> starData(
rData.begin() + starIdx, rData.begin() + tailBound);
AnalysisResult starList = std::make_shared<StrictList>(
ListType(), context_.caller, std::move(starData));
expr_ty starElt = reinterpret_cast<expr_ty>(asdl_seq_GET(elts, starIdx));
assignToTarget(starElt, std::move(starList));
// process the part after star
for (int i = 0; i < restSize; ++i) {
expr_ty elt =
reinterpret_cast<expr_ty>(asdl_seq_GET(elts, starIdx + i + 1));
assignToTarget(elt, rData[tailBound + i]);
}
}
// normal assign
else if (eltsSize == int(rData.size())) {
for (int i = 0; i < eltsSize; ++i) {
expr_ty elt = reinterpret_cast<expr_ty>(asdl_seq_GET(elts, i));
assignToTarget(elt, rData[i]);
}
} else {
// failed to unpack
context_.error<FailedToUnpackException>(std::to_string(eltsSize));
for (int i = 0; i < eltsSize; ++i) {
expr_ty elt = reinterpret_cast<expr_ty>(asdl_seq_GET(elts, i));
assignToTarget(elt, makeUnknown(context_, "<failed unpack>"));
}
}
}
void Analyzer::assignToAttribute(const expr_ty attr, AnalysisResult value) {
auto attrExpr = attr->v.Attribute;
AnalysisResult base = visitExpr(attrExpr.value);
std::string name = PyUnicode_AsUTF8(attrExpr.attr);
if (value == nullptr) {
iDelAttr(std::move(base), name, context_);
} else {
iStoreAttr(std::move(base), name, std::move(value), context_);
}
}
void Analyzer::assignToSubscript(const expr_ty subscr, AnalysisResult value) {
auto subExpr = subscr->v.Subscript;
AnalysisResult base = visitExpr(subExpr.value);
AnalysisResult slice = visitSliceHelper(subExpr.slice);
if (value == nullptr) {
iDelElement(std::move(base), std::move(slice), context_);
} else {
iSetElement(std::move(base), std::move(slice), std::move(value), context_);
}
}
void Analyzer::assignToStarred(const expr_ty starred, AnalysisResult value) {
assignToTarget(starred->v.Starred.value, std::move(value));
}
void Analyzer::analyze() {
try {
visitMod(root_);
} catch (StrictModuleUserException<BaseStrictObject>& exc) {
processUnhandledUserException(exc);
}
}
void Analyzer::analyzeFunction(
std::vector<stmt_ty> body,
SymtableEntry entry,
std::unique_ptr<DictType> callArgs,
AnalysisResult firstArg) {
auto scope = Analyzer::scopeFactory(std::move(entry), std::move(callArgs));
scope->setScopeData(AnalysisScopeData(std::move(firstArg)));
// enter function body scope
auto scopeManager = stack_.enterScope(std::move(scope));
visitStmtSeq(std::move(body));
}
void Analyzer::processUnhandledUserException(
StrictModuleUserException<BaseStrictObject>& exc) {
std::shared_ptr<BaseStrictObject> wrappedObject =
std::static_pointer_cast<BaseStrictObject>(exc.getWrapped());
std::vector<std::string> args;
if (!wrappedObject->isUnknown()) {
auto wrappedInst = std::static_pointer_cast<StrictInstance>(wrappedObject);
AnalysisResult argsV = wrappedInst->getAttr("args");
auto argsSeq = std::dynamic_pointer_cast<StrictSequence>(argsV);
if (argsSeq) {
args.reserve(argsSeq->getData().size());
for (auto& elem : argsSeq->getData()) {
args.push_back(elem->getDisplayName());
}
}
}
context_.errorSink->error<StrictModuleUnhandledException>(
exc.getLineno(),
exc.getCol(),
exc.getFilename(),
exc.getScopeName(),
wrappedObject->getDisplayName(),
std::move(args),
exc.getCause());
}
std::unique_ptr<Analyzer::ScopeT> Analyzer::scopeFactory(
SymtableEntry entry,
std::shared_ptr<DictType> map) {
return std::make_unique<ScopeT>(entry, std::move(map), AnalysisScopeData());
}
void Analyzer::analyzeExec(
int execLino,
int execCol,
std::shared_ptr<StrictDict> globals,
std::shared_ptr<StrictDict> locals) {
// fix the inner caller context's lineno and col
context_.lineno = execLino;
context_.col = execCol;
SymtableEntry entry = stack_.getSymtable().entryFromAst(root_);
auto scope =
Analyzer::scopeFactory(std::move(entry), std::make_unique<DictType>());
scope->setScopeData(AnalysisScopeData(context_, nullptr, std::move(globals)));
if (locals == globals) {
auto scopeManager = stack_.enterScope(std::move(scope));
visitMod(root_);
} else {
auto localScope =
Analyzer::scopeFactory(std::move(entry), std::make_unique<DictType>());
localScope->setScopeData(
AnalysisScopeData(context_, nullptr, std::move(locals)));
auto scopeManagerGlobals = stack_.enterScope(std::move(scope));
auto scopeManagerLocals = stack_.enterScope(std::move(localScope));
try {
visitMod(root_);
} catch (const ImportLoaderUnavailableException&) {
context_.error<ImportDisallowedException>("exec()");
}
}
}
std::optional<AnalysisResult> Analyzer::getFromScope(const std::string& name) {
std::optional<AnalysisResult> result;
ScopeT* resultScope;
std::tie(result, resultScope) = stack_.at_and_scope(name);
if (result.has_value() && (*result)->isLazy()) {
assert(resultScope != nullptr);
auto evaluated =
std::static_pointer_cast<StrictLazyObject>(*result)->evaluate();
resultScope->set(name, evaluated);
return evaluated;
} else {
return result;
}
}
// TryFinallyManager
TryFinallyManager::TryFinallyManager(Analyzer& analyzer, asdl_seq* finalbody)
: analyzer_(analyzer), finalbody_(finalbody) {}
TryFinallyManager::~TryFinallyManager() {
analyzer_.visitStmtSeq(finalbody_);
}
} // namespace strictmod