Skip to content

setter inferred from union has incorrect varianceΒ #56894

Closed
@craigphicks

Description

@craigphicks

πŸ”Ž Search Terms

setter union variance

is:issue in-title: setter (nothing relevant found)

is:issue in-title: setter union variance (nothing relevant found)

πŸ•— Version & Regression Information

confirmed 4.3.5 ~ 5.3.2

⏯ Playground Link

playground

πŸ’» Code

declare const id:<T>()=>T

function f2(x: {f:((a:1|2)=>1|2)} | {f:((a:2|3)=>2|3)} ){
    const r = x.f(id())
//                ^ const id: <2>() => 2,
//        ^ const r: 1 | 2 | 3
}

function f3(f: ((a:1|2)=>1|2) | ((a:2|3)=>2|3) ){
    const r = f(id())
//              ^ const id: <2>() => 2,  
//        ^ const r: 1 | 2 | 3
}


function f4(x: {set d(a:1|2); get r():1|2} | {set d(a:2|3); get r():2|3} ){
//           ^ (parameter) x: { d: 1 | 2; readonly r: 1 | 2; } | { d: 2 | 3; readonly r: 2 | 3; }
    x.d = id();
//        ^ const id: <1 | 2 | 3>() => 1 | 2 | 3
    x.r; 
//    ^    (property) r: 1 | 2 | 3
}

πŸ™ Actual behavior

function f4(x: {set d(a:1|2); get r():1|2} | {set d(a:2|3); get r():2|3} ){
    x.d = id();
//        ^ const id: <1 | 2 | 3>() => 1 | 2 | 3

The setter function has range 1|2|3.

πŸ™‚ Expected behavior

function f4(x: {set d(a:1|2); get r():1|2} | {set d(a:2|3); get r():2|3} ){
//           ^ (parameter) x: {set d(a:1|2); get r():1|2;} | {set d(a:2|3); get r():2|3;}
    x.d = id();
//        ^ const id: <2>() => 2
  • The setter function has range 2, because that is the valid range that can be assigned to both setter functions in the union.
    • Compare to the ranges 2 and 2 computed for function parameters a in f2 and f3.
  • The type display for x should show getters and setters instead of plain (readonly for getter) properties.

Additional information about the issue

  • The parameter range of the union of two functions is the intersection of those two functions parameter ranges. That is because anything outside of that intersection would be invalid input in at least one of the two.

  • Theoretically, writes and reads of plain properties of unions of object could also "legally" have opposite variances, with writes being narrower - but they are not treated that way, which although unsound is ok because type widening is allowed for convenience.

  • For the special cases where user want a setter, or setter-getter same-name pair, to behave write-unsoundly exactly like a plain property, they can specify the type as being a plain type, e.g. { d: T } instead of { set d(value:T): }. That's an argument for allowing write-sound behavior by default in a type definition { set d(value:T): }.

  • The setters are converted to plain properties (and the getters are converted to a readonly plain properties (probably at point of declaration) as can be seen by tool tipping to x which shows (parameter) x: { d: 1 | 2; readonly r: 1 | 2; } | { d: 2 | 3; readonly r: 2 | 3; }. Related to Flow is improperly narrowing getter valuesΒ #56899.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions