clojure-dev

danieroux 2026-02-21T16:40:27.810079Z

Is this a known issue? We are doing something Historically Dumb here, but it caused a very subtle bug when we AOT compiled this namespace.

danieroux 2026-02-21T16:40:37.309339Z

(ns mangling)

(println "When AOT compiled, the second fn will _always_ be the chosen function")

(defn a_b->a-b
  []
  (println "first fn: a_b->a-b"))

(defn a-b->a_b
  []
  (println "second fn: a-b->a_b"))

(defn mangled
  [_args]
  (a_b->a-b) ; When AOT, this is _not_ the function that is called.
  (println "first fn: a_b->a-b called")

  (a-b->a_b)
  (println "second fn: a-b->a_b called"))

danieroux 2026-02-21T16:41:09.080969Z

https://gist.github.com/danieroux/1451597e9d77a5c26f023cc076b5b9b3 is what I am seeing

2026-02-21T16:41:38.841989Z

yes this is known

danieroux 2026-02-21T16:42:18.637329Z

Bah. Do you have a link at hand perhaps?

danieroux 2026-02-21T16:43:43.046459Z

And also @nbtheduke, this is above both functions 😆

#_{:splint/disable [naming/lisp-case]}

😍 1
2026-02-21T16:43:43.352669Z

i don't think there's a specific Ask for this but here's this one: https://ask.clojure.org/index.php/746/compilerexception-classformaterror-duplicate-namesignature

danieroux 2026-02-21T16:44:26.032079Z

This is different though, the compilation succeeds.

2026-02-21T16:58:42.733429Z

well you're compiling two vars with the same munged name so like calling def with the same name twice, it just creates the var twice in a row with different values

2026-02-21T16:58:54.185699Z

the second overwrites the first

danieroux 2026-02-21T17:00:07.195599Z

Yes, that makes sense in hindsight. What was surprising is that the compilation succeeded, and then the wrong function got called in AOT. So code that has been running fine for years failed after AOT compiling.

👍 1
2026-02-21T17:04:20.344569Z

hmmm both can be called? from the repl?

danieroux 2026-02-21T17:05:40.156959Z

100% yes:

$ make run-in-time

clj -X:in-time
When AOT compiled, the second fn will _always_ be the chosen function
first fn: a_b->a-b
first fn: a_b->a-b called
second fn: a-b->a_b
second fn: a-b->a_b called

$ make run-aot
rm -rf classes; mkdir classes
clojure -M -e "(compile 'mangling)"
When AOT compiled, the second fn will _always_ be the chosen function
mangling

clj -X:aot
When AOT compiled, the second fn will _always_ be the chosen function
second fn: a-b->a_b
first fn: a_b->a-b called
second fn: a-b->a_b
second fn: a-b->a_b called

2026-02-21T17:06:32.934629Z

i don't understand what those are telling me but i'll trust you

danieroux 2026-02-21T17:08:13.285769Z

I wonder how I could make it more obvious for everyone (and an Ask?) clj -X:in-time executes without AOT clj -X:aot executes the same code, with AOT Both executes the ns mangling

danieroux 2026-02-21T17:09:31.383949Z

And to be clear: I would have been very happy if the AOT compilation failed with an error.

2026-02-21T17:10:45.253869Z

i mean in a repl

danieroux 2026-02-21T17:14:17.936179Z

In the REPL it works as expected. Both functions are called and returns separately:

$ clojure
Clojure 1.12.3
user=> (load-file "mangling.clj")
When AOT compiled, the second fn will _always_ be the chosen function
#'mangling/mangled
user=> (in-ns 'mangling)
#object[clojure.lang.Namespace 0x2a49fe "mangling"]
mangling=> (a_b->a-b)
first fn: a_b->a-b
nil
mangling=> (a-b->a_b)
second fn: a-b->a_b
nil
mangling=>

2026-02-21T17:16:16.460369Z

hmm interesting

seancorfield 2026-02-21T17:18:57.060119Z

Presumably, this is because you have two distinct symbols in the ns so they can be looked up and called when run from source, but when you compile, they both map to the same class name and the second one overwrites the first one.

➕ 3
seancorfield 2026-02-21T17:20:16.221159Z

(even if there's a path for both of the Vars to be resolved and dereferenced, in AOT'd code, they both point to the same class for invocation)

2026-02-21T17:21:03.190819Z

there you go

seancorfield 2026-02-21T17:22:03.983219Z

You'll see the same with any munged names that collide, e.g., foo* and foo__STAR__, but those are less likely than the - / _ munging collision.

2026-02-21T17:26:29.653369Z

i wish they'd gone with _MINUS_ but hard to anticipate this 20 years ago

danieroux 2026-02-21T17:28:06.006889Z

My wish is for the compilation to fail, and secondarily for past-self to not make weird names

😅 2
seancorfield 2026-02-21T17:30:55.221689Z

Compilation succeeds for the same reason that this is legal Clojure:

(defn bar [x] x)
(def a (bar 13))
(defn bar [x] (* 2 x))
(def b (bar 42))
That has well-defined semantics (even if it is a bad idea 🙂 ).

seancorfield 2026-02-21T17:33:06.719449Z

(I too look back at some of the code my past-self wrote and wish it were different!)

💯 1
2026-02-25T19:51:45.751389Z

Sounds like a good request for a lint tool warning.

2026-02-25T19:52:15.224879Z

Probably a minor variation to checks that kondo already does?