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

feat(reactivity): enhance type inference for context-sensitive inputs #9516

Closed
wants to merge 1 commit into from

Conversation

jh-leong
Copy link
Member

Close #1930.


Based on the issue description, I simplified the problem with the following code:

declare function reactive<T extends object>(target: T): UnwrapNestedRefs<T>;

interface Bar {
  a: number;
  b: (v: any) => void;
}

const a: ComputedRef<number> = computed(() => 1);

const bar: Bar = reactive({
  // issue happens here: `Type 'ComputedRef<number>' is not assignable to type 'number'`
  a,
  b: (v) => {}, // error depending on --noImplicitAny
});

The error occurs due to context-sensitive type inference, specifically with the b property. The compiler defers type inference for the v callback parameter until after inferring the generic type parameter T. This leads to a fallback to infer T from the expected return type of Bar, a behavior introduced in microsoft/TypeScript#16072

The use of a homomorphic mapped type UnwrapNestedRefs<T> further complicates inference, causing T to be inferred from Bar. This check succeeds for the return type but fails for the target argument, resulting in an error.


On the other hand, in this PR, the following code is provided:

type NoInfer<T> = [T][T extends any ? 0 : never];

declare function reactive<T extends object>(target: T): UnwrapNestedRefs<T>;
declare function reactive<T extends object>(
  target: T
): NoInfer<UnwrapNestedRefs<T>>;

interface Bar {
  a: number;
  b: (v: any) => void;
}

const a: ComputedRef<number> = computed(() => 1);

const bar: Bar = reactive({
  a, // okay
  b: (v) => {}, // error depending on --noImplicitAny
});

In this case, the return type of reactive is still contextually typed from Bar, but T can now only be inferred from the value passed in as target. There was an old feature request at microsoft/TypeScript#7234 that would have supported such inference, but it hasn't been implemented yet.

This behavior is sometimes used intentionally to block inference. There's an open feature request at microsoft/TypeScript#14829 asking for a way to prevent the use of a type parameter from being used as a generic type argument inference site. Although native support for this doesn't exist, one of the workarounds is to use NoInfer, as mentioned in this comment on ms/TS#14829.

With the added overload function, similar scenarios can ensure that T is inferred from the function argument.

@github-actions
Copy link

Size Report

Bundles

File Size Gzip Brotli
runtime-dom.global.prod.js 86.5 kB 32.9 kB 29.7 kB
vue.global.prod.js 132 kB 49.6 kB 44.5 kB

Usages

Name Size Gzip Brotli
createApp 47.9 kB 18.9 kB 17.2 kB
createSSRApp 51.2 kB 20.2 kB 18.4 kB
defineCustomElement 50.3 kB 19.7 kB 17.9 kB
overall 61.3 kB 23.7 kB 21.6 kB

@haoqunjiang haoqunjiang added scope: types 🍰 p2-nice-to-have Priority 2: this is not breaking anything but nice to have it addressed. labels Oct 31, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🍰 p2-nice-to-have Priority 2: this is not breaking anything but nice to have it addressed. scope: types
Projects
Status: Rejected
Development

Successfully merging this pull request may close these issues.

Ref unwrapping causes generic type inference to fail
2 participants