// RUN: %target-swift-frontend  -strict-concurrency=complete -target %target-swift-5.1-abi-triple -parse-as-library -emit-sil -o /dev/null -verify %s -enable-upcoming-feature GlobalActorIsolatedTypesUsability
// RUN: %target-swift-frontend  -strict-concurrency=complete -target %target-swift-5.1-abi-triple -parse-as-library -emit-sil -o /dev/null -verify %s -enable-upcoming-feature GlobalActorIsolatedTypesUsability -enable-upcoming-feature NonisolatedNonsendingByDefault

// REQUIRES: concurrency
// REQUIRES: swift_feature_GlobalActorIsolatedTypesUsability
// REQUIRES: swift_feature_NonisolatedNonsendingByDefault

/*
 This file tests the early features of the flow-sensitive Sendable checking implemented by the TransferNonSendable SIL pass.
 In particular, it tests the behavior of applications that cross isolation domains:
 - non-Sendable values are statically grouped into "regions" of values that may alias or point to each other
 - passing a non-Sendable value to an isolation-crossing call "consumed" the region of that value
 - accessing a non-Sendable value requires its region to have not been consumed

 All non-Sendable arguments, and "self" for non-Sendable classes, are assumed to be in the same region at the start of
 each function, and that region must be preserved at all times - it cannot be passed to another isolation domain.
 */

class NonSendable { // expected-complete-note 73{{}}
  var x : Any
  var y : Any

  init() {
    x = 0;
    y = 0;
  }

  init(_ z : Any) {
    x = z;
    y = z;
  }
}

actor A {
  func run_closure(_ closure : () -> ()) async {
    closure();
  }

  func foo(_ ns : Any) {}

  func foo_multi(_ : Any, _ : Any, _ : Any) {}

  func foo_vararg(_ : Any ...) {}
}

func foo_noniso(_ ns : Any) {}

func foo_noniso_multi(_ : Any, _ : Any, _ : Any) {}

func foo_noniso_vararg(_ : Any ...) {}

var bool : Bool { false }

func test_isolation_crossing_sensitivity(a : A) async {
  let ns0 = NonSendable();
  let ns1 = NonSendable();

  // This call does not consume ns0
  foo_noniso(ns0);

  // This call consumes ns1
  await a.foo(ns1); // expected-warning {{sending 'ns1' risks causing data races}}
  // expected-note @-1 {{sending 'ns1' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
  // expected-complete-warning @-2 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}

  print(ns0);
  print(ns1); // expected-note {{access can happen concurrently}}
}

func test_arg_nonconsumable(a : A, ns_arg : NonSendable) async {
  let ns_let = NonSendable();

  // Safe to consume an rvalue.
  await a.foo(NonSendable()); // expected-complete-warning {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}

  // Safe to consume an lvalue.
  await a.foo(ns_let); // expected-complete-warning {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}

  // Not safe to consume an arg.
  await a.foo(ns_arg); // expected-warning {{sending 'ns_arg' risks causing data races}}
  // expected-note @-1 {{sending task-isolated 'ns_arg' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and task-isolated uses}}
  // expected-complete-warning @-2 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}

  // Check for no duplicate warnings once self is "consumed"
  await a.foo(NonSendable()); // expected-complete-warning {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}
}

func test_closure_capture(a : A) async {
  let ns0 = NonSendable();
  let ns1 = NonSendable();
  let ns2 = NonSendable();
  let ns3 = NonSendable();

  let captures0 = { print(ns0) };
  let captures12 = { print(ns1); print(ns2)};

  let captures3 = { print(ns3) };
  let captures3indirect = { captures3() };

  // all lets should still be accessible - no consumed have happened yet
  print(ns0)
  print(ns1)
  print(ns2)
  print(ns3)

  // this should consume ns0
  await a.run_closure(captures0)
  // expected-warning @-1 {{sending 'captures0' risks causing data races}}
  // expected-note @-2 {{sending 'captures0' to actor-isolated instance method 'run_closure' risks causing data races between actor-isolated and local nonisolated uses}}

  // expected-complete-warning @-1 {{passing argument of non-Sendable type '() -> ()' into actor-isolated context may introduce data races}}
  // expected-complete-note @-2 {{a function type must be marked '@Sendable' to conform to 'Sendable'}}

  print(ns0) // expected-note {{access can happen concurrently}}
  print(ns1)
  print(ns2)
  print(ns3)

  // this should consume ns1 and ns2
  await a.run_closure(captures12)
  // expected-warning @-1 {{sending 'captures12' risks causing data races}}
  // expected-note @-2 {{sending 'captures12' to actor-isolated instance method 'run_closure' risks causing data races between actor-isolated and local nonisolated uses}}
  // expected-complete-warning @-1 {{passing argument of non-Sendable type '() -> ()' into actor-isolated context may introduce data races}}
  // expected-complete-note @-2 {{a function type must be marked '@Sendable' to conform to 'Sendable'}}

  // This only touches ns1/ns2 so we get an error on ns1 since it is first.
  print(ns0)
  print(ns1) // expected-note {{access can happen concurrently}}
  print(ns2)
  print(ns3)

  // this should consume ns3
  await a.run_closure(captures3indirect)
  // expected-warning @-1 {{sending 'captures3indirect' risks causing data races}}
  // expected-note @-2 {{sending 'captures3indirect' to actor-isolated instance method 'run_closure' risks causing data races between actor-isolated and local nonisolated uses}}
  // expected-complete-warning @-1 {{passing argument of non-Sendable type '() -> ()' into actor-isolated context may introduce data races}}
  // expected-complete-note @-2 {{a function type must be marked '@Sendable' to conform to 'Sendable'}}

  // This only uses ns3 so we should only emit an error on ns3.
  print(ns0)
  print(ns1)
  print(ns2)
  print(ns3) // expected-note {{access can happen concurrently}}
}

func test_regions(a : A, b : Bool) async {
  // definitely aliases - should be in the same region
  let ns0_0 = NonSendable();
  let ns0_1 = ns0_0;

  // possibly aliases - should be in the same region
  let ns1_0 = NonSendable();
  var ns1_1 = NonSendable();
  if (b) {
    ns1_1 = ns1_0;
  }

  // accessible via pointer - should be in the same region
  let ns2_0 = NonSendable();
  let ns2_1 = NonSendable();
  ns2_0.x = ns2_1;

  // check for each of the above pairs that consuming half of it consumes the other half

  if (b) {
    await a.foo(ns0_0) // expected-warning {{sending 'ns0_0' risks causing data races}}
    // expected-note @-1 {{sending 'ns0_0' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
    // expected-complete-warning @-2 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}

    if (b) {
      print(ns0_0) // expected-note {{access can happen concurrently}}
    } else {
      print(ns0_1) // expected-note {{access can happen concurrently}}
    }
  } else {
    await a.foo(ns0_1) // expected-warning {{sending 'ns0_1' risks causing data races}}
    // expected-note @-1 {{sending 'ns0_1' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
    // expected-complete-warning @-2 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}

    if b {
      print(ns0_0) // expected-note {{access can happen concurrently}}
    } else {
      print(ns0_1) // expected-note {{access can happen concurrently}}
    }
  }

  if (b) {
    await a.foo(ns1_0) // expected-warning {{sending 'ns1_0' risks causing data races}}
    // expected-note @-1 {{sending 'ns1_0' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
    // expected-complete-warning @-2 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}

    if b {
      print(ns1_0) // expected-note {{access can happen concurrently}}
    } else {
      print(ns1_1) // expected-note {{access can happen concurrently}}
    }
  } else {
    await a.foo(ns1_1) // expected-warning {{sending 'ns1_1' risks causing data races}}
    // expected-note @-1 {{sending 'ns1_1' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
    // expected-complete-warning @-2 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}

    if b {
      print(ns1_0) // expected-note {{access can happen concurrently}}
    } else {
      print(ns1_1) // expected-note {{access can happen concurrently}}
    }
  }

  if (b) {
    await a.foo(ns2_0) // expected-warning {{sending 'ns2_0' risks causing data races}}
    // expected-note @-1 {{sending 'ns2_0' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
    // expected-complete-warning @-2 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}

    if b {
      print(ns2_0) // expected-note {{access can happen concurrently}}
    } else {
      print(ns2_1) // expected-note {{access can happen concurrently}}
    }
  } else {
    await a.foo(ns2_1) // expected-warning {{sending 'ns2_1' risks causing data races}}
    // expected-note @-1 {{sending 'ns2_1' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
    // expected-complete-warning @-2 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}

    if b {
      print(ns2_0) // expected-note {{access can happen concurrently}}
    } else {
      print(ns2_1) // expected-note {{access can happen concurrently}}
    }
  }
}

