From 2d9f6f926453c46f542789927bcd30d15da9c24b Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 21 Apr 2023 16:48:21 +0800 Subject: [PATCH] fix(compiler-sfc): avoid all hard errors when inferring runtime type --- .../compileScript/resolveType.spec.ts | 6 + .../compiler-sfc/src/script/resolveType.ts | 300 +++++++++--------- 2 files changed, 161 insertions(+), 145 deletions(-) diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts index c7e2b9b694a..dd8f82e1db0 100644 --- a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts @@ -690,6 +690,12 @@ describe('resolveType', () => { test('should not error on unresolved type when inferring runtime type', () => { expect(() => resolve(`defineProps<{ foo: T }>()`)).not.toThrow() expect(() => resolve(`defineProps<{ foo: T['bar'] }>()`)).not.toThrow() + expect(() => + resolve(` + import type P from 'unknown' + defineProps<{ foo: P }>() + `) + ).not.toThrow() }) }) }) diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index 526569843ec..13fe57e46a5 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -1180,156 +1180,164 @@ export function inferRuntimeType( node: Node & MaybeWithScope, scope = node._ownerScope || ctxToScope(ctx) ): string[] { - switch (node.type) { - case 'TSStringKeyword': - return ['String'] - case 'TSNumberKeyword': - return ['Number'] - case 'TSBooleanKeyword': - return ['Boolean'] - case 'TSObjectKeyword': - return ['Object'] - case 'TSNullKeyword': - return ['null'] - case 'TSTypeLiteral': - case 'TSInterfaceDeclaration': { - // TODO (nice to have) generate runtime property validation - const types = new Set() - const members = - node.type === 'TSTypeLiteral' ? node.members : node.body.body - for (const m of members) { - if ( - m.type === 'TSCallSignatureDeclaration' || - m.type === 'TSConstructSignatureDeclaration' - ) { - types.add('Function') - } else { - types.add('Object') + try { + switch (node.type) { + case 'TSStringKeyword': + return ['String'] + case 'TSNumberKeyword': + return ['Number'] + case 'TSBooleanKeyword': + return ['Boolean'] + case 'TSObjectKeyword': + return ['Object'] + case 'TSNullKeyword': + return ['null'] + case 'TSTypeLiteral': + case 'TSInterfaceDeclaration': { + // TODO (nice to have) generate runtime property validation + const types = new Set() + const members = + node.type === 'TSTypeLiteral' ? node.members : node.body.body + for (const m of members) { + if ( + m.type === 'TSCallSignatureDeclaration' || + m.type === 'TSConstructSignatureDeclaration' + ) { + types.add('Function') + } else { + types.add('Object') + } } + return types.size ? Array.from(types) : ['Object'] } - return types.size ? Array.from(types) : ['Object'] - } - case 'TSPropertySignature': - if (node.typeAnnotation) { - return inferRuntimeType(ctx, node.typeAnnotation.typeAnnotation, scope) - } - case 'TSMethodSignature': - case 'TSFunctionType': - return ['Function'] - case 'TSArrayType': - case 'TSTupleType': - // TODO (nice to have) generate runtime element type/length checks - return ['Array'] - - case 'TSLiteralType': - switch (node.literal.type) { - 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': + case 'TSPropertySignature': + if (node.typeAnnotation) { + return inferRuntimeType( + ctx, + node.typeAnnotation.typeAnnotation, + scope + ) + } + case 'TSMethodSignature': + case 'TSFunctionType': + return ['Function'] + case 'TSArrayType': + case 'TSTupleType': + // TODO (nice to have) generate runtime element type/length checks + return ['Array'] + + case 'TSLiteralType': + switch (node.literal.type) { + case 'StringLiteral': return ['String'] + case 'BooleanLiteral': + return ['Boolean'] + case 'NumericLiteral': + case 'BigIntLiteral': + return ['Number'] + default: + return [UNKNOWN_TYPE] + } - case 'Parameters': - case 'ConstructorParameters': - return ['Array'] - - case 'NonNullable': - if (node.typeParameters && node.typeParameters.params[0]) { - 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 + 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'] + + case 'Parameters': + case 'ConstructorParameters': + return ['Array'] + + case 'NonNullable': + if (node.typeParameters && node.typeParameters.params[0]) { + 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 } - // cannot infer, fallback to UNKNOWN: ThisParameterType - break - } - case 'TSParenthesizedType': - return inferRuntimeType(ctx, node.typeAnnotation, scope) + case 'TSParenthesizedType': + return inferRuntimeType(ctx, node.typeAnnotation, scope) - case 'TSUnionType': - return flattenTypes(ctx, node.types, scope) - case 'TSIntersectionType': { - return flattenTypes(ctx, node.types, scope).filter( - t => t !== UNKNOWN_TYPE - ) - } + case 'TSUnionType': + 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 'TSEnumDeclaration': + return inferEnumType(node) - case 'TSSymbolKeyword': - return ['Symbol'] + case 'TSSymbolKeyword': + return ['Symbol'] - case 'TSIndexedAccessType': { - try { + case 'TSIndexedAccessType': { const types = resolveIndexType(ctx, node, scope) return flattenTypes(ctx, types, scope) - } catch (e) { - break } - } - case 'ClassDeclaration': - return ['Object'] + case 'ClassDeclaration': + return ['Object'] - case 'TSImportType': { - try { + case 'TSImportType': { const sourceScope = importSourceToScope( ctx, node.argument, @@ -1340,21 +1348,23 @@ export function inferRuntimeType( if (resolved) { return inferRuntimeType(ctx, resolved, resolved._ownerScope) } - } catch (e) {} - break - } + break + } - case 'TSTypeQuery': { - const id = node.exprName - if (id.type === 'Identifier') { - // typeof only support identifier in local scope - const matched = scope.declares[id.name] - if (matched) { - return inferRuntimeType(ctx, matched, matched._ownerScope) + case 'TSTypeQuery': { + const id = node.exprName + if (id.type === 'Identifier') { + // typeof only support identifier in local scope + const matched = scope.declares[id.name] + if (matched) { + return inferRuntimeType(ctx, matched, matched._ownerScope) + } } + break } - break } + } catch (e) { + // always soft fail on failed runtime type inference } return [UNKNOWN_TYPE] // no runtime check }