deepkit-framework icon indicating copy to clipboard operation
deepkit-framework copied to clipboard

Incorrect narrowing of functions with `keyof` parameters

Open fergusean opened this issue 2 years ago • 1 comments

When omitting [props whose values are functions] from a type, if the function includes a keyof X parameter, the TS type is correctly narrowed, but the runtime type still includes that prop.

Sandbox: https://replit.com/@fergusean/DKTypeIssueDemo2

Code:

import { typeOf, ReflectionKind } from '@deepkit/type';

export class SomeClass {
  fieldA!: string;
  fieldB!: number;
  fieldC!: boolean;

  someFunctionA() { }
  someFunctionB(input: string) { }
  someFunctionC(input: keyof this /* behaves the same with keyof anything */) { }
}

export type ArrowFunction = (...args: any) => any;
type MethodKeys<T> = {
  [K in keyof T]: T[K] extends ArrowFunction ? K : never
}[keyof T];
export type EntityFields<T extends object> = Omit<T, MethodKeys<T>>;

function typeTest(): EntityFields<SomeClass> {
  return null as any;
}

const theType = typeOf<typeof typeTest>();
console.log(theType.kind === ReflectionKind.function && theType.return);

// output yields subtypes fieldA, fieldB, fieldC, and someFunctionC
// the TS type does NOT include someFunctionC

Output:

$ ts-node index.ts
<ref *1> {
  kind: 30,
  typeName: 'EntityFields',
  typeArguments: [
    {
      kind: 20,
      classType: [Function],
      types: [Array],
      typeArguments: undefined
    }
  ],
  types: [
    {
      kind: 32,
      name: 'fieldA',
      type: [Object],
      optional: undefined,
      parent: [Object]
    },
    {
      kind: 32,
      name: 'fieldB',
      type: [Object],
      optional: undefined,
      parent: [Object]
    },
    {
      kind: 32,
      name: 'fieldC',
      type: [Object],
      optional: undefined,
      parent: [Object]
    },
    {
      kind: 32,
      name: 'someFunctionC',
      type: [Object],
      optional: undefined,
      parent: [Object]
    }
  ],
  originTypes: [
    { typeName: 'Omit', typeArguments: [Array] },
    { typeName: 'Pick', typeArguments: [Array] }
  ],
  parent: {
    kind: 17,
    name: 'typeTest',
    return: [Circular *1],
    parameters: [],
    function: [Function: typeTest] { __type: [Array] }
  }
}

fergusean avatar Apr 08 '23 03:04 fergusean

Unfortunately, reference to this is not implemented at all in our type compiler, so referring to it breaks stuff in unexpected ways. I've added now a workaround that keyof this is handled as any (instead of never), so that your code should work. Please test and let me know! Thanks

marcj avatar Apr 12 '23 21:04 marcj