func test_indirect_regions(a : A, b : Bool) async {
  // in each of the following, nsX_0 and nsX_1 should be in the same region for various reasons

  // 1 constructed using 0
  let ns0_0 = NonSendable();
  let ns0_1 = NonSendable(NonSendable(ns0_0));

  // 1 constructed using 0, indirectly
  let ns1_0 = NonSendable();
  let ns1_aux = NonSendable(ns1_0);
  let ns1_1 = NonSendable(ns1_aux);

  // 1 and 0 constructed with same aux value
  let ns2_aux = NonSendable();
  let ns2_0 = NonSendable(ns2_aux);
  let ns2_1 = NonSendable(ns2_aux);

  // 0 points to aux, which points to 1
  let ns3_aux = NonSendable();
  let ns3_0 = NonSendable();
  let ns3_1 = NonSendable();
  ns3_0.x = ns3_aux;
  ns3_aux.x = ns3_1;

  // 1 and 0 point to same aux value
  let ns4_aux = NonSendable();
  let ns4_0 = NonSendable();
  let ns4_1 = NonSendable();
  ns4_0.x = ns4_aux;
  ns4_1.x = ns4_aux;

  // same aux value points to both 0 and 1
  let ns5_aux = NonSendable();
  let ns5_0 = ns5_aux.x;
  let ns5_1 = ns5_aux.y;

  // now check for each pair that consuming half of it consumed the other half

  if (b) {
    await a.foo(ns0_0) // expected-warning {{sending 'ns0_0' risks causing data races}}
    // expected-note @-1 {{sending 'ns0_0' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
    // expected-complete-warning @-2 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}

    if b {
      print(ns0_0) // expected-note {{access can happen concurrently}}
    } else {
      print(ns0_1) // expected-note {{access can happen concurrently}}
    }
  } else {
    await a.foo(ns0_1) // expected-warning {{sending 'ns0_1' risks causing data races}}
    // expected-note @-1 {{sending 'ns0_1' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
    // expected-complete-warning @-2 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}

    if b {
      print(ns0_0) // expected-note {{access can happen concurrently}}
    } else {
      print(ns0_1) // expected-note {{access can happen concurrently}}
    }
  }

  if (b) {
    await a.foo(ns1_0) // expected-warning {{sending 'ns1_0' risks causing data races}}
    // expected-note @-1 {{sending 'ns1_0' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
    // expected-complete-warning @-2 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}

    if b {
      print(ns1_0) // expected-note {{access can happen concurrently}}
    } else {
      print(ns1_1) // expected-note {{access can happen concurrently}}
    }
  } else {
    await a.foo(ns1_1) // expected-warning {{sending 'ns1_1' risks causing data races}}
    // expected-note @-1 {{sending 'ns1_1' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
    // expected-complete-warning @-2 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}

    if b {
      print(ns1_0) // expected-note {{access can happen concurrently}}
    } else {
      print(ns1_1) // expected-note {{access can happen concurrently}}
    }
  }

  if (b) {
    await a.foo(ns2_0) // expected-warning {{sending 'ns2_0' risks causing data races}}
    // expected-note @-1 {{sending 'ns2_0' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
    // expected-complete-warning @-2 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}

    if b {
      print(ns2_0) // expected-note {{access can happen concurrently}}
    } else {
      print(ns2_1) // expected-note {{access can happen concurrently}}
    }
  } else {
    await a.foo(ns2_1) // expected-warning {{sending 'ns2_1' risks causing data races}}
    // expected-note @-1 {{sending 'ns2_1' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
    // expected-complete-warning @-2 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}

    if b {
      print(ns2_0) // expected-note {{access can happen concurrently}}
    } else {
      print(ns2_1) // expected-note {{access can happen concurrently}}
    }
  }

  if (b) {
    await a.foo(ns3_0) // expected-warning {{sending 'ns3_0' risks causing data races}}
    // expected-note @-1 {{sending 'ns3_0' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
    // expected-complete-warning @-2 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}

    if b {
      print(ns3_0) // expected-note {{access can happen concurrently}}
    } else {
      print(ns3_1) // expected-note {{access can happen concurrently}}
    }
  } else {
    await a.foo(ns3_1) // expected-warning {{sending 'ns3_1' risks causing data races}}
    // expected-note @-1 {{sending 'ns3_1' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
    // expected-complete-warning @-2 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}

    if b {
      print(ns3_0) // expected-note {{access can happen concurrently}}
    } else {
      print(ns3_1) // expected-note {{access can happen concurrently}}
    }
  }

  if (b) {
    await a.foo(ns4_0) // expected-warning {{sending 'ns4_0' risks causing data races}}
    // expected-note @-1 {{sending 'ns4_0' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
    // expected-complete-warning @-2 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}

    if b {
      print(ns4_0) // expected-note {{access can happen concurrently}}
    } else {
      print(ns4_1) // expected-note {{access can happen concurrently}}
    }
  } else {
    await a.foo(ns4_1) // expected-warning {{sending 'ns4_1' risks causing data races}}
    // expected-note @-1 {{sending 'ns4_1' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
    // expected-complete-warning @-2 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}

    if b {
      print(ns4_0) // expected-note {{access can happen concurrently}}
    } else {
      print(ns4_1) // expected-note {{access can happen concurrently}}
    }
  }

  if (b) {
    await a.foo(ns5_0) // expected-warning {{sending 'ns5_0' risks causing data races}}
    // expected-note @-1 {{sending 'ns5_0' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
    // expected-complete-warning @-2 {{passing argument of non-Sendable type 'Any' into actor-isolated context may introduce data races}}

    if b {
      print(ns5_0) // expected-note {{access can happen concurrently}}
    } else {
      print(ns5_1) // expected-note {{access can happen concurrently}}
    }
  } else {
    await a.foo(ns5_1) // expected-warning {{sending 'ns5_1' risks causing data races}}
    // expected-note @-1 {{sending 'ns5_1' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
    // expected-complete-warning @-2 {{passing argument of non-Sendable type 'Any' into actor-isolated context may introduce data races}}

    if b {
      print(ns5_0) // expected-note {{access can happen concurrently}}
    } else {
      print(ns5_1) // expected-note {{access can happen concurrently}}
    }
  }
}

// class methods in general cannot consume self, check that this is true except for Sendable classes (and actors)

class C_NonSendable {
  func bar() {}

