Skip to content

Using rest destructing on class objects seems to include getters in the resulting type #46967

Closed
@divillysausages

Description

@divillysausages

Bug Report

If you have a class along the lines of:

class User {
    constructor(
        public uid: number,
        public password: string // sensitive info
    ) { }
	
    get score(): number {
        return 10; // example code - imagine having to fetch something a little more complex
    }
}

If we then want to destructure a User object (e.g. returning a client-friendly basic object from a server), we first declare a type:

type UserClient = Omit<User, 'password' | 'score'>;

We pass score to the Omit here because, as we're using destructing, only basic properties will be returned, not any getters. We can then create our method:

toClient(): UserClient {
    const { password, ...client } = this;
    return client;
}

However, score is something that we want to return, so we change our UserClient type to:

type UserClient = Omit<User, 'password' | 'score'> | { score: number };

and our toClient() method now becomes:

toClient(): UserClient {
    const { password, ...client } = this;
    return {
        score: this.score,
        ...client
    }
}

This, however, gives the error 'score' is specified more than once, so this usage will be overwritten, even though when we destructure an object, it doesn't give us the getters - it seems like the inferred type from this line:

const { password, ...client } = this;

is wrong (specifically, it's Omit<this, 'password'> for client). In order to fix it, I need to change that line to

const { password, score, ...client } = this;

This doesn't really have any effect on the underlying JavaScript code, so it's a minor bug (I think)

🔎 Search Terms

  • rest
  • destructuring

🕗 Version & Regression Information

I'm currently on 4.4.3 though running in TypeScript Playground, 3.9.7 is when it first seems to show up.

In that version, the inferred type is Pick<this, Exclude<keyof this, "password">>

In 3.8.3 the inferred type is the same, though the error isn't present, so there must have been a change to the underlying Pick code (perhaps to include getters?)

  • This changed between versions 3.8.3 and 3.9.7

⏯ Playground Link

https://www.typescriptlang.org/play?ts=4.4.4#code/MYGwhgzhAECqEFMBO0DeAoAkMA9gOwgBckBXYQnJACi0wAcSAjEAS2GhJYBMAuaPEgFtGyADS0GzNtDqQIAd0q9oRJCzwBzaAHptKhARaEWANwTR1AMxxYAlGmgBfLFg0JCK3EgRVbfAcLIaLTehCRIeNAAjAAMANw6eggAHmCCdCDmuFzmALQWgmAa6uYAFmAm6loU0JbuwKUqOILupVXQYNCshISZ0IKUWc0ZKVjO6NCT0BQAwqwGhL588MhzLAvBU1vQuAQeqDJyikhcotAAdJeg63gejtAAvNNtEHET25Oh4ZEYHx8QXgQfEIL3OAMG4j+20u52uC3eH2cW2czkIAE86OYVkg1hsngB5QRGAA82LOAHJZFBjlxydAAD7Qcng7zkgB8DIcLKB-CEIhQjjiQA

💻 Code

class User {
    constructor(
        public uid: number,
        public password: string // sensitive info
    ) { }
	
    get score(): number {
        return 10; // example code - imagine having to fetch something a little more complex
    }

    toClient(): UserClient {
        const { password, ...client } = this;
        return {
            score: this.score, // ERROR: 'score' is specified more than once, so this usage will be overwritten
            ...client
        }
    }
}
type UserClient = Omit<User, 'password' | 'score'> | { score: number };

🙁 Actual behavior

The inferred type on client seems to include getters

🙂 Expected behavior

The inferred type on client to only include basic properties as it's created through destructuring

Metadata

Metadata

Assignees

Labels

Breaking ChangeWould introduce errors in existing codeBugA bug in TypeScriptFix AvailableA PR has been opened for this issue

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions