How to emulate @:native metadata for typedefs?

Haxe only supports @:native metadata on externs, but not on typedef. This is problematic: I wanted to create externs for the rcedit package on npm but I couldn’t find a satisfying implementation. Most option fields use dashes. Does anyone have a suggestion on how best to do this?

Currently, I’m using:

// Externs.
package rcedit;

import haxe.DynamicAccess;
import js.lib.Promise;

@:jsRequire("rcedit")
extern class RcEdit {
	@:selfCall
	static function rcedit(executable: String, options: DynamicAccess<Any>): Promise<Dynamic>;
}

@:structInit class RcEditOptions {
	public var applicationManifest: Null<String> = null;
	public var fileVersion: Null<String> = null;
	public var icon: Null<String> = null;
	public var productVersion: Null<String> = null;
	public var requestedExecutionLevel: Null<RequestedExecutionLevel> = null;
	public var versionString: Null<VersionString> = null;

	public var raw(get, never): DynamicAccess<Any>;
	function get_raw() {
		final object: DynamicAccess<Any> = {};
		if (applicationManifest != null) object["application-manifest"] = applicationManifest;
		if (fileVersion != null) object["file-version"] = fileVersion;
		if (icon != null) object["icon"] = icon;
		if (productVersion != null) object["product-version"] = productVersion;
		if (requestedExecutionLevel != null) object["requested-execution-level"] = requestedExecutionLevel;
		if (versionString != null) object["version-string"] = versionString;
		return object;
	}
}

// Code using the externs.
import rcedit.RcEdit;

final options: RcEditOptions = {
	fileVersion: "1.0.0",
	icon: "my_icon.ico",
	productVersion: "1.0.0",
	versionString: { /*...*/ }
};

RcEdit.rcedit("my_app.exe", options.raw);
1 Like

I think that there’s no way to make it work directly in the haxe type system so you wont be able to avoid having an intermediary type.

Using abstracts could make it a bit cleaner looking:

import haxe.DynamicAccess;

typedef OptionsStruct = {
	fileVersion:String,
	productVersion:String,
}

abstract Options(DynamicAccess<Any>) {
	public var fileVersion(get, set):String;
	public var productVersion(get, set):String;

	public function new(o:OptionsStruct) {
		final object:DynamicAccess<Any> = {};
		if (o.fileVersion != null)
			object["file-version"] = o.fileVersion;
		if (o.productVersion != null)
			object["product-version"] = o.productVersion;
		this = object;
	}

	@:from
	static public function fromOptions(o:OptionsStruct) {
		return new Options(o);
	}

	inline function get_fileVersion():String {
		return this["file-version"];
	}

	inline function set_fileVersion(v:String):String {
		return this["file-version"] = v;
	}

	inline function get_productVersion():String {
		return this["product-version"];
	}

	inline function set_productVersion(v:String):String {
		return this["product-version"] = v;
	}
}

class Test {
	static function takesOptions(opt:Options) {
		trace(haxe.Json.stringify(opt));
	}

	static function main() {
		final options = new Options({fileVersion : "1.1.1", productVersion: "2.2.2"});

		trace(options.fileVersion);
		options.fileVersion = "1.1.2";

		takesOptions(options);

		takesOptions({fileVersion: "3.3.3", productVersion: "4.4.4"});

		// Is properly type safe.
		// takesOptions({productVersion: "4.4.4"}); // this will error because fileVersion is missing
	}
}

Here it is on tryhaxe: Try Haxe !

1 Like

Thanks @basro for the suggestion: a bit verbose but much closer to what I wanted.

There is a plan for syntax like obj.”-field” (and PRs in the past)

No ETA however, but hopefully it’s not too far off