  func bar(a : A) async {
    let captures_self = { self.bar() }

    // this is not a cross-isolation call, so it should be permitted
    foo_noniso(captures_self)

    // this is a cross-isolation call that captures non-Sendable self, so it should not be permitted
    await a.foo(captures_self) // expected-warning {{sending 'captures_self' risks causing data races}}
    // expected-note @-1 {{sending task-isolated 'captures_self' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and task-isolated uses}}
    // expected-complete-warning @-2 {{passing argument of non-Sendable type '() -> ()' into actor-isolated context may introduce data races}}
    // expected-complete-note @-3 {{a function type must be marked '@Sendable' to conform to 'Sendable'}}
  }
}

final class C_Sendable : Sendable {
  func bar() {}

  func bar(a : A) async {
    let captures_self = { self.bar() }

    // this is not a cross-isolation call, so it should be permitted
    foo_noniso(captures_self)

    // this is a cross-isolation, but self is Sendable, so it should be permitted
    await a.foo(captures_self)
    // expected-complete-warning @-1 {{passing argument of non-Sendable type '() -> ()' into actor-isolated context may introduce data races}}
    // expected-complete-note @-2 {{a function type must be marked '@Sendable' to conform to 'Sendable'}}
  }
}

actor A_Sendable {
  func bar() {}

  func bar(a : A) async {
    let captures_self = { self.bar() }

    // This is not a cross-isolation call, so it should be permitted
    foo_noniso(captures_self)

    // This is a cross-isolation call passing a call that should be treated
    // as isolated to self since it captures self which is isolated to our
    // actor and is non-Sendable. For now, we ban this since we do not
    // support the ability to dynamically invoke the synchronous closure on
    // the specific actor.
    await a.foo(captures_self) // expected-warning {{sending 'captures_self' risks causing data races}}
    // expected-note @-1 {{sending 'self'-isolated 'captures_self' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and 'self'-isolated uses}}
    // expected-complete-warning @-2 {{passing argument of non-Sendable type '() -> ()' into actor-isolated context may introduce data races}}
    // expected-complete-note @-3 {{a function type must be marked '@Sendable' to conform to 'Sendable'}}
  }
}

func basic_loopiness(a : A, b : Bool) async {
  let ns = NonSendable()

  while (b) {
    await a.foo(ns) // expected-warning {{sending 'ns' risks causing data races}}
    // expected-note @-1 {{sending 'ns' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
    // expected-note @-2 {{access can happen concurrently}}
    // expected-complete-warning @-3 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}
  }
}

func basic_loopiness_unsafe(a : A, b : Bool) async {
  var ns0 = NonSendable()
  var ns1 = NonSendable()
  var ns2 = NonSendable()
  var ns3 = NonSendable()

  //should merge all vars
  while (b) {
    (ns0, ns1, ns2, ns3) = (ns1, ns2, ns3, ns0)
  }

  await a.foo(ns0) // expected-warning {{sending 'ns0' risks causing data races}}
  // expected-note @-1 {{sending 'ns0' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
  // expected-complete-warning @-2 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}
  await a.foo(ns3) // expected-note {{access can happen concurrently}}
  // expected-complete-warning @-1 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}
}

func basic_loopiness_safe(a : A, b : Bool) async {
  var ns0 = NonSendable()
  var ns1 = NonSendable()
  var ns2 = NonSendable()
  var ns3 = NonSendable()

  //should keep ns0 and ns3 separate
  while (b) {
    (ns0, ns1, ns2, ns3) = (ns1, ns0, ns3, ns2)
  }

  await a.foo(ns0)
  // expected-complete-warning @-1 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}
  await a.foo(ns3)
  // expected-complete-warning @-1 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}
}

struct StructBox {
  var contents : NonSendable

  init() {
    contents = NonSendable()
  }
}

func test_struct_assign_doesnt_merge(a : A, b : Bool) async {
  let ns0 = NonSendable()
  let ns1 = NonSendable()

  //this should merge ns0 and ns1
  var box = StructBox()
  box.contents = ns0
  box.contents = ns1

  await a.foo(ns0)
  // expected-complete-warning @-1 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}
  await a.foo(ns1)
  // expected-complete-warning @-1 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}
}

class ClassBox {
  var contents : NonSendable

  init() {
    contents = NonSendable()
  }
}

func test_class_assign_merges(a : A, b : Bool) async {
  let ns0 = NonSendable()
  let ns1 = NonSendable()

  // This should merge ns0 and ns1.
  var box = ClassBox()
  box = ClassBox()
  box.contents = ns0
  box.contents = ns1

  await a.foo(ns0) // expected-warning {{sending 'ns0' risks causing data races}}
  // expected-note @-1 {{sending 'ns0' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
  // expected-complete-warning @-2 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}
  await a.foo(ns1) // expected-note {{access can happen concurrently}}
  // expected-complete-warning @-1 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}
}

func test_stack_assign_doesnt_merge(a : A, b : Bool) async {
  let ns0 = NonSendable()
  let ns1 = NonSendable()

  //this should NOT merge ns0 and ns1
  var contents : NonSendable
  contents = ns0
  contents = ns1

  foo_noniso(contents) //must use var to avoid warning

  await a.foo(ns0)
  // expected-complete-warning @-1 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}
  await a.foo(ns1)
  // expected-complete-warning @-1 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}
}

