Skip to content

defineModel: named model with PropType + factory default infers wrong generic (regression from #14968) #15096

Description

@yeonyew

Vue version

3.5.39

Link to minimal reproduction

https://play.vuejs.org/#eNqtk81qGzEQx19l0MUJmDXUTaGLbUhKDi20DY1JD1Gold3ZjWJZEvpwbcwee+sjtC/XJ+lIi50EQk65rWb+M/Obj92xU2uLdURWsomvnLQBPIZoQQndTjkLnrMZ13JljQsQthZhBxfO2Hn67KBxZgUDSjDgmuvRCP79/Q1nsS1hfglSN+g8zHtVuENY1NiIqMICGlEF47Ygg0fVkNQHFDWYBhapymKYs1UieqlbEOBtdNJE3zOgc8YBuSqEq4gwLk6K8fuC68pQIrh1ZokapkDlpMbPpkZ1NFhLL2+lkmE7GMKOa8i5Sjh1TmxB+ENfEx8cVb2+mREFpCSJuYSjY5jO4PqGrN3xvt8/v+C7cUvhTNR1CVbq3GmLGp2sADdWyUoGtd3DNXKD9VO2h4KPKd+8DuZk1C+W1kiPgCurREB6AUxquZ7tdvt5dR3Qo+fruskoeVP8IYYN6SCoi0a2xb03mq4mA3JWmZWVCt1XGyR1yVnZoyefUMr8/JRtwUXMsDnmDqvlM/Z7v0k2zi4cenRr5OzgC8K1GHr3+eUX3ND3wbkydVSkfsH5Db1RMTH2sjNaGmE/0mXaj/neabhzf74JqP2+qQSalF3Wc0aX/+GF1h9wx8XbHEc7oSn+WNOPQWoaYH+6yRb81cF6Urwrxqz7D9k1OqM=

Steps to reproduce

In the reproduction, broken declares a named model with a runtime type: PropType<string[]> plus a factory default: () => []. fixed declares the same thing but with the generic pinned explicitly (defineModel<string[]>(...)).

Run the type checker (or vue-tsc --noEmit locally) over this SFC.

What is expected?

Both broken and fixed should type-check cleanly — this is the recommended pattern for avoiding shared mutable state (per #14966 / #14968): a factory default combined with a runtime PropType declaration.

What is actually happening?

Only broken fails. TS infers the generic T from the default factory function's own type (() => never[]) instead of from the type: PropType<string[]> field:

error TS2769: No overload matches this call.
Overload 1 of 4, '(name: string, options: ({ required: true; } | { default: DefineModelDefault<() => never[]>; }) & Omit<PropOptions<() => never[], () => never[]>, "default"> & DefineModelOptions<...>): ModelRef<...>', gave the following error.
Type 'PropType<string[]>' is not assignable to type 'true | PropType<() => never[]> | null | undefined'.
Type 'new (...args: any[]) => string[]' is not assignable to type 'true | PropType<() => never[]> | null | undefined'.
Type 'new (...args: any[]) => string[]' is not assignable to type 'new (...args: any[]) => () => never[]'.
Type 'string[]' is not assignable to type '() => never[]'.
Type 'string[]' provides no match for the signature '(): never[]'.
Overload 2 of 4, '(name: string, options?: DefineModelRuntimeOptions<string[], string[], string[]> | undefined): ModelRef<string[] | undefined, string, string[] | undefined, string[] | undefined>', gave the following error.
Object literal may only specify known properties, and 'default' does not exist in type 'DefineModelRuntimeOptions<string[], string[], string[]>'.

his is a regression from #14968 (landed in 3.5.39), which introduced DefineModelDefault<T> = InferDefault<Data, T> for the named-model overload. When both a runtime type: PropType<T> field and a factory default are present on the same options object, TS resolves T from the default field first, breaking a pattern that worked prior to 3.5.39.

Workaround: explicitly pin the generic, as shown by fixed in the reproduction:

const fixed = defineModel<string[]>('visibility2', {
  type: Array as PropType<string[]>,
  default: () => [],
})

System Info

vue: 3.5.39
vue-tsc: 3.3.7
typescript: 5.7.3

Any additional comments?

Related to / regression from #14966 and #14968. Only reproduces on the named defineModel(name, options) overload when type/PropType and a factory default are combined; the unnamed defineModel<T>({ ... }) form with an explicit generic is unaffected.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions