22
loading...
This website collects cookies to deliver better user experience
type User = {
name: string;
}
number
?type User = {
name: string;
}
type User1 = User & {
name: number;
}
type User2 = {
[P in keyof User]: P extends 'name' ? number : User[P]
}
type User3 = Omit<User, 'name'> & { name: number }
type Type = {
name: string
}
type SubTypeA = Type & {
salary: string
}
type SubTypeB = Type & {
car: boolean
}
type Extends<T, U> =
T extends U ? true : false
let employee: SubTypeA = {
name: 'John Doe',
salary: '1000$'
}
let human: Type = {
name: 'Morgan Freeman'
}
let student: SubTypeB = {
name: 'Will',
car: true
}
// same direction
type Covariance<T> = {
box: T
}
let employeeInBox: Covariance<SubTypeA> = {
box: employee
}
let humanInBox: Covariance<Type> = {
box: human
}
/**
* MUTATION
*/
let test: Covariance<Type> = employeeInBox
test.box = student // mutation of employeeInBox
// while result_0 is undefined, it is infered a a string
const result_0 = employeeInBox.box.salary
/**
* MUTATION
*/
let array: Array<Type> = []
let employees = [employee]
array = employees
array.push(student)
// while result_1 is [string, undefined], it is infered as string[]
const result_1 = employees.map(elem => elem.salary)
readonly
flag to Covariance
and use ReadonlyArray
type Covariance<T> = {
readonly box: T
}
let array: ReadonlyArray<Type> = []
interface InjectMap {
"A": "B",
"C": "D"
}
type InjectKey = keyof InjectMap;
const input: Partial<InjectMap> = {};
const output: Partial<InjectMap> = {};
const keys: InjectKey[] = []
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const inp = input[key] // "B" | "D" | undefined
const out = output[key] // "B" | "D" | undefined
output[key] = input[key] // error
}
input
and output
share same type, they could have different value.type KeyType_ = "B" | "D" | undefined
let keyB: KeyType_ = 'B';
let keyD: KeyType_ = 'D'
output[keyB] = input[keyD] // Boom, illegal state! Runtime error!
const foo = <T extends { [key: string]: any }>(obj: T) => {
obj['a'] = 2 // error
}
obj
argument can lead to runtime errors.let index: { [key: string]: any } = {}
let immutable = {
a: 'a'
} as const
let record: Record<'a', 1> = { a: 1 }
index = immutable // ok
index = record // ok
const foo = <T extends { [key: string]: any }>(obj: T) => {
obj['a'] = 2 // error
return obj
}
const result1 = foo(immutable) // unsound, see return type
const result2 = foo(record) // unsound , see return type
Reflect.deleteProperty
or delete
operatorlet index: { [key: string]: any } = {}
let immutable = {
a: 'a'
} as const
let record: Record<'a', 1> = { a: 1 }
index = immutable // ok
index = record // ok
const foo = <T extends { [key: string]: any }>(obj: T) => {
Reflect.deleteProperty(obj, 'a') // or delete obj.a
return obj
}
const result1 = foo(immutable) // unsound, see return type
const result2 = foo(record) // unsound , see return type
type Foo = {
age: number
}
const foo: Foo = { age: 42 }
delete foo.age // error
const paths = ['a', 'b'] as const
type Path = typeof paths[number]
type PathMap = {
[path in Path]: path
}
const BASE_PATHS = paths.reduce((map: PathMap, p: Path) => {
let x = map[p]
map[p] = p // error
return map
}, {} as PathMap)
type a = 'a'
type b = 'b'
type c = a & b // never
With this PR we improve soundness of indexed access types in a number of ways:
T[K]
occurs on the source side of a type relationship, it resolves to a union type of the properties selected by T[K]
, but when it occurs on the target side of a type relationship, it now resolves to an intersection type of the properties selected by T[K]
. Previously, the target side would resolve to a union type as well, which is unsound.T
with a constraint C
, when an indexed access T[K]
occurs on the target side of a type relationship, index signatures in C
are now ignored. This is because a type argument for T
isn't actually required to have an index signature, it is just required to have properties with matching types.{ [key: string]: number }
is no longer related to a mapped type { [P in K]: number }
, where K
is a type variable. This is consistent with a string index signature in the source not matching actual properties in the target.T
and K extends 'a' | 'b'
, the types { a: T, b: T }[K]
and T
are now considered related where previously they weren't.Some examples:
function f1(obj: { a: number, b: string }, key: 'a' | 'b') {
obj[key] = 1; // Error
obj[key] = 'x'; // Error
}
function f2(obj: { a: number, b: 0 | 1 }, key: 'a' | 'b') {
obj[key] = 1;
obj[key] = 2; // Error
}
function f3<T extends { [key: string]: any }>(obj: T) {
let foo = obj['foo'];
let bar = obj['bar'];
obj['foo'] = 123; // Error
obj['bar'] = 'x'; // Error
}
function f4<K extends string>(a: { [P in K]: number }, b: { [key: string]: number }) {
a = b; // Error
b = a;
}
Previously, none of the above errors were reported.
Fixes #27895. Fixes #30603.
type A = {
data: string;
check: (a: A['data']) => string
}
type B = {
data: number;
check: (a: B['data']) => number
}
type C = {
data: number[];
check: (a: C['data']) => number
}
type Props = A | B | C;
const Comp = (props: Props) => {
// check(a: never): string | number
props.check()
return null
}