func test_stack_assign_and_capture_merges(a : A, b : Bool) async {
  let ns0 = NonSendable()
  let ns1 = NonSendable()

  //this should NOT merge ns0 and ns1
  var contents = NonSendable()

  let closure = { print(contents) }
  foo_noniso(closure) //must use let to avoid warning

  contents = ns0
  contents = ns1

  await a.foo(ns0) // expected-warning {{sending 'ns0' risks causing data races}}
  // expected-note @-1 {{sending 'ns0' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
  // expected-complete-warning @-2 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}
  await a.foo(ns1) // expected-note {{access can happen concurrently}}
  // expected-complete-warning @-1 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}
}

func test_tuple_formation(a : A, i : Int) async {
  let ns0 = NonSendable();
  let ns1 = NonSendable();
  let ns2 = NonSendable();
  let ns3 = NonSendable();
  let ns4 = NonSendable();
  let ns012 = (ns0, ns1, ns2);
  let ns13 = (ns1, ns3);

  switch (i) {
  case 0:
    await a.foo(ns0) // expected-warning {{sending 'ns0' risks causing data races}}
    // expected-note @-1 {{sending 'ns0' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
    // expected-complete-warning @-2 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}

    if bool {
      foo_noniso(ns0); // expected-note {{access can happen concurrently}}
    } else if bool {
      foo_noniso(ns1); // expected-note {{access can happen concurrently}}
    } else if bool {
      foo_noniso(ns2); // expected-note {{access can happen concurrently}}
    } else if bool {
      foo_noniso(ns3); // expected-note {{access can happen concurrently}}
    } else if bool {
      foo_noniso(ns4);
    } else if bool {
      foo_noniso(ns012); // expected-note {{access can happen concurrently}}
    } else {
      foo_noniso(ns13); // expected-note {{access can happen concurrently}}
    }
  case 1:
    await a.foo(ns1) // expected-warning {{sending 'ns1' risks causing data races}}
    // expected-note @-1 {{sending 'ns1' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
    // expected-complete-warning @-2 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}

    if bool {
      foo_noniso(ns0); // expected-note {{access can happen concurrently}}
    } else if bool {
      foo_noniso(ns1); // expected-note {{access can happen concurrently}}
    } else if bool {
      foo_noniso(ns2); // expected-note {{access can happen concurrently}}
    } else if bool {
      foo_noniso(ns3); // expected-note {{access can happen concurrently}}
    } else if bool {
      foo_noniso(ns4);
    } else if bool {
      foo_noniso(ns012); // expected-note {{access can happen concurrently}}
    } else {
      foo_noniso(ns13); // expected-note {{access can happen concurrently}}
    }
  case 2:
    await a.foo(ns2) // expected-warning {{sending 'ns2' risks causing data races}}
    // expected-note @-1 {{sending 'ns2' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
    // expected-complete-warning @-2 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}

    if bool {
      foo_noniso(ns0); // expected-note {{access can happen concurrently}}
    } else if bool {
      foo_noniso(ns1); // expected-note {{access can happen concurrently}}
    } else if bool {
      foo_noniso(ns2); // expected-note {{access can happen concurrently}}
    } else if bool {
      foo_noniso(ns3); // expected-note {{access can happen concurrently}}
    } else if bool {
      foo_noniso(ns4);
    } else if bool {
      foo_noniso(ns012); // expected-note {{access can happen concurrently}}
    } else {
      foo_noniso(ns13); // expected-note {{access can happen concurrently}}
    }
  case 3:
    await a.foo(ns4) // expected-warning {{sending 'ns4' risks causing data races}}
    // expected-note @-1 {{sending 'ns4' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
    // expected-complete-warning @-2 {{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}

    foo_noniso(ns0);
    foo_noniso(ns1);
    foo_noniso(ns2);
    foo_noniso(ns4); // expected-note {{access can happen concurrently}}
    foo_noniso(ns012);
    foo_noniso(ns13);
  case 4:
    await a.foo(ns012) // expected-warning {{sending value of non-Sendable type '(NonSendable, NonSendable, NonSendable)' risks causing data races}}
    // expected-note @-1 {{sending value of non-Sendable type '(NonSendable, NonSendable, NonSendable)' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
    // expected-complete-warning @-2 3{{passing argument of non-Sendable type '(NonSendable, NonSendable, NonSendable)' into actor-isolated context may introduce data races}}

    if bool {
      foo_noniso(ns0); // expected-note {{access can happen concurrently}}
    } else if bool {
      foo_noniso(ns1); // expected-note {{access can happen concurrently}}
    } else if bool {
      foo_noniso(ns2); // expected-note {{access can happen concurrently}}
    } else if bool {
      foo_noniso(ns3); // expected-note {{access can happen concurrently}}
    } else if bool {
      foo_noniso(ns4);
    } else if bool {
      foo_noniso(ns012); // expected-note {{access can happen concurrently}}
    } else {
      foo_noniso(ns13); // expected-note {{access can happen concurrently}}
    }
  default:
      await a.foo(ns13) // expected-warning {{sending value of non-Sendable type '(NonSendable, NonSendable)' risks causing data races}}
      // expected-note @-1 {{sending value of non-Sendable type '(NonSendable, NonSendable)' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
    // expected-complete-warning @-1 2{{passing argument of non-Sendable type '(NonSendable, NonSendable)' into actor-isolated context may introduce data races}}

    if bool {
      foo_noniso(ns0); // expected-note {{access can happen concurrently}}
    } else if bool {
      foo_noniso(ns1); // expected-note {{access can happen concurrently}}
    } else if bool {
      foo_noniso(ns2); // expected-note {{access can happen concurrently}}
    } else if bool {
      foo_noniso(ns3); // expected-note {{access can happen concurrently}}
    } else if bool {
      foo_noniso(ns4);
    } else if bool {
      foo_noniso(ns012); // expected-note {{access can happen concurrently}}
    } else  {
      foo_noniso(ns13); // expected-note {{access can happen concurrently}}
    }
  }
}

