diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index d19cf6b214b79..eb66e6e677e3f 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -6749,9 +6749,9 @@ func (c *Checker) getIterationTypesOfMethod(t *Type, resolver *IterationTypesRes mapper := methodType.Mapper() var nextType *Type if methodName == "next" { - nextType = mapper.Map(typeParameters[2]) + nextType = getMappedType(typeParameters[2], mapper) } - return IterationTypes{mapper.Map(typeParameters[0]), mapper.Map(typeParameters[1]), nextType} + return IterationTypes{getMappedType(typeParameters[0], mapper), getMappedType(typeParameters[1], mapper), nextType} } } // Extract the first parameter and return type of each signature. @@ -21403,7 +21403,7 @@ func (c *Checker) getArrayMemberCallSignatures(t *Type) []*Signature { } // Transform the type from `(A[] | B[])["member"]` to `(A | B)[]["member"]` (since we pretend array is covariant anyway). arrayArg := c.mapType(t, func(t *Type) *Type { - return t.Mapper().Map(core.IfElse(c.isReadonlyArraySymbol(t.symbol.Parent), c.globalReadonlyArrayType, c.globalArrayType).AsInterfaceType().TypeParameters()[0]) + return getMappedType(core.IfElse(c.isReadonlyArraySymbol(t.symbol.Parent), c.globalReadonlyArrayType, c.globalArrayType).AsInterfaceType().TypeParameters()[0], t.Mapper()) }) arrayType := c.createArrayTypeEx(arrayArg, someType(t, func(t *Type) bool { return c.isReadonlyArraySymbol(t.symbol.Parent) @@ -22562,7 +22562,7 @@ func (c *Checker) instantiateTypeWorker(t *Type, m *TypeMapper, alias *TypeAlias flags := t.flags switch { case flags&TypeFlagsTypeParameter != 0: - return m.Map(t) + return getMappedType(t, m) case flags&TypeFlagsObject != 0: objectFlags := t.objectFlags if objectFlags&(ObjectFlagsReference|ObjectFlagsAnonymous|ObjectFlagsMapped) != 0 { @@ -22837,7 +22837,7 @@ func (c *Checker) getConditionalTypeInstantiation(t *Type, mapper *TypeMapper, f checkType := root.checkType var distributionType *Type if root.isDistributive { - distributionType = c.getReducedType(newMapper.Map(checkType)) + distributionType = c.getReducedType(getMappedType(checkType, newMapper)) } // Distributive conditional types are distributed over union types. For example, when the // distributive conditional type T extends U ? X : Y is instantiated with A | B for T, the @@ -23352,12 +23352,48 @@ func (c *Checker) getTypeFromTypeReference(node *ast.Node) *Type { } else if t := c.getIntendedTypeFromJSDocTypeReference(node); t != nil { links.resolvedType = t } else { - links.resolvedType = c.getTypeReferenceType(node, c.getSymbolFromTypeReference(node)) + links.resolvedType = c.getDistributedTypeParameter(node, c.getTypeReferenceType(node, c.getSymbolFromTypeReference(node))) } } return links.resolvedType } +func (c *Checker) getDistributedTypeParameter(node *ast.Node, t *Type) *Type { + if t.flags&TypeFlagsTypeParameter != 0 && !t.AsTypeParameter().isDistributed { + for n := node.Parent; n != nil && !ast.IsStatement(n); n = n.Parent { + if ast.IsConditionalTypeNode(n) { + if checkTypeNode := n.AsConditionalTypeNode().CheckType; isSimpleIdentifierTypeReference(checkTypeNode) && c.getSymbolFromTypeReference(checkTypeNode) == t.symbol { + // If node is contained in a distributive conditional type for the given type parameter, + // return the distributed form of the type parameter. + return c.getDistributedTypeFromTypeParameter(t) + } + } + } + } + return t +} + +func (c *Checker) getDistributedTypeFromTypeParameter(t *Type) *Type { + tp := t.AsTypeParameter() + if tp.distributedType == nil { + tp.distributedType = c.newTypeParameter(t.symbol) + tp.distributedType.AsTypeParameter().isDistributed = true + tp.distributedType.AsTypeParameter().constraint = t + } + return tp.distributedType +} + +func getNonDistributedTypeParameter(t *Type) *Type { + if t.flags&TypeFlagsTypeParameter != 0 && t.AsTypeParameter().isDistributed { + return t.AsTypeParameter().constraint + } + return t +} + +func isSimpleIdentifierTypeReference(node *ast.Node) bool { + return ast.IsTypeReferenceNode(node) && ast.IsIdentifier(node.AsTypeReferenceNode().TypeName) && node.TypeArgumentList() == nil +} + func (c *Checker) getIntendedTypeFromJSDocTypeReference(node *ast.Node) *Type { if node.Flags&ast.NodeFlagsJSDoc != 0 && ast.IsTypeReferenceNode(node) { typeName := node.AsTypeReferenceNode().TypeName @@ -24802,11 +24838,11 @@ func (c *Checker) getTailRecursionRoot(newType *Type, newMapper *TypeMapper) (*C newRoot := newType.AsConditionalType().root if len(newRoot.outerTypeParameters) != 0 { typeParamMapper := c.combineTypeMappers(newType.AsConditionalType().mapper, newMapper) - typeArguments := core.Map(newRoot.outerTypeParameters, func(t *Type) *Type { return typeParamMapper.Map(t) }) + typeArguments := core.Map(newRoot.outerTypeParameters, typeParamMapper.Map) newRootMapper := newTypeMapper(newRoot.outerTypeParameters, typeArguments) var newCheckType *Type if newRoot.isDistributive { - newCheckType = newRootMapper.Map(newRoot.checkType) + newCheckType = getMappedType(newRoot.checkType, newRootMapper) } if newCheckType == nil || newCheckType == newRoot.checkType || newCheckType.flags&(TypeFlagsUnion|TypeFlagsNever) == 0 { return newRoot, newRootMapper @@ -28490,7 +28526,7 @@ func (c *Checker) getModifiersTypeFromMappedType(t *Type) *Type { constraint := c.getConstraintTypeFromMappedType(declaredType) extendedConstraint := constraint if constraint != nil && constraint.flags&TypeFlagsTypeParameter != 0 { - extendedConstraint = c.getConstraintOfTypeParameter(constraint) + extendedConstraint = c.getConstraintOfTypeParameter(getNonDistributedTypeParameter(constraint)) } if extendedConstraint != nil && extendedConstraint.flags&TypeFlagsIndex != 0 { m.modifiersType = c.instantiateType(extendedConstraint.AsIndexType().target, m.mapper) diff --git a/tsc/internal/checker/exports.go b/tsc/internal/checker/exports.go index f75b8c6cd714e..f617d37557529 100644 --- a/tsc/internal/checker/exports.go +++ b/tsc/internal/checker/exports.go @@ -386,3 +386,7 @@ func (c *Checker) GetWidenedType(t *Type) *Type { func (c *Checker) CompareSymbols(s1, s2 *ast.Symbol) int { return c.compareSymbols(s1, s2) } + +func IsDistributedTypeParameter(t *Type) bool { + return t.flags&TypeFlagsTypeParameter != 0 && t.AsTypeParameter().isDistributed +} diff --git a/tsc/internal/checker/inference.go b/tsc/internal/checker/inference.go index 4bb9d2923d9ab..cb4ca250b61fd 100644 --- a/tsc/internal/checker/inference.go +++ b/tsc/internal/checker/inference.go @@ -564,7 +564,7 @@ func (c *Checker) inferToConditionalType(n *InferenceState, source *Type, target } func (c *Checker) inferToTemplateLiteralType(n *InferenceState, source *Type, target *TemplateLiteralType) { - matches := c.inferTypesFromTemplateLiteralType(source, target) + matches := c.inferTypesFromTemplateLiteralType(source, target, c.compareTypesAssignable) types := target.types // When the target template literal contains only placeholders (meaning that inference is intended to extract // single characters and remainder strings) and inference fails to produce matches, we want to infer 'never' for @@ -1518,6 +1518,7 @@ func (c *Checker) getTypeFromInference(inference *InferenceInfo) *Type { func getInferenceInfoForType(n *InferenceState, t *Type) *InferenceInfo { if t.flags&TypeFlagsTypeVariable != 0 { + t = getNonDistributedTypeParameter(t) for _, inference := range n.inferences { if t == inference.typeParameter { return inference diff --git a/tsc/internal/checker/mapper.go b/tsc/internal/checker/mapper.go index 6393911137576..24a94c94ffdf1 100644 --- a/tsc/internal/checker/mapper.go +++ b/tsc/internal/checker/mapper.go @@ -37,6 +37,10 @@ type TypeMapperData interface { // Factory functions +func getMappedType(t *Type, mapper *TypeMapper) *Type { + return mapper.Map(getNonDistributedTypeParameter(t)) +} + func newTypeMapper(sources []*Type, targets []*Type) *TypeMapper { if len(sources) == 1 { return newSimpleTypeMapper(sources[0], targets[0]) @@ -53,13 +57,13 @@ func (c *Checker) combineTypeMappers(m1 *TypeMapper, m2 *TypeMapper) *TypeMapper func (c *Checker) mapTypeWithCompositeMapper(t *Type, m1 *TypeMapper, m2 *TypeMapper) *Type { if m1 == nil { - return m2.Map(t) + return getMappedType(t, m2) } - t1 := m1.Map(t) + t1 := getMappedType(t, m1) if t1 != t { return c.instantiateType(t1, m2) } - return m2.Map(t) + return getMappedType(t, m2) } func mergeTypeMappers(m1 *TypeMapper, m2 *TypeMapper) *TypeMapper { @@ -71,16 +75,16 @@ func mergeTypeMappers(m1 *TypeMapper, m2 *TypeMapper) *TypeMapper { func prependTypeMapping(source *Type, target *Type, mapper *TypeMapper) *TypeMapper { if mapper == nil { - return newSimpleTypeMapper(source, target) + return newSimpleTypeMapper(getNonDistributedTypeParameter(source), target) } - return newMergedTypeMapper(newSimpleTypeMapper(source, target), mapper) + return newMergedTypeMapper(newSimpleTypeMapper(getNonDistributedTypeParameter(source), target), mapper) } func appendTypeMapping(mapper *TypeMapper, source *Type, target *Type) *TypeMapper { if mapper == nil { - return newSimpleTypeMapper(source, target) + return newSimpleTypeMapper(getNonDistributedTypeParameter(source), target) } - return newMergedTypeMapper(mapper, newSimpleTypeMapper(source, target)) + return newMergedTypeMapper(mapper, newSimpleTypeMapper(getNonDistributedTypeParameter(source), target)) } // Maps forward-references to later types parameters to the empty object type. diff --git a/tsc/internal/checker/nodebuilderimpl.go b/tsc/internal/checker/nodebuilderimpl.go index 0f0a010195363..f740a1161a5f7 100644 --- a/tsc/internal/checker/nodebuilderimpl.go +++ b/tsc/internal/checker/nodebuilderimpl.go @@ -3290,14 +3290,6 @@ func (b *NodeBuilderImpl) visitAndTransformType(t *Type, transform func(b *NodeB } func (b *NodeBuilderImpl) typeToTypeNode(t *Type) *ast.TypeNode { - // Push type onto typeStack for expansion depth tracking - if b.ctx.maxExpansionDepth >= 0 && t != nil { - b.ctx.typeStack = append(b.ctx.typeStack, t) - defer func() { - b.ctx.typeStack = b.ctx.typeStack[:len(b.ctx.typeStack)-1] - }() - } - inTypeAlias := b.ctx.flags & nodebuilder.FlagsInTypeAlias b.ctx.flags &^= nodebuilder.FlagsInTypeAlias @@ -3311,6 +3303,16 @@ func (b *NodeBuilderImpl) typeToTypeNode(t *Type) *ast.TypeNode { return b.f.NewKeywordTypeNode(ast.KindAnyKeyword) } + t = getNonDistributedTypeParameter(t) + + // Push type onto typeStack for expansion depth tracking + if b.ctx.maxExpansionDepth >= 0 { + b.ctx.typeStack = append(b.ctx.typeStack, t) + defer func() { + b.ctx.typeStack = b.ctx.typeStack[:len(b.ctx.typeStack)-1] + }() + } + if b.ctx.flags&nodebuilder.FlagsNoTypeReduction == 0 { t = b.ch.getReducedType(t) } diff --git a/tsc/internal/checker/nodecopy.go b/tsc/internal/checker/nodecopy.go index efef4c7f5d505..822a68634fdb0 100644 --- a/tsc/internal/checker/nodecopy.go +++ b/tsc/internal/checker/nodecopy.go @@ -423,7 +423,7 @@ func getExistingNodeTreeVisitor(b *NodeBuilderImpl, bound *recoveryBoundary) *as } if s.Flags&ast.SymbolFlagsTypeParameter != 0 { declaredType := b.ch.getDeclaredTypeOfSymbol(s) - if b.ctx.mapper != nil && b.ctx.mapper.Map(declaredType) != declaredType { + if b.ctx.mapper != nil && getMappedType(declaredType, b.ctx.mapper) != declaredType { return nil // refers to type parameter remapped by context (TODO improvement: just return the remapped param name?) } } diff --git a/tsc/internal/checker/relater.go b/tsc/internal/checker/relater.go index 9008420fb7cd1..d11740001284c 100644 --- a/tsc/internal/checker/relater.go +++ b/tsc/internal/checker/relater.go @@ -2363,7 +2363,7 @@ func (c *Checker) templateLiteralTypesDefinitelyUnrelated(source *TemplateLitera } func (c *Checker) isTypeMatchedByTemplateLiteralType(source *Type, target *TemplateLiteralType, compareTypes TypeComparer) bool { - inferences := c.inferTypesFromTemplateLiteralType(source, target) + inferences := c.inferTypesFromTemplateLiteralType(source, target, compareTypes) if inferences != nil { for i, inference := range inferences { if !c.isValidTypeForTemplateLiteralPlaceholder(inference, target.types[i], compareTypes) { @@ -2375,14 +2375,14 @@ func (c *Checker) isTypeMatchedByTemplateLiteralType(source *Type, target *Templ return false } -func (c *Checker) inferTypesFromTemplateLiteralType(source *Type, target *TemplateLiteralType) []*Type { +func (c *Checker) inferTypesFromTemplateLiteralType(source *Type, target *TemplateLiteralType, compareTypes TypeComparer) []*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])) { + if compareTypes(c.getBaseConstraintOrType(s), c.getBaseConstraintOrType(target.types[i]), false /*partialMatch*/) != TernaryFalse { return s } return c.getStringLikeTypeForType(s) @@ -4800,6 +4800,8 @@ func (r *Relater) reportRelationError(message *diagnostics.Message, source *Type if targetFlags&TypeFlagsTypeParameter != 0 && target != r.c.markerSuperTypeForCheck && target != r.c.markerSubTypeForCheck { constraint := r.c.getBaseConstraintOfType(target) switch { + case IsDistributedTypeParameter(target) && r.c.isTypeAssignableTo(generalizedSource, target.AsTypeParameter().constraint): + r.reportError(diagnostics.X_0_is_only_assignable_to_the_non_distributed_1_and_1_has_been_distributed_here, generalizedSourceType, targetType) 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): diff --git a/tsc/internal/checker/types.go b/tsc/internal/checker/types.go index 2e8bacbd5df29..f411f29628358 100644 --- a/tsc/internal/checker/types.go +++ b/tsc/internal/checker/types.go @@ -1170,7 +1170,9 @@ type TypeParameter struct { target *Type mapper *TypeMapper isThisType bool + isDistributed bool resolvedDefaultType *Type + distributedType *Type } func (t *TypeParameter) IsThisType() bool { return t.isThisType } diff --git a/tsc/internal/diagnostics/diagnosticMessages.json b/tsc/internal/diagnostics/diagnosticMessages.json index 95383075f4012..3ab1773c12218 100644 --- a/tsc/internal/diagnostics/diagnosticMessages.json +++ b/tsc/internal/diagnostics/diagnosticMessages.json @@ -4768,6 +4768,10 @@ "category": "Error", "code": 5112 }, + "'{0}' is only assignable to the non-distributed '{1}' and '{1}' has been distributed here.": { + "category": "Error", + "code": 5113 + }, "Generates a sourcemap for each corresponding '.d.ts' file.": { "category": "Message", diff --git a/tsc/internal/diagnostics/diagnostics_generated.go b/tsc/internal/diagnostics/diagnostics_generated.go index 44194d9cca924..646723f59553b 100644 --- a/tsc/internal/diagnostics/diagnostics_generated.go +++ b/tsc/internal/diagnostics/diagnostics_generated.go @@ -2386,6 +2386,8 @@ var Visit_https_Colon_Slash_Slashaka_ms_Slashts6_for_migration_information = &Me var X_tsconfig_json_is_present_but_will_not_be_loaded_if_files_are_specified_on_commandline_Use_ignoreConfig_to_skip_this_error = &Message{code: 5112, category: CategoryError, key: "tsconfig_json_is_present_but_will_not_be_loaded_if_files_are_specified_on_commandline_Use_ignoreConf_5112", text: "tsconfig.json is present but will not be loaded if files are specified on commandline. Use '--ignoreConfig' to skip this error."} +var X_0_is_only_assignable_to_the_non_distributed_1_and_1_has_been_distributed_here = &Message{code: 5113, category: CategoryError, key: "_0_is_only_assignable_to_the_non_distributed_1_and_1_has_been_distributed_here_5113", text: "'{0}' is only assignable to the non-distributed '{1}' and '{1}' has been distributed here."} + var Generates_a_sourcemap_for_each_corresponding_d_ts_file = &Message{code: 6000, category: CategoryMessage, key: "Generates_a_sourcemap_for_each_corresponding_d_ts_file_6000", text: "Generates a sourcemap for each corresponding '.d.ts' file."} var Concatenate_and_emit_output_to_single_file = &Message{code: 6001, category: CategoryMessage, key: "Concatenate_and_emit_output_to_single_file_6001", text: "Concatenate and emit output to single file."} @@ -6812,6 +6814,8 @@ func keyToMessage(key Key) *Message { return Visit_https_Colon_Slash_Slashaka_ms_Slashts6_for_migration_information case "tsconfig_json_is_present_but_will_not_be_loaded_if_files_are_specified_on_commandline_Use_ignoreConf_5112": return X_tsconfig_json_is_present_but_will_not_be_loaded_if_files_are_specified_on_commandline_Use_ignoreConfig_to_skip_this_error + case "_0_is_only_assignable_to_the_non_distributed_1_and_1_has_been_distributed_here_5113": + return X_0_is_only_assignable_to_the_non_distributed_1_and_1_has_been_distributed_here case "Generates_a_sourcemap_for_each_corresponding_d_ts_file_6000": return Generates_a_sourcemap_for_each_corresponding_d_ts_file case "Concatenate_and_emit_output_to_single_file_6001": diff --git a/tsc/internal/fourslash/tests/quickInfoDistributedTypeParameter_test.go b/tsc/internal/fourslash/tests/quickInfoDistributedTypeParameter_test.go new file mode 100644 index 0000000000000..e29c90852030c --- /dev/null +++ b/tsc/internal/fourslash/tests/quickInfoDistributedTypeParameter_test.go @@ -0,0 +1,26 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/TypeScript/tsc/internal/fourslash" + "github.com/microsoft/TypeScript/tsc/internal/testutil" +) + +func TestQuickInfoDistributedTypeParameter(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `type Conditional = + T/*check*/ extends T/*extends*/ + ? T/*trueType*/ + : T/*falseType*/; + +type NonDistributed = [T/*nonDistributed*/] extends [unknown] ? T : never;` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + f.VerifyQuickInfoAt(t, "check", "(type parameter) (distributed) T in type Conditional", "") + f.VerifyQuickInfoAt(t, "extends", "(type parameter) (distributed) T in type Conditional", "") + f.VerifyQuickInfoAt(t, "trueType", "(type parameter) (distributed) T in type Conditional", "") + f.VerifyQuickInfoAt(t, "falseType", "(type parameter) (distributed) T in type Conditional", "") + f.VerifyQuickInfoAt(t, "nonDistributed", "(type parameter) T in type NonDistributed", "") +} diff --git a/tsc/internal/ls/hover.go b/tsc/internal/ls/hover.go index 7a97db6a9fb92..7a3ba006efeff 100644 --- a/tsc/internal/ls/hover.go +++ b/tsc/internal/ls/hover.go @@ -873,6 +873,11 @@ func getQuickInfoAndDeclarationAtLocation(c *checker.Checker, symbol *ast.Symbol dpw.WritePunctuation("(") dpw.Write("type parameter") dpw.WritePunctuation(") ") + if ast.IsIdentifier(node) && ast.IsTypeReferenceNode(node.Parent) && checker.IsDistributedTypeParameter(c.GetTypeAtLocation(node.Parent)) { + dpw.WritePunctuation("(") + dpw.Write("distributed") + dpw.WritePunctuation(") ") + } tp := c.GetDeclaredTypeOfSymbol(symbol) writeSymbolClassified(symbol, container, ast.SymbolFlagsNone, symbolFormatFlags) cons := c.GetConstraintOfTypeParameter(tp) diff --git a/tsc/testdata/baselines/reference/compiler/distributedTypeParameters.errors.txt b/tsc/testdata/baselines/reference/compiler/distributedTypeParameters.errors.txt new file mode 100644 index 0000000000000..0b3ec10274bde --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/distributedTypeParameters.errors.txt @@ -0,0 +1,138 @@ +distributedTypeParameters.ts(8,17): error TS2344: Type 'B' does not satisfy the constraint 'A'. + 'B' is only assignable to the non-distributed 'A' and 'A' has been distributed here. +distributedTypeParameters.ts(15,17): error TS2344: Type 'B' does not satisfy the constraint 'A'. + 'B' is only assignable to the non-distributed 'A' and 'A' has been distributed here. +distributedTypeParameters.ts(29,17): error TS2344: Type 'A & B' does not satisfy the constraint 'A'. + 'A & B' is only assignable to the non-distributed 'A' and 'A' has been distributed here. +distributedTypeParameters.ts(41,13): error TS2344: Type 'B' does not satisfy the constraint 'A'. + 'B' is only assignable to the non-distributed 'A' and 'A' has been distributed here. +distributedTypeParameters.ts(45,13): error TS2344: Type 'B' does not satisfy the constraint 'A'. + 'B' is only assignable to the non-distributed 'A' and 'A' has been distributed here. +distributedTypeParameters.ts(50,13): error TS2344: Type 'C' does not satisfy the constraint 'A'. + 'C' is only assignable to the non-distributed 'A' and 'A' has been distributed here. +distributedTypeParameters.ts(56,15): error TS2344: Type 'C' does not satisfy the constraint 'A'. + 'C' is only assignable to the non-distributed 'A' and 'A' has been distributed here. +distributedTypeParameters.ts(57,15): error TS2344: Type 'C' does not satisfy the constraint 'B'. + 'C' is only assignable to the non-distributed 'B' and 'B' has been distributed here. + + +==== distributedTypeParameters.ts (8 errors) ==== + // https://github.com/microsoft/TypeScript/issues/63708 + + type Show = [A, B] & {}; + + type Issue1 = + A extends unknown ? + B extends unknown ? + Show : // Error + ~ +!!! error TS2344: Type 'B' does not satisfy the constraint 'A'. +!!! error TS2344: 'B' is only assignable to the non-distributed 'A' and 'A' has been distributed here. + never : + never; + + type Issue2 = + B extends unknown ? + A extends unknown ? + Show : // Error + ~ +!!! error TS2344: Type 'B' does not satisfy the constraint 'A'. +!!! error TS2344: 'B' is only assignable to the non-distributed 'A' and 'A' has been distributed here. + never : + never; + + type Issue3 = + A extends B ? + B extends A ? + Show : + never : + never; + + type Issue4 = + B extends A ? + A extends B ? + Show : // Error + ~ +!!! error TS2344: Type 'A & B' does not satisfy the constraint 'A'. +!!! error TS2344: 'A & B' is only assignable to the non-distributed 'A' and 'A' has been distributed here. + never : + never; + + type X1 = Issue1<0 | 1, 0 | 1>; + type X2 = Issue2<0 | 1, 0 | 1>; + type X3 = Issue3<0 | 1, 0 | 1>; + type X4 = Issue4<0 | 1, 0 | 1>; + + type T1 = + A extends B ? + never : + Show; // Error + ~ +!!! error TS2344: Type 'B' does not satisfy the constraint 'A'. +!!! error TS2344: 'B' is only assignable to the non-distributed 'A' and 'A' has been distributed here. + + type T2 = + A extends B ? + Show : // Error + ~ +!!! error TS2344: Type 'B' does not satisfy the constraint 'A'. +!!! error TS2344: 'B' is only assignable to the non-distributed 'A' and 'A' has been distributed here. + never; + + type T3 = + A extends unknown ? + Show : // Error + ~ +!!! error TS2344: Type 'C' does not satisfy the constraint 'A'. +!!! error TS2344: 'C' is only assignable to the non-distributed 'A' and 'A' has been distributed here. + Show; + + type T4 = + A extends unknown ? + B extends unknown ? + Show : // Error + ~ +!!! error TS2344: Type 'C' does not satisfy the constraint 'A'. +!!! error TS2344: 'C' is only assignable to the non-distributed 'A' and 'A' has been distributed here. + Show : // Error + ~ +!!! error TS2344: Type 'C' does not satisfy the constraint 'B'. +!!! error TS2344: 'C' is only assignable to the non-distributed 'B' and 'B' has been distributed here. + never; + + type T5 = + [A] extends [B] ? + Show : + never; + + // Ensure mapped type modifiers are computed correctly, example from type-fest + + type IsReadonlyKeyOf = + IsAny extends true ? never + : Key extends unknown // For distributing `Key` + ? Type extends unknown // For distributing `Type` + ? IsEqual< + {[K in Key]: Type[Key]}, + {readonly [K in Key]: Type[Key]} + > + : never // Should never happen + : never; // Should never happen + + type IsAny = 0 extends 1 & NoInfer ? true : false; + + type IsEqual = + [A] extends [B] + ? [B] extends [A] + ? _IsEqual + : false + : false; + + type _IsEqual = + (() => G extends A & G | G ? 1 : 2) extends + (() => G extends B & G | G ? 1 : 2) + ? true + : false; + + type T10 = IsReadonlyKeyOf<{ a: string }, 'a'>; // false + type T11 = IsReadonlyKeyOf<{ readonly b: string }, 'b'>; // true + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/distributedTypeParameters.symbols b/tsc/testdata/baselines/reference/compiler/distributedTypeParameters.symbols new file mode 100644 index 0000000000000..4d04f842c3e63 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/distributedTypeParameters.symbols @@ -0,0 +1,305 @@ +//// [tests/cases/compiler/distributedTypeParameters.ts] //// + +=== distributedTypeParameters.ts === +// https://github.com/microsoft/TypeScript/issues/63708 + +type Show = [A, B] & {}; +>Show : Symbol(Show, Decl(distributedTypeParameters.ts, 0, 0)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 2, 10)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 2, 12)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 2, 10)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 2, 10)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 2, 12)) + +type Issue1 = +>Issue1 : Symbol(Issue1, Decl(distributedTypeParameters.ts, 2, 40)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 4, 12)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 4, 14)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 4, 12)) + + A extends unknown ? +>A : Symbol(A, Decl(distributedTypeParameters.ts, 4, 12)) + + B extends unknown ? +>B : Symbol(B, Decl(distributedTypeParameters.ts, 4, 14)) + + Show : // Error +>Show : Symbol(Show, Decl(distributedTypeParameters.ts, 0, 0)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 4, 12)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 4, 14)) + + never : + never; + +type Issue2 = +>Issue2 : Symbol(Issue2, Decl(distributedTypeParameters.ts, 9, 10)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 11, 12)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 11, 14)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 11, 12)) + + B extends unknown ? +>B : Symbol(B, Decl(distributedTypeParameters.ts, 11, 14)) + + A extends unknown ? +>A : Symbol(A, Decl(distributedTypeParameters.ts, 11, 12)) + + Show : // Error +>Show : Symbol(Show, Decl(distributedTypeParameters.ts, 0, 0)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 11, 12)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 11, 14)) + + never : + never; + +type Issue3 = +>Issue3 : Symbol(Issue3, Decl(distributedTypeParameters.ts, 16, 10)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 18, 12)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 18, 14)) + + A extends B ? +>A : Symbol(A, Decl(distributedTypeParameters.ts, 18, 12)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 18, 14)) + + B extends A ? +>B : Symbol(B, Decl(distributedTypeParameters.ts, 18, 14)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 18, 12)) + + Show : +>Show : Symbol(Show, Decl(distributedTypeParameters.ts, 0, 0)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 18, 12)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 18, 14)) + + never : + never; + +type Issue4 = +>Issue4 : Symbol(Issue4, Decl(distributedTypeParameters.ts, 23, 10)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 25, 12)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 25, 14)) + + B extends A ? +>B : Symbol(B, Decl(distributedTypeParameters.ts, 25, 14)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 25, 12)) + + A extends B ? +>A : Symbol(A, Decl(distributedTypeParameters.ts, 25, 12)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 25, 14)) + + Show : // Error +>Show : Symbol(Show, Decl(distributedTypeParameters.ts, 0, 0)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 25, 12)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 25, 14)) + + never : + never; + +type X1 = Issue1<0 | 1, 0 | 1>; +>X1 : Symbol(X1, Decl(distributedTypeParameters.ts, 30, 10)) +>Issue1 : Symbol(Issue1, Decl(distributedTypeParameters.ts, 2, 40)) + +type X2 = Issue2<0 | 1, 0 | 1>; +>X2 : Symbol(X2, Decl(distributedTypeParameters.ts, 32, 31)) +>Issue2 : Symbol(Issue2, Decl(distributedTypeParameters.ts, 9, 10)) + +type X3 = Issue3<0 | 1, 0 | 1>; +>X3 : Symbol(X3, Decl(distributedTypeParameters.ts, 33, 31)) +>Issue3 : Symbol(Issue3, Decl(distributedTypeParameters.ts, 16, 10)) + +type X4 = Issue4<0 | 1, 0 | 1>; +>X4 : Symbol(X4, Decl(distributedTypeParameters.ts, 34, 31)) +>Issue4 : Symbol(Issue4, Decl(distributedTypeParameters.ts, 23, 10)) + +type T1 = +>T1 : Symbol(T1, Decl(distributedTypeParameters.ts, 35, 31)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 37, 8)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 37, 25)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 37, 8)) + + A extends B ? +>A : Symbol(A, Decl(distributedTypeParameters.ts, 37, 8)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 37, 25)) + + never : + Show; // Error +>Show : Symbol(Show, Decl(distributedTypeParameters.ts, 0, 0)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 37, 8)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 37, 25)) + +type T2 = +>T2 : Symbol(T2, Decl(distributedTypeParameters.ts, 40, 15)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 42, 8)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 42, 25)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 42, 8)) + + A extends B ? +>A : Symbol(A, Decl(distributedTypeParameters.ts, 42, 8)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 42, 25)) + + Show : // Error +>Show : Symbol(Show, Decl(distributedTypeParameters.ts, 0, 0)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 42, 8)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 42, 25)) + + never; + +type T3 = +>T3 : Symbol(T3, Decl(distributedTypeParameters.ts, 45, 10)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 47, 8)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 47, 10)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 47, 8)) +>C : Symbol(C, Decl(distributedTypeParameters.ts, 47, 23)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 47, 10)) + + A extends unknown ? +>A : Symbol(A, Decl(distributedTypeParameters.ts, 47, 8)) + + Show : // Error +>Show : Symbol(Show, Decl(distributedTypeParameters.ts, 0, 0)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 47, 8)) +>C : Symbol(C, Decl(distributedTypeParameters.ts, 47, 23)) + + Show; +>Show : Symbol(Show, Decl(distributedTypeParameters.ts, 0, 0)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 47, 10)) +>C : Symbol(C, Decl(distributedTypeParameters.ts, 47, 23)) + +type T4 = +>T4 : Symbol(T4, Decl(distributedTypeParameters.ts, 50, 15)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 52, 8)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 52, 10)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 52, 8)) +>C : Symbol(C, Decl(distributedTypeParameters.ts, 52, 23)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 52, 10)) + + A extends unknown ? +>A : Symbol(A, Decl(distributedTypeParameters.ts, 52, 8)) + + B extends unknown ? +>B : Symbol(B, Decl(distributedTypeParameters.ts, 52, 10)) + + Show : // Error +>Show : Symbol(Show, Decl(distributedTypeParameters.ts, 0, 0)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 52, 8)) +>C : Symbol(C, Decl(distributedTypeParameters.ts, 52, 23)) + + Show : // Error +>Show : Symbol(Show, Decl(distributedTypeParameters.ts, 0, 0)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 52, 10)) +>C : Symbol(C, Decl(distributedTypeParameters.ts, 52, 23)) + + never; + +type T5 = +>T5 : Symbol(T5, Decl(distributedTypeParameters.ts, 57, 10)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 59, 8)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 59, 25)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 59, 8)) + + [A] extends [B] ? +>A : Symbol(A, Decl(distributedTypeParameters.ts, 59, 8)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 59, 25)) + + Show : +>Show : Symbol(Show, Decl(distributedTypeParameters.ts, 0, 0)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 59, 8)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 59, 25)) + + never; + +// Ensure mapped type modifiers are computed correctly, example from type-fest + +type IsReadonlyKeyOf = +>IsReadonlyKeyOf : Symbol(IsReadonlyKeyOf, Decl(distributedTypeParameters.ts, 62, 10)) +>Type : Symbol(Type, Decl(distributedTypeParameters.ts, 66, 21)) +>Key : Symbol(Key, Decl(distributedTypeParameters.ts, 66, 41)) +>Type : Symbol(Type, Decl(distributedTypeParameters.ts, 66, 21)) + + IsAny extends true ? never +>IsAny : Symbol(IsAny, Decl(distributedTypeParameters.ts, 75, 11)) +>Type : Symbol(Type, Decl(distributedTypeParameters.ts, 66, 21)) +>Key : Symbol(Key, Decl(distributedTypeParameters.ts, 66, 41)) + + : Key extends unknown // For distributing `Key` +>Key : Symbol(Key, Decl(distributedTypeParameters.ts, 66, 41)) + + ? Type extends unknown // For distributing `Type` +>Type : Symbol(Type, Decl(distributedTypeParameters.ts, 66, 21)) + + ? IsEqual< +>IsEqual : Symbol(IsEqual, Decl(distributedTypeParameters.ts, 77, 56)) + + {[K in Key]: Type[Key]}, +>K : Symbol(K, Decl(distributedTypeParameters.ts, 71, 7)) +>Key : Symbol(Key, Decl(distributedTypeParameters.ts, 66, 41)) +>Type : Symbol(Type, Decl(distributedTypeParameters.ts, 66, 21)) +>Key : Symbol(Key, Decl(distributedTypeParameters.ts, 66, 41)) + + {readonly [K in Key]: Type[Key]} +>K : Symbol(K, Decl(distributedTypeParameters.ts, 72, 16)) +>Key : Symbol(Key, Decl(distributedTypeParameters.ts, 66, 41)) +>Type : Symbol(Type, Decl(distributedTypeParameters.ts, 66, 21)) +>Key : Symbol(Key, Decl(distributedTypeParameters.ts, 66, 41)) + + > + : never // Should never happen + : never; // Should never happen + +type IsAny = 0 extends 1 & NoInfer ? true : false; +>IsAny : Symbol(IsAny, Decl(distributedTypeParameters.ts, 75, 11)) +>T : Symbol(T, Decl(distributedTypeParameters.ts, 77, 11)) +>NoInfer : Symbol(NoInfer, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(distributedTypeParameters.ts, 77, 11)) + +type IsEqual = +>IsEqual : Symbol(IsEqual, Decl(distributedTypeParameters.ts, 77, 56)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 79, 13)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 79, 15)) + + [A] extends [B] +>A : Symbol(A, Decl(distributedTypeParameters.ts, 79, 13)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 79, 15)) + + ? [B] extends [A] +>B : Symbol(B, Decl(distributedTypeParameters.ts, 79, 15)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 79, 13)) + + ? _IsEqual +>_IsEqual : Symbol(_IsEqual, Decl(distributedTypeParameters.ts, 84, 10)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 79, 13)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 79, 15)) + + : false + : false; + +type _IsEqual = +>_IsEqual : Symbol(_IsEqual, Decl(distributedTypeParameters.ts, 84, 10)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 86, 14)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 86, 16)) + + (() => G extends A & G | G ? 1 : 2) extends +>G : Symbol(G, Decl(distributedTypeParameters.ts, 87, 3)) +>G : Symbol(G, Decl(distributedTypeParameters.ts, 87, 3)) +>A : Symbol(A, Decl(distributedTypeParameters.ts, 86, 14)) +>G : Symbol(G, Decl(distributedTypeParameters.ts, 87, 3)) +>G : Symbol(G, Decl(distributedTypeParameters.ts, 87, 3)) + + (() => G extends B & G | G ? 1 : 2) +>G : Symbol(G, Decl(distributedTypeParameters.ts, 88, 3)) +>G : Symbol(G, Decl(distributedTypeParameters.ts, 88, 3)) +>B : Symbol(B, Decl(distributedTypeParameters.ts, 86, 16)) +>G : Symbol(G, Decl(distributedTypeParameters.ts, 88, 3)) +>G : Symbol(G, Decl(distributedTypeParameters.ts, 88, 3)) + + ? true + : false; + +type T10 = IsReadonlyKeyOf<{ a: string }, 'a'>; // false +>T10 : Symbol(T10, Decl(distributedTypeParameters.ts, 90, 10)) +>IsReadonlyKeyOf : Symbol(IsReadonlyKeyOf, Decl(distributedTypeParameters.ts, 62, 10)) +>a : Symbol(a, Decl(distributedTypeParameters.ts, 92, 28)) + +type T11 = IsReadonlyKeyOf<{ readonly b: string }, 'b'>; // true +>T11 : Symbol(T11, Decl(distributedTypeParameters.ts, 92, 47)) +>IsReadonlyKeyOf : Symbol(IsReadonlyKeyOf, Decl(distributedTypeParameters.ts, 62, 10)) +>b : Symbol(b, Decl(distributedTypeParameters.ts, 93, 28)) + diff --git a/tsc/testdata/baselines/reference/compiler/distributedTypeParameters.types b/tsc/testdata/baselines/reference/compiler/distributedTypeParameters.types new file mode 100644 index 0000000000000..37fe5d3abaed0 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/distributedTypeParameters.types @@ -0,0 +1,146 @@ +//// [tests/cases/compiler/distributedTypeParameters.ts] //// + +=== distributedTypeParameters.ts === +// https://github.com/microsoft/TypeScript/issues/63708 + +type Show = [A, B] & {}; +>Show : [A, B] + +type Issue1 = +>Issue1 : Issue1 + + A extends unknown ? + B extends unknown ? + Show : // Error + never : + never; + +type Issue2 = +>Issue2 : Issue2 + + B extends unknown ? + A extends unknown ? + Show : // Error + never : + never; + +type Issue3 = +>Issue3 : Issue3 + + A extends B ? + B extends A ? + Show : + never : + never; + +type Issue4 = +>Issue4 : Issue4 + + B extends A ? + A extends B ? + Show : // Error + never : + never; + +type X1 = Issue1<0 | 1, 0 | 1>; +>X1 : X1 + +type X2 = Issue2<0 | 1, 0 | 1>; +>X2 : X2 + +type X3 = Issue3<0 | 1, 0 | 1>; +>X3 : X3 + +type X4 = Issue4<0 | 1, 0 | 1>; +>X4 : X4 + +type T1 = +>T1 : T1 + + A extends B ? + never : + Show; // Error + +type T2 = +>T2 : T2 + + A extends B ? + Show : // Error + never; + +type T3 = +>T3 : T3 + + A extends unknown ? + Show : // Error + Show; + +type T4 = +>T4 : T4 + + A extends unknown ? + B extends unknown ? + Show : // Error + Show : // Error + never; + +type T5 = +>T5 : T5 + + [A] extends [B] ? + Show : + never; + +// Ensure mapped type modifiers are computed correctly, example from type-fest + +type IsReadonlyKeyOf = +>IsReadonlyKeyOf : IsReadonlyKeyOf + + IsAny extends true ? never +>true : true + + : Key extends unknown // For distributing `Key` + ? Type extends unknown // For distributing `Type` + ? IsEqual< + {[K in Key]: Type[Key]}, + {readonly [K in Key]: Type[Key]} + > + : never // Should never happen + : never; // Should never happen + +type IsAny = 0 extends 1 & NoInfer ? true : false; +>IsAny : IsAny +>true : true +>false : false + +type IsEqual = +>IsEqual : IsEqual + + [A] extends [B] + ? [B] extends [A] + ? _IsEqual + : false +>false : false + + : false; +>false : false + +type _IsEqual = +>_IsEqual : _IsEqual + + (() => G extends A & G | G ? 1 : 2) extends + (() => G extends B & G | G ? 1 : 2) + ? true +>true : true + + : false; +>false : false + +type T10 = IsReadonlyKeyOf<{ a: string }, 'a'>; // false +>T10 : false +>a : string + +type T11 = IsReadonlyKeyOf<{ readonly b: string }, 'b'>; // true +>T11 : true +>b : string + diff --git a/tsc/testdata/tests/cases/compiler/distributedTypeParameters.ts b/tsc/testdata/tests/cases/compiler/distributedTypeParameters.ts new file mode 100644 index 0000000000000..cb0eedf5dc15a --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/distributedTypeParameters.ts @@ -0,0 +1,96 @@ +// @noEmit: true + +// https://github.com/microsoft/TypeScript/issues/63708 + +type Show = [A, B] & {}; + +type Issue1 = + A extends unknown ? + B extends unknown ? + Show : // Error + never : + never; + +type Issue2 = + B extends unknown ? + A extends unknown ? + Show : // Error + never : + never; + +type Issue3 = + A extends B ? + B extends A ? + Show : + never : + never; + +type Issue4 = + B extends A ? + A extends B ? + Show : // Error + never : + never; + +type X1 = Issue1<0 | 1, 0 | 1>; +type X2 = Issue2<0 | 1, 0 | 1>; +type X3 = Issue3<0 | 1, 0 | 1>; +type X4 = Issue4<0 | 1, 0 | 1>; + +type T1 = + A extends B ? + never : + Show; // Error + +type T2 = + A extends B ? + Show : // Error + never; + +type T3 = + A extends unknown ? + Show : // Error + Show; + +type T4 = + A extends unknown ? + B extends unknown ? + Show : // Error + Show : // Error + never; + +type T5 = + [A] extends [B] ? + Show : + never; + +// Ensure mapped type modifiers are computed correctly, example from type-fest + +type IsReadonlyKeyOf = + IsAny extends true ? never + : Key extends unknown // For distributing `Key` + ? Type extends unknown // For distributing `Type` + ? IsEqual< + {[K in Key]: Type[Key]}, + {readonly [K in Key]: Type[Key]} + > + : never // Should never happen + : never; // Should never happen + +type IsAny = 0 extends 1 & NoInfer ? true : false; + +type IsEqual = + [A] extends [B] + ? [B] extends [A] + ? _IsEqual + : false + : false; + +type _IsEqual = + (() => G extends A & G | G ? 1 : 2) extends + (() => G extends B & G | G ? 1 : 2) + ? true + : false; + +type T10 = IsReadonlyKeyOf<{ a: string }, 'a'>; // false +type T11 = IsReadonlyKeyOf<{ readonly b: string }, 'b'>; // true