MACRO: Rebuild A Class with Context.onAfterTyping() ?

349 views
Skip to first unread message

AlienCoder

unread,
Apr 28, 2017, 1:44:25 PM4/28/17
to Haxe
Dear Community,

according to the problem, I described in a different post, I ask myself, if I can rebuild a class with Context.onAfterTyping() ?

The documentation implies this.

Is there a possibility to manipulate the ModuleType that is handed over? Is there a set() function? Or do I change the ModuleType in place? The latter one seems to be very unlikely to me, as I have tried out to do so with different macro.Types.

Can you help me out?

Kind regards
Michael

Juraj Kirchheim

unread,
Apr 29, 2017, 6:38:04 AM4/29/17
to haxe...@googlegroups.com
There are only two things you can ever modify on typed classes:

1. their metadata
2. exclude them from compilation

With that, you would start with a class like this:

```haxe
class ToBeReplaced {}
```

Then, in `onAfterTyping` you `Context.defineType` a class called e.g. `ModifiedVersion` with the same signature, you `exclude()` the original from compilation and give it a `@:native("ModifiedVersion")`.

Should work. You may need to sprinkle `@:keep` here and there to avoid issues with DCE.

For the issue you're trying to solve though, I would suggest using resources:

```haxe
class Main {
  static function main() {
    var packageInfo:PackageInfo = haxe.Unserializer.run(haxe.Resource.getString("packageInfo"));
  }
}
```

And then in `Context.onGenerate` you collect the infos use `Context.addResource("packageInfo", Bytes.ofString(haxe.Serializer.run(packageInfo)))`.

Best,
Juraj

--
To post to this group haxe...@googlegroups.com
http://groups.google.com/group/haxelang?hl=en
---
You received this message because you are subscribed to the Google Groups "Haxe" group.
For more options, visit https://groups.google.com/d/optout.

AlienCoder

unread,
Apr 29, 2017, 8:16:17 AM4/29/17
to Haxe
Hello Juraj,

thanks a lot for your answer. That really takes me forward in different aspects.

I will try it out.

Kind regards
Michael

AlienCoder

unread,
May 2, 2017, 8:42:11 AM5/2/17
to Haxe

Hello Juraj,

may I ask you for help again?

I have tried to simplify your approach. Just to understand, what happens (or what doesn't).

But anyhow it does not work.

How can I construct a new class from scratch?

Consider the   Context.onAfterTyping( callback )   to be fired.

Inside the callback's function body I have the following code:


var anyClass  = macro class AnyClass
{
public static var x : Int = 13;
// public static var initialFunctionLinkPackageStructure : Map<String,NestedPackages> = ${ Context.parse( FunctionLock.tempPackageStructure.toString(), Context.currentPos() ) };
};

var importExpr : ImportExpr =
{
// mode:ImportMode
mode : ImportMode.INormal,
// path:Array<{pos:Position, name:String}>
path : [ { pos : Context.currentPos(), name: "package01" },
{ pos : Context.currentPos(), name: "package02" },
{ pos : Context.currentPos(), name: "AnyOtherClass" }
]
} //var importExpr : ImportExpr =

Context.defineModule( "package01.package02", [ anyClass ], [ importExpr ] );
} // public static function main() : Void

I also have tried this without to define a module but just to define a class. Either way it didn't work.

Context.currentPos() is probably one mistake, isn't it? But how can I get the right position of an expression if the class does not yet exist? 
 
Or did I accidentally skip a step?
Can I test if the initialization has worked right after the line with    Context.defineModule()     within the callback?

Kind regards
Michael



Juraj Kirchheim

unread,
May 2, 2017, 2:54:55 PM5/2/17
to haxe...@googlegroups.com
On Tue, May 2, 2017 at 2:42 PM, AlienCoder <blackma...@gmail.com> wrote:

Hello Juraj,

may I ask you for help again?

I have tried to simplify your approach. Just to understand, what happens (or what doesn't).

But anyhow it does not work.

How can I construct a new class from scratch?

Context.defineModule or Context.defineType are indeed what you need.
var anyClass  = macro class AnyClass
{
public static var x : Int = 13;
// public static var initialFunctionLinkPackageStructure : Map<String,NestedPackages> = ${ Context.parse( FunctionLock.tempPackageStructure.toString(), Context.currentPos() ) };
};

var importExpr : ImportExpr =
{
// mode:ImportMode
mode : ImportMode.INormal,
// path:Array<{pos:Position, name:String}>
path : [ { pos : Context.currentPos(), name: "package01" },
{ pos : Context.currentPos(), name: "package02" },
{ pos : Context.currentPos(), name: "AnyOtherClass" }
]
} //var importExpr : ImportExpr =

Context.defineModule( "package01.package02", [ anyClass ], [ importExpr ] );
} // public static function main() : Void

You need to do `Context.defineModule("package01.package02.AnyClass")`. A "module" is the same thing as a haxe file. I must have an uppercase name and can contain as many classes (or other types) as required.
 
I also have tried this without to define a module but just to define a class. Either way it didn't work.

Could you be more specific about the errors you get? ;)
Context.currentPos() is probably one mistake, isn't it? But how can I get the right position of an expression if the class does not yet exist? 
You would have to pick a position of something that exists. `Context.currentPos()` probably points nowhere at that point in time. You can always do `(macro null).pos` to get a position (that will be exactly where you write it). You can use `TypeTools.getClass(Context.getType("package01.package02.AnyOtherClass")).pos` to use the position of the other class.
 
Or did I accidentally skip a step?
Can I test if the initialization has worked right after the line with    Context.defineModule()     within the callback?

