Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix contradictory typing of IFrameMessageResponse and refactor into modular types (#207) #215

Merged
merged 1 commit into from
Dec 4, 2023

Conversation

legobeat
Copy link
Contributor

@legobeat legobeat commented Dec 3, 2023

@legobeat legobeat requested a review from a team as a code owner December 3, 2023 01:23
@legobeat legobeat marked this pull request as draft December 3, 2023 01:23
@legobeat legobeat mentioned this pull request Dec 3, 2023
… modular types (MetaMask#207)

## Motivation

`IFrameMessageResponse` is currently defined with a generic parameter `TAction`. This parameter doesn't usefully narrow or widen the type. 
- The `action: TAction` property appears to *widen* `IFrameMessageResponse` to accept any action type, but `TAction` is also constrained as a subtype of `IFrameMessageActions`.
- The `TAction` generic param appears to *narrow* `IFrameMessageResponse` to select individual union members, but it interferes with the inference-based type narrowing we can get if `IFrameMessageResponse` is an exhaustively enumerated discriminated union. 

This causes two problems:

1. Type errors on assignment operations that should be valid.
- e.g. [`as` type casting](https://github.com/MetaMask/eth-ledger-bridge-keyring/pull/207/files#diff-c901ebd8641651c9156a86894574a6d9567f2ae79495de0edff2cbef8ae958f0L302-L304) is necessary here because supertype is being assigned to a subtype i.e. `(response: IFrameMessageResponse<TAction>) => void` to `(response: IFrameMessageResponse<IFrameMessageActions>) => void`.
  - In general, `(x: SuperType) => T` is a *subtype* of `(x: SubType) => T`.
- **This error indicates an underlying issue with the typing and shouldn't be suppressed.**
- Removing `TAction` puts `(response: SomeIFrameMessageResponse) => void` on the LHS (assignee, supertype) and `(response: IFrameMessageResponse) => void` on the RHS (assigned, subtype), resolving this logical error.

2. `TAction` is also silencing type errors we should be getting from type narrowing based on `action` value. 
- e.g. Some union members of `IFrameMessageResponse` do not include a `success`, `error`, `payload`, or `payload.error` property, but because of `TAction`, TypeScript doesn't alert us that we should be performing `in` checks on them in addition to null checks. 
- This can cause unexpected failure at runtime, especially from the destructuring assignments that are being used.

Constraining `IFrameMessageResponse['action']` to `IFrameMessageActions` both resolves these issues and guides us towards writing type-safe logic about these actions that conform to their specific type signatures. It appears that this was the original intention of writing `IFrameMessageActions` as a discriminated union instead of a wider type encompassing all possible actions.

## Changes

- **BREAKING** Narrows `IFrameMessageResponse` type definition

## Explanation

- To resolve these issues, This PR makes `IFrameMessageResponse` non-generic and removes `as` casting.
- For improved readability and maintainability, `IFrameMessageResponse` is also refactored into a union of named types.
- A `IFrameMessageResponseBase` type is defined for modularity and better visibility of `IFrameMessageResponse` types with atypical shapes.

## References

- Closes MetaMask#208
- More info on function subtyping: MetaMask/core#1890 (comment)
@legobeat legobeat marked this pull request as ready for review December 4, 2023 08:19
@legobeat legobeat requested review from MajorLift, mikesposito and a team December 4, 2023 08:19
Copy link
Member

@mikesposito mikesposito left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

@legobeat legobeat merged commit 23a3951 into MetaMask:main Dec 4, 2023
17 checks passed
@legobeat legobeat deleted the reapply-207 branch December 4, 2023 09:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants