问题陈述:某些规范实现返回没有生成器但返回nil,在它们的gen*实现中,当它们的*recursion-limit*达到时(例如s/or)。规范实现组合其他规范时,有时会从其他规范的gen*中接收无生成器,并相应地调整自己的gen*的行为,有时甚至会什么也不返回(例如s/or的gen*,如果其中的所有分支规范也没有生成器,则返回nil,否则只使用它获得的那些生成器)。但是,有许多规范不尊重从gen*接收无生成器(例如s/every,s/map-of),它们是许多实际递归规范的基本构建块。然后它们最终抛出“无法构建gen...”异常。
以下是一个具有实际规范的问题的简化示例(不是实际用例的说明)
;; A ::B是一个通过::B递归的分支进行s/or的规范
(s/def ::B (s/or :A ::A))
;; 一个::A是一个关键字到::Bs的映射(或者它为空,作为递归终止)
(s/def ::A (s/map-of keyword? ::B :gen-max 3))
:gen-max 3))
(gen/sample (s/gen ::A))
ExceptionInfo 在以下位置无法构建gen: [1 :A 1 :A 1 :A 1 :A 1] for: :spec.examples.tree/B clojure.core/ex-info (core.clj:4725)
规范的上述有效值(我可以发给您一个实际用例,该用例强制执行上述模式,其中我们解析内部查询 DSL)是:{}、{:a {}}、{:foo {:bar {}}}等。
当前规范实现无法为上述规范生成值的缺陷在于,当::B的gen*返回nil时,::A的map-of不会生成一个空映射,而是抛出异常。s/every和所有派生规范都受此影响,可能还有其他规范。
建议的修复:规范的gen*实现必须始终尊重其他规范gen*返回nil,不通过抛出异常,而是通过调整返回的gen或自行返回nil,以便不返回gen的行为传播回调用方,此时应抛出异常。