Please consider this example:
// all properties in Item should be optional, this is by design
type Item = {
id?: number
name?: string
}
interface WithVersion {
version: number
}
export type ResultType =
& WithVersion // #1 try to remove it
& {
version: number
list: Item[];
};
export interface Data {
list: Array<string>;
}
// As per my understanding, this function should not compile, because elem.list is not assignable to list in ResultType
const builder = <T extends Data>(data: Array<T>): ResultType[] => {
const result = data.map((elem) => ({
list: elem.list, // #2 list is string[], whereas ResultType expects list to be Item[]
version: 2,
}))
return result // #3
}
I am a bit confused by the assignability rules of TypeScript.
Try to remove WithVersion
from ResultType
. TypeScript will complain about the assignability of result
to return type of builder
function ( ResultType
).
Further more, if you define explicit return type for Array.prototype.map
callback, TypeScript will complain just as I expect:
const builder = <T extends Data>(data: Array<T>): ResultType[] => {
const result = data.map((elem):ResultType => ({
list: elem.list, // error as expected
version: 2,
}))
return result
}
My questions are:
Why there is no error without explicit type for map
callback. It is clear that string[]
is not assignable to Item[]
.
declare let foo: string[] declare let bar: Item[] foo = bar bar = foo
Why does an error appear when I remove WithVersion
from the ResultType
definition? It looks like that intersection of WithVersion
and { version: number list: Item[]; }
{ version: number list: Item[]; }
somehow affects ResultType
, whereas in my opinion, this intersection should not affect it at all.
SIMPLIFIED VERSION
type Item = {
id?: number
name?: string
}
interface WithVersion {
version: number
}
export type ResultType =
& WithVersion // #1 try to remove it
& {
version: number
list: Item[];
};
declare let result: ResultType;
declare let list: string[];
let a = {
list,
version: 2,
};
result = a;
I have created an issue in TS repo
TL;DR Weak type detection doesn't always occur for intersection types ; this seems to be behaving as designed, although the particular behavior you're seeing might be a design limitation.
From a purely structural standpoint, string
is indeed assignable to Item
. The Item
type has optional properties id
and name
, and values of type string
are missing these properties, so there's no apparent conflict. Reading "foo".id
or "bar".name
gives you undefined
in both cases. If TypeScript only cared about structural compatibility, then none of your examples would have compiler errors.
Of course, it is probably a mistake to assign a string
value to something that expects an Item
. TypeScript has several features to try to catch these sort of non-type-safety mistakes. These are more like linter warnings than type errors.
One such feature is weak type detection . A weak type is an object type whose properties are all optional, like Item
. Weak type detection causes the compiler to complain if you try to assign something to a weak type if there is no overlap in properties. (Aside: a more well-known such feature is excess property checking , in which object literals are not allowed to have unexpected properties.) Weak type detection is why you will get a warning if you assign a string
to an Item
or a string[]
to an Item[]
or an {x: string; y: string}
{x: string; y: string}
to an {x: Item; y: string}
{x: Item; y: string}
:
const foo = { x: "", y: "" }
const bar: { x: Item; y: string } = foo; // error
So then: why don't you get a warning when you try to assign an {x: string, y: string}
to an {x: Item} & {y: string}
?
const baz: { x: Item } & { y: string } = foo; // no error?!
Why is there a difference with intersection types ?
Well, according to microsoft/TypeScript#16047 , the pull request that implemented weak type detection, weak type detection does not occur in intersection types unless all the intersected types are weak types. Since {y: string}
is not a weak type (and technically neither is {x: Item}
because the x
property is not optional), then the intersection does not undergo weak type checking at all. Therefore things fall back to the normal structural type check, and the assignment succeeds.
This still raises the question of why weak type detection was implemented so that intersections are usually exempt. I don't really see this explicitly documented, but it looks like there was a prior version of this feature implemented at microsoft/TypeScript#3842 where weak type detection for intersections caused undesirable behavior. My presumption here is that the intent was to be somewhat conservative and only emit errors in cases known to be bad, and there are some intersection situations we want to accept (maybe generics?).
In any case, this is definitely behaving as designed. It might be a design limitation. I suppose we will wait to see what the official word is on microsoft/TypeScript#50608 before knowing for sure.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.