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

Bad type checking enum based objects #48569

Closed
picasocro1 opened this issue Apr 5, 2022 · 6 comments
Closed

Bad type checking enum based objects #48569

picasocro1 opened this issue Apr 5, 2022 · 6 comments
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@picasocro1
Copy link

picasocro1 commented Apr 5, 2022

Bug Report

πŸ”Ž Search Terms

type checking enum based objects
type checking enum params

πŸ•— Version & Regression Information

Tested on versions (4.6.2, Nightly - v4.7.0-dev.20220405) on playground all failed.

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

enum Locale {
  en_US = 'en-US',
  de_DE = 'de-DE'
}

type AtLeastOne<Obj, Keys = keyof Obj> = Keys extends keyof Obj ? Pick<Obj, Keys> : never
export type NonEmpty<T> = Partial<T> & AtLeastOne<T>

type TranslationBase = {
  [locale in Locale]: string
}

type Translation = NonEmpty<TranslationBase>

// @ts-expect-error
const badTranslationBecauseItsEmpty: Translation = {}

const correctTranslationBecauseItHasAtLeastOneKey: Translation = {
  [Locale.en_US]: ''
}

const shouldCreateCorrectTranslationButHasUnexpectedError = (locale: Locale): Translation => ({
  [locale]: ''
})

πŸ™ Actual behavior

TS produces the error:

Type '{ [x: string]: string; }' is not assignable to type 'Translation'.
  Type '{ [x: string]: string; }' is not assignable to type 'Partial<TranslationBase> & Pick<TranslationBase, Locale.de_DE>'.
    Property '"de-DE"' is missing in type '{ [x: string]: string; }' but required in type 'Pick<TranslationBase, Locale.de_DE>'

πŸ™‚ Expected behavior

No errors should be produced. In function, we expect locale which is an enum (not string) and TS prevents us from using something different. Thus the returned object should be of an appropriate Translation type.

@MartinJohns
Copy link
Contributor

Simplified example:

type Translation = { [Locale.en_US]: string } | { [Locale.de_DE]: string }
declare const locale: Locale;

const translation: Translation = { [locale]: '' }

@jcalz
Copy link
Contributor

jcalz commented Apr 5, 2022

Duplicate of or strongly related to #13948

@RyanCavanaugh
Copy link
Member

The underlying problem is that we don't have any accurate + performant representation of the type of { [locale]: '' }

It's not Record<Locale, string> because that type implies that it has both properties, when it very obviously does not.

It's not Partial<Record<Locale, string>> because that type includes the empty type; this representation would yield the same error as the current implementation (just with a different message).

It's something like { Locale.en_US: string } | { Locale.de_DE: string } except that people can and do write code like { [locale1]: '', [locale2]: '', [locale3]: '', ... (ten more) } and we can't go around creating k ** N possible inhabitant types whenever we see a computed property name.

@RyanCavanaugh RyanCavanaugh added the Design Limitation Constraints of the existing architecture prevent this from being fixed label Apr 8, 2022
@edrennikov
Copy link

I'm not sure if it is related, but sounds so. This code:

enum E {A, B};
const x = {a: E.A};
const y: {a: E.A} = x;

generates an error:

Type '{ a: E; }' is not assignable to type '{ a: E.A; }'.
  Types of property 'a' are incompatible.
    Type 'E' is not assignable to type 'E.A'.(2322)

which sounds strange, because x obviously has correct type and can be assigned to y.
But if written this way:

enum E {A, B};
const x = {a: E.A as E.A};
const y: {a: E.A} = x;

then there is no error.

@RyanCavanaugh
Copy link
Member

@edrennikov different issue; the problem is that it's legal for you to write

enum E {A, B};
const x = {a: E.A};
x.a = E.B; // <- anywhere in your program
const y: {a: E.A} = x; // <- now unsound

You can also use const x = { a: E.A} as const; to prevent that from happening

@edrennikov
Copy link

Thank you, now I understand it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

5 participants