Preview:```ts
interface A {}
interface B extends A {}
export const myFunc = () => {
return [...([] as A[]), ...([] as B[])]
}```
You can choose specific lines to embed by selecting them before copying the link.
35 messages ยท Page 1 of 1 (latest)
Preview:```ts
interface A {}
interface B extends A {}
export const myFunc = () => {
return [...([] as A[]), ...([] as B[])]
}```
A and B are identical there (both refer to the same type: {}), so A[] is the same as (A | B)[], which is what the return type is inferred as if you drop the annotation
interface A {}
interface B extends A {}
const myFunc = () => {
return [
...([] as A[]),
...([] as B[])
]
};
const x = myFunc()
// ^? - const x: A[]
and even if they weren't literally identical, since B extends A the type (A | B)[] is still the same type as A[]
interface A {
a: 'a'
}
interface B extends A {
b: 'b'
}
const myFunc = () => {
return [
...([] as A[]),
...([] as B[])
]
};
const x = myFunc()
// ^? - const x: A[]
if they're instead unrelated types, then the union type you expect to be inferred is inferred:
interface A {
a: 'a'
}
interface B {
b: 'b'
}
const myFunc = () => {
return [
...([] as A[]),
...([] as B[])
]
};
const x = myFunc()
// ^? - const x: (A | B)[]
yes both are an a, but I want to know if this function myFunc CAN return b, now I will believe this function only returns a
and TS should be able to know this by parsing the code but I guess TS isn't just there yet? ๐ฎ
then I will use explic types for now.
the type (A | B)[] is the same as the type A[] in your original example. it's also the same type as {}[]. they're all just different ways to write the same exact thing, and typescript is just picking one to use for display
I think it should pick both because I rely on my IDE hovering over types to know those things ๐ฆ
a function that returns A can always return a B. in fact there are an infinite number of possible subtypes of A. you should expect A to always mean "exactly A or any subtype of A", regardless of where you see it
I'm not saying TS does anything wrong. I just think it can do something better
it's like a function that returns string is enough information to tell you that it might return string | 'some specific string' | 'another specific string' | ...
(A|B)[] is better than A[] ๐
i'm trying to explain that they're exactly the same. they mean the same thing in the type system
oh, maybe you just mean for how TS displays that type in a type hint?
yes. essentially looking at the type of what this returns I want to know if I can do like if("propertyThatOnlyBHas" in item) { // it's b } else { // it's a }
because where I want to do this in the code is "far away" from this function
i'm saying you can always do that for any function that returns an A. the | B does not change anything about that. does that make sense?
well, if the function would look like
const myFunc = () => {
return [
...([] as A[])
]
};
then trying to see if an element from this function contains a property in B is completely useless, but other developers now are in the belief it can return a B, but it never will.
why is it completely useless? this is a valid implementation of a myFunc that returns just A[]:
interface A {}
interface B extends A {
propertyThatOnlyBHas: true
}
const myFunc = (): A[] => {
const b: B = { propertyThatOnlyBHas: true }
return [b]
}
a caller of such a function may want to narrow the array element to B still
Alright I think I know why TS does this, still not completely happy I could end up with dead code but I accept it ๐
i'm curious what actual code that led to this question is doing. there may be a different way to model things that doesn't involve one of the types being a subtype of the other, which sounds like it might make your life easier. i'd be happy to offer suggestions if you feel like sharing the real code/use case
I think it's too tied to our business logic but in our app we have A and then a bunch of interfaces extend A, B C or D.
But I know my function can only return A or B. TS should also be able to know this. So if TS says the function return only A[] then any developer think it's an A or anything else that implements A.
But I have no idea if it's completely empty, only filled with A, only filled with B, or filled with both A and B. But I'm certain it can't ever contain C or D.
but I still of course see your point that the function "could" return C or B "disguised", maybe not the correct term ๐
that's helpful even at such a high level. maybe you could introduce a new subtype of A (let's call it E) that models specifically what your function returns when it's not returning B (in a way that's narrower than the vague constraints A imposes). then the return type would be (B | E)[], and since B and E have no overlap and are both at the same "level" as C and D it'll help make it more clear what callers can expect to get back
sounds like you may also be interested in learning about discriminated unions if you don't already know about them. the idea is that you make sure all of your types in the union can have no overlap by tagging them in an explicit way, which makes narrowing much nicer
Understand how TypeScript uses JavaScript knowledge to reduce the amount of type syntax in your projects.
e.g. you could just check thingy.type === 'B' or somesuch rather than looking at other properties to infer its B-ness
const filteredPlanTransactionsOnOrganizationalUnits = useMemo<
(Transaction | ScenarioTransaction)[]
>(() => {
return [
...planActualTransactions, // Transaction[]
...activeScenariosTransactions, // (ScenarioTransaction | ReferableScenarioTransaction)[]
...nonScenarioTransactions, // Transaction[]
...temporaryTransactions, // Transaction[]
].filter(filterTransactionsByOrganizationalUnits);
}, [
planActualTransactions,
activeScenariosTransactions,
nonScenarioTransactions,
temporaryTransactions,
filterTransactionsByOrganizationalUnits,
]);
This is the code I'm having trouble with. You can see here I've asserted useMemo to return (Transaction | ScenarioTransaction)[](useMemo is a react hook).
My goto would be to just leave out the generic so TS can infer it.
However by doing so the return type looks like this:
const filteredPlanTransactionsOnOrganizationalUnits: (({
versionId: string;
versionName: string;
accountNumber: string;
ccCode: string;
transactionDate: string;
amount: number;
transactiondescr?: string | undefined;
type: "1" | "2";
customerCode: string;
... 4 more ...;
employeeCode: string;
} & {
...;
}) | ({
versionId: string;
versionName: string;
accountNumber: string;
ccCode: string;
transactionDate: string;
amount: number;
transactiondescr?: string | undefined;
type: "1" | "2";
customerCode: string;
... 4 more ...;
employeeCode: string;
} & {
...;
}))[]
In my perfect world the inferred return type would be (Transaction | ScenarioTransaction | ReferableScenarioTransaction)[]. What happened now is that someone forgot to add ReferableScenarioTransaction to the generic when activeScenariosTransactions was changed to being a ScenarioTransaction | ReferableScenarioTransaction