local_name_repair_quiet()

test_that("zero length input returns NULL", {
  expect_equal(vec_c(), NULL)
  expect_equal(vec_c(NULL), NULL)
  expect_equal(vec_c(NULL, ), NULL)
  expect_equal(vec_c(NULL, NULL), NULL)
})

test_that("NULL is idempotent", {
  expect_equal(vec_c(NULL, 1L), 1L)
  expect_equal(vec_c(1L, NULL), 1L)
})

test_that("NA is idempotent", {
  expect_equal(vec_c(NA, 1L), c(NA, 1L))
  expect_equal(vec_c(NA, "x"), c(NA, "x"))
  expect_equal(vec_c(NA, factor("x")), factor(c(NA, "x")))
  expect_equal(vec_c(NA, new_date(0)), new_date(c(NA, 0)))
  expect_equal(vec_c(NA, new_datetime(0)), new_datetime(c(NA, 0)))
  expect_equal(vec_c(NA, new_duration(0)), new_duration(c(NA, 0)))
})

test_that("NA is logical if no other types intervene", {
  expect_equal(vec_c(logical()), logical())
  expect_equal(vec_c(NA), NA)
  expect_equal(vec_c(NA, NA), c(NA, NA))
})


test_that("different types are coerced to common", {
  expect_equal(vec_c(TRUE, 1L, 1), c(1, 1, 1))
  expect_equal(vec_c(TRUE, 2:4), 1:4)
})

test_that("specified .ptypes do not allow more casts", {
  expect_error(
    vec_c(TRUE, .ptype = character()),
    class = "vctrs_error_incompatible_type"
  )
})

test_that("common type failure uses error call and error arg (#1641, #1692)", {
  expect_snapshot(error = TRUE, {
    vec_c("x", 1, .error_call = call("foo"), .error_arg = "arg")
  })
  expect_snapshot(error = TRUE, {
    vec_c(
      "x",
      .ptype = integer(),
      .error_call = call("foo"),
      .error_arg = "arg"
    )
  })
})

test_that("common type failure uses positional errors", {
  expect_snapshot({
    # Looking for `..1` and `a`
    (expect_error(vec_c(1, a = "x", 2)))

    # Directed cast should also produce positional errors (#1690)
    (expect_error(vec_c(1, a = "x", 2, .ptype = double(), .error_arg = "arg")))

    # Lossy cast
    (expect_error(vec_c(1, a = 2.5, .ptype = integer())))
  })
})

test_that("combines outer an inner names", {
  expect_equal(vec_c(x = 1), c(x = 1))
  expect_equal(vec_c(c(x = 1)), c(x = 1))

  expect_equal(vec_c(c(x = 1:2)), c(x1 = 1, x2 = 2))
  expect_error(vec_c(y = c(x = 1)), "Please supply")
})

test_that("can bind data.frame columns", {
  df <- data.frame(x = NA, y = 1:2)
  df$x <- data.frame(a = 1:2)

  expected <- data.frame(x = NA, y = c(1:2, 1:2))
  expected$x <- data.frame(a = c(1:2, 1:2))

  expect_equal(vec_c(df, df), expected)
})

test_that("vec_c() handles matrices", {
  m <- matrix(1:4, nrow = 2)
  dimnames(m) <- list(c("foo", "bar"), c("baz", "quux"))

  # FIXME: `vec_ptype_common(m, m)` doesn't return dimension names
  exp <- matrix(c(1:2, 1:2, 3:4, 3:4), nrow = 4)
  rownames(exp) <- c("foo", "bar", "foo", "bar")

  expect_identical(vec_c(m, m), exp)

  expect_error(vec_c(outer = m), "Please supply")
})

test_that("vec_c() includes index in argument tag", {
  df1 <- tibble(x = tibble(y = tibble(z = 1)))
  df2 <- tibble(x = tibble(y = tibble(z = "a")))

  expect_snapshot(error = TRUE, vec_c(df1, df2))
  expect_snapshot(error = TRUE, vec_c(df1, df1, df2))
  expect_snapshot(error = TRUE, vec_c(foo = df1, bar = df2))
})

