Skip to content

Fix internal names colliding with argument names in macros - #335

Open
devmotion wants to merge 2 commits into
masterfrom
dmw/self-name-collision
Open

devmotion wants to merge 2 commits into
masterfrom
dmw/self-name-collision

Conversation

@devmotion

Copy link
Copy Markdown
Member

Since #328, @grad_from_chainrules k(func, x::TrackedArray) fails with syntax: function argument name not unique: "func", which breaks ImplicitAD and its dependents (#334).

The self argument func is not escaped, so macro hygiene should rename it. It doesn't, because of a quirk in Julia's hygiene (src/macroexpand.scm):

  • Escaped argument names that are bare symbols are deliberately left unrenamed (safe-llist-keyword-args: "count escaped argument names as 'keywords' to prevent renaming").
  • The name or self position of a definition, e.g. (func::T)(…) or a local function closure(…), is resolved with that same mapping. So an internal name there that coincides with an escaped argument name stays unrenamed as well, and collides.

Minimal example without ReverseDiff:

struct S end
macro a(T, n) quote (func::$(esc(T)))($(esc(n))) = func end end
@macroexpand @a S func  # (func::S)(func) = func

Trying every internal name as an argument name in both macros found one more case: an untyped argument named closure in @grad fails with cannot add method to function argument closure. The existing test missed it because typed arguments are escaped as a whole :: expression, which hygiene does not pin. Contrary to the guess in #334, func works in @grad. All other internal names (args, kwargs, tp, back, output, …) work in both macros.

Hygiene alone cannot fix this: each macro has to put one of its own names in such a position. Dropping the self name and calling $f in the body would let an argument named like the function shadow it again, which #328 avoided on purpose. So this PR uses a gensym for exactly these two names: the self argument in @grad_from_chainrules and the local function in @grad.

Regression tests: an untyped argument func in @grad_from_chainrules (as in ImplicitAD), and untyped closure and func in @grad.

Fixes #334.

🤖 Generated with Claude Code

Julia's hygiene does not rename a definition's name or self argument if
an escaped argument of that definition has the same name. So
`@grad_from_chainrules k(func, x::TrackedArray)` failed with "function
argument name not unique", and `@grad function k(closure, x)` with
"cannot add method to function argument closure". Use a gensym for
these two names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.78%. Comparing base (31b0e3d) to head (0dad5d8).

Additional details and impacted files
@@           Coverage Diff           @@
##           master     #335   +/-   ##
=======================================
  Coverage   94.77%   94.78%           
=======================================
  Files          19       19           
  Lines        1703     1705    +2     
=======================================
+ Hits         1614     1616    +2     
  Misses         89       89           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@devmotion
devmotion force-pushed the dmw/self-name-collision branch from 202e204 to ee0b544 Compare November 13, 2026 14:19
@devmotion

Copy link
Copy Markdown
Member Author

Reported the underlying hygiene bug upstream: JuliaLang/julia#64945.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1.18.8: @grad_from_chainrules fails when a rule argument is named func ("function argument name not unique"), breaks ImplicitAD

1 participant