microsoft / microsoft/TypeScript
Stricter disjunction of interfaces
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 111k
- Forks
- 14.4k
- Avg merge
- 1d 19h
- Merged PRs (30d)
- 117
Description
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
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
No files, tests, or entry points are named. Start by reproducing the three TypeScript examples and comparing current union-property read and write behavior; done requires an agreed design for stricter disjunction handling, including whether it is breaking or flag-dependent, with implementation scope and validation tests identified.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- compilers
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100