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

"Index signature is missing", but only when the type is inferred #24748

Closed
jamesknelson opened this issue Jun 7, 2018 · 4 comments
Closed

"Index signature is missing", but only when the type is inferred #24748

jamesknelson opened this issue Jun 7, 2018 · 4 comments
Labels
Duplicate An existing issue was already created

Comments

@jamesknelson
Copy link

TypeScript Version: 3.0.0-dev.20180607

Search Terms:
Index signature missing inferred mapped types

Code

declare function map<FromValue>(
  from: Publisher<FromValue>,
  to: (props: FromValue) => any
);

declare function combine<CombinedValue>(children: {
  [K in keyof CombinedValue]: Publisher<CombinedValue[K]>
}): Publisher<CombinedValue>;


type Publisher<Value> = { value: Value; }
type Box<Value> = { data: Value }
type X = { a: string }
type Y = { b: number }

// ---

let a = combine({
  x: undefined as Publisher<Box<X>>,
  y: undefined as Publisher<Box<Y>>,
})
map(a, boxes => {
  // Index signature if missing in type { x: Box<X>, y: Box<Y> },
  let fail1: {
    [name: string]: Box<any>
  } = boxes

  // But not when type { x: Box<X>, y: Box<Y> } is specified manually
  // instead of inferred
  let pass1: {
    [name: string]: Box<any>
  } = boxes as {
    x: Box<X>;
    y: Box<Y>;
  }

  // And it can still be assigned to index-typed objects...
  let pass2: {
    [name: string]: any
  } = boxes

})

// Here `b` has the same type as the type inferred for `a`, but it works.
// The behavior must stem from inferred type.
let b: Publisher<{
  x: Box<X>,
  y: Box<Y>, 
}>
map(b, boxes => {
  let pass3: {
    [name: string]: Box<any>
  } = boxes
})

Expected behavior:

Should compile.

Given that boxes can be assigned to { [name: string]: any }, it feels like it should also be able to be assigned to { [name: string]: Box<any> }.

Actual behavior:

Fails with:

test.ts:24:7 - error TS2322: Type '{ x: Box<X>; y: Box<Y>; }' is not assignable to type '{ [name: string]: Box<any>; }'.
  Index signature is missing in type '{ x: Box<X>; y: Box<Y>; }'.

24   let fail1: {

Playground Link:

http://www.typescriptlang.org/play/#src=declare%20function%20map%3CFromValue%3E(%0D%0A%20%20from%3A%20Publisher%3CFromValue%3E%2C%0D%0A%20%20to%3A%20(props%3A%20FromValue)%20%3D%3E%20any%0D%0A)%3B%0D%0A%0D%0Adeclare%20function%20combine%3CCombinedValue%3E(children%3A%20%7B%0D%0A%20%20%5BK%20in%20keyof%20CombinedValue%5D%3A%20Publisher%3CCombinedValue%5BK%5D%3E%0D%0A%7D)%3A%20Publisher%3CCombinedValue%3E%3B%0D%0A%0D%0A%0D%0Atype%20Publisher%3CValue%3E%20%3D%20%7B%20value%3A%20Value%3B%20%7D%0D%0Atype%20Box%3CValue%3E%20%3D%20%7B%20data%3A%20Value%20%7D%0D%0Atype%20X%20%3D%20%7B%20a%3A%20string%20%7D%0D%0Atype%20Y%20%3D%20%7B%20b%3A%20number%20%7D%0D%0A%0D%0A%2F%2F%20---%0D%0A%0D%0Alet%20a%20%3D%20combine(%7B%0D%0A%20%20x%3A%20undefined%20as%20Publisher%3CBox%3CX%3E%3E%2C%0D%0A%20%20y%3A%20undefined%20as%20Publisher%3CBox%3CY%3E%3E%2C%0D%0A%7D)%0D%0Amap(a%2C%20boxes%20%3D%3E%20%7B%0D%0A%20%20%2F%2F%20Index%20signature%20if%20missing%20in%20type%20%7B%20x%3A%20Box%3CX%3E%2C%20y%3A%20Box%3CY%3E%20%7D%2C%0D%0A%20%20let%20fail1%3A%20%7B%0D%0A%20%20%20%20%5Bname%3A%20string%5D%3A%20Box%3Cany%3E%0D%0A%20%20%7D%20%3D%20boxes%0D%0A%0D%0A%20%20%2F%2F%20But%20not%20when%20type%20%7B%20x%3A%20Box%3CX%3E%2C%20y%3A%20Box%3CY%3E%20%7D%20is%20specified%20manually%0D%0A%20%20%2F%2F%20instead%20of%20inferred%0D%0A%20%20let%20pass1%3A%20%7B%0D%0A%20%20%20%20%5Bname%3A%20string%5D%3A%20Box%3Cany%3E%0D%0A%20%20%7D%20%3D%20boxes%20as%20%7B%0D%0A%20%20%20%20x%3A%20Box%3CX%3E%3B%0D%0A%20%20%20%20y%3A%20Box%3CY%3E%3B%0D%0A%20%20%7D%0D%0A%0D%0A%20%20%2F%2F%20And%20it%20can%20still%20be%20assigned%20to%20index-typed%20objects...%0D%0A%20%20let%20pass2%3A%20%7B%0D%0A%20%20%20%20%5Bname%3A%20string%5D%3A%20any%0D%0A%20%20%7D%20%3D%20boxes%0D%0A%0D%0A%7D)%0D%0A%0D%0A%2F%2F%20Here%20%60b%60%20has%20the%20same%20type%20as%20the%20type%20inferred%20for%20%60a%60%2C%20but%20it%20works.%0D%0A%2F%2F%20The%20behavior%20must%20stem%20from%20inferred%20type.%0D%0Alet%20b%3A%20Publisher%3C%7B%0D%0A%20%20x%3A%20Box%3CX%3E%2C%0D%0A%20%20y%3A%20Box%3CY%3E%2C%20%0D%0A%7D%3E%0D%0Amap(b%2C%20boxes%20%3D%3E%20%7B%0D%0A%20%20let%20pass3%3A%20%7B%0D%0A%20%20%20%20%5Bname%3A%20string%5D%3A%20Box%3Cany%3E%0D%0A%20%20%7D%20%3D%20boxes%0D%0A%7D)%0D%0A

Related Issues:

@mhegazy mhegazy added Working as Intended The behavior described is the intended behavior; this is not a bug and removed Working as Intended The behavior described is the intended behavior; this is not a bug labels Jun 7, 2018
@mhegazy
Copy link
Contributor

mhegazy commented Jun 7, 2018

This is partially the same underlying issue as #15300.

@mhegazy mhegazy added the Duplicate An existing issue was already created label Jun 7, 2018
@jamesknelson
Copy link
Author

The thing that confused me is that the error message says Index signature if missing in type { x: Box<X>, y: Box<Y> }, however { x: Box<X>, y: Box<Y> } can still be assigned - just not when inferred.

If this is the intended behavior, perhaps the error message could indicate that the inferred { x: Box<X>, y: Box<Y> } somehow differs from the manually specified { x: Box<X>, y: Box<Y> }?

@jamesknelson
Copy link
Author

Found a temporary solution that allows inferred types to be treated as having an index signature:

type Publisher<Value> = { value: Value; }
type Box<Value> = { data: Value }
type X = { a: string }
type Y = { b: number }

declare function map<FromValue>(
  from: Publisher<FromValue>,
  to: (props: FromValue) => any
);

declare function combine<CombinedValue>(children: {
  [K in keyof CombinedValue]: Publisher<CombinedValue[K]>
}): Publisher<CombinedValue>;

declare function processBoxes<T extends { [name: string]: Box<any> }>(boxes: T): T;

// ---

let combinedPublisher = combine({
  x: undefined as Publisher<Box<{ a: string }>>,
  y: undefined as Publisher<Box<{ b: number }>>,
})
map(combinedPublisher, boxes => {
  // Error: Index signature if missing in type { x: Box<X>, y: Box<Y> },
  let a = processBoxes(boxes)

  // Compiles
  let b = processBoxes(boxes as {
    [K in keyof typeof boxes]: typeof boxes[K]
  })

  // "typeof c" is string
  let c = b.x.data.a
})

@typescript-bot
Copy link
Collaborator

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

3 participants