1621 lines
74 KiB
Go
1621 lines
74 KiB
Go
package checker
|
|
|
|
import (
|
|
"slices"
|
|
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/jsnum"
|
|
)
|
|
|
|
type InferenceKey struct {
|
|
s TypeId
|
|
t TypeId
|
|
}
|
|
|
|
type InferenceState struct {
|
|
inferences []*InferenceInfo
|
|
originalSource *Type
|
|
originalTarget *Type
|
|
priority InferencePriority
|
|
inferencePriority InferencePriority
|
|
contravariant bool
|
|
bivariant bool
|
|
expandingFlags ExpandingFlags
|
|
propagationType *Type
|
|
visited map[InferenceKey]InferencePriority
|
|
sourceStack []*Type
|
|
targetStack []*Type
|
|
next *InferenceState
|
|
}
|
|
|
|
func (c *Checker) getInferenceState() *InferenceState {
|
|
n := c.freeinferenceState
|
|
if n == nil {
|
|
n = &InferenceState{}
|
|
}
|
|
c.freeinferenceState = n.next
|
|
return n
|
|
}
|
|
|
|
func (c *Checker) putInferenceState(n *InferenceState) {
|
|
clear(n.visited)
|
|
*n = InferenceState{
|
|
inferences: n.inferences[:0],
|
|
visited: n.visited,
|
|
sourceStack: n.sourceStack[:0],
|
|
targetStack: n.targetStack[:0],
|
|
next: c.freeinferenceState,
|
|
}
|
|
c.freeinferenceState = n
|
|
}
|
|
|
|
func (c *Checker) inferTypes(inferences []*InferenceInfo, originalSource *Type, originalTarget *Type, priority InferencePriority, contravariant bool) {
|
|
n := c.getInferenceState()
|
|
n.inferences = inferences
|
|
n.originalSource = originalSource
|
|
n.originalTarget = originalTarget
|
|
n.priority = priority
|
|
n.inferencePriority = InferencePriorityMaxValue
|
|
n.contravariant = contravariant
|
|
c.inferFromTypes(n, originalSource, originalTarget)
|
|
c.putInferenceState(n)
|
|
}
|
|
|
|
func (c *Checker) inferFromTypes(n *InferenceState, source *Type, target *Type) {
|
|
if !c.couldContainTypeVariables(target) || c.isNoInferType(target) {
|
|
return
|
|
}
|
|
if source == c.wildcardType || source == c.blockedStringType {
|
|
// We are inferring from an 'any' type. We want to infer this type for every type parameter
|
|
// referenced in the target type, so we record it as the propagation type and infer from the
|
|
// target to itself. Then, as we find candidates we substitute the propagation type.
|
|
savePropagationType := n.propagationType
|
|
n.propagationType = source
|
|
c.inferFromTypes(n, target, target)
|
|
n.propagationType = savePropagationType
|
|
return
|
|
}
|
|
if source.alias != nil && target.alias != nil && source.alias.symbol == target.alias.symbol {
|
|
if len(source.alias.typeArguments) != 0 || len(target.alias.typeArguments) != 0 {
|
|
// Source and target are types originating in the same generic type alias declaration.
|
|
// Simply infer from source type arguments to target type arguments, with defaults applied.
|
|
params := c.typeAliasLinks.Get(source.alias.symbol).typeParameters
|
|
minParams := c.getMinTypeArgumentCount(params)
|
|
nodeIsInJsFile := ast.IsInJSFile(source.alias.symbol.ValueDeclaration)
|
|
sourceTypes := c.fillMissingTypeArguments(source.alias.typeArguments, params, minParams, nodeIsInJsFile)
|
|
targetTypes := c.fillMissingTypeArguments(target.alias.typeArguments, params, minParams, nodeIsInJsFile)
|
|
c.inferFromTypeArguments(n, sourceTypes, targetTypes, c.getAliasVariances(source.alias.symbol))
|
|
}
|
|
// And if there weren't any type arguments, there's no reason to run inference as the types must be the same.
|
|
return
|
|
}
|
|
if source == target && source.flags&TypeFlagsUnionOrIntersection != 0 {
|
|
// When source and target are the same union or intersection type, just relate each constituent
|
|
// type to itself.
|
|
for _, t := range source.Types() {
|
|
c.inferFromTypes(n, t, t)
|
|
}
|
|
return
|
|
}
|
|
if target.flags&TypeFlagsUnion != 0 && source.flags&TypeFlagsNever == 0 {
|
|
// First, infer between identically matching source and target constituents and remove the
|
|
// matching types.
|
|
tempSources, tempTargets := c.inferFromMatchingTypes(n, source.Distributed(), target.Distributed(), (*Checker).isTypeOrBaseIdenticalTo)
|
|
// Next, infer between closely matching source and target constituents and remove
|
|
// the matching types. Types closely match when they are instantiations of the same
|
|
// object type or instantiations of the same type alias.
|
|
sources, targets := c.inferFromMatchingTypes(n, tempSources, tempTargets, (*Checker).isTypeCloselyMatchedBy)
|
|
if len(targets) == 0 {
|
|
return
|
|
}
|
|
target = c.getUnionType(targets)
|
|
if len(sources) == 0 {
|
|
// All source constituents have been matched and there is nothing further to infer from.
|
|
// However, simply making no inferences is undesirable because it could ultimately mean
|
|
// inferring a type parameter constraint. Instead, make a lower priority inference from
|
|
// the full source to whatever remains in the target. For example, when inferring from
|
|
// string to 'string | T', make a lower priority inference of string for T.
|
|
c.inferWithPriority(n, source, target, InferencePriorityNakedTypeVariable)
|
|
return
|
|
}
|
|
source = c.getUnionType(sources)
|
|
} else if target.flags&TypeFlagsIntersection != 0 && !core.Every(target.Types(), c.isNonGenericObjectType) {
|
|
// We reduce intersection types unless they're simple combinations of object types. For example,
|
|
// when inferring from 'string[] & { extra: any }' to 'string[] & T' we want to remove string[] and
|
|
// infer { extra: any } for T. But when inferring to 'string[] & Iterable<T>' we want to keep the
|
|
// string[] on the source side and infer string for T.
|
|
if source.flags&TypeFlagsUnion == 0 {
|
|
var sourceTypes []*Type
|
|
if source.flags&TypeFlagsIntersection != 0 {
|
|
sourceTypes = source.Types()
|
|
} else {
|
|
sourceTypes = []*Type{source}
|
|
}
|
|
// Infer between identically matching source and target constituents and remove the matching types.
|
|
sources, targets := c.inferFromMatchingTypes(n, sourceTypes, target.Types(), (*Checker).isTypeIdenticalTo)
|
|
if len(sources) == 0 || len(targets) == 0 {
|
|
return
|
|
}
|
|
source = c.getIntersectionType(sources)
|
|
target = c.getIntersectionType(targets)
|
|
}
|
|
}
|
|
if target.flags&(TypeFlagsIndexedAccess|TypeFlagsSubstitution) != 0 {
|
|
if c.isNoInferType(target) {
|
|
return
|
|
}
|
|
target = c.getActualTypeVariable(target)
|
|
}
|
|
if target.flags&TypeFlagsTypeVariable != 0 {
|
|
// Skip inference if the source is "blocked", which is used by the language service to
|
|
// prevent inference on nodes currently being edited.
|
|
if c.isFromInferenceBlockedSource(source) {
|
|
return
|
|
}
|
|
inference := getInferenceInfoForType(n, target)
|
|
if inference != nil {
|
|
// If target is a type parameter, make an inference, unless the source type contains
|
|
// a "non-inferrable" type. Types with this flag set are markers used to prevent inference.
|
|
//
|
|
// For example:
|
|
// - anyFunctionType is a wildcard type that's used to avoid contextually typing functions;
|
|
// it's internal, so should not be exposed to the user by adding it as a candidate.
|
|
// - autoType (and autoArrayType) is a special "any" used in control flow; like anyFunctionType,
|
|
// it's internal and should not be observable.
|
|
// - silentNeverType is returned by getInferredType when instantiating a generic function for
|
|
// inference (and a type variable has no mapping).
|
|
//
|
|
// This flag is infectious; if we produce Box<never> (where never is silentNeverType), Box<never> is
|
|
// also non-inferrable.
|
|
//
|
|
// As a special case, also ignore nonInferrableAnyType, which is a special form of the any type
|
|
// used as a stand-in for binding elements when they are being inferred.
|
|
if source.objectFlags&ObjectFlagsNonInferrableType != 0 || source == c.nonInferrableAnyType {
|
|
return
|
|
}
|
|
if !inference.isFixed {
|
|
candidate := core.OrElse(n.propagationType, source)
|
|
if candidate == c.blockedStringType {
|
|
return
|
|
}
|
|
if n.priority < inference.priority {
|
|
inference.candidates = nil
|
|
inference.contraCandidates = nil
|
|
inference.topLevel = true
|
|
inference.priority = n.priority
|
|
}
|
|
if n.priority == inference.priority {
|
|
// Inferring A to [A[0]] is a zero information inference (it guarantees A becomes its constraint), but oft arises from generic argument list inferences
|
|
// By discarding it early, we can allow more fruitful results to be used instead.
|
|
if c.isTupleOfSelf(inference.typeParameter, candidate) {
|
|
return
|
|
}
|
|
// We make contravariant inferences only if we are in a pure contravariant position,
|
|
// i.e. only if we have not descended into a bivariant position.
|
|
if n.contravariant && !n.bivariant {
|
|
if !slices.Contains(inference.contraCandidates, candidate) {
|
|
inference.contraCandidates = append(inference.contraCandidates, candidate)
|
|
clearCachedInferences(n.inferences)
|
|
}
|
|
} else if !slices.Contains(inference.candidates, candidate) {
|
|
inference.candidates = append(inference.candidates, candidate)
|
|
clearCachedInferences(n.inferences)
|
|
}
|
|
}
|
|
if n.priority&InferencePriorityReturnType == 0 && target.flags&TypeFlagsTypeParameter != 0 && inference.topLevel && !c.isTypeParameterAtTopLevel(n.originalTarget, target, 0) {
|
|
inference.topLevel = false
|
|
clearCachedInferences(n.inferences)
|
|
}
|
|
}
|
|
n.inferencePriority = min(n.inferencePriority, n.priority)
|
|
return
|
|
}
|
|
// Infer to the simplified version of an indexed access, if possible, to (hopefully) expose more bare type parameters to the inference engine
|
|
simplified := c.getSimplifiedType(target, false /*writing*/)
|
|
if simplified != target {
|
|
c.inferFromTypes(n, source, simplified)
|
|
} else if target.flags&TypeFlagsIndexedAccess != 0 {
|
|
indexType := c.getSimplifiedType(target.AsIndexedAccessType().indexType, false /*writing*/)
|
|
// Generally simplifications of instantiable indexes are avoided to keep relationship checking correct, however if our target is an access, we can consider
|
|
// that key of that access to be "instantiated", since we're looking to find the infernce goal in any way we can.
|
|
if indexType.flags&TypeFlagsInstantiable != 0 {
|
|
simplified := c.distributeIndexOverObjectType(c.getSimplifiedType(target.AsIndexedAccessType().objectType, false /*writing*/), indexType, false /*writing*/)
|
|
if simplified != nil && simplified != target {
|
|
c.inferFromTypes(n, source, simplified)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
switch {
|
|
case source.objectFlags&ObjectFlagsReference != 0 && target.objectFlags&ObjectFlagsReference != 0 && (source.AsTypeReference().target == target.AsTypeReference().target || c.isArrayType(source) && c.isArrayType(target)) && !(source.AsTypeReference().node != nil && target.AsTypeReference().node != nil):
|
|
// If source and target are references to the same generic type, infer from type arguments
|
|
c.inferFromTypeArguments(n, c.getTypeArguments(source), c.getTypeArguments(target), c.getVariances(source.AsTypeReference().target))
|
|
case source.flags&TypeFlagsIndex != 0 && target.flags&TypeFlagsIndex != 0:
|
|
c.inferFromContravariantTypes(n, source.AsIndexType().target, target.AsIndexType().target)
|
|
case (isLiteralType(source) || source.flags&TypeFlagsString != 0) && target.flags&TypeFlagsIndex != 0:
|
|
empty := c.createEmptyObjectTypeFromStringLiteral(source)
|
|
c.inferFromContravariantTypesWithPriority(n, empty, target.AsIndexType().target, InferencePriorityLiteralKeyof)
|
|
case source.flags&TypeFlagsIndexedAccess != 0 && target.flags&TypeFlagsIndexedAccess != 0:
|
|
c.inferFromTypes(n, source.AsIndexedAccessType().objectType, target.AsIndexedAccessType().objectType)
|
|
c.inferFromTypes(n, source.AsIndexedAccessType().indexType, target.AsIndexedAccessType().indexType)
|
|
case source.flags&TypeFlagsStringMapping != 0 && target.flags&TypeFlagsStringMapping != 0:
|
|
if source.symbol == target.symbol {
|
|
c.inferFromTypes(n, source.AsStringMappingType().target, target.AsStringMappingType().target)
|
|
}
|
|
case source.flags&TypeFlagsSubstitution != 0:
|
|
c.inferFromTypes(n, source.AsSubstitutionType().baseType, target)
|
|
// Make substitute inference at a lower priority
|
|
c.inferWithPriority(n, c.getSubstitutionIntersection(source), target, InferencePrioritySubstituteSource)
|
|
case target.flags&TypeFlagsConditional != 0:
|
|
c.invokeOnce(n, source, target, (*Checker).inferToConditionalType)
|
|
case target.flags&TypeFlagsUnionOrIntersection != 0:
|
|
c.inferToMultipleTypes(n, source, target.Types(), target.flags)
|
|
case source.flags&TypeFlagsUnion != 0:
|
|
// Source is a union or intersection type, infer from each constituent type
|
|
for _, sourceType := range source.Types() {
|
|
c.inferFromTypes(n, sourceType, target)
|
|
}
|
|
case target.flags&TypeFlagsTemplateLiteral != 0:
|
|
c.inferToTemplateLiteralType(n, source, target.AsTemplateLiteralType())
|
|
default:
|
|
source = c.getReducedType(source)
|
|
if c.isGenericMappedType(source) && c.isGenericMappedType(target) {
|
|
c.invokeOnce(n, source, target, (*Checker).inferFromGenericMappedTypes)
|
|
}
|
|
if !(n.priority&InferencePriorityNoConstraints != 0 && source.flags&(TypeFlagsIntersection|TypeFlagsInstantiable) != 0) {
|
|
apparentSource := c.getApparentType(source)
|
|
// getApparentType can return _any_ type, since an indexed access or conditional may simplify to any other type.
|
|
// If that occurs and it doesn't simplify to an object or intersection, we'll need to restart `inferFromTypes`
|
|
// with the simplified source.
|
|
if apparentSource != source && apparentSource.flags&(TypeFlagsObject|TypeFlagsIntersection) == 0 {
|
|
c.inferFromTypes(n, apparentSource, target)
|
|
return
|
|
}
|
|
source = apparentSource
|
|
}
|
|
if source.flags&(TypeFlagsObject|TypeFlagsIntersection) != 0 {
|
|
c.invokeOnce(n, source, target, (*Checker).inferFromObjectTypes)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Checker) inferFromTypeArguments(n *InferenceState, sourceTypes []*Type, targetTypes []*Type, variances []VarianceFlags) {
|
|
for i := range min(len(sourceTypes), len(targetTypes)) {
|
|
if i < len(variances) && variances[i]&VarianceFlagsVarianceMask == VarianceFlagsContravariant {
|
|
c.inferFromContravariantTypes(n, sourceTypes[i], targetTypes[i])
|
|
} else {
|
|
c.inferFromTypes(n, sourceTypes[i], targetTypes[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Checker) inferWithPriority(n *InferenceState, source *Type, target *Type, newPriority InferencePriority) {
|
|
savePriority := n.priority
|
|
n.priority |= newPriority
|
|
c.inferFromTypes(n, source, target)
|
|
n.priority = savePriority
|
|
}
|
|
|
|
func (c *Checker) inferFromContravariantTypesWithPriority(n *InferenceState, source *Type, target *Type, newPriority InferencePriority) {
|
|
savePriority := n.priority
|
|
n.priority |= newPriority
|
|
c.inferFromContravariantTypes(n, source, target)
|
|
n.priority = savePriority
|
|
}
|
|
|
|
func (c *Checker) inferFromContravariantTypes(n *InferenceState, source *Type, target *Type) {
|
|
n.contravariant = !n.contravariant
|
|
c.inferFromTypes(n, source, target)
|
|
n.contravariant = !n.contravariant
|
|
}
|
|
|
|
func (c *Checker) inferFromContravariantTypesIfStrictFunctionTypes(n *InferenceState, source *Type, target *Type) {
|
|
if c.strictFunctionTypes || n.priority&InferencePriorityAlwaysStrict != 0 {
|
|
c.inferFromContravariantTypes(n, source, target)
|
|
} else {
|
|
c.inferFromTypes(n, source, target)
|
|
}
|
|
}
|
|
|
|
// Ensure an inference action is performed only once for the given source and target types.
|
|
// This includes two things:
|
|
// Avoiding inferring between the same pair of source and target types,
|
|
// and avoiding circularly inferring between source and target types.
|
|
// For an example of the last, consider if we are inferring between source type
|
|
// `type Deep<T> = { next: Deep<Deep<T>> }` and target type `type Loop<U> = { next: Loop<U> }`.
|
|
// We would then infer between the types of the `next` property: `Deep<Deep<T>>` = `{ next: Deep<Deep<Deep<T>>> }` and `Loop<U>` = `{ next: Loop<U> }`.
|
|
// We will then infer again between the types of the `next` property:
|
|
// `Deep<Deep<Deep<T>>>` and `Loop<U>`, and so on, such that we would be forever inferring
|
|
// between instantiations of the same types `Deep` and `Loop`.
|
|
// In particular, we would be inferring from increasingly deep instantiations of `Deep` to `Loop`,
|
|
// such that we would go on inferring forever, even though we would never infer
|
|
// between the same pair of types.
|
|
func (c *Checker) invokeOnce(n *InferenceState, source *Type, target *Type, action func(c *Checker, n *InferenceState, source *Type, target *Type)) {
|
|
key := InferenceKey{s: source.id, t: target.id}
|
|
if status, ok := n.visited[key]; ok {
|
|
n.inferencePriority = min(n.inferencePriority, status)
|
|
return
|
|
}
|
|
if n.visited == nil {
|
|
n.visited = make(map[InferenceKey]InferencePriority)
|
|
}
|
|
n.visited[key] = InferencePriorityCircularity
|
|
saveInferencePriority := n.inferencePriority
|
|
n.inferencePriority = InferencePriorityMaxValue
|
|
// We stop inferring and report a circularity if we encounter duplicate recursion identities on both
|
|
// the source side and the target side.
|
|
saveExpandingFlags := n.expandingFlags
|
|
n.sourceStack = append(n.sourceStack, source)
|
|
n.targetStack = append(n.targetStack, target)
|
|
if c.isDeeplyNestedType(source, n.sourceStack, 2) {
|
|
n.expandingFlags |= ExpandingFlagsSource
|
|
}
|
|
if c.isDeeplyNestedType(target, n.targetStack, 2) {
|
|
n.expandingFlags |= ExpandingFlagsTarget
|
|
}
|
|
if n.expandingFlags != ExpandingFlagsBoth {
|
|
action(c, n, source, target)
|
|
} else {
|
|
n.inferencePriority = InferencePriorityCircularity
|
|
}
|
|
n.targetStack = n.targetStack[:len(n.targetStack)-1]
|
|
n.sourceStack = n.sourceStack[:len(n.sourceStack)-1]
|
|
n.expandingFlags = saveExpandingFlags
|
|
n.visited[key] = n.inferencePriority
|
|
n.inferencePriority = min(n.inferencePriority, saveInferencePriority)
|
|
}
|
|
|
|
func (c *Checker) inferFromMatchingTypes(n *InferenceState, sources []*Type, targets []*Type, matches func(c *Checker, s *Type, t *Type) bool) ([]*Type, []*Type) {
|
|
var matchedSources []*Type
|
|
var matchedTargets []*Type
|
|
for _, t := range targets {
|
|
for _, s := range sources {
|
|
if matches(c, s, t) {
|
|
c.inferFromTypes(n, s, t)
|
|
matchedSources = core.AppendIfUnique(matchedSources, s)
|
|
matchedTargets = core.AppendIfUnique(matchedTargets, t)
|
|
}
|
|
}
|
|
}
|
|
if len(matchedSources) != 0 {
|
|
sources = core.Filter(sources, func(t *Type) bool { return !slices.Contains(matchedSources, t) })
|
|
}
|
|
if len(matchedTargets) != 0 {
|
|
targets = core.Filter(targets, func(t *Type) bool { return !slices.Contains(matchedTargets, t) })
|
|
}
|
|
return sources, targets
|
|
}
|
|
|
|
func (c *Checker) inferToMultipleTypes(n *InferenceState, source *Type, targets []*Type, targetFlags TypeFlags) {
|
|
typeVariableCount := 0
|
|
if targetFlags&TypeFlagsUnion != 0 {
|
|
var nakedTypeVariable *Type
|
|
sources := source.Distributed()
|
|
matched := make([]bool, len(sources))
|
|
inferenceCircularity := false
|
|
// First infer to types that are not naked type variables. For each source type we
|
|
// track whether inferences were made from that particular type to some target with
|
|
// equal priority (i.e. of equal quality) to what we would infer for a naked type
|
|
// parameter.
|
|
for _, t := range targets {
|
|
if getInferenceInfoForType(n, t) != nil {
|
|
nakedTypeVariable = t
|
|
typeVariableCount++
|
|
} else {
|
|
for i := range sources {
|
|
saveInferencePriority := n.inferencePriority
|
|
n.inferencePriority = InferencePriorityMaxValue
|
|
c.inferFromTypes(n, sources[i], t)
|
|
if n.inferencePriority == n.priority {
|
|
matched[i] = true
|
|
}
|
|
inferenceCircularity = inferenceCircularity || n.inferencePriority == InferencePriorityCircularity
|
|
n.inferencePriority = min(n.inferencePriority, saveInferencePriority)
|
|
}
|
|
}
|
|
}
|
|
if typeVariableCount == 0 {
|
|
// If every target is an intersection of types containing a single naked type variable,
|
|
// make a lower priority inference to that type variable. This handles inferring from
|
|
// 'A | B' to 'T & (X | Y)' where we want to infer 'A | B' for T.
|
|
intersectionTypeVariable := getSingleTypeVariableFromIntersectionTypes(n, targets)
|
|
if intersectionTypeVariable != nil {
|
|
c.inferWithPriority(n, source, intersectionTypeVariable, InferencePriorityNakedTypeVariable)
|
|
}
|
|
return
|
|
}
|
|
// If the target has a single naked type variable and no inference circularities were
|
|
// encountered above (meaning we explored the types fully), create a union of the source
|
|
// types from which no inferences have been made so far and infer from that union to the
|
|
// naked type variable.
|
|
if typeVariableCount == 1 && !inferenceCircularity {
|
|
var unmatched []*Type
|
|
for i, s := range sources {
|
|
if !matched[i] {
|
|
unmatched = append(unmatched, s)
|
|
}
|
|
}
|
|
if len(unmatched) != 0 {
|
|
c.inferFromTypes(n, c.getUnionType(unmatched), nakedTypeVariable)
|
|
return
|
|
}
|
|
}
|
|
} else {
|
|
// We infer from types that are not naked type variables first so that inferences we
|
|
// make from nested naked type variables and given slightly higher priority by virtue
|
|
// of being first in the candidates array.
|
|
for _, t := range targets {
|
|
if getInferenceInfoForType(n, t) != nil {
|
|
typeVariableCount++
|
|
} else {
|
|
c.inferFromTypes(n, source, t)
|
|
}
|
|
}
|
|
}
|
|
// Inferences directly to naked type variables are given lower priority as they are
|
|
// less specific. For example, when inferring from Promise<string> to T | Promise<T>,
|
|
// we want to infer string for T, not Promise<string> | string. For intersection types
|
|
// we only infer to single naked type variables.
|
|
if targetFlags&TypeFlagsIntersection != 0 && typeVariableCount == 1 || targetFlags&TypeFlagsIntersection == 0 && typeVariableCount > 0 {
|
|
for _, t := range targets {
|
|
if getInferenceInfoForType(n, t) != nil {
|
|
c.inferWithPriority(n, source, t, InferencePriorityNakedTypeVariable)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func getSingleTypeVariableFromIntersectionTypes(n *InferenceState, types []*Type) *Type {
|
|
var typeVariable *Type
|
|
for _, t := range types {
|
|
if t.flags&TypeFlagsIntersection == 0 {
|
|
return nil
|
|
}
|
|
v := core.Find(t.Types(), func(t *Type) bool { return getInferenceInfoForType(n, t) != nil })
|
|
if v == nil || typeVariable != nil && v != typeVariable {
|
|
return nil
|
|
}
|
|
typeVariable = v
|
|
}
|
|
return typeVariable
|
|
}
|
|
|
|
func (c *Checker) inferToMultipleTypesWithPriority(n *InferenceState, source *Type, targets []*Type, targetFlags TypeFlags, newPriority InferencePriority) {
|
|
savePriority := n.priority
|
|
n.priority |= newPriority
|
|
c.inferToMultipleTypes(n, source, targets, targetFlags)
|
|
n.priority = savePriority
|
|
}
|
|
|
|
func (c *Checker) inferToConditionalType(n *InferenceState, source *Type, target *Type) {
|
|
if source.flags&TypeFlagsConditional != 0 {
|
|
c.inferFromTypes(n, source.AsConditionalType().checkType, target.AsConditionalType().checkType)
|
|
c.inferFromTypes(n, source.AsConditionalType().extendsType, target.AsConditionalType().extendsType)
|
|
c.inferFromTypes(n, c.getTrueTypeFromConditionalType(source), c.getTrueTypeFromConditionalType(target))
|
|
c.inferFromTypes(n, c.getFalseTypeFromConditionalType(source), c.getFalseTypeFromConditionalType(target))
|
|
} else {
|
|
targetTypes := []*Type{c.getTrueTypeFromConditionalType(target), c.getFalseTypeFromConditionalType(target)}
|
|
c.inferToMultipleTypesWithPriority(n, source, targetTypes, target.flags, core.IfElse(n.contravariant, InferencePriorityContravariantConditional, 0))
|
|
}
|
|
}
|
|
|
|
func (c *Checker) inferToTemplateLiteralType(n *InferenceState, source *Type, target *TemplateLiteralType) {
|
|
matches := c.inferTypesFromTemplateLiteralType(source, target)
|
|
types := target.types
|
|
// When the target template literal contains only placeholders (meaning that inference is intended to extract
|
|
// single characters and remainder strings) and inference fails to produce matches, we want to infer 'never' for
|
|
// each placeholder such that instantiation with the inferred value(s) produces 'never', a type for which an
|
|
// assignment check will fail. If we make no inferences, we'll likely end up with the constraint 'string' which,
|
|
// upon instantiation, would collapse all the placeholders to just 'string', and an assignment check might
|
|
// succeed. That would be a pointless and confusing outcome.
|
|
if len(matches) != 0 || core.Every(target.texts, func(s string) bool { return s == "" }) {
|
|
for i, target := range types {
|
|
var source *Type
|
|
if len(matches) != 0 {
|
|
source = matches[i]
|
|
} else {
|
|
source = c.neverType
|
|
}
|
|
// If we are inferring from a string literal type to a type variable whose constraint includes one of the
|
|
// allowed template literal placeholder types, infer from a literal type corresponding to the constraint.
|
|
if source.flags&TypeFlagsStringLiteral != 0 && target.flags&TypeFlagsTypeVariable != 0 {
|
|
if inferenceContext := getInferenceInfoForType(n, target); inferenceContext != nil {
|
|
if constraint := c.getBaseConstraintOfType(inferenceContext.typeParameter); constraint != nil && !IsTypeAny(constraint) {
|
|
allTypeFlags := TypeFlagsNone
|
|
for _, t := range constraint.Distributed() {
|
|
allTypeFlags |= t.flags
|
|
}
|
|
// If the constraint contains `string`, we don't need to look for a more preferred type
|
|
if allTypeFlags&TypeFlagsString == 0 {
|
|
str := getStringLiteralValue(source)
|
|
// If the type contains `number` or a number literal and the string isn't a valid number, exclude numbers
|
|
if allTypeFlags&TypeFlagsNumberLike != 0 && !isValidNumberString(str, true /*roundTripOnly*/) {
|
|
allTypeFlags &^= TypeFlagsNumberLike
|
|
}
|
|
// If the type contains `bigint` or a bigint literal and the string isn't a valid bigint, exclude bigints
|
|
if allTypeFlags&TypeFlagsBigIntLike != 0 && !isValidBigIntString(str, true /*roundTripOnly*/) {
|
|
allTypeFlags &^= TypeFlagsBigIntLike
|
|
}
|
|
choose := func(left *Type, right *Type) *Type {
|
|
switch {
|
|
case right.flags&allTypeFlags == 0:
|
|
return left
|
|
case left.flags&TypeFlagsString != 0:
|
|
return left
|
|
case right.flags&TypeFlagsString != 0:
|
|
return source
|
|
case left.flags&TypeFlagsTemplateLiteral != 0:
|
|
return left
|
|
case right.flags&TypeFlagsTemplateLiteral != 0 && c.isTypeMatchedByTemplateLiteralType(source, right.AsTemplateLiteralType()):
|
|
return source
|
|
case left.flags&TypeFlagsStringMapping != 0:
|
|
return left
|
|
case right.flags&TypeFlagsStringMapping != 0 && str == applyStringMapping(right.symbol, str):
|
|
return source
|
|
case left.flags&TypeFlagsStringLiteral != 0:
|
|
return left
|
|
case right.flags&TypeFlagsStringLiteral != 0 && getStringLiteralValue(right) == str:
|
|
return right
|
|
case left.flags&TypeFlagsNumber != 0:
|
|
return left
|
|
case right.flags&TypeFlagsNumber != 0:
|
|
return c.getNumberLiteralType(jsnum.FromString(str))
|
|
case left.flags&TypeFlagsEnum != 0:
|
|
return left
|
|
case right.flags&TypeFlagsEnum != 0:
|
|
return c.getNumberLiteralType(jsnum.FromString(str))
|
|
case left.flags&TypeFlagsNumberLiteral != 0:
|
|
return left
|
|
case right.flags&TypeFlagsNumberLiteral != 0 && getNumberLiteralValue(right) == jsnum.FromString(str):
|
|
return right
|
|
case left.flags&TypeFlagsBigInt != 0:
|
|
return left
|
|
case right.flags&TypeFlagsBigInt != 0:
|
|
return c.parseBigIntLiteralType(str)
|
|
case left.flags&TypeFlagsBigIntLiteral != 0:
|
|
return left
|
|
case right.flags&TypeFlagsBigIntLiteral != 0 && pseudoBigIntToString(getBigIntLiteralValue(right)) == str:
|
|
return right
|
|
case left.flags&TypeFlagsBoolean != 0:
|
|
return left
|
|
case right.flags&TypeFlagsBoolean != 0:
|
|
switch {
|
|
case str == "true":
|
|
return c.trueType
|
|
case str == "false":
|
|
return c.falseType
|
|
default:
|
|
return c.booleanType
|
|
}
|
|
case left.flags&TypeFlagsBooleanLiteral != 0:
|
|
return left
|
|
case right.flags&TypeFlagsBooleanLiteral != 0 && core.IfElse(getBooleanLiteralValue(right), "true", "false") == str:
|
|
return right
|
|
case left.flags&TypeFlagsUndefined != 0:
|
|
return left
|
|
case right.flags&TypeFlagsUndefined != 0 && right.AsIntrinsicType().intrinsicName == str:
|
|
return right
|
|
case left.flags&TypeFlagsNull != 0:
|
|
return left
|
|
case right.flags&TypeFlagsNull != 0 && right.AsIntrinsicType().intrinsicName == str:
|
|
return right
|
|
default:
|
|
return left
|
|
}
|
|
}
|
|
matchingType := c.neverType
|
|
for _, t := range constraint.Distributed() {
|
|
matchingType = choose(matchingType, t)
|
|
}
|
|
if matchingType.flags&TypeFlagsNever == 0 {
|
|
c.inferFromTypes(n, matchingType, target)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
c.inferFromTypes(n, source, target)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Checker) inferFromGenericMappedTypes(n *InferenceState, source *Type, target *Type) {
|
|
// The source and target types are generic types { [P in S]: X } and { [P in T]: Y }, so we infer
|
|
// from S to T and from X to Y.
|
|
c.inferFromTypes(n, c.getConstraintTypeFromMappedType(source), c.getConstraintTypeFromMappedType(target))
|
|
c.inferFromTypes(n, c.getTemplateTypeFromMappedType(source), c.getTemplateTypeFromMappedType(target))
|
|
sourceNameType := c.getNameTypeFromMappedType(source)
|
|
targetNameType := c.getNameTypeFromMappedType(target)
|
|
if sourceNameType != nil && targetNameType != nil {
|
|
c.inferFromTypes(n, sourceNameType, targetNameType)
|
|
}
|
|
}
|
|
|
|
func (c *Checker) inferFromObjectTypes(n *InferenceState, source *Type, target *Type) {
|
|
if source.objectFlags&ObjectFlagsReference != 0 && target.objectFlags&ObjectFlagsReference != 0 && (source.Target() == target.Target() || c.isArrayType(source) && c.isArrayType(target)) {
|
|
// If source and target are references to the same generic type, infer from type arguments
|
|
c.inferFromTypeArguments(n, c.getTypeArguments(source), c.getTypeArguments(target), c.getVariances(source.Target()))
|
|
return
|
|
}
|
|
if c.isGenericMappedType(source) && c.isGenericMappedType(target) {
|
|
c.inferFromGenericMappedTypes(n, source, target)
|
|
}
|
|
if target.objectFlags&ObjectFlagsMapped != 0 && target.AsMappedType().declaration.NameType == nil {
|
|
constraintType := c.getConstraintTypeFromMappedType(target)
|
|
if c.inferToMappedType(n, source, target, constraintType) {
|
|
return
|
|
}
|
|
}
|
|
// Infer from the members of source and target only if the two types are possibly related
|
|
if c.typesDefinitelyUnrelated(source, target) {
|
|
return
|
|
}
|
|
if c.isArrayOrTupleType(source) {
|
|
if isTupleType(target) {
|
|
sourceArity := c.getTypeReferenceArity(source)
|
|
targetArity := c.getTypeReferenceArity(target)
|
|
elementTypes := c.getTypeArguments(target)
|
|
elementInfos := target.TargetTupleType().elementInfos
|
|
// When source and target are tuple types with the same structure (fixed, variadic, and rest are matched
|
|
// to the same kind in each position), simply infer between the element types.
|
|
if isTupleType(source) && c.isTupleTypeStructureMatching(source, target) {
|
|
for i := range targetArity {
|
|
c.inferFromTypes(n, c.getTypeArguments(source)[i], elementTypes[i])
|
|
}
|
|
return
|
|
}
|
|
startLength := 0
|
|
endLength := 0
|
|
if isTupleType(source) {
|
|
startLength = min(source.TargetTupleType().fixedLength, target.TargetTupleType().fixedLength)
|
|
if target.TargetTupleType().combinedFlags&ElementFlagsVariable != 0 {
|
|
endLength = min(getEndElementCount(source.TargetTupleType(), ElementFlagsFixed), getEndElementCount(target.TargetTupleType(), ElementFlagsFixed))
|
|
}
|
|
}
|
|
// Infer between starting fixed elements.
|
|
for i := range startLength {
|
|
c.inferFromTypes(n, c.getTypeArguments(source)[i], elementTypes[i])
|
|
}
|
|
if !isTupleType(source) || sourceArity-startLength-endLength == 1 && source.TargetTupleType().elementInfos[startLength].flags&ElementFlagsRest != 0 {
|
|
// Single rest element remains in source, infer from that to every element in target
|
|
restType := c.getTypeArguments(source)[startLength]
|
|
for i := startLength; i < targetArity-endLength; i++ {
|
|
t := restType
|
|
if elementInfos[i].flags&ElementFlagsVariadic != 0 {
|
|
t = c.createArrayType(t)
|
|
}
|
|
c.inferFromTypes(n, t, elementTypes[i])
|
|
}
|
|
} else {
|
|
middleLength := targetArity - startLength - endLength
|
|
if middleLength == 2 {
|
|
if elementInfos[startLength].flags&elementInfos[startLength+1].flags&ElementFlagsVariadic != 0 {
|
|
// Middle of target is [...T, ...U] and source is tuple type
|
|
targetInfo := getInferenceInfoForType(n, elementTypes[startLength])
|
|
if targetInfo != nil && targetInfo.impliedArity >= 0 {
|
|
// Infer slices from source based on implied arity of T.
|
|
c.inferFromTypes(n, c.sliceTupleType(source, startLength, endLength+sourceArity-targetInfo.impliedArity), elementTypes[startLength])
|
|
c.inferFromTypes(n, c.sliceTupleType(source, startLength+targetInfo.impliedArity, endLength), elementTypes[startLength+1])
|
|
}
|
|
} else if elementInfos[startLength].flags&ElementFlagsVariadic != 0 && elementInfos[startLength+1].flags&ElementFlagsRest != 0 {
|
|
// Middle of target is [...T, ...rest] and source is tuple type
|
|
// if T is constrained by a fixed-size tuple we might be able to use its arity to infer T
|
|
if info := getInferenceInfoForType(n, elementTypes[startLength]); info != nil {
|
|
constraint := c.getBaseConstraintOfType(info.typeParameter)
|
|
if constraint != nil && isTupleType(constraint) && constraint.TargetTupleType().combinedFlags&ElementFlagsVariable == 0 {
|
|
impliedArity := constraint.TargetTupleType().fixedLength
|
|
c.inferFromTypes(n, c.sliceTupleType(source, startLength, sourceArity-(startLength+impliedArity)), elementTypes[startLength])
|
|
c.inferFromTypes(n, c.getElementTypeOfSliceOfTupleType(source, startLength+impliedArity, endLength, false, false), elementTypes[startLength+1])
|
|
}
|
|
}
|
|
} else if elementInfos[startLength].flags&ElementFlagsRest != 0 && elementInfos[startLength+1].flags&ElementFlagsVariadic != 0 {
|
|
// Middle of target is [...rest, ...T] and source is tuple type
|
|
// if T is constrained by a fixed-size tuple we might be able to use its arity to infer T
|
|
if info := getInferenceInfoForType(n, elementTypes[startLength+1]); info != nil {
|
|
constraint := c.getBaseConstraintOfType(info.typeParameter)
|
|
if constraint != nil && isTupleType(constraint) && constraint.TargetTupleType().combinedFlags&ElementFlagsVariable == 0 {
|
|
impliedArity := constraint.TargetTupleType().fixedLength
|
|
endIndex := sourceArity - getEndElementCount(target.TargetTupleType(), ElementFlagsFixed)
|
|
startIndex := endIndex - impliedArity
|
|
trailingSlice := c.createTupleTypeEx(c.getTypeArguments(source)[startIndex:endIndex], source.TargetTupleType().elementInfos[startIndex:endIndex], false /*readonly*/)
|
|
c.inferFromTypes(n, c.getElementTypeOfSliceOfTupleType(source, startLength, endLength+impliedArity, false, false), elementTypes[startLength])
|
|
c.inferFromTypes(n, trailingSlice, elementTypes[startLength+1])
|
|
}
|
|
}
|
|
}
|
|
} else if middleLength == 1 && elementInfos[startLength].flags&ElementFlagsVariadic != 0 {
|
|
// Middle of target is exactly one variadic element. Infer the slice between the fixed parts in the source.
|
|
// If target ends in optional element(s), make a lower priority a speculative inference.
|
|
priority := core.IfElse(elementInfos[targetArity-1].flags&ElementFlagsOptional != 0, InferencePrioritySpeculativeTuple, 0)
|
|
sourceSlice := c.sliceTupleType(source, startLength, endLength)
|
|
c.inferWithPriority(n, sourceSlice, elementTypes[startLength], priority)
|
|
} else if middleLength == 1 && elementInfos[startLength].flags&ElementFlagsRest != 0 {
|
|
// Middle of target is exactly one rest element. If middle of source is not empty, infer union of middle element types.
|
|
restType := c.getElementTypeOfSliceOfTupleType(source, startLength, endLength, false, false)
|
|
if restType != nil {
|
|
c.inferFromTypes(n, restType, elementTypes[startLength])
|
|
}
|
|
}
|
|
}
|
|
// Infer between ending fixed elements
|
|
for i := range endLength {
|
|
c.inferFromTypes(n, c.getTypeArguments(source)[sourceArity-i-1], elementTypes[targetArity-i-1])
|
|
}
|
|
return
|
|
}
|
|
if c.isArrayType(target) {
|
|
c.inferFromIndexTypes(n, source, target)
|
|
return
|
|
}
|
|
}
|
|
c.inferFromProperties(n, source, target)
|
|
c.inferFromSignatures(n, source, target, SignatureKindCall)
|
|
c.inferFromSignatures(n, source, target, SignatureKindConstruct)
|
|
c.inferFromIndexTypes(n, source, target)
|
|
}
|
|
|
|
func (c *Checker) inferFromProperties(n *InferenceState, source *Type, target *Type) {
|
|
properties := c.getPropertiesOfObjectType(target)
|
|
for _, targetProp := range properties {
|
|
sourceProp := c.getPropertyOfType(source, targetProp.Name)
|
|
if sourceProp != nil && !core.Some(sourceProp.Declarations, c.isSkipDirectInferenceNode) {
|
|
c.inferFromTypes(n, c.removeMissingType(c.getTypeOfSymbol(sourceProp), sourceProp.Flags&ast.SymbolFlagsOptional != 0), c.removeMissingType(c.getTypeOfSymbol(targetProp), targetProp.Flags&ast.SymbolFlagsOptional != 0))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Checker) inferFromSignatures(n *InferenceState, source *Type, target *Type, kind SignatureKind) {
|
|
sourceSignatures := c.getSignaturesOfType(source, kind)
|
|
sourceLen := len(sourceSignatures)
|
|
if sourceLen > 0 {
|
|
// We match source and target signatures from the bottom up, and if the source has fewer signatures
|
|
// than the target, we infer from the first source signature to the excess target signatures.
|
|
targetSignatures := c.getSignaturesOfType(target, kind)
|
|
targetLen := len(targetSignatures)
|
|
for i := range targetLen {
|
|
sourceIndex := max(sourceLen-targetLen+i, 0)
|
|
c.inferFromSignature(n, c.getBaseSignature(sourceSignatures[sourceIndex]), c.getErasedSignature(targetSignatures[i]))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Checker) inferFromSignature(n *InferenceState, source *Signature, target *Signature) {
|
|
if source.flags&SignatureFlagsIsNonInferrable == 0 {
|
|
saveBivariant := n.bivariant
|
|
kind := ast.KindUnknown
|
|
if target.declaration != nil {
|
|
kind = target.declaration.Kind
|
|
}
|
|
// Once we descend into a bivariant signature we remain bivariant for all nested inferences
|
|
n.bivariant = n.bivariant || kind == ast.KindMethodDeclaration || kind == ast.KindMethodSignature || kind == ast.KindConstructor
|
|
c.applyToParameterTypes(source, target, func(s, t *Type) { c.inferFromContravariantTypesIfStrictFunctionTypes(n, s, t) })
|
|
n.bivariant = saveBivariant
|
|
}
|
|
c.applyToReturnTypes(source, target, func(s, t *Type) { c.inferFromTypes(n, s, t) })
|
|
}
|
|
|
|
func (c *Checker) applyToParameterTypes(source *Signature, target *Signature, callback func(s *Type, t *Type)) {
|
|
sourceCount := c.getParameterCount(source)
|
|
targetCount := c.getParameterCount(target)
|
|
sourceRestType := c.getEffectiveRestType(source)
|
|
targetRestType := c.getEffectiveRestType(target)
|
|
targetNonRestCount := targetCount
|
|
if targetRestType != nil {
|
|
targetNonRestCount--
|
|
}
|
|
paramCount := targetNonRestCount
|
|
if sourceRestType == nil {
|
|
paramCount = min(sourceCount, targetNonRestCount)
|
|
}
|
|
sourceThisType := c.getThisTypeOfSignature(source)
|
|
if sourceThisType != nil {
|
|
targetThisType := c.getThisTypeOfSignature(target)
|
|
if targetThisType != nil {
|
|
callback(sourceThisType, targetThisType)
|
|
}
|
|
}
|
|
for i := range paramCount {
|
|
callback(c.getTypeAtPosition(source, i), c.getTypeAtPosition(target, i))
|
|
}
|
|
if targetRestType != nil {
|
|
callback(c.getRestTypeAtPosition(source, paramCount, c.isConstTypeVariable(targetRestType, 0) && !someType(targetRestType, c.isMutableArrayLikeType) /*readonly*/), targetRestType)
|
|
}
|
|
}
|
|
|
|
func (c *Checker) applyToReturnTypes(source *Signature, target *Signature, callback func(s *Type, t *Type)) {
|
|
targetTypePredicate := c.getTypePredicateOfSignature(target)
|
|
if targetTypePredicate != nil {
|
|
sourceTypePredicate := c.getTypePredicateOfSignature(source)
|
|
if sourceTypePredicate != nil && c.typePredicateKindsMatch(sourceTypePredicate, targetTypePredicate) && sourceTypePredicate.t != nil && targetTypePredicate.t != nil {
|
|
callback(sourceTypePredicate.t, targetTypePredicate.t)
|
|
return
|
|
}
|
|
}
|
|
targetReturnType := c.getReturnTypeOfSignature(target)
|
|
if c.couldContainTypeVariables(targetReturnType) {
|
|
callback(c.getReturnTypeOfSignature(source), targetReturnType)
|
|
}
|
|
}
|
|
|
|
func (c *Checker) inferFromIndexTypes(n *InferenceState, source *Type, target *Type) {
|
|
// Inferences across mapped type index signatures are pretty much the same a inferences to homomorphic variables
|
|
priority := InferencePriorityNone
|
|
if source.objectFlags&target.objectFlags&ObjectFlagsMapped != 0 {
|
|
priority = InferencePriorityHomomorphicMappedType
|
|
}
|
|
indexInfos := c.getIndexInfosOfType(target)
|
|
if c.isObjectTypeWithInferableIndex(source) {
|
|
for _, targetInfo := range indexInfos {
|
|
var propTypes []*Type
|
|
for _, prop := range c.getPropertiesOfType(source) {
|
|
if c.isApplicableIndexType(c.getLiteralTypeFromProperty(prop, TypeFlagsStringOrNumberLiteralOrUnique, false), targetInfo.keyType) {
|
|
propType := c.getTypeOfSymbol(prop)
|
|
if prop.Flags&ast.SymbolFlagsOptional != 0 {
|
|
propType = c.removeMissingOrUndefinedType(propType)
|
|
}
|
|
propTypes = append(propTypes, propType)
|
|
}
|
|
}
|
|
for _, info := range c.getIndexInfosOfType(source) {
|
|
if c.isApplicableIndexType(info.keyType, targetInfo.keyType) {
|
|
propTypes = append(propTypes, info.valueType)
|
|
}
|
|
}
|
|
if len(propTypes) != 0 {
|
|
c.inferWithPriority(n, c.getUnionType(propTypes), targetInfo.valueType, priority)
|
|
}
|
|
}
|
|
}
|
|
for _, targetInfo := range indexInfos {
|
|
sourceInfo := c.getApplicableIndexInfo(source, targetInfo.keyType)
|
|
if sourceInfo != nil {
|
|
c.inferWithPriority(n, sourceInfo.valueType, targetInfo.valueType, priority)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Checker) inferToMappedType(n *InferenceState, source *Type, target *Type, constraintType *Type) bool {
|
|
if constraintType.flags&TypeFlagsUnion != 0 || constraintType.flags&TypeFlagsIntersection != 0 {
|
|
result := false
|
|
for _, t := range constraintType.Types() {
|
|
result = core.OrElse(c.inferToMappedType(n, source, target, t), result)
|
|
}
|
|
return result
|
|
}
|
|
if constraintType.flags&TypeFlagsIndex != 0 {
|
|
// We're inferring from some source type S to a homomorphic mapped type { [P in keyof T]: X },
|
|
// where T is a type variable. Use inferTypeForHomomorphicMappedType to infer a suitable source
|
|
// type and then make a secondary inference from that type to T. We make a secondary inference
|
|
// such that direct inferences to T get priority over inferences to Partial<T>, for example.
|
|
inference := getInferenceInfoForType(n, constraintType.AsIndexType().target)
|
|
if inference != nil && !inference.isFixed && !c.isFromInferenceBlockedSource(source) {
|
|
inferredType := c.inferTypeForHomomorphicMappedType(source, target, constraintType)
|
|
if inferredType != nil {
|
|
// We assign a lower priority to inferences made from types containing non-inferrable
|
|
// types because we may only have a partial result (i.e. we may have failed to make
|
|
// reverse inferences for some properties).
|
|
c.inferWithPriority(n, inferredType, inference.typeParameter, core.IfElse(source.objectFlags&ObjectFlagsNonInferrableType != 0, InferencePriorityPartialHomomorphicMappedType, InferencePriorityHomomorphicMappedType))
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
if constraintType.flags&TypeFlagsTypeParameter != 0 {
|
|
// We're inferring from some source type S to a mapped type { [P in K]: X }, where K is a type
|
|
// parameter. First infer from 'keyof S' to K.
|
|
c.inferWithPriority(n, c.getIndexTypeEx(source, core.IfElse(c.patternForType[source] != nil, IndexFlagsNoIndexSignatures, IndexFlagsNone)), constraintType, InferencePriorityMappedTypeConstraint)
|
|
// If K is constrained to a type C, also infer to C. Thus, for a mapped type { [P in K]: X },
|
|
// where K extends keyof T, we make the same inferences as for a homomorphic mapped type
|
|
// { [P in keyof T]: X }. This enables us to make meaningful inferences when the target is a
|
|
// Pick<T, K>.
|
|
extendedConstraint := c.getConstraintOfType(constraintType)
|
|
if extendedConstraint != nil && c.inferToMappedType(n, source, target, extendedConstraint) {
|
|
return true
|
|
}
|
|
// If no inferences can be made to K's constraint, infer from a union of the property types
|
|
// in the source to the template type X.
|
|
propTypes := core.Map(c.getPropertiesOfType(source), c.getTypeOfSymbol)
|
|
indexTypes := core.Map(c.getIndexInfosOfType(source), func(info *IndexInfo) *Type {
|
|
if info != c.enumNumberIndexInfo {
|
|
return info.valueType
|
|
}
|
|
return c.neverType
|
|
})
|
|
c.inferFromTypes(n, c.getUnionType(core.Concatenate(propTypes, indexTypes)), c.getTemplateTypeFromMappedType(target))
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Infer a suitable input type for a homomorphic mapped type { [P in keyof T]: X }. We construct
|
|
// an object type with the same set of properties as the source type, where the type of each
|
|
// property is computed by inferring from the source property type to X for the type
|
|
// variable T[P] (i.e. we treat the type T[P] as the type variable we're inferring for).
|
|
func (c *Checker) inferTypeForHomomorphicMappedType(source *Type, target *Type, constraint *Type) *Type {
|
|
key := ReverseMappedTypeKey{sourceId: source.id, targetId: target.id, constraintId: constraint.id}
|
|
if cached := c.reverseHomomorphicMappedCache[key]; cached != nil {
|
|
return cached
|
|
}
|
|
t := c.createReverseMappedType(source, target, constraint)
|
|
c.reverseHomomorphicMappedCache[key] = t
|
|
return t
|
|
}
|
|
|
|
func (c *Checker) createReverseMappedType(source *Type, target *Type, constraint *Type) *Type {
|
|
// We consider a source type reverse mappable if it has a string index signature or if
|
|
// it has one or more properties and is of a partially inferable type.
|
|
if !(c.getIndexInfoOfType(source, c.stringType) != nil || len(c.getPropertiesOfType(source)) != 0 && c.isPartiallyInferableType(source)) {
|
|
return nil
|
|
}
|
|
// For arrays and tuples we infer new arrays and tuples where the reverse mapping has been
|
|
// applied to the element type(s).
|
|
if c.isArrayType(source) {
|
|
elementType := c.inferReverseMappedType(c.getTypeArguments(source)[0], target, constraint)
|
|
if elementType == nil {
|
|
return nil
|
|
}
|
|
return c.createArrayTypeEx(elementType, c.isReadonlyArrayType(source))
|
|
}
|
|
if isTupleType(source) {
|
|
elementTypes := core.Map(c.getElementTypes(source), func(t *Type) *Type {
|
|
return c.inferReverseMappedType(t, target, constraint)
|
|
})
|
|
if !core.Every(elementTypes, func(t *Type) bool { return t != nil }) {
|
|
return nil
|
|
}
|
|
elementInfos := source.TargetTupleType().elementInfos
|
|
if getMappedTypeModifiers(target)&MappedTypeModifiersIncludeOptional != 0 {
|
|
elementInfos = core.SameMap(elementInfos, func(info TupleElementInfo) TupleElementInfo {
|
|
if info.flags&ElementFlagsOptional != 0 {
|
|
return TupleElementInfo{flags: ElementFlagsRequired, labeledDeclaration: info.labeledDeclaration}
|
|
}
|
|
return info
|
|
})
|
|
}
|
|
return c.createTupleTypeEx(elementTypes, elementInfos, source.TargetTupleType().readonly)
|
|
}
|
|
// For all other object types we infer a new object type where the reverse mapping has been
|
|
// applied to the type of each property.
|
|
reversed := c.newObjectType(ObjectFlagsReverseMapped|ObjectFlagsAnonymous, nil /*symbol*/)
|
|
reversed.AsReverseMappedType().source = source
|
|
reversed.AsReverseMappedType().mappedType = target
|
|
reversed.AsReverseMappedType().constraintType = constraint
|
|
return reversed
|
|
}
|
|
|
|
// We consider a type to be partially inferable if it isn't marked non-inferable or if it is
|
|
// an object literal type with at least one property of an inferable type. For example, an object
|
|
// literal { a: 123, b: x => true } is marked non-inferable because it contains a context sensitive
|
|
// arrow function, but is considered partially inferable because property 'a' has an inferable type.
|
|
func (c *Checker) isPartiallyInferableType(t *Type) bool {
|
|
return t.objectFlags&ObjectFlagsNonInferrableType == 0 || isObjectLiteralType(t) && core.Some(c.getPropertiesOfType(t), func(prop *ast.Symbol) bool {
|
|
return c.isPartiallyInferableType(c.getTypeOfSymbol(prop))
|
|
}) || isTupleType(t) && core.Some(c.getElementTypes(t), c.isPartiallyInferableType)
|
|
}
|
|
|
|
func (c *Checker) inferReverseMappedType(source *Type, target *Type, constraint *Type) *Type {
|
|
key := ReverseMappedTypeKey{sourceId: source.id, targetId: target.id, constraintId: constraint.id}
|
|
if cached, ok := c.reverseMappedCache[key]; ok {
|
|
return core.OrElse(cached, c.unknownType)
|
|
}
|
|
c.reverseMappedSourceStack = append(c.reverseMappedSourceStack, source)
|
|
c.reverseMappedTargetStack = append(c.reverseMappedTargetStack, target)
|
|
saveExpandingFlags := c.reverseExpandingFlags
|
|
if c.isDeeplyNestedType(source, c.reverseMappedSourceStack, 2) {
|
|
c.reverseExpandingFlags |= ExpandingFlagsSource
|
|
}
|
|
if c.isDeeplyNestedType(target, c.reverseMappedTargetStack, 2) {
|
|
c.reverseExpandingFlags |= ExpandingFlagsTarget
|
|
}
|
|
var t *Type
|
|
if c.reverseExpandingFlags != ExpandingFlagsBoth {
|
|
t = c.inferReverseMappedTypeWorker(source, target, constraint)
|
|
}
|
|
c.reverseMappedSourceStack = c.reverseMappedSourceStack[:len(c.reverseMappedSourceStack)-1]
|
|
c.reverseMappedTargetStack = c.reverseMappedTargetStack[:len(c.reverseMappedTargetStack)-1]
|
|
c.reverseExpandingFlags = saveExpandingFlags
|
|
c.reverseMappedCache[key] = t
|
|
return t
|
|
}
|
|
|
|
func (c *Checker) inferReverseMappedTypeWorker(source *Type, target *Type, constraint *Type) *Type {
|
|
typeParameter := c.getIndexedAccessType(constraint.AsIndexType().target, c.getTypeParameterFromMappedType(target))
|
|
templateType := c.getTemplateTypeFromMappedType(target)
|
|
inference := newInferenceInfo(typeParameter)
|
|
c.inferTypes([]*InferenceInfo{inference}, source, templateType, InferencePriorityNone, false)
|
|
return core.OrElse(c.getTypeFromInference(inference), c.unknownType)
|
|
}
|
|
|
|
func (c *Checker) resolveReverseMappedTypeMembers(t *Type) {
|
|
r := t.AsReverseMappedType()
|
|
indexInfo := c.getIndexInfoOfType(r.source, c.stringType)
|
|
modifiers := getMappedTypeModifiers(r.mappedType)
|
|
readonlyMask := modifiers&MappedTypeModifiersIncludeReadonly == 0
|
|
optionalMask := core.IfElse(modifiers&MappedTypeModifiersIncludeOptional != 0, 0, ast.SymbolFlagsOptional)
|
|
var indexInfos []*IndexInfo
|
|
if indexInfo != nil {
|
|
indexInfos = []*IndexInfo{c.newIndexInfo(c.stringType, core.OrElse(c.inferReverseMappedType(indexInfo.valueType, r.mappedType, r.constraintType), c.unknownType), readonlyMask && indexInfo.isReadonly, nil, nil)}
|
|
}
|
|
members := make(ast.SymbolTable)
|
|
limitedConstraint := c.getLimitedConstraint(t)
|
|
for _, prop := range c.getPropertiesOfType(r.source) {
|
|
// In case of a reverse mapped type with an intersection constraint, if we were able to
|
|
// extract the filtering type literals we skip those properties that are not assignable to them,
|
|
// because the extra properties wouldn't get through the application of the mapped type anyway
|
|
if limitedConstraint != nil {
|
|
propertyNameType := c.getLiteralTypeFromProperty(prop, TypeFlagsStringOrNumberLiteralOrUnique, false)
|
|
if !c.isTypeAssignableTo(propertyNameType, limitedConstraint) {
|
|
continue
|
|
}
|
|
}
|
|
checkFlags := ast.CheckFlagsReverseMapped | core.IfElse(readonlyMask && c.isReadonlySymbol(prop), ast.CheckFlagsReadonly, 0)
|
|
inferredProp := c.newSymbolEx(ast.SymbolFlagsProperty|prop.Flags&optionalMask, prop.Name, checkFlags)
|
|
inferredProp.Declarations = prop.Declarations
|
|
c.valueSymbolLinks.Get(inferredProp).nameType = c.valueSymbolLinks.Get(prop).nameType
|
|
links := c.ReverseMappedSymbolLinks.Get(inferredProp)
|
|
links.propertyType = c.getTypeOfSymbol(prop)
|
|
constraintTarget := r.constraintType.AsIndexType().target
|
|
if constraintTarget.flags&TypeFlagsIndexedAccess != 0 && constraintTarget.AsIndexedAccessType().objectType.flags&TypeFlagsTypeParameter != 0 && constraintTarget.AsIndexedAccessType().indexType.flags&TypeFlagsTypeParameter != 0 {
|
|
// A reverse mapping of `{[K in keyof T[K_1]]: T[K_1]}` is the same as that of `{[K in keyof T]: T}`, since all we care about is
|
|
// inferring to the "type parameter" (or indexed access) shared by the constraint and template. So, to reduce the number of
|
|
// type identities produced, we simplify such indexed access occurrences
|
|
newTypeParam := constraintTarget.AsIndexedAccessType().objectType
|
|
newMappedType := c.replaceIndexedAccess(r.mappedType, constraintTarget, newTypeParam)
|
|
links.mappedType = newMappedType
|
|
links.constraintType = c.getIndexType(newTypeParam)
|
|
} else {
|
|
links.mappedType = r.mappedType
|
|
links.constraintType = r.constraintType
|
|
}
|
|
members[prop.Name] = inferredProp
|
|
}
|
|
c.setStructuredTypeMembers(t, members, nil, nil, indexInfos)
|
|
}
|
|
|
|
func (c *Checker) getTypeOfReverseMappedSymbol(symbol *ast.Symbol) *Type {
|
|
links := c.valueSymbolLinks.Get(symbol)
|
|
if links.resolvedType == nil {
|
|
reverseLinks := c.ReverseMappedSymbolLinks.Get(symbol)
|
|
links.resolvedType = core.OrElse(c.inferReverseMappedType(reverseLinks.propertyType, reverseLinks.mappedType, reverseLinks.constraintType), c.unknownType)
|
|
}
|
|
return links.resolvedType
|
|
}
|
|
|
|
// If the original mapped type had an intersection constraint we extract its components,
|
|
// and we make an attempt to do so even if the intersection has been reduced to a union.
|
|
// This entire process allows us to possibly retrieve the filtering type literals.
|
|
// e.g. { [K in keyof U & ("a" | "b") ] } -> "a" | "b"
|
|
func (c *Checker) getLimitedConstraint(t *Type) *Type {
|
|
constraint := c.getConstraintTypeFromMappedType(t.AsReverseMappedType().mappedType)
|
|
if !(constraint.flags&TypeFlagsUnion != 0 || constraint.flags&TypeFlagsIntersection != 0) {
|
|
return nil
|
|
}
|
|
origin := constraint
|
|
if constraint.flags&TypeFlagsUnion != 0 {
|
|
origin = constraint.AsUnionType().origin
|
|
}
|
|
if origin == nil || origin.flags&TypeFlagsIntersection == 0 {
|
|
return nil
|
|
}
|
|
constraintType := t.AsReverseMappedType().constraintType
|
|
limitedConstraint := c.getIntersectionType(core.Filter(origin.Types(), func(t *Type) bool { return t != constraintType }))
|
|
if limitedConstraint != c.neverType {
|
|
return limitedConstraint
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Checker) replaceIndexedAccess(instantiable *Type, t *Type, replacement *Type) *Type {
|
|
// map type.indexType to 0
|
|
// map type.objectType to `[TReplacement]`
|
|
// thus making the indexed access `[TReplacement][0]` or `TReplacement`
|
|
return c.instantiateType(instantiable, newTypeMapper([]*Type{t.AsIndexedAccessType().indexType, t.AsIndexedAccessType().objectType}, []*Type{c.getNumberLiteralType(0), c.createTupleType([]*Type{replacement})}))
|
|
}
|
|
|
|
func (c *Checker) typesDefinitelyUnrelated(source *Type, target *Type) bool {
|
|
// Two tuple types with incompatible arities are definitely unrelated.
|
|
// Two object types that each have a property that is unmatched in the other are definitely unrelated.
|
|
if isTupleType(source) && isTupleType(target) {
|
|
return tupleTypesDefinitelyUnrelated(source, target)
|
|
}
|
|
return c.getUnmatchedProperty(source, target, false /*requireOptionalProperties*/, true /*matchDiscriminantProperties*/) != nil &&
|
|
c.getUnmatchedProperty(target, source, false /*requireOptionalProperties*/, false /*matchDiscriminantProperties*/) != nil
|
|
}
|
|
|
|
func tupleTypesDefinitelyUnrelated(source *Type, target *Type) bool {
|
|
s := source.TargetTupleType()
|
|
t := target.TargetTupleType()
|
|
return t.combinedFlags&ElementFlagsVariadic == 0 && t.minLength > s.minLength ||
|
|
t.combinedFlags&ElementFlagsVariable == 0 && (s.combinedFlags&ElementFlagsVariable != 0 || t.fixedLength < s.fixedLength)
|
|
}
|
|
|
|
func (c *Checker) isTupleTypeStructureMatching(t1 *Type, t2 *Type) bool {
|
|
if c.getTypeReferenceArity(t1) != c.getTypeReferenceArity(t2) {
|
|
return false
|
|
}
|
|
for i, e := range t1.TargetTupleType().elementInfos {
|
|
if e.flags&ElementFlagsVariable != t2.TargetTupleType().elementInfos[i].flags&ElementFlagsVariable {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (c *Checker) isTypeOrBaseIdenticalTo(s *Type, t *Type) bool {
|
|
if t == c.missingType {
|
|
return s == t
|
|
}
|
|
return c.isTypeIdenticalTo(s, t) ||
|
|
t.flags&TypeFlagsString != 0 && s.flags&TypeFlagsStringLiteral != 0 ||
|
|
t.flags&TypeFlagsNumber != 0 && s.flags&TypeFlagsNumberLiteral != 0
|
|
}
|
|
|
|
func (c *Checker) isTypeCloselyMatchedBy(s *Type, t *Type) bool {
|
|
return s.flags&TypeFlagsObject != 0 && t.flags&TypeFlagsObject != 0 && s.symbol != nil && s.symbol == t.symbol ||
|
|
s.alias != nil && t.alias != nil && len(s.alias.typeArguments) != 0 && s.alias.symbol == t.alias.symbol
|
|
}
|
|
|
|
// Create an object with properties named in the string literal type. Every property has type `any`.
|
|
func (c *Checker) createEmptyObjectTypeFromStringLiteral(t *Type) *Type {
|
|
members := make(ast.SymbolTable)
|
|
for _, t := range t.Distributed() {
|
|
if t.flags&TypeFlagsStringLiteral == 0 {
|
|
continue
|
|
}
|
|
name := getStringLiteralValue(t)
|
|
literalProp := c.newSymbol(ast.SymbolFlagsProperty, name)
|
|
c.valueSymbolLinks.Get(literalProp).resolvedType = c.anyType
|
|
if t.symbol != nil {
|
|
literalProp.Declarations = t.symbol.Declarations
|
|
literalProp.ValueDeclaration = t.symbol.ValueDeclaration
|
|
}
|
|
members[name] = literalProp
|
|
}
|
|
var indexInfos []*IndexInfo
|
|
if t.flags&TypeFlagsString != 0 {
|
|
indexInfos = []*IndexInfo{c.newIndexInfo(c.stringType, c.emptyObjectType, false /*isReadonly*/, nil, nil)}
|
|
}
|
|
return c.newAnonymousType(nil, members, nil, nil, indexInfos)
|
|
}
|
|
|
|
func (c *Checker) newInferenceContext(typeParameters []*Type, signature *Signature, flags InferenceFlags, compareTypes TypeComparer) *InferenceContext {
|
|
if compareTypes == nil {
|
|
compareTypes = c.compareTypesAssignable
|
|
}
|
|
return c.newInferenceContextWorker(core.Map(typeParameters, newInferenceInfo), signature, flags, compareTypes)
|
|
}
|
|
|
|
func (c *Checker) cloneInferenceContext(n *InferenceContext, extraFlags InferenceFlags) *InferenceContext {
|
|
if n == nil {
|
|
return nil
|
|
}
|
|
return c.newInferenceContextWorker(core.Map(n.inferences, cloneInferenceInfo), n.signature, n.flags|extraFlags, n.compareTypes)
|
|
}
|
|
|
|
func (c *Checker) cloneInferredPartOfContext(n *InferenceContext) *InferenceContext {
|
|
inferences := core.Filter(n.inferences, hasInferenceCandidates)
|
|
if len(inferences) == 0 {
|
|
return nil
|
|
}
|
|
return c.newInferenceContextWorker(core.Map(inferences, cloneInferenceInfo), n.signature, n.flags, n.compareTypes)
|
|
}
|
|
|
|
func (c *Checker) newInferenceContextWorker(inferences []*InferenceInfo, signature *Signature, flags InferenceFlags, compareTypes TypeComparer) *InferenceContext {
|
|
n := &InferenceContext{
|
|
inferences: inferences,
|
|
signature: signature,
|
|
flags: flags,
|
|
compareTypes: compareTypes,
|
|
}
|
|
n.mapper = c.newInferenceTypeMapper(n, true /*fixing*/)
|
|
n.nonFixingMapper = c.newInferenceTypeMapper(n, false /*fixing*/)
|
|
return n
|
|
}
|
|
|
|
func (c *Checker) addIntraExpressionInferenceSite(n *InferenceContext, node *ast.Node, t *Type) {
|
|
n.intraExpressionInferenceSites = append(n.intraExpressionInferenceSites, IntraExpressionInferenceSite{node: node, t: t})
|
|
}
|
|
|
|
// We collect intra-expression inference sites within object and array literals to handle cases where
|
|
// inferred types flow between context sensitive element expressions. For example:
|
|
//
|
|
// declare function foo<T>(arg: [(n: number) => T, (x: T) => void]): void;
|
|
// foo([_a => 0, n => n.toFixed()]);
|
|
//
|
|
// Above, both arrow functions in the tuple argument are context sensitive, thus both are omitted from the
|
|
// pass that collects inferences from the non-context sensitive parts of the arguments. In the subsequent
|
|
// pass where nothing is omitted, we need to commit to an inference for T in order to contextually type the
|
|
// parameter in the second arrow function, but we want to first infer from the return type of the first
|
|
// arrow function. This happens automatically when the arrow functions are discrete arguments (because we
|
|
// infer from each argument before processing the next), but when the arrow functions are elements of an
|
|
// object or array literal, we need to perform intra-expression inferences early.
|
|
func (c *Checker) inferFromIntraExpressionSites(n *InferenceContext) {
|
|
for _, site := range n.intraExpressionInferenceSites {
|
|
var contextualType *Type
|
|
if ast.IsMethodDeclaration(site.node) {
|
|
contextualType = c.getContextualTypeForObjectLiteralMethod(site.node, ContextFlagsNoConstraints)
|
|
} else {
|
|
contextualType = c.getContextualType(site.node, ContextFlagsNoConstraints)
|
|
}
|
|
if contextualType != nil {
|
|
c.inferTypes(n.inferences, site.t, contextualType, InferencePriorityNone, false)
|
|
}
|
|
}
|
|
n.intraExpressionInferenceSites = nil
|
|
}
|
|
|
|
func (c *Checker) getInferredType(n *InferenceContext, index int) *Type {
|
|
inference := n.inferences[index]
|
|
if inference.inferredType == nil {
|
|
if inference.typeParameter == c.errorType {
|
|
return inference.typeParameter
|
|
}
|
|
var inferredType *Type
|
|
var fallbackType *Type
|
|
if n.signature != nil {
|
|
var inferredCovariantType *Type
|
|
if len(inference.candidates) != 0 {
|
|
inferredCovariantType = c.getCovariantInference(inference, n.signature)
|
|
}
|
|
var inferredContravariantType *Type
|
|
if len(inference.contraCandidates) != 0 {
|
|
inferredContravariantType = c.getContravariantInference(inference)
|
|
}
|
|
if inferredCovariantType != nil || inferredContravariantType != nil {
|
|
// If we have both co- and contra-variant inferences, we prefer the co-variant inference if it is not 'never',
|
|
// all co-variant inferences are assignable to it (i.e. it isn't one of a conflicting set of candidates), it is
|
|
// assignable to some contra-variant inference, and no other type parameter is constrained to this type parameter
|
|
// and has inferences that would conflict. Otherwise, we prefer the contra-variant inference.
|
|
// Similarly ignore co-variant `any` inference when both are available as almost everything is assignable to it
|
|
// and it would spoil the overall inference.
|
|
preferCovariantType := inferredCovariantType != nil && (inferredContravariantType == nil ||
|
|
inferredCovariantType.flags&(TypeFlagsNever|TypeFlagsAny) == 0 &&
|
|
core.Some(inference.contraCandidates, func(t *Type) bool { return c.isTypeAssignableTo(inferredCovariantType, t) }) &&
|
|
core.Every(n.inferences, func(other *InferenceInfo) bool {
|
|
return other != inference && c.getConstraintOfTypeParameter(other.typeParameter) != inference.typeParameter ||
|
|
core.Every(other.candidates, func(t *Type) bool { return c.isTypeAssignableTo(t, inferredCovariantType) })
|
|
}))
|
|
if preferCovariantType {
|
|
inferredType = inferredCovariantType
|
|
fallbackType = inferredContravariantType
|
|
} else {
|
|
inferredType = inferredContravariantType
|
|
fallbackType = inferredCovariantType
|
|
}
|
|
} else if n.flags&InferenceFlagsNoDefault != 0 {
|
|
// We use silentNeverType as the wildcard that signals no inferences.
|
|
inferredType = c.silentNeverType
|
|
} else {
|
|
// Infer either the default or the empty object type when no inferences were
|
|
// made. It is important to remember that in this case, inference still
|
|
// succeeds, meaning there is no error for not having inference candidates. An
|
|
// inference error only occurs when there are *conflicting* candidates, i.e.
|
|
// candidates with no common supertype.
|
|
defaultType := c.getDefaultFromTypeParameter(inference.typeParameter)
|
|
if defaultType != nil {
|
|
// Instantiate the default type. Any forward reference to a type
|
|
// parameter should be instantiated to the empty object type.
|
|
inferredType = c.instantiateType(defaultType, mergeTypeMappers(c.newBackreferenceMapper(n, index), n.nonFixingMapper))
|
|
}
|
|
}
|
|
} else {
|
|
inferredType = c.getTypeFromInference(inference)
|
|
}
|
|
inference.inferredType = inferredType
|
|
if inference.inferredType == nil {
|
|
inference.inferredType = core.IfElse(n.flags&InferenceFlagsAnyDefault != 0, c.anyType, c.unknownType)
|
|
}
|
|
constraint := c.getConstraintOfTypeParameter(inference.typeParameter)
|
|
if constraint != nil {
|
|
instantiatedConstraint := c.instantiateType(constraint, n.nonFixingMapper)
|
|
if inferredType != nil {
|
|
constraintWithThis := c.getTypeWithThisArgument(instantiatedConstraint, inferredType, false)
|
|
if n.compareTypes(inferredType, constraintWithThis, false) == TernaryFalse {
|
|
var filteredByConstraint *Type
|
|
if inference.priority == InferencePriorityReturnType {
|
|
// If we have a pure return type inference, we may succeed by removing constituents of the inferred type
|
|
// that aren't assignable to the constraint type (pure return type inferences are speculation anyway).
|
|
filteredByConstraint = c.mapType(inferredType, func(t *Type) *Type {
|
|
return core.IfElse(n.compareTypes(t, constraintWithThis, false) != TernaryFalse, t, c.neverType)
|
|
})
|
|
}
|
|
inferredType = core.IfElse(filteredByConstraint != nil && filteredByConstraint.flags&TypeFlagsNever == 0, filteredByConstraint, nil)
|
|
}
|
|
}
|
|
if inferredType == nil {
|
|
// If the fallback type satisfies the constraint, we pick it. Otherwise, we pick the constraint.
|
|
inferredType = core.IfElse(fallbackType != nil && n.compareTypes(fallbackType, c.getTypeWithThisArgument(instantiatedConstraint, fallbackType, false), false) != TernaryFalse, fallbackType, instantiatedConstraint)
|
|
}
|
|
inference.inferredType = inferredType
|
|
}
|
|
c.clearActiveMapperCaches()
|
|
}
|
|
return inference.inferredType
|
|
}
|
|
|
|
func (c *Checker) getInferredTypes(n *InferenceContext) []*Type {
|
|
result := make([]*Type, len(n.inferences))
|
|
for i := range n.inferences {
|
|
result[i] = c.getInferredType(n, i)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (c *Checker) getMapperFromContext(n *InferenceContext) *TypeMapper {
|
|
if n == nil {
|
|
return nil
|
|
}
|
|
return n.mapper
|
|
}
|
|
|
|
// Return a type mapper that combines the context's return mapper with a mapper that erases any additional type parameters
|
|
// to their inferences at the time of creation.
|
|
func (c *Checker) createOuterReturnMapper(context *InferenceContext) *TypeMapper {
|
|
if context.outerReturnMapper == nil {
|
|
mapper := c.cloneInferenceContext(context, InferenceFlagsNone).mapper
|
|
if context.returnMapper != nil {
|
|
mapper = newMergedTypeMapper(context.returnMapper, mapper)
|
|
}
|
|
context.outerReturnMapper = mapper
|
|
}
|
|
return context.outerReturnMapper
|
|
}
|
|
|
|
func (c *Checker) getCovariantInference(inference *InferenceInfo, signature *Signature) *Type {
|
|
// Extract all object and array literal types and replace them with a single widened and normalized type.
|
|
candidates := c.unionObjectAndArrayLiteralCandidates(inference.candidates)
|
|
// We widen inferred literal types if
|
|
// all inferences were made to top-level occurrences of the type parameter, and
|
|
// the type parameter has no constraint or its constraint includes no primitive or literal types, and
|
|
// the type parameter was fixed during inference or does not occur at top-level in the return type.
|
|
primitiveConstraint := c.hasPrimitiveConstraint(inference.typeParameter) || c.isConstTypeVariable(inference.typeParameter, 0)
|
|
widenLiteralTypes := !primitiveConstraint && inference.topLevel && (inference.isFixed || !c.isTypeParameterAtTopLevelInReturnType(signature, inference.typeParameter))
|
|
var baseCandidates []*Type
|
|
switch {
|
|
case primitiveConstraint:
|
|
baseCandidates = core.SameMap(candidates, c.getRegularTypeOfLiteralType)
|
|
case widenLiteralTypes:
|
|
baseCandidates = core.SameMap(candidates, c.getWidenedLiteralType)
|
|
default:
|
|
baseCandidates = candidates
|
|
}
|
|
// If all inferences were made from a position that implies a combined result, infer a union type.
|
|
// Otherwise, infer a common supertype.
|
|
var unwidenedType *Type
|
|
if inference.priority&InferencePriorityPriorityImpliesCombination != 0 {
|
|
unwidenedType = c.getUnionTypeEx(baseCandidates, UnionReductionSubtype, nil, nil)
|
|
} else {
|
|
unwidenedType = c.getCommonSupertype(baseCandidates)
|
|
}
|
|
return c.getWidenedType(unwidenedType)
|
|
}
|
|
|
|
func (c *Checker) getContravariantInference(inference *InferenceInfo) *Type {
|
|
if inference.priority&InferencePriorityPriorityImpliesCombination != 0 {
|
|
return c.getIntersectionType(inference.contraCandidates)
|
|
}
|
|
return c.getCommonSubtype(inference.contraCandidates)
|
|
}
|
|
|
|
func (c *Checker) unionObjectAndArrayLiteralCandidates(candidates []*Type) []*Type {
|
|
if len(candidates) > 1 {
|
|
objectLiterals := core.Filter(candidates, isObjectOrArrayLiteralType)
|
|
if len(objectLiterals) != 0 {
|
|
literalsType := c.getUnionTypeEx(objectLiterals, UnionReductionSubtype, nil, nil)
|
|
nonLiteralTypes := core.Filter(candidates, func(t *Type) bool { return !isObjectOrArrayLiteralType(t) })
|
|
return core.Concatenate(nonLiteralTypes, []*Type{literalsType})
|
|
}
|
|
}
|
|
return candidates
|
|
}
|
|
|
|
func (c *Checker) hasPrimitiveConstraint(t *Type) bool {
|
|
constraint := c.getConstraintOfTypeParameter(t)
|
|
if constraint != nil {
|
|
if constraint.flags&TypeFlagsConditional != 0 {
|
|
constraint = c.getDefaultConstraintOfConditionalType(constraint)
|
|
}
|
|
return c.maybeTypeOfKind(constraint, TypeFlagsPrimitive|TypeFlagsIndex|TypeFlagsTemplateLiteral|TypeFlagsStringMapping)
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (c *Checker) isTypeParameterAtTopLevel(t *Type, tp *Type, depth int) bool {
|
|
return t == tp ||
|
|
t.flags&TypeFlagsUnionOrIntersection != 0 && core.Some(t.Types(), func(t *Type) bool { return c.isTypeParameterAtTopLevel(t, tp, depth) }) ||
|
|
depth < 3 && t.flags&TypeFlagsConditional != 0 &&
|
|
(c.isTypeParameterAtTopLevel(c.getTrueTypeFromConditionalType(t), tp, depth+1) ||
|
|
c.isTypeParameterAtTopLevel(c.getFalseTypeFromConditionalType(t), tp, depth+1))
|
|
}
|
|
|
|
func (c *Checker) isTypeParameterAtTopLevelInReturnType(signature *Signature, typeParameter *Type) bool {
|
|
typePredicate := c.getTypePredicateOfSignature(signature)
|
|
if typePredicate != nil {
|
|
return typePredicate.t != nil && c.isTypeParameterAtTopLevel(typePredicate.t, typeParameter, 0)
|
|
}
|
|
return c.isTypeParameterAtTopLevel(c.getReturnTypeOfSignature(signature), typeParameter, 0)
|
|
}
|
|
|
|
func (c *Checker) getTypeFromInference(inference *InferenceInfo) *Type {
|
|
switch {
|
|
case inference.candidates != nil:
|
|
return c.getUnionTypeEx(inference.candidates, UnionReductionSubtype, nil, nil)
|
|
case inference.contraCandidates != nil:
|
|
return c.getIntersectionType(inference.contraCandidates)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getInferenceInfoForType(n *InferenceState, t *Type) *InferenceInfo {
|
|
if t.flags&TypeFlagsTypeVariable != 0 {
|
|
for _, inference := range n.inferences {
|
|
if t == inference.typeParameter {
|
|
return inference
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Checker) getCommonSupertype(types []*Type) *Type {
|
|
if len(types) == 1 {
|
|
return types[0]
|
|
}
|
|
// Remove nullable types from each of the candidates.
|
|
primaryTypes := types
|
|
if c.strictNullChecks {
|
|
primaryTypes = core.SameMap(types, func(t *Type) *Type {
|
|
return c.filterType(t, func(u *Type) bool { return u.flags&TypeFlagsNullable == 0 })
|
|
})
|
|
}
|
|
// When the candidate types are all literal types with the same base type, return a union
|
|
// of those literal types. Otherwise, return the leftmost type for which no type to the
|
|
// right is a supertype.
|
|
var supertype *Type
|
|
if c.literalTypesWithSameBaseType(primaryTypes) {
|
|
supertype = c.getUnionType(primaryTypes)
|
|
} else {
|
|
supertype = c.getSingleCommonSupertype(primaryTypes)
|
|
}
|
|
// Add any nullable types that occurred in the candidates back to the result.
|
|
if core.Same(primaryTypes, types) {
|
|
return supertype
|
|
}
|
|
return c.getNullableType(supertype, c.getCombinedTypeFlags(types)&TypeFlagsNullable)
|
|
}
|
|
|
|
func (c *Checker) getSingleCommonSupertype(types []*Type) *Type {
|
|
// First, find the leftmost type for which no type to the right is a strict supertype, and if that
|
|
// type is a strict supertype of all other candidates, return it. Otherwise, return the leftmost type
|
|
// for which no type to the right is a (regular) supertype.
|
|
candidate := c.findLeftmostType(types, (*Checker).isTypeStrictSubtypeOf)
|
|
if core.Every(types, func(t *Type) bool { return t == candidate || c.isTypeStrictSubtypeOf(t, candidate) }) {
|
|
return candidate
|
|
}
|
|
return c.findLeftmostType(types, (*Checker).isTypeSubtypeOf)
|
|
}
|
|
|
|
func (c *Checker) findLeftmostType(types []*Type, f func(c *Checker, s *Type, t *Type) bool) *Type {
|
|
var candidate *Type
|
|
for _, t := range types {
|
|
if candidate == nil || f(c, candidate, t) {
|
|
candidate = t
|
|
}
|
|
}
|
|
return candidate
|
|
}
|
|
|
|
// Return the leftmost type for which no type to the right is a subtype.
|
|
func (c *Checker) getCommonSubtype(types []*Type) *Type {
|
|
var subtype *Type
|
|
for _, t := range types {
|
|
if subtype == nil || c.isTypeSubtypeOf(t, subtype) {
|
|
subtype = t
|
|
}
|
|
}
|
|
return subtype
|
|
}
|
|
|
|
func (c *Checker) getCombinedTypeFlags(types []*Type) TypeFlags {
|
|
flags := TypeFlagsNone
|
|
for _, t := range types {
|
|
if t.flags&TypeFlagsUnion != 0 {
|
|
flags |= c.getCombinedTypeFlags(t.Types())
|
|
} else {
|
|
flags |= t.flags
|
|
}
|
|
}
|
|
return flags
|
|
}
|
|
|
|
func (c *Checker) literalTypesWithSameBaseType(types []*Type) bool {
|
|
var commonBaseType *Type
|
|
for _, t := range types {
|
|
if t.flags&TypeFlagsNever == 0 {
|
|
baseType := c.getBaseTypeOfLiteralType(t)
|
|
if commonBaseType == nil {
|
|
commonBaseType = baseType
|
|
}
|
|
if baseType == t || baseType != commonBaseType {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (c *Checker) isFromInferenceBlockedSource(t *Type) bool {
|
|
return t.symbol != nil && core.Some(t.symbol.Declarations, c.isSkipDirectInferenceNode)
|
|
}
|
|
|
|
func (c *Checker) isSkipDirectInferenceNode(node *ast.Node) bool {
|
|
return c.skipDirectInferenceNodes.Has(node)
|
|
}
|
|
|
|
// Returns `true` if `type` has the shape `[T[0]]` where `T` is `typeParameter`
|
|
func (c *Checker) isTupleOfSelf(tp *Type, t *Type) bool {
|
|
return isTupleType(t) && c.getTupleElementType(t, 0) == c.getIndexedAccessType(tp, c.getNumberLiteralType(0)) && c.getTypeOfPropertyOfType(t, "1") == nil
|
|
}
|
|
|
|
func newInferenceInfo(typeParameter *Type) *InferenceInfo {
|
|
return &InferenceInfo{typeParameter: typeParameter, priority: InferencePriorityMaxValue, topLevel: true, impliedArity: -1}
|
|
}
|
|
|
|
func cloneInferenceInfo(info *InferenceInfo) *InferenceInfo {
|
|
return &InferenceInfo{
|
|
typeParameter: info.typeParameter,
|
|
candidates: slices.Clone(info.candidates),
|
|
contraCandidates: slices.Clone(info.contraCandidates),
|
|
inferredType: info.inferredType,
|
|
priority: info.priority,
|
|
topLevel: info.topLevel,
|
|
isFixed: info.isFixed,
|
|
impliedArity: info.impliedArity,
|
|
}
|
|
}
|
|
|
|
func clearCachedInferences(inferences []*InferenceInfo) {
|
|
for _, inference := range inferences {
|
|
if !inference.isFixed {
|
|
inference.inferredType = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func hasInferenceCandidates(info *InferenceInfo) bool {
|
|
return len(info.candidates) != 0 || len(info.contraCandidates) != 0
|
|
}
|
|
|
|
func hasInferenceCandidatesOrDefault(info *InferenceInfo) bool {
|
|
return info.candidates != nil || info.contraCandidates != nil || hasTypeParameterDefault(info.typeParameter)
|
|
}
|
|
|
|
func hasTypeParameterDefault(tp *Type) bool {
|
|
if tp.symbol != nil {
|
|
for _, d := range tp.symbol.Declarations {
|
|
if ast.IsTypeParameterDeclaration(d) && d.AsTypeParameter().DefaultType != nil {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func hasOverlappingInferences(a []*InferenceInfo, b []*InferenceInfo) bool {
|
|
for i := range a {
|
|
if hasInferenceCandidates(a[i]) && hasInferenceCandidates(b[i]) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (c *Checker) mergeInferences(target []*InferenceInfo, source []*InferenceInfo) {
|
|
for i := range target {
|
|
if !hasInferenceCandidates(target[i]) && hasInferenceCandidates(source[i]) {
|
|
target[i] = source[i]
|
|
}
|
|
}
|
|
}
|