Skip to content

Reverse dependency tidytable fails due to type mismatch in a merge operation #7604

Description

@aitap

Found while selectively checking reverse dependencies before the 1.18.2 release:

library(tidytable)
df1 <- tidytable(x = 1:3)
df2 <- tidytable(x = c(2, 3, 3), y = c("a", "b", "c"))
out <- nest_join(df1, df2)
Error in bmerge(i, x, leftcols, rightcols, roll, rollends, nomatch, mult,  : typeof x.x (double) != typeof i.x (integer)
Calls: nest_join -> left_join -> [ -> [.data.table -> bmerge
Execution halted

Bisects to 85a4cf5 (#7538).

Activity

  1. aitap commented on Jan 19, 2026

    @aitap
    MemberAuthor

    This works:

    library(data.table)
    y <- data.table(as.double(2:3), list('foo','bar'))
    x <- data.table(1:3)
    y[x, on='V1']

    But this doesn't:

    (y <- tidytable(x = as.double(2:3), y = list('foo','bar')))
    # # A tidytable: 2 × 2
    #       x y
    #   <dbl> <list>
    # 1     2 <chr [1]>
    # 2     3 <chr [1]>
    (x <- tidytable(x = 1:3))
    # # A tidytable: 3 × 1
    #       x
    #   <int>
    # 1     1
    # 2     2
    # 3     3
    y[x, on = 'x']
    # Error in bmerge(i, x, leftcols, rightcols, roll, rollends, nomatch, mult,  :
    #   typeof x.x (double) != typeof i.x (integer)

    In the second example, x and y are not completely valid (missing the self-reference attribute, not growable). Installing the development version of tidytable fixes the problem, probably due to markfairbanks/tidytable#840.

  2. aitap commented on Jan 19, 2026

    @aitap
    MemberAuthor

    The problem is due to bmerge → coerce_col → set changing the data.table in the call frame for coerce_col but not bmerge:

    set(dt, j=col, value=cast_with_attrs(dt[[col]], cast_fun))

    Should bmerge add a check for selfrefok and setalloccol before calling coerce_col?

  3. aitap commented on Jan 19, 2026

    @aitap
    MemberAuthor

    A more systematic check for internal use of set() reveals that almost all calls are with a properly intialised data.table, usually created a few lines above, or at least something that is not shared with the caller. The only suspect uses are in setdroplevels

    set(x, i=NULL, j=nx, value=fdroplevels(x[[nx]], exclude=exclude))

    and bmerge:
    set(dt, j=col, value=cast_with_attrs(dt[[col]], cast_fun))

    Indeed, setdroplevels silently doesn't work on invalid data.tables:

    x <- structure(list(factor('a', levels = letters)), class = c('data.table', 'data.frame'), names = 'x')
    setdroplevels(x)
    levels(x$x)
    #  [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s"
    # [20] "t" "u" "v" "w" "x" "y" "z"

    Should we:

    • make the behaviour of set() closer to how it used to work, only requiring setalloccol when changing the number of columns, or
    • change setdroplevels and coerce_col to propagate the re-created data.table to the caller?
  4. added this to the 1.18.2 milestone on Jan 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    revdepReverse dependencies

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions