Better type error message for `<MatchTag>` with missing cases
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 48/100
- Issue type
- Feature
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- typescript
- Domain
- frontend
Research direction
Start by locating the MatchTag declaration and reviewing the linked TypeScript Playground, then compare the current overload diagnostics with the proposed conditional-type examples. Done means missing cases produce an understandable error, while the normal, explicit-generic, partial, and default-tag examples retain their intended type behavior.
Written by the indexing model from the issue text.
Description
Describe The Problem To Be Solved
Currently MatchTag uses overloads to determine user's intent.
While it is straightforward when reading the declaration, when there are missing cases, it generates an error message that is hard to understand:
type MyUnion =
| {
kind: "foo";
foo: "foo-value";
}
| {
kind: "bar";
bar: "bar-value";
};
<MatchTag
on={value()}
// No overload matches this call.
// The last overload gave the following error.
// Type 'MyUnion' is not assignable to type '{ type: PropertyKey; } | null | undefined'.
// Property 'type' is missing in type '{ kind: "foo"; foo: "foo-value"; }' but required in type '{ type: PropertyKey; }'.
tag="kind"
case={{
}}
/>;
As you can see, the error message only shows type is missing from your on value, which is the result of evaluating the last overload. (to be honest, because of this I thought the author did not consider tag's value in the type declaration at first glance.)
This is quite confusing, isn't it?
Suggest A Solution
I suggest unifying the overloads using conditional types.
My sketch (simple test cases included):
Playground
Code block (long)
import type { Accessor, JSX } from 'solid-js'
type Cases<IsPartial extends boolean, T> =
boolean extends IsPartial
? { 'no `boolean` allowed, please use either `true` or `false`': never }
: true extends IsPartial
? Partial<T>
: Required<T>
type NotProvidedOrTyped<K extends string, V, Default> =
| (V extends Default ? Partial<Record<K, never>> : never)
| Required<Record<K, V>>
type Tag = string | number
type TagKeyDefault = "type"
type PartialDefault = false
declare function MatchTag<
T extends { [k in TagKey]: Tag },
TagKey extends string = TagKeyDefault,
IsPartial extends boolean = PartialDefault,
>(
props:
& {
on: T | null | undefined;
case: Cases<IsPartial, { [Tag in T[TagKey]]: (v: Accessor<Extract<T, Record<TagKey, Tag>>>) => JSX.Element }>;
fallback?: JSX.Element;
}
& NotProvidedOrTyped<"tag", TagKey, TagKeyDefault>
& NotProvidedOrTyped<"partial", IsPartial, PartialDefault>
): JSX.Element;
type MyUnion =
| {
kind: "foo";
foo: "foo-value";
}
| {
kind: "bar";
bar: "bar-value";
};
declare const value: () => MyUnion
// normal case
<MatchTag
on={value()}
tag="kind"
case={{
foo: props => <>{props().foo}</>,
bar: props => <>{props().bar}</>,
}}
/>;
// edge cases
<MatchTag
on={value()}
tag="kind"
case={{}}
// much more clear error message:
// Type '{}' is missing the following properties from
// type 'Required<{
// foo: (v: Accessor<{ kind: "foo"; foo: "foo-value"; }>) => Element;
// bar: (v: Accessor<{ kind: "bar"; bar: "bar-value"; }>) => Element;
// }>': foo, bar
/>;
<MatchTag<MyUnion, "kind">
// properly handles "explicit generic argument without providing optional arguments" hazard (see https://github.com/microsoft/TypeScript/issues/58977)
on={value()}
// `tag` prop is not actually provided
case={{
foo: () => <></>,
bar: () => <></>,
}}
/>;
<MatchTag<MyUnion, "kind", true>
// handles `partial` too!
on={value()}
tag="kind"
case={{}}
// `partial` prop is not actually provided
/>;
<MatchTag<MyUnion, "kind", boolean>
on={value()}
tag="kind"
case={{}} // bans ambiguous `partial: boolean`
/>;
type MyUnionWithTypeTag =
| {
type: "foo";
foo: "foo-value";
}
| {
type: "bar";
bar: "bar-value";
};
declare const valueTypeTag: () => MyUnionWithTypeTag
// normal case
<MatchTag
on={valueTypeTag()}
case={{
foo: props => <>{props().foo}</>,
bar: props => <>{props().bar}</>,
}}
/>;
// edge cases
<MatchTag
on={valueTypeTag()}
case={{}}
/>;
I tried to write the code easy to read, but I understand it still is hard to read.
Feedbacks appreciated!
- Dominant language
- TypeScript
- Stars
- 1.6k
- Forks
- 162
- Avg merge
- 19h 40m
- Merged PRs (30d)
- 8
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from solidjs-community/solid-primitives
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
-
bug
Difficulty 3/5 1-2 days Newbie friendliness 68/100
-
bug
Difficulty 3/5 1-2 days Newbie friendliness 55/100
solidjs-community/solid-primitives#1000 · 3 comments · 5 reactions ·
-
enhancement
Difficulty 4/5 3-5 days Newbie friendliness 48/100
-
help wanted
Difficulty 2/5 1-3 hours Newbie friendliness 52/100
solidjs-community/solid-primitives#830 · 2 comments ·
All issues in solidjs-community/solid-primitives
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
copse-dev/agent-pane#2953 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Eynzof/Hermes-CN-Desktop#610 ·
-
bug clawsweeper:linked-pr-open clawsweeper:needs-live-repro clawsweeper:no-new-fix-pr impact:message-loss issue-rating: 🐚 platinum hermit P2 regression
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
calcite-components needs triage refactor
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Esri/calcite-design-system#15203 ·