In theory `Context.getType("package01.package02.AnyClass")` should give you back the class you have just declared.

Best,
Juraj

AlienCoder

unread,
May 3, 2017, 5:50:03 AM5/3/17
to Haxe

Hello Juraj,

thank you very much for your answer. That helped a lot. Thanks to your hint, the new module and its components now have positions other than "unknown".

Regrettably, the code still doesn't work. The issue of the package path name without the typename was just because of too many copy-/ pastes (it was correct in the orriginal code).

The structure of the simplified code is as follows:
 
compile.hxml  --> --macro Main.compilerDirector()
class Main {
#if macro
compilerDirector() {
Context.onAfterTyping( onAfterTypingCallback )
}
onAfterTypingCallback( moduleTypes : Array<ModuleType> ) {
... iterates all moduleTypes
... checks if "package01.package02.AnyClass" actually exists
if not:
... creates a module via
Context.defineModule( "package01.package02.AnyClass", [ newClassMacro ], [ importExpr ] );
... attaches a meta tag 'keep'
newClassMacro.meta =
[
{ name : "keep", // I have tried ":keep" || "@:keep" as well
// pos : ( macro null ).pos
pos : ( macro null ).pos
} ]


 When this code is compiled the callback is called twice. The second time the compiler knows the class   package01.package02.AnyClass   and shows the correct meta tag. But it does not show any other class but the new class   (AnyClass)   and its imported class   (AnotherClass)   within the iteration of the callback argument moduleTypes.

When the main() is called and the access on    package01.package02.AnyClass    is attempted, the following error message prompts:

src/Main.hx:140: characters 32-41 : Type not found : package01.package02.AnyClass


For me (without having the deeper insight of how the compiler works on macroTime), it looks like the new class   AnyClass  is accidentally packed into another array by the compiler (or by me???). And then it is "forgotten" there...

The original code is:

package ;




import extensions.BaseClass;
import haxe.Serializer;

#if macro
import haxe.macro.Context;
import haxe.macro.Type.ModuleType;
import haxe.macro.TypeTools;
import haxe.macro.Type;
import haxe.macro.Expr.ImportExpr;
import haxe.macro.Expr;
//#else
//import package01.package02.AnyClass;
#end



class Main
{

#if macro
public static function compilerDirector() : Void
{
Context.onAfterTyping( onAfterTypingCallback );
// Context.onTypeNotFound( onTypeNotFoundCallback );

} // public static function compilerDirector() : Void


public static function onAfterTypingCallback( moduleTypes : Array<ModuleType> ) : Void
{
trace( "onAfterTypingCallback()" );
trace( "moduleTypes.length: " + moduleTypes.length );


var newClassExists : Bool = false;

for( moduleType in moduleTypes )
{
switch( moduleType )
{
case ModuleType.TClassDecl( clPath ) :
{
var classType : ClassType = clPath.get();

trace( classType.module );

if( classType.module == "package01.package02.AnyClass" )
{
trace( "package01.package02.AnyClass was found." );
newClassExists = true;
} // if( clPath == "package01.package02.AnyClass" )

} // case ModuleType.TClassDecl( clPath ) :
default :
{}
} // switch( moduleType )
} // for( moduleType in moduleTypes )
trace( "" );
if( !newClassExists )
{
var newClassMacro = macro class AnyClass
{
public static var x : String = "Great!";
};

newClassMacro.meta = [
{ name : "keep",
// pos : ( macro null ).pos
pos : ( macro null ).pos
}
];

trace( "newClassMacro.meta: " + newClassMacro.meta );




var importExpr : ImportExpr =
{
// mode:ImportMode
mode : ImportMode.INormal,
// path:Array<{pos:Position, name:String}>
                path : [    { pos : ( macro null ).pos, name: "package01" },
{ pos : ( macro null ).pos, name: "package02" },
{ pos :( macro null ).pos, name: "AnotherClass" }
]
} //var importExpr : ImportExpr =


Context.defineModule( "package01.package02.AnyClass", [ newClassMacro ], [ importExpr ] );

trace( "::::::::::::::::: " + Context.getType( "package01.package02.AnyClass" ) + " :::::::::::::::::" );
// ... shows that the class is known shortly after creation.
        } // if( !newClassExists )




} // public static function onAfterTypingCallback( moduleTypes : Array<ModuleType> ) : Void
#else

static function main()
{

trace( "AnyClass.x: " + package01.package02.AnyClass.x );
// The recently created class is not known anymore.
} // static function main()

#end


} // class Test
 
I am sorry, for keeping you busy. But I have tried any variation. So a huge 'Thank you' for taking the time and having a sophisticated look on my approach.

Kind regards
Michael
 
 
 





Juraj Kirchheim

unread,
May 3, 2017, 10:06:16 AM5/3/17
to haxe...@googlegroups.com
The problem is this: Main cannot refer to classes that are created in `onAfterTyping`. How would the compiler know that some class may or may not be added later?

As I said before, your best chance is for Main to refer to `FakeClass` and then in `onAfterTyping` declare `RealClass` and `exclude()` the `FakeClass` while adding add a `@:native("RealClass")` to it. That way Main compiles fine, referring to a class that is replaced later (i.e. during generation).

Best,
Juraj

AlienCoder

unread,
May 3, 2017, 11:35:40 AM5/3/17
to Haxe
Thanks a lot, Juraj.

I was hoping to have a simpler backdoor...

But then I have to do exactly as you told me. It's certainly not as complicated as it sounds in the first moment.
;-)

Kind regards
Michael
Reply all
Reply to author
Forward
0 new messages