skip_on_cran()

test_that("local closures support if/else control flow in sapply()", {
  fn <- function(x, thresh) {
    declare(type(x = double(NA)), type(thresh = double(1)))
    out <- double(length(x))
    f <- function(i) {
      val <- 0.0
      if (x[i] > thresh) {
        val <- x[i] * 2.0
      } else {
        val <- x[i] + 1.0
      }
      val
    }
    out <- sapply(seq_along(out), f)
    out
  }

  x0 <- c(0.2, 0.4)
  expect_identical(fn(x0, 0.3), c(1.2, 0.8))
  expect_identical(fn(x0, 0.5), c(1.2, 1.4))

  set.seed(1)
  x <- runif(20)
  expect_quick_identical(
    fn,
    list(x0, 0.3),
    list(x0, 0.5),
    list(x, 0.5)
  )
})

test_that("local closures support repeat/break/next control flow in sapply()", {
  fn <- function(x, thresh, limit) {
    declare(
      type(x = double(NA)),
      type(thresh = double(1)),
      type(limit = double(1))
    )
    out <- double(length(x))
    f <- function(i) {
      acc <- 0.0
      j <- 0L
      repeat {
        j <- j + 1L
        if (j > i) {
          break
        }
        if (x[j] < thresh) {
          next
        }
        acc <- acc + x[j]
        if (acc > limit) break
      }
      acc
    }
    out <- sapply(seq_along(out), f)
    out
  }

  x0 <- c(0.1, 0.5, 0.6)
  expect_identical(fn(x0, 0.2, 0.8), c(0.0, 0.5, 1.1))
  expect_identical(fn(x0, 0.2, 0.4), c(0.0, 0.5, 0.5))

  set.seed(1)
  x <- runif(15)
  expect_quick_identical(
    fn,
    list(x0, 0.2, 0.8),
    list(x0, 0.2, 0.4),
    list(x, 0.2, 1.0)
  )
})

test_that("statement local closures support while loops + <<- host mutation", {
  fn <- function(n) {
    declare(type(n = integer(1)))
    x <- double(n)
    fill <- function() {
      i <- 1L
      while (i <= n) {
        x[i] <<- as.double(i)
        i <- i + 1L
      }
      NULL
    }
    fill()
    x
  }

  expect_identical(fn(1L), 1.0)
  expect_identical(fn(3L), c(1.0, 2.0, 3.0))
  expect_quick_identical(fn, list(1L), list(2L), list(3L), list(7L))
})

test_that("loop variables in local closures shadow host variables", {
  fn <- function(x) {
    declare(type(x = double(NA)))
    j <- 999L
    out <- double(length(x))
    f <- function(i) {
      acc <- 0.0
      for (j in seq_len(i)) {
        acc <- acc + x[j]
      }
      acc
    }
    out <- sapply(seq_along(out), f)
    list(j = j, out = out)
  }

  x1 <- c(1, 2, 3)
  x2 <- c(0, 0, 0)
  expect_identical(fn(x1), list(j = 999L, out = c(1, 3, 6)))
  expect_identical(fn(x2), list(j = 999L, out = c(0, 0, 0)))

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

test_that("inline function calls are lowered correctly", {
  fn <- function(x) {
    declare(type(x = double(1)))
    y <- (function(a) a + 1)(x)
    y
  }

  expect_identical(fn(1), 2)
  expect_quick_identical(fn, list(1), list(5))
})

test_that("parenthesized closure calls work", {
  fn <- function(x) {
    declare(type(x = double(1)))
    f <- function(a) a * 2
    y <- ((f))(x)
    y
  }

  expect_identical(fn(3), 6)
  expect_quick_identical(fn, list(3), list(7))
})

test_that("local closures support NULL defaults via optional args", {
  fn <- function(x) {
    declare(type(x = double(1)))
    f <- function(a = NULL) {
      if (is.null(a)) {
        a <- x + 1
      }
      a * 2
    }
    f()
  }

  expect_translation_snapshots(fn)
  expect_identical(fn(2), 6)
  expect_quick_identical(fn, list(2), list(5))

  fn <- function(x) {
    declare(type(x = double(1)))
    f <- function(a = NULL) {
      if (is.null(a)) {
        a <- x + 1
      }
      a * 2
    }
    f(4)
  }

  expect_identical(fn(2), 8)
  expect_quick_identical(fn, list(2), list(5))
})

test_that("optional NULL defaults require a presence guard", {
  fn <- function(x) {
    declare(type(x = double(1)))
    f <- function(a = NULL) {
      if (!is.null(a)) {
        a <- x
      }
      a + x
    }
    f()
  }

  expect_identical(fn(2), numeric())
  expect_error(
    quick(fn),
    "optional argument\\(s\\) used without initializing when NULL: a"
  )
})

test_that("optional NULL defaults allow unconditional assignment", {
  fn <- function(x) {
    declare(type(x = double(1)))
    f <- function(a = NULL) {
      a <- x + 1
      a * 2
    }
    f()
  }

  expect_identical(fn(2), 6)
  expect_quick_identical(fn, list(2), list(5))
})

test_that("closure returning logical assigned to return variable", {
  fn <- function(x) {
    declare(type(x = double(NA)))
    check <- function(i) x[i] > 0.5
    out <- sapply(seq_along(x), check)
    out
  }

  x <- c(0.1, 0.6, 0.3, 0.9)
  expect_identical(fn(x), c(FALSE, TRUE, FALSE, TRUE))
  expect_quick_identical(fn, list(x))
})

test_that("sapply over scalar iterable works", {
  fn <- function(x) {
    declare(type(x = double(1)))
    out <- sapply(x, function(v) v * 2)
    out
  }

  expect_identical(fn(3), 6)
  expect_quick_identical(fn, list(3), list(5))
})

test_that("closure captures and shadows host variable on subscript assign", {
  fn <- function(x) {
    declare(type(x = double(NA)))
    modify <- function(i) {
      x[i] <- x[i] + 1
      x[i]
    }
    out <- sapply(seq_along(x), modify)
    out
  }

  x <- c(1, 2, 3)
  expect_identical(fn(x), c(2, 3, 4))
  expect_quick_identical(fn, list(x))
})

test_that("superassignment to non-existent variable errors", {
  expect_error(
    quick(function(x) {
      declare(type(x = double(NA)))
      modify <- function(i) {
        nonexistent[i] <<- 1
      }
      modify(1L)
      x
    }),
    "must resolve to an existing variable"
  )
})

test_that("closure with scalar indexing on scalar value via superassign", {
  fn <- function(x) {
    declare(type(x = double(1)))
    modify <- function() {
      x[1] <<- x[1] + 1
      NULL
    }
    modify()
    x
  }

  expect_identical(fn(5), 6)
  expect_quick_identical(fn, list(5), list(10))
})
