2025-10-15 10:12:44 +03:00

4924 lines
225 KiB
Go

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