fix(compiler-sfc): fix type resolution for shared type w/ different generic parameters

close #9871
This commit is contained in:
Evan You 2023-12-23 00:43:22 +08:00
parent 1b522cae07
commit a8d0b1b38b
2 changed files with 49 additions and 12 deletions

View File

@ -939,6 +939,34 @@ describe('resolveType', () => {
manufacturer: ['Object'] manufacturer: ['Object']
}) })
}) })
// #9871
test('shared generics with different args', () => {
const files = {
'/foo.ts': `export interface Foo<T> { value: T }`
}
const { props } = resolve(
`import type { Foo } from './foo'
defineProps<Foo<string>>()`,
files,
undefined,
`/One.vue`
)
expect(props).toStrictEqual({
value: ['String']
})
const { props: props2 } = resolve(
`import type { Foo } from './foo'
defineProps<Foo<number>>()`,
files,
undefined,
`/Two.vue`,
false /* do not invalidate cache */
)
expect(props2).toStrictEqual({
value: ['Number']
})
})
}) })
describe('errors', () => { describe('errors', () => {
@ -1012,7 +1040,8 @@ function resolve(
code: string, code: string,
files: Record<string, string> = {}, files: Record<string, string> = {},
options?: Partial<SFCScriptCompileOptions>, options?: Partial<SFCScriptCompileOptions>,
sourceFileName: string = '/Test.vue' sourceFileName: string = '/Test.vue',
invalidateCache = true
) { ) {
const { descriptor } = parse(`<script setup lang="ts">\n${code}\n</script>`, { const { descriptor } = parse(`<script setup lang="ts">\n${code}\n</script>`, {
filename: sourceFileName filename: sourceFileName
@ -1030,8 +1059,10 @@ function resolve(
...options ...options
}) })
for (const file in files) { if (invalidateCache) {
invalidateTypeCache(file) for (const file in files) {
invalidateTypeCache(file)
}
} }
// ctx.userImports is collected when calling compileScript(), but we are // ctx.userImports is collected when calling compileScript(), but we are

View File

@ -90,7 +90,7 @@ export class TypeScope {
public types: Record<string, ScopeTypeNode> = Object.create(null), public types: Record<string, ScopeTypeNode> = Object.create(null),
public declares: Record<string, ScopeTypeNode> = Object.create(null) public declares: Record<string, ScopeTypeNode> = Object.create(null)
) {} ) {}
isGenericScope = false
resolvedImportSources: Record<string, string> = Object.create(null) resolvedImportSources: Record<string, string> = Object.create(null)
exportedTypes: Record<string, ScopeTypeNode> = Object.create(null) exportedTypes: Record<string, ScopeTypeNode> = Object.create(null)
exportedDeclares: Record<string, ScopeTypeNode> = Object.create(null) exportedDeclares: Record<string, ScopeTypeNode> = Object.create(null)
@ -121,15 +121,17 @@ export function resolveTypeElements(
scope?: TypeScope, scope?: TypeScope,
typeParameters?: Record<string, Node> typeParameters?: Record<string, Node>
): ResolvedElements { ): ResolvedElements {
if (node._resolvedElements) { const canCache = !typeParameters
if (canCache && node._resolvedElements) {
return node._resolvedElements return node._resolvedElements
} }
return (node._resolvedElements = innerResolveTypeElements( const resolved = innerResolveTypeElements(
ctx, ctx,
node, node,
node._ownerScope || scope || ctxToScope(ctx), node._ownerScope || scope || ctxToScope(ctx),
typeParameters typeParameters
)) )
return canCache ? (node._resolvedElements = resolved) : resolved
} }
function innerResolveTypeElements( function innerResolveTypeElements(
@ -190,17 +192,18 @@ function innerResolveTypeElements(
} }
const resolved = resolveTypeReference(ctx, node, scope) const resolved = resolveTypeReference(ctx, node, scope)
if (resolved) { if (resolved) {
const typeParams: Record<string, Node> = Object.create(null) let typeParams: Record<string, Node> | undefined
if ( if (
(resolved.type === 'TSTypeAliasDeclaration' || (resolved.type === 'TSTypeAliasDeclaration' ||
resolved.type === 'TSInterfaceDeclaration') && resolved.type === 'TSInterfaceDeclaration') &&
resolved.typeParameters && resolved.typeParameters &&
node.typeParameters node.typeParameters
) { ) {
typeParams = Object.create(null)
resolved.typeParameters.params.forEach((p, i) => { resolved.typeParameters.params.forEach((p, i) => {
let param = typeParameters && typeParameters[p.name] let param = typeParameters && typeParameters[p.name]
if (!param) param = node.typeParameters!.params[i] if (!param) param = node.typeParameters!.params[i]
typeParams[p.name] = param typeParams![p.name] = param
}) })
} }
return resolveTypeElements( return resolveTypeElements(
@ -297,6 +300,7 @@ function typeElementsToMap(
// capture generic parameters on node's scope // capture generic parameters on node's scope
if (typeParameters) { if (typeParameters) {
scope = createChildScope(scope) scope = createChildScope(scope)
scope.isGenericScope = true
Object.assign(scope.types, typeParameters) Object.assign(scope.types, typeParameters)
} }
;(e as MaybeWithScope)._ownerScope = scope ;(e as MaybeWithScope)._ownerScope = scope
@ -669,16 +673,18 @@ function resolveTypeReference(
name?: string, name?: string,
onlyExported = false onlyExported = false
): ScopeTypeNode | undefined { ): ScopeTypeNode | undefined {
if (node._resolvedReference) { const canCache = !scope?.isGenericScope
if (canCache && node._resolvedReference) {
return node._resolvedReference return node._resolvedReference
} }
return (node._resolvedReference = innerResolveTypeReference( const resolved = innerResolveTypeReference(
ctx, ctx,
scope || ctxToScope(ctx), scope || ctxToScope(ctx),
name || getReferenceName(node), name || getReferenceName(node),
node, node,
onlyExported onlyExported
)) )
return canCache ? (node._resolvedReference = resolved) : resolved
} }
function innerResolveTypeReference( function innerResolveTypeReference(