Open
Description
TypeScript Version: 3.5.1
Search Terms:
function, intersection, conditional type, parameter list, assignable
Code
/**
* We should never be able to create a value of this type legitimately.
*
* `ErrorMessageT` is our error message
*/
interface CompileError<ErrorMessageT extends any[]> {
/**
* There should never be a value of this type
*/
readonly __compileError : never;
}
type ErrorA = CompileError<["I am error A"]>;
type ErrorB = CompileError<["I am error B"]>;
declare const errorA : ErrorA;
/**
* Different compile errors are assignable to each other.
*/
const errorB : ErrorB = errorA;
/**
* Pretend this is `v1.0.0` of your library.
*/
declare function foo <N extends number> (
/**
* This is how we use `CompileError<>` to prevent `3` from being
* a parameter
*/
n : (
N &
(Extract<3, N> extends never ?
unknown :
CompileError<[3, "is not allowed; received", N]>)
)
) : void;
/**
* Argument of type '3' is not assignable to parameter of type
* 'CompileError<[3, "is not allowed; received", 3]>'.
*/
foo(3);
/**
* OK!
*/
foo(5);
/**
* Argument of type '3 | 5' is not assignable to parameter of type
* 'CompileError<[3, "is not allowed; received", 3 | 5]>'.
*/
foo(5 as 3|5);
/**
* Argument of type 'number' is not assignable to parameter of type
* 'CompileError<[3, "is not allowed; received", number]>'.
*/
foo(5 as number);
///////////////////////////////////////////////////////////////////
/**
* The same as `foo<>()` but with a different error message.
*
* Pretend this is `v1.1.0` of your library.
*/
declare function bar <N extends number> (
n : (
N &
(Extract<3, N> extends never ?
unknown :
CompileError<[3, "is not allowed; received", N]>)
)
) : void;
/**
* Expected: Assignable to each other
* Actual: Not assignable to each other
*/
const fooIsAssignableToBar : typeof bar = foo;
const barIsAssignableToFoo : typeof foo = bar;
Expected behavior:
The following should have no errors,
const fooIsAssignableToBar : typeof bar = foo;
const barIsAssignableToFoo : typeof foo = bar;
Actual behavior:
It has errors
Playground Link:
Related Issues:
Also, related to my comment here,
Metadata
Metadata
Assignees
Type
Projects
Milestone
Relationships
Development
No branches or pull requests
Activity
AnyhowStep commentedon Jul 17, 2019
If you remove the
N &
part from the parameters and replaceunknown
withN
, it works.Playground
AnyhowStep commentedon Jul 17, 2019
The motivation behind introducing intersection types is so that multiple compile-time requirements can be chained together,
Of course, with such a "basic" type like
number
, the conditions aren't very interesting.A code snippet of something more complicated,
If all the conditions are satisfied,
AssertValidJoinTarget<QueryT, AliasedTableT>
resolves toAliasedTableT
.If at least one of the conditions fail, it resolves to
AliasedTableT & CompileError<>
AnyhowStep commentedon Jul 17, 2019
It seems like if I nest conditional types instead of introducing intersections, it works,
Playground
AnyhowStep commentedon Jul 27, 2019
This assignability situation is actually making life kind of hard ._.
Playground
In the above, you see something like
A
is not assignable toA
. And that just confuses me.My actual use-case has to do with having function properties (not methods) on a generic class.
And the function uses the
this
keyword.AnyhowStep commentedon Jul 27, 2019
Smaller repro,
Playground
AnyhowStep commentedon Jul 27, 2019
Playground
Even a type assertion does not work,
Playground
AnyhowStep commentedon Jul 27, 2019
So, if I put the generic type param on the
RHS
of theextends
, it is not assignable,Playground
If I put it on the
LHS
of theextends
, it is assignable,Playground
And I have zero intuition for why this is the case.
RyanCavanaugh commentedon Jul 30, 2019
@AnyhowStep TL;DR ?
AnyhowStep commentedon Jul 30, 2019
Umm... Kind of hard to make a TL;DR.
TL;DR
I have a generic function.
It has a parameter.
That parameter uses a conditional type and intersection type (at the same time).
Because of the conditional and intersection type, a different function with the exact same signature is treated as a different type... Even though they are the same type. A type assertion does not work, either.
I don't know if it is a bug, design limitation, or by design.
And I don't have an intuition for why it thinks it is assignable in some cases, but not assginable in other cases.
Playground
Playground showing type assertion not working
Notice that the type annotation and the type of the function have the same "type". But the compiler thinks they're different types.
But I have found that this is not always the case. In certain cases, the compiler knows they're the same type.
Like when I put
N
on theLHS
of theextends
.Playground
F
has one implicitly typed param #33042