Closed
Description
TypeScript Version: 2.7.0-dev.20171214
Code
export class Missing {
public str : string;
}
export type ResultOrMissing<A> = A | Missing;
export interface IBar<A> {
myParam: ResultOrMissing<A>;
}
export interface IFoo<A> {
myParam: A;
}
export type BothOrUndefined<A> = (IFoo<A> | IBar<A>);
export interface IReturnsBoth {
<A>() : (BothOrUndefined<A> | undefined);
}
const helper : IReturnsBoth = <A>() : (BothOrUndefined<A> | undefined) => {
return undefined;
};
Expected behavior:
Compiling the above code with "tsc .ts -strictFunctionTypes" should succeed.
Actual behavior:
Somehow wires get crossed and we get this error:
repro.ts(20,7): error TS2322: Type '<A>() => IFoo<A> | IBar<A>' is not assignable to type 'IReturnsBoth'.
Type 'IFoo<ResultOrMissing<A>> | IBar<ResultOrMissing<A>>' is not assignable to type 'IFoo<A> | IBar<A>'.
Type 'IBar<ResultOrMissing<A>>' is not assignable to type 'IFoo<A> | IBar<A>'.
Type 'IBar<ResultOrMissing<A>>' is not assignable to type 'IBar<A>'.
Type 'ResultOrMissing<A>' is not assignable to type 'A'.
Type 'Missing' is not assignable to type 'A'.
Note the unexplained switch from IBar<A>
to IBar<ResultOrMissing<A>>
.
Metadata
Metadata
Assignees
Type
Projects
Milestone
Relationships
Development
No branches or pull requests
Activity
RyanCavanaugh commentedon Dec 15, 2017
@ahejlsberg can you elucidate? Simplified repro with unique names:
geovanisouza92 commentedon Dec 15, 2017
Seems similar to #20702
ahejlsberg commentedon Dec 15, 2017
Here's what happens: In order to check the assignment to
helper
we need to check that<T>() => (IFoo<T> | IBar<T>)
(the source type) is assignable to<A>() => (IFoo<A> | IBar<A>)
(the target type). In--strictFunctionTypes
mode that means we need to instantiate source type in the context of the target type, which means we need to infer from<A>() => (IFoo<A> | IBar<A>)
to<T>() => (IFoo<T> | IBar<T>)
for the type variableT
. In that process we make two inferences forT
:A
and""
. We get two inferences becauseIFoo
andIBar
are structurally identical, so we succeed in making inferences from bothIFoo<A>
andIBar<A>
toIFoo<T>
. In other words, structurally both are candidates.We could potentially consider being more selective in inference between union types by attempting to pairwise match types in the source and target to avoid "cross contamination", but it gets complicated pretty quickly.
liminf commentedon Dec 16, 2017
My original issue had the two props being functions that returned different things:
interface IFoo<B> { handler: () => B } interface IBar<C> { handler: () => () => C }
And I can see the issue here even better - in trying to infer
T
, it would come up with a few possibilities. In addition to the obviousT
mapping toA
, there is also the possibility ofIBar<A>
can be assigned toIFoo<T>
ifT is () => A
. This makes it so thatT : A | () => A
which results in an effective return type of(IFoo<A | () => A> | IBar<A | () => A>)
because the inference is separated from the application of the inferred value to the function signature.It sounds like the "right" solution would be along the lines that you mention: break up the union and infer (and compare) them all pairwise. Is this something that could reasonably be done or is it too destabilizing and should be saved as part of some bigger refactor?
RyanCavanaugh commentedon Jun 23, 2021
This works as expected now