COMMUNITY

Context.defineType doesn't work

Hello! I’m trying to write a macro, that creates a class with fields of every subclass of given interface. I do it by using @:autobuild(spork.core.Macro.registerProperty()) for interface SharedProperty and defining a new type spork.core.PropertyHolder in onAfterTyping callback.

However, when I try to create an instance of spork.core.PropertyHolder, it results in:
Type not found : spork.core.PropertyHolder

I tried using a onTypeNotFound callback, but it never gets called for PropertyHolder, apparently.

The Macro class looks like this:

	private static var setupFinished = false;
	private static var holder: TypeDefinition = null;
	private static var holderFields: StringMap<ComplexType> = new StringMap<ComplexType>();

	public static function typeNotFoundCallback(typename: String): TypeDefinition {
		if (typename.indexOf("PropertyHolder") != -1) {
			trace("getting property holder");
			return holder;
		} else {
			return null;
		}
	}

	public static function buildPropHolder(types: Array<ModuleType>): Void {
		if (holder == null) {
			holder = macro class PropertyHolder {
				public function new() {}
			};
			holder.pack = ["spork", "core"];

			//add shared property fields
			for (iter in holderFields.keyValueIterator()) {
				holder.fields.push({
					name: iter.key,
					access: [APublic],
					pos: Context.currentPos(),
					kind: FVar(iter.value, null)
				});
			}
			Context.defineType(holder);
		}
	}

	public static macro function buildComponent(): Array<Field> {
		var fields = Context.getBuildFields();

		return fields;
	}

	public static macro function registerProperty(): Array<Field> {
		var fields = Context.getBuildFields();

		// register callbacks for after typing and type not found
		if (!setupFinished) {
			Context.onAfterTyping(buildPropHolder);
			Context.onTypeNotFound(typeNotFoundCallback);
			setupFinished = true;
		}

		// get field name for property holder
		var clazz = Context.getLocalClass().get();
		var meta = clazz.meta.extract("name");
		var fieldName = "";
		// if metadata has field name, use it
		if (meta.length != 0 && meta[0].params.length != 0) {
			fieldName = ExprTools.getValue(meta[0].params[0]);
		} else {
			// otherwise, generate from path
			clazz.pack.push(clazz.name);
			fieldName = makeVarName(clazz.pack);
		}
		holderFields.set(fieldName, TypeTools.toComplexType(Context.getLocalType()));

		return fields;
	}

	/**
	 * Gets a field name for property from package, according to the following format:
	 * org.example.Module.Type -> orgExampleModuleType
	 * @param pack package array
	 * @return String
	 */
	public static inline function makeVarName(pack: Array<String>): String {
		//irrelevant code here
	}
}

I would really appreciate your help.

Idk why onTypeNotFound is not invoked for PropertyHolder, but if you define a new type in onAfterTyping, that type does not yet exist during the typing process (after typing, you know :slight_smile: )
So, you can’t reference that type in a regular Haxe code.
But you can access it through reflection: Type.createInstance(Type.resolveClass('path.to.PropertyHolder'))
Or you can create a static initializer in PropertyHolder and populate some factory method in it.

E.g. have this in a regular code:

interface IPropertyHolder {}

class PropertyHolderFactory {
  static public dynamic function create():IPropertyHolder {
    throw "PropertyHolderFactory is not initialized";
  }
}

And then generate PropertyHolder like this:

class PropertyHolder implements IPropertyHolder {
  static function __init__() {
    PropertyHolderFactory.create = function() return new PropertyHolder();
  }
}

Is it possible to do it without resorting to reflections and dynamic?
With initialization macros, perhaps?

Anyway, I think I found a solution. I use an autobuild macro(buildPropHolder) on PropertyHolder, to build its implementations.
The implementations have a metadata @propertiesClassPath, which defines the package containing the shared properties. Autobuild macro uses it to go through the file path, get types from all modules, get ones, that are subclasses of interface SharedProperty and adds them as fields.

The source code is too long, so here’s a github gist: Macro.hx · GitHub

I hope I explained it well enough. Your opinions?