test_that("vec_c() handles record classes", {
  local_rational_class()

  out <- vec_c(rational(1, 2), 1L, NA)

  expect_true(vec_is(out, rational(1, 2)))
  expect_size(out, 3)
  expect_identical(
    vec_proxy(out),
    data.frame(n = c(1L, 1L, NA), d = c(2L, 1L, NA))
  )
})

test_that("can mix named and unnamed vectors (#271)", {
  expect_identical(vec_c(c(a = 1), 2), c(a = 1, 2))
  expect_identical(vec_c(0, c(a = 1), 2, b = 3), c(0, a = 1, 2, b = 3))
})

test_that("preserves names when inputs are cast to a common type (#1690)", {
  expect_named(vec_c(c(a = 1), .ptype = integer()), "a")
  expect_named(
    vec_c(foo = c(a = 1), .ptype = integer(), .name_spec = "{outer}_{inner}"),
    "foo_a"
  )
  expect_named(
    vec_c(foo = c(a = 1), .ptype = integer(), .name_spec = "inner"),
    "a"
  )
})

test_that("vec_c() repairs names", {
  local_name_repair_quiet()

  # Default minimal repair
  expect_named(vec_c(a = 1, a = 2, `_` = 3), c("a", "a", "_"))
  out <- vec_c(!!!set_names(1, NA))
  expect_named(out, "")

  expect_named(
    vec_c(a = 1, a = 2, `_` = 3, .name_repair = "unique"),
    c("a...1", "a...2", "_")
  )
  expect_error(
    vec_c(a = 1, a = 2, `_` = 3, .name_repair = "check_unique"),
    class = "vctrs_error_names_must_be_unique"
  )

  expect_named(
    vec_c(a = 1, a = 2, `_` = 3, .name_repair = "universal"),
    c("a...1", "a...2", "._")
  )

  expect_named(vec_c(a = 1, a = 2, .name_repair = ~ toupper(.)), c("A", "A"))
})

test_that("vec_c() can repair names quietly", {
  local_name_repair_verbose()

  expect_snapshot({
    res_unique <- vec_c(x = TRUE, x = 0, .name_repair = "unique_quiet")
    res_universal <- vec_c(
      "if" = TRUE,
      "in" = 0,
      .name_repair = "universal_quiet"
    )
  })
  expect_named(res_unique, c("x...1", "x...2"))
  expect_named(res_universal, c(".if", ".in"))
})

test_that("vec_c() doesn't use outer names for data frames (#524)", {
  x <- data.frame(inner = 1)
  expect_equal(vec_c(outer = x), x)

  a <- data.frame(x = 1L)
  b <- data.frame(x = 2L)
  expect_equal(vec_c(foo = a, bar = b), data.frame(x = 1:2))
})

test_that("vec_c() preserves row names and inner names", {
  x <- data.frame(a = 1, row.names = "r1")
  y <- data.frame(a = 2, row.names = "r2")
  expect_equal(rownames(vec_c(x, y)), c("r1", "r2"))
  expect_equal(rownames(vec_c(x, x)), c("r1...1", "r1...2"))

  vec_x <- set_names(1:3, letters[1:3])
  vec_y <- c(FOO = 4L)
  oo_x <- set_names(
    as.POSIXlt(c("2020-01-01", "2020-01-02", "2020-01-03")),
    letters[1:3]
  )
  oo_y <- as.POSIXlt(c(FOO = "2020-01-04"))
  df_x <- new_data_frame(list(x = 1:3), row.names = letters[1:3])
  df_y <- new_data_frame(list(x = 4L), row.names = "d")
  mat_x <- matrix(1:3, 3, dimnames = list(letters[1:3]))
  mat_y <- matrix(4L, 1, dimnames = list("d"))
  nested_x <- new_data_frame(
    list(df = df_x, mat = mat_x, vec = vec_x, oo = oo_x),
    row.names = c("foo", "bar", "baz")
  )
  nested_y <- new_data_frame(
    list(df = df_y, mat = mat_y, vec = vec_y, oo = oo_y),
    row.names = c("quux")
  )

  nested_out <- vec_c(nested_x, nested_y)
  expect_identical(row.names(nested_out), c("foo", "bar", "baz", "quux"))
  expect_identical(row.names(nested_out$df), c("a", "b", "c", "d"))
  expect_identical(row.names(nested_out$mat), c("a", "b", "c", "d"))
  expect_identical(names(nested_out$vec), c("a", "b", "c", "FOO"))
  expect_identical(names(nested_out$oo), c("a", "b", "c", "FOO"))
})

