I want to make a macro that adds a field to my classes, and the field value should be Class<T> of class they are sitting in. Here is my minimal example, and it does not compile with this error:
src/Base.hx:3: characters 2-11 : Unknown identifier : foo.Foo
But if I move Foo from package foo to root package, then it compiles and works.
Main.hx
import foo.Foo;
class Main
{
static function main()
{
trace(Foo.data);
}
}
Build.hx
import haxe.macro.Context;
import haxe.macro.Type;
import haxe.macro.Expr;
class Build
{
macro static public function build(DataClass):Array<Field>
{
var cls = Context.getLocalClass().get();
var pack = cls.pack.concat([cls.name]);
var name = pack.join(".");
trace(name);
var expr = {
expr: ExprDef.EConst(Constant.CIdent(name)),
pos: Context.currentPos()
}
var newFieldCls = macro class {
public static var data:Class<Dynamic> = $expr;
}
var fields = Context.getBuildFields();
return fields.concat(newFieldCls.fields);
}
}
Base.hx
package;
#:autoBuild(Build.build(Main.Data))
class Base
{
public function new()
{
}
}
foo/Foo.hx
package foo;
class Foo extends Base
{
}
EConst(CIdent("foo.Bar")) won't work, because that's very much as if you could specify a name with . periods in it. Instead, you need to do EField({ expr: EConst(CIdent("foo")) }, "Bar"), since that is what foo.Bar gets parsed to by a compiler itself (try traceing expressions from a macro).
So the correct code would be
import haxe.macro.Context;
import haxe.macro.Expr;
class Build {
public static macro function build():Array<Field> {
var self = Context.getLocalClass().get();
var cpos = Context.currentPos();
var out:Expr = null;
inline function add(name:String) {
if (out == null) {
out = { expr: EConst(CIdent(name)), pos: cpos };
} else out = { expr: EField(out, name), pos: cpos };
}
for (name in self.pack) add(name);
add(self.name);
return Context.getBuildFields().concat((macro class {
public static var data:Class<Dynamic> = $out;
}).fields);
}
}
handling creation of a EConst(CIdent) expression and subsequent wrapping it in EField layers for trailing packages, and, finally, the class name.
Related
I have classes called bird and dog:
class Bird extends Animal with FlyAbility, JumpAbility {
// ...
}
class Dog extends Animal with RunAbility, JumpAbility {
// ...
}
Both extend abstract class Animal and use mixin for their own ability.
Now I like to use some extensions on it to output their information:
extension BirdSheetExporter on Bird {
RowData export() {
return RowData(values: [
// type
CellData(stringValue: 'bird'),
// name
CellData(stringValue: name),
// ...
]);
}
}
extension DogSheetExporter on Dog {
RowData export() {
return RowData(values: [
// type
CellData(stringValue: 'dog'),
// name
CellData(stringValue: name),
// ...
]);
}
}
Their are also other exporter like RawTextExporter:
extension BirdRawTextExporter on Bird {
String export() {
String flyAbility = canFly ? 'can' : "can't";
return 'The bird named $name $flyAbility fly';
}
}
This is user responsibility to choose what extension they want. My question is how to give some interface for these extensions and type it?
Something like:
// extension file: raw_text_exporters.dart
abstract class RawTextExporter {
String export();
}
// I know this is not allowed
extension BirdRawTextExporter implements RawTextExporter on Bird {
// ..
}
// client file: export_animal_raw_text.dart
void exportAnimal(RawTextExporter animal) {
print(animal.export());
}
Or any suggestion for this situation? I have found some work around:
typedef rawTextExporter = String Function();
void exportAnimalByExporter(rawTextExporter exporter) {
print(exporter());
}
The reason I'm not using dynamic is code analyzer will consider the imported file not used.
import 'extensions/raw_text_exporters.dart';
// Analyzer thinks `raw_text_exporters.dart` has nothing to do with this function
void exportAnimal(dynamin animal) {
print(animal.export());
}
// type from `raw_text_exporters.dart` and it will keeps the file
void exportAnimal(RawTextExporter animal) {
print(animal.export());
}
I think the better solution will be dependency injection:
class Bird extends Animal with FlyAbility, JumpAbility, Exportable {
// ...
}
mixin Exportable {
T export<T>(Exporter exporter) {
return exporter.export(bird);
}
}
And the exporter's interface will be like:
abstract class Exporter<T> {
T export(Animal animal);
}
class RawTextExporter<String> {
#override
String export(Animal animal) {
final species = animal is Bird ? 'bird' : 'unknown';
return 'The $species named $name ...';
}
}
Is it possible to use Class like a type in interface ? For example, I have a class Animal, can I use something like:
interface I {
object: Animal
}
I've got en error on this situation:
class A {
public static foo(text: string): string {
return text;
}
}
interface IA {
testProp: A;
otherProp: any;
}
class B {
constructor(prop: IA) {
console.log(prop.otherProp);
console.log(prop.testProp.foo('hello!'));
}
}
TS2339: Property 'foo' does not exist on type 'A'
You need to use typeof A:
class A {
public static foo(text: string): string {
return text;
}
}
interface IA {
testProp: typeof A;
otherProp: any;
}
class B {
constructor(prop: IA) {
console.log(prop.otherProp);
console.log(prop.testProp.foo('hello!'));
}
}
The problem in your code is that the foo method is static. Static can only be used on classes not object.
In your case:
A.foo("hello); //works
new A().foo("hello"); //doesn't work since it's an instance of A
How can I declare a class type, so that I ensure the object is a constructor of a general class?
In the following example, I want to know which type should I give to AnimalClass so that it could either be Penguin or Lion:
class Animal {
constructor() {
console.log("Animal");
}
}
class Penguin extends Animal {
constructor() {
super();
console.log("Penguin");
}
}
class Lion extends Animal {
constructor() {
super();
console.log("Lion");
}
}
class Zoo {
AnimalClass: class // AnimalClass could be 'Lion' or 'Penguin'
constructor(AnimalClass: class) {
this.AnimalClass = AnimalClass
let Hector = new AnimalClass();
}
}
Of course, the class type does not work, and it would be too general anyway.
Edit: This question was answered in 2016 and is kind of outdated. Look at #Nenad up-to-date answer below.
Solution from typescript interfaces reference:
interface ClockConstructor {
new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
tick();
}
function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
return new ctor(hour, minute);
}
class DigitalClock implements ClockInterface {
constructor(h: number, m: number) { }
tick() {
console.log("beep beep");
}
}
class AnalogClock implements ClockInterface {
constructor(h: number, m: number) { }
tick() {
console.log("tick tock");
}
}
let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);
So the previous example becomes:
interface AnimalConstructor {
new (): Animal;
}
class Animal {
constructor() {
console.log("Animal");
}
}
class Penguin extends Animal {
constructor() {
super();
console.log("Penguin");
}
}
class Lion extends Animal {
constructor() {
super();
console.log("Lion");
}
}
class Zoo {
AnimalClass: AnimalConstructor // AnimalClass can be 'Lion' or 'Penguin'
constructor(AnimalClass: AnimalConstructor) {
this.AnimalClass = AnimalClass
let Hector = new AnimalClass();
}
}
I am not sure if this was possible in TypeScript when the question was originally asked, but my preferred solution is with generics:
class Zoo<T extends Animal> {
constructor(public readonly AnimalClass: new () => T) {
}
}
This way variables penguin and lion infer concrete type Penguin or Lion even in the TypeScript intellisense.
const penguinZoo = new Zoo(Penguin);
const penguin = new penguinZoo.AnimalClass(); // `penguin` is of `Penguin` type.
const lionZoo = new Zoo(Lion);
const lion = new lionZoo.AnimalClass(); // `lion` is `Lion` type.
Like that:
class Zoo {
AnimalClass: typeof Animal;
constructor(AnimalClass: typeof Animal ) {
this.AnimalClass = AnimalClass
let Hector = new AnimalClass();
}
}
Or just:
class Zoo {
constructor(public AnimalClass: typeof Animal ) {
let Hector = new AnimalClass();
}
}
typeof Class is the type of the class constructor. It's preferable to the custom constructor type declaration because it processes static class members properly.
Here's the relevant part of TypeScript docs. Search for the typeof. As a part of a TypeScript type annotation, it means "give me the type of the symbol called Animal" which is the type of the class constructor function in our case.
How can I declare a class type, so that I ensure the object is a constructor of a general class?
A Constructor type could be defined as:
type AConstructorTypeOf<T> = new (...args:any[]) => T;
class A { ... }
function factory(Ctor: AConstructorTypeOf<A>){
return new Ctor();
}
const aInstance = factory(A);
unlike class var, where they can overridden in subclasses, I believe same applies to static as well but unfortunately not. Here's an example
public class A {
private static let NAME: String = "A"
}
public class B: A {
private static let NAME: String = "B" //error
}
in my opinion static means association with that particular class, so in the above example B should get it's own space to redefine that variable as it's associated to B only, I'm reverting to stored properties unless someone has a better solution.
You can use computed properties:
class A {
class var Name: String {
return "A"
}
}
class B: A {
override class var Name: String {
return "B"
}
}
Usage:
print(A.Name) // "A"
print(B.Name) // "B"
The documentation says:
“
static
” methods and properties are now allowed in classes (as an alias for “
class final
”).
So it is final, which means you cannot override it.
As suggested, you cannot override static variables but can use class (static) function to override.
class A {
class func StaticValue() -> AnyObject {
return "I am a String"
}
}
class B: A {
override class func StaticValue() -> AnyObject {
return 2
}
}
You can have computed class properties:
public class A {
class var NAME: String {
return "A"
}
}
public class B {
class var NAME: String {
return "B"
}
}
If need be, you could even "branch" to a stored property in the subclass:
public class A {
// Needs to be overriden in subclass
class var NAME: String {
return "A"
}
}
public class B {
class var NAME: String {
return B.storedName
}
static var storedName: String = defineName()
func defineName() -> String {
// You could do something more complex here
return "B"
}
}
Is it currently possible to implement an indexer on a class in TypeScript?
class MyCollection {
[name: string]: MyType;
}
This doesn't compile. I can specify an indexer on an interface, of course, but I need methods on this type as well as the indexer, so an interface won't suffice.
Thanks.
You cannot implement a class with an indexer. You can create an interface, but that interface cannot be implemented by a class. It can be implemented in plain JavaScript, and you can specify functions as well as the indexer on the interface:
class MyType {
constructor(public someVal: string) {
}
}
interface MyCollection {
[name: string]: MyType;
}
var collection: MyCollection = {};
collection['First'] = new MyType('Val');
collection['Second'] = new MyType('Another');
var a = collection['First'];
alert(a.someVal);
This is an old question, for those looking for the answer: now it's possible to define a indexed property like:
let lookup : {[key:string]:AnyType};
the signature of the key must be either string or integer see:
Interfaces on www.typescriptlang.org
Is not possible to define an indexed property getter/setter in a class but you can "simulate" that in a way like this using Proxy:
class IndexedPropSample {
[name: string | symbol]: any;
private static indexedHandler: ProxyHandler<IndexedPropSample> = {
get(target, property) {
return target[property];
},
set(target, property, value): boolean {
target[property] = value;
return true;
}
};
constructor() {
return new Proxy(this, IndexedPropSample.indexedHandler);
}
readIndexedProp = (prop: string | symbol): any => {
return this[prop];
}
}
var test = new IndexedPropSample();
test["propCustom"] = "valueCustom";
console.log(test["propCustom"]); // "valueCustom"
console.log(test.readIndexedProp("propCustom")); // "valueCustom"
console.log(test instanceof IndexedPropSample); // true
console.log(Object.keys(test)); // ["propCustom", "readIndexedProp"]
you can try it in Typescript Playground