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

(Partial<Type>)[keyof Type] is udefined in certain settings and assignment is not possible #45003

Closed
mckravchyk opened this issue Jul 13, 2021 · 9 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@mckravchyk
Copy link

mckravchyk commented Jul 13, 2021

Bug Report

🔎 Search Terms

Partial keyof undefined

🕗 Version & Regression Information

I'm using 4.2.4 in production and it occurs. I have tested with 4.4 beta on the playground and it also occurs. I have no knowledge if the bug occurred in prior versions.

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about it.

⏯ Playground Link

Playground link with relevant code

💻 Code

type Data1 = {
  test1: string
  test2: string
}

type Data2 = {
  test1: string
  test2: boolean
}

let data1: Partial<Data1> = { };
let data2: Partial<Data2> = { };

// metadata.test = 'test';
let key = 'test1';
let value = 'test';

// Ok
data1[key as keyof Data1] = value;

// Error: Type 'string' is not assignable to type 'undefined'
// It seems like the bug occurs only if the object type has values of
// different types
data2[key as keyof Data2] = value;

// The real case scenario is I want to iterate some properties and
// assign them to the partial, so I expect that the partial has no
// initial properties defined at all and can be assigned any property of
// partialed type.

type TableData =  {
    id: string
    title: string
    icon: string
}

type Metadata = {
    url: string
    active: boolean
}

type CreateProps = Omit<TableData, 'id'> & Partial<Metadata>;

function create(props: CreateProps): void {
  const id = 'test';

  const tableData: TableData = {
    id,
    title: props.title,
    icon: props.icon
  }
  
  const metadata: Partial<Metadata> = { };

  for (const key of Object.keys(props)) {
      // Build metadata from those props which are not tableData
      if (typeof tableData[key as keyof TableData] === 'undefined') {
          // Error:
          // Type 'string | boolean' is not assignable to type 'undefined' 
          metadata[key as keyof Metadata] = props[key as keyof CreateProps]!;
      }
    }
  }

🙁 Actual behavior

data2[key as keyof Data2] is undefined and it's not possible to assign anything to it.

🙂 Expected behavior

data2[key as keyof Data2] should not be undefined. At least it should be undefined | string | boolean. It should also be possible to assign a value to it, despite being possible undefined.

@mckravchyk mckravchyk changed the title (Partial<Type>)[keyof Type] is udefined in certain settings (Partial<Type>)[keyof Type] is udefined in certain settings and assignment is not possible Jul 13, 2021
@MartinJohns
Copy link
Contributor

Related: #32693

@RyanCavanaugh
Copy link
Member

data2[key as keyof Data2] is undefined and it's not possible to assign anything to it.

This is the correct behavior. data2 has two properties, which have the legal values string | undefined and boolean | undefined. If you're assigning into an arbitrary property of data2, the only value which is legal for both possible target properties is undefined.

  metadata[key as keyof Metadata] = props[key as keyof CreateProps]!;

The same logic applies here.

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Jul 13, 2021
@fatcerberus
Copy link

See also #30769. In particular:

When an indexed access T[K] occurs on the source side of a type relationship, it resolves to a union type of the properties selected by T[K], but when it occurs on the target side of a type relationship, it now resolves to an intersection type of the properties selected by T[K]. Previously, the target side would resolve to a union type as well, which is unsound.

Emphasis mine. (string | undefined) & (boolean | undefined) is undefined.

@mckravchyk
Copy link
Author

Thanks. Is there any workaround to allow the assignment besides using ts-ignore?

@RyanCavanaugh
Copy link
Member

I would a type assertion corresponding to why the code isn't actually wrong, e.g. data2[key as 'test1'] = value

@mckravchyk
Copy link
Author

mckravchyk commented Jul 15, 2021

I'm sorry, to be more precise I'm looking for a workaround to the more complex example.

I understand that the compiler is right, but I'm not able to code something which is pretty standard without using ts-ignore on the error line.

Playground

type TableData =  {
    id: string
    title: string
    icon: string
}

type Metadata = {
    url: string
    active: boolean
}

type CreateProps = Omit<TableData, 'id'> & Partial<Metadata>;

function create(props: CreateProps): void {
  const id = 'test';

  const tableData: TableData = {
    id,
    title: props.title,
    icon: props.icon
  }
  
  const metadata: Partial<Metadata> = { };

  for (const key of Object.keys(props)) {
      // Build metadata from those props which are not tableData
      if (typeof tableData[key as keyof TableData] === 'undefined') {
          // Error:
          // Type 'string | boolean' is not assignable to type 'undefined' 
          metadata[key as keyof Metadata] = props[key as keyof CreateProps]!;
      }
    }
  }

@mckravchyk
Copy link
Author

After some more thinking, using ts-ignore is probably the most elegant option if the actual solution would require writing some additional generic types. Unless there is something simple.

@typescript-bot
Copy link
Collaborator

This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@mckravchyk
Copy link
Author

mckravchyk commented Mar 22, 2023

Just coming back to it after some time, instead of using @ts-ignore, I would do this today:

metadata[key as keyof Metadata] = props[key as keyof CreateProps] as unknown as undefined;

Not pretty, but come to think of it the key type is any arbitrary key and there's no relation between them so casting has to be used anyway, there's probably a more correct way to do it but it would be way too much effort for an internal implementation of one function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

5 participants