fix(compiler-sfc): improve type inference for generic type aliases types (#12876)

close #12872
This commit is contained in:
edison 2025-08-21 09:48:40 +08:00 committed by GitHub
parent 4a2953f57b
commit d9dd628800
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 61 additions and 18 deletions

View File

@ -538,7 +538,7 @@ describe('resolveType', () => {
expect(props).toStrictEqual({
foo: ['Symbol', 'String', 'Number'],
bar: [UNKNOWN_TYPE],
bar: ['String', 'Number'],
})
})
@ -749,7 +749,7 @@ describe('resolveType', () => {
})
})
test('fallback to Unknown', () => {
test('with intersection type', () => {
expect(
resolve(`
type Brand<T> = T & {};
@ -758,7 +758,18 @@ describe('resolveType', () => {
}>()
`).props,
).toStrictEqual({
foo: [UNKNOWN_TYPE],
foo: ['String', 'Object'],
})
})
test('with union type', () => {
expect(
resolve(`
type Wrapped<T> = T | symbol | number
defineProps<{foo?: Wrapped<boolean>}>()
`).props,
).toStrictEqual({
foo: ['Boolean', 'Symbol', 'Number'],
})
})
})

View File

@ -1500,6 +1500,7 @@ export function inferRuntimeType(
node: Node & MaybeWithScope,
scope: TypeScope = node._ownerScope || ctxToScope(ctx),
isKeyOf = false,
typeParameters?: Record<string, Node>,
): string[] {
try {
switch (node.type) {
@ -1588,19 +1589,43 @@ export function inferRuntimeType(
case 'TSTypeReference': {
const resolved = resolveTypeReference(ctx, node, scope)
if (resolved) {
if (resolved.type === 'TSTypeAliasDeclaration') {
// #13240
// Special case for function type aliases to ensure correct runtime behavior
// other type aliases still fallback to unknown as before
if (
resolved.type === 'TSTypeAliasDeclaration' &&
resolved.typeAnnotation.type === 'TSFunctionType'
) {
if (resolved.typeAnnotation.type === 'TSFunctionType') {
return ['Function']
}
return inferRuntimeType(ctx, resolved, resolved._ownerScope, isKeyOf)
if (node.typeParameters) {
const typeParams: Record<string, Node> = Object.create(null)
if (resolved.typeParameters) {
resolved.typeParameters.params.forEach((p, i) => {
typeParams![p.name] = node.typeParameters!.params[i]
})
}
return inferRuntimeType(
ctx,
resolved.typeAnnotation,
resolved._ownerScope,
isKeyOf,
typeParams,
)
}
}
return inferRuntimeType(ctx, resolved, resolved._ownerScope, isKeyOf)
}
if (node.typeName.type === 'Identifier') {
if (typeParameters && typeParameters[node.typeName.name]) {
return inferRuntimeType(
ctx,
typeParameters[node.typeName.name],
scope,
isKeyOf,
typeParameters,
)
}
if (isKeyOf) {
switch (node.typeName.name) {
case 'String':
@ -1733,11 +1758,15 @@ export function inferRuntimeType(
return inferRuntimeType(ctx, node.typeAnnotation, scope)
case 'TSUnionType':
return flattenTypes(ctx, node.types, scope, isKeyOf)
return flattenTypes(ctx, node.types, scope, isKeyOf, typeParameters)
case 'TSIntersectionType': {
return flattenTypes(ctx, node.types, scope, isKeyOf).filter(
t => t !== UNKNOWN_TYPE,
)
return flattenTypes(
ctx,
node.types,
scope,
isKeyOf,
typeParameters,
).filter(t => t !== UNKNOWN_TYPE)
}
case 'TSEnumDeclaration':
@ -1808,14 +1837,17 @@ function flattenTypes(
types: TSType[],
scope: TypeScope,
isKeyOf: boolean = false,
typeParameters: Record<string, Node> | undefined = undefined,
): string[] {
if (types.length === 1) {
return inferRuntimeType(ctx, types[0], scope, isKeyOf)
return inferRuntimeType(ctx, types[0], scope, isKeyOf, typeParameters)
}
return [
...new Set(
([] as string[]).concat(
...types.map(t => inferRuntimeType(ctx, t, scope, isKeyOf)),
...types.map(t =>
inferRuntimeType(ctx, t, scope, isKeyOf, typeParameters),
),
),
),
]