Hi all,
We currently have a AST model for TypeScript which is useful for compilers, editors, and linters.
However, it would be extremely useful to have a type model, a la Java's reflective model. There are a huge number of use-cases supporting this, but I will list a few here:
A good example of what could be achieved with this, is something like the Spring platform for Java where our applications are built as decorated components and most of the boilerplate code is abstracted away by the platform thanks to reflection and decorators.
I have a "first stab" at implementing such a thing: typescript-schema. It's not feature complete and the design isn't finished either. But hopefully it gives a feel for what a reflective model could look like.
Is this something the TypeScript team would be interested in having as a first class citizen?
You might be interested in https://github.com/Microsoft/TypeScript/issues/2577. Reflection type functionality has been an oft requested but controversial issue for us.
Another related topic would be #3136.
I use injection decorators on class properties/constructor parameters, and use the type model to reflectively wire the correct instance.
can you elaborate on how this is done.
@danquirk So when @jonathandturner said at http://blogs.msdn.com/b/typescript/archive/2015/03/05/angular-2-0-built-on-typescript.aspx
We've also added a way to retrieve type information at runtime. When enabled, this will enable developers to do a simple type introspection.
he meant metadata generated only for decorators, not full-scale reflection (for all public symbols, for example)?
he meant metadata generated only for decorators, not full-scale reflection (for all public symbols, for example)?
correct.
What he said :)
I'm happy to see:
A few notes on metadata:
- Type metadata uses the metadata key "design:type".
- Parameter type metadata uses the metadata key "design:paramtypes".
- Return type metadata uses the metadata key "design:returntype".
From https://github.com/Microsoft/TypeScript/pull/2589
What other metadata are you planing to support in the future? There are a few things that I would like to see:
"design:implements"
. "design:paramnames"
.Of course "design:implements"
would require the interface names to be serialized using the interfaces names not Object
(or complex type serialization). The same any case in which an interface is serialized.
Parameter name metadata uses the metadata key "design:paramnames".
this sounds interesting. and should be doable
The same any case in which an interface is serialized.
Interfaces are not serialized today. only names with a value attached to them (i.e. classes) and built-ins are serialized.
About:
Interfaces are not serialized today
I'm aware of a gist (by Ron Buckton) about complex type serialization, is that going ahead? Are you guys considering adding interface serialization support at some point?
As mentioned in my original post, I've got a project called typescript-schema that provides this kind of serialisation, as well as a reflective type model:
It has two types of model: A serialisable model, and a runtime reflective model.
It works by: AST -> Serialisable Model -> Runtime reflective model
It currently only supports 1.5, and only exported types. It's not 100% complete, but is able to process the entire TypeScript library itself, and the node type library too (as well as a number of other large libraries).
What I'd really like is to understand is:
a) if the TypeScript team would want any of this code, and if so
b) what would be required to make it ready for inclusion? I.e.: as it stands it's largely standalone (and probably doesn't follow all the code standards)
To give you an example of it at work, imagine this TypeScript code:
import {X} from 'my-module'
export interface MyInterface<T> {
f(x: X, s: string):T
}
This will serialise to:
{
"instanceType": {
"typeKind": "COMPOSITE",
"members": {
"f": {
"optional": false,
"type": {
"typeKind": "FUNCTION",
"parameters": [{
"name": "x",
"type": {
"module": "my-module"
"name": "X"
},
{
"name": "s",
"type": {
"typeKind": "PRIMITIVE",
"primitiveTypeKind": "STRING"
}
}],
"type": {
"module": "@",
"name": "T"
}
}
}
}
}
}
}
This can then be converted into a reflective model, which can be used like:
let myInterfaceConstructor:InterfaceConstructor //Called an interface constructor because it has type parameters
myInterfaceConstructor.parent // The parent module or namespace
myInterfaceConstructor.name // "MyInterface"
myInterfaceConstructor.typeParameters[0].name // "T"
myInterfaceConstructor.instanceType.members['f'].type.name // "T"
myInterfaceConstructor.instanceType.members['f'].parameters[0].name // "x"
You can then "close" that reflective interface by providing a type argument, and the output will be the corresponding interface with all type parameters correctly substituted:
let myInterface = closeInterfaceConstructor(myInterfaceConstructor, [someType])
myInterface.typeConstructor // myInterfaceConstructor
myInterface.instanceType.members['f'].type // someType
To check it out, go to:
and
typescript-package (which provides the code for converting a package of TypeScript into the serialisable form - including typings and nested packages via the node resolve algorithm)
When I asked for a "design:paramnames"
metadata key.
@mhegazy said:
this sounds interesting. and should be doable
So I have created a new issue to request it: https://github.com/Microsoft/TypeScript/issues/4905
Interfaces are not serialized today. only names with a value attached to them (i.e. classes) and built-ins are serialized.
What if you guys will introduce new language semantic? Something like realtime interface Name {...}
which will remain realtime (with empty method bodies for easing mocking in testing frameworks)?
I doubt the interface serialization will be implemented somewhen, since there is a much code written which assuming that interfaces are not realtime objects.
I forced to use classes as interfaces now to make DI work
Is this issue getting at being able to do something like the following for using interface annotations as DI tokens?
interface IAlgorithm {
execute(): void;
}
class FancyAlgorithm implements IAlgorithm {
execute(): void {
let i = 123 * 456;
}
}
class AlgorithmExecuter {
constructor(private algorithm: IAlgorithm) { }
execute(): void {
this.algorithm.execute();
}
}
class Program {
main() {
// DI API based on autofac DI for .NET
// container could be populated any number of ways just imperative for illustration
let builder: Container = new ContainerBuilder();
builder.RegisterType(FancyAlgorithm).As(IAlgorithm);
builder.RegisterType(AlgorithmExecuter).AsSelf();
// now invoke the composition root
let executer = builder.Resolve(AlgorithmExecuter);
executer.execute();
}
}
The specific reason I'm asking is because when using a DI system for TypeScript that uses type annotations as metadata (such as what exists for Angular 2) this is currently not possible because no runtime "Reflect" unique metadata is emitted for interfaces. In DI circles this is basically the way DI based apps are built. It's extremely powerful and makes for very flexible and easily testable apps.
If this issue doesn't involve this kind of metadata please let me know as I'd like to create a separate proposal issue. :smile:
@mikehaas763 nothing stops you today from doing the same thing but using strings in place of types. i am assuming this would be sufficient. your DI system needs to have a key to store and locate these, if you use strings, then nothing in this issue should block you.
@export("IAlgorithm")
class FancyAlgorithm implements IAlgorithm {
execute(): void {
let i = 123 * 456;
}
}
@mhegazy I don't understand what you're trying to portray with that code snippet. What I would like to do is be able to register an interface type as a key in a DI container. Can you elaborate?
I don't understand what you're trying to portray with that code snippet. What I would like to do is be able to register an interface type as a key in a DI container.
I do not know much about autofac, so my comments are assuming you have a central registry that records mapping between a type and an exported entity, something a la MEF.
I am also assuming you need a key, and you want to use the name of the interface as key, all what i am saying is you can do that by making the key as a string (in this case using the name of the interface, but it should be something more unique).
the export
decorator i am using refers to something like MEF ExportAttribute, in JS terms this would be something that takes a key (interface name) and a value (a class constructor), and keeps them in the list (along with arguments to the constructor possibly). that is basically will call your builder.RegisterType(FancyAlgorithm).As(IAlgorithm);
except that it will be somethign like builder.RegisterType(FancyAlgorithm).As("IAlgorithm");
later on your call to builder.Resolve("IAlgorithm");
and that would return you all constructors with this key.
UPDATE: I wrote this before you replied above. Reading your response now.
@mhegazy A problem with using strings in place of types is that you lose the automatic given uniqueness of that type. Two different libraries may have an IAlgorithm interface type and both may need to be registered with a single DI container. That's easy enough to do like so if the compiler generated some sort of unique metadata token to help guarantee uniqueness and a fake import could somehow still work at runtime. So something like the following:
import IAlgorithm as ILibAAlgorithm from './libA';
import IAlgorithm as ILibBAlgorithm from './libB';
import AImplementation from './a';
import BImplementation from './b';
builder.RegisterType(AImplementation).As(ILibAAlgorithm);
builder.RegisterType(BImplementation).As(ILibBAlgorithm);
The very nature of the two IAlgorithms being two different things is enforced by them being defined separately in separate modules. I understand that this is not possible today because there is no IAlgorithm to import at runtime (or do I have this wrong?). What I'm saying is that this would be a nice feature to have in TS. I'm speaking for myself now but also for the swaths of developers that I guarantee will reiterate wanting to see the same capability as TS becomes used more.
So either I have this completely wrong and it's already possible :smile: or if not it would be awesome if we could start talking about what the actual implementation would look like and make it a formal proposal for TS or there is just better ways to do this and I'm bringing over too much cruft to TS from Java/C# etc.
UPDATE: I finished reading your reply above. Yes you can assume there is a registry (the container in autofac). I get that it would be possible by registering a type against a string key in a container but do you see the concerns I have with that around uniqueness and robustness?
I am also assuming you need a key, and you want to use the name of the interface as key
I don't want to use the string name of the interface as a key, I want to use the interface itself as a key. This is possible with concrete types because they exist at runtime. Interfaces don't exist at runtime and obviously for good reason but it would be nice to have this sort of metadata for interfaces at runtime.
One implementation that may work is to compile the interface to an empty lightweight concrete type so it can be used at runtime.
// for example
export default interface IFoo {
foo(): void;
bar(): void;
lorem(): void;
ipsum(): void;
}
// could be compiled to (depending on the target but in this case ES6)
export default class IFoo {}
That way, IFoo is a guaranteed unique key at runtime.
I think Mike's point addresses a real concern for serious application development which utilize frameworks and Dependency Injection systems. With out something like Mike's proposal in place DI systems typically work as you suggest on strings or tokenized versions of the strings. In a non TS world, that works for the most part. But in a TS/typed universe I think we should be including and utilizing types where possible in our DI systems.
@mhegazy Do you think I should start a new issue to propose/track this?
I'd like to get more people's thoughts on this. After stepping away I think the most seamless way to do this would be like I mentioned above is to compile an interface type to a "class" type. This is already done with abstract class types.
Maybe a compiler option such as --runtimeInterfaces
? I realize that this in a way is a huge change because the spec is explicit that interfaces do not exist at runtime. I know that abstract classes are now supported. Am I forced as a developer using DI to now use abstract classes wherever I would have previously used just an interface?
I mentioned above is to compile an interface type to a "class" type. This is already done with abstract class types.
I am not sure i understand what you mean by "compile" to "class type". interfaces do exist in a different space (type space), and emit a value for them would cause problems when it comes to merging (see declaration merging docs for more information).
I want to use the interface itself as a key. This is possible with concrete types because they exist at runtime.
one thing to note is TypeScript's type system is structural, it is not nominal (like other languages such as C# and Java with DI systems). so the interface name, or declaration is of no consequence here; consider an example with two different classes looking for interfaces with different names, but comparable structures, do you want your DI to find them or not?
interface Person {
name: string;
title?: string;
}
interface SomethingWithAName {
name: string;
}
class C {
constructor(c: SomethingWithAName) {}
}
var p: Person;
new C(p); // this is fine, the two interfaces are valid
then what would you do with things that do not have a name?
class C {
constructor(a: {name: string}) {}
}
or type aliases?
type myType = string;
class C {
constructor(a: myType) {}
}
or more complex type operators:
type myType = string;
class C {
constructor(a: (string | { name: string }) & EventTarget) {}
}
Obviously a typical DI system from a language with nominal type system like C# would not fit here with no compromises. i would say you will have to limit the set of supported language constructs to allow your DI system to work, i.e. say only classes are supported by this system, interfaces, structural types, etc will not be allowed.
If you agree with this proposition, then it should just work with classes. classes have values, and can be used at rumtime as keys to your DI system. and that should be easy to model today using decorators, e.g.:
// use an abstract class instead of an interface
abstract class Algorithm {
abstract execute(): void;
}
@exportAs(Algorithm) // use the class constructor as a key for your type lookups
class FancyAlgorithm implements Algorithm { // use implements here, so no runtime changes (i.e no calls to __extend)
execute(): void {
let i = 123 * 456;
}
}
abstract class Class { }
function exportAs<T extends Class>(typeObject: T) {
return function (target: T): void {
// wire the export
builder.RegisterType(target).As(typeObject);
}
}
// later at runtime you would do:
let executer = builder.Resolve(Algorithm); // should find FancyAlgorithm
Obviously a typical DI system from a language with nominal type system like C# would not fit here with no compromises. i would say you will have to limit the set of supported language constructs to allow your DI system to work, i.e. say only classes are supported by this system, interfaces, structural types, etc will not be allowed.
If you agree with this proposition, then it should just work with classes. classes have values, and can be used at rumtime as keys to your DI system. and that should be easy to model today using decorators, e.g.:
Using abstract classes as interfaces approach works not very well for DI. Any abstract methods will be omitted from compiled JS, so it's not possible to create tests stubs from abstracted classes (for ex. using sinon.createStubInstance()). Also the compiler doesn't emit any runtime checks to prevent creation abstract classes at runtime. If you forgot to bind implementation to abstract class acting as interface, it will create empty object of your interface instance, instead giving you error:
abstract class TestInterface { ... }
class TestController {
@Inject
public myService: TestInterface;
}
// Forgot to bind any implementation to TestInterface
let controller = container.resolve(TestController); // Doesn't throw any error since abstract class could be created at runtime.
I forced to use now these ugly constructions:
class Algorithm {
constructor() {throw new Error();}
public execute(): void {}
public calculate(): number {return}
}
class MyAlgorithm implements Algorithm { .... }
With this, runtime checking and mocking methods (like sinon.createStubInstance()) will work correctly.
As an FYI, I was just suggesting compiling an interface to a type as one specific way to solve the problem of providing this sort of interface metadata. It could be handled other ways.
interfaces do exist in a different space (type space)
I'm aware. I suggested this purely as a means to be able to use interfaces in a meta way at runtime by just compiling it to a value type. I'm not saying it's the only way or even the proper way.
and emit a value for them would cause problems when it comes to merging
Why would this cause issues with merging? Merging does occur when an interface is defined in separate modules does it?
I'm aware that the type system is structural which I admit makes it harder for me to reason about this.
then what would you do with things that do not have a name?
If I was declaring the type annotation of something with a literal, than I wouldn't expect to be able to use that as a key in my DI system.
If you agree with this proposition, then it should just work with classes. classes have values, and can be used at rumtime as keys to your DI system
The problem with depending on classes is that it blatantly violates the dependency inversion principle: "Depend upon Abstractions. Do not depend upon concretions.". I can see people saying "see, just use an abstract class for that". Well, if I just end up using an abstract class just like an interface, what is the point of having interfaces in TS?
At the end of the day this conversation is just about possibilities to provide a means to an end and allowing something that makes development better and easier. That end being I want to be able to continue to program like the following snippet but also have dependencies injected as expected based on the type annotation that is _already_ specified (IDependency).
class Foo {
constructor(dependency: IDependency) {}
}
In the meantime I had planned on just using abstract classes as interfaces (like in the following snippet), but will have to look more closely at the problems that introduces that @asvetliakov mentioned above.
abstract class IAlgorithm {
abstract execute(): void;
}
Ideally it will be cool if you guys implement something like that:
interface realtime MyInterface {
public method1(): void;
public method2(): string;
}
Which will be compiled to:
function MyInterface() {
throw new Error("Not possible to create MyInterface instance");
}
MyInterface.prototype.method1 = function () { throw new Error("Not possible to call interface method MyInterface::method1"); }
MyInterface.prototype.method2 = function() { throw new Error("Not possible to call interface method MyInterface::method2"); }
This will not interfere with existing interfaces in type scope and people will be able to use DI as it was intended.
Or at least provide runtime constructor checking & empty function stubs for abstract classes & methods
@asvetliakov I like where you're going with that but at the same time IDK if I like that it has to be explicitly marked. If I'm implementing an interface that some 3rd party lib has provided, then I don't have the ability to mark that interface.
@mikehaas763 Don't see any issues. 3rd party libs are usually declaring variable (with interface type) if exporting something, so there will be runtime information and you can use this variable as key for DI.
@asvetliakov I mean them declaring an interface to use as in the concept of inversion of control. They define an interface that a consumer implements and passed back into their lib.
Why would this cause issues with merging? Merging does occur when an interface is defined in separate modules does it?
now you are talking about limiting it to only module scopes. that was not in the OP.
Well, if I just end up using an abstract class just like an interface, what is the point of having interfaces in TS?
interfaces in TypeScript are design-only constructs, just like the rest of the types. using these constructs in the value space is not a TS design goal.
talking with @rbuckton today, here is another option that works today and does not use classes:
const IAlgorithm = new Symbol("IAlgorithm"); // unique identifier for the interface
interface IAlgorithm {
execute(): void;
}
@exportAs(IAlgorithm) // the interface and the var are merged
class FancyAlgorithm implements Algorithm {
execute(): void {
let i = 123 * 456;
}
}
@mhegazy Your approach is good because you only have one magic string:
declare class Symbol {
constructor(a: string);
};
function bind<T>(a : Symbol, b : T) {}
function resolve<T>(a : Symbol) {}
function inject(...types : Symbol[]){
return function(target : any) {
}
}
const IFoo = new Symbol("IFoo"); // The only magic string in the whole process
interface IFoo {}
class Foo implements IFoo {}
@inject(IFoo) // No more magic strings when declaring injections
class Test {
constructor(algorithm : IFoo) {
}
}
bind<IFoo>(IFoo, Foo); // No more magic strings when declaring bindings
var foo = resolve<IFoo>(IFoo); // No more magic strings when resolving a dependency
I think that we need to come up with some way to enable this without having to do extra work. When I say extra work I mean declaring magic strings or symbols. This are just "manual metadata hacks" metadata should be generated by the compiler.
I don't know if it is the best way but we could use a new compiler option emitInterfaceSymbols
.
When the emitInterfaceSymbols
flag is used and we declare an interface:
interface IFoo{}
The symbols are generated by the compiler:
const IFoo= new Symbol("IFoo");
At design time we would need to use declare var IFoo : Symbol;
to avoid compilation errors:
// the run-time symbol is generated by the compiler but we need to declare at design-time
// is extra work but at least it is not a magic string...
declare var IFoo : Symbol;
declare class Symbol {
constructor(a: string);
};
function bind<T>(a : Symbol, b : T) {}
function resolve<T>(a : Symbol) {}
function inject(...types : Symbol[]){
return function(target : any) {
// ...
}
}
interface IFoo {}
class Foo implements IFoo {}
@inject(IFoo)
class Test {
constructor(algorithm : IFoo) {
}
}
bind<IFoo>(IFoo, Foo);
var foo = resolve<IFoo>(IFoo);
It would be cool if the IDE is able to identify when we are declaring an interface and using the emitInterfaceSymbols
flag and it would automatically assume that the a symbol with he same name as the interface is declared. So we don't need to type the line below to avoid compilation errors:
declare var IFoo : Symbol;
Hi everybody, I would just add my 2 cents to the discussion. Some time ago I started working on a type serializer that is based on rbuckton's proposal types.d.ts; currently it's in a prototyping stage and not published yet, but I have uploaded some sample code in order to discuss a possible way of implementing full type serialization. I know, there could be many drawbacks when doing this, but in some cases could be useful to have all information about implemented interfaces, generic types and so on. I have included some open points in the readme, I would be glad if you took a look at it here.
Ok, this discussion is definitly interresting, and I will openly admit that I'm no expert on this, yet.
However, reading through it all, it seems like the thing most people are asking for is actually something very simple - just compile interfaces to an empty constructor function, so we have something at runtime we can use as a key when doing dependency injection. Thats it. No theoretical discussion of type systems and stuff there. We just need that key.
To me, this sounds like something that should be fairly simple to implement, and it would be consistent with how abstract classes are currently compiled. Abstract classes do already exist at runtime, and we just need interfaces to be compiled in exactly the same way. I really don't see the difference here, and the DI problem everyone is talking about is very real - when building real-world apps, we absolutely need the ability to use interfaces as keys for DI.
I think it would be a good idea to track this in a separate issue, and then deal with the the whole question of reflection and type model separately, as it is really a completely different issue. I therefore suggest that #3060 is reopened, as it represents exactly what is being asked for here.
Unless of course I'm missing the point, and there is a good reason why this is not technically possible?
I really see no reason why we should have to deal with magic strings anywhere to acheive this.
However, reading through it all, it seems like the thing most people are asking for is actually something very simple - just compile interfaces to an empty constructor function
So just write your interfaces as classes. that should be a simple regexp.
So just write your interfaces as classes. that should be a simple regexp.
Then what's the point of having interfaces in TS in the first place? Why not just stop using interfaces in your codebase, deprecate the construct from the lang, and move on?
However, it is what I'm doing now. I think many people (including myself earlier on) didn't realize that you can implement constructs other than interfaces like class Foo implements AbstractOrConcreteType{}
. A stringent code review process can help ensure that no implementations are allowed into abstract classes and we just treat the abstract classes purely as an interface. It does still come back to the idea of why even have interfaces in TS going forward?
Then what's the point of having interfaces in TS in the first place? Why not just stop using interfaces in your codebase, deprecate the construct from the lang, and move on?
interfaces, as well as type aliases, do not exist at run-time. They are merely a design time construct. this is the whole point of TypeScript :) a design time tool to allow you to make sense of your code with no runtime cost.
Classes, variables, etc.. are runtime constructs by definition. So I am confused why you want interfaces to have a runtime manifestation, but at the same time do not use classes.
If you want a specific use case for this, the dependency injection in the Aurelia framework is a very good example. This works today if IEngine
is an abstract class, and would work for interfaces too, if only the interface existed at runtime.
var container = new Container();
container.registerSingleton(IEngine, Engine);
@autoInject()
export class Car {
constructor(public engine: IEngine) {
}
}
The problem is, I can't just replace my interfaces with abstract classes, as that would prevent my implementation from inheriting from anything other than the interface. Maybe I want my Engine
to inherit from CarPart
or something - interfaces are about composition, _not_ inheritance.
Is there any technical reason why interfaces cannot be compiled in the same way abstract classes are?
Of course, in the IDE the two things are quite different, but we just need the same output in JS.
interfaces, as well as type aliases, do not exist at run-time. They are merely a design time construct. this is the whole point of TypeScript :) a design time tool to allow you to make sense of your code with no runtime cost.
I know this and you've reiterated it to me several times. The point comes down to DI. If I can't use interfaces for pragmatic use cases such as with a DI framework, how much value do they really add. A developer (the consumer) should not be thinking about whether certain constructs are available at runtime or not when they are just trying to get work done.
I absolutely agree.
@mhegazy If an abstract class has a runtime manifistation, why should it be any different for an interface? I realize typescript is considered a typed superset of ecma script, but interfaces and DI is absolutely essential to building real world apps, and I see no reason why typescript should not make this possible.
interfaces are about composition, not inheritance.
I see your point. But generally interfaces are about enabling one to compose, but really anything that implements an interface etc does become a sub type of it. Therefore it's kind of inheritance too. :smile:
The problem is, I can't just replace my interfaces with abstract classes, as that would prevent my implementation from inheriting from anything other than the interface.
I'm not following why it's not technically possible?
abstract class Foo
{
void DoFoo();
}
class Bar implements Foo
{
void DoFoo() {}
}
someFunction(fooer : Foo){}
someFunction(new Bar());
Is there any technical reason why interfaces cannot be compiled in the same way abstract classes are?
Not technical reasons but it's a change to the spec and I think historically the reason has been about not shipping extra cruft to the browser.
Hi guys, I would just to point up that this kind of things IS already feasible (I did it, just I said previously), but it may be better if the compiler would be a bit more "extensible", so I should not have to wire up things in the compiler in order to emit interfaces, classes and other metadata; we could simply recall a compiler plugin to do this job. The main issue is not the extraction of metadata (I'm using the plain compiler API), but its "linking" to the emitted code.
@mikehaas763: abstract classes won't cover the case of mixin interfaces. A Dependency Injection mechanism could be driven by more interfaces, and still injecting the same singleton object, just because it implements more than one interface.
Hmmm, ok, I think I might have missed the point that a class can implement
other classes like that - that's really interresting, and it does in fact make what I'm talking about possible using abstract classes.
However, it does feel extremely strange to use an abstract class as if it was an interface - and the fact that it is possible to e.g. define actual methods, and not just method signatures, in an abstract class, that just feels plain wrong when what I'm really trying to do, is to define a contract the class must implement.
While I guess abstract classes can be used as a workaround like this, I _still_ think interfaces should have a runtime manifistation, and I'm pretty sure that's what 99% of developers expect when they start using typescript. Having a class implement an interface makes the intent much clearer, compared to abusing abstract classes as if they were interfaces (i.e. contracts with no behavior), and then having code reviews to avoid getting method bodies and other mistakes in there.
I really think you guys should reconsider this, and emit code for interfaces too - again, I don't see why it should be any different from abstract classes. It would just mean that interfaces would work the way pretty much _everyone_ expects them to - otherwise I really see no value in the interface construct at all.
I am firmly of the belief that any DI system for JavaScript is better served by using unique exported string or symbol values. I've been thinking more and more about adding special compiler-specific decorators that could target things like interfaces and add metadata to the classes that implement them.
Just to clarify, I don't really have a strong preference as to whether an interface is emitted as a constructor function or any other unique value - I just used the abstract class as an example due to the similarity between the two, but I guess emitting a Symbol
might technically be more correct, as what we're talking about here is an identity and not something that should ever be instantiated - but really, anything that can be used as a unique key for DI would make me a very happy developer :-)
@remojansen, the solution you suggested, where setting an emitInterfaceSymbols
option will cause symbols to be emitted for interfaces, that sounds a lot like the solution I would like to see - as long as it doesn't require any code changes :-)
Just a quick note here - isn't the question of whether something should be emitted to represent an interface very similar to the case of const enum
declarations?
Normally a const enum
declaration would be compiled away, but here we have the --preserveConstEnums
options to ensure it is preserved in the generated code.
https://github.com/Microsoft/TypeScript/wiki/Compiler-Options
To me, this appears to be a very similar issues... maybe a preserveInterfaces
option could be added, so we can reference an interface
just as if it was a class
, thus enabling the DI scenario discussed here?
Just like code referencing a const enum
may _appear_ to work while coding, and then breaks at runtime as described in https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#94-constant-enum-declarations, the code in my comment https://github.com/Microsoft/TypeScript/issues/3628#issuecomment-163349629 compiles just fine and looks like it should work as expected - but then it all blows up at runtime due to the missing runtime representation of the interface. This is a highly frustrating and unexpected experience, especially for developers new to typescript, and I still believe this is a very legitimate use case for interfaces and therefore something that should be enabled.
@rbuckton I think most of us agree that using string (rather than complex type serialization) is the right balance between what we want to achieve and run-time overhead. In my opinion runtime type assertion is a bad idea.
I saw your comment about interface names not being enough information. This causes the following issue in my project:
interface IA {
x : number;
}
interface IB {
x : number;
}
class A implements IA {
x : number;
}
class B implements IB {
x : number;
}
kernel.bind(new Binding<IA>("IA", A));
kernel.bind(new Binding<IA>("IA", B)); // OK because structural typing
I personally don't mins this issue because:
At the end of the day what really matters when injecting a dependency is its interface not its name.
I'm not planing to do auto-wiring so I can live without 100% identifiable interfaces.
At the moment developers need to manually type the names of the interfaces to be injected:
@Inject("FooInterface", "BarInterface")
class FooBar implements FooBarInterface {
public foo : FooInterface;
public bar : BarInterface;
public log(){
console.log("foobar");
}
constructor(foo : FooInterface, bar : BarInterface) {
this.foo = foo;
this.bar = bar;
}
}
Magic strings are error-prompt and not refactoring-friendly. It would be great if we could leave the compiler add those strings to the metadata using:
Reflect.getMetadata("design:paramtypes", FooBar);
It should return ["FooInterface","BarInterface"]
so users can forget about magic strings.
Note that we need actual strings not functions named like the interfaces because we need to be able to use JS compressors.
I don't think using Symbols is a good idea because I think it could create some problems:
var f2 = Symbol("FooInterface");
var f = Symbol("FooInterface");
f == f2 // false
Specially if we are consuming third party code with metadata.
The problem with Reflect.getMetadata
returning an array of strings is the possibility of collisions. Consider this:
// a.ts
export interface Service { x: number; }
// b.ts
export interface Service { y: number; }
// main.ts
import * as a from "a";
import * as b from "b";
class ServicesImpl implements a.Service, b.Service {
}
Reflect.getMetadata("design:paramtypes", ServicesImpl); // ["Service", "Service"]
Strings are useful if they are unique, and there's no reliable way for TypeScript to generate a unique string that doesn't change per build. It becomes a refactoring and code reorganization hazard.
A better approach would be this:
// a.ts
export interface Service { x: number; }
export const ServiceName = "urn:a:Service";
// b.ts
export interface Service { y: number; }
export const ServiceName = "urn:b:Service";
// main.ts
import * as a from "a";
import * as b from "b";
import { provide, getService } from "some-composition-framework";
@provide(a.ServiceName)
@provide(b.ServiceName)
class ServicesImpl implements a.Service, b.Service {
}
const aService = getService<a.Service>(a.ServiceName);
const bService = getService<b.Service>(b.ServiceName);
Symbols are event better, as they can guarantee uniqueness:
// a.ts
export interface Service { x: number; }
export const ServiceKey = Symbol("Service");
// b.ts
export interface Service { y: number; }
export const ServiceKey= Symbol("Service");
// main.ts
import * as a from "a";
import * as b from "b";
import { provide, getService } from "some-composition-framework";
@provide(a.ServiceKey)
@provide(b.ServiceKey)
class ServicesImpl implements a.Service, b.Service {
}
const aService = getService<a.Service>(a.ServiceKey);
const bService = getService<b.Service>(b.ServiceKey);
The problem with Reflect.getMetadata returning an array of strings is the possibility of collisions.
I don't know if this option has already been discussed, but... What about a fully qualified name? the tsconfig.json (in the "root package") already contains the list of each ts file, which could be used as the "base name" for interfaces.
Example:
// common/baz/StringUtils.ts
export interface Service { x: number; }
// foo/bar/AnotherService.ts
export interface Service { y: string; }
// Test.ts
import * as a from "common/baz/StringUtils";
import * as b from "foo/bar/AnotherService";
class ServicesImpl implements a.Service, b.Service {
}
Reflect.getMetadata("design:paramtypes", ServicesImpl); // ["common.baz.StringUtils#Service", "foo.bar.AnotherService#Service"]
Maybe this could be better than nothing :)
EDIT:
the same strategy may be used for modules, for example:
// common/baz/StringUtils.ts
export module foo {
export interface Service { x: number; }
}
// emitted as: "common.baz.StringUtils#foo.Service"
If someone is still interested in reflection capabilities, I just released an enhanced version of the compiler that emits classes/interfaces metadata for runtime type checking. The compiler injects some synthetic instructions in the AST just after the parsing phase, so the metadata is available also for intellisense/autocompletion. I haven't tested this with Visual Studio, but with Atom it works really well. Keep an eye on this.
@pcan great work :+1: we really need better reflection in TypeScript :cry: I hope to see something in the official compiler soon. We could create so many amazing tools.
I just implemented a proof-of-concept for using decorators to define Models in sequelize:
https://github.com/felixfbecker/sequelize-decorators
If you don't know, Sequelize is the most popular ORM for NodeJS. In version 4 we are enabling users to use classes to define their models. This is great for TypeScript, because we can use decorators to define the attribute metadata, like what column type it should be, if it should allow NULL, if it should be a primary key, if it should have a unique constraint etc etc.
I actually implemented it in a way that you can leave out the type for the types that are already emitted under design:type
. So if the property is a Date
, you don't need to pass to the @Attribute
decorator that the sequelize / database data type should be DATE
, it is inferred. Similarly, Buffer
is inferred as BLOB
. But it is very limited atm, and I see so much more potential. For example:
Date | null
(TS 2.0), we could automatically allow NULL
, else set a NOT NULL
constraint. Currently this will just become Object
. We need union type info.design:types
will currently simply be Array
. But a database needs to know what _type_ of array it can be, so we need generic type info. The type would follow the same inference rules described here.enum
or a union type of string literals (or a type alias to such), we could infer an ENUM
, with the possible values. This would be really awesome because currently you have duplicate the possible values for the type and for the model definition.{[key: string]: string}
, or a Map
, we could infer HSTORE
as a typeDECIMAL
Sequelize.Range<SubType>
(which is an interface provided by the type declarations), it would be inferred as a RANGE
of the declared subtype, with subtype following the same inference rules outlined here.Object
or any interface, we could infer JSON
.GEOGRAPHY
.Basically, it should be possible to get all the info TypeScript has about a property. Which leads me to the thought, by can't TypeScript simply expose the AST node? I'm sure it already has structures that contain all the necessary information.
Hi guys! { structuralTypes: false }
in tsconfig.json
is never gonna happen right? 😢
I'd propose a special decorator on interfaces
export const MyInterface = Symbol("MyInterface")
@reflectAs(MyInterface) // here MyInterface is the constant
export interface MyInterface { // that will be emmited as a reflected type
a: number;
b: boolean;
}
It would be extremely simple and efficient solution for DI
@Artazor
The problem is interfaces are not compiled to any real js code, but decorators are. What should be the output of code you suggested?
I think, that's the reason why it should be keyword, not decorator.
@Artazor @koroandr I think the best solution is to add a compiler option, something like the current --emitDecoratorMetadata
but --emitInterfaceMetadata
or something similar but I also think that --nominalTyping
which would disable structural typing. I assume this is quite complicated but I'm not a compiler guy so I'm not sure about it...
I know that the structural type system was designed to facilitate interoperability with JavaScript libraries but disabling structural typing would enable some awesome tools just like the strict null checks mode does.
@remojansen I believe that may cause some overhead, because in fact you wouldn't need reflection metadata for every interface you create, just for injectable ones (in case of DI), so that will produce a lot of wasteful code when compiled to js. That's why I suggested to use some keyword, like so:
export reflexed interface MyInterface {
a: number;
b: boolean;
}
Can we please just have an --emitInterfaces
option and be done with this?
This discussion has been ongoing for so long now, and it really should not be that complicated. There is a crystal clear and highly desirable use case for this, and we just need the compiler to emit _something_ that we can reference at runtime - and I seriously doubt this would cause any measurable overhead, so no need to complicate things with new keywords or decorators.
So when I write something like this in foo.ts
:
export interface Foo {}
The compiler could just emit code similar to what would be emittted if it was written like this:
export abstract class Foo {}
(but of course, it is still an interface
, and therefore still can't be used with instanceof
, etc.)
That way, when I do something like this in bar.ts
, the metadata emitted for the foo
constructor parameter would reference the imported Foo
- instead of always referencing Object
as it currently does.
import {Foo} from "./foo";
@autoinject
class
{
constructor(foo: Foo) {}
}
Done, problem solved - The metadata now contains a reference to _something_ representing the imported interface, which can then be used as a key for dependency injection. Whether that _something_ is then an Symbol
, Object
, or Function
really doesn't matter - it's a reference, and that's all that matters.
I'd love to see this happening too. I find it essential for evolving the language and creating better software.
I understand that metadata supports only names with a value attached to them (i.e. classes) and builtins. Is it prohibitively difficult to extend support to native objects like Date
?
Can we please just have an --emitInterfaces option and be done with this?
This would be problematic in that it would change the meaning of existing code. Once you start reifying interfaces arbitrarily you will get variable collisions and variable shadowing in all sorts of unexpected places. Even referencing lib.d.ts
would cause code to fail.
interface Element {
}
takes on a whole different meaning.
Similar idea: #12605
Based on my own scenario and what I'm seeing here, it seems like the commonality is that this functionality is only ever desired for classes and interfaces.
A few suggestions and caveats have surfaced in this discussion, but a way forward seems apparent as I read:
Two years for a feature that's generated an awful lot of chatter. I would file this ticket under "more impactful than it's being given credit for".
https://github.com/inversify/InversifyJS/issues/534
✏️
We also had a problem with DI and interfaces and made up two sollutions.
/// ISomeService.ts
export interface ISomeService {}
// Now ISomeService is also an empty object emmited as ISomeService = {};
export module ISomeService { let ISomeService: void; }
/// SomeService1.ts
import { ISomeService } from "./ISomeService";
// And could be used as a var
@Injectable(ISomeService)
export class SomeService1 implements ISomeService {}
/// ISomeDBType.ts
export class ISomeDBType
{
// Since ISomeDBType is a class we could apply decorators to its properties,
// but TS will emmit empty function/class.
@PrimaryKey
public ID: number;
}
/// ISomeValidationType.ts
export class ISomeValidationType
{
@Validate("number")
public ID: number;
}
/// SomeType.ts
import { ISomeDBType } from "./ISomeDBType";
import { ISomeValidationType } from "./ISomeValidationType";
// We also have a special "partial" decorator which copies all metadata from ISomeDBType and
// ISomeValidationType to SomeType and lets us keep classes clean (since we heavily rely on
// metadata for almost everything).
@Partial(ISomeDBType)
@Partial(ISomeValidationType)
// TS can use class as an interface, so everything is fine.
class SomeType implements ISomeDBType, ISomeValidationType
{
private id: number;
public get ID(): number{ return this.id; }
public set ID(ID: number) { this.id = ID; }
}
For those who interested:
I wrote little library, which uses customTranformers api. It emits some basic classes and props metadata, including generic arguments and union types.
Looks like typescript team doesn't want to include such functionality into the main repository (at least for now).
But same can be achieved using transformers api
So, this is proof of concept, comments are welcome.
https://github.com/goloveychuk/tsruntime
linked with #3015
I agree that custom transformers can be a solution.
The following are the examples of what you can do with custom transformers.
import { enumerate } from 'ts-transformer-enumerate';
type Colors = 'green' | 'yellow' | 'red';
const Colors = enumerate<Colors>();
console.log(Colors.green); // 'green'
console.log(Colors.yellow); // 'yellow'
console.log(Colors.red); // 'red'
import { keys } from 'ts-transformer-keys';
interface Props {
id: string;
name: string;
age: number;
}
const keysOfProps = keys<Props>();
console.log(keysOfProps); // ['id', 'name', 'age']
@christyharagan Also see:
Can smbd help me?
https://github.com/goloveychuk/tsruntime/blob/master/src/transformer.ts#L58
There I'm emitting reference type. But if this type was imported from another module, and not used anywhere on this file, typescript removing it (as unused). So I need somehow mark this type as used. How can I do this? What mechanisms checking this? ImportTracker
? codefixes/unusedIdentifierFixes
?
upd: smth found, looks like it's isReferencedAliasDeclaration
Yes. Import elision is handled in the checker. isReferencedAliasDeclaration
pulls information from the checker to indicate whether the import was referenced.
Just some thoughts. If typescript team don't going to make enhanced metadata emitting inside typescript codebase, it would be great boost (for custom libraries) if you'll move metadata logic to the external library, which uses only public api. It will help to understood, what api is needed and currently not available.
And it will really help people to write solid custom library for metadata emitting.
E.g. for now
https://github.com/Microsoft/TypeScript/blob/2150a7796b0dfe83c5286755410e0fd1646ed86c/src/compiler/transformers/ts.ts#L1806
EmitResolver
api is not available.
Thanks.
Is already there any solution for emitting better metadata? With better I mean metadata with correct complex types or metadata about interfaces (which are known to disappear after transpiling).
You can look at my tsruntime library. But it doesn’t emit info for interfaces
On Aug 12, 2017, 3:15 PM +0300, lilezek notifications@github.com, wrote:
Is already there any solution for emitting better metadata? With better I mean metadata with correct complex types or metadata about interfaces (which are known to disappear after transpiling).
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub, or mute the thread.
@goloveychuk Well, as I've seen no solution for what I want to achieve I've made two libraries:
The first one generates a TypeScript file including some metadata, just like tsruntime
but without the need of placing decorators. It also aims to emit interface metadata in the near future.
https://github.com/lilezek/awesome-metadata
The second one uses generated TypeScript metadata to implement the functionality that GSON offers in Java, for serializing and deserializing classes.
https://github.com/lilezek/tson
I hope someone finds these projects useful.
Almost 3 years passed and there is no kind of 'official' statement from Typescript team about this issue.
First-class Reflective features would unlock many possibilities as described in issue description.
Many third-party libraries have been created and they're doing good job, but in fact, it's dividing the way things can be achieved with typescript. First class support would avoid having several standards of doing something.
As this issue is important, such standards will eventually emerge if there will be void in this area. I consider it bad thing because community fragmented by the way of doing something important would slow down development and stability of language in real world scenarios.
Also I think that even if Typescript team have no plans for implementing it or finds it unnecessary or low-priori, it'd be valuable to have some information about it.
Thanks 👍
I'm pretty sure that typescript team will not implement reflection support.
https://github.com/Microsoft/TypeScript/issues/21479#issuecomment-361685802
Even if they implement more powerful (then now we have) reflection for classes (which have runtime representation),
they will not make this for interfaces (which some developers want).
But it's not a big deal, since it could easilly be imlemented via plugins.
The only thing is needed from Typescript team is to give us an option to have in `tsconfig.json'
{
plugins: ["awesomePlugin"]
}
and in 'awesomePlugin.ts'
export default {
initialize(program: Program) {}
emit(){}
}
Which is pretty similar to customTransformers api, but with access to Program
and with ease installation (no mess with typescript loaders configs).
And this, looking at ts source code, pretty easy to implement.
Many third-party libraries have been created and they're doing good job, but in fact, it's dividing the way things can be achieved with typescript. First class support would avoid having several standards of doing something.
As this issue is important, such standards will eventually emerge if there will be void in this area. I consider it bad thing.
I completely agree with you. The most dangerous thing is not the lack of reflection, but the fragmentation of the community-built solutions.
they will not make this for interfaces (which some developers want).
I did this two years ago in my reflec-ts fork of the official compiler, and with a very low effort, too. I repeat: we just need a standard track for this topic, otherwise in the future many of the libraries/frameworks produced by the community will be very fragmented and incompatibilities will raise, consequently.
I'd be happy to write this code by hand...
const IFoo = Symbol('IFoo');
interface IFoo {
bar();
}
If using emitDecoratorMetadata
in this scenario...
export class Baz {
constructor(foo: IFoo) {}
}
...actually generated design metadata with the IFoo
symbol value in it, rather than Object
. To me, this seems like a bug, since everywhere else, the compiler seems to understand when to use the interface type vs. when to use the constant. But when it emits design data, it does it incorrectly.
As an FYI, this is what we actually do today:
export const IFoo = DI.createInterface('IFoo');
export interface IFoo {
...
}
export class Bar {
constructor(@IFoo foo: IFoo) { ... }
}
Our createInterface
helper creates a decorator that will push itself into the metadata so it can be used as a key by the DI. This is a workaround that is needed because of the emit issue I described above. I'd prefer to just write this...
@autoinject
export class Bar {
constructor(foo: IFoo) { ... }
}
(Would be better to not need the autoinject
decorator to turn metadata emit on, but I'd be happy to just see the interface/value emit fixed.)
While this fix wouldn't satisfy every need on this thread, it's a reasonable non-breaking change that would open up the DI scenarios a little more.
Interfaces should have been called Shapes, that would have prevented a lot of confusion and kept room for actual interfaces known from other languages.
Maybe its worth introducing a nominal typing variant of interface? Possible name could be contract
or, to avoid the confusion with other languages (read: interfaces are shapes and contracts are interfaces), a subqualifier for interfaces: nominal interface
, runtime interface
When using Symbols for runtime transpilation then the code representing the interface at runtime must be moved to a separate module to avoid side effects (because importing only an interface from another would result in this import being removed when transpiled).
A possible way to represent interfaces at runtime:
/** INTERFACE RUNTIME */
const Implements = Symbol('Typescript Interface Implementations');
function Interface(desc?: string) {
const symbol = Symbol(desc);
const instance = function() {} as any;
Object.setPrototypeOf(instance, Interface.prototype);
Object.defineProperty(instance, Symbol.hasInstance, {
value: (other: any) => {
if (!other.constructor || !other.constructor[Implements]) {
return false;
}
return other.constructor[Implements].includes(instance);
}
});
Object.defineProperty(instance, Symbol.toPrimitive, { value: () => symbol });
Object.defineProperty(instance, 'toString', { value: () => desc || '' });
return instance;
};
Interface.prototype = Object.create(Function.prototype);
/** INTERFACE RUNTIME */
const MyInterface = Interface('b::MyInterface'); // added by transpilation
interface MyInterface {
}
class Other implements MyInterface {
static readonly [Implements] = [MyInterface]; // added by transpilation
}
const implementor = new Other();
const bindings = {
[MyInterface]: implementor,
}
console.log(MyInterface); // [Function: instance]
console.log(typeof MyInterface); // function
console.log(MyInterface instanceof Interface); // true
console.log(MyInterface instanceof Function); // true
console.log(MyInterface instanceof Object); // true
console.log(MyInterface instanceof Symbol); // false
console.log(MyInterface.toString()); // b::MyInterface
// console.log(MyInterface + ''); // Error
console.log(implementor instanceof Other); // true
console.log(implementor instanceof MyInterface); // true
console.log(bindings); // { [Symbol(b::MyInterface)]: Other {} }
Object.getOwnPropertySymbols(bindings).forEach((k) => {
console.log(k); // Symbol(b::MyInterface)
console.log(k == MyInterface); // true
console.log(MyInterface == k); // true
console.log(k === MyInterface); // false ! some sadness, but we can live with that
})
Any news about official support from the TS team?
Maybe it can help someone, I found this lib (https://github.com/dsherret/ts-simple-ast), looks very promising for compile-time reflection
I've been playing around with custom transformers in trying to solve the problem with dependency injection and interfaces. I've been able to make a custom transformer and a simple dependency injection library to demonstrate the capabilities of my transformer. The transformer is able to do the following:
InterfaceSymbol<MyInterface>()
. It will always return the same Symbol for the same interface (so far in my testing).InterfaceSymbol<T>()
.Example of how to use it:
interface IBird {
canFly(): boolean;
}
class Crow implements IBird {
public canFly() {
return true;
}
}
class App {
constructor(bird: IBird) {
if (bird.canFly()) {
console.log("Bird can fly");
} else {
console.log("Bird can't fly");
}
}
}
const container = new Container();
container.bind(InterfaceSymbol<IBird>(), Crow);
container.resolve(App);
If you're interested in it you can go to https://github.com/YePpHa/ts-di-transformer for more information. Please note that it's mostly a proof-of-concept even though it's kind of working.
I think that custom transformers are the solution for this issue. It might be possible that there's other possible solutions. But so far I think they're the way to go.
The only hope is this package. https://www.npmjs.com/package/reflect-metadata
Up!
This would definitely profide a clean solution to parse/validate untrusted JSON input at runtime!
This would definitely profide a clean solution to parse/validate untrusted JSON input at runtime!
I suggest to try the TypeOnly checker.
https://github.com/tomko-team/typeonly-checker
You can also try https://github.com/goloveychuk/tsruntime
I have been playing with this idea for a few day and published a quick POC using a transformer.
https://github.com/aenario/tsmirror. If you are interested, feel free to open an issue there to describe your usecase.
For the "REST interface" use case, I've worked on a library I hope can solve it conveniently (requires a Babel setup at the moment):
https://github.com/edbentley/ts-validate-type
Hasn't anyone used Pascal? haha .. In Object Pascal there'd be a compiler flag to emit run-time type information for anything in the block.. I know it wouldn't work for Typescript to control what is emitted, but adding a keyword might work?
export reflect class SomeClass {
} // Class //
export class SomeOtherClass {
public reflect someMethod(): void {
} // Procedure //
} // Class //
const reflect someVar: string = "hi";
const metaData = Reflect.getMetadata("design:type", someVar);
Ideally there would be more information emitted as well.. Like array with type, Enum Type information (what is the Enum's name, and values), and interface implementation checking.
Most helpful comment
Almost 3 years passed and there is no kind of 'official' statement from Typescript team about this issue.
First-class Reflective features would unlock many possibilities as described in issue description.
Many third-party libraries have been created and they're doing good job, but in fact, it's dividing the way things can be achieved with typescript. First class support would avoid having several standards of doing something.
As this issue is important, such standards will eventually emerge if there will be void in this area. I consider it bad thing because community fragmented by the way of doing something important would slow down development and stability of language in real world scenarios.
Also I think that even if Typescript team have no plans for implementing it or finds it unnecessary or low-priori, it'd be valuable to have some information about it.
Thanks 👍