Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

withDefaults shows error when using generic #8310

Closed
pikax opened this issue May 14, 2023 · 25 comments · Fixed by #8335
Closed

withDefaults shows error when using generic #8310

pikax opened this issue May 14, 2023 · 25 comments · Fixed by #8335

Comments

@pikax
Copy link
Member

pikax commented May 14, 2023

Vue version

3.3.2

Link to minimal reproduction

https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbzgEwKYDNgDtUAUoRgDOANHAO7AwAWAIhgIYCuANjEXAL5zoEhwByAG5NUAgFDj0TLAGMYwCFh4MA1qgDCEcEtRYYAHgAqcVAA8Ye5ByIwo2AOZwAPnCysWAPgAUASkTicEFwUKgwTFDKlDT06MxsRN5omDj4hEQGCIHBOZa2AFxwRtlBnD6+ZFk5uagFcH5wALyegnkwEjmcvuKc4kA

Steps to reproduce

import { defineProps, withDefaults } from 'vue'

function fakeComponent<T extends string | null>() {
    return withDefaults(defineProps<{
        test: T
    }>(), {
        test: () => 'test'  as T // this is error 
    })
}

Or

<script lang="ts" generic="T extends string | null">
withDefaults(defineProps<{
    test: T
  }>(), {
    test: () => 'test' as T // this is error 
})
</script>

What is expected?

The error not to be showing, like the non generic version:

import { defineProps, withDefaults } from 'vue'

function fakeComponent() {
    return withDefaults(defineProps<{
        test: string | null
    }>(), {
        test: () => 'test' as T
    })
}

Or

<script lang="ts" generic="T extends string | null">
withDefaults(defineProps<{
    test: T
  }>(), {
    test: () => 'test'  as T
})
</script>

What is actually happening?

withDefaults is not accepting the generic type

System Info

No response

Any additional comments?

No response

@morgan-willock
Copy link

I'm having the same issue using [email protected]

type Props = {
  options: T[];
  valueKey: keyof T;
  displayKey: keyof T;
  multiple?: boolean;
  closeMenu?: boolean;
};

const props = withDefaults(defineProps<Props>(), {
  multiple: false,
  closeMenu: false,
});

Screenshot 2023-05-15 at 8 46 05 am

@xiaoxiangmoe
Copy link
Contributor

xiaoxiangmoe commented May 16, 2023

@pikax This is work as intended

For example:

function someFunction<T extends string | null>(
    test: T = 'test'
) {
}
Type '"test"' is not assignable to type 'T'.
  '"test"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'string | null'.ts(2322)
(parameter) test: T extends string | null

So withDefaults should throw a error for this, also.

@an501920078
Copy link

Can't generic and withDefaults be used together?

@an501920078
Copy link

an501920078 commented May 16, 2023

There is also a BUG, InstanceType<typeof genericComponent>, which is also problematic
Dingtalk_20230516104144

@LQ6666666
Copy link

There is also a BUG, InstanceType<typeof genericComponent>, which is also problematic Dingtalk_20230516104144

+1

@Anubarak
Copy link

Same error, this should be documented if it is intended. At least they don't show an example in the official documentation.

@xiaoxiangmoe
Copy link
Contributor

Same error, this should be documented if it is intended.

@pikax 's demo is TypeScript's feature. Not Vue's feature.

@xiaoxiangmoe
Copy link
Contributor

@an501920078 The problem you are having is not the same as the problem with pikax. It is recommended to file an issue separately.

@Anubarak
Copy link

Anubarak commented May 16, 2023

Same error, this should be documented if it is intended.

@pikax 's demo is TypeScript's feature. Not Vue's feature.

But withDefaults is a Vue function/feature isn't it? Why should TS fix this?
I mean: it works when I don't use Vue and it's withDefaults function (when I create my own function that defines a default object for example.. TS is able to do that)

If this fails on purpose because TypeScript is unable to do it, it should rather be documented in Vue because the mayority is Vue code here:

<script setup lang="ts" generic="T">
const props = withDefaults(defineProps<{
	modelValue: T;
	title?: string;
}>(), {
	title: 'Hello',
});
</script>

@xiaoxiangmoe
Copy link
Contributor

xiaoxiangmoe commented May 16, 2023

@pikax 's demo is work as intended.
@Anubarak Your demo is a Vue error, not work as intended and different with @pikax 's demo.
Maybe we should create a separately issue.

@an501920078
Copy link

@an501920078 The problem you are having is not the same as the problem with pikax. It is recommended to file an issue separately.
Sorry, because github where I am is unstable, so it is troublesome to submit it alone. Please check whether others have submitted this problem later

@Anubarak
Copy link

@pikax 's demo is work as intended. @Anubarak Your demo is a Vue error, not work as intended and different with @pikax 's demo. Maybe we should create a separately issue.

Okay, there is already an issue but you closed it and the other issue referenced this one. That's why I thought we should continue here. Sorry my mistake

@xiaoxiangmoe
Copy link
Contributor

xiaoxiangmoe commented May 16, 2023

<script setup lang="ts" generic="T extends { a: string }">
const props = withDefaults(
    defineProps<{
        foo?: T;
    }>(),
    {
        foo: () => ({ a: '' })
    },
);

const emit = defineEmits<{
    (event: 'update:foo', foo: T): void;
}>();

function onClick(){
    emit('update:foo',props.foo)
}
</script>
<template>
    <div @clicko="onClick" >hello, the comp</div>
