diff --git a/.release-notes/fix-double-partial-call-error.md b/.release-notes/fix-double-partial-call-error.md new file mode 100644 index 0000000000..67c38dabf5 --- /dev/null +++ b/.release-notes/fix-double-partial-call-error.md @@ -0,0 +1,27 @@ +## Fix duplicate error for partial method call with type arguments + +Previously, when a partial method with type parameters was called and the required `?` was missing, the compiler emitted the same error twice: + +```pony +class C + fun f1[A: Any val](x: A): A ? => error + fun f2() ? => f1[U32](42) // missing `?` +``` + +``` +main.pony:3:15: call is not partial but the method is - a question mark is required after this call + fun f2() ? => f1[U32](42) + ^ + Info: + main.pony:2:34: method is here + +main.pony:3:15: call is not partial but the method is - a question mark is required after this call + fun f2() ? => f1[U32](42) + ^ + Info: + main.pony:2:34: method is here +``` + +The same duplication occurred for the reverse mistake — a `?` on a call to a non-partial method with type parameters. It also occurred for chained `.>` calls, for partial constructor calls, and when type arguments were filled in from defaults rather than written explicitly. The compiler now emits each diagnostic exactly once. + +Additionally, taking the address of a partial method with type arguments (e.g. `addressof obj.method[U32]`) previously triggered a compiler assertion failure. The same construct now compiles correctly. diff --git a/src/libponyc/verify/call.c b/src/libponyc/verify/call.c index 3ac9881e0b..83f8c55db3 100644 --- a/src/libponyc/verify/call.c +++ b/src/libponyc/verify/call.c @@ -9,6 +9,17 @@ static bool check_partial_function_call(pass_opt_t* opt, ast_t* ast) { pony_assert((ast_id(ast) == TK_FUNREF) || (ast_id(ast) == TK_FUNCHAIN) || (ast_id(ast) == TK_NEWREF)); + + // When a method is qualified with type arguments (e.g. `f1[U32](42)`), + // the qualification wraps the original funref/newref in another node of + // the same kind. The outer node performs the partiality check by + // unwrapping its receiver below, so skip this inner node to avoid + // emitting the same error twice. + ast_t* parent = ast_parent(ast); + if((parent != NULL) && (ast_id(parent) == ast_id(ast)) && + (ast_child(parent) == ast)) + return true; + AST_GET_CHILDREN(ast, receiver, method); // Receiver might be wrapped in another funref/newref diff --git a/test/libponyc/verify.cc b/test/libponyc/verify.cc index 0523eb8b0c..8dcef188e2 100644 --- a/test/libponyc/verify.cc +++ b/test/libponyc/verify.cc @@ -419,6 +419,94 @@ TEST_F(VerifyTest, NonPartialFunctionCallPartialFunction) TEST_ERRORS_1(src, "call is not partial but the method is"); } +TEST_F(VerifyTest, NonPartialFunctionCallPartialFunctionWithTypeArgs) +{ + // Regression test for https://github.com/ponylang/ponyc/issues/5332 + // A qualified call to a partial method should emit the error exactly once. + const char* src = + "class Foo\n" + " fun partial[A: Any val](x: A): A ? => error\n" + " fun apply() ? =>\n" + " partial[U32](42)"; + + TEST_ERRORS_1(src, "call is not partial but the method is"); +} + +TEST_F(VerifyTest, PartialFunctionCallNonPartialFunctionWithTypeArgs) +{ + // Regression test for https://github.com/ponylang/ponyc/issues/5332 + // A qualified call to a non-partial method should emit the error + // exactly once when an extraneous question mark is present. + const char* src = + "class Foo\n" + " fun non_partial[A: Any val](x: A): A => x\n" + " fun apply() =>\n" + " non_partial[U32](42)?"; + + TEST_ERRORS_1(src, "call is partial but the method is not"); +} + +TEST_F(VerifyTest, NonPartialFunctionCallPartialFunctionWithTypeArgsChain) +{ + // Regression test for https://github.com/ponylang/ponyc/issues/5332 + // A qualified `.>` chain call to a partial method should also emit + // the error exactly once. + const char* src = + "class Foo\n" + " fun partial[A: Any val](x: A): A ? => error\n" + " fun apply() ? =>\n" + " this.>partial[U32](42)"; + + TEST_ERRORS_1(src, "call is not partial but the method is"); +} + +TEST_F(VerifyTest, NonPartialConstructorCallPartialConstructorWithTypeArgs) +{ + // Regression test for https://github.com/ponylang/ponyc/issues/5332 + // A qualified call to a partial constructor with type arguments should + // emit the error exactly once. + const char* src = + "class Foo\n" + " new create[A: Any val](x: A) ? => error\n" + "primitive Bar\n" + " fun apply() ? =>\n" + " Foo.create[U32](42)"; + + TEST_ERRORS_1(src, "call is not partial but the method is"); +} + +TEST_F(VerifyTest, NonPartialFunctionCallPartialFunctionWithDefaultTypeArgs) +{ + // Regression test for https://github.com/ponylang/ponyc/issues/5332 + // The wrap shape also arises when a method's type parameters have + // defaults and the caller omits explicit type arguments. The error + // must still be emitted exactly once. + const char* src = + "class Foo\n" + " fun partial[A: Any val = U32](x: A): A ? => error\n" + " fun apply() ? =>\n" + " partial(U32(42))"; + + TEST_ERRORS_1(src, "call is not partial but the method is"); +} + +TEST_F(VerifyTest, AddressOfPartialFunctionWithTypeArgs) +{ + // Regression test for https://github.com/ponylang/ponyc/issues/5332 + // Taking the address of a partial method with type arguments must not + // trigger a compiler assertion. The inner funref of the wrapped node + // walks up through its TK_ADDRESS grandparent and used to fail the + // `pony_assert(ast_id(call) == TK_CALL)` in check_partial_function_call. + const char* src = + "use @foo[None](fn: Pointer[None] tag)\n" + "actor Main\n" + " new create(env: Env) =>\n" + " @foo(addressof fn[U32])\n" + " fun fn[A: Any val]() ? => error"; + + TEST_COMPILE(src); +} + TEST_F(VerifyTest, NonPartialFunctionError) { const char* src =