Skip to content

Extend ts(2322) to cover non-factory object/array defaults in defineModel #6103

Description

@whysopaul

What problem does this feature solve?

Hello,

I noticed an inconsistency in how object/array defaults are validated between withDefaults and defineModel, and wanted to check if this is worth addressing.

Problem

withDefaults already enforces factory functions for object/array prop defaults at the type level. Passing a literal value produces ts(2322):

const props = withDefaults(defineProps<{
  items?: string[]
}>(), {
  items: [],  /* Type 'never[]' is not assignable to type 'InferDefault<LooseRequired<__VLS_Props>, string[] | undefined>'.
                 Type 'never[]' provides no match for the signature '(props: LooseRequired<__VLS_Props>): string[]'. ts(2322) */
});

defineModel has the same shared-state problem because it declares a model prop under the hood (docs) but currently produces no diagnostic:

const list = defineModel<string[]>({ default: [] }); // no error
const config = defineModel<Record<string, unknown>>({ default: {} }); // no error

Questions

  1. Is it appropriate to add this check in the codegen phase, mirroring the withDefaults pattern? Or is there a better place?
  2. Should defineModel without a type parameter also be covered? In that case the type of the default would need to be inferred from the value itself, which risks a circular reference in the generated code.

What does the proposed solution look like?

The idea is to add a local helper type __VLS_ModelDefault<T> to localTypes.ts and use it as a type annotation for the generated __VLS_defaultModels const in generateModels. This way the check mirrors the existing withDefaults codegen pattern:

// __VLS_ModelDefault resolves to () => T for object/array types, T otherwise
type __VLS_ModelDefault<T> = [T] extends [object | any[]] ? () => T : T;

The square-bracket form [T] extends [...] is intentional. It prevents union distribution, so string | string[] resolves to itself rather than being split and partially enforced.

For defineModel<string[]>({ default: [] }), the generated virtual TS would change from:

const __VLS_defaultModels = {
  'modelValue': [],
};

to:

// string[] is taken from defineModel<string[]>
const __VLS_defaultModels: {
  'modelValue': __VLS_ModelDefault<string[]>
} = {
  'modelValue': [],  // ts(2322)
};

Metadata

Metadata

Assignees

No one assigned

    Fields

    No fields configured for Feature.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions