COMMUNITY

@:build enum fields not available in all modules

I made build macro that creates a kind of wrapper enum for each class that inherits a certain class

class Base {}

class Child1 extends Base {}

class Child2 extends Base {}

And with these classes the enum would look like this:

enum Kind {
  KBase(?base:Base);
  KChild1(?child1:Child1);
  KChild2(?child2:Child2);
}

In Main I can access the fields just fine but if I try to access them in Base that doesn’t work.

class Base {
  var kind:Kind; // Valid
  var kind2:Kind = Kind.KBase() // Error: Missing fields for this
}

It is seemingly random if the fields exist. I though I saw a pattern at first with the fields being non accessible if there was a class with a field of type Kind but that didn’t always hold true. So I really don’t know what causes the fields to not exist. I need them to be available in Base, Child1, Child2 to do what I want to do(which they are not). Can someone shed some light on this?

Thanks in advance.

Hi Scart_Cable!

Have you had any succes in this case?
This library seems very useful - do you have any code to share?

/ Jonas

I have not had any luck in solving this problem. Fields still randomly do not exist. Though I can still share the code that builds the enum if you want.

The code is a little finique because I use the compiler argument --xml to output a xml file of all type data and then I read that file to get the paths of all classes that inherit Base.
Sometimes the xml doesn’t output certain classes even with -dce no so that’s a problem(I’ll probably have to make a post about that too, really bizarre). I use Type.resolveClass() to get classes from the paths so if you don’t import the subclasses explicitly it may error because there exists no such class as pack.Child1.

The code looks like this:


// This is the data used for building kind
public static final DATA:{var CLASS_PATHS:Array<String>; var ABSTRACT_CLASSES:Array<String>;} = createData(Base);

// This is how I get the xml, all the data lies in the last node so that is what I return
public static inline function getTypeData() {
		var filePath = FileSystem.fullPath("./res/types.xml");
		var file = File.read(filePath, false);

		var text = file.readAll().toString();
		var xml = Xml.parse(text);

		for (node in xml.iterator()) {
			if (node.nodeType == Element) {
				xml = node;
			}
		}

		return xml;
	}

static inline function gotoBase(c:Class<Dynamic>) {
		while (Type.getSuperClass(c) != null) {
			c = Type.getSuperClass(c);
		}
		return c;
	}

public static function createData(base:Class<Dynamic>) {
		// Iterate through all the nodes and if they are a class get their classpath attribute
		var paths:Array<String> = [];
		var typeData = getTypeData();

		for (node in typeData.iterator()) {
			if (node.nodeType == Element && node.nodeName == "class") {
				for (attr in node.attributes()) {
					if (attr == "path")
						paths.push(node.get(attr));
				}
			}
		}

		var subs = new Array<String>();

		for (path in paths) {
			var c = Type.resolveClass(path);

			if (c != null && Type.getClassName(gotoBase(c)) == Type.getClassName(base)) {
				subs.push(Type.getClassName(c));
			}
		}

		// Get which classes are abstracts
		var abstracts:Array<String> = [];
		var path = "";
		for (node in typeData.iterator()) {
			if (node.nodeType == Element && node.nodeName == "class") {
				for (attr in node.attributes()) {
					if (attr == "path")
						path = node.get(attr);
					if (attr == "abstract" && node.get(attr) == "1")
						abstracts.push(path);
				}
			}
		}

		return {CLASS_PATHS: subs, ABSTRACT_CLASSES: abstracts};
	}

#if macro
public static function buildKindEnum() {
		var fields = Context.getBuildFields();

		trace("Building kind enum");
		var msg = "Built: ";

		for (path in DATA.CLASS_PATHS) {
			// Check if it is a abstract, if so skip enum creation
			var skip;
			for (abs in DATA.ABSTRACT_CLASSES) {
				skip = path == abs;
			}
			if (skip)
				continue;

			// Resolve class path and get complex type of class
			var type = Context.getType(path);
			var ctype = type.toComplexType();
			var name = path.split(".").pop();

			// Build the enum body(?) name is name but with first char lowercase, type is the type of path
			var body:Function = {
				args: [{name: name.charAt(0).toLowerCase() + name.substring(1), opt: true, type: ctype}]
			}

			// Create enum field
			fields.push({
				name: "K" + name,
				pos: Context.currentPos(),
				kind: FFun(body),
				// kind: FVar(null),
				doc: 'Kind representing the `${name}` class'
			});

			msg += 'K$name${if (path != DATA.CLASS_PATHS[DATA.CLASS_PATHS.length - 1]) ","; else "";} ';
		}

		trace(msg);
		return fields;
	}
#end

build.hxml:

--xml res/types.xml

getTypeData() reads types.xml and returns the last root node(I don’t know the terminology) which contains all the relevant information. createData() then iterates through the xml and follows each path attr to it’s base class and if the path leads back to Base it includes it in the subs array. It also checks which of the sub-classes are abstract and stores it in abstracts. buildKindEnum() then takes that DATA and resolves each path to a type with Context.getType(). Context.getType() will not work if you haven’t explicitly imported the classes. It skips building constructors for abstract classes because that seems kindof superflous.

That’s about it.