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 Event.emit type error for enums #128

Merged
merged 1 commit into from
Sep 6, 2024

Conversation

bgschiller
Copy link
Contributor

@bgschiller bgschiller commented Sep 5, 2024

Rust enums are represented in TS as a type union. For some reason, T extends null collapses the type into an intersection, which makes it a type error to call SomeEvent.emit(x) for any value x. That may be a TypeScript error—I'm still investigating. In the meantime, this patch avoids the issue.

Here's a minimal example to try in the typescript playground. The definition of __EventObj__ is as it is before my patch.

// These definitions come from @tauri-apps/api/event
interface Event<T> {
    /** Event name */
    event: EventName;
    /** Event identifier used to unlisten */
    id: number;
    /** Event payload */
    payload: T;
}
declare function emit(event: string, payload?: unknown): Promise<void>;
type EventCallback<T> = (event: Event<T>) => void;
type EventName = string & Record<never, never>

// This is generated by tauri-specta based on my rust types
export type SocketAction =
  | { MessageForClient: { conn_id: string; message: string } }
  | { CloseSocket: { conn_id: string } }

// This is also from tauri-specta, but doesn't depend on my types
type __EventObj__<T> = {
  emit: T extends null
    ? (payload?: T) => ReturnType<typeof emit>
    : (payload: T) => ReturnType<typeof emit>
}

// pretend we have one of these
declare const eo: __EventObj__<SocketAction>;

const x = { MessageForClient: { conn_id: 'string', message: 'str' }} satisfies SocketAction

eo.emit(x) // 💥 Type error

Rust enums are represented in TS as a type union. For some reason, `T extends null` collapses the type into an intersection, which makes it a type error to call `SomeEvent.emit(x)` for any value x.

Here's a minimal example to try in the typescript playground. The definition of __EventObj__ is as it is *before* my patch.

```ts
// These definitions come from @tauri-apps/api/event
interface Event<T> {
    /** Event name */
    event: EventName;
    /** Event identifier used to unlisten */
    id: number;
    /** Event payload */
    payload: T;
}
declare function emit(event: string, payload?: unknown): Promise<void>;
type EventCallback<T> = (event: Event<T>) => void;
type EventName = string & Record<never, never>

// This is generated by tauri-specta based on my rust types
export type SocketAction =
  | { MessageForClient: { conn_id: string; message: string } }
  | { CloseSocket: { conn_id: string } }

// This is also from tauri-specta, but doesn't depend on my types
type __EventObj__<T> = {
  emit: T extends null
    ? (payload?: T) => ReturnType<typeof emit>
    : (payload: T) => ReturnType<typeof emit>
}

// pretend we have one of these
declare const eo: __EventObj__<SocketAction>;

const x = { MessageForClient: { conn_id: 'string', message: 'str' }} satisfies SocketAction

eo.emit(x) // 💥 Type error
```
@oscartbeaumont
Copy link
Member

For anyone coming across this I think this comment is a solid explanation for why this change makes sense.

Personally was not aware of it until now.

@oscartbeaumont oscartbeaumont merged commit ea8b314 into specta-rs:main Sep 6, 2024
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.

2 participants