test_that("vec_c() outer names work with proxied objects", {
  x <- as.POSIXlt(new_datetime(0))
  exp <- set_names(x, "outer")
  expect_equal(vec_c(outer = x), exp)

  named_x <- set_names(x, "inner")
  exp <- set_names(named_x, "outer_inner")
  expect_error(vec_c(outer = named_x), "Please supply")
  expect_equal(vec_c(outer = named_x, .name_spec = "{outer}_{inner}"), exp)

  xs <- as.POSIXlt(new_datetime(c(0, 1)))
  exp <- set_names(xs, c("outer_1", "outer_2"))
  expect_error(vec_c(outer = xs), "Please supply")
  expect_equal(vec_c(outer = xs, .name_spec = "{outer}_{inner}"), exp)
})

test_that("vec_c() works with simple homogeneous foreign S3 classes", {
  expect_identical(vec_c(foobar(1), foobar(2)), vec_c(foobar(c(1, 2))))
  expect_identical(vec_c(NULL, foobar(1), foobar(2)), vec_c(foobar(c(1, 2))))
})

test_that("vec_c() works with simple homogeneous foreign S4 classes", {
  joe1 <- .Counts(c(1L, 2L), name = "Joe")
  joe2 <- .Counts(3L, name = "Joe")
  expect_identical(vec_c(joe1, joe2), .Counts(1:3, name = "Joe"))
})

test_that("vec_c() fails with complex foreign S3 classes", {
  expect_snapshot({
    x <- structure(foobar(1), attr_foo = "foo")
    y <- structure(foobar(2), attr_bar = "bar")
    (expect_error(vec_c(x, y), class = "vctrs_error_incompatible_type"))
    (expect_error(
      vec_c(x, y, .error_call = call("foo"), .error_arg = "arg"),
      class = "vctrs_error_incompatible_type"
    ))
  })
})

test_that("vec_c() fails with complex foreign S4 classes", {
  expect_snapshot({
    joe <- .Counts(c(1L, 2L), name = "Joe")
    jane <- .Counts(3L, name = "Jane")
    (expect_error(vec_c(joe, jane), class = "vctrs_error_incompatible_type"))
    (expect_error(
      vec_c(joe, jane, .error_call = call("foo"), .error_arg = "arg"),
      class = "vctrs_error_incompatible_type"
    ))
  })
})

test_that("vec_c() falls back to c() if S3 method is available", {
  # Check off-by-one error
  expect_error(
    vec_c(foobar(1), "", foobar(2)),
    class = "vctrs_error_incompatible_type"
  )

  # Fallback when the class implements `c()`
  method <- function(...) rep_along(list(...), "dispatched")
  local_methods(
    c.vctrs_foobar = method
  )
  expect_identical(
    vec_c(foobar(1), foobar(2, class = "foo")),
    c("dispatched", "dispatched")
  )
  expect_identical(
    vec_c(NULL, foobar(1), NULL, foobar(2, class = "foo")),
    c("dispatched", "dispatched")
  )

  # Registered fallback
  s3_register("base::c", "vctrs_c_fallback", method)
  expect_identical(
    vec_c(
      structure(1, class = "vctrs_c_fallback"),
      structure(2, class = "vctrs_c_fallback")
    ),
    c("dispatched", "dispatched")
  )

  # Don't fallback for S3 lists which are treated as scalars by default
  expect_error(
    vec_c(foobar(list(1)), foobar(list(2))),
    class = "vctrs_error_scalar_type"
  )
})

