Haxe macros, calling static method from generated var setter - macros

I'm adding getter and setters to a variable using haxe macros, now I'm stuck trying to call a static function from within the newly generated setter:
public static function build():Array<Field> {
//.........
// create setter
var setterBody = macro {
$variableRef = v;
// mypackage.MyClass.myFunc(this) <-------- DOES NOT WORK!!
return $variableRef;
};
newFields.push({
pos: Context.currentPos(),
name: "set_" + field.name,
meta: [],
kind: FieldType.FFun({
ret: readType,
params: [],
expr: setterBody,
args: [{
value: null,
type: readType,
opt: false,
name: "v"
}]
}),
doc: "",
access: []
});
In the code above I can't find a way to call MyClass.myFun(this), I don't know how to generate that code for the setter, this refers to the instance of the object where the setter is called.
Thank you very much.

Without a more complete example it's hard to know what went wrong. What I can do is show you code that works:
TiagoLrMacroTest.hx:
#:build( TiagoLrMacro.build() )
class TiagoLrMacroTest {
public static function main() {
var test = new TiagoLrMacroTest();
test.name = "hello";
}
function new() {}
public var name(default,set):String;
}
class MyStaticClass {
public static function staticMethod( a:TiagoLrMacroTest ) {
trace( a.name );
}
}
TiagoLrMacro.hx
import haxe.macro.Expr;
import haxe.macro.Context;
class TiagoLrMacro {
public static function build():Array<Field> {
var fields = Context.getBuildFields();
var setterBody = macro {
name = v;
TiagoLrMacroTest.MyStaticClass.staticMethod( this );
return name;
};
fields.push({
pos: Context.currentPos(),
name: "set_name",
meta: [],
kind: FieldType.FFun({
ret: macro :String,
params: [],
expr: setterBody,
args: [{
value: null,
type: macro :String,
opt: false,
name: "v"
}]
}),
doc: "",
access: []
});
return fields;
}
}
Result (Haxe 3.1.3):
TiagoLrMacroTest.hx:15: hello
The one common gotcha I run into with calling static methods in macros is that imports are not respected, so you have to use full type paths like mypackage.MyClass.myFunc(this), but you are already doing this, so the error must be somewhere else in your code. Happy macro-ing :)

Related

Some errors with getting #:genericbuild working (haxe nightly)

Now Solved
This post is a follow up on a previous post
I have taken example code with #:genericbuild which runs ok and modified it, but then it doesn't run.
While I am not new to haxe I am totally new when it comes to macro's. I am used to thoroughly look in documentation and even code if documentation is hard to find. But with haxe macro's that isn't so easy, that's why I hope to find some answers here. With those answers I hope to gain knowledge so I can help improving haxe documentation, so others new to macro's have an easier time.
I have studied documentation available and code.
There are 3 files. The file with driver code (Main.hx), a parameterised enum being generated by a macro(Either.hx) making use of #:genericbuild and the macro file that generates that file (EitherBuildMacro.hx).
I have the latest haxe nightly running. I copied some code of a library that uses genericbuild and runs on my computer. This I will call "reference code" from now on. But when I run the copied and modified above 3 files, I get the error messages:
1) 'Invalid number of type parameters for shl.ds.Either'
2) 'Enum> has no field _1'
It appears the macro can not be executed, also due to errors. It seems the library code which I took as an example doesnt run as an initialization macro, but as a build macro. This is probably the part where I am getting things wrong.
There are also errors about the macrofile (EitherBuildMacro.hx)
1) 'Class has no field defineType'
2) 'Class has no field currentPos'
When I look to the reference code, there all these fields do exist.
Main.hx
class Main{
public static function main(){
var a:Either<String,Int>=Either._1('test'); //<--------
var b:Either<String,Int>=Either._2(123); //<--------
}
}
Either.hx
package shl.ds;
#:genericBuild(shl.macros.build.EitherBuildMacro.build()) //<--------
enum Either<Rest> {} //<--------
EitherBuildMacro.hx
import haxe.macro.Context;
import haxe.macro.Expr;
import haxe.macro.Type;
using haxe.macro.Tools;
class EitherBuildMacro {
public static function build() {
for (i in 2...11) {
Context.defineType({ //<--------
pack: [],
name: "Either" + i,
pos: Context.currentPos(), //<--------
kind: TDEnum,
fields: [
for (j in 0...i) {
name: "_" + (j + 1),
kind: FFun({
args: [
{
name: "value",
type: TPath({
name: String.fromCharCode(65 + j),
pack: []
})
}
],
ret: null,
expr: null
}),
pos: Context.currentPos()
}
],
params: [
for (j in 0...i) {
name: String.fromCharCode(65 + j)
}
]
});
}
}
}
The reference code you can find here (https://gist.github.com/nadako/b086569b9fffb759a1b5)
This is the code I took as inspiration
Signal.hx
#:genericBuild(SignalMacro.build())
class Signal<Rest> {} //<--------
SignalMacro.hx
import haxe.macro.Context;
import haxe.macro.Expr;
import haxe.macro.Type;
using haxe.macro.Tools;
class SignalMacro {
static var signalTypes = new Map<Int,Bool>();
static function build():ComplexType {
return switch (Context.getLocalType()) {
case TInst(_.get() => {name: "Signal"}, params):
buildSignalClass(params);
default:
throw false;
}
}
static function buildSignalClass(params:Array<Type>):ComplexType {
var numParams = params.length;
var name = 'Signal$numParams';
if (!signalTypes.exists(numParams))
{
var typeParams:Array<TypeParamDecl> = [];
var superClassFunctionArgs:Array<ComplexType> = [];
var dispatchArgs:Array<FunctionArg> = [];
var listenerCallParams:Array<Expr> = [];
for (i in 0...numParams) {
typeParams.push({name: 'T$i'});
superClassFunctionArgs.push(TPath({name: 'T$i', pack: []}));
dispatchArgs.push({name: 'arg$i', type: TPath({name: 'T$i', pack: []})});
listenerCallParams.push(macro $i{'arg$i'});
}
var pos = Context.currentPos(); //<--------
Context.defineType({ //<--------
pack: [],
name: name,
pos: pos,
params: typeParams,
kind: TDClass({
pack: [],
name: "Signal",
sub: "SignalBase",
params: [TPType(TFunction(superClassFunctionArgs, macro : Void))]
}),
fields: [
{
name: "dispatch",
access: [APublic],
pos: pos,
kind: FFun({
args: dispatchArgs,
ret: macro : Void,
expr: macro {
startDispatch();
var conn = head;
while (conn != null) {
conn.listener($a{listenerCallParams});
if (conn.once)
conn.dispose();
conn = conn.next;
}
endDispatch();
}
})
}
]
});
signalTypes[numParams] = true;
}
return TPath({pack: [], name: name, params: [for (t in params) TPType(t.toComplexType())]});
}
}
And some test driver code
Main.hx
class Main {
static function main() {
var signal = new Signal<Int,String>(); //<--------
var conn = signal.connect(function(a, b) {
trace('Well done $a $b');
});
signal.dispatch(10, "lol");
}
}

Generate constructor call with build macro

How can I generate a main() (access) method for a Haxe class with a constructor call?
For example
static function main() new App()
function new() {
//....
}
and I want to create this with macro, like this:
import haxe.macro.Context;
import haxe.macro.Expr;
class Build {
macro public static function buildFields():Array<Field> {
var fields:Array<Field> = Context.getBuildFields();
var cls = Context.getLocalClass().get();
var pack = cls.pack.concat([cls.name]);
var name = pack.join(".");
fields.push({
name: "main",
access: [Access.APublic, Access.AStatic],
kind: FieldType.FFun({
expr: macro {
Type.createInstance(Type.resolveClass($v{name}), []);
},
args: [],
ret: null
}),
pos: Context.currentPos()
});
return fields;
}
}
#:build(Build.buildFields())
class App {
function new() {
//....
}
}
This generates the main() method fine, but I'm not sure how to generate new App() instead of resorting to Type.createInstance().
To generate a constructor call like new App(), you can reify a haxe.macro.TypePath as documented here.
var typePath:haxe.macro.TypePath = {
pack: cls.pack,
name: cls.name
}
expr: macro new $typePath(),
Btw, instead of manually constructing fields like that, I would suggest using class reification, which lets you use regular Haxe syntax for declaring fields:
fields = fields.concat((macro class {
public static function main() {
new $typePath();
}
}).fields);

Is it possible to create dynamic getters/setters in typescript?

I'm new in typescript, and I'm trying to rewrite our application from es2016 to TypeScript.
My task is to have a class with data property and make each element from data object available as class property.
I get stuck on this JavaScript code:
for(let key in this.data) {
Object.defineProperty(this, key, {
get: function(value:any) { return this.data[key]; },
set: function(value:any) {
if (this.data[key] !== value) {
this.data[key] = value;
this.updatedKeys.push(key);
}
},
});
}
It is pretty easy to use getter/setters for typescript, but i get confused if i can create them dynamically?
interface IData {
id: number;
[propName: string]: any;
}
class Model {
protected updatedKeys:string[] = [];
baseUrl:string = null;
data:IData;
fields:IData;
constructor(data:IData={id:null}, fields:IData={id:null}) {
super();
this.data = data;
this.fields = fields;
for(let key in this.data) {
Object.defineProperty(this, key, {
get: function(value:any) { return this.data[key]; },
set: function(value:any) {
if (this.data[key] !== value) {
this.data[key] = value;
this.updatedKeys.push(key);
}
},
});
}
}
}
tsc -t ES2016 --lib "es2016","dom" models.ts
will give this error:
models.ts(33,40): error TS2345: Argument of type '{ get: (value: any) => any; set: (value: any) => void; }' is not assignable to parameter of type 'PropertyDescriptor & ThisType<any>'.
Type '{ get: (value: any) => any; set: (value: any) => void; }' is not assignable to type 'PropertyDescriptor'.
Types of property 'get' are incompatible.
Type '(value: any) => any' is not assignable to type '() => any'.
And I don't know how to get rid of this problem.
thanks to the https://github.com/epicgirl1998, she helped me to find the solution. I'll post it here:
the error is that the getter has a value parameter even though getters
aren't passed any value
i replaced it with get: function() { return this.data[key]; }, and now
the only error is that there's a super call in the class which is only
needed if the class extends another class
also, this inside the accessors doesn't refer to the class instance,
but using arrow functions for them should fix it
try this:
interface IData {
id: number;
[propName: string]: any;
}
class Model {
protected updatedKeys:string[] = [];
baseUrl:string = null;
data:IData;
fields:IData;
constructor(data:IData={id:null}, fields:IData={id:null}) {
this.data = data;
this.fields = fields;
for(let key in this.data) {
Object.defineProperty(this, key, {
get: () => { return this.data[key]; },
set: (value:any) => {
if (this.data[key] !== value) {
this.data[key] = value;
this.updatedKeys.push(key);
}
},
});
}
}
}
In typescript, you generally don't need to create objects with methods and properties dynamically. You either create instances of classes, or you type your data using an interface.
If all you want is to convert loaded (json) data to typed data, you can use an interface that describes the structure of your json data.
interface describes the properties of actor data
interface Actor {
name: string;
height: number;
}
fetch generic json data from somewhere
let data : any = getSingleActorData();
type the actor to an interface and put it in an actor array
let actorData : Actor[] = [];
actorData.push(data as Actor);
Now your IDE will allow you to access the name and height of the actor variable:
console.log(actorData[0].name);
If you do want a complete 'object' with getters and setters you can create an Actor class and then instantiate it with the data you loaded:
class Actor {
private _name:string;
private _height:string;
get name {}
set name {}
get height {}
set height {}
constructor(name:string, height:number){
}
}
And then you can put your json data in an actor instance:
actorData.push(new Actor(jsondata.name, jsondata.height));

Angular 2. Set value of ControlGroup in data driven form

Let's say I have this model:
export class MyModel {
constructor(
public id: number,
public name: string
) {}
}
and this ControlGroup:
export class MyComponent {
form: ControlGroup;
model: MyModel;
constructor(builder: FormBuilder) {
this.form = this.builder({
'id' : [''],
'name' : ['']
})
}
}
To get form's data I can simply do that (if field names match):
this.model = this.form.value;
But how can I set form's value in the same manner?
something like: this.form.value = model;
Getting the following error:
Cannot set property value of #<AbstractControl> which has only a getter
Thank you!
UPD: Based on Günter Zöchbauer's suggestion below I ended up with that helper method:
setFormValues(form: ControlGroup, model: any) {
for(var key in model) {
var ctrl = (<Control>form.controls[key]);
if ( ctrl != undefined )
ctrl.updateValue(model[key]);
}
}
The ControlGroup returned from this.builder.group(...) doesn't support to set the value. To set the value you have to set it on each control individually like:
setValue() {
let value = {id: 'xxx', name: 'yyy'};
Object.keys(value).forEach((k) => {
this.form.controls[k].updateValue(value[k]);
});
}
Plunker example

How to exclude property of a collection in JSON rendering in Grails 2.3

I am trying to setup a rest webservice (JSON) this is what I am getting:
{"name":"test","routines":[{"class":"Routine","id":1},{"class":"Routine","id":2}]}
This is what I want to get:
{"name":"test","routines":[{"name": "routine-1"},{"name": "routine-2"}]}
I have these domains:
class Program {
String name;
static hasMany = [routines: Routine]
}
class Routine {
String name
}
I have this controller:
class ProgramController extends RestfulController {
static responseFormats = ['json']
def show(Program program) {
respond program
}
}
I added this in the resources.groovy
programRenderer(JsonRenderer, Program) {
excludes = ['class', 'id']
}
routineRenderer(JsonRenderer, Routine) {
excludes = ['class', 'id']
}
How do I include the name property of Routine in the json response using the show method/action of ProgramController?
The ObjectMarshaller approach is the technically correct way. However, the code is cumbersome to write and it's a maintenance headache syncing the fields of the domain with the marshaller.
In the spirit of being Groovy and keeping things really simple, we've been quite happy just adding a little out() method to each REST domain.
Program.groovy
class Program {
String name
static hasMany = [routines: Routine]
def out() {
return [
name: name,
count: routines?.size(),
routines: routines?.collect { [name: it.name] }
]
}
}
ProgramController.groovy
import grails.converters.JSON
class ProgramController {
def show() {
def resource = Program.read(params.id)
render resource.out() as JSON
}
}
JSON Response
{
name: "test",
count: 2,
routines: [{ name: "routine-1" }, { name: "routine-2" }]
}
The out() method approach makes it easy to customize the response JSON, such as adding count for the number of routines.