core-typed

HS Son 2025-06-24T06:58:42.539269Z

Hi, I think I'm facing a bug when using unchecked mode. For example, when I write code like below, it should fail on type check using (t/check-ns-clj 'typed-example.unchecked-flow {:check-config {:unannotated-var :unchecked}}) but it says :ok.

(ns ^:typed.clojure typed-example.unchecked-flow
  (:require [malli.core :as m]))

;; Typed function that expects an int
(m/=> requires-int [:-> :int :string])
(defn requires-int [x] (str "Got: " x))

;; Unannotated function (gets Unchecked type)
(defn unannotated-fn [x] x)

;; BUG: This should FAIL type checking but PASSES with :unchecked
(defn demonstrate-bug []
  (let [unchecked-val (unannotated-fn 42)]
    (cond
      (string? unchecked-val) "it's a string"
      ;; This :else branch should be type-checked but isn't!
      :else (requires-int "NOT-AN-INT"))))
I suspect that if there's a unchecked var inside cond branch, typed clojure assumes that :else branch is always unreachable? So I changed typed.cljc.checker.check.if a bit to fix this.
(defn check-if [...]
  (let [unchecked-mode? (= :unchecked (get-in opts [::vs/check-config :unannotated-var]))
        chk-thn #(let [[env-thn reachable+] (update-lex+reachable lex-env fs+ opts)
                       forced-reachable+ (or reachable+ unchecked-mode?)]
                   {:env-thn env-thn
                    :cthen (check-if-reachable then env-thn forced-reachable+ expected opts)})
        chk-els #(let [[env-els reachable-] (update-lex+reachable lex-env fs- opts)
                       forced-reachable- (or reachable- unchecked-mode?)]
                   {:env-els env-els
                    :celse (check-if-reachable else env-els forced-reachable- expected opts)})]))
This way the problem solved. Or, maybe we can fix reachability on env+ and remove* function to properly address unchecked type. Can I write a PR for this? Or am I doing wrong about this?

2025-07-01T01:23:05.992779Z

Good idea.

2025-07-01T01:27:43.543859Z

Maybe it's really a set? :unannotated-var #{:any :from-arglists} Could also have :from-runtime, e.g., just type the var as (Var (Val @var)) if it's a number.

2025-07-01T01:28:33.784249Z

or perhaps a sequential for reproducibility?

2025-07-01T01:28:58.953359Z

:unannotated-var [:from-runtime :from-arglists :any] tries to find a decent type from left-to-right.

2025-07-01T01:30:21.767859Z

then each one could be configured with overrides. :unannotated-var [{:strategy :from-runtime, :exclude [#'my-var]} ...]

2025-06-24T17:16:20.188189Z

> Or, maybe we can fix reachability on env+ and remove* function to properly address unchecked type. Yes it needs to be here. Please file an issue, since this is performance-sensitive code. I really don't like Unchecked, perhaps there is a better solution. For example, inferring a generous function type like [Any -> Any] instead of Uncheckedfor annotated-fn.

👍 1
2025-06-24T17:16:32.377419Z

This is trivial from the arglists.

2025-06-24T17:17:17.743759Z

and we can add another :unchecked-var option like :from-arglists.

👍 1
HS Son 2025-06-30T02:49:31.138449Z

Thanks for replying. What do you think about improving :unannotated-var :any to properly address a function?

HS Son 2025-07-07T11:28:38.918619Z

Seems good. What about starting with some simple improvements like, inferring to [Any * -> Any]?

;; In typed.clj.checker.check/check-var
;; ...
(impl/impl-case opts
        :clojure (= :any (:unannotated-var check-config))
        :cljs nil)
      (do
        (let [var-value (try @var (catch Exception _ nil))
              inferred-type (if (fn? var-value)
                              ;; If it's a function, assign [Any * -> Any] type
                              (do
                                (println (str "Inferring " vsym " dereference as [Any * -> Any] (function)"))
                                (r/make-Function [] r/-any :rest r/-any))
                              ;; Otherwise, assign Any type
                              (do
                                (println (str "Inferring " vsym " dereference as Any"))
                                r/-any))]
          (assoc expr
                 u/expr-type (below/maybe-check-below
                               (r/ret inferred-type)
                               expected
                               opts))))

2025-07-07T22:30:50.040649Z

Yep that's good. I'm envisioning a cond that starts with :arglists meta then move to stuff like this (this is also fine to start with).