test_that("c() fallback is consistent", {
  dispatched <- function(x) structure(x, class = "dispatched")
  c_method <- function(...) dispatched(NextMethod())

  out <- with_methods(
    vec_ptype2.vctrs_foobaz.vctrs_foobaz = function(...) foobaz(df_ptype2(...)),
    vec_cast.vctrs_foobaz.vctrs_foobaz = function(...) foobaz(df_cast(...)),
    c.vctrs_foobar = c_method,
    list(
      direct = vec_c(foobar(1L), foobar(2L)),
      df = vec_c(data_frame(x = foobar(1L)), data_frame(x = foobar(2L))),
      tib = vec_c(tibble(x = foobar(1L)), tibble(x = foobar(2L))),
      foreign_df = vec_c(
        foobaz(data_frame(x = foobar(1L))),
        foobaz(data_frame(x = foobar(2L)))
      )
    )
  )
  expect_equal(out$direct, dispatched(1:2))
  expect_equal(out$df$x, dispatched(1:2))
  expect_equal(out$tib$x, dispatched(1:2))
  expect_equal(out$foreign_df$x, dispatched(1:2))

  # Hard case: generic record vectors
  my_rec_record <- function(x) {
    new_rcrd(list(x = x), class = "my_rec_record")
  }

  out <- with_methods(
    c.vctrs_foobar = c_method,
    vec_ptype2.my_rec_record.my_rec_record = function(x, y, ...) {
      my_rec_record(vec_ptype2(field(x, "x"), field(y, "x"), ...))
    },
    vec_cast.my_rec_record.my_rec_record = function(x, to, ...) {
      x
    },
    vec_c(
      data_frame(x = my_rec_record(foobar(1L))),
      data_frame(x = my_rec_record(foobar(2L)))
    )
  )
  expect_equal(field(out$x, "x"), dispatched(1:2))
})

test_that("vec_c() falls back to c() if S4 method is available", {
  joe1 <- .Counts(c(1L, 2L), name = "Joe")
  joe2 <- .Counts(3L, name = "Joe")

  c_counts <- function(x, ...) {
    xs <- list(x, ...)

    xs_data <- lapply(xs, function(x) x@.Data)
    new_data <- do.call(c, xs_data)

    .Counts(new_data, name = x@name)
  }

  local_s4_method("c", methods::signature(x = "vctrs_Counts"), c_counts)

  expect_identical(
    vec_c(joe1, joe2),
    .Counts(c(1L, 2L, 3L), name = "Joe")
  )

  expect_identical(
    vec_c(NULL, joe1, joe2),
    .Counts(c(1L, 2L, 3L), name = "Joe")
  )
})

test_that("vec_c() fallback doesn't support (most) `name_spec` or `ptype`", {
  expect_snapshot({
    (expect_error(
      with_c_foobar(vec_c(
        foobar(1),
        foobar(2),
        .name_spec = "{outer}_{inner}"
      )),
      "name specification"
    ))

    # Used to be an error about `ptype`
    (expect_error(
      with_c_foobar(vec_c(foobar(1), foobar(2), .ptype = "")),
      class = "vctrs_error_incompatible_type"
    ))

    # Uses error call (#1641)
    (expect_error(
      with_c_foobar(vec_c(
        foobar(1),
        foobar(2),
        .error_call = call("foo"),
        .name_spec = "{outer}_{inner}"
      ))
    ))
  })
})

test_that("vec_c() fallback does support `.name_spec = 'inner'`", {
  # Because of how useful it is, and how easy it is to implement!
  # It's extremely useful to be able to hardcode `.name_spec = "inner"`
  # and not have to worry about hitting a fallback type that wouldn't support
  # `name_spec`.
  expect_identical(
    with_c_foobar(vec_c(foobar(1), foobar(2), .name_spec = "inner")),
    foobar_c(c(1, 2))
  )
  expect_identical(
    with_c_foobar(vec_c(x = foobar(1), y = foobar(2), .name_spec = "inner")),
    foobar_c(c(1, 2))
  )
  expect_identical(
    with_c_foobar(vec_c(
      x = foobar(c(a = 1)),
      y = foobar(c(b = 2)),
      z = foobar(3),
      .name_spec = "inner"
    )),
    foobar_c(c(a = 1, b = 2, 3))
  )
})

test_that("vec_c() doesn't fall back when ptype2 is implemented", {
  new_quux <- function(x) structure(x, class = "vctrs_quux")

  with_methods(
    vec_ptype2.vctrs_foobar.vctrs_foobar = function(x, y, ...) new_quux(int()),
    vec_cast.vctrs_quux.vctrs_foobar = function(x, to, ...) new_quux(x),
    vec_restore.vctrs_quux = function(x, ...) new_quux(x),
    c.vctrs_foobar = function(...) foobar(NextMethod()),
    {
      expect_s3_class(c(foobar(1:3), foobar(4L)), "vctrs_foobar")
      expect_s3_class(vec_c(foobar(1:3), foobar(4L)), "vctrs_quux")
    }
  )
})

