ts-simple-type icon indicating copy to clipboard operation
ts-simple-type copied to clipboard

[Bug] number[] not assignable to ConcatArray<number>

Open AnyhowStep opened this issue 6 years ago • 4 comments
trafficstars

declare const src : number[];
declare function foo (dst : ConcatArray<number>) : void;
//This is allowed by TS
foo(src);

According to TS, number[] is assignable to ConcatArray<number>.

But ts-simple-type does not think so.

AnyhowStep avatar Jul 24 '19 05:07 AnyhowStep

Actually, let me investigate this further.

AnyhowStep avatar Jul 24 '19 06:07 AnyhowStep

I was experiencing other suspicious problems with this library and re-installed. It fixed those suspicious problems but this one remained.

AnyhowStep avatar Jul 24 '19 06:07 AnyhowStep

Thanks for this issue! First of all, I'm sorry that you are experiencing problems with ts-simple-type, I'll try to fix them as quickly as possible. I suspect the problems that you experienced before can be because of two different version of Typescript in your node_modules, because it will result in strange behavior due to incompatible TypeFlags. If you encounter strange problems again, I export an undocumented function called setTypescriptModule that can set a specific Typescript module for this library to use.

Regarding your problems with ConcatArray, this actually taps into the biggest shortcoming in ts-simple-type as of now (I have been thinking a lot on how to handle it). Basically the problem can be boiled down to balancing "ease of use" and "correctness". Let me explain.

Requirement 1: First of all, an array can be represent with different types: ReadonlyArray<number>, ConcatArray<number>, Array<number>, number[] (this is the same as Array<number>) and ArrayLike<number>. Typescript compares the structure of these types when checking assignability, so you can even make your special own array types if you implement all required members of the Array type.

Requirement 2: Secondly, the aim of this library is to provide an easy way to work with Typescript types. Therefore you should be able easily construct highly used types like Array<number> like this: {kind: SimpleTypeKind.ARRAY, type: {kind: SimpleTypeKind.NUMBER}} (this also increases the speed of comparing types). If I didn't make SimpleTypeKind.ARRAY, you would need to create the type like this by hand:

const myArray = { 
  kind: 'GENERIC_ARGUMENTS',
  typeArguments: [ { kind: 'NUMBER' } ] ,
  target: { 
     kind: 'OBJECT',
     name: 'Array',
     typeParameters: [ { kind: 'GENERIC_PARAMETER', name: 'T' } ],
     members: [ 
         { name: 'length', optional: false, type: { kind: 'NUMBER' } },
         { name: 'slice',
          optional: false,
          type:
           { kind: 'FUNCTION',
             returnType:
              { kind: 'ARRAY',
                type: { kind: 'GENERIC_PARAMETER', name: 'T' },
                name: 'Array' },
             argTypes:
              [ { name: 'start',
                  optional: true,
                  type: { kind: 'NUMBER' },
                  spread: false,
                  initializer: false },
                { name: 'end',
                  optional: true,
                  type: { kind: 'NUMBER' },
                  spread: false,
                  initializer: false } ] } } ],
     
       .........
       .........
       .........

     ]
  }
}

However less used types like ConcatArray<T> aren't parsed into SimpleTypeKind.ARRAY but into the large object shown above.

Requirement 3: Thirdly, when comparing Typescript types, I first transform them to SimpleType and then I compare them. This means that you can compare Typescript types against your own hand made SimpleTypes. It even means that you can compare two SimpleTypes without the need for a TypeChecker, for example in the browser!

The Problem: For requirement 2, I parse highly used types like Array<T> into SimpleTypeKind.ARRAY, but sadly I lose the structure of the Array<T> type in the process. The problem is that when I'm checking assignability I can end up comparing a SimpleTypeKind.ARRAY with the entire parsed type of ConcatArray<T> taken directly from lib.d.ts, and this will result in ts-simple-type telling that they are not assignable (just like you are experiencing). I would be able to solve it easly if I had access to the Typescript program in the comparison step, but due to requirement 2, I want this library to work without a Typescript program in that phase.

Solution: I have been thinking a lot on how to handle it. I don't want to build a half-baked, hard-coded solution, but I still want to include all 3 requirements. I also don't want to make a SimpleTypeKind for each lib type. I think the following is the solution that I'm going to make:

  • When building this library, I should parse and generate snapshots of highly used types like Array, Promise and Date and ship them together with the library. Then, when I have to compare SimpleTypeKind.ARRAY against another type, I would be able to instead compare structurally because I would have access to the entire structure of the array type. The only shortcoming I can see with this solution is that I end up baking a specific version of lib.d.ts into the shipped library.

This ended up a bit long and technical, but I hope you understand my thoughts and concerns. I'll keep you updated on the progress in this issue 👍

runem avatar Jul 24 '19 15:07 runem

I actually appreciate that you went long and technical.

I understand that type checking is a beast, even more so with structural type systems! And even way more so when there are rules involved that aren't even documented!

AnyhowStep avatar Jul 24 '19 16:07 AnyhowStep