deepkit / deepkit-framework

A new full-featured and high-performance TypeScript framework
https://deepkit.io/
MIT License
3.2k stars 123 forks source link

Get type declaration from type/generic type #340

Closed CristianPi closed 2 years ago

CristianPi commented 2 years ago
type A = number;

interface I<T>{
  a: T;
  b: A,
  c: B
}

interface B{
  a: A;
}

// @ts-ignore
const ia = typeOf<I>(); // type declaration
const gi = typeOf<I<number>>(); // generic

ia === getTypeDeclaration(gi)

I need to resolve the type declaration from a generic (ex. I\<number> -> I\<T>) any way to do that?

marcj commented 2 years ago

I don't understand your goal. typeOf<I> is not a valid syntax since I is generic. Do you want number from a I<number> expression?

CristianPi commented 2 years ago

@marcj I want to reflect that generic interface I<T> to extract the definition: { a: T; b: A, c: B }

typeOf<I> works in the sense that i can obtain the objectLiteral

My problem is: How do i get the objectLiteral defined on the type declaration { a: T; b: A, c: B } from a generic like this I<number>?

I'm building a custom http client and i need to export all used types with all the declarations (classes, interfaces, alias ect.) I'm using a custom stringifyType function to do that, the only problem is that i can print a generic I<number> but not the declaration interface I<T>{ a: T; b: A, c: B }

Maybe adding a declaration?:Type to TypeAnnotations could solve this.

Hope this is more clear,

marcj commented 2 years ago

How do i get the objectLiteral defined on the type declaration { a: T; b: A, c: B } from a generic like this I?

That's not possible and making it available means a performance hit.

So if you use somewhere in the http stuff the interface I you had to specify a type argument T, right? At no place should you get { a: T; b: A, c: B } since generic interface always have to be instantiated. Do you have example code of that http API? I can't imagine that you have somewhere a reference I without a specified generic type argument.

marcj commented 2 years ago

Also, we can not print declaration. What would you print if the interface is like that?

interface I<T>{
  a: T extends string ? 'yes' : 'no';
  b: A,
  c: B
}

All expressions can not be printed as the information is not always available and neither is there a parser of bytecode to string type expressions.

CristianPi commented 2 years ago

Also, we can not print declaration. What would you print if the interface is like that?

interface I<T>{
  a: T extends string ? 'yes' : 'no';
  b: A,
  c: B
}

All expressions can not be printed as the information is not always available and neither is there a parser of bytecode to string type expressions.

Yes i see, this does not work, a= literal "no" in this case. In general what i want is something like this

interface I<T>{
  a: T;
  b: A,
  c: B
}
// this print the type
typeToTs<I>() === `
interface I<T>{
  a: T;
  b: A,
  c: B
}
`
// this print the type
typeToTs(resolveTypeDeclaration<I<number>>()) === `
interface I<T>{
  a: T;
  b: A,
  c: B
}
`

With time it should be possible to serialize all expressions, although i'm happy with simple generic interface for now.

adding declaration?:Type to TypeAnnotations is a performance hit?

const __ΩA = ['T', 'b!e!!'];
const __ΩI = ['T', 'a', () => __ΩA, 'b', () => __ΩB, 'c', 'b!Pe"!4"e"!o#"4$n%4&M'];
const __ΩI2 = ['T', "yes", "no", 'a', () => __ΩA, 'b', () => __ΩB, 'c', 'l4."R.#RPe#!&qk#&QRb!PPde#!p)4$e"!o%"4&n\'4(M'];
const __ΩB = [() => __ΩA, 'a', 'P)o!"4"M'];
// @ts-ignore
const ia = (0, type_1.typeOf)([], [() => __ΩI2, 'n!']);
const ib = (0, type_1.typeOf)([], [() => __ΩI, '\'o!"']); // __ΩI the interface "declaration"

The information is already there.

marcj commented 2 years ago

No, the information is not there automatically. You'd have to run I through the VM without type arguments to get a computed I with T in place and wrongly resolved type expressions.

With time it should be possible to serialize all expressions

What do you mean? We do not plan to allow to serialise declarations. Stringifying type declarations is an entirely different thing compared to runtime type computation. The runtime type system is not meant to get back the declaration syntax of your TS source code.

CristianPi commented 2 years ago

It would be useful for all sort or things like: https://github.com/hanayashiki/deepkit-openapi

The alternative is to use another runtime like typescript-rtti, but it's an overkill to use one runtime for validation and another to get a full type reflection.

marcj commented 2 years ago

It would be useful for all sort or things like: https://github.com/hanayashiki/deepkit-openapi

No, that works perfectly fine without having declaration/source code information available in runtime. I don't think you ever need the declaration source code of an interface or in other words the unresolved uninstantiated type in any http library or in any use-case. You still haven't shown a concrete use-case. deepkit-openapi and Deepkit API console do not need it and everything works fine.

The alternative is to use another runtime like typescript-rtti

No, since it doesn't support that either. Also typescript-rtti's emitted JS is 10x bigger.

marcj commented 2 years ago

I'm building a custom http client and i need to export all used types with all the declarations (classes, interfaces, alias ect.)

This is very ambiguous, but let's assume you mean for example that you have code like that

interface I<T>{
  a: T;
}

interface HttpRoutes {
    route1(): I<string>
}

Does your http client library want to export that I<T> because it is used in a HTTP route? If so, then that's wrong and an information that is not valuable. In reality only a very tiny subset is used, namely I<string>. This I<string> is what you should export, anything else is a wrong type (in fact it's not a type, it's a type expression).

CristianPi commented 2 years ago

@marcj

Yes it's mostly for readability

 interface I<T>{
  a: T;
}

interface HttpRoutes {
    route1(): I<string>
}

output without generic "support"

interface HttpRoutes {
    route1(): {a:string}
}

It's not important anymore, i managed to export aliases and interfaces (non generic type) Generics are printed as is. Thanks for the great support! :100:

About typescript-rtti i made a pull to support generic aliases/interfaces, but yes, i think this project is superior, the size it's a deal breaker for the front-end.