test_that("vec_c() falls back even when ptype is supplied", {
  expect_foobar(vec_c(foobar(1), foobar(2), .ptype = foobar(dbl())))

  with_methods(
    c.vctrs_foobar = function(...) quux(NextMethod()),
    {
      expect_quux(vec_c(foobar(1), foobar(2), .ptype = foobar(dbl())))
      expect_quux(vec_c(
        foobar(1, foo = TRUE),
        foobar(2, bar = TRUE),
        .ptype = foobar(dbl())
      ))
    }
  )
})

test_that("vec_implements_ptype2() is FALSE for scalars", {
  expect_false(vec_implements_ptype2(quote(foo)))
})

test_that("vec_implements_ptype2() and vec_c() fallback are compatible with old registration", {
  foo <- structure(NA, class = "vctrs_implements_ptype2_false")
  expect_false(vec_implements_ptype2(foo))

  vec_ptype2.vctrs_implements_ptype2_true <- function(...) NULL
  s3_register(
    "vctrs::vec_ptype2",
    "vctrs_implements_ptype2_true",
    vec_ptype2.vctrs_implements_ptype2_true
  )

  bar <- structure(NA, class = "vctrs_implements_ptype2_true")
  expect_true(vec_implements_ptype2(bar))

  local_methods(
    `c.vctrs_implements_ptype2_true` = function(...) stop("never called")
  )

  expect_identical(vec_c(bar), bar)
})

test_that("can ignore names in `vec_c()` by providing a `zap()` name-spec (#232)", {
  expect_error(vec_c(a = c(b = 1:2)))
  expect_identical(vec_c(a = c(b = 1:2), b = 3L, .name_spec = zap()), 1:3)

  expect_snapshot({
    (expect_error(
      vec_c(a = c(b = letters), b = 1, .name_spec = zap()),
      class = "vctrs_error_incompatible_type"
    ))
  })
})

test_that("can ignore outer names in `vec_c()` by providing an 'inner' name-spec (#1988)", {
  expect_identical(
    vec_c(x = c(a = 1, 2), y = c(3, b = 4), .name_spec = "inner"),
    c(a = 1, 2, 3, b = 4)
  )

  # Importantly, outer names are still used in error messages!
  expect_snapshot(error = TRUE, {
    vec_c(x = c(a = 1), y = c(b = "2"), .name_spec = "inner")
  })
})

test_that("can concatenate subclasses of `vctrs_vctr` which don't have ptype2 methods", {
  x <- new_vctr(1, class = "vctrs_foo")
  expect_identical(vec_c(x, x), new_vctr(c(1, 1), class = "vctrs_foo"))
})

test_that("base c() fallback handles unspecified chunks", {
  local_methods(
    c.vctrs_foobar = function(...) {
      x <- NextMethod()

      # Should not be passed any unspecified chunks
      if (anyNA(x)) {
        abort("tilt")
      }

      foobar(x)
    },
    `[.vctrs_foobar` = function(x, i, ...) {
      # Return a quux to detect dispatch
      quux(NextMethod())
    }
  )

  out <- vec_c(foobar(1:2), rep(NA, 2))
  expect_identical(out, quux(c(1:2, NA, NA)))

  out <- vec_c(rep(NA, 2), foobar(1:2), NA)
  expect_identical(out, quux(c(NA, NA, 1:2, NA)))
})

