practical-fp / union-types

A Typescript library for creating discriminating union types.
MIT License
70 stars 2 forks source link

Provide typed tag in match function #6

Closed lucasavila00 closed 1 year ago

lucasavila00 commented 1 year ago

I stumbled across this situation, which union-types kinda couldn't handle:

type LabelConfig =
    | Variant<"TextAndIcon", string>
    | Variant<"JustIcon", string>
    | Variant<"JustText", { text: string; icon: string }>;

type IconVariant = "TextAndIcon" | "JustIcon";
const renderIcon = (icon: string, placement: IconVariant) => {
    return "...";
};

const current1 = (cfg: LabelConfig) => {
    return matchExhaustive(cfg, {
        JustText: () => <></>,
        JustIcon: icon => renderIcon(icon, "TextAndIcon"), // <--- typo here, compiles but it is wrong
        TextAndIcon: icon => renderIcon(icon, "JustIcon"), // <--- typo here, compiles but it is wrong
    });
};
const curent2 = (cfg: LabelConfig) => {
    return matchExhaustive(cfg, {
        JustText: () => <></>,
        JustIcon: icon => renderIcon(icon, cfg.tag), // <--- typescript error, does not compile
        TextAndIcon: icon => renderIcon(icon, cfg.tag), // <--- typescript error, does not compile
    });
};

const proposed = (cfg: LabelConfig) => {
    return matchExhaustive(cfg, {
        JustText: () => <></>,
       // notice the new tag parameter
        JustIcon: (icon, tag) => renderIcon(icon, tag),
       // notice the new tag parameter
        TextAndIcon: (icon, tag) => renderIcon(icon, tag),
    });
};

const proposed2 = (cfg: LabelConfig) => {
    return matchExhaustive(cfg, {
        JustText: () => <></>,
        JustIcon: renderIcon,
        TextAndIcon: renderIcon
    });
};

Is the proposed addition ok? I've been using it on my fork and could upstream the change if you like it.

felixschorer commented 1 year ago

Instead of augmenting match I would change the signature of the renderIcon function.

type LabelConfig =
    | Variant<"TextAndIcon", string>
    | Variant<"JustIcon", string>
    | Variant<"JustText", { text: string; icon: string }>;

type IconVariant = Narrow<LabelConfig, "TextAndIcon" | "JustIcon">;

const renderIcon = (icon: IconVariant) => {
    return "...";
};

const current = (cfg: LabelConfig) => {
    if (JustText.is(cfg)) return <></>;
    return renderIcon(cfg);
};