package checker

import (
	"slices"
	"strconv"
	"strings"

	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/binder"
	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections"
	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/debug"
	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/diagnostics"
	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/jsnum"
	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/scanner"
)

type SignatureCheckMode uint32

const (
	SignatureCheckModeNone               SignatureCheckMode = 0
	SignatureCheckModeBivariantCallback  SignatureCheckMode = 1 << 0
	SignatureCheckModeStrictCallback     SignatureCheckMode = 1 << 1
	SignatureCheckModeIgnoreReturnTypes  SignatureCheckMode = 1 << 2
	SignatureCheckModeStrictArity        SignatureCheckMode = 1 << 3
	SignatureCheckModeStrictTopSignature SignatureCheckMode = 1 << 4
	SignatureCheckModeCallback           SignatureCheckMode = SignatureCheckModeBivariantCallback | SignatureCheckModeStrictCallback
)

type MinArgumentCountFlags uint32

const (
	MinArgumentCountFlagsNone                    MinArgumentCountFlags = 0
	MinArgumentCountFlagsStrongArityForUntypedJS MinArgumentCountFlags = 1 << 0
	MinArgumentCountFlagsVoidIsNonOptional       MinArgumentCountFlags = 1 << 1
)

type IntersectionState uint32

const (
	IntersectionStateNone   IntersectionState = 0
	IntersectionStateSource IntersectionState = 1 << 0 // Source type is a constituent of an outer intersection
	IntersectionStateTarget IntersectionState = 1 << 1 // Target type is a constituent of an outer intersection
)

type RecursionFlags uint32

const (
	RecursionFlagsNone   RecursionFlags = 0
	RecursionFlagsSource RecursionFlags = 1 << 0
	RecursionFlagsTarget RecursionFlags = 1 << 1
	RecursionFlagsBoth                  = RecursionFlagsSource | RecursionFlagsTarget
)

type ExpandingFlags uint8

const (
	ExpandingFlagsNone   ExpandingFlags = 0
	ExpandingFlagsSource ExpandingFlags = 1 << 0
	ExpandingFlagsTarget ExpandingFlags = 1 << 1
	ExpandingFlagsBoth                  = ExpandingFlagsSource | ExpandingFlagsTarget
)

type RelationComparisonResult uint32

const (
	RelationComparisonResultNone                RelationComparisonResult = 0
	RelationComparisonResultSucceeded           RelationComparisonResult = 1 << 0
	RelationComparisonResultFailed              RelationComparisonResult = 1 << 1
	RelationComparisonResultReportsUnmeasurable RelationComparisonResult = 1 << 3
	RelationComparisonResultReportsUnreliable   RelationComparisonResult = 1 << 4
	RelationComparisonResultComplexityOverflow  RelationComparisonResult = 1 << 5
	RelationComparisonResultStackDepthOverflow  RelationComparisonResult = 1 << 6
	RelationComparisonResultReportsMask                                  = RelationComparisonResultReportsUnmeasurable | RelationComparisonResultReportsUnreliable
	RelationComparisonResultOverflow                                     = RelationComparisonResultComplexityOverflow | RelationComparisonResultStackDepthOverflow
)

type DiagnosticAndArguments struct {
	message   *diagnostics.Message
	arguments []any
}

type ErrorOutputContainer struct {
	errors      []*ast.Diagnostic
	skipLogging bool
}

type ErrorReporter func(message *diagnostics.Message, args ...any)

type RecursionId struct {
	value any
}

// This function exists to constrain the types of values that can be used as recursion IDs.
func asRecursionId[T *ast.Node | *ast.Symbol | *Type](value T) RecursionId {
	return RecursionId{value: value}
}

type Relation struct {
	results map[string]RelationComparisonResult
}

func (r *Relation) get(key string) RelationComparisonResult {
	return r.results[key]
}

func (r *Relation) set(key string, result RelationComparisonResult) {
	if r.results == nil {
		r.results = make(map[string]RelationComparisonResult)
	}
	r.results[key] = result
}

func (r *Relation) size() int {
	return len(r.results)
}

func (c *Checker) isTypeIdenticalTo(source *Type, target *Type) bool {
	return c.isTypeRelatedTo(source, target, c.identityRelation)
}

func (c *Checker) compareTypesIdentical(source *Type, target *Type) Ternary {
	if c.isTypeRelatedTo(source, target, c.identityRelation) {
		return TernaryTrue
	}
	return TernaryFalse
}

func (c *Checker) compareTypesAssignableSimple(source *Type, target *Type) Ternary {
	if c.isTypeRelatedTo(source, target, c.assignableRelation) {
		return TernaryTrue
	}
	return TernaryFalse
}

func (c *Checker) compareTypesAssignable(source *Type, target *Type, reportErrors bool) Ternary {
	if c.isTypeRelatedTo(source, target, c.assignableRelation) {
		return TernaryTrue
	}
	return TernaryFalse
}

func (c *Checker) compareTypesSubtypeOf(source *Type, target *Type) Ternary {
	if c.isTypeRelatedTo(source, target, c.subtypeRelation) {
		return TernaryTrue
	}
	return TernaryFalse
}

func (c *Checker) isTypeAssignableTo(source *Type, target *Type) bool {
	return c.isTypeRelatedTo(source, target, c.assignableRelation)
}

func (c *Checker) isTypeSubtypeOf(source *Type, target *Type) bool {
	return c.isTypeRelatedTo(source, target, c.subtypeRelation)
}

func (c *Checker) isTypeStrictSubtypeOf(source *Type, target *Type) bool {
	return c.isTypeRelatedTo(source, target, c.strictSubtypeRelation)
}

func (c *Checker) isTypeComparableTo(source *Type, target *Type) bool {
	return c.isTypeRelatedTo(source, target, c.comparableRelation)
}

func (c *Checker) areTypesComparable(type1 *Type, type2 *Type) bool {
	return c.isTypeComparableTo(type1, type2) || c.isTypeComparableTo(type2, type1)
}

func (c *Checker) isTypeRelatedTo(source *Type, target *Type, relation *Relation) bool {
	if isFreshLiteralType(source) {
		source = source.AsLiteralType().regularType
	}
	if isFreshLiteralType(target) {
		target = target.AsLiteralType().regularType
	}
	if source == target {
		return true
	}
	if relation != c.identityRelation {
		if relation == c.comparableRelation && target.flags&TypeFlagsNever == 0 && c.isSimpleTypeRelatedTo(target, source, relation, nil) || c.isSimpleTypeRelatedTo(source, target, relation, nil) {
			return true
		}
	} else if !((source.flags|target.flags)&(TypeFlagsUnionOrIntersection|TypeFlagsIndexedAccess|TypeFlagsConditional|TypeFlagsSubstitution) != 0) {
		// We have excluded types that may simplify to other forms, so types must have identical flags
		if source.flags != target.flags {
			return false
		}
		if source.flags&TypeFlagsSingleton != 0 {
			return true
		}
	}
	if source.flags&TypeFlagsObject != 0 && target.flags&TypeFlagsObject != 0 {
		related := relation.get(getRelationKey(source, target, IntersectionStateNone, relation == c.identityRelation, false))
		if related != RelationComparisonResultNone {
			return related&RelationComparisonResultSucceeded != 0
		}
	}
	if source.flags&TypeFlagsStructuredOrInstantiable != 0 || target.flags&TypeFlagsStructuredOrInstantiable != 0 {
		return c.checkTypeRelatedTo(source, target, relation, nil /*errorNode*/)
	}
	return false
}

func (c *Checker) isSimpleTypeRelatedTo(source *Type, target *Type, relation *Relation, errorReporter ErrorReporter) bool {
	s := source.flags
	t := target.flags
	if t&TypeFlagsAny != 0 || s&TypeFlagsNever != 0 || source == c.wildcardType {
		return true
	}
	if t&TypeFlagsUnknown != 0 && !(relation == c.strictSubtypeRelation && s&TypeFlagsAny != 0) {
		return true
	}
	if t&TypeFlagsNever != 0 {
		return false
	}
	if s&TypeFlagsStringLike != 0 && t&TypeFlagsString != 0 {
		return true
	}
	if s&TypeFlagsStringLiteral != 0 && s&TypeFlagsEnumLiteral != 0 && t&TypeFlagsStringLiteral != 0 && t&TypeFlagsEnumLiteral == 0 && source.AsLiteralType().value == target.AsLiteralType().value {
		return true
	}
	if s&TypeFlagsNumberLike != 0 && t&TypeFlagsNumber != 0 {
		return true
	}
	if s&TypeFlagsNumberLiteral != 0 && s&TypeFlagsEnumLiteral != 0 && t&TypeFlagsNumberLiteral != 0 && t&TypeFlagsEnumLiteral == 0 && source.AsLiteralType().value == target.AsLiteralType().value {
		return true
	}
	if s&TypeFlagsBigIntLike != 0 && t&TypeFlagsBigInt != 0 {
		return true
	}
	if s&TypeFlagsBooleanLike != 0 && t&TypeFlagsBoolean != 0 {
		return true
	}
	if s&TypeFlagsESSymbolLike != 0 && t&TypeFlagsESSymbol != 0 {
		return true
	}
	if s&TypeFlagsEnum != 0 && t&TypeFlagsEnum != 0 && source.symbol.Name == target.symbol.Name && c.isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter) {
		return true
	}
	if s&TypeFlagsEnumLiteral != 0 && t&TypeFlagsEnumLiteral != 0 {
		if s&TypeFlagsUnion != 0 && t&TypeFlagsUnion != 0 && c.isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter) {
			return true
		}
		if s&TypeFlagsLiteral != 0 && t&TypeFlagsLiteral != 0 && source.AsLiteralType().value == target.AsLiteralType().value && c.isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter) {
			return true
		}
	}
	// In non-strictNullChecks mode, `undefined` and `null` are assignable to anything except `never`.
	// Since unions and intersections may reduce to `never`, we exclude them here.
	if s&TypeFlagsUndefined != 0 && (!c.strictNullChecks && t&TypeFlagsUnionOrIntersection == 0 || t&(TypeFlagsUndefined|TypeFlagsVoid) != 0) {
		return true
	}
	if s&TypeFlagsNull != 0 && (!c.strictNullChecks && t&TypeFlagsUnionOrIntersection == 0 || t&TypeFlagsNull != 0) {
		return true
	}
	if s&TypeFlagsObject != 0 && t&TypeFlagsNonPrimitive != 0 && !(relation == c.strictSubtypeRelation && c.IsEmptyAnonymousObjectType(source) && source.objectFlags&ObjectFlagsFreshLiteral == 0) {
		return true
	}
	if relation == c.assignableRelation || relation == c.comparableRelation {
		if s&TypeFlagsAny != 0 {
			return true
		}
		// Type number is assignable to any computed numeric enum type or any numeric enum literal type, and
		// a numeric literal type is assignable any computed numeric enum type or any numeric enum literal type
		// with a matching value. These rules exist such that enums can be used for bit-flag purposes.
		if s&TypeFlagsNumber != 0 && (t&TypeFlagsEnum != 0 || t&TypeFlagsNumberLiteral != 0 && t&TypeFlagsEnumLiteral != 0) {
			return true
		}
		if s&TypeFlagsNumberLiteral != 0 && s&TypeFlagsEnumLiteral == 0 && (t&TypeFlagsEnum != 0 || t&TypeFlagsNumberLiteral != 0 && t&TypeFlagsEnumLiteral != 0 && source.AsLiteralType().value == target.AsLiteralType().value) {
			return true
		}
		// Anything is assignable to a union containing undefined, null, and {}
		if c.isUnknownLikeUnionType(target) {
			return true
		}
	}
	return false
}

func (c *Checker) isEnumTypeRelatedTo(source *ast.Symbol, target *ast.Symbol, errorReporter ErrorReporter) bool {
	sourceSymbol := core.IfElse(source.Flags&ast.SymbolFlagsEnumMember != 0, c.getParentOfSymbol(source), source)
	targetSymbol := core.IfElse(target.Flags&ast.SymbolFlagsEnumMember != 0, c.getParentOfSymbol(target), target)
	if sourceSymbol == targetSymbol {
		return true
	}
	if sourceSymbol.Name != targetSymbol.Name || sourceSymbol.Flags&ast.SymbolFlagsRegularEnum == 0 || targetSymbol.Flags&ast.SymbolFlagsRegularEnum == 0 {
		return false
	}
	key := EnumRelationKey{sourceId: ast.GetSymbolId(sourceSymbol), targetId: ast.GetSymbolId(targetSymbol)}
	if entry := c.enumRelation[key]; entry != RelationComparisonResultNone && !(entry&RelationComparisonResultFailed != 0 && errorReporter != nil) {
		return entry&RelationComparisonResultSucceeded != 0
	}
	targetEnumType := c.getTypeOfSymbol(targetSymbol)
	for _, sourceProperty := range c.getPropertiesOfType(c.getTypeOfSymbol(sourceSymbol)) {
		if sourceProperty.Flags&ast.SymbolFlagsEnumMember != 0 {
			targetProperty := c.getPropertyOfType(targetEnumType, sourceProperty.Name)
			if targetProperty == nil || targetProperty.Flags&ast.SymbolFlagsEnumMember == 0 {
				if errorReporter != nil {
					errorReporter(diagnostics.Property_0_is_missing_in_type_1, c.symbolToString(sourceProperty), c.TypeToString(c.getDeclaredTypeOfSymbol(targetSymbol)))
				}
				c.enumRelation[key] = RelationComparisonResultFailed
				return false
			}
			sourceValue := c.getEnumMemberValue(ast.GetDeclarationOfKind(sourceProperty, ast.KindEnumMember)).Value
			targetValue := c.getEnumMemberValue(ast.GetDeclarationOfKind(targetProperty, ast.KindEnumMember)).Value
			if sourceValue != targetValue {
				// If we have 2 enums with *known* values that differ, they are incompatible.
				if sourceValue != nil && targetValue != nil {
					if errorReporter != nil {
						errorReporter(diagnostics.Each_declaration_of_0_1_differs_in_its_value_where_2_was_expected_but_3_was_given, c.symbolToString(targetSymbol), c.symbolToString(targetProperty), c.valueToString(targetValue), c.valueToString(sourceValue))
					}
					c.enumRelation[key] = RelationComparisonResultFailed
					return false
				}
				// At this point we know that at least one of the values is 'undefined'.
				// This may mean that we have an opaque member from an ambient enum declaration,
				// or that we were not able to calculate it (which is basically an error).
				//
				// Either way, we can assume that it's numeric.
				// If the other is a string, we have a mismatch in types.
				_, sourceIsString := sourceValue.(string)
				_, targetIsString := targetValue.(string)
				if sourceIsString || targetIsString {
					if errorReporter != nil {
						knownStringValue := core.OrElse(sourceValue, targetValue)
						errorReporter(diagnostics.One_value_of_0_1_is_the_string_2_and_the_other_is_assumed_to_be_an_unknown_numeric_value, c.symbolToString(targetSymbol), c.symbolToString(targetProperty), c.valueToString(knownStringValue))
					}
					c.enumRelation[key] = RelationComparisonResultFailed
					return false
				}
			}
		}
	}
	c.enumRelation[key] = RelationComparisonResultSucceeded
	return true
}

func (c *Checker) checkTypeAssignableTo(source *Type, target *Type, errorNode *ast.Node, headMessage *diagnostics.Message) bool {
	return c.checkTypeRelatedToEx(source, target, c.assignableRelation, errorNode, headMessage, nil)
}

func (c *Checker) checkTypeAssignableToEx(source *Type, target *Type, errorNode *ast.Node, headMessage *diagnostics.Message, diagnosticOutput *[]*ast.Diagnostic) bool {
	return c.checkTypeRelatedToEx(source, target, c.assignableRelation, errorNode, headMessage, diagnosticOutput)
}

func (c *Checker) checkTypeComparableTo(source *Type, target *Type, errorNode *ast.Node, headMessage *diagnostics.Message) bool {
	return c.checkTypeRelatedToEx(source, target, c.comparableRelation, errorNode, headMessage, nil)
}

func (c *Checker) checkTypeRelatedTo(source *Type, target *Type, relation *Relation, errorNode *ast.Node) bool {
	return c.checkTypeRelatedToEx(source, target, relation, errorNode, nil, nil)
}

// Check that source is related to target according to the given relation. When errorNode is non-nil, errors are
// reported to the checker's diagnostic collection or through diagnosticOutput when non-nil. Callers can assume that
// this function only reports zero or one error to diagnosticOutput (unlike checkTypeRelatedToAndOptionallyElaborate).
func (c *Checker) checkTypeRelatedToEx(
	source *Type,
	target *Type,
	relation *Relation,
	errorNode *ast.Node,
	headMessage *diagnostics.Message,
	diagnosticOutput *[]*ast.Diagnostic,
) bool {
	r := c.getRelater()
	r.relation = relation
	r.errorNode = errorNode
	r.relationCount = (16_000_000 - relation.size()) / 8
	result := r.isRelatedToEx(source, target, RecursionFlagsBoth, errorNode != nil /*reportErrors*/, headMessage, IntersectionStateNone)
	if r.overflow {
		// Record this relation as having failed such that we don't attempt the overflowing operation again.
		id := getRelationKey(source, target, IntersectionStateNone, relation == c.identityRelation, false /*ignoreConstraints*/)
		relation.set(id, RelationComparisonResultFailed|core.IfElse(r.relationCount <= 0, RelationComparisonResultComplexityOverflow, RelationComparisonResultStackDepthOverflow))
		message := core.IfElse(r.relationCount <= 0, diagnostics.Excessive_complexity_comparing_types_0_and_1, diagnostics.Excessive_stack_depth_comparing_types_0_and_1)
		if errorNode == nil {
			errorNode = c.currentNode
		}
		c.reportDiagnostic(NewDiagnosticForNode(errorNode, message, c.TypeToString(source), c.TypeToString(target)), diagnosticOutput)
	} else if r.errorChain != nil {
		// Check if we should issue an extra diagnostic to produce a quickfix for a slightly incorrect import statement
		if headMessage != nil && errorNode != nil && result == TernaryFalse && source.symbol != nil && c.exportTypeLinks.Has(source.symbol) {
			links := c.exportTypeLinks.Get(source.symbol)
			if links.originatingImport != nil && !ast.IsImportCall(links.originatingImport) {
				helpfulRetry := c.checkTypeRelatedTo(c.getTypeOfSymbol(links.target), target, relation /*errorNode*/, nil)
				if helpfulRetry {
					// Likely an incorrect import. Issue a helpful diagnostic to produce a quickfix to change the import
					r.relatedInfo = append(r.relatedInfo, createDiagnosticForNode(links.originatingImport, diagnostics.Type_originates_at_this_import_A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime_Consider_using_a_default_import_or_import_require_here_instead))
				}
			}
		}
		c.reportDiagnostic(createDiagnosticChainFromErrorChain(r.errorChain, r.errorNode, r.relatedInfo), diagnosticOutput)
	}
	c.putRelater(r)
	return result != TernaryFalse
}

func createDiagnosticChainFromErrorChain(chain *ErrorChain, errorNode *ast.Node, relatedInfo []*ast.Diagnostic) *ast.Diagnostic {
	for chain != nil && chain.message.ElidedInCompatibilityPyramid() {
		chain = chain.next
	}
	if chain == nil {
		return nil
	}
	next := createDiagnosticChainFromErrorChain(chain.next, errorNode, relatedInfo)
	if next == nil {
		return NewDiagnosticForNode(errorNode, chain.message, chain.args...).SetRelatedInfo(relatedInfo)
	}
	return ast.NewDiagnosticChain(next, chain.message, chain.args...)
}

func (c *Checker) reportDiagnostic(diagnostic *ast.Diagnostic, diagnosticOutput *[]*ast.Diagnostic) {
	if diagnostic != nil {
		if diagnosticOutput != nil {
			*diagnosticOutput = append(*diagnosticOutput, diagnostic)
		} else {
			c.diagnostics.Add(diagnostic)
		}
	}
}

func (c *Checker) checkTypeAssignableToAndOptionallyElaborate(source *Type, target *Type, errorNode *ast.Node, expr *ast.Node, headMessage *diagnostics.Message, diagnosticOutput *[]*ast.Diagnostic) bool {
	return c.checkTypeRelatedToAndOptionallyElaborate(source, target, c.assignableRelation, errorNode, expr, headMessage, diagnosticOutput)
}

func (c *Checker) checkTypeRelatedToAndOptionallyElaborate(source *Type, target *Type, relation *Relation, errorNode *ast.Node, expr *ast.Node, headMessage *diagnostics.Message, diagnosticOutput *[]*ast.Diagnostic) bool {
	if c.isTypeRelatedTo(source, target, relation) {
		return true
	}
	if errorNode != nil && !c.elaborateError(expr, source, target, relation, headMessage, diagnosticOutput) {
		return c.checkTypeRelatedToEx(source, target, relation, errorNode, headMessage, diagnosticOutput)
	}
	return false
}

func (c *Checker) elaborateError(node *ast.Node, source *Type, target *Type, relation *Relation, headMessage *diagnostics.Message, diagnosticOutput *[]*ast.Diagnostic) bool {
	if node == nil || c.isOrHasGenericConditional(target) {
		return false
	}
	if c.elaborateDidYouMeanToCallOrConstruct(node, source, target, relation, SignatureKindConstruct, headMessage, diagnosticOutput) ||
		c.elaborateDidYouMeanToCallOrConstruct(node, source, target, relation, SignatureKindCall, headMessage, diagnosticOutput) {
		return true
	}
	switch node.Kind {
	case ast.KindAsExpression:
		if !isConstAssertion(node) {
			break
		}
		fallthrough
	case ast.KindJsxExpression, ast.KindParenthesizedExpression:
		return c.elaborateError(node.Expression(), source, target, relation, headMessage, diagnosticOutput)
	case ast.KindBinaryExpression:
		switch node.AsBinaryExpression().OperatorToken.Kind {
		case ast.KindEqualsToken, ast.KindCommaToken:
			return c.elaborateError(node.AsBinaryExpression().Right, source, target, relation, headMessage, diagnosticOutput)
		}
	case ast.KindObjectLiteralExpression:
		return c.elaborateObjectLiteral(node, source, target, relation, diagnosticOutput)
	case ast.KindArrayLiteralExpression:
		return c.elaborateArrayLiteral(node, source, target, relation, diagnosticOutput)
	case ast.KindArrowFunction:
		return c.elaborateArrowFunction(node, source, target, relation, diagnosticOutput)
	case ast.KindJsxAttributes:
		return c.elaborateJsxComponents(node, source, target, relation, diagnosticOutput)
	}
	return false
}

func (c *Checker) isOrHasGenericConditional(t *Type) bool {
	return t.flags&TypeFlagsConditional != 0 || (t.flags&TypeFlagsIntersection != 0 && core.Some(t.Types(), c.isOrHasGenericConditional))
}

func (c *Checker) elaborateDidYouMeanToCallOrConstruct(node *ast.Node, source *Type, target *Type, relation *Relation, kind SignatureKind, headMessage *diagnostics.Message, diagnosticOutput *[]*ast.Diagnostic) bool {
	if core.Some(c.getSignaturesOfType(source, kind), func(s *Signature) bool {
		returnType := c.getReturnTypeOfSignature(s)
		return returnType.flags&(TypeFlagsAny|TypeFlagsNever) == 0 && c.checkTypeRelatedTo(returnType, target, relation, nil /*errorNode*/)
	}) {
		var diags []*ast.Diagnostic
		if !c.checkTypeRelatedToEx(source, target, relation, node, headMessage, &diags) {
			diagnostic := diags[0]
			message := core.IfElse(kind == SignatureKindConstruct,
				diagnostics.Did_you_mean_to_use_new_with_this_expression,
				diagnostics.Did_you_mean_to_call_this_expression)
			c.reportDiagnostic(diagnostic.AddRelatedInfo(createDiagnosticForNode(node, message)), diagnosticOutput)
			return true
		}
	}
	return false
}

func (c *Checker) elaborateObjectLiteral(node *ast.Node, source *Type, target *Type, relation *Relation, diagnosticOutput *[]*ast.Diagnostic) bool {
	if target.flags&(TypeFlagsPrimitive|TypeFlagsNever) != 0 {
		return false
	}
	reportedError := false
	for _, prop := range node.AsObjectLiteralExpression().Properties.Nodes {
		if ast.IsSpreadAssignment(prop) {
			continue
		}
		nameType := c.getLiteralTypeFromProperty(c.getSymbolOfDeclaration(prop), TypeFlagsStringOrNumberLiteralOrUnique, false)
		if nameType == nil || nameType.flags&TypeFlagsNever != 0 {
			continue
		}
		switch prop.Kind {
		case ast.KindSetAccessor, ast.KindGetAccessor, ast.KindMethodDeclaration, ast.KindShorthandPropertyAssignment:
			reportedError = c.elaborateElement(source, target, relation, prop.Name(), nil, nameType, nil, diagnosticOutput) || reportedError
		case ast.KindPropertyAssignment:
			message := core.IfElse(ast.IsComputedNonLiteralName(prop.Name()), diagnostics.Type_of_computed_property_s_value_is_0_which_is_not_assignable_to_type_1, nil)
			reportedError = c.elaborateElement(source, target, relation, prop.Name(), prop.Initializer(), nameType, message, diagnosticOutput) || reportedError
		}
	}
	return reportedError
}

func (c *Checker) elaborateArrayLiteral(node *ast.Node, source *Type, target *Type, relation *Relation, diagnosticOutput *[]*ast.Diagnostic) bool {
	if target.flags&(TypeFlagsPrimitive|TypeFlagsNever) != 0 {
		return false
	}
	if !c.isTupleLikeType(source) {
		c.pushContextualType(node, target, false /*isCache*/)
		source = c.checkArrayLiteral(node, CheckModeContextual|CheckModeForceTuple)
		c.popContextualType()
		if !c.isTupleLikeType(source) {
			return false
		}
	}
	reportedError := false
	for i, element := range node.AsArrayLiteralExpression().Elements.Nodes {
		if ast.IsOmittedExpression(element) || c.isTupleLikeType(target) && c.getPropertyOfType(target, jsnum.Number(i).String()) == nil {
			continue
		}
		nameType := c.getNumberLiteralType(jsnum.Number(i))
		checkNode := c.getEffectiveCheckNode(element)
		reportedError = c.elaborateElement(source, target, relation, checkNode, checkNode, nameType, nil, diagnosticOutput) || reportedError
	}
	return reportedError
}

func (c *Checker) elaborateElement(source *Type, target *Type, relation *Relation, prop *ast.Node, next *ast.Node, nameType *Type, errorMessage *diagnostics.Message, diagnosticOutput *[]*ast.Diagnostic) bool {
	targetPropType := c.getBestMatchIndexedAccessTypeOrUndefined(source, target, nameType)
	if targetPropType == nil || targetPropType.flags&TypeFlagsIndexedAccess != 0 {
		// Don't elaborate on indexes on generic variables
		return false
	}
	sourcePropType := c.getIndexedAccessTypeOrUndefined(source, nameType, AccessFlagsNone, nil, nil)
	if sourcePropType == nil || c.checkTypeRelatedTo(sourcePropType, targetPropType, relation, nil /*errorNode*/) {
		// Don't elaborate on indexes on generic variables or when types match
		return false
	}
	if next != nil && c.elaborateError(next, sourcePropType, targetPropType, relation, nil /*headMessage*/, diagnosticOutput) {
		return true
	}
	// Issue error on the prop itself, since the prop couldn't elaborate the error
	var diags []*ast.Diagnostic
	// Use the expression type, if available
	specificSource := sourcePropType
	if next != nil {
		specificSource = c.checkExpressionForMutableLocationWithContextualType(next, sourcePropType)
	}
	if c.exactOptionalPropertyTypes && c.isExactOptionalPropertyMismatch(specificSource, targetPropType) {
		diags = append(diags, createDiagnosticForNode(prop, diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_type_of_the_target, c.TypeToString(specificSource), c.TypeToString(targetPropType)))
	} else {
		propName := c.getPropertyNameFromIndex(nameType, nil /*accessNode*/)
		targetIsOptional := core.OrElse(c.getPropertyOfType(target, propName), c.unknownSymbol).Flags&ast.SymbolFlagsOptional != 0
		sourceIsOptional := core.OrElse(c.getPropertyOfType(source, propName), c.unknownSymbol).Flags&ast.SymbolFlagsOptional != 0
		targetPropType = c.removeMissingType(targetPropType, targetIsOptional)
		sourcePropType = c.removeMissingType(sourcePropType, targetIsOptional && sourceIsOptional)
		result := c.checkTypeRelatedToEx(specificSource, targetPropType, relation, prop, errorMessage, &diags)
		if result && specificSource != sourcePropType {
			// If for whatever reason the expression type doesn't yield an error, make sure we still issue an error on the sourcePropType
			c.checkTypeRelatedToEx(sourcePropType, targetPropType, relation, prop, errorMessage, &diags)
		}
	}
	if len(diags) == 0 {
		return false
	}
	diagnostic := diags[0]
	var propertyName string
	var targetProp *ast.Symbol
	if isTypeUsableAsPropertyName(nameType) {
		propertyName = getPropertyNameFromType(nameType)
		targetProp = c.getPropertyOfType(target, propertyName)
	}
	issuedElaboration := false
	if targetProp == nil {
		indexInfo := c.getApplicableIndexInfo(target, nameType)
		if indexInfo != nil && indexInfo.declaration != nil && !c.program.IsSourceFileDefaultLibrary(ast.GetSourceFileOfNode(indexInfo.declaration).Path()) {
			issuedElaboration = true
			diagnostic.AddRelatedInfo(createDiagnosticForNode(indexInfo.declaration, diagnostics.The_expected_type_comes_from_this_index_signature))
		}
	}
	if !issuedElaboration && (targetProp != nil && len(targetProp.Declarations) != 0 || target.symbol != nil && len(target.symbol.Declarations) != 0) {
		var targetNode *ast.Node
		if targetProp != nil && len(targetProp.Declarations) != 0 {
			targetNode = targetProp.Declarations[0]
		} else {
			targetNode = target.symbol.Declarations[0]
		}
		if propertyName == "" || nameType.flags&TypeFlagsUniqueESSymbol != 0 {
			propertyName = c.TypeToString(nameType)
		}
		if !c.program.IsSourceFileDefaultLibrary(ast.GetSourceFileOfNode(targetNode).Path()) {
			diagnostic.AddRelatedInfo(createDiagnosticForNode(targetNode, diagnostics.The_expected_type_comes_from_property_0_which_is_declared_here_on_type_1, propertyName, c.TypeToString(target)))
		}
	}
	c.reportDiagnostic(diagnostic, diagnosticOutput)
	return true
}

