skip_on_cran()

test_that("<<- mutates host locals in statement local closures", {
  fn <- function(nx, ny) {
    declare(
      type(nx = integer(1)),
      type(ny = integer(1))
    )
    temp <- matrix(0, nx, ny)

    bc <- function() {
      temp[1, ] <<- 1
      temp[nx, ] <<- 2
      temp[, 1] <<- 3
      temp[, ny] <<- 4
      NULL
    }

    bc()
    temp
  }

  expect_quick_identical(fn, list(4L, 5L), list(2L, 3L))
})

test_that("<<- mutating a host argument preserves caller copy-on-modify semantics", {
  fn <- function(x) {
    declare(type(x = double(NA)))
    f <- function(i) {
      x[i] <<- x[i] + 1
      NULL
    }
    f(1L)
    x
  }

  x1 <- c(1, 2, 3)
  x2 <- c(-1, 0, 4)

  expect_identical(fn(x1), c(2, 2, 3))
  expect_identical(fn(x2), c(0, 0, 4))
  expect_quick_identical(fn, x1, x2, as.double(seq(-10, 10)))

  qfn <- quick(fn)
  for (x0 in list(x1, x2)) {
    x <- x0
    out <- qfn(x)
    expect_identical(out, fn(x0))
    expect_identical(x, x0)
  }
})

test_that("sapply + <<- can mutate host and return values", {
  fn <- function(x) {
    declare(type(x = double(NA)), type(out = double(length(x))))
    f <- function(i) {
      x[i] <<- x[i] * 2
      x[i]
    }
    out <- sapply(seq_along(x), f)
    list(x = x, out = out)
  }

  set.seed(1)
  x <- runif(10)
  expect_quick_identical(fn, list(x))
})

test_that("<- shadows captured variables (use <<- to mutate the host)", {
  fn <- function(x) {
    declare(type(x = double(NA)))
    out <- double(length(x))
    f <- function(i) {
      x <- x * 2
      x[i]
    }
    out <- sapply(seq_along(out), f)
    out
  }

  x1 <- c(1, 2, 3)
  x2 <- c(-1.5, 0.25, 2.0)

  expect_identical(fn(x1), x1 * 2)
  expect_identical(fn(x2), x2 * 2)
  expect_quick_identical(fn, x1, x2, as.double(seq(-10, 10)))

  qfn <- quick(fn)
  for (x0 in list(x1, x2)) {
    x <- x0
    out <- qfn(x)

    expect_identical(out, x0 * 2)
    expect_identical(out, fn(x0))
    expect_identical(x, x0)
  }
})

test_that("<<- targets the host when the name is shadowed by <- (vector)", {
  fn <- function(x) {
    declare(type(x = double(NA)))
    f <- function(i) {
      x <- x + 1.0
      x[i] <<- x[i] + 1.0
      NULL
    }
    f(1L)
    x
  }

  x1 <- as.double(1:5)
  x2 <- c(-1.5, 0.25, 2.0)

  expected1 <- x1
  expected1[[1L]] <- x1[[1L]] + 2.0
  expected2 <- x2
  expected2[[1L]] <- x2[[1L]] + 2.0

  expect_identical(fn(x1), expected1)
  expect_identical(fn(x2), expected2)
  expect_quick_identical(fn, x1, x2, as.double(seq(-10, 10)))

  qfn <- quick(fn)
  for (x0 in list(x1, x2)) {
    x <- x0
    out <- qfn(x)

    expected <- x0
    expected[[1L]] <- x0[[1L]] + 2.0
    expect_identical(out, expected)
    expect_identical(out, fn(x0))
    expect_identical(x, x0)
  }
})

test_that("<<- targets the host when the name is shadowed by <- (matrix subset)", {
  fn <- function(x) {
    declare(type(x = double(3, 4)))
    i <- 2L
    j <- 3L

    f <- function() {
      x <- x + 1.0
      x[i, j] <<- x[i, j] + 1.0
      NULL
    }
    f()
    x
  }

  x1 <- matrix(as.double(1:12), 3, 4)
  x2 <- matrix(as.double(-11:0), 3, 4)

  expected1 <- x1
  expected1[2, 3] <- x1[2, 3] + 2.0
  expected2 <- x2
  expected2[2, 3] <- x2[2, 3] + 2.0

  expect_identical(fn(x1), expected1)
  expect_identical(fn(x2), expected2)
  expect_quick_identical(fn, x1, x2)

  qfn <- quick(fn)
  for (x0 in list(x1, x2)) {
    x <- x0
    out <- qfn(x)

    expected <- x0
    expected[2, 3] <- x0[2, 3] + 2.0
    expect_identical(out, expected)
    expect_identical(out, fn(x0))
    expect_identical(x, x0)
  }
})

test_that("<<- supports 2D subset targets", {
  fn <- function(nx, ny, dt) {
    declare(
      type(nx = integer(1)),
      type(ny = integer(1)),
      type(dt = double(1))
    )
    temp <- matrix(0, nx, ny)

    bump <- function(i, j) {
      temp[i, j] <<- temp[i, j] + dt
      NULL
    }

    bump(2L, 3L)
    bump(nx, ny)
    temp
  }

  expect_quick_identical(fn, list(4L, 5L, 0.25), list(2L, 3L, 1.0))
})