func reuse_args_safe(a : A) async {
  let ns0 = NonSendable();
  let ns1 = NonSendable();
  let ns2 = NonSendable();

  // this should be perfectly legal - arguments are assumed to come from the same region
  await a.foo_multi(ns0, ns0, ns0);
  // expected-complete-warning @-1 3{{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}
  await a.foo_multi(ns1, ns2, ns1);
  // expected-complete-warning @-1 3{{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}
}

func one_consume_many_require(a : A) async {
  let ns0 = NonSendable();
  let ns1 = NonSendable();
  let ns2 = NonSendable();
  let ns3 = NonSendable();
  let ns4 = NonSendable();

  await a.foo_multi(ns0, ns1, ns2);
  // expected-complete-warning @-1 3{{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}
  // expected-warning @-2 {{sending 'ns0' risks causing data races}}
  // expected-note @-3 {{sending 'ns0' to actor-isolated instance method 'foo_multi' risks causing data races between actor-isolated and local nonisolated uses}}
  // expected-warning @-4 {{sending 'ns1' risks causing data races}}
  // expected-note @-5 {{sending 'ns1' to actor-isolated instance method 'foo_multi' risks causing data races between actor-isolated and local nonisolated uses}}
  // expected-warning @-6 {{sending 'ns2' risks causing data races}}
  // expected-note @-7 {{sending 'ns2' to actor-isolated instance method 'foo_multi' risks causing data races between actor-isolated and local nonisolated uses}}

  foo_noniso_multi(ns0, ns3, ns4); // expected-note {{access can happen concurrently}}
  foo_noniso_multi(ns3, ns1, ns4); // expected-note {{access can happen concurrently}}
  foo_noniso_multi(ns4, ns3, ns2); // expected-note {{access can happen concurrently}}
}

func one_consume_one_require(a : A) async {
  let ns0 = NonSendable();
  let ns1 = NonSendable();
  let ns2 = NonSendable();

  await a.foo_multi(ns0, ns1, ns2);
  // expected-complete-warning @-1 3{{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}
  // expected-warning @-2 {{sending 'ns0' risks causing data races}}
  // expected-note @-3 {{sending 'ns0' to actor-isolated instance method 'foo_multi' risks causing data races between actor-isolated and local nonisolated uses}}
  // expected-warning @-4 {{sending 'ns1' risks causing data races}}
  // expected-note @-5 {{sending 'ns1' to actor-isolated instance method 'foo_multi' risks causing data races between actor-isolated and local nonisolated uses}}
  // expected-warning @-6 {{sending 'ns2' risks causing data races}}
  // expected-note @-7 {{sending 'ns2' to actor-isolated instance method 'foo_multi' risks causing data races between actor-isolated and local nonisolated uses}}

  foo_noniso_multi(ns0, ns1, ns2); // expected-note 3{{access can happen concurrently}}
}

func many_consume_one_require(a : A) async {
  let ns0 = NonSendable();
  let ns1 = NonSendable();
  let ns2 = NonSendable();
  let ns3 = NonSendable();
  let ns4 = NonSendable();
  let ns5 = NonSendable();

  await a.foo_multi(ns0, ns3, ns3) // expected-warning {{sending 'ns0' risks causing data races}}
  // expected-note @-1 {{sending 'ns0' to actor-isolated instance method 'foo_multi' risks causing data races between actor-isolated and local nonisolated uses}}
  // expected-complete-warning @-2 3{{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}
  await a.foo_multi(ns4, ns1, ns4) // expected-warning {{sending 'ns1' risks causing data races}}
  // expected-note @-1 {{sending 'ns1' to actor-isolated instance method 'foo_multi' risks causing data races between actor-isolated and local nonisolated uses}}
  // expected-complete-warning @-2 3{{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}
  await a.foo_multi(ns5, ns5, ns2) // expected-warning {{sending 'ns2' risks causing data races}}
  // expected-note @-1 {{sending 'ns2' to actor-isolated instance method 'foo_multi' risks causing data races between actor-isolated and local nonisolated uses}}
  // expected-complete-warning @-2 3{{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}
  foo_noniso_multi(ns0, ns1, ns2); //expected-note 3{{access can happen concurrently}}
}

