From 3446d5e58fa4c22b9815febb2b5ed6dfa38c31f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ibrahim=20Numanagic=CC=81?= Date: Sat, 7 Dec 2024 11:49:53 -0800 Subject: [PATCH] New error handling via llvm::Error [wip] --- codon/app/main.cpp | 8 +- codon/cir/module.cpp | 19 +- codon/compiler/compiler.cpp | 35 +- codon/compiler/error.cpp | 19 +- codon/compiler/error.h | 263 ++++----- codon/compiler/jit.cpp | 33 +- codon/parser/ast/attr.cpp | 6 + codon/parser/ast/attr.h | 6 + codon/parser/ast/error.h | 122 ++++- codon/parser/ast/expr.cpp | 90 +-- codon/parser/ast/expr.h | 12 - codon/parser/ast/stmt.cpp | 301 ---------- codon/parser/ast/stmt.h | 8 - codon/parser/ast/types/union.cpp | 16 +- codon/parser/ast/types/union.h | 2 +- codon/parser/cache.cpp | 6 +- codon/parser/cache.h | 3 +- codon/parser/peg/grammar.peg | 15 +- codon/parser/peg/peg.cpp | 49 +- codon/parser/peg/peg.h | 14 +- codon/parser/visitors/doc/doc.cpp | 36 +- codon/parser/visitors/format/format.h | 4 +- codon/parser/visitors/scoping/scoping.cpp | 515 ++++++++++++++---- codon/parser/visitors/scoping/scoping.h | 51 +- codon/parser/visitors/typecheck/access.cpp | 7 +- codon/parser/visitors/typecheck/assign.cpp | 79 +++ codon/parser/visitors/typecheck/call.cpp | 38 +- codon/parser/visitors/typecheck/class.cpp | 5 +- codon/parser/visitors/typecheck/function.cpp | 8 +- codon/parser/visitors/typecheck/import.cpp | 11 +- codon/parser/visitors/typecheck/infer.cpp | 24 +- codon/parser/visitors/typecheck/loops.cpp | 16 +- codon/parser/visitors/typecheck/op.cpp | 9 +- codon/parser/visitors/typecheck/special.cpp | 62 +-- codon/parser/visitors/typecheck/typecheck.cpp | 42 +- codon/parser/visitors/typecheck/typecheck.h | 2 + codon/parser/visitors/visitor.h | 41 -- test/main.cpp | 2 +- 38 files changed, 1009 insertions(+), 970 deletions(-) diff --git a/codon/app/main.cpp b/codon/app/main.cpp index 7d602dae..57e08fa5 100644 --- a/codon/app/main.cpp +++ b/codon/app/main.cpp @@ -64,16 +64,18 @@ std::string makeOutputFilename(const std::string &filename, void display(const codon::error::ParserErrorInfo &e) { using codon::MessageGroupPos; - for (auto &group : e) { + for (auto &group : e.getErrors()) { + int i = 0; for (auto &msg : group) { MessageGroupPos pos = MessageGroupPos::NONE; - if (&msg == &group.front()) { + if (i == 0) { pos = MessageGroupPos::HEAD; - } else if (&msg == &group.back()) { + } else if (i == group.size() - 1) { pos = MessageGroupPos::LAST; } else { pos = MessageGroupPos::MID; } + i++; codon::compilationError(msg.getMessage(), msg.getFile(), msg.getLine(), msg.getColumn(), msg.getLength(), msg.getErrorCode(), /*terminate=*/false, pos); diff --git a/codon/cir/module.cpp b/codon/cir/module.cpp index 71026020..12a99669 100644 --- a/codon/cir/module.cpp +++ b/codon/cir/module.cpp @@ -173,9 +173,10 @@ Func *Module::getOrRealizeMethod(types::Type *parent, const std::string &methodN return cache->realizeFunction(method, translateArgs(cache, args), translateGenerics(cache, generics), cls); } catch (const exc::ParserException &e) { - for (int i = 0; i < e.messages.size(); i++) - LOG_IR("getOrRealizeMethod parser error at {}: {}", e.locations[i], - e.messages[i]); + for (auto &trace : e.getErrors()) + for (auto &msg : trace) + LOG_IR("getOrRealizeMethod parser error at {}: {}", msg.getSrcInfo(), + msg.getMessage()); return nullptr; } } @@ -196,8 +197,10 @@ Func *Module::getOrRealizeFunc(const std::string &funcName, try { return cache->realizeFunction(func, arg, gens); } catch (const exc::ParserException &e) { - for (int i = 0; i < e.messages.size(); i++) - LOG("getOrRealizeFunc parser error at {}: {}", e.locations[i], e.messages[i]); + for (auto &trace : e.getErrors()) + for (auto &msg : trace) + LOG("getOrRealizeFunc parser error at {}: {}", msg.getSrcInfo(), + msg.getMessage()); return nullptr; } } @@ -213,8 +216,10 @@ types::Type *Module::getOrRealizeType(const std::string &typeName, try { return cache->realizeType(type, translateGenerics(cache, generics)); } catch (const exc::ParserException &e) { - for (int i = 0; i < e.messages.size(); i++) - LOG_IR("getOrRealizeType parser error at {}: {}", e.locations[i], e.messages[i]); + for (auto &trace : e.getErrors()) + for (auto &msg : trace) + LOG_IR("getOrRealizeType parser error at {}: {}", msg.getSrcInfo(), + msg.getMessage()); return nullptr; } } diff --git a/codon/compiler/compiler.cpp b/codon/compiler/compiler.cpp index cd65168f..b9a23f45 100644 --- a/codon/compiler/compiler.cpp +++ b/codon/compiler/compiler.cpp @@ -73,16 +73,18 @@ Compiler::parse(bool isCode, const std::string &file, const std::string &code, input = file; std::string abspath = (file != "-") ? ast::getAbsolutePath(file) : file; try { - ast::Stmt *codeStmt = isCode ? ast::parseCode(cache.get(), abspath, code, startLine) - : ast::parseFile(cache.get(), abspath); + auto nodeOrErr = isCode ? ast::parseCode(cache.get(), abspath, code, startLine) + : ast::parseFile(cache.get(), abspath); + if (!nodeOrErr) + throw exc::ParserException(nodeOrErr.takeError()); + auto codeStmt = *nodeOrErr; cache->module0 = file; Timer t2("typecheck"); t2.logged = true; - auto typechecked = - ast::TypecheckVisitor::apply(cache.get(), std::move(codeStmt), abspath, defines, - getEarlyDefines(), (testFlags > 1)); + auto typechecked = ast::TypecheckVisitor::apply( + cache.get(), codeStmt, abspath, defines, getEarlyDefines(), (testFlags > 1)); LOG_TIME("[T] parse = {:.1f}", totalPeg); LOG_TIME("[T] typecheck = {:.1f}", t2.elapsed() - totalPeg); @@ -115,24 +117,7 @@ Compiler::parse(bool isCode, const std::string &file, const std::string &code, ast::TranslateVisitor::apply(cache.get(), std::move(typechecked)); t4.log(); } catch (const exc::ParserException &exc) { - std::vector messages; - if (exc.messages.empty()) { - const int MAX_ERRORS = 5; - int ei = 0; - for (auto &e : cache->errors) { - for (unsigned i = 0; i < e.messages.size(); i++) { - if (!e.messages[i].empty()) - messages.emplace_back(e.messages[i], e.locations[i].file, - e.locations[i].line, e.locations[i].col, - e.locations[i].len, e.errorCode); - } - if (ei++ > MAX_ERRORS) - break; - } - return llvm::make_error(messages); - } else { - return llvm::make_error(exc); - } + return llvm::make_error(exc.getErrors()); } module->setSrcInfo({abspath, 0, 0, 0}); if (codon::getLogger().flags & codon::Logger::FLAG_USER) { @@ -181,8 +166,8 @@ llvm::Expected Compiler::docgen(const std::vector &fil try { auto j = ast::DocVisitor::apply(argv0, files); return j->toString(); - } catch (exc::ParserException &e) { - return llvm::make_error(e); + } catch (exc::ParserException &exc) { + return llvm::make_error(exc.getErrors()); } } diff --git a/codon/compiler/error.cpp b/codon/compiler/error.cpp index 16e345eb..5a2900e2 100644 --- a/codon/compiler/error.cpp +++ b/codon/compiler/error.cpp @@ -26,15 +26,16 @@ char PluginErrorInfo::ID = 0; char IOErrorInfo::ID = 0; -void raise_error(const char *format) { throw exc::ParserException(format); } - -void raise_error(int e, const ::codon::SrcInfo &info, const char *format) { - throw exc::ParserException(e, format, info); -} - -void raise_error(int e, const ::codon::SrcInfo &info, const std::string &format) { - throw exc::ParserException(e, format, info); -} +void E(llvm::Error &&error) { throw exc::ParserException(std::move(error)); } } // namespace error + +namespace exc { +ParserException::ParserException(llvm::Error &&e) noexcept : std::runtime_error("") { + llvm::handleAllErrors(std::move(e), [this](const error::ParserErrorInfo &e) { + errors = e.getErrors(); + }); +} + +} // namespace exc } // namespace codon diff --git a/codon/compiler/error.h b/codon/compiler/error.h index aacad377..7d6a102c 100644 --- a/codon/compiler/error.h +++ b/codon/compiler/error.h @@ -12,153 +12,6 @@ namespace codon { namespace error { -class Message { -private: - std::string msg; - std::string file; - int line = 0; - int col = 0; - int len = 0; - int errorCode = -1; - -public: - explicit Message(const std::string &msg, const std::string &file = "", int line = 0, - int col = 0, int len = 0, int errorCode = -1) - : msg(msg), file(file), line(line), col(col), len(len), errorCode(-1) {} - - std::string getMessage() const { return msg; } - std::string getFile() const { return file; } - int getLine() const { return line; } - int getColumn() const { return col; } - int getLength() const { return len; } - int getErrorCode() const { return errorCode; } - - void log(llvm::raw_ostream &out) const { - if (!getFile().empty()) { - out << getFile(); - if (getLine() != 0) { - out << ":" << getLine(); - if (getColumn() != 0) { - out << ":" << getColumn(); - } - } - out << ": "; - } - out << getMessage(); - } -}; - -class ParserErrorInfo : public llvm::ErrorInfo { -private: - std::vector> messages; - -public: - explicit ParserErrorInfo(const std::vector &m) : messages() { - for (auto &msg : m) { - messages.push_back({msg}); - } - } - explicit ParserErrorInfo(const exc::ParserException &e) : messages() { - std::vector group; - for (unsigned i = 0; i < e.messages.size(); i++) { - if (!e.messages[i].empty()) - group.emplace_back(e.messages[i], e.locations[i].file, e.locations[i].line, - e.locations[i].col, e.locations[i].len); - } - messages.push_back(group); - } - - auto begin() { return messages.begin(); } - auto end() { return messages.end(); } - auto begin() const { return messages.begin(); } - auto end() const { return messages.end(); } - - void log(llvm::raw_ostream &out) const override { - for (auto &group : messages) { - for (auto &msg : group) { - msg.log(out); - out << "\n"; - } - } - } - - std::error_code convertToErrorCode() const override { - return llvm::inconvertibleErrorCode(); - } - - static char ID; -}; - -class RuntimeErrorInfo : public llvm::ErrorInfo { -private: - std::string output; - std::string type; - Message message; - std::vector backtrace; - -public: - RuntimeErrorInfo(const std::string &output, const std::string &type, - const std::string &msg, const std::string &file = "", int line = 0, - int col = 0, std::vector backtrace = {}) - : output(output), type(type), message(msg, file, line, col), - backtrace(std::move(backtrace)) {} - - std::string getOutput() const { return output; } - std::string getType() const { return type; } - std::string getMessage() const { return message.getMessage(); } - std::string getFile() const { return message.getFile(); } - int getLine() const { return message.getLine(); } - int getColumn() const { return message.getColumn(); } - std::vector getBacktrace() const { return backtrace; } - - void log(llvm::raw_ostream &out) const override { - out << type << ": "; - message.log(out); - } - - std::error_code convertToErrorCode() const override { - return llvm::inconvertibleErrorCode(); - } - - static char ID; -}; - -class PluginErrorInfo : public llvm::ErrorInfo { -private: - std::string message; - -public: - explicit PluginErrorInfo(const std::string &message) : message(message) {} - - std::string getMessage() const { return message; } - - void log(llvm::raw_ostream &out) const override { out << message; } - - std::error_code convertToErrorCode() const override { - return llvm::inconvertibleErrorCode(); - } - - static char ID; -}; - -class IOErrorInfo : public llvm::ErrorInfo { -private: - std::string message; - -public: - explicit IOErrorInfo(const std::string &message) : message(message) {} - - std::string getMessage() const { return message; } - - void log(llvm::raw_ostream &out) const override { out << message; } - - std::error_code convertToErrorCode() const override { - return llvm::inconvertibleErrorCode(); - } - - static char ID; -}; - enum Error { CALL_NAME_ORDER, CALL_NAME_STAR, @@ -261,6 +114,111 @@ enum Error { __END__ }; +class ParserErrorInfo : public llvm::ErrorInfo { +private: + ParserErrors errors; + +public: + static char ID; + +public: + explicit ParserErrorInfo(const ErrorMessage &msg) : errors(msg) {} + explicit ParserErrorInfo(const std::vector &msgs) : errors(msgs) {} + explicit ParserErrorInfo(const ParserErrors &errors) : errors(errors) {} + + template + ParserErrorInfo(error::Error e, const codon::SrcInfo &o = codon::SrcInfo(), const TA &...args) { + auto msg = Emsg(e, args...); + errors = ParserErrors(ErrorMessage(msg, o, (int)e)); + } + + const ParserErrors &getErrors() const { return errors; } + ParserErrors &getErrors() { return errors; } + + void log(llvm::raw_ostream &out) const override { + for (const auto &trace : errors) { + for (const auto &msg : trace.getMessages()) { + msg.log(out); + out << "\n"; + } + } + } + + std::error_code convertToErrorCode() const override { + return llvm::inconvertibleErrorCode(); + } +}; + +class RuntimeErrorInfo : public llvm::ErrorInfo { +private: + std::string output; + std::string type; + ErrorMessage message; + std::vector backtrace; + +public: + RuntimeErrorInfo(const std::string &output, const std::string &type, + const std::string &msg, const std::string &file = "", int line = 0, + int col = 0, std::vector backtrace = {}) + : output(output), type(type), message(msg, file, line, col), + backtrace(std::move(backtrace)) {} + + std::string getOutput() const { return output; } + std::string getType() const { return type; } + std::string getMessage() const { return message.getMessage(); } + std::string getFile() const { return message.getFile(); } + int getLine() const { return message.getLine(); } + int getColumn() const { return message.getColumn(); } + std::vector getBacktrace() const { return backtrace; } + + void log(llvm::raw_ostream &out) const override { + out << type << ": "; + message.log(out); + } + + std::error_code convertToErrorCode() const override { + return llvm::inconvertibleErrorCode(); + } + + static char ID; +}; + +class PluginErrorInfo : public llvm::ErrorInfo { +private: + std::string message; + +public: + explicit PluginErrorInfo(const std::string &message) : message(message) {} + + std::string getMessage() const { return message; } + + void log(llvm::raw_ostream &out) const override { out << message; } + + std::error_code convertToErrorCode() const override { + return llvm::inconvertibleErrorCode(); + } + + static char ID; +}; + +class IOErrorInfo : public llvm::ErrorInfo { +private: + std::string message; + +public: + explicit IOErrorInfo(const std::string &message) : message(message) {} + + std::string getMessage() const { return message; } + + void log(llvm::raw_ostream &out) const override { out << message; } + + std::error_code convertToErrorCode() const override { + return llvm::inconvertibleErrorCode(); + } + + static char ID; +}; + template std::string Emsg(Error e, const TA &...args) { switch (e) { /// Validations @@ -489,17 +447,14 @@ template std::string Emsg(Error e, const TA &...args) { } } -/// Raise a parsing error. -void raise_error(const char *format); -/// Raise a parsing error at a source location p. -void raise_error(int e, const codon::SrcInfo &info, const char *format); -void raise_error(int e, const codon::SrcInfo &info, const std::string &format); - template void E(Error e, const codon::SrcInfo &o = codon::SrcInfo(), const TA &...args) { auto msg = Emsg(e, args...); - raise_error((int)e, o, msg); + auto err = ParserErrors(ErrorMessage(msg, o, (int)e)); + throw exc::ParserException(err); } +void E(llvm::Error &&error); + } // namespace error } // namespace codon diff --git a/codon/compiler/jit.cpp b/codon/compiler/jit.cpp index 0822abd0..068c220c 100644 --- a/codon/compiler/jit.cpp +++ b/codon/compiler/jit.cpp @@ -90,11 +90,16 @@ llvm::Expected JIT::compile(const std::string &code, ast::TypeContext bType = *(cache->typeCtx); ast::TranslateContext bTranslate = *(cache->codegenCtx); try { - ast::Stmt *node = ast::parseCode(cache, file.empty() ? JIT_FILENAME : file, code, - /*startLine=*/line); + auto nodeOrErr = ast::parseCode(cache, file.empty() ? JIT_FILENAME : file, code, + /*startLine=*/line); + if (!nodeOrErr) + throw exc::ParserException(nodeOrErr.takeError()); + auto *node = *nodeOrErr; + ast::Stmt **e = &node; while (auto se = ast::cast(*e)) { - if (se->empty()) break; + if (se->empty()) + break; e = &se->back(); } if (e) @@ -104,11 +109,12 @@ llvm::Expected JIT::compile(const std::string &code, cache->N(mode))); } auto tv = ast::TypecheckVisitor(sctx, preamble); - ast::ScopingVisitor::apply(sctx->cache, node); + if (auto err = ast::ScopingVisitor::apply(sctx->cache, node)) + throw exc::ParserException(std::move(err)); node = tv.transform(node); if (!cache->errors.empty()) - throw exc::ParserException(); + throw exc::ParserException(cache->errors); auto typechecked = cache->N(); for (auto &s : *preamble) typechecked->addStmt(s); @@ -128,18 +134,6 @@ llvm::Expected JIT::compile(const std::string &code, return func; } catch (const exc::ParserException &exc) { - std::vector messages; - if (exc.messages.empty()) { - for (auto &e : cache->errors) { - for (unsigned i = 0; i < e.messages.size(); i++) { - if (!e.messages[i].empty()) - messages.emplace_back(e.messages[i], e.locations[i].file, - e.locations[i].line, e.locations[i].col, - e.locations[i].len, e.errorCode); - } - } - } - for (auto &f : cache->functions) for (auto &r : f.second.realizations) if (!(in(bCache.functions, f.first) && @@ -153,10 +147,7 @@ llvm::Expected JIT::compile(const std::string &code, *(cache->typeCtx) = bType; *(cache->codegenCtx) = bTranslate; - if (exc.messages.empty()) - return llvm::make_error(messages); - else - return llvm::make_error(exc); + return llvm::make_error(exc.getErrors()); } } diff --git a/codon/parser/ast/attr.cpp b/codon/parser/ast/attr.cpp index 31acadb6..d5ff4518 100644 --- a/codon/parser/ast/attr.cpp +++ b/codon/parser/ast/attr.cpp @@ -21,6 +21,10 @@ const std::string Attr::HiddenFromUser = "__hidden__"; const std::string Attr::ForceRealize = "__force__"; const std::string Attr::RealizeWithoutSelf = "std.internal.attributes.realize_without_self.0:0"; +const std::string Attr::ParentCallExpr = "parentCallExpr"; +const std::string Attr::TupleCall = "tupleFn"; +const std::string Attr::Validated = "validated"; +const std::string Attr::AutoGenerated = "autogenerated"; const std::string Attr::CVarArg = ".__vararg__"; const std::string Attr::Method = ".__method__"; @@ -36,6 +40,8 @@ const std::string Attr::ClassNoTuple = "__notuple__"; const std::string Attr::Test = "std.internal.attributes.test.0:0"; const std::string Attr::Overload = "overload:0"; const std::string Attr::Export = "std.internal.attributes.export.0:0"; +const std::string Attr::Inline = "std.internal.attributes.inline.0:0"; +const std::string Attr::NoArgReorder = "std.internal.attributes.no_arg_reorder.0:0"; const std::string Attr::ClassMagic = "classMagic"; const std::string Attr::ExprSequenceItem = "exprSequenceItem"; diff --git a/codon/parser/ast/attr.h b/codon/parser/ast/attr.h index 899126fb..22ad7cc3 100644 --- a/codon/parser/ast/attr.h +++ b/codon/parser/ast/attr.h @@ -26,6 +26,10 @@ struct Attr { const static std::string HiddenFromUser; const static std::string ForceRealize; const static std::string RealizeWithoutSelf; // not internal + const static std::string ParentCallExpr; + const static std::string TupleCall; + const static std::string Validated; + const static std::string AutoGenerated; // Compiler-generated attributes const static std::string CVarArg; const static std::string Method; @@ -41,6 +45,8 @@ struct Attr { const static std::string Test; const static std::string Overload; const static std::string Export; + const static std::string Inline; + const static std::string NoArgReorder; // Expression-related attributes const static std::string ClassMagic; const static std::string ExprSequenceItem; diff --git a/codon/parser/ast/error.h b/codon/parser/ast/error.h index 8f5c87c0..9731b959 100644 --- a/codon/parser/ast/error.h +++ b/codon/parser/ast/error.h @@ -2,6 +2,7 @@ #pragma once +#include "llvm/Support/Error.h" #include #include #include @@ -28,6 +29,95 @@ struct SrcInfo { bool operator==(const SrcInfo &src) const; }; +class ErrorMessage { +private: + std::string msg; + SrcInfo loc; + int errorCode = -1; + +public: + explicit ErrorMessage(const std::string &msg, const SrcInfo &loc = SrcInfo(), + int errorCode = -1) + : msg(msg), loc(loc), errorCode(-1) {} + explicit ErrorMessage(const std::string &msg, const std::string &file = "", + int line = 0, int col = 0, int len = 0, int errorCode = -1) + : msg(msg), loc(file, line, col, len), errorCode(-1) {} + + std::string getMessage() const { return msg; } + std::string getFile() const { return loc.file; } + int getLine() const { return loc.line; } + int getColumn() const { return loc.col; } + int getLength() const { return loc.len; } + int getErrorCode() const { return errorCode; } + SrcInfo getSrcInfo() const { return loc; } + void setSrcInfo(const SrcInfo &s) { loc = s; } + + void log(llvm::raw_ostream &out) const { + if (!getFile().empty()) { + out << getFile(); + if (getLine() != 0) { + out << ":" << getLine(); + if (getColumn() != 0) { + out << ":" << getColumn(); + } + } + out << ": "; + } + out << getMessage(); + } +}; + +struct ParserErrors { + struct Backtrace { + std::vector trace; + const std::vector &getMessages() const { return trace; } + auto begin() const { return trace.begin(); } + auto front() const { return trace.front(); } + auto front() { return trace.front(); } + auto end() const { return trace.end(); } + auto back() { return trace.back(); } + auto back() const { return trace.back(); } + auto size() const { return trace.size(); } + void addMessage(const std::string &msg, const SrcInfo &info = SrcInfo()) { + trace.emplace_back(msg, info); + } + }; + std::vector errors; + + ParserErrors() {} + ParserErrors(const ErrorMessage &msg) : errors{Backtrace{{msg}}} {} + ParserErrors(const std::string &msg, const SrcInfo &info) + : ParserErrors({msg, info}) {} + ParserErrors(const std::string &msg) : ParserErrors(msg, {}) {} + ParserErrors(const ParserErrors &e) : errors(e.errors) {} + ParserErrors(const std::vector &m) : ParserErrors() { + for (auto &msg : m) + errors.push_back(Backtrace{{msg}}); + } + + auto begin() { return errors.begin(); } + auto end() { return errors.end(); } + auto begin() const { return errors.begin(); } + auto end() const { return errors.end(); } + auto empty() const { return errors.empty(); } + auto size() const { return errors.size(); } + void append(const ParserErrors &e) { + errors.insert(errors.end(), e.errors.begin(), e.errors.end()); + } + + Backtrace &getLast() { + assert(!empty() && "empty error trace"); + return errors.back(); + } + + /// Add an error message to the current backtrace + void addError(const std::vector &trace) { errors.push_back({trace}); } + std::string getMessage() const { + if (empty()) + return ""; + return errors.front().trace.front().getMessage(); + } +}; } // namespace codon namespace codon::exc { @@ -37,38 +127,16 @@ namespace codon::exc { * Used for parsing, transformation and type-checking errors. */ class ParserException : public std::runtime_error { -public: /// These vectors (stacks) store an error stack-trace. - std::vector locations; - std::vector messages; - int errorCode = -1; + ParserErrors errors; public: - ParserException(int errorCode, const std::string &msg, const SrcInfo &info) noexcept - : std::runtime_error(msg), errorCode(errorCode) { - messages.push_back(msg); - locations.push_back(info); - } ParserException() noexcept : std::runtime_error("") {} - ParserException(int errorCode, const std::string &msg) noexcept - : ParserException(errorCode, msg, {}) {} - explicit ParserException(const std::string &msg) noexcept - : ParserException(-1, msg, {}) {} - ParserException(const ParserException &e) noexcept - : std::runtime_error(e), locations(e.locations), messages(e.messages), - errorCode(e.errorCode) {} + ParserException(const ParserErrors &errors) noexcept + : std::runtime_error(errors.getMessage()), errors(errors) {} + ParserException(llvm::Error &&e) noexcept; - /// Add an error message to the current stack trace - void trackRealize(const std::string &msg, const SrcInfo &info) { - locations.push_back(info); - messages.push_back("during the realization of " + msg); - } - - /// Add an error message to the current stack trace - void track(const std::string &msg, const SrcInfo &info) { - locations.push_back(info); - messages.push_back(msg); - } + ParserErrors getErrors() const { return errors; } }; } // namespace codon::exc diff --git a/codon/parser/ast/expr.cpp b/codon/parser/ast/expr.cpp index 2d544b55..cc64304d 100644 --- a/codon/parser/ast/expr.cpp +++ b/codon/parser/ast/expr.cpp @@ -39,7 +39,6 @@ Expr::Expr(const Expr &expr, bool clean) : Expr(expr) { done = false; } } -void Expr::validate() const {} types::ClassType *Expr::getClassType() const { return type ? type->getClass() : nullptr; } @@ -51,13 +50,13 @@ std::string Expr::wrapType(const std::string &sexpr) const { type && !done ? format(" #:type \"{}\"", type->debugString(2)) : ""); return s; } -Expr *Expr::operator<<(types::Type *t) { - seqassert(type, "lhs is nullptr"); - if ((*type) << t) { - E(Error::TYPE_UNIFY, getSrcInfo(), type->prettyString(), t->prettyString()); - } - return this; -} +// llvm::Expected *Expr::operator<<(types::Type *t) { +// seqassert(type, "lhs is nullptr"); +// if ((*type) << t) { +// E(Error::TYPE_UNIFY, getSrcInfo(), type->prettyString(), t->prettyString()); +// } +// return this; +// } Param::Param(std::string name, Expr *type, Expr *defaultValue, int status) : name(std::move(name)), type(type), defaultValue(defaultValue) { @@ -162,9 +161,7 @@ std::string FloatExpr::toString(int) const { } StringExpr::StringExpr(std::vector s) - : AcceptorExtend(), strings(std::move(s)) { - unpack(); -} + : AcceptorExtend(), strings(std::move(s)) {} StringExpr::StringExpr(std::string value, std::string prefix) : StringExpr(std::vector{{value, prefix}}) {} StringExpr::StringExpr(const StringExpr &expr, bool clean) @@ -186,60 +183,6 @@ std::string StringExpr::getValue() const { bool StringExpr::isSimple() const { return strings.size() == 1 && strings[0].prefix.empty(); } -void StringExpr::unpack() { - std::vector exprs; - for (auto &p : strings) { - if (p.prefix == "f" || p.prefix == "F") { - /// Transform an F-string - for (auto pf : unpackFString(p.value)) { - if (pf.prefix.empty() && !exprs.empty() && exprs.back().prefix.empty()) { - exprs.back().value += pf.value; - } else { - exprs.emplace_back(pf); - } - } - } else if (!p.prefix.empty()) { - exprs.emplace_back(p); - } else if (!exprs.empty() && exprs.back().prefix.empty()) { - exprs.back().value += p.value; - } else { - exprs.emplace_back(p); - } - } - strings = exprs; -} -std::vector -StringExpr::unpackFString(const std::string &value) const { - // Strings to be concatenated - std::vector items; - int braceCount = 0, braceStart = 0; - for (int i = 0; i < value.size(); i++) { - if (value[i] == '{') { - if (braceStart < i) - items.emplace_back(value.substr(braceStart, i - braceStart)); - if (!braceCount) - braceStart = i + 1; - braceCount++; - } else if (value[i] == '}') { - braceCount--; - if (!braceCount) { - std::string code = value.substr(braceStart, i - braceStart); - auto offset = getSrcInfo(); - offset.col += i; - items.emplace_back(code, "#f"); - items.back().setSrcInfo(offset); - } - braceStart = i + 1; - } - } - if (braceCount > 0) - E(Error::STR_FSTRING_BALANCE_EXTRA, getSrcInfo()); - if (braceCount < 0) - E(Error::STR_FSTRING_BALANCE_MISSING, getSrcInfo()); - if (braceStart != value.size()) - items.emplace_back(value.substr(braceStart, value.size() - braceStart)); - return items; -} IdExpr::IdExpr(std::string value) : AcceptorExtend(), value(std::move(value)) {} IdExpr::IdExpr(const IdExpr &expr, bool clean) @@ -447,7 +390,6 @@ PipeExpr::PipeExpr(std::vector items) PipeExpr::PipeExpr(const PipeExpr &expr, bool clean) : AcceptorExtend(expr, clean), Items(ast::clone(expr.items, clean)), inTypes(expr.inTypes) {} -void PipeExpr::validate() const {} std::string PipeExpr::toString(int indent) const { std::vector s; for (auto &i : items) @@ -483,28 +425,12 @@ CallExpr::CallExpr(const CallExpr &expr, bool clean) CallExpr::CallExpr(Expr *expr, std::vector args) : AcceptorExtend(), Items(std::move(args)), expr(expr), ordered(false), partial(false) { - validate(); } CallExpr::CallExpr(Expr *expr, std::vector args) : AcceptorExtend(), Items({}), expr(expr), ordered(false), partial(false) { for (auto a : args) if (a) items.emplace_back("", a); - validate(); -} -void CallExpr::validate() const { - bool namesStarted = false, foundEllipsis = false; - for (auto &a : *this) { - if (a.name.empty() && namesStarted && - !(cast(a.value) || cast(a.value))) - E(Error::CALL_NAME_ORDER, a.value); - if (!a.name.empty() && (cast(a.value) || cast(a.value))) - E(Error::CALL_NAME_STAR, a.value); - if (cast(a.value) && foundEllipsis) - E(Error::CALL_ELLIPSIS, a.value); - foundEllipsis |= bool(cast(a.value)); - namesStarted |= !a.name.empty(); - } } std::string CallExpr::toString(int indent) const { std::vector s; diff --git a/codon/parser/ast/expr.h b/codon/parser/ast/expr.h index 3803bfd1..7a0e3488 100644 --- a/codon/parser/ast/expr.h +++ b/codon/parser/ast/expr.h @@ -52,8 +52,6 @@ struct Expr : public AcceptorExtend { void setDone() { done = true; } Expr *getOrigExpr() const { return origExpr; } void setOrigExpr(Expr *orig) { origExpr = orig; } - /// Validate a node. Throw ParseASTException if a node is not valid. - void validate() const; static const char NodeId; SERIALIZE(Expr, BASE(ASTNode), /*type,*/ done, origExpr); @@ -211,12 +209,6 @@ struct StringExpr : public AcceptorExtend { private: std::vector strings; - void unpack(); - /// Split a Python-like f-string into a list: - /// `f"foo {x+1} bar"` -> `["foo ", str(x+1), " bar"] - /// Supports "{x=}" specifier (that prints the raw expression as well): - /// `f"{x+1=}"` -> `["x+1=", str(x+1)]` - std::vector unpackFString(const std::string &) const; auto begin() { return strings.begin(); } auto end() { return strings.end(); } @@ -430,8 +422,6 @@ private: /// Output type of a "prefix" pipe ending at the index position. /// Example: for a |> b |> c, inTypes[1] is typeof(a |> b). std::vector inTypes; - - void validate() const; }; /// Index expression (expr[index]). @@ -489,8 +479,6 @@ private: bool ordered; /// True if the call is partial bool partial = false; - - void validate() const; }; /// Dot (access) expression (expr.member). diff --git a/codon/parser/ast/stmt.cpp b/codon/parser/ast/stmt.cpp index 18694d2b..962d5605 100644 --- a/codon/parser/ast/stmt.cpp +++ b/codon/parser/ast/stmt.cpp @@ -96,89 +96,6 @@ std::string AssignStmt::toString(int indent) const { type ? format(" #:type {}", type->toString(indent)) : ""); } -/// Unpack an assignment expression `lhs = rhs` into a list of simple assignment -/// expressions (e.g., `a = b`, `a.x = b`, or `a[x] = b`). -/// Handle Python unpacking rules. -/// @example -/// `(a, b) = c` -> `a = c[0]; b = c[1]` -/// `a, b = c` -> `a = c[0]; b = c[1]` -/// `[a, *x, b] = c` -> `a = c[0]; x = c[1:-1]; b = c[-1]`. -/// Non-trivial right-hand expressions are first stored in a temporary variable. -/// @example -/// `a, b = c, d + foo()` -> `assign = (c, d + foo); a = assign[0]; b = assign[1]`. -/// Each assignment is unpacked recursively to allow cases like `a, (b, c) = d`. -Stmt *AssignStmt::unpack() const { - std::vector leftSide; - if (auto et = cast(lhs)) { - // Case: (a, b) = ... - for (auto *i : *et) - leftSide.push_back(i); - } else if (auto el = cast(lhs)) { - // Case: [a, b] = ... - for (auto *i : *el) - leftSide.push_back(i); - } else { - // Case: simple assignment (a = b, a.x = b, or a[x] = b) - return cache->NS(this, lhs, rhs, type); - } - - // Prepare the right-side expression - auto srcPos = rhs; - SuiteStmt *block = cache->NS(this); - auto rhs = this->rhs; - if (!cast(rhs)) { - // Store any non-trivial right-side expression into a variable - auto var = cache->getTemporaryVar("assign"); - rhs = cache->NS(this->rhs, var); - block->addStmt(cache->NS(this, rhs, ast::clone(this->rhs))); - } - - // Process assignments until the fist StarExpr (if any) - size_t st = 0; - for (; st < leftSide.size(); st++) { - if (cast(leftSide[st])) - break; - // Transformation: `leftSide_st = rhs[st]` where `st` is static integer - auto rightSide = - cache->NS(rhs, ast::clone(rhs), cache->NS(rhs, st)); - // Recursively process the assignment because of cases like `(a, (b, c)) = d)` - auto aa = AssignStmt(leftSide[st], rightSide); - aa.cache = cache; - auto ns = aa.unpack(); - block->addStmt(ns); - } - // Process StarExpr (if any) and the assignments that follow it - if (st < leftSide.size() && cast(leftSide[st])) { - // StarExpr becomes SliceExpr (e.g., `b` in `(a, *b, c) = d` becomes `d[1:-2]`) - auto rightSide = cache->NS( - rhs, ast::clone(rhs), - cache->NS(rhs, cache->NS(rhs, st), - // this slice is either [st:] or [st:-lhs_len + st + 1] - leftSide.size() == st + 1 - ? nullptr - : cache->NS(rhs, -leftSide.size() + st + 1), - nullptr)); - auto aa = AssignStmt(cast(leftSide[st])->getExpr(), rightSide); - aa.cache = cache; - auto ns = aa.unpack(); - block->addStmt(ns); - st += 1; - // Process remaining assignments. They will use negative indices (-1, -2 etc.) - // because we do not know how big is StarExpr - for (; st < leftSide.size(); st++) { - if (cast(leftSide[st])) - E(Error::ASSIGN_MULTI_STAR, leftSide[st]); - rightSide = cache->NS( - rhs, ast::clone(rhs), cache->NS(rhs, -int(leftSide.size() - st))); - auto aa = AssignStmt(leftSide[st], rightSide); - aa.cache = cache; - auto ns = aa.unpack(); - block->addStmt(ns); - } - } - return block; -} - DelStmt::DelStmt(Expr *expr) : AcceptorExtend(), expr(expr) {} DelStmt::DelStmt(const DelStmt &stmt, bool clean) : AcceptorExtend(stmt, clean), expr(ast::clone(stmt.expr, clean)) {} @@ -319,7 +236,6 @@ ImportStmt::ImportStmt(Expr *from, Expr *what, std::vector args, Expr *re std::string as, size_t dots, bool isFunction) : AcceptorExtend(), from(from), what(what), as(std::move(as)), dots(dots), args(std::move(args)), ret(ret), isFunction(isFunction) { - validate(); } ImportStmt::ImportStmt(const ImportStmt &stmt, bool clean) : AcceptorExtend(stmt, clean), from(ast::clone(stmt.from, clean)), @@ -337,25 +253,6 @@ std::string ImportStmt::toString(int indent) const { va.empty() ? "" : format(" #:args ({})", join(va)), ret ? format(" #:ret {}", ret->toString(indent)) : ""); } -void ImportStmt::validate() const { - if (from) { - Expr *e = from; - while (auto d = cast(e)) - e = d->getExpr(); - if (!isId(from, "C") && !isId(from, "python")) { - if (!cast(e)) - E(Error::IMPORT_IDENTIFIER, e); - if (!args.empty()) - E(Error::IMPORT_FN, args[0]); - if (ret) - E(Error::IMPORT_FN, ret); - if (what && !cast(what)) - E(Error::IMPORT_IDENTIFIER, what); - } - if (!isFunction && !args.empty()) - E(Error::IMPORT_FN, args[0]); - } -} ExceptStmt::ExceptStmt(const std::string &var, Expr *exc, Stmt *suite) : var(var), exc(exc), suite(SuiteStmt::wrap(suite)) {} @@ -413,7 +310,6 @@ FunctionStmt::FunctionStmt(std::string name, Expr *ret, std::vector args, Stmt *suite, std::vector decorators) : AcceptorExtend(), Items(std::move(args)), name(std::move(name)), ret(ret), suite(SuiteStmt::wrap(suite)), decorators(std::move(decorators)) { - parseDecorators(); } FunctionStmt::FunctionStmt(const FunctionStmt &stmt, bool clean) : AcceptorExtend(stmt, clean), Items(ast::clone(stmt.items, clean)), @@ -438,93 +334,12 @@ std::string FunctionStmt::toString(int indent) const { suite ? suite->toString(indent >= 0 ? indent + INDENT_SIZE : -1) : "(suite)"); } -void FunctionStmt::validate() const { - if (!ret && (hasAttribute(Attr::LLVM) || hasAttribute(Attr::C))) - E(Error::FN_LLVM, getSrcInfo()); - - std::unordered_set seenArgs; - bool defaultsStarted = false, hasStarArg = false, hasKwArg = false; - for (size_t ia = 0; ia < size(); ia++) { - auto &a = items[ia]; - auto [stars, n] = a.getNameWithStars(); - if (stars == 2) { - if (hasKwArg) - E(Error::FN_MULTIPLE_ARGS, a); - if (a.defaultValue) - E(Error::FN_DEFAULT_STARARG, a.defaultValue); - if (ia != size() - 1) - E(Error::FN_LAST_KWARG, a); - hasKwArg = true; - } else if (stars == 1) { - if (hasStarArg) - E(Error::FN_MULTIPLE_ARGS, a); - if (a.defaultValue) - E(Error::FN_DEFAULT_STARARG, a.defaultValue); - hasStarArg = true; - } - if (in(seenArgs, n)) - E(Error::FN_ARG_TWICE, a, n); - seenArgs.insert(n); - if (!a.defaultValue && defaultsStarted && !stars && a.isValue()) - E(Error::FN_DEFAULT, a, n); - defaultsStarted |= bool(a.defaultValue); - if (hasAttribute(Attr::C)) { - if (a.defaultValue) - E(Error::FN_C_DEFAULT, a.defaultValue, n); - if (stars != 1 && !a.type) - E(Error::FN_C_TYPE, a, n); - } - } -} std::string FunctionStmt::signature() const { std::vector s; for (auto &a : items) s.push_back(a.type ? a.type->toString() : "-"); return format("{}", join(s, ":")); } -void FunctionStmt::parseDecorators() { - std::vector newDecorators; - for (auto &d : decorators) { - if (isId(d, Attr::Attribute)) { - if (decorators.size() != 1) - E(Error::FN_SINGLE_DECORATOR, decorators[1], Attr::Attribute); - setAttribute(Attr::Attribute); - } else if (isId(d, Attr::LLVM)) { - setAttribute(Attr::LLVM); - } else if (isId(d, Attr::Python)) { - if (decorators.size() != 1) - E(Error::FN_SINGLE_DECORATOR, decorators[1], Attr::Python); - setAttribute(Attr::Python); - } else if (isId(d, Attr::Internal)) { - setAttribute(Attr::Internal); - } else if (isId(d, Attr::HiddenFromUser)) { - setAttribute(Attr::HiddenFromUser); - } else if (isId(d, Attr::Atomic)) { - setAttribute(Attr::Atomic); - } else if (isId(d, Attr::Property)) { - setAttribute(Attr::Property); - } else if (isId(d, Attr::StaticMethod)) { - setAttribute(Attr::StaticMethod); - } else if (isId(d, Attr::ForceRealize)) { - setAttribute(Attr::ForceRealize); - } else if (isId(d, Attr::C)) { - setAttribute(Attr::C); - } else { - newDecorators.emplace_back(d); - } - } - if (hasAttribute(Attr::C)) { - for (auto &a : items) { - if (a.name.size() > 1 && a.name[0] == '*' && a.name[1] != '*') - setAttribute(Attr::CVarArg); - } - } - if (!items.empty() && !items[0].type && items[0].name == "self") { - setAttribute(Attr::HasSelf); - } - decorators = newDecorators; - validate(); -} size_t FunctionStmt::getStarArgs() const { size_t i = 0; while (i < items.size()) { @@ -616,7 +431,6 @@ ClassStmt::ClassStmt(std::string name, std::vector args, Stmt *suite, this->baseClasses.push_back(b); } } - parseDecorators(); } ClassStmt::ClassStmt(const ClassStmt &stmt, bool clean) : AcceptorExtend(stmt, clean), Items(ast::clone(stmt.items, clean)), @@ -646,122 +460,7 @@ std::string ClassStmt::toString(int indent) const { suite ? suite->toString(indent >= 0 ? indent + INDENT_SIZE : -1) : "(suite)"); } -void ClassStmt::validate() const { - std::unordered_set seen; - if (hasAttribute(Attr::Extend) && !items.empty()) - E(Error::CLASS_EXTENSION, items[0]); - if (hasAttribute(Attr::Extend) && !(baseClasses.empty() && staticBaseClasses.empty())) - E(Error::CLASS_EXTENSION, - baseClasses.empty() ? staticBaseClasses[0] : baseClasses[0]); - for (auto &a : items) { - if (!a.type && !a.defaultValue) - E(Error::CLASS_MISSING_TYPE, a, a.name); - if (in(seen, a.name)) - E(Error::CLASS_ARG_TWICE, a, a.name); - seen.insert(a.name); - } -} bool ClassStmt::isRecord() const { return hasAttribute(Attr::Tuple); } -void ClassStmt::parseDecorators() { - // @tuple(init=, repr=, eq=, order=, hash=, pickle=, container=, python=, add=, - // internal=...) - // @dataclass(...) - // @extend - - std::map tupleMagics = { - {"new", true}, {"repr", false}, {"hash", false}, - {"eq", false}, {"ne", false}, {"lt", false}, - {"le", false}, {"gt", false}, {"ge", false}, - {"pickle", true}, {"unpickle", true}, {"to_py", false}, - {"from_py", false}, {"iter", false}, {"getitem", false}, - {"len", false}, {"to_gpu", false}, {"from_gpu", false}, - {"from_gpu_new", false}, {"tuplesize", true}}; - - for (auto &d : decorators) { - if (isId(d, "deduce")) { - setAttribute(Attr::ClassDeduce); - } else if (isId(d, "__notuple__")) { - setAttribute(Attr::ClassNoTuple); - } else if (isId(d, "dataclass")) { - } else if (auto c = cast(d)) { - if (isId(c->getExpr(), Attr::Tuple)) { - setAttribute(Attr::Tuple); - for (auto &m : tupleMagics) - m.second = true; - } else if (!isId(c->getExpr(), "dataclass")) { - E(Error::CLASS_BAD_DECORATOR, c->getExpr()); - } else if (hasAttribute(Attr::Tuple)) { - E(Error::CLASS_CONFLICT_DECORATOR, c, "dataclass", Attr::Tuple); - } - for (const auto &a : *c) { - auto b = cast(a); - if (!b) - E(Error::CLASS_NONSTATIC_DECORATOR, a); - char val = char(b->getValue()); - if (a.getName() == "init") { - tupleMagics["new"] = val; - } else if (a.getName() == "repr") { - tupleMagics["repr"] = val; - } else if (a.getName() == "eq") { - tupleMagics["eq"] = tupleMagics["ne"] = val; - } else if (a.getName() == "order") { - tupleMagics["lt"] = tupleMagics["le"] = tupleMagics["gt"] = - tupleMagics["ge"] = val; - } else if (a.getName() == "hash") { - tupleMagics["hash"] = val; - } else if (a.getName() == "pickle") { - tupleMagics["pickle"] = tupleMagics["unpickle"] = val; - } else if (a.getName() == "python") { - tupleMagics["to_py"] = tupleMagics["from_py"] = val; - } else if (a.getName() == "gpu") { - tupleMagics["to_gpu"] = tupleMagics["from_gpu"] = - tupleMagics["from_gpu_new"] = val; - } else if (a.getName() == "container") { - tupleMagics["iter"] = tupleMagics["getitem"] = val; - } else { - E(Error::CLASS_BAD_DECORATOR_ARG, a); - } - } - } else if (isId(d, Attr::Tuple)) { - if (hasAttribute(Attr::Tuple)) - E(Error::CLASS_MULTIPLE_DECORATORS, d, Attr::Tuple); - setAttribute(Attr::Tuple); - for (auto &m : tupleMagics) { - m.second = true; - } - } else if (isId(d, Attr::Extend)) { - setAttribute(Attr::Extend); - if (decorators.size() != 1) - E(Error::CLASS_SINGLE_DECORATOR, decorators[decorators[0] == d], Attr::Extend); - } else if (isId(d, Attr::Internal)) { - setAttribute(Attr::Internal); - } else { - E(Error::CLASS_BAD_DECORATOR, d); - } - } - if (hasAttribute(Attr::ClassDeduce)) - tupleMagics["new"] = false; - if (!hasAttribute(Attr::Tuple)) { - tupleMagics["init"] = tupleMagics["new"]; - tupleMagics["new"] = tupleMagics["raw"] = true; - tupleMagics["len"] = false; - } - tupleMagics["dict"] = true; - // Internal classes do not get any auto-generated members. - std::vector magics; - if (!hasAttribute(Attr::Internal)) { - for (auto &m : tupleMagics) - if (m.second) { - if (m.first == "new") - magics.insert(magics.begin(), m.first); - else - magics.push_back(m.first); - } - } - setAttribute(Attr::ClassMagic, std::make_unique(magics)); - - validate(); -} bool ClassStmt::isClassVar(const Param &p) { if (!p.defaultValue) return false; diff --git a/codon/parser/ast/stmt.h b/codon/parser/ast/stmt.h index fc6f7d30..e919abda 100644 --- a/codon/parser/ast/stmt.h +++ b/codon/parser/ast/stmt.h @@ -47,8 +47,6 @@ struct Stmt : public AcceptorExtend { /// @return the first statement in a suite; if a statement is not a suite, returns the /// statement itself virtual Stmt *firstInBlock() { return this; } - /// Validate a node. Throw ParseASTException if a node is not valid. - void validate() const; static const char NodeId; SERIALIZE(Stmt, BASE(ASTNode), done); @@ -133,7 +131,6 @@ struct AssignStmt : public AcceptorExtend { bool isAtomicUpdate() const { return update == UpdateAtomic; } void setUpdate() { update = Update; } void setAtomicUpdate() { update = UpdateAtomic; } - Stmt *unpack() const; ACCEPT(AssignStmt, ASTVisitor, lhs, rhs, type, update); @@ -376,7 +373,6 @@ struct ImportStmt : public AcceptorExtend { Expr *getReturnType() const { return ret; } const std::vector &getArgs() const { return args; } bool isCVar() const { return !isFunction; } - void validate() const; ACCEPT(ImportStmt, ASTVisitor, from, what, as, dots, args, ret, isFunction); @@ -494,8 +490,6 @@ struct FunctionStmt : public AcceptorExtend, Items { std::string signature() const; size_t getStarArgs() const; size_t getKwStarArgs() const; - void validate() const; - void parseDecorators(); std::string getDocstr() const; std::unordered_set getNonInferrableGenerics() const; @@ -532,8 +526,6 @@ struct ClassStmt : public AcceptorExtend, Items { /// @return true if a class is a tuple-like record (e.g. has a "@tuple" attribute) bool isRecord() const; std::string getDocstr() const; - void validate() const; - void parseDecorators(); static bool isClassVar(const Param &p); diff --git a/codon/parser/ast/types/union.cpp b/codon/parser/ast/types/union.cpp index fb8d8ba7..a1f3ceb8 100644 --- a/codon/parser/ast/types/union.cpp +++ b/codon/parser/ast/types/union.cpp @@ -101,22 +101,24 @@ std::string UnionType::realizedName() const { return ClassType::realizedName(); } -void UnionType::addType(Type *typ) { +bool UnionType::addType(Type *typ) { seqassert(!isSealed(), "union already sealed"); if (this == typ) - return; + return true; if (auto tu = typ->getUnion()) { if (tu->isSealed()) { for (auto &t : tu->generics[0].type->getClass()->generics) - addType(t.type.get()); + if (!addType(t.type.get())) + return false; } else { for (auto &t : tu->pendingTypes) { if (t->getLink() && t->getLink()->kind == LinkType::Unbound) break; - else - addType(t.get()); + else if (!addType(t.get())) + return false; } } + return true; } else { // Find first pending generic to which we can attach this! Unification us; @@ -124,10 +126,10 @@ void UnionType::addType(Type *typ) { if (auto l = t->getLink()) { if (l->kind == LinkType::Unbound) { t->unify(typ, &us); - return; + return true; } } - E(error::Error::UNION_TOO_BIG, this, pendingTypes.size()); + return false; } } diff --git a/codon/parser/ast/types/union.h b/codon/parser/ast/types/union.h index 5fbef865..a88c4d06 100644 --- a/codon/parser/ast/types/union.h +++ b/codon/parser/ast/types/union.h @@ -34,7 +34,7 @@ public: UnionType *getUnion() override { return this; } - void addType(Type *); + bool addType(Type *); void seal(); std::vector getRealizationTypes(); }; diff --git a/codon/parser/cache.cpp b/codon/parser/cache.cpp index 7c6eeaef..84b8fbc7 100644 --- a/codon/parser/cache.cpp +++ b/codon/parser/cache.cpp @@ -170,9 +170,11 @@ ir::types::Type *Cache::makeUnion(const std::vector &types) { } void Cache::parseCode(const std::string &code) { - auto node = ast::parseCode(this, "", code, /*startLine=*/0); + auto nodeOrErr = ast::parseCode(this, "", code, /*startLine=*/0); + if (nodeOrErr) + throw exc::ParserException(nodeOrErr.takeError()); auto sctx = imports[MAIN_IMPORT].ctx; - node = ast::TypecheckVisitor::apply(sctx, node); + auto node = ast::TypecheckVisitor::apply(sctx, *nodeOrErr); ast::TranslateVisitor(codegenCtx).translateStmts(node); } diff --git a/codon/parser/cache.h b/codon/parser/cache.h index aa7a1c31..5e4404e2 100644 --- a/codon/parser/cache.h +++ b/codon/parser/cache.h @@ -33,6 +33,7 @@ #define VAR_CLASS_TOPLEVEL ":toplevel" #define VAR_ARGV "__argv__" +#define MAX_ERRORS 5 #define MAX_TUPLE 2048 #define MAX_INT_WIDTH 10000 #define MAX_REALIZATION_DEPTH 200 @@ -255,7 +256,7 @@ struct Cache { std::unordered_map generatedTuples; std::vector> generatedTupleNames = {{}}; - std::vector errors; + ParserErrors errors; /// Set if Codon operates in Python compatibility mode (e.g., with Python numerics) bool pythonCompat = false; diff --git a/codon/parser/peg/grammar.peg b/codon/parser/peg/grammar.peg index 73afcb83..72fd1bbc 100644 --- a/codon/parser/peg/grammar.peg +++ b/codon/parser/peg/grammar.peg @@ -114,18 +114,14 @@ assignment <- / (star_targets _ (!'==' '=') _)+ star_expressions !(_ '=') { vector stmts; for (int i = int(VS.size()) - 2; i >= 0; i--) { - auto a = AssignStmt(ac_expr(VS[i]), ac_expr(VS[i + 1])); - a.setSrcInfo(LOC); - a.cache = CTX.cache; - stmts.push_back(a.unpack()); + auto a = asts(Assign, LOC, ac_expr(VS[i]), ac_expr(VS[i + 1])); + stmts.push_back(a); } return asts(Suite, LOC, std::move(stmts)); } / star_expression _ augassign '=' ^ _ star_expressions { - auto a = AssignStmt(ac_expr(V0), aste(Binary, LOC, clone(ac_expr(V0)), ac(V1), ac_expr(V2), true)); - a.setSrcInfo(LOC); - a.cache = CTX.cache; - return a.unpack(); + auto a = asts(Assign, LOC, ac_expr(V0), aste(Binary, LOC, clone(ac_expr(V0)), ac(V1), ac_expr(V2), true)); + return a; } augassign <- < '+' / '-' / '**' / '*' / '@' / '//' / '/' / '%' / '&' / '|' / '^' / '<<' / '>>' @@ -320,7 +316,6 @@ function <- auto fn = (FunctionStmt*)(ac_stmt(V1)); fn->setDecorators(ac>(V0)); fn->setSuite(SuiteStmt::wrap(asts(Expr, LOC, aste(String, LOC, ac(V2))))); - fn->parseDecorators(); return (Stmt*)fn; } / decorators? function_def _ suite { @@ -328,7 +323,6 @@ function <- if (VS.size() > 2) fn->setDecorators(ac>(V0)); fn->setSuite(SuiteStmt::wrap(ac_stmt(VS.size() > 2 ? V2 : V1))); - fn->parseDecorators(); return (Stmt*)fn; } extern <- (empty_line* EXTERNDENT (!EOL .)* EOL empty_line*)+ { @@ -395,7 +389,6 @@ class <- decorators? class_def { if (VS.size() == 2) { auto fn = ac_stmt(V1); cast(fn)->setDecorators(ac>(V0)); - cast(fn)->parseDecorators(); return fn; } return ac_stmt(V0); diff --git a/codon/parser/peg/peg.cpp b/codon/parser/peg/peg.cpp index b42df19b..e759b10d 100644 --- a/codon/parser/peg/peg.cpp +++ b/codon/parser/peg/peg.cpp @@ -53,15 +53,16 @@ std::shared_ptr initParser() { } template -T parseCode(Cache *cache, const std::string &file, const std::string &code, - int line_offset, int col_offset, const std::string &rule) { +llvm::Expected parseCode(Cache *cache, const std::string &file, + const std::string &code, int line_offset, int col_offset, + const std::string &rule) { Timer t(""); t.logged = true; // Initialize if (!grammar) grammar = initParser(); - std::vector> errors; + std::vector errors; auto log = [&](size_t line, size_t col, const std::string &msg, const std::string &) { size_t ed = msg.size(); if (startswith(msg, "syntax error, unexpected")) { @@ -69,7 +70,7 @@ T parseCode(Cache *cache, const std::string &file, const std::string &code, if (i != std::string::npos) ed = i; } - errors.emplace_back(line, col, msg.substr(0, ed)); + errors.emplace_back(msg.substr(0, ed), file, line, col); }; T result; auto ctx = std::make_any(cache, 0, line_offset, col_offset); @@ -79,33 +80,27 @@ T parseCode(Cache *cache, const std::string &file, const std::string &code, if (!ret) r.error_info.output_log(log, code.c_str(), code.size()); totalPeg += t.elapsed(); - exc::ParserException ex; - if (!errors.empty()) { - for (auto &e : errors) - ex.track(fmt::format("{}", std::get<2>(e)), - SrcInfo(file, std::get<0>(e), std::get<1>(e), 0)); - throw ex; - return T(); - } + + if (!errors.empty()) + return llvm::make_error(errors); return result; } -Stmt *parseCode(Cache *cache, const std::string &file, const std::string &code, - int line_offset) { +llvm::Expected parseCode(Cache *cache, const std::string &file, + const std::string &code, int line_offset) { return parseCode(cache, file, code + "\n", line_offset, 0, "program"); } -std::pair +llvm::Expected> parseExpr(Cache *cache, const std::string &code, const codon::SrcInfo &offset) { auto newCode = code; ltrim(newCode); rtrim(newCode); - auto e = parseCode>( + return parseCode>( cache, offset.file, newCode, offset.line, offset.col, "fstring"); - return e; } -Stmt *parseFile(Cache *cache, const std::string &file) { +llvm::Expected parseFile(Cache *cache, const std::string &file) { std::vector lines; std::string code; if (file == "-") { @@ -116,7 +111,8 @@ Stmt *parseFile(Cache *cache, const std::string &file) { } else { std::ifstream fin(file); if (!fin) - E(error::Error::COMPILER_NO_FILE, SrcInfo(), file); + return llvm::make_error(error::Error::COMPILER_NO_FILE, + SrcInfo(), file); for (std::string line; getline(fin, line);) { lines.push_back(line); code += line + "\n"; @@ -143,14 +139,14 @@ std::shared_ptr initOpenMPParser() { return g; } -std::vector parseOpenMP(Cache *cache, const std::string &code, - const codon::SrcInfo &loc) { +llvm::Expected> parseOpenMP(Cache *cache, const std::string &code, + const codon::SrcInfo &loc) { if (!ompGrammar) ompGrammar = initOpenMPParser(); - std::vector> errors; + std::vector errors; auto log = [&](size_t line, size_t col, const std::string &msg, const std::string &) { - errors.emplace_back(line, col, msg); + errors.emplace_back(fmt::format("openmp: {}", msg), loc.file, loc.line, loc.col); }; std::vector result; auto ctx = std::make_any(cache, 0, 0, 0); @@ -159,11 +155,8 @@ std::vector parseOpenMP(Cache *cache, const std::string &code, auto ret = r.ret && r.len == code.size(); if (!ret) r.error_info.output_log(log, code.c_str(), code.size()); - exc::ParserException ex; - if (!errors.empty()) { - ex.track(fmt::format("openmp {}", std::get<2>(errors[0])), loc); - throw ex; - } + if (!errors.empty()) + return llvm::make_error(errors); return result; } diff --git a/codon/parser/peg/peg.h b/codon/parser/peg/peg.h index 1cea468d..ab6278d7 100644 --- a/codon/parser/peg/peg.h +++ b/codon/parser/peg/peg.h @@ -12,21 +12,19 @@ namespace codon::ast { - - /// Parse a Seq code block with the appropriate file and position offsets. -Stmt *parseCode(Cache *cache, const std::string &file, const std::string &code, - int line_offset = 0); +llvm::Expected parseCode(Cache *cache, const std::string &file, + const std::string &code, int line_offset = 0); /// Parse a Seq code expression. /// @return pair of Expr * and a format specification /// (empty if not available). -std::pair +llvm::Expected> parseExpr(Cache *cache, const std::string &code, const codon::SrcInfo &offset); /// Parse a Seq file. -Stmt *parseFile(Cache *cache, const std::string &file); +llvm::Expected parseFile(Cache *cache, const std::string &file); /// Parse a OpenMP clause. -std::vector parseOpenMP(Cache *cache, const std::string &code, - const codon::SrcInfo &loc); +llvm::Expected> parseOpenMP(Cache *cache, const std::string &code, + const codon::SrcInfo &loc); } // namespace codon::ast diff --git a/codon/parser/visitors/doc/doc.cpp b/codon/parser/visitors/doc/doc.cpp index bbec6f07..9e8fbfd7 100644 --- a/codon/parser/visitors/doc/doc.cpp +++ b/codon/parser/visitors/doc/doc.cpp @@ -17,6 +17,7 @@ using fmt::format; namespace codon::ast { +using namespace error; using namespace matcher; // clang-format off @@ -99,24 +100,30 @@ std::shared_ptr DocVisitor::apply(const std::string &argv0, shared->j = std::make_shared(); auto stdlib = getImportFile(argv0, STDLIB_INTERNAL_MODULE, "", true, ""); - auto ast = ast::parseFile(shared->cache, stdlib->path); - auto core = + auto astOrErr = ast::parseFile(shared->cache, stdlib->path); + if (!astOrErr) + throw exc::ParserException(astOrErr.takeError()); + auto coreOrErr = ast::parseCode(shared->cache, stdlib->path, "from internal.core import *"); + if (!coreOrErr) + throw exc::ParserException(coreOrErr.takeError()); shared->modules[""]->setFilename(stdlib->path); shared->modules[""]->add("__py_numerics__", std::make_shared(shared->itemID++)); shared->modules[""]->add("__py_extension__", std::make_shared(shared->itemID++)); shared->modules[""]->add("__debug__", std::make_shared(shared->itemID++)); shared->modules[""]->add("__apple__", std::make_shared(shared->itemID++)); - DocVisitor(shared->modules[""]).transformModule(std::move(core)); - DocVisitor(shared->modules[""]).transformModule(std::move(ast)); + DocVisitor(shared->modules[""]).transformModule(*coreOrErr); + DocVisitor(shared->modules[""]).transformModule(*astOrErr); auto ctx = std::make_shared(shared); for (auto &f : files) { auto path = getAbsolutePath(f); ctx->setFilename(path); LOG("-> parsing {}", path); - auto ast = ast::parseFile(shared->cache, path); - DocVisitor(ctx).transformModule(std::move(ast)); + auto astOrErr = ast::parseFile(shared->cache, path); + if (!astOrErr) + throw exc::ParserException(astOrErr.takeError()); + DocVisitor(ctx).transformModule(*astOrErr); } shared->cache = nullptr; @@ -206,7 +213,7 @@ void DocVisitor::visit(IntExpr *expr) { void DocVisitor::visit(IdExpr *expr) { auto i = ctx->find(expr->getValue()); if (!i) - error("unknown identifier {}", expr->getValue()); + E(Error::CUSTOM, expr->getSrcInfo(), "unknown identifier {}", expr->getValue()); resultExpr = std::make_shared(*i ? std::to_string(*i) : expr->getValue()); } @@ -391,7 +398,7 @@ void DocVisitor::visit(ImportStmt *stmt) { } if (!cast(e) || !stmt->getArgs().empty() || stmt->getReturnType() || (stmt->getWhat() && !cast(stmt->getWhat()))) - error("invalid import statement"); + E(Error::CUSTOM, stmt->getSrcInfo(), "invalid import statement"); // We have an empty stmt->from in "from .. import". if (!cast(e)->getValue().empty()) dirs.push_back(cast(e)->getValue()); @@ -399,7 +406,7 @@ void DocVisitor::visit(ImportStmt *stmt) { auto ee = cast(e); if (!ee || !stmt->getArgs().empty() || stmt->getReturnType() || (stmt->getWhat() && !cast(stmt->getWhat()))) - error("invalid import statement"); + E(Error::CUSTOM, stmt->getSrcInfo(), "invalid import statement"); // We have an empty stmt->from in "from .. import". if (!ee->getValue().empty()) dirs.push_back(ee->getValue()); @@ -413,7 +420,7 @@ void DocVisitor::visit(ImportStmt *stmt) { // Fetch the import! auto file = getImportFile(ctx->shared->argv0, path, ctx->getFilename()); if (!file) - error(stmt, "cannot locate import '{}'", path); + E(Error::CUSTOM, stmt->getSrcInfo(), "cannot locate import '{}'", path); auto ictx = ctx; auto it = ctx->shared->modules.find(file->path); @@ -421,8 +428,10 @@ void DocVisitor::visit(ImportStmt *stmt) { ctx->shared->modules[file->path] = ictx = std::make_shared(ctx->shared); ictx->setFilename(file->path); LOG("=> parsing {}", file->path); - auto tmp = parseFile(ctx->shared->cache, file->path); - DocVisitor(ictx).transformModule(std::move(tmp)); + auto tmpOrErr = parseFile(ctx->shared->cache, file->path); + if (!tmpOrErr) + throw exc::ParserException(tmpOrErr.takeError()); + DocVisitor(ictx).transformModule(*tmpOrErr); } else { ictx = it->second; } @@ -440,7 +449,8 @@ void DocVisitor::visit(ImportStmt *stmt) { if (auto c = ictx->find(i->getValue())) ctx->add(stmt->getAs().empty() ? i->getValue() : stmt->getAs(), c); else - error(stmt, "symbol '{}' not found in {}", i->getValue(), file->path); + E(Error::CUSTOM, stmt->getSrcInfo(), "symbol '{}' not found in {}", i->getValue(), + file->path); } } diff --git a/codon/parser/visitors/format/format.h b/codon/parser/visitors/format/format.h index 6b9c7fff..ee1b9209 100644 --- a/codon/parser/visitors/format/format.h +++ b/codon/parser/visitors/format/format.h @@ -63,8 +63,8 @@ public: return fmt::format("{}{}{}", t.header, t.transform(stmt), t.footer); } - void defaultVisit(Expr *e) override { error("cannot format {}", *e); } - void defaultVisit(Stmt *e) override { error("cannot format {}", *e); } + void defaultVisit(Expr *e) override { seqassertn(false, "cannot format {}", *e); } + void defaultVisit(Stmt *e) override { seqassertn(false, "cannot format {}", *e); } public: void visit(NoneExpr *) override; diff --git a/codon/parser/visitors/scoping/scoping.cpp b/codon/parser/visitors/scoping/scoping.cpp index 4b5570bb..a50a103f 100644 --- a/codon/parser/visitors/scoping/scoping.cpp +++ b/codon/parser/visitors/scoping/scoping.cpp @@ -11,13 +11,24 @@ #include "codon/parser/visitors/scoping/scoping.h" #include +#define CHECK(x) \ + { \ + if (!(x)) \ + return; \ + } +#define STOP_ERROR(...) \ + do { \ + addError(__VA_ARGS__); \ + return; \ + } while (0) + using fmt::format; using namespace codon::error; using namespace codon::matcher; namespace codon::ast { -void ScopingVisitor::apply(Cache *cache, Stmt *s) { +llvm::Error ScopingVisitor::apply(Cache *cache, Stmt *s) { auto c = std::make_shared(); c->cache = cache; c->functionScope = nullptr; @@ -25,60 +36,72 @@ void ScopingVisitor::apply(Cache *cache, Stmt *s) { v.ctx = c; ConditionalBlock cb(c.get(), s, 0); - v.transform(s); + if (!v.transform(s)) + return llvm::make_error(v.errors); v.processChildCaptures(); + return llvm::Error::success(); } -void ScopingVisitor::transform(Expr *expr) { +bool ScopingVisitor::transform(Expr *expr) { ScopingVisitor v(*this); if (expr) { setSrcInfo(expr->getSrcInfo()); expr->accept(v); + if (!canContinue()) + return false; } + return true; } -void ScopingVisitor::transform(Stmt *stmt) { +bool ScopingVisitor::transform(Stmt *stmt) { ScopingVisitor v(*this); if (stmt) { setSrcInfo(stmt->getSrcInfo()); stmt->accept(v); + if (!canContinue()) + return false; } + return true; } -void ScopingVisitor::transformScope(Expr *e) { +bool ScopingVisitor::transformScope(Expr *e) { if (e) { ConditionalBlock c(ctx.get(), nullptr); - transform(e); + return transform(e); } + return true; } -void ScopingVisitor::transformScope(Stmt *s) { +bool ScopingVisitor::transformScope(Stmt *s) { if (s) { ConditionalBlock c(ctx.get(), s); - transform(s); + return transform(s); } + return true; } -void ScopingVisitor::transformAdding(Expr *e, ASTNode *root) { - seqassert(e, "bad call to transformAdding"); +bool ScopingVisitor::transformAdding(Expr *e, ASTNode *root) { if (cast(e)) { - transform(e); + return transform(e); } else if (auto de = cast(e)) { - transform(e); + if (!transform(e)) + return false; if (!ctx->classDeduce.first.empty() && match(de->getExpr(), M(ctx->classDeduce.first))) ctx->classDeduce.second.insert(de->getMember()); + return true; } else if (cast(e) || cast(e) || cast(e)) { SetInScope s1(&(ctx->adding), true); SetInScope s2(&(ctx->root), root); - transform(e); + return transform(e); } else { - E(Error::ASSIGN_INVALID, e); + seqassert(e, "bad call to transformAdding"); + addError(Error::ASSIGN_INVALID, e); + return false; } } void ScopingVisitor::visit(IdExpr *expr) { - // LOG("-------> {} {} {}", expr->getSrcInfo(), *expr, ctx->getScope()); if (ctx->adding && ctx->tempScope) ctx->renames.back()[expr->getValue()] = ctx->cache->getTemporaryVar(expr->getValue()); @@ -87,44 +110,104 @@ void ScopingVisitor::visit(IdExpr *expr) { expr->setValue(*v); break; } - if (visitName(expr->getValue(), ctx->adding, ctx->root, expr->getSrcInfo())) { + if (visitName(expr->getValue(), ctx->adding, ctx->root, expr->getSrcInfo())) expr->setAttribute(Attr::ExprDominatedUndefCheck); - } } void ScopingVisitor::visit(StringExpr *expr) { - for (auto &s : *expr) - if (s.prefix == "#f") { - auto e = expr->getSrcInfo(); - e.line += s.getSrcInfo().col; - e.col += s.getSrcInfo().col; - auto [expr, format] = parseExpr(ctx->cache, s.value, e); - s.expr = expr; - s.format = format; - transform(s.expr); + std::vector exprs; + for (auto &p : *expr) { + if (p.prefix == "f" || p.prefix == "F") { + /// Transform an F-string + auto fstr = unpackFString(p.value); + if (!canContinue()) + return; + for (auto pf : fstr) { + if (pf.prefix.empty() && !exprs.empty() && exprs.back().prefix.empty()) { + exprs.back().value += pf.value; + } else { + exprs.emplace_back(pf); + } + } + } else if (!p.prefix.empty()) { + exprs.emplace_back(p); + } else if (!exprs.empty() && exprs.back().prefix.empty()) { + exprs.back().value += p.value; + } else { + exprs.emplace_back(p); } + } + expr->strings = exprs; +} + +/// Split a Python-like f-string into a list: +/// `f"foo {x+1} bar"` -> `["foo ", str(x+1), " bar"] +/// Supports "{x=}" specifier (that prints the raw expression as well): +/// `f"{x+1=}"` -> `["x+1=", str(x+1)]` +std::vector +ScopingVisitor::unpackFString(const std::string &value) { + // Strings to be concatenated + std::vector items; + int braceCount = 0, braceStart = 0; + for (int i = 0; i < value.size(); i++) { + if (value[i] == '{') { + if (braceStart < i) + items.emplace_back(value.substr(braceStart, i - braceStart)); + if (!braceCount) + braceStart = i + 1; + braceCount++; + } else if (value[i] == '}') { + braceCount--; + if (!braceCount) { + std::string code = value.substr(braceStart, i - braceStart); + + auto offset = getSrcInfo(); + offset.col += i; + items.emplace_back(code, "#f"); + items.back().setSrcInfo(offset); + + auto val = parseExpr(ctx->cache, code, offset); + if (!val) { + addError(val.takeError()); + } else { + items.back().expr = val->first; + if (!transform(items.back().expr)) + return items; + items.back().format = val->second; + } + } + braceStart = i + 1; + } + } + if (braceCount > 0) + addError(Error::STR_FSTRING_BALANCE_EXTRA, getSrcInfo()); + else if (braceCount < 0) + addError(Error::STR_FSTRING_BALANCE_MISSING, getSrcInfo()); + if (braceStart != value.size()) + items.emplace_back(value.substr(braceStart, value.size() - braceStart)); + return items; } void ScopingVisitor::visit(GeneratorExpr *expr) { SetInScope s(&(ctx->tempScope), true); ctx->renames.emplace_back(); - transform(expr->getFinalSuite()); + CHECK(transform(expr->getFinalSuite())); ctx->renames.pop_back(); } void ScopingVisitor::visit(IfExpr *expr) { - transform(expr->getCond()); - transformScope(expr->getIf()); - transformScope(expr->getElse()); + CHECK(transform(expr->getCond())); + CHECK(transformScope(expr->getIf())); + CHECK(transformScope(expr->getElse())); } void ScopingVisitor::visit(BinaryExpr *expr) { - transform(expr->getLhs()); - - if (expr->getOp() == "&&" || expr->getOp() == "||") - transformScope(expr->getRhs()); - else - transform(expr->getRhs()); + CHECK(transform(expr->getLhs())); + if (expr->getOp() == "&&" || expr->getOp() == "||") { + CHECK(transformScope(expr->getRhs())); + } else { + CHECK(transform(expr->getRhs())); + } } void ScopingVisitor::visit(AssignExpr *expr) { @@ -132,8 +215,8 @@ void ScopingVisitor::visit(AssignExpr *expr) { "only simple assignment expression are supported"); SetInScope s(&(ctx->tempScope), false); - transform(expr->getExpr()); - transformAdding(expr->getVar(), expr); + CHECK(transform(expr->getExpr())); + CHECK(transformAdding(expr->getVar(), expr)); } void ScopingVisitor::visit(LambdaExpr *expr) { @@ -154,6 +237,10 @@ void ScopingVisitor::visit(LambdaExpr *expr) { v.transform(expr->getExpr()); v.processChildCaptures(); c->scope.pop_back(); + if (v.hasErrors()) + errors.append(v.errors); + if (!canContinue()) + return; auto b = std::make_unique(); b->captures = c->captures; @@ -165,101 +252,116 @@ void ScopingVisitor::visit(LambdaExpr *expr) { } // todo)) Globals/nonlocals cannot be shadowed in children scopes (as in Python) -// val->canShadow = false; void ScopingVisitor::visit(AssignStmt *stmt) { - // seqassert(stmt->lhs->getId(), "assignment not unpacked"); - transform(stmt->getRhs()); - transform(stmt->getTypeExpr()); - transformAdding(stmt->getLhs(), stmt); + CHECK(transform(stmt->getRhs())); + CHECK(transform(stmt->getTypeExpr())); + CHECK(transformAdding(stmt->getLhs(), stmt)); } void ScopingVisitor::visit(IfStmt *stmt) { - transform(stmt->getCond()); - transformScope(stmt->getIf()); - transformScope(stmt->getElse()); + CHECK(transform(stmt->getCond())); + CHECK(transformScope(stmt->getIf())); + CHECK(transformScope(stmt->getElse())); } void ScopingVisitor::visit(MatchStmt *stmt) { - transform(stmt->getExpr()); + CHECK(transform(stmt->getExpr())); for (auto &m : *stmt) { - transform(m.getPattern()); - transform(m.getGuard()); - transformScope(m.getSuite()); + CHECK(transform(m.getPattern())); + CHECK(transform(m.getGuard())); + CHECK(transformScope(m.getSuite())); } } void ScopingVisitor::visit(WhileStmt *stmt) { - transform(stmt->getCond()); + CHECK(transform(stmt->getCond())); std::unordered_set seen; { ConditionalBlock c(ctx.get(), stmt->getSuite()); ctx->scope.back().seenVars = std::make_unique>(); - transform(stmt->getSuite()); + CHECK(transform(stmt->getSuite())); seen = *(ctx->scope.back().seenVars); } for (auto &var : seen) findDominatingBinding(var); - transformScope(stmt->getElse()); + CHECK(transformScope(stmt->getElse())); } void ScopingVisitor::visit(ForStmt *stmt) { - transform(stmt->getIter()); - transform(stmt->getDecorator()); + CHECK(transform(stmt->getIter())); + CHECK(transform(stmt->getDecorator())); for (auto &a : stmt->ompArgs) - transform(a.value); + CHECK(transform(a.value)); - std::unordered_set seen; + std::unordered_set seen, seenDef; { ConditionalBlock c(ctx.get(), stmt->getSuite()); - if (!cast(stmt->getVar())) { - auto var = N(ctx->cache->getTemporaryVar("for")); - auto e = N(clone(stmt->getVar()), clone(var)); - stmt->suite = N(e->unpack(), stmt->getSuite()); - stmt->var = var; - } - transformAdding(stmt->var, stmt); ctx->scope.back().seenVars = std::make_unique>(); - transform(stmt->getSuite()); + CHECK(transformAdding(stmt->var, stmt)); + seenDef = *(ctx->scope.back().seenVars); + + ctx->scope.back().seenVars = std::make_unique>(); + CHECK(transform(stmt->getSuite())); seen = *(ctx->scope.back().seenVars); } for (auto &var : seen) - if (var != cast(stmt->getVar())->getValue()) + if (!in(seenDef, var)) findDominatingBinding(var); - transformScope(stmt->getElse()); + CHECK(transformScope(stmt->getElse())); } void ScopingVisitor::visit(ImportStmt *stmt) { + // Validate + if (stmt->getFrom()) { + Expr *e = stmt->getFrom(); + while (auto d = cast(e)) + e = d->getExpr(); + if (!isId(stmt->getFrom(), "C") && !isId(stmt->getFrom(), "python")) { + if (!cast(e)) + STOP_ERROR(Error::IMPORT_IDENTIFIER, e); + if (!stmt->getArgs().empty()) + STOP_ERROR(Error::IMPORT_FN, stmt->getArgs().front().getSrcInfo()); + if (stmt->getReturnType()) + STOP_ERROR(Error::IMPORT_FN, stmt->getReturnType()); + if (stmt->getWhat() && !cast(stmt->getWhat())) + STOP_ERROR(Error::IMPORT_IDENTIFIER, stmt->getWhat()); + } + if (stmt->isCVar() && !stmt->getArgs().empty()) + STOP_ERROR(Error::IMPORT_FN, stmt->getArgs().front().getSrcInfo()); + } if (ctx->functionScope && stmt->getWhat() && isId(stmt->getWhat(), "*")) - E(error::Error::IMPORT_STAR, stmt); + STOP_ERROR(error::Error::IMPORT_STAR, stmt); + return; // dylib C imports if (stmt->getFrom() && isId(stmt->getFrom(), "C") && cast(stmt->getWhat())) - transform(cast(stmt->getWhat())->getExpr()); + CHECK(transform(cast(stmt->getWhat())->getExpr())); if (stmt->getAs().empty()) { - if (stmt->getWhat()) - transformAdding(stmt->getWhat(), stmt); - else - transformAdding(stmt->getFrom(), stmt); + if (stmt->getWhat()) { + CHECK(transformAdding(stmt->getWhat(), stmt)); + } else { + CHECK(transformAdding(stmt->getFrom(), stmt)); + } } else { visitName(stmt->getAs(), true, stmt, stmt->getSrcInfo()); } for (const auto &a : stmt->getArgs()) { - transform(a.type); - transform(a.defaultValue); + CHECK(transform(a.type)); + CHECK(transform(a.defaultValue)); } - transform(stmt->getReturnType()); + CHECK(transform(stmt->getReturnType())); } void ScopingVisitor::visit(TryStmt *stmt) { - transformScope(stmt->getSuite()); + CHECK(transformScope(stmt->getSuite())); for (auto *a : *stmt) { - transform(a->getException()); + CHECK(transform(a->getException())); ConditionalBlock c(ctx.get(), a->getSuite()); if (!a->getVar().empty()) { auto newName = ctx->cache->getTemporaryVar(a->getVar()); @@ -267,24 +369,25 @@ void ScopingVisitor::visit(TryStmt *stmt) { a->var = newName; visitName(a->getVar(), true, a, a->getException()->getSrcInfo()); } - transform(a->getSuite()); + CHECK(transform(a->getSuite())); if (!a->getVar().empty()) ctx->renames.pop_back(); } - transform(stmt->getFinally()); + CHECK(transform(stmt->getFinally())); } void ScopingVisitor::visit(DelStmt *stmt) { /// TODO - transform(stmt->getExpr()); + CHECK(transform(stmt->getExpr())); } /// Process `global` statements. Remove them upon completion. void ScopingVisitor::visit(GlobalStmt *stmt) { if (!ctx->functionScope) - E(Error::FN_OUTSIDE_ERROR, stmt, stmt->isNonLocal() ? "nonlocal" : "global"); + STOP_ERROR(Error::FN_OUTSIDE_ERROR, stmt, + stmt->isNonLocal() ? "nonlocal" : "global"); if (in(ctx->map, stmt->getVar()) || in(ctx->captures, stmt->getVar())) - E(Error::FN_GLOBAL_ASSIGNED, stmt, stmt->getVar()); + STOP_ERROR(Error::FN_GLOBAL_ASSIGNED, stmt, stmt->getVar()); visitName(stmt->getVar(), true, stmt, stmt->getSrcInfo()); ctx->captures[stmt->getVar()] = stmt->isNonLocal() @@ -295,7 +398,7 @@ void ScopingVisitor::visit(GlobalStmt *stmt) { void ScopingVisitor::visit(YieldStmt *stmt) { if (ctx->functionScope) ctx->functionScope->setAttribute(Attr::IsGenerator); - transform(stmt->getExpr()); + CHECK(transform(stmt->getExpr())); } void ScopingVisitor::visit(YieldExpr *expr) { @@ -304,6 +407,87 @@ void ScopingVisitor::visit(YieldExpr *expr) { } void ScopingVisitor::visit(FunctionStmt *stmt) { + // Validate + std::vector newDecorators; + for (auto &d : stmt->getDecorators()) { + if (isId(d, Attr::Attribute)) { + if (stmt->getDecorators().size() != 1) + STOP_ERROR(Error::FN_SINGLE_DECORATOR, stmt->getDecorators()[1], + Attr::Attribute); + stmt->setAttribute(Attr::Attribute); + } else if (isId(d, Attr::LLVM)) { + stmt->setAttribute(Attr::LLVM); + } else if (isId(d, Attr::Python)) { + if (stmt->getDecorators().size() != 1) + STOP_ERROR(Error::FN_SINGLE_DECORATOR, stmt->getDecorators()[1], Attr::Python); + stmt->setAttribute(Attr::Python); + } else if (isId(d, Attr::Internal)) { + stmt->setAttribute(Attr::Internal); + } else if (isId(d, Attr::HiddenFromUser)) { + stmt->setAttribute(Attr::HiddenFromUser); + } else if (isId(d, Attr::Atomic)) { + stmt->setAttribute(Attr::Atomic); + } else if (isId(d, Attr::Property)) { + stmt->setAttribute(Attr::Property); + } else if (isId(d, Attr::StaticMethod)) { + stmt->setAttribute(Attr::StaticMethod); + } else if (isId(d, Attr::ForceRealize)) { + stmt->setAttribute(Attr::ForceRealize); + } else if (isId(d, Attr::C)) { + stmt->setAttribute(Attr::C); + } else { + newDecorators.emplace_back(d); + } + } + if (stmt->hasAttribute(Attr::C)) { + for (auto &a : *stmt) { + if (a.getName().size() > 1 && a.getName()[0] == '*' && a.getName()[1] != '*') + stmt->setAttribute(Attr::CVarArg); + } + } + if (!stmt->empty() && !stmt->front().getType() && stmt->front().getName() == "self") { + stmt->setAttribute(Attr::HasSelf); + } + stmt->setDecorators(newDecorators); + if (!stmt->getReturn() && + (stmt->hasAttribute(Attr::LLVM) || stmt->hasAttribute(Attr::C))) + STOP_ERROR(Error::FN_LLVM, getSrcInfo()); + // Set attributes + std::unordered_set seenArgs; + bool defaultsStarted = false, hasStarArg = false, hasKwArg = false; + for (size_t ia = 0; ia < stmt->size(); ia++) { + auto &a = (*stmt)[ia]; + auto [stars, n] = a.getNameWithStars(); + if (stars == 2) { + if (hasKwArg) + STOP_ERROR(Error::FN_MULTIPLE_ARGS, a.getSrcInfo()); + if (a.getDefault()) + STOP_ERROR(Error::FN_DEFAULT_STARARG, a.getDefault()); + if (ia != stmt->size() - 1) + STOP_ERROR(Error::FN_LAST_KWARG, a.getSrcInfo()); + + hasKwArg = true; + } else if (stars == 1) { + if (hasStarArg) + STOP_ERROR(Error::FN_MULTIPLE_ARGS, a.getSrcInfo()); + if (a.getDefault()) + STOP_ERROR(Error::FN_DEFAULT_STARARG, a.getDefault()); + hasStarArg = true; + } + if (in(seenArgs, n)) + STOP_ERROR(Error::FN_ARG_TWICE, a.getSrcInfo(), n); + seenArgs.insert(n); + if (!a.getDefault() && defaultsStarted && !stars && a.isValue()) + STOP_ERROR(Error::FN_DEFAULT, a.getSrcInfo(), n); + defaultsStarted |= bool(a.getDefault()); + if (stmt->hasAttribute(Attr::C)) { + if (a.getDefault()) + STOP_ERROR(Error::FN_C_DEFAULT, a.getDefault(), n); + if (stars != 1 && !a.getType()) + STOP_ERROR(Error::FN_C_TYPE, a.getSrcInfo(), n); + } + } + bool isOverload = false; for (auto &d : stmt->getDecorators()) if (isId(d, "overload")) { @@ -326,7 +510,7 @@ void ScopingVisitor::visit(FunctionStmt *stmt) { auto [_, n] = a.getNameWithStars(); v.visitName(n, true, stmt, a.getSrcInfo()); if (a.defaultValue) - transform(a.defaultValue); + CHECK(transform(a.defaultValue)); } c->scope.pop_back(); @@ -334,6 +518,10 @@ void ScopingVisitor::visit(FunctionStmt *stmt) { v.transform(stmt->getSuite()); v.processChildCaptures(); c->scope.pop_back(); + if (v.hasErrors()) + errors.append(v.errors); + if (!canContinue()) + return; auto b = std::make_unique(); b->captures = c->captures; @@ -354,14 +542,132 @@ void ScopingVisitor::visit(FunctionStmt *stmt) { void ScopingVisitor::visit(WithStmt *stmt) { ConditionalBlock c(ctx.get(), stmt->getSuite()); for (size_t i = 0; i < stmt->size(); i++) { - transform((*stmt)[i]); + CHECK(transform((*stmt)[i])); if (!stmt->getVars()[i].empty()) visitName(stmt->getVars()[i], true, stmt, stmt->getSrcInfo()); } - transform(stmt->getSuite()); + CHECK(transform(stmt->getSuite())); } void ScopingVisitor::visit(ClassStmt *stmt) { + // @tuple(init=, repr=, eq=, order=, hash=, pickle=, container=, python=, add=, + // internal=...) + // @dataclass(...) + // @extend + + std::map tupleMagics = { + {"new", true}, {"repr", false}, {"hash", false}, + {"eq", false}, {"ne", false}, {"lt", false}, + {"le", false}, {"gt", false}, {"ge", false}, + {"pickle", true}, {"unpickle", true}, {"to_py", false}, + {"from_py", false}, {"iter", false}, {"getitem", false}, + {"len", false}, {"to_gpu", false}, {"from_gpu", false}, + {"from_gpu_new", false}, {"tuplesize", true}}; + + for (auto &d : stmt->getDecorators()) { + if (isId(d, "deduce")) { + stmt->setAttribute(Attr::ClassDeduce); + } else if (isId(d, "__notuple__")) { + stmt->setAttribute(Attr::ClassNoTuple); + } else if (isId(d, "dataclass")) { + } else if (auto c = cast(d)) { + if (isId(c->getExpr(), Attr::Tuple)) { + stmt->setAttribute(Attr::Tuple); + for (auto &m : tupleMagics) + m.second = true; + } else if (!isId(c->getExpr(), "dataclass")) { + STOP_ERROR(Error::CLASS_BAD_DECORATOR, c->getExpr()); + } else if (stmt->hasAttribute(Attr::Tuple)) { + STOP_ERROR(Error::CLASS_CONFLICT_DECORATOR, c, "dataclass", Attr::Tuple); + } + for (const auto &a : *c) { + auto b = cast(a); + if (!b) + STOP_ERROR(Error::CLASS_NONSTATIC_DECORATOR, a.getSrcInfo()); + char val = char(b->getValue()); + if (a.getName() == "init") { + tupleMagics["new"] = val; + } else if (a.getName() == "repr") { + tupleMagics["repr"] = val; + } else if (a.getName() == "eq") { + tupleMagics["eq"] = tupleMagics["ne"] = val; + } else if (a.getName() == "order") { + tupleMagics["lt"] = tupleMagics["le"] = tupleMagics["gt"] = + tupleMagics["ge"] = val; + } else if (a.getName() == "hash") { + tupleMagics["hash"] = val; + } else if (a.getName() == "pickle") { + tupleMagics["pickle"] = tupleMagics["unpickle"] = val; + } else if (a.getName() == "python") { + tupleMagics["to_py"] = tupleMagics["from_py"] = val; + } else if (a.getName() == "gpu") { + tupleMagics["to_gpu"] = tupleMagics["from_gpu"] = + tupleMagics["from_gpu_new"] = val; + } else if (a.getName() == "container") { + tupleMagics["iter"] = tupleMagics["getitem"] = val; + } else { + STOP_ERROR(Error::CLASS_BAD_DECORATOR_ARG, a.getSrcInfo()); + } + } + } else if (isId(d, Attr::Tuple)) { + if (stmt->hasAttribute(Attr::Tuple)) + STOP_ERROR(Error::CLASS_MULTIPLE_DECORATORS, d, Attr::Tuple); + stmt->setAttribute(Attr::Tuple); + for (auto &m : tupleMagics) { + m.second = true; + } + } else if (isId(d, Attr::Extend)) { + stmt->setAttribute(Attr::Extend); + if (stmt->getDecorators().size() != 1) { + STOP_ERROR( + Error::CLASS_SINGLE_DECORATOR, + stmt->getDecorators()[stmt->getDecorators().front() == d]->getSrcInfo(), + Attr::Extend); + } + } else if (isId(d, Attr::Internal)) { + stmt->setAttribute(Attr::Internal); + } else { + STOP_ERROR(Error::CLASS_BAD_DECORATOR, d); + } + } + if (stmt->hasAttribute(Attr::ClassDeduce)) + tupleMagics["new"] = false; + if (!stmt->hasAttribute(Attr::Tuple)) { + tupleMagics["init"] = tupleMagics["new"]; + tupleMagics["new"] = tupleMagics["raw"] = true; + tupleMagics["len"] = false; + } + tupleMagics["dict"] = true; + // Internal classes do not get any auto-generated members. + std::vector magics; + if (!stmt->hasAttribute(Attr::Internal)) { + for (auto &m : tupleMagics) + if (m.second) { + if (m.first == "new") + magics.insert(magics.begin(), m.first); + else + magics.push_back(m.first); + } + } + stmt->setAttribute(Attr::ClassMagic, + std::make_unique(magics)); + std::unordered_set seen; + if (stmt->hasAttribute(Attr::Extend) && !stmt->empty()) + STOP_ERROR(Error::CLASS_EXTENSION, stmt->front().getSrcInfo()); + if (stmt->hasAttribute(Attr::Extend) && + !(stmt->getBaseClasses().empty() && stmt->getStaticBaseClasses().empty())) { + STOP_ERROR(Error::CLASS_EXTENSION, stmt->getBaseClasses().empty() + ? stmt->getStaticBaseClasses().front() + : stmt->getBaseClasses().front()); + } + for (auto &a : *stmt) { + if (!a.getType() && !a.getDefault()) + STOP_ERROR(Error::CLASS_MISSING_TYPE, a.getSrcInfo(), a.getName()); + if (in(seen, a.getName())) + STOP_ERROR(Error::CLASS_ARG_TWICE, a.getSrcInfo(), a.getName()); + seen.insert(a.getName()); + } + if (stmt->hasAttribute(Attr::Extend)) visitName(stmt->getName()); else @@ -380,13 +686,15 @@ void ScopingVisitor::visit(ClassStmt *stmt) { } v.transform(stmt->getSuite()); c->scope.pop_back(); + if (v.hasErrors()) + errors.append(v.errors); + if (!canContinue()) + return; - // for (auto &d : stmt->decorators) - // transform(d); for (auto &d : stmt->getBaseClasses()) - transform(d); + CHECK(transform(d)); for (auto &d : stmt->getStaticBaseClasses()) - transform(d); + CHECK(transform(d)); } void ScopingVisitor::processChildCaptures() { @@ -396,7 +704,6 @@ void ScopingVisitor::processChildCaptures() { continue; } if (!findDominatingBinding(n.first)) { - // LOG("-> add: {} / {}", ctx->functionScope ? ctx->functionScope->name : "-", n); ctx->captures.insert(n); // propagate! } } @@ -404,7 +711,6 @@ void ScopingVisitor::processChildCaptures() { void ScopingVisitor::switchToUpdate(ASTNode *binding, const std::string &name, bool gotUsedVar) { - // LOG("switch: {} {} : {}", name, gotUsedVar, binding ? binding->toString(0) : "-"); if (binding && binding->hasAttribute(Attr::Bindings)) { binding->getAttribute(Attr::Bindings)->bindings.erase(name); } @@ -414,9 +720,9 @@ void ScopingVisitor::switchToUpdate(ASTNode *binding, const std::string &name, binding->setAttribute(gotUsedVar ? Attr::ExprDominatedUsed : Attr::ExprDominated); } if (cast(binding)) - E(error::Error::ID_INVALID_BIND, binding, name); - if (cast(binding)) - E(error::Error::ID_INVALID_BIND, binding, name); + STOP_ERROR(error::Error::ID_INVALID_BIND, binding, name); + else if (cast(binding)) + STOP_ERROR(error::Error::ID_INVALID_BIND, binding, name); } bool ScopingVisitor::visitName(const std::string &name, bool adding, ASTNode *root, @@ -425,9 +731,10 @@ bool ScopingVisitor::visitName(const std::string &name, bool adding, ASTNode *ro return false; if (adding) { if (auto p = in(ctx->captures, name)) { - if (*p == BindingsAttribute::CaptureType::Read) - E(error::Error::ASSIGN_LOCAL_REFERENCE, ctx->firstSeen[name], name, src); - else if (root) { // global, nonlocal + if (*p == BindingsAttribute::CaptureType::Read) { + addError(error::Error::ASSIGN_LOCAL_REFERENCE, ctx->firstSeen[name], name, src); + return false; + } else if (root) { // global, nonlocal switchToUpdate(root, name, false); } } else { @@ -441,15 +748,11 @@ bool ScopingVisitor::visitName(const std::string &name, bool adding, ASTNode *ro ctx->scope.front() .suite->getAttribute(Attr::Bindings) ->bindings[name] = false; - // LOG("-> promote {} / F", name); auto newItem = ScopingVisitor::Context::Item(src, newScope, nullptr); ctx->map[name].push_back(newItem); - // LOG("add: {} | {} := {} {}", src, name, newScope, "-"); } } ctx->map[name].emplace_front(src, ctx->getScope(), root); - // LOG("add: {} | {} := {} {}", src, name, ctx->getScope(), - // root ? root->toString(-1) : "-"); } } else { if (!in(ctx->firstSeen, name)) @@ -478,8 +781,6 @@ bool ScopingVisitor::visitName(const std::string &name, bool adding, ASTNode *ro } // Variable binding check for variables that are defined within conditional blocks - // LOG("{} : var {}: {} vs {}", getSrcInfo(), name, val->accessChecked, - // ctx->getScope()); if (!val->accessChecked.empty()) { bool checked = false; for (size_t ai = val->accessChecked.size(); ai-- > 0;) { @@ -573,10 +874,6 @@ ScopingVisitor::findDominatingBinding(const std::string &name, bool allowShadow) auto newItem = ScopingVisitor::Context::Item( getSrcInfo(), newScope, ctx->scope[si].suite, {lastGood->scope}); lastGood = it->insert(++lastGood, newItem); - // LOG("-> {}: promote {} / T from {} to {} | {}", - // getSrcInfo(), - // name, ctx->getScope(), - // lastGood->scope, lastGood->accessChecked); gotUsedVar = true; break; } diff --git a/codon/parser/visitors/scoping/scoping.h b/codon/parser/visitors/scoping/scoping.h index 4770d4f1..ab0f5323 100644 --- a/codon/parser/visitors/scoping/scoping.h +++ b/codon/parser/visitors/scoping/scoping.h @@ -34,7 +34,7 @@ private: std::ostream &doFormat(std::ostream &os) const override { return os << "Bindings"; } }; -class ScopingVisitor : public CallbackASTVisitor { +class ScopingVisitor : public CallbackASTVisitor { struct Context { /// A pointer to the shared cache. Cache *cache; @@ -112,34 +112,55 @@ class ScopingVisitor : public CallbackASTVisitor { }; public: - static void apply(Cache *, Stmt *s); - void transform(Expr *expr) override; - void transform(Stmt *stmt) override; + ParserErrors errors; + bool hasErrors() const { return !errors.empty(); } + bool canContinue() const { return errors.size() <= MAX_ERRORS; } + template + void addError(error::Error e, const SrcInfo &o, const TA &...args) { + auto msg = + ErrorMessage(error::Emsg(e, args...), o.file, o.line, o.col, o.len, int(e)); + errors.addError({msg}); + } + template void addError(error::Error e, ASTNode *o, const TA &...args) { + this->addError(e, o->getSrcInfo(), args...); + } + void addError(llvm::Error &&e) { + llvm::handleAllErrors(std::move(e), [this](const error::ParserErrorInfo &e) { + this->errors.append(e.getErrors()); + }); + } + + static llvm::Error apply(Cache *, Stmt *s); + bool transform(Expr *expr) override; + bool transform(Stmt *stmt) override; + + // Can error! bool visitName(const std::string &name, bool = false, ASTNode * = nullptr, const SrcInfo & = SrcInfo()); - void transformAdding(Expr *e, ASTNode *); - void transformScope(Expr *); - void transformScope(Stmt *); + bool transformAdding(Expr *e, ASTNode *); + bool transformScope(Expr *); + bool transformScope(Stmt *); - void visit(IdExpr *) override; void visit(StringExpr *) override; + void visit(IdExpr *) override; void visit(GeneratorExpr *) override; - void visit(AssignExpr *) override; - void visit(LambdaExpr *) override; void visit(IfExpr *) override; void visit(BinaryExpr *) override; + void visit(LambdaExpr *) override; void visit(YieldExpr *) override; + void visit(AssignExpr *) override; + void visit(AssignStmt *) override; - void visit(IfStmt *) override; - void visit(MatchStmt *) override; + void visit(DelStmt *) override; + void visit(YieldStmt *) override; void visit(WhileStmt *) override; void visit(ForStmt *) override; + void visit(IfStmt *) override; + void visit(MatchStmt *) override; void visit(ImportStmt *) override; - void visit(DelStmt *) override; void visit(TryStmt *) override; void visit(GlobalStmt *) override; - void visit(YieldStmt *) override; void visit(FunctionStmt *) override; void visit(ClassStmt *) override; void visit(WithStmt *) override; @@ -148,6 +169,8 @@ public: void processChildCaptures(); void switchToUpdate(ASTNode *binding, const std::string &, bool); + std::vector unpackFString(const std::string &value); + template Tn *N(Ts &&...args) { Tn *t = ctx->cache->N(std::forward(args)...); t->setSrcInfo(getSrcInfo()); diff --git a/codon/parser/visitors/typecheck/access.cpp b/codon/parser/visitors/typecheck/access.cpp index 7ac1dd17..3feb5c61 100644 --- a/codon/parser/visitors/typecheck/access.cpp +++ b/codon/parser/visitors/typecheck/access.cpp @@ -83,7 +83,7 @@ void TypecheckVisitor::visit(DotExpr *expr) { // Check if this is being called from CallExpr (e.g., foo.bar(...)) // and mark it as such (to inline useless partial expression) CallExpr *parentCall = cast(ctx->getParentNode()); - if (parentCall && !parentCall->hasAttribute("CallExpr")) + if (parentCall && !parentCall->hasAttribute(Attr::ParentCallExpr)) parentCall = nullptr; // Flatten imports: @@ -366,7 +366,8 @@ TypecheckVisitor::getImport(const std::vector &chain) { getRootModulePath(), getPluginImportPaths()); if (file) { // auto-load support Stmt *s = N(N(N(chain[importEnd]), nullptr)); - ScopingVisitor::apply(ctx->cache, s); + if (auto err = ScopingVisitor::apply(ctx->cache, s)) + throw exc::ParserException(std::move(err)); s = TypecheckVisitor(import->ctx, preamble).transform(s); prependStmts->push_back(s); return getImport(chain); @@ -412,7 +413,7 @@ types::FuncType *TypecheckVisitor::getDispatch(const std::string &fn) { auto ast = N(name, nullptr, std::vector{Param("*" + nar), Param("**" + nkw)}, N(N(root))); - ast->setAttribute("autogenerated"); + ast->setAttribute(Attr::AutoGenerated); ast->setAttribute(Attr::Module, ctx->moduleName.path); ctx->cache->reverseIdentifierLookup[name] = getUnmangledName(fn); diff --git a/codon/parser/visitors/typecheck/assign.cpp b/codon/parser/visitors/typecheck/assign.cpp index 0fcabc32..c690dd80 100644 --- a/codon/parser/visitors/typecheck/assign.cpp +++ b/codon/parser/visitors/typecheck/assign.cpp @@ -30,6 +30,11 @@ void TypecheckVisitor::visit(AssignExpr *expr) { /// See @c transformAssignment and @c unpackAssignments for more details. /// See @c wrapExpr for more examples. void TypecheckVisitor::visit(AssignStmt *stmt) { + if (cast(stmt->lhs) || cast(stmt->lhs)) { + resultStmt = transform(unpackAssignment(stmt->lhs, stmt->rhs)); + return; + } + bool mustUpdate = stmt->isUpdate() || stmt->isAtomicUpdate(); mustUpdate |= stmt->hasAttribute(Attr::ExprDominated); mustUpdate |= stmt->hasAttribute(Attr::ExprDominatedUsed); @@ -82,6 +87,80 @@ void TypecheckVisitor::visit(DelStmt *stmt) { } } +/// Unpack an assignment expression `lhs = rhs` into a list of simple assignment +/// expressions (e.g., `a = b`, `a.x = b`, or `a[x] = b`). +/// Handle Python unpacking rules. +/// @example +/// `(a, b) = c` -> `a = c[0]; b = c[1]` +/// `a, b = c` -> `a = c[0]; b = c[1]` +/// `[a, *x, b] = c` -> `a = c[0]; x = c[1:-1]; b = c[-1]`. +/// Non-trivial right-hand expressions are first stored in a temporary variable. +/// @example +/// `a, b = c, d + foo()` -> `assign = (c, d + foo); a = assign[0]; b = assign[1]`. +/// Each assignment is unpacked recursively to allow cases like `a, (b, c) = d`. +Stmt *TypecheckVisitor::unpackAssignment(Expr *lhs, Expr *rhs) { + std::vector leftSide; + if (auto et = cast(lhs)) { + // Case: (a, b) = ... + for (auto *i : *et) + leftSide.push_back(i); + } else if (auto el = cast(lhs)) { + // Case: [a, b] = ... + for (auto *i : *el) + leftSide.push_back(i); + } + + // Prepare the right-side expression + auto oldSrcInfo = getSrcInfo(); + setSrcInfo(rhs->getSrcInfo()); + auto srcPos = rhs; + SuiteStmt *block = N(); + if (!cast(rhs)) { + // Store any non-trivial right-side expression into a variable + auto var = getTemporaryVar("assign"); + auto newRhs = N(var); + block->addStmt(N(newRhs, ast::clone(rhs))); + rhs = newRhs; + } + + // Process assignments until the fist StarExpr (if any) + size_t st = 0; + for (; st < leftSide.size(); st++) { + if (cast(leftSide[st])) + break; + // Transformation: `leftSide_st = rhs[st]` where `st` is static integer + auto rightSide = N(ast::clone(rhs), N(st)); + // Recursively process the assignment because of cases like `(a, (b, c)) = d)` + auto ns = unpackAssignment(leftSide[st], rightSide); + block->addStmt(ns); + } + // Process StarExpr (if any) and the assignments that follow it + if (st < leftSide.size() && cast(leftSide[st])) { + // StarExpr becomes SliceExpr (e.g., `b` in `(a, *b, c) = d` becomes `d[1:-2]`) + auto rightSide = N( + ast::clone(rhs), + N(N(st), + // this slice is either [st:] or [st:-lhs_len + st + 1] + leftSide.size() == st + 1 ? nullptr + : N(-leftSide.size() + st + 1), + nullptr)); + auto ns = unpackAssignment(cast(leftSide[st])->getExpr(), rightSide); + block->addStmt(ns); + st += 1; + // Process remaining assignments. They will use negative indices (-1, -2 etc.) + // because we do not know how big is StarExpr + for (; st < leftSide.size(); st++) { + if (cast(leftSide[st])) + E(Error::ASSIGN_MULTI_STAR, leftSide[st]->getSrcInfo()); + rightSide = N(ast::clone(rhs), N(-int(leftSide.size() - st))); + auto ns = unpackAssignment(leftSide[st], rightSide); + block->addStmt(ns); + } + } + setSrcInfo(oldSrcInfo); + return block; +} + /// Transform simple assignments. /// @example /// `a[x] = b` -> `a.__setitem__(x, b)` diff --git a/codon/parser/visitors/typecheck/call.cpp b/codon/parser/visitors/typecheck/call.cpp index 3bedc5ee..ad4d02eb 100644 --- a/codon/parser/visitors/typecheck/call.cpp +++ b/codon/parser/visitors/typecheck/call.cpp @@ -60,21 +60,23 @@ void TypecheckVisitor::visit(EllipsisExpr *expr) { void TypecheckVisitor::visit(CallExpr *expr) { auto orig = expr->toString(0); if (match(expr->getExpr(), M("tuple")) && expr->size() == 1) - expr->setAttribute("TupleFn"); + expr->setAttribute(Attr::TupleCall); + + validateCall(expr); // Check if this call is partial call PartialCallData part; - expr->setAttribute("CallExpr"); + expr->setAttribute(Attr::ParentCallExpr); expr->expr = transform(expr->getExpr()); - expr->eraseAttribute("CallExpr"); + expr->eraseAttribute(Attr::ParentCallExpr); if (isUnbound(expr->getExpr())) return; // delay auto [calleeFn, newExpr] = getCalleeFn(expr, part); // Transform `tuple(i for i in tup)` into a GeneratorExpr that will be handled during // the type checking. - if (!calleeFn && expr->hasAttribute("TupleFn")) { + if (!calleeFn && expr->hasAttribute(Attr::TupleCall)) { if (cast(expr->begin()->getExpr())) { auto g = cast(expr->begin()->getExpr()); if (!g || g->kind != GeneratorExpr::Generator || g->loopCount() != 1) @@ -244,6 +246,24 @@ void TypecheckVisitor::visit(CallExpr *expr) { } } +void TypecheckVisitor::validateCall(CallExpr *expr) { + if (expr->hasAttribute(Attr::Validated)) + return; + bool namesStarted = false, foundEllipsis = false; + for (auto &a : *expr) { + if (a.name.empty() && namesStarted && + !(cast(a.value) || cast(a.value))) + E(Error::CALL_NAME_ORDER, a.value); + if (!a.name.empty() && (cast(a.value) || cast(a.value))) + E(Error::CALL_NAME_STAR, a.value); + if (cast(a.value) && foundEllipsis) + E(Error::CALL_ELLIPSIS, a.value); + foundEllipsis |= bool(cast(a.value)); + namesStarted |= !a.name.empty(); + } + expr->setAttribute(Attr::Validated); +} + /// Transform call arguments. Expand *args and **kwargs to the list of @c CallArg /// objects. /// @return false if expansion could not be completed; true otherwise @@ -346,10 +366,10 @@ TypecheckVisitor::getCalleeFn(CallExpr *expr, PartialCallData &part) { return {nullptr, nullptr}; auto clsName = typ->name; if (typ->isRecord()) { - if (expr->hasAttribute("TupleFn")) { + if (expr->hasAttribute(Attr::TupleCall)) { if (extractType(expr->getExpr())->is(TYPE_TUPLE)) return {nullptr, nullptr}; - expr->eraseAttribute("TupleFn"); + expr->eraseAttribute(Attr::TupleCall); } // Case: tuple constructor. Transform to: `T.__new__(args)` auto e = @@ -419,7 +439,7 @@ TypecheckVisitor::getCalleeFn(CallExpr *expr, PartialCallData &part) { /// `foo(1, 2, baz=3, baf=4)` -> `foo(a=1, baz=2, args=(3, ), kwargs=KwArgs(baf=4))` Expr *TypecheckVisitor::callReorderArguments(FuncType *calleeFn, CallExpr *expr, PartialCallData &part) { - if (calleeFn->ast->hasAttribute("std.internal.attributes.no_arg_reorder.0:0")) + if (calleeFn->ast->hasAttribute(Attr::NoArgReorder)) return nullptr; std::vector args; // stores ordered and processed arguments @@ -546,7 +566,7 @@ Expr *TypecheckVisitor::callReorderArguments(FuncType *calleeFn, CallExpr *expr, reorderNamedArgs( calleeFn, expr->items, reorderFn, [&](error::Error e, const SrcInfo &o, const std::string &errorMsg) { - error::raise_error(e, o, errorMsg); + E(e, o, errorMsg); return -1; }, part.known); @@ -585,7 +605,7 @@ Expr *TypecheckVisitor::callReorderArguments(FuncType *calleeFn, CallExpr *expr, } else { if (isUnbound(gen.getType()) && !(*calleeFn->ast)[si].getDefault() && !partial && in(niGenerics, gen.name)) { - error("generic '{}' not provided", gen.niceName); + E(Error::CUSTOM, getSrcInfo(), "generic '{}' not provided", gen.niceName); } } } diff --git a/codon/parser/visitors/typecheck/class.cpp b/codon/parser/visitors/typecheck/class.cpp index 67bdec9a..f899574e 100644 --- a/codon/parser/visitors/typecheck/class.cpp +++ b/codon/parser/visitors/typecheck/class.cpp @@ -253,7 +253,6 @@ void TypecheckVisitor::visit(ClassStmt *stmt) { cls.ast->baseClasses = stmt->baseClasses; for (auto &b : staticBaseASTs) cls.staticParentClasses.emplace_back(b->getClass()->name); - cls.ast->validate(); cls.module = ctx->moduleName.path; // Codegen default magic methods @@ -269,7 +268,7 @@ void TypecheckVisitor::visit(ClassStmt *stmt) { for (auto &mf : getOverloads(mm.second)) { const auto &fp = getFunction(mf); auto f = fp->origAst; - if (f && !f->hasAttribute("autogenerated")) { + if (f && !f->hasAttribute(Attr::AutoGenerated)) { fnStmts.push_back( cast(withClassGenerics(base->getClass(), [&]() { auto cf = clean_clone(f); @@ -755,7 +754,7 @@ types::ClassType *TypecheckVisitor::generateTuple(size_t n, bool generateNew) { ext->setAttribute(Attr::Extend); ext = N(ext); - ScopingVisitor::apply(ctx->cache, ext); + llvm::cantFail(ScopingVisitor::apply(ctx->cache, ext)); auto rctx = getImport(STDLIB_IMPORT)->ctx; auto oldBases = rctx->bases; rctx->bases.clear(); diff --git a/codon/parser/visitors/typecheck/function.cpp b/codon/parser/visitors/typecheck/function.cpp index cfb8a4d5..e41652f9 100644 --- a/codon/parser/visitors/typecheck/function.cpp +++ b/codon/parser/visitors/typecheck/function.cpp @@ -29,7 +29,8 @@ void TypecheckVisitor::visit(LambdaExpr *expr) { params.emplace_back(s); Stmt *f = N(name, nullptr, params, N(N(expr->getExpr()))); - ScopingVisitor::apply(ctx->cache, N(f)); + if (auto err = ScopingVisitor::apply(ctx->cache, N(f))) + throw exc::ParserException(std::move(err)); f = transform(f); if (auto a = expr->getAttribute(Attr::Bindings)) f->setAttribute(Attr::Bindings, a->clone()); @@ -557,7 +558,10 @@ Stmt *TypecheckVisitor::transformLLVMDefinition(Stmt *codeStmt) { std::string exprCode = code.substr(braceStart, i - braceStart); auto offset = getSrcInfo(); offset.col += i; - auto expr = transform(parseExpr(ctx->cache, exprCode, offset).first, true); + auto exprOrErr = parseExpr(ctx->cache, exprCode, offset); + if (!exprOrErr) + throw exc::ParserException(exprOrErr.takeError()); + auto expr = transform(exprOrErr->first, true); items.push_back(N(expr)); braceStart = i + 1; finalCode += '}'; diff --git a/codon/parser/visitors/typecheck/import.cpp b/codon/parser/visitors/typecheck/import.cpp index 02478ac5..761b0470 100644 --- a/codon/parser/visitors/typecheck/import.cpp +++ b/codon/parser/visitors/typecheck/import.cpp @@ -357,13 +357,16 @@ Stmt *TypecheckVisitor::transformNewImport(const ImportFile &file) { getImport(STDLIB_IMPORT)->ctx->addToplevel(importVar, val); registerGlobal(val->getName()); } - n = N(n, parseFile(ctx->cache, file.path)); + auto nodeOrErr = parseFile(ctx->cache, file.path); + if (!nodeOrErr) + throw exc::ParserException(nodeOrErr.takeError()); + n = N(n, *nodeOrErr); auto tv = TypecheckVisitor(ictx, preamble); - ScopingVisitor::apply(ctx->cache, n); - // n = tv.transform(n); + if (auto err = ScopingVisitor::apply(ctx->cache, n)) + throw exc::ParserException(std::move(err)); if (!ctx->cache->errors.empty()) - throw exc::ParserException(); + throw exc::ParserException(ctx->cache->errors); // Add comment to the top of import for easier dump inspection auto comment = N(format("import: {} at {}", file.module, file.path)); auto suite = N(comment, n); diff --git a/codon/parser/visitors/typecheck/infer.cpp b/codon/parser/visitors/typecheck/infer.cpp index 5992adc4..a7222d1c 100644 --- a/codon/parser/visitors/typecheck/infer.cpp +++ b/codon/parser/visitors/typecheck/infer.cpp @@ -51,9 +51,9 @@ Stmt *TypecheckVisitor::inferTypes(Stmt *result, bool isToplevel) { LOG_TYPECHECK("[iter] {} :: {}", ctx->getBase()->name, ctx->getBase()->iteration); if (ctx->getBase()->iteration >= MAX_TYPECHECK_ITER) { LOG("[error=>] {}", result->toString(2)); - error(result, "cannot typecheck '{}' in reasonable time", - ctx->getBase()->name.empty() ? "toplevel" - : getUnmangledName(ctx->getBase()->name)); + E(Error::CUSTOM, result, "cannot typecheck '{}' in reasonable time", + ctx->getBase()->name.empty() ? "toplevel" + : getUnmangledName(ctx->getBase()->name)); } // Keep iterating until: @@ -92,7 +92,8 @@ Stmt *TypecheckVisitor::inferTypes(Stmt *result, bool isToplevel) { if (!tu->isSealed()) { if (tu->pendingTypes[0]->getLink() && tu->pendingTypes[0]->getLink()->kind == LinkType::Unbound) { - tu->addType(getStdLibType("NoneType")); + auto r = tu->addType(getStdLibType("NoneType")); + seqassert(r, "cannot add type to union {}", tu->debugString(2)); tu->seal(); } } @@ -160,12 +161,13 @@ types::Type *TypecheckVisitor::realize(types::Type *typ) { auto t = realizeType(c); return t; } - } catch (exc::ParserException &e) { - if (e.errorCode == Error::MAX_REALIZATION) + } catch (exc::ParserException &exc) { + auto &bt = exc.getErrors().getLast(); + if (bt.front().getErrorCode() == Error::MAX_REALIZATION) throw; if (auto f = typ->getFunc()) { if (f->ast->hasAttribute(Attr::HiddenFromUser)) { - e.locations.back() = getSrcInfo(); + bt.back().setSrcInfo(getSrcInfo()); } else { std::vector args; for (size_t i = 0, ai = 0, gi = 0; i < f->ast->size(); i++) { @@ -188,10 +190,12 @@ types::Type *TypecheckVisitor::realize(types::Type *typ) { name = getUnmangledName(f->ast->name); name_args = fmt::format("({})", fmt::join(args, ", ")); } - e.trackRealize(fmt::format("{}{}", name, name_args), getSrcInfo()); + bt.addMessage(fmt::format("during the realization of {}{}", name, name_args), + getSrcInfo()); } } else { - e.trackRealize(typ->prettyString(), getSrcInfo()); + bt.addMessage(fmt::format("during the realization of {}", typ->prettyString()), + getSrcInfo()); } throw; } @@ -415,7 +419,7 @@ types::Type *TypecheckVisitor::realizeFunc(types::FuncType *type, bool force) { // Lambda typecheck failures are "ignored" as they are treated as statements, // not functions. // TODO: generalize this further. - error("cannot typecheck the program [1]"); + E(Error::CUSTOM, getSrcInfo(), "cannot typecheck the program [1]"); } this->ctx = oldCtx; return nullptr; // inference must be delayed diff --git a/codon/parser/visitors/typecheck/loops.cpp b/codon/parser/visitors/typecheck/loops.cpp index 724746f1..6efb5017 100644 --- a/codon/parser/visitors/typecheck/loops.cpp +++ b/codon/parser/visitors/typecheck/loops.cpp @@ -116,6 +116,14 @@ void TypecheckVisitor::visit(ForStmt *stmt) { if (!iterType) return; // wait until the iterator is known + // Replace for (i, j) in ... { ... } with for tmp in ...: { i, j = tmp ; ... } + if (!cast(stmt->getVar())) { + auto var = N(ctx->cache->getTemporaryVar("for")); + auto ns = unpackAssignment(stmt->getVar(), var); + stmt->suite = N(ns, stmt->getSuite()); + stmt->var = var; + } + auto [delay, staticLoop] = transformStaticForLoop(stmt); if (delay) return; @@ -201,8 +209,12 @@ Expr *TypecheckVisitor::transformForDecorator(Expr *decorator) { for (auto &a : *c) { if (a.getName() == "openmp" || (a.getName().empty() && openmp.empty() && cast(a.getExpr()))) { - omp = parseOpenMP(ctx->cache, cast(a.getExpr())->getValue(), - a.value->getSrcInfo()); + auto ompOrErr = + parseOpenMP(ctx->cache, cast(a.getExpr())->getValue(), + a.value->getSrcInfo()); + if (!ompOrErr) + throw exc::ParserException(ompOrErr.takeError()); + omp = *ompOrErr; } else { args.emplace_back(a.getName(), transform(a.getExpr())); } diff --git a/codon/parser/visitors/typecheck/op.cpp b/codon/parser/visitors/typecheck/op.cpp index d62d3b74..4a1fd0b9 100644 --- a/codon/parser/visitors/typecheck/op.cpp +++ b/codon/parser/visitors/typecheck/op.cpp @@ -398,10 +398,13 @@ void TypecheckVisitor::visit(InstantiateExpr *expr) { if (!isTypeExpr((*expr)[i])) E(Error::EXPECTED_TYPE, (*expr)[i], "type"); } - if (isUnion) - typ->getUnion()->addType(t.get()); - else + if (isUnion) { + if (!typ->getUnion()->addType(t.get())) + E(error::Error::UNION_TOO_BIG, (*expr)[i], + typ->getUnion()->pendingTypes.size()); + } else { unify(t.get(), generics[i].getType()); + } } if (isUnion) { typ->getUnion()->seal(); diff --git a/codon/parser/visitors/typecheck/special.cpp b/codon/parser/visitors/typecheck/special.cpp index b0ab62bf..dafa32dd 100644 --- a/codon/parser/visitors/typecheck/special.cpp +++ b/codon/parser/visitors/typecheck/special.cpp @@ -159,7 +159,7 @@ FunctionStmt *TypecheckVisitor::generateThunkAST(FuncType *fp, ClassType *base, auto thunkAst = N( thunkName, nullptr, fnArgs, N(N(N(N(m->ast->name), callArgs)))); - thunkAst->setAttribute("std.internal.attributes.inline.0:0"); + thunkAst->setAttribute(Attr::Inline); return cast(transform(thunkAst)); } @@ -278,7 +278,7 @@ SuiteStmt *TypecheckVisitor::generateUnionTagAST(FuncType *type) { SuiteStmt *TypecheckVisitor::generateNamedKeysAST(FuncType *type) { auto n = getIntLiteral(extractFuncGeneric(type)); if (n < 0 || n >= ctx->cache->generatedTupleNames.size()) - error("bad namedkeys index"); + E(Error::CUSTOM, getSrcInfo(), "bad namedkeys index"); std::vector s; for (auto &k : ctx->cache->generatedTupleNames[n]) s.push_back(N(k)); @@ -304,11 +304,11 @@ SuiteStmt *TypecheckVisitor::generateTupleMulAST(FuncType *type) { SuiteStmt *TypecheckVisitor::generateSpecialAst(types::FuncType *type) { // Clone the generic AST that is to be realized auto ast = type->ast; - if (ast->hasAttribute("autogenerated") && endswith(ast->name, ".__iter__") && + if (ast->hasAttribute(Attr::AutoGenerated) && endswith(ast->name, ".__iter__") && extractFuncArgType(type, 0)->getHeterogenousTuple()) { // Special case: do not realize auto-generated heterogenous __iter__ E(Error::EXPECTED_TYPE, getSrcInfo(), "iterable"); - } else if (ast->hasAttribute("autogenerated") && + } else if (ast->hasAttribute(Attr::AutoGenerated) && endswith(ast->name, ".__getitem__") && extractFuncArgType(type, 0)->getHeterogenousTuple()) { // Special case: do not realize auto-generated heterogenous __getitem__ @@ -799,8 +799,8 @@ Expr *TypecheckVisitor::transformStaticFnCanCall(CallExpr *expr) { Expr *TypecheckVisitor::transformStaticFnArgHasType(CallExpr *expr) { auto fn = extractFunction(expr->begin()->getExpr()->getType()); if (!fn) - error("expected a function, got '{}'", - expr->begin()->getExpr()->getType()->prettyString()); + E(Error::CUSTOM, getSrcInfo(), "expected a function, got '{}'", + expr->begin()->getExpr()->getType()->prettyString()); auto idx = extractFuncGeneric(expr->getExpr()->getType())->getIntStatic(); seqassert(idx, "expected a static integer"); return transform(N(idx->value >= 0 && idx->value < fn->size() && @@ -810,20 +810,20 @@ Expr *TypecheckVisitor::transformStaticFnArgHasType(CallExpr *expr) { Expr *TypecheckVisitor::transformStaticFnArgGetType(CallExpr *expr) { auto fn = extractFunction(expr->begin()->getExpr()->getType()); if (!fn) - error("expected a function, got '{}'", - expr->begin()->getExpr()->getType()->prettyString()); + E(Error::CUSTOM, getSrcInfo(), "expected a function, got '{}'", + expr->begin()->getExpr()->getType()->prettyString()); auto idx = extractFuncGeneric(expr->getExpr()->getType())->getIntStatic(); seqassert(idx, "expected a static integer"); if (idx->value < 0 || idx->value >= fn->size() || !(*fn)[idx->value]->canRealize()) - error("argument does not have type"); + E(Error::CUSTOM, getSrcInfo(), "argument does not have type"); return transform(N((*fn)[idx->value]->realizedName())); } Expr *TypecheckVisitor::transformStaticFnArgs(CallExpr *expr) { auto fn = extractFunction(expr->begin()->value->getType()); if (!fn) - error("expected a function, got '{}'", - expr->begin()->getExpr()->getType()->prettyString()); + E(Error::CUSTOM, getSrcInfo(), "expected a function, got '{}'", + expr->begin()->getExpr()->getType()->prettyString()); std::vector v; v.reserve(fn->ast->size()); for (const auto &a : *fn->ast) { @@ -837,24 +837,24 @@ Expr *TypecheckVisitor::transformStaticFnArgs(CallExpr *expr) { Expr *TypecheckVisitor::transformStaticFnHasDefault(CallExpr *expr) { auto fn = extractFunction(expr->begin()->getExpr()->getType()); if (!fn) - error("expected a function, got '{}'", - expr->begin()->getExpr()->getType()->prettyString()); + E(Error::CUSTOM, getSrcInfo(), "expected a function, got '{}'", + expr->begin()->getExpr()->getType()->prettyString()); auto idx = extractFuncGeneric(expr->getExpr()->getType())->getIntStatic(); seqassert(idx, "expected a static integer"); if (idx->value < 0 || idx->value >= fn->ast->size()) - error("argument out of bounds"); + E(Error::CUSTOM, getSrcInfo(), "argument out of bounds"); return transform(N((*fn->ast)[idx->value].getDefault() != nullptr)); } Expr *TypecheckVisitor::transformStaticFnGetDefault(CallExpr *expr) { auto fn = extractFunction(expr->begin()->getExpr()->getType()); if (!fn) - error("expected a function, got '{}'", - expr->begin()->getExpr()->getType()->prettyString()); + E(Error::CUSTOM, getSrcInfo(), "expected a function, got '{}'", + expr->begin()->getExpr()->getType()->prettyString()); auto idx = extractFuncGeneric(expr->getExpr()->getType())->getIntStatic(); seqassert(idx, "expected a static integer"); if (idx->value < 0 || idx->value >= fn->ast->size()) - error("argument out of bounds"); + E(Error::CUSTOM, getSrcInfo(), "argument out of bounds"); return transform((*fn->ast)[idx->value].getDefault()); } @@ -865,8 +865,8 @@ Expr *TypecheckVisitor::transformStaticFnWrapCallArgs(CallExpr *expr) { auto fn = extractFunction(expr->begin()->getExpr()->getType()); if (!fn) - error("expected a function, got '{}'", - expr->begin()->getExpr()->getType()->prettyString()); + E(Error::CUSTOM, getSrcInfo(), "expected a function, got '{}'", + expr->begin()->getExpr()->getType()->prettyString()); std::vector callArgs; if (auto tup = cast((*expr)[1].getExpr()->getOrigExpr())) { @@ -924,7 +924,7 @@ Expr *TypecheckVisitor::transformStaticTupleType(CallExpr *expr) { types::TypePtr typ = nullptr; auto f = getClassFields(t); if (n < 0 || n >= f.size()) - error("invalid index"); + E(Error::CUSTOM, getSrcInfo(), "invalid index"); auto rt = realize(instantiateType(f[n].getType(), t)); return transform(N(rt->realizedName())); } @@ -936,7 +936,7 @@ TypecheckVisitor::populateStaticTupleLoop(Expr *iter, auto stmt = N(N(vars[0]), nullptr, nullptr); auto call = cast(cast(iter)->front()); if (vars.size() != 1) - error("expected one item"); + E(Error::CUSTOM, getSrcInfo(), "expected one item"); for (auto &a : *call) { stmt->rhs = transform(clean_clone(a.value)); if (auto st = stmt->rhs->getType()->getStatic()) { @@ -953,7 +953,7 @@ std::vector TypecheckVisitor::populateSimpleStaticRangeLoop(Expr *iter, const std::vector &vars) { if (vars.size() != 1) - error("expected one item"); + E(Error::CUSTOM, getSrcInfo(), "expected one item"); auto fn = cast(iter) ? cast(cast(iter)->getExpr()) : nullptr; auto stmt = N(N(vars[0]), nullptr, nullptr); @@ -973,7 +973,7 @@ std::vector TypecheckVisitor::populateStaticRangeLoop(Expr *iter, const std::vector &vars) { if (vars.size() != 1) - error("expected one item"); + E(Error::CUSTOM, getSrcInfo(), "expected one item"); auto fn = cast(iter) ? cast(cast(iter)->getExpr()) : nullptr; auto stmt = N(N(vars[0]), nullptr, nullptr); @@ -996,7 +996,7 @@ std::vector TypecheckVisitor::populateStaticFnOverloadsLoop(Expr *iter, const std::vector &vars) { if (vars.size() != 1) - error("expected one item"); + E(Error::CUSTOM, getSrcInfo(), "expected one item"); auto fn = cast(iter) ? cast(cast(iter)->getExpr()) : nullptr; auto stmt = N(N(vars[0]), nullptr, nullptr); @@ -1012,7 +1012,7 @@ TypecheckVisitor::populateStaticFnOverloadsLoop(Expr *iter, if (isDispatch(method) || !cfn->type) continue; if (typ->getHeterogenousTuple()) { - if (cfn->ast->hasAttribute("autogenerated") && + if (cfn->ast->hasAttribute(Attr::AutoGenerated) && (endswith(cfn->ast->name, ".__iter__") || endswith(cfn->ast->name, ".__getitem__"))) { // ignore __getitem__ and other heterogenuous methods @@ -1030,7 +1030,7 @@ std::vector TypecheckVisitor::populateStaticEnumerateLoop(Expr *iter, const std::vector &vars) { if (vars.size() != 2) - error("expected two items"); + E(Error::CUSTOM, getSrcInfo(), "expected two items"); auto fn = cast(iter) ? cast(cast(iter)->getExpr()) : nullptr; auto stmt = N(N(vars[0]), nullptr, nullptr); @@ -1047,7 +1047,7 @@ TypecheckVisitor::populateStaticEnumerateLoop(Expr *iter, block.push_back(b); } } else { - error("staticenumerate needs a tuple"); + E(Error::CUSTOM, getSrcInfo(), "staticenumerate needs a tuple"); } return block; } @@ -1059,9 +1059,9 @@ TypecheckVisitor::populateStaticVarsLoop(Expr *iter, cast(iter) ? cast(cast(iter)->getExpr()) : nullptr; bool withIdx = getBoolLiteral(extractFuncGeneric(fn->getType())); if (!withIdx && vars.size() != 2) - error("expected two items"); + E(Error::CUSTOM, getSrcInfo(), "expected two items"); else if (withIdx && vars.size() != 3) - error("expected three items"); + E(Error::CUSTOM, getSrcInfo(), "expected three items"); std::vector block; auto typ = extractFuncArgType(fn->getType())->getClass(); size_t idx = 0; @@ -1092,9 +1092,9 @@ TypecheckVisitor::populateStaticVarTypesLoop(Expr *iter, auto typ = realize(extractFuncGeneric(fn->getType(), 0)->getClass()); bool withIdx = getBoolLiteral(extractFuncGeneric(fn->getType(), 1)); if (!withIdx && vars.size() != 1) - error("expected one item"); + E(Error::CUSTOM, getSrcInfo(), "expected one item"); else if (withIdx && vars.size() != 2) - error("expected two items"); + E(Error::CUSTOM, getSrcInfo(), "expected two items"); seqassert(typ, "vars_types expects a realizable type, got '{}' instead", *(extractFuncGeneric(fn->getType(), 0))); diff --git a/codon/parser/visitors/typecheck/typecheck.cpp b/codon/parser/visitors/typecheck/typecheck.cpp index cb623e07..d4de876e 100644 --- a/codon/parser/visitors/typecheck/typecheck.cpp +++ b/codon/parser/visitors/typecheck/typecheck.cpp @@ -63,11 +63,12 @@ Stmt *TypecheckVisitor::apply( tv.N(tv.N("__name__"), tv.N(MODULE_MAIN))); stmts.push_back(node); - ScopingVisitor::apply(cache, suite); + if (auto err = ScopingVisitor::apply(cache, suite)) + throw exc::ParserException(std::move(err)); auto n = tv.inferTypes(suite, true); if (!n) { LOG("[error=>] {}", suite->toString(2)); - tv.error("cannot typecheck the program"); + E(Error::CUSTOM, suite, "cannot typecheck the program"); } suite = tv.N(); @@ -82,7 +83,7 @@ Stmt *TypecheckVisitor::apply( tv.prepareVTables(); if (!ctx->cache->errors.empty()) - throw exc::ParserException(); + throw exc::ParserException(ctx->cache->errors); return suite; } @@ -116,8 +117,13 @@ void TypecheckVisitor::loadStdLibrary( // 1. Core definitions cache->classes[VAR_CLASS_TOPLEVEL] = Cache::Class(); - auto core = parseCode(stdlib->cache, stdlibPath->path, "from internal.core import *"); - ScopingVisitor::apply(stdlib->cache, core); + auto coreOrErr = + parseCode(stdlib->cache, stdlibPath->path, "from internal.core import *"); + if (!coreOrErr) + throw exc::ParserException(coreOrErr.takeError()); + auto *core = *coreOrErr; + if (auto err = ScopingVisitor::apply(stdlib->cache, core)) + throw exc::ParserException(std::move(err)); auto tv = TypecheckVisitor(stdlib, preamble); core = tv.inferTypes(core, true); preamble->push_back(core); @@ -133,8 +139,12 @@ void TypecheckVisitor::loadStdLibrary( } // 3. Load stdlib - auto std = parseFile(stdlib->cache, stdlibPath->path); - ScopingVisitor::apply(stdlib->cache, std); + auto stdOrErr = parseFile(stdlib->cache, stdlibPath->path); + if (!stdOrErr) + throw exc::ParserException(stdOrErr.takeError()); + auto std = *stdOrErr; + if (auto err = ScopingVisitor::apply(stdlib->cache, std)) + throw exc::ParserException(std::move(err)); tv = TypecheckVisitor(stdlib, preamble); std = tv.inferTypes(std, true); preamble->push_back(std); @@ -150,12 +160,10 @@ Stmt *TypecheckVisitor::apply(const std::shared_ptr &ctx, Stmt *nod auto tv = TypecheckVisitor(ctx, preamble); auto n = tv.inferTypes(node, true); ctx->setFilename(oldFilename); - if (!n) { - tv.error("cannot typecheck the program"); - } - if (!ctx->cache->errors.empty()) { - throw exc::ParserException(); - } + if (!n) + E(Error::CUSTOM, tv.getSrcInfo(), "cannot typecheck the program"); + if (!ctx->cache->errors.empty()) + throw exc::ParserException(ctx->cache->errors); auto suite = ctx->cache->N(*preamble); suite->addStmt(n); @@ -687,7 +695,9 @@ TypecheckVisitor::canWrapExpr(Type *exprType, Type *expectedType, FuncType *call } else if (exprClass && expectedClass && expectedClass->getUnion()) { // Make union types via __internal__.new_union if (!expectedClass->getUnion()->isSealed()) { - expectedClass->getUnion()->addType(exprClass); + if (!expectedClass->getUnion()->addType(exprClass)) + E(error::Error::UNION_TOO_BIG, expectedClass->getSrcInfo(), + expectedClass->getUnion()->pendingTypes.size()); } if (auto t = realize(expectedClass)) { if (expectedClass->unify(exprClass, nullptr) == -1) { @@ -1116,7 +1126,7 @@ types::TypePtr TypecheckVisitor::instantiateType(const SrcInfo &srcInfo, // look for generics [todo: speed-up!] std::unordered_set generics; for (auto &g : ft->funcGenerics) - generics.insert(getUnmangledName(g.name)); + generics.insert(getUnmangledName(g.name)); for (auto parent = ft->funcParent; parent;) { if (auto f = parent->getFunc()) { for (auto &g : f->funcGenerics) @@ -1431,7 +1441,7 @@ ir::PyType TypecheckVisitor::cythonizeClass(const std::string &name) { for (const auto &[n, ofnn] : methods) { auto canonicalName = getOverloads(ofnn).back(); auto fn = getFunction(canonicalName); - if (getOverloads(ofnn).size() == 1 && fn->ast->hasAttribute("autogenerated")) + if (getOverloads(ofnn).size() == 1 && fn->ast->hasAttribute(Attr::AutoGenerated)) continue; auto fna = fn->ast; bool isMethod = fna->hasAttribute(Attr::Method); diff --git a/codon/parser/visitors/typecheck/typecheck.h b/codon/parser/visitors/typecheck/typecheck.h index 03934834..90c5c677 100644 --- a/codon/parser/visitors/typecheck/typecheck.h +++ b/codon/parser/visitors/typecheck/typecheck.h @@ -133,6 +133,7 @@ private: // Node typechecking rules void visit(KeywordStarExpr *) override; void visit(EllipsisExpr *) override; void visit(CallExpr *) override; + void validateCall(CallExpr *expr); bool transformCallArgs(CallExpr *); std::pair, Expr *> getCalleeFn(CallExpr *, PartialCallData &); @@ -144,6 +145,7 @@ private: // Node typechecking rules /* Assignments (assign.cpp) */ void visit(AssignExpr *) override; void visit(AssignStmt *) override; + Stmt *unpackAssignment(Expr *lhs, Expr *rhs); Stmt *transformUpdate(AssignStmt *); Stmt *transformAssignment(AssignStmt *, bool = false); void visit(DelStmt *) override; diff --git a/codon/parser/visitors/visitor.h b/codon/parser/visitors/visitor.h index 73192362..af1d3151 100644 --- a/codon/parser/visitors/visitor.h +++ b/codon/parser/visitors/visitor.h @@ -101,47 +101,6 @@ struct CallbackASTVisitor : public ASTVisitor, public SrcObject { return r; } - // /// Convenience method that constructs a clone of a node. - // template auto N(const Tn &ptr) { return std::make_shared(ptr); } - // /// Convenience method that constructs a node. - // /// @param s source location. - // template auto N(codon::SrcInfo s, Ts &&...args) { - // auto t = std::make_shared(std::forward(args)...); - // t->setSrcInfo(s); - // return t; - // } - // /// Convenience method that constructs a node with the visitor's source location. - // template auto N(Ts &&...args) { - // auto t = std::make_shared(std::forward(args)...); - // t->setSrcInfo(getSrcInfo()); - // return t; - // } - // template - // auto NT(const Tt &tt, Ts &&...args) { - // auto t = std::make_shared(std::forward(args)...); - // t->setSrcInfo(getSrcInfo()); - // t->setType(tt); - // return t; - // } - - /// Convenience method that raises an error at the current source location. - template void error(const char *format, TArgs &&...args) { - error::raise_error(-1, getSrcInfo(), fmt::format(format, args...).c_str()); - } - - /// Convenience method that raises an error at the source location of p. - template - void error(const T &p, const char *format, TArgs &&...args) { - error::raise_error(-1, p->getSrcInfo(), fmt::format(format, args...).c_str()); - } - - /// Convenience method that raises an internal error. - template - void internalError(const char *format, TArgs &&...args) { - throw exc::ParserException( - fmt::format("INTERNAL: {}", fmt::format(format, args...), getSrcInfo())); - } - public: void visit(NoneExpr *expr) override {} void visit(BoolExpr *expr) override {} diff --git a/test/main.cpp b/test/main.cpp index 7732bca8..cb9a18de 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -279,7 +279,7 @@ public: ? compiler->parseFile(file, testFlags) : compiler->parseCode(file, code, startLine, testFlags), [](const error::ParserErrorInfo &e) { - for (auto &group : e) { + for (auto &group : e.getErrors()) { for (auto &msg : group) { getLogger().level = 0; printf("%s\n", msg.getMessage().c_str());