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 when do object destructuring to separate variable #42021

Closed
rifler opened this issue Dec 17, 2020 · 10 comments
Closed
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@rifler
Copy link

rifler commented Dec 17, 2020

TypeScript Version: 4.1.2

Search Terms:
destructuring, index signature is missing

Expected behavior:
it works

Actual behavior:
when I do object destructuring to separate variable index signature is lost somewhere

Related Issues:

Code

type InitialIndexType = {
    key: string;
};

type FinalIndexType = Record<string, string | number | boolean | null | undefined>;

const runtimeInitialVariable: InitialIndexType = { key: 'value' };

// 1. ok, InitialIndexType is compatible with FinalIndexType
const runtimeFinalVariable: FinalIndexType = runtimeInitialVariable;

// 2. error with destructuring, but only when do it to separate variable
const { ...runtimeInitialVariable_2 } = runtimeInitialVariable;

const runtimeFinalVariable_2: FinalIndexType = runtimeInitialVariable_2; // Index signature is missing in type '{ key: string; }'.

// 3. ok with destructuring, without separate variable
const runtimeFinalVariable_3: FinalIndexType = { ...runtimeInitialVariable };

// 4. ok even if I copy&paste type from error message and cast destructured variable to it
const { ...runtimeInitialVariable_4 } = runtimeInitialVariable;

const runtimeFinalVariable_4: FinalIndexType = runtimeInitialVariable_4 as { key: string }; // also works as `as InitialIndexType`
Output
"use strict";
var __rest = (this && this.__rest) || function (s, e) {
    var t = {};
    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
        t[p] = s[p];
    if (s != null && typeof Object.getOwnPropertySymbols === "function")
        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                t[p[i]] = s[p[i]];
        }
    return t;
};
const runtimeInitialVariable = { key: 'value' };
// ok, InitialIndexType is compatible with FinalIndexType
const runtimeFinalVariable = runtimeInitialVariable;
// error with ...rest, but only when do ...rest to separate variable
const runtimeInitialVariable_2 = __rest(runtimeInitialVariable, []);
const runtimeFinalVariable_2 = runtimeInitialVariable_2;
// ok with rest, without separate variable
const runtimeFinalVariable_3 = Object.assign({}, runtimeInitialVariable);
// ok even if I copy&paste type from error message and cast ...rest variable to it
const runtimeInitialVariable_4 = __rest(runtimeInitialVariable, []);
const runtimeFinalVariable_4 = runtimeInitialVariable_4; // also works as `as InitialIndexType`
Compiler Options
{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true,
    "strictBindCallApply": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "alwaysStrict": true,
    "esModuleInterop": true,
    "declaration": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "moduleResolution": 2,
    "target": "ES2017",
    "jsx": "React",
    "module": "ESNext"
  }
}

Playground Link: Provided

@rifler rifler changed the title index signature is missing when do ...rest to separate variable index signature is missing when do object destructuring to separate variable Dec 17, 2020
@RyanCavanaugh RyanCavanaugh added the Design Limitation Constraints of the existing architecture prevent this from being fixed label Dec 23, 2020
@RyanCavanaugh
Copy link
Member

This behavior falls out of the inference rules and it's not clear to me which one could be changed given the current type mechanics available. runtimeInitialVariable_2 would have need to have a sealed/exact type (#12936) to be safely assigned to InitialIndexType

@2A5F
Copy link

2A5F commented Dec 29, 2020

Shorter code

let { ...obj } = { a: 1 }
let foo: Record<string, number> = obj
// ^
// Type '{ a: number; }' is not assignable to type 'Record<string, number>'.
//   Index signature is missing in type '{ a: number; }'.(2322)

let { a } = { a: 1 }
let foo: Record<string, number> = { a }
// ^ this ok

Playground

@paulboocock
Copy link

I've just bumped into this one as well. It feels to me like the destructured object should be assignable to Record<string, unknown>.

Perhaps interestingly, it turns out if you spread the object into a new one it works whilst the original does not 🤷🏻

const { prop1, ...rest } = { prop1: 'a string', prop2: 2 };
const options1: Record<string, unknown> = rest; // This doesn't work
const options2: Record<string, unknown> = {...rest}; // This does work

@kirpichenko
Copy link

In addition to the last comment if you use any instead of unknown it somehow works 😕

const { prop1, ...rest } = { prop1: 'a string', prop2: 2 };
const options1: Record<string, unknown> = rest; // This doesn't work
const options2: Record<string, unknown> = {...rest}; // This does work

const options3: Record<string, any> = rest; // This does work
const options4: Record<string, any> = {...rest}; // This does work

@jcalz
Copy link
Contributor

jcalz commented Oct 11, 2021

Now that we have pattern template literal index signatures, this is even more noticeable:

interface Foo {
  x: string,
  [k: `y${string}`]: number;
}

declare const foo: Foo;
const bar = { ...foo, z: true };
// const bar: {  z: boolean;  x: string; } ... no index signature here

@jcalz
Copy link
Contributor

jcalz commented Feb 21, 2022

Can someone comment on why we're not allowing a destructured rest element to have an implicit index signature? The argument in #15300 against doing this for interfaces (i.e., declaration merging issues) doesn't seem to apply here.

@RyanCavanaugh
Copy link
Member

@jcalz can you post a code sample so I can be sure I'm understanding the question?

@jcalz
Copy link
Contributor

jcalz commented Feb 22, 2022

Oh, sure, something like this:

const foo = (x: Record<string, unknown>) => void 0

const x = { a: 0 }
// const x: { a: number }
foo(x); // okay (implicit index signature, right?)

const { ...y } = x;
// const y: { a: number }
foo(y); // error (no implicit index signature)
//  ~ <--  Index signature for type 'string' is missing in type '{ a: number; }'

Playground link

@RyanCavanaugh
Copy link
Member

The current rule for implicit index signatures is that the originating declaration must be an object type literal, or inferred from an actual object literal (let's call this "objectish"). IOW, types coming from classes or interfaces do not qualify:

declare class C {
    // In general we cannot assume this list
    // of declarations to be exhaustive
    a: number;
}
// Error (correct)
foo(new C());
const { ...z } = new C();
// Error (correct)
foo(z);

const { ...z } makes z look objectish because it's using { }, but it's really not, since it can source properties from types which aren't objectish.

So I think a reasonable proposal would be to grant z obectishness iff its initializing expression is objectish -- that's no less safe than the current rules, even if other properties are picked out. Happy to take a new suggestion issue on that and we can probably just prototype it to see what happens.

@sybereal
Copy link

Not sure if it's the same or a related issue, but I just ran into something that feels similar: spreading an object with an index signature loses the index signature on the result type.

type Foo = {[key: string]: string}

const foo: Foo = {a: "b", c: "d"}
const bar = {e: "f", g: "h"}
const baz = {...bar, ...foo}

// Should work, but index signature disappears during spread
const a = baz["a"]

Playground

My use case was adding some defaults to the index signature object.

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

7 participants