Skip to content

Type simultaneously does and does not have property #30657

Closed
@jwalton

Description

@jwalton

TypeScript Version: 3.4.1 and 3.2.2

Code

interface TextChannel {
    id: string;
    type: 'text';
    phoneNumber: string;
}

interface EmailChannel {
    id: string;
    type: 'email';
    addres: string;
}

type Channel = TextChannel | EmailChannel;

export type ChannelType = Channel extends { type: infer R } ? R : never; // 'text' | 'email'

// Stolen from lodash.
type Omit<T, K extends keyof T> = Pick<
    T,
    ({ [P in keyof T]: P } & { [P in K]: never } & { [x: string]: never })[keyof T]
>;

type ChannelOfType<T extends ChannelType, A = Channel> = A extends { type: T }
    ? A
    : never;

/**
 * A NewChannel is a Channel with a 'type', a 'localChannelId', with no id,
 * and every other field is optional.
 */
export type NewChannel<T extends Channel> = Pick<T, 'type'> &
    Partial<Omit<T, 'type' | 'id'>> & { localChannelId: string };

/**
 * Create a NewChannel of the specified type, ready to hand off to, for example,
 * a react form which can fill it in with values and turn it into a real Channel.
 */
export function makeNewChannel<T extends ChannelType>(type: T): NewChannel<ChannelOfType<T>> {
    const localChannelId = `blahblahblah`;
    return { type, localChannelId };
}

// Here's the exciting bit:

const newTextChannel = makeNewChannel('text');
// Property 'phoneNumber' does not exist on type 'NewChannel<TextChannel>'. ts(2339)
newTextChannel.phoneNumber = '613-555-1234';

const newTextChannel2 : NewChannel<TextChannel> = makeNewChannel('text');
// But this works!
newTextChannel2.phoneNumber = '613-555-1234';

Expected behavior:

Since newTextChannel and newTextChannel2 are both of type NewChannel<TextChannel>, I'd expect them both to have a phoneNumber property (or both to not have a phoneNumber property).

Actual behavior:

newTextChannel does not have this property, and newTextChannel2 does, even though these two seem to be of the same type!

Playground Link: link

Related Issues: No

Metadata

Metadata

Assignees

Labels

BugA bug in TypeScript

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions