Confusion over Macro Code

So, I’m not sure if this is a compiler issue or if this is a problem that I have generated for myself.

I have a local assigns variable which is capturing haxe.macro.Expr in this array which I later use $a{assigns}; for. This is so that I can capture the necessary expressions within the respective context.

I iterate over each field within a class marked with the @:build meta tag, and detect which fields are functions, taking into account that those fields are marked with @sllCapture meta data.

The code I use for this is as follows:

class Capture
{

	public static macro function build():Array<Field>
	{
		var fields = Context.getBuildFields();
		var cls = Context.getLocalClass().get();
		var assigns:Array<Expr> = [];
		for (field in fields)
		{
			var isCapture = false;
			if (field.meta != null)
			{
				for (m in field.meta)
				{
					if (m.name == "sllCapture")
					{
						isCapture = true;
						break;
					}
				}
			}

			if (isCapture)
			{
				switch (field.kind)
				{
					case FFun(body):
					{
						var fullName = "";
						for (pack in cls.pack)
						{
							fullName += pack + "_";
						}
						fullName += cls.name + "_" + field.name;

						var argTypesIdentifier = fullName + "_types";

						assigns.push(macro { var $argTypesIdentifier : Array<sll.Token.ArgumentType> = []; });

						for (arg in body.args)
						{
							if (arg.type == macro :String)
							{
								assigns.push(macro { $i{argTypesIdentifier}.push(sll.Token.ArgumentType.ARG_TYPE_STRING); });
							}
							else if (arg.type == macro :sll.Token.Byte)
							{
								assigns.push(macro { $i{argTypesIdentifier}.push(sll.Token.ArgumentType.ARG_TYPE_BYTE); });
							}
							else if (arg.type == macro :sll.Token.Short)
							{
								assigns.push(macro { $i{argTypesIdentifier}.push(sll.Token.ArgumentType.ARG_TYPE_SHORT); });
							}
							else if (arg.type == macro :sll.Token.Byte)
							{
								assigns.push(macro { $i{argTypesIdentifier}.push(sll.Token.ArgumentType.ARG_TYPE_BYTE); });
							}
							else if (arg.type == macro :Int)
							{
								assigns.push(macro { $i{argTypesIdentifier}.push(sll.Token.ArgumentType.ARG_TYPE_INT); });
							}
							else if (arg.type == macro :sll.Token.SByte)
							{
								assigns.push(macro { $i{argTypesIdentifier}.push(sll.Token.ArgumentType.ARG_TYPE_SBYTE); });
							}
							else if (arg.type == macro :sll.Token.UShort)
							{
								assigns.push(macro { $i{argTypesIdentifier}.push(sll.Token.ArgumentType.ARG_TYPE_USHORT); });
							}
							else if (arg.type == macro :UInt)
							{
								assigns.push(macro { $i{argTypesIdentifier}.push(sll.Token.ArgumentType.ARG_TYPE_UINT); });
							}
							else if (arg.type == macro :Float)
							{
								assigns.push(macro { $i{argTypesIdentifier}.push(sll.Token.ArgumentType.ARG_TYPE_FLOAT); });
							}
							else if (arg.type == macro :Bool)
							{
								assigns.push(macro { $i{argTypesIdentifier}.push(sll.Token.ArgumentType.ARG_TYPE_BOOL); });
							}
						}

						assigns.push(macro { sll.Parser.functionTypes.set($v{field.name}, $i{argTypesIdentifier}); });
					}
					default:
					{
						// ignore everything not a function
					}
				}
			}
		}

		// build `initSLL()` function
		{
			var exprBody = macro {
				if (sll.Parser.tokens == null)
					sll.Parser.tokens = [];
				
				if (sll.Parser.typeMap == null)
					sll.Parser.typeMap = new Map<String, haxe.Constraints.Function>();
				
				if (sll.Parser.interpretedTypes == null)
					sll.Parser.interpretedTypes = new Map<String, Any>();

				if (sll.Parser.functionTypes == null)
					sll.Parser.functionTypes = new Map<String, Array<sll.Token.ArgumentType>>();

				$a{assigns};
			};

			var func:Function = {
				expr: exprBody,
				args: [],
				ret: macro :Void
			};

			var initField:Field = {
				name: "initSLL",
				access: [APublic, AStatic],
				pos: Context.currentPos(),
				kind: FFun(func)
			};

			fields.push(initField);
		}

		return fields;
	}

}

