Tw1ddle/geometrize-haxe

View on GitHub
geometrize/AbstractEnumTools.hx

Summary

Maintainability
Test Coverage
package geometrize;

#if macro

import haxe.macro.Context;
import haxe.macro.Expr;

using haxe.macro.Tools;

#end

/**
 * Abstract enum tools.
 * Based on https://code.haxe.org/category/macros/enum-abstract-values.html by Dan Korostelev
 */
class AbstractEnumTools {
    /**
     * Extracts the values of an enum abstract and returns them in an array.
     * For example, passing an enum abstract MyInts(Int) { var a = 1; var b = 2; var c = 3; } would yield [1, 2, 3].
     * @param    typePath    The type path of the enum abstract.
     * @return    An array containing the values defined by the enum abstract.
     */
    public static macro function getValues(typePath:Expr):Expr {
        // Get the type from a given expression converted to string.
        // This will work for identifiers and field access which is what we need, it will also consider local imports.
        // If expression is not a valid type path or type is not found, compiler will give a error here.
        var type = Context.getType(typePath.toString());
        
        // Switch on the type and check if it's an abstract with @:enum metadata
        switch (type.follow()) {
            case TAbstract(_.get() => ab, _) if (ab.meta.has(":enum")):
                // @:enum abstract values are actually static fields of the abstract implementation class, marked with @:enum and @:impl metadata.
                // We generate an array of expressions that access those fields.
                // Note that this is a bit of implementation detail, so it can change in future Haxe versions, but it's been stable so far.
                var valueExprs = [];
                for (field in ab.impl.get().statics.get()) {
                    if (field.meta.has(":enum") && field.meta.has(":impl")) {
                        var fieldName = field.name;
                        valueExprs.push(macro $typePath.$fieldName);
                    }
                }
                
                if (valueExprs.length == 0) {
                    throw new Error("No values found on this enum abstract, assuming this is because this macro is broken", typePath.pos);
                }
                return macro $a{valueExprs}; // Return collected expressions as an array declaration.
            
            default:
                // The given type is not an abstract, or doesn't have @:enum metadata, show a nice error message.
                throw new Error(type.toString() + " should be @:enum abstract, or the macro's abstract enum checking logic is broken", typePath.pos);
        }
    }
}