</template>
<script setup lang="ts" >
import TheComp from './TheComp.vue'
const foo:{ a: string, b: number }|undefined = undefined
const console = globalThis.console
</script>
<template>
    <TheComp :foo="foo" @update:foo="console.log($event.b.toString())"/>
</template>

This will throw an exception. So we can't use code as @pikax 's initial demo

<script lang="ts" generic="T extends string | null">
withDefaults(defineProps<{
    test: T
  }>(), {
    test: () => 'test'
})
</script>

If you don't care about type safety very much, and allow a small part of type unsafety to appear, you can consider

<script lang="ts" generic="T extends string | null">
withDefaults(defineProps<{
    test: T
  }>(), {
    test: () => 'test' as T // UNSAFE code, if don't care about type safety here, use at your own risk.
})
</script>

But currently, the later code also throw a error. Maybe Vue should fix it.

@Anubarak
Copy link

Anubarak commented May 16, 2023

@xiaoxiangmoe So in the end, all comes down to withDefaults is not accepting the generic type. I'm not entirely sure what your issue is or why you don't want to accept the problem we have. We are just talking about the fact, that you can't use generics along with withDefaults. In the end all your examples don't work - that's our issue and what we are trying to explain.

@xiaoxiangmoe
Copy link
Contributor

xiaoxiangmoe commented May 16, 2023

I'm not entirely sure what your issue is or why you don't want to accept the problem we have.

<!--  the original demo -->
<script lang="ts" generic="T extends string | null">
withDefaults(defineProps<{
    test: T
  }>(), {
    test: () => 'test'
})
</script>

@Anubarak If the original demo is supported, it will introduce serious type safety issues, so I express my strong disapproval. And then I discuss with @pikax to change his demo from 'test' to 'test' as T.

I'm very sorry, before communicating with pikax, I didn't notice that there is such an unsafe use requirement of as T and some user don't care about type safety in some default props.This is my mistake, here I apologize to you.

that you can't use generics along with withDefaults.

that's our issue and what we are trying to explain.

I think the usage in #8331 is the reasonable and legal usage. And it have a better demostration about can't use generics along with withDefaults, maybe more people need this usage.

@Anubarak
Copy link

Anubarak commented May 16, 2023

@xiaoxiangmoe I guess we talk about two different things or maybe I don't understand what you mean at all.
It's not about the correct usage of TS and it's not about how TS should be used. That's not the point here. It's simply about the fact that you cannot use withDefaults with generics no matter how many times you cast your variables or what you do.

Because no demo here works... I always get the very same error
image

Even with casting
image

I would be happy if you could provide a working example - even if it's not type save or ugly or whatever but at least an example that does not throw the Argument of type X is not assignable to parameter of type 'InferDefaults

@Dobril
Copy link

Dobril commented May 17, 2023

After I update to 3.3.2 same error occurred:

Screenshot 2023-05-17 110650

When I use interface instead of type there are error too:

image

@Anubarak
Copy link

@Dobril 3.3.2 was 5 days ago, the pull request here is not yet in a release.

@pikax thank you very much for your efforts to fix it

@rodrigocfd
Copy link

@Dobril 3.3.2 was 5 days ago, the pull request here is not yet in a release.

@Anubarak, what is the planned release for this fix?

@pikax
Copy link
Member Author

pikax commented May 18, 2023

@rodrigocfd it was realeased on 3.3.3

@rodrigocfd
Copy link

@pikax what am I doing wrong here?

gen-err

Reproducible:

<script setup lang="ts" generic="T">
const props = withDefaults(defineProps<{
	value?: T | null;
	list: T[];
}>(), {
	value: null,
});
</script>

<template>
	<select>
		<option v-for="item of props.list">

		</option>
	</select>
</template>

Using:

  • Vue: 3.3.4
  • Volar: 1.6.4

@pikax
Copy link
Member Author

pikax commented May 19, 2023

@rodrigocfd
Drop the props. from props.list, since the type might get mutated when is exposed to the render, I would advise against using it like that.

image

@rodrigocfd
Copy link

@pikax thanks for the tip. But since props.list is a valid construct, the bug still exists. The issue should be reopened, right?

@pikax
Copy link
Member Author

pikax commented May 19, 2023

@rodrigocfd your issue is not the same issue on this issue, you can open another issue if you think your usage is valid.

The issue is not present inside the setup:

<script setup lang="ts" generic="T">
const props = withDefaults(
  defineProps<{
    value?: T | null;
    list: T[];
  }>(),
  {
    value: null,
  }
);

//no error here
for (const it of props.list) {
  console.log(it);
}
</script>

<template>
  <select>
    <option v-for="item of props.list"></option>
  </select>
</template>
image

A fix might be possible by shortcuting the types resolve on the props, feel free to PR.

@emilyAzz
Copy link

Hello, I have vue version 3.3.4 and upon using the generic type within the defineProps with defaults value managad with the new updates to make it work but when I come to build I encounter the below error with the defineEmits when I am emitting a generic type back

error TS5088: The inferred type of 'default' references a type with a cyclic structure which cannot be trivially serialized. A type annotation is necessary.

Tried <script setup lang="ts" generic="I extends any"> and also adding
"vueCompilerOptions": { "jsxTemplates": true, "experimentalRfc436": true }

but nothing is removing this error, anyone has any idea how this can be solved?

@github-actions github-actions bot locked and limited conversation to collaborators Sep 9, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

9 participants