I’ve been bashing my head against a wall (metaphorically) trying to understand why the Haxe compiler is producing a nonsensical error:

e:\StoryDev\Websites\StoryDev\Sources/sll/Capture.hx:90: characters 75-95 : Unknown identifier : SLLUI_title_types
e:\StoryDev\Websites\StoryDev\Sources/sll/Capture.hx:90: characters 75-95 : For function argument 'value'
e:\StoryDev\Websites\StoryDev\Sources/SLLUI.hx:10: lines 10-29 : Defined in this class

The identifier SLLUI_title_types Haxe is trying to find is declared here: var argTypesIdentifier = fullName + "_types"; and right below it I add it to the assigns value as an expression like so:

assigns.push(macro { var $argTypesIdentifier : Array<sll.Token.ArgumentType> = []; });

Why would Haxe think it cannot identify $argTypesIdentifier even though that very code very clearly shows it’s being defined.

In the error above, line 90 is referring to: assigns.push(macro { sll.Parser.functionTypes.set($v{field.name}, $i{argTypesIdentifier}); });

For me, this just doesn’t make any sense, unless the reification I’m using is wrong. Any suggestions?

For the record, the variable being assigned to is of type Map<String, Array<sll.Token.ArgumentType>>.

Hello,

don’t have sll->Parser->etc., changed Array<sll.Token.ArgumentType> to Array<Dynamic>, changed isCapture to true by default (array is empty at line 18), got

MyCapture.hx:14: [{name: title, doc
aracters 26-28), expr: EBlock(<...>
MyCapture.hx:18: []
MyCapture.hx:45: SLLUI_title_types
MyCapture.hx:119: {
        {
                var SLLUI_title_types:Array<Dynamic> = [];
        };
}
Main.hx:7: $SLLUI
Main.hx:9: [title]
Main.hx:11: [initSLL]
Main.hx:12: function#1F8B46C3598

import MyCapture;

class Main {
  static public function main() {
	var type = Type.resolveClass("SLLUI");
	trace(type);
	var fields = Type.getInstanceFields(type);
	trace(fields);
	var statics = Type.getClassFields(type);
	trace(statics);
	trace(SLLUI.initSLL);
  }
}
@:build(MyCapture.build())
class SLLUI {
	public function title() {}
}

Curious that seems to compile. What version of Haxe are you using?

I did try hard-coding sllui_title_types into the macro but it produced the same error for me.

latest Haxe ver. 4.2.5

Right… I fixed my first problem. I’ve been effectively forced to explicitly create a variable through the Expr typedef rather than through reification. So, the following works:

var argTypesIdentifier = fullName + "_types";
var fullName_Name = fullName + "_name";
argTypesIdentifier = argTypesIdentifier.toLowerCase();
assigns.push({ pos: Context.currentPos(), expr: EVars([{ name: fullName_Name, type: macro :String, expr: macro $v{fullName} }]) });
assigns.push({ pos: Context.currentPos(), expr: EVars([{ name: argTypesIdentifier, type: macro :Array<Int>, expr: macro [] }]) });

But the original code produces the “Unknown identifier” issue. Not sure if it’s my own code or there is a problem with reification in certain circumstances.

Got another problem which, again, Haxe isn’t making sense with, but hopefully I can work around this one as well. Just don’t understand why macros can be so difficult to work with sometimes.

I encountered the same problem when trying to inject my own profiler code into functions using macros. I wanted to generate a local variable at the top of a function using reification and it failed to work, where it was fine using expr and exprdefs.

Just wanted to post this because I feel like this is a specific problem with reification where it feels like it should work.