From be90172a528a169a28bd4df636bd92122b68adad Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 21 Oct 2026 10:38:59 -0700 Subject: [PATCH] Strengthen and unify negated type <-> filtering conditional workaround rule --- tsc/internal/checker/checker.go | 7 + tsc/internal/checker/relater.go | 103 ++++-- ...xMappedTypeWithFilteredKeysWithNegation.js | 138 ++++++++ ...edTypeWithFilteredKeysWithNegation.symbols | 331 ++++++++++++++++++ ...ppedTypeWithFilteredKeysWithNegation.types | 181 ++++++++++ ...hFilteredKeysWithNegationErrors.errors.txt | 148 ++++++++ ...dTypeWithFilteredKeysWithNegationErrors.js | 153 ++++++++ ...WithFilteredKeysWithNegationErrors.symbols | 283 +++++++++++++++ ...peWithFilteredKeysWithNegationErrors.types | 185 ++++++++++ ...xMappedTypeWithFilteredKeysWithNegation.ts | 96 +++++ ...dTypeWithFilteredKeysWithNegationErrors.ts | 96 +++++ 11 files changed, 1689 insertions(+), 32 deletions(-) create mode 100644 tsc/testdata/baselines/reference/compiler/indexMappedTypeWithFilteredKeysWithNegationErrors.errors.txt create mode 100644 tsc/testdata/baselines/reference/compiler/indexMappedTypeWithFilteredKeysWithNegationErrors.js create mode 100644 tsc/testdata/baselines/reference/compiler/indexMappedTypeWithFilteredKeysWithNegationErrors.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/indexMappedTypeWithFilteredKeysWithNegationErrors.types create mode 100644 tsc/testdata/tests/cases/compiler/indexMappedTypeWithFilteredKeysWithNegationErrors.ts diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index f3ccc9f04e1..bd6eb44dbcd 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -152,6 +152,11 @@ type CachedTypeKey struct { typeId TypeId } +type DistributiveInstantiationKey struct { + typeId TypeId + typeParameterId TypeId +} + // NarrowedTypeKey type NarrowedTypeKey struct { @@ -651,6 +656,7 @@ type Checker struct { thisExpandoLocations map[*ast.Symbol]*ast.Node subtypeReductionCache map[CacheHashKey][]*Type cachedTypes map[CachedTypeKey]*Type + distributiveInstantiationCache map[DistributiveInstantiationKey]bool cachedSignatures map[CachedSignatureKey]*Signature undefinedProperties map[*ast.Symbol]*ast.Symbol narrowedTypes map[NarrowedTypeKey]*Type @@ -990,6 +996,7 @@ func NewChecker(program Program, tracer *Tracer) (*Checker, *sync.Mutex) { c.thisExpandoLocations = make(map[*ast.Symbol]*ast.Node) c.subtypeReductionCache = make(map[CacheHashKey][]*Type) c.cachedTypes = make(map[CachedTypeKey]*Type) + c.distributiveInstantiationCache = make(map[DistributiveInstantiationKey]bool) c.cachedSignatures = make(map[CachedSignatureKey]*Signature) c.undefinedProperties = make(map[*ast.Symbol]*ast.Symbol) c.narrowedTypes = make(map[NarrowedTypeKey]*Type) diff --git a/tsc/internal/checker/relater.go b/tsc/internal/checker/relater.go index 5db176bc2b0..c8c41a2d8f3 100644 --- a/tsc/internal/checker/relater.go +++ b/tsc/internal/checker/relater.go @@ -3616,8 +3616,8 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo nameType := r.c.getNameTypeFromMappedType(targetType) constraintType := r.c.getConstraintTypeFromMappedType(targetType) var targetKeys *Type - isFiltering := nameType != nil && r.c.isMappedTypeWithKeyofConstraintDeclaration(targetType) - if isFiltering { + isRemappedKeyof := nameType != nil && r.c.isMappedTypeWithKeyofConstraintDeclaration(targetType) + if isRemappedKeyof { // 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) @@ -3628,15 +3628,14 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo } else { targetKeys = constraintType } - if isFiltering && source.flags&TypeFlagsIntersection != 0 && r.someTypeRelatedToType(source, constraintType, false, intersectionState) == TernaryTrue { - // run the filter over `source` - some part of `source` is related to the constraint - if source's other elements end up filtering the same way the nameType does, the assignment can pass - // This effectively is a workaround for a lack of distributive conditional/distributive homomorphic mapped types relationship rules that uses execution of the distribution (even at higher order) - // to approximate a better result. - // See the homomorphic case in tsc\testdata\tests\cases\compiler\indexMappedTypeWithFilteredKeysWithNegation.ts - sourceInstantiatedTarget := r.c.instantiateType(nameType, newSimpleTypeMapper(r.c.getTypeParameterFromMappedType(targetType), source)) - broadTargetKeys := r.c.getUnionType([]*Type{targetKeys, sourceInstantiatedTarget}) - if r.isRelatedTo(source, broadTargetKeys, RecursionFlagsTarget, false) == TernaryTrue { - return TernaryTrue + if isRemappedKeyof && source.flags&TypeFlagsIntersection != 0 { + if filteredNameType := r.getOptionalSimplificationOfConditional(nameType); filteredNameType != nil && r.someTypeRelatedToType(source, constraintType, false, intersectionState) == TernaryTrue { + // Apply the filter only to a source already constrained to the mapped keys. + sourceInstantiatedTarget := r.c.instantiateType(filteredNameType, newSimpleTypeMapper(r.c.getTypeParameterFromMappedType(targetType), source)) + broadTargetKeys := r.c.getUnionType([]*Type{targetKeys, sourceInstantiatedTarget}) + if r.isRelatedTo(source, broadTargetKeys, RecursionFlagsTarget, false) == TernaryTrue { + return TernaryTrue + } } } if r.isRelatedTo(source, targetKeys, RecursionFlagsTarget, reportErrors) == TernaryTrue { @@ -4024,32 +4023,72 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo return TernaryFalse } +// TODO: Replace this heuristic with proper distributive conditional type relationship rules. +// For a distributive A extends B ? never : Q without infer parameters, rewrite to Q. +// A definitely false check uses Q unchanged; a definitely true check cannot use the rewrite. +// Otherwise B must be independent of A, and Q must be never. The conditional already distributes, +// but we also probe whether Q agrees with Q | Q before moving the union inside Q. +// See Repeated in indexMappedTypeWithFilteredKeysWithNegationErrors.ts, where moving the union +// inside `${A}-${A}` would lose the correlation between occurrences of A. func (r *Relater) getOptionalSimplificationOfConditional(target *Type) *Type { if target.flags&TypeFlagsConditional == 0 { return nil } - // TODO: This is a kludge for not having proper rules for relating distributive conditional types. - // We rewrite `A extends B ? never : Q` into `Q` - provided that substitution - // actually changes it. This is not *particularly* accurate, but enables the common case of relating a filtering mapped type to the equivalent filtering negated type. - // See the non-homomorphic case in tsc\testdata\tests\cases\compiler\indexMappedTypeWithFilteredKeysWithNegation.ts for an example. - root := target.AsConditionalType().root - if root.isDistributive && r.c.getTrueTypeFromConditionalType(target).flags&TypeFlagsNever != 0 { - f := r.c.getFalseTypeFromConditionalType(target) - remappedFalseType := r.c.instantiateType( - r.c.getTypeFromTypeNode(root.node.FalseType), - r.c.combineTypeMappers( - newSimpleTypeMapper( - getNonDistributedTypeParameter(root.checkType), - r.c.getIntersectionType([]*Type{root.checkType, r.c.getNegatedType(root.extendsType)}), - ), - target.AsConditionalType().mapper, - ), - ) - if remappedFalseType != f { - return remappedFalseType - } + conditional := target.AsConditionalType() + root := conditional.root + if !root.isDistributive || len(root.inferTypeParameters) != 0 || r.c.getTrueTypeFromConditionalType(target).flags&TypeFlagsNever == 0 { + return nil } - return nil + permissiveExtendsType := r.c.getPermissiveInstantiation(conditional.extendsType) + skipTrue := !someType(r.c.getPermissiveInstantiation(conditional.checkType), func(t *Type) bool { + return r.c.isTypeAssignableTo(t, permissiveExtendsType) + }) + if skipTrue { + return r.c.getFalseTypeFromConditionalType(target) + } + if r.c.isTypeAssignableTo(r.c.getRestrictiveInstantiation(conditional.checkType), r.c.getRestrictiveInstantiation(conditional.extendsType)) { + return nil + } + checkType := getNonDistributedTypeParameter(root.checkType) + if r.c.isTypeParameterPossiblyReferenced(checkType, root.node.ExtendsType) { + return nil + } + falseType := r.c.getTypeFromTypeNode(root.node.FalseType) + if !r.canDistributeTypeInstantiation(falseType, checkType) { + return nil + } + return r.c.instantiateType( + falseType, + r.c.combineTypeMappers( + newSimpleTypeMapper(checkType, r.c.getIntersectionType([]*Type{root.checkType, r.c.getNegatedType(root.extendsType)})), + conditional.mapper, + ), + ) +} + +func (r *Relater) canDistributeTypeInstantiation(t *Type, typeParameter *Type) bool { + key := DistributiveInstantiationKey{typeId: t.id, typeParameterId: typeParameter.id} + if result, ok := r.c.distributiveInstantiationCache[key]; ok { + return result + } + // A recursive probe is unproven until the outer comparison completes. + r.c.distributiveInstantiationCache[key] = false + // Filtering can remove the entire check type, so the false branch must also preserve never. + if r.c.instantiateType(t, newSimpleTypeMapper(typeParameter, r.c.neverType)).flags&TypeFlagsNever == 0 { + return false + } + left := r.c.markerSuperType + right := r.c.markerOtherType + instantiate := func(argument *Type) *Type { + return r.c.getSimplifiedType(r.c.instantiateType(t, newSimpleTypeMapper(typeParameter, argument)), false /*writing*/) + } + // Require execution over unrelated union members to agree with distributing first. + // Use the existing identity comparison rather than introducing conditional relationship rules. + instantiatedUnion := instantiate(r.c.getUnionType([]*Type{left, right})) + unionOfInstantiations := r.c.getUnionType([]*Type{instantiate(left), instantiate(right)}) + result := r.c.isTypeIdenticalTo(instantiatedUnion, unionOfInstantiations) + r.c.distributiveInstantiationCache[key] = result + return result } func (r *Relater) typeArgumentsRelatedTo(sources []*Type, targets []*Type, variances []VarianceFlags, reportErrors bool, intersectionState IntersectionState) Ternary { diff --git a/tsc/testdata/baselines/reference/compiler/indexMappedTypeWithFilteredKeysWithNegation.js b/tsc/testdata/baselines/reference/compiler/indexMappedTypeWithFilteredKeysWithNegation.js index 089cb2ff363..bd0ec8a4a61 100644 --- a/tsc/testdata/baselines/reference/compiler/indexMappedTypeWithFilteredKeysWithNegation.js +++ b/tsc/testdata/baselines/reference/compiler/indexMappedTypeWithFilteredKeysWithNegation.js @@ -39,6 +39,102 @@ const t1: T1 = ['b', 'c']; type S1 = { [K in keyof T & string as K extends '_key' ? never : K]: 1 }[LocalExclude]; type S2 = { [K in string & keyof T as K extends '_key' ? never : K]: 1 }[LocalExclude]; + +type ConditionalExclude = T extends U ? never : T; + +function exclude(value: T & not U): ConditionalExclude { + return value; +} + +type Identity = T; +type AliasedExclude = T extends U ? never : Identity; + +function aliasedExclude(value: T & not U): AliasedExclude { + return value; +} + +function mappedExclude(value: keyof T & not U): keyof ExcludeKey { + return value; +} + +type AliasedExcludeKey = { + [K in keyof T as ConditionalExclude]: T[K] +}; + +function aliasedMappedExclude(value: keyof T & not U): keyof AliasedExcludeKey { + return value; +} + +type Prefixed = T extends "skip" ? never : `prefix${T}`; + +function prefixed(value: `prefix${T & not "skip"}`): Prefixed { + return value; +} + +type WithSuffix = T extends "skip" ? never : `prefix${T}:${S}`; + +function withSuffix(value: `prefix${T & not "skip"}:${S}`): WithSuffix { + return value; +} + +type Cased = Uppercase<`prefix${Lowercase}`>; +type FilteredCased = T extends "skip" ? never : Cased; + +function cased(value: Cased): FilteredCased { + return value; +} + +type Alternatives = T extends "skip" ? never : `left${T}` | `right${Uppercase}`; + +function alternatives( + value: `left${T & not "skip"}` | `right${Uppercase}` +): Alternatives { + return value; +} + +type Branded = T extends "skip" ? never : T & { brand: true }; + +function branded(value: T & not "skip" & { brand: true }): Branded { + return value; +} + +interface Values { + a: 1; + b: 2; + skip: 3; +} + +type Lookup = T extends "skip" ? never : Values[T]; + +function lookup(value: Values[T & not "skip"]): Lookup { + return value; +} + +type PrefixedKeys = { + [K in keyof T & string as K extends "_key" ? never : `prefix${K}`]: T[K] +}; + +function prefixedKey(value: `prefix${keyof T & string & not "_key"}`): keyof PrefixedKeys { + return value; +} + +type FunctionResult = T extends string ? never : () => T; + +function falseFunction(value: () => [T]): FunctionResult<[T]> { + return value; +} + +type TupleResult = T extends string ? never : [T, T]; + +function falseTuple(value: [[T], [T]]): TupleResult<[T]> { + return value; +} + +type Nested = T extends "skip" ? never : ConditionalExclude<`prefix${T}`, "prefixomit">; + +function nested(value: ConditionalExclude<`prefix${T & not "skip"}`, "prefixomit">): Nested { + return value; +} //// [indexMappedTypeWithFilteredKeysWithNegation.js] @@ -48,3 +144,45 @@ function f(test, possibleKey) { test[possibleKey]; } const t1 = ['b', 'c']; +function exclude(value) { + return value; +} +function aliasedExclude(value) { + return value; +} +function mappedExclude(value) { + return value; +} +function aliasedMappedExclude(value) { + return value; +} +function prefixed(value) { + return value; +} +function withSuffix(value) { + return value; +} +function cased(value) { + return value; +} +function alternatives(value) { + return value; +} +function branded(value) { + return value; +} +function lookup(value) { + return value; +} +function prefixedKey(value) { + return value; +} +function falseFunction(value) { + return value; +} +function falseTuple(value) { + return value; +} +function nested(value) { + return value; +} diff --git a/tsc/testdata/baselines/reference/compiler/indexMappedTypeWithFilteredKeysWithNegation.symbols b/tsc/testdata/baselines/reference/compiler/indexMappedTypeWithFilteredKeysWithNegation.symbols index 0480638a442..5a5380e5710 100644 --- a/tsc/testdata/baselines/reference/compiler/indexMappedTypeWithFilteredKeysWithNegation.symbols +++ b/tsc/testdata/baselines/reference/compiler/indexMappedTypeWithFilteredKeysWithNegation.symbols @@ -137,3 +137,334 @@ type S2 = { [K in string & keyof T as K extends '_key' ? never : K]: 1 }[Loca >LocalExclude : Symbol(LocalExclude, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 18, 1)) >T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 37, 8)) +type ConditionalExclude = T extends U ? never : T; +>ConditionalExclude : Symbol(ConditionalExclude, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 37, 116)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 39, 24)) +>U : Symbol(U, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 39, 26)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 39, 24)) +>U : Symbol(U, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 39, 26)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 39, 24)) + +function exclude(value: T & not U): ConditionalExclude { +>exclude : Symbol(exclude, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 39, 56)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 41, 17)) +>U : Symbol(U, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 41, 19)) +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 41, 23)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 41, 17)) +>U : Symbol(U, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 41, 19)) +>ConditionalExclude : Symbol(ConditionalExclude, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 37, 116)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 41, 17)) +>U : Symbol(U, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 41, 19)) + + return value; +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 41, 23)) +} + +type Identity = T; +>Identity : Symbol(Identity, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 43, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 45, 14)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 45, 14)) + +type AliasedExclude = T extends U ? never : Identity; +>AliasedExclude : Symbol(AliasedExclude, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 45, 21)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 46, 20)) +>U : Symbol(U, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 46, 22)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 46, 20)) +>U : Symbol(U, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 46, 22)) +>Identity : Symbol(Identity, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 43, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 46, 20)) + +function aliasedExclude(value: T & not U): AliasedExclude { +>aliasedExclude : Symbol(aliasedExclude, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 46, 62)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 48, 24)) +>U : Symbol(U, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 48, 26)) +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 48, 30)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 48, 24)) +>U : Symbol(U, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 48, 26)) +>AliasedExclude : Symbol(AliasedExclude, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 45, 21)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 48, 24)) +>U : Symbol(U, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 48, 26)) + + return value; +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 48, 30)) +} + +function mappedExclude(value: keyof T & not U): keyof ExcludeKey { +>mappedExclude : Symbol(mappedExclude, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 50, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 52, 23)) +>U : Symbol(U, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 52, 25)) +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 52, 29)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 52, 23)) +>U : Symbol(U, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 52, 25)) +>ExcludeKey : Symbol(ExcludeKey, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 0, 0)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 52, 23)) +>U : Symbol(U, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 52, 25)) + + return value; +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 52, 29)) +} + +type AliasedExcludeKey = { +>AliasedExcludeKey : Symbol(AliasedExcludeKey, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 54, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 56, 23)) +>U : Symbol(U, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 56, 25)) + + [K in keyof T as ConditionalExclude]: T[K] +>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 57, 5)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 56, 23)) +>ConditionalExclude : Symbol(ConditionalExclude, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 37, 116)) +>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 57, 5)) +>U : Symbol(U, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 56, 25)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 56, 23)) +>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 57, 5)) + +}; + +function aliasedMappedExclude(value: keyof T & not U): keyof AliasedExcludeKey { +>aliasedMappedExclude : Symbol(aliasedMappedExclude, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 58, 2)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 60, 30)) +>U : Symbol(U, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 60, 32)) +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 60, 36)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 60, 30)) +>U : Symbol(U, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 60, 32)) +>AliasedExcludeKey : Symbol(AliasedExcludeKey, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 54, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 60, 30)) +>U : Symbol(U, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 60, 32)) + + return value; +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 60, 36)) +} + +type Prefixed = T extends "skip" ? never : `prefix${T}`; +>Prefixed : Symbol(Prefixed, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 62, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 64, 14)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 64, 14)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 64, 14)) + +function prefixed(value: `prefix${T & not "skip"}`): Prefixed { +>prefixed : Symbol(prefixed, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 64, 74)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 66, 18)) +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 66, 36)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 66, 18)) +>Prefixed : Symbol(Prefixed, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 62, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 66, 18)) + + return value; +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 66, 36)) +} + +type WithSuffix = T extends "skip" ? never : `prefix${T}:${S}`; +>WithSuffix : Symbol(WithSuffix, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 68, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 70, 16)) +>S : Symbol(S, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 70, 33)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 70, 16)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 70, 16)) +>S : Symbol(S, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 70, 33)) + +function withSuffix(value: `prefix${T & not "skip"}:${S}`): WithSuffix { +>withSuffix : Symbol(withSuffix, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 70, 99)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 72, 20)) +>S : Symbol(S, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 72, 37)) +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 72, 56)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 72, 20)) +>S : Symbol(S, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 72, 37)) +>WithSuffix : Symbol(WithSuffix, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 68, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 72, 20)) +>S : Symbol(S, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 72, 37)) + + return value; +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 72, 56)) +} + +type Cased = Uppercase<`prefix${Lowercase}`>; +>Cased : Symbol(Cased, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 74, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 76, 11)) +>Uppercase : Symbol(Uppercase, Decl(lib.es5.d.ts, --, --)) +>Lowercase : Symbol(Lowercase, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 76, 11)) + +type FilteredCased = T extends "skip" ? never : Cased; +>FilteredCased : Symbol(FilteredCased, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 76, 66)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 77, 19)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 77, 19)) +>Cased : Symbol(Cased, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 74, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 77, 19)) + +function cased(value: Cased): FilteredCased { +>cased : Symbol(cased, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 77, 75)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 79, 15)) +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 79, 33)) +>Cased : Symbol(Cased, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 74, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 79, 15)) +>FilteredCased : Symbol(FilteredCased, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 76, 66)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 79, 15)) + + return value; +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 79, 33)) +} + +type Alternatives = T extends "skip" ? never : `left${T}` | `right${Uppercase}`; +>Alternatives : Symbol(Alternatives, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 81, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 83, 18)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 83, 18)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 83, 18)) +>Uppercase : Symbol(Uppercase, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 83, 18)) + +function alternatives( +>alternatives : Symbol(alternatives, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 83, 101)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 85, 22)) + + value: `left${T & not "skip"}` | `right${Uppercase}` +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 85, 40)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 85, 22)) +>Uppercase : Symbol(Uppercase, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 85, 22)) + +): Alternatives { +>Alternatives : Symbol(Alternatives, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 81, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 85, 22)) + + return value; +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 85, 40)) +} + +type Branded = T extends "skip" ? never : T & { brand: true }; +>Branded : Symbol(Branded, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 89, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 91, 13)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 91, 13)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 91, 13)) +>brand : Symbol(brand, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 91, 50)) + +function branded(value: T & not "skip" & { brand: true }): Branded { +>branded : Symbol(branded, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 91, 65)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 93, 17)) +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 93, 20)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 93, 17)) +>brand : Symbol(brand, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 93, 45)) +>Branded : Symbol(Branded, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 89, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 93, 17)) + + return value; +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 93, 20)) +} + +interface Values { +>Values : Symbol(Values, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 95, 1)) + + a: 1; +>a : Symbol(Values.a, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 97, 18)) + + b: 2; +>b : Symbol(Values.b, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 98, 9)) + + skip: 3; +>skip : Symbol(Values.skip, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 99, 9)) +} + +type Lookup = T extends "skip" ? never : Values[T]; +>Lookup : Symbol(Lookup, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 101, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 103, 12)) +>Values : Symbol(Values, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 95, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 103, 12)) +>Values : Symbol(Values, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 95, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 103, 12)) + +function lookup(value: Values[T & not "skip"]): Lookup { +>lookup : Symbol(lookup, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 103, 75)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 105, 16)) +>Values : Symbol(Values, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 95, 1)) +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 105, 40)) +>Values : Symbol(Values, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 95, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 105, 16)) +>Lookup : Symbol(Lookup, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 101, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 105, 16)) + + return value; +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 105, 40)) +} + +type PrefixedKeys = { +>PrefixedKeys : Symbol(PrefixedKeys, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 107, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 109, 18)) + + [K in keyof T & string as K extends "_key" ? never : `prefix${K}`]: T[K] +>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 110, 5)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 109, 18)) +>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 110, 5)) +>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 110, 5)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 109, 18)) +>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 110, 5)) + +}; + +function prefixedKey(value: `prefix${keyof T & string & not "_key"}`): keyof PrefixedKeys { +>prefixedKey : Symbol(prefixedKey, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 111, 2)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 113, 21)) +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 113, 24)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 113, 21)) +>PrefixedKeys : Symbol(PrefixedKeys, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 107, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 113, 21)) + + return value; +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 113, 24)) +} + +type FunctionResult = T extends string ? never : () => T; +>FunctionResult : Symbol(FunctionResult, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 115, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 117, 20)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 117, 20)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 117, 20)) + +function falseFunction(value: () => [T]): FunctionResult<[T]> { +>falseFunction : Symbol(falseFunction, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 117, 60)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 119, 23)) +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 119, 26)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 119, 23)) +>FunctionResult : Symbol(FunctionResult, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 115, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 119, 23)) + + return value; +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 119, 26)) +} + +type TupleResult = T extends string ? never : [T, T]; +>TupleResult : Symbol(TupleResult, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 121, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 123, 17)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 123, 17)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 123, 17)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 123, 17)) + +function falseTuple(value: [[T], [T]]): TupleResult<[T]> { +>falseTuple : Symbol(falseTuple, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 123, 56)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 125, 20)) +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 125, 23)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 125, 20)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 125, 20)) +>TupleResult : Symbol(TupleResult, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 121, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 125, 20)) + + return value; +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 125, 23)) +} + +type Nested = T extends "skip" ? never : ConditionalExclude<`prefix${T}`, "prefixomit">; +>Nested : Symbol(Nested, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 127, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 129, 12)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 129, 12)) +>ConditionalExclude : Symbol(ConditionalExclude, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 37, 116)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 129, 12)) + +function nested(value: ConditionalExclude<`prefix${T & not "skip"}`, "prefixomit">): Nested { +>nested : Symbol(nested, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 129, 106)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 131, 16)) +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 131, 34)) +>ConditionalExclude : Symbol(ConditionalExclude, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 37, 116)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 131, 16)) +>Nested : Symbol(Nested, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 127, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 131, 16)) + + return value; +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 131, 34)) +} + diff --git a/tsc/testdata/baselines/reference/compiler/indexMappedTypeWithFilteredKeysWithNegation.types b/tsc/testdata/baselines/reference/compiler/indexMappedTypeWithFilteredKeysWithNegation.types index df04ec5a817..414a259a75d 100644 --- a/tsc/testdata/baselines/reference/compiler/indexMappedTypeWithFilteredKeysWithNegation.types +++ b/tsc/testdata/baselines/reference/compiler/indexMappedTypeWithFilteredKeysWithNegation.types @@ -79,3 +79,184 @@ type S1 = { [K in keyof T & string as K extends '_key' ? never : K]: 1 }[Loca type S2 = { [K in string & keyof T as K extends '_key' ? never : K]: 1 }[LocalExclude]; >S2 : S2 +type ConditionalExclude = T extends U ? never : T; +>ConditionalExclude : ConditionalExclude + +function exclude(value: T & not U): ConditionalExclude { +>exclude : (value: T & not U) => ConditionalExclude +>value : T & not U + + return value; +>value : T & not U +} + +type Identity = T; +>Identity : T + +type AliasedExclude = T extends U ? never : Identity; +>AliasedExclude : AliasedExclude + +function aliasedExclude(value: T & not U): AliasedExclude { +>aliasedExclude : (value: T & not U) => AliasedExclude +>value : T & not U + + return value; +>value : T & not U +} + +function mappedExclude(value: keyof T & not U): keyof ExcludeKey { +>mappedExclude : (value: keyof T & not U) => keyof ExcludeKey +>value : keyof T & not U + + return value; +>value : keyof T & not U +} + +type AliasedExcludeKey = { +>AliasedExcludeKey : AliasedExcludeKey + + [K in keyof T as ConditionalExclude]: T[K] +}; + +function aliasedMappedExclude(value: keyof T & not U): keyof AliasedExcludeKey { +>aliasedMappedExclude : (value: keyof T & not U) => keyof AliasedExcludeKey +>value : keyof T & not U + + return value; +>value : keyof T & not U +} + +type Prefixed = T extends "skip" ? never : `prefix${T}`; +>Prefixed : Prefixed + +function prefixed(value: `prefix${T & not "skip"}`): Prefixed { +>prefixed : (value: `prefix${T & not "skip"}`) => Prefixed +>value : `prefix${T & not "skip"}` + + return value; +>value : `prefix${T & not "skip"}` +} + +type WithSuffix = T extends "skip" ? never : `prefix${T}:${S}`; +>WithSuffix : WithSuffix + +function withSuffix(value: `prefix${T & not "skip"}:${S}`): WithSuffix { +>withSuffix : (value: `prefix${T & not "skip"}:${S}`) => WithSuffix +>value : `prefix${T & not "skip"}:${S}` + + return value; +>value : `prefix${T & not "skip"}:${S}` +} + +type Cased = Uppercase<`prefix${Lowercase}`>; +>Cased : `PREFIX${Uppercase>}` + +type FilteredCased = T extends "skip" ? never : Cased; +>FilteredCased : FilteredCased + +function cased(value: Cased): FilteredCased { +>cased : (value: Cased) => FilteredCased +>value : `PREFIX${Uppercase>}` + + return value; +>value : `PREFIX${Uppercase>}` +} + +type Alternatives = T extends "skip" ? never : `left${T}` | `right${Uppercase}`; +>Alternatives : Alternatives + +function alternatives( +>alternatives : (value: `left${T & not "skip"}` | `right${Uppercase}`) => Alternatives + + value: `left${T & not "skip"}` | `right${Uppercase}` +>value : `left${T & not "skip"}` | `right${Uppercase}` + +): Alternatives { + return value; +>value : `left${T & not "skip"}` | `right${Uppercase}` +} + +type Branded = T extends "skip" ? never : T & { brand: true }; +>Branded : Branded +>brand : true +>true : true + +function branded(value: T & not "skip" & { brand: true }): Branded { +>branded : (value: T & not "skip" & { brand: true; }) => Branded +>value : T & not "skip" & { brand: true; } +>brand : true +>true : true + + return value; +>value : T & not "skip" & { brand: true; } +} + +interface Values { + a: 1; +>a : 1 + + b: 2; +>b : 2 + + skip: 3; +>skip : 3 +} + +type Lookup = T extends "skip" ? never : Values[T]; +>Lookup : Lookup + +function lookup(value: Values[T & not "skip"]): Lookup { +>lookup : (value: Values[T & not "skip"]) => Lookup +>value : Values[T & not "skip"] + + return value; +>value : Values[T & not "skip"] +} + +type PrefixedKeys = { +>PrefixedKeys : PrefixedKeys + + [K in keyof T & string as K extends "_key" ? never : `prefix${K}`]: T[K] +}; + +function prefixedKey(value: `prefix${keyof T & string & not "_key"}`): keyof PrefixedKeys { +>prefixedKey : (value: `prefix${keyof T & string & not "_key"}`) => keyof PrefixedKeys +>value : `prefix${keyof T & string & not "_key"}` + + return value; +>value : `prefix${keyof T & string & not "_key"}` +} + +type FunctionResult = T extends string ? never : () => T; +>FunctionResult : FunctionResult + +function falseFunction(value: () => [T]): FunctionResult<[T]> { +>falseFunction : (value: () => [T]) => FunctionResult<[T]> +>value : () => [T] + + return value; +>value : () => [T] +} + +type TupleResult = T extends string ? never : [T, T]; +>TupleResult : TupleResult + +function falseTuple(value: [[T], [T]]): TupleResult<[T]> { +>falseTuple : (value: [[T], [T]]) => TupleResult<[T]> +>value : [[T], [T]] + + return value; +>value : [[T], [T]] +} + +type Nested = T extends "skip" ? never : ConditionalExclude<`prefix${T}`, "prefixomit">; +>Nested : Nested + +function nested(value: ConditionalExclude<`prefix${T & not "skip"}`, "prefixomit">): Nested { +>nested : (value: ConditionalExclude<`prefix${T & not "skip"}`, "prefixomit">) => Nested +>value : ConditionalExclude<`prefix${T & not "skip"}`, "prefixomit"> + + return value; +>value : ConditionalExclude<`prefix${T & not "skip"}`, "prefixomit"> +} + diff --git a/tsc/testdata/baselines/reference/compiler/indexMappedTypeWithFilteredKeysWithNegationErrors.errors.txt b/tsc/testdata/baselines/reference/compiler/indexMappedTypeWithFilteredKeysWithNegationErrors.errors.txt new file mode 100644 index 00000000000..252365eef68 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/indexMappedTypeWithFilteredKeysWithNegationErrors.errors.txt @@ -0,0 +1,148 @@ +indexMappedTypeWithFilteredKeysWithNegationErrors.ts(5,5): error TS2322: Type '() => never' is not assignable to type 'FilteredFunction'. +indexMappedTypeWithFilteredKeysWithNegationErrors.ts(14,5): error TS2322: Type '[T & not false & not true, T & not false & not true]' is not assignable to type 'FilteredPair'. +indexMappedTypeWithFilteredKeysWithNegationErrors.ts(24,5): error TS2322: Type 'T & not Omitted' is not assignable to type 'DependentFilter'. +indexMappedTypeWithFilteredKeysWithNegationErrors.ts(33,5): error TS2322: Type 'T' is not assignable to type 'Filter'. +indexMappedTypeWithFilteredKeysWithNegationErrors.ts(42,5): error TS2322: Type 'keyof T & not "c"' is not assignable to type 'keyof FilteredKeys'. + Type 'keyof T & not "c"' is not assignable to type 'K extends "b" ? never : K'. +indexMappedTypeWithFilteredKeysWithNegationErrors.ts(47,5): error TS2322: Type 'string & not "b"' is not assignable to type 'keyof FilteredKeys'. + Type 'string & not "b"' is not assignable to type 'K extends "b" ? never : K'. +indexMappedTypeWithFilteredKeysWithNegationErrors.ts(58,5): error TS2322: Type 'keyof T & not "c"' is not assignable to type 'keyof RemappedKeys'. + Type 'keyof T & not "c"' is not assignable to type '"a" | "b" extends K ? K : never'. +indexMappedTypeWithFilteredKeysWithNegationErrors.ts(67,5): error TS2322: Type 'keyof T & not Omitted' is not assignable to type 'keyof DependentKeys'. + Type 'keyof T & not Omitted' is not assignable to type 'K extends Omitted ? never : K'. +indexMappedTypeWithFilteredKeysWithNegationErrors.ts(74,5): error TS2322: Type '`${T & not "skip"}-${T & not "skip"}`' is not assignable to type 'Repeated'. + Type '`${string}-${string}`' is not assignable to type 'Repeated'. +indexMappedTypeWithFilteredKeysWithNegationErrors.ts(83,5): error TS2322: Type 'T & not string & { value: T & not string; }' is not assignable to type 'Correlated'. +indexMappedTypeWithFilteredKeysWithNegationErrors.ts(88,5): error TS2322: Type '() => string & { value: T; }' is not assignable to type 'never'. +indexMappedTypeWithFilteredKeysWithNegationErrors.ts(93,5): error TS2322: Type '() => string | [T]' is not assignable to type '() => [T]'. + Type 'string | [T]' is not assignable to type '[T]'. + Type 'string' is not assignable to type '[T]'. + + +==== indexMappedTypeWithFilteredKeysWithNegationErrors.ts (12 errors) ==== + type FilteredFunction = T extends string ? never : () => T; + + function makeFunction(): FilteredFunction { + // When T is string, FilteredFunction is never, but this still returns a function. + return () => { throw new Error(); }; + ~~~~~~ +!!! error TS2322: Type '() => never' is not assignable to type 'FilteredFunction'. + } + + const impossibleFunction: never = makeFunction(); + + type FilteredPair = T extends boolean ? never : [T, T]; + + function makePair(value: [T & not boolean, T & not boolean]): FilteredPair { + // The tuple elements can come from different union members, unlike the distributed FilteredPair. + return value; + ~~~~~~ +!!! error TS2322: Type '[T & not false & not true, T & not false & not true]' is not assignable to type 'FilteredPair'. + } + + const homogeneousPair: [string, string] | [number, number] = makePair(["a", 1]); + + type Omitted = "a" extends T ? never : "b"; + type DependentFilter = T extends Omitted ? never : T; + + function dependentFilter(value: T & not Omitted): DependentFilter { + // For T = "a" | "b", Omitted is never, so value can be "b", but DependentFilter is "a". + return value; + ~~~~~~ +!!! error TS2322: Type 'T & not Omitted' is not assignable to type 'DependentFilter'. + } + + const onlyA: "a" = dependentFilter<"a" | "b">("b"); + + type Filter = T extends U ? never : T; + + function unfiltered(value: T): Filter { + // value can still belong to U, whose members are excluded from Filter. + return value; + ~~~~~~ +!!! error TS2322: Type 'T' is not assignable to type 'Filter'. + } + + type FilteredKeys = { + [K in keyof T as K extends "b" ? never : K]: T[K] + }; + + function excludedKey(value: keyof T & not "c"): keyof FilteredKeys { + // Excluding "c" does not prevent value from being the filtered-out key "b". + return value; + ~~~~~~ +!!! error TS2322: Type 'keyof T & not "c"' is not assignable to type 'keyof FilteredKeys'. +!!! error TS2322: Type 'keyof T & not "c"' is not assignable to type 'K extends "b" ? never : K'. + } + + function unrelatedKey(value: string & not "b"): keyof FilteredKeys { + // value is not constrained to keyof T and may name a property T does not have. + return value; + ~~~~~~ +!!! error TS2322: Type 'string & not "b"' is not assignable to type 'keyof FilteredKeys'. +!!! error TS2322: Type 'string & not "b"' is not assignable to type 'K extends "b" ? never : K'. + } + + type RemappedKeys = { + [K in keyof T as ("a" | "b") extends K ? K : never]: T[K] + }; + + function remappedKey( + value: keyof T & not "c" + ): keyof RemappedKeys { + // For T with just "a" and "b", neither key accepts their union, so no keys survive. + return value; + ~~~~~~ +!!! error TS2322: Type 'keyof T & not "c"' is not assignable to type 'keyof RemappedKeys'. +!!! error TS2322: Type 'keyof T & not "c"' is not assignable to type '"a" | "b" extends K ? K : never'. + } + + type DependentKeys = { + [K in keyof T as K extends Omitted ? never : K]: T[K] + }; + + function dependentKey(value: keyof T & not Omitted): keyof DependentKeys { + // For T = { a: number; b: number }, value can be "b", but only "a" survives the per-key filter. + return value; + ~~~~~~ +!!! error TS2322: Type 'keyof T & not Omitted' is not assignable to type 'keyof DependentKeys'. +!!! error TS2322: Type 'keyof T & not Omitted' is not assignable to type 'K extends Omitted ? never : K'. + } + + type Repeated = T extends "skip" ? never : `${T}-${T}`; + + function repeated(value: `${T & not "skip"}-${T & not "skip"}`): Repeated { + // value can mix different union members in its two T positions, unlike Repeated. + return value; + ~~~~~~ +!!! error TS2322: Type '`${T & not "skip"}-${T & not "skip"}`' is not assignable to type 'Repeated'. +!!! error TS2322: Type '`${string}-${string}`' is not assignable to type 'Repeated'. + } + + const same: "a-a" | "b-b" = repeated<"a" | "b">("a-b"); + + type Correlated = T extends string ? never : T & { value: T }; + + function correlated(value: T & not string & { value: T & not string }): Correlated { + // The outer T and the value property's T can come from different union members, unlike Correlated. + return value; + ~~~~~~ +!!! error TS2322: Type 'T & not string & { value: T & not string; }' is not assignable to type 'Correlated'. + } + + function trueFunction(value: () => (string & { value: T })): FilteredFunction { + // The check type always extends string, so the result is never, not a function. + return value; + ~~~~~~ +!!! error TS2322: Type '() => string & { value: T; }' is not assignable to type 'never'. + } + + function unionFunction(value: () => ([T] | string)): FilteredFunction<[T] | string> { + // The string union member is filtered out, so the returned function must return [T], not string. + return value; + ~~~~~~ +!!! error TS2322: Type '() => string | [T]' is not assignable to type '() => [T]'. +!!! error TS2322: Type 'string | [T]' is not assignable to type '[T]'. +!!! error TS2322: Type 'string' is not assignable to type '[T]'. + } + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/indexMappedTypeWithFilteredKeysWithNegationErrors.js b/tsc/testdata/baselines/reference/compiler/indexMappedTypeWithFilteredKeysWithNegationErrors.js new file mode 100644 index 00000000000..e0850ce82cc --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/indexMappedTypeWithFilteredKeysWithNegationErrors.js @@ -0,0 +1,153 @@ +//// [tests/cases/compiler/indexMappedTypeWithFilteredKeysWithNegationErrors.ts] //// + +//// [indexMappedTypeWithFilteredKeysWithNegationErrors.ts] +type FilteredFunction = T extends string ? never : () => T; + +function makeFunction(): FilteredFunction { + // When T is string, FilteredFunction is never, but this still returns a function. + return () => { throw new Error(); }; +} + +const impossibleFunction: never = makeFunction(); + +type FilteredPair = T extends boolean ? never : [T, T]; + +function makePair(value: [T & not boolean, T & not boolean]): FilteredPair { + // The tuple elements can come from different union members, unlike the distributed FilteredPair. + return value; +} + +const homogeneousPair: [string, string] | [number, number] = makePair(["a", 1]); + +type Omitted = "a" extends T ? never : "b"; +type DependentFilter = T extends Omitted ? never : T; + +function dependentFilter(value: T & not Omitted): DependentFilter { + // For T = "a" | "b", Omitted is never, so value can be "b", but DependentFilter is "a". + return value; +} + +const onlyA: "a" = dependentFilter<"a" | "b">("b"); + +type Filter = T extends U ? never : T; + +function unfiltered(value: T): Filter { + // value can still belong to U, whose members are excluded from Filter. + return value; +} + +type FilteredKeys = { + [K in keyof T as K extends "b" ? never : K]: T[K] +}; + +function excludedKey(value: keyof T & not "c"): keyof FilteredKeys { + // Excluding "c" does not prevent value from being the filtered-out key "b". + return value; +} + +function unrelatedKey(value: string & not "b"): keyof FilteredKeys { + // value is not constrained to keyof T and may name a property T does not have. + return value; +} + +type RemappedKeys = { + [K in keyof T as ("a" | "b") extends K ? K : never]: T[K] +}; + +function remappedKey( + value: keyof T & not "c" +): keyof RemappedKeys { + // For T with just "a" and "b", neither key accepts their union, so no keys survive. + return value; +} + +type DependentKeys = { + [K in keyof T as K extends Omitted ? never : K]: T[K] +}; + +function dependentKey(value: keyof T & not Omitted): keyof DependentKeys { + // For T = { a: number; b: number }, value can be "b", but only "a" survives the per-key filter. + return value; +} + +type Repeated = T extends "skip" ? never : `${T}-${T}`; + +function repeated(value: `${T & not "skip"}-${T & not "skip"}`): Repeated { + // value can mix different union members in its two T positions, unlike Repeated. + return value; +} + +const same: "a-a" | "b-b" = repeated<"a" | "b">("a-b"); + +type Correlated = T extends string ? never : T & { value: T }; + +function correlated(value: T & not string & { value: T & not string }): Correlated { + // The outer T and the value property's T can come from different union members, unlike Correlated. + return value; +} + +function trueFunction(value: () => (string & { value: T })): FilteredFunction { + // The check type always extends string, so the result is never, not a function. + return value; +} + +function unionFunction(value: () => ([T] | string)): FilteredFunction<[T] | string> { + // The string union member is filtered out, so the returned function must return [T], not string. + return value; +} + + +//// [indexMappedTypeWithFilteredKeysWithNegationErrors.js] +"use strict"; +function makeFunction() { + // When T is string, FilteredFunction is never, but this still returns a function. + return () => { throw new Error(); }; +} +const impossibleFunction = makeFunction(); +function makePair(value) { + // The tuple elements can come from different union members, unlike the distributed FilteredPair. + return value; +} +const homogeneousPair = makePair(["a", 1]); +function dependentFilter(value) { + // For T = "a" | "b", Omitted is never, so value can be "b", but DependentFilter is "a". + return value; +} +const onlyA = dependentFilter("b"); +function unfiltered(value) { + // value can still belong to U, whose members are excluded from Filter. + return value; +} +function excludedKey(value) { + // Excluding "c" does not prevent value from being the filtered-out key "b". + return value; +} +function unrelatedKey(value) { + // value is not constrained to keyof T and may name a property T does not have. + return value; +} +function remappedKey(value) { + // For T with just "a" and "b", neither key accepts their union, so no keys survive. + return value; +} +function dependentKey(value) { + // For T = { a: number; b: number }, value can be "b", but only "a" survives the per-key filter. + return value; +} +function repeated(value) { + // value can mix different union members in its two T positions, unlike Repeated. + return value; +} +const same = repeated("a-b"); +function correlated(value) { + // The outer T and the value property's T can come from different union members, unlike Correlated. + return value; +} +function trueFunction(value) { + // The check type always extends string, so the result is never, not a function. + return value; +} +function unionFunction(value) { + // The string union member is filtered out, so the returned function must return [T], not string. + return value; +} diff --git a/tsc/testdata/baselines/reference/compiler/indexMappedTypeWithFilteredKeysWithNegationErrors.symbols b/tsc/testdata/baselines/reference/compiler/indexMappedTypeWithFilteredKeysWithNegationErrors.symbols new file mode 100644 index 00000000000..8bb86bc8465 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/indexMappedTypeWithFilteredKeysWithNegationErrors.symbols @@ -0,0 +1,283 @@ +//// [tests/cases/compiler/indexMappedTypeWithFilteredKeysWithNegationErrors.ts] //// + +=== indexMappedTypeWithFilteredKeysWithNegationErrors.ts === +type FilteredFunction = T extends string ? never : () => T; +>FilteredFunction : Symbol(FilteredFunction, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 0, 0)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 0, 22)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 0, 22)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 0, 22)) + +function makeFunction(): FilteredFunction { +>makeFunction : Symbol(makeFunction, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 0, 62)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 2, 22)) +>FilteredFunction : Symbol(FilteredFunction, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 0, 0)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 2, 22)) + + // When T is string, FilteredFunction is never, but this still returns a function. + return () => { throw new Error(); }; +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2022.error.d.ts, --, --)) +} + +const impossibleFunction: never = makeFunction(); +>impossibleFunction : Symbol(impossibleFunction, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 7, 5)) +>makeFunction : Symbol(makeFunction, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 0, 62)) + +type FilteredPair = T extends boolean ? never : [T, T]; +>FilteredPair : Symbol(FilteredPair, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 7, 57)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 9, 18)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 9, 18)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 9, 18)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 9, 18)) + +function makePair(value: [T & not boolean, T & not boolean]): FilteredPair { +>makePair : Symbol(makePair, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 9, 58)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 11, 18)) +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 11, 21)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 11, 18)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 11, 18)) +>FilteredPair : Symbol(FilteredPair, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 7, 57)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 11, 18)) + + // The tuple elements can come from different union members, unlike the distributed FilteredPair. + return value; +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 11, 21)) +} + +const homogeneousPair: [string, string] | [number, number] = makePair(["a", 1]); +>homogeneousPair : Symbol(homogeneousPair, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 16, 5)) +>makePair : Symbol(makePair, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 9, 58)) + +type Omitted = "a" extends T ? never : "b"; +>Omitted : Symbol(Omitted, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 16, 97)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 18, 13)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 18, 13)) + +type DependentFilter = T extends Omitted ? never : T; +>DependentFilter : Symbol(DependentFilter, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 18, 46)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 19, 21)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 19, 21)) +>Omitted : Symbol(Omitted, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 16, 97)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 19, 21)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 19, 21)) + +function dependentFilter(value: T & not Omitted): DependentFilter { +>dependentFilter : Symbol(dependentFilter, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 19, 59)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 21, 25)) +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 21, 28)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 21, 25)) +>Omitted : Symbol(Omitted, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 16, 97)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 21, 25)) +>DependentFilter : Symbol(DependentFilter, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 18, 46)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 21, 25)) + + // For T = "a" | "b", Omitted is never, so value can be "b", but DependentFilter is "a". + return value; +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 21, 28)) +} + +const onlyA: "a" = dependentFilter<"a" | "b">("b"); +>onlyA : Symbol(onlyA, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 26, 5)) +>dependentFilter : Symbol(dependentFilter, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 19, 59)) + +type Filter = T extends U ? never : T; +>Filter : Symbol(Filter, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 26, 51)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 28, 12)) +>U : Symbol(U, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 28, 14)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 28, 12)) +>U : Symbol(U, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 28, 14)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 28, 12)) + +function unfiltered(value: T): Filter { +>unfiltered : Symbol(unfiltered, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 28, 44)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 30, 20)) +>U : Symbol(U, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 30, 22)) +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 30, 26)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 30, 20)) +>Filter : Symbol(Filter, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 26, 51)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 30, 20)) +>U : Symbol(U, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 30, 22)) + + // value can still belong to U, whose members are excluded from Filter. + return value; +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 30, 26)) +} + +type FilteredKeys = { +>FilteredKeys : Symbol(FilteredKeys, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 33, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 35, 18)) + + [K in keyof T as K extends "b" ? never : K]: T[K] +>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 36, 5)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 35, 18)) +>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 36, 5)) +>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 36, 5)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 35, 18)) +>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 36, 5)) + +}; + +function excludedKey(value: keyof T & not "c"): keyof FilteredKeys { +>excludedKey : Symbol(excludedKey, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 37, 2)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 39, 21)) +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 39, 24)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 39, 21)) +>FilteredKeys : Symbol(FilteredKeys, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 33, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 39, 21)) + + // Excluding "c" does not prevent value from being the filtered-out key "b". + return value; +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 39, 24)) +} + +function unrelatedKey(value: string & not "b"): keyof FilteredKeys { +>unrelatedKey : Symbol(unrelatedKey, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 42, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 44, 22)) +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 44, 25)) +>FilteredKeys : Symbol(FilteredKeys, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 33, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 44, 22)) + + // value is not constrained to keyof T and may name a property T does not have. + return value; +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 44, 25)) +} + +type RemappedKeys = { +>RemappedKeys : Symbol(RemappedKeys, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 47, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 49, 18)) + + [K in keyof T as ("a" | "b") extends K ? K : never]: T[K] +>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 50, 5)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 49, 18)) +>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 50, 5)) +>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 50, 5)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 49, 18)) +>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 50, 5)) + +}; + +function remappedKey( +>remappedKey : Symbol(remappedKey, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 51, 2)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 53, 21)) +>a : Symbol(a, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 53, 32)) +>b : Symbol(b, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 53, 44)) + + value: keyof T & not "c" +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 53, 59)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 53, 21)) + +): keyof RemappedKeys { +>RemappedKeys : Symbol(RemappedKeys, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 47, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 53, 21)) + + // For T with just "a" and "b", neither key accepts their union, so no keys survive. + return value; +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 53, 59)) +} + +type DependentKeys = { +>DependentKeys : Symbol(DependentKeys, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 58, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 60, 19)) + + [K in keyof T as K extends Omitted ? never : K]: T[K] +>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 61, 5)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 60, 19)) +>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 61, 5)) +>Omitted : Symbol(Omitted, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 16, 97)) +>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 61, 5)) +>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 61, 5)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 60, 19)) +>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 61, 5)) + +}; + +function dependentKey(value: keyof T & not Omitted): keyof DependentKeys { +>dependentKey : Symbol(dependentKey, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 62, 2)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 64, 22)) +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 64, 25)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 64, 22)) +>Omitted : Symbol(Omitted, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 16, 97)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 64, 22)) +>DependentKeys : Symbol(DependentKeys, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 58, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 64, 22)) + + // For T = { a: number; b: number }, value can be "b", but only "a" survives the per-key filter. + return value; +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 64, 25)) +} + +type Repeated = T extends "skip" ? never : `${T}-${T}`; +>Repeated : Symbol(Repeated, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 67, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 69, 14)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 69, 14)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 69, 14)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 69, 14)) + +function repeated(value: `${T & not "skip"}-${T & not "skip"}`): Repeated { +>repeated : Symbol(repeated, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 69, 73)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 71, 18)) +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 71, 36)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 71, 18)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 71, 18)) +>Repeated : Symbol(Repeated, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 67, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 71, 18)) + + // value can mix different union members in its two T positions, unlike Repeated. + return value; +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 71, 36)) +} + +const same: "a-a" | "b-b" = repeated<"a" | "b">("a-b"); +>same : Symbol(same, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 76, 5)) +>repeated : Symbol(repeated, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 69, 73)) + +type Correlated = T extends string ? never : T & { value: T }; +>Correlated : Symbol(Correlated, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 76, 55)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 78, 16)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 78, 16)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 78, 16)) +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 78, 53)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 78, 16)) + +function correlated(value: T & not string & { value: T & not string }): Correlated { +>correlated : Symbol(correlated, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 78, 65)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 80, 20)) +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 80, 23)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 80, 20)) +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 80, 48)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 80, 20)) +>Correlated : Symbol(Correlated, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 76, 55)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 80, 20)) + + // The outer T and the value property's T can come from different union members, unlike Correlated. + return value; +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 80, 23)) +} + +function trueFunction(value: () => (string & { value: T })): FilteredFunction { +>trueFunction : Symbol(trueFunction, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 83, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 85, 22)) +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 85, 25)) +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 85, 49)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 85, 22)) +>FilteredFunction : Symbol(FilteredFunction, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 0, 0)) +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 85, 91)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 85, 22)) + + // The check type always extends string, so the result is never, not a function. + return value; +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 85, 25)) +} + +function unionFunction(value: () => ([T] | string)): FilteredFunction<[T] | string> { +>unionFunction : Symbol(unionFunction, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 88, 1)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 90, 23)) +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 90, 26)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 90, 23)) +>FilteredFunction : Symbol(FilteredFunction, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 0, 0)) +>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 90, 23)) + + // The string union member is filtered out, so the returned function must return [T], not string. + return value; +>value : Symbol(value, Decl(indexMappedTypeWithFilteredKeysWithNegationErrors.ts, 90, 26)) +} + diff --git a/tsc/testdata/baselines/reference/compiler/indexMappedTypeWithFilteredKeysWithNegationErrors.types b/tsc/testdata/baselines/reference/compiler/indexMappedTypeWithFilteredKeysWithNegationErrors.types new file mode 100644 index 00000000000..8b7fa85b48a --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/indexMappedTypeWithFilteredKeysWithNegationErrors.types @@ -0,0 +1,185 @@ +//// [tests/cases/compiler/indexMappedTypeWithFilteredKeysWithNegationErrors.ts] //// + +=== indexMappedTypeWithFilteredKeysWithNegationErrors.ts === +type FilteredFunction = T extends string ? never : () => T; +>FilteredFunction : FilteredFunction + +function makeFunction(): FilteredFunction { +>makeFunction : () => FilteredFunction + + // When T is string, FilteredFunction is never, but this still returns a function. + return () => { throw new Error(); }; +>() => { throw new Error(); } : () => never +>new Error() : Error +>Error : ErrorConstructor +} + +const impossibleFunction: never = makeFunction(); +>impossibleFunction : never +>makeFunction() : never +>makeFunction : () => FilteredFunction + +type FilteredPair = T extends boolean ? never : [T, T]; +>FilteredPair : FilteredPair + +function makePair(value: [T & not boolean, T & not boolean]): FilteredPair { +>makePair : (value: [T & not boolean, T & not boolean]) => FilteredPair +>value : [T & not false & not true, T & not false & not true] + + // The tuple elements can come from different union members, unlike the distributed FilteredPair. + return value; +>value : [T & not false & not true, T & not false & not true] +} + +const homogeneousPair: [string, string] | [number, number] = makePair(["a", 1]); +>homogeneousPair : [string, string] | [number, number] +>makePair(["a", 1]) : [string, string] | [number, number] +>makePair : (value: [T & not boolean, T & not boolean]) => FilteredPair +>["a", 1] : [string, number] +>"a" : "a" +>1 : 1 + +type Omitted = "a" extends T ? never : "b"; +>Omitted : Omitted + +type DependentFilter = T extends Omitted ? never : T; +>DependentFilter : DependentFilter + +function dependentFilter(value: T & not Omitted): DependentFilter { +>dependentFilter : (value: T & not Omitted) => DependentFilter +>value : T & not Omitted + + // For T = "a" | "b", Omitted is never, so value can be "b", but DependentFilter is "a". + return value; +>value : T & not Omitted +} + +const onlyA: "a" = dependentFilter<"a" | "b">("b"); +>onlyA : "a" +>dependentFilter<"a" | "b">("b") : "a" +>dependentFilter : (value: T & not Omitted) => DependentFilter +>"b" : "b" + +type Filter = T extends U ? never : T; +>Filter : Filter + +function unfiltered(value: T): Filter { +>unfiltered : (value: T) => Filter +>value : T + + // value can still belong to U, whose members are excluded from Filter. + return value; +>value : T +} + +type FilteredKeys = { +>FilteredKeys : FilteredKeys + + [K in keyof T as K extends "b" ? never : K]: T[K] +}; + +function excludedKey(value: keyof T & not "c"): keyof FilteredKeys { +>excludedKey : (value: keyof T & not "c") => keyof FilteredKeys +>value : keyof T & not "c" + + // Excluding "c" does not prevent value from being the filtered-out key "b". + return value; +>value : keyof T & not "c" +} + +function unrelatedKey(value: string & not "b"): keyof FilteredKeys { +>unrelatedKey : (value: string & not "b") => keyof FilteredKeys +>value : string & not "b" + + // value is not constrained to keyof T and may name a property T does not have. + return value; +>value : string & not "b" +} + +type RemappedKeys = { +>RemappedKeys : RemappedKeys + + [K in keyof T as ("a" | "b") extends K ? K : never]: T[K] +}; + +function remappedKey( +>remappedKey : (value: keyof T & not "c") => keyof RemappedKeys +>a : unknown +>b : unknown + + value: keyof T & not "c" +>value : keyof T & not "c" + +): keyof RemappedKeys { + // For T with just "a" and "b", neither key accepts their union, so no keys survive. + return value; +>value : keyof T & not "c" +} + +type DependentKeys = { +>DependentKeys : DependentKeys + + [K in keyof T as K extends Omitted ? never : K]: T[K] +}; + +function dependentKey(value: keyof T & not Omitted): keyof DependentKeys { +>dependentKey : (value: keyof T & not Omitted) => keyof DependentKeys +>value : keyof T & not Omitted + + // For T = { a: number; b: number }, value can be "b", but only "a" survives the per-key filter. + return value; +>value : keyof T & not Omitted +} + +type Repeated = T extends "skip" ? never : `${T}-${T}`; +>Repeated : Repeated + +function repeated(value: `${T & not "skip"}-${T & not "skip"}`): Repeated { +>repeated : (value: `${T & not "skip"}-${T & not "skip"}`) => Repeated +>value : `${T & not "skip"}-${T & not "skip"}` + + // value can mix different union members in its two T positions, unlike Repeated. + return value; +>value : `${T & not "skip"}-${T & not "skip"}` +} + +const same: "a-a" | "b-b" = repeated<"a" | "b">("a-b"); +>same : "a-a" | "b-b" +>repeated<"a" | "b">("a-b") : "a-a" | "b-b" +>repeated : (value: `${T & not "skip"}-${T & not "skip"}`) => Repeated +>"a-b" : "a-b" + +type Correlated = T extends string ? never : T & { value: T }; +>Correlated : Correlated +>value : T + +function correlated(value: T & not string & { value: T & not string }): Correlated { +>correlated : (value: T & not string & { value: T & not string; }) => Correlated +>value : T & not string & { value: T & not string; } +>value : T & not string + + // The outer T and the value property's T can come from different union members, unlike Correlated. + return value; +>value : T & not string & { value: T & not string; } +} + +function trueFunction(value: () => (string & { value: T })): FilteredFunction { +>trueFunction : (value: () => (string & { value: T; })) => FilteredFunction +>value : () => (string & { value: T; }) +>value : T +>value : T + + // The check type always extends string, so the result is never, not a function. + return value; +>value : () => (string & { value: T; }) +} + +function unionFunction(value: () => ([T] | string)): FilteredFunction<[T] | string> { +>unionFunction : (value: () => ([T] | string)) => FilteredFunction<[T] | string> +>value : () => ([T] | string) + + // The string union member is filtered out, so the returned function must return [T], not string. + return value; +>value : () => ([T] | string) +} + diff --git a/tsc/testdata/tests/cases/compiler/indexMappedTypeWithFilteredKeysWithNegation.ts b/tsc/testdata/tests/cases/compiler/indexMappedTypeWithFilteredKeysWithNegation.ts index d923c137484..0504f79a557 100644 --- a/tsc/testdata/tests/cases/compiler/indexMappedTypeWithFilteredKeysWithNegation.ts +++ b/tsc/testdata/tests/cases/compiler/indexMappedTypeWithFilteredKeysWithNegation.ts @@ -38,3 +38,99 @@ const t1: T1 = ['b', 'c']; type S1 = { [K in keyof T & string as K extends '_key' ? never : K]: 1 }[LocalExclude]; type S2 = { [K in string & keyof T as K extends '_key' ? never : K]: 1 }[LocalExclude]; + +type ConditionalExclude = T extends U ? never : T; + +function exclude(value: T & not U): ConditionalExclude { + return value; +} + +type Identity = T; +type AliasedExclude = T extends U ? never : Identity; + +function aliasedExclude(value: T & not U): AliasedExclude { + return value; +} + +function mappedExclude(value: keyof T & not U): keyof ExcludeKey { + return value; +} + +type AliasedExcludeKey = { + [K in keyof T as ConditionalExclude]: T[K] +}; + +function aliasedMappedExclude(value: keyof T & not U): keyof AliasedExcludeKey { + return value; +} + +type Prefixed = T extends "skip" ? never : `prefix${T}`; + +function prefixed(value: `prefix${T & not "skip"}`): Prefixed { + return value; +} + +type WithSuffix = T extends "skip" ? never : `prefix${T}:${S}`; + +function withSuffix(value: `prefix${T & not "skip"}:${S}`): WithSuffix { + return value; +} + +type Cased = Uppercase<`prefix${Lowercase}`>; +type FilteredCased = T extends "skip" ? never : Cased; + +function cased(value: Cased): FilteredCased { + return value; +} + +type Alternatives = T extends "skip" ? never : `left${T}` | `right${Uppercase}`; + +function alternatives( + value: `left${T & not "skip"}` | `right${Uppercase}` +): Alternatives { + return value; +} + +type Branded = T extends "skip" ? never : T & { brand: true }; + +function branded(value: T & not "skip" & { brand: true }): Branded { + return value; +} + +interface Values { + a: 1; + b: 2; + skip: 3; +} + +type Lookup = T extends "skip" ? never : Values[T]; + +function lookup(value: Values[T & not "skip"]): Lookup { + return value; +} + +type PrefixedKeys = { + [K in keyof T & string as K extends "_key" ? never : `prefix${K}`]: T[K] +}; + +function prefixedKey(value: `prefix${keyof T & string & not "_key"}`): keyof PrefixedKeys { + return value; +} + +type FunctionResult = T extends string ? never : () => T; + +function falseFunction(value: () => [T]): FunctionResult<[T]> { + return value; +} + +type TupleResult = T extends string ? never : [T, T]; + +function falseTuple(value: [[T], [T]]): TupleResult<[T]> { + return value; +} + +type Nested = T extends "skip" ? never : ConditionalExclude<`prefix${T}`, "prefixomit">; + +function nested(value: ConditionalExclude<`prefix${T & not "skip"}`, "prefixomit">): Nested { + return value; +} diff --git a/tsc/testdata/tests/cases/compiler/indexMappedTypeWithFilteredKeysWithNegationErrors.ts b/tsc/testdata/tests/cases/compiler/indexMappedTypeWithFilteredKeysWithNegationErrors.ts new file mode 100644 index 00000000000..a1d6b09d9b0 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/indexMappedTypeWithFilteredKeysWithNegationErrors.ts @@ -0,0 +1,96 @@ +// @strict: true + +type FilteredFunction = T extends string ? never : () => T; + +function makeFunction(): FilteredFunction { + // When T is string, FilteredFunction is never, but this still returns a function. + return () => { throw new Error(); }; +} + +const impossibleFunction: never = makeFunction(); + +type FilteredPair = T extends boolean ? never : [T, T]; + +function makePair(value: [T & not boolean, T & not boolean]): FilteredPair { + // The tuple elements can come from different union members, unlike the distributed FilteredPair. + return value; +} + +const homogeneousPair: [string, string] | [number, number] = makePair(["a", 1]); + +type Omitted = "a" extends T ? never : "b"; +type DependentFilter = T extends Omitted ? never : T; + +function dependentFilter(value: T & not Omitted): DependentFilter { + // For T = "a" | "b", Omitted is never, so value can be "b", but DependentFilter is "a". + return value; +} + +const onlyA: "a" = dependentFilter<"a" | "b">("b"); + +type Filter = T extends U ? never : T; + +function unfiltered(value: T): Filter { + // value can still belong to U, whose members are excluded from Filter. + return value; +} + +type FilteredKeys = { + [K in keyof T as K extends "b" ? never : K]: T[K] +}; + +function excludedKey(value: keyof T & not "c"): keyof FilteredKeys { + // Excluding "c" does not prevent value from being the filtered-out key "b". + return value; +} + +function unrelatedKey(value: string & not "b"): keyof FilteredKeys { + // value is not constrained to keyof T and may name a property T does not have. + return value; +} + +type RemappedKeys = { + [K in keyof T as ("a" | "b") extends K ? K : never]: T[K] +}; + +function remappedKey( + value: keyof T & not "c" +): keyof RemappedKeys { + // For T with just "a" and "b", neither key accepts their union, so no keys survive. + return value; +} + +type DependentKeys = { + [K in keyof T as K extends Omitted ? never : K]: T[K] +}; + +function dependentKey(value: keyof T & not Omitted): keyof DependentKeys { + // For T = { a: number; b: number }, value can be "b", but only "a" survives the per-key filter. + return value; +} + +type Repeated = T extends "skip" ? never : `${T}-${T}`; + +function repeated(value: `${T & not "skip"}-${T & not "skip"}`): Repeated { + // value can mix different union members in its two T positions, unlike Repeated. + return value; +} + +const same: "a-a" | "b-b" = repeated<"a" | "b">("a-b"); + +type Correlated = T extends string ? never : T & { value: T }; + +function correlated(value: T & not string & { value: T & not string }): Correlated { + // The outer T and the value property's T can come from different union members, unlike Correlated. + return value; +} + +function trueFunction(value: () => (string & { value: T })): FilteredFunction { + // The check type always extends string, so the result is never, not a function. + return value; +} + +function unionFunction(value: () => ([T] | string)): FilteredFunction<[T] | string> { + // The string union member is filtered out, so the returned function must return [T], not string. + return value; +}