tests/testthat/test-reexport.R

# Tests for re-export functionality

write_module <- function(path, name, body) {
  lines <- c(
    sprintf("(module %s", name),
    body,
    ")"
  )
  writeLines(lines, path)
}

thin <- make_cran_thinner()

test_that("explicit re-export: imported symbol available to consumers", {
  thin()
  eng <- make_engine()
  tmp_dir <- tempfile()
  dir.create(tmp_dir)
  on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)

  # Module B exports foo
  write_module(file.path(tmp_dir, "b.arl"), "b", c(
    "  (export foo bar)",
    "  (define foo 42)",
    "  (define bar 99)"
  ))

  # Module A imports from B, re-exports foo along with own binding
  write_module(file.path(tmp_dir, "a.arl"), "a", c(
    "  (export foo baz)",
    '  (import "b.arl" :refer :all)',
    "  (define baz 7)"
  ))

  old_wd <- getwd()
  setwd(tmp_dir)
  on.exit(setwd(old_wd), add = TRUE)

  eng$load_file_in_env(file.path(tmp_dir, "a.arl"))

  # Module C imports from A
  eng$eval_text('(import a :refer :all)')
  expect_equal(eng$eval_text("foo"), 42)
  expect_equal(eng$eval_text("baz"), 7)
})

test_that("export-all :re-export includes imported symbols", {
  thin()
  eng <- make_engine()
  tmp_dir <- tempfile()
  dir.create(tmp_dir)
  on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)

  write_module(file.path(tmp_dir, "provider.arl"), "provider", c(
    "  (export val)",
    "  (define val 100)"
  ))

  # Facade module re-exports everything
  write_module(file.path(tmp_dir, "facade.arl"), "facade", c(
    "  (export-all :re-export)",
    '  (import "provider.arl" :refer :all)',
    "  (define own-val 200)"
  ))

  old_wd <- getwd()
  setwd(tmp_dir)
  on.exit(setwd(old_wd), add = TRUE)

  eng$load_file_in_env(file.path(tmp_dir, "facade.arl"))

  entry <- engine_field(eng, "env")$module_registry$get("facade")
  expect_true("val" %in% entry$exports)
  expect_true("own-val" %in% entry$exports)

  eng$eval_text('(import facade :refer :all)')
  expect_equal(eng$eval_text("val"), 100)
  expect_equal(eng$eval_text("own-val"), 200)
})

test_that("export-all without :re-export excludes imports (unchanged behavior)", {
  thin()
  eng <- make_engine()
  tmp_dir <- tempfile()
  dir.create(tmp_dir)
  on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)

  write_module(file.path(tmp_dir, "src.arl"), "src", c(
    "  (export x)",
    "  (define x 1)"
  ))

  write_module(file.path(tmp_dir, "consumer.arl"), "consumer", c(
    "  (export-all)",
    '  (import "src.arl" :refer :all)',
    "  (define own 2)"
  ))

  old_wd <- getwd()
  setwd(tmp_dir)
  on.exit(setwd(old_wd), add = TRUE)

  eng$load_file_in_env(file.path(tmp_dir, "consumer.arl"))

  entry <- engine_field(eng, "env")$module_registry$get("consumer")
  expect_true("own" %in% entry$exports)
  expect_false("x" %in% entry$exports)
})

test_that("re-export with :only modifier", {
  thin()
  eng <- make_engine()
  tmp_dir <- tempfile()
  dir.create(tmp_dir)
  on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)

  write_module(file.path(tmp_dir, "b.arl"), "b", c(
    "  (export x y)",
    "  (define x 10)",
    "  (define y 20)"
  ))

  write_module(file.path(tmp_dir, "a.arl"), "a", c(
    "  (export x y own)",
    '  (import "b.arl" :refer :all)',
    "  (define own 30)"
  ))

  old_wd <- getwd()
  setwd(tmp_dir)
  on.exit(setwd(old_wd), add = TRUE)

  eng$load_file_in_env(file.path(tmp_dir, "a.arl"))

  # Import only x from facade a
  eng$eval_text('(import a :refer (x))')
  expect_equal(eng$eval_text("x"), 10)
  expect_error(eng$eval_text("y"))
})

test_that("re-export with :as modifier for qualified access", {
  thin()
  eng <- make_engine()
  tmp_dir <- tempfile()
  dir.create(tmp_dir)
  on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)

  write_module(file.path(tmp_dir, "b.arl"), "b", c(
    "  (export val)",
    "  (define val 42)"
  ))

  write_module(file.path(tmp_dir, "a.arl"), "a", c(
    "  (export val)",
    '  (import "b.arl" :refer :all)'
  ))

  old_wd <- getwd()
  setwd(tmp_dir)
  on.exit(setwd(old_wd), add = TRUE)

  eng$load_file_in_env(file.path(tmp_dir, "a.arl"))
  eng$eval_text('(import a :as aa)')
  expect_equal(eng$eval_text("aa/val"), 42)
})

test_that("re-export with :rename modifier", {
  thin()
  eng <- make_engine()
  tmp_dir <- tempfile()
  dir.create(tmp_dir)
  on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)

  write_module(file.path(tmp_dir, "b.arl"), "b", c(
    "  (export val)",
    "  (define val 42)"
  ))

  write_module(file.path(tmp_dir, "a.arl"), "a", c(
    "  (export val)",
    '  (import "b.arl" :refer :all)'
  ))

  old_wd <- getwd()
  setwd(tmp_dir)
  on.exit(setwd(old_wd), add = TRUE)

  eng$load_file_in_env(file.path(tmp_dir, "a.arl"))
  eng$eval_text('(import a :rename ((val renamed-val)))')
  expect_equal(eng$eval_text("renamed-val"), 42)
})

test_that("macro re-export: consumer can use macro from transitive module", {
  thin()
  eng <- make_engine()
  tmp_dir <- tempfile()
  dir.create(tmp_dir)
  on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)

  # Module B defines a macro
  write_module(file.path(tmp_dir, "b.arl"), "b", c(
    "  (export my-when)",
    "  (defmacro my-when (test . body)",
    "    `(if ,test (begin ,@body) #f))"
  ))

  # Module A re-exports macro from B
  write_module(file.path(tmp_dir, "a.arl"), "a", c(
    "  (export my-when)",
    '  (import "b.arl" :refer :all)'
  ))

  old_wd <- getwd()
  setwd(tmp_dir)
  on.exit(setwd(old_wd), add = TRUE)

  eng$load_file_in_env(file.path(tmp_dir, "a.arl"))
  eng$eval_text('(import a :refer :all)')
  expect_equal(eng$eval_text("(my-when #t 42)"), 42)
  expect_equal(eng$eval_text("(my-when #f 42)"), FALSE)
})

test_that("transitive re-export: A -> B -> C -> D", {
  thin()
  eng <- make_engine()
  tmp_dir <- tempfile()
  dir.create(tmp_dir)
  on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)

  write_module(file.path(tmp_dir, "d.arl"), "d", c(
    "  (export deep-val)",
    "  (define deep-val 999)"
  ))

  write_module(file.path(tmp_dir, "c.arl"), "c", c(
    "  (export deep-val)",
    '  (import "d.arl" :refer :all)'
  ))

  write_module(file.path(tmp_dir, "b.arl"), "b", c(
    "  (export deep-val)",
    '  (import "c.arl" :refer :all)'
  ))

  old_wd <- getwd()
  setwd(tmp_dir)
  on.exit(setwd(old_wd), add = TRUE)

  eng$load_file_in_env(file.path(tmp_dir, "b.arl"))
  eng$eval_text('(import b :refer :all)')
  expect_equal(eng$eval_text("deep-val"), 999)
})

test_that("reload value change reflected through re-export (live forwarding)", {
  thin()
  eng <- make_engine()
  tmp_dir <- tempfile()
  dir.create(tmp_dir)
  on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)

  write_module(file.path(tmp_dir, "src.arl"), "src", c(
    "  (export val)",
    "  (define val 1)"
  ))

  write_module(file.path(tmp_dir, "facade.arl"), "facade", c(
    "  (export val)",
    '  (import "src.arl" :refer :all)'
  ))

  old_wd <- getwd()
  setwd(tmp_dir)
  on.exit(setwd(old_wd), add = TRUE)

  eng$load_file_in_env(file.path(tmp_dir, "facade.arl"))
  eng$eval_text('(import facade :refer :all)')
  expect_equal(eng$eval_text("val"), 1)

  # Change the source module's value and reload
  write_module(file.path(tmp_dir, "src.arl"), "src", c(
    "  (export val)",
    "  (define val 999)"
  ))

  src_path <- normalizePath(file.path(tmp_dir, "src.arl"), winslash = "/")
  eng$eval_text(sprintf('(import "%s" :reload)', src_path))

  # Re-export forwarding should reflect new value
  expect_equal(eng$eval_text("val"), 999)
})

