diff --git a/src/ast/nodes.h b/src/ast/nodes.h index 57bf031d..a5ebf8e0 100644 --- a/src/ast/nodes.h +++ b/src/ast/nodes.h @@ -345,7 +345,7 @@ struct MatchValue: public Pattern { struct MatchSingleton: public Pattern { Value value; - ValueDeleter deleter; + ValueDeleter deleter = nullptr; ~MatchSingleton() { if (deleter) { diff --git a/src/ast/ops/print.cpp b/src/ast/ops/print.cpp index 2e3cdba2..8b8adbe0 100644 --- a/src/ast/ops/print.cpp +++ b/src/ast/ops/print.cpp @@ -559,24 +559,38 @@ ReturnType Printer::call(Call const* self, int depth, std::ostream& out, int lev exec(self->func, depth, out, level); out << "("; + int k = 0; for (int i = 0; i < self->args.size(); i++) { + if (k > 0) { + out << ", "; + } exec(self->args[i], depth, out, level); + k += 1; + } - if (i < self->args.size() - 1 || !self->keywords.empty()) + + for (int i = 0; i < self->varargs.size(); i++) { + if (k > 0) { out << ", "; + } + exec(self->varargs[i], depth, out, level); + k += 1; } + for (int i = 0; i < self->keywords.size(); i++) { + if (k > 0) { + out << ", "; + } + auto const& key = self->keywords[i]; out << self->keywords[i].arg; out << "="; exec(key.value, depth, out, level); - - if (i < self->keywords.size() - 1) - out << ", "; + k += 1; } out << ")"; @@ -1284,76 +1298,6 @@ void Printer::arguments(Arguments const& self, int depth, std::ostream& out, int i += 1; prev = kind; }); - -#if 0 - for (auto& arg: self.args) { - out << arg.arg; - - if (arg.annotation.has_value()) { - out << ": "; - exec(arg.annotation.value(), depth, out, level); - } - - auto default_offset = self.args.size() - 1 - i; - if (self.defaults.size() > 0 && default_offset < self.defaults.size()) { - if (arg.annotation.has_value()) { - out << " = "; - } else { - out << "="; - } - exec(self.defaults[default_offset], depth, out, -1); - } - - if (i + 1 < self.args.size()) { - out << ", "; - } - i += 1; - } - - if (self.vararg.has_value()) { - if (self.args.size() > 0) { - out << ", "; - } - - out << "*" << self.vararg.value().arg; - } - - if ((self.args.size() > 0 || self.vararg.has_value()) && self.kwonlyargs.size() > 0) { - out << ", "; - } - - i = 0; - for (auto const& kw: self.kwonlyargs) { - out << kw.arg; - - if (kw.annotation.has_value()) { - out << ": "; - exec(kw.annotation.value(), depth, out, level); - } - - auto default_offset = self.kwonlyargs.size() - 1 - i; - if (self.kw_defaults.size() > 0 && default_offset < self.kw_defaults.size()) { - if (kw.annotation.has_value()) { - out << " = "; - } else { - out << "="; - } - exec(self.kw_defaults[default_offset], depth, out, -1); - } - - if (i + 1 < self.kwonlyargs.size()) { - out << ", "; - } - i += 1; - } - - if (self.kwarg.has_value()) { - if (!self.kwonlyargs.empty()) { - out << ", "; - } - out << "**" << self.kwarg.value().arg; - } - #endif } int get_precedence(Node const* node) { diff --git a/src/sema/sema.cpp b/src/sema/sema.cpp index bb65af85..6ca5c975 100644 --- a/src/sema/sema.cpp +++ b/src/sema/sema.cpp @@ -648,19 +648,17 @@ SemanticAnalyser::get_arrow(ExprNode* fun, ExprNode* type, int depth, int& offse return nullptr; } -void reorder_arguments(Call* call, FunctionDef* def) { +bool SemanticAnalyser::reorder_arguments(Call* call, FunctionDef* def) { + // return; // Transform as many argument as possible to positional // - // We should probably only allow *args && **kwargs for code generation + // We should probably only allow *args && **kwargs at compile time + // as code generator helper // - // - - // fetch the arguments - // this only works if vararg & kwarg are not set Array args; Array final_args; Array var_args; - Array kw_args; + Array kw_args; Array used_keywords; @@ -731,8 +729,21 @@ void reorder_arguments(Call* call, FunctionDef* def) { } }); - final_args.resize(args.size()); + bool is_ok = true; + final_args.reserve(args.size()); for(Keyword kw: args) { + if (kw.value == nullptr) { + is_ok = false; + SEMA_ERROR( + call, + TypeError, + fmt::format( + "TypeError: {} missing 1 required positional argument: '{}'", + str(call), + str(kw.arg) + ) + ); + } final_args.push_back(kw.value); } @@ -742,6 +753,7 @@ void reorder_arguments(Call* call, FunctionDef* def) { call->args = final_args; call->varargs = var_args; call->keywords = kw_args; + return is_ok; } TypeExpr* SemanticAnalyser::call(Call* n, int depth) { @@ -750,10 +762,16 @@ TypeExpr* SemanticAnalyser::call(Call* n, int depth) { // this returns the type of the function auto* type = exec(n->func, depth); - // we are calling a type, this is a constructor - // if (equal(type, Type_t())) { - // - //} + // + bool is_call_valid = false; + if (Name* name = cast(n->func)) { + BindingEntry const* entry = lookup(name); + if (entry) { + if (FunctionDef* def = cast(entry->value)){ + is_call_valid = reorder_arguments(n, def); + } + } + } int offset = 0; ClassDef* cls = nullptr; @@ -814,13 +832,7 @@ TypeExpr* SemanticAnalyser::call(Call* n, int depth) { } // FIXME: we do not know the returns so we just use the one we have - if (arrow != nullptr) { - if (got->arg_count() != arrow->arg_count()) { - // we do not really that check - // SEMA_ERROR(TypeError(" missing {} required positional arguments")); - // - } - + if (arrow != nullptr && is_call_valid) { got->returns = arrow->returns; // (Point, Point) -> Point @@ -1174,6 +1186,63 @@ TypeExpr* SemanticAnalyser::slice(Slice* n, int depth) { // } void SemanticAnalyser::add_arguments(Arguments& args, Arrow* arrow, ClassDef* def, int depth) { + int i = 0; + TypeExpr* class_t = nullptr; + if (def != nullptr) { + class_t = make_ref(arrow, str(def->name), Type_t()); + } + + args.visit([&](ArgumentIter const& iter) { + if (in(iter.kind, ArgumentKind::VarArg, ArgumentKind::KwArg)) { + kwdebug(semalog, "Unsupported argument type"); + return; + } + + TypeExpr* ann_t = nullptr; + TypeExpr* val_t = nullptr; + + bool ann_v = false; + + if(iter.arg.annotation.has_value()) { + ann_t = exec(iter.arg.annotation.value(), depth); + ann_v = is_type(ann_t, depth, LOC); + } + + if (iter.value) { + val_t = exec(iter.value, depth); + } + + if (ann_v && val_t) { + typecheck(nullptr, iter.arg.annotation.value(), iter.value, val_t, LOC); + } + + TypeExpr* arg_t = ann_v ? iter.arg.annotation.value(): val_t; + + // First argument might be self: cls + if (def != nullptr && i == 0 && class_t) { + if (arg_t) { + typecheck(nullptr, arg_t, nullptr, class_t, LOC); + } + arg_t = class_t; + } + + // we could populate the default value here + // but we would not want sema to think this is a constant + add_name(iter.arg.arg, nullptr, arg_t); + + if (arrow != nullptr) { + arrow->names.push_back(iter.arg.arg); + + if (!arrow->add_arg_type(arg_t)) { + SEMA_ERROR( + arrow, TypeError, fmt::format("Cannot have a function type refer to itself")); + } + } + + i += 1; + }); + + #if 0 TypeExpr* class_t = nullptr; if (def != nullptr) { class_t = make_ref(arrow, str(def->name), Type_t()); @@ -1273,6 +1342,7 @@ void SemanticAnalyser::add_arguments(Arguments& args, Arrow* arrow, ClassDef* de } } } + #endif } Arrow* SemanticAnalyser::functiondef_arrow(FunctionDef* n, StmtNode* class_t, int depth) { diff --git a/src/sema/sema.h b/src/sema/sema.h index 3c26f221..04e0dda2 100644 --- a/src/sema/sema.h +++ b/src/sema/sema.h @@ -141,6 +141,8 @@ struct SemanticAnalyser: public BaseVisitor void sema_error(Node* node, lython::CodeLocation const& loc, Args... args) { errors.push_back(std::unique_ptr(new T(args...))); diff --git a/tests/cases/parser/Inline.py b/tests/cases/parser/Inline.py index ac68a9d3..01cc79f4 100644 --- a/tests/cases/parser/Inline.py +++ b/tests/cases/parser/Inline.py @@ -1,9 +1,9 @@ -# >>> case: 0 -# >>> code -a = 2; b = c; d = e# <<< - -# >>> error -NameError: name 'c' is not defined# <<< -# >>> error -NameError: name 'e' is not defined# <<< - +# >>> case: 0 +# >>> code +a = 2; b = c; d = e# <<< + +# >>> error +NameError: name 'c' is not defined# <<< +# >>> error +NameError: name 'e' is not defined# <<< + diff --git a/tests/cases/sema/FunctionDef.py b/tests/cases/sema/FunctionDef.py index ce101bc7..a533e694 100644 --- a/tests/cases/sema/FunctionDef.py +++ b/tests/cases/sema/FunctionDef.py @@ -4,7 +4,7 @@ def fun(): return x # <<< -# >>> call +# >>> error NameError: name 'x' is not defined# <<< # >>> case: 1 @@ -16,49 +16,65 @@ def fun(a: i32) -> i32: # >>> case: 2 # >>> code +def fun(a: i32, b: i32 = 2, /, c: i32 = 4, *args, d: i32 = 4, **kwargs) -> i32: + return a + b + c + d +# <<< + +# >>> call +fun(5, 6, 9, 10, d=7, c=8, e=4)# <<< + +# >>> expected +(i32, i32, i32, i32) -> i32# <<< + + +# >>> case: 3 +# >>> code def fun(a: i32) -> i32: return a x: i32 = fun(1) # <<< -# >>> case: 3 +# >>> case: 4 # >>> code def fun(a: i32) -> i32: return a x = fun(1.0) # <<< -# >>> call +# >>> error TypeError: expression `fun(1.0)` of type `(f64) -> i32` is not compatible with expression `fun` of type `(i32) -> i32`# <<< -# >>> case: 4 +# >>> case: 5 # >>> code def fun(a: i32) -> i32: return a x: f32 = fun(1) # <<< -# >>> call +# >>> error TypeError: expression `x` of type `f32` is not compatible with expression `fun(1)` of type `i32`# <<< -# >>> case: 5 +# >>> case: 6 # >>> code def fun(a: i32, b: f64) -> i32: return a x: i32 = fun(b=1.0, a=1) # <<< -# >>> case: 6 +# >>> case: 7 # >>> code def fun(a: i32 = 1, b: f64 = 1.1) -> i32: return a x: i32 = fun() # <<< -# >>> case: 7 +# >>> case: 8 # >>> code def fun(a: i32, b: f64 = 1.1) -> i32: return a x: i32 = fun() # <<< +# >>> error +TypeError: fun() missing 1 required positional argument: 'a'# <<< + diff --git a/tests/sema_test.cpp b/tests/sema_test.cpp index ef7bffc7..e5c1f1cc 100644 --- a/tests/sema_test.cpp +++ b/tests/sema_test.cpp @@ -22,40 +22,49 @@ void run_testcase(String const& folder, String const& name, Array case TEST_CASE("SEMA_FunctionDef_Typing") { static Array ex = { - { + { // 0 "def fun():\n" " return x\n", // Name error - { + TestErrors({ NE("x"), - }, + }), }, - { + { // 1 "def fun(a: i32) -> i32:\n" " return a\n" "x = fun(1)\n" // Works }, - { + { // 2 + "def fun(a: i32, b: i32 = 2, /, c: i32 = 4, *args, d: i32 = 4, **kwargs) -> i32:\n" + " return a + b + c + d\n", + // Call + "fun(5, 6, 9, 10, d=7, c=8, e=4)", + // Type + // FIXME + "(i32, i32, i32, i32) -> i32" + }, + { // 3 "def fun(a: i32) -> i32:\n" " return a\n" "x: i32 = fun(1)\n" // Works }, - { + { // 4 "def fun(a: i32) -> i32:\n" " return a\n" "x = fun(1.0)\n", // Type Error - { + TestErrors({ TE("fun(1.0)", "(f64) -> i32", "fun", "(i32) -> i32"), - }, + }), }, { "def fun(a: i32) -> i32:\n" " return a\n" "x: f32 = fun(1)\n", // Type Error - { + TestErrors({ TE("x", "f32", "fun(1)", "i32"), - }, + }), }, { @@ -75,6 +84,7 @@ TEST_CASE("SEMA_FunctionDef_Typing") { " return a\n" "x: i32 = fun()\n", // missing argument TestErrors({ + String("TypeError: fun() missing 1 required positional argument: 'a'"), // TE("Argument a is not defined"); }), }, @@ -340,6 +350,22 @@ void run_testcase(String const& folder, String const& name, Array case std::tie(deduced_type, errors) = sema_it(c.code, mod); + + if (!c.call.empty()) { + StringBuffer reader(c.call); + Lexer lex(reader); + Parser parser(lex); + parser.parse_to_module(mod); + + SemanticAnalyser sema; + sema.exec(mod, 0); + + StmtNode* stmt = mod->body[int(mod->body.size()) - 1]; + std::cout << c.call << "\n"; + REQUIRE(str(stmt) == c.call); + } + + REQUIRE(errors == c.errors); if (c.expected_type != "") {