流水账,做个记录。 我猜tsserver在启动的时候回会扫描整个工程,收集类型信息。下面就在代码中找一找这个收集过程的代码。 恰好又在尝试调试简单的条件类型。就将两件事揉在一起了。另外调试方法会单写一篇。
从 getQuickInfoAtPosition 入手
getQuickInfoAtPosition是vscode调用tsserver获取类型信息的入口
今天在调试type R2 = void extends undefined ? true : false;时发现,tsserver会区那R2对应的symbol.id去一个映射表叫symbolLinks的中去查询R2别名对应的类型。显然这个类型是已经解析好的。这个symbolLinks也是解析好的。
getSymbolLinks
在getSymbolLinks函数开始初打断点得到这样的调用堆栈:
可以看出最近的几个函数调用:
getTypeChecker
-> createTypeChecker
-> initializeTypeChecker
-> getSymbolLinks
但此时的symbolLinks是空的,只有在第一次执行下面代码时才开始创建:
此时堆栈是:
在initializeTypeChecker中有一段初始化内建类型的代码。
getConditionalType
当鼠标放到R2上时,断点停在getSymbolLinks,跟出,来到getDeclaredTypeOfTypeAlias,最终执行到getConditionalType。
(很显然嘛,猜都可以猜到)。所以,为什么void extends undefined是true就藏在这个函数中:
(注释代码格外多)
⚠️ 注意:只有第一次鼠标放在R2上是才会执行这段代码,再次放在R2时会从缓存读取。
function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type {
let result;
let extraTypes: Type[] | undefined;
// We loop here for an immediately nested conditional type in the false position, effectively treating
// types of the form 'A extends B ? X : C extends D ? Y : E extends F ? Z : ...' as a single construct for
// purposes of resolution. This means such types aren't subject to the instantiation depth limiter.
while (true) {
const isUnwrapped = isTypicalNondistributiveConditional(root);
const checkType = instantiateType(unwrapNondistributiveConditionalTuple(root, getActualTypeVariable(root.checkType)), mapper);
const checkTypeInstantiable = isGenericObjectType(checkType) || isGenericIndexType(checkType);
const extendsType = instantiateType(unwrapNondistributiveConditionalTuple(root, root.extendsType), mapper);
if (checkType === wildcardType || extendsType === wildcardType) {
return wildcardType;
}
let combinedMapper: TypeMapper | undefined;
if (root.inferTypeParameters) {
const context = createInferenceContext(root.inferTypeParameters, /*signature*/ undefined, InferenceFlags.None);
if (!checkTypeInstantiable) {
// We don't want inferences from constraints as they may cause us to eagerly resolve the
// conditional type instead of deferring resolution. Also, we always want strict function
// types rules (i.e. proper contravariance) for inferences.
inferTypes(context.inferences, checkType, extendsType, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict);
}
// It's possible for 'infer T' type paramteters to be given uninstantiated constraints when the
// those type parameters are used in type references (see getInferredTypeParameterConstraint). For
// that reason we need context.mapper to be first in the combined mapper. See #42636 for examples.
combinedMapper = mapper ? combineTypeMappers(context.mapper, mapper) : context.mapper;
}
// Instantiate the extends type including inferences for 'infer T' type parameters
const inferredExtendsType = combinedMapper ? instantiateType(unwrapNondistributiveConditionalTuple(root, root.extendsType), combinedMapper) : extendsType;
// We attempt to resolve the conditional type only when the check and extends types are non-generic
if (!checkTypeInstantiable && !isGenericObjectType(inferredExtendsType) && !isGenericIndexType(inferredExtendsType)) {
// Return falseType for a definitely false extends check. We check an instantiations of the two
// types with type parameters mapped to the wildcard type, the most permissive instantiations
// possible (the wildcard type is assignable to and from all types). If those are not related,
// then no instantiations will be and we can just return the false branch type.
if (!(inferredExtendsType.flags & TypeFlags.AnyOrUnknown) && ((checkType.flags & TypeFlags.Any && !isUnwrapped) || !isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType)))) {
// Return union of trueType and falseType for 'any' since it matches anything
if (checkType.flags & TypeFlags.Any && !isUnwrapped) {
(extraTypes || (extraTypes = [])).push(instantiateType(getTypeFromTypeNode(root.node.trueType), combinedMapper || mapper));
}
// If falseType is an immediately nested conditional type that isn't distributive or has an
// identical checkType, switch to that type and loop.
const falseType = getTypeFromTypeNode(root.node.falseType);
if (falseType.flags & TypeFlags.Conditional) {
const newRoot = (falseType as ConditionalType).root;
if (newRoot.node.parent === root.node && (!newRoot.isDistributive || newRoot.checkType === root.checkType)) {
root = newRoot;
continue;
}
}
result = instantiateType(falseType, mapper);
break;
}
// Return trueType for a definitely true extends check. We check instantiations of the two
// types with type parameters mapped to their restrictive form, i.e. a form of the type parameter
// that has no constraint. This ensures that, for example, the type
// type Foo<T extends { x: any }> = T extends { x: string } ? string : number
// doesn't immediately resolve to 'string' instead of being deferred.
if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(inferredExtendsType))) {
result = instantiateType(getTypeFromTypeNode(root.node.trueType), combinedMapper || mapper);
break;
}
}
// Return a deferred type for a check that is neither definitely true nor definitely false
result = createType(TypeFlags.Conditional) as ConditionalType;
result.root = root;
result.checkType = instantiateType(root.checkType, mapper);
result.extendsType = instantiateType(root.extendsType, mapper);
result.mapper = mapper;
result.combinedMapper = combinedMapper;
result.aliasSymbol = aliasSymbol || root.aliasSymbol;
result.aliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(root.aliasTypeArguments, mapper!); // TODO: GH#18217
break;
}
return extraTypes ? getUnionType(append(extraTypes, result)) : result;
}
isTypeRelatedTo
下面是执行情况:
跟踪代码,最终定位到isTypeRelatedTo,它附近还有一系列判断类型关系的函数!
isSimpleTypeRelatedTo
好了,找到了。当source为undefined,target是undefined或void时(或strictNullChecks为false时),返回true。
2021/08/11
type K = keyof unknown; // type K = never
function getIndexType(type: Type, stringsOnly = keyofStringsOnly, noIndexSignatures?: boolean): Type {
type = getReducedType(type);
return type.flags & TypeFlags.Union ? getIntersectionType(map((type as UnionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) :
type.flags & TypeFlags.Intersection ? getUnionType(map((type as IntersectionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) :
type.flags & TypeFlags.InstantiableNonPrimitive || isGenericTupleType(type) || isGenericMappedType(type) && !hasDistributiveNameType(type) ? getIndexTypeForGenericType(type as InstantiableType | UnionOrIntersectionType, stringsOnly) :
getObjectFlags(type) & ObjectFlags.Mapped ? getIndexTypeForMappedType(type as MappedType, noIndexSignatures) :
type === wildcardType ? wildcardType :
type.flags & TypeFlags.Unknown ? neverType :
type.flags & (TypeFlags.Any | TypeFlags.Never) ? keyofConstraintType :
getLiteralTypeFromProperties(type, (noIndexSignatures ? TypeFlags.StringLiteral : TypeFlags.StringLike) | (stringsOnly ? 0 : TypeFlags.NumberLike | TypeFlags.ESSymbolLike),
stringsOnly === keyofStringsOnly && !noIndexSignatures);
}
type K = keyof Symbol