fix(compiler-sfc): avoid all hard errors when inferring runtime type

This commit is contained in:
Evan You 2023-04-21 16:48:21 +08:00
parent 1447596bf4
commit 2d9f6f9264
2 changed files with 165 additions and 149 deletions

View File

@ -690,6 +690,12 @@ describe('resolveType', () => {
test('should not error on unresolved type when inferring runtime type', () => { test('should not error on unresolved type when inferring runtime type', () => {
expect(() => resolve(`defineProps<{ foo: T }>()`)).not.toThrow() expect(() => resolve(`defineProps<{ foo: T }>()`)).not.toThrow()
expect(() => resolve(`defineProps<{ foo: T['bar'] }>()`)).not.toThrow() expect(() => resolve(`defineProps<{ foo: T['bar'] }>()`)).not.toThrow()
expect(() =>
resolve(`
import type P from 'unknown'
defineProps<{ foo: P }>()
`)
).not.toThrow()
}) })
}) })
}) })

View File

@ -1180,156 +1180,164 @@ export function inferRuntimeType(
node: Node & MaybeWithScope, node: Node & MaybeWithScope,
scope = node._ownerScope || ctxToScope(ctx) scope = node._ownerScope || ctxToScope(ctx)
): string[] { ): string[] {
switch (node.type) { try {
case 'TSStringKeyword': switch (node.type) {
return ['String'] case 'TSStringKeyword':
case 'TSNumberKeyword': return ['String']
return ['Number'] case 'TSNumberKeyword':
case 'TSBooleanKeyword': return ['Number']
return ['Boolean'] case 'TSBooleanKeyword':
case 'TSObjectKeyword': return ['Boolean']
return ['Object'] case 'TSObjectKeyword':
case 'TSNullKeyword': return ['Object']
return ['null'] case 'TSNullKeyword':
case 'TSTypeLiteral': return ['null']
case 'TSInterfaceDeclaration': { case 'TSTypeLiteral':
// TODO (nice to have) generate runtime property validation case 'TSInterfaceDeclaration': {
const types = new Set<string>() // TODO (nice to have) generate runtime property validation
const members = const types = new Set<string>()
node.type === 'TSTypeLiteral' ? node.members : node.body.body const members =
for (const m of members) { node.type === 'TSTypeLiteral' ? node.members : node.body.body
if ( for (const m of members) {
m.type === 'TSCallSignatureDeclaration' || if (
m.type === 'TSConstructSignatureDeclaration' m.type === 'TSCallSignatureDeclaration' ||
) { m.type === 'TSConstructSignatureDeclaration'
types.add('Function') ) {
} else { types.add('Function')
types.add('Object') } else {
types.add('Object')
}
} }
return types.size ? Array.from(types) : ['Object']
} }
return types.size ? Array.from(types) : ['Object'] case 'TSPropertySignature':
} if (node.typeAnnotation) {
case 'TSPropertySignature': return inferRuntimeType(
if (node.typeAnnotation) { ctx,
return inferRuntimeType(ctx, node.typeAnnotation.typeAnnotation, scope) node.typeAnnotation.typeAnnotation,
} scope
case 'TSMethodSignature': )
case 'TSFunctionType': }
return ['Function'] case 'TSMethodSignature':
case 'TSArrayType': case 'TSFunctionType':
case 'TSTupleType': return ['Function']
// TODO (nice to have) generate runtime element type/length checks case 'TSArrayType':
return ['Array'] case 'TSTupleType':
// TODO (nice to have) generate runtime element type/length checks
return ['Array']
case 'TSLiteralType': case 'TSLiteralType':
switch (node.literal.type) { switch (node.literal.type) {
case 'StringLiteral': case 'StringLiteral':
return ['String']
case 'BooleanLiteral':
return ['Boolean']
case 'NumericLiteral':
case 'BigIntLiteral':
return ['Number']
default:
return [UNKNOWN_TYPE]
}
case 'TSTypeReference': {
const resolved = resolveTypeReference(ctx, node, scope)
if (resolved) {
return inferRuntimeType(ctx, resolved, resolved._ownerScope)
}
if (node.typeName.type === 'Identifier') {
switch (node.typeName.name) {
case 'Array':
case 'Function':
case 'Object':
case 'Set':
case 'Map':
case 'WeakSet':
case 'WeakMap':
case 'Date':
case 'Promise':
return [node.typeName.name]
// TS built-in utility types
// https://www.typescriptlang.org/docs/handbook/utility-types.html
case 'Partial':
case 'Required':
case 'Readonly':
case 'Record':
case 'Pick':
case 'Omit':
case 'InstanceType':
return ['Object']
case 'Uppercase':
case 'Lowercase':
case 'Capitalize':
case 'Uncapitalize':
return ['String'] return ['String']
case 'BooleanLiteral':
case 'Parameters': return ['Boolean']
case 'ConstructorParameters': case 'NumericLiteral':
return ['Array'] case 'BigIntLiteral':
return ['Number']
case 'NonNullable': default:
if (node.typeParameters && node.typeParameters.params[0]) { return [UNKNOWN_TYPE]
return inferRuntimeType(
ctx,
node.typeParameters.params[0],
scope
).filter(t => t !== 'null')
}
break
case 'Extract':
if (node.typeParameters && node.typeParameters.params[1]) {
return inferRuntimeType(ctx, node.typeParameters.params[1], scope)
}
break
case 'Exclude':
case 'OmitThisParameter':
if (node.typeParameters && node.typeParameters.params[0]) {
return inferRuntimeType(ctx, node.typeParameters.params[0], scope)
}
break
} }
}
// cannot infer, fallback to UNKNOWN: ThisParameterType
break
}
case 'TSParenthesizedType': case 'TSTypeReference': {
return inferRuntimeType(ctx, node.typeAnnotation, scope) const resolved = resolveTypeReference(ctx, node, scope)
if (resolved) {
return inferRuntimeType(ctx, resolved, resolved._ownerScope)
}
if (node.typeName.type === 'Identifier') {
switch (node.typeName.name) {
case 'Array':
case 'Function':
case 'Object':
case 'Set':
case 'Map':
case 'WeakSet':
case 'WeakMap':
case 'Date':
case 'Promise':
return [node.typeName.name]
case 'TSUnionType': // TS built-in utility types
return flattenTypes(ctx, node.types, scope) // https://www.typescriptlang.org/docs/handbook/utility-types.html
case 'TSIntersectionType': { case 'Partial':
return flattenTypes(ctx, node.types, scope).filter( case 'Required':
t => t !== UNKNOWN_TYPE case 'Readonly':
) case 'Record':
} case 'Pick':
case 'Omit':
case 'InstanceType':
return ['Object']
case 'TSEnumDeclaration': case 'Uppercase':
return inferEnumType(node) case 'Lowercase':
case 'Capitalize':
case 'Uncapitalize':
return ['String']
case 'TSSymbolKeyword': case 'Parameters':
return ['Symbol'] case 'ConstructorParameters':
return ['Array']
case 'TSIndexedAccessType': { case 'NonNullable':
try { if (node.typeParameters && node.typeParameters.params[0]) {
const types = resolveIndexType(ctx, node, scope) return inferRuntimeType(
return flattenTypes(ctx, types, scope) ctx,
} catch (e) { node.typeParameters.params[0],
scope
).filter(t => t !== 'null')
}
break
case 'Extract':
if (node.typeParameters && node.typeParameters.params[1]) {
return inferRuntimeType(
ctx,
node.typeParameters.params[1],
scope
)
}
break
case 'Exclude':
case 'OmitThisParameter':
if (node.typeParameters && node.typeParameters.params[0]) {
return inferRuntimeType(
ctx,
node.typeParameters.params[0],
scope
)
}
break
}
}
// cannot infer, fallback to UNKNOWN: ThisParameterType
break break
} }
}
case 'ClassDeclaration': case 'TSParenthesizedType':
return ['Object'] return inferRuntimeType(ctx, node.typeAnnotation, scope)
case 'TSImportType': { case 'TSUnionType':
try { return flattenTypes(ctx, node.types, scope)
case 'TSIntersectionType': {
return flattenTypes(ctx, node.types, scope).filter(
t => t !== UNKNOWN_TYPE
)
}
case 'TSEnumDeclaration':
return inferEnumType(node)
case 'TSSymbolKeyword':
return ['Symbol']
case 'TSIndexedAccessType': {
const types = resolveIndexType(ctx, node, scope)
return flattenTypes(ctx, types, scope)
}
case 'ClassDeclaration':
return ['Object']
case 'TSImportType': {
const sourceScope = importSourceToScope( const sourceScope = importSourceToScope(
ctx, ctx,
node.argument, node.argument,
@ -1340,21 +1348,23 @@ export function inferRuntimeType(
if (resolved) { if (resolved) {
return inferRuntimeType(ctx, resolved, resolved._ownerScope) return inferRuntimeType(ctx, resolved, resolved._ownerScope)
} }
} catch (e) {} break
break }
}
case 'TSTypeQuery': {
case 'TSTypeQuery': { const id = node.exprName
const id = node.exprName if (id.type === 'Identifier') {
if (id.type === 'Identifier') { // typeof only support identifier in local scope
// typeof only support identifier in local scope const matched = scope.declares[id.name]
const matched = scope.declares[id.name] if (matched) {
if (matched) { return inferRuntimeType(ctx, matched, matched._ownerScope)
return inferRuntimeType(ctx, matched, matched._ownerScope) }
} }
break
} }
break
} }
} catch (e) {
// always soft fail on failed runtime type inference
} }
return [UNKNOWN_TYPE] // no runtime check return [UNKNOWN_TYPE] // no runtime check
} }