mirror of https://github.com/vuejs/core.git
fix(compiler-sfc): improve type inference for generic type aliases types (#12876)
close #12872
This commit is contained in:
parent
4a2953f57b
commit
d9dd628800
|
@ -538,7 +538,7 @@ describe('resolveType', () => {
|
||||||
|
|
||||||
expect(props).toStrictEqual({
|
expect(props).toStrictEqual({
|
||||||
foo: ['Symbol', 'String', 'Number'],
|
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(
|
expect(
|
||||||
resolve(`
|
resolve(`
|
||||||
type Brand<T> = T & {};
|
type Brand<T> = T & {};
|
||||||
|
@ -758,7 +758,18 @@ describe('resolveType', () => {
|
||||||
}>()
|
}>()
|
||||||
`).props,
|
`).props,
|
||||||
).toStrictEqual({
|
).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'],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1500,6 +1500,7 @@ export function inferRuntimeType(
|
||||||
node: Node & MaybeWithScope,
|
node: Node & MaybeWithScope,
|
||||||
scope: TypeScope = node._ownerScope || ctxToScope(ctx),
|
scope: TypeScope = node._ownerScope || ctxToScope(ctx),
|
||||||
isKeyOf = false,
|
isKeyOf = false,
|
||||||
|
typeParameters?: Record<string, Node>,
|
||||||
): string[] {
|
): string[] {
|
||||||
try {
|
try {
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
|
@ -1588,19 +1589,43 @@ export function inferRuntimeType(
|
||||||
case 'TSTypeReference': {
|
case 'TSTypeReference': {
|
||||||
const resolved = resolveTypeReference(ctx, node, scope)
|
const resolved = resolveTypeReference(ctx, node, scope)
|
||||||
if (resolved) {
|
if (resolved) {
|
||||||
// #13240
|
if (resolved.type === 'TSTypeAliasDeclaration') {
|
||||||
// Special case for function type aliases to ensure correct runtime behavior
|
// #13240
|
||||||
// other type aliases still fallback to unknown as before
|
// Special case for function type aliases to ensure correct runtime behavior
|
||||||
if (
|
// other type aliases still fallback to unknown as before
|
||||||
resolved.type === 'TSTypeAliasDeclaration' &&
|
if (resolved.typeAnnotation.type === 'TSFunctionType') {
|
||||||
resolved.typeAnnotation.type === 'TSFunctionType'
|
return ['Function']
|
||||||
) {
|
}
|
||||||
return ['Function']
|
|
||||||
|
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)
|
return inferRuntimeType(ctx, resolved, resolved._ownerScope, isKeyOf)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.typeName.type === 'Identifier') {
|
if (node.typeName.type === 'Identifier') {
|
||||||
|
if (typeParameters && typeParameters[node.typeName.name]) {
|
||||||
|
return inferRuntimeType(
|
||||||
|
ctx,
|
||||||
|
typeParameters[node.typeName.name],
|
||||||
|
scope,
|
||||||
|
isKeyOf,
|
||||||
|
typeParameters,
|
||||||
|
)
|
||||||
|
}
|
||||||
if (isKeyOf) {
|
if (isKeyOf) {
|
||||||
switch (node.typeName.name) {
|
switch (node.typeName.name) {
|
||||||
case 'String':
|
case 'String':
|
||||||
|
@ -1733,11 +1758,15 @@ export function inferRuntimeType(
|
||||||
return inferRuntimeType(ctx, node.typeAnnotation, scope)
|
return inferRuntimeType(ctx, node.typeAnnotation, scope)
|
||||||
|
|
||||||
case 'TSUnionType':
|
case 'TSUnionType':
|
||||||
return flattenTypes(ctx, node.types, scope, isKeyOf)
|
return flattenTypes(ctx, node.types, scope, isKeyOf, typeParameters)
|
||||||
case 'TSIntersectionType': {
|
case 'TSIntersectionType': {
|
||||||
return flattenTypes(ctx, node.types, scope, isKeyOf).filter(
|
return flattenTypes(
|
||||||
t => t !== UNKNOWN_TYPE,
|
ctx,
|
||||||
)
|
node.types,
|
||||||
|
scope,
|
||||||
|
isKeyOf,
|
||||||
|
typeParameters,
|
||||||
|
).filter(t => t !== UNKNOWN_TYPE)
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'TSEnumDeclaration':
|
case 'TSEnumDeclaration':
|
||||||
|
@ -1808,14 +1837,17 @@ function flattenTypes(
|
||||||
types: TSType[],
|
types: TSType[],
|
||||||
scope: TypeScope,
|
scope: TypeScope,
|
||||||
isKeyOf: boolean = false,
|
isKeyOf: boolean = false,
|
||||||
|
typeParameters: Record<string, Node> | undefined = undefined,
|
||||||
): string[] {
|
): string[] {
|
||||||
if (types.length === 1) {
|
if (types.length === 1) {
|
||||||
return inferRuntimeType(ctx, types[0], scope, isKeyOf)
|
return inferRuntimeType(ctx, types[0], scope, isKeyOf, typeParameters)
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
...new Set(
|
...new Set(
|
||||||
([] as string[]).concat(
|
([] as string[]).concat(
|
||||||
...types.map(t => inferRuntimeType(ctx, t, scope, isKeyOf)),
|
...types.map(t =>
|
||||||
|
inferRuntimeType(ctx, t, scope, isKeyOf, typeParameters),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in New Issue