@@ -3616,8 +3616,8 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo
36163616 nameType := r .c .getNameTypeFromMappedType (targetType )
36173617 constraintType := r .c .getConstraintTypeFromMappedType (targetType )
36183618 var targetKeys * Type
3619- isFiltering := nameType != nil && r .c .isMappedTypeWithKeyofConstraintDeclaration (targetType )
3620- if isFiltering {
3619+ isRemappedKeyof := nameType != nil && r .c .isMappedTypeWithKeyofConstraintDeclaration (targetType )
3620+ if isRemappedKeyof {
36213621 // we need to get the apparent mappings and union them with the generic mappings, since some properties may be
36223622 // missing from the `constraintType` which will otherwise be mapped in the object
36233623 mappedKeys := r .c .getApparentMappedTypeKeys (nameType , targetType )
@@ -3628,15 +3628,14 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo
36283628 } else {
36293629 targetKeys = constraintType
36303630 }
3631- if isFiltering && source .flags & TypeFlagsIntersection != 0 && r .someTypeRelatedToType (source , constraintType , false , intersectionState ) == TernaryTrue {
3632- // 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
3633- // 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)
3634- // to approximate a better result.
3635- // See the homomorphic case in tsc\testdata\tests\cases\compiler\indexMappedTypeWithFilteredKeysWithNegation.ts
3636- sourceInstantiatedTarget := r .c .instantiateType (nameType , newSimpleTypeMapper (r .c .getTypeParameterFromMappedType (targetType ), source ))
3637- broadTargetKeys := r .c .getUnionType ([]* Type {targetKeys , sourceInstantiatedTarget })
3638- if r .isRelatedTo (source , broadTargetKeys , RecursionFlagsTarget , false ) == TernaryTrue {
3639- return TernaryTrue
3631+ if isRemappedKeyof && source .flags & TypeFlagsIntersection != 0 {
3632+ if filteredNameType := r .getOptionalSimplificationOfConditional (nameType ); filteredNameType != nil && r .someTypeRelatedToType (source , constraintType , false , intersectionState ) == TernaryTrue {
3633+ // Apply the filter only to a source already constrained to the mapped keys.
3634+ sourceInstantiatedTarget := r .c .instantiateType (filteredNameType , newSimpleTypeMapper (r .c .getTypeParameterFromMappedType (targetType ), source ))
3635+ broadTargetKeys := r .c .getUnionType ([]* Type {targetKeys , sourceInstantiatedTarget })
3636+ if r .isRelatedTo (source , broadTargetKeys , RecursionFlagsTarget , false ) == TernaryTrue {
3637+ return TernaryTrue
3638+ }
36403639 }
36413640 }
36423641 if r .isRelatedTo (source , targetKeys , RecursionFlagsTarget , reportErrors ) == TernaryTrue {
@@ -4024,32 +4023,72 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo
40244023 return TernaryFalse
40254024}
40264025
4026+ // TODO: Replace this heuristic with proper distributive conditional type relationship rules.
4027+ // For a distributive A extends B ? never : Q<A> without infer parameters, rewrite to Q<A & not B>.
4028+ // A definitely false check uses Q<A> unchanged; a definitely true check cannot use the rewrite.
4029+ // Otherwise B must be independent of A, and Q<never> must be never. The conditional already distributes,
4030+ // but we also probe whether Q<X | Y> agrees with Q<X> | Q<Y> before moving the union inside Q.
4031+ // See Repeated in indexMappedTypeWithFilteredKeysWithNegationErrors.ts, where moving the union
4032+ // inside `${A}-${A}` would lose the correlation between occurrences of A.
40274033func (r * Relater ) getOptionalSimplificationOfConditional (target * Type ) * Type {
40284034 if target .flags & TypeFlagsConditional == 0 {
40294035 return nil
40304036 }
4031- // TODO: This is a kludge for not having proper rules for relating distributive conditional types.
4032- // We rewrite `A extends B ? never : Q<A>` into `Q<A & not B>` - provided that substitution
4033- // 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.
4034- // See the non-homomorphic case in tsc\testdata\tests\cases\compiler\indexMappedTypeWithFilteredKeysWithNegation.ts for an example.
4035- root := target .AsConditionalType ().root
4036- if root .isDistributive && r .c .getTrueTypeFromConditionalType (target ).flags & TypeFlagsNever != 0 {
4037- f := r .c .getFalseTypeFromConditionalType (target )
4038- remappedFalseType := r .c .instantiateType (
4039- r .c .getTypeFromTypeNode (root .node .FalseType ),
4040- r .c .combineTypeMappers (
4041- newSimpleTypeMapper (
4042- getNonDistributedTypeParameter (root .checkType ),
4043- r .c .getIntersectionType ([]* Type {root .checkType , r .c .getNegatedType (root .extendsType )}),
4044- ),
4045- target .AsConditionalType ().mapper ,
4046- ),
4047- )
4048- if remappedFalseType != f {
4049- return remappedFalseType
4050- }
4037+ conditional := target .AsConditionalType ()
4038+ root := conditional .root
4039+ if ! root .isDistributive || len (root .inferTypeParameters ) != 0 || r .c .getTrueTypeFromConditionalType (target ).flags & TypeFlagsNever == 0 {
4040+ return nil
40514041 }
4052- return nil
4042+ permissiveExtendsType := r .c .getPermissiveInstantiation (conditional .extendsType )
4043+ skipTrue := ! someType (r .c .getPermissiveInstantiation (conditional .checkType ), func (t * Type ) bool {
4044+ return r .c .isTypeAssignableTo (t , permissiveExtendsType )
4045+ })
4046+ if skipTrue {
4047+ return r .c .getFalseTypeFromConditionalType (target )
4048+ }
4049+ if r .c .isTypeAssignableTo (r .c .getRestrictiveInstantiation (conditional .checkType ), r .c .getRestrictiveInstantiation (conditional .extendsType )) {
4050+ return nil
4051+ }
4052+ checkType := getNonDistributedTypeParameter (root .checkType )
4053+ if r .c .isTypeParameterPossiblyReferenced (checkType , root .node .ExtendsType ) {
4054+ return nil
4055+ }
4056+ falseType := r .c .getTypeFromTypeNode (root .node .FalseType )
4057+ if ! r .canDistributeTypeInstantiation (falseType , checkType ) {
4058+ return nil
4059+ }
4060+ return r .c .instantiateType (
4061+ falseType ,
4062+ r .c .combineTypeMappers (
4063+ newSimpleTypeMapper (checkType , r .c .getIntersectionType ([]* Type {root .checkType , r .c .getNegatedType (root .extendsType )})),
4064+ conditional .mapper ,
4065+ ),
4066+ )
4067+ }
4068+
4069+ func (r * Relater ) canDistributeTypeInstantiation (t * Type , typeParameter * Type ) bool {
4070+ key := DistributiveInstantiationKey {typeId : t .id , typeParameterId : typeParameter .id }
4071+ if result , ok := r .c .distributiveInstantiationCache [key ]; ok {
4072+ return result
4073+ }
4074+ // A recursive probe is unproven until the outer comparison completes.
4075+ r .c .distributiveInstantiationCache [key ] = false
4076+ // Filtering can remove the entire check type, so the false branch must also preserve never.
4077+ if r .c .instantiateType (t , newSimpleTypeMapper (typeParameter , r .c .neverType )).flags & TypeFlagsNever == 0 {
4078+ return false
4079+ }
4080+ left := r .c .markerSuperType
4081+ right := r .c .markerOtherType
4082+ instantiate := func (argument * Type ) * Type {
4083+ return r .c .getSimplifiedType (r .c .instantiateType (t , newSimpleTypeMapper (typeParameter , argument )), false /*writing*/ )
4084+ }
4085+ // Require execution over unrelated union members to agree with distributing first.
4086+ // Use the existing identity comparison rather than introducing conditional relationship rules.
4087+ instantiatedUnion := instantiate (r .c .getUnionType ([]* Type {left , right }))
4088+ unionOfInstantiations := r .c .getUnionType ([]* Type {instantiate (left ), instantiate (right )})
4089+ result := r .c .isTypeIdenticalTo (instantiatedUnion , unionOfInstantiations )
4090+ r .c .distributiveInstantiationCache [key ] = result
4091+ return result
40534092}
40544093
40554094func (r * Relater ) typeArgumentsRelatedTo (sources []* Type , targets []* Type , variances []VarianceFlags , reportErrors bool , intersectionState IntersectionState ) Ternary {
0 commit comments