diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 967ad86941b40..74ae23a638359 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -21599,8 +21599,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { ? getIntersectionType([filteredByApplicability, typeParameter]) : typeParameter; const indexedAccessType = getIndexedAccessType(source, indexingType); + + if (filteredByApplicability && isTypeAssignableTo(filteredByApplicability, target.constraintType!)) { + const mapper = appendTypeMapping(target.mapper, getTypeParameterFromMappedType(target), filteredByApplicability); + const propType = instantiateType(templateType, mapper); + if (result = isRelatedTo(indexedAccessType, propType)) { + return result; + } + } // Compare `S[indexingType]` to `T`, where `T` is the type of a property of the target type. - if (result = isRelatedTo(indexedAccessType, templateType, RecursionFlags.Both, reportErrors)) { + else if (result = isRelatedTo(indexedAccessType, templateType, RecursionFlags.Both, reportErrors)) { return result; } } diff --git a/tests/baselines/reference/mappedTypeAsClauseRelationships.errors.txt b/tests/baselines/reference/mappedTypeAsClauseRelationships.errors.txt index b2a07d632f892..c3fbe545bff77 100644 --- a/tests/baselines/reference/mappedTypeAsClauseRelationships.errors.txt +++ b/tests/baselines/reference/mappedTypeAsClauseRelationships.errors.txt @@ -1,10 +1,9 @@ mappedTypeAsClauseRelationships.ts(12,9): error TS2322: Type 'T' is not assignable to type 'Modify'. -mappedTypeAsClauseRelationships.ts(22,9): error TS2322: Type 'T' is not assignable to type 'ModifyInclOpt'. mappedTypeAsClauseRelationships.ts(23,9): error TS2322: Type 'T' is not assignable to type 'FilterExclOpt'. mappedTypeAsClauseRelationships.ts(24,9): error TS2322: Type 'T' is not assignable to type 'ModifyExclOpt'. -==== mappedTypeAsClauseRelationships.ts (4 errors) ==== +==== mappedTypeAsClauseRelationships.ts (3 errors) ==== // From original issue #45212: type Methods = { [P in keyof T as T[P] extends Function ? P : never]: T[P] }; type H = T[keyof Methods]; // Ok @@ -30,9 +29,6 @@ mappedTypeAsClauseRelationships.ts(24,9): error TS2322: Type 'T' is not assignab function fun2(val: T) { let x: FilterInclOpt = val; // Ok let y: ModifyInclOpt = val; // Ok - ~ -!!! error TS2322: Type 'T' is not assignable to type 'ModifyInclOpt'. -!!! related TS2208 mappedTypeAsClauseRelationships.ts:20:15: This type parameter might need an `extends ModifyInclOpt` constraint. let z: FilterExclOpt = val; // Error ~ !!! error TS2322: Type 'T' is not assignable to type 'FilterExclOpt'. diff --git a/tests/baselines/reference/mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.symbols b/tests/baselines/reference/mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.symbols new file mode 100644 index 0000000000000..5ce6dababb262 --- /dev/null +++ b/tests/baselines/reference/mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.symbols @@ -0,0 +1,143 @@ +//// [tests/cases/compiler/mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts] //// + +=== mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts === +type ExtractEvent< +>ExtractEvent : Symbol(ExtractEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 0, 0)) + + TEvent extends { type: string }, +>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 0, 18)) +>type : Symbol(type, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 1, 18)) + + TEventType extends TEvent["type"] +>TEventType : Symbol(TEventType, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 1, 34)) +>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 0, 18)) + +> = TEvent extends { +>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 0, 18)) + + type: TEventType; +>type : Symbol(type, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 3, 20)) +>TEventType : Symbol(TEventType, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 1, 34)) +} + ? TEvent +>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 0, 18)) + + : never; + +type TransitionConfig = { +>TransitionConfig : Symbol(TransitionConfig, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 7, 10)) +>TContext : Symbol(TContext, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 9, 22)) +>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 9, 31)) +>type : Symbol(type, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 9, 48)) + + actions?: { +>actions : Symbol(actions, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 9, 68)) + + type: string; +>type : Symbol(type, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 10, 13)) + + }; +}; + +type IntersectedTransitionConfigMap = { +>IntersectedTransitionConfigMap : Symbol(IntersectedTransitionConfigMap, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 13, 2)) +>TContext : Symbol(TContext, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 15, 36)) +>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 15, 45)) +>type : Symbol(type, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 15, 62)) + + [K in TEvent["type"]]?: TransitionConfig>; +>K : Symbol(K, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 16, 3)) +>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 15, 45)) +>TransitionConfig : Symbol(TransitionConfig, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 7, 10)) +>TContext : Symbol(TContext, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 15, 36)) +>ExtractEvent : Symbol(ExtractEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 0, 0)) +>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 15, 45)) +>K : Symbol(K, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 16, 3)) + +} & { + "*": TransitionConfig; +>"*" : Symbol("*", Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 17, 5)) +>TransitionConfig : Symbol(TransitionConfig, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 7, 10)) +>TContext : Symbol(TContext, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 15, 36)) +>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 15, 45)) + +}; + +type TransitionConfigMap = { +>TransitionConfigMap : Symbol(TransitionConfigMap, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 19, 2)) +>TContext : Symbol(TContext, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 21, 25)) +>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 21, 34)) +>type : Symbol(type, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 21, 51)) + + [K in TEvent["type"] | "*"]?: K extends "*" +>K : Symbol(K, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 22, 3)) +>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 21, 34)) +>K : Symbol(K, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 22, 3)) + + ? TransitionConfig +>TransitionConfig : Symbol(TransitionConfig, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 7, 10)) +>TContext : Symbol(TContext, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 21, 25)) +>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 21, 34)) + + : TransitionConfig>; +>TransitionConfig : Symbol(TransitionConfig, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 7, 10)) +>TContext : Symbol(TContext, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 21, 25)) +>ExtractEvent : Symbol(ExtractEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 0, 0)) +>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 21, 34)) +>K : Symbol(K, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 22, 3)) + +}; + +export function genericFn() { +>genericFn : Symbol(genericFn, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 25, 2)) +>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 27, 26)) +>type : Symbol(type, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 27, 42)) + + const wildcardTransitionConfig = { +>wildcardTransitionConfig : Symbol(wildcardTransitionConfig, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 28, 7)) + + "*": { actions: { type: "someAction" } }, +>"*" : Symbol("*", Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 28, 36)) +>actions : Symbol(actions, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 29, 10)) +>type : Symbol(type, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 29, 21)) + + } as const; +>const : Symbol(const) + + // this should be assignable, in the same way as the following assignment is OK + let test: TransitionConfigMap< +>test : Symbol(test, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 33, 5)) +>TransitionConfigMap : Symbol(TransitionConfigMap, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 19, 2)) + + { counter: number }, +>counter : Symbol(counter, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 34, 5)) + + { type: TEvent["type"] } +>type : Symbol(type, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 35, 5)) +>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 27, 26)) + + > = {} as typeof wildcardTransitionConfig; +>wildcardTransitionConfig : Symbol(wildcardTransitionConfig, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 28, 7)) + + // concrete prop is assignable to the concrete prop of this mapped type + test["*"] = {} as typeof wildcardTransitionConfig["*"]; +>test : Symbol(test, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 33, 5)) +>"*" : Symbol("*") +>wildcardTransitionConfig : Symbol(wildcardTransitionConfig, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 28, 7)) + + // similar intersected type accepts this concrete object + let test2: IntersectedTransitionConfigMap< +>test2 : Symbol(test2, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 42, 5)) +>IntersectedTransitionConfigMap : Symbol(IntersectedTransitionConfigMap, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 13, 2)) + + { counter: number }, +>counter : Symbol(counter, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 43, 5)) + + { type: TEvent["type"] } +>type : Symbol(type, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 44, 5)) +>TEvent : Symbol(TEvent, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 27, 26)) + + > = {} as typeof wildcardTransitionConfig; +>wildcardTransitionConfig : Symbol(wildcardTransitionConfig, Decl(mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts, 28, 7)) +} + diff --git a/tests/baselines/reference/mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.types b/tests/baselines/reference/mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.types new file mode 100644 index 0000000000000..b09f34589f099 --- /dev/null +++ b/tests/baselines/reference/mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.types @@ -0,0 +1,110 @@ +//// [tests/cases/compiler/mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts] //// + +=== mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts === +type ExtractEvent< +>ExtractEvent : ExtractEvent + + TEvent extends { type: string }, +>type : string + + TEventType extends TEvent["type"] +> = TEvent extends { + type: TEventType; +>type : TEventType +} + ? TEvent + : never; + +type TransitionConfig = { +>TransitionConfig : TransitionConfig +>type : string + + actions?: { +>actions : { type: string; } | undefined + + type: string; +>type : string + + }; +}; + +type IntersectedTransitionConfigMap = { +>IntersectedTransitionConfigMap : IntersectedTransitionConfigMap +>type : string + + [K in TEvent["type"]]?: TransitionConfig>; +} & { + "*": TransitionConfig; +>"*" : TransitionConfig + +}; + +type TransitionConfigMap = { +>TransitionConfigMap : TransitionConfigMap +>type : string + + [K in TEvent["type"] | "*"]?: K extends "*" + ? TransitionConfig + : TransitionConfig>; +}; + +export function genericFn() { +>genericFn : () => void +>type : string + + const wildcardTransitionConfig = { +>wildcardTransitionConfig : { readonly "*": { readonly actions: { readonly type: "someAction"; }; }; } +>{ "*": { actions: { type: "someAction" } }, } as const : { readonly "*": { readonly actions: { readonly type: "someAction"; }; }; } +>{ "*": { actions: { type: "someAction" } }, } : { readonly "*": { readonly actions: { readonly type: "someAction"; }; }; } + + "*": { actions: { type: "someAction" } }, +>"*" : { readonly actions: { readonly type: "someAction"; }; } +>{ actions: { type: "someAction" } } : { readonly actions: { readonly type: "someAction"; }; } +>actions : { readonly type: "someAction"; } +>{ type: "someAction" } : { readonly type: "someAction"; } +>type : "someAction" +>"someAction" : "someAction" + + } as const; + + // this should be assignable, in the same way as the following assignment is OK + let test: TransitionConfigMap< +>test : TransitionConfigMap<{ counter: number; }, { type: TEvent["type"]; }> + + { counter: number }, +>counter : number + + { type: TEvent["type"] } +>type : TEvent["type"] + + > = {} as typeof wildcardTransitionConfig; +>{} as typeof wildcardTransitionConfig : { readonly "*": { readonly actions: { readonly type: "someAction"; }; }; } +>{} : {} +>wildcardTransitionConfig : { readonly "*": { readonly actions: { readonly type: "someAction"; }; }; } + + // concrete prop is assignable to the concrete prop of this mapped type + test["*"] = {} as typeof wildcardTransitionConfig["*"]; +>test["*"] = {} as typeof wildcardTransitionConfig["*"] : { readonly actions: { readonly type: "someAction"; }; } +>test["*"] : TransitionConfig<{ counter: number; }, { type: TEvent["type"]; }> | undefined +>test : TransitionConfigMap<{ counter: number; }, { type: TEvent["type"]; }> +>"*" : "*" +>{} as typeof wildcardTransitionConfig["*"] : { readonly actions: { readonly type: "someAction"; }; } +>{} : {} +>wildcardTransitionConfig : { readonly "*": { readonly actions: { readonly type: "someAction"; }; }; } + + // similar intersected type accepts this concrete object + let test2: IntersectedTransitionConfigMap< +>test2 : IntersectedTransitionConfigMap<{ counter: number; }, { type: TEvent["type"]; }> + + { counter: number }, +>counter : number + + { type: TEvent["type"] } +>type : TEvent["type"] + + > = {} as typeof wildcardTransitionConfig; +>{} as typeof wildcardTransitionConfig : { readonly "*": { readonly actions: { readonly type: "someAction"; }; }; } +>{} : {} +>wildcardTransitionConfig : { readonly "*": { readonly actions: { readonly type: "someAction"; }; }; } +} + diff --git a/tests/cases/compiler/mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts b/tests/cases/compiler/mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts new file mode 100644 index 0000000000000..498d2a1f1eb58 --- /dev/null +++ b/tests/cases/compiler/mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts @@ -0,0 +1,50 @@ +// @strict: true +// @noEmit: true + +type ExtractEvent< + TEvent extends { type: string }, + TEventType extends TEvent["type"] +> = TEvent extends { + type: TEventType; +} + ? TEvent + : never; + +type TransitionConfig = { + actions?: { + type: string; + }; +}; + +type IntersectedTransitionConfigMap = { + [K in TEvent["type"]]?: TransitionConfig>; +} & { + "*": TransitionConfig; +}; + +type TransitionConfigMap = { + [K in TEvent["type"] | "*"]?: K extends "*" + ? TransitionConfig + : TransitionConfig>; +}; + +export function genericFn() { + const wildcardTransitionConfig = { + "*": { actions: { type: "someAction" } }, + } as const; + + // this should be assignable, in the same way as the following assignment is OK + let test: TransitionConfigMap< + { counter: number }, + { type: TEvent["type"] } + > = {} as typeof wildcardTransitionConfig; + + // concrete prop is assignable to the concrete prop of this mapped type + test["*"] = {} as typeof wildcardTransitionConfig["*"]; + + // similar intersected type accepts this concrete object + let test2: IntersectedTransitionConfigMap< + { counter: number }, + { type: TEvent["type"] } + > = {} as typeof wildcardTransitionConfig; +}