fix(language-core): make generic component internal context inference type-safe across .d.ts boundary#6104
Merged
KazariEX merged 2 commits intoJun 20, 2026
Conversation
KazariEX
approved these changes
Jun 20, 2026
.d.ts boundary
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
In
generateGeneric(packages/language-core/lib/codegen/script/scriptSetup.ts), the emitted return type of a generic component uses__ctx?: Awaited<typeof __VLS_setup>without aNonNullablewrapper, while theprops,ctxandexposedparameter positions already wrap it asNonNullable<Awaited<typeof __VLS_setup>>. This wraps__ctxto match.Why
Event-handler parameters on a generic component are typed as implicit
any(TS7006 undernoImplicitAny) in a consumer when the component is consumed across the emitted.d.tsboundary (a built package / Nuxt layer), even though slot props are inferred correctly.In the live virtual code
__VLS_setupis a parameter with a default value, sotypeof __VLS_setuphas noundefined. On declaration emit, TypeScript turns the defaulted parameter into an optional one (__VLS_setup?: Promise<…>), sotypeof __VLS_setupnow includesundefined, andAwaited<Promise<Setup> | undefined>becomesSetup | undefined. The consumer helper__VLS_FunctionalComponentPropsextracts props via a single-step nested inferenceK extends { __ctx?: { props?: infer P } }, which cannot inferPthrough the inner| undefined, so props resolves tonever. Withneverprops,__VLS_NormalizeComponentEventfalls back and the event handler loses its contextual type → implicitany.Slot props are unaffected because they go through
__VLS_FunctionalComponentCtx, which infers the whole__ctxand appliesNonNullableto it. This change makes the return-type__ctxconsistent with the parameter positions and with that helper.This completes #4577, which added the same
NonNullablewrapping to the parameter positions but left the return-type__ctx.Reproduction
Reproduces only across the emitted
.d.tsboundary (a same-project live-source test does not trigger it):<script setup generic="T">component withdefineEmits<{ change: [e: SomeType] }>().vue-tsc --declaration --emitDeclarationOnly --noCheck)..d.tsand writes@change="(e) => …".vue-tsc --noEmitundernoImplicitAnyreports TS7006 onewithout this change, and is clean with it.No runtime impact: in the live (non-emitted) path the wrapper is a no-op because
typeof __VLS_setuphas noundefined.