Fix assign tests

typecheck-v2
Ibrahim Numanagić 2023-09-23 10:01:34 -07:00
parent 6321a03868
commit 99973373e0
8 changed files with 124 additions and 106 deletions

View File

@ -646,6 +646,7 @@ void ClassStmt::parseDecorators() {
attributes.customAttr.insert("deduce");
} else if (d->isId("__notuple__")) {
attributes.customAttr.insert("__notuple__");
} else if (d->isId("dataclass")) {
} else if (auto c = d->getCall()) {
if (c->expr->isId(Attr::Tuple)) {
attributes.set(Attr::Tuple);

View File

@ -35,8 +35,8 @@ void TypecheckVisitor::visit(IdExpr *expr) {
// ctx->getBase()->captures.insert(expr->value);
if (!val) {
ctx->dump();
// LOG("=================================================================");
// ctx->cache->typeCtx->dump();
LOG("=================================================================");
ctx->cache->typeCtx->dump();
E(Error::ID_NOT_FOUND, expr, expr->value);
}
if (expr->type->getUnbound() && in(ctx->cache->overloads, val->canonicalName))

View File

@ -57,6 +57,7 @@ void TypecheckVisitor::visit(DelStmt *stmt) {
if (ctx->getScope() != val->scope)
E(Error::DEL_NOT_ALLOWED, ei, ei->value);
ctx->remove(ei->value);
ctx->remove(ctx->cache->rev(ei->value));
} else {
E(Error::DEL_INVALID, stmt);
}
@ -99,13 +100,15 @@ StmtPtr TypecheckVisitor::transformAssignment(AssignStmt *stmt, bool mustExist)
// Make sure that existing values that cannot be shadowed are only updated
// mustExist |= val && !ctx->isOuter(val);
if (mustExist) {
if (val && val->isVar() && !ctx->isOuter(val)) {
if (val && val->isVar() /*&& !ctx->isOuter(val)*/) {
// commented out: should be handled by namevisitor
auto s = N<AssignStmt>(stmt->lhs, stmt->rhs);
if (ctx->getBase()->attributes && ctx->getBase()->attributes->has(Attr::Atomic))
s->setAtomicUpdate();
else
s->setUpdate();
transformUpdate(s.get());
if (auto u = transformUpdate(s.get()))
return u;
return s;
} else {
E(Error::ASSIGN_LOCAL_REFERENCE, e, e->value, e->getSrcInfo());
@ -172,7 +175,7 @@ StmtPtr TypecheckVisitor::transformAssignment(AssignStmt *stmt, bool mustExist)
/// Transform binding updates. Special handling is done for atomic or in-place
/// statements (e.g., `a += b`).
/// See @c transformInplaceUpdate and @c wrapExpr for details.
void TypecheckVisitor::transformUpdate(AssignStmt *stmt) {
StmtPtr TypecheckVisitor::transformUpdate(AssignStmt *stmt) {
transform(stmt->lhs);
if (stmt->lhs->isStatic())
E(Error::ASSIGN_UNEXPECTED_STATIC, stmt->lhs);
@ -189,11 +192,12 @@ void TypecheckVisitor::transformUpdate(AssignStmt *stmt) {
auto [inPlace, inPlaceExpr] = transformInplaceUpdate(stmt);
if (inPlace) {
if (inPlaceExpr) {
resultStmt = N<ExprStmt>(inPlaceExpr);
auto s = N<ExprStmt>(inPlaceExpr);
if (inPlaceExpr->isDone())
resultStmt->setDone();
s->setDone();
return s;
}
return;
return nullptr;
}
transform(stmt->rhs);
@ -202,6 +206,7 @@ void TypecheckVisitor::transformUpdate(AssignStmt *stmt) {
unify(stmt->rhs->type, stmt->lhs->type);
if (stmt->rhs->done && realize(stmt->lhs->type))
stmt->setDone();
return nullptr;
}
/// Typecheck instance member assignments (e.g., `a.b = c`) and handle optional
@ -282,19 +287,22 @@ std::pair<bool, ExprPtr> TypecheckVisitor::transformInplaceUpdate(AssignStmt *st
auto lhsClass = stmt->lhs->getType()->getClass();
auto call = stmt->rhs->getCall();
if (stmt->isAtomicUpdate() && call && stmt->lhs->getId() &&
(call->expr->isId("min") || call->expr->isId("max")) && call->args.size() == 2 &&
call->args[0].value->isId(std::string(stmt->lhs->getId()->value))) {
// `type(a).__atomic_min__(__ptr__(a), b)`
auto ptrTyp = ctx->instantiateGeneric(stmt->lhs->getSrcInfo(),
ctx->forceFind("Ptr")->type, {lhsClass});
call->args[1].value = transform(call->args[1].value);
auto rhsTyp = call->args[1].value->getType()->getClass();
if (auto method = findBestMethod(
lhsClass, format("__atomic_{}__", call->expr->getId()->value),
{ptrTyp, rhsTyp})) {
return {true, transform(N<CallExpr>(N<IdExpr>(method->ast->name),
N<CallExpr>(N<IdExpr>("__ptr__"), stmt->lhs),
call->args[1].value))};
(call->expr->isId("min") || call->expr->isId("max")) && call->args.size() == 2) {
transform(call->args[0].value);
if (call->args[0].value->isId(std::string(stmt->lhs->getId()->value))) {
// `type(a).__atomic_min__(__ptr__(a), b)`
auto ptrTyp = ctx->instantiateGeneric(stmt->lhs->getSrcInfo(),
ctx->forceFind("Ptr")->type, {lhsClass});
call->args[1].value = transform(call->args[1].value);
auto rhsTyp = call->args[1].value->getType()->getClass();
if (auto method = findBestMethod(
lhsClass, format("__atomic_{}__", call->expr->getId()->value),
{ptrTyp, rhsTyp})) {
return {true,
transform(N<CallExpr>(N<IdExpr>(method->ast->name),
N<CallExpr>(N<IdExpr>("__ptr__"), stmt->lhs),
call->args[1].value))};
}
}
}

View File

@ -89,29 +89,29 @@ void TypecheckVisitor::visit(YieldFromStmt *stmt) {
/// Process `global` statements. Remove them upon completion.
void TypecheckVisitor::visit(GlobalStmt *stmt) {
if (!ctx->inFunction())
E(Error::FN_OUTSIDE_ERROR, stmt, stmt->nonLocal ? "nonlocal" : "global");
// if (!ctx->inFunction())
// E(Error::FN_OUTSIDE_ERROR, stmt, stmt->nonLocal ? "nonlocal" : "global");
// Dominate the binding
auto val = ctx->find(stmt->var);
if (!val || !val->isVar())
E(Error::ID_NOT_FOUND, stmt, stmt->var);
if (val->getBaseName() == ctx->getBaseName())
E(Error::FN_GLOBAL_ASSIGNED, stmt, stmt->var);
// // Dominate the binding
// auto val = ctx->find(stmt->var);
// if (!val || !val->isVar())
// E(Error::ID_NOT_FOUND, stmt, stmt->var);
// if (val->getBaseName() == ctx->getBaseName())
// E(Error::FN_GLOBAL_ASSIGNED, stmt, stmt->var);
// Check global/nonlocal distinction
if (!stmt->nonLocal && !val->getBaseName().empty())
E(Error::FN_GLOBAL_NOT_FOUND, stmt, "global", stmt->var);
else if (stmt->nonLocal && val->getBaseName().empty())
E(Error::FN_GLOBAL_NOT_FOUND, stmt, "nonlocal", stmt->var);
seqassert(!val->canonicalName.empty(), "'{}' does not have a canonical name",
stmt->var);
// // Check global/nonlocal distinction
// if (!stmt->nonLocal && !val->getBaseName().empty())
// E(Error::FN_GLOBAL_NOT_FOUND, stmt, "global", stmt->var);
// else if (stmt->nonLocal && val->getBaseName().empty())
// E(Error::FN_GLOBAL_NOT_FOUND, stmt, "nonlocal", stmt->var);
// seqassert(!val->canonicalName.empty(), "'{}' does not have a canonical name",
// stmt->var);
// Register as global if needed
ctx->cache->addGlobal(val->canonicalName);
// // Register as global if needed
// ctx->cache->addGlobal(val->canonicalName);
val = ctx->addVar(stmt->var, val->canonicalName, val->type);
val->baseName = ctx->getBaseName();
// val = ctx->addVar(stmt->var, val->canonicalName, val->type);
// val->baseName = ctx->getBaseName();
// Erase the statement
resultStmt = N<SuiteStmt>();
}
@ -179,9 +179,9 @@ void TypecheckVisitor::visit(FunctionStmt *stmt) {
// Handle captures. Add additional argument to the function for every capture.
// Make sure to account for **kwargs if present
std::map<std::string, TypeContext::Item> captures;
for (auto &[c, _] : stmt->attributes.captures) {
for (auto &[c, t] : stmt->attributes.captures) {
if (auto v = ctx->find(c)) {
if (!v->isGlobal() && !v->isGeneric()) {
if (t != Attr::CaptureType::Global && !v->isGlobal() && !v->isGeneric()) {
captures[c] = v;
}
}

View File

@ -50,9 +50,10 @@ StmtPtr TypecheckVisitor::inferTypes(StmtPtr result, bool isToplevel) {
for (ctx->getBase()->iteration = 1;; ctx->getBase()->iteration++) {
LOG_TYPECHECK("[iter] {} :: {}", ctx->getBase()->name, ctx->getBase()->iteration);
if (ctx->getBase()->iteration >= MAX_TYPECHECK_ITER)
error(result, "cannot typecheck '{}' in reasonable time",
ctx->cache->rev(ctx->getBase()->name));
if (ctx->getBase()->iteration >= MAX_TYPECHECK_ITER) {
LOG("[error=>] {}", result->toString(2));
error(result, "cannot typecheck '{}' in reasonable time", ctx->getBase()->name);
}
// Keep iterating until:
// (1) success: the statement is marked as done; or

View File

@ -67,25 +67,78 @@ StmtPtr ScopingVisitor::transform(std::shared_ptr<Stmt> &stmt) {
return stmt;
}
void ScopingVisitor::switchToUpdate(std::shared_ptr<SrcObject> binding,
const std::string &name, bool gotUsedVar) {
// These bindings (and their canonical identifiers) will be replaced by the
// dominating binding during the type checking pass.
auto used = format("{}.__used__", name);
if (auto s = std::dynamic_pointer_cast<SuiteStmt>(binding)) {
seqassert(!s->stmts.empty() && s->stmts[0]->getAssign(), "bad suite");
auto a = s->stmts[0]->getAssign();
if (a->rhs) {
a->setUpdate();
if (gotUsedVar && s->stmts.size() < 2) {
s->stmts.push_back(N<AssignStmt>(N<IdExpr>(used), N<BoolExpr>(true), nullptr,
AssignStmt::UpdateMode::Update));
}
} else {
s->stmts.clear();
}
} else if (auto f = std::dynamic_pointer_cast<ForStmt>(binding)) {
f->var->setAttr(ExprAttr::Dominated);
if (gotUsedVar) {
bool skip = false;
if (auto s = f->suite->firstInBlock())
skip = s->getAssign() && s->getAssign()->lhs->isId(used);
if (!skip) {
f->suite = N<SuiteStmt>(N<AssignStmt>(N<IdExpr>(used), N<BoolExpr>(true),
nullptr, AssignStmt::UpdateMode::Update),
f->suite);
}
}
} else if (auto f = std::dynamic_pointer_cast<TryStmt::Catch>(binding)) {
f->exc->setAttr(ExprAttr::Dominated);
if (gotUsedVar) {
bool skip = false;
if (auto s = f->suite->firstInBlock())
skip = s->getAssign() && s->getAssign()->lhs->isId(used);
if (!skip) {
f->suite = N<SuiteStmt>(N<AssignStmt>(N<IdExpr>(used), N<BoolExpr>(true),
nullptr, AssignStmt::UpdateMode::Update),
f->suite);
}
}
} else if (binding) {
// class; function; func-arg; comprehension-arg; catch-name; import-name[anything
// really]
// todo)) generators?!
E(error::Error::ID_INVALID_BIND, binding, name);
}
}
void ScopingVisitor::visitName(const std::string &name, bool adding,
const std::shared_ptr<SrcObject> &root,
const SrcInfo &src) {
if (adding && ctx->inClass)
return;
if (adding) {
if (auto p = in(ctx->captures, name))
if (auto p = in(ctx->captures, name)) {
if (*p == Attr::CaptureType::Read)
E(error::Error::ASSIGN_LOCAL_REFERENCE, ctx->firstSeen[name], name, src);
if (in(ctx->childCaptures, name) && ctx->functionScope) {
auto newScope = std::vector<int>{ctx->scope[0].id};
auto b = N<AssignStmt>(N<IdExpr>(name), nullptr, nullptr);
auto newItem = ScopingVisitor::Context::Item(src, newScope, b);
ctx->scope.front().stmts.emplace_back(b);
ctx->map[name].push_back(newItem);
else if (root) // global, nonlocal
switchToUpdate(root, name, false);
} else {
if (in(ctx->childCaptures, name) && ctx->functionScope) {
auto newScope = std::vector<int>{ctx->scope[0].id};
auto b = N<AssignStmt>(N<IdExpr>(name), nullptr, nullptr);
auto newItem = ScopingVisitor::Context::Item(src, newScope, b);
ctx->scope.front().stmts.emplace_back(b);
ctx->map[name].push_back(newItem);
}
ctx->map[name].emplace_front(src, ctx->getScope(), root);
if (!root)
ctx->temps.back().insert(name);
}
ctx->map[name].emplace_front(src, ctx->getScope(), root);
if (!root)
ctx->temps.back().insert(name);
} else {
if (!in(ctx->firstSeen, name))
ctx->firstSeen[name] = src;
@ -220,56 +273,7 @@ ScopingVisitor::findDominatingBinding(const std::string &name, bool allowShadow)
for (auto i = it->begin(); i != it->end(); i++) {
if (i == lastGood)
break;
// if (!(*i)->canDominate())
// continue;
// These bindings (and their canonical identifiers) will be replaced by the
// dominating binding during the type checking pass.
auto used = format("{}.__used__", name);
if (auto s = std::dynamic_pointer_cast<SuiteStmt>(i->binding)) {
seqassert(!s->stmts.empty() && s->stmts[0]->getAssign(), "bad suite");
auto a = s->stmts[0]->getAssign();
if (a->rhs) {
a->setUpdate();
if (gotUsedVar && s->stmts.size() < 2) {
s->stmts.push_back(N<AssignStmt>(N<IdExpr>(used), N<BoolExpr>(true), nullptr,
AssignStmt::UpdateMode::Update));
}
} else {
s->stmts.clear();
}
} else if (auto f = std::dynamic_pointer_cast<ForStmt>(i->binding)) {
f->var->setAttr(ExprAttr::Dominated);
if (gotUsedVar) {
bool skip = false;
if (auto s = f->suite->firstInBlock())
skip = s->getAssign() && s->getAssign()->lhs->isId(used);
if (!skip) {
f->suite =
N<SuiteStmt>(N<AssignStmt>(N<IdExpr>(used), N<BoolExpr>(true), nullptr,
AssignStmt::UpdateMode::Update),
f->suite);
}
}
} else if (auto f = std::dynamic_pointer_cast<TryStmt::Catch>(i->binding)) {
f->exc->setAttr(ExprAttr::Dominated);
if (gotUsedVar) {
bool skip = false;
if (auto s = f->suite->firstInBlock())
skip = s->getAssign() && s->getAssign()->lhs->isId(used);
if (!skip) {
f->suite =
N<SuiteStmt>(N<AssignStmt>(N<IdExpr>(used), N<BoolExpr>(true), nullptr,
AssignStmt::UpdateMode::Update),
f->suite);
}
}
} else if (i->binding) {
// class; function; func-arg; comprehension-arg; catch-name; import-name[anything
// really]
// todo)) generators?!
E(error::Error::ID_INVALID_BIND, i->binding, name);
}
switchToUpdate(i->binding, name, gotUsedVar);
}
it->erase(it->begin(), lastGood);
return &(*lastGood);

View File

@ -182,7 +182,7 @@ private: // Node typechecking rules
/* Assignments (assign.cpp) */
void visit(AssignExpr *) override;
void visit(AssignStmt *) override;
void transformUpdate(AssignStmt *);
StmtPtr transformUpdate(AssignStmt *);
StmtPtr transformAssignment(AssignStmt *, bool = false);
void unpackAssignments(const ExprPtr &, ExprPtr, std::vector<StmtPtr> &);
void visit(DelStmt *) override;
@ -425,6 +425,7 @@ public:
void transformBlock(StmtPtr &s);
ExprPtr makeAnonFn(std::vector<StmtPtr>, const std::vector<std::string> & = {});
void switchToUpdate(std::shared_ptr<SrcObject> binding, const std::string &, bool);
};
} // namespace codon::ast

View File

@ -146,6 +146,9 @@ class str:
def __repr__(self) -> str:
return f"'{self}'"
set = Set
dict = Dict
from internal.builtin import *
# from openmp import Ident as __OMPIdent, for_par