Skip to content

Commit a3460d5

Browse files
m7prgithub-actions[bot]Copilot
authored
276 handle subassignemnts with the same object (#277)
Fixes #276 ```r > teal.data::teal_data() |> within({ ADSL <- teal.data::rADSL ADSL$Yada[ADSL$AGE > 30] <- 1 }) |> teal.code::get_code(names = "ADSL") |> cat() ``` now gives the second line ```r ADSL <- teal.data::rADSL ADSL$Yada[ADSL$AGE > 30] <- 1 ``` --------- Signed-off-by: Marcin <[email protected]> Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <[email protected]>
1 parent 22d94ed commit a3460d5

File tree

3 files changed

+186
-5
lines changed

3 files changed

+186
-5
lines changed

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ Config/Needs/website: insightsengineering/nesttemplate
5353
Encoding: UTF-8
5454
Language: en-US
5555
Roxygen: list(markdown = TRUE)
56-
RoxygenNote: 7.3.2
56+
RoxygenNote: 7.3.3
5757
Collate:
5858
'qenv-c.R'
5959
'qenv-class.R'

R/utils-get_code_dependency.R

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,17 @@ extract_occurrence <- function(pd) {
306306
ans <- move_functions_after_arrow(ans, unique(x[sym_fc_cond, "text"]))
307307
roll <- in_parenthesis(pd)
308308
if (length(roll)) {
309-
c(setdiff(ans, roll), roll)
309+
# detect elements appeared in parenthesis and move them on RHS
310+
# but only their first appearance
311+
# as the same object can appear as regular object and the one used in parenthesis
312+
result <- ans
313+
for (elem in roll) {
314+
idx <- which(result == elem)[1]
315+
if (!is.na(idx)) {
316+
result <- result[-idx]
317+
}
318+
}
319+
c(result, roll)
310320
} else {
311321
ans
312322
}
@@ -330,9 +340,21 @@ move_functions_after_arrow <- function(ans, functions) {
330340
if (length(arrow_pos) == 0) {
331341
return(ans)
332342
}
333-
before_arrow <- setdiff(ans[1:arrow_pos], functions)
334-
after_arrow <- ans[(arrow_pos + 1):length(ans)]
335-
c(before_arrow, unique(c(intersect(ans[1:arrow_pos], functions), after_arrow)))
343+
if (length(functions) == 0) {
344+
return(ans)
345+
}
346+
ans_pre <- ans[1:arrow_pos]
347+
# it's setdiff but without the removal of duplicates
348+
# do not use setdiff(ans_pre, functions)
349+
# as it removes duplicates from ans_pre even if they do not appear in functions
350+
# check setdiff(c("A", "A"), "B") - gives "A", where we want to keep c("A", "A")
351+
for (fun in functions) {
352+
if (any(ans_pre == fun)) ans_pre <- ans_pre[-match(fun, ans_pre)]
353+
}
354+
after_arrow <- if (arrow_pos < length(ans)) {
355+
ans[(arrow_pos + 1):length(ans)]
356+
}
357+
c(ans_pre, after_arrow)
336358
}
337359

338360
#' Extract side effects

tests/testthat/test-utils-get_code_dependency.R

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,162 @@ testthat::describe("get_code with multiple assignments inside an expression", {
8686
testthat::expect_equal(get_code(td, names = "var2"), code_source)
8787
})
8888
})
89+
90+
testthat::describe("get_code with subassignments", {
91+
it("tracks [ subassignment as producing the base object", {
92+
td <- qenv() |>
93+
within({
94+
x <- 1:10
95+
x[1:3] <- c(10, 20, 30)
96+
})
97+
98+
code_source <- "x <- 1:10\nx[1:3] <- c(10, 20, 30)"
99+
100+
testthat::expect_equal(get_code(td, names = "x"), code_source)
101+
})
102+
103+
it("tracks [[ subassignment as producing the base object", {
104+
td <- qenv() |>
105+
within({
106+
lst <- list(a = 1, b = 2)
107+
lst[["c"]] <- 3
108+
})
109+
110+
code_source <- "lst <- list(a = 1, b = 2)\nlst[[\"c\"]] <- 3"
111+
112+
testthat::expect_equal(get_code(td, names = "lst"), code_source)
113+
})
114+
115+
it("tracks nested subassignments", {
116+
td <- qenv() |>
117+
within({
118+
df <- data.frame(x = 1:5, y = 6:10)
119+
df$x[df$y > 8] <- 99
120+
})
121+
122+
code_source <- "df <- data.frame(x = 1:5, y = 6:10)\ndf$x[df$y > 8] <- 99"
123+
124+
testthat::expect_equal(get_code(td, names = "df"), code_source)
125+
})
126+
127+
it("tracks multiple subassignments to same object", {
128+
td <- qenv() |>
129+
within({
130+
iris <- iris
131+
iris$Species[sample.int(nrow(iris), 10)] <- NA
132+
iris$Sepal.Length[1:5] <- 0
133+
})
134+
135+
code_source <- "iris <- iris\niris$Species[sample.int(nrow(iris), 10)] <- NA\niris$Sepal.Length[1:5] <- 0"
136+
137+
testthat::expect_equal(get_code(td, names = "iris"), code_source)
138+
})
139+
140+
it("tracks subassignments with complex expressions", {
141+
td <- qenv() |>
142+
within({
143+
mat <- matrix(1:12, nrow = 3)
144+
mat[mat > 5 & mat < 10] <- 0
145+
})
146+
147+
code_source <- "mat <- matrix(1:12, nrow = 3)\nmat[mat > 5 & mat < 10] <- 0"
148+
149+
testthat::expect_equal(get_code(td, names = "mat"), code_source)
150+
})
151+
152+
it("tracks subassignments with function calls on LHS", {
153+
td <- qenv() |>
154+
within({
155+
lst <- list(a = 1, b = 2)
156+
names(lst)[1] <- "first"
157+
})
158+
159+
code_source <- "lst <- list(a = 1, b = 2)\nnames(lst)[1] <- \"first\""
160+
161+
testthat::expect_equal(get_code(td, names = "lst"), code_source)
162+
})
163+
164+
it("tracks -> operator with subassignments", {
165+
td <- qenv() |>
166+
within({
167+
x <- 1:10
168+
c(10, 20, 30) -> x[1:3] # nolint: assignment.
169+
})
170+
171+
code_source <- "x <- 1:10\nx[1:3] <- c(10, 20, 30)"
172+
173+
testthat::expect_equal(get_code(td, names = "x"), code_source)
174+
})
175+
176+
it("tracks attributes() function with subassignments", {
177+
td <- qenv() |>
178+
within({
179+
x <- 1:5
180+
attributes(x)$names <- letters[1:5]
181+
})
182+
183+
code_source <- "x <- 1:5\nattributes(x)$names <- letters[1:5]"
184+
185+
testthat::expect_equal(get_code(td, names = "x"), code_source)
186+
})
187+
188+
it("handles complex nested subassignments", {
189+
td <- qenv() |>
190+
within({
191+
df <- data.frame(x = 1:5, y = 6:10)
192+
df[df$x > 2, "y"][1:2] <- c(99, 100)
193+
})
194+
195+
code_source <- "df <- data.frame(x = 1:5, y = 6:10)\ndf[df$x > 2, \"y\"][1:2] <- c(99, 100)"
196+
197+
testthat::expect_equal(get_code(td, names = "df"), code_source)
198+
})
199+
200+
it("handles subassignments with multiple operators", {
201+
td <- qenv() |>
202+
within({
203+
lst <- list(a = list(b = 1, c = 2))
204+
lst$a$b[2] <- 99
205+
})
206+
207+
code_source <- "lst <- list(a = list(b = 1, c = 2))\nlst$a$b[2] <- 99"
208+
209+
testthat::expect_equal(get_code(td, names = "lst"), code_source)
210+
})
211+
212+
it("handles subassignments with data frame column creation", {
213+
td <- qenv() |>
214+
within({
215+
df <- data.frame(x = 1:3)
216+
df$new_col <- c("a", "b", "c")
217+
})
218+
219+
code_source <- "df <- data.frame(x = 1:3)\ndf$new_col <- c(\"a\", \"b\", \"c\")"
220+
221+
testthat::expect_equal(get_code(td, names = "df"), code_source)
222+
})
223+
224+
it("handles subassignments with matrix indexing", {
225+
td <- qenv() |>
226+
within({
227+
mat <- matrix(1:9, nrow = 3)
228+
mat[1:2, 2:3] <- matrix(0, nrow = 2, ncol = 2)
229+
})
230+
231+
code_source <- "mat <- matrix(1:9, nrow = 3)\nmat[1:2, 2:3] <- matrix(0, nrow = 2, ncol = 2)"
232+
233+
testthat::expect_equal(get_code(td, names = "mat"), code_source)
234+
})
235+
236+
it("handles subassignments with logical indexing", {
237+
td <- qenv() |>
238+
within({
239+
vec <- 1:10
240+
vec[vec %% 2 == 0] <- vec[vec %% 2 == 0] * 2
241+
})
242+
243+
code_source <- "vec <- 1:10\nvec[vec%%2 == 0] <- vec[vec%%2 == 0] * 2"
244+
245+
testthat::expect_equal(get_code(td, names = "vec"), code_source)
246+
})
247+
})

0 commit comments

Comments
 (0)