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

Target of object rest destructuring should allow an implicit index signature if its source does #48014

Open
5 tasks done
jcalz opened this issue Feb 23, 2022 · 3 comments
Open
5 tasks done
Labels
Experimentation Needed Someone needs to try this out to see what happens In Discussion Not yet reached consensus Suggestion An idea for TypeScript

Comments

@jcalz
Copy link
Contributor

jcalz commented Feb 23, 2022

Suggestion

πŸ” Search Terms

destructuring assignment, object rest, implicit index signature,

βœ… Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

From a comment on the related #42021:

Implicit index signatures are only allowed for "objectish" types that are either object literal types or inferred from an object literal value. They are not permitted for interfaces or the instance type of classes, as discussed in #15300.

When using rest in object destructuring assignment to copy properties from a source object to a new target variable, the new variable is not considered to be "objectish" even if the source expression is. This is surprising as shown by this Stack Overflow question and various examples in #42021.

The proposal here is that the type of the new variable should inherit "objectishness" from the initializing expression.

πŸ“ƒ Motivating Example

Consider the following example demonstrating the current behavior:

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

const x = { a: 0 }
// const x: { a: number }
foo(x); // okay, `x` is granted an implicit index signature

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

Playground link

It is surprising that while y is a copy of x and the types of x and y are seemingly identical, you are allowed to call foo(x) but prohibited from calling foo(y). The suggestion here is to make it so foo(y) succeeds if and only if foo(x) succeeds.

πŸ’» Use Cases

  • This helps TypeScript more fully support rest destructuring assignment const {...y} = x as an alternative to const y = Object.assign({}, x) for copying properties into a new object, which is the current workaround for things like this (modulo define-vs-set semantics and other things that go bump in the night).

  • This also helps support the removal of some properties const { a, ...y } = x while maintaining "objectishness", which currently doesn't have a nice and simple workaround.

@RyanCavanaugh RyanCavanaugh added In Discussion Not yet reached consensus Suggestion An idea for TypeScript labels Feb 23, 2022
@RyanCavanaugh
Copy link
Member

I think this is a fairly straightforward PR (relatively speaking) if anyone wants to experiment with it

@DanielRosenwasser DanielRosenwasser added the Experimentation Needed Someone needs to try this out to see what happens label Feb 23, 2022
@spenceradolph
Copy link

spenceradolph commented Jun 23, 2022

I recently ran into a similar problem (I think) with TS not complaining about extra methods after destructuring.
This stackoverflow helped me out.

I believe if a feature like this was implemented I could simplify this (prisma wrapper class)

export const Project extends ProjectProperties {
    // remove methods from class instance, only send 'fields' to DB
    dbSync = async () => {
        type StrictPropertyCheck<T, TExpected, TError> = Exclude<keyof T, keyof TExpected> extends never ? {} : TError;
	const { dbSync, ...rest }: Project = this;
	const data: StrictPropertyCheck<typeof rest, ProjectProperties, 'Fields only.'> = rest;
	await prisma.project.update({ where: { id: this.id }, data });
    };
}

into maybe something like this

dbSync = async () => {
	const { dbSync, ...data}: Project = this; // remove methods since they are not stored in the DB
	await prisma.project.update({ where: { id: this.id }, data }); // would yell if not all removed
};

@greg-hornby-roam
Copy link

greg-hornby-roam commented Aug 29, 2022

I found destructing the object again allowed it to be passed a function call.

type SimpleObject = Record<string, string | number>;

declare function processData(data: SimpleObject): void;

const data = {
  a: "a",
  b: 2,
  c: {}
};

const {c, ...rest1} = data;
const rest2 = {...rest1};

processData(rest1); //error
processData(rest2); //works despite rest2 being the same type as rest1

However you still can't access random properties on rest2 even though Typescript is suddenly allowing it as an object with index signature

//if rest2 has Index signature for type 'string'
//then this statement should not error, but it does.
rest2.randomProperty;

https://www.typescriptlang.org/play?ssl=19&ssc=22&pln=17&pc=1#code/C4TwDgpgBAyglgWzAGwgeQEYCsIGNhQC8UASngPYBOAJgDwDOwlcAdgOYA0Ujz7UAPlBYBXBBgiUAfAG4AULOp5kAQ0rQAZsJb445FlDCVyuCPXoARZcGUAKaleUAuWIhTpseYAEpnAN3Jw1HKyuHqMUPbWRFAA3rJQUE5QAETKyRzxUBjOAEwZCbjOMQC+ssXBoSzhMbhcAHQNaowAjMXRkcpyleFNwDnRMQ11va3BhsamFg42I17SUAD0CxJGlLLjJmaW1jOmfXOLCwDuVADW9BGmYHDA0L394qxsUMAAFtD0ygjQoJCJFyN5Es4OooPcoK9lBcAJIsRQAD24cDYLCswjUUHUVBe4GgAHIeE88bIlm8IPo3nALowrBBviwCPRXuRhMhqEJyAQVlQuBhhAQbhFyKY6rJ7sNlHDyAgAApGSCUUDBRS4FQY7oEW6MTBYZzwJCoHWeORa4A6iVS2XyiRKoA

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Experimentation Needed Someone needs to try this out to see what happens In Discussion Not yet reached consensus Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

5 participants