func (c *Checker) getBestMatchIndexedAccessTypeOrUndefined(source *Type, target *Type, nameType *Type) *Type {
	idx := c.getIndexedAccessTypeOrUndefined(target, nameType, AccessFlagsNone, nil, nil)
	if idx != nil {
		return idx
	}
	if target.flags&TypeFlagsUnion != 0 {
		best := c.getBestMatchingType(source, target, c.compareTypesAssignableSimple)
		if best != nil {
			return c.getIndexedAccessTypeOrUndefined(best, nameType, AccessFlagsNone, nil, nil)
		}
	}
	return nil
}

func (c *Checker) checkExpressionForMutableLocationWithContextualType(next *ast.Node, sourcePropType *Type) *Type {
	c.pushContextualType(next, sourcePropType, false /*isCache*/)
	result := c.checkExpressionForMutableLocation(next, CheckModeContextual)
	c.popContextualType()
	return result
}

func (c *Checker) elaborateArrowFunction(node *ast.Node, source *Type, target *Type, relation *Relation, diagnosticOutput *[]*ast.Diagnostic) bool {
	// Don't elaborate blocks or functions with annotated parameter types
	if ast.IsBlock(node.Body()) || core.Some(node.Parameters(), hasType) {
		return false
	}
	sourceSig := c.getSingleCallSignature(source)
	if sourceSig == nil {
		return false
	}
	targetSignatures := c.getSignaturesOfType(target, SignatureKindCall)
	if len(targetSignatures) == 0 {
		return false
	}
	returnExpression := node.Body()
	sourceReturn := c.getReturnTypeOfSignature(sourceSig)
	targetReturn := c.getUnionType(core.Map(targetSignatures, c.getReturnTypeOfSignature))
	if c.checkTypeRelatedTo(sourceReturn, targetReturn, relation, nil /*errorNode*/) {
		return false
	}
	if returnExpression != nil && c.elaborateError(returnExpression, sourceReturn, targetReturn, relation, nil /*headMessage*/, diagnosticOutput) {
		return true
	}
	var diags []*ast.Diagnostic
	c.checkTypeRelatedToEx(sourceReturn, targetReturn, relation, returnExpression, nil /*headMessage*/, &diags)
	if len(diags) != 0 {
		diagnostic := diags[0]
		if target.symbol != nil && len(target.symbol.Declarations) != 0 {
			diagnostic.AddRelatedInfo(createDiagnosticForNode(target.symbol.Declarations[0], diagnostics.The_expected_type_comes_from_the_return_type_of_this_signature))
		}
		if getFunctionFlags(node)&FunctionFlagsAsync == 0 && c.getTypeOfPropertyOfType(sourceReturn, "then") == nil && c.checkTypeRelatedTo(c.createPromiseType(sourceReturn), targetReturn, relation, nil /*errorNode*/) {
			diagnostic.AddRelatedInfo(createDiagnosticForNode(node, diagnostics.Did_you_mean_to_mark_this_function_as_async))
		}
		c.reportDiagnostic(diagnostic, diagnosticOutput)
		return true
	}
	return false
}

// A type is 'weak' if it is an object type with at least one optional property
// and no required properties, call/construct signatures or index signatures
func (c *Checker) isWeakType(t *Type) bool {
	if t.flags&TypeFlagsObject != 0 {
		resolved := c.resolveStructuredTypeMembers(t)
		return len(resolved.signatures) == 0 && len(resolved.indexInfos) == 0 && len(resolved.properties) > 0 && core.Every(resolved.properties, func(p *ast.Symbol) bool {
			return p.Flags&ast.SymbolFlagsOptional != 0
		})
	}
	if t.flags&TypeFlagsSubstitution != 0 {
		return c.isWeakType(t.AsSubstitutionType().baseType)
	}
	if t.flags&TypeFlagsIntersection != 0 {
		return core.Every(t.Types(), c.isWeakType)
	}
	return false
}

func (c *Checker) hasCommonProperties(source *Type, target *Type, isComparingJsxAttributes bool) bool {
	for _, prop := range c.getPropertiesOfType(source) {
		if c.isKnownProperty(target, prop.Name, isComparingJsxAttributes) {
			return true
		}
	}
	return false
}

/**
 * Check if a property with the given name is known anywhere in the given type. In an object type, a property
 * is considered known if
 * 1. the object type is empty and the check is for assignability, or
 * 2. if the object type has index signatures, or
 * 3. if the property is actually declared in the object type
 *    (this means that 'toString', for example, is not usually a known property).
 * 4. In a union or intersection type,
 *    a property is considered known if it is known in any constituent type.
 * @param targetType a type to search a given name in
 * @param name a property name to search
 * @param isComparingJsxAttributes a boolean flag indicating whether we are searching in JsxAttributesType
 */
func (c *Checker) isKnownProperty(targetType *Type, name string, isComparingJsxAttributes bool) bool {
	if targetType.flags&TypeFlagsObject != 0 {
		// For backwards compatibility a symbol-named property is satisfied by a string index signature. This
		// is incorrect and inconsistent with element access expressions, where it is an error, so eventually
		// we should remove this exception.
		if c.getPropertyOfObjectType(targetType, name) != nil ||
			c.getApplicableIndexInfoForName(targetType, name) != nil ||
			isLateBoundName(name) && c.getIndexInfoOfType(targetType, c.stringType) != nil ||
			isComparingJsxAttributes && isHyphenatedJsxName(name) {
			// For JSXAttributes, if the attribute has a hyphenated name, consider that the attribute to be known.
			return true
		}
	}
	if targetType.flags&TypeFlagsSubstitution != 0 {
		return c.isKnownProperty(targetType.AsSubstitutionType().baseType, name, isComparingJsxAttributes)
	}
	if targetType.flags&TypeFlagsUnionOrIntersection != 0 && isExcessPropertyCheckTarget(targetType) {
		for _, t := range targetType.Types() {
			if c.isKnownProperty(t, name, isComparingJsxAttributes) {
				return true
			}
		}
	}
	return false
}

func isHyphenatedJsxName(name string) bool {
	return strings.Contains(name, "-")
}

func isExcessPropertyCheckTarget(t *Type) bool {
	return t.flags&TypeFlagsObject != 0 && t.objectFlags&ObjectFlagsObjectLiteralPatternWithComputedProperties == 0 ||
		t.flags&TypeFlagsNonPrimitive != 0 ||
		t.flags&TypeFlagsSubstitution != 0 && isExcessPropertyCheckTarget(t.AsSubstitutionType().baseType) ||
		t.flags&TypeFlagsUnion != 0 && core.Some(t.Types(), isExcessPropertyCheckTarget) ||
		t.flags&TypeFlagsIntersection != 0 && core.Every(t.Types(), isExcessPropertyCheckTarget)
}

// Return true if the given type is deeply nested. We consider this to be the case when the given stack contains
// maxDepth or more occurrences of types with the same recursion identity as the given type. The recursion identity
// provides a shared identity for type instantiations that repeat in some (possibly infinite) pattern. For example,
// in `type Deep<T> = { next: Deep<Deep<T>> }`, repeatedly referencing the `next` property leads to an infinite
// sequence of ever deeper instantiations with the same recursion identity (in this case the symbol associated with
// the object type literal).
// A homomorphic mapped type is considered deeply nested if its target type is deeply nested, and an intersection is
// considered deeply nested if any constituent of the intersection is deeply nested.
// It is possible, though highly unlikely, for the deeply nested check to be true in a situation where a chain of
// instantiations is not infinitely expanding. Effectively, we will generate a false positive when two types are
// structurally equal to at least maxDepth levels, but unequal at some level beyond that.
func (c *Checker) isDeeplyNestedType(t *Type, stack []*Type, maxDepth int) bool {
	if len(stack) >= maxDepth {
		if t.objectFlags&ObjectFlagsInstantiatedMapped == ObjectFlagsInstantiatedMapped {
			t = c.getMappedTargetWithSymbol(t)
		}
		if t.flags&TypeFlagsIntersection != 0 {
			for _, t := range t.Types() {
				if c.isDeeplyNestedType(t, stack, maxDepth) {
					return true
				}
			}
		}
		identity := getRecursionIdentity(t)
		count := 0
		lastTypeId := TypeId(0)
		for _, t := range stack {
			if c.hasMatchingRecursionIdentity(t, identity) {
				// We only count occurrences with a higher type id than the previous occurrence, since higher
				// type ids are an indicator of newer instantiations caused by recursion.
				if t.id >= lastTypeId {
					count++
					if count >= maxDepth {
						return true
					}
				}
				lastTypeId = t.id
			}
		}
	}
	return false
}

// Unwrap nested homomorphic mapped types and return the deepest target type that has a symbol. This better
// preserves unique type identities for mapped types applied to explicitly written object literals. For example
// in `Mapped<{ x: Mapped<{ x: Mapped<{ x: string }>}>}>`, each of the mapped type applications will have a
// unique recursion identity (that of their target object type literal) and thus avoid appearing deeply nested.
func (c *Checker) getMappedTargetWithSymbol(t *Type) *Type {
	for {
		if t.objectFlags&ObjectFlagsInstantiatedMapped == ObjectFlagsInstantiatedMapped {
			target := c.getModifiersTypeFromMappedType(t)
			if target != nil && (target.symbol != nil || target.flags&TypeFlagsIntersection != 0 &&
				core.Some(target.Types(), func(t *Type) bool { return t.symbol != nil })) {
				t = target
				continue
			}
		}
		return t
	}
}

func (c *Checker) hasMatchingRecursionIdentity(t *Type, identity RecursionId) bool {
	if t.objectFlags&ObjectFlagsInstantiatedMapped == ObjectFlagsInstantiatedMapped {
		t = c.getMappedTargetWithSymbol(t)
	}
	if t.flags&TypeFlagsIntersection != 0 {
		for _, t := range t.Types() {
			if c.hasMatchingRecursionIdentity(t, identity) {
				return true
			}
		}
		return false
	}
	return getRecursionIdentity(t) == identity
}

// The recursion identity of a type is an object identity that is shared among multiple instantiations of the type.
// We track recursion identities in order to identify deeply nested and possibly infinite type instantiations with
// the same origin. For example, when type parameters are in scope in an object type such as { x: T }, all
// instantiations of that type have the same recursion identity. The default recursion identity is the object
// identity of the type, meaning that every type is unique. Generally, types with constituents that could circularly
// reference the type have a recursion identity that differs from the object identity.
func getRecursionIdentity(t *Type) RecursionId {
	// Object and array literals are known not to contain recursive references and don't need a recursion identity.
	if t.flags&TypeFlagsObject != 0 && !isObjectOrArrayLiteralType(t) {
		if t.objectFlags&ObjectFlagsReference != 0 && t.AsTypeReference().node != nil {
			// Deferred type references are tracked through their associated AST node. This gives us finer
			// granularity than using their associated target because each manifest type reference has a
			// unique AST node.
			return asRecursionId(t.AsTypeReference().node)
		}
		if t.symbol != nil && !(t.objectFlags&ObjectFlagsAnonymous != 0 && t.symbol.Flags&ast.SymbolFlagsClass != 0) {
			// We track object types that have a symbol by that symbol (representing the origin of the type), but
			// exclude the static side of a class since it shares its symbol with the instance side.
			return asRecursionId(t.symbol)
		}
		if isTupleType(t) {
			return asRecursionId(t.Target())
		}
	}
	if t.flags&TypeFlagsTypeParameter != 0 && t.symbol != nil {
		// We use the symbol of the type parameter such that all "fresh" instantiations of that type parameter
		// have the same recursion identity.
		return asRecursionId(t.symbol)
	}
	if t.flags&TypeFlagsIndexedAccess != 0 {
		// Identity is the leftmost object type in a chain of indexed accesses, eg, in A[P1][P2][P3] it is A.
		t = t.AsIndexedAccessType().objectType
		for t.flags&TypeFlagsIndexedAccess != 0 {
			t = t.AsIndexedAccessType().objectType
		}
		return asRecursionId(t)
	}
	if t.flags&TypeFlagsConditional != 0 {
		// The root object represents the origin of the conditional type
		return asRecursionId(t.AsConditionalType().root.node.AsNode())
	}
	return asRecursionId(t)
}

func (c *Checker) getBestMatchingType(source *Type, target *Type, isRelatedTo func(source *Type, target *Type) Ternary) *Type {
	if t := c.findMatchingDiscriminantType(source, target, isRelatedTo); t != nil {
		return t
	}
	if t := c.findMatchingTypeReferenceOrTypeAliasReference(source, target); t != nil {
		return t
	}
	if t := c.findBestTypeForObjectLiteral(source, target); t != nil {
		return t
	}
	if t := c.findBestTypeForInvokable(source, target, SignatureKindCall); t != nil {
		return t
	}
	if t := c.findBestTypeForInvokable(source, target, SignatureKindConstruct); t != nil {
		return t
	}
	return c.findMostOverlappyType(source, target)
}

func (c *Checker) findMatchingTypeReferenceOrTypeAliasReference(source *Type, unionTarget *Type) *Type {
	sourceObjectFlags := source.objectFlags
	if sourceObjectFlags&(ObjectFlagsReference|ObjectFlagsAnonymous) != 0 && unionTarget.flags&TypeFlagsUnion != 0 {
		for _, target := range unionTarget.Types() {
			if target.flags&TypeFlagsObject != 0 {
				overlapObjFlags := sourceObjectFlags & target.objectFlags
				if overlapObjFlags&ObjectFlagsReference != 0 && source.Target() == target.Target() {
					return target
				}
				if overlapObjFlags&ObjectFlagsAnonymous != 0 && source.alias != nil && target.alias != nil && source.alias.symbol == target.alias.symbol {
					return target
				}
			}
		}
	}
	return nil
}

func (c *Checker) findBestTypeForInvokable(source *Type, unionTarget *Type, kind SignatureKind) *Type {
	if len(c.getSignaturesOfType(source, kind)) != 0 {
		return core.Find(unionTarget.Types(), func(t *Type) bool { return len(c.getSignaturesOfType(t, kind)) != 0 })
	}
	return nil
}

func (c *Checker) findMostOverlappyType(source *Type, unionTarget *Type) *Type {
	var bestMatch *Type
	if source.flags&(TypeFlagsPrimitive|TypeFlagsInstantiablePrimitive) == 0 {
		matchingCount := 0
		for _, target := range unionTarget.Types() {
			if target.flags&(TypeFlagsPrimitive|TypeFlagsInstantiablePrimitive) == 0 {
				overlap := c.getIntersectionType([]*Type{c.getIndexType(source), c.getIndexType(target)})
				if overlap.flags&TypeFlagsIndex != 0 {
					// perfect overlap of keys
					return target
				} else if isUnitType(overlap) || overlap.flags&TypeFlagsUnion != 0 {
					// We only want to account for literal types otherwise.
					// If we have a union of index types, it seems likely that we
					// needed to elaborate between two generic mapped types anyway.
					length := 1
					if overlap.flags&TypeFlagsUnion != 0 {
						length = core.CountWhere(overlap.Types(), isUnitType)
					}
					if length >= matchingCount {
						bestMatch = target
						matchingCount = length
					}
				}
			}
		}
	}
	return bestMatch
}

func (c *Checker) findBestTypeForObjectLiteral(source *Type, unionTarget *Type) *Type {
	if source.objectFlags&ObjectFlagsObjectLiteral != 0 && someType(unionTarget, c.isArrayLikeType) {
		return core.Find(unionTarget.Types(), func(t *Type) bool { return !c.isArrayLikeType(t) })
	}
	return nil
}

func (c *Checker) shouldReportUnmatchedPropertyError(source *Type, target *Type) bool {
	typeCallSignatures := c.getSignaturesOfStructuredType(source, SignatureKindCall)
	typeConstructSignatures := c.getSignaturesOfStructuredType(source, SignatureKindConstruct)
	typeProperties := c.getPropertiesOfObjectType(source)
	if (len(typeCallSignatures) != 0 || len(typeConstructSignatures) != 0) && len(typeProperties) == 0 {
		if (len(c.getSignaturesOfType(target, SignatureKindCall)) != 0 && len(typeCallSignatures) != 0) ||
			len(c.getSignaturesOfType(target, SignatureKindConstruct)) != 0 && len(typeConstructSignatures) != 0 {
			// target has similar signature kinds to source, still focus on the unmatched property
			return true
		}
		return false
	}
	return true
}

func (c *Checker) getUnmatchedProperty(source *Type, target *Type, requireOptionalProperties bool, matchDiscriminantProperties bool) *ast.Symbol {
	return c.getUnmatchedPropertiesWorker(source, target, requireOptionalProperties, matchDiscriminantProperties, nil)
}

func (c *Checker) getUnmatchedProperties(source *Type, target *Type, requireOptionalProperties bool, matchDiscriminantProperties bool) []*ast.Symbol {
	var props []*ast.Symbol
	c.getUnmatchedPropertiesWorker(source, target, requireOptionalProperties, matchDiscriminantProperties, &props)
	return props
}

func (c *Checker) getUnmatchedPropertiesWorker(source *Type, target *Type, requireOptionalProperties bool, matchDiscriminantProperties bool, propsOut *[]*ast.Symbol) *ast.Symbol {
	properties := c.getPropertiesOfType(target)
	for _, targetProp := range properties {
		// TODO: remove this when we support static private identifier fields and find other solutions to get privateNamesAndStaticFields test to pass
		if isStaticPrivateIdentifierProperty(targetProp) {
			continue
		}
		if requireOptionalProperties || targetProp.Flags&ast.SymbolFlagsOptional == 0 && targetProp.CheckFlags&ast.CheckFlagsPartial == 0 {
			sourceProp := c.getPropertyOfType(source, targetProp.Name)
			if sourceProp == nil {
				if propsOut == nil {
					return targetProp
				}
				*propsOut = append(*propsOut, targetProp)
			} else if matchDiscriminantProperties {
				targetType := c.getTypeOfSymbol(targetProp)
				if targetType.flags&TypeFlagsUnit != 0 {
					sourceType := c.getTypeOfSymbol(sourceProp)
					if !(sourceType.flags&TypeFlagsAny != 0 || c.getRegularTypeOfLiteralType(sourceType) == c.getRegularTypeOfLiteralType(targetType)) {
						if propsOut == nil {
							return targetProp
						}
						*propsOut = append(*propsOut, targetProp)
					}
				}
			}
		}
	}
	return nil
}

func excludeProperties(properties []*ast.Symbol, excludedProperties collections.Set[string]) []*ast.Symbol {
	if excludedProperties.Len() == 0 || len(properties) == 0 {
		return properties
	}
	var reduced []*ast.Symbol
	var excluded bool
	for i, prop := range properties {
		if !excludedProperties.Has(prop.Name) {
			if excluded {
				reduced = append(reduced, prop)
			}
		} else if !excluded {
			reduced = slices.Clip(properties[:i])
			excluded = true
		}
	}
	if excluded {
		return reduced
	}
	return properties
}

type TypeDiscriminator struct {
	c           *Checker
	props       []*ast.Symbol
	isRelatedTo func(*Type, *Type) Ternary
}

func (d *TypeDiscriminator) len() int {
	return len(d.props)
}

func (d *TypeDiscriminator) name(index int) string {
	return d.props[index].Name
}

func (d *TypeDiscriminator) matches(index int, t *Type) bool {
	propType := d.c.getTypeOfSymbol(d.props[index])
	for _, s := range propType.Distributed() {
		if d.isRelatedTo(s, t) != TernaryFalse {
			return true
		}
	}
	return false
}

// Keep this up-to-date with the same logic within `getApparentTypeOfContextualType`, since they should behave similarly
func (c *Checker) findMatchingDiscriminantType(source *Type, target *Type, isRelatedTo func(source *Type, target *Type) Ternary) *Type {
	if target.flags&TypeFlagsUnion != 0 && source.flags&(TypeFlagsIntersection|TypeFlagsObject) != 0 {
		if match := c.getMatchingUnionConstituentForType(target, source); match != nil {
			return match
		}
		if discriminantProperties := c.findDiscriminantProperties(c.getPropertiesOfType(source), target); len(discriminantProperties) != 0 {
			discriminator := &TypeDiscriminator{c: c, props: discriminantProperties, isRelatedTo: isRelatedTo}
			if discriminated := c.discriminateTypeByDiscriminableItems(target, discriminator); discriminated != target {
				return discriminated
			}
		}
	}
	return nil
}

func (c *Checker) findDiscriminantProperties(sourceProperties []*ast.Symbol, target *Type) []*ast.Symbol {
	var result []*ast.Symbol
	for _, sourceProperty := range sourceProperties {
		if c.isDiscriminantProperty(target, sourceProperty.Name) {
			result = append(result, sourceProperty)
		}
	}
	return result
}

func (c *Checker) isDiscriminantProperty(t *Type, name string) bool {
	if t != nil && t.flags&TypeFlagsUnion != 0 {
		prop := c.getUnionOrIntersectionProperty(t, name, false /*skipObjectFunctionPropertyAugment*/)
		if prop != nil && prop.CheckFlags&ast.CheckFlagsSyntheticProperty != 0 {
			if prop.CheckFlags&ast.CheckFlagsIsDiscriminantComputed == 0 {
				prop.CheckFlags |= ast.CheckFlagsIsDiscriminantComputed
				if prop.CheckFlags&ast.CheckFlagsNonUniformAndLiteral == ast.CheckFlagsNonUniformAndLiteral && !c.isGenericType(c.getTypeOfSymbol(prop)) {
					prop.CheckFlags |= ast.CheckFlagsIsDiscriminant
				}
			}
			return prop.CheckFlags&ast.CheckFlagsIsDiscriminant != 0
		}
	}
	return false
}

func (c *Checker) getMatchingUnionConstituentForType(unionType *Type, t *Type) *Type {
	keyPropertyName := c.getKeyPropertyName(unionType)
	if keyPropertyName == "" {
		return nil
	}
	propType := c.getTypeOfPropertyOfType(t, keyPropertyName)
	if propType == nil {
		return nil
	}
	return c.getConstituentTypeForKeyType(unionType, propType)
}

// Return the name of a discriminant property for which it was possible and feasible to construct a map of
// constituent types keyed by the literal types of the property by that name in each constituent type. Return
// an empty string if no such discriminant property exists.
func (c *Checker) getKeyPropertyName(t *Type) string {
	u := t.AsUnionType()
	if u.keyPropertyName == "" {
		u.keyPropertyName, u.constituentMap = c.computeKeyPropertyNameAndMap(t)
	}
	if u.keyPropertyName == ast.InternalSymbolNameMissing {
		return ""
	}
	return u.keyPropertyName
}

// Given a union type for which getKeyPropertyName returned a non-empty string, return the constituent
// that corresponds to the given key type for that property name.
func (c *Checker) getConstituentTypeForKeyType(t *Type, keyType *Type) *Type {
	result := t.AsUnionType().constituentMap[c.getRegularTypeOfLiteralType(keyType)]
	if result != c.unknownType {
		return result
	}
	return nil
}

func (c *Checker) computeKeyPropertyNameAndMap(t *Type) (string, map[*Type]*Type) {
	types := t.Types()
	if len(types) < 10 || t.objectFlags&ObjectFlagsPrimitiveUnion != 0 || core.CountWhere(types, isObjectOrInstantiableNonPrimitive) < 10 {
		return ast.InternalSymbolNameMissing, nil
	}
	keyPropertyName := c.getKeyPropertyCandidateName(types)
	if keyPropertyName == "" {
		return ast.InternalSymbolNameMissing, nil
	}
	mapByKeyProperty := c.mapTypesByKeyProperty(types, keyPropertyName)
	if mapByKeyProperty == nil {
		return ast.InternalSymbolNameMissing, nil
	}
	return keyPropertyName, mapByKeyProperty
}

func isObjectOrInstantiableNonPrimitive(t *Type) bool {
	return t.flags&(TypeFlagsObject|TypeFlagsInstantiableNonPrimitive) != 0
}

func (c *Checker) getKeyPropertyCandidateName(types []*Type) string {
	for _, t := range types {
		if t.flags&(TypeFlagsObject|TypeFlagsInstantiableNonPrimitive) != 0 {
			for _, p := range c.getPropertiesOfType(t) {
				if isUnitType(c.getTypeOfSymbol(p)) {
					return p.Name
				}
			}
		}
	}
	return ""
}

// Given a set of constituent types and a property name, create and return a map keyed by the literal
// types of the property by that name in each constituent type. No map is returned if some key property
// has a non-literal type or if less than 10 or less than 50% of the constituents have a unique key.
// Entries with duplicate keys have unknownType as the value.
func (c *Checker) mapTypesByKeyProperty(types []*Type, keyPropertyName string) map[*Type]*Type {
	typesByKey := make(map[*Type]*Type)
	count := 0
	for _, t := range types {
		if t.flags&(TypeFlagsObject|TypeFlagsIntersection|TypeFlagsInstantiableNonPrimitive) != 0 {
			discriminant := c.getTypeOfPropertyOfType(t, keyPropertyName)
			if discriminant == nil || !isLiteralType(discriminant) {
				return nil
			}
			duplicate := false
			for _, d := range discriminant.Distributed() {
				key := c.getRegularTypeOfLiteralType(d)
				if existing := typesByKey[key]; existing == nil {
					typesByKey[key] = t
				} else if existing != c.unknownType {
					typesByKey[key] = c.unknownType
					duplicate = true
				}
			}
			if !duplicate {
				count++
			}
		}
	}
	if count >= 10 && count*2 >= len(types) {
		return typesByKey
	}
	return nil
}

type Discriminator interface {
	len() int                        // Number of discriminant properties
	name(index int) string           // Property name of index-th discriminator
	matches(index int, t *Type) bool // True if index-th discriminator matches the given type
}

func (c *Checker) discriminateTypeByDiscriminableItems(target *Type, discriminator Discriminator) *Type {
	types := target.Types()
	include := make([]Ternary, len(types))
	for i, t := range types {
		if t.flags&TypeFlagsPrimitive == 0 {
			include[i] = TernaryTrue
		}
	}
	for n := range discriminator.len() {
		// If the remaining target types include at least one with a matching discriminant, eliminate those that
		// have non-matching discriminants. This ensures that we ignore erroneous discriminators and gradually
		// refine the target set without eliminating every constituent (which would lead to `never`).
		matched := false
		for i := range types {
			if include[i] != TernaryFalse {
				targetType := c.getTypeOfPropertyOrIndexSignatureOfType(types[i], discriminator.name(n))
				if targetType != nil {
					if discriminator.matches(n, targetType) {
						matched = true
					} else {
						include[i] = TernaryMaybe
					}
				}
			}
		}
		// Turn each Ternary.Maybe into Ternary.False if there was a match. Otherwise, revert to Ternary.True.
		for i := range types {
			if include[i] == TernaryMaybe {
				if matched {
					include[i] = TernaryFalse
				} else {
					include[i] = TernaryTrue
				}
			}
		}
	}
	if slices.Contains(include, TernaryFalse) {
		var filteredTypes []*Type
		for i, t := range types {
			if include[i] == TernaryTrue {
				filteredTypes = append(filteredTypes, t)
			}
		}
		filtered := c.getUnionTypeEx(filteredTypes, UnionReductionNone, nil, nil)
		if filtered.flags&TypeFlagsNever == 0 {
			return filtered
		}
	}
	return target
}

func (c *Checker) filterPrimitivesIfContainsNonPrimitive(unionType *Type) *Type {
	if c.maybeTypeOfKind(unionType, TypeFlagsNonPrimitive) {
		result := c.filterType(unionType, isNonPrimitiveType)
		if result.flags&TypeFlagsNever == 0 {
			return result
		}
	}
	return unionType
}

func isNonPrimitiveType(t *Type) bool {
	return t.flags&TypeFlagsPrimitive == 0
}

func (c *Checker) getTypeNamesForErrorDisplay(left *Type, right *Type) (string, string) {
	var leftStr string
	if c.symbolValueDeclarationIsContextSensitive(left.symbol) {
		leftStr = c.typeToString(left, left.symbol.ValueDeclaration)
	} else {
		leftStr = c.TypeToString(left)
	}
	var rightStr string
	if c.symbolValueDeclarationIsContextSensitive(right.symbol) {
		rightStr = c.typeToString(right, right.symbol.ValueDeclaration)
	} else {
		rightStr = c.TypeToString(right)
	}
	if leftStr == rightStr {
		leftStr = c.getTypeNameForErrorDisplay(left)
		rightStr = c.getTypeNameForErrorDisplay(right)
	}
	return leftStr, rightStr
}

func (c *Checker) getTypeNameForErrorDisplay(t *Type) string {
	return c.typeToStringEx(t, nil /*enclosingDeclaration*/, TypeFormatFlagsUseFullyQualifiedType)
}

func (c *Checker) symbolValueDeclarationIsContextSensitive(symbol *ast.Symbol) bool {
	return symbol != nil && symbol.ValueDeclaration != nil && ast.IsExpression(symbol.ValueDeclaration) && !c.isContextSensitive(symbol.ValueDeclaration)
}

func (c *Checker) typeCouldHaveTopLevelSingletonTypes(t *Type) bool {
	// Okay, yes, 'boolean' is a union of 'true | false', but that's not useful
	// in error reporting scenarios. If you need to use this function but that detail matters,
	// feel free to add a flag.
	if t.flags&TypeFlagsBoolean != 0 {
		return false
	}
	if t.flags&TypeFlagsUnionOrIntersection != 0 {
		return core.Some(t.Types(), c.typeCouldHaveTopLevelSingletonTypes)
	}
	if t.flags&TypeFlagsInstantiable != 0 {
		constraint := c.getConstraintOfType(t)
		if constraint != nil && constraint != t {
			return c.typeCouldHaveTopLevelSingletonTypes(constraint)
		}
	}
	return isUnitType(t) || t.flags&TypeFlagsTemplateLiteral != 0 || t.flags&TypeFlagsStringMapping != 0
}

func (c *Checker) getVariances(t *Type) []VarianceFlags {
	// Arrays and tuples are known to be covariant, no need to spend time computing this.
	if t == c.globalArrayType || t == c.globalReadonlyArrayType || t.objectFlags&ObjectFlagsTuple != 0 {
		return c.arrayVariances
	}
	return c.getVariancesWorker(t.symbol, t.AsInterfaceType().TypeParameters())
}

func (c *Checker) getAliasVariances(symbol *ast.Symbol) []VarianceFlags {
	return c.getVariancesWorker(symbol, c.typeAliasLinks.Get(symbol).typeParameters)
}

// Return an array containing the variance of each type parameter. The variance is effectively
// a digest of the type comparisons that occur for each type argument when instantiations of the
// generic type are structurally compared. We infer the variance information by comparing
// instantiations of the generic type for type arguments with known relations. The function
// returns an empty slice when invoked recursively for the given generic type.
func (c *Checker) getVariancesWorker(symbol *ast.Symbol, typeParameters []*Type) []VarianceFlags {
	links := c.varianceLinks.Get(symbol)
	if links.variances == nil {
		oldVarianceComputation := c.inVarianceComputation
		saveResolutionStart := c.resolutionStart
		if !c.inVarianceComputation {
			c.inVarianceComputation = true
			c.resolutionStart = len(c.typeResolutions)
		}
		links.variances = []VarianceFlags{}
		variances := make([]VarianceFlags, len(typeParameters))
		for i, tp := range typeParameters {
			modifiers := c.getTypeParameterModifiers(tp)
			var variance VarianceFlags
			switch {
			case modifiers&ast.ModifierFlagsOut != 0:
				if modifiers&ast.ModifierFlagsIn != 0 {
					variance = VarianceFlagsInvariant
				} else {
					variance = VarianceFlagsCovariant
				}
			case modifiers&ast.ModifierFlagsIn != 0:
				variance = VarianceFlagsContravariant
			default:
				saveReliabilityFlags := c.reliabilityFlags
				c.reliabilityFlags = 0
				// We first compare instantiations where the type parameter is replaced with
				// marker types that have a known subtype relationship. From this we can infer
				// invariance, covariance, contravariance or bivariance.
				typeWithSuper := c.createMarkerType(symbol, tp, c.markerSuperType)
				typeWithSub := c.createMarkerType(symbol, tp, c.markerSubType)
				variance = (core.IfElse(c.isTypeAssignableTo(typeWithSub, typeWithSuper), VarianceFlagsCovariant, 0)) |
					(core.IfElse(c.isTypeAssignableTo(typeWithSuper, typeWithSub), VarianceFlagsContravariant, 0))
				// If the instantiations appear to be related bivariantly it may be because the
				// type parameter is independent (i.e. it isn't witnessed anywhere in the generic
				// type). To determine this we compare instantiations where the type parameter is
				// replaced with marker types that are known to be unrelated.
				if variance == VarianceFlagsBivariant && c.isTypeAssignableTo(c.createMarkerType(symbol, tp, c.markerOtherType), typeWithSuper) {
					variance = VarianceFlagsIndependent
				}
				if c.reliabilityFlags&RelationComparisonResultReportsUnmeasurable != 0 {
					variance |= VarianceFlagsUnmeasurable
				}
				if c.reliabilityFlags&RelationComparisonResultReportsUnreliable != 0 {
					variance |= VarianceFlagsUnreliable
				}
				c.reliabilityFlags = saveReliabilityFlags
			}
			variances[i] = variance
		}
		if !oldVarianceComputation {
			c.inVarianceComputation = false
			c.resolutionStart = saveResolutionStart
		}
		links.variances = variances
	}
	return links.variances
}

func (c *Checker) createMarkerType(symbol *ast.Symbol, source *Type, target *Type) *Type {
	mapper := newSimpleTypeMapper(source, target)
	t := c.getDeclaredTypeOfSymbol(symbol)
	if c.isErrorType(t) {
		return t
	}
	var result *Type
	if symbol.Flags&ast.SymbolFlagsTypeAlias != 0 {
		result = c.getTypeAliasInstantiation(symbol, c.instantiateTypes(c.typeAliasLinks.Get(symbol).typeParameters, mapper), nil)
	} else {
		result = c.createTypeReference(t, c.instantiateTypes(t.AsInterfaceType().TypeParameters(), mapper))
	}
	c.markerTypes.Add(result)
	return result
}

func (c *Checker) isMarkerType(t *Type) bool {
	return c.markerTypes.Has(t)
}

func (c *Checker) getTypeParameterModifiers(tp *Type) ast.ModifierFlags {
	var flags ast.ModifierFlags
	if tp.symbol != nil {
		for _, d := range tp.symbol.Declarations {
			flags |= d.ModifierFlags()
		}
	}
	return flags & (ast.ModifierFlagsIn | ast.ModifierFlagsOut | ast.ModifierFlagsConst)
}

// Return true if the given type reference has a 'void' type argument for a covariant type parameter.
// See comment at call in recursiveTypeRelatedTo for when this case matters.
func (c *Checker) hasCovariantVoidArgument(typeArguments []*Type, variances []VarianceFlags) bool {
	for i, v := range variances {
		if v&VarianceFlagsVarianceMask == VarianceFlagsCovariant && typeArguments[i].flags&TypeFlagsVoid != 0 {
			return true
		}
	}
	return false
}

func (c *Checker) isSignatureAssignableTo(source *Signature, target *Signature, ignoreReturnTypes bool) bool {
	return c.compareSignaturesRelated(source, target, core.IfElse(ignoreReturnTypes, SignatureCheckModeIgnoreReturnTypes, SignatureCheckModeNone), false /*reportErrors*/, nil /*errorReporter*/, c.compareTypesAssignable, nil /*reportUnreliableMarkers*/) != TernaryFalse
}

func (c *Checker) compareSignaturesRelated(source *Signature, target *Signature, checkMode SignatureCheckMode, reportErrors bool, errorReporter ErrorReporter, compareTypes TypeComparer, reportUnreliableMarkers *TypeMapper) Ternary {
	if source == target {
		return TernaryTrue
	}
	if !(checkMode&SignatureCheckModeStrictTopSignature != 0 && c.isTopSignature(source)) && c.isTopSignature(target) {
		return TernaryTrue
	}
	if checkMode&SignatureCheckModeStrictTopSignature != 0 && c.isTopSignature(source) && !c.isTopSignature(target) {
		return TernaryFalse
	}
	targetCount := c.getParameterCount(target)
	var sourceHasMoreParameters bool
	if !c.hasEffectiveRestParameter(target) {
		if checkMode&SignatureCheckModeStrictArity != 0 {
			sourceHasMoreParameters = c.hasEffectiveRestParameter(source) || c.getParameterCount(source) > targetCount
		} else {
			sourceHasMoreParameters = c.getMinArgumentCount(source) > targetCount
		}
	}
	if sourceHasMoreParameters {
		if reportErrors && (checkMode&SignatureCheckModeStrictArity == 0) {
			// the second condition should be redundant, because there is no error reporting when comparing signatures by strict arity
			// since it is only done for subtype reduction
			errorReporter(diagnostics.Target_signature_provides_too_few_arguments_Expected_0_or_more_but_got_1, c.getMinArgumentCount(source), targetCount)
		}
		return TernaryFalse
	}
	if len(source.typeParameters) != 0 && !core.Same(source.typeParameters, target.typeParameters) {
		target = c.getCanonicalSignature(target)
		source = c.instantiateSignatureInContextOf(source, target /*inferenceContext*/, nil, compareTypes)
	}
	sourceCount := c.getParameterCount(source)
	sourceRestType := c.getNonArrayRestType(source)
	targetRestType := c.getNonArrayRestType(target)
	if sourceRestType != nil || targetRestType != nil {
		c.instantiateType(core.IfElse(sourceRestType != nil, sourceRestType, targetRestType), reportUnreliableMarkers)
	}
	kind := ast.KindUnknown
	if target.declaration != nil {
		kind = target.declaration.Kind
	}
	strictVariance := checkMode&SignatureCheckModeCallback == 0 && c.strictFunctionTypes && kind != ast.KindMethodDeclaration && kind != ast.KindMethodSignature && kind != ast.KindConstructor
	result := TernaryTrue
	sourceThisType := c.getThisTypeOfSignature(source)
	if sourceThisType != nil && sourceThisType != c.voidType {
		targetThisType := c.getThisTypeOfSignature(target)
		if targetThisType != nil {
			// void sources are assignable to anything.
			var related Ternary
			if !strictVariance {
				related = compareTypes(sourceThisType, targetThisType, false /*reportErrors*/)
			}
			if related == TernaryFalse {
				related = compareTypes(targetThisType, sourceThisType, reportErrors)
			}
			if related == TernaryFalse {
				if reportErrors {
					errorReporter(diagnostics.The_this_types_of_each_signature_are_incompatible)
				}
				return TernaryFalse
			}
			result &= related
		}
	}
	var paramCount int
	if sourceRestType != nil || targetRestType != nil {
		paramCount = min(sourceCount, targetCount)
	} else {
		paramCount = max(sourceCount, targetCount)
	}
	var restIndex int
	if sourceRestType != nil || targetRestType != nil {
		restIndex = paramCount - 1
	} else {
		restIndex = -1
	}
	for i := range paramCount {
		var sourceType *Type
		if i == restIndex {
			sourceType = c.getRestOrAnyTypeAtPosition(source, i)
		} else {
			sourceType = c.tryGetTypeAtPosition(source, i)
		}
		var targetType *Type
		if i == restIndex {
			targetType = c.getRestOrAnyTypeAtPosition(target, i)
		} else {
			targetType = c.tryGetTypeAtPosition(target, i)
		}
		if sourceType != nil && targetType != nil && (sourceType != targetType || checkMode&SignatureCheckModeStrictArity != 0) {
			// In order to ensure that any generic type Foo<T> is at least co-variant with respect to T no matter
			// how Foo uses T, we need to relate parameters bi-variantly (given that parameters are input positions,
			// they naturally relate only contra-variantly). However, if the source and target parameters both have
			// function types with a single call signature, we know we are relating two callback parameters. In
			// that case it is sufficient to only relate the parameters of the signatures co-variantly because,
			// similar to return values, callback parameters are output positions. This means that a Promise<T>,
			// where T is used only in callback parameter positions, will be co-variant (as opposed to bi-variant)
			// with respect to T.
			var sourceSig *Signature
			var targetSig *Signature
			if checkMode&SignatureCheckModeCallback == 0 && !c.isInstantiatedGenericParameter(source, i) {
				sourceSig = c.getSingleCallSignature(c.GetNonNullableType(sourceType))
			}
			if checkMode&SignatureCheckModeCallback == 0 && !c.isInstantiatedGenericParameter(target, i) {
				targetSig = c.getSingleCallSignature(c.GetNonNullableType(targetType))
			}
			callbacks := sourceSig != nil && targetSig != nil && c.getTypePredicateOfSignature(sourceSig) == nil && c.getTypePredicateOfSignature(targetSig) == nil &&
				c.getTypeFacts(sourceType, TypeFactsIsUndefinedOrNull) == c.getTypeFacts(targetType, TypeFactsIsUndefinedOrNull)
			var related Ternary
			if callbacks {
				related = c.compareSignaturesRelated(targetSig, sourceSig, checkMode&SignatureCheckModeStrictArity|core.IfElse(strictVariance, SignatureCheckModeStrictCallback, SignatureCheckModeBivariantCallback), reportErrors, errorReporter, compareTypes, reportUnreliableMarkers)
			} else {
				if checkMode&SignatureCheckModeCallback == 0 && !strictVariance {
					related = compareTypes(sourceType, targetType, false /*reportErrors*/)
				}
				if related == TernaryFalse {
					related = compareTypes(targetType, sourceType, reportErrors)
				}
			}
			// With strict arity, (x: number | undefined) => void is a subtype of (x?: number | undefined) => void
			if related != TernaryFalse && checkMode&SignatureCheckModeStrictArity != 0 && i >= c.getMinArgumentCount(source) && i < c.getMinArgumentCount(target) && compareTypes(sourceType, targetType, false /*reportErrors*/) != TernaryFalse {
				related = TernaryFalse
			}
			if related == TernaryFalse {
				if reportErrors {
					errorReporter(diagnostics.Types_of_parameters_0_and_1_are_incompatible, c.getParameterNameAtPosition(source, i), c.getParameterNameAtPosition(target, i))
				}
				return TernaryFalse
			}
			result &= related
		}
	}
	if checkMode&SignatureCheckModeIgnoreReturnTypes == 0 {
		// If a signature resolution is already in-flight, skip issuing a circularity error
		// here and just use the `any` type directly
		targetReturnType := c.getNonCircularReturnTypeOfSignature(target)
		if targetReturnType == c.voidType || targetReturnType == c.anyType {
			return result
		}
		sourceReturnType := c.getNonCircularReturnTypeOfSignature(source)
		// The following block preserves behavior forbidding boolean returning functions from being assignable to type guard returning functions
		targetTypePredicate := c.getTypePredicateOfSignature(target)
		if targetTypePredicate != nil {
			sourceTypePredicate := c.getTypePredicateOfSignature(source)
			if sourceTypePredicate != nil {
				result &= c.compareTypePredicateRelatedTo(sourceTypePredicate, targetTypePredicate, reportErrors, errorReporter, compareTypes)
			} else if targetTypePredicate.kind == TypePredicateKindIdentifier || targetTypePredicate.kind == TypePredicateKindThis {
				if reportErrors {
					errorReporter(diagnostics.Signature_0_must_be_a_type_predicate, c.signatureToString(source))
				}
				return TernaryFalse
			}
		} else {
			// When relating callback signatures, we still need to relate return types bi-variantly as otherwise
			// the containing type wouldn't be co-variant. For example, interface Foo<T> { add(cb: () => T): void }
			// wouldn't be co-variant for T without this rule.
			var related Ternary
			if checkMode&SignatureCheckModeBivariantCallback != 0 {
				related = compareTypes(targetReturnType, sourceReturnType, false /*reportErrors*/)
			}
			if related == TernaryFalse {
				related = compareTypes(sourceReturnType, targetReturnType, reportErrors)
			}
			result &= related
			if result == TernaryFalse && reportErrors {
				// The errors reported here serve as markers that trigger error chain reduction in the (*Relater).reportError
				// method. The markers are elided in the final diagnostic chain and never actually reported.
				var message *diagnostics.Message
				if len(source.parameters) == 0 && len(target.parameters) == 0 {
					message = core.IfElse(source.flags&SignatureFlagsConstruct != 0,
						diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1,
						diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1)
				} else {
					message = core.IfElse(source.flags&SignatureFlagsConstruct != 0,
						diagnostics.Construct_signature_return_types_0_and_1_are_incompatible,
						diagnostics.Call_signature_return_types_0_and_1_are_incompatible)
				}
				errorReporter(message, c.TypeToString(sourceReturnType), c.TypeToString(targetReturnType))
			}
		}
	}
	return result
}

func (c *Checker) compareTypePredicateRelatedTo(source *TypePredicate, target *TypePredicate, reportErrors bool, errorReporter ErrorReporter, compareTypes TypeComparer) Ternary {
	if source.kind != target.kind {
		if reportErrors {
			errorReporter(diagnostics.A_this_based_type_guard_is_not_compatible_with_a_parameter_based_type_guard)
			errorReporter(diagnostics.Type_predicate_0_is_not_assignable_to_1, c.typePredicateToString(source), c.typePredicateToString(target))
		}
		return TernaryFalse
	}
	if source.kind == TypePredicateKindIdentifier || source.kind == TypePredicateKindAssertsIdentifier {
		if source.parameterIndex != target.parameterIndex {
			if reportErrors {
				errorReporter(diagnostics.Parameter_0_is_not_in_the_same_position_as_parameter_1, source.parameterName, target.parameterName)
				errorReporter(diagnostics.Type_predicate_0_is_not_assignable_to_1, c.typePredicateToString(source), c.typePredicateToString(target))
			}
			return TernaryFalse
		}
	}
	var related Ternary
	switch {
	case source.t == target.t:
		related = TernaryTrue
	case source.t != nil && target.t != nil:
		related = compareTypes(source.t, target.t, reportErrors)
	default:
		related = TernaryFalse
	}
	if related == TernaryFalse && reportErrors {
		errorReporter(diagnostics.Type_predicate_0_is_not_assignable_to_1, c.typePredicateToString(source), c.typePredicateToString(target))
	}
	return related
}

// Returns true if `s` is `(...args: A) => R` where `A` is `any`, `any[]`, `never`, or `never[]`, and `R` is `any` or `unknown`.
func (c *Checker) isTopSignature(s *Signature) bool {
	if len(s.typeParameters) == 0 && (s.thisParameter == nil || IsTypeAny(c.getTypeOfParameter(s.thisParameter))) && len(s.parameters) == 1 && signatureHasRestParameter(s) {
		paramType := c.getTypeOfParameter(s.parameters[0])
		var restType *Type
		if c.isArrayType(paramType) {
			restType = c.getTypeArguments(paramType)[0]
		} else {
			restType = paramType
		}
		return restType.flags&(TypeFlagsAny|TypeFlagsNever) != 0 && c.getReturnTypeOfSignature(s).flags&TypeFlagsAnyOrUnknown != 0
	}
	return false
}

// Return the number of parameters in a signature. The rest parameter, if present, counts as one
// parameter. For example, the parameter count of (x: number, y: number, ...z: string[]) is 3 and
// the parameter count of (x: number, ...args: [number, ...string[], boolean])) is also 3. In the
// latter example, the effective rest type is [...string[], boolean].
func (c *Checker) getParameterCount(signature *Signature) int {
	length := len(signature.parameters)
	if signatureHasRestParameter(signature) {
		restType := c.getTypeOfSymbol(signature.parameters[length-1])
		if isTupleType(restType) {
			return length + restType.TargetTupleType().fixedLength - core.IfElse(restType.TargetTupleType().combinedFlags&ElementFlagsVariable != 0, 0, 1)
		}
	}
	return length
}

func (c *Checker) getMinArgumentCount(signature *Signature) int {
	return c.getMinArgumentCountEx(signature, MinArgumentCountFlagsNone)
}

func (c *Checker) getMinArgumentCountEx(signature *Signature, flags MinArgumentCountFlags) int {
	strongArityForUntypedJS := flags & MinArgumentCountFlagsStrongArityForUntypedJS
	voidIsNonOptional := flags & MinArgumentCountFlagsVoidIsNonOptional
	if voidIsNonOptional != 0 || signature.resolvedMinArgumentCount == -1 {
		minArgumentCount := -1
		if signatureHasRestParameter(signature) {
			restType := c.getTypeOfSymbol(signature.parameters[len(signature.parameters)-1])
			if isTupleType(restType) {
				firstOptionalIndex := core.FindIndex(restType.TargetTupleType().elementInfos, func(info TupleElementInfo) bool {
					return info.flags&ElementFlagsRequired == 0
				})
				requiredCount := firstOptionalIndex
				if firstOptionalIndex < 0 {
					requiredCount = restType.TargetTupleType().fixedLength
				}
				if requiredCount > 0 {
					minArgumentCount = len(signature.parameters) - 1 + requiredCount
				}
			}
		}
		if minArgumentCount == -1 {
			if strongArityForUntypedJS == 0 && signature.flags&SignatureFlagsIsUntypedSignatureInJSFile != 0 {
				return 0
			}
			minArgumentCount = int(signature.minArgumentCount)
		}
		if voidIsNonOptional != 0 {
			return minArgumentCount
		}
		for i := minArgumentCount - 1; i >= 0; i-- {
			t := c.getTypeAtPosition(signature, i)
			if !someType(t, func(t *Type) bool { return t.flags&TypeFlagsVoid != 0 }) {
				break
			}
			minArgumentCount = i
		}
		signature.resolvedMinArgumentCount = int32(minArgumentCount)
	}
	return int(signature.resolvedMinArgumentCount)
}

func (c *Checker) hasEffectiveRestParameter(signature *Signature) bool {
	if signatureHasRestParameter(signature) {
		restType := c.getTypeOfSymbol(signature.parameters[len(signature.parameters)-1])
		return !isTupleType(restType) || restType.TargetTupleType().combinedFlags&ElementFlagsVariable != 0
	}
	return false
}

func (c *Checker) getTypeAtPosition(signature *Signature, pos int) *Type {
	t := c.tryGetTypeAtPosition(signature, pos)
	if t != nil {
		return t
	}
	return c.anyType
}

func (c *Checker) tryGetTypeAtPosition(signature *Signature, pos int) *Type {
	paramCount := len(signature.parameters) - core.IfElse(signatureHasRestParameter(signature), 1, 0)
	if pos < paramCount {
		return c.getTypeOfParameter(signature.parameters[pos])
	}
	if signatureHasRestParameter(signature) {
		// We want to return the value undefined for an out of bounds parameter position,
		// so we need to check bounds here before calling getIndexedAccessType (which
		// otherwise would return the type 'undefined').
		restType := c.getTypeOfSymbol(signature.parameters[paramCount])
		index := pos - paramCount
		if !isTupleType(restType) || restType.TargetTupleType().combinedFlags&ElementFlagsVariable != 0 || index < restType.TargetTupleType().fixedLength {
			return c.getIndexedAccessType(restType, c.getNumberLiteralType(jsnum.Number(index)))
		}
	}
	return nil
}

// Return the rest type at the given position, transforming `any[]` into just `any`. We do this because
// in signatures we want `any[]` in a rest position to be compatible with anything, but `any[]` isn't
// assignable to tuple types with required elements.
func (c *Checker) getRestOrAnyTypeAtPosition(source *Signature, pos int) *Type {
	restType := c.getRestTypeAtPosition(source, pos, false)
	if restType != nil {
		if elementType := c.getElementTypeOfArrayType(restType); elementType != nil && IsTypeAny(elementType) {
			return c.anyType
		}
	}
	return restType
}

func (c *Checker) getRestTypeAtPosition(source *Signature, pos int, readonly bool) *Type {
	parameterCount := c.getParameterCount(source)
	minArgumentCount := c.getMinArgumentCount(source)
	restType := c.getEffectiveRestType(source)
	if restType != nil && pos >= parameterCount-1 {
		if pos == parameterCount-1 {
			return restType
		} else {
			return c.createArrayType(c.getIndexedAccessType(restType, c.numberType))
		}
	}
	types := make([]*Type, parameterCount-pos)
	infos := make([]TupleElementInfo, parameterCount-pos)
	for i := range types {
		var flags ElementFlags
		if restType == nil || i < len(types)-1 {
			types[i] = c.getTypeAtPosition(source, i+pos)
			flags = core.IfElse(i+pos < minArgumentCount, ElementFlagsRequired, ElementFlagsOptional)
		} else {
			types[i] = restType
			flags = ElementFlagsVariadic
		}
		infos[i] = TupleElementInfo{flags: flags, labeledDeclaration: c.getNameableDeclarationAtPosition(source, i+pos)}
	}
	return c.createTupleTypeEx(types, infos, readonly)
}

func (c *Checker) getNameableDeclarationAtPosition(signature *Signature, pos int) *ast.Node {
	paramCount := len(signature.parameters) - core.IfElse(signatureHasRestParameter(signature), 1, 0)
	if pos < paramCount {
		decl := signature.parameters[pos].ValueDeclaration
		if decl != nil && c.isValidDeclarationForTupleLabel(decl) {
			return decl
		}
		return nil
	}
	if signatureHasRestParameter(signature) {
		restParameter := signature.parameters[paramCount]
		restType := c.getTypeOfSymbol(restParameter)
		if isTupleType(restType) {
			elementInfos := restType.TargetTupleType().elementInfos
			index := pos - paramCount
			if index < len(elementInfos) {
				return elementInfos[index].labeledDeclaration
			}
			return nil
		}
		if restParameter.ValueDeclaration != nil && c.isValidDeclarationForTupleLabel(restParameter.ValueDeclaration) {
			return restParameter.ValueDeclaration
		}
	}
	return nil
}

func (c *Checker) isValidDeclarationForTupleLabel(d *ast.Node) bool {
	return ast.IsNamedTupleMember(d) || ast.IsParameter(d) && d.Name() != nil && ast.IsIdentifier(d.Name())
}

func (c *Checker) getNonArrayRestType(signature *Signature) *Type {
	restType := c.getEffectiveRestType(signature)
	if restType != nil && !c.isArrayType(restType) && !IsTypeAny(restType) {
		return restType
	}
	return nil
}

func (c *Checker) getEffectiveRestType(signature *Signature) *Type {
	if signatureHasRestParameter(signature) {
		restType := c.getTypeOfSymbol(signature.parameters[len(signature.parameters)-1])
		if !isTupleType(restType) {
			if IsTypeAny(restType) {
				return c.anyArrayType
			}
			return restType
		}
		if restType.TargetTupleType().combinedFlags&ElementFlagsVariable != 0 {
			return c.sliceTupleType(restType, restType.TargetTupleType().fixedLength, 0)
		}
	}
	return nil
}

func (c *Checker) sliceTupleType(t *Type, index int, endSkipCount int) *Type {
	target := t.TargetTupleType()
	endIndex := c.getTypeReferenceArity(t) - max(endSkipCount, 0)
	if index > target.fixedLength {
		if restArrayType := c.getRestArrayTypeOfTupleType(t); restArrayType != nil {
			return restArrayType
		}
		return c.createTupleType(nil)
	}
	return c.createTupleTypeEx(c.getTypeArguments(t)[index:endIndex], target.elementInfos[index:endIndex], false /*readonly*/)
}

func (c *Checker) getKnownKeysOfTupleType(t *Type) *Type {
	fixedLength := t.TargetTupleType().fixedLength
	keys := make([]*Type, fixedLength+1)
	for i := range fixedLength {
		keys[i] = c.getStringLiteralType(strconv.Itoa(i))
	}
	keys[fixedLength] = c.getIndexType(core.IfElse(t.TargetTupleType().readonly, c.globalReadonlyArrayType, c.globalArrayType))
	return c.getUnionType(keys)
}

func (c *Checker) getRestArrayTypeOfTupleType(t *Type) *Type {
	if restType := c.getRestTypeOfTupleType(t); restType != nil {
		return c.createArrayType(restType)
	}
	return nil
}

func (c *Checker) getThisTypeOfSignature(signature *Signature) *Type {
	if signature.thisParameter != nil {
		return c.getTypeOfSymbol(signature.thisParameter)
	}
	return nil
}

func (c *Checker) isInstantiatedGenericParameter(signature *Signature, pos int) bool {
	if signature.target == nil {
		return false
	}
	t := c.tryGetTypeAtPosition(signature.target, pos)
	return t != nil && c.isGenericType(t)
}

func (c *Checker) getParameterNameAtPosition(signature *Signature, pos int) string {
	paramCount := len(signature.parameters) - core.IfElse(signatureHasRestParameter(signature), 1, 0)
	if pos < paramCount {
		return signature.parameters[pos].Name
	}
	restParameter := signature.parameters[paramCount]
	restType := c.getTypeOfSymbol(restParameter)
	if isTupleType(restType) {
		index := pos - paramCount
		c.getTupleElementLabel(restType.TargetTupleType().elementInfos[index], restParameter, index)
	}
	return restParameter.Name
}

func (c *Checker) getTupleElementLabel(elementInfo TupleElementInfo, restSymbol *ast.Symbol, index int) string {
	if elementInfo.labeledDeclaration != nil {
		return elementInfo.labeledDeclaration.Name().Text()
	}
	if restSymbol != nil && restSymbol.ValueDeclaration != nil && ast.IsParameter(restSymbol.ValueDeclaration) {
		return c.getTupleElementLabelFromBindingElement(restSymbol.ValueDeclaration, index, elementInfo.flags)
	}
	var rootName string
	if restSymbol != nil {
		rootName = restSymbol.Name
	} else {
		rootName = "arg"
	}
	return rootName + "_" + strconv.Itoa(index)
}

func (c *Checker) getTupleElementLabelFromBindingElement(node *ast.Node, index int, elementFlags ElementFlags) string {
	if node.Name() != nil {
		switch node.Name().Kind {
		case ast.KindIdentifier:
			name := node.Name().Text()
			if hasDotDotDotToken(node) {
				// given
				//   (...[x, y, ...z]: [number, number, ...number[]]) => ...
				// this produces
				//   (x: number, y: number, ...z: number[]) => ...
				// which preserves rest elements of 'z'

				// given
				//   (...[x, y, ...z]: [number, number, ...[...number[], number]]) => ...
				// this produces
				//   (x: number, y: number, ...z: number[], z_1: number) => ...
				// which preserves rest elements of z but gives distinct numbers to fixed elements of 'z'
				if elementFlags&ElementFlagsVariable != 0 {
					return name
				}
				return name + "_" + strconv.Itoa(index)
			}
			// given
			//   (...[x]: [number]) => ...
			// this produces
			//   (x: number) => ...
			// which preserves fixed elements of 'x'

			// given
			//   (...[x]: ...number[]) => ...
			// this produces
			//   (x_0: number) => ...
			// which which numbers fixed elements of 'x' whose tuple element type is variable
			if elementFlags&ElementFlagsFixed != 0 {
				return name
			}
			return name + "_n"
		case ast.KindArrayBindingPattern:
			if hasDotDotDotToken(node) {
				elements := node.Name().AsBindingPattern().Elements.Nodes
				lastElement := core.LastOrNil(elements)
				lastElementIsBindingElementRest := lastElement != nil && ast.IsBindingElement(lastElement) && hasDotDotDotToken(lastElement)
				elementCount := len(elements) - core.IfElse(lastElementIsBindingElementRest, 1, 0)
				if index < elementCount {
					element := elements[index]
					if ast.IsBindingElement(element) {
						return c.getTupleElementLabelFromBindingElement(element, index, elementFlags)
					}
				} else if lastElementIsBindingElementRest {
					return c.getTupleElementLabelFromBindingElement(lastElement, index-elementCount, elementFlags)
				}
			}
		}
	}
	return "arg_" + strconv.Itoa(index)
}

func (c *Checker) getTypePredicateOfSignature(sig *Signature) *TypePredicate {
	if sig.resolvedTypePredicate == nil {
		switch {
		case sig.target != nil:
			targetTypePredicate := c.getTypePredicateOfSignature(sig.target)
			if targetTypePredicate != nil {
				sig.resolvedTypePredicate = c.instantiateTypePredicate(targetTypePredicate, sig.mapper)
			}
		case sig.composite != nil:
			sig.resolvedTypePredicate = c.getUnionOrIntersectionTypePredicate(sig.composite.signatures, sig.composite.isUnion)
		default:
			if sig.declaration != nil {
				typeNode := sig.declaration.Type()
				var jsdocTypePredicate *TypePredicate
				if typeNode == nil {
					if jsdocSignature := c.getSignatureOfFullSignatureType(sig.declaration); jsdocSignature != nil {
						jsdocTypePredicate = c.getTypePredicateOfSignature(jsdocSignature)
					}
				}
				switch {
				case typeNode != nil:
					if ast.IsTypePredicateNode(typeNode) {
						sig.resolvedTypePredicate = c.createTypePredicateFromTypePredicateNode(typeNode, sig)
					}
				case jsdocTypePredicate != nil:
					sig.resolvedTypePredicate = jsdocTypePredicate
				case ast.IsFunctionLikeDeclaration(sig.declaration) && (sig.resolvedReturnType == nil || sig.resolvedReturnType.flags&TypeFlagsBoolean != 0) && c.getParameterCount(sig) > 0:
					sig.resolvedTypePredicate = c.noTypePredicate // avoid infinite loop
					sig.resolvedTypePredicate = c.getTypePredicateFromBody(sig.declaration)
				}
			}
		}
		if sig.resolvedTypePredicate == nil {
			sig.resolvedTypePredicate = c.noTypePredicate
		}
	}
	if sig.resolvedTypePredicate == c.noTypePredicate {
		return nil
	}
	return sig.resolvedTypePredicate
}

func (c *Checker) getUnionOrIntersectionTypePredicate(signatures []*Signature, isUnion bool) *TypePredicate {
	var last *TypePredicate
	var types []*Type
	for _, sig := range signatures {
		pred := c.getTypePredicateOfSignature(sig)
		if pred != nil {
			// Constituent type predicates must all have matching kinds. We don't create composite type predicates for assertions.
			if pred.kind != TypePredicateKindThis && pred.kind != TypePredicateKindIdentifier || last != nil && !c.typePredicateKindsMatch(last, pred) {
				return nil
			}
			last = pred
			types = append(types, pred.t)
		} else {
			// In composite union signatures we permit and ignore signatures with a return type `false`.
			var returnType *Type
			if isUnion {
				returnType = c.getReturnTypeOfSignature(sig)
			}
			if returnType != c.falseType && returnType != c.regularFalseType {
				return nil
			}
		}
	}
	if last == nil {
		return nil
	}
	compositeType := c.getUnionOrIntersectionType(types, isUnion, UnionReductionLiteral)
	return c.newTypePredicate(last.kind, last.parameterName, last.parameterIndex, compositeType)
}

func (c *Checker) typePredicateKindsMatch(a *TypePredicate, b *TypePredicate) bool {
	return a.kind == b.kind && a.parameterIndex == b.parameterIndex
}

func (c *Checker) createTypePredicateFromTypePredicateNode(node *ast.Node, signature *Signature) *TypePredicate {
	predicateNode := node.AsTypePredicateNode()
	var t *Type
	if predicateNode.Type != nil {
		t = c.getTypeFromTypeNode(predicateNode.Type)
	}
	if ast.IsThisTypeNode(predicateNode.ParameterName) {
		kind := core.IfElse(predicateNode.AssertsModifier != nil, TypePredicateKindAssertsThis, TypePredicateKindThis)
		return c.newTypePredicate(kind, "" /*parameterName*/, 0 /*parameterIndex*/, t)
	}
	kind := core.IfElse(predicateNode.AssertsModifier != nil, TypePredicateKindAssertsIdentifier, TypePredicateKindIdentifier)
	name := predicateNode.ParameterName.Text()
	index := core.FindIndex(signature.parameters, func(p *ast.Symbol) bool { return p.Name == name })
	return c.newTypePredicate(kind, name, int32(index), t)
}

func (c *Checker) instantiateTypePredicate(predicate *TypePredicate, mapper *TypeMapper) *TypePredicate {
	t := c.instantiateType(predicate.t, mapper)
	if t == predicate.t {
		return predicate
	}
	return c.newTypePredicate(predicate.kind, predicate.parameterName, predicate.parameterIndex, t)
}

func (c *Checker) newTypePredicate(kind TypePredicateKind, parameterName string, parameterIndex int32, t *Type) *TypePredicate {
	return &TypePredicate{kind: kind, parameterIndex: parameterIndex, parameterName: parameterName, t: t}
}

func (c *Checker) isResolvingReturnTypeOfSignature(signature *Signature) bool {
	if signature.composite != nil && core.Some(signature.composite.signatures, c.isResolvingReturnTypeOfSignature) {
		return true
	}
	return signature.resolvedReturnType == nil && c.findResolutionCycleStartIndex(signature, TypeSystemPropertyNameResolvedReturnType) >= 0
}

func (c *Checker) findMatchingSignatures(signatureLists [][]*Signature, signature *Signature, listIndex int) []*Signature {
	if len(signature.typeParameters) != 0 {
		// We require an exact match for generic signatures, so we only return signatures from the first
		// signature list and only if they have exact matches in the other signature lists.
		if listIndex > 0 {
			return nil
		}
		for i := 1; i < len(signatureLists); i++ {
			if c.findMatchingSignature(signatureLists[i], signature, false /*partialMatch*/, false /*ignoreThisTypes*/, false /*ignoreReturnTypes*/) == nil {
				return nil
			}
		}
		return []*Signature{signature}
	}
	var result []*Signature
	for i := range signatureLists {
		// Allow matching non-generic signatures to have excess parameters (as a fallback if exact parameter match is not found) and different return types.
		// Prefer matching this types if possible.
		var match *Signature
		if i == listIndex {
			match = signature
		} else {
			match = c.findMatchingSignature(signatureLists[i], signature, false /*partialMatch*/, false /*ignoreThisTypes*/, true /*ignoreReturnTypes*/)
			if match == nil {
				match = c.findMatchingSignature(signatureLists[i], signature, true /*partialMatch*/, false /*ignoreThisTypes*/, true /*ignoreReturnTypes*/)
			}
		}
		if match == nil {
			return nil
		}
		result = core.AppendIfUnique(result, match)
	}
	return result
}

func (c *Checker) findMatchingSignature(signatureList []*Signature, signature *Signature, partialMatch bool, ignoreThisTypes bool, ignoreReturnTypes bool) *Signature {
	compareTypes := core.IfElse(partialMatch, c.compareTypesSubtypeOf, c.compareTypesIdentical)
	for _, s := range signatureList {
		if c.compareSignaturesIdentical(s, signature, partialMatch, ignoreThisTypes, ignoreReturnTypes, compareTypes) != 0 {
			return s
		}
	}
	return nil
}

/**
 * See signatureRelatedTo, compareSignaturesIdentical
 */
func (c *Checker) compareSignaturesIdentical(source *Signature, target *Signature, partialMatch bool, ignoreThisTypes bool, ignoreReturnTypes bool, compareTypes func(s *Type, t *Type) Ternary) Ternary {
	if source == target {
		return TernaryTrue
	}
	if !c.isMatchingSignature(source, target, partialMatch) {
		return TernaryFalse
	}
	// Check that the two signatures have the same number of type parameters.
	if len(source.typeParameters) != len(target.typeParameters) {
		return TernaryFalse
	}
	// Check that type parameter constraints and defaults match. If they do, instantiate the source
	// signature with the type parameters of the target signature and continue the comparison.
	if len(target.typeParameters) != 0 {
		mapper := newTypeMapper(source.typeParameters, target.typeParameters)
		for i := range len(target.typeParameters) {
			s := source.typeParameters[i]
			t := target.typeParameters[i]
			if !(s == t || compareTypes(c.instantiateType(c.getConstraintOrUnknownFromTypeParameter(s), mapper), c.getConstraintOrUnknownFromTypeParameter(t)) != TernaryFalse &&
				compareTypes(c.instantiateType(c.getDefaultOrUnknownFromTypeParameter(s), mapper), c.getDefaultOrUnknownFromTypeParameter(t)) != TernaryFalse) {
				return TernaryFalse
			}
		}
		source = c.instantiateSignatureEx(source, mapper, true /*eraseTypeParameters*/)
	}
	result := TernaryTrue
	if !ignoreThisTypes {
		sourceThisType := c.getThisTypeOfSignature(source)
		if sourceThisType != nil {
			targetThisType := c.getThisTypeOfSignature(target)
			if targetThisType != nil {
				related := compareTypes(sourceThisType, targetThisType)
				if related == TernaryFalse {
					return TernaryFalse
				}
				result &= related
			}
		}
	}
	for i := range c.getParameterCount(target) {
		s := c.getTypeAtPosition(source, i)
		t := c.getTypeAtPosition(target, i)
		related := compareTypes(t, s)
		if related == TernaryFalse {
			return TernaryFalse
		}
		result &= related
	}
	if !ignoreReturnTypes {
		sourceTypePredicate := c.getTypePredicateOfSignature(source)
		targetTypePredicate := c.getTypePredicateOfSignature(target)
		if sourceTypePredicate != nil || targetTypePredicate != nil {
			result &= c.compareTypePredicatesIdentical(sourceTypePredicate, targetTypePredicate, compareTypes)
		} else {
			result &= compareTypes(c.getReturnTypeOfSignature(source), c.getReturnTypeOfSignature(target))
		}
	}
	return result
}

func (c *Checker) isMatchingSignature(source *Signature, target *Signature, partialMatch bool) bool {
	sourceParameterCount := c.getParameterCount(source)
	targetParameterCount := c.getParameterCount(target)
	sourceMinArgumentCount := c.getMinArgumentCount(source)
	targetMinArgumentCount := c.getMinArgumentCount(target)
	sourceHasRestParameter := c.hasEffectiveRestParameter(source)
	targetHasRestParameter := c.hasEffectiveRestParameter(target)
	// A source signature matches a target signature if the two signatures have the same number of required,
	// optional, and rest parameters.
	if sourceParameterCount == targetParameterCount && sourceMinArgumentCount == targetMinArgumentCount && sourceHasRestParameter == targetHasRestParameter {
		return true
	}
	// A source signature partially matches a target signature if the target signature has no fewer required
	// parameters
	if partialMatch && sourceMinArgumentCount <= targetMinArgumentCount {
		return true
	}
	return false
}

func (c *Checker) compareTypeParametersIdentical(sourceParams []*Type, targetParams []*Type) bool {
	if len(sourceParams) != len(targetParams) {
		return false
	}
	mapper := newTypeMapper(targetParams, sourceParams)
	for i := range sourceParams {
		source := sourceParams[i]
		target := targetParams[i]
		if source == target {
			continue
		}
		// We instantiate the target type parameter constraints into the source types so we can recognize `<T, U extends T>` as the same as `<A, B extends A>`
		if !c.isTypeIdenticalTo(core.OrElse(c.getConstraintFromTypeParameter(source), c.unknownType), c.instantiateType(core.OrElse(c.getConstraintFromTypeParameter(target), c.unknownType), mapper)) {
			return false
		}
		// We don't compare defaults - we just use the type parameter defaults from the first signature that seems to match.
		// It might make sense to combine these defaults in the future, but doing so intelligently requires knowing
		// if the parameter is used covariantly or contravariantly (so we intersect if it's used like a parameter or union if used like a return type)
		// and, since it's just an inference _default_, just picking one arbitrarily works OK.
	}
	return true
}

func (c *Checker) compareTypePredicatesIdentical(source *TypePredicate, target *TypePredicate, compareTypes func(s *Type, t *Type) Ternary) Ternary {
	switch {
	case source == nil || target == nil || !c.typePredicateKindsMatch(source, target):
		return TernaryFalse
	case source.t == target.t:
		return TernaryTrue
	case source.t != nil && target.t != nil:
		return compareTypes(source.t, target.t)
	}
	return TernaryFalse
}

func (c *Checker) getEffectiveConstraintOfIntersection(types []*Type, targetIsUnion bool) *Type {
	var constraints []*Type
	hasDisjointDomainType := false
	for _, t := range types {
		if t.flags&TypeFlagsInstantiable != 0 {
			// We keep following constraints as long as we have an instantiable type that is known
			// not to be circular or infinite (hence we stop on index access types).
			constraint := c.getConstraintOfType(t)
			for constraint != nil && constraint.flags&(TypeFlagsTypeParameter|TypeFlagsIndex|TypeFlagsConditional) != 0 {
				constraint = c.getConstraintOfType(constraint)
			}
			if constraint != nil {
				constraints = append(constraints, constraint)
				if targetIsUnion {
					constraints = append(constraints, t)
				}
			}
		} else if t.flags&TypeFlagsDisjointDomains != 0 || c.IsEmptyAnonymousObjectType(t) {
			hasDisjointDomainType = true
		}
	}
	// If the target is a union type or if we are intersecting with types belonging to one of the
	// disjoint domains, we may end up producing a constraint that hasn't been examined before.
	if constraints != nil && (targetIsUnion || hasDisjointDomainType) {
		if hasDisjointDomainType {
			// We add any types belong to one of the disjoint domains because they might cause the final
			// intersection operation to reduce the union constraints.
			for _, t := range types {
				if t.flags&TypeFlagsDisjointDomains != 0 || c.IsEmptyAnonymousObjectType(t) {
					constraints = append(constraints, t)
				}
			}
		}
		// The source types were normalized; ensure the result is normalized too.
		return c.getNormalizedType(c.getIntersectionTypeEx(constraints, IntersectionFlagsNoConstraintReduction, nil), false /*writing*/)
	}
	return nil
}

func (c *Checker) templateLiteralTypesDefinitelyUnrelated(source *TemplateLiteralType, target *TemplateLiteralType) bool {
	// Two template literal types with differences in their starting or ending text spans are definitely unrelated.
	sourceStart := source.texts[0]
	targetStart := target.texts[0]
	sourceEnd := source.texts[len(source.texts)-1]
	targetEnd := target.texts[len(target.texts)-1]
	startLen := min(len(sourceStart), len(targetStart))
	endLen := min(len(sourceEnd), len(targetEnd))
	return sourceStart[:startLen] != targetStart[:startLen] || sourceEnd[len(sourceEnd)-endLen:] != targetEnd[len(targetEnd)-endLen:]
}

func (c *Checker) isTypeMatchedByTemplateLiteralType(source *Type, target *TemplateLiteralType) bool {
	inferences := c.inferTypesFromTemplateLiteralType(source, target)
	if inferences != nil {
		for i, inference := range inferences {
			if !c.isValidTypeForTemplateLiteralPlaceholder(inference, target.types[i]) {
				return false
			}
		}
		return true
	}
	return false
}

func (c *Checker) inferTypesFromTemplateLiteralType(source *Type, target *TemplateLiteralType) []*Type {
	switch {
	case source.flags&TypeFlagsStringLiteral != 0:
		return c.inferFromLiteralPartsToTemplateLiteral([]string{getStringLiteralValue(source)}, nil, target)
	case source.flags&TypeFlagsTemplateLiteral != 0:
		if slices.Equal(source.AsTemplateLiteralType().texts, target.texts) {
			return core.MapIndex(source.AsTemplateLiteralType().types, func(s *Type, i int) *Type {
				if c.isTypeAssignableTo(c.getBaseConstraintOrType(s), c.getBaseConstraintOrType(target.types[i])) {
					return s
				}
				return c.getStringLikeTypeForType(s)
			})
		}
		return c.inferFromLiteralPartsToTemplateLiteral(source.AsTemplateLiteralType().texts, source.AsTemplateLiteralType().types, target)
	default:
		return nil
	}
}

// This function infers from the text parts and type parts of a source literal to a target template literal. The number
// of text parts is always one more than the number of type parts, and a source string literal is treated as a source
// with one text part and zero type parts. The function returns an array of inferred string or template literal types
// corresponding to the placeholders in the target template literal, or undefined if the source doesn't match the target.
//
// We first check that the starting source text part matches the starting target text part, and that the ending source
// text part ends matches the ending target text part. We then iterate through the remaining target text parts, finding
// a match for each in the source and inferring string or template literal types created from the segments of the source
// that occur between the matches. During this iteration, seg holds the index of the current text part in the sourceTexts
// array and pos holds the current character position in the current text part.
//
// Consider inference from type `<<${string}>.<${number}-${number}>>` to type `<${string}.${string}>`, i.e.
//
//	sourceTexts = ['<<', '>.<', '-', '>>']
//	sourceTypes = [string, number, number]
//	target.texts = ['<', '.', '>']
//
// We first match '<' in the target to the start of '<<' in the source and '>' in the target to the end of '>>' in
// the source. The first match for the '.' in target occurs at character 1 in the source text part at index 1, and thus
// the first inference is the template literal type `<${string}>`. The remainder of the source makes up the second
// inference, the template literal type `<${number}-${number}>`.
func (c *Checker) inferFromLiteralPartsToTemplateLiteral(sourceTexts []string, sourceTypes []*Type, target *TemplateLiteralType) []*Type {
	lastSourceIndex := len(sourceTexts) - 1
	sourceStartText := sourceTexts[0]
	sourceEndText := sourceTexts[lastSourceIndex]
	targetTexts := target.texts
	lastTargetIndex := len(targetTexts) - 1
	targetStartText := targetTexts[0]
	targetEndText := targetTexts[lastTargetIndex]
	if lastSourceIndex == 0 && len(sourceStartText) < len(targetStartText)+len(targetEndText) || !strings.HasPrefix(sourceStartText, targetStartText) || !strings.HasSuffix(sourceEndText, targetEndText) {
		return nil
	}
	remainingEndText := sourceEndText[:len(sourceEndText)-len(targetEndText)]
	seg := 0
	pos := len(targetStartText)
	var matches []*Type
	getSourceText := func(index int) string {
		if index < lastSourceIndex {
			return sourceTexts[index]
		}
		return remainingEndText
	}
	addMatch := func(s int, p int) {
		var matchType *Type
		if s == seg {
			matchType = c.getStringLiteralType(getSourceText(s)[pos:p])
		} else {
			matchTexts := make([]string, s-seg+1)
			matchTexts[0] = sourceTexts[seg][pos:]
			copy(matchTexts[1:], sourceTexts[seg+1:s])
			matchTexts[s-seg] = getSourceText(s)[:p]
			matchType = c.getTemplateLiteralType(matchTexts, sourceTypes[seg:s])
		}
		matches = append(matches, matchType)
		seg = s
		pos = p
	}
	for i := 1; i < lastTargetIndex; i++ {
		delim := targetTexts[i]
		if len(delim) > 0 {
			s := seg
			p := pos
			for {
				d := strings.Index(getSourceText(s)[p:], delim)
				if d >= 0 {
					p += d
					break
				}
				s++
				if s == len(sourceTexts) {
					return nil
				}
				p = 0
			}
			addMatch(s, p)
			pos += len(delim)
		} else if pos < len(getSourceText(seg)) {
			addMatch(seg, pos+1)
		} else if seg < lastSourceIndex {
			addMatch(seg+1, 0)
		} else {
			return nil
		}
	}
	addMatch(lastSourceIndex, len(getSourceText(lastSourceIndex)))
	return matches
}

func (c *Checker) getStringLikeTypeForType(t *Type) *Type {
	if t.flags&(TypeFlagsAny|TypeFlagsStringLike) != 0 {
		return t
	}
	return c.getTemplateLiteralType([]string{"", ""}, []*Type{t})
}

func (c *Checker) isValidTypeForTemplateLiteralPlaceholder(source *Type, target *Type) bool {
	switch {
	case target.flags&TypeFlagsIntersection != 0:
		return core.Every(target.Types(), func(t *Type) bool {
			return t == c.emptyTypeLiteralType || c.isValidTypeForTemplateLiteralPlaceholder(source, t)
		})
	case target.flags&TypeFlagsString != 0 || c.isTypeAssignableTo(source, target):
		return true
	case source.flags&TypeFlagsStringLiteral != 0:
		value := getStringLiteralValue(source)
		return target.flags&TypeFlagsNumber != 0 && isValidNumberString(value, false /*roundTripOnly*/) ||
			target.flags&TypeFlagsBigInt != 0 && isValidBigIntString(value, false /*roundTripOnly*/) ||
			target.flags&(TypeFlagsBooleanLiteral|TypeFlagsNullable) != 0 && value == target.AsIntrinsicType().intrinsicName ||
			target.flags&TypeFlagsStringMapping != 0 && c.isMemberOfStringMapping(source, target) ||
			target.flags&TypeFlagsTemplateLiteral != 0 && c.isTypeMatchedByTemplateLiteralType(source, target.AsTemplateLiteralType())
	case source.flags&TypeFlagsTemplateLiteral != 0:
		texts := source.AsTemplateLiteralType().texts
		return len(texts) == 2 && texts[0] == "" && texts[1] == "" && c.isTypeAssignableTo(source.AsTemplateLiteralType().types[0], target)
	}
	return false
}

func (c *Checker) isMemberOfStringMapping(source *Type, target *Type) bool {
	switch {
	case target.flags&TypeFlagsAny != 0:
		return true
	case target.flags&(TypeFlagsString|TypeFlagsTemplateLiteral) != 0:
		return c.isTypeAssignableTo(source, target)
	case target.flags&TypeFlagsStringMapping != 0:
		// We need to see whether applying the same mappings of the target
		// onto the source would produce an identical type *and* that
		// it's compatible with the inner-most non-string-mapped type.
		//
		// The intuition here is that if same mappings don't affect the source at all,
		// and the source is compatible with the unmapped target, then they must
		// still reside in the same domain.
		mapped, inner := c.applyTargetStringMappingToSource(source, target)
		return mapped == source && c.isMemberOfStringMapping(source, inner)
	}
	return false
}

func (c *Checker) applyTargetStringMappingToSource(source *Type, target *Type) (*Type, *Type) {
	inner := target.AsStringMappingType().target
	if inner.flags&TypeFlagsStringMapping != 0 {
		source, inner = c.applyTargetStringMappingToSource(source, inner)
	}
	return c.getStringMappingType(target.symbol, source), inner
}

func visibilityToString(flags ast.ModifierFlags) string {
	if flags == ast.ModifierFlagsPrivate {
		return "private"
	}
	if flags == ast.ModifierFlagsProtected {
		return "protected"
	}
	return "public"
}

type errorState struct {
	errorChain  *ErrorChain
	relatedInfo []*ast.Diagnostic
}

type ErrorChain struct {
	next    *ErrorChain
	message *diagnostics.Message
	args    []any
}

type Relater struct {
	c              *Checker
	relation       *Relation
	errorNode      *ast.Node
	errorChain     *ErrorChain
	relatedInfo    []*ast.Diagnostic
	maybeKeys      []string
	maybeKeysSet   collections.Set[string]
	sourceStack    []*Type
	targetStack    []*Type
	maybeCount     int
	sourceDepth    int
	targetDepth    int
	expandingFlags ExpandingFlags
	overflow       bool
	relationCount  int
	next           *Relater
}

func (c *Checker) getRelater() *Relater {
	r := c.freeRelater
	if r == nil {
		r = &Relater{c: c}
	}
	c.freeRelater = r.next
	return r
}

func (c *Checker) putRelater(r *Relater) {
	r.maybeKeysSet.Clear()
	*r = Relater{
		c:            c,
		maybeKeys:    r.maybeKeys[:0],
		maybeKeysSet: r.maybeKeysSet,
		sourceStack:  r.sourceStack[:0],
		targetStack:  r.targetStack[:0],
		next:         c.freeRelater,
	}
	c.freeRelater = r
}

func (r *Relater) isRelatedToSimple(source *Type, target *Type) Ternary {
	return r.isRelatedToEx(source, target, RecursionFlagsBoth, false /*reportErrors*/, nil /*headMessage*/, IntersectionStateNone)
}

func (r *Relater) isRelatedToWorker(source *Type, target *Type, reportErrors bool) Ternary {
	return r.isRelatedToEx(source, target, RecursionFlagsBoth, reportErrors, nil, IntersectionStateNone)
}

func (r *Relater) isRelatedTo(source *Type, target *Type, recursionFlags RecursionFlags, reportErrors bool) Ternary {
	return r.isRelatedToEx(source, target, recursionFlags, reportErrors, nil, IntersectionStateNone)
}

func (r *Relater) isRelatedToEx(originalSource *Type, originalTarget *Type, recursionFlags RecursionFlags, reportErrors bool, headMessage *diagnostics.Message, intersectionState IntersectionState) Ternary {
	if originalSource == originalTarget {
		return TernaryTrue
	}
	// Before normalization: if `source` is type an object type, and `target` is primitive,
	// skip all the checks we don't need and just return `isSimpleTypeRelatedTo` result
	if originalSource.flags&TypeFlagsObject != 0 && originalTarget.flags&TypeFlagsPrimitive != 0 {
		if r.relation == r.c.comparableRelation && originalTarget.flags&TypeFlagsNever == 0 && r.c.isSimpleTypeRelatedTo(originalTarget, originalSource, r.relation, nil) ||
			r.c.isSimpleTypeRelatedTo(originalSource, originalTarget, r.relation, core.IfElse(reportErrors, r.reportError, nil)) {
			return TernaryTrue
		}
		if reportErrors {
			r.reportErrorResults(originalSource, originalTarget, originalSource, originalTarget, headMessage)
		}
		return TernaryFalse
	}
	// Normalize the source and target types: Turn fresh literal types into regular literal types,
	// turn deferred type references into regular type references, simplify indexed access and
	// conditional types, and resolve substitution types to either the substitution (on the source
	// side) or the type variable (on the target side).
	source := r.c.getNormalizedType(originalSource, false /*writing*/)
	target := r.c.getNormalizedType(originalTarget, true /*writing*/)
	if source == target {
		return TernaryTrue
	}
	if r.relation == r.c.identityRelation {
		if source.flags != target.flags {
			return TernaryFalse
		}
		if source.flags&TypeFlagsSingleton != 0 {
			return TernaryTrue
		}
		return r.recursiveTypeRelatedTo(source, target, false /*reportErrors*/, IntersectionStateNone, recursionFlags)
	}
	// We fastpath comparing a type parameter to exactly its constraint, as this is _super_ common,
	// and otherwise, for type parameters in large unions, causes us to need to compare the union to itself,
	// as we break down the _target_ union first, _then_ get the source constraint - so for every
	// member of the target, we attempt to find a match in the source. This avoids that in cases where
	// the target is exactly the constraint.
	if source.flags&TypeFlagsTypeParameter != 0 && r.c.getConstraintOfType(source) == target {
		return TernaryTrue
	}
	// See if we're relating a definitely non-nullable type to a union that includes null and/or undefined
	// plus a single non-nullable type. If so, remove null and/or undefined from the target type.
	if source.flags&TypeFlagsDefinitelyNonNullable != 0 && target.flags&TypeFlagsUnion != 0 {
		types := target.Types()
		var candidate *Type
		switch {
		case len(types) == 2 && types[0].flags&TypeFlagsNullable != 0:
			candidate = types[1]
		case len(types) == 3 && types[0].flags&TypeFlagsNullable != 0 && types[1].flags&TypeFlagsNullable != 0:
			candidate = types[2]
		}
		if candidate != nil && candidate.flags&TypeFlagsNullable == 0 {
			target = r.c.getNormalizedType(candidate /*writing*/, true)
			if source == target {
				return TernaryTrue
			}
		}
	}
	if r.relation == r.c.comparableRelation && target.flags&TypeFlagsNever == 0 && r.c.isSimpleTypeRelatedTo(target, source, r.relation, nil) ||
		r.c.isSimpleTypeRelatedTo(source, target, r.relation, core.IfElse(reportErrors, r.reportError, nil)) {
		return TernaryTrue
	}
	if source.flags&TypeFlagsStructuredOrInstantiable != 0 || target.flags&TypeFlagsStructuredOrInstantiable != 0 {
		isPerformingExcessPropertyChecks := intersectionState&IntersectionStateTarget == 0 && isObjectLiteralType(source) && source.objectFlags&ObjectFlagsFreshLiteral != 0
		if isPerformingExcessPropertyChecks {
			if r.hasExcessProperties(source, target, reportErrors) {
				if reportErrors {
					r.reportRelationError(headMessage, source, core.IfElse(originalTarget.alias != nil, originalTarget, target))
				}
				return TernaryFalse
			}
		}
		isPerformingCommonPropertyChecks := (r.relation != r.c.comparableRelation || isUnitType(source)) &&
			intersectionState&IntersectionStateTarget == 0 &&
			source.flags&(TypeFlagsPrimitive|TypeFlagsObject|TypeFlagsIntersection) != 0 && source != r.c.globalObjectType &&
			target.flags&(TypeFlagsObject|TypeFlagsIntersection) != 0 && r.c.isWeakType(target) && (len(r.c.getPropertiesOfType(source)) > 0 || r.c.typeHasCallOrConstructSignatures(source))
		isComparingJsxAttributes := source.objectFlags&ObjectFlagsJsxAttributes != 0
		if isPerformingCommonPropertyChecks && !r.c.hasCommonProperties(source, target, isComparingJsxAttributes) {
			if reportErrors {
				sourceString := r.c.TypeToString(core.IfElse(originalSource.alias != nil, originalSource, source))
				targetString := r.c.TypeToString(core.IfElse(originalTarget.alias != nil, originalTarget, target))
				calls := r.c.getSignaturesOfType(source, SignatureKindCall)
				constructs := r.c.getSignaturesOfType(source, SignatureKindConstruct)
				if len(calls) > 0 && r.isRelatedTo(r.c.getReturnTypeOfSignature(calls[0]), target, RecursionFlagsSource, false /*reportErrors*/) != TernaryFalse ||
					len(constructs) > 0 && r.isRelatedTo(r.c.getReturnTypeOfSignature(constructs[0]), target, RecursionFlagsSource, false /*reportErrors*/) != TernaryFalse {
					r.reportError(diagnostics.Value_of_type_0_has_no_properties_in_common_with_type_1_Did_you_mean_to_call_it, sourceString, targetString)
				} else {
					r.reportError(diagnostics.Type_0_has_no_properties_in_common_with_type_1, sourceString, targetString)
				}
			}
			return TernaryFalse
		}
		skipCaching := source.flags&TypeFlagsUnion != 0 && len(source.Types()) < 4 && target.flags&TypeFlagsUnion == 0 ||
			target.flags&TypeFlagsUnion != 0 && len(target.Types()) < 4 && source.flags&TypeFlagsStructuredOrInstantiable == 0
		var result Ternary
		if skipCaching {
			result = r.unionOrIntersectionRelatedTo(source, target, reportErrors, intersectionState)
		} else {
			result = r.recursiveTypeRelatedTo(source, target, reportErrors, intersectionState, recursionFlags)
		}
		if result != TernaryFalse {
			return result
		}
	}
	if reportErrors {
		r.reportErrorResults(originalSource, originalTarget, source, target, headMessage)
	}
	return TernaryFalse
}

func (r *Relater) hasExcessProperties(source *Type, target *Type, reportErrors bool) bool {
	if !isExcessPropertyCheckTarget(target) || !r.c.noImplicitAny && target.objectFlags&ObjectFlagsJSLiteral != 0 {
		// Disable excess property checks on JS literals to simulate having an implicit "index signature" - but only outside of noImplicitAny
		return false
	}
	isComparingJsxAttributes := source.objectFlags&ObjectFlagsJsxAttributes != 0
	if (r.relation == r.c.assignableRelation || r.relation == r.c.comparableRelation) && (r.c.isTypeSubsetOf(r.c.globalObjectType, target) || (!isComparingJsxAttributes && r.c.isEmptyObjectType(target))) {
		return false
	}
	reducedTarget := target
	var checkTypes []*Type
	if target.flags&TypeFlagsUnion != 0 {
		reducedTarget = r.c.findMatchingDiscriminantType(source, target, r.isRelatedToSimple)
		if reducedTarget == nil {
			reducedTarget = r.c.filterPrimitivesIfContainsNonPrimitive(target)
		}
		checkTypes = reducedTarget.Distributed()
	}
	for _, prop := range r.c.getPropertiesOfType(source) {
		if shouldCheckAsExcessProperty(prop, source.symbol) && !isIgnoredJsxProperty(source, prop) {
			if !r.c.isKnownProperty(reducedTarget, prop.Name, isComparingJsxAttributes) {
				if reportErrors {
					// Report error in terms of object types in the target as those are the only ones
					// we check in isKnownProperty.
					errorTarget := r.c.filterType(reducedTarget, isExcessPropertyCheckTarget)
					// We know *exactly* where things went wrong when comparing the types.
					// Use this property as the error node as this will be more helpful in
					// reasoning about what went wrong.
					if r.errorNode == nil {
						panic("No errorNode in hasExcessProperties")
					}
					if ast.IsJsxAttributes(r.errorNode) || ast.IsJsxOpeningLikeElement(r.errorNode) || ast.IsJsxOpeningLikeElement(r.errorNode.Parent) {
						// JsxAttributes has an object-literal flag and undergo same type-assignablity check as normal object-literal.
						// However, using an object-literal error message will be very confusing to the users so we give different a message.
						if prop.ValueDeclaration != nil && ast.IsJsxAttribute(prop.ValueDeclaration) && ast.GetSourceFileOfNode(r.errorNode) == ast.GetSourceFileOfNode(prop.ValueDeclaration.Name()) {
							// Note that extraneous children (as in `<NoChild>extra</NoChild>`) don't pass this check,
							// since `children` is a Kind.PropertySignature instead of a Kind.JsxAttribute.
							r.errorNode = prop.ValueDeclaration.Name()
						}
						propName := r.c.symbolToString(prop)
						suggestionSymbol := r.c.getSuggestedSymbolForNonexistentJSXAttribute(propName, errorTarget)
						if suggestionSymbol != nil {
							r.reportError(diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, propName, r.c.TypeToString(errorTarget), r.c.symbolToString(suggestionSymbol))
						} else {
							r.reportError(diagnostics.Property_0_does_not_exist_on_type_1, propName, r.c.TypeToString(errorTarget))
						}
					} else {
						// use the property's value declaration if the property is assigned inside the literal itself
						var objectLiteralDeclaration *ast.Node
						if source.symbol != nil {
							objectLiteralDeclaration = core.FirstOrNil(source.symbol.Declarations)
						}
						var suggestion string
						if prop.ValueDeclaration != nil && ast.IsObjectLiteralElement(prop.ValueDeclaration) &&
							ast.FindAncestor(prop.ValueDeclaration, func(d *ast.Node) bool { return d == objectLiteralDeclaration }) != nil &&
							ast.GetSourceFileOfNode(objectLiteralDeclaration) == ast.GetSourceFileOfNode(r.errorNode) {
							name := prop.ValueDeclaration.Name()
							r.errorNode = name
							if ast.IsIdentifier(name) {
								suggestion = r.c.getSuggestionForNonexistentProperty(name.Text(), errorTarget)
							}
						}
						if suggestion != "" {
							r.reportError(diagnostics.Object_literal_may_only_specify_known_properties_but_0_does_not_exist_in_type_1_Did_you_mean_to_write_2, r.c.symbolToString(prop), r.c.TypeToString(errorTarget), suggestion)
						} else {
							r.reportError(diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1, r.c.symbolToString(prop), r.c.TypeToString(errorTarget))
						}
					}
				}
				return true
			}
			if checkTypes != nil && r.isRelatedTo(r.c.getTypeOfSymbol(prop), r.c.getTypeOfPropertyInTypes(checkTypes, prop.Name), RecursionFlagsBoth, reportErrors) == TernaryFalse {
				if reportErrors {
					r.reportError(diagnostics.Types_of_property_0_are_incompatible, r.c.symbolToString(prop))
				}
				return true
			}
		}
	}
	return false
}

func (c *Checker) getTypeOfPropertyInTypes(types []*Type, name string) *Type {
	var propTypes []*Type
	for _, t := range types {
		propTypes = append(propTypes, c.getTypeOfPropertyInType(t, name))
	}
	return c.getUnionType(propTypes)
}

func (c *Checker) getTypeOfPropertyInType(t *Type, name string) *Type {
	t = c.getApparentType(t)
	var prop *ast.Symbol
	if t.flags&TypeFlagsUnionOrIntersection != 0 {
		prop = c.getPropertyOfUnionOrIntersectionType(t, name, false)
	} else {
		prop = c.getPropertyOfObjectType(t, name)
	}
	if prop != nil {
		return c.getTypeOfSymbol(prop)
	}
	indexInfo := c.getApplicableIndexInfoForName(t, name)
	if indexInfo != nil {
		return indexInfo.valueType
	}
	return c.undefinedType
}

func shouldCheckAsExcessProperty(prop *ast.Symbol, container *ast.Symbol) bool {
	return prop.ValueDeclaration != nil && container.ValueDeclaration != nil && prop.ValueDeclaration.Parent == container.ValueDeclaration
}

func isIgnoredJsxProperty(source *Type, sourceProp *ast.Symbol) bool {
	return source.objectFlags&ObjectFlagsJsxAttributes != 0 && isHyphenatedJsxName(sourceProp.Name)
}

func (c *Checker) isTypeSubsetOf(source *Type, target *Type) bool {
	return source == target || source.flags&TypeFlagsNever != 0 || target.flags&TypeFlagsUnion != 0 && c.isTypeSubsetOfUnion(source, target)
}

func (c *Checker) isTypeSubsetOfUnion(source *Type, target *Type) bool {
	if source.flags&TypeFlagsUnion != 0 {
		for _, t := range source.Types() {
			if !containsType(target.Types(), t) {
				return false
			}
		}
		return true
	}
	if source.flags&TypeFlagsEnumLike != 0 && c.getBaseTypeOfEnumLikeType(source) == target {
		return true
	}
	return containsType(target.Types(), source)
}

func (r *Relater) unionOrIntersectionRelatedTo(source *Type, target *Type, reportErrors bool, intersectionState IntersectionState) Ternary {
	// Note that these checks are specifically ordered to produce correct results. In particular,
	// we need to deconstruct unions before intersections (because unions are always at the top),
	// and we need to handle "each" relations before "some" relations for the same kind of type.
	if source.flags&TypeFlagsUnion != 0 {
		if target.flags&TypeFlagsUnion != 0 {
			// Intersections of union types are normalized into unions of intersection types, and such normalized
			// unions can get very large and expensive to relate. The following fast path checks if the source union
			// originated in an intersection. If so, and if that intersection contains the target type, then we know
			// the result to be true (for any two types A and B, A & B is related to both A and B).
			sourceOrigin := source.AsUnionType().origin
			if sourceOrigin != nil && sourceOrigin.flags&TypeFlagsIntersection != 0 && target.alias != nil && slices.Contains(sourceOrigin.Types(), target) {
				return TernaryTrue
			}
			// Similarly, in unions of unions the we preserve the original list of unions. This original list is often
			// much shorter than the normalized result, so we scan it in the following fast path.
			targetOrigin := target.AsUnionType().origin
			if targetOrigin != nil && targetOrigin.flags&TypeFlagsUnion != 0 && source.alias != nil && slices.Contains(targetOrigin.Types(), source) {
				return TernaryTrue
			}
		}
		if r.relation == r.c.comparableRelation {
			return r.someTypeRelatedToType(source, target, reportErrors && source.flags&TypeFlagsPrimitive == 0, intersectionState)
		}
		return r.eachTypeRelatedToType(source, target, reportErrors && source.flags&TypeFlagsPrimitive == 0, intersectionState)
	}
	if target.flags&TypeFlagsUnion != 0 {
		return r.typeRelatedToSomeType(r.c.getRegularTypeOfObjectLiteral(source), target, reportErrors && source.flags&TypeFlagsPrimitive == 0 && target.flags&TypeFlagsPrimitive == 0, intersectionState)
	}
	if target.flags&TypeFlagsIntersection != 0 {
		return r.typeRelatedToEachType(source, target, reportErrors, IntersectionStateTarget)
	}
	// Source is an intersection. For the comparable relation, if the target is a primitive type we hoist the
	// constraints of all non-primitive types in the source into a new intersection. We do this because the
	// intersection may further constrain the constraints of the non-primitive types. For example, given a type
	// parameter 'T extends 1 | 2', the intersection 'T & 1' should be reduced to '1' such that it doesn't
	// appear to be comparable to '2'.
	if r.relation == r.c.comparableRelation && target.flags&TypeFlagsPrimitive != 0 {
		constraints := core.SameMap(source.Types(), func(t *Type) *Type {
			if t.flags&TypeFlagsInstantiable != 0 {
				constraint := r.c.getBaseConstraintOfType(t)
				if constraint != nil {
					return constraint
				}
				return r.c.unknownType
			}
			return t
		})
		if !core.Same(constraints, source.Types()) {
			source = r.c.getIntersectionType(constraints)
			if source.flags&TypeFlagsNever != 0 {
				return TernaryFalse
			}
			if source.flags&TypeFlagsIntersection == 0 {
				result := r.isRelatedTo(source, target, RecursionFlagsSource, false /*reportErrors*/)
				if result != TernaryFalse {
					return result
				}
				return r.isRelatedTo(target, source, RecursionFlagsSource, false /*reportErrors*/)
			}
		}
	}
	// Check to see if any constituents of the intersection are immediately related to the target.
	// Don't report errors though. Elaborating on whether a source constituent is related to the target is
	// not actually useful and leads to some confusing error messages. Instead, we rely on the caller
	// checking whether the full intersection viewed as an object is related to the target.
	return r.someTypeRelatedToType(source, target, false /*reportErrors*/, IntersectionStateSource)
}

func (r *Relater) someTypeRelatedToType(source *Type, target *Type, reportErrors bool, intersectionState IntersectionState) Ternary {
	sourceTypes := source.Types()
	if source.flags&TypeFlagsUnion != 0 && containsType(sourceTypes, target) {
		return TernaryTrue
	}
	for i, t := range sourceTypes {
		related := r.isRelatedToEx(t, target, RecursionFlagsSource, reportErrors && i == len(sourceTypes)-1, nil /*headMessage*/, intersectionState)
		if related != TernaryFalse {
			return related
		}
	}
	return TernaryFalse
}

func (r *Relater) eachTypeRelatedToType(source *Type, target *Type, reportErrors bool, intersectionState IntersectionState) Ternary {
	result := TernaryTrue
	sourceTypes := source.Types()
	// We strip `undefined` from the target if the `source` trivially doesn't contain it for our correspondence-checking fastpath
	// since `undefined` is frequently added by optionality and would otherwise spoil a potentially useful correspondence
	strippedTarget := r.getUndefinedStrippedTargetIfNeeded(source, target)
	var strippedTypes []*Type
	if strippedTarget.flags&TypeFlagsUnion != 0 {
		strippedTypes = strippedTarget.Types()
	}
	for i, sourceType := range sourceTypes {
		if strippedTarget.flags&TypeFlagsUnion != 0 && len(sourceTypes) >= len(strippedTypes) && len(sourceTypes)%len(strippedTypes) == 0 {
			// many unions are mappings of one another; in such cases, simply comparing members at the same index can shortcut the comparison
			// such unions will have identical lengths, and their corresponding elements will match up. Another common scenario is where a large
			// union has a union of objects intersected with it. In such cases, if the input was, eg `("a" | "b" | "c") & (string | boolean | {} | {whatever})`,
			// the result will have the structure `"a" | "b" | "c" | "a" & {} | "b" & {} | "c" & {} | "a" & {whatever} | "b" & {whatever} | "c" & {whatever}`
			// - the resulting union has a length which is a multiple of the original union, and the elements correspond modulo the length of the original union
			related := r.isRelatedToEx(sourceType, strippedTypes[i%len(strippedTypes)], RecursionFlagsBoth, false /*reportErrors*/, nil /*headMessage*/, intersectionState)
			if related != TernaryFalse {
				result &= related
				continue
			}
		}
		related := r.isRelatedToEx(sourceType, target, RecursionFlagsSource, reportErrors, nil /*headMessage*/, intersectionState)
		if related == TernaryFalse {
			return TernaryFalse
		}
		result &= related
	}
	return result
}

func (r *Relater) getUndefinedStrippedTargetIfNeeded(source *Type, target *Type) *Type {
	if source.flags&TypeFlagsUnion != 0 && target.flags&TypeFlagsUnion != 0 && source.Types()[0].flags&TypeFlagsUndefined == 0 && target.Types()[0].flags&TypeFlagsUndefined != 0 {
		return r.c.extractTypesOfKind(target, ^TypeFlagsUndefined)
	}
	return target
}

func (r *Relater) typeRelatedToSomeType(source *Type, target *Type, reportErrors bool, intersectionState IntersectionState) Ternary {
	targetTypes := target.Types()
	if target.flags&TypeFlagsUnion != 0 {
		if containsType(targetTypes, source) {
			return TernaryTrue
		}
		if r.relation != r.c.comparableRelation && target.objectFlags&ObjectFlagsPrimitiveUnion != 0 && source.flags&TypeFlagsEnumLiteral == 0 &&
			(source.flags&(TypeFlagsStringLiteral|TypeFlagsBooleanLiteral|TypeFlagsBigIntLiteral) != 0 ||
				(r.relation == r.c.subtypeRelation || r.relation == r.c.strictSubtypeRelation) && source.flags&TypeFlagsNumberLiteral != 0) {
			// When relating a literal type to a union of primitive types, we know the relation is false unless
			// the union contains the base primitive type or the literal type in one of its fresh/regular forms.
			// We exclude numeric literals for non-subtype relations because numeric literals are assignable to
			// numeric enum literals with the same value. Similarly, we exclude enum literal types because
			// identically named enum types are related (see isEnumTypeRelatedTo). We exclude the comparable
			// relation in entirety because it needs to be checked in both directions.
			var alternateForm *Type
			if source == source.AsLiteralType().regularType {
				alternateForm = source.AsLiteralType().freshType
			} else {
				alternateForm = source.AsLiteralType().regularType
			}
			var primitive *Type
			switch {
			case source.flags&TypeFlagsStringLiteral != 0:
				primitive = r.c.stringType
			case source.flags&TypeFlagsNumberLiteral != 0:
				primitive = r.c.numberType
			case source.flags&TypeFlagsBigIntLiteral != 0:
				primitive = r.c.bigintType
			}
			if primitive != nil && containsType(targetTypes, primitive) || alternateForm != nil && containsType(targetTypes, alternateForm) {
				return TernaryTrue
			}
			return TernaryFalse
		}
		match := r.c.getMatchingUnionConstituentForType(target, source)
		if match != nil {
			related := r.isRelatedToEx(source, match, RecursionFlagsTarget, false /*reportErrors*/, nil /*headMessage*/, intersectionState)
			if related != TernaryFalse {
				return related
			}
		}
	}
	for _, t := range targetTypes {
		related := r.isRelatedToEx(source, t, RecursionFlagsTarget, false /*reportErrors*/, nil /*headMessage*/, intersectionState)
		if related != TernaryFalse {
			return related
		}
	}
	if reportErrors {
		// Elaborate only if we can find a best matching type in the target union
		bestMatchingType := r.c.getBestMatchingType(source, target, r.isRelatedToSimple)
		if bestMatchingType != nil {
			r.isRelatedToEx(source, bestMatchingType, RecursionFlagsTarget, true /*reportErrors*/, nil /*headMessage*/, intersectionState)
		}
	}
	return TernaryFalse
}

func (r *Relater) typeRelatedToEachType(source *Type, target *Type, reportErrors bool, intersectionState IntersectionState) Ternary {
	result := TernaryTrue
	targetTypes := target.Types()
	for _, targetType := range targetTypes {
		related := r.isRelatedToEx(source, targetType, RecursionFlagsTarget, reportErrors /*headMessage*/, nil, intersectionState)
		if related == TernaryFalse {
			return TernaryFalse
		}
		result &= related
	}
	return result
}

func (r *Relater) eachTypeRelatedToSomeType(source *Type, target *Type) Ternary {
	result := TernaryTrue
	sourceTypes := source.Types()
	for _, sourceType := range sourceTypes {
		related := r.typeRelatedToSomeType(sourceType, target, false /*reportErrors*/, IntersectionStateNone)
		if related == TernaryFalse {
			return TernaryFalse
		}
		result &= related
	}
	return result
}

// Determine if possibly recursive types are related. First, check if the result is already available in the global cache.
// Second, check if we have already started a comparison of the given two types in which case we assume the result to be true.
// Third, check if both types are part of deeply nested chains of generic type instantiations and if so assume the types are
// equal and infinitely expanding. Fourth, if we have reached a depth of 100 nested comparisons, assume we have runaway recursion
// and issue an error. Otherwise, actually compare the structure of the two types.
func (r *Relater) recursiveTypeRelatedTo(source *Type, target *Type, reportErrors bool, intersectionState IntersectionState, recursionFlags RecursionFlags) Ternary {
	if r.overflow {
		return TernaryFalse
	}
	id := getRelationKey(source, target, intersectionState, r.relation == r.c.identityRelation, false /*ignoreConstraints*/)
	if entry := r.relation.get(id); entry != RelationComparisonResultNone {
		if reportErrors && entry&RelationComparisonResultFailed != 0 && entry&RelationComparisonResultOverflow == 0 {
			// We are elaborating errors and the cached result is a failure not due to a comparison overflow,
			// so we will do the comparison again to generate an error message.
		} else {
			r.c.reliabilityFlags |= entry & (RelationComparisonResultReportsUnmeasurable | RelationComparisonResultReportsUnreliable)
			if reportErrors && entry&RelationComparisonResultOverflow != 0 {
				message := core.IfElse(entry&RelationComparisonResultComplexityOverflow != 0,
					diagnostics.Excessive_complexity_comparing_types_0_and_1,
					diagnostics.Excessive_stack_depth_comparing_types_0_and_1)
				r.reportError(message, r.c.TypeToString(source), r.c.TypeToString(target))
			}
			if entry&RelationComparisonResultSucceeded != 0 {
				return TernaryTrue
			}
			return TernaryFalse
		}
	}
	if r.relationCount <= 0 {
		r.overflow = true
		return TernaryFalse
	}
	// If source and target are already being compared, consider them related with assumptions
	if r.maybeKeysSet.Has(id) {
		return TernaryMaybe
	}
	// A key that ends with "*" is an indication that we have type references that reference constrained
	// type parameters. For such keys we also check against the key we would have gotten if all type parameters
	// were unconstrained.
	if strings.HasSuffix(id, "*") {
		broadestEquivalentId := getRelationKey(source, target, intersectionState, r.relation == r.c.identityRelation, true /*ignoreConstraints*/)
		if r.maybeKeysSet.Has(broadestEquivalentId) {
			return TernaryMaybe
		}
	}
	if len(r.sourceStack) == 100 || len(r.targetStack) == 100 {
		r.overflow = true
		return TernaryFalse
	}
	maybeStart := len(r.maybeKeys)
	r.maybeKeys = append(r.maybeKeys, id)
	r.maybeKeysSet.Add(id)
	saveExpandingFlags := r.expandingFlags
	if recursionFlags&RecursionFlagsSource != 0 {
		r.sourceStack = append(r.sourceStack, source)
		if r.expandingFlags&ExpandingFlagsSource == 0 && r.c.isDeeplyNestedType(source, r.sourceStack, 3) {
			r.expandingFlags |= ExpandingFlagsSource
		}
	}
	if recursionFlags&RecursionFlagsTarget != 0 {
		r.targetStack = append(r.targetStack, target)
		if r.expandingFlags&ExpandingFlagsTarget == 0 && r.c.isDeeplyNestedType(target, r.targetStack, 3) {
			r.expandingFlags |= ExpandingFlagsTarget
		}
	}
	saveReliabilityFlags := r.c.reliabilityFlags
	r.c.reliabilityFlags = 0
	var result Ternary
	if r.expandingFlags == ExpandingFlagsBoth {
		result = TernaryMaybe
	} else {
		result = r.structuredTypeRelatedTo(source, target, reportErrors, intersectionState)
	}
	propagatingVarianceFlags := r.c.reliabilityFlags
	r.c.reliabilityFlags |= saveReliabilityFlags
	if recursionFlags&RecursionFlagsSource != 0 {
		r.sourceStack = r.sourceStack[:len(r.sourceStack)-1]
	}
	if recursionFlags&RecursionFlagsTarget != 0 {
		r.targetStack = r.targetStack[:len(r.targetStack)-1]
	}
	r.expandingFlags = saveExpandingFlags
	if result != TernaryFalse {
		if result == TernaryTrue || (len(r.sourceStack) == 0 && len(r.targetStack) == 0) {
			if result == TernaryTrue || result == TernaryMaybe {
				// If result is definitely true, record all maybe keys as having succeeded. Also, record Ternary.Maybe
				// results as having succeeded once we reach depth 0, but never record Ternary.Unknown results.
				r.resetMaybeStack(maybeStart, propagatingVarianceFlags, true)
			} else {
				r.resetMaybeStack(maybeStart, propagatingVarianceFlags, false)
			}
		}
		// Note: it's intentional that we don't reset in the else case;
		// we leave them on the stack such that when we hit depth zero
		// above, we can report all of them as successful.
	} else {
		// A false result goes straight into global cache (when something is false under
		// assumptions it will also be false without assumptions)
		r.relation.set(id, RelationComparisonResultFailed|propagatingVarianceFlags)
		r.relationCount--
		r.resetMaybeStack(maybeStart, propagatingVarianceFlags, false)
	}
	return result
}

func (r *Relater) resetMaybeStack(maybeStart int, propagatingVarianceFlags RelationComparisonResult, markAllAsSucceeded bool) {
	for i := maybeStart; i < len(r.maybeKeys); i++ {
		r.maybeKeysSet.Delete(r.maybeKeys[i])
		if markAllAsSucceeded {
			r.relation.set(r.maybeKeys[i], RelationComparisonResultSucceeded|propagatingVarianceFlags)
			r.relationCount--
		}
	}
	r.maybeKeys = r.maybeKeys[:maybeStart]
}

func (r *Relater) getErrorState() errorState {
	return errorState{
		errorChain:  r.errorChain,
		relatedInfo: r.relatedInfo,
	}
}

func (r *Relater) restoreErrorState(e errorState) {
	r.errorChain = e.errorChain
	r.relatedInfo = e.relatedInfo
}

func (r *Relater) structuredTypeRelatedTo(source *Type, target *Type, reportErrors bool, intersectionState IntersectionState) Ternary {
	saveErrorState := r.getErrorState()
	result := r.structuredTypeRelatedToWorker(source, target, reportErrors, intersectionState)
	if r.relation != r.c.identityRelation {
		// The combined constraint of an intersection type is the intersection of the constraints of
		// the constituents. When an intersection type contains instantiable types with union type
		// constraints, there are situations where we need to examine the combined constraint. One is
		// when the target is a union type. Another is when the intersection contains types belonging
		// to one of the disjoint domains. For example, given type variables T and U, each with the
		// constraint 'string | number', the combined constraint of 'T & U' is 'string | number' and
		// we need to check this constraint against a union on the target side. Also, given a type
		// variable V constrained to 'string | number', 'V & number' has a combined constraint of
		// 'string & number | number & number' which reduces to just 'number'.
		// This also handles type parameters, as a type parameter with a union constraint compared against a union
		// needs to have its constraint hoisted into an intersection with said type parameter, this way
		// the type param can be compared with itself in the target (with the influence of its constraint to match other parts)
		// For example, if `T extends 1 | 2` and `U extends 2 | 3` and we compare `T & U` to `T & U & (1 | 2 | 3)`
		if result == TernaryFalse && (source.flags&TypeFlagsIntersection != 0 || source.flags&TypeFlagsTypeParameter != 0 && target.flags&TypeFlagsUnion != 0) {
			var sourceTypes []*Type
			if source.flags&TypeFlagsIntersection != 0 {
				sourceTypes = source.Types()
			} else {
				sourceTypes = []*Type{source}
			}
			constraint := r.c.getEffectiveConstraintOfIntersection(sourceTypes, target.flags&TypeFlagsUnion != 0)
			if constraint != nil && everyType(constraint, func(c *Type) bool { return c != source }) {
				// TODO: Stack errors so we get a pyramid for the "normal" comparison above, _and_ a second for this
				result = r.isRelatedToEx(constraint, target, RecursionFlagsSource, false /*reportErrors*/, nil /*headMessage*/, intersectionState)
			}
		}
		switch {
		// When the target is an intersection we need an extra property check in order to detect nested excess
		// properties and nested weak types. The following are motivating examples that all should be errors, but
		// aren't without this extra property check:
		//
		//   let obj: { a: { x: string } } & { c: number } = { a: { x: 'hello', y: 2 }, c: 5 };  // Nested excess property
		//
		//   declare let wrong: { a: { y: string } };
		//   let weak: { a?: { x?: number } } & { c?: string } = wrong;  // Nested weak object type
		//
		case result != TernaryFalse && intersectionState&IntersectionStateTarget == 0 && target.flags&TypeFlagsIntersection != 0 && !r.c.isGenericObjectType(target) && source.flags&(TypeFlagsObject|TypeFlagsIntersection) != 0:
			result &= r.propertiesRelatedTo(source, target, reportErrors, collections.Set[string]{} /*excludedProperties*/, false /*optionalsOnly*/, IntersectionStateNone)
			if result != 0 && isObjectLiteralType(source) && source.objectFlags&ObjectFlagsFreshLiteral != 0 {
				result &= r.indexSignaturesRelatedTo(source, target, false /*sourceIsPrimitive*/, reportErrors, IntersectionStateNone)
			}
		// When the source is an intersection we need an extra check of any optional properties in the target to
		// detect possible mismatched property types. For example:
		//
		//   function foo<T extends object>(x: { a?: string }, y: T & { a: boolean }) {
		//     x = y;  // Mismatched property in source intersection
		//   }
		//
		case result != 0 && r.c.isNonGenericObjectType(target) && !r.c.isArrayOrTupleType(target) && r.isSourceIntersectionNeedingExtraCheck(source, target):
			result &= r.propertiesRelatedTo(source, target, reportErrors, collections.Set[string]{} /*excludedProperties*/, true /*optionalsOnly*/, intersectionState)
		}
	}
	if result != TernaryFalse {
		r.restoreErrorState(saveErrorState)
	}
	return result
}

func (r *Relater) isSourceIntersectionNeedingExtraCheck(source *Type, target *Type) bool {
	return source.flags&TypeFlagsIntersection != 0 && r.c.getApparentType(source).flags&TypeFlagsStructuredType != 0 &&
		!core.Some(source.Types(), func(t *Type) bool {
			return t == target || t.objectFlags&ObjectFlagsNonInferrableType != 0
		})
}

func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, reportErrors bool, intersectionState IntersectionState) Ternary {
	var result Ternary
	var varianceCheckFailed bool
	var originalErrorChain *ErrorChain
	saveErrorState := r.getErrorState()
	relateVariances := func(sourceTypeArguments []*Type, targetTypeArguments []*Type, variances []VarianceFlags, intersectionState IntersectionState) (Ternary, bool) {
		if result = r.typeArgumentsRelatedTo(sourceTypeArguments, targetTypeArguments, variances, reportErrors, intersectionState); result != TernaryFalse {
			return result, true
		}
		if core.Some(variances, func(v VarianceFlags) bool { return v&VarianceFlagsAllowsStructuralFallback != 0 }) {
			// If some type parameter was `Unmeasurable` or `Unreliable`, and we couldn't pass by assuming it was identical, then we
			// have to allow a structural fallback check
			// We elide the variance-based error elaborations, since those might not be too helpful, since we'll potentially
			// be assuming identity of the type parameter.
			originalErrorChain = nil
			r.restoreErrorState(saveErrorState)
			return TernaryFalse, false
		}
		allowStructuralFallback := r.c.hasCovariantVoidArgument(targetTypeArguments, variances)
		varianceCheckFailed = !allowStructuralFallback
		// The type arguments did not relate appropriately, but it may be because we have no variance
		// information (in which case typeArgumentsRelatedTo defaulted to covariance for all type
		// arguments). It might also be the case that the target type has a 'void' type argument for
		// a covariant type parameter that is only used in return positions within the generic type
		// (in which case any type argument is permitted on the source side). In those cases we proceed
		// with a structural comparison. Otherwise, we know for certain the instantiations aren't
		// related and we can return here.
		if len(variances) != 0 && !allowStructuralFallback {
			// In some cases generic types that are covariant in regular type checking mode become
			// invariant in --strictFunctionTypes mode because one or more type parameters are used in
			// both co- and contravariant positions. In order to make it easier to diagnose *why* such
			// types are invariant, if any of the type parameters are invariant we reset the reported
			// errors and instead force a structural comparison (which will include elaborations that
			// reveal the reason).
			// We can switch on `reportErrors` here, since varianceCheckFailed guarantees we return `False`,
			// we can return `False` early here to skip calculating the structural error message we don't need.
			if varianceCheckFailed && !(reportErrors && core.Some(variances, func(v VarianceFlags) bool { return (v & VarianceFlagsVarianceMask) == VarianceFlagsInvariant })) {
				return TernaryFalse, true
			}
			// We remember the original error information so we can restore it in case the structural
			// comparison unexpectedly succeeds. This can happen when the structural comparison result
			// is a Ternary.Maybe for example caused by the recursion depth limiter.
			originalErrorChain = r.errorChain
			r.restoreErrorState(saveErrorState)
		}
		return TernaryFalse, false
	}
	switch {
	case r.relation == r.c.identityRelation:
		// We've already checked that source.flags and target.flags are identical
		switch {
		case source.flags&TypeFlagsUnionOrIntersection != 0:
			result := r.eachTypeRelatedToSomeType(source, target)
			if result != TernaryFalse {
				result &= r.eachTypeRelatedToSomeType(target, source)
			}
			return result
		case source.flags&TypeFlagsIndex != 0:
			return r.isRelatedTo(source.Target(), target.Target(), RecursionFlagsBoth, false /*reportErrors*/)
		case source.flags&TypeFlagsIndexedAccess != 0:
			result = r.isRelatedTo(source.AsIndexedAccessType().objectType, target.AsIndexedAccessType().objectType, RecursionFlagsBoth, false /*reportErrors*/)
			if result != TernaryFalse {
				result &= r.isRelatedTo(source.AsIndexedAccessType().indexType, target.AsIndexedAccessType().indexType, RecursionFlagsBoth, false /*reportErrors*/)
				if result != TernaryFalse {
					return result
				}
			}
		case source.flags&TypeFlagsConditional != 0:
			if source.AsConditionalType().root.isDistributive == target.AsConditionalType().root.isDistributive {
				result = r.isRelatedTo(source.AsConditionalType().checkType, target.AsConditionalType().checkType, RecursionFlagsBoth, false /*reportErrors*/)
				if result != TernaryFalse {
					result &= r.isRelatedTo(source.AsConditionalType().extendsType, target.AsConditionalType().extendsType, RecursionFlagsBoth, false /*reportErrors*/)
					if result != TernaryFalse {
						result &= r.isRelatedTo(r.c.getTrueTypeFromConditionalType(source), r.c.getTrueTypeFromConditionalType(target), RecursionFlagsBoth, false /*reportErrors*/)
						if result != TernaryFalse {
							result &= r.isRelatedTo(r.c.getFalseTypeFromConditionalType(source), r.c.getFalseTypeFromConditionalType(target), RecursionFlagsBoth, false /*reportErrors*/)
							if result != TernaryFalse {
								return result
							}
						}
					}
				}
			}
		case source.flags&TypeFlagsSubstitution != 0:
			result = r.isRelatedTo(source.AsSubstitutionType().baseType, target.AsSubstitutionType().baseType, RecursionFlagsBoth, false /*reportErrors*/)
			if result != TernaryFalse {
				result &= r.isRelatedTo(source.AsSubstitutionType().constraint, target.AsSubstitutionType().constraint, RecursionFlagsBoth, false /*reportErrors*/)
				if result != TernaryFalse {
					return result
				}
			}
		case source.flags&TypeFlagsTemplateLiteral != 0:
			if slices.Equal(source.AsTemplateLiteralType().texts, target.AsTemplateLiteralType().texts) {
				result = TernaryTrue
				for i, sourceType := range source.AsTemplateLiteralType().types {
					targetType := target.AsTemplateLiteralType().types[i]
					result &= r.isRelatedTo(sourceType, targetType, RecursionFlagsBoth, false /*reportErrors*/)
					if result == TernaryFalse {
						return result
					}
				}
				return result
			}
		case source.flags&TypeFlagsStringMapping != 0:
			if source.AsStringMappingType().Symbol() == target.AsStringMappingType().Symbol() {
				return r.isRelatedTo(source.AsStringMappingType().target, target.AsStringMappingType().target, RecursionFlagsBoth, false /*reportErrors*/)
			}
		}
		if source.flags&TypeFlagsObject == 0 {
			return TernaryFalse
		}
	case source.flags&TypeFlagsUnionOrIntersection != 0 || target.flags&TypeFlagsUnionOrIntersection != 0:
		result = r.unionOrIntersectionRelatedTo(source, target, reportErrors, intersectionState)
		if result != TernaryFalse {
			return result
		}
		// The ordered decomposition above doesn't handle all cases. Specifically, we also need to handle:
		// Source is instantiable (e.g. source has union or intersection constraint).
		// Source is an object, target is a union (e.g. { a, b: boolean } <=> { a, b: true } | { a, b: false }).
		// Source is an intersection, target is an object (e.g. { a } & { b } <=> { a, b }).
		// Source is an intersection, target is a union (e.g. { a } & { b: boolean } <=> { a, b: true } | { a, b: false }).
		// Source is an intersection, target instantiable (e.g. string & { tag } <=> T["a"] constrained to string & { tag }).
		if !(source.flags&TypeFlagsInstantiable != 0 ||
			source.flags&TypeFlagsObject != 0 && target.flags&TypeFlagsUnion != 0 ||
			source.flags&TypeFlagsIntersection != 0 && target.flags&(TypeFlagsObject|TypeFlagsUnion|TypeFlagsInstantiable) != 0) {
			return TernaryFalse
		}
	}
	// We limit alias variance probing to only object and conditional types since their alias behavior
	// is more predictable than other, interned types, which may or may not have an alias depending on
	// the order in which things were checked.
	if source.flags&(TypeFlagsObject|TypeFlagsConditional) != 0 && source.alias != nil && len(source.alias.typeArguments) != 0 &&
		target.alias != nil && source.alias.symbol == target.alias.symbol && !(r.c.isMarkerType(source) || r.c.isMarkerType(target)) {
		variances := r.c.getAliasVariances(source.alias.symbol)
		if len(variances) == 0 {
			return TernaryUnknown
		}
		params := r.c.typeAliasLinks.Get(source.alias.symbol).typeParameters
		minParams := r.c.getMinTypeArgumentCount(params)
		nodeIsInJsFile := ast.IsInJSFile(source.alias.symbol.ValueDeclaration)
		sourceTypes := r.c.fillMissingTypeArguments(source.alias.typeArguments, params, minParams, nodeIsInJsFile)
		targetTypes := r.c.fillMissingTypeArguments(target.alias.typeArguments, params, minParams, nodeIsInJsFile)
		varianceResult, ok := relateVariances(sourceTypes, targetTypes, variances, intersectionState)
		if ok {
			return varianceResult
		}
	}
	// For a generic type T and a type U that is assignable to T, [...U] is assignable to T, U is assignable to readonly [...T],
	// and U is assignable to [...T] when U is constrained to a mutable array or tuple type.
	if isSingleElementGenericTupleType(source) && !source.TargetTupleType().readonly {
		result = r.isRelatedTo(r.c.getTypeArguments(source)[0], target, RecursionFlagsSource, false /*reportErrors*/)
		if result != TernaryFalse {
			return result
		}
	}
	if isSingleElementGenericTupleType(target) && (target.TargetTupleType().readonly || r.c.isMutableArrayOrTuple(r.c.getBaseConstraintOrType(source))) {
		result = r.isRelatedTo(source, r.c.getTypeArguments(target)[0], RecursionFlagsTarget, false /*reportErrors*/)
		if result != TernaryFalse {
			return result
		}
	}
	switch {
	case target.flags&TypeFlagsTypeParameter != 0:
		// A source type { [P in Q]: X } is related to a target type T if keyof T is related to Q and X is related to T[Q].
		if source.objectFlags&ObjectFlagsMapped != 0 && source.AsMappedType().declaration.NameType == nil && r.isRelatedTo(r.c.getIndexType(target), r.c.getConstraintTypeFromMappedType(source), RecursionFlagsBoth, false) != TernaryFalse {
			if getMappedTypeModifiers(source)&MappedTypeModifiersIncludeOptional == 0 {
				templateType := r.c.getTemplateTypeFromMappedType(source)
				indexedAccessType := r.c.getIndexedAccessType(target, r.c.getTypeParameterFromMappedType(source))
				result = r.isRelatedTo(templateType, indexedAccessType, RecursionFlagsBoth, reportErrors)
				if result != TernaryFalse {
					return result
				}
			}
		}
		if r.relation == r.c.comparableRelation && source.flags&TypeFlagsTypeParameter != 0 {
			// This is a carve-out in comparability to essentially forbid comparing a type parameter with another type parameter
			// unless one extends the other. (Remember: comparability is mostly bidirectional!)
			if constraint := r.c.getConstraintOfTypeParameter(source); constraint != nil && someType(constraint, func(c *Type) bool { return c.flags&TypeFlagsTypeParameter != 0 }) {
				return r.isRelatedTo(constraint, target, RecursionFlagsSource, false /*reportErrors*/)
			}
			return TernaryFalse
		}
	case target.flags&TypeFlagsIndexedAccess != 0:
		if source.flags&TypeFlagsIndexedAccess != 0 {
			// Relate components directly before falling back to constraint relationships
			// A type S[K] is related to a type T[J] if S is related to T and K is related to J.
			result = r.isRelatedTo(source.AsIndexedAccessType().objectType, target.AsIndexedAccessType().objectType, RecursionFlagsBoth, reportErrors)
			if result != TernaryFalse {
				result &= r.isRelatedTo(source.AsIndexedAccessType().indexType, target.AsIndexedAccessType().indexType, RecursionFlagsBoth, reportErrors)
			}
			if result != TernaryFalse {
				return result
			}
			if reportErrors {
				originalErrorChain = r.errorChain
			}
		}
		// A type S is related to a type T[K] if S is related to C, where C is the base
		// constraint of T[K] for writing.
		if r.relation == r.c.assignableRelation || r.relation == r.c.comparableRelation {
			objectType := target.AsIndexedAccessType().objectType
			indexType := target.AsIndexedAccessType().indexType
			baseObjectType := r.c.getBaseConstraintOrType(objectType)
			baseIndexType := r.c.getBaseConstraintOrType(indexType)
			if !r.c.isGenericObjectType(baseObjectType) && !r.c.isGenericIndexType(baseIndexType) {
				accessFlags := AccessFlagsWriting | (core.IfElse(baseObjectType != objectType, AccessFlagsNoIndexSignatures, 0))
				constraint := r.c.getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType, accessFlags, nil, nil)
				if constraint != nil {
					if reportErrors && originalErrorChain != nil {
						// create a new chain for the constraint error
						r.restoreErrorState(saveErrorState)
					}
					result = r.isRelatedToEx(source, constraint, RecursionFlagsTarget, reportErrors, nil /*headMessage*/, intersectionState)
					if result != TernaryFalse {
						return result
					}
					// prefer the shorter chain of the constraint comparison chain, and the direct comparison chain
					if reportErrors && originalErrorChain != nil && r.errorChain != nil {
						if chainDepth(originalErrorChain) <= chainDepth(r.errorChain) {
							r.errorChain = originalErrorChain
						}
					}
				}
			}
		}
		if reportErrors {
			originalErrorChain = nil
		}
	case target.flags&TypeFlagsIndex != 0:
		targetType := target.AsIndexType().target
		// A keyof S is related to a keyof T if T is related to S.
		if source.flags&TypeFlagsIndex != 0 {
			result = r.isRelatedTo(targetType, source.AsIndexType().target, RecursionFlagsBoth, false /*reportErrors*/)
			if result != TernaryFalse {
				return result
			}
		}
		if isTupleType(targetType) {
			// An index type can have a tuple type target when the tuple type contains variadic elements.
			// Check if the source is related to the known keys of the tuple type.
			result = r.isRelatedTo(source, r.c.getKnownKeysOfTupleType(targetType), RecursionFlagsTarget, reportErrors)
			if result != TernaryFalse {
				return result
			}
		} else {
			// A type S is assignable to keyof T if S is assignable to keyof C, where C is the
			// simplified form of T or, if T doesn't simplify, the constraint of T.
			constraint := r.c.getSimplifiedTypeOrConstraint(targetType)
			if constraint != nil {
				// We require Ternary.True here such that circular constraints don't cause
				// false positives. For example, given 'T extends { [K in keyof T]: string }',
				// 'keyof T' has itself as its constraint and produces a Ternary.Maybe when
				// related to other types.
				if r.isRelatedTo(source, r.c.getIndexTypeEx(constraint, target.AsIndexType().indexFlags|IndexFlagsNoReducibleCheck), RecursionFlagsTarget, reportErrors) == TernaryTrue {
					return TernaryTrue
				}
			} else if r.c.isGenericMappedType(targetType) {
				// generic mapped types that don't simplify or have a constraint still have a very simple set of keys we can compare against
				// - their nameType or constraintType.
				// In many ways, this comparison is a deferred version of what `getIndexTypeForMappedType` does to actually resolve the keys for _non_-generic types
				nameType := r.c.getNameTypeFromMappedType(targetType)
				constraintType := r.c.getConstraintTypeFromMappedType(targetType)
				var targetKeys *Type
				if nameType != nil && r.c.isMappedTypeWithKeyofConstraintDeclaration(targetType) {
					// we need to get the apparent mappings and union them with the generic mappings, since some properties may be
					// missing from the `constraintType` which will otherwise be mapped in the object
					mappedKeys := r.c.getApparentMappedTypeKeys(nameType, targetType)
					// We still need to include the non-apparent (and thus still generic) keys in the target side of the comparison (in case they're in the source side)
					targetKeys = r.c.getUnionType([]*Type{mappedKeys, nameType})
				} else if nameType != nil {
					targetKeys = nameType
				} else {
					targetKeys = constraintType
				}
				if r.isRelatedTo(source, targetKeys, RecursionFlagsTarget, reportErrors) == TernaryTrue {
					return TernaryTrue
				}
			}
		}
	case target.flags&TypeFlagsConditional != 0:
		// If we reach 10 levels of nesting for the same conditional type, assume it is an infinitely expanding recursive
		// conditional type and bail out with a Ternary.Maybe result.
		if r.c.isDeeplyNestedType(target, r.targetStack, 10) {
			return TernaryMaybe
		}
		c := target.AsConditionalType()
		// We check for a relationship to a conditional type target only when the conditional type has no
		// 'infer' positions, is not distributive or is distributive but doesn't reference the check type
		// parameter in either of the result types, and the source isn't an instantiation of the same
		// conditional type (as happens when computing variance).
		if c.root.inferTypeParameters == nil && !r.c.isDistributionDependent(c.root) && !(source.flags&TypeFlagsConditional != 0 && source.AsConditionalType().root == c.root) {
			// Check if the conditional is always true or always false but still deferred for distribution purposes.
			skipTrue := !r.c.isTypeAssignableTo(r.c.getPermissiveInstantiation(c.checkType), r.c.getPermissiveInstantiation(c.extendsType))
			skipFalse := !skipTrue && r.c.isTypeAssignableTo(r.c.getRestrictiveInstantiation(c.checkType), r.c.getRestrictiveInstantiation(c.extendsType))
			// TODO: Find a nice way to include potential conditional type breakdowns in error output, if they seem good (they usually don't)
			if skipTrue {
				result = TernaryTrue
			} else {
				result = r.isRelatedToEx(source, r.c.getTrueTypeFromConditionalType(target), RecursionFlagsTarget, false /*reportErrors*/, nil /*headMessage*/, intersectionState)
			}
			if result != TernaryFalse {
				if skipFalse {
					result &= TernaryTrue
				} else {
					result &= r.isRelatedToEx(source, r.c.getFalseTypeFromConditionalType(target), RecursionFlagsTarget, false /*reportErrors*/, nil /*headMessage*/, intersectionState)
				}
				if result != TernaryFalse {
					return result
				}
			}
		}
	case target.flags&TypeFlagsTemplateLiteral != 0:
		if source.flags&TypeFlagsTemplateLiteral != 0 {
			if r.relation == r.c.comparableRelation {
				if r.c.templateLiteralTypesDefinitelyUnrelated(source.AsTemplateLiteralType(), target.AsTemplateLiteralType()) {
					return TernaryFalse
				}
				return TernaryTrue
			}
			// Report unreliable variance for type variables referenced in template literal type placeholders.
			// For example, `foo-${number}` is related to `foo-${string}` even though number isn't related to string.
			r.c.instantiateType(source, r.c.reportUnreliableMapper)
		}
		if r.c.isTypeMatchedByTemplateLiteralType(source, target.AsTemplateLiteralType()) {
			return TernaryTrue
		}
	case target.flags&TypeFlagsStringMapping != 0:
		if source.flags&TypeFlagsStringMapping == 0 {
			if r.c.isMemberOfStringMapping(source, target) {
				return TernaryTrue
			}
		}
	case r.c.isGenericMappedType(target) && r.relation != r.c.identityRelation:
		// Check if source type `S` is related to target type `{ [P in Q]: T }` or `{ [P in Q as R]: T}`.
		keysRemapped := target.AsMappedType().declaration.NameType != nil
		templateType := r.c.getTemplateTypeFromMappedType(target)
		modifiers := getMappedTypeModifiers(target)
		if modifiers&MappedTypeModifiersExcludeOptional == 0 {
			// If the mapped type has shape `{ [P in Q]: T[P] }`,
			// source `S` is related to target if `T` = `S`, i.e. `S` is related to `{ [P in Q]: S[P] }`.
			if !keysRemapped && templateType.flags&TypeFlagsIndexedAccess != 0 && templateType.AsIndexedAccessType().objectType == source && templateType.AsIndexedAccessType().indexType == r.c.getTypeParameterFromMappedType(target) {
				return TernaryTrue
			}
			if !r.c.isGenericMappedType(source) {
				// If target has shape `{ [P in Q as R]: T}`, then its keys have type `R`.
				// If target has shape `{ [P in Q]: T }`, then its keys have type `Q`.
				var targetKeys *Type
				if keysRemapped {
					targetKeys = r.c.getNameTypeFromMappedType(target)
				} else {
					targetKeys = r.c.getConstraintTypeFromMappedType(target)
				}
				// Type of the keys of source type `S`, i.e. `keyof S`.
				sourceKeys := r.c.getIndexTypeEx(source, IndexFlagsNoIndexSignatures)
				includeOptional := modifiers&MappedTypeModifiersIncludeOptional != 0
				var filteredByApplicability *Type
				if includeOptional {
					filteredByApplicability = r.c.intersectTypes(targetKeys, sourceKeys)
				}
				// A source type `S` is related to a target type `{ [P in Q]: T }` if `Q` is related to `keyof S` and `S[Q]` is related to `T`.
				// A source type `S` is related to a target type `{ [P in Q as R]: T }` if `R` is related to `keyof S` and `S[R]` is related to `T.
				// A source type `S` is related to a target type `{ [P in Q]?: T }` if some constituent `Q'` of `Q` is related to `keyof S` and `S[Q']` is related to `T`.
				// A source type `S` is related to a target type `{ [P in Q as R]?: T }` if some constituent `R'` of `R` is related to `keyof S` and `S[R']` is related to `T`.
				if includeOptional && filteredByApplicability.flags&TypeFlagsNever == 0 || !includeOptional && r.isRelatedTo(targetKeys, sourceKeys, RecursionFlagsBoth, false) != TernaryFalse {
					templateType := r.c.getTemplateTypeFromMappedType(target)
					typeParameter := r.c.getTypeParameterFromMappedType(target)
					// Fastpath: When the template type has the form `Obj[P]` where `P` is the mapped type parameter, directly compare source `S` with `Obj`
					// to avoid creating the (potentially very large) number of new intermediate types made by manufacturing `S[P]`.
					nonNullComponent := r.c.extractTypesOfKind(templateType, ^TypeFlagsNullable)
					if !keysRemapped && nonNullComponent.flags&TypeFlagsIndexedAccess != 0 && nonNullComponent.AsIndexedAccessType().indexType == typeParameter {
						result = r.isRelatedTo(source, nonNullComponent.AsIndexedAccessType().objectType, RecursionFlagsTarget, reportErrors)
						if result != TernaryFalse {
							return result
						}
					} else {
						// We need to compare the type of a property on the source type `S` to the type of the same property on the target type,
						// so we need to construct an indexing type representing a property, and then use indexing type to index the source type for comparison.
						// If the target type has shape `{ [P in Q]: T }`, then a property of the target has type `P`.
						// If the target type has shape `{ [P in Q]?: T }`, then a property of the target has type `P`,
						// but the property is optional, so we only want to compare properties `P` that are common between `keyof S` and `Q`.
						// If the target type has shape `{ [P in Q as R]: T }`, then a property of the target has type `R`.
						// If the target type has shape `{ [P in Q as R]?: T }`, then a property of the target has type `R`,
						// but the property is optional, so we only want to compare properties `R` that are common between `keyof S` and `R`.
						indexingType := typeParameter
						switch {
						case keysRemapped:
							indexingType = core.OrElse(filteredByApplicability, targetKeys)
						case filteredByApplicability != nil:
							indexingType = r.c.getIntersectionType([]*Type{filteredByApplicability, typeParameter})
						}
						indexedAccessType := r.c.getIndexedAccessType(source, indexingType)
						// Compare `S[indexingType]` to `T`, where `T` is the type of a property of the target type.
						result = r.isRelatedTo(indexedAccessType, templateType, RecursionFlagsBoth, reportErrors)
						if result != TernaryFalse {
							return result
						}
					}
				}
				originalErrorChain = r.errorChain
				r.restoreErrorState(saveErrorState)
			}
		}
	}
	switch {
	case source.flags&TypeFlagsTypeVariable != 0:
		// IndexedAccess comparisons are handled above in the `target.flags&TypeFlagsIndexedAccess` branch
		if source.flags&TypeFlagsIndexedAccess == 0 || target.flags&TypeFlagsIndexedAccess == 0 {
			constraint := r.c.getConstraintOfType(source)
			if constraint == nil {
				constraint = r.c.unknownType
			}
			// hi-speed no-this-instantiation check (less accurate, but avoids costly `this`-instantiation when the constraint will suffice), see #28231 for report on why this is needed
			result = r.isRelatedToEx(constraint, target, RecursionFlagsSource, false /*reportErrors*/, nil /*headMessage*/, intersectionState)
			if result != TernaryFalse {
				return result
			}
			constraintWithThis := r.c.getTypeWithThisArgument(constraint, source, false /*needApparentType*/)
			result = r.isRelatedToEx(constraintWithThis, target, RecursionFlagsSource, reportErrors && constraint != r.c.unknownType && target.flags&source.flags&TypeFlagsTypeParameter == 0, nil /*headMessage*/, intersectionState)
			if result != TernaryFalse {
				return result
			}
			if r.c.isMappedTypeGenericIndexedAccess(source) {
				// For an indexed access type { [P in K]: E}[X], above we have already explored an instantiation of E with X
				// substituted for P. We also want to explore type { [P in K]: E }[C], where C is the constraint of X.
				indexConstraint := r.c.getConstraintOfType(source.AsIndexedAccessType().indexType)
				if indexConstraint != nil {
					result = r.isRelatedTo(r.c.getIndexedAccessType(source.AsIndexedAccessType().objectType, indexConstraint), target, RecursionFlagsSource, reportErrors)
					if result != TernaryFalse {
						return result
					}
				}
			}
		}
	case source.flags&TypeFlagsIndex != 0:
		isDeferredMappedIndex := r.c.shouldDeferIndexType(source.AsIndexType().target, source.AsIndexType().indexFlags) && source.AsIndexType().target.objectFlags&ObjectFlagsMapped != 0
		result = r.isRelatedTo(r.c.stringNumberSymbolType, target, RecursionFlagsSource, reportErrors && !isDeferredMappedIndex)
		if result != TernaryFalse {
			return result
		}
		if isDeferredMappedIndex {
			mappedType := source.AsIndexType().target
			nameType := r.c.getNameTypeFromMappedType(mappedType)
			// Unlike on the target side, on the source side we do *not* include the generic part of the `nameType`, since that comes from a
			// (potentially anonymous) mapped type local type parameter, so that'd never assign outside the mapped type body, but we still want to
			// allow assignments of index types of identical (or similar enough) mapped types.
			// eg, `keyof {[X in keyof A]: Obj[X]}` should be assignable to `keyof {[Y in keyof A]: Tup[Y]}` because both map over the same set of keys (`keyof A`).
			// Without this source-side breakdown, a `keyof {[X in keyof A]: Obj[X]}` style type won't be assignable to anything except itself, which is much too strict.
			var sourceMappedKeys *Type
			if nameType != nil && r.c.isMappedTypeWithKeyofConstraintDeclaration(mappedType) {
				sourceMappedKeys = r.c.getApparentMappedTypeKeys(nameType, mappedType)
			} else if nameType != nil {
				sourceMappedKeys = nameType
			} else {
				sourceMappedKeys = r.c.getConstraintTypeFromMappedType(mappedType)
			}
			result = r.isRelatedTo(sourceMappedKeys, target, RecursionFlagsSource, reportErrors)
			if result != TernaryFalse {
				return result
			}
		}
	case source.flags&TypeFlagsConditional != 0:
		// If we reach 10 levels of nesting for the same conditional type, assume it is an infinitely expanding recursive
		// conditional type and bail out with a Ternary.Maybe result.
		if r.c.isDeeplyNestedType(source, r.sourceStack, 10) {
			return TernaryMaybe
		}
		if target.flags&TypeFlagsConditional != 0 {
			// Two conditional types 'T1 extends U1 ? X1 : Y1' and 'T2 extends U2 ? X2 : Y2' are related if
			// one of T1 and T2 is related to the other, U1 and U2 are identical types, X1 is related to X2,
			// and Y1 is related to Y2.
			sourceParams := source.AsConditionalType().root.inferTypeParameters
			sourceExtends := source.AsConditionalType().extendsType
			var mapper *TypeMapper
			if len(sourceParams) != 0 {
				// If the source has infer type parameters, we instantiate them in the context of the target
				ctx := r.c.newInferenceContext(sourceParams, nil /*signature*/, InferenceFlagsNone, r.isRelatedToWorker)
				r.c.inferTypes(ctx.inferences, target.AsConditionalType().extendsType, sourceExtends, InferencePriorityNoConstraints|InferencePriorityAlwaysStrict, false)
				sourceExtends = r.c.instantiateType(sourceExtends, ctx.mapper)
				mapper = ctx.mapper
			}
			if r.c.isTypeIdenticalTo(sourceExtends, target.AsConditionalType().extendsType) && (r.isRelatedTo(source.AsConditionalType().checkType, target.AsConditionalType().checkType, RecursionFlagsBoth, false) != 0 || r.isRelatedTo(target.AsConditionalType().checkType, source.AsConditionalType().checkType, RecursionFlagsBoth, false) != 0) {
				result = r.isRelatedTo(r.c.instantiateType(r.c.getTrueTypeFromConditionalType(source), mapper), r.c.getTrueTypeFromConditionalType(target), RecursionFlagsBoth, reportErrors)
				if result != TernaryFalse {
					result &= r.isRelatedTo(r.c.getFalseTypeFromConditionalType(source), r.c.getFalseTypeFromConditionalType(target), RecursionFlagsBoth, reportErrors)
				}
				if result != TernaryFalse {
					return result
				}
			}
		}
		// conditionals can be related to one another via normal constraint, as, eg, `A extends B ? O : never` should be assignable to `O`
		// when `O` is a conditional (`never` is trivially assignable to `O`, as is `O`!).
		defaultConstraint := r.c.getDefaultConstraintOfConditionalType(source)
		if defaultConstraint != nil {
			result = r.isRelatedTo(defaultConstraint, target, RecursionFlagsSource, reportErrors)
			if result != TernaryFalse {
				return result
			}
		}
		// conditionals aren't related to one another via distributive constraint as it is much too inaccurate and allows way
		// more assignments than are desirable (since it maps the source check type to its constraint, it loses information).
		if target.flags&TypeFlagsConditional == 0 && r.c.hasNonCircularBaseConstraint(source) {
			distributiveConstraint := r.c.getConstraintOfDistributiveConditionalType(source)
			if distributiveConstraint != nil {
				r.restoreErrorState(saveErrorState)
				result = r.isRelatedTo(distributiveConstraint, target, RecursionFlagsSource, reportErrors)
				if result != TernaryFalse {
					return result
				}
			}
		}
	case source.flags&TypeFlagsTemplateLiteral != 0 && target.flags&TypeFlagsObject == 0:
		if target.flags&TypeFlagsTemplateLiteral == 0 {
			constraint := r.c.getBaseConstraintOfType(source)
			if constraint != nil && constraint != source {
				result = r.isRelatedTo(constraint, target, RecursionFlagsSource, reportErrors)
				if result != TernaryFalse {
					return result
				}
			}
		}
	case source.flags&TypeFlagsStringMapping != 0:
		if target.flags&TypeFlagsStringMapping != 0 {
			if source.AsStringMappingType().symbol != target.AsStringMappingType().symbol {
				return TernaryFalse
			}
			result = r.isRelatedTo(source.AsStringMappingType().target, target.AsStringMappingType().target, RecursionFlagsBoth, reportErrors)
			if result != TernaryFalse {
				return result
			}
		} else {
			constraint := r.c.getBaseConstraintOfType(source)
			if constraint != nil {
				result = r.isRelatedTo(constraint, target, RecursionFlagsSource, reportErrors)
				if result != TernaryFalse {
					return result
				}
			}
		}
	default:
		// An empty object type is related to any mapped type that includes a '?' modifier.
		if r.relation != r.c.subtypeRelation && r.relation != r.c.strictSubtypeRelation && isPartialMappedType(target) && r.c.isEmptyObjectType(source) {
			return TernaryTrue
		}
		if r.c.isGenericMappedType(target) {
			if r.c.isGenericMappedType(source) {
				result = r.mappedTypeRelatedTo(source, target, reportErrors)
				if result != TernaryFalse {
					return result
				}
			}
			return TernaryFalse
		}
		sourceIsPrimitive := source.flags&TypeFlagsPrimitive != 0
		if r.relation != r.c.identityRelation {
			source = r.c.getApparentType(source)
		} else if r.c.isGenericMappedType(source) {
			return TernaryFalse
		}
		switch {
		case source.objectFlags&ObjectFlagsReference != 0 && target.objectFlags&ObjectFlagsReference != 0 && source.Target() == target.Target() && !isTupleType(source) && !r.c.isMarkerType(source) && !r.c.isMarkerType(target):
			// When strictNullChecks is disabled, the element type of the empty array literal is undefinedWideningType,
			// and an empty array literal wouldn't be assignable to a `never[]` without this check.
			if r.c.isEmptyArrayLiteralType(source) {
				return TernaryTrue
			}
			// We have type references to the same generic type, and the type references are not marker
			// type references (which are intended by be compared structurally). Obtain the variance
			// information for the type parameters and relate the type arguments accordingly.
			variances := r.c.getVariances(source.Target())
			// We return Ternary.Maybe for a recursive invocation of getVariances (signaled by emptyArray). This
			// effectively means we measure variance only from type parameter occurrences that aren't nested in
			// recursive instantiations of the generic type.
			if len(variances) == 0 {
				return TernaryUnknown
			}
			varianceResult, ok := relateVariances(r.c.getTypeArguments(source), r.c.getTypeArguments(target), variances, intersectionState)
			if ok {
				return varianceResult
			}
		case r.c.isArrayType(target) && (r.c.isReadonlyArrayType(target) && everyType(source, r.c.isArrayOrTupleType) || everyType(source, isMutableTupleType)):
			if r.relation != r.c.identityRelation {
				return r.isRelatedTo(r.c.getIndexTypeOfTypeEx(source, r.c.numberType, r.c.anyType), r.c.getIndexTypeOfTypeEx(target, r.c.numberType, r.c.anyType), RecursionFlagsBoth, reportErrors)
			}
			// By flags alone, we know that the `target` is a readonly array while the source is a normal array or tuple
			// or `target` is an array and source is a tuple - in both cases the types cannot be identical, by construction
			return TernaryFalse
		case r.c.isGenericTupleType(source) && isTupleType(target) && !r.c.isGenericTupleType(target):
			constraint := r.c.getBaseConstraintOrType(source)
			if constraint != source {
				return r.isRelatedTo(constraint, target, RecursionFlagsSource, reportErrors)
			}
		case (r.relation == r.c.subtypeRelation || r.relation == r.c.strictSubtypeRelation) && r.c.isEmptyObjectType(target) && target.objectFlags&ObjectFlagsFreshLiteral != 0 && !r.c.isEmptyObjectType(source):
			return TernaryFalse
		}
		// Even if relationship doesn't hold for unions, intersections, or generic type references,
		// it may hold in a structural comparison.
		// In a check of the form X = A & B, we will have previously checked if A relates to X or B relates
		// to X. Failing both of those we want to check if the aggregation of A and B's members structurally
		// relates to X. Thus, we include intersection types on the source side here.
		if source.flags&(TypeFlagsObject|TypeFlagsIntersection) != 0 && target.flags&TypeFlagsObject != 0 {
			// Report structural errors only if we haven't reported any errors yet
			reportStructuralErrors := reportErrors && r.errorChain == saveErrorState.errorChain && !sourceIsPrimitive
			result = r.propertiesRelatedTo(source, target, reportStructuralErrors, collections.Set[string]{} /*excludedProperties*/, false /*optionalsOnly*/, intersectionState)
			if result != TernaryFalse {
				result &= r.signaturesRelatedTo(source, target, SignatureKindCall, reportStructuralErrors, intersectionState)
				if result != TernaryFalse {
					result &= r.signaturesRelatedTo(source, target, SignatureKindConstruct, reportStructuralErrors, intersectionState)
					if result != TernaryFalse {
						result &= r.indexSignaturesRelatedTo(source, target, sourceIsPrimitive, reportStructuralErrors, intersectionState)
					}
				}
			}
			if result != TernaryFalse {
				if !varianceCheckFailed {
					return result
				}
				if originalErrorChain != nil {
					r.errorChain = originalErrorChain
				} else if r.errorChain == nil {
					r.errorChain = saveErrorState.errorChain
				}
				// Use variance error (there is no structural one) and return false
			}
		}
		// If S is an object type and T is a discriminated union, S may be related to T if
		// there exists a constituent of T for every combination of the discriminants of S
		// with respect to T. We do not report errors here, as we will use the existing
		// error result from checking each constituent of the union.
		if source.flags&(TypeFlagsObject|TypeFlagsIntersection) != 0 && target.flags&TypeFlagsUnion != 0 {
			objectOnlyTarget := r.c.extractTypesOfKind(target, TypeFlagsObject|TypeFlagsIntersection|TypeFlagsSubstitution)
			if objectOnlyTarget.flags&TypeFlagsUnion != 0 {
				result := r.typeRelatedToDiscriminatedType(source, objectOnlyTarget)
				if result != TernaryFalse {
					return result
				}
			}
		}
	}
	return TernaryFalse
}

func (r *Relater) typeArgumentsRelatedTo(sources []*Type, targets []*Type, variances []VarianceFlags, reportErrors bool, intersectionState IntersectionState) Ternary {
	if len(sources) != len(targets) && r.relation == r.c.identityRelation {
		return TernaryFalse
	}
	length := min(len(sources), len(targets))
	result := TernaryTrue
	for i := range length {
		// When variance information isn't available we default to covariance. This happens
		// in the process of computing variance information for recursive types and when
		// comparing 'this' type arguments.
		varianceFlags := VarianceFlagsCovariant
		if i < len(variances) {
			varianceFlags = variances[i]
		}
		variance := varianceFlags & VarianceFlagsVarianceMask
		// We ignore arguments for independent type parameters (because they're never witnessed).
		if variance != VarianceFlagsIndependent {
			s := sources[i]
			t := targets[i]
			var related Ternary
			if varianceFlags&VarianceFlagsUnmeasurable != 0 {
				// Even an `Unmeasurable` variance works out without a structural check if the source and target are _identical_.
				// We can't simply assume invariance, because `Unmeasurable` marks nonlinear relations, for example, a relation tainted by
				// the `-?` modifier in a mapped type (where, no matter how the inputs are related, the outputs still might not be)
				if r.relation == r.c.identityRelation {
					related = r.isRelatedTo(s, t, RecursionFlagsBoth, false /*reportErrors*/)
				} else {
					related = r.c.compareTypesIdentical(s, t)
				}
			} else if variance == VarianceFlagsCovariant {
				related = r.isRelatedToEx(s, t, RecursionFlagsBoth, reportErrors, nil /*headMessage*/, intersectionState)
			} else if variance == VarianceFlagsContravariant {
				related = r.isRelatedToEx(t, s, RecursionFlagsBoth, reportErrors, nil /*headMessage*/, intersectionState)
			} else if variance == VarianceFlagsBivariant {
				// In the bivariant case we first compare contravariantly without reporting
				// errors. Then, if that doesn't succeed, we compare covariantly with error
				// reporting. Thus, error elaboration will be based on the covariant check,
				// which is generally easier to reason about.
				related = r.isRelatedTo(t, s, RecursionFlagsBoth, false /*reportErrors*/)
				if related == TernaryFalse {
					related = r.isRelatedToEx(s, t, RecursionFlagsBoth, reportErrors, nil /*headMessage*/, intersectionState)
				}
			} else {
				// In the invariant case we first compare covariantly, and only when that
				// succeeds do we proceed to compare contravariantly. Thus, error elaboration
				// will typically be based on the covariant check.
				related = r.isRelatedToEx(s, t, RecursionFlagsBoth, reportErrors, nil /*headMessage*/, intersectionState)
				if related != TernaryFalse {
					related &= r.isRelatedToEx(t, s, RecursionFlagsBoth, reportErrors, nil /*headMessage*/, intersectionState)
				}
			}
			if related == TernaryFalse {
				return TernaryFalse
			}
			result &= related
		}
	}
	return result
}

// A type [P in S]: X is related to a type [Q in T]: Y if T is related to S and X' is
// related to Y, where X' is an instantiation of X in which P is replaced with Q. Notice
// that S and T are contra-variant whereas X and Y are co-variant.
func (r *Relater) mappedTypeRelatedTo(source *Type, target *Type, reportErrors bool) Ternary {
	modifiersRelated := r.relation == r.c.comparableRelation ||
		r.relation == r.c.identityRelation && getMappedTypeModifiers(source) == getMappedTypeModifiers(target) ||
		r.relation != r.c.identityRelation && r.c.getCombinedMappedTypeOptionality(source) <= r.c.getCombinedMappedTypeOptionality(target)
	if modifiersRelated {
		targetConstraint := r.c.getConstraintTypeFromMappedType(target)
		sourceConstraint := r.c.instantiateType(r.c.getConstraintTypeFromMappedType(source), core.IfElse(r.c.getCombinedMappedTypeOptionality(source) < 0, r.c.reportUnmeasurableMapper, r.c.reportUnreliableMapper))
		if result := r.isRelatedTo(targetConstraint, sourceConstraint, RecursionFlagsBoth, reportErrors); result != TernaryFalse {
			mapper := newSimpleTypeMapper(r.c.getTypeParameterFromMappedType(source), r.c.getTypeParameterFromMappedType(target))
			if r.c.instantiateType(r.c.getNameTypeFromMappedType(source), mapper) == r.c.instantiateType(r.c.getNameTypeFromMappedType(target), mapper) {
				return result & r.isRelatedTo(r.c.instantiateType(r.c.getTemplateTypeFromMappedType(source), mapper), r.c.getTemplateTypeFromMappedType(target), RecursionFlagsBoth, reportErrors)
			}
		}
	}
	return TernaryFalse
}

func (r *Relater) typeRelatedToDiscriminatedType(source *Type, target *Type) Ternary {
	// 1. Generate the combinations of discriminant properties & types 'source' can satisfy.
	//    a. If the number of combinations is above a set limit, the comparison is too complex.
	// 2. Filter 'target' to the subset of types whose discriminants exist in the matrix.
	//    a. If 'target' does not satisfy all discriminants in the matrix, 'source' is not related.
	// 3. For each type in the filtered 'target', determine if all non-discriminant properties of
	//    'target' are related to a property in 'source'.
	//
	// NOTE: See ~/tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts
	//       for examples.
	sourceProperties := r.c.getPropertiesOfType(source)
	sourcePropertiesFiltered := r.c.findDiscriminantProperties(sourceProperties, target)
	if len(sourcePropertiesFiltered) == 0 {
		return TernaryFalse
	}
	// Though we could compute the number of combinations as we generate
	// the matrix, this would incur additional memory overhead due to
	// array allocations. To reduce this overhead, we first compute
	// the number of combinations to ensure we will not surpass our
	// fixed limit before incurring the cost of any allocations:
	numCombinations := 1
	for _, sourceProperty := range sourcePropertiesFiltered {
		numCombinations *= countTypes(r.c.getNonMissingTypeOfSymbol(sourceProperty))
		if numCombinations == 0 || numCombinations > 25 {
			return TernaryFalse
		}
	}
	// Compute the set of types for each discriminant property.
	sourceDiscriminantTypes := make([][]*Type, len(sourcePropertiesFiltered))
	var excludedProperties collections.Set[string]
	for i, sourceProperty := range sourcePropertiesFiltered {
		sourcePropertyType := r.c.getNonMissingTypeOfSymbol(sourceProperty)
		sourceDiscriminantTypes[i] = sourcePropertyType.Distributed()
		excludedProperties.Add(sourceProperty.Name)
	}
	// Build the cartesian product
	discriminantCombinations := make([][]*Type, numCombinations)
	for i := range numCombinations {
		combination := make([]*Type, len(sourceDiscriminantTypes))
		n := i
		for j := len(sourceDiscriminantTypes) - 1; j >= 0; j-- {
			sourceTypes := sourceDiscriminantTypes[j]
			length := len(sourceTypes)
			combination[j] = sourceTypes[n%length]
			n = n / length
		}
		discriminantCombinations[i] = combination
	}
	// Match each combination of the cartesian product of discriminant properties to one or more
	// constituents of 'target'. If any combination does not have a match then 'source' is not relatable.
	var matchingTypes []*Type
	for _, combination := range discriminantCombinations {
		hasMatch := false
	outer:
		for _, t := range target.Types() {
			for i := range sourcePropertiesFiltered {
				sourceProperty := sourcePropertiesFiltered[i]
				targetProperty := r.c.getPropertyOfType(t, sourceProperty.Name)
				if targetProperty == nil {
					continue outer
				}
				if sourceProperty == targetProperty {
					continue
				}
				// We compare the source property to the target in the context of a single discriminant type.
				related := r.propertyRelatedTo(source, target, sourceProperty, targetProperty, func(*ast.Symbol) *Type { return combination[i] },
					false /*reportErrors*/, IntersectionStateNone, r.c.strictNullChecks || r.relation == r.c.comparableRelation /*skipOptional*/)
				// If the target property could not be found, or if the properties were not related,
				// then this constituent is not a match.
				if related == TernaryFalse {
					continue outer
				}
			}
			matchingTypes = core.AppendIfUnique(matchingTypes, t)
			hasMatch = true
		}
		if !hasMatch {
			// We failed to match any type for this combination.
			return TernaryFalse
		}
	}
	// Compare the remaining non-discriminant properties of each match.
	result := TernaryTrue
	for _, t := range matchingTypes {
		result &= r.propertiesRelatedTo(source, t /*reportErrors*/, false, excludedProperties /*optionalsOnly*/, false, IntersectionStateNone)
		if result != TernaryFalse {
			result &= r.signaturesRelatedTo(source, t, SignatureKindCall /*reportErrors*/, false, IntersectionStateNone)
			if result != TernaryFalse {
				result &= r.signaturesRelatedTo(source, t, SignatureKindConstruct /*reportErrors*/, false, IntersectionStateNone)
				if result != TernaryFalse && !(isTupleType(source) && isTupleType(t)) {
					// Comparing numeric index types when both `source` and `type` are tuples is unnecessary as the
					// element types should be sufficiently covered by `propertiesRelatedTo`. It also causes problems
					// with index type assignability as the types for the excluded discriminants are still included
					// in the index type.
					result &= r.indexSignaturesRelatedTo(source, t /*sourceIsPrimitive*/, false /*reportErrors*/, false, IntersectionStateNone)
				}
			}
		}
		if result == TernaryFalse {
			return result
		}
	}
	return result
}

func (r *Relater) propertiesRelatedTo(source *Type, target *Type, reportErrors bool, excludedProperties collections.Set[string], optionalsOnly bool, intersectionState IntersectionState) Ternary {
	if r.relation == r.c.identityRelation {
		return r.propertiesIdenticalTo(source, target, excludedProperties)
	}
	result := TernaryTrue
	if isTupleType(target) {
		if r.c.isArrayOrTupleType(source) {
			if !target.TargetTupleType().readonly && (r.c.isReadonlyArrayType(source) || isTupleType(source) && source.TargetTupleType().readonly) {
				return TernaryFalse
			}
			sourceArity := r.c.getTypeReferenceArity(source)
			targetArity := r.c.getTypeReferenceArity(target)
			var sourceRest bool
			if isTupleType(source) {
				sourceRest = source.TargetTupleType().combinedFlags&ElementFlagsRest != 0
			} else {
				sourceRest = true
			}
			targetHasRestElement := target.TargetTupleType().combinedFlags&ElementFlagsVariable != 0
			var sourceMinLength int
			if isTupleType(source) {
				sourceMinLength = source.TargetTupleType().minLength
			} else {
				sourceMinLength = 0
			}
			targetMinLength := target.TargetTupleType().minLength
			if !sourceRest && sourceArity < targetMinLength {
				if reportErrors {
					r.reportError(diagnostics.Source_has_0_element_s_but_target_requires_1, sourceArity, targetMinLength)
				}
				return TernaryFalse
			}
			if !targetHasRestElement && targetArity < sourceMinLength {
				if reportErrors {
					r.reportError(diagnostics.Source_has_0_element_s_but_target_allows_only_1, sourceMinLength, targetArity)
				}
				return TernaryFalse
			}
			if !targetHasRestElement && (sourceRest || targetArity < sourceArity) {
				if reportErrors {
					if sourceMinLength < targetMinLength {
						r.reportError(diagnostics.Target_requires_0_element_s_but_source_may_have_fewer, targetMinLength)
					} else {
						r.reportError(diagnostics.Target_allows_only_0_element_s_but_source_may_have_more, targetArity)
					}
				}
				return TernaryFalse
			}
			sourceTypeArguments := r.c.getTypeArguments(source)
			targetTypeArguments := r.c.getTypeArguments(target)
			targetStartCount := getStartElementCount(target.TargetTupleType(), ElementFlagsNonRest)
			targetEndCount := getEndElementCount(target.TargetTupleType(), ElementFlagsNonRest)
			canExcludeDiscriminants := excludedProperties.Len() != 0
			for sourcePosition := range sourceArity {
				var sourceFlags ElementFlags
				if isTupleType(source) {
					sourceFlags = source.TargetTupleType().elementInfos[sourcePosition].flags
				} else {
					sourceFlags = ElementFlagsRest
				}
				sourcePositionFromEnd := sourceArity - 1 - sourcePosition
				var targetPosition int
				if targetHasRestElement && sourcePosition >= targetStartCount {
					targetPosition = targetArity - 1 - min(sourcePositionFromEnd, targetEndCount)
				} else {
					targetPosition = sourcePosition
				}
				targetFlags := ElementFlagsNone
				if targetPosition >= 0 {
					targetFlags = target.TargetTupleType().elementInfos[targetPosition].flags
				}
				if targetFlags&ElementFlagsVariadic != 0 && sourceFlags&ElementFlagsVariadic == 0 {
					if reportErrors {
						r.reportError(diagnostics.Source_provides_no_match_for_variadic_element_at_position_0_in_target, targetPosition)
					}
					return TernaryFalse
				}
				if sourceFlags&ElementFlagsVariadic != 0 && targetFlags&ElementFlagsVariable == 0 {
					if reportErrors {
						r.reportError(diagnostics.Variadic_element_at_position_0_in_source_does_not_match_element_at_position_1_in_target, sourcePosition, targetPosition)
					}
					return TernaryFalse
				}
				if targetFlags&ElementFlagsRequired != 0 && sourceFlags&ElementFlagsRequired == 0 {
					if reportErrors {
						r.reportError(diagnostics.Source_provides_no_match_for_required_element_at_position_0_in_target, targetPosition)
					}
					return TernaryFalse
				}
				// We can only exclude discriminant properties if we have not yet encountered a variable-length element.
				if canExcludeDiscriminants {
					if sourceFlags&ElementFlagsVariable != 0 || targetFlags&ElementFlagsVariable != 0 {
						canExcludeDiscriminants = false
					}
					if canExcludeDiscriminants && excludedProperties.Has(strconv.Itoa(sourcePosition)) {
						continue
					}
				}
				sourceType := r.c.removeMissingType(sourceTypeArguments[sourcePosition], sourceFlags&targetFlags&ElementFlagsOptional != 0)
				targetType := targetTypeArguments[targetPosition]
				var targetCheckType *Type
				if sourceFlags&ElementFlagsVariadic != 0 && targetFlags&ElementFlagsRest != 0 {
					targetCheckType = r.c.createArrayType(targetType)
				} else {
					targetCheckType = r.c.removeMissingType(targetType, targetFlags&ElementFlagsOptional != 0)
				}
				related := r.isRelatedToEx(sourceType, targetCheckType, RecursionFlagsBoth, reportErrors, nil /*headMessage*/, intersectionState)
				if related == TernaryFalse {
					if reportErrors && (targetArity > 1 || sourceArity > 1) {
						if targetHasRestElement && sourcePosition >= targetStartCount && sourcePositionFromEnd >= targetEndCount && targetStartCount != sourceArity-targetEndCount-1 {
							r.reportError(diagnostics.Type_at_positions_0_through_1_in_source_is_not_compatible_with_type_at_position_2_in_target, targetStartCount, sourceArity-targetEndCount-1, targetPosition)
						} else {
							r.reportError(diagnostics.Type_at_position_0_in_source_is_not_compatible_with_type_at_position_1_in_target, sourcePosition, targetPosition)
						}
					}
					return TernaryFalse
				}
				result &= related
			}
			return result
		}
		if target.TargetTupleType().combinedFlags&ElementFlagsVariable != 0 {
			return TernaryFalse
		}
	}
	requireOptionalProperties := (r.relation == r.c.subtypeRelation || r.relation == r.c.strictSubtypeRelation) && !isObjectLiteralType(source) && !r.c.isEmptyArrayLiteralType(source) && !isTupleType(source)
	unmatchedProperty := r.c.getUnmatchedProperty(source, target, requireOptionalProperties, false /*matchDiscriminantProperties*/)
	if unmatchedProperty != nil {
		if reportErrors && r.c.shouldReportUnmatchedPropertyError(source, target) {
			r.reportUnmatchedProperty(source, target, unmatchedProperty, requireOptionalProperties)
		}
		return TernaryFalse
	}
	if isObjectLiteralType(target) {
		for _, sourceProp := range excludeProperties(r.c.getPropertiesOfType(source), excludedProperties) {
			if r.c.getPropertyOfObjectType(target, sourceProp.Name) == nil {
				if reportErrors {
					r.reportError(diagnostics.Property_0_does_not_exist_on_type_1, r.c.symbolToString(sourceProp), r.c.TypeToString(target))
				}
				return TernaryFalse
			}
		}
	}
	// We only call this for union target types when we're attempting to do excess property checking - in those cases, we want to get _all possible props_
	// from the target union, across all members
	properties := r.c.getPropertiesOfType(target)
	numericNamesOnly := isTupleType(source) && isTupleType(target)
	for _, targetProp := range excludeProperties(properties, excludedProperties) {
		name := targetProp.Name
		if targetProp.Flags&ast.SymbolFlagsPrototype == 0 && (!numericNamesOnly || isNumericLiteralName(name) || name == "length") && (!optionalsOnly || targetProp.Flags&ast.SymbolFlagsOptional != 0) {
			sourceProp := r.c.getPropertyOfType(source, name)
			if sourceProp != nil && sourceProp != targetProp {
				related := r.propertyRelatedTo(source, target, sourceProp, targetProp, r.c.getNonMissingTypeOfSymbol, reportErrors, intersectionState, r.relation == r.c.comparableRelation)
				if related == TernaryFalse {
					return TernaryFalse
				}
				result &= related
			}
		}
	}
	return result
}

func (r *Relater) propertyRelatedTo(source *Type, target *Type, sourceProp *ast.Symbol, targetProp *ast.Symbol, getTypeOfSourceProperty func(sym *ast.Symbol) *Type, reportErrors bool, intersectionState IntersectionState, skipOptional bool) Ternary {
	sourcePropFlags := getDeclarationModifierFlagsFromSymbol(sourceProp)
	targetPropFlags := getDeclarationModifierFlagsFromSymbol(targetProp)
	switch {
	case sourcePropFlags&ast.ModifierFlagsPrivate != 0 || targetPropFlags&ast.ModifierFlagsPrivate != 0:
		if sourceProp.ValueDeclaration != targetProp.ValueDeclaration {
			if reportErrors {
				if sourcePropFlags&ast.ModifierFlagsPrivate != 0 && targetPropFlags&ast.ModifierFlagsPrivate != 0 {
					r.reportError(diagnostics.Types_have_separate_declarations_of_a_private_property_0, r.c.symbolToString(targetProp))
				} else {
					r.reportError(diagnostics.Property_0_is_private_in_type_1_but_not_in_type_2, r.c.symbolToString(targetProp), r.c.TypeToString(core.IfElse(sourcePropFlags&ast.ModifierFlagsPrivate != 0, source, target)), r.c.TypeToString(core.IfElse(sourcePropFlags&ast.ModifierFlagsPrivate != 0, target, source)))
				}
			}
			return TernaryFalse
		}
	case targetPropFlags&ast.ModifierFlagsProtected != 0:
		if !r.c.isValidOverrideOf(sourceProp, targetProp) {
			if reportErrors {
				sourceType := core.OrElse(r.c.getDeclaringClass(sourceProp), source)
				targetType := core.OrElse(r.c.getDeclaringClass(targetProp), target)
				r.reportError(diagnostics.Property_0_is_protected_but_type_1_is_not_a_class_derived_from_2, r.c.symbolToString(targetProp), r.c.TypeToString(sourceType), r.c.TypeToString(targetType))
			}
			return TernaryFalse
		}
	case sourcePropFlags&ast.ModifierFlagsProtected != 0:
		if reportErrors {
			r.reportError(diagnostics.Property_0_is_protected_in_type_1_but_public_in_type_2, r.c.symbolToString(targetProp), r.c.TypeToString(source), r.c.TypeToString(target))
		}
		return TernaryFalse
	}
	// Ensure {readonly a: whatever} is not a subtype of {a: whatever},
	// while {a: whatever} is a subtype of {readonly a: whatever}.
	// This ensures the subtype relationship is ordered, and preventing declaration order
	// from deciding which type "wins" in union subtype reduction.
	// They're still assignable to one another, since `readonly` doesn't affect assignability.
	// This is only applied during the strictSubtypeRelation -- currently used in subtype reduction
	if r.relation == r.c.strictSubtypeRelation && r.c.isReadonlySymbol(sourceProp) && !r.c.isReadonlySymbol(targetProp) {
		return TernaryFalse
	}
	// If the target comes from a partial union prop, allow `undefined` in the target type
	related := r.isPropertySymbolTypeRelated(sourceProp, targetProp, getTypeOfSourceProperty, reportErrors, intersectionState)
	if related == TernaryFalse {
		if reportErrors {
			r.reportError(diagnostics.Types_of_property_0_are_incompatible, r.c.symbolToString(targetProp))
		}
		return TernaryFalse
	}
	// When checking for comparability, be more lenient with optional properties.
	if !skipOptional && sourceProp.Flags&ast.SymbolFlagsOptional != 0 && targetProp.Flags&ast.SymbolFlagsClassMember != 0 && targetProp.Flags&ast.SymbolFlagsOptional == 0 {
		// TypeScript 1.0 spec (April 2014): 3.8.3
		// S is a subtype of a type T, and T is a supertype of S if ...
		// S' and T are object types and, for each member M in T..
		// M is a property and S' contains a property N where
		// if M is a required property, N is also a required property
		// (M - property in T)
		// (N - property in S)
		if reportErrors {
			r.reportError(diagnostics.Property_0_is_optional_in_type_1_but_required_in_type_2, r.c.symbolToString(targetProp), r.c.TypeToString(source), r.c.TypeToString(target))
		}
		return TernaryFalse
	}
	return related
}

func (r *Relater) isPropertySymbolTypeRelated(sourceProp *ast.Symbol, targetProp *ast.Symbol, getTypeOfSourceProperty func(sym *ast.Symbol) *Type, reportErrors bool, intersectionState IntersectionState) Ternary {
	targetIsOptional := r.c.strictNullChecks && targetProp.CheckFlags&ast.CheckFlagsPartial != 0
	effectiveTarget := r.c.addOptionalityEx(r.c.getNonMissingTypeOfSymbol(targetProp), false /*isProperty*/, targetIsOptional)
	// source could resolve to `any` and that's not related to `unknown` target under strict subtype relation
	if effectiveTarget.flags&core.IfElse(r.relation == r.c.strictSubtypeRelation, TypeFlagsAny, TypeFlagsAnyOrUnknown) != 0 {
		return TernaryTrue
	}
	effectiveSource := getTypeOfSourceProperty(sourceProp)
	return r.isRelatedToEx(effectiveSource, effectiveTarget, RecursionFlagsBoth, reportErrors, nil /*headMessage*/, intersectionState)
}

func (r *Relater) reportUnmatchedProperty(source *Type, target *Type, unmatchedProperty *ast.Symbol, requireOptionalProperties bool) {
	// give specific error in case where private names have the same description
	if unmatchedProperty.ValueDeclaration != nil &&
		unmatchedProperty.ValueDeclaration.Name() != nil &&
		ast.IsPrivateIdentifier(unmatchedProperty.ValueDeclaration.Name()) &&
		source.symbol != nil &&
		source.symbol.Flags&ast.SymbolFlagsClass != 0 {
		privateIdentifierDescription := unmatchedProperty.ValueDeclaration.Name().Text()
		symbolTableKey := binder.GetSymbolNameForPrivateIdentifier(source.symbol, privateIdentifierDescription)
		if r.c.getPropertyOfType(source, symbolTableKey) != nil {
			sourceName := scanner.DeclarationNameToString(ast.GetNameOfDeclaration(source.symbol.ValueDeclaration))
			targetName := scanner.DeclarationNameToString(ast.GetNameOfDeclaration(target.symbol.ValueDeclaration))
			r.reportError(diagnostics.Property_0_in_type_1_refers_to_a_different_member_that_cannot_be_accessed_from_within_type_2, privateIdentifierDescription, sourceName, targetName)
			return
		}
	}
	props := r.c.getUnmatchedProperties(source, target, requireOptionalProperties, false /*matchDiscriminantProperties*/)
	if len(props) == 1 {
		sourceType, targetType := r.c.getTypeNamesForErrorDisplay(source, target)
		propName := r.c.symbolToString(unmatchedProperty)
		r.reportError(diagnostics.Property_0_is_missing_in_type_1_but_required_in_type_2, propName, sourceType, targetType)
		if len(unmatchedProperty.Declarations) != 0 {
			r.relatedInfo = append(r.relatedInfo, createDiagnosticForNode(unmatchedProperty.Declarations[0], diagnostics.X_0_is_declared_here, propName))
		}
	} else if r.tryElaborateArrayLikeErrors(source, target, false /*reportErrors*/) {
		sourceType, targetType := r.c.getTypeNamesForErrorDisplay(source, target)
		if len(props) > 5 {
			propNames := strings.Join(core.Map(props[:4], r.c.symbolToString), ", ")
			r.reportError(diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2_and_3_more, sourceType, targetType, propNames, len(props)-4)
		} else {
			propNames := strings.Join(core.Map(props, r.c.symbolToString), ", ")
			r.reportError(diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2, sourceType, targetType, propNames)
		}
	}
}

