From 797204ab4266602ad044be05b6b026dac6aa6b55 Mon Sep 17 00:00:00 2001 From: i Date: Wed, 26 Aug 2026 17:28:19 -0400 Subject: [PATCH 1/6] Add one-shot fn compatibility patch --- .github/workflows/apply-fn-compat.yml | 42 +++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/apply-fn-compat.yml diff --git a/.github/workflows/apply-fn-compat.yml b/.github/workflows/apply-fn-compat.yml new file mode 100644 index 00000000000..d16e0db4f88 --- /dev/null +++ b/.github/workflows/apply-fn-compat.yml @@ -0,0 +1,42 @@ +name: Apply fn compatibility patch + +on: + push: + branches: + - fn-identifier-compat + +permissions: + contents: write + +jobs: + patch: + if: github.actor != 'github-actions[bot]' + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + with: + ref: fn-identifier-compat + + - name: Remove fn from reserved keywords + run: | + python3 - <<'PY' + from pathlib import Path + + needle = ' { "fn", FUNCTION },\n' + for filename in ("code/src/main/gram.y", "code/src/main/gram.c"): + path = Path(filename) + text = path.read_text() + count = text.count(needle) + if count != 1: + raise SystemExit(f"expected exactly one reserved fn entry in {filename}, found {count}") + path.write_text(text.replace(needle, "")) + PY + + - name: Commit parser patch + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add code/src/main/gram.y code/src/main/gram.c + git diff --cached --check + git commit -m "Restore fn as an ordinary identifier" + git push origin HEAD:fn-identifier-compat From be52dcddff3293bc3dd3112f9af9e5ff17b505a6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 26 Aug 2026 21:28:32 +0000 Subject: [PATCH 2/6] Restore fn as an ordinary identifier --- code/src/main/gram.c | 1 - code/src/main/gram.y | 1 - 2 files changed, 2 deletions(-) diff --git a/code/src/main/gram.c b/code/src/main/gram.c index d15fd020d38..6e871b82262 100644 --- a/code/src/main/gram.c +++ b/code/src/main/gram.c @@ -4493,7 +4493,6 @@ static keywords[] = { { "NA_character_", NUM_CONST }, { "NA_complex_", NUM_CONST }, { "function", FUNCTION }, - { "fn", FUNCTION }, { "λ", FUNCTION }, { "ƒ", FUNCTION }, { "while", WHILE }, diff --git a/code/src/main/gram.y b/code/src/main/gram.y index cf39e473eb8..e15141d81cb 100644 --- a/code/src/main/gram.y +++ b/code/src/main/gram.y @@ -2179,7 +2179,6 @@ static keywords[] = { { "NA_character_", NUM_CONST }, { "NA_complex_", NUM_CONST }, { "function", FUNCTION }, - { "fn", FUNCTION }, { "λ", FUNCTION }, { "ƒ", FUNCTION }, { "while", WHILE }, From f7b7a059b53655dbfa595433bab494e02afcacaa Mon Sep 17 00:00:00 2001 From: i Date: Wed, 26 Aug 2026 17:29:28 -0400 Subject: [PATCH 3/6] Remove one-shot fn patch workflow --- .github/workflows/apply-fn-compat.yml | 42 --------------------------- 1 file changed, 42 deletions(-) delete mode 100644 .github/workflows/apply-fn-compat.yml diff --git a/.github/workflows/apply-fn-compat.yml b/.github/workflows/apply-fn-compat.yml deleted file mode 100644 index d16e0db4f88..00000000000 --- a/.github/workflows/apply-fn-compat.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Apply fn compatibility patch - -on: - push: - branches: - - fn-identifier-compat - -permissions: - contents: write - -jobs: - patch: - if: github.actor != 'github-actions[bot]' - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v4 - with: - ref: fn-identifier-compat - - - name: Remove fn from reserved keywords - run: | - python3 - <<'PY' - from pathlib import Path - - needle = ' { "fn", FUNCTION },\n' - for filename in ("code/src/main/gram.y", "code/src/main/gram.c"): - path = Path(filename) - text = path.read_text() - count = text.count(needle) - if count != 1: - raise SystemExit(f"expected exactly one reserved fn entry in {filename}, found {count}") - path.write_text(text.replace(needle, "")) - PY - - - name: Commit parser patch - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add code/src/main/gram.y code/src/main/gram.c - git diff --cached --check - git commit -m "Restore fn as an ordinary identifier" - git push origin HEAD:fn-identifier-compat From 539d2719e1dadfb9a8e84e192a88bdb11c83d70a Mon Sep 17 00:00:00 2001 From: i Date: Wed, 26 Aug 2026 17:29:46 -0400 Subject: [PATCH 4/6] Document fn as an ordinary identifier --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 77c6c4cdccb..1664b7978ad 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -### fn λ → ÷ ≟ ← +### λ ƒ → ÷ ≟ ← → and ← can now be used for assignment, not just `<-` and `->`. -`function(x) x**3` can now be written `fn(x) x**3`, `λ(x) x**3`, or `ƒ(x) x**3`. +`function(x) x**3` can now be written `λ(x) x**3` or `ƒ(x) x**3`. ÷ means division. @@ -35,7 +35,7 @@ because ≟ tests equality. | `3 → x` | assign | | `3 ↠ x` | assign in an enclosing frame | | `left ≟ right` | test equality | -| `fn(x) expression`, `λ(x) expression`, or `ƒ(x) expression` | construct a function | +| `λ(x) expression` or `ƒ(x) expression` | construct a function | | `left ÷ right` | divide | @@ -61,7 +61,7 @@ stopifnot((answer = 4)) Code that used a single `=` as assignment must use an arrow in this R. The comma and argument-label rules have otherwise been left alone. -`fn` also used to be an ordinary name. It is reserved here, so old code using bare `fn` as a variable or argument must choose another name. This source uses `fun`; in particular, write `optim(par, fun = ...)` rather than `optim(par, fn = ...)`. +`fn` is an ordinary identifier, as in standard R. Code using `fn` as a variable, formal argument, or named argument does not need to change; for example, `optim(par, fn = ...)` remains valid. `fn(...)` is an ordinary call to a function named `fn`, not function-constructor syntax. ------ From bb706a4d55781f85a9dad4560e904418bbf3bc2c Mon Sep 17 00:00:00 2001 From: i Date: Wed, 26 Aug 2026 17:30:00 -0400 Subject: [PATCH 5/6] Add fn package compatibility smoke test --- code/tests/ir-package-compat.R | 40 ++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 code/tests/ir-package-compat.R diff --git a/code/tests/ir-package-compat.R b/code/tests/ir-package-compat.R new file mode 100644 index 00000000000..8658c538e4d --- /dev/null +++ b/code/tests/ir-package-compat.R @@ -0,0 +1,40 @@ +## IR compatibility smoke: bare fn must remain ordinary R syntax. + +parse1 <- function(text) parse(text = text, keep.source = FALSE)[[1L]] + +## Regression for the shape used by rlang: fn is a formal and is assigned to. +stopifnot(identical(parse1("fn"), as.name("fn"))) +call_trace_context <- eval(parse1( + "function(call, fn) { + fn <- fn + fn + }" +)) +stopifnot( + identical(names(formals(call_trace_context)), c("call", "fn")), + identical(call_trace_context(NULL, 7L), 7L) +) + +## The IR constructor spellings remain distinctive syntax. +lambda_identity <- eval(parse1("λ(fn) fn")) +florin_identity <- eval(parse1("ƒ(fn) fn")) +stopifnot( + identical(lambda_identity(11L), 11L), + identical(florin_identity(12L), 12L) +) + +## Upstream packages are the oracle: do not patch or translate them. +compat_lib <- Sys.getenv("IR_COMPAT_LIB", "build/compat-lib") +.libPaths(c(compat_lib, .libPaths())) + +packages <- c("rlang", "testthat", "tibble", "dplyr") +for (package in packages) { + suppressPackageStartupMessages( + library(package, character.only = TRUE) + ) +} + +testthat::test_that("IR spellings work inside upstream testthat", { + answer ← 8 ÷ 2 + testthat::expect_true(answer ≟ 4) +}) From 2ae021ee650194b27072cff73a349edd4b487051 Mon Sep 17 00:00:00 2001 From: i Date: Wed, 26 Aug 2026 17:30:12 -0400 Subject: [PATCH 6/6] Run unmodified R package compatibility smoke --- .github/workflows/lexer.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/lexer.yml b/.github/workflows/lexer.yml index 3e646dd3d48..c65765c189c 100644 --- a/.github/workflows/lexer.yml +++ b/.github/workflows/lexer.yml @@ -11,6 +11,7 @@ on: - code/src/include/Rinlinedfuns.h - code/tests/reg-encodings.R - code/tests/ir-unicode-lexer.R + - code/tests/ir-package-compat.R - .github/workflows/lexer.yml jobs: @@ -46,3 +47,14 @@ jobs: - name: Test Unicode lexer priority run: build/bin/R --vanilla < code/tests/ir-unicode-lexer.R + + - name: Install package compatibility oracle + run: | + mkdir -p build/compat-lib + IR_COMPAT_LIB="$PWD/build/compat-lib" \ + build/bin/Rscript -e 'install.packages(c("rlang", "testthat", "tibble", "dplyr"), lib = Sys.getenv("IR_COMPAT_LIB"), repos = "https://cloud.r-project.org", type = "source", Ncpus = 2L)' + + - name: Test unmodified-package compatibility + env: + IR_COMPAT_LIB: ${{ github.workspace }}/build/compat-lib + run: build/bin/R --vanilla < code/tests/ir-package-compat.R