func many_consume_many_require(a : A) async {
  let ns0 = NonSendable();
  let ns1 = NonSendable();
  let ns2 = NonSendable();
  let ns3 = NonSendable();
  let ns4 = NonSendable();
  let ns5 = NonSendable();
  let ns6 = NonSendable();
  let ns7 = NonSendable();

  await a.foo_multi(ns0, ns3, ns3) // expected-warning {{sending 'ns0' risks causing data races}}
  // expected-note @-1 {{sending 'ns0' to actor-isolated instance method 'foo_multi' risks causing data races between actor-isolated and local nonisolated uses}}
  // expected-complete-warning @-2 3{{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}
  await a.foo_multi(ns4, ns1, ns4) // expected-warning {{sending 'ns1' risks causing data races}}
  // expected-note @-1 {{sending 'ns1' to actor-isolated instance method 'foo_multi' risks causing data races between actor-isolated and local nonisolated uses}}
  // expected-complete-warning @-2 3{{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}
  await a.foo_multi(ns5, ns5, ns2) // expected-warning {{sending 'ns2' risks causing data races}}
  // expected-note @-1 {{sending 'ns2' to actor-isolated instance method 'foo_multi' risks causing data races between actor-isolated and local nonisolated uses}}
  // expected-complete-warning @-2 3{{passing argument of non-Sendable type 'NonSendable' into actor-isolated context may introduce data races}}

  foo_noniso_multi(ns0, ns6, ns7); // expected-note {{access can happen concurrently}}
  foo_noniso_multi(ns6, ns1, ns7); // expected-note {{access can happen concurrently}}
  foo_noniso_multi(ns7, ns6, ns2); // expected-note {{access can happen concurrently}}
}


func reuse_args_safe_vararg(a : A) async {
  let ns0 = NonSendable();
  let ns1 = NonSendable();
  let ns2 = NonSendable();

  // this should be perfectly legal - arguments are assumed to come from the same region
  await a.foo_vararg(ns0, ns0, ns0);
  // expected-complete-warning @-1 {{passing argument of non-Sendable type 'Any...' into actor-isolated context may introduce data races}}
  await a.foo_vararg(ns1, ns2, ns1);
  // expected-complete-warning @-1 {{passing argument of non-Sendable type 'Any...' into actor-isolated context may introduce data races}}
}

  
// The varargs tests are currently disabled because they fail on macos.
// TODO: fix handling of Array initialization in the SendNonSenable pass

