Skip to content

Represent the types of function parameters that mutate inside the function. #22865

Open
@lilezek

Description

@lilezek

In JavaScript is possible to mutate objects inside functions. Right now, the following code in JavaScript:

function merge(x,y) {
  Object.assign(x,y);
} 

let x = {a: 1};   
merge(x, {b: 2});
console.log(x.b);

Can't be written in TypeScript without casting the type. There are a few options whose type definition is wrong in all scenarios I can think of (maybe I'm missing a better option):

Option 1

let x: {a: number, b: number} = {a: 1}; // Error, missing b
merge(x, {b: 2});

Option 2

let x: {a: number, b: number} = {a: 1, b: 2};
merge(x, {b: null});
// From here, x.b is not a number anymore, but you could do
let y: number= x.b;

Suggestion

There could be an extension to function parameter definition like the following:

// then keyword indicates that before it can be type A, and after it will be of type A&B.
function merge<A,B>(x: A then x2: A&B, y: B) {
  Object.assign(x2,y);
}

let x: {a: number, b: number} = {a: 1, b: 2};
merge(x, {b: null});
// Here, type of x is {a: number, b: number} & {b: null}
x.b; // Type null

There, we indicate that whatever type was x before, now it is something different. The code above could be written in TypeScript as follows:

// then keyword indicates that before it can be type A, and after it will be of type A&B.
function merge<A,B>(x: A, y: B) {
  Object.assign(x,y);
}

let x: {a: number, b: number} = {a: 1, b: 2};
merge(x, {b: null});
// Here, type of x is {a: number, b: number} & {b: null}
let xAfterMerge = x as {a: number, b: number} & {b: null};
// Since this line, x should not be used but xAfterMerge
xAfterMerge.b; // Type null

Another example

interface Before {
  address: string;
}

interface After {
  addr: string;
}

function map(userb: Before then usera: After) {
   usera.addr = userb.address;
   delete userb.address;
}

let u = {adress: "my street"};
map(u);
console.log(u.addr);

That could be syntax sugar for this:

interface Before {
  address: string;
}

interface After {
  addr: string;
}

function map(userb: any) {
   userb.addr = userb.address;
   delete userb.address;
}

let u = {adress: "my street"};
map(u); 
console.log((u as After).addr);

Syntax

It could be something like:

identifier: type *then* identifier: type  

With the identifiers being different, and with the types being mandatory an extension of Object.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Awaiting More FeedbackThis means we'd like to hear from more people who would be helped by this featureSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions