当使用哈希的 case
语句发生哈希冲突时,它们会嵌入一个 condp
表达式。然而,这个表达式会直接插入常量,而没有进行引号引用。如果任何常量包含一个符号,则这些会导致错误。
例如
(case 'a #{a} 1 :foo 2 a 3)
导致: 无法在当前上下文中解析符号:a
当展开宏时可以看见这一点
(macroexpand '(case 'a #{a} 1 :foo 2 a 3))
(let* [G__151 (quote a)] (case* G__151 0 1 (throw (java.lang.IllegalArgumentException. (clojure.core/str "No matching clause: " G__151))) {0 [-1640525200 (clojure.core/condp clojure.core/= G__151 #{a} 1 a 3 (throw (java.lang.IllegalArgumentException. (clojure.core/str "No matching clause: " G__151))))], 1 [:foo 2]} :compact :hash-equiv #{0}))
这显示了 case*
的调用。前两个参数是 0,1。当它们与 clojure.core/shift-mask
一起使用时,'#{a}
和 'a
都会哈希到 0
。
第 5 个参数包含一个哈希码到值的映射,其中包含
{0 [-1640525200 (clojure.core/condp clojure.core/= G__151
#{a} 1
a 3
(throw (java.lang.IllegalArgumentException. (clojure.core/str "No matching clause: " G__151))))],
1 [:foo 2]}
这个 condp
块包含了未加引号的 #{a}
和 a
的值,所以它们将错误地尝试与当前绑定(如果存在)的 a
的值进行匹配。
由于 case
将每个测试常量解释为字面值(明确表示它们不需要带引号),因此在这些值出现在像这样的内嵌 Clojure 表达式中的时候应该将它们加上引号。
以下是一个建议的补丁,用于修正生成的代码
diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj
index 0dba6fcd..b21d4556 100644
--- a/src/clj/clojure/core.clj
+++ b/src/clj/clojure/core.clj
@@ -6690,7 +6690,7 @@ fails, attempts to require sym's namespace and retries."
(next ks) (next vs))
m))
assoc-multi (fn [m h bucket]
- (let [testexprs (apply concat bucket)
+ (let [testexprs (mapcat (fn [kv] [(list 'quote (first kv)) (second kv)]) bucket)
expr `(condp = ~expr-sym ~@testexprs ~default)]
(assoc m h expr)))
hmap (reduce1
diff --git a/test/clojure/test_clojure/control.clj b/test/clojure/test_clojure/control.clj
index 92846ad3..f3fe436b 100644
--- a/test/clojure/test_clojure/control.clj
+++ b/test/clojure/test_clojure/control.clj
@@ -421,7 +421,14 @@
:b 1
:c -2
:d 4294967296
- :d 3))
+ :d 3)
+ (are [result input] (= result (case input
+ #{a} :set
+ :foo :keyword
+ a :symbol))
+ :symbol 'a
+ :keyword :foo
+ :set '#{a}))
(testing "test warn for hash collision"
(should-print-err-message
#"Performance warning, .*:\d+ - hash collision of some case test constants; if selected, those entries will be tested sequentially..*\r?\n"