[labs/task] Fix Task.render()'s return type always being undefined#4070
[labs/task] Fix Task.render()'s return type always being undefined#4070justinfagnani merged 3 commits intolit:mainfrom
Conversation
In TypeScript:
- `unknown` is the top type: everything can be assigned to `unknown`,
and `unknown` can be only assigned to `unknown`.
- `never` is the bottom type: `never` can be assigned to everything, and
only `never` can be assigned to `never`.
- Assuming a `Cat` can be assigned to an `Animal`, but not the other way
around:
- (read-only) arrays are covariant: a `Cat[]` can be assigned to an
`Animal[]`, but not the other way around. In other words, arrays
don't change the "order" of types, as expected.
- function arguments are contravariant: a `(x: Animal) => boolean` can
be assigned to a `(x: Cat) => boolean`, but not the other way
around. In other words, function arguments flip the "order" of
types.
The right-hand of the `extends` in `MaybeReturnType` is a
`(...args: unknown[]) => ?`. If `(...args: T[]) => ?` is assignable to
that, then `unknown` is assignable to `T`… but `unknown` can only be
assigned to `unknown`, so `T` must be `unknown`. No functions can be
assigned to a type of `(...args: unknown[]) => ?` unless it is also that
type.
Therefore, the `extends` clause in `MaybeReturnType` was always failing
for all functions, apart from the ones which had an arguments array of
`unknown[]`. This caused `render`'s return type to only include
`undefined`.
The function type in `MaybeReturnType` should be a function type which
can have _any_ function arguments, so we should use
`(...args: never[]) => ?` instead - as `never` can be assigned to
everything, so everything can be assigned to `(...args: never[]) => ?`.
🦋 Changeset detectedLatest commit: 8fbe5f2 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
📊 Tachometer Benchmark ResultsSummarynop-update
render
update
update-reflect
Resultsthis-change
render
update
update-reflect
this-change, tip-of-tree, previous-release
render
update
nop-update
this-change, tip-of-tree, previous-release
render
update
this-change, tip-of-tree, previous-release
render
update
update-reflect
|
|
@rictic Does this need a changeset? |
|
Yes, run |
|
Done (as a new commit). I assume this PR will be squashed when merged - let me know if you'd like me to squash it on my branch. |
|
Yep, will squash and merge! |
justinfagnani
left a comment
There was a problem hiding this comment.
We need to update the test associated with this too: https://github.com/lit/lit/blob/main/packages/labs/task/src/test/task_test.ts#L1029
The current test is intended to show that render() does return a type based on the callback return types - which it does now because the callbacks have no arguments and so can be assigned to (...args: unknown[]) => ?.
I think the only change needed to show the typing failing is to add an argument to the complete callback in the last assertion:
accept<number>(
el.task.render({
initial: () => 123,
- complete: () => 123,
+ complete: (x: string) => Number(x),
pending: () => 123,
error: () => 123,
})
);|
Done. A new type-only test was added to test for this, which fails if the change is reverted. |
|
Thanks a lot @mlcui-google! |
In TypeScript:
unknownis the top type: everything can be assigned tounknown, andunknowncan be only assigned tounknown.neveris the bottom type:nevercan be assigned to everything, and onlynevercan be assigned tonever.Catcan be assigned to anAnimal, but not the other way around:Cat[]can be assigned to anAnimal[], but not the other way around. In other words, arrays don't change the "order" of types, as expected.(x: Animal) => booleancan be assigned to a(x: Cat) => boolean, but not the other way around. In other words, function arguments flip the "order" of types.The right-hand of the
extendsinMaybeReturnTypeis a(...args: unknown[]) => ?. If(...args: T[]) => ?is assignable to that, thenunknownis assignable toT… butunknowncan only be assigned tounknown, soTmust beunknown. No functions can be assigned to a type of(...args: unknown[]) => ?unless it is also that type.Therefore, the
extendsclause inMaybeReturnTypewas always failing for all functions, apart from the ones which had an arguments array ofunknown[]. This causedrender's return type to only includeundefined.The function type in
MaybeReturnTypeshould be a function type which can have any function arguments, so we should use(...args: never[]) => ?instead - asnevercan be assigned to everything, so everything can be assigned to(...args: never[]) => ?.