test_that("can zap outer names from a name-spec (#1215)", {
  zap_outer_spec <- function(outer, inner) if (is_character(inner)) inner

  expect_null(
    names(vec_c(a = 1:2, .name_spec = zap_outer_spec))
  )
  expect_identical(
    names(vec_c(a = 1:2, c(foo = 3L), .name_spec = zap_outer_spec)),
    c("", "", "foo")
  )

  expect_null(
    names(list_unchop(
      list(a = 1:2),
      indices = list(1:2),
      name_spec = zap_outer_spec
    ))
  )
  expect_identical(
    names(list_unchop(
      list(a = 1:2, c(foo = 3L)),
      indices = list(1:2, 3),
      name_spec = zap_outer_spec
    )),
    c("", "", "foo")
  )

  # These days it is more efficient to use a name-spec of "inner" (#1988)
  expect_identical(
    vec_c(a = 1:2, .name_spec = zap_outer_spec),
    vec_c(a = 1:2, .name_spec = "inner")
  )
  expect_identical(
    vec_c(a = 1:2, c(foo = 3L), .name_spec = zap_outer_spec),
    vec_c(a = 1:2, c(foo = 3L), .name_spec = "inner")
  )
  expect_identical(
    list_unchop(list(a = 1:2), indices = list(1:2), name_spec = zap_outer_spec),
    list_unchop(list(a = 1:2), indices = list(1:2), name_spec = "inner")
  )
  expect_identical(
    list_unchop(
      list(a = 1:2, c(foo = 3L)),
      indices = list(1:2, 3),
      name_spec = zap_outer_spec
    ),
    list_unchop(
      list(a = 1:2, c(foo = 3L)),
      indices = list(1:2, 3),
      name_spec = "inner"
    )
  )
})

test_that("named empty vectors force named output (#1263)", {
  x <- set_names(int(), chr())

  expect_named(vec_c(x), chr())
  expect_named(vec_c(x, x), chr())
  expect_named(vec_c(x, 1L), "")
  expect_named(vec_c(x, 1), "")

  expect_named(list_unchop(list(x), indices = list(int())), chr())
  expect_named(list_unchop(list(x, x), indices = list(int(), int())), chr())
  expect_named(list_unchop(list(x, 1L), indices = list(int(), 1)), "")

  expect_named(list_unchop(list(x, 1), indices = list(int(), 1)), "")
})

test_that("calls cast method even with empty objects", {
  # https://github.com/paleolimbot/wk/issues/230

  # There is a common type, but the cast method is intended
  # to fail here for this test
  local_methods(
    vec_ptype2.vctrs_foobar.vctrs_foobar = function(x, y, ...) {
      x
    },
    vec_cast.vctrs_foobar.default = function(x, to, ...) {
      vec_default_cast(x, to)
    }
  )

  expect_snapshot(error = TRUE, {
    vec_c(
      foobar(integer()),
      foobar(integer(), foo = "bar")
    )
  })
})

test_that("can handle spliced lists (#1578)", {
  expect_identical(
    vec_c(!!!list(foo = 1), !!!list(bar = 2)),
    c(foo = 1, bar = 2)
  )
})

# Golden tests -------------------------------------------------------

test_that("concatenation performs expected allocations", {
  vec_c_list <- function(x, ptype = NULL) {
    vec_c(!!!x, .ptype = ptype)
  }

  expect_snapshot({
    ints <- rep(list(1L), 1e2)
    dbls <- rep(list(1), 1e2)

    # Extra allocations from `list2()`, see r-lib/rlang#937
    "# `vec_c()` "
    "Integers"
    with_memory_prof(vec_c_list(ints))

    "Doubles"
    with_memory_prof(vec_c_list(dbls))

    "Integers to integer"
    with_memory_prof(vec_c_list(ints, ptype = int()))

    "Doubles to integer"
    with_memory_prof(vec_c_list(dbls, ptype = int()))

    "# `list_unchop()` "
    "Integers"
    with_memory_prof(list_unchop(ints))

    "Doubles"
    with_memory_prof(list_unchop(dbls))

    "Integers to integer"
    with_memory_prof(list_unchop(ints, ptype = int()))

    "Doubles to integer"
    with_memory_prof(list_unchop(dbls, ptype = int()))

    "# Concatenation with names"

    "Named integers"
    ints <- rep(list(set_names(1:3, letters[1:3])), 1e2)
    with_memory_prof(list_unchop(ints))

    "Named matrices"
    mat <- matrix(1:4, 2, dimnames = list(c("foo", "bar")))
    mats <- rep(list(mat), 1e2)
    with_memory_prof(list_unchop(mats))

    "Data frame with named columns"
    df <- data_frame(
      x = set_names(as.list(1:2), c("a", "b")),
      y = set_names(1:2, c("A", "B")),
      z = data_frame(Z = set_names(1:2, c("Za", "Zb")))
    )
    dfs <- rep(list(df), 1e2)
    with_memory_prof(list_unchop(dfs))

    "Data frame with rownames (non-repaired, non-recursive case)"
    df <- data_frame(x = 1:2)
    dfs <- rep(list(df), 1e2)
    dfs <- map2(dfs, seq_along(dfs), set_rownames_recursively)
    with_memory_prof(list_unchop(dfs))

    "Data frame with rownames (repaired, non-recursive case)"
    dfs <- map(dfs, set_rownames_recursively)
    with_memory_prof(list_unchop(dfs))

    "Data frame with rownames (non-repaired, recursive case) (#1217)"
    df <- data_frame(
      x = 1:2,
      y = data_frame(x = 1:2)
    )
    dfs <- rep(list(df), 1e2)
    dfs <- map2(dfs, seq_along(dfs), set_rownames_recursively)
    with_memory_prof(list_unchop(dfs))

    "Data frame with rownames (repaired, recursive case) (#1217)"
    dfs <- map(dfs, set_rownames_recursively)
    with_memory_prof(list_unchop(dfs))

    "list-ofs (#1496)"
    make_list_of <- function(n) {
      df <- tibble::tibble(
        x = new_list_of(vec_chop(1:n), ptype = integer())
      )
      vec_chop(df)
    }
    with_memory_prof(list_unchop(make_list_of(1e3)))
    with_memory_prof(list_unchop(make_list_of(2e3)))
    with_memory_prof(list_unchop(make_list_of(4e3)))
  })
})

test_that("can dispatch many times", {
  # This caused a crash when counters were not correctly protected
  foo <- structure(
    list(x.sorted = numeric(0), tp = numeric(0), fp = numeric(0)),
    row.names = integer(0),
    class = c("vctrs_foobar", "tbl_df", "tbl", "data.frame")
  )
  x <- lapply(1:200, function(...) foo)
  expect_error(NA, object = vctrs::list_unchop(x))
})

test_that("dots splicing clones as appropriate", {
  x <- list(a = 1)
  vctrs::vec_cbind(!!!x)
  expect_equal(x, list(a = 1))

  x <- list(a = 1)
  vctrs::vec_rbind(!!!x)
  expect_equal(x, list(a = 1))

  x <- list(a = 1)
  vctrs::vec_c(!!!x)
  expect_equal(x, list(a = 1))

  x <- list(a = 1)
  vctrs::vec_cbind(!!!x, 2)
  expect_equal(x, list(a = 1))

  x <- list(a = 1)
  vctrs::vec_rbind(!!!x, 2)
  expect_equal(x, list(a = 1))

  x <- list(a = 1)
  vctrs::vec_c(!!!x, 2)
  expect_equal(x, list(a = 1))
})

test_that("can combine records wrapped in data frames", {
  local_methods(
    vec_proxy.vctrs_foobar = function(x, ...) {
      data_frame(x = unclass(x), y = seq_along(x))
    },
    vec_restore.vctrs_foobar = function(x, to, ...) {
      foobar(x$x)
    }
  )

  x <- foobar(1:2)
  y <- foobar(3:4)

  expect_equal(
    vec_c(x, y),
    foobar(1:4)
  )

  expect_equal(
    list_unchop(list(x, y), indices = list(1:2, 3:4)),
    foobar(1:4)
  )

  expect_equal(
    vec_rbind(data_frame(x = x), data_frame(x = y)),
    data_frame(x = foobar(1:4))
  )
})

test_that("fallback works with subclasses of `vctrs_vctr`", {
  # Used to fail because of interaction between common class fallback
  # for `base::c()` and the `c()` method for `vctrs_vctr` that called
  # back into `vec_c()`.

  # Reprex for failure in the ricu package
  x <- new_rcrd(list(a = 1), class = "vctrs_foobar")
  expect_equal(
    vec_c(x, x, .name_spec = "{inner}"),
    new_rcrd(list(a = c(1, 1)), class = "vctrs_foobar")
  )

  # Reprex for failure in the groupr package
  x <- new_rcrd(list(a = 1), class = "vctrs_foobar")
  df <- data_frame(x = x)
  expect_equal(
    vec_rbind(df, data.frame()),
    df
  )
  expect_equal(
    vec_cast_common(df, data.frame()),
    list(df, data_frame(x = x[0]))
  )
})
