-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Improve reverse mapped types #31221
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
Improve reverse mapped types #31221
Changes from all commits
8891d4f
f73308b
6aaeb52
4af3a3b
c104aa1
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 |
---|---|---|
|
@@ -14887,10 +14887,19 @@ namespace ts { | |
return type; | ||
} | ||
|
||
// We consider a type to be partially inferable if it isn't marked non-inferable or if it is | ||
// an object literal type with at least one property of an inferable type. For example, an object | ||
// literal { a: 123, b: x => true } is marked non-inferable because it contains a context sensitive | ||
// arrow function, but is considered partially inferable because property 'a' has an inferable type. | ||
function isPartiallyInferableType(type: Type): boolean { | ||
return !(getObjectFlags(type) & ObjectFlags.NonInferrableType) || | ||
isObjectLiteralType(type) && some(getPropertiesOfType(type), prop => isPartiallyInferableType(getTypeOfSymbol(prop))); | ||
} | ||
|
||
function createReverseMappedType(source: Type, target: MappedType, constraint: IndexType) { | ||
// If any property contains context sensitive functions that have been skipped, the source type | ||
// is incomplete and we can't infer a meaningful input type. | ||
if (getObjectFlags(source) & ObjectFlags.NonInferrableType || getPropertiesOfType(source).length === 0 && !getIndexInfoOfType(source, IndexKind.String)) { | ||
// We consider a source type reverse mappable if it has a string index signature or if | ||
// it has one or more properties and is of a partially inferable type. | ||
if (!(getIndexInfoOfType(source, IndexKind.String) || getPropertiesOfType(source).length !== 0 && isPartiallyInferableType(source))) { | ||
return undefined; | ||
} | ||
// For arrays and tuples we infer new arrays and tuples where the reverse mapping has been | ||
|
@@ -15275,7 +15284,11 @@ namespace ts { | |
const inferredType = inferTypeForHomomorphicMappedType(source, target, <IndexType>constraintType); | ||
if (inferredType) { | ||
const savePriority = priority; | ||
priority |= InferencePriority.HomomorphicMappedType; | ||
// We assign a lower priority to inferences made from types containing non-inferrable | ||
// types because we may only have a partial result (i.e. we may have failed to make | ||
// reverse inferences for some properties). | ||
priority |= getObjectFlags(source) & ObjectFlags.NonInferrableType ? | ||
InferencePriority.PartialHomomorphicMappedType : InferencePriority.HomomorphicMappedType; | ||
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. I experimented with a top-level priority instead of this inner one. There are some error messages I think it makes look nicer (erroring on " 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. As I mentioned yesterday, I don't think that's the right approach anyway. The |
||
inferFromTypes(inferredType, inference.typeParameter); | ||
priority = savePriority; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
tests/cases/compiler/reverseMappedPartiallyInferableTypes.ts(91,20): error TS2571: Object is of type 'unknown'. | ||
|
||
|
||
==== tests/cases/compiler/reverseMappedPartiallyInferableTypes.ts (1 errors) ==== | ||
// Repro from #30505 | ||
|
||
export type Prop<T> = { (): T } | ||
export type PropType<T> = Prop<T>; | ||
export type PropDefaultValue<T> = T; | ||
|
||
|
||
export type PropValidatorFunction<T> = (value: T) => boolean; | ||
export type PropValidator<T> = PropOptions<T>; | ||
|
||
|
||
export type PropOptions<T> = { | ||
type: PropType<T>; | ||
|
||
value?: PropDefaultValue<T>, | ||
required?: boolean; | ||
validator?: PropValidatorFunction<T>; | ||
} | ||
|
||
export type RecordPropsDefinition<T> = { | ||
[K in keyof T]: PropValidator<T[K]> | ||
} | ||
export type PropsDefinition<T> = RecordPropsDefinition<T>; | ||
|
||
|
||
declare function extend<T>({ props }: { props: PropsDefinition<T> }): PropsDefinition<T>; | ||
|
||
interface MyType { | ||
valid: boolean; | ||
} | ||
|
||
const r = extend({ | ||
props: { | ||
notResolved: { | ||
type: Object as PropType<MyType>, | ||
validator: x => { | ||
return x.valid; | ||
} | ||
}, | ||
explicit: { | ||
type: Object as PropType<MyType>, | ||
validator: (x: MyType) => { | ||
return x.valid; | ||
} | ||
} | ||
} | ||
}) | ||
|
||
r.explicit | ||
r.notResolved | ||
r.explicit.required | ||
r.notResolved.required | ||
|
||
// Modified repro from #30505 | ||
|
||
type Box<T> = { | ||
contents?: T; | ||
contains?(content: T): boolean; | ||
}; | ||
|
||
type Mapped<T> = { | ||
[K in keyof T]: Box<T[K]>; | ||
} | ||
|
||
declare function id<T>(arg: Mapped<T>): Mapped<T>; | ||
|
||
// All properties have inferable types | ||
|
||
const obj1 = id({ | ||
foo: { | ||
contents: "" | ||
} | ||
}); | ||
|
||
// Some properties have inferable types | ||
|
||
const obj2 = id({ | ||
foo: { | ||
contents: "", | ||
contains(k) { | ||
return k.length > 0; | ||
} | ||
} | ||
}); | ||
|
||
// No properties have inferable types | ||
|
||
const obj3 = id({ | ||
foo: { | ||
contains(k) { | ||
return k.length > 0; | ||
~ | ||
!!! error TS2571: Object is of type 'unknown'. | ||
} | ||
} | ||
}); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
//// [reverseMappedPartiallyInferableTypes.ts] | ||
// Repro from #30505 | ||
|
||
export type Prop<T> = { (): T } | ||
export type PropType<T> = Prop<T>; | ||
export type PropDefaultValue<T> = T; | ||
|
||
|
||
export type PropValidatorFunction<T> = (value: T) => boolean; | ||
export type PropValidator<T> = PropOptions<T>; | ||
|
||
|
||
export type PropOptions<T> = { | ||
type: PropType<T>; | ||
|
||
value?: PropDefaultValue<T>, | ||
required?: boolean; | ||
validator?: PropValidatorFunction<T>; | ||
} | ||
|
||
export type RecordPropsDefinition<T> = { | ||
[K in keyof T]: PropValidator<T[K]> | ||
} | ||
export type PropsDefinition<T> = RecordPropsDefinition<T>; | ||
|
||
|
||
declare function extend<T>({ props }: { props: PropsDefinition<T> }): PropsDefinition<T>; | ||
|
||
interface MyType { | ||
valid: boolean; | ||
} | ||
|
||
const r = extend({ | ||
props: { | ||
notResolved: { | ||
type: Object as PropType<MyType>, | ||
validator: x => { | ||
return x.valid; | ||
} | ||
}, | ||
explicit: { | ||
type: Object as PropType<MyType>, | ||
validator: (x: MyType) => { | ||
return x.valid; | ||
} | ||
} | ||
} | ||
}) | ||
|
||
r.explicit | ||
r.notResolved | ||
r.explicit.required | ||
r.notResolved.required | ||
|
||
// Modified repro from #30505 | ||
|
||
type Box<T> = { | ||
contents?: T; | ||
contains?(content: T): boolean; | ||
}; | ||
|
||
type Mapped<T> = { | ||
[K in keyof T]: Box<T[K]>; | ||
} | ||
|
||
declare function id<T>(arg: Mapped<T>): Mapped<T>; | ||
|
||
// All properties have inferable types | ||
|
||
const obj1 = id({ | ||
foo: { | ||
contents: "" | ||
} | ||
}); | ||
|
||
// Some properties have inferable types | ||
|
||
const obj2 = id({ | ||
foo: { | ||
contents: "", | ||
contains(k) { | ||
return k.length > 0; | ||
} | ||
} | ||
}); | ||
|
||
// No properties have inferable types | ||
|
||
const obj3 = id({ | ||
foo: { | ||
contains(k) { | ||
return k.length > 0; | ||
} | ||
} | ||
}); | ||
|
||
|
||
//// [reverseMappedPartiallyInferableTypes.js] | ||
"use strict"; | ||
// Repro from #30505 | ||
exports.__esModule = true; | ||
var r = extend({ | ||
props: { | ||
notResolved: { | ||
type: Object, | ||
validator: function (x) { | ||
return x.valid; | ||
} | ||
}, | ||
explicit: { | ||
type: Object, | ||
validator: function (x) { | ||
return x.valid; | ||
} | ||
} | ||
} | ||
}); | ||
r.explicit; | ||
r.notResolved; | ||
r.explicit.required; | ||
r.notResolved.required; | ||
// All properties have inferable types | ||
var obj1 = id({ | ||
foo: { | ||
contents: "" | ||
} | ||
}); | ||
// Some properties have inferable types | ||
var obj2 = id({ | ||
foo: { | ||
contents: "", | ||
contains: function (k) { | ||
return k.length > 0; | ||
} | ||
} | ||
}); | ||
// No properties have inferable types | ||
var obj3 = id({ | ||
foo: { | ||
contains: function (k) { | ||
return k.length > 0; | ||
} | ||
} | ||
}); |
Uh oh!
There was an error while loading. Please reload this page.