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

Two level deep indexed access: read works, write fails #47352

Closed
quixot1c opened this issue Jan 8, 2022 · 6 comments
Closed

Two level deep indexed access: read works, write fails #47352

quixot1c opened this issue Jan 8, 2022 · 6 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@quixot1c
Copy link

quixot1c commented Jan 8, 2022

Bug Report

Reading from a two level deep Indexed Access type works, but writing gives "Type cannot be used to index".

🔎 Search Terms

  • Indexed Access
  • Mapped Type

🕗 Version & Regression Information

  • This changed between versions 3.3.3 and 3.5.1

⏯ Playground Link

Playground link with relevant code

💻 Code

enum A {
    First = "1",
    Second = "2"
}

type Values = { [a in A]: { [b: string]: string }}
const values: Values = {} as any;

function test<T extends A>(a: T, b: string) {
    const valueA = values[a];
    values[a] = valueA;

    const valueB = values[a][b]; // indexing Values[T] with string works
    values[a][b] = valueB; // => Type 'string' cannot be used to index type 'Values[T]'    
}

🙁 Actual behavior

Indexing a type while reading works, while writing fails with the same indexer.

🙂 Expected behavior

Indexed reads and writes both work when using the same indexer.

@jcalz
Copy link
Contributor

jcalz commented Jan 9, 2022

I don't see what this has to do with TS4.6 or the linked PR. Your code example has the same behavior in TS4.5 and indeed all the way back to TS 3.5. I assume it's a consequence of #30769.

@quixot1c
Copy link
Author

quixot1c commented Jan 9, 2022

@jcalz seems you are right. I previously tested with a different return type for each of the Values with

interface Lookup {
  [A.First]: number
  [A.Second]: string
}

type Values = { [a in A]: { [b: string]: Lookup[a] }}

This is where 4.6 did come in to play. Simplifying the problem further I must have forgotten to check if it persisted in older versions. Have updated the issue.

@quixot1c quixot1c changed the title TS4.6 Two level deep indexed access of Mapped Type: read works, write fails Two level deep indexed access of Mapped Type: read works, write fails Jan 9, 2022
@quixot1c quixot1c changed the title Two level deep indexed access of Mapped Type: read works, write fails Two level deep indexed access: read works, write fails Jan 9, 2022
@quixot1c
Copy link
Author

quixot1c commented Jan 9, 2022

As @jcalz pointed out it does seem related to #30769. In particular the following:

  • Given a type variable T with a constraint C, when an indexed access T[K] occurs on the target side of a type relationship, index signatures in C are now ignored. This is because a type argument for T isn't actually required to have an index signature, it is just required to have properties with matching types.
function f3<T extends { [key: string]: any }>(obj: T) {
    let foo = obj['foo'];
    let bar = obj['bar'];
    obj['foo'] = 123;  // Error
    obj['bar'] = 'x';  // Error
}

If in this case values[a] is typed as Values[T], does that mean that it in effect becomes the T with constraint C that is referred to in above quote from the PR, meaning its index signatures are ignored?

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Jan 11, 2022
@RyanCavanaugh
Copy link
Member

On read you're allowed to fall back to the constraint type, but as noted above this isn't allowed during a write

@quixot1c
Copy link
Author

quixot1c commented Jan 11, 2022

@RyanCavanaugh I can't say I understand why, that may be way above my pay grade :) But it seems to me

const valueB = values[a][b];
values[a][b] = valueB;

should never fail given no changes in a and b.

@RyanCavanaugh
Copy link
Member

To correctly analyze that, the type of valueB would somehow have to encode that it came from the specific indices given by a and b. The type system is generally designed to track sets of values (types), not specific values or variables.

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

3 participants