test_that("reload does not cascade: facade export set unchanged after source reload", {
  thin()
  eng <- make_engine()
  tmp_dir <- tempfile()
  dir.create(tmp_dir)
  on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)

  write_module(file.path(tmp_dir, "src.arl"), "src", c(
    "  (export x)",
    "  (define x 1)"
  ))

  write_module(file.path(tmp_dir, "facade.arl"), "facade", c(
    "  (export-all :re-export)",
    '  (import "src.arl" :refer :all)'
  ))

  old_wd <- getwd()
  setwd(tmp_dir)
  on.exit(setwd(old_wd), add = TRUE)

  eng$load_file_in_env(file.path(tmp_dir, "facade.arl"))

  entry_before <- engine_field(eng, "env")$module_registry$get("facade")
  exports_before <- sort(entry_before$exports)

  # Reload source with a new export added
  write_module(file.path(tmp_dir, "src.arl"), "src", c(
    "  (export x y)",
    "  (define x 1)",
    "  (define y 2)"
  ))
  src_path <- normalizePath(file.path(tmp_dir, "src.arl"), winslash = "/")
  eng$eval_text(sprintf('(import "%s" :reload)', src_path))

  # Facade's export set should NOT have changed (no cascade)
  entry_after <- engine_field(eng, "env")$module_registry$get("facade")
  exports_after <- sort(entry_after$exports)
  expect_equal(exports_before, exports_after)
})

test_that("error: exporting undefined/unimported name", {
  thin()
  eng <- make_engine()
  tmp_dir <- tempfile()
  dir.create(tmp_dir)
  on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)

  write_module(file.path(tmp_dir, "bad.arl"), "bad", c(
    "  (export nonexistent)",
    "  (define something-else 1)"
  ))

  old_wd <- getwd()
  setwd(tmp_dir)
  on.exit(setwd(old_wd), add = TRUE)

  expect_error(
    eng$load_file_in_env(file.path(tmp_dir, "bad.arl")),
    "not defined or imported"
  )
})

test_that("export-all excludes _-prefixed names (private convention)", {
  thin()
  eng <- make_engine()
  tmp_dir <- tempfile()
  dir.create(tmp_dir)
  on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)

  write_module(file.path(tmp_dir, "mod.arl"), "mod", c(
    "  (export-all)",
    "  (define public-fn 1)",
    "  (define _private-helper 2)",
    "  (define __also-private 3)",
    "  (define _another 4)"
  ))

  old_wd <- getwd()
  setwd(tmp_dir)
  on.exit(setwd(old_wd), add = TRUE)

  eng$load_file_in_env(file.path(tmp_dir, "mod.arl"))

  entry <- engine_field(eng, "env")$module_registry$get("mod")
  expect_true("public-fn" %in% entry$exports)
  expect_false("_private-helper" %in% entry$exports)
  expect_false("__also-private" %in% entry$exports)
  expect_false("_another" %in% entry$exports)
})

test_that("export-all :re-export still excludes _-prefixed own bindings", {
  thin()
  eng <- make_engine()
  tmp_dir <- tempfile()
  dir.create(tmp_dir)
  on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)

  write_module(file.path(tmp_dir, "provider.arl"), "provider", c(
    "  (export val)",
    "  (define val 100)"
  ))

  write_module(file.path(tmp_dir, "facade.arl"), "facade", c(
    "  (export-all :re-export)",
    '  (import "provider.arl" :refer :all)',
    "  (define own-public 200)",
    "  (define _own-private 300)"
  ))

  old_wd <- getwd()
  setwd(tmp_dir)
  on.exit(setwd(old_wd), add = TRUE)

  eng$load_file_in_env(file.path(tmp_dir, "facade.arl"))

  entry <- engine_field(eng, "env")$module_registry$get("facade")
  expect_true("val" %in% entry$exports)
  expect_true("own-public" %in% entry$exports)
  expect_false("_own-private" %in% entry$exports)
})

test_that("explicit export of _-prefixed name still works", {
  thin()
  eng <- make_engine()
  tmp_dir <- tempfile()
  dir.create(tmp_dir)
  on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)

  write_module(file.path(tmp_dir, "mod.arl"), "mod", c(
    "  (export _intentionally-public helper)",
    "  (define _intentionally-public 1)",
    "  (define helper 2)"
  ))

  old_wd <- getwd()
  setwd(tmp_dir)
  on.exit(setwd(old_wd), add = TRUE)

  eng$load_file_in_env(file.path(tmp_dir, "mod.arl"))

  eng$eval_text('(import mod :refer :all)')
  expect_equal(eng$eval_text("_intentionally-public"), 1)
  expect_equal(eng$eval_text("helper"), 2)
})

test_that("compile error: export-all with bad modifier", {
  thin()
  eng <- make_engine()
  expect_error(
    eng$eval_text("(module bad (export-all :bad-modifier) (define x 1))"),
    "only accepts :re-export"
  )
})

Try the arl package in your browser

Any scripts or data that you put into this service are public.

arl documentation built on March 19, 2026, 5:09 p.m.