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

Merged
merged 13 commits into from
Nov 7, 2023

Conversation

MajorLift
Copy link
Contributor

@MajorLift MajorLift commented Oct 26, 2023

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 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.
  1. 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

@MajorLift MajorLift requested a review from a team as a code owner October 26, 2023 20:00
@MajorLift MajorLift self-assigned this Oct 26, 2023
@MajorLift MajorLift force-pushed the 231026-typing-fix-iframemessageresponse branch from 7e8760d to d868b03 Compare October 26, 2023 20:05
@MajorLift MajorLift changed the title Make IFrameMessageResponse non-generic Fix contradictory typing for IFrameMessageResponse and refactor for modularity, maintainability Oct 26, 2023
@MajorLift MajorLift changed the title Fix contradictory typing for IFrameMessageResponse and refactor for modularity, maintainability Fix contradictory typing of IFrameMessageResponse and refactor for modularity, maintainability Oct 26, 2023
@MajorLift MajorLift changed the title Fix contradictory typing of IFrameMessageResponse and refactor for modularity, maintainability Fix contradictory typing of IFrameMessageResponse and refactor into modular types Oct 26, 2023
@MajorLift MajorLift force-pushed the 231026-typing-fix-iframemessageresponse branch from 65ba771 to c2a051f Compare October 27, 2023 04:19
Copy link

@mcmire mcmire left a comment

Choose a reason for hiding this comment

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

Overall these changes look great and make sense! I just had some suggestions below.

src/ledger-iframe-bridge.ts Show resolved Hide resolved
src/ledger-iframe-bridge.ts Outdated Show resolved Hide resolved
src/ledger-iframe-bridge.ts Outdated Show resolved Hide resolved
src/ledger-iframe-bridge.ts Show resolved Hide resolved
@MajorLift MajorLift force-pushed the 231026-typing-fix-iframemessageresponse branch from 01f704a to 9307a30 Compare November 7, 2023 16:28
Copy link

@mcmire mcmire 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!

@MajorLift MajorLift merged commit 2f0da8f into main Nov 7, 2023
17 checks passed
@MajorLift MajorLift deleted the 231026-typing-fix-iframemessageresponse branch November 7, 2023 21:11
legobeat added a commit to legobeat/eth-ledger-bridge-keyring that referenced this pull request Dec 2, 2023
@legobeat legobeat mentioned this pull request Dec 2, 2023
legobeat pushed a commit to legobeat/eth-ledger-bridge-keyring that referenced 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 added a commit that referenced this pull request Dec 3, 2023
legobeat pushed a commit to legobeat/eth-ledger-bridge-keyring that referenced this pull request Dec 4, 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 added a commit that referenced this pull request Dec 4, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Fix typing of IFrameMessageResponse and refactor into modular types
2 participants