笔记:TypeScript源码阅读 - 调试简单条件类型

498 阅读3分钟

流水账,做个记录。 我猜tsserver在启动的时候回会扫描整个工程,收集类型信息。下面就在代码中找一找这个收集过程的代码。 恰好又在尝试调试简单的条件类型。就将两件事揉在一起了。另外调试方法会单写一篇。

从 getQuickInfoAtPosition 入手

getQuickInfoAtPosition是vscode调用tsserver获取类型信息的入口

今天在调试type R2 = void extends undefined ? true : false;时发现,tsserver会区那R2对应的symbol.id去一个映射表叫symbolLinks的中去查询R2别名对应的类型。显然这个类型是已经解析好的。这个symbolLinks也是解析好的。

getSymbolLinks

在getSymbolLinks函数开始初打断点得到这样的调用堆栈:

image.png

可以看出最近的几个函数调用:

getTypeChecker
  -> createTypeChecker
    -> initializeTypeChecker
      -> getSymbolLinks

但此时的symbolLinks是空的,只有在第一次执行下面代码时才开始创建:

image.png

此时堆栈是:

image.png

initializeTypeChecker中有一段初始化内建类型的代码。

getConditionalType

当鼠标放到R2上时,断点停在getSymbolLinks,跟出,来到getDeclaredTypeOfTypeAlias,最终执行到getConditionalType。 (很显然嘛,猜都可以猜到)。所以,为什么void extends undefinedtrue就藏在这个函数中: (注释代码格外多) ⚠️ 注意:只有第一次鼠标放在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

下面是执行情况:

image.png

跟踪代码,最终定位到isTypeRelatedTo,它附近还有一系列判断类型关系的函数!

image.png

isSimpleTypeRelatedTo

image.png

好了,找到了。当source为undefined,target是undefined或void时(或strictNullChecks为false时),返回true。

image.png

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);
        }

image.png

type K = keyof Symbol

image.png

image.png

image.png

image.png