borkdude / edamame

Configurable EDN/Clojure parser with location metadata

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Splicing Reader conditional breaks map literals when :read-cond not :allow

NoahTheDuke opened this issue · comments

commented

version

1.3.22

platform

Ubuntu

problem

Parsing a map literal that has a splicing reader conditional in it throws an exception.

repro

$ deps-try borkdude/edamame 1.3.22
[Rebel readline] Type :repl/help for online help info
user=> (require '[edamame.core :as e])
nil
user=> (e/parse-string-all "(def example {:a 1 #?@(:clj [:b 2 :c 3])})" {:read-cond :allow})
[(def example {:a 1})]
user=> (e/parse-string-all "(def example {:a 1 #?@(:clj [:b 2 :c 3])})" {:read-cond :preserve})
Execution error (ExceptionInfo) at edamame.impl.parser/throw-reader (parser.cljc:44).
The map literal starting with :a contains 3 form(s). Map literals must contain an even number of forms.
user=> (e/parse-string-all "(def example {:a 1 #?@(:clj [:b 2 :c 3])})" {:read-cond (fn [obj] (list 'read-cond obj))})
Execution error (ExceptionInfo) at edamame.impl.parser/throw-reader (parser.cljc:44).
The map literal starting with :a contains 3 form(s). Map literals must contain an even number of forms.

expected behavior

I'm honestly not sure how to handle it. Unlike clj-kondo (cf clj-kondo #2049), edamame doesn't produce multiple versions of a given string/file so it's not really feasible to expand in the same way.

Maybe an edamame keyword to vector that contains each splicing reader conditional? This would work with :allow, :preserve, and the function option.

{:a 1 #?@(:clj [:b 2 :c 3]) #?@(:clj [:d 4 :e 5])} read in as {:a 1 :edamame.impl.parser/read-cond [(:clj [:b 2 :c 3]) (:clj [:d 4 :e 5])]}. That doesn't warn when one of them will produce an incorrect map (odd number of entries in the spliced vector), but that feels outside of edamame's scope.

full stack trace

user=> *e
#error {
 :cause "The map literal starting with :a contains 3 form(s). Map literals must contain an even number of forms."
 :data {:type :edamame/error, :row 1, :col 14}
 :via
 [{:type clojure.lang.ExceptionInfo
   :message "The map literal starting with :a contains 3 form(s). Map literals must contain an even number of forms."
   :data {:type :edamame/error, :row 1, :col 14}
   :at [edamame.impl.parser$throw_reader invokeStatic "parser.cljc" 44]}]
 :trace
 [[edamame.impl.parser$throw_reader invokeStatic "parser.cljc" 44]
  [edamame.impl.parser$throw_reader invoke "parser.cljc" 31]
  [edamame.impl.parser$throw_odd_map invokeStatic "parser.cljc" 507]
  [edamame.impl.parser$throw_odd_map invoke "parser.cljc" 505]
  [edamame.impl.parser$parse_map invokeStatic "parser.cljc" 525]
  [edamame.impl.parser$parse_map invoke "parser.cljc" 517]
  [edamame.impl.parser$dispatch invokeStatic "parser.cljc" 643]
  [edamame.impl.parser$dispatch invoke "parser.cljc" 570]
  [edamame.impl.parser$parse_next invokeStatic "parser.cljc" 710]
  [edamame.impl.parser$parse_next invoke "parser.cljc" 696]
  [edamame.impl.parser$parse_next invokeStatic "parser.cljc" 697]
  [edamame.impl.parser$parse_next invoke "parser.cljc" 696]
  [edamame.impl.parser$parse_to_delimiter invokeStatic "parser.cljc" 202]
  [edamame.impl.parser$parse_to_delimiter invoke "parser.cljc" 189]
  [edamame.impl.parser$parse_to_delimiter invokeStatic "parser.cljc" 191]
  [edamame.impl.parser$parse_to_delimiter invoke "parser.cljc" 189]
  [edamame.impl.parser$parse_list invokeStatic "parser.cljc" 223]
  [edamame.impl.parser$parse_list invoke "parser.cljc" 222]
  [edamame.impl.parser$dispatch invokeStatic "parser.cljc" 641]
  [edamame.impl.parser$dispatch invoke "parser.cljc" 570]
  [edamame.impl.parser$parse_next invokeStatic "parser.cljc" 710]
  [edamame.impl.parser$parse_next invoke "parser.cljc" 696]
  [edamame.impl.parser$parse_next invokeStatic "parser.cljc" 697]
  [edamame.impl.parser$parse_next invoke "parser.cljc" 696]
  [edamame.impl.parser$parse_string_all invokeStatic "parser.cljc" 842]
  [edamame.impl.parser$parse_string_all invoke "parser.cljc" 836]
  [edamame.core$parse_string_all invokeStatic "core.cljc" 75]
  [edamame.core$parse_string_all invoke "core.cljc" 69]

@NoahTheDuke

The above problem is the same with Clojure's own reader:

$ clj
Clojure 1.11.0
user=> (read-string {:read-cond :preserve} "(def example {:a 1 #?@(:clj [:b 2 :c 3])})")
Execution error at user/eval1 (REPL:1).
Map literal must contain an even number of forms

How clj-kondo does this: it first parses the file using the first reader conditional feature, then lints that and then does the same for the second reader conditional.

What I'm saying is: parse the file twice:

user=> (e/parse-string "(def example {:a 1 #?@(:clj [:b 2 :c 3])})" {:read-cond :allow :features #{:clj}})
(def example {:a 1, :b 2, :c 3})
user=> (e/parse-string "(def example {:a 1 #?@(:clj [:b 2 :c 3])})" {:read-cond :allow :features #{:cljs}})
(def example {:a 1})

and then work with those results.

commented

Hmm okay.