package checker

import (
	"slices"

	"github.com/microsoft/typescript-go/internal/ast"
	"github.com/microsoft/typescript-go/internal/core"
	"github.com/microsoft/typescript-go/internal/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
	depth             int
}

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 {
		var sourceTypes []*Type
		if source.flags&TypeFlagsUnion != 0 {
			sourceTypes = source.Types()
		} else {
			sourceTypes = []*Type{source}
		}
		// First, infer between identically matching source and target constituents and remove the
		// matching types.
		tempSources, tempTargets := c.inferFromMatchingTypes(n, sourceTypes, 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.candidateDepths = nil
					inference.contraCandidates = nil
					inference.topLevel = true
					inference.priority = n.priority
				}
				if n.priority == inference.priority {
					// 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 {
						index := slices.Index(inference.candidates, candidate)
						if index < 0 || inference.candidateDepths[index] < n.depth {
							// Candidate isn't present or is present with lower depth
							if index >= 0 {
								// Remove candidate with lower depth
								inference.candidates = slices.Delete(inference.candidates, index, index+1)
								inference.candidateDepths = slices.Delete(inference.candidateDepths, index, index+1)
							}
							index = 0
							for index < len(inference.candidateDepths) {
								if inference.candidateDepths[index] < n.depth {
									break
								}
								index++
							}
							// Insert candidate at end or immediately before first candidate with lower depth.
							// This ensures candidates with the highest depth are stored first.
							inference.candidates = slices.Insert(inference.candidates, index, candidate)
							inference.candidateDepths = slices.Insert(inference.candidateDepths, index, n.depth)
							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) {
	n.depth++
	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])
		}
	}
	n.depth--
}

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
		var sources []*Type
		if source.flags&TypeFlagsUnion != 0 {
			sources = source.Types()
		} else {
			sources = []*Type{source}
		}
		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(), c.compareTypesAssignable):
									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])
								if restType := c.getElementTypeOfSliceOfTupleType(source, startLength+impliedArity, endLength, false, false); restType != nil {
									c.inferFromTypes(n, restType, 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
								if startIndex >= startLength {
									trailingSlice := c.createTupleTypeEx(c.getTypeArguments(source)[startIndex:endIndex], source.TargetTupleType().elementInfos[startIndex:endIndex], false /*readonly*/)
									if restType := c.getElementTypeOfSliceOfTupleType(source, startLength, endLength+impliedArity, false, false); restType != nil {
										c.inferFromTypes(n, restType, 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 c.getWidenedType(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 len(inference.candidates) != 0:
		return c.getUnionTypeEx(inference.candidates, UnionReductionSubtype, nil, nil)
	case len(inference.contraCandidates) != 0:
		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)
}

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),
		candidateDepths:  slices.Clone(info.candidateDepths),
		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 hasInferenceCandidates(info) || hasTypeParameterDefault(info.typeParameter)
}

func hasTypeParameterDefault(tp *Type) bool {
	if tp.symbol != nil {
		for _, d := range tp.symbol.Declarations {
			if ast.IsTypeParameterDeclaration(d) && d.AsTypeParameterDeclaration().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]
		}
	}
}
