]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler-sfc): improve type inference for generic type aliases types (#12876)
authoredison <daiwei521@126.com>
Thu, 21 Aug 2025 01:48:40 +0000 (09:48 +0800)
committerGitHub <noreply@github.com>
Thu, 21 Aug 2025 01:48:40 +0000 (09:48 +0800)
close #12872

packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts
packages/compiler-sfc/src/script/resolveType.ts

index c6a2f9c38dd315ad9d413157ad793ebe0b89c249..c0f4db820805b6d1f0990de4c2a188c40934a858 100644 (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'],
       })
     })
   })
index 910e8839a3ebee3e5ea6d7335909f33e7db27b6c..d8f430700502922f69767112b975121b6d1881ac 100644 (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) {
-          // #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'
-          ) {
-            return ['Function']
+          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.typeAnnotation.type === 'TSFunctionType') {
+              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)
         }
-
         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),
+        ),
       ),
     ),
   ]