Skip to content

Commit 06236f9

Browse files
committed
Fix false positive in _final send checking for generic classes
The finalisers pass conservatively reports FINAL_MAY_SEND when it encounters a method call on an unknown type. When _final calls a method on a generic class instantiated with concrete type args (e.g., Generic[Prim]), the pass follows into the method body where expressions still reference the generic type parameter A. Since A's constraint is a trait, is_known(A) returns false and the pass reports a false positive. Fix by reifying the method body with the entity's concrete type arguments before analyzing it. This replaces type parameters with their concrete types so is_known sees the actual type. The recursion guard is set on the original body (not the reified copy) so that recursive lookups correctly detect cycles. Closes #4249
1 parent cf527d3 commit 06236f9

File tree

3 files changed

+90
-1
lines changed

3 files changed

+90
-1
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
## Fix false positive in `_final` send checking for generic classes
2+
3+
The compiler incorrectly rejected `_final` methods that called methods on generic classes instantiated with concrete type arguments. For example, calling a method on a `Generic[Prim]` where `Generic` has a trait-constrained type parameter would produce a spurious "_final cannot create actors or send messages" error, even though the concrete type is known and does not send messages.
4+
5+
The finaliser pass now resolves generic type parameters to their concrete types before analyzing method bodies for potential message sends.

src/libponyc/pass/finalisers.c

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,44 @@ static int check_call_send(ast_t* ast, bool in_final)
126126
pony_assert(fun != NULL);
127127

128128
AST_GET_CHILDREN(fun, cap, id, typeparams, params, result, can_error, body);
129-
int r = check_body_send(body, false);
129+
130+
// If the receiver type has type arguments and the entity has type parameters,
131+
// reify the method body so generic type parameters are replaced with their
132+
// concrete types. Without this, expressions like `_min.lt(_max)` inside
133+
// `Range[USize]` have receiver type `A` (the type parameter), which fails
134+
// is_known() and produces a false positive FINAL_MAY_SEND.
135+
ast_t* reified_body = NULL;
136+
int r;
137+
138+
if(ast_id(type) == TK_NOMINAL)
139+
{
140+
ast_t* typeargs = ast_childidx(type, 2);
141+
ast_t* entity_typeparams = ast_childidx(def, 1);
142+
143+
if((ast_id(typeargs) == TK_TYPEARGS) &&
144+
(ast_id(entity_typeparams) == TK_TYPEPARAMS))
145+
{
146+
reified_body = reify(body, entity_typeparams, typeargs, NULL, true);
147+
}
148+
}
149+
150+
if(reified_body != NULL)
151+
{
152+
// Set the recursion guard on the original body so that recursive calls
153+
// (which look up the original, not the reified copy) see FINAL_RECURSE.
154+
bool already_recursing = ast_checkflag(body, AST_FLAG_RECURSE_1);
155+
if(!already_recursing)
156+
ast_setflag(body, AST_FLAG_RECURSE_1);
157+
158+
r = check_body_send(reified_body, false);
159+
160+
if(!already_recursing)
161+
ast_clearflag(body, AST_FLAG_RECURSE_1);
162+
163+
ast_free_unattached(reified_body);
164+
} else {
165+
r = check_body_send(body, false);
166+
}
130167

131168
if(r == FINAL_NO_SEND)
132169
{

test/libponyc/finalisers.cc

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,53 @@ TEST_F(FinalisersTest, FinalCannotCallChainedBehaviour)
111111
TEST_ERRORS_1(src, "_final cannot create actors or send messages");
112112
}
113113

114+
TEST_F(FinalisersTest, FinalCanCallMethodOnGenericClassWithConcreteTypeArgs)
115+
{
116+
const char* src =
117+
"trait Gettable\n"
118+
" fun get(): U32\n"
119+
"primitive Prim is Gettable\n"
120+
" fun get(): U32 => 0\n"
121+
"class Generic[A: Gettable val]\n"
122+
" let _value: A\n"
123+
" new val create(value: A) =>\n"
124+
" _value = value\n"
125+
" fun get(): U32 =>\n"
126+
" _value.get()\n"
127+
"class Foo\n"
128+
" fun _final() =>\n"
129+
" Generic[Prim](Prim).get()";
130+
131+
TEST_COMPILE(src);
132+
}
133+
134+
TEST_F(FinalisersTest, FinalCannotCallMethodOnGenericClassThatSends)
135+
{
136+
// The send_msg method sends a message via a behavior call on an actor field.
137+
// Even though Generic is instantiated with concrete type args (U32),
138+
// the finaliser pass should still detect the send inside send_msg.
139+
const char* src =
140+
"actor Ping\n"
141+
" new create() => None\n"
142+
" be ping() => None\n"
143+
"class Holder\n"
144+
" let _p: Ping\n"
145+
" new create(p: Ping) => _p = p\n"
146+
" fun get(): Ping => _p\n"
147+
"class Generic[A]\n"
148+
" let _h: Holder\n"
149+
" new create(h: Holder) => _h = h\n"
150+
" fun send_msg() =>\n"
151+
" _h.get().ping()\n"
152+
"class Foo\n"
153+
" let _g: Generic[U32]\n"
154+
" new create(h: Holder) => _g = Generic[U32](h)\n"
155+
" fun _final() =>\n"
156+
" _g.send_msg()";
157+
158+
TEST_ERRORS_1(src, "_final cannot create actors or send messages");
159+
}
160+
114161
TEST_F(FinalisersTest, CannotLookupFinal)
115162
{
116163
const char* src =

0 commit comments

Comments
 (0)