Skip to content

Fixed an issue with a concrete object not being assignable to a generic mapped type with a partially concrete constraint #49064

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
mappedTypeAsClauseRelationships.ts(12,9): error TS2322: Type 'T' is not assignable to type 'Modify<T>'.
mappedTypeAsClauseRelationships.ts(22,9): error TS2322: Type 'T' is not assignable to type 'ModifyInclOpt<T>'.
mappedTypeAsClauseRelationships.ts(23,9): error TS2322: Type 'T' is not assignable to type 'FilterExclOpt<T>'.
mappedTypeAsClauseRelationships.ts(24,9): error TS2322: Type 'T' is not assignable to type 'ModifyExclOpt<T>'.


==== mappedTypeAsClauseRelationships.ts (4 errors) ====
==== mappedTypeAsClauseRelationships.ts (3 errors) ====
// From original issue #45212:
type Methods<T> = { [P in keyof T as T[P] extends Function ? P : never]: T[P] };
type H<T> = T[keyof Methods<T>]; // Ok
Expand All @@ -30,9 +29,6 @@ mappedTypeAsClauseRelationships.ts(24,9): error TS2322: Type 'T' is not assignab
function fun2<T>(val: T) {
let x: FilterInclOpt<T> = val; // Ok
let y: ModifyInclOpt<T> = val; // Ok
~
!!! error TS2322: Type 'T' is not assignable to type 'ModifyInclOpt<T>'.
!!! related TS2208 mappedTypeAsClauseRelationships.ts:20:15: This type parameter might need an `extends ModifyInclOpt<T>` constraint.
let z: FilterExclOpt<T> = val; // Error
~
!!! error TS2322: Type 'T' is not assignable to type 'FilterExclOpt<T>'.
Expand Down
Original file line number Diff line number Diff line change
@@ -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<TContext, TEvent extends { type: string }> = {
>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<TContext, TEvent extends { type: string }> = {
>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<TContext, ExtractEvent<TEvent, K>>;
>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<TContext, TEvent>;
>"*" : 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<TContext, TEvent extends { type: string }> = {
>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<TContext, TEvent>
>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<TContext, ExtractEvent<TEvent, K>>;
>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<TEvent extends { type: string }>() {
>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))
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//// [tests/cases/compiler/mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts] ////

=== mappedTypeWithPartiallyConcreteConstraintAcceptsConcreteObject.ts ===
type ExtractEvent<
>ExtractEvent : ExtractEvent<TEvent, TEventType>

TEvent extends { type: string },
>type : string

TEventType extends TEvent["type"]
> = TEvent extends {
type: TEventType;
>type : TEventType
}
? TEvent
: never;

type TransitionConfig<TContext, TEvent extends { type: string }> = {
>TransitionConfig : TransitionConfig<TContext, TEvent>
>type : string

actions?: {
>actions : { type: string; } | undefined

type: string;
>type : string

};
};

type IntersectedTransitionConfigMap<TContext, TEvent extends { type: string }> = {
>IntersectedTransitionConfigMap : IntersectedTransitionConfigMap<TContext, TEvent>
>type : string

[K in TEvent["type"]]?: TransitionConfig<TContext, ExtractEvent<TEvent, K>>;
} & {
"*": TransitionConfig<TContext, TEvent>;
>"*" : TransitionConfig<TContext, TEvent>

};

type TransitionConfigMap<TContext, TEvent extends { type: string }> = {
>TransitionConfigMap : TransitionConfigMap<TContext, TEvent>
>type : string

[K in TEvent["type"] | "*"]?: K extends "*"
? TransitionConfig<TContext, TEvent>
: TransitionConfig<TContext, ExtractEvent<TEvent, K>>;
};

export function genericFn<TEvent extends { type: string }>() {
>genericFn : <TEvent extends { type: string; }>() => 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"; }; }; }
}

Original file line number Diff line number Diff line change
@@ -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<TContext, TEvent extends { type: string }> = {
actions?: {
type: string;
};
};

type IntersectedTransitionConfigMap<TContext, TEvent extends { type: string }> = {
[K in TEvent["type"]]?: TransitionConfig<TContext, ExtractEvent<TEvent, K>>;
} & {
"*": TransitionConfig<TContext, TEvent>;
};

type TransitionConfigMap<TContext, TEvent extends { type: string }> = {
[K in TEvent["type"] | "*"]?: K extends "*"
? TransitionConfig<TContext, TEvent>
: TransitionConfig<TContext, ExtractEvent<TEvent, K>>;
};

export function genericFn<TEvent extends { type: string }>() {
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;
}