type Obj = { p: number | string }

function f () {}

function def_assign_function_havoc(obj: Obj) {
    obj.p = 10;                 // (obj.p : number)
    f();                        // clears refi
    var x: number = obj.p;      // error, obj.p : number | string
}

function def_assign_setprop_havoc(obj: Obj, obj2: Obj) {
    obj.p = 10;                 // (obj.p : number)
    obj2.p = 'hey';             // clears refi
    var x: number = obj.p;      // error, obj.p : number | string
}

function def_assign_index_havoc(obj: Obj, obj2: Obj) {
    obj.p = 10;                 // (obj.p : number)
    obj2['p'] = 'hey';          // clears refi
    var x: number = obj.p;      // error, obj.p : number | string
}

function def_assign_within_if(b: boolean, obj: Obj) {
    if (b) {
        obj.p = 10;             // (obj.p : number)
        var x: number = obj.p   // ok by def assign
    }
    var y: number = obj.p;      // error, obj.p : number | string
}

function def_assign_within_while(b: boolean, obj: Obj) {
    while (b) {
        obj.p = 10;             // (obj.p : number)
        var x: number = obj.p   // ok by def assign
    }
    var y: number = obj.p;      // error, obj.p : number | string
}

function def_assign_within_do(b: boolean, obj: Obj) {
    do {
        obj.p = 10;             // (obj.p : number)
        var x: number = obj.p   // ok by def assign
    } while (b);
    var y: number = obj.p;      // no error, loop runs at least once
}

function def_assign_within_try(b: boolean, obj: Obj) {
    obj.p = 10;                 // (obj.p : number)
    try {
        f();                    // clears refi and might throw
        obj.p = 'hey';
    } catch (e) {
        f();                    // clears refi and might throw
        obj.p = 'hey';
    } finally {
        // NOTE: the values understood to flow to obj.p at this point
        // include the number 42 written downstream;
        // so if we did y:string, we would get at least a spurious error
        // (among other reasonable errors caused by values written upstream)
        var y: number = obj.p;  // error, string ~/~ number
        obj.p = 42;
    }
    var z:string = obj.p;       // error, number ~/~ string
}

function def_assign_within_if_both_branches(b: boolean, obj: Obj) {
    obj.p = "a";
    if (b) {
        f();
        obj.p = 10;             // (obj.p : number)
    } else {
        f();
        obj.p = 10;
    }
    var y: number = obj.p;      // error, obj.p : number | string
}

function def_assign_within_for(b: boolean, obj: Obj) {
    for (; b; ) {
        obj.p = 10;             // (obj.p : number)
        var x: number = obj.p   // ok by def assign
    }
    var z: number = obj.p;      // error, (number | string) ~/~ number
}

// --- name-sensitive havoc ---

type Obj2 = { q: number | string }

function def_assign_setprop_nohavoc(obj: Obj, obj2: Obj2) {
    obj.p = 10;                 // (obj.p : number)
    obj2.q = 'hey';             // doesn't clear refi of .p
    var x: number = obj.p;      // still ok
}
