Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# coro (development version)

* Generators and async functions no longer hang when their body contains an empty `{}` block, such as `generator(function() {})`. Empty blocks now behave like `NULL` (#75).

* Fixed performance issue related to JIT compilation of generator/async instances (#71). The JIT compiler is now able to properly cache the instances, making repeated instantiations fast, and preventing the JIT from kicking in on every call.

* Async functions and generators can now be R6 methods (#63).
Expand Down
11 changes: 11 additions & 0 deletions R/parser.R
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,11 @@ block_states <- function(block, counter, continue, last, return, info) {
saved_last <- last
last <- FALSE

# An empty block has no states, which would loop forever (#75)
if (is_null(node_cdr(block))) {
block <- quote({ NULL })
}

block <- duplicate(block, shallow = TRUE)
refs <- block_refs(block)
states <- NULL
Expand Down Expand Up @@ -412,6 +417,12 @@ block_states <- function(block, counter, continue, last, return, info) {
},
`{` = {
nested_node <- node_cdr(expr)
# Splicing an empty block adds nothing and can drop the final
# state, so treat it as `NULL` (#75)
if (is_null(nested_node)) {
node_poke_car(node, NULL)
next
}
nested_refs <- block_refs(expr)
splice(nested_node, nested_refs)
next
Expand Down
7 changes: 7 additions & 0 deletions tests/testthat/helper-flowery.R
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,10 @@ expect_exhausted <- function(x) {
local_methods <- function(..., .frame = caller_env()) {
local_bindings(..., .env = global_env(), .frame = .frame)
}

# Turns an infinite loop into an error so a hang fails the test
with_time_limit <- function(expr, seconds = 5) {
setTimeLimit(elapsed = seconds, transient = TRUE)
on.exit(setTimeLimit(elapsed = Inf))
expr
}
7 changes: 7 additions & 0 deletions tests/testthat/test-async.R
Original file line number Diff line number Diff line change
Expand Up @@ -405,3 +405,10 @@ test_that("async methods in R6 classes", {
expect_promise(out, status = "pending")
})
})

test_that("empty `{}` blocks behave like `NULL` (#75)", {
with_time_limit({
expect_null(wait_for(async(function() {})()))
expect_null(wait_for(async_generator(function() {})()()))
})
})
9 changes: 9 additions & 0 deletions tests/testthat/test-generator.R
Original file line number Diff line number Diff line change
Expand Up @@ -609,3 +609,12 @@ test_that("compiled instances do not pin their environment (#36)", {

expect_null(wref_key(ref))
})

test_that("empty `{}` blocks behave like `NULL` (#75)", {
with_time_limit({
expect_exhausted(generator(function() {})()())
expect_equal(collect(generator(function() { yield(1); {} })()), list(1))
expect_equal(collect(generator(function() { {}; yield(1) })()), list(1))
expect_equal(collect(generator(function() { {}; yield(1); {} })()), list(1))
})
})
Loading