Skip to content

clj-kondo hook produces false "Expected: deref" errors for defstate vars #141

@jwr

Description

@jwr

Mount's clj-kondo hook for defstate causes false-positive errors like Expected: deref, received: nil. on every @state usage when the :stop function has a known return type (e.g. explicitly returns nil).

Disclaimer: I used AI to investigate this problem and suggest a fix. The problem is real, the fix as proposed works in my case for the warnings that I encounter. But perhaps there are better ways of addressing this: I do not claim to fully understand all the issues involved.

Minimal reproduction

;; src/repro.clj
(ns repro
  (:require [mount.core :refer [defstate]]))

(defn- start! [] {:bus "hello"})
(defn- stop! [_state] nil)

(defstate state
  :start (start!)
  :stop (stop! @state))

(defn get-bus []
  (:bus @state))
$ clj-kondo --lint src/repro.clj
src/repro.clj:12:10: error: Expected: deref, received: nil.

Root cause

The hook in resources/clj-kondo.exports/mount/mount/hooks/defstate.clj transforms:

(defstate state :start (start!) :stop (stop! @state))

Into:

(def state (do :start (start!) :stop (stop! @state)))

clj-kondo infers state's type from the last expression in the do block, which is (stop! @state). When stop! has a known return type (e.g. explicitly returns nil), clj-kondo infers state has type nil and flags every @state as an invalid deref.

The error does not appear when stop! has an unknown return type (e.g. ends with a call to an external function), because clj-kondo doesn't flag deref on unknown types. This makes the bug appear intermittently depending on how stop! is written.

Suggested fix

Here is the patched :else branch of the hook (edited after further testing locally):

      :else
      {:node (api/list-node
              (cond-> [(api/token-node 'def) n]
                docs (conj docs)
                true (conj (api/list-node
                            [(api/token-node 'atom)
                             (api/list-node
                              (list*
                               (api/token-node 'do)
                               args))]))))}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions