Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tsc/internal/api/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -764,7 +764,7 @@ func (setup checkerSetup) newIndexInfoResponse(info *checker.IndexInfo) *IndexIn
}
result := &IndexInfoResponse{
KeyType: *setup.sd.newTypeResponse(setup.projectID, info.KeyType(), setup.checker),
ValueType: *setup.sd.newTypeResponse(setup.projectID, info.ValueType(), setup.checker),
ValueType: *setup.sd.newTypeResponse(setup.projectID, setup.checker.GetValueTypeOfIndexInfo(info), setup.checker),
IsReadonly: info.IsReadonly(),
}
if info.Declaration() != nil {
Expand Down
110 changes: 84 additions & 26 deletions tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const (
TypeSystemPropertyNameWriteType
TypeSystemPropertyNameInitializerIsUndefined
TypeSystemPropertyNameAliasTarget
TypeSystemPropertyNameIndexInfoValueType
)

type TypeResolution struct {
Expand Down Expand Up @@ -4957,8 +4958,8 @@ func (c *Checker) checkIndexConstraintForProperty(t *Type, prop *ast.Symbol, pro
}) {
errorNode = interfaceDeclaration
}
if errorNode != nil && !c.isTypeAssignableTo(propType, info.valueType) {
diagnostic := NewDiagnosticForNode(errorNode, diagnostics.Property_0_of_type_1_is_not_assignable_to_2_index_type_3, c.symbolToString(prop), c.TypeToString(propType), c.TypeToString(info.keyType), c.TypeToString(info.valueType))
if errorNode != nil && !c.isTypeAssignableTo(propType, c.getValueTypeOfIndexInfo(info)) {
diagnostic := NewDiagnosticForNode(errorNode, diagnostics.Property_0_of_type_1_is_not_assignable_to_2_index_type_3, c.symbolToString(prop), c.TypeToString(propType), c.TypeToString(info.keyType), c.TypeToString(c.getValueTypeOfIndexInfo(info)))
if propDeclaration != nil && errorNode != propDeclaration {
diagnostic.AddRelatedInfo(NewDiagnosticForNode(propDeclaration, diagnostics.X_0_is_declared_here, c.symbolToString(prop)))
}
Expand Down Expand Up @@ -4998,8 +4999,8 @@ func (c *Checker) checkIndexConstraintForIndexSignature(t *Type, checkInfo *Inde
}) {
errorNode = interfaceDeclaration
}
if errorNode != nil && !c.isTypeAssignableTo(checkInfo.valueType, info.valueType) {
c.error(errorNode, diagnostics.X_0_index_type_1_is_not_assignable_to_2_index_type_3, c.TypeToString(checkInfo.keyType), c.TypeToString(checkInfo.valueType), c.TypeToString(info.keyType), c.TypeToString(info.valueType))
if errorNode != nil && !c.isTypeAssignableTo(c.getValueTypeOfIndexInfo(checkInfo), c.getValueTypeOfIndexInfo(info)) {
c.error(errorNode, diagnostics.X_0_index_type_1_is_not_assignable_to_2_index_type_3, c.TypeToString(checkInfo.keyType), c.TypeToString(c.getValueTypeOfIndexInfo(checkInfo)), c.TypeToString(info.keyType), c.TypeToString(c.getValueTypeOfIndexInfo(info)))
}
}
}
Expand Down Expand Up @@ -11598,7 +11599,7 @@ func (c *Checker) checkPropertyAccessExpressionOrQualifiedName(node *ast.Node, l
if indexInfo.isReadonly && (ast.IsAssignmentTarget(node) || isDeleteTarget(node)) {
c.error(node, diagnostics.Index_signature_in_type_0_only_permits_reading, c.TypeToString(apparentType))
}
propType = indexInfo.valueType
propType = c.getValueTypeOfIndexInfo(indexInfo)
if c.compilerOptions.NoUncheckedIndexedAccess == core.TSTrue && getAssignmentTargetKind(node) != AssignmentKindDefinite {
propType = c.getUnionType([]*Type{propType, c.missingType})
}
Expand Down Expand Up @@ -13758,7 +13759,7 @@ func (c *Checker) getSpreadType(left *Type, right *Type, symbol *ast.Symbol, obj

func (c *Checker) getIndexInfoWithReadonly(info *IndexInfo, readonly bool) *IndexInfo {
if info.isReadonly != readonly {
return c.newIndexInfo(info.keyType, info.valueType, readonly, info.declaration, info.components)
return c.newIndexInfo(info.keyType, c.getValueTypeOfIndexInfo(info), readonly, info.declaration, info.components)
}
return info
}
Expand Down Expand Up @@ -18872,7 +18873,7 @@ func (c *Checker) getWidenedTypeOfObjectLiteral(t *Type, context *WideningContex
}
}
result := c.newAnonymousType(t.symbol, members, nil, nil, core.SameMap(c.getIndexInfosOfType(t), func(info *IndexInfo) *IndexInfo {
return c.newIndexInfo(info.keyType, c.getWidenedType(info.valueType), info.isReadonly, info.declaration, info.components)
return c.newIndexInfo(info.keyType, c.getWidenedType(c.getValueTypeOfIndexInfo(info)), info.isReadonly, info.declaration, info.components)
}))
// Retain js literal flag through widening
result.objectFlags |= t.objectFlags & (ObjectFlagsJSLiteral | ObjectFlagsNonInferrableType)
Expand Down Expand Up @@ -19272,6 +19273,8 @@ func (c *Checker) typeResolutionHasProperty(r *TypeResolution) bool {
return c.valueSymbolLinks.Get(r.target.(*ast.Symbol)).getWriteType() != nil
case TypeSystemPropertyNameAliasTarget:
return c.aliasSymbolLinks.Get(r.target.(*ast.Symbol)).aliasTarget != nil
case TypeSystemPropertyNameIndexInfoValueType:
return r.target.(*IndexInfo).valueType != nil
}
panic("Unhandled case in typeResolutionHasProperty")
}
Expand Down Expand Up @@ -19480,7 +19483,7 @@ func (c *Checker) getIndexInfoOfType(t *Type, keyType *Type) *IndexInfo {
func (c *Checker) getIndexTypeOfType(t *Type, keyType *Type) *Type {
info := c.getIndexInfoOfType(t, keyType)
if info != nil {
return info.valueType
return c.getValueTypeOfIndexInfo(info)
}
return nil
}
Expand Down Expand Up @@ -19529,7 +19532,7 @@ func (c *Checker) findApplicableIndexInfo(indexInfos []*IndexInfo, keyType *Type
isReadonly := true
types := make([]*Type, len(applicableInfos))
for i, info := range applicableInfos {
types[i] = info.valueType
types[i] = c.getValueTypeOfIndexInfo(info)
if !info.isReadonly {
isReadonly = false
}
Expand Down Expand Up @@ -20225,12 +20228,18 @@ func (c *Checker) getIndexInfosOfIndexSymbol(indexSymbol *ast.Symbol, siblingSym
if len(parameters) == 1 {
typeNode := parameters[0].Type()
if typeNode != nil {
valueType := c.anyType
if returnTypeNode != nil {
valueType = c.getTypeFromTypeNode(returnTypeNode)
}
// Defer resolving the index signature's value type. Eager resolution is
// problematic for patterns like `type T = keyof C` where C declares
// `[key: K]: T` — computing keyof only needs key types, and resolving the
// value type while T is still resolving incorrectly marks T circular
// (see microsoft/TypeScript#64998). The value type is resolved lazily via
// getValueTypeOfIndexInfo when first needed.
forEachType(c.getTypeFromTypeNode(typeNode), func(keyType *Type) {
if c.isValidIndexKeyType(keyType) && findIndexInfo(indexInfos, keyType) == nil {
var valueType *Type
if returnTypeNode == nil {
valueType = c.anyType
}
indexInfo := c.newIndexInfo(keyType, valueType, ast.HasModifier(declaration, ast.ModifierFlagsReadonly), declaration, nil)
indexInfos = append(indexInfos, indexInfo)
}
Expand Down Expand Up @@ -21218,8 +21227,23 @@ func (c *Checker) instantiateSignatureEx(sig *Signature, m *TypeMapper, eraseTyp
}

func (c *Checker) instantiateIndexInfo(info *IndexInfo, m *TypeMapper) *IndexInfo {
newValueType := c.instantiateType(info.valueType, m)
if newValueType == info.valueType {
// Keep deferred value types deferred across instantiation. Eagerly resolving the
// value type here reintroduces circularity for `type T = keyof C` where C has an
// index signature typed as T — classes always instantiate index infos because of
// the implicit `this` type (microsoft/TypeScript#64998).
if info.valueType == nil && info.declaration != nil {
keyType := c.instantiateType(info.keyType, m)
combined := c.combineTypeMappers(info.mapper, m)
if keyType == info.keyType && combined == info.mapper {
return info
}
result := c.newIndexInfo(keyType, nil, info.isReadonly, info.declaration, info.components)
result.mapper = combined
return result
}
valueType := c.getValueTypeOfIndexInfo(info)
newValueType := c.instantiateType(valueType, m)
if newValueType == valueType {
return info
}
return c.newIndexInfo(info.keyType, newValueType, info.isReadonly, info.declaration, info.components)
Expand Down Expand Up @@ -21932,10 +21956,10 @@ func (c *Checker) appendIndexInfo(indexInfos []*IndexInfo, newInfo *IndexInfo, u
var valueType *Type
var isReadonly bool
if union {
valueType = c.getUnionType([]*Type{info.valueType, newInfo.valueType})
valueType = c.getUnionType([]*Type{c.getValueTypeOfIndexInfo(info), c.getValueTypeOfIndexInfo(newInfo)})
isReadonly = info.isReadonly || newInfo.isReadonly
} else {
valueType = c.getIntersectionType([]*Type{info.valueType, newInfo.valueType})
valueType = c.getIntersectionType([]*Type{c.getValueTypeOfIndexInfo(info), c.getValueTypeOfIndexInfo(newInfo)})
isReadonly = info.isReadonly && newInfo.isReadonly
}
indexInfos[i] = c.newIndexInfo(info.keyType, valueType, isReadonly, nil, nil)
Expand Down Expand Up @@ -22129,7 +22153,7 @@ func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name s
}
indexTypes = append(indexTypes, indexType)
} else {
indexTypes = append(indexTypes, indexInfo.valueType)
indexTypes = append(indexTypes, c.getValueTypeOfIndexInfo(indexInfo))
}
} else if isObjectLiteralType(t) && t.objectFlags&ObjectFlagsContainsSpread == 0 {
checkFlags |= ast.CheckFlagsWritePartial
Expand Down Expand Up @@ -26010,6 +26034,40 @@ func (c *Checker) newIndexInfo(keyType *Type, valueType *Type, isReadonly bool,
return info
}

// getValueTypeOfIndexInfo returns the value type of an index signature, resolving it
// lazily from the declaration when it was deferred by getIndexInfosOfIndexSymbol.
// Resolution is tracked with the type-resolution stack so direct recursion such as
// `interface I { [k: string]: I[string] }` reports circularity instead of caching the
// in-progress unresolved sentinel as a usable any-flagged type (#64998 review).
func (c *Checker) getValueTypeOfIndexInfo(info *IndexInfo) *Type {
if info.valueType != nil {
return info.valueType
}
if info.declaration == nil {
info.valueType = c.anyType
return info.valueType
}
returnTypeNode := info.declaration.Type()
if returnTypeNode == nil {
info.valueType = c.anyType
return info.valueType
}
if !c.pushTypeResolution(info, TypeSystemPropertyNameIndexInfoValueType) {
return c.errorType
}
t := c.instantiateType(c.getTypeFromTypeNode(returnTypeNode), info.mapper)
if !c.popTypeResolution() {
// Index signature value types are the annotation on the index signature
// declaration (analogous to a return type annotation).
c.error(returnTypeNode, diagnostics.Return_type_annotation_circularly_references_itself)
t = c.errorType
}
if info.valueType == nil {
info.valueType = t
}
return info.valueType
}

func (c *Checker) getRegularTypeOfLiteralType(t *Type) *Type {
if t.flags&TypeFlagsFreshable != 0 {
return t.AsLiteralType().regularType
Expand Down Expand Up @@ -27862,9 +27920,9 @@ func (c *Checker) getPropertyTypeForIndexType(originalObjectType *Type, objectTy
indexNode := getIndexNodeForAccessExpression(accessNode)
c.error(indexNode, diagnostics.Type_0_cannot_be_used_as_an_index_type, c.TypeToString(indexType))
if accessFlags&AccessFlagsIncludeUndefined != 0 {
return c.getUnionType([]*Type{indexInfo.valueType, c.missingType})
return c.getUnionType([]*Type{c.getValueTypeOfIndexInfo(indexInfo), c.missingType})
} else {
return indexInfo.valueType
return c.getValueTypeOfIndexInfo(indexInfo)
}
}
c.errorIfWritingToReadonlyIndex(indexInfo, objectType, accessExpression)
Expand All @@ -27877,9 +27935,9 @@ func (c *Checker) getPropertyTypeForIndexType(originalObjectType *Type, objectTy
(indexType.symbol != nil &&
indexType.flags&TypeFlagsEnumLiteral != 0 &&
c.getParentOfSymbol(indexType.symbol) == objectType.symbol)) {
return c.getUnionType([]*Type{indexInfo.valueType, c.missingType})
return c.getUnionType([]*Type{c.getValueTypeOfIndexInfo(indexInfo), c.missingType})
}
return indexInfo.valueType
return c.getValueTypeOfIndexInfo(indexInfo)
}
if indexType.flags&TypeFlagsNever != 0 {
return c.neverType
Expand Down Expand Up @@ -30113,7 +30171,7 @@ func (c *Checker) getTypeOfPropertyOrIndexSignatureOfType(t *Type, name string)
}
indexInfo := c.getApplicableIndexInfoForName(t, name)
if indexInfo != nil {
return c.addOptionalityEx(indexInfo.valueType, true /*isProperty*/, true /*isOptional*/)
return c.addOptionalityEx(c.getValueTypeOfIndexInfo(indexInfo), true /*isProperty*/, true /*isOptional*/)
}
return nil
}
Expand Down Expand Up @@ -30747,7 +30805,7 @@ func (c *Checker) getContextualTypeForObjectLiteralElement(element *ast.Node, co
if indexInfo == nil {
return nil
}
return indexInfo.valueType
return c.getValueTypeOfIndexInfo(indexInfo)
}, true /*noReductions*/)
}
}
Expand Down Expand Up @@ -31453,7 +31511,7 @@ func (c *Checker) getTypeFromIndexInfosOfContextualType(t *Type, name string, na
if indexInfo == nil {
return nil
}
return indexInfo.valueType
return c.getValueTypeOfIndexInfo(indexInfo)
}

func (c *Checker) isCircularMappedProperty(symbol *ast.Symbol) bool {
Expand Down Expand Up @@ -32903,7 +32961,7 @@ func (c *Checker) getApplicableIndexSymbol(t *Type, keyType *Type) *ast.Symbol {
symbol.SetValueDeclaration(declarations[0])
symbol.SetParent(t.symbol)
links := c.valueSymbolLinks.Get(symbol)
links.resolvedType = info.valueType
links.resolvedType = c.getValueTypeOfIndexInfo(info)
info.indexSymbol = symbol
}
}
Expand Down
4 changes: 4 additions & 0 deletions tsc/internal/checker/exports.go
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,10 @@ func (c *Checker) GetIndexInfoOfType(t *Type, keyType *Type) *IndexInfo {
return c.getIndexInfoOfType(t, keyType)
}

func (c *Checker) GetValueTypeOfIndexInfo(info *IndexInfo) *Type {
return c.getValueTypeOfIndexInfo(info)
}

func (c *Checker) GetIndexTypeOfType(t *Type, keyType *Type) *Type {
return c.getIndexTypeOfType(t, keyType)
}
Expand Down
2 changes: 1 addition & 1 deletion tsc/internal/checker/flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -2372,7 +2372,7 @@ func (c *Checker) getTypeOfDestructuredProperty(t *Type, name *ast.Node) *Type {
return propType
}
if indexInfo := c.getApplicableIndexInfoForName(t, text); indexInfo != nil {
return c.includeUndefinedInIndexSignature(indexInfo.valueType)
return c.includeUndefinedInIndexSignature(c.getValueTypeOfIndexInfo(indexInfo))
}
return c.errorType
}
Expand Down
10 changes: 5 additions & 5 deletions tsc/internal/checker/inference.go
Original file line number Diff line number Diff line change
Expand Up @@ -940,18 +940,18 @@ func (c *Checker) inferFromIndexTypes(n *InferenceState, source *Type, target *T
}
for _, info := range c.getIndexInfosOfType(source) {
if c.isApplicableIndexType(info.keyType, targetInfo.keyType) {
propTypes = append(propTypes, info.valueType)
propTypes = append(propTypes, c.getValueTypeOfIndexInfo(info))
}
}
if len(propTypes) != 0 {
c.inferWithPriority(n, c.getUnionType(propTypes), targetInfo.valueType, priority)
c.inferWithPriority(n, c.getUnionType(propTypes), c.getValueTypeOfIndexInfo(targetInfo), priority)
}
}
}
for _, targetInfo := range indexInfos {
sourceInfo := c.getApplicableIndexInfo(source, targetInfo.keyType)
if sourceInfo != nil {
c.inferWithPriority(n, sourceInfo.valueType, targetInfo.valueType, priority)
c.inferWithPriority(n, c.getValueTypeOfIndexInfo(sourceInfo), c.getValueTypeOfIndexInfo(targetInfo), priority)
}
}
}
Expand Down Expand Up @@ -998,7 +998,7 @@ func (c *Checker) inferToMappedType(n *InferenceState, source *Type, target *Typ
propTypes := core.Map(c.getPropertiesOfType(source), c.getTypeOfSymbol)
indexTypes := core.Map(c.getIndexInfosOfType(source), func(info *IndexInfo) *Type {
if info != c.enumNumberIndexInfo {
return info.valueType
return c.getValueTypeOfIndexInfo(info)
}
return c.neverType
})
Expand Down Expand Up @@ -1115,7 +1115,7 @@ func (c *Checker) resolveReverseMappedTypeMembers(t *Type) {
optionalMask := core.IfElse(modifiers&MappedTypeModifiersIncludeOptional != 0, 0, ast.SymbolFlagsOptional)
var indexInfos []*IndexInfo
if indexInfo != nil {
indexInfos = []*IndexInfo{c.newIndexInfo(c.stringType, core.OrElse(c.inferReverseMappedType(indexInfo.valueType, r.mappedType, r.constraintType), c.unknownType), readonlyMask && indexInfo.isReadonly, nil, nil)}
indexInfos = []*IndexInfo{c.newIndexInfo(c.stringType, core.OrElse(c.inferReverseMappedType(c.getValueTypeOfIndexInfo(indexInfo), r.mappedType, r.constraintType), c.unknownType), readonlyMask && indexInfo.isReadonly, nil, nil)}
}
members := ast.NewSymbolTable()
limitedConstraint := c.getLimitedConstraint(t)
Expand Down
2 changes: 1 addition & 1 deletion tsc/internal/checker/jsx.go
Original file line number Diff line number Diff line change
Expand Up @@ -1199,7 +1199,7 @@ func (c *Checker) getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node *ast.
if links.jsxFlags&JsxFlagsIntrinsicIndexedElement != 0 {
indexInfo := c.getApplicableIndexInfoForName(c.getJsxType(JsxNames.IntrinsicElements, node), node.TagName().Text())
if indexInfo != nil {
links.resolvedJsxElementAttributesType = indexInfo.valueType
links.resolvedJsxElementAttributesType = c.getValueTypeOfIndexInfo(indexInfo)
return links.resolvedJsxElementAttributesType
}
}
Expand Down
2 changes: 1 addition & 1 deletion tsc/internal/checker/nodebuilder_hover.go
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ func (b *NodeBuilderImpl) serializeIndexSignaturesOfType(input *Type, baseType *
for _, info := range b.ch.getIndexInfosOfType(input) {
if baseType != nil {
baseInfo := b.ch.getIndexInfoOfType(baseType, info.keyType)
if baseInfo != nil && b.ch.isTypeIdenticalTo(info.valueType, baseInfo.valueType) {
if baseInfo != nil && b.ch.isTypeIdenticalTo(b.ch.getValueTypeOfIndexInfo(info), b.ch.getValueTypeOfIndexInfo(baseInfo)) {
continue
}
}
Expand Down
13 changes: 8 additions & 5 deletions tsc/internal/checker/nodebuilderimpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -2226,15 +2226,18 @@ func (b *NodeBuilderImpl) indexInfoToIndexSignatureDeclarationHelper(indexInfo *

indexingParameter := b.f.NewParameterDeclaration(nil, nil, b.newIdentifier(name, nil /*symbol*/), nil, indexerTypeNode, nil)
if typeNode == nil {
if indexInfo.valueType == nil {
// Resolve deferred index value types (see getIndexInfosOfIndexSymbol / #64998).
// Intentionally empty index infos (no declaration and no value type) still surface as any
// and set encounteredError unless AllowEmptyIndexInfoType is set.
if indexInfo.Declaration() == nil && indexInfo.ValueType() == nil {
typeNode = b.f.NewKeywordTypeNode(ast.KindAnyKeyword)
if b.ctx.flags&nodebuilder.FlagsAllowEmptyIndexInfoType == 0 {
b.ctx.encounteredError = true
}
} else {
typeNode = b.typeToTypeNode(indexInfo.valueType)
typeNode = b.typeToTypeNode(b.ch.getValueTypeOfIndexInfo(indexInfo))
}
}
if indexInfo.valueType == nil && b.ctx.flags&nodebuilder.FlagsAllowEmptyIndexInfoType == 0 {
b.ctx.encounteredError = true
}
b.ctx.approximateLength += len(name) + 4
var modifiers *ast.ModifierList
if indexInfo.isReadonly {
Expand Down
Loading