Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .release-notes/fix-double-partial-call-error.md
Original file line number Diff line number Diff line change
@@ -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.
11 changes: 11 additions & 0 deletions src/libponyc/verify/call.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
88 changes: 88 additions & 0 deletions test/libponyc/verify.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
Loading