declare opaque type O1;
declare opaque type O2;
declare opaque type P1;
declare opaque type P2;

type F1 = (x: number) => O1;
type F2 = <A>(x: Array<A>) => O1;
type F3 = (x: string) => O2;

type G1 = (x: number) => P1;
type G2 = <A>(x: Array<A>) => P1;
type G3 = (x: string) => P2;

type Foo = { foo: unknown };
type Bar = { bar: unknown };
type Baz = { baz: () => unknown };

function test1() {
  declare const foo: F1 & F2 & F3;
  const x1 = foo(1);
//           ^?
  const x2 = foo([""]);
//           ^?
  const x3 = foo("");
//           ^?
  const error = foo(true);
//              ^?
}

function test2() {
  declare const foo: F1 & (F2 & F3);

  const x1 = foo(1);
//           ^?
  const x2 = foo([""]);
//           ^?
  const x3 = foo("");
//           ^?
  const error = foo(true);
//              ^?
}

function test3() {
  declare const foo: (F1 & F2) & F3;

  const x1 = foo(1);
//           ^?
  const x2 = foo([""]);
//           ^?
  const x3 = foo("");
//           ^?
  const error = foo(true);
//              ^?
}

function test4() {
  declare const foo: F1 | G1;
  const x = foo(1);
//          ^?
}

function test5() {
  declare const foo: (F1 | G1) & F2;

  const x1 = foo(1);
//           ^?
  const x2 = foo([""]);
//           ^?
  const x2 = foo("");
//           ^?
  const error = foo(true);
//              ^?
}

function test6() {
  declare function foo<X>(x: X): X;
  const x = foo(1);
//          ^?
}

function test7() {
  declare const foo: ?(<X>(x: X) => X);
  const x = foo?.(1);
//          ^?
}

function test8() {
  declare const foo:
    | ((F1 & F2) & F3)
    | ((G1 & G2) & G3);

  const x1 = foo(1);
//           ^?
  const x2 = foo([""]);
//           ^?
  const x3 = foo("");
//           ^?
  const error = foo(true);
//              ^?
}

function test9() {
  declare class A {
    foo(x: number): O1;
    foo<A>(x: Array<A>): O1;
    foo(x: string): O2;
  }

  declare const a: A;
  const x1 = a.foo(1);
//             ^?
  const x2 = a.foo([""]);
//             ^?
  const x3 = a.foo("");
//             ^?
  const error = a.foo(true);
//                ^?
}

function test10() {
  declare class a {
    static foo(x: number): O1;
    static foo<A>(x: Array<A>): O1;
    static foo(x: string): O2;
  }

  const x1 = a.foo(1);
//             ^?
  const x2 = a.foo([""]);
//             ^?
  const x3 = a.foo("");
//             ^?
  const error = a.foo(true);
//                ^?
}

function test11() {
  declare const obj: { foo?: ?<X>(x: X) => X } ;
  const x1 = obj.foo?.(1);
//           ^?
  const x2 = obj.foo?.(1);
//               ^?
}

function test12() {
  declare const obj: {
    foo?: F1 & F2 & F3;
  };
  const x = obj.foo?.(1);
//          ^?
}

function test13() {
  declare const obj: any | { f: <V>(x: V) => V };

  const x1 = obj.f(1);
//               ^?
}

function test14() {
  declare const o1: any & { f: <V>(x: V) => V };
  declare const o2: { f: <V>(x: V) => V } & any;

  const x1 = o1.f(1); // any
//              ^?
  const x2 = o2.f(1); // number => number
//              ^?
}

function test15() {
  declare const o1: any & ?{ f: <V>(x: V) => V };
  declare const o2: ?{ f: <V>(x: V) => V } & any;

  const x1 = o1?.f(1); // any
//               ^?
  const x2 = o2?.f(1); // number => number
//               ^?
}

function test16() {
  declare const x: { f: () => {} } | void;
  x.f(); // (() => void) | any
//  ^?
}

function test17() {
  // This test handles a speculation edge case, where we call into statement.ml
  // with a non-empty speculation stack.
  declare function from<A>(iter: A): A;
  declare function from(iter: any): any;

  declare class A {
    setLabel(label: any): any;
  };
  const a = new A().setLabel("hello").setLabel(from(1));
//                  ^?
}

function test18() {
  declare const x: (Foo & Bar) & Baz;
  x.baz(); // () => mixed
//  ^?
}

function test19() {
  declare const error: unknown;
  error();
// ^?
}

function test20(
  Foo1: <T>(x: T, y: T) => void,
  Foo2: <T>(x: T, y: unknown) => void,
  Foo3: <T extends number>(x: T, y: T) => void,
  Foo4: <T, S extends string>(x: T, y: S) => void,
  Bar: (typeof Foo1 | typeof Foo2) & (typeof Foo3 | typeof Foo4),
) {
  Bar("", 1); // TODO should include type of both Foo1 and Foo2
//  ^?
}

function test_21() {
  class C<X> {
    constructor(x: X) {}
  }

  const c1 = new C(1);
//           ^?
  const c2 = new C(1);
//              ^?
  const c3 = new C(1);
//               ^?
}

function test_22() {
  declare class C<X> {
    constructor(x: X): void;
    constructor(x: X, y: X): void;
  }

  const c = new C(1, "");
//          ^?
}

function test_23() {
  declare class C<X> {
    constructor(x: X): C<string>;
  }

  const c = new C(1);
//          ^?
}

function test_24() {
  declare const C1: any
  const c1 = new C1(1);
//           ^?
  declare const C2: unknown;
  const c2 = new C2(1); // error
//           ^?
}
