-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Add transformation to indexing intersection of homomorphic mapepd type and generic parameter #25490
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8999,6 +8999,14 @@ namespace ts { | |
return !!(getObjectFlags(type) & ObjectFlags.Mapped) && getTemplateTypeFromMappedType(type as MappedType) === neverType; | ||
} | ||
|
||
function isMappedTypeOverKeyofGenericInSet(set: ReadonlyArray<Type>): (type: Type) => boolean { | ||
return (type) => { | ||
if (!(getObjectFlags(type) & ObjectFlags.Mapped)) return false; | ||
const constraint = getConstraintTypeFromMappedType(type as MappedType); | ||
return some(set, t => getIndexType(t) === constraint); | ||
}; | ||
} | ||
|
||
function getSimplifiedType(type: Type): Type { | ||
return type.flags & TypeFlags.IndexedAccess ? getSimplifiedIndexedAccessType(<IndexedAccessType>type) : type; | ||
} | ||
|
@@ -9042,6 +9050,33 @@ namespace ts { | |
const nonNeverTypes = filter((<IntersectionType>objectType).types, t => !isMappedTypeToNever(t)); | ||
return type.simplified = getSimplifiedType(getIndexedAccessType(getIntersectionType(nonNeverTypes), type.indexType)); | ||
} | ||
|
||
// Given an indexed access type T[K], if T is an intersection containing a generic type and one or | ||
// more mapped types which map over the keys of that generic, '(T & {[P in keyof T]: Foo})[K]', return a | ||
// transformed type that moves the top-level intersection into the mapped type: '{[P in keyof T]: T[P] & Foo}[K]' | ||
// If we don't do such a transformation now, due to how we reason over intersections, we will never come to the | ||
// realization that the member we're trying to pluck out is influenced by both `T[P]` _and_ the mapped type template | ||
const isInSet = isMappedTypeOverKeyofGenericInSet((<IntersectionType>objectType).types); | ||
if (some((<IntersectionType>objectType).types, isInSet)) { | ||
for (const t of (<IntersectionType>objectType).types) { | ||
if (isInSet(t)) { | ||
const constraint = getConstraintTypeFromMappedType(t as MappedType); | ||
let matchIndex: number | undefined; | ||
const match = forEach((<IntersectionType>objectType).types, (t, i) => getIndexType(t) === constraint ? (matchIndex = i, t) : undefined)!; | ||
const indexedMatch = getIndexedAccessType(match, getTypeParameterFromMappedType(t as MappedType)); | ||
const newTemaplate = getIntersectionType([indexedMatch, getTemplateTypeFromMappedType(t as MappedType)]); | ||
const newMappedType = <MappedType>createObjectType(ObjectFlags.Mapped, t.symbol); | ||
newMappedType.declaration = (t as MappedType).declaration; | ||
newMappedType.templateType = newTemaplate; | ||
newMappedType.typeParameter = (t as MappedType).typeParameter; | ||
newMappedType.constraintType = (t as MappedType).constraintType; | ||
const newSet = (<IntersectionType>objectType).types.slice(); | ||
newSet.splice(matchIndex!, 1); // Remove the generic | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
newSet.splice(newSet.indexOf(t), 1, newMappedType); // Remove the original mapped type | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not directly overwrite the original location? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ... fair point. I was in a splicy mood, I suppose. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔥 🔥 🔥 🔥 |
||
return type.simplified = getSimplifiedType(getIndexedAccessType(getIntersectionType(newSet), type.indexType)); | ||
} | ||
} | ||
} | ||
} | ||
// If the object type is a mapped type { [P in K]: E }, where K is generic, instantiate E using a mapper | ||
// that substitutes the index type for P. For example, for an index access { [P in K]: Box<T[P]> }[X], we | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
tests/cases/compiler/intersectionMembersOfMappedTypeHaveCorrectApparentType.ts(20,32): error TS2339: Property 'returnValue' does not exist on type 'Function'. | ||
|
||
|
||
==== tests/cases/compiler/intersectionMembersOfMappedTypeHaveCorrectApparentType.ts (1 errors) ==== | ||
interface Spy { | ||
(...params: any[]): any; | ||
|
||
identity: string; | ||
and: Function; | ||
mostRecentCall: { args: any[]; }; | ||
argsForCall: any[]; | ||
} | ||
|
||
type SpyObj<T> = T & { | ||
[k in keyof T]: Spy; | ||
} | ||
|
||
declare function createSpyObj<T>( | ||
name: string, names: Array<keyof T>): SpyObj<T>; | ||
|
||
function mock<T>(spyName: string, methodNames: Array<keyof T>): SpyObj<T> { | ||
const spyObj = createSpyObj<T>(spyName, methodNames); | ||
for (const methodName of methodNames) { | ||
spyObj[methodName].and.returnValue(1); | ||
~~~~~~~~~~~ | ||
!!! error TS2339: Property 'returnValue' does not exist on type 'Function'. | ||
} | ||
return spyObj; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
//// [intersectionMembersOfMappedTypeHaveCorrectApparentType.ts] | ||
interface Spy { | ||
(...params: any[]): any; | ||
|
||
identity: string; | ||
and: Function; | ||
mostRecentCall: { args: any[]; }; | ||
argsForCall: any[]; | ||
} | ||
|
||
type SpyObj<T> = T & { | ||
[k in keyof T]: Spy; | ||
} | ||
|
||
declare function createSpyObj<T>( | ||
name: string, names: Array<keyof T>): SpyObj<T>; | ||
|
||
function mock<T>(spyName: string, methodNames: Array<keyof T>): SpyObj<T> { | ||
const spyObj = createSpyObj<T>(spyName, methodNames); | ||
for (const methodName of methodNames) { | ||
spyObj[methodName].and.returnValue(1); | ||
} | ||
return spyObj; | ||
} | ||
|
||
//// [intersectionMembersOfMappedTypeHaveCorrectApparentType.js] | ||
function mock(spyName, methodNames) { | ||
var spyObj = createSpyObj(spyName, methodNames); | ||
for (var _i = 0, methodNames_1 = methodNames; _i < methodNames_1.length; _i++) { | ||
var methodName = methodNames_1[_i]; | ||
spyObj[methodName].and.returnValue(1); | ||
} | ||
return spyObj; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
=== tests/cases/compiler/intersectionMembersOfMappedTypeHaveCorrectApparentType.ts === | ||
interface Spy { | ||
>Spy : Symbol(Spy, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 0, 0)) | ||
|
||
(...params: any[]): any; | ||
>params : Symbol(params, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 1, 5)) | ||
|
||
identity: string; | ||
>identity : Symbol(Spy.identity, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 1, 28)) | ||
|
||
and: Function; | ||
>and : Symbol(Spy.and, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 3, 21)) | ||
>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) | ||
|
||
mostRecentCall: { args: any[]; }; | ||
>mostRecentCall : Symbol(Spy.mostRecentCall, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 4, 18)) | ||
>args : Symbol(args, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 5, 21)) | ||
|
||
argsForCall: any[]; | ||
>argsForCall : Symbol(Spy.argsForCall, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 5, 37)) | ||
} | ||
|
||
type SpyObj<T> = T & { | ||
>SpyObj : Symbol(SpyObj, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 7, 1)) | ||
>T : Symbol(T, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 9, 12)) | ||
>T : Symbol(T, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 9, 12)) | ||
|
||
[k in keyof T]: Spy; | ||
>k : Symbol(k, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 10, 5)) | ||
>T : Symbol(T, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 9, 12)) | ||
>Spy : Symbol(Spy, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 0, 0)) | ||
} | ||
|
||
declare function createSpyObj<T>( | ||
>createSpyObj : Symbol(createSpyObj, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 11, 1)) | ||
>T : Symbol(T, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 13, 30)) | ||
|
||
name: string, names: Array<keyof T>): SpyObj<T>; | ||
>name : Symbol(name, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 13, 33)) | ||
>names : Symbol(names, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 14, 17)) | ||
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) | ||
>T : Symbol(T, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 13, 30)) | ||
>SpyObj : Symbol(SpyObj, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 7, 1)) | ||
>T : Symbol(T, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 13, 30)) | ||
|
||
function mock<T>(spyName: string, methodNames: Array<keyof T>): SpyObj<T> { | ||
>mock : Symbol(mock, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 14, 52)) | ||
>T : Symbol(T, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 16, 14)) | ||
>spyName : Symbol(spyName, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 16, 17)) | ||
>methodNames : Symbol(methodNames, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 16, 33)) | ||
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) | ||
>T : Symbol(T, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 16, 14)) | ||
>SpyObj : Symbol(SpyObj, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 7, 1)) | ||
>T : Symbol(T, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 16, 14)) | ||
|
||
const spyObj = createSpyObj<T>(spyName, methodNames); | ||
>spyObj : Symbol(spyObj, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 17, 9)) | ||
>createSpyObj : Symbol(createSpyObj, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 11, 1)) | ||
>T : Symbol(T, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 16, 14)) | ||
>spyName : Symbol(spyName, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 16, 17)) | ||
>methodNames : Symbol(methodNames, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 16, 33)) | ||
|
||
for (const methodName of methodNames) { | ||
>methodName : Symbol(methodName, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 18, 14)) | ||
>methodNames : Symbol(methodNames, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 16, 33)) | ||
|
||
spyObj[methodName].and.returnValue(1); | ||
>spyObj[methodName].and : Symbol(Spy.and, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 3, 21)) | ||
>spyObj : Symbol(spyObj, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 17, 9)) | ||
>methodName : Symbol(methodName, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 18, 14)) | ||
>and : Symbol(Spy.and, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 3, 21)) | ||
} | ||
return spyObj; | ||
>spyObj : Symbol(spyObj, Decl(intersectionMembersOfMappedTypeHaveCorrectApparentType.ts, 17, 9)) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
=== tests/cases/compiler/intersectionMembersOfMappedTypeHaveCorrectApparentType.ts === | ||
interface Spy { | ||
>Spy : Spy | ||
|
||
(...params: any[]): any; | ||
>params : any[] | ||
|
||
identity: string; | ||
>identity : string | ||
|
||
and: Function; | ||
>and : Function | ||
>Function : Function | ||
|
||
mostRecentCall: { args: any[]; }; | ||
>mostRecentCall : { args: any[]; } | ||
>args : any[] | ||
|
||
argsForCall: any[]; | ||
>argsForCall : any[] | ||
} | ||
|
||
type SpyObj<T> = T & { | ||
>SpyObj : SpyObj<T> | ||
>T : T | ||
>T : T | ||
|
||
[k in keyof T]: Spy; | ||
>k : k | ||
>T : T | ||
>Spy : Spy | ||
} | ||
|
||
declare function createSpyObj<T>( | ||
>createSpyObj : <T>(name: string, names: (keyof T)[]) => SpyObj<T> | ||
>T : T | ||
|
||
name: string, names: Array<keyof T>): SpyObj<T>; | ||
>name : string | ||
>names : (keyof T)[] | ||
>Array : T[] | ||
>T : T | ||
>SpyObj : SpyObj<T> | ||
>T : T | ||
|
||
function mock<T>(spyName: string, methodNames: Array<keyof T>): SpyObj<T> { | ||
>mock : <T>(spyName: string, methodNames: (keyof T)[]) => SpyObj<T> | ||
>T : T | ||
>spyName : string | ||
>methodNames : (keyof T)[] | ||
>Array : T[] | ||
>T : T | ||
>SpyObj : SpyObj<T> | ||
>T : T | ||
|
||
const spyObj = createSpyObj<T>(spyName, methodNames); | ||
>spyObj : SpyObj<T> | ||
>createSpyObj<T>(spyName, methodNames) : SpyObj<T> | ||
>createSpyObj : <T>(name: string, names: (keyof T)[]) => SpyObj<T> | ||
>T : T | ||
>spyName : string | ||
>methodNames : (keyof T)[] | ||
|
||
for (const methodName of methodNames) { | ||
>methodName : keyof T | ||
>methodNames : (keyof T)[] | ||
|
||
spyObj[methodName].and.returnValue(1); | ||
>spyObj[methodName].and.returnValue(1) : any | ||
>spyObj[methodName].and.returnValue : any | ||
>spyObj[methodName].and : Function | ||
>spyObj[methodName] : SpyObj<T>[keyof T] | ||
>spyObj : SpyObj<T> | ||
>methodName : keyof T | ||
>and : Function | ||
>returnValue : any | ||
>1 : 1 | ||
} | ||
return spyObj; | ||
>spyObj : SpyObj<T> | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
interface Spy { | ||
(...params: any[]): any; | ||
|
||
identity: string; | ||
and: Function; | ||
mostRecentCall: { args: any[]; }; | ||
argsForCall: any[]; | ||
} | ||
|
||
type SpyObj<T> = T & { | ||
[k in keyof T]: Spy; | ||
} | ||
|
||
declare function createSpyObj<T>( | ||
name: string, names: Array<keyof T>): SpyObj<T>; | ||
|
||
function mock<T>(spyName: string, methodNames: Array<keyof T>): SpyObj<T> { | ||
const spyObj = createSpyObj<T>(spyName, methodNames); | ||
for (const methodName of methodNames) { | ||
spyObj[methodName].and.returnValue(1); | ||
} | ||
return spyObj; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you turn this into a
findIndex
and then avoid the inner loop?