HaxeFoundation / HaxeFoundation/haxe
dce should consider all members of a type consumed by an extern as used when the extern function is referenced
- Dominant language
- Haxe
- Stars
- 6.9k
- Forks
- 715
- Avg merge
- 2d 2h
- Merged PRs (30d)
- 11
Description
I have written two distinct modules. One exports a function which takes an `IMap` and iterates over it:
`packageA/src/packageA/MapConsumer.hx`:
```haxe
package packageA;
import haxe.Constraints;
@:keep
@:expose
class MapConsumer {
public static function consume(map:IMap) {
for (key in map.keys()) {
trace('${key}=>${map.get(key)}');
}
}
}
```
`packageA/externs/packageA/MapConsumer.hx`:
```haxe
package packageA;
@:keep @:expose extern class MapConsumer
{
static function consume(map:haxe.Constraints.IMap) : Void;
}
```
My other module instantiates a `StringMap` and calls the function through the extern definition:
`packageB/src/packageB/MapConsumerConsumer.hx`:
```haxe
package packageB;
import packageA.MapConsumer;
class MapConsumerConsumer {
static function main() {
MapConsumer.consume(['a' => '0', 'b' => '1',]);
}
}
```
When compiling `packageB` with default settings and its classpath including `packageA/externs`, I get the following code generated for the JavaScript target:
```javascript
// Generated by Haxe 3.4.0
(function ($hx_exports) { "use strict";
$hx_exports["packageA"] = $hx_exports["packageA"] || {};
var haxe_IMap = function() { };
var haxe_ds_StringMap = function() {
this.h = { };
};
haxe_ds_StringMap.__interfaces__ = [haxe_IMap];
haxe_ds_StringMap.prototype = {
setReserved: function(key,value) {
if(this.rh == null) {
this.rh = { };
}
this.rh["$" + key] = value;
}
};
var packageB_MapConsumerConsumer = function() { };
packageB_MapConsumerConsumer.main = function() {
var _g = new haxe_ds_StringMap();
if(__map_reserved["a"] != null) {
_g.setReserved("a","0");
} else {
_g.h["a"] = "0";
}
if(__map_reserved["b"] != null) {
_g.setReserved("b","1");
} else {
_g.h["b"] = "1";
}
packageA.MapConsumer.consume(_g);
};
var __map_reserved = {}
packageB_MapConsumerConsumer.main();
})(typeof exports != "undefined" ? exports : typeof window != "undefined" ? window : typeof self != "undefined" ? self : this);
```
Trying to run the program (after manually assembling the emitted JavaScript as modules together) results in `Failed: TypeError: values.keys is not a function` being thrown by `packageA.MapConsumer.consume()` (in a JavaScript target).
As you can see, none of the [`IMap`](http://api.haxe.org/haxe/IMap.html) are defined in the `StringMap` implementation due to dead code elimination. It would be nice if DCE could mark types that an extern definition consumes as needing to be kept if the extern is being referenced in kept code. In this case, with an extern consuming `IMap`, the compiler has no knowledge of what methods the extern might call on the interface, so it *should* keep all of the implementations in the generated calling code.
As a workaround, I am probably going to have to write a dummy file which consumes `IMap` to and mark it with `@:keep` to force the type to be more fully generated so that it can be passed to `extern` consumers. But this is difficult to maintain.
Contributor guide
Assessment
This issue has not been assessed yet.