Subscribe on changes!

Invalid/incomplete type inferred by generic components

avatar
Nov 15th 2023

Vue version

3.3.8

Link to minimal reproduction

https://play.vuejs.org/#eNqlU8Fu2zAM/RVCl7ZAkADrLXM9ZG0P6aEp1qCXKBg8m/aUOpImyW0Dw/32UbLjOFjRDigQICb5SD4+UjWbaT1+qpBNWeRwq8vEYcwlQKQNxnUNN/eL27F1RshC5LvTnw6tG4GsynIEX86gaaKJR36UMjMm2b2ZF00Gfcm0qRHagUVXaSgTWVxw5ixnUKBEI1Iyl5yFhmKrlXHgdhqhhjuj9NJ/NpAbtYUTGuvElwRIlbQONAEsXECGuZDo4fa09mEAT3EKnUEmlZnC4tcGUweJ7UtHy3i0xxj8UwmD2RScqbBzN91/P/I/RYP3uOZq/V9Vm7N2mBIdBE1plDDS2Bs+MpnA6yvMZapIUHTYKiNkjoZK+qa5Kkv1bAnW4VeBHn0cGA6G/ToIHjPbR5o14ItDmVlY+cQodIOHUdsWruI1fINKPkr1LHvoA/nm+UzuIgLS7yqGKTk7sT7BZrjuw90dCRU8B7UuFWlDW/5YKtqTv9b2Pun+2IjuklrlohhvrJL0hAJ9zrz+okSz0E4QFc76K+As8VVvgm+wYcr5jenjG/6NffE+zu4MWjRPyFkfc4kp0LXh6/tbUncQ3KqsKgn9TvAHWlVWnmML+17JjGgPcIHtPDwzes1Le+0XaPdDeaLhMgOeM3pwl++MfqB7Pj4PeVw2rPkLt6l5Mg==

Steps to reproduce

I have a generic component, with a property mapping straight to the generic type:

<script setup lang="ts" generic="T">
  import type { PropType } from 'vue'

  const props = defineProps({
    test: {
      type: Object as PropType<T>,
      required: true,
      default: undefined,
    },
  })
</script>

The type inferred by props.test is not fully resolved, and instead of being T, it becomes the following:

[{
    type: PropType<T>;
    required: true;
}] extends [Prop<infer V, infer D>] ? unknown extends V ? IfAny<V, V, D> : V : {
    type: PropType<T>;
    required: true;
}

What is expected?

The type of the property should be resolved correctly to the generic type.

What is actually happening?

I narrowed this down to the InferPropType<T> type around here: https://github.com/vuejs/core/blob/83e618f316d367b43586646be285a41fd8a30c78/packages/runtime-core/src/componentProps.ts#L125-L128

Adding an extra condition to narrow inference even further makes the type work correctly:

type InferPropType<T> = [T] extends [null]
  ? any // null & true would fail to infer
  : [T] extends [{ type: null | true }]
  ? any // As TS issue https://github.com/Microsoft/TypeScript/issues/14829 // somehow `ObjectConstructor` when inferred from { (): T } becomes `any` // `BooleanConstructor` when inferred from PropConstructor(with PropMethod) becomes `Boolean`
  : [T] extends [ObjectConstructor | { type: ObjectConstructor }]
  ? Record<string, any>
  : [T] extends [BooleanConstructor | { type: BooleanConstructor }]
  ? boolean
  : [T] extends [DateConstructor | { type: DateConstructor }]
  ? Date
  : [T] extends [(infer U)[] | { type: (infer U)[] }]
  ? U extends DateConstructor
    ? Date | InferPropType<U>
    : InferPropType<U>
  // ~~~~~~~~ extra condition below, specific for `PropOptions`
  : [T] extends [PropOptions<infer V, infer D>]
  ? unknown extends D
    ? V 
    : V | D :
  // ~~~~~~~~
  : [T] extends [Prop<infer V, infer D>]
  ? unknown extends V
    ? IfAny<V, V, D>
    : V
  : T

System Info

No response

Any additional comments?

No response

avatar
Nov 15th 2023

Relates to (and extra condition seems to fix):

  • #9546
  • #9277
avatar
Nov 17th 2023

A potential workaround is to use the (experimental) defineModel(...) macro. Typings for that are actually OK!

https://github.com/vuejs/rfcs/discussions/503

avatar
Nov 21st 2023

Proposed fix available in #9652