Skip to content

[hl] fix closure type when override dynamic function in generic class - #13043

Open
yuxiaomao wants to merge 4 commits into
HaxeFoundation:developmentfrom
yuxiaomao:dev-hl-dyn-gen-override
Open

yuxiaomao wants to merge 4 commits into
HaxeFoundation:developmentfrom
yuxiaomao:dev-hl-dyn-gen-override

Conversation

@yuxiaomao

Copy link
Copy Markdown
Contributor

It's a rare case in our codebase, and it doesn't really cause problem at runtime. However it fail hl-check

class Base<T> {
	public dynamic function onItem(item : T) : Void {}
	public function new() {}
}

class Sub extends Base<String> {
	override function onItem(item : String) {}
}

function main() {
	new Sub();
}

Fail hl-check at:

 6 | class Sub extends Base<String> {
 7 |  override function onItem(item : String) {}
 8 | }
   |
   | Check failure at fun@25 @3 - Register 1((String):void) should be (dyn):void and not (String):void

Before fix

	fun@25(19h) (Sub):void
	; src/Main.hx:6 (Sub.new)
		r0 Sub
		r1 (String):void
		r2 void
		.6     @0 getthis 1,[0]
		.6     @1 jnotnull 1,2
		.6     @2 instanceclosure 1, Sub.onItem(0)
		.6     @3 setthis [0],1 ; <- error here, this[0] is field in Base (generic class), with type (dyn):void
		.6     @4 call 2, Base.new(0)
		.6     @5 ret 2

After fix

	fun@25(19h) (Sub):void
	; src/Main.hx:6 (Sub.new)
		r0 Sub
		r1 (dyn):void
		r2 (String):void
		r3 void
		.6     @0 getthis 1,[0]
		.6     @1 jnotnull 1,3
		.6     @2 instanceclosure 2, Sub.onItem(0)
		.6     @3 instanceclosure 1, fun$27(2) ; <- added a level of closure when type can't safe_cast, as if we do a cast_to
		.6     @4 setthis [0],1
		.6     @5 call 3, Base.new(0)
		.6     @6 ret 3
	fun@27(1Bh) ((String):void,dyn):void
	; src/Main.hx:7 (fun$27)
		r0 (String):void
		r1 dyn
		r2 String
		r3 void
		.7     @0 safecast 2,1
		.7     @1 callclosure 3, 0(2)
		.7     @2 ret 3

@yuxiaomao

Copy link
Copy Markdown
Contributor Author

With more tests there is

s.onItem = function(item) { trace(item + " !"); }

fail with

[ERROR] src/Main.hx:13: characters 2-3

 13 |  s.onItem = function(item) { trace(item + " !"); }
    |  ^
    | Check failure at fun@32 @3 - Register 2((String):void) should be (dyn):void and not (String):void

So added a new fix (I removed the local optimization that skip nullcheck in cast_to, it's better fit in hlopt but that might introduce other problem so better in another PR).

Cpp, jvm are really failing the tests. Fix suggested by Claude (seems that previously a new field is created for the dynamic function, which isn't good. But I'm not able to understand much of it.)

@Simn

Simn commented Sep 18, 2026

Copy link
Copy Markdown
Member

The JVM fix looks a bit silly, surely this should become part of the if not (has_class_flag c CInterface) && is_physical_field cf then self#generate_field gctx jc c mtype cf condition so it doesn't even call generate_field.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants