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.
(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"))https://gist.github.com/danieroux/1451597e9d77a5c26f023cc076b5b9b3 is what I am seeing
yes this is known
Bah. Do you have a link at hand perhaps?
And also @nbtheduke, this is above both functions 😆
#_{:splint/disable [naming/lisp-case]}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
This is different though, the compilation succeeds.
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
the second overwrites the first
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.
hmmm both can be called? from the repl?
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 calledi don't understand what those are telling me but i'll trust you
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
And to be clear: I would have been very happy if the AOT compilation failed with an error.
i mean in a repl
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=>hmm interesting
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.
(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)
there you go
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.
i wish they'd gone with _MINUS_ but hard to anticipate this 20 years ago
My wish is for the compilation to fail, and secondarily for past-self to not make weird names
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 🙂 ).(I too look back at some of the code my past-self wrote and wish it were different!)
Sounds like a good request for a lint tool warning.
Probably a minor variation to checks that kondo already does?