Skip to content

Commit 2d9f6f9

Browse files
committed
fix(compiler-sfc): avoid all hard errors when inferring runtime type
1 parent 1447596 commit 2d9f6f9

File tree

2 files changed

+161
-145
lines changed

2 files changed

+161
-145
lines changed

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

+6
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,12 @@ describe('resolveType', () => {
690690
test('should not error on unresolved type when inferring runtime type', () => {
691691
expect(() => resolve(`defineProps<{ foo: T }>()`)).not.toThrow()
692692
expect(() => resolve(`defineProps<{ foo: T['bar'] }>()`)).not.toThrow()
693+
expect(() =>
694+
resolve(`
695+
import type P from 'unknown'
696+
defineProps<{ foo: P }>()
697+
`)
698+
).not.toThrow()
693699
})
694700
})
695701
})

packages/compiler-sfc/src/script/resolveType.ts

+155-145
Original file line numberDiff line numberDiff line change
@@ -1180,156 +1180,164 @@ export function inferRuntimeType(
11801180
node: Node & MaybeWithScope,
11811181
scope = node._ownerScope || ctxToScope(ctx)
11821182
): string[] {
1183-
switch (node.type) {
1184-
case 'TSStringKeyword':
1185-
return ['String']
1186-
case 'TSNumberKeyword':
1187-
return ['Number']
1188-
case 'TSBooleanKeyword':
1189-
return ['Boolean']
1190-
case 'TSObjectKeyword':
1191-
return ['Object']
1192-
case 'TSNullKeyword':
1193-
return ['null']
1194-
case 'TSTypeLiteral':
1195-
case 'TSInterfaceDeclaration': {
1196-
// TODO (nice to have) generate runtime property validation
1197-
const types = new Set<string>()
1198-
const members =
1199-
node.type === 'TSTypeLiteral' ? node.members : node.body.body
1200-
for (const m of members) {
1201-
if (
1202-
m.type === 'TSCallSignatureDeclaration' ||
1203-
m.type === 'TSConstructSignatureDeclaration'
1204-
) {
1205-
types.add('Function')
1206-
} else {
1207-
types.add('Object')
1183+
try {
1184+
switch (node.type) {
1185+
case 'TSStringKeyword':
1186+
return ['String']
1187+
case 'TSNumberKeyword':
1188+
return ['Number']
1189+
case 'TSBooleanKeyword':
1190+
return ['Boolean']
1191+
case 'TSObjectKeyword':
1192+
return ['Object']
1193+
case 'TSNullKeyword':
1194+
return ['null']
1195+
case 'TSTypeLiteral':
1196+
case 'TSInterfaceDeclaration': {
1197+
// TODO (nice to have) generate runtime property validation
1198+
const types = new Set<string>()
1199+
const members =
1200+
node.type === 'TSTypeLiteral' ? node.members : node.body.body
1201+
for (const m of members) {
1202+
if (
1203+
m.type === 'TSCallSignatureDeclaration' ||
1204+
m.type === 'TSConstructSignatureDeclaration'
1205+
) {
1206+
types.add('Function')
1207+
} else {
1208+
types.add('Object')
1209+
}
12081210
}
1211+
return types.size ? Array.from(types) : ['Object']
12091212
}
1210-
return types.size ? Array.from(types) : ['Object']
1211-
}
1212-
case 'TSPropertySignature':
1213-
if (node.typeAnnotation) {
1214-
return inferRuntimeType(ctx, node.typeAnnotation.typeAnnotation, scope)
1215-
}
1216-
case 'TSMethodSignature':
1217-
case 'TSFunctionType':
1218-
return ['Function']
1219-
case 'TSArrayType':
1220-
case 'TSTupleType':
1221-
// TODO (nice to have) generate runtime element type/length checks
1222-
return ['Array']
1223-
1224-
case 'TSLiteralType':
1225-
switch (node.literal.type) {
1226-
case 'StringLiteral':
1227-
return ['String']
1228-
case 'BooleanLiteral':
1229-
return ['Boolean']
1230-
case 'NumericLiteral':
1231-
case 'BigIntLiteral':
1232-
return ['Number']
1233-
default:
1234-
return [UNKNOWN_TYPE]
1235-
}
1236-
1237-
case 'TSTypeReference': {
1238-
const resolved = resolveTypeReference(ctx, node, scope)
1239-
if (resolved) {
1240-
return inferRuntimeType(ctx, resolved, resolved._ownerScope)
1241-
}
1242-
if (node.typeName.type === 'Identifier') {
1243-
switch (node.typeName.name) {
1244-
case 'Array':
1245-
case 'Function':
1246-
case 'Object':
1247-
case 'Set':
1248-
case 'Map':
1249-
case 'WeakSet':
1250-
case 'WeakMap':
1251-
case 'Date':
1252-
case 'Promise':
1253-
return [node.typeName.name]
1254-
1255-
// TS built-in utility types
1256-
// https://www.typescriptlang.org/docs/handbook/utility-types.html
1257-
case 'Partial':
1258-
case 'Required':
1259-
case 'Readonly':
1260-
case 'Record':
1261-
case 'Pick':
1262-
case 'Omit':
1263-
case 'InstanceType':
1264-
return ['Object']
1265-
1266-
case 'Uppercase':
1267-
case 'Lowercase':
1268-
case 'Capitalize':
1269-
case 'Uncapitalize':
1213+
case 'TSPropertySignature':
1214+
if (node.typeAnnotation) {
1215+
return inferRuntimeType(
1216+
ctx,
1217+
node.typeAnnotation.typeAnnotation,
1218+
scope
1219+
)
1220+
}
1221+
case 'TSMethodSignature':
1222+
case 'TSFunctionType':
1223+
return ['Function']
1224+
case 'TSArrayType':
1225+
case 'TSTupleType':
1226+
// TODO (nice to have) generate runtime element type/length checks
1227+
return ['Array']
1228+
1229+
case 'TSLiteralType':
1230+
switch (node.literal.type) {
1231+
case 'StringLiteral':
12701232
return ['String']
1233+
case 'BooleanLiteral':
1234+
return ['Boolean']
1235+
case 'NumericLiteral':
1236+
case 'BigIntLiteral':
1237+
return ['Number']
1238+
default:
1239+
return [UNKNOWN_TYPE]
1240+
}
12711241

1272-
case 'Parameters':
1273-
case 'ConstructorParameters':
1274-
return ['Array']
1275-
1276-
case 'NonNullable':
1277-
if (node.typeParameters && node.typeParameters.params[0]) {
1278-
return inferRuntimeType(
1279-
ctx,
1280-
node.typeParameters.params[0],
1281-
scope
1282-
).filter(t => t !== 'null')
1283-
}
1284-
break
1285-
case 'Extract':
1286-
if (node.typeParameters && node.typeParameters.params[1]) {
1287-
return inferRuntimeType(ctx, node.typeParameters.params[1], scope)
1288-
}
1289-
break
1290-
case 'Exclude':
1291-
case 'OmitThisParameter':
1292-
if (node.typeParameters && node.typeParameters.params[0]) {
1293-
return inferRuntimeType(ctx, node.typeParameters.params[0], scope)
1294-
}
1295-
break
1242+
case 'TSTypeReference': {
1243+
const resolved = resolveTypeReference(ctx, node, scope)
1244+
if (resolved) {
1245+
return inferRuntimeType(ctx, resolved, resolved._ownerScope)
1246+
}
1247+
if (node.typeName.type === 'Identifier') {
1248+
switch (node.typeName.name) {
1249+
case 'Array':
1250+
case 'Function':
1251+
case 'Object':
1252+
case 'Set':
1253+
case 'Map':
1254+
case 'WeakSet':
1255+
case 'WeakMap':
1256+
case 'Date':
1257+
case 'Promise':
1258+
return [node.typeName.name]
1259+
1260+
// TS built-in utility types
1261+
// https://www.typescriptlang.org/docs/handbook/utility-types.html
1262+
case 'Partial':
1263+
case 'Required':
1264+
case 'Readonly':
1265+
case 'Record':
1266+
case 'Pick':
1267+
case 'Omit':
1268+
case 'InstanceType':
1269+
return ['Object']
1270+
1271+
case 'Uppercase':
1272+
case 'Lowercase':
1273+
case 'Capitalize':
1274+
case 'Uncapitalize':
1275+
return ['String']
1276+
1277+
case 'Parameters':
1278+
case 'ConstructorParameters':
1279+
return ['Array']
1280+
1281+
case 'NonNullable':
1282+
if (node.typeParameters && node.typeParameters.params[0]) {
1283+
return inferRuntimeType(
1284+
ctx,
1285+
node.typeParameters.params[0],
1286+
scope
1287+
).filter(t => t !== 'null')
1288+
}
1289+
break
1290+
case 'Extract':
1291+
if (node.typeParameters && node.typeParameters.params[1]) {
1292+
return inferRuntimeType(
1293+
ctx,
1294+
node.typeParameters.params[1],
1295+
scope
1296+
)
1297+
}
1298+
break
1299+
case 'Exclude':
1300+
case 'OmitThisParameter':
1301+
if (node.typeParameters && node.typeParameters.params[0]) {
1302+
return inferRuntimeType(
1303+
ctx,
1304+
node.typeParameters.params[0],
1305+
scope
1306+
)
1307+
}
1308+
break
1309+
}
12961310
}
1311+
// cannot infer, fallback to UNKNOWN: ThisParameterType
1312+
break
12971313
}
1298-
// cannot infer, fallback to UNKNOWN: ThisParameterType
1299-
break
1300-
}
13011314

1302-
case 'TSParenthesizedType':
1303-
return inferRuntimeType(ctx, node.typeAnnotation, scope)
1315+
case 'TSParenthesizedType':
1316+
return inferRuntimeType(ctx, node.typeAnnotation, scope)
13041317

1305-
case 'TSUnionType':
1306-
return flattenTypes(ctx, node.types, scope)
1307-
case 'TSIntersectionType': {
1308-
return flattenTypes(ctx, node.types, scope).filter(
1309-
t => t !== UNKNOWN_TYPE
1310-
)
1311-
}
1318+
case 'TSUnionType':
1319+
return flattenTypes(ctx, node.types, scope)
1320+
case 'TSIntersectionType': {
1321+
return flattenTypes(ctx, node.types, scope).filter(
1322+
t => t !== UNKNOWN_TYPE
1323+
)
1324+
}
13121325

1313-
case 'TSEnumDeclaration':
1314-
return inferEnumType(node)
1326+
case 'TSEnumDeclaration':
1327+
return inferEnumType(node)
13151328

1316-
case 'TSSymbolKeyword':
1317-
return ['Symbol']
1329+
case 'TSSymbolKeyword':
1330+
return ['Symbol']
13181331

1319-
case 'TSIndexedAccessType': {
1320-
try {
1332+
case 'TSIndexedAccessType': {
13211333
const types = resolveIndexType(ctx, node, scope)
13221334
return flattenTypes(ctx, types, scope)
1323-
} catch (e) {
1324-
break
13251335
}
1326-
}
13271336

1328-
case 'ClassDeclaration':
1329-
return ['Object']
1337+
case 'ClassDeclaration':
1338+
return ['Object']
13301339

1331-
case 'TSImportType': {
1332-
try {
1340+
case 'TSImportType': {
13331341
const sourceScope = importSourceToScope(
13341342
ctx,
13351343
node.argument,
@@ -1340,21 +1348,23 @@ export function inferRuntimeType(
13401348
if (resolved) {
13411349
return inferRuntimeType(ctx, resolved, resolved._ownerScope)
13421350
}
1343-
} catch (e) {}
1344-
break
1345-
}
1351+
break
1352+
}
13461353

1347-
case 'TSTypeQuery': {
1348-
const id = node.exprName
1349-
if (id.type === 'Identifier') {
1350-
// typeof only support identifier in local scope
1351-
const matched = scope.declares[id.name]
1352-
if (matched) {
1353-
return inferRuntimeType(ctx, matched, matched._ownerScope)
1354+
case 'TSTypeQuery': {
1355+
const id = node.exprName
1356+
if (id.type === 'Identifier') {
1357+
// typeof only support identifier in local scope
1358+
const matched = scope.declares[id.name]
1359+
if (matched) {
1360+
return inferRuntimeType(ctx, matched, matched._ownerScope)
1361+
}
13541362
}
1363+
break
13551364
}
1356-
break
13571365
}
1366+
} catch (e) {
1367+
// always soft fail on failed runtime type inference
13581368
}
13591369
return [UNKNOWN_TYPE] // no runtime check
13601370
}

0 commit comments

Comments
 (0)