4924 lines
225 KiB
Go
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))
|
|
}
|