Skip to content

Stricter disjunction of interfacesΒ #54051

Open
@dpohvar

Description

@dpohvar

Suggestion

πŸ” Search Terms

List of keywords you searched for before creating this issue. Write them down here so that others can find this suggestion more easily and help provide feedback.

βœ… Viability Checklist

My suggestion meets these guidelines:

  • !!! This can be a breaking change
  • 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

πŸ“ƒ Motivating Example

Disjunction of interfaces can lead to an error

Here is example 1:

interface A { id: string }
interface B { id: number }

const a: A = {id: "xxxxx"};

function modifyObject(value: A | B){
    value.id = 100; // this is OK
    //    ^? (property) id: string | number
}

modifyObject(a);

console.log(a.id.toUpperCase());
//             ^? A.id: string
// [ERR]: a.id.toUpperCase is not a function

Problem: Typescript didn't find the error of type mismatch.

Example 2:

interface A { id: string }
interface B { id: number }

const v = {id: "xxxxx"} as A | B;

function modifyObject(value: A | B){
    value.id = 100; // this is OK
    //    ^? (property) id: string | number
}

modifyObject(v);

console.log(v.id.toUpperCase());
//             ^? (property) id: string | number
// TS: Property 'toUpperCase' does not exist on type 'number' --- OK!

Signature of modifyObject is same, but now code is correct.

Example 3:

interface A { id: string, readonly dataType: "A" }
interface B { id: number, readonly dataType: "B" }

const v = {id: "xxxxx", dataType: "A"} as A | B;

function modifyObject(value: A | B){
    value.id = 100; // this is NOT OK, but works
}

modifyObject(v);

console.log(v); // {id: 100, dataType: "A"} // type mismatch

It would be better if interface disjunction handled getters and setters

interface A { id: string, readonly dataType: "A" }
interface B { id: number, readonly dataType: "B" }

const v: A = {id: "xxxxx", dataType: "A"};

function modifyObject(
    value: /* automatically calculates from (A | B) */ {
        get id(): string | number;
        set id(value: string & number);
        get dataType(): "A" | "B";
    }
){
    console.log(value.id) // OK! This is number | string
    value.id = 100; // TS ERROR: 'number' is not assignable to type 'never'.
}

modifyObject(v); // OK

In this form, it will lead to breaking changes, so there can be 2 options:

  • make this behavior dependent on the flag
  • make additional type like:
function modifyObject(value: AnyOfType<A | B>){
    value.id = 100; // this is OK
    //    ^? (property) id: string | number
}

but this seems impossible

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