Hookyns / tst-reflect

Advanced TypeScript runtime reflection system
MIT License
328 stars 11 forks source link

Some container types have empty string for `fullName` so equaility can't be checked with `is` #61

Closed avin-kavish closed 1 year ago

avin-kavish commented 1 year ago
export type ManagedReference<Other> = Other & Ref<any, Other> // Ref is a class

// When used in this way, fullName is empty
ManagedReference<Car | null>

// When used in this way, fullName is ManagedReference
ManagedReference<Car>

TypeActivator {
  _name: '',
  _fullName: '',
  _kind: 3,
  _constructors: [],
  _properties: [],
  _indexes: [],
  _methods: [],
  _decorators: [],
  _typeParameters: [],
  _ctor: undefined,
  _ctorDesc: ConstructorImportActivator {},
  _interface: undefined,
  _isUnion: false,
  _isIntersection: true,
  _types: [
    LazyType {
      typeResolver: [Function (anonymous)],
      resolvedType: [TypeActivator]
    },
    LazyType {
      typeResolver: [Function (anonymous)],
      resolvedType: [TypeActivator]
    }
  ],
avin-kavish commented 1 year ago

I'm getting some inconsistent behaviours with how it handles container types. For example, using the above type,

class Foo {}

type ManagedReference<Other> = Other & Ref<any, Other> 
type X = ManagedReference<any> // resolves to any
type Y = ManagedReference<unknown> // resolves to Ref<any, unknown> 
type Z = ManagedReference<Foo> // resolves to intersection with Foo & Ref<any, Foo> 
type V = ManagedReference<Foo | null> // same type as above but empty name & fullName, I think this is a bug

When it resolved to any, it's not visible whether a container was used. I think we should be capturing the type the user has used, not the final simplified type.

Hookyns commented 1 year ago

That simplification is what TypeScript does, that's how the type aliases works. It's just "virtual" placeholder; it's not a real type. Type alias is direct type reference or alias/placeholder for some unions and/or intersections. That's why container types should not have name nor fullName, never. U should not try to compare containers directly IMHO. You can end up with one container which will be union of two other containers eg. intersections. type A = (B & C) | (D & E) Maybe the top level container will have a name, but those inner containers will not, they have no name, because they are not aliased.

Eg. type T = any & Ref<any, any>. I'm able to get information about that alias by reading its declaration (traversing the AST) but that's complicated. I would have to handle all possible declarations. That's why Record, Omit etc. were not working for a long time. I removed manual reading and now I just ask TS what type it is.

But I can change a behavior around types a little. I can introduce new kind Alias. It will have name and fullName but it will still resolve into final simplified type. It will be like Container kind but you will know that it is specific type alias with name and fullName.

avin-kavish commented 1 year ago

Alias kind should work for my use case but if you want to keep original typescript behaviour, even a way to check the "deep equality" of the type could work. I'm not really interested in name of the type but it's just knowing that they are exactly equal, (not structurally). Side-effect of fullname not being there is is comparison doesn't work.

In containers, then equality would have to be checked recursively descending into the types property. And by equality I mean, exact class is used. (even though ts considers classes structurally assignable, instanceof checks fail). And there would have to be a similar rule set for other types.

P.S. when doing Alias there will be a generic case to consider. eg, my type above type ManagedReference<Other>

Hookyns commented 1 year ago

Fixed in v1. Not in the current version.