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
- Is it appropriate to add this check in the codegen phase, mirroring the withDefaults pattern? Or is there a better place?
- 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)
};
What problem does this feature solve?
Hello,
I noticed an inconsistency in how object/array defaults are validated between
withDefaultsanddefineModel, and wanted to check if this is worth addressing.Problem
withDefaultsalready enforces factory functions for object/array prop defaults at the type level. Passing a literal value producests(2322):defineModelhas the same shared-state problem because it declares a model prop under the hood (docs) but currently produces no diagnostic:Questions
What does the proposed solution look like?
The idea is to add a local helper type
__VLS_ModelDefault<T>tolocalTypes.tsand use it as a type annotation for the generated__VLS_defaultModelsconst ingenerateModels. This way the check mirrors the existingwithDefaultscodegen pattern:The square-bracket form
[T] extends [...]is intentional. It prevents union distribution, sostring | string[]resolves to itself rather than being split and partially enforced.For
defineModel<string[]>({ default: [] }), the generated virtual TS would change from:to: