language-tools
language-tools copied to clipboard
`defineComponent` cross-property generics usage is broadened on usage
I have a .vue
file that accepts generic types like so:
<!-- Test.vue -->
<script setup lang="ts" generic="TStr extends 'one' | 'two', TNum extends TStr extends 'one' ? 1 : 2">
const props = defineProps<{
str: TStr,
num: TNum,
numFn: (num: TNum) => TNum
}>()
</script>
<template>
<div>
<p>str: {{ props.str }}</p>
<p>num: {{ props.num }}</p>
</div>
</template>
And when using it in App.vue
, it works just as expected to infer the values of numFn
:
<!-- App.vue -->
<script setup lang="ts">
import Test from "./Test.vue"
</script>
<template>
<!-- val is `1` -->
<Test str="one" :num="1" :numFn="val => val" />
<!-- val is `2` -->
<Test str="two" :num="2" :numFn="val => val" />
</template>
However, when using defineComponent
and generics inside like so:
// Test2.ts
import { defineComponent } from "vue";
export const Test2 = defineComponent(
<TStr extends "one" | "two", TNum extends TStr extends "one" ? 1 : 2>(props: {
str: TStr;
num: TNum;
numFn: (num: TNum) => TNum;
}) => {
return () => props.str;
}
);
The type inferencing no longer works:
<script setup lang="ts">
import {Test2} from './Test2'
</script>
<template>
<!-- val is `1 | 2` -->
<Test2 str="one" :num="1" :numFn="val => val" />
<!-- val is `1 | 2` -->
<Test2 str="two" :num="2" :numFn="val => val" />
</template>
Link to Reproduction
https://github.com/crutchcorn/vue-define-component-ts-broadening-bug
https://stackblitz.com/github/crutchcorn/vue-define-component-ts-broadening-bug
Duplicate of https://github.com/vuejs/language-tools/issues/3745?
Maybe @so1ve? I think this is different behavior (altho probably linked) as there seems to be a unionization of possible values on top of what #3745 describes, even when they shouldn't technically be possible to union over
A hack I’ve used to fix a similar issue:
Define an explicit return type, including Volar’s special __ctx
attribute.
In your case,
import { type VNode, defineComponent } from "vue";
export const Test2 = defineComponent(
<TStr extends "one" | "two", TNum extends TStr extends "one" ? 1 : 2>(props: {
str: TStr;
num: TNum;
numFn: (num: TNum) => TNum;
}) => {
return () => props.str;
}
) as <TStr extends "one" | "two", TNum extends TStr extends "one" ? 1 : 2>(props: {
str: TStr;
num: TNum;
numFn: (num: TNum) => TNum;
}) => VNode & {
__ctx?: {
props: {
str: TStr;
num: TNum;
numFn: (num: TNum) => TNum;
};
};
};
By changing __ctx
, you can add type inference for emits and slots too.