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

Array with option allof in definition to tell TS that all options must be implemented from that array #56273

Open
5 tasks done
schettgen opened this issue Oct 31, 2023 · 4 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@schettgen
Copy link

🔍 Search Terms

Typescript Define Array type with variations
typescript array mandatory
typescript array with predefined options

✅ Viability Checklist

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals

⭐ Suggestion

Typescript should be extended by a list of available options which a type must implement mandatory

type LanguageLocale = "en-gb" | "de" | "fr" | "nl-nl" | "nb-NO"

[
  { id: 'en-gb', name: 'EN' },
  { id: 'de', name: 'DE' },
  { id: 'fr', name: 'FR' },
  { id: 'nl-nl', name: 'NL' },
] satisfies {id: allof LanguageLocale, name: string}[]

here in my suggestion the keyword allof would describe that this array need to implement all options of LanguageLocale.
allof should be only usable together with [] or in case definition is split it should be ignored (e.g. LanguageEntry is used directly somewhere)

type LanguageLocale = "en-gb" | "de" | "fr" | "nl-nl" | "nb-NO"
type LanguageEntry = {id: allO fLanaugeLocale, name: string}
type LanguageEntries = LanguageEntry[]

It should work as well for type Array<T>

📃 Motivating Example

The motivation is to find places in the code where options are missed but mandatory
Imaging a Language Picker and you added a new language.
In the code the following was present.

It have had following data:

type LanguageLocale = "en-gb" | "de" | "fr" | "nl-nl" 
[
  { id: 'en-gb', name: 'EN' },
  { id: 'de', name: 'DE' },
  { id: 'fr', name: 'FR' },
  { id: 'nl-nl', name: 'NL' },
] satisfies {id: LanguageLocale, name: string}[]

so after adding "nb-NO"

type LanguageLocale = "en-gb" | "de" | "fr" | "nl-nl"|"nb-NO"
[
  { id: 'en-gb', name: 'EN' },
  { id: 'de', name: 'DE' },
  { id: 'fr', name: 'FR' },
  { id: 'nl-nl', name: 'NL' },
] satisfies {id: LanguageLocale, name: string}[]

type is still valid and for now there is now simple solution to make nb-NO mandatory to implement

💻 Use Cases

It would it make easy to turn runtime errors ins transpile time errors by TS since this could be found before commiting.

There is a workaround but you need to repeat yourfself and have mulitple definitions. So in the end it's just moving the error away from the place in code to the type definition

type LanguageLocale = "en-gb" | "de" | "fr" | "nl-nl" | "nb-NO";
 
interface LanguageOption {
  id: LanguageLocale;
  name: string;
}
 
const allLanguageLocales: { [K in LanguageLocale]: null } = {
  "en-gb": null,
  "de": null,
  "fr": null,
  "nl-nl": null,
  "nb-NO": null,
};
 
type CheckLanguages<T extends LanguageOption[]> = T extends (LanguageOption & { id: keyof typeof allLanguageLocales })[] ? T : never;
 
const languages = [
  { id: 'en-gb', name: i18n('EN') },
  { id: 'de', name: i18n('DE') },
  { id: 'fr', name: i18n('FR') },
  { id: 'nl-nl', name: i18n('NL') },
  // { id: 'nb-NO', name: i18n('NO') }, // Uncomment this line to fix the error
] as const;
 
type CheckedLanguages = CheckLanguages<typeof languages>;
@nmain
Copy link

nmain commented Oct 31, 2023

Typescript provides enough infrastructure for you to build this yourself, if you want it:

import { AssertTrue, IsExact } from 'conditional-type-checks';

type LanguageLocale = "en-gb" | "de" | "fr" | "nl-nl" | "nb-NO"

const foo = [
  { id: 'en-gb', name: 'EN' },
  { id: 'de', name: 'DE' },
  { id: 'fr', name: 'FR' },
  { id: 'nl-nl', name: 'NL' },
]  as const;

type ActualLanguageLocale = (typeof foo)[number]["id"];
//   ^?

type _ = AssertTrue<IsExact<LanguageLocale, ActualLanguageLocale>>;

Playground

Perhaps a bit long-winded, but you can easily tweak the implementation as needed.

@RyanCavanaugh
Copy link
Member

Previously discussed at #53171

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature labels Oct 31, 2023
@fatcerberus
Copy link

fatcerberus commented Nov 9, 2023

This seems kind of tricky w.r.t. the placement of the allof quantifier. { foo: allof U }[] describes a relationship between U and the containing array, but syntactically the quantifier is applied directly to U, which is weird. It also becomes ambiguous in case of nested arrays—which set of [] does it modify?

There’s also the fact that, today, T[] is a synonym for Array<T>, which exists in userland, but this doesn’t generalize to arbitrary generic types… it feels odd that you’d be able to write Array<allof U> but not Box<allof U> (since that doesn’t make sense).

@mon-jai
Copy link

mon-jai commented Aug 12, 2024

Also discussed at: #13298

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

5 participants