microsoft / microsoft/TypeScript

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

Open
#22,865 9 comments 19 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Awaiting More Feedback Suggestion
Dominant language
Go
Stars
111k
Forks
14.3k
Avg merge
2d 4h
Merged PRs (30d)
132

Description

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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reviewing the proposed merge and map examples, including the suggested then parameter syntax and the before/after type behavior. Determine whether the proposal has settled semantics for mutation, aliases, property deletion, and nullability; done would require an agreed design precise enough to guide implementation and tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, typescript
Domain
compilers
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.