I'd like to write my own macro for creating property like objects in Haxe.
This question is not so much about properties but more about writing macros.
(probably NME has already a macro for that).
having this class in haxe
class Foo {
#:property var bar:String;
}
I like this to be expanded into
class Foo {
private var bar:String;
public function setBar(_val:String):void {
this.bar = _val;
}
public function getBar():String {
return this.bar;
}
}
I read the corresponding docs but honestly I find them very confusing.
thanks
You might want to take a look at how tinkerbell resolves the same issue: https://github.com/back2dos/tinkerbell/wiki/tink_lang#wiki-accessors
This Type Builder example (pasted below for reference, but there's better description at the link) found in the Haxe Manual is a nice, simple example of adding a function to a Class.
Adding a property would be much the same. I added a trace(field) loop to help get a feel for how they're defined:
Main.hx
#:build(TypeBuildingMacro.build("myFunc"))
class Main {
static public function main() {
trace(Main.myFunc); // my default
}
}
TypeBuildingMacro.hx
import haxe.macro.Context;
import haxe.macro.Expr;
class TypeBuildingMacro {
macro static public function build(fieldName:String):Array<Field> {
var fields = Context.getBuildFields();
for (field in fields) { trace(field); }
var newField = {
name: fieldName,
doc: null,
meta: [],
access: [AStatic, APublic],
kind: FVar(macro : String, macro "my default"),
pos: Context.currentPos()
};
fields.push(newField);
return fields;
}
}
Note that Main.hx must invoke the macro with the #:build metadata, so the compiler knows to run the macro (which adds the function) before processing the Main class itself.
Related
When using a static macro function, meant to be used as a static extension, how can I limit types of variables that will get this function on an autocompletion list? Caveat: I know I can use ExprOf<T> but I need this for multiple types to check inside my macro if expr unifies with a specific abstract.
Besides leveraging the type system to perform that unification by itself, if possible, you might be able to use a temporary abstract exclusively for this "filtering".
// exclusively for static extension x autocomplete
private abstract PseudoType(Dynamic)
from ActualType1
from ActualType2
from ActualType3 {}
[...]
public static macro function myMacro(value:ExprOf<PseudoType>}
{
// ExprOf doesn't do anything other than help with autocomplete
// do actual unification here
// return the appropriate result
}
[EDIT] here's an example (live on Try Haxe/alt.):
Macro.hx:
import haxe.macro.Expr;
private abstract PseudoType(Dynamic)
from String
from Int
from { val:Float } {}
class Macro {
public static macro function magic(value:ExprOf<PseudoType>)
{
return macro Std.string($value);
}
}
Test.hx:
using Macro;
class Test {
static function main()
{
trace("Haxe is great!".magic());
trace(42.magic());
trace({ val : 3.14 }.magic());
}
}
I'm trying to debug a library which uses haxe.macro.TypeTools::findField. I've created a simple code for that:
package;
using haxe.macro.TypeTools;
class Main
{
public function new()
{
var test = findField(Child, "hello");
trace(test);
}
}
class Base
{
private function hello()
{
}
}
class Child extends Base
{
public function new() {}
}
However I'm getting error Unknown identifier : findField. Is this because it can only be used in build macro context?
This is what I'm trying to emulate.
First of all, function findField() is not from the haxe.macro.TypeTools.
It is a helper function from edge.core.macro.Macros.
To use it without a class path, import it's class with a wildcard import edge.core.macro.Macros.*
Secondly, findField() should be used in a build macro context only, since it expects Array<Field>, which is obtained by haxe.macro.Context.getBuildFields().
I'm trying to write a CommonJS declaration file for Bluebird, a promise library that directly exports a generic Promise class. However, the library also exports several other generic classes as static members (PromiseInspection), and it seems like its impossible to model this with typescript.
Edit: Usage example, to illustrate how the module's exported class works:
import Promise = require('bluebird');
var promise:Promise<number> = Promise.cast(5);
var x:Promise.PromiseInspection<number> = promise.inspect();
I tried several strategies - simplified examples follow:
1. The obvious way
declare module "bluebird" {
class PromiseInspection<T> {
// ...
}
class Promise<T> {
PromiseInspection: typeof PromiseInspection; // error
constructor<T>();
inspect():PromiseInspection<T>; // error
static cast<U>(value:U):Promise<U>;
// ...
}
export = Promise;
}
Fails with the error unable to use private type PromiseInspection as a public property
2. Using a static interface
declare module "bluebird2" {
interface PromiseInspection<T> {
// ...
}
interface Promise<T> {
constructor<T>();
inspect():PromiseInspection<T>;
}
interface PromiseStatic {
new<T>();
PromiseInspection:typeof PromiseInspection;
cast<U>(value:U):Promise<U>; // error
}
export = PromiseStatic;
}
Also fails similarly, but this time the private type is Promise
3. Trying to directly export a constructor function from the module
declare module "bluebird3" {
export interface PromiseInspection<T> {
// ...
}
export interface Promise<T> {
constructor<T>();
inspect():PromiseInspection<T>;
}
export new<T>(); // syntax error
export function cast<U>(value:U):Promise<U>;
}
This almost works, except of course its impossible to a constructor function that way.
4. The namespace polluting way (Works, with downsides)
interface PromiseInspection<T> {
// ...
}
interface Promise<T> {
constructor<T>();
inspect():PromiseInspection<T>;
}
declare module "bluebird4" {
interface PromiseStatic {
new<T>():Promise<T>;
PromiseInspection: typeof PromiseInspection;
cast<U>(value:U):Promise<U>;
}
export = PromiseStatic;
}
Works, but it pollutes the global namespace with both Promise and PromiseInspection. This might be okay but I'd rather avoid it as in CommonJS its usually considered unacceptable.
5. With declaration merging (gets me 90% of the way...)
declare module "bluebird5" {
module Promise {
export interface PromiseInspection<T> {
value(): T;
// ...
}
export
function cast<U>(value: U): Promise<U> ;
}
class Promise<T> {
new <T> (): Promise <T> ;
inspect(): Promise.PromiseInspection <T> ;
}
export = Promise;
}
Almost there - except that now I'm not allowed to replace class Promise<T> with interface Promise<T>, making Promise<T> unextendable. If I try to do it, the following code:
import Promise = require('bluebird');
var x = new Promise<number>();
x.inspect().value().toExponential();
fails with the error "Invalid 'new' expression"
Link to the actual, work-in-progress bluebird.d.ts - this one currently pollutes the global namespace (uses solution 4)
Is there a better way to do this, or did I hit a language limitation?
Anders Hejlsberg posted an answer on CodePlex, so I'm going to add it here. The declaration merging solution was close - but I also needed a "var" declaration to declare the static interface as it is the only one that can accept a constructor function.
declare module "bluebird" {
module Promise {
export interface PromiseInspection<T> {
value(): T;
}
}
interface Promise<T> {
inspect(): Promise.PromiseInspection <T> ;
}
var Promise: {
new<U>(): Promise<U>;
cast<U>(value: U): Promise<U> ;
}
export = Promise;
}
So basically:
interface members in the module declaration (as long as they declare just types i.e. non-physical)
instance members in the main interface
static function members, the constructor and other "physical" members in the var declaration.
Also, his comment:
Writing it this way you have a separate declaration for each of the three meanings of the identifier Promise: As a namespace (a module containing only types), as a type (that happens to be generic), and as a value.
Looking at your code I noticed you were missing a few export statements. The code below compiles - would it suit?
declare module bluebird {
export class PromiseInspection<T> {
// ...
}
export class Promise<T> {
constructor<T>();
inspect():PromiseInspection<T>;
static all<T>(promises:Promise<T>[]):Promise<T[]>;
}
}
declare module "bluebird" {
export = bluebird;
}
Though I generally favour using interfaces when defining typings as in #2:
declare module bluebird {
export interface PromiseInspection<T> {
// ...
}
export interface Promise<T> {
constructor<T>();
inspect():PromiseInspection<T>;
}
export interface PromiseStatic {
new<T>();
all<T>(promises:Promise<T>[]):Promise<T[]>;
}
}
declare module "bluebird" {
export = bluebird;
}
Failing that have you tried using another promises library as the basis for your typings? You could do worse than look at https://github.com/borisyankov/DefinitelyTyped/blob/master/q/Q.d.ts
Roughly speaking they look a little like this:
declare function Q<T>(promise: Q.IPromise<T>): Q.Promise<T>;
declare function Q<T>(promise: JQueryPromise<T>): Q.Promise<T>;
declare function Q<T>(value: T): Q.Promise<T>;
declare module Q {
//… functions etc in here
}
declare module "q" {
export = Q;
}
EDIT:
Put another way the following within a .d.ts file shouldn't produce compiler error TS2137 'Class "MyClass" does not implement interface "IInterface"':
interface IInterface {
someMethod():void;
}
declare module "mod" {
export class MyClass implements IInterface {
constructor();
}
}
because I'm not (and can't in a declaration) implementing anything. Is this a bug in the compiler or is there some other way/syntax to do what the above implies? I would think the compiler smart enough to know to precisely include IInterface's signature as part of MyClass, and not require its methods be redeclared.
ORIGINAL:
I'm trying to write a d.ts for the node component bunyan. Having a problem with exporting a class that implements an external interface, specifically RingBuffer which extends node's EventEmitter. The problem simplified is (below in a bunyan.d.ts file):
// this interface declared in <reference..., put inline here for simplicity
interface IExternal {
inheritedMethod():void;
}
interface RingBuffer extends IExternal {
write():void;
}
declare var RingBuffer: {
new():RingBuffer;
}
declare module "bunyan" {
export var RingBuffer;
}
then used in myNodeApp.js
/// <references path="bunyan.d.ts" />
import bunyan = require( 'bunyan' );
var rb = new bunyan.RingBuffer();
// compiler doesn't error on this; thinks RingBuffer is type any.
// also, no intellisense to show write() method.
rb.badFunc();
changing bunyan.d.ts to:
declare module "bunyan" {
export class RingBuffer { constructor(); }
}
compiles, but same problem when used; no intellisense, no compile errors.
changing bunyan.d.ts to
declare module "bunyan" {
export var RingBuffer:RingBuffer;
}
causes compile error in myNodeApp.js
// error TS2083: Invalid 'new' expression
import rb = new bunyan.RingBuffer();
removing from bunyan.d.ts
declare module "bunyan" {
...
}
causes compile error in myNodeApp.js
// error TS2071: Unable to resolve external module ''bunyan''
import bunyan = require( 'bunyan' );
changing bunyan.d.ts
interface IExternal {
inheritedMethod():void;
}
interface IRingBuffer extends IExternal {
}
declare module "bunyan" {
export class RingBuffer implements IRingBuffer {}
}
cause compile error
// error TS2137: Class "bunyan".RingBuffer declares interface IRingBuffer but
// does not implement it: type '"bunyan".RingBuffer' is missing property
// 'inheritedMethod' from type 'IRingBuffer'
implying I have to redeclare all inherited methods from all extended interfaces, besides IRingBuffer, which seems a bit ridiculuous to have to do in a d.ts file
Does anyone know the 'correct' way to declare an ambient class that implements an interface for consumption in another CommonJS module??
An alternate way to define it would be the way Jquery's typescript definition is defined. You have separate interfaces for static and instance members. Here is a sample complete definition:
interface IExternal {
inheritedMethod():void;
}
interface IRingBuffer extends IExternal {
write():void;
}
// Static functions and constructors
interface IRingBufferStatic{
new():IRingBuffer;
}
declare var RingBuffer:IRingBufferStatic;
declare module "bunyan" {
export var RingBuffer:IRingBufferStatic;
}
// In the second file
import bunyan = require( 'bunyan' );
var rb = new bunyan.RingBuffer();
// you get an error here
rb.badFunc();
Try it online
I seem to be experiencing a problem when using Jackson to serialize to XML. My code is below:
TEST CONTAINER
package com.test;
import java.util.ArrayList;
import com.fasterxml.jackson.annotation.JsonProperty;
public class TestContainer {
private String testContainerID;
private String testContainerMessage;
private ArrayList<TestChild> testContainerChildren;
#JsonProperty("TestContainerID")
public String getTestContainerID() {
return testContainerID;
}
#JsonProperty("TestContainerID")
public void setTestContainerID(String testContainerID) {
this.testContainerID = testContainerID;
}
#JsonProperty("TestContainerMessage")
public String getTestContainerMessage() {
return testContainerMessage;
}
#JsonProperty("TestContainerMessage")
public void setTestContainerMessage(String testContainerMessage) {
this.testContainerMessage = testContainerMessage;
}
#JsonProperty("TestContainerChildren")
public ArrayList<TestChild> getTestContainerChildren() {
return testContainerChildren;
}
#JsonProperty("TestContainerChildren")
public void setTestContainerChildren(ArrayList<TestChild> testContainerChildren) {
this.testContainerChildren = testContainerChildren;
}
}
TESTCHILD
package com.test;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonRootName;
#JsonRootName(value="TestChild")
public class TestChild {
private String testChildID;
private String testChildMessage;
#JsonProperty("TestChildID")
public String getTestChildID() {
return testChildID;
}
#JsonProperty("TestChildID")
public void setTestChildID(String testChildID) {
this.testChildID = testChildID;
}
#JsonProperty("TestChildMessage")
public String getTestChildMessage() {
return testChildMessage;
}
#JsonProperty("TestChildMessage")
public void setTestChildMessage(String testChildMessage) {
this.testChildMessage = testChildMessage;
}
}
USE
Serialization:
XmlMapper xm = new XmlMapper();
TestContainer tc = xm.readValue(sb.toString(), TestContainer.class);
Deserialization:
System.out.println(xm.writeValueAsString(tc));
tc = xm.readValue(sb.toString(), TestContainer.class);
What I'm doing is loading an XML file from a folder on the classpath and putting the contents of the file into a StringBuffer. The problem is the generated XML for the collection of objects. When writing the XML, I want something like:
<TestContainerChildren><TestChild><...(Element Details)...></TestChild></TestContainerChildren>
but I'm getting:
<TestContainerChildren><TestContainerChildren><...(Element Details)...><TestContainerChildren></TestContainerChildren>
I'm not sure what I'm missing, here. I have no problem with the JSON part of the serialization/deserialization, only the XML. I've tried using both Jackson and JAXB annotations to turn off wrapping, I have tried using the following annotations:
#JsonRootName
#JsonProperty
#JacksonXmlElementWrapper
#JacksonElement
#XmlElementWrapper
#XmlElement
I'm pretty sure this is something stupid on my part, but any help would be most appreciated.
Ok, couple of notes. First, #JsonRootName only affects name used for the root of XML document, as name implies. So it is not used for TestChild. Second, it sounds like you want to use so-called "unwrapped" output for Lists, omitting element for property that contains List elements. This is doable with:
#JacksonXmlElementWrapper(useWrapping=false)
#JsonProperty("TestContainerChildren")
public ArrayList<TestChild> getTestContainerChildren() { ... }
since default setting is to use wrapper (this is different from JAXB, where unwrapped is the default). Or, if you want to change this globally to assume unwrapped as default, you can change the defaults via XmlModule:
JacksonXmlModule module = new JacksonXmlModule();
// to default to using "unwrapped" Lists:
module.setDefaultUseWrapper(false);
XmlMapper xmlMapper = new XmlMapper(module);
Hope this helps!
I got this working by using the following annotations above the variable declaration:
#JacksonXmlElementWrapper(localName="[insert collection name]")
#JacksonXmlProperty(localName="[insert collection element name]")
This was a simple case of RTFM, as it's documented here.