func (r *Relater) tryElaborateArrayLikeErrors(source *Type, target *Type, reportErrors bool) bool {
	/**
	 * The spec for elaboration is:
	 * - If the source is a readonly tuple and the target is a mutable array or tuple, elaborate on mutability and skip property elaborations.
	 * - If the source is a tuple then skip property elaborations if the target is an array or tuple.
	 * - If the source is a readonly array and the target is a mutable array or tuple, elaborate on mutability and skip property elaborations.
	 * - If the source an array then skip property elaborations if the target is a tuple.
	 */
	if isTupleType(source) {
		if source.TargetTupleType().readonly && r.c.isMutableArrayOrTuple(target) {
			if reportErrors {
				r.reportError(diagnostics.The_type_0_is_readonly_and_cannot_be_assigned_to_the_mutable_type_1, r.c.TypeToString(source), r.c.TypeToString(target))
			}
			return false
		}
		return r.c.isArrayOrTupleType(target)
	}
	if r.c.isReadonlyArrayType(source) && r.c.isMutableArrayOrTuple(target) {
		if reportErrors {
			r.reportError(diagnostics.The_type_0_is_readonly_and_cannot_be_assigned_to_the_mutable_type_1, r.c.TypeToString(source), r.c.TypeToString(target))
		}
		return false
	}
	if isTupleType(target) {
		return r.c.isArrayType(source)
	}
	return true
}

func (r *Relater) tryElaborateErrorsForPrimitivesAndObjects(source *Type, target *Type) {
	if (source == r.c.globalStringType && target == r.c.stringType) ||
		(source == r.c.globalNumberType && target == r.c.numberType) ||
		(source == r.c.globalBooleanType && target == r.c.booleanType) ||
		(source == r.c.getGlobalESSymbolType() && target == r.c.esSymbolType) {
		r.reportError(diagnostics.X_0_is_a_primitive_but_1_is_a_wrapper_object_Prefer_using_0_when_possible, r.c.TypeToString(target), r.c.TypeToString(source))
	}
}

func (r *Relater) propertiesIdenticalTo(source *Type, target *Type, excludedProperties collections.Set[string]) Ternary {
	if source.flags&TypeFlagsObject == 0 || target.flags&TypeFlagsObject == 0 {
		return TernaryFalse
	}
	sourceProperties := excludeProperties(r.c.getPropertiesOfObjectType(source), excludedProperties)
	targetProperties := excludeProperties(r.c.getPropertiesOfObjectType(target), excludedProperties)
	if len(sourceProperties) != len(targetProperties) {
		return TernaryFalse
	}
	result := TernaryTrue
	for _, sourceProp := range sourceProperties {
		targetProp := r.c.getPropertyOfObjectType(target, sourceProp.Name)
		if targetProp == nil {
			return TernaryFalse
		}
		related := r.c.compareProperties(sourceProp, targetProp, r.isRelatedToSimple)
		if related == TernaryFalse {
			return TernaryFalse
		}
		result &= related
	}
	return result
}

func (r *Relater) signaturesRelatedTo(source *Type, target *Type, kind SignatureKind, reportErrors bool, intersectionState IntersectionState) Ternary {
	if r.relation == r.c.identityRelation {
		return r.signaturesIdenticalTo(source, target, kind)
	}
	// With respect to signatures, the anyFunctionType wildcard is a subtype of every other function type.
	if source == r.c.anyFunctionType {
		return TernaryTrue
	}
	if target == r.c.anyFunctionType {
		return TernaryFalse
	}
	sourceSignatures := r.c.getSignaturesOfType(source, kind)
	targetSignatures := r.c.getSignaturesOfType(target, kind)
	if kind == SignatureKindConstruct && len(sourceSignatures) != 0 && len(targetSignatures) != 0 {
		sourceIsAbstract := sourceSignatures[0].flags&SignatureFlagsAbstract != 0
		targetIsAbstract := targetSignatures[0].flags&SignatureFlagsAbstract != 0
		if sourceIsAbstract && !targetIsAbstract {
			// An abstract constructor type is not assignable to a non-abstract constructor type
			// as it would otherwise be possible to new an abstract class. Note that the assignability
			// check we perform for an extends clause excludes construct signatures from the target,
			// so this check never proceeds.
			if reportErrors {
				r.reportError(diagnostics.Cannot_assign_an_abstract_constructor_type_to_a_non_abstract_constructor_type)
			}
			return TernaryFalse
		}
		if !r.constructorVisibilitiesAreCompatible(sourceSignatures[0], targetSignatures[0], reportErrors) {
			return TernaryFalse
		}
	}
	result := TernaryTrue
	switch {
	case source.objectFlags&ObjectFlagsInstantiated != 0 && target.objectFlags&ObjectFlagsInstantiated != 0 && source.symbol == target.symbol ||
		source.objectFlags&ObjectFlagsReference != 0 && target.objectFlags&ObjectFlagsReference != 0 && source.Target() == target.Target():
		// We have instantiations of the same anonymous type (which typically will be the type of a
		// method). Simply do a pairwise comparison of the signatures in the two signature lists instead
		// of the much more expensive N * M comparison matrix we explore below. We erase type parameters
		// as they are known to always be the same.
		for i := range targetSignatures {
			related := r.signatureRelatedTo(sourceSignatures[i], targetSignatures[i], true /*erase*/, reportErrors, intersectionState)
			if related == TernaryFalse {
				return TernaryFalse
			}
			result &= related
		}
	case len(sourceSignatures) == 1 && len(targetSignatures) == 1:
		// For simple functions (functions with a single signature) we only erase type parameters for
		// the comparable relation. Otherwise, if the source signature is generic, we instantiate it
		// in the context of the target signature before checking the relationship. Ideally we'd do
		// this regardless of the number of signatures, but the potential costs are prohibitive due
		// to the quadratic nature of the logic below.
		eraseGenerics := r.relation == r.c.comparableRelation
		result = r.signatureRelatedTo(sourceSignatures[0], targetSignatures[0], eraseGenerics, reportErrors, intersectionState)
	default:
	outer:
		for _, t := range targetSignatures {
			saveErrorState := r.getErrorState()
			// Only elaborate errors from the first failure
			shouldElaborateErrors := reportErrors
			for _, s := range sourceSignatures {
				related := r.signatureRelatedTo(s, t, true /*erase*/, shouldElaborateErrors, intersectionState)
				if related != TernaryFalse {
					result &= related
					r.restoreErrorState(saveErrorState)
					continue outer
				}
				shouldElaborateErrors = false
			}
			if shouldElaborateErrors {
				r.reportError(diagnostics.Type_0_provides_no_match_for_the_signature_1, r.c.TypeToString(source), r.c.signatureToString(t))
			}
			return TernaryFalse
		}
	}
	return result
}

func (r *Relater) constructorVisibilitiesAreCompatible(sourceSignature *Signature, targetSignature *Signature, reportErrors bool) bool {
	if sourceSignature.declaration == nil || targetSignature.declaration == nil {
		return true
	}
	sourceAccessibility := sourceSignature.declaration.ModifierFlags() & ast.ModifierFlagsNonPublicAccessibilityModifier
	targetAccessibility := targetSignature.declaration.ModifierFlags() & ast.ModifierFlagsNonPublicAccessibilityModifier
	// A public, protected and private signature is assignable to a private signature.
	if targetAccessibility == ast.ModifierFlagsPrivate {
		return true
	}
	// A public and protected signature is assignable to a protected signature.
	if targetAccessibility == ast.ModifierFlagsProtected && sourceAccessibility != ast.ModifierFlagsPrivate {
		return true
	}
	// Only a public signature is assignable to public signature.
	if targetAccessibility != ast.ModifierFlagsProtected && sourceAccessibility == 0 {
		return true
	}
	if reportErrors {
		r.reportError(diagnostics.Cannot_assign_a_0_constructor_type_to_a_1_constructor_type, visibilityToString(sourceAccessibility), visibilityToString(targetAccessibility))
	}
	return false
}

// See signatureAssignableTo, compareSignaturesIdentical
func (r *Relater) signatureRelatedTo(source *Signature, target *Signature, erase bool, reportErrors bool, intersectionState IntersectionState) Ternary {
	checkMode := SignatureCheckModeNone
	switch {
	case r.relation == r.c.subtypeRelation:
		checkMode = SignatureCheckModeStrictTopSignature
	case r.relation == r.c.strictSubtypeRelation:
		checkMode = SignatureCheckModeStrictTopSignature | SignatureCheckModeStrictArity
	}
	if erase {
		source = r.c.getErasedSignature(source)
		target = r.c.getErasedSignature(target)
	}
	isRelatedToWorker := func(source *Type, target *Type, reportErrors bool) Ternary {
		return r.isRelatedToEx(source, target, RecursionFlagsBoth, reportErrors, nil /*headMessage*/, intersectionState)
	}
	return r.c.compareSignaturesRelated(source, target, checkMode, reportErrors, r.reportError, isRelatedToWorker, r.c.reportUnreliableMapper)
}

func (r *Relater) signaturesIdenticalTo(source *Type, target *Type, kind SignatureKind) Ternary {
	sourceSignatures := r.c.getSignaturesOfType(source, kind)
	targetSignatures := r.c.getSignaturesOfType(target, kind)
	if len(sourceSignatures) != len(targetSignatures) {
		return TernaryFalse
	}
	result := TernaryTrue
	for i := range sourceSignatures {
		related := r.c.compareSignaturesIdentical(sourceSignatures[i], targetSignatures[i], false /*partialMatch*/, false /*ignoreThisTypes*/, false /*ignoreReturnTypes*/, r.isRelatedToSimple)
		if related == 0 {
			return TernaryFalse
		}
		result &= related
	}
	return result
}

func (r *Relater) indexSignaturesRelatedTo(source *Type, target *Type, sourceIsPrimitive bool, reportErrors bool, intersectionState IntersectionState) Ternary {
	if r.relation == r.c.identityRelation {
		return r.indexSignaturesIdenticalTo(source, target)
	}
	indexInfos := r.c.getIndexInfosOfType(target)
	targetHasStringIndex := core.Some(indexInfos, func(info *IndexInfo) bool { return info.keyType == r.c.stringType })
	result := TernaryTrue
	for _, targetInfo := range indexInfos {
		var related Ternary
		switch {
		case r.relation != r.c.strictSubtypeRelation && !sourceIsPrimitive && targetHasStringIndex && targetInfo.valueType.flags&TypeFlagsAny != 0:
			related = TernaryTrue
		case r.c.isGenericMappedType(source) && targetHasStringIndex:
			related = r.isRelatedTo(r.c.getTemplateTypeFromMappedType(source), targetInfo.valueType, RecursionFlagsBoth, reportErrors)
		default:
			related = r.typeRelatedToIndexInfo(source, targetInfo, reportErrors, intersectionState)
		}
		if related == TernaryFalse {
			return TernaryFalse
		}
		result &= related
	}
	return result
}

func (r *Relater) typeRelatedToIndexInfo(source *Type, targetInfo *IndexInfo, reportErrors bool, intersectionState IntersectionState) Ternary {
	sourceInfo := r.c.getApplicableIndexInfo(source, targetInfo.keyType)
	if sourceInfo != nil {
		return r.indexInfoRelatedTo(sourceInfo, targetInfo, reportErrors, intersectionState)
	}
	// Intersection constituents are never considered to have an inferred index signature. Also, in the strict subtype relation,
	// only fresh object literals are considered to have inferred index signatures. This ensures { [x: string]: xxx } <: {} but
	// not vice-versa. Without this rule, those types would be mutual strict subtypes.
	if intersectionState&IntersectionStateSource == 0 && (r.relation != r.c.strictSubtypeRelation || source.objectFlags&ObjectFlagsFreshLiteral != 0) && r.c.isObjectTypeWithInferableIndex(source) {
		return r.membersRelatedToIndexInfo(source, targetInfo, reportErrors, intersectionState)
	}
	if reportErrors {
		r.reportError(diagnostics.Index_signature_for_type_0_is_missing_in_type_1, r.c.TypeToString(targetInfo.keyType), r.c.TypeToString(source))
	}
	return TernaryFalse
}

/**
 * Return true if type was inferred from an object literal, written as an object type literal, or is the shape of a module
 * with no call or construct signatures.
 */
func (c *Checker) isObjectTypeWithInferableIndex(t *Type) bool {
	if t.flags&TypeFlagsIntersection != 0 {
		return core.Every(t.Types(), c.isObjectTypeWithInferableIndex)
	}
	return t.symbol != nil && t.symbol.Flags&(ast.SymbolFlagsObjectLiteral|ast.SymbolFlagsTypeLiteral|ast.SymbolFlagsEnum|ast.SymbolFlagsValueModule) != 0 &&
		t.symbol.Flags&ast.SymbolFlagsClass == 0 && !c.typeHasCallOrConstructSignatures(t) ||
		t.objectFlags&ObjectFlagsObjectRestType != 0 ||
		t.objectFlags&ObjectFlagsReverseMapped != 0 && c.isObjectTypeWithInferableIndex(t.AsReverseMappedType().source)
}

func (r *Relater) membersRelatedToIndexInfo(source *Type, targetInfo *IndexInfo, reportErrors bool, intersectionState IntersectionState) Ternary {
	result := TernaryTrue
	keyType := targetInfo.keyType
	var props []*ast.Symbol
	if source.flags&TypeFlagsIntersection != 0 {
		props = r.c.getPropertiesOfUnionOrIntersectionType(source)
	} else {
		props = r.c.getPropertiesOfObjectType(source)
	}
	for _, prop := range props {
		// Skip over ignored JSX and symbol-named members
		if isIgnoredJsxProperty(source, prop) {
			continue
		}
		if r.c.isApplicableIndexType(r.c.getLiteralTypeFromProperty(prop, TypeFlagsStringOrNumberLiteralOrUnique, false), keyType) {
			propType := r.c.getNonMissingTypeOfSymbol(prop)
			var t *Type
			if r.c.exactOptionalPropertyTypes || propType.flags&TypeFlagsUndefined != 0 || keyType == r.c.numberType || prop.Flags&ast.SymbolFlagsOptional == 0 {
				t = propType
			} else {
				t = r.c.getTypeWithFacts(propType, TypeFactsNEUndefined)
			}
			related := r.isRelatedToEx(t, targetInfo.valueType, RecursionFlagsBoth, reportErrors, nil /*headMessage*/, intersectionState)
			if related == TernaryFalse {
				if reportErrors {
					r.reportError(diagnostics.Property_0_is_incompatible_with_index_signature, r.c.symbolToString(prop))
				}
				return TernaryFalse
			}
			result &= related
		}
	}
	for _, info := range r.c.getIndexInfosOfType(source) {
		if r.c.isApplicableIndexType(info.keyType, keyType) {
			related := r.indexInfoRelatedTo(info, targetInfo, reportErrors, intersectionState)
			if !(related != 0) {
				return TernaryFalse
			}
			result &= related
		}
	}
	return result
}

func (r *Relater) indexInfoRelatedTo(sourceInfo *IndexInfo, targetInfo *IndexInfo, reportErrors bool, intersectionState IntersectionState) Ternary {
	related := r.isRelatedToEx(sourceInfo.valueType, targetInfo.valueType, RecursionFlagsBoth, reportErrors, nil /*headMessage*/, intersectionState)
	if related == TernaryFalse && reportErrors {
		if sourceInfo.keyType == targetInfo.keyType {
			r.reportError(diagnostics.X_0_index_signatures_are_incompatible, r.c.TypeToString(sourceInfo.keyType))
		} else {
			r.reportError(diagnostics.X_0_and_1_index_signatures_are_incompatible, r.c.TypeToString(sourceInfo.keyType), r.c.TypeToString(targetInfo.keyType))
		}
	}
	return related
}

func (r *Relater) indexSignaturesIdenticalTo(source *Type, target *Type) Ternary {
	sourceInfos := r.c.getIndexInfosOfType(source)
	targetInfos := r.c.getIndexInfosOfType(target)
	if len(sourceInfos) != len(targetInfos) {
		return TernaryFalse
	}
	for _, targetInfo := range targetInfos {
		sourceInfo := r.c.getIndexInfoOfType(source, targetInfo.keyType)
		if !(sourceInfo != nil && r.isRelatedTo(sourceInfo.valueType, targetInfo.valueType, RecursionFlagsBoth, false) != TernaryFalse && sourceInfo.isReadonly == targetInfo.isReadonly) {
			return TernaryFalse
		}
	}
	return TernaryTrue
}

func (r *Relater) reportErrorResults(originalSource *Type, originalTarget *Type, source *Type, target *Type, headMessage *diagnostics.Message) {
	sourceHasBase := r.c.getSingleBaseForNonAugmentingSubtype(originalSource) != nil
	targetHasBase := r.c.getSingleBaseForNonAugmentingSubtype(originalTarget) != nil
	if originalSource.alias != nil || sourceHasBase {
		source = originalSource
	}
	if originalTarget.alias != nil || targetHasBase {
		target = originalTarget
	}
	if source.flags&TypeFlagsObject != 0 && target.flags&TypeFlagsObject != 0 {
		r.tryElaborateArrayLikeErrors(source, target, true /*reportErrors*/)
	}
	switch {
	case source.flags&TypeFlagsObject != 0 && target.flags&TypeFlagsPrimitive != 0:
		r.tryElaborateErrorsForPrimitivesAndObjects(source, target)
	case source.symbol != nil && source.flags&TypeFlagsObject != 0 && r.c.globalObjectType == source:
		r.reportError(diagnostics.The_Object_type_is_assignable_to_very_few_other_types_Did_you_mean_to_use_the_any_type_instead)
	case source.objectFlags&ObjectFlagsJsxAttributes != 0 && target.flags&TypeFlagsIntersection != 0:
		targetTypes := target.Types()
		intrinsicAttributes := r.c.getJsxType(JsxNames.IntrinsicAttributes, r.errorNode)
		intrinsicClassAttributes := r.c.getJsxType(JsxNames.IntrinsicClassAttributes, r.errorNode)
		if !r.c.isErrorType(intrinsicAttributes) && !r.c.isErrorType(intrinsicClassAttributes) && (slices.Contains(targetTypes, intrinsicAttributes) || slices.Contains(targetTypes, intrinsicClassAttributes)) {
			return
		}
	case originalTarget.flags&TypeFlagsIntersection != 0 && originalTarget.objectFlags&ObjectFlagsIsNeverIntersection != 0:
		message := diagnostics.The_intersection_0_was_reduced_to_never_because_property_1_has_conflicting_types_in_some_constituents
		prop := core.Find(r.c.getPropertiesOfUnionOrIntersectionType(originalTarget), r.c.isDiscriminantWithNeverType)
		if prop == nil {
			message = diagnostics.The_intersection_0_was_reduced_to_never_because_property_1_exists_in_multiple_constituents_and_is_private_in_some
			prop = core.Find(r.c.getPropertiesOfUnionOrIntersectionType(originalTarget), isConflictingPrivateProperty)
		}
		if prop != nil {
			r.reportError(message, r.c.typeToStringEx(originalTarget, nil /*enclosingDeclaration*/, TypeFormatFlagsNoTypeReduction), r.c.symbolToString(prop))
		}
	}
	r.reportRelationError(headMessage, source, target)
	if source.flags&TypeFlagsTypeParameter != 0 && source.symbol != nil && len(source.symbol.Declarations) != 0 && r.c.getConstraintOfType(source) == nil {
		syntheticParam := r.c.cloneTypeParameter(source)
		syntheticParam.AsTypeParameter().constraint = r.c.instantiateType(target, newSimpleTypeMapper(source, syntheticParam))
		if r.c.hasNonCircularBaseConstraint(syntheticParam) {
			targetConstraintString := r.c.TypeToString(target)
			r.relatedInfo = append(r.relatedInfo, NewDiagnosticForNode(source.symbol.Declarations[0], diagnostics.This_type_parameter_might_need_an_extends_0_constraint, targetConstraintString))
		}
	}
}

func (r *Relater) reportRelationError(message *diagnostics.Message, source *Type, target *Type) {
	sourceType, targetType := r.c.getTypeNamesForErrorDisplay(source, target)
	generalizedSource := source
	generalizedSourceType := sourceType
	// Don't generalize on 'never' - we really want the original type
	// to be displayed for use-cases like 'assertNever'.
	if target.flags&TypeFlagsNever == 0 && isLiteralType(source) && !r.c.typeCouldHaveTopLevelSingletonTypes(target) {
		generalizedSource = r.c.getBaseTypeOfLiteralType(source)
		debug.Assert(!r.c.isTypeAssignableTo(generalizedSource, target), "generalized source shouldn't be assignable")
		generalizedSourceType = r.c.getTypeNameForErrorDisplay(generalizedSource)
	}
	// If `target` is of indexed access type (and `source` it is not), we use the object type of `target` for better error reporting
	var targetFlags TypeFlags
	if target.flags&TypeFlagsIndexedAccess != 0 && source.flags&TypeFlagsIndexedAccess == 0 {
		targetFlags = target.AsIndexedAccessType().objectType.flags
	} else {
		targetFlags = target.flags
	}
	if targetFlags&TypeFlagsTypeParameter != 0 && target != r.c.markerSuperTypeForCheck && target != r.c.markerSubTypeForCheck {
		constraint := r.c.getBaseConstraintOfType(target)
		switch {
		case constraint != nil && r.c.isTypeAssignableTo(generalizedSource, constraint):
			r.reportError(diagnostics.X_0_is_assignable_to_the_constraint_of_type_1_but_1_could_be_instantiated_with_a_different_subtype_of_constraint_2, generalizedSourceType, targetType, r.c.TypeToString(constraint))
		case constraint != nil && r.c.isTypeAssignableTo(source, constraint):
			r.reportError(diagnostics.X_0_is_assignable_to_the_constraint_of_type_1_but_1_could_be_instantiated_with_a_different_subtype_of_constraint_2, sourceType, targetType, r.c.TypeToString(constraint))
		default:
			r.errorChain = nil // Only report this error once
			r.reportError(diagnostics.X_0_could_be_instantiated_with_an_arbitrary_type_which_could_be_unrelated_to_1, targetType, generalizedSourceType)
		}
	}
	if message == nil {
		switch {
		case r.relation == r.c.comparableRelation:
			message = diagnostics.Type_0_is_not_comparable_to_type_1
		case sourceType == targetType:
			message = diagnostics.Type_0_is_not_assignable_to_type_1_Two_different_types_with_this_name_exist_but_they_are_unrelated
		case r.c.exactOptionalPropertyTypes && len(r.c.getExactOptionalUnassignableProperties(source, target)) != 0:
			message = diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_types_of_the_target_s_properties
		default:
			if source.flags&TypeFlagsStringLiteral != 0 && target.flags&TypeFlagsUnion != 0 {
				suggestedType := r.c.getSuggestedTypeForNonexistentStringLiteralType(source, target)
				if suggestedType != nil {
					r.reportError(diagnostics.Type_0_is_not_assignable_to_type_1_Did_you_mean_2, generalizedSourceType, targetType, r.c.TypeToString(suggestedType))
					return
				}
			}
			message = diagnostics.Type_0_is_not_assignable_to_type_1
		}
	}
	switch r.getChainMessage(0) {
	// Suppress if next message is an excess property error
	case diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1,
		diagnostics.Object_literal_may_only_specify_known_properties_but_0_does_not_exist_in_type_1_Did_you_mean_to_write_2:
		return
	// Suppress if next message is an excessive complexity/stack depth message for source and target or a readonly
	// vs. mutable error for source and target
	case diagnostics.Excessive_complexity_comparing_types_0_and_1,
		diagnostics.Excessive_stack_depth_comparing_types_0_and_1,
		diagnostics.The_type_0_is_readonly_and_cannot_be_assigned_to_the_mutable_type_1:
		if r.chainArgsMatch(generalizedSourceType, targetType) {
			return
		}
	// Suppress if next message is a missing property message for source and target and we're not
	// reporting on conversion or interface implementation
	case diagnostics.Property_0_is_missing_in_type_1_but_required_in_type_2:
		if !isConversionOrInterfaceImplementationMessage(message) && r.chainArgsMatch(nil, generalizedSourceType, targetType) {
			return
		}
	// Suppress if next message is a missing property message for source and target and we're not
	// reporting on conversion or interface implementation
	case diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2_and_3_more,
		diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2:
		if !isConversionOrInterfaceImplementationMessage(message) && r.chainArgsMatch(generalizedSourceType, targetType) {
			return
		}
	}
	r.reportError(message, generalizedSourceType, targetType)
}

func (r *Relater) reportError(message *diagnostics.Message, args ...any) {
	if message == diagnostics.Types_of_property_0_are_incompatible {
		// Suppress if next message is an excess property error
		switch r.getChainMessage(0) {
		case diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1,
			diagnostics.Object_literal_may_only_specify_known_properties_but_0_does_not_exist_in_type_1_Did_you_mean_to_write_2:
			return
		}
		// Transform a property incompatibility message for property 'x' followed by some elaboration message
		// followed by a signature return type incompatibility message into a single return type incompatibility
		// message for 'x()' or 'x(...)'
		var arg string
		switch r.getChainMessage(1) {
		case diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1:
			arg = getPropertyNameArg(args[0]) + "()"
		case diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1:
			arg = "new " + getPropertyNameArg(args[0]) + "()"
		case diagnostics.Call_signature_return_types_0_and_1_are_incompatible:
			arg = getPropertyNameArg(args[0]) + "(...)"
		case diagnostics.Construct_signature_return_types_0_and_1_are_incompatible:
			arg = "new " + getPropertyNameArg(args[0]) + "(...)"
		}
		if arg != "" {
			message = diagnostics.The_types_returned_by_0_are_incompatible_between_these_types
			args[0] = arg
			r.errorChain = r.errorChain.next.next
		}
		// Transform a property incompatibility message for property 'x' followed by some elaboration message
		// followed by a property incompatibility message for property 'y' into a single property incompatibility
		// message for 'x.y'
		switch r.getChainMessage(1) {
		case diagnostics.Types_of_property_0_are_incompatible,
			diagnostics.The_types_of_0_are_incompatible_between_these_types,
			diagnostics.The_types_returned_by_0_are_incompatible_between_these_types:
			head := getPropertyNameArg(args[0])
			tail := getPropertyNameArg(r.errorChain.next.args[0])
			arg := addToDottedName(head, tail)
			r.errorChain = r.errorChain.next.next
			if message == diagnostics.Types_of_property_0_are_incompatible {
				message = diagnostics.The_types_of_0_are_incompatible_between_these_types
			}
			r.reportError(message, arg)
			return
		}
	}
	r.errorChain = &ErrorChain{next: r.errorChain, message: message, args: args}
}

func addToDottedName(head string, tail string) string {
	if strings.HasPrefix(head, "new ") {
		head = "(" + head + ")"
	}
	pos := 0
	for {
		if strings.HasPrefix(tail[pos:], "(") {
			pos++
		} else if strings.HasPrefix(tail[pos:], "new ") {
			pos += 4
		} else {
			break
		}
	}
	prefix := tail[:pos]
	suffix := tail[pos:]
	if strings.HasPrefix(suffix, "[") {
		return prefix + head + suffix
	}
	return prefix + head + "." + suffix
}

func (r *Relater) getChainMessage(index int) *diagnostics.Message {
	e := r.errorChain
	for {
		if e == nil {
			return nil
		}
		if index == 0 {
			return e.message
		}
		e = e.next
		index--
	}
}

// Return true if the arguments of the first entry on the error chain match the
// given arguments (where nil acts as a wildcard).
func (r *Relater) chainArgsMatch(args ...any) bool {
	for i, a := range args {
		if a != nil && a != r.errorChain.args[i] {
			return false
		}
	}
	return true
}

func getPropertyNameArg(arg any) string {
	s := arg.(string)
	if len(s) != 0 && (s[0] == '"' || s[0] == '\'' || s[0] == '`') {
		return "[" + s + "]"
	}
	return s
}

func isConversionOrInterfaceImplementationMessage(message *diagnostics.Message) bool {
	return message == diagnostics.Class_0_incorrectly_implements_interface_1 ||
		message == diagnostics.Class_0_incorrectly_implements_class_1_Did_you_mean_to_extend_1_and_inherit_its_members_as_a_subclass ||
		message == diagnostics.Conversion_of_type_0_to_type_1_may_be_a_mistake_because_neither_type_sufficiently_overlaps_with_the_other_If_this_was_intentional_convert_the_expression_to_unknown_first
}

func chainDepth(chain *ErrorChain) int {
	depth := 0
	for chain != nil {
		depth++
		chain = chain.next
	}
	return depth
}

// An object type S is considered to be derived from an object type T if
// S is a union type and every constituent of S is derived from T,
// T is a union type and S is derived from at least one constituent of T, or
// S is an intersection type and some constituent of S is derived from T, or
// S is a type variable with a base constraint that is derived from T, or
// T is {} and S is an object-like type (ensuring {} is less derived than Object), or
// T is one of the global types Object and Function and S is a subtype of T, or
// T occurs directly or indirectly in an 'extends' clause of S.
// Note that this check ignores type parameters and only considers the
// inheritance hierarchy.
func (c *Checker) isTypeDerivedFrom(source *Type, target *Type) bool {
	switch {
	case source.flags&TypeFlagsUnion != 0:
		return core.Every(source.AsUnionType().types, func(t *Type) bool {
			return c.isTypeDerivedFrom(t, target)
		})
	case target.flags&TypeFlagsUnion != 0:
		return core.Some(target.AsUnionType().types, func(t *Type) bool {
			return c.isTypeDerivedFrom(source, t)
		})
	case source.flags&TypeFlagsIntersection != 0:
		return core.Some(source.AsIntersectionType().types, func(t *Type) bool {
			return c.isTypeDerivedFrom(t, target)
		})
	case source.flags&TypeFlagsInstantiableNonPrimitive != 0:
		constraint := c.getBaseConstraintOfType(source)
		if constraint == nil {
			constraint = c.unknownType
		}
		return c.isTypeDerivedFrom(constraint, target)
	case c.IsEmptyAnonymousObjectType(target):
		return source.flags&(TypeFlagsObject|TypeFlagsNonPrimitive) != 0
	case target == c.globalObjectType:
		return source.flags&(TypeFlagsObject|TypeFlagsNonPrimitive) != 0 && !c.IsEmptyAnonymousObjectType(source)
	case target == c.globalFunctionType:
		return source.flags&TypeFlagsObject != 0 && c.isFunctionObjectType(source)
	default:
		return c.hasBaseType(source, c.getTargetType(target)) || (c.isArrayType(target) && !c.isReadonlyArrayType(target) && c.isTypeDerivedFrom(source, c.globalReadonlyArrayType))
	}
}

func (c *Checker) isDistributionDependent(root *ConditionalRoot) bool {
	return root.isDistributive && (c.isTypeParameterPossiblyReferenced(root.checkType, root.node.TrueType) || c.isTypeParameterPossiblyReferenced(root.checkType, root.node.FalseType))
}
