// @strict: true
// @declaration: true

type T01 = {} & string;  // {} & string
type T02 = {} & 'a';  // 'a'
type T03 = {} & object;  // object
type T04 = {} & { x: number };  // { x: number }
type T05 = {} & null;  // never
type T06 = {} & undefined;  // never
type T07 = undefined & void;  // undefined

type T10 = string & {};  // Specially preserved
type T11 = number & {};  // Specially preserved
type T12 = bigint & {};  // Specially preserved

type ThisNode = {};
type ThatNode = {};
type ThisOrThatNode = ThisNode | ThatNode;

function f01(u: unknown) {
    let x1: {} = u;  // Error
    let x2: {} | null | undefined = u;
    let x3: {} | { x: string } | null | undefined = u;
    let x4: ThisOrThatNode | null | undefined = u;
}

function f10(x: unknown) {
    if (x) {
        x;  // {}
    }
    else {
        x;  // unknown
    }
    if (!x) {
        x;  // unknown
    }
    else {
        x;  // {}
    }
}

function f11<T>(x: T) {
    if (x) {
        x;  // T & {}
    }
    else {
        x;  // T
    }
    if (!x) {
        x;  // T
    }
    else {
        x;  // T & {}
    }
}

function f12<T extends {}>(x: T) {
    if (x) {
        x;  // T
    }
    else {
        x;  // T
    }
}

function f20(x: unknown) {
    if (x !== undefined) {
        x;  // {} | null
    }
    else {
        x;  // undefined
    }
    if (x !== null) {
        x;  // {} | undefined
    }
    else {
        x;  // null
    }
    if (x !== undefined && x !== null) {
        x;  // {}
    }
    else {
        x;  // null | undefined
    }
    if (x != undefined) {
        x;  // {}
    }
    else {
        x;  // null | undefined
    }
    if (x != null) {
        x;  // {}
    }
    else {
        x;  // null | undefined
    }
}

function f21<T>(x: T) {
    if (x !== undefined) {
        x;  // T & ({} | null)
    }
    else {
        x;  // T
    }
    if (x !== null) {
        x;  // T & ({} | undefined)
    }
    else {
        x;  // T
    }
    if (x !== undefined && x !== null) {
        x;  // T & {}
    }
    else {
        x;  // T
    }
    if (x != undefined) {
        x;  // T & {}
    }
    else {
        x;  // T
    }
    if (x != null) {
        x;  // T & {}
    }
    else {
        x;  // T
    }
}

function f22<T extends {} | undefined>(x: T) {
    if (x !== undefined) {
        x;  // T & {}
    }
    else {
        x;  // T
    }
    if (x !== null) {
        x;  // T
    }
    else {
        x;  // T
    }
    if (x !== undefined && x !== null) {
        x;  // T & {}
    }
    else {
        x;  // T
    }
    if (x != undefined) {
        x;  // T & {}
    }
    else {
        x;  // T
    }
    if (x != null) {
        x;  // T & {}
    }
    else {
        x;  // T
    }
}

function f23<T>(x: T | undefined | null) {
    if (x !== undefined) {
        x;  // T & {} | null
    }
    if (x !== null) {
        x;  // T & {} | undefined
    }
    if (x != undefined) {
        x;  // T & {}
    }
    if (x != null) {
        x;  // T & {}
    }
}

function f30(x: {}) {
    if (typeof x === "object") {
        x;  // object
    }
}

function f31<T>(x: T) {
    if (typeof x === "object") {
        x;  // T & object | T & null
    }
    if (x && typeof x === "object") {
        x;  // T & object
    }
    if (typeof x === "object" && x) {
        x;  // T & object
    }
}

function f32<T extends {} | undefined>(x: T) {
    if (typeof x === "object") {
        x;  // T & object
    }
}

function possiblyNull<T>(x: T) {
    return !!true ? x : null;  // T | null
}

function possiblyUndefined<T>(x: T) {
    return !!true ? x : undefined;  // T | undefined
}

function possiblyNullOrUndefined<T>(x: T) {
    return possiblyUndefined(possiblyNull(x));  // T | null | undefined
}

function ensureNotNull<T>(x: T) {
    if (x === null) throw Error();
    return x;  // T & ({} | undefined)
}

function ensureNotUndefined<T>(x: T) {
    if (x === undefined) throw Error();
    return x;  // T & ({} | null)
}

function ensureNotNullOrUndefined<T>(x: T) {
    return ensureNotUndefined(ensureNotNull(x));  // T & {}
}

function f40(a: string | undefined, b: number | null | undefined) {
    let a1 = ensureNotNullOrUndefined(a);  // string
    let b1 = ensureNotNullOrUndefined(b);  // number
}

type QQ<T> = NonNullable<NonNullable<NonNullable<T>>>;

function f41<T>(a: T) {
    let a1 = ensureNotUndefined(ensureNotNull(a));  // T & {}
    let a2 = ensureNotNull(ensureNotUndefined(a));  // T & {}
    let a3 = ensureNotNull(ensureNotNull(a));  // T & {} | T & undefined
    let a4 = ensureNotUndefined(ensureNotUndefined(a));  // T & {} | T & null
    let a5 = ensureNotNullOrUndefined(ensureNotNullOrUndefined(a));  // T & {}
    let a6 = ensureNotNull(possiblyNullOrUndefined(a));  // T & {} | undefined
    let a7 = ensureNotUndefined(possiblyNullOrUndefined(a));  // T & {} | null
    let a8 = ensureNotNull(possiblyUndefined(a));  // T & {} | undefined
    let a9 = ensureNotUndefined(possiblyNull(a));  // T & {} | null
}

// Repro from #48468

function deepEquals<T>(a: T, b: T): boolean {
    if (typeof a !== 'object' || typeof b !== 'object' || !a || !b) {
        return false;
    }
    if (Array.isArray(a) || Array.isArray(b)) {
        return false;
    }
    if (Object.keys(a).length !== Object.keys(b).length) { // Error here
        return false;
    }
    return true;
}

// Repro from #49386

function foo<T>(x: T | null) {
    let y = x;
    if (y !== null) {
        y;
    }
}

// We allow an unconstrained object of a generic type `T` to be indexed by a key of type `keyof T`
// without a check that the object is non-undefined and non-null. This is safe because `keyof T`
// is `never` (meaning no possible keys) for any `T` that includes `undefined` or `null`.

function ff1<T>(t: T, k: keyof T) {
    t[k];
}

function ff2<T>(t: T & {}, k: keyof T) {
    t[k];
}

function ff3<T>(t: T, k: keyof (T & {})) {
    t[k];  // Error
}

function ff4<T>(t: T & {}, k: keyof (T & {})) {
    t[k];
}

ff1(null, 'foo');  // Error
ff2(null, 'foo');  // Error
ff3(null, 'foo');
ff4(null, 'foo');  // Error

// Repro from #49681

type Foo = { [key: string]: unknown };
type NullableFoo = Foo | undefined;

type Bar<T extends NullableFoo> = NonNullable<T>[string];

// Generics and intersections with {}

function fx0<T>(value: T & ({} | null)) {
    if (value === 42) {
        value;  // T & {}
    }
    else {
        value;  // T & ({} | null)
    }
}

function fx1<T extends unknown>(value: T & ({} | null)) {
    if (value === 42) {
        value;  // T & {}
    }
    else {
        value;  // T & ({} | null)
    }
}

function fx2<T extends {}>(value: T & ({} | null)) {
    if (value === 42) {
        value;  // T & {}
    }
    else {
        value;  // T & ({} | null)
    }
}

function fx3<T extends {} | undefined>(value: T & ({} | null)) {
    if (value === 42) {
        value;  // T & {}
    }
    else {
        value;  // T & ({} | null)
    }
}

function fx4<T extends {} | null>(value: T & ({} | null)) {
    if (value === 42) {
        value;  // T & {}
    }
    else {
        value;  // T & ({} | null)
    }
}

function fx5<T extends {} | null | undefined>(value: T & ({} | null)) {
    if (value === 42) {
        value;  // T & {}
    }
    else {
        value;  // T & ({} | null)
    }
}

// Double-equals narrowing

function fx10(x: string | number, y: number) {
    if (x == y) {
        x;  // string | number
    }
    else {
        x;  // string | number
    }
    if (x != y) {
        x;  // string | number
    }
    else {
        x;  // string | number
    }
}

// Repros from #50706

function SendBlob(encoding: unknown) {
    if (encoding !== undefined && encoding !== 'utf8') {
        throw new Error('encoding');
    }
    encoding;
};

function doSomething1<T extends unknown>(value: T): T {
    if (value === undefined) {
        return value;
    }
    if (value === 42) {
        throw Error('Meaning of life value');
    }
    return value;
}

function doSomething2(value: unknown): void {
    if (value === undefined) {
        return;
    }
    if (value === 42) {
        value;
    }
}

// Repro from #51009

type TypeA = {
    A: 'A',
    B: 'B',
}

type TypeB = {
    A: 'A',
    B: 'B',
    C: 'C',
}

type R<T extends keyof TypeA> =
    T extends keyof TypeB ? [TypeA[T], TypeB[T]] : never;

type R2<T extends PropertyKey> =
    T extends keyof TypeA ? T extends keyof TypeB ? [TypeA[T], TypeB[T]] : never : never;

// Repro from #51041

type AB = "A" | "B";

function x<T_AB extends AB>(x: T_AB & undefined, y: any) {
    let r2: never = y as T_AB & undefined;
} 

// Repro from #51538

type Left = 'left';
type Right = 'right' & { right: 'right' };
type Either = Left | Right;

function assertNever(v: never): never {
    throw new Error('never');
}

function fx20(value: Either) {
    if (value === 'left') {
        const foo: 'left' = value;
    }
    else if (value === 'right') {
        const bar: 'right' = value;
    }
    else {
        assertNever(value);
    }
}