/*
func one_consume_many_require_varag(a : A) async {
  let ns0 = NonSendable();
  let ns1 = NonSendable();
  let ns2 = NonSendable();
  let ns3 = NonSendable();
  let ns4 = NonSendable();

  // TODO: find a way to make the type used in the diagnostic more specific than the signature type
  await a.foo_vararg(ns0, ns1, ns2);
  // xxpected-warning @-1 {{sending value of non-Sendable type 'Any...' risks causing data races}}
  // xxpected-note @-2 {{sending value of non-Sendable type 'Any...' to actor-isolated instance method 'foo_vararg' risks causing data races between actor-isolated and local nonisolated uses}}
  // xxpected-complete-warning @-2 {{passing argument of non-Sendable type 'Any...' into actor-isolated context may introduce data races}}

  if bool {
    foo_noniso_vararg(ns0, ns3, ns4); // xxpected-note {{access can happen concurrently}}
  } else if bool {
    foo_noniso_vararg(ns3, ns1, ns4); // xxpected-note {{access can happen concurrently}}
  } else {
    foo_noniso_vararg(ns4, ns3, ns2); // xxpected-note {{access can happen concurrently}}
  }
}

func one_consume_one_require_vararg(a : A) async {
  let ns0 = NonSendable();
  let ns1 = NonSendable();
  let ns2 = NonSendable();

  await a.foo_vararg(ns0, ns1, ns2);
  // xxpected-warning @-1 {{sending value of non-Sendable type 'Any...' risks causing data races}}
  // xxpected-note @-2 {{sending value of non-Sendable type 'Any...' to actor-isolated instance method 'foo_vararg' risks causing data races between actor-isolated and local nonisolated uses}}
  // xxpected-complete-warning @-2 {{passing argument of non-Sendable type 'Any...' into actor-isolated context may introduce data races}}

  foo_noniso_vararg(ns0, ns1, ns2); // xxpected-note 1{{access can happen concurrently}}
}

func many_consume_one_require_vararg(a : A) async {
  let ns0 = NonSendable();
  let ns1 = NonSendable();
  let ns2 = NonSendable();
  let ns3 = NonSendable();
  let ns4 = NonSendable();
  let ns5 = NonSendable();

  await a.foo_vararg(ns0, ns3, ns3)
  // xxpected-warning @-1 {{sending value of non-Sendable type 'Any...' risks causing data races}}
  // xxpected-note @-2 {{sending value of non-Sendable type 'Any...' to actor-isolated instance method 'foo_vararg' risks causing data races between actor-isolated and local nonisolated uses}}
  // xxpected-complete-warning @-2 {{passing argument of non-Sendable type 'Any...' into actor-isolated context may introduce data races}}
  await a.foo_vararg(ns4, ns1, ns4)
  // xxpected-warning @-1 {{sending value of non-Sendable type 'Any...' risks causing data races}}
  // xxpected-note @-2 {{sending value of non-Sendable type 'Any...' to actor-isolated instance method 'foo_vararg' risks causing data races between actor-isolated and local nonisolated uses}}
  // xxpected-complete-warning @-2 {{passing argument of non-Sendable type 'Any...' into actor-isolated context may introduce data races}}
  await a.foo_vararg(ns5, ns5, ns2)
  // xxpected-warning @-1 {{sending value of non-Sendable type 'Any...' risks causing data races}}
  // xxpected-note @-2 {{sending value of non-Sendable type 'Any...' to actor-isolated instance method 'foo_vararg' risks causing data races between actor-isolated and local nonisolated uses}}
  // xxpected-complete-warning @-2 {{passing argument of non-Sendable type 'Any...' into actor-isolated context may introduce data races}}

  foo_noniso_vararg(ns0, ns1, ns2); // xxpected-note 3{{access can happen concurrently}}
}

func many_consume_many_require_vararg(a : A) async {
  let ns0 = NonSendable();
  let ns1 = NonSendable();
  let ns2 = NonSendable();
  let ns3 = NonSendable();
  let ns4 = NonSendable();
  let ns5 = NonSendable();
  let ns6 = NonSendable();
  let ns7 = NonSendable();

  await a.foo_vararg(ns0, ns3, ns3)
  // xxpected-warning @-1 {{sending value of non-Sendable type 'Any...' risks causing data races}}
  // xxpected-note @-2 {{sending value of non-Sendable type 'Any...' to actor-isolated instance method 'foo_vararg' risks causing data races between actor-isolated and local nonisolated uses}}
  // xxpected-complete-warning @-2 {{passing argument of non-Sendable type 'Any...' into actor-isolated context may introduce data races}}
  await a.foo_vararg(ns4, ns1, ns4)
  // xxpected-warning @-1 {{sending value of non-Sendable type 'Any...' risks causing data races}}
  // xxpected-note @-2 {{sending value of non-Sendable type 'Any...' to actor-isolated instance method 'foo_vararg' risks causing data races between actor-isolated and local nonisolated uses}}
  // xxpected-complete-warning @-2 {{passing argument of non-Sendable type 'Any...' into actor-isolated context may introduce data races}}
  await a.foo_vararg(ns5, ns5, ns2)
  // xxpected-warning @-1 {{sending value of non-Sendable type 'Any...' risks causing data races}}
  // xxpected-note @-2 {{sending value of non-Sendable type 'Any...' to actor-isolated instance method 'foo_vararg' risks causing data races between actor-isolated and local nonisolated uses}}
  // xxpected-complete-warning @-2 {{passing argument of non-Sendable type 'Any...' into actor-isolated context may introduce data races}}

  if bool {
    foo_noniso_vararg(ns0, ns6, ns7); // xxpected-note {{access can happen concurrently}}
  } else if bool {
    foo_noniso_vararg(ns6, ns1, ns7);  // xxpected-note {{access can happen concurrently}}
  } else {
    foo_noniso_vararg(ns7, ns6, ns2);  // xxpected-note {{access can happen concurrently}}
  }
}
*/
 
enum E { // expected-complete-note {{consider making enum 'E' conform to the 'Sendable' protocol}}
  case E1(NonSendable)
  case E2(NonSendable)
  case E3(NonSendable)
}

func enum_test(a : A) async {
  let e1 = E.E1(NonSendable())
  let e2 = E.E2(NonSendable())
  let e3 = E.E3(NonSendable())
  let e4 = E.E1(NonSendable())

  switch (e1) {

    //this case merges e1 and e2
  case let .E1(ns1):
    switch (e2) {
    case let .E2(ns2):
      ns1.x = ns2.x
    default: ()
    }

    //this case consumes e3
    case .E2:
      switch (e3) {
      case let .E3(ns3):
        await a.foo(ns3.x) // expected-warning {{sending value of non-Sendable type 'Any' risks causing data races}}
        // expected-note @-1 {{sending value of non-Sendable type 'Any' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
        // expected-complete-warning @-1 {{passing argument of non-Sendable type 'Any' into actor-isolated context may introduce data races}}
      default: ()
      }

      //this case does nothing
      case .E3:
        foo_noniso(e4);
  }

  await a.foo(e1); // expected-warning {{sending 'e1' risks causing data races}}
  // expected-note @-1 {{sending 'e1' to actor-isolated instance method 'foo' risks causing data races between actor-isolated and local nonisolated uses}}
  // expected-complete-warning @-2 {{passing argument of non-Sendable type 'E' into actor-isolated context may introduce data races}}
  foo_noniso(e2); // expected-note {{access can happen concurrently}}
  foo_noniso(e3); // expected-note {{access can happen concurrently}}
  foo_noniso(e4);
}
