├── script ├── gen-doc.sh ├── repl ├── test ├── copy-sha ├── move-ns.sh ├── gen_doc.clj └── sync-master.sh ├── images └── part-of-typed-clojure-project.png ├── .gitignore ├── .github └── workflows │ └── clj.yml ├── src ├── main │ ├── clojure │ │ ├── clojure │ │ │ └── core │ │ │ │ └── typed │ │ │ │ ├── special_form.clj │ │ │ │ ├── unsafe.clj │ │ │ │ ├── env.cljc │ │ │ │ ├── hole.clj │ │ │ │ ├── all_envs.cljc │ │ │ │ ├── load_if_needed.cljc │ │ │ │ ├── import_macros.clj │ │ │ │ ├── util_vars.cljc │ │ │ │ ├── coerce_utils.clj │ │ │ │ ├── contract_utils.cljc │ │ │ │ ├── ast_ops.cljc │ │ │ │ ├── load.cljc │ │ │ │ ├── ast_utils.clj │ │ │ │ ├── macros.clj │ │ │ │ ├── errors.cljc │ │ │ │ ├── rules.clj │ │ │ │ ├── async.clj │ │ │ │ ├── type_contract.clj │ │ │ │ ├── internal.cljc │ │ │ │ └── contract.cljc │ │ └── cljs │ │ │ └── core │ │ │ ├── typed │ │ │ └── async.clj │ │ │ └── typed.clj │ └── cljs │ │ └── cljs │ │ └── core │ │ ├── typed.cljs │ │ └── typed │ │ └── async.cljs └── test │ └── clojure │ └── clojure │ └── core │ └── typed │ ├── test_rt.clj │ └── test_contract.clj ├── deps.edn ├── pom.xml ├── README.md └── epl-v10.html /script/gen-doc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | clj -Acodox:test 4 | -------------------------------------------------------------------------------- /script/repl: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | clj -Atest:nREPL "$@" 4 | -------------------------------------------------------------------------------- /script/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | clojure -Atest:runner "$@" 4 | -------------------------------------------------------------------------------- /script/copy-sha: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # no trailing new line 4 | git rev-parse HEAD | tr -d '\n' | pbcopy 5 | -------------------------------------------------------------------------------- /images/part-of-typed-clojure-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojure/core.typed.runtime.jvm/master/images/part-of-typed-clojure-project.png -------------------------------------------------------------------------------- /script/move-ns.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | git grep -l clojure\.core\.typed\.$1 | xargs sed -i '' "s/clojure\.core\.typed\.$1/clojure.core.typed.$2/g" 4 | -------------------------------------------------------------------------------- /script/gen_doc.clj: -------------------------------------------------------------------------------- 1 | (ns gen-doc 2 | (:require [codox.main :as codox])) 3 | 4 | (defn -main [& args] 5 | (codox/generate-docs 6 | {:source-paths ["src/main/clojure"] 7 | :output-path "target/doc"})) 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | *jar 3 | /lib/ 4 | /classes/ 5 | .lein* 6 | *.swp 7 | *.swo 8 | *.aux 9 | *.dvi 10 | *.pdf 11 | *.log 12 | *~ 13 | /.classpath 14 | /.project 15 | /.settings 16 | /bin 17 | .nrepl-port 18 | .repl 19 | .\#* 20 | .idea 21 | **.class 22 | *.iml 23 | .nrepl-port 24 | .DS_Store 25 | .cljs_* 26 | nashorn_* 27 | .cpcache 28 | .rebel_readline_history 29 | junit-output.xml 30 | -------------------------------------------------------------------------------- /.github/workflows/clj.yml: -------------------------------------------------------------------------------- 1 | name: Run tests with clj 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - name: Set up JDK 1.11 11 | uses: actions/setup-java@v1 12 | with: 13 | java-version: 1.11 14 | - uses: DeLaGuardo/setup-clojure@2.0 15 | with: 16 | tools-deps: latest 17 | - name: Run tests 18 | run: ./script/test 19 | -------------------------------------------------------------------------------- /script/sync-master.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | branch_name="$(git symbolic-ref HEAD 2>/dev/null)" || 4 | branch_name="(unnamed branch)" # detached HEAD 5 | 6 | branch_name=${branch_name##refs/heads/} 7 | 8 | MASTER="master" 9 | 10 | set -e 11 | 12 | if [ $branch_name != "$MASTER" ]; then 13 | echo "Must be on $MASTER" 14 | exit 1; 15 | fi 16 | 17 | git pull clojure --ff-only master 18 | git pull typedclojure --ff-only master 19 | git push typedclojure master 20 | git push clojure master 21 | -------------------------------------------------------------------------------- /src/main/clojure/clojure/core/typed/special_form.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Ambrose Bonnaire-Sergeant, Rich Hickey & contributors. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns ^:no-doc clojure.core.typed.special-form) 10 | 11 | (def special-form ::special-form) 12 | -------------------------------------------------------------------------------- /src/main/clojure/clojure/core/typed/unsafe.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Ambrose Bonnaire-Sergeant, Rich Hickey & contributors. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns ^:skip-wiki clojure.core.typed.unsafe) 10 | 11 | ;(ann ignore-with-unchecked-cast* [Any Any -> Any]) 12 | (defn ^:skip-wiki ignore-with-unchecked-cast* [form ty] 13 | form) 14 | 15 | (defmacro ignore-with-unchecked-cast 16 | "Assumes the form is well typed and annotates it with the provided 17 | type without verifying." 18 | [form ty] 19 | `(ignore-with-unchecked-cast* ~form '~ty)) 20 | -------------------------------------------------------------------------------- /src/main/clojure/clojure/core/typed/env.cljc: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Ambrose Bonnaire-Sergeant, Rich Hickey & contributors. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns clojure.core.typed.env) 10 | 11 | (def ^:dynamic *checker* nil) 12 | 13 | (defn checker-or-nil [] 14 | {:post [(or #?(:clj (instance? clojure.lang.IAtom %) 15 | :cljs (instance? Atom %)) 16 | (nil? %))]} 17 | *checker*) 18 | 19 | (defn checker [] 20 | (let [c *checker*] 21 | (assert #?(:clj (instance? clojure.lang.IAtom c) 22 | :cljs (instance? Atom c)) 23 | (str "No checker state: " (pr-str c))) 24 | c)) 25 | 26 | (defn empty-checker [] 27 | {}) 28 | 29 | (defn init-checker [] 30 | (atom (empty-checker) 31 | :validator map?)) 32 | 33 | (defn deref-checker [] 34 | {:post [(map? %)]} 35 | @(checker)) 36 | 37 | (defn swap-checker! [& args] 38 | (apply swap! (checker) args)) 39 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:deps {org.clojure/clojure {:mvn/version "1.9.0"}} 2 | :paths ["src/main/clojure" 3 | "src/main/cljs"] 4 | :aliases {:test 5 | {:extra-paths ["src/test/clojure"] 6 | :extra-deps {org.clojure/core.async {:mvn/version "0.3.465" 7 | :exclusions [org.clojure/clojure]} 8 | org.clojure/clojurescript {:mvn/version "0.0-2311" 9 | :exclusions [org.clojure/clojure]}}} 10 | :nREPL 11 | {:extra-deps 12 | {nrepl/nrepl {:mvn/version "0.6.0"} 13 | cider/cider-nrepl {:mvn/version "0.22.4"}}, 14 | :main-opts ["-m" "nrepl.cmdline" 15 | "--interactive" 16 | "--middleware" "[\"cider.nrepl/cider-middleware\"]" 17 | ]} 18 | 19 | :codox 20 | {:extra-deps {codox {:mvn/version "0.10.5"}} 21 | :extra-paths ["script"] 22 | :main-opts ["-m" "gen-doc"]} 23 | 24 | :runner 25 | {:extra-deps {com.cognitect/test-runner 26 | {:git/url "https://github.com/cognitect-labs/test-runner" 27 | :sha "3cb0a9daf1cb746259dc8309b218f9211ad3b33b"}} 28 | :main-opts ["-m" "cognitect.test-runner" 29 | "-d" "src/test/clojure" 30 | "-r" ".*" 31 | ]}}} 32 | -------------------------------------------------------------------------------- /src/main/clojure/clojure/core/typed/hole.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Ambrose Bonnaire-Sergeant, Rich Hickey & contributors. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns 10 | ^{:see-also [["http://matthew.brecknell.net/post/hole-driven-haskell/" "Hole Driven Development"]] 11 | :doc "This namespace contains easy tools for hole driven development"} 12 | clojure.core.typed.hole 13 | (:require [clojure.core.typed :refer [ann ann-datatype] :as t])) 14 | 15 | (ann silent-hole [-> t/Nothing]) 16 | (defn silent-hole 17 | "A silent hole. (silent-hole) passes for any other type 18 | when type checking. 19 | Use (silent-hole) as a placeholder for code. 20 | Throws an exception when evaluted." 21 | [] 22 | (throw (Exception. "silent hole"))) 23 | 24 | (ann-datatype NoisyHole []) 25 | (deftype NoisyHole []) 26 | 27 | (ann noisy-hole [-> NoisyHole]) 28 | (defn noisy-hole 29 | "A noisy hole. The type system will complain when 30 | (noisy-hole) is used in positions that expect a type 31 | more specific than Object or Any. 32 | Use (noisy-hole) as a placeholder for code. 33 | Throws an exception when evaluted." 34 | [] 35 | (throw (Exception. "noisy hole"))) 36 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | core.typed.runtime.jvm 5 | An optional type system for Clojure — zero-dependency artifact for annotations only. 6 | 0.7.2-SNAPSHOT 7 | core.typed.runtime.jvm 8 | 9 | 10 | 11 | Eclipse Public License 1.0 12 | http://opensource.org/licenses/eclipse-1.0.php 13 | repo 14 | 15 | 16 | 17 | 18 | org.clojure 19 | pom.contrib 20 | 0.3.0 21 | 22 | 23 | 24 | 1.9.0 25 | 26 | 27 | 28 | 29 | 30 | com.theoryinpractise 31 | clojure-maven-plugin 32 | 1.7.1 33 | 34 | 35 | clojure-compile 36 | none 37 | 38 | 39 | clojure-test 40 | none 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/main/cljs/cljs/core/typed.cljs: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Ambrose Bonnaire-Sergeant, Rich Hickey & contributors. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns cljs.core.typed 10 | "Internal functions for CLJS" 11 | (:refer-clojure :exclude [IFn]) 12 | (:require-macros 13 | [clojure.core.typed.bootstrap-cljs :as boot])) 14 | 15 | (defn ^:skip-wiki 16 | ann* 17 | "Internal use only. Use ann." 18 | [qsym typesyn check? form] 19 | nil) 20 | 21 | (defn ^:skip-wiki 22 | ann-protocol* 23 | "Internal use only. Use ann-protocol." 24 | [vbnd varsym mth] 25 | nil) 26 | 27 | (defn ^:skip-wiki 28 | ann-datatype* 29 | "Internal use only. Use ann-datatype." 30 | [vbnd dname fields opts] 31 | nil) 32 | 33 | (defn ^:skip-wiki 34 | def-alias* 35 | "Internal use only. Use defalias." 36 | [sym type] 37 | nil) 38 | 39 | (defn ^:skip-wiki 40 | inst-poly 41 | "Internal use only. Use inst." 42 | [inst-of types-syn] 43 | inst-of) 44 | 45 | (defn ^:skip-wiki 46 | loop>-ann 47 | "Internal use only. Use loop>" 48 | [loop-of bnding-types] 49 | loop-of) 50 | 51 | (defn ^:skip-wiki 52 | typed-deps* 53 | "Internal use only. Use typed-deps." 54 | [args] 55 | nil) 56 | 57 | ; populate this namespace with core aliases 58 | (boot/base-aliases) 59 | -------------------------------------------------------------------------------- /src/main/clojure/clojure/core/typed/all_envs.cljc: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Ambrose Bonnaire-Sergeant, Rich Hickey & contributors. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns ^:no-doc ^:skip-wiki clojure.core.typed.all-envs 10 | (:require [clojure.core.typed.util-vars :as vs] 11 | [clojure.core.typed.current-impl :as impl] 12 | [clojure.core.typed.load-if-needed :refer [load-if-needed]])) 13 | 14 | (def ^:private unparse-type (delay (impl/dynaload 'clojure.core.typed.checker.jvm.parse-unparse/unparse-type))) 15 | 16 | (let [nme-env (delay (impl/dynaload 'clojure.core.typed.checker.name-env/name-env))] 17 | (defn name-env [] 18 | (load-if-needed) 19 | (binding [vs/*verbose-types* true] 20 | (into {} 21 | (for [[k v] (@nme-env)] 22 | (when-not (keyword? v) 23 | [k (@unparse-type v)])))))) 24 | 25 | (let [venv (delay (impl/dynaload 'clojure.core.typed.checker.var-env/var-annotations))] 26 | (defn var-env [] 27 | (load-if-needed) 28 | (assert var-env) 29 | (binding [vs/*verbose-types* true] 30 | (into {} 31 | (for [[k v] (@venv)] 32 | [k (@unparse-type (force v))]))))) 33 | 34 | (defn all-envs-clj [] 35 | (impl/with-clojure-impl 36 | {:aliases (name-env) 37 | :vars (var-env)})) 38 | -------------------------------------------------------------------------------- /src/main/clojure/clojure/core/typed/load_if_needed.cljc: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Ambrose Bonnaire-Sergeant, Rich Hickey & contributors. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns ^:no-doc ^:skip-wiki clojure.core.typed.load-if-needed 10 | (:require [clojure.core.typed.errors :as err] 11 | #?(:clj [clojure.java.io :as io]) 12 | [clojure.core.typed.util-vars :as vs])) 13 | 14 | #?(:clj 15 | (defn load-if-needed 16 | "Load and initialize all of core.typed if not already" 17 | ([] (load-if-needed false)) 18 | ([cljs?] 19 | (when-not vs/*currently-loading* 20 | (binding [vs/*currently-loading* true] 21 | (when-not (io/resource "clojure/core/typed/checker/init.clj") 22 | (err/int-error "core.typed checker is not found on classpath")) 23 | (when-not (find-ns 'clojure.core.typed.checker.init) 24 | (require 'clojure.core.typed.checker.init)) 25 | (let [init-ns (find-ns 'clojure.core.typed.checker.init)] 26 | (assert init-ns) 27 | (when (or (not (@(ns-resolve init-ns 'loaded?))) 28 | (and cljs? 29 | (not (@(ns-resolve init-ns 'has-cljs-loaded?))))) 30 | (println "Initializing core.typed ...") 31 | (flush) 32 | (time (@(ns-resolve init-ns 'load-impl) cljs?)) 33 | (println "core.typed initialized.") 34 | (flush))))))) 35 | :cljs 36 | (defn load-if-needed 37 | "Load and initialize all of core.typed if not already" 38 | ([] (err/int-error "Cannot load the checker in CLJS")) 39 | ([cljs?] (err/int-error "Cannot load the checker in CLJS")))) 40 | -------------------------------------------------------------------------------- /src/main/clojure/clojure/core/typed/import_macros.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Ambrose Bonnaire-Sergeant, Rich Hickey & contributors. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns ^:no-doc clojure.core.typed.import-macros 10 | (:require [clojure.core :as core])) 11 | 12 | ;copied from ClojureScript 13 | (defmacro import-macros [ns [& vars]] 14 | (core/let [ns (find-ns ns) 15 | vars (map (core/fn [vsym] 16 | {:pre [(symbol? vsym)] 17 | :post [(instance? clojure.lang.Var %)]} 18 | (let [v (ns-resolve ns vsym)] 19 | (assert v (str "Internal error: " vsym " does not exist")) 20 | v)) 21 | vars) 22 | syms (map (core/fn [^clojure.lang.Var v] 23 | {:pre [(instance? clojure.lang.Var v)] 24 | :post [(symbol? %)]} 25 | (core/-> v .sym (with-meta {:macro true}))) 26 | vars) 27 | defs (map (core/fn [sym var] 28 | {:pre [(symbol? sym) 29 | (instance? clojure.lang.Var var)]} 30 | `(do (def ~sym (deref ~var)) 31 | ;for AOT compilation 32 | (alter-meta! (var ~sym) 33 | merge 34 | (dissoc (meta ~var) :ns :name) 35 | {:macro true}))) 36 | syms vars)] 37 | `(do ~@defs 38 | :imported))) 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # core.typed.runtime.jvm 2 | 3 | 4 | 5 | Runtime dependency needed to annotate Typed Clojure code in JVM Clojure. 6 | 7 | ## DEPRECATION NOTICE 8 | 9 | This repository is DEPRECATED and development has been moved 10 | to the [core.typed](https://github.com/clojure/core.typed) monorepo. 11 | Please follow [these](https://github.com/clojure/core.typed/blob/master/UPGRADING.md#upgrading-from-07x-to-monorepo) 12 | instructions to upgrade. 13 | 14 | ## Releases and Dependency Information 15 | 16 | Latest stable release is 0.7.1. 17 | 18 | * [All Released Versions](https://search.maven.org/search?q=g:org.clojure%20AND%20a:core.typed.runtime.jvm) 19 | 20 | [deps.edn](https://clojure.org/reference/deps_and_cli) dependency information: 21 | 22 | ```clj 23 | org.clojure/core.typed.runtime.jvm {:mvn/version "0.7.1"} 24 | ``` 25 | 26 | [Leiningen](https://github.com/technomancy/leiningen) dependency information: 27 | 28 | ```clojure 29 | [org.clojure/core.typed.runtime.jvm "0.7.1"] 30 | ``` 31 | 32 | [Maven](https://maven.apache.org/) dependency information: 33 | 34 | ```XML 35 | 36 | org.clojure 37 | core.typed.runtime.jvm 38 | 0.7.1 39 | 40 | ``` 41 | 42 | ## Continuous Integration 43 | 44 | [![CircleCI](https://circleci.com/gh/typedclojure/core.typed.runtime.jvm.svg?style=svg)](https://circleci.com/gh/typedclojure/core.typed.runtime.jvm) 45 | 46 | ## YourKit 47 | 48 | YourKit is kindly supporting core.typed and other open source projects with its full-featured Java Profiler. 49 | YourKit, LLC is the creator of innovative and intelligent tools for profiling 50 | Java and .NET applications. Take a look at YourKit's leading software products: 51 | 52 | * YourKit Java Profiler and 53 | * YourKit .NET Profiler. 54 | 55 | ## License 56 | 57 | Copyright © Ambrose Bonnaire-Sergeant, Rich Hickey & contributors. 58 | 59 | Licensed under the EPL (see the file epl-v10.html). 60 | -------------------------------------------------------------------------------- /src/main/clojure/clojure/core/typed/util_vars.cljc: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Ambrose Bonnaire-Sergeant, Rich Hickey & contributors. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns clojure.core.typed.util-vars) 10 | 11 | (defonce ^:skip-wiki registered-ann-ns (atom {:register? false :namespaces #{}})) 12 | 13 | (defonce ^:skip-wiki ^:dynamic *current-env* nil) 14 | (defonce ^:skip-wiki ^:dynamic *current-expr* nil) 15 | (defonce ^:skip-wiki ^:dynamic *in-check-form* nil) 16 | 17 | (defonce ^:dynamic 18 | ^{:doc 19 | "If true, print fully qualified types in error messages 20 | and return values. Bind around a type checking form like 21 | cf or check-ns. 22 | 23 | eg. 24 | (binding [*verbose-types* true] 25 | (cf 1 Number)) 26 | ;=> java.lang.Number"} 27 | *verbose-types* 28 | nil) 29 | 30 | (defonce ^:dynamic 31 | ^{:doc 32 | "If true, print complete forms in error messages. Bind 33 | around a type checking form like cf or check-ns. 34 | 35 | eg. 36 | (binding [*verbose-forms* true] 37 | (cf ['deep ['deep ['deep ['deep]]]] Number)) 38 | ;=> "} 39 | *verbose-forms* 40 | nil) 41 | 42 | (defonce ^:dynamic 43 | ^{:doc "If true, print tracing information during type checking."} 44 | *trace-checker* 45 | nil) 46 | 47 | (def ^:skip-wiki ^:dynamic *currently-loading* false) 48 | (defonce ^{:doc "Internal use only"} ^:skip-wiki ^:dynamic *already-checked* nil) 49 | (defonce ^{:doc "Internal use only"} ^:skip-wiki ^:dynamic *delayed-errors* nil) 50 | (defonce ^{:doc "Internal use only"} ^:skip-wiki ^:dynamic *analyze-ns-cache* nil) 51 | (defonce ^{:doc "Internal use only"} ^:skip-wiki ^:dynamic *checked-asts* nil) 52 | (defonce ^{:doc "Internal use only"} ^:skip-wiki ^:dynamic *lexical-env* nil) 53 | (defonce ^{:doc "Internal use only"} ^:skip-wiki ^:dynamic *can-rewrite* nil) 54 | (defonce ^{:doc "Internal use only"} ^:skip-wiki ^:dynamic *in-typed-load* nil) 55 | ;; keep track of state throughout a `load` 56 | (defonce ^{:doc "Internal use only"} ^:skip-wiki ^:dynamic *typed-load-atom* nil) 57 | (defonce ^{:doc "Internal use only"} ^:skip-wiki ^:dynamic *prepare-infer-ns* nil) 58 | (defonce ^{:doc "Internal use only"} ^:skip-wiki ^:dynamic *instrument-infer-config* nil) 59 | (defonce ^{:doc "Internal use only"} ^:skip-wiki ^:dynamic *check-config* nil) 60 | (defonce ^{:doc "Internal use only"} ^:skip-wiki ^:dynamic *custom-expansions* nil) 61 | ;;TODO replace with pass state 62 | (defonce ^{:doc "Internal use only"} ^:skip-wiki ^:dynamic *beta-count* nil) 63 | -------------------------------------------------------------------------------- /src/main/clojure/clojure/core/typed/coerce_utils.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Ambrose Bonnaire-Sergeant, Rich Hickey & contributors. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns ^:no-doc ^:skip-wiki clojure.core.typed.coerce-utils 10 | (:require [clojure.string :as str] 11 | [clojure.java.io :as io] 12 | [clojure.core.typed.current-impl :as impl]) 13 | (:import (clojure.lang RT Var))) 14 | 15 | ;(t/ann symbol->Class [Symbol -> Class]) 16 | (defn symbol->Class 17 | "Returns the Class represented by the symbol. Works for 18 | primitives (eg. byte, int). Does not further resolve the symbol." 19 | [sym] 20 | {:pre [(symbol? sym)] 21 | :post [(class? %)]} 22 | (case sym 23 | byte Byte/TYPE 24 | short Short/TYPE 25 | int Integer/TYPE 26 | long Long/TYPE 27 | float Float/TYPE 28 | double Double/TYPE 29 | boolean Boolean/TYPE 30 | char Character/TYPE 31 | (RT/classForName (str sym)))) 32 | 33 | ;(t/ann Class->symbol [Class -> Symbol]) 34 | (defn Class->symbol [^Class cls] 35 | {:pre [(class? cls)] 36 | :post [(symbol? %)]} 37 | (symbol (.getName cls))) 38 | 39 | ;(t/ann ^:no-check var->symbol [(Var Nothing Any) -> Symbol]) 40 | (defn var->symbol [^Var var] 41 | {:pre [(var? var)] 42 | :post [(symbol? %) 43 | (namespace %)]} 44 | (let [ns (.ns var) 45 | _ (assert ns)] 46 | (symbol (str (ns-name ns)) 47 | (str (.sym var))))) 48 | 49 | ;(t/ann ^:no-check kw->symbol [Kw -> Symbol]) 50 | (defn kw->symbol [kw] 51 | {:pre [(keyword? kw)] 52 | :post [(symbol? %)]} 53 | (symbol (namespace kw) 54 | (name kw))) 55 | 56 | (defn ns->file 57 | ([nsym] (ns->file nsym true)) 58 | ([nsym suffix?] 59 | {:pre [(symbol? nsym)] 60 | :post [(string? %)]} 61 | ;copied basic approach from tools.emitter.jvm 62 | (let [res (munge nsym) 63 | f (str/replace res #"\." "/") 64 | ex (when suffix? 65 | (impl/impl-case 66 | :clojure ".clj" 67 | :cljs ".cljs")) 68 | p (str f ex) 69 | p (if (or (io/resource p) 70 | (not suffix?)) 71 | p 72 | (str f ".cljc")) 73 | p (if (.startsWith p "/") (subs p 1) p)] 74 | p))) 75 | 76 | (defn ns->URL [nsym] 77 | {:pre [(symbol? nsym)] 78 | :post [((some-fn #(instance? java.net.URL %) 79 | nil?) 80 | %)]} 81 | (let [p (ns->file nsym)] 82 | (io/resource p))) 83 | 84 | (defn sym->kw [sym] 85 | {:pre [(symbol? sym)] 86 | :post [(keyword? %)]} 87 | (keyword (namespace sym) 88 | (name sym))) 89 | -------------------------------------------------------------------------------- /src/main/clojure/clojure/core/typed/contract_utils.cljc: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Ambrose Bonnaire-Sergeant, Rich Hickey & contributors. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns ^:no-doc clojure.core.typed.contract-utils 10 | {:skip-wiki true 11 | :core.typed {:collect-only true}} 12 | (:require [clojure.set :as set]) 13 | #?(:clj (:import (clojure.lang PersistentArrayMap)))) 14 | 15 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 16 | ;; Constraint shorthands 17 | 18 | (defn every-c? [c] 19 | #(every? c %)) 20 | 21 | (def nne-seq? (some-fn nil? (every-pred seq seq?))) 22 | 23 | #?(:clj 24 | (def namespace? #(instance? clojure.lang.Namespace %))) 25 | 26 | (defn =-c? [& as] 27 | #(apply = (concat as %&))) 28 | 29 | (defn hvector-c? [& ps] 30 | (apply every-pred vector? 31 | (map (fn [p i] #(p (nth % i false))) ps (range)))) 32 | 33 | (defn reduced-c? [c] 34 | (fn [r] 35 | (and (reduced? r) 36 | (c @r)))) 37 | 38 | (defn maybe-reduced-c? [c] 39 | (fn [r] 40 | (if (reduced? r) 41 | (c @r) 42 | (c r)))) 43 | 44 | (defn array-map-c? [ks-c? vs-c?] 45 | (every-pred #(instance? PersistentArrayMap %) 46 | #(every? ks-c? (keys %)) 47 | #(every? vs-c? (vals %)))) 48 | 49 | (defrecord OptionalKey [k]) 50 | 51 | (defn optional [k] 52 | (->OptionalKey k)) 53 | 54 | (defn hmap-c? [& key-vals] 55 | {:pre [(even? (count key-vals))]} 56 | (every-pred map? 57 | (fn [m] 58 | (letfn [(mandatory-check [m k vc] 59 | (and (contains? m k) 60 | (vc (get m k)))) 61 | (optional-check [m k vc] 62 | (or (not (contains? m k)) 63 | (mandatory-check m k vc)))] 64 | (every? identity 65 | (for [[k vc] (partition 2 key-vals)] 66 | (cond 67 | (instance? OptionalKey k) (optional-check m (:k k) vc) 68 | :else (mandatory-check m k vc)))))))) 69 | 70 | (defn hash-c? [ks-c? vs-c?] 71 | (every-pred map? 72 | #(every? ks-c? (keys %)) 73 | #(every? vs-c? (vals %)))) 74 | 75 | (defn set-c? [c?] 76 | (every-pred set? 77 | (every-c? c?))) 78 | 79 | (defn vec-c? [c?] 80 | (every-pred vector? 81 | (every-c? c?))) 82 | 83 | (defn sorted-set-c? [c?] 84 | (every-pred sorted? 85 | (set-c? c?))) 86 | 87 | (defn sequential-c? [c?] 88 | (every-pred sequential? 89 | (every-c? c?))) 90 | 91 | (def local-sym? (every-pred symbol? (complement namespace))) 92 | 93 | ;; FIXME when 1.7 is released, change to IAtom 94 | (defn atom? [v] (instance? #?(:clj clojure.lang.IAtom :cljs Atom) v)) 95 | -------------------------------------------------------------------------------- /src/test/clojure/clojure/core/typed/test_rt.clj: -------------------------------------------------------------------------------- 1 | (ns clojure.core.typed.test-rt 2 | (:require [clojure.core.typed :as t] 3 | [cljs.core.typed :as tcljs] 4 | [clojure.core.typed.errors :as err] 5 | [clojure.java.io :as io]) 6 | (:use clojure.test)) 7 | 8 | (deftest typed-clojure-loaded 9 | (is (nil? (require 'clojure.core.typed)))) 10 | 11 | (deftest async-ns 12 | (is (io/resource "clojure/core/typed/async.clj"))) 13 | 14 | (deftest checking-ops 15 | (is (err/tc-error-thrown? 16 | (t/load-if-needed))) 17 | (is (err/tc-error-thrown? 18 | (t/reset-caches))) 19 | (is (err/tc-error-thrown? 20 | (t/method-type 'foo))) 21 | (is (err/tc-error-thrown? 22 | (t/into-array> 'foo 'bar [1]))) 23 | (is (err/tc-error-thrown? 24 | (t/cf 1))) 25 | (is (err/tc-error-thrown? 26 | (t/check-form* 1))) 27 | (is (err/tc-error-thrown? 28 | (t/check-form-info 1))) 29 | (is (err/tc-error-thrown? 30 | (t/check-ns 'foo))) 31 | (is (err/tc-error-thrown? 32 | (t/check-ns-info 'foo))) 33 | (is (err/tc-error-thrown? 34 | (t/statistics ['foo]))) 35 | (is (err/tc-error-thrown? 36 | (t/var-coverage)))) 37 | 38 | (defmacro catch-compiler-exception 39 | [& body] 40 | `(try (do ~@body 41 | nil) 42 | (catch RuntimeException e# 43 | (if (instance? clojure.lang.ExceptionInfo e#) 44 | ; before clojure 1.7.0-alpha2 45 | (err/tc-error-thrown? 46 | (throw e#)) 47 | ; clojure 1.7.0-alpha2 48 | (err/tc-error-thrown? 49 | (throw (.getCause e#))))))) 50 | 51 | (deftest checking-cljs-ops 52 | (is (err/tc-error-thrown? 53 | (tcljs/load-if-needed))) 54 | (is (err/tc-error-thrown? 55 | (tcljs/reset-caches))) 56 | (is (err/tc-error-thrown? 57 | (tcljs/cf* 1 nil nil))) 58 | (is (err/tc-error-thrown? 59 | (tcljs/cf 1))) 60 | (is (err/tc-error-thrown? 61 | (tcljs/check-ns*))) 62 | (is (err/tc-error-thrown? 63 | (tcljs/check-ns* 'foo))) 64 | ; these throw at macroexpansion time 65 | (is (catch-compiler-exception 66 | (eval '(cljs.core.typed/check-ns)))) 67 | (is (catch-compiler-exception 68 | (eval '(cljs.core.typed/check-ns foo))))) 69 | 70 | (defmacro thrown-blame? [& e] 71 | `(try (try (do ~@e) 72 | false 73 | (catch clojure.lang.Compiler$CompilerException e# 74 | (throw (.source e#)))) 75 | (catch clojure.lang.ExceptionInfo e# 76 | (boolean (-> e# ex-data :blame))))) 77 | 78 | ; commented - these require c.t.lib.clojure which is not a dependency of this project atm 79 | 80 | #_ 81 | (deftest pred-test 82 | ;pred forces a few namespaces to load 83 | (is ((t/pred Number) 1))) 84 | 85 | #_ 86 | (deftest cast-test 87 | (is (= 1 (t/cast t/Int 1))) 88 | (is (= nil (t/cast nil nil))) 89 | (is (= 1 (t/cast t/Int 1))) 90 | ;; unions 91 | (is (thrown-blame? (t/cast t/Int nil 92 | {:positive '+ve 93 | :negative '-ve 94 | :file "my/file.clj" 95 | :line 20 96 | :column 30}))) 97 | (is (= 1 (t/cast (t/U t/Int) 1))) 98 | (is (thrown-blame? (t/cast (t/U t/Int) nil))) 99 | (is (thrown-blame? (t/cast (t/U t/Bool t/Int) nil))) 100 | (is (= 1 (t/cast (t/U (t/U t/Int)) 1))) 101 | (is (thrown-blame? (t/cast (t/U (t/U t/Int)) nil))) 102 | ;; intersections 103 | (is (= 1 (t/cast (t/I t/Int) 1))) 104 | (is (thrown-blame? (t/cast (t/I t/Int) nil))) 105 | (is (= 1 (t/cast (t/I (t/I t/Int)) 1))) 106 | (is (thrown-blame? (t/cast (t/I (t/I t/Int)) nil))) 107 | 108 | (is (thrown-blame? (t/cast (t/I (t/I t/Int)) nil))) 109 | ) 110 | -------------------------------------------------------------------------------- /src/main/clojure/clojure/core/typed/ast_ops.cljc: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Ambrose Bonnaire-Sergeant, Rich Hickey & contributors. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns ^:no-doc ^:skip-wiki clojure.core.typed.ast-ops 10 | (:require [clojure.core.typed.current-impl :as impl] 11 | [clojure.core.typed.errors :as err])) 12 | 13 | (defn resolve-Name [{:keys [name] :as expr}] 14 | {:pre [(#{:Name} (:op expr))]} 15 | (let [e (force (get (impl/alias-env) name)) 16 | _ (when-not e 17 | (err/int-error (str "No alias found for " name)))] 18 | e)) 19 | 20 | ;copied from tools.analyzer 21 | (defn children* 22 | "Return a vector of the children expression of the AST node, if it has any. 23 | The returned vector returns the childrens in the order as they appear in the 24 | :children field of the AST, and may be either a node or a vector of nodes." 25 | [{:keys [children] :as ast}] 26 | (when children 27 | (mapv ast children))) 28 | 29 | ;copied from tools.analyzer 30 | (defn update-children 31 | "Applies `f` to the nodes in the AST nodes children. 32 | Optionally applies `fix` to the children before applying `f` to the 33 | children nodes and then applies `fix` to the update children. 34 | An example of a useful `fix` function is `rseq`." 35 | ([ast f] (update-children ast f identity)) 36 | ([ast f fix] 37 | (if-let [c (children* ast)] 38 | (reduce (fn [ast [k v]] 39 | (assoc ast k (if (vector? v) 40 | (fix (mapv f (fix v))) 41 | (f v)))) 42 | ast (mapv list (fix (:children ast)) (fix c))) 43 | ast))) 44 | 45 | ;copied from tools.analyzer 46 | (defn rseqv 47 | "Same as (comp vec rseq)" 48 | [v] 49 | (vec (rseq v))) 50 | 51 | ;copied from tools.analyzer 52 | (defn walk 53 | "Walk the ast applying pre when entering the nodes, and post when exiting. 54 | If reversed? is not-nil, pre and post will be applied starting from the last 55 | children of the AST node to the first one." 56 | ([ast pre post] 57 | (walk ast pre post false)) 58 | ([ast pre post reversed?] 59 | (let [fix (if reversed? rseqv identity) 60 | walk #(walk % pre post reversed?)] 61 | (post (update-children (pre ast) walk fix))))) 62 | 63 | ;copied from tools.analyzer 64 | (defn prewalk 65 | "Shorthand for (walk ast f identity)" 66 | [ast f] 67 | (walk ast f identity)) 68 | 69 | ;copied from tools.analyzer 70 | (defn postwalk 71 | "Shorthand for (walk ast identity f reversed?)" 72 | ([ast f] 73 | (walk ast identity f false)) 74 | ([ast f reversed?] 75 | (walk ast identity f reversed?))) 76 | 77 | (declare -replace-frees) 78 | 79 | (defn replace-frees* [ast replacem] 80 | (update-children ast #(-replace-frees % replacem))) 81 | 82 | (defmulti -replace-frees (fn [e & args] (:op e))) 83 | (defmethod -replace-frees :F 84 | [t replacem] 85 | (or (replacem (:name t)) 86 | t)) 87 | 88 | (defmethod -replace-frees :default 89 | [ast replacem] 90 | (replace-frees* ast replacem)) 91 | 92 | (defn replace-frees [t replacem] 93 | (-replace-frees t replacem)) 94 | 95 | (defn unwrap-rec [{:keys [f type] :as rec} unwrap-id] 96 | (replace-frees type 97 | {(:name f) (assoc rec :unwrap-id unwrap-id)})) 98 | 99 | (defn instantiate-TFn [{:keys [binder body] :as tfn} args] 100 | (let [names (map :name binder)] 101 | (replace-frees body 102 | (zipmap names args)))) 103 | -------------------------------------------------------------------------------- /src/main/clojure/clojure/core/typed/load.cljc: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Ambrose Bonnaire-Sergeant, Rich Hickey & contributors. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns clojure.core.typed.load 10 | "Front end for actual implementation in clojure.core.typed.load1. 11 | 12 | Indirection is necessary to delay loading core.typed as long as possible." 13 | (:require [clojure.core.typed.load-if-needed :refer [load-if-needed]] 14 | [clojure.core.typed.current-impl :as impl])) 15 | 16 | ;; based on clojure.tools.analyzer.jvm/analyze-ns 17 | ;; (IFn [String -> nil] 18 | ;; [String ToolsAnalyzerEnv -> nil] 19 | ;; [String ToolsAnalyzerEnv ToolsReaderOpts -> nil]) 20 | (let [ltf (delay (impl/dynaload 'clojure.core.typed.load1/load-typed-file))] 21 | (defn load-typed-file 22 | "Loads a whole typed namespace, returns nil. Assumes the file is typed." 23 | ([filename] 24 | (load-if-needed) 25 | (@ltf filename)) 26 | ([filename env] 27 | (load-if-needed) 28 | (@ltf filename env)) 29 | ([filename env opts] 30 | {:pre [(string? filename)] 31 | :post [(nil? %)]} 32 | (load-if-needed) 33 | (@ltf filename env opts)))) 34 | 35 | (let [tl1 (delay (impl/dynaload 'clojure.core.typed.load1/typed-load1))] 36 | (defn typed-load1 37 | "For each path, checks if the given file is typed, and loads it with core.typed if so, 38 | otherwise with clojure.core/load" 39 | [& base-resource-paths] 40 | {:pre [(every? string? base-resource-paths)] 41 | :post [(nil? %)]} 42 | (load-if-needed) 43 | (apply @tl1 base-resource-paths))) 44 | 45 | (let [te (delay (impl/dynaload 'clojure.core.typed.load1/typed-eval))] 46 | (defn typed-eval [form] 47 | (load-if-needed) 48 | (@te form))) 49 | 50 | (let [itl (delay (impl/dynaload 'clojure.core.typed.load1/install-typed-load))] 51 | (defn install-typed-load 52 | "Extend the :lang dispatch table with the :core.typed language" 53 | [] 54 | {:post [(nil? %)]} 55 | (load-if-needed) 56 | (@itl))) 57 | 58 | (let [mptl (delay (impl/dynaload 'clojure.core.typed.load1/monkey-patch-typed-load))] 59 | (defn monkey-patch-typed-load 60 | "Install the :core.typed :lang, and monkey patch `load`" 61 | [] 62 | {:post [(nil? %)]} 63 | (load-if-needed) 64 | (@mptl))) 65 | 66 | (let [mpte (delay (impl/dynaload 'clojure.core.typed.load1/monkey-patch-typed-eval))] 67 | (defn monkey-patch-typed-eval 68 | "Install the :core.typed :lang, and monkey patch `eval`" 69 | [] 70 | {:post [(nil? %)]} 71 | (load-if-needed) 72 | (@mpte))) 73 | 74 | (let [intl (delay (impl/dynaload 'clojure.core.typed.load1/install))] 75 | (defn install 76 | "Install the :core.typed :lang. Takes an optional set of features 77 | to install, defaults to #{:load :eval}. 78 | 79 | Features: 80 | - :load Installs typed `load` over `clojure.core/load` 81 | - :eval Installs typed `eval` over `clojure.core/eval` 82 | 83 | eg. (install) ; installs `load` and `eval` 84 | eg. (install #{:eval}) ; installs `eval` 85 | eg. (install #{:load}) ; installs `load`" 86 | ([] (install :all)) 87 | ([features] 88 | {:pre [((some-fn set? #{:all}) features)] 89 | :post [(nil? %)]} 90 | (load-if-needed) 91 | (@intl features)))) 92 | 93 | (comment (find-resource "clojure/core/typed/test/load_file.clj") 94 | (typed-load "/clojure/core/typed/test/load_file.clj") 95 | (load "/clojure/core/typed/test/load_file") 96 | (require 'clojure.core.typed.test.load-file :reload :verbose) 97 | ) 98 | -------------------------------------------------------------------------------- /src/main/clojure/cljs/core/typed/async.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Ambrose Bonnaire-Sergeant, Rich Hickey & contributors. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns ^:no-doc cljs.core.typed.async 10 | (:require [cljs.core.async.impl.ioc-macros :as ioc] 11 | [cljs.core.typed :as t])) 12 | 13 | (defmacro chan> 14 | "A statically typed core.async channel. 15 | 16 | (chan> t ...) creates a buffer that can read and write type t. 17 | Subsequent arguments are passed directly to clojure.core.async/chan. 18 | 19 | Note: 20 | (chan> t ...) is the same as ((inst chan t) ...)" 21 | [t & args] 22 | `((cljs.core.typed/inst cljs.core.async/chan ~t) ~@args)) 23 | 24 | (defmacro go> 25 | "Asynchronously executes the body, returning immediately to the 26 | calling thread. Additionally, any visible calls to ! and alt!/alts! 27 | channel operations within the body will block (if necessary) by 28 | 'parking' the calling thread rather than tying up an OS thread (or 29 | the only JS thread when in ClojureScript). Upon completion of the 30 | operation, the body will be resumed. 31 | 32 | The first argument is the type for the channel being created/returned. 33 | 34 | Returns a channel which will receive the result of the body when 35 | completed" 36 | [t & body] 37 | `(let [c# (chan> ~t 1)] 38 | (t/tc-ignore 39 | (cljs.core.async.impl.dispatch/run 40 | (fn [] 41 | (let [f# ~(ioc/state-machine body 1 &env ioc/async-custom-terminators) 42 | state# (-> (f#) 43 | (ioc/aset-all! cljs.core.async.impl.ioc-helpers/USER-START-IDX c#))] 44 | (cljs.core.async.impl.ioc-helpers/run-state-machine state#)))) 45 | ) 46 | c#)) 47 | 48 | ;;;;;;;;;;;;;;;;;;;;; 49 | ;;; Typed wrappers 50 | ; 51 | ;(t/tc-ignore 52 | ;(defn ^:private v [vsym] 53 | ; {:pre [(symbol? vsym) 54 | ; (namespace vsym)]} 55 | ; (let [ns (find-ns (symbol (namespace vsym))) 56 | ; _ (assert ns (str "Cannot find namespace: " (namespace vsym))) 57 | ; var (ns-resolve ns (symbol (name vsym)))] 58 | ; (assert (var? var) (str "Cannot find var: " vsym)) 59 | ; @var)) 60 | ; ) 61 | ; 62 | ;(defmacro go> 63 | ; "Asynchronously executes the body, returning immediately to the 64 | ; calling thread. Additionally, any visible calls to ! and alt!/alts! 65 | ; channel operations within the body will block (if necessary) by 66 | ; 'parking' the calling thread rather than tying up an OS thread (or 67 | ; the only JS thread when in ClojureScript). Upon completion of the 68 | ; operation, the body will be resumed. 69 | ; 70 | ; Returns a channel which will receive the result of the body when 71 | ; completed" 72 | ; [& body] 73 | ; `(let [c# (chan> ~'Any 1) 74 | ; captured-bindings# (clojure.lang.Var/getThreadBindingFrame)] 75 | ; (tc-ignore 76 | ; (clojure.core.async.impl.dispatch/run 77 | ; (fn [] 78 | ; (let [f# ~((v 'clojure.core.async.impl.ioc-macros/state-machine) 79 | ; body 1 &env (v 'clojure.core.async.impl.ioc-macros/async-custom-terminators)) 80 | ; state# (-> (f#) 81 | ; (clojure.core.async.impl.ioc-macros/aset-all! 82 | ; clojure.core.async.impl.ioc-macros/USER-START-IDX c# 83 | ; clojure.core.async.impl.ioc-macros/BINDINGS-IDX captured-bindings#))] 84 | ; (clojure.core.async.impl.ioc-macros/run-state-machine state#))))) 85 | ; c#)) 86 | ; 87 | ; 88 | ; 89 | ; 90 | ;(defmacro buffer> 91 | ; "A statically typed core.async buffer. 92 | ; 93 | ; (buffer> t ...) creates a buffer that can read and write type t. 94 | ; Subsequent arguments are passed directly to clojure.core.async/buffer. 95 | ; 96 | ; Note: (buffer> t ...) is the same as ((inst buffer t) ...)" 97 | ; [t & args] 98 | ; `((inst clojure.core.async/buffer ~t) ~@args)) 99 | ; 100 | ;(defmacro sliding-buffer> 101 | ; "A statically typed core.async sliding buffer. 102 | ; 103 | ; (sliding-buffer> t ...) creates a sliding buffer that can read and write type t. 104 | ; Subsequent arguments are passed directly to clojure.core.async/sliding-buffer. 105 | ; 106 | ; Note: (sliding-buffer> t ...) is the same as ((inst sliding-buffer t) ...)" 107 | ; [t & args] 108 | ; `((inst clojure.core.async/sliding-buffer ~t) ~@args)) 109 | ; 110 | ;(defmacro dropping-buffer> 111 | ; "A statically typed core.async dropping buffer. 112 | ; 113 | ; (dropping-buffer> t ...) creates a dropping buffer that can read and write type t. 114 | ; Subsequent arguments are passed directly to clojure.core.async/dropping-buffer. 115 | ; 116 | ; Note: (dropping-buffer> t ...) is the same as ((inst dropping-buffer t) ...)" 117 | ; [t & args] 118 | ; `((inst clojure.core.async/dropping-buffer ~t) ~@args)) 119 | -------------------------------------------------------------------------------- /src/main/cljs/cljs/core/typed/async.cljs: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Ambrose Bonnaire-Sergeant, Rich Hickey & contributors. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns 10 | ^{:doc 11 | "This namespace contains annotations and helper macros for type 12 | checking core.async code. Ensure clojure.core.async is require'd 13 | before performing type checking. 14 | 15 | go 16 | use go> 17 | 18 | chan 19 | use chan> 20 | 21 | buffer 22 | use buffer> (similar for other buffer constructors) 23 | "} 24 | ^:no-doc 25 | cljs.core.typed.async 26 | (:require-macros [cljs.core.typed :refer [ann ann-datatype def-alias ann-protocol inst 27 | tc-ignore] 28 | :as t]) 29 | (:require [cljs.core.typed :refer [AnyInteger Seqable]] 30 | [cljs.core.async])) 31 | 32 | ;TODO how do we encode that nil is illegal to provide to Ports/Channels? 33 | ; Is it essential? 34 | 35 | ;;;;;;;;;;;;;;;;;;;; 36 | ;; Protocols 37 | 38 | (ann-protocol [[w :variance :contravariant] 39 | [r :variance :covariant]] 40 | cljs.core.async.impl.protocols/Channel) 41 | 42 | (ann-protocol [[r :variance :covariant]] 43 | cljs.core.async.impl.protocols/ReadPort) 44 | 45 | (ann-protocol [[w :variance :contravariant]] 46 | cljs.core.async.impl.protocols/WritePort) 47 | 48 | (ann-protocol [[x :variance :invariant]] 49 | cljs.core.async.impl.protocols/Buffer) 50 | 51 | ;unchecked-ancestors NYI 52 | #_(ann-datatype [[w :variance :contravariant] 53 | [r :variance :covariant]] 54 | cljs.core.async.impl.channels.ManyToManyChannel 55 | [] 56 | :ancestors [(cljs.core.async.impl.protocols/Channel w r) 57 | (cljs.core.async.impl.protocols/ReadPort r) 58 | (cljs.core.async.impl.protocols/WritePort w)]) 59 | 60 | ;;;;;;;;;;;;;;;;;;;;; 61 | ;;; Aliases 62 | 63 | (def-alias 64 | ^{:forms [(ReadOnlyChan t)]} 65 | ReadOnlyChan 66 | "A core.async channel that statically disallows writes." 67 | (TFn [[r :variance :covariant]] 68 | (Extends [(cljs.core.async.impl.protocols/WritePort Nothing) 69 | (cljs.core.async.impl.protocols/ReadPort r) 70 | (cljs.core.async.impl.protocols/Channel Nothing r)]))) 71 | 72 | (def-alias 73 | ^{:forms [(Chan t)]} 74 | Chan 75 | "A core.async channel" 76 | (TFn [[x :variance :invariant]] 77 | (Extends [(cljs.core.async.impl.protocols/WritePort x) 78 | (cljs.core.async.impl.protocols/ReadPort x) 79 | (cljs.core.async.impl.protocols/Channel x x)]))) 80 | 81 | (def-alias 82 | ^{:forms [TimeoutChan]} 83 | TimeoutChan 84 | "A timeout channel" 85 | (Chan Any)) 86 | 87 | (def-alias 88 | ^{:forms [(Buffer t)]} 89 | Buffer 90 | "A buffer of type x." 91 | (TFn [[x :variance :invariant]] 92 | (cljs.core.async.impl.protocols/Buffer x))) 93 | 94 | (def-alias 95 | ^{:forms [(ReadOnlyPort t)]} 96 | ReadOnlyPort 97 | "A read-only port that can read type x" 98 | (TFn [[r :variance :covariant]] 99 | (Extends [(cljs.core.async.impl.protocols/ReadPort r) 100 | (cljs.core.async.impl.protocols/WritePort Nothing)]))) 101 | 102 | (def-alias 103 | ^{:forms [(WriteOnlyPort t)]} 104 | WriteOnlyPort 105 | "A write-only port that can write type x" 106 | (TFn [[x :variance :invariant]] 107 | (Extends [(cljs.core.async.impl.protocols/ReadPort x) 108 | (cljs.core.async.impl.protocols/WritePort x)]))) 109 | 110 | (def-alias 111 | ^{:forms [(Port t)]} 112 | Port 113 | "A port that can read and write type x" 114 | (TFn [[x :variance :invariant]] 115 | (Extends [(cljs.core.async.impl.protocols/ReadPort x) 116 | (cljs.core.async.impl.protocols/WritePort x)]))) 117 | 118 | ;;;;;;;;;;;;;;;;;;;;; 119 | ;;; Var annotations 120 | 121 | (ann ^:no-check cljs.core.async/buffer (All [x] [AnyInteger -> (Buffer x)])) 122 | (ann ^:no-check cljs.core.async/dropping-buffer (All [x] [AnyInteger -> (Buffer x)])) 123 | (ann ^:no-check cljs.core.async/sliding-buffer (All [x] [AnyInteger -> (Buffer x)])) 124 | 125 | (ann ^:no-check cljs.core.async/thread-call (All [x] [[-> x] -> (Chan x)])) 126 | 127 | (ann ^:no-check cljs.core.async/timeout [AnyInteger -> TimeoutChan]) 128 | 129 | (ann ^:no-check cljs.core.async/chan (All [x] 130 | (Fn [-> (Chan x)] 131 | [(U (Buffer x) AnyInteger) -> (Chan x)]))) 132 | ;(ann clojure.core.async/>! (All [x] [(Chan x) -> (Chan x)])) 133 | 134 | ;(ann ^:no-check clojure.core.async.impl.ioc-macros/aget-object [AtomicReferenceArray AnyInteger -> Any]) 135 | ;(ann ^:no-check clojure.core.async.impl.ioc-macros/aset-object [AtomicReferenceArray Any -> nil]) 136 | ;(ann ^:no-check clojure.core.async.impl.ioc-macros/run-state-machine [AtomicReferenceArray -> Any]) 137 | 138 | ;FIXME what is 2nd arg? 139 | (ann ^:no-check cljs.core.async.impl.ioc-macros/put! (All [x] [AnyInteger Any (Chan x) x -> Any])) 140 | ;(ann ^:no-check clojure.core.async.impl.ioc-macros/return-chan (All [x] [AtomicReferenceArray x -> (Chan x)])) 141 | 142 | (ann ^:no-check cljs.core.async/ (U nil x)])) 143 | (ann ^:no-check cljs.core.async/>!! (All [x] [(WriteOnlyPort x) x -> nil])) 144 | (ann ^:no-check cljs.core.async/alts!! 145 | (All [x d] 146 | (Fn [(Seqable (U (Port x) '[(Port x) x])) (Seqable (Port x)) & :mandatory {:default d} :optional {:priority (U nil true)} -> 147 | (U '[d ':default] '[(U nil x) (Port x)])] 148 | [(Seqable (U (Port x) '[(Port x) x])) & :optional {:priority (U nil true)} -> '[(U nil x) (Port x)]]))) 149 | 150 | (ann ^:no-check cljs.core.async/close! [(ReadOnlyChan Any) -> nil]) 151 | 152 | ;(ann ^:no-check clojure.core.async.impl.dispatch/run [[-> (ReadOnlyChan Any)] -> Executor]) 153 | ;(ann clojure.core.async.impl.ioc-macros/async-chan-wrapper kV 154 | 155 | -------------------------------------------------------------------------------- /src/main/clojure/clojure/core/typed/ast_utils.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Ambrose Bonnaire-Sergeant, Rich Hickey & contributors. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns ^:no-doc ^:skip-wiki clojure.core.typed.ast-utils 10 | (:require [clojure.core.typed.current-impl :as impl] 11 | [clojure.core.typed.contract-utils :as con] 12 | [clojure.core.typed.coerce-utils :as coerce])) 13 | 14 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 15 | ;; AST ops 16 | 17 | 18 | ;AnalysisExpr -> Form 19 | ;(ann emit-form-fn [Any -> Any]) 20 | (let [emit-form-clj (delay (impl/dynaload 'clojure.tools.analyzer.passes.jvm.emit-form/emit-form)) 21 | emit-form-cljs (delay (impl/dynaload 'clojure.core.typed.util-cljs/emit-form))] 22 | (defn emit-form-fn [expr] 23 | (impl/impl-case 24 | :clojure (@emit-form-clj expr) 25 | :cljs (@emit-form-cljs expr)))) 26 | 27 | (defn constant-expr [expr] 28 | {:pre [(#{:quote} (:op expr)) 29 | (#{:const} (:op (:expr expr)))]} 30 | (-> expr :expr :val)) 31 | 32 | (let [constant-lift (delay (impl/dynaload 'clojure.tools.analyzer.passes.jvm.constant-lifter/constant-lift))] 33 | (defn map-expr-at [expr key] 34 | (impl/impl-case 35 | :clojure (case (:op expr) 36 | :map (let [const (@constant-lift expr)] 37 | (assert (#{:const} (:op const))) 38 | (map-expr-at const key)) 39 | :const (let [v (:val expr)] 40 | (assert (contains? v key) key) 41 | (get v key))) 42 | :cljs (let [_ (assert (#{:map} (:op expr))) 43 | m (zipmap (map :form (:keys expr)) 44 | (:vals expr)) 45 | _ (assert (contains? m key)) 46 | vexpr (get m key)] 47 | (:form vexpr))))) 48 | 49 | (defn constant-exprs [exprs] 50 | (map constant-expr exprs)) 51 | 52 | (defn quote-expr-val [{:keys [op expr] :as q}] 53 | {:pre [(or (and (#{:quote} op) 54 | (#{:const} (:op expr))) 55 | (#{:const} op))]} 56 | (if (#{:quote} op) 57 | (:val expr) 58 | (:val q))) 59 | 60 | (defn dummy-if-expr [test then else env] 61 | {:op :if 62 | :test test 63 | :then then 64 | :else else 65 | :children [:test :then :else] 66 | :env env}) 67 | 68 | (defn dummy-invoke-expr [fexpr args env] 69 | {:op :invoke 70 | :env env 71 | :children [:fn :args] 72 | :fn fexpr 73 | :args args}) 74 | 75 | (defn dummy-fn-method-expr [body required-params rest-param env] 76 | (let [params (vec (concat required-params (when rest-param [rest-param])))] 77 | {:op :fn-method 78 | :env env 79 | :children [:body] 80 | :body body 81 | :params params 82 | :fixed-arity (count params) 83 | :variadic? (boolean rest-param)})) 84 | 85 | (defn dummy-fn-expr [methods variadic-method env] 86 | {:op :fn 87 | :env env 88 | :children [:methods] 89 | :methods (vec (concat methods (when variadic-method [variadic-method]))) 90 | :variadic? (boolean variadic-method)}) 91 | 92 | (defn dummy-local-binding-expr [sym env] 93 | {:op :local 94 | :env env 95 | :name sym}) 96 | 97 | (defn dummy-var-expr [vsym env] 98 | (let [v (resolve vsym)] 99 | (assert (var? v)) 100 | {:op :var 101 | :env env 102 | :var v 103 | :form vsym})) 104 | 105 | (defn dummy-do-expr [statements ret env] 106 | {:op :do 107 | :statements statements 108 | :ret ret 109 | :env env}) 110 | 111 | (defn dummy-const-expr [val env] 112 | {:op :const 113 | :val val 114 | :env env 115 | :form val}) 116 | 117 | ;; FIXME delete 118 | (defn method-body-kw [] 119 | :body) 120 | 121 | (defn method-required-params [method] 122 | (case (:op method) 123 | (:fn-method) ((if (:variadic? method) butlast identity) 124 | (:params method)) 125 | ;include deftype's 'this' param 126 | (:method) (concat [(:this method)] (:params method)))) 127 | 128 | (defn method-rest-param [method] 129 | (case (:op method) 130 | ;deftype methods are never variadic 131 | (:method) nil 132 | (:fn-method) ((if (:variadic? method) last (constantly nil)) 133 | (:params method)))) 134 | 135 | (defn reconstruct-arglist [method required-params rest-param] 136 | (impl/impl-case 137 | :clojure (case (:op method) 138 | :fn-method (assoc method 139 | :params (vec (concat required-params 140 | (when rest-param 141 | [rest-param])))) 142 | :method (do (assert (nil? rest-param)) 143 | (assert (seq required-params)) 144 | (assoc method 145 | :this (first required-params) 146 | :params (vec (rest required-params))))) 147 | :cljs (assoc method 148 | :params (vec (concat required-params 149 | (when rest-param 150 | [rest-param])))))) 151 | 152 | (defn let-body-kw [] 153 | :body) 154 | 155 | (defn def-var-name [expr] 156 | {:post [(symbol? %)]} 157 | (impl/impl-case 158 | :clojure (coerce/var->symbol (:var expr)) 159 | :cljs (:name expr))) 160 | 161 | (defn new-op-class [expr] 162 | {:pre [(#{:new} (:op expr)) 163 | (#{:const} (:op (:class expr)))] 164 | :post [(class? %)]} 165 | (-> expr :class :val)) 166 | 167 | (defn catch-op-class [expr] 168 | {:pre [(#{:catch} (:op expr))] 169 | :post [(class? %)]} 170 | ; future tools.analyzer 171 | (-> expr :class :val)) 172 | 173 | (def deftype-method? (fn [m] 174 | (impl/impl-case 175 | :clojure ((every-pred map? (comp #{:method} :op)) 176 | m) 177 | ; FIXME should be nyi-error but c.c.t.errors depends on this namespace 178 | :cljs (assert nil "Method for CLJS")))) 179 | 180 | (def fn-method? (fn [m] 181 | ((every-pred map? (comp #{:fn-method} :op)) 182 | m))) 183 | (def fn-methods? (fn [ms] 184 | (impl/impl-case 185 | :clojure ((con/vec-c? fn-method?) ms) 186 | :cljs ((every-pred (con/every-c? fn-method?) 187 | seq?) 188 | ms)))) 189 | 190 | (defn variadic-method? [m] 191 | {:pre [((some-fn fn-method? deftype-method?) m)] 192 | :post [(boolean? %)]} 193 | (cond 194 | (fn-method? m) (:variadic? m) 195 | ; :method does not have :variadic? field 196 | :else false)) 197 | 198 | (defn fixed-arity 199 | "Returns the number of parameters for a :fn-method or :method. 200 | Note :method AST nodes include the 'this' parameter." 201 | [m] 202 | {:pre [((some-fn fn-method? deftype-method?) m)] 203 | :post [(integer? %)]} 204 | (let [fixed (:fixed-arity m)] 205 | (assert (integer? fixed)) 206 | ((if (fn-method? m) identity inc) fixed))) 207 | 208 | (defn walk-children [check {:keys [children] :as expr}] 209 | (reduce 210 | (fn [expr c] 211 | (update expr c 212 | (fn [ce] 213 | (if (vector? ce) 214 | (mapv check ce) 215 | (check ce))))) 216 | expr 217 | children)) 218 | -------------------------------------------------------------------------------- /src/test/clojure/clojure/core/typed/test_contract.clj: -------------------------------------------------------------------------------- 1 | (ns clojure.core.typed.test-contract 2 | (:require [clojure.core.typed.contract :as con 3 | :refer :all] 4 | [clojure.test :refer :all])) 5 | 6 | (defmacro thrown-blame? [& e] 7 | `(try (do ~@e) 8 | false 9 | (catch clojure.lang.ExceptionInfo e# 10 | (boolean (-> e# ex-data :blame))))) 11 | 12 | (deftest int-c-test 13 | (is (= (contract int-c 1) 14 | 1)) 15 | (is (thrown-blame? (contract int-c nil)))) 16 | 17 | (deftest ifn-test 18 | (is (= ((contract (ifn-c [int-c] int-c) (fn [x] x)) 1) 19 | 1)) 20 | (testing "violate input" 21 | (is (thrown-blame? 22 | ((contract (ifn-c [int-c] int-c) (fn [x] x)) nil)))) 23 | (testing "violate output" 24 | (is (thrown-blame? 25 | ((contract (ifn-c [int-c] int-c) str) 1))))) 26 | 27 | (deftest Object-c-test 28 | (is (= (contract Object-c 1) 29 | 1)) 30 | (is (thrown-blame? 31 | (= (contract Object-c nil) 1)))) 32 | 33 | (deftest val-c-test 34 | (is (= (contract nil-c nil) 35 | nil)) 36 | (is (thrown-blame? (contract nil-c 1)))) 37 | 38 | ;TODO 39 | #_(deftest seqable-c-test 40 | (is (= (contract (seqable-c int-c) (list 1 2 3)) (list 1 2 3))) 41 | (is (thrown-blame? (doall (contract (seqable-c int-c) (list nil 2 3)))))) 42 | 43 | (deftest hmap-c-test 44 | (testing "no options" 45 | (is (= (contract (hmap-c) 46 | {}) 47 | {})) 48 | (is (= (contract (hmap-c) 49 | {:a 1}) 50 | {:a 1})) 51 | (is (= (contract (hmap-c) 52 | {:a 1 :b 2}) 53 | {:a 1 :b 2}))) 54 | (testing "mandatory keys, partial map, no absent" 55 | (testing "flat '{:a Int}" 56 | (let [c (hmap-c :mandatory {:a int-c})] 57 | (testing "good value" 58 | (is (= (contract c {:a 1}) 59 | {:a 1}))) 60 | (testing "extra entry ok" 61 | (is (= (contract c {:a 1 :b 2}) 62 | {:a 1 :b 2}))) 63 | (testing "bad value" 64 | (is (thrown-blame? 65 | (contract c {:a nil})))))) 66 | (testing "higher-order '{:a [Int -> Int]}" 67 | (let [c (hmap-c :mandatory {:a (ifn-c [int-c] int-c)})] 68 | (testing "good value" 69 | (is (= ((:a (contract c 70 | {:a inc})) 71 | 1) 72 | 2))) 73 | (testing "extra entry ok" 74 | (is (= ((:a (contract c 75 | {:a inc 76 | :b 2})) 77 | 1) 78 | 2))) 79 | (testing "bad value" 80 | (testing "violate input" 81 | (is (thrown-blame? 82 | ((:a (contract (hmap-c :mandatory {:a (ifn-c [int-c] int-c)}) 83 | {:a inc})) 84 | nil)))) 85 | (testing "violate output" 86 | (is (thrown-blame? 87 | ((:a (contract (hmap-c :mandatory {:a (ifn-c [int-c] int-c)}) 88 | {:a str})) 89 | 1))))))) 90 | (testing "missing entry" 91 | (testing "flat" 92 | (is (thrown-blame? 93 | (contract (hmap-c :mandatory {:a int-c}) 94 | {})))) 95 | (testing "higher-order" 96 | (is (thrown-blame? 97 | (contract (hmap-c :mandatory {:a (ifn-c [int-c] int-c)}) 98 | {})))))) 99 | 100 | (testing "mandatory keys, partial map, no absent" 101 | (testing "flat (HMap :optional {:a Int})" 102 | (let [c (hmap-c :optional {:a int-c})] 103 | (testing "good value" 104 | (is (= (contract c {:a 1}) 105 | {:a 1})) 106 | (testing "extra entry ok" 107 | (is (= (contract c {:a 1 :b 2}) 108 | {:a 1 :b 2})))) 109 | (testing "bad value" 110 | (is (thrown-blame? 111 | (contract c {:a nil})))))) 112 | (testing "higher-order (HMap :optional {:a [Int -> Int]})" 113 | (let [c (hmap-c :optional {:a (ifn-c [int-c] int-c)})] 114 | (testing "good value" 115 | (is (= ((:a (contract c 116 | {:a inc})) 117 | 1) 118 | 2))) 119 | (testing "extra entry ok" 120 | (is (= ((:a (contract c 121 | {:a inc 122 | :b 1})) 123 | 1) 124 | 2))) 125 | (testing "bad value" 126 | (testing "violate input" 127 | (is (thrown-blame? 128 | ((:a (contract c 129 | {:a inc})) 130 | nil)))) 131 | (testing "violate output" 132 | (is (thrown-blame? 133 | ((:a (contract c 134 | {:a str})) 135 | 1))))))) 136 | (testing "missing entry" 137 | (testing "flat" 138 | (is (= (contract (hmap-c :optional {:a int-c}) 139 | {})))) 140 | (testing "higher-order" 141 | (is (= (contract (hmap-c :optional {:a (ifn-c [int-c] int-c)}) 142 | {}) 143 | {}))))) 144 | (testing "mandatory keys, complete map" 145 | (testing "flat (HMap :complete? true, :mandatory {:a Int})" 146 | (let [c (hmap-c 147 | :complete? true 148 | :mandatory {:a int-c})] 149 | (testing "extra entry bad" 150 | (is (thrown-blame? 151 | (contract c {:a 1 :b 2})))))) 152 | (testing "higher-order (HMap :complete? true :mandatory {:a [Int -> Int]})" 153 | (let [c (hmap-c 154 | :complete? true 155 | :mandatory {:a (ifn-c [int-c] int-c)})] 156 | (testing "extra entry bad" 157 | (is (thrown-blame? 158 | (contract c {:a inc :b 2}))))))) 159 | (testing "optional keys, complete map" 160 | (testing "flat (HMap :complete? true, :mandatory {:a Int})" 161 | (let [c (hmap-c 162 | :complete? true 163 | :optional {:a int-c})] 164 | (testing "missing optional ok" 165 | (is (= (contract c {}) 166 | {}))) 167 | (testing "extra entry bad" 168 | (is (thrown-blame? 169 | (contract c {:a 1 :b 2}))) 170 | (is (thrown-blame? 171 | (contract c {:b 2})))))) 172 | (testing "higher-order (HMap :optional {:a [Int -> Int]})" 173 | (let [c (hmap-c 174 | :complete? true 175 | :mandatory {:a (ifn-c [int-c] int-c)})] 176 | (testing "extra entry bad" 177 | (is (thrown-blame? 178 | (contract c {:a inc :b 2}))))))) 179 | (testing "absent keys" 180 | (testing "flat (HMap :absent-keys #{:a} :mandatory {:b Int})" 181 | (let [c (hmap-c 182 | :mandatory {:b int-c} 183 | :absent-keys #{:a})] 184 | (testing "absent ok" 185 | (is (= (contract c {:b 1}) 186 | {:b 1}))) 187 | (testing "present bad" 188 | (is (thrown-blame? 189 | (contract c {:a 1 :b 2})))) 190 | (testing "missing mandatory" 191 | (is (thrown-blame? 192 | (contract c {})))))) 193 | (testing "higher-order (HMap :optional {:a [Int -> Int]})" 194 | (let [c (hmap-c 195 | :absent-keys #{:a} 196 | :mandatory {:b (ifn-c [int-c] int-c)})] 197 | (testing "absent ok" 198 | (is ((:b (contract c {:b inc})) 199 | 1))) 200 | (testing "present bad" 201 | (is (thrown-blame? 202 | (contract c {:a 1 :b inc})))) 203 | (testing "missing mandatory" 204 | (is (thrown-blame? 205 | (contract c {})))))) 206 | (testing "with complete" 207 | (let [c (hmap-c 208 | :complete? true 209 | :absent-keys #{:a} 210 | :mandatory {:b int-c})] 211 | (testing "fine" 212 | (is (= (contract c {:b 1}) 213 | {:b 1}))) 214 | (testing "missing mandatory" 215 | (is (thrown-blame? 216 | (contract c {})))) 217 | (testing "bad absent" 218 | (is (thrown-blame? 219 | (contract c {:a 1})))) 220 | (testing "bad complete" 221 | (is (thrown-blame? 222 | (contract c {:c 1})))))))) 223 | -------------------------------------------------------------------------------- /src/main/clojure/clojure/core/typed/macros.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Ambrose Bonnaire-Sergeant, Rich Hickey & contributors. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns ^:skip-wiki clojure.core.typed.macros 10 | (:refer-clojure :exclude [type defprotocol fn loop dotimes let for doseq 11 | defn atom ref]) 12 | (:require [clojure.core :as core] 13 | [clojure.core.typed.special-form :as spec])) 14 | 15 | ;also defined in clojure.core.typed 16 | (core/defn dynaload 17 | [s] 18 | (core/let [ns (namespace s)] 19 | (assert ns) 20 | (require (symbol ns)) 21 | (core/let [v (resolve s)] 22 | (if v 23 | @v 24 | (throw (RuntimeException. (str "Var " s " is not on the classpath"))))))) 25 | 26 | (core/defn core-kw [kw] 27 | (keyword "clojure.core.typed" 28 | (name kw))) 29 | 30 | (core/defn parse-colon 31 | "Returns a vector of [provided? t args]" 32 | [fdecl name] 33 | (if (#{:-} (first fdecl)) 34 | (core/let [[colon t & body] fdecl] 35 | [true t body]) 36 | [false nil fdecl])) 37 | 38 | (core/let [take-when (delay (dynaload 'clojure.core.typed.internal/take-when))] 39 | (defmacro 40 | ^{:forms '[(def name docstring? :- type? expr)]} 41 | def 42 | "Like clojure.core/def with optional type annotations 43 | 44 | NB: in Clojure it is impossible to refer a var called `def` as it is a 45 | special form. Use an alias prefix (eg. `t/def`). 46 | 47 | If an annotation is provided, a corresponding `ann` form 48 | is generated, otherwise it expands identically to clojure.core/def 49 | 50 | eg. ;same as clojure.core/def 51 | (def vname 1) 52 | 53 | ;with Number `ann` 54 | (def vname :- Number 1) 55 | 56 | ;doc 57 | (def vname 58 | \"Docstring\" 59 | :- Long 60 | 1)" 61 | [name & fdecl] 62 | (core/let [[docstring fdecl] (@take-when string? fdecl) 63 | [provided? t [body :as args]] (parse-colon fdecl 'def)] 64 | (assert (= 1 (count args)) "Wrong arguments to def") 65 | `(def ~(vary-meta name #(merge 66 | % 67 | (when docstring 68 | {:doc docstring}))) 69 | ~(if provided? 70 | `(ann-form ~body ~t) 71 | body))))) 72 | 73 | (core/let [parse-fn* (delay (dynaload 'clojure.core.typed.internal/parse-fn*))] 74 | (core/defn expand-typed-fn [form] 75 | (core/let [{:keys [poly fn ann]} (@parse-fn* form)] 76 | `(do ~spec/special-form 77 | ~(core-kw :fn) 78 | {:ann '~ann 79 | :poly '~poly} 80 | ~fn)))) 81 | 82 | (defmacro 83 | ^{:forms '[(fn name? [param :- type* & param :- type * ?] :- type? exprs*) 84 | (fn name? ([param :- type* & param :- type * ?] :- type? exprs*)+)]} 85 | fn 86 | "Like clojure.core/fn, but with optional annotations. 87 | 88 | eg. ;these forms are equivalent 89 | (fn [a] b) 90 | (fn [a :- Any] b) 91 | (fn [a :- Any] :- Any b) 92 | (fn [a] :- Any b) 93 | 94 | ;annotate return 95 | (fn [a :- String] :- String body) 96 | 97 | ;named fn 98 | (fn fname [a :- String] :- String body) 99 | 100 | ;rest parameter 101 | (fn [a :- String & b :- Number *] body) 102 | 103 | ;dotted rest parameter 104 | (fn [a :- String & b :- Number ... x] body) 105 | 106 | ;multi-arity 107 | (fn fname 108 | ([a :- String] :- String ...) 109 | ([a :- String, b :- Number] :- String ...)) 110 | 111 | ; polymorphic binder 112 | (fn :forall [x y z] 113 | fname 114 | ([a :- String] :- String ...) 115 | ([a :- String, b :- Number] :- String ...)) 116 | " 117 | [& forms] 118 | (expand-typed-fn &form)) 119 | 120 | (core/let [parse-loop* (delay (dynaload 'clojure.core.typed.internal/parse-loop*))] 121 | (defmacro 122 | ^{:forms '[(loop [binding :- type?, init*] exprs*)]} 123 | loop 124 | "Like clojure.core/loop, and supports optional type annotations. 125 | Arguments default to a generalised type based on the initial value. 126 | 127 | eg. (loop [a :- Number 1 128 | b :- (U nil Number) nil] 129 | ...)" 130 | [bindings & exprs] 131 | (core/let [{:keys [ann loop]} (@parse-loop* `(~bindings ~@exprs))] 132 | `(do ~spec/special-form 133 | ~(core-kw :loop) 134 | {:ann '~ann} 135 | ~loop)))) 136 | 137 | (core/let [parse-let* (delay (dynaload 'clojure.core.typed.internal/parse-let*))] 138 | (defmacro 139 | ^{:forms '[(let [binding :- type?, init*] exprs*)]} 140 | let 141 | "Like clojure.core/let but supports optional type annotations. 142 | 143 | eg. (let [a :- Type, b 144 | a2 1.2] 145 | body)" 146 | [bvec & forms] 147 | (core/let [{:keys [let]} (@parse-let* (cons bvec forms))] 148 | let))) 149 | 150 | (defmacro ann-form 151 | "Annotate a form with an expected type." 152 | [form ty] 153 | `(do ~spec/special-form 154 | ~(core-kw :ann-form) 155 | {:type '~ty} 156 | ~form)) 157 | 158 | (core/let [parse-defprotocol* (delay (dynaload 'clojure.core.typed.internal/parse-defprotocol*))] 159 | (defmacro defprotocol 160 | "Like defprotocol, but with optional type annotations. 161 | 162 | Omitted annotations default to Any. The first argument 163 | of a protocol cannot be annotated. 164 | 165 | Add a binder before the protocol name to define a polymorphic 166 | protocol. A binder before the method name defines a polymorphic 167 | method, however a method binder must not shadow type variables 168 | introduced by a protocol binder. 169 | 170 | Return types for each method arity can be annotated. 171 | 172 | Unlike clojure.core/defprotocol, successive methods can 173 | have the same arity. Semantically, providing multiple successive 174 | methods of the same arity is the same as just providing the left-most 175 | method. However the types for these methods will be accumulated into 176 | a Fn type. 177 | 178 | eg. ;annotate single method 179 | (defprotocol MyProtocol 180 | (a [this a :- Integer] :- Number)) 181 | 182 | ;polymorphic protocol 183 | (defprotocol [[x :variance :covariant]] 184 | MyProtocol 185 | (a [this a :- Integer] :- Number)) 186 | 187 | ;multiple types for the same method 188 | (defprotocol [[x :variance :covariant]] 189 | MyProtocol 190 | (a [this a :- Integer] :- Integer 191 | [this a :- Long] :- Long 192 | [this a :- Number] :- Number)) 193 | 194 | ;polymorphic method+protocol 195 | (defprotocol [[x :variance :covariant]] 196 | MyProtocol 197 | ([y] a [this a :- x, b :- y] :- y)) 198 | " 199 | [& body] 200 | (core/let [{:keys [ann-protocol defprotocol]} (@parse-defprotocol* body)] 201 | `(do ~ann-protocol 202 | (tc-ignore 203 | ~defprotocol))))) 204 | 205 | (defmacro tc-ignore 206 | "Ignore forms in body during type checking" 207 | [& body] 208 | `(do ~spec/special-form 209 | ~(core-kw :tc-ignore) 210 | {} 211 | (do ~@(or body [nil])))) 212 | 213 | (defmacro when-let-fail 214 | "Like when-let, but fails if the binding yields a false value." 215 | [b & body] 216 | `(if-let ~b 217 | (do ~@body) 218 | (throw (ex-info (str "Expression was nil or false") {:form '~(second b)})))) 219 | 220 | (defmacro atom 221 | "Like atom, but with optional type annotations. 222 | 223 | Same as (atom (ann-form init t) args*) 224 | 225 | eg. (atom 1) : (Atom1 (Value 1)) 226 | (atom :- Num, 1) : (Atom1 Num)" 227 | [& args] 228 | (core/let [[provided? t args] (parse-colon args 'atom) 229 | [init & args] args] 230 | `(core/atom ~(if provided? 231 | `(ann-form ~init ~t) 232 | init) 233 | ~@args))) 234 | 235 | (defmacro ref 236 | "Like ref, but with optional type annotations. 237 | 238 | Same as (ref (ann-form init t) args*) 239 | 240 | eg. (ref 1) : (Ref1 (Value 1)) 241 | (ref :- Num, 1) : (Ref1 Num)" 242 | [& args] 243 | (core/let [[provided? t args] (parse-colon args 'ref) 244 | [init & args] args] 245 | `(core/ref ~(if provided? 246 | `(ann-form ~init ~t) 247 | init) 248 | ~@args))) 249 | 250 | (core/let [parse-defn* (delay (dynaload 'clojure.core.typed.internal/parse-defn*))] 251 | (defmacro 252 | ^{:forms '[(defn kw-args? name docstring? attr-map? [param :- type *] :- type exprs*) 253 | (defn kw-args? name docstring? attr-map? ([param :- type *] :- type exprs*)+)]} 254 | defn 255 | "Like defn, but expands to clojure.core.typed/fn. If a polymorphic binder is 256 | supplied before the var name, expands to clojure.core.typed/pfn. 257 | 258 | eg. (defn fname [a :- Number, b :- (U Symbol nil)] :- Integer ...) 259 | 260 | ;annotate return 261 | (defn fname [a :- String] :- String ...) 262 | 263 | ;multi-arity 264 | (defn fname 265 | ([a :- String] :- String ...) 266 | ([a :- String, b :- Number] :- Long ...)) 267 | 268 | ;polymorphic function 269 | (defn :forall [x y] 270 | fname 271 | ([a :- x] :- (Coll y) ...) 272 | ([a :- Str, b :- y] :- y ...))" 273 | [& args] 274 | (core/let [{:keys [name args]} (@parse-defn* args)] 275 | `(def ~name (fn ~@args))))) 276 | -------------------------------------------------------------------------------- /src/main/clojure/clojure/core/typed/errors.cljc: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Ambrose Bonnaire-Sergeant, Rich Hickey & contributors. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns ^:no-doc clojure.core.typed.errors 10 | {:skip-wiki true 11 | :core.typed {:collect-only true}} 12 | (:require [clojure.core.typed.util-vars :refer [*current-env*] :as uvs] 13 | [clojure.core.typed.current-impl :as impl] 14 | [clojure.pprint :as pp] 15 | [clojure.core.typed.ast-utils :as ast-u])) 16 | 17 | (def int-error-kw ::internal-error) 18 | (def nyi-error-kw ::nyi-error) 19 | 20 | (def tc-error-parent ::tc-error-parent) 21 | 22 | (defn derive-error [kw] 23 | (derive kw tc-error-parent)) 24 | 25 | (derive-error int-error-kw) 26 | (derive-error nyi-error-kw) 27 | 28 | ;(t/ann ^:no-check env-for-error [Any -> Any]) 29 | (defn env-for-error [env] 30 | ; impl-case probably can't be done here 31 | (merge (select-keys env [:line :column]) 32 | ;clojure specific 33 | (let [f (:file env)] 34 | (when (string? f) 35 | {:file f})) 36 | ;cljs specific 37 | ;FIXME filename? 38 | (let [n (get-in env [:ns :name])] 39 | (when (symbol? n) 40 | {:ns n})))) 41 | 42 | (defn int-error 43 | ([estr] (int-error estr {})) 44 | ([estr {:keys [use-current-env] :as opt}] 45 | (let [{:keys [line column file] :as env} *current-env*] 46 | (throw (ex-info (str "Internal Error " 47 | "(" (or file 48 | (impl/impl-case 49 | :clojure (:ns env) 50 | :cljs (:name (:ns env)) 51 | :unknown "?")) 52 | ":" 53 | (or line "") 54 | (when column 55 | (str ":" column)) 56 | ") " 57 | estr) 58 | {:type-error int-error-kw 59 | :env (or (when (and uvs/*current-expr* 60 | (not use-current-env)) 61 | (:env uvs/*current-expr*)) 62 | (env-for-error *current-env*))}))))) 63 | 64 | ;[Any * -> String] 65 | (defn ^String error-msg 66 | [& msg] 67 | (apply str (when *current-env* 68 | (str (:line *current-env*) ":" 69 | (:col *current-env*) 70 | " ")) 71 | (concat msg))) 72 | 73 | ;errors from check-ns or cf 74 | (defn top-level-error? [{:keys [type-error] :as exdata}] 75 | (boolean (#{:top-level-error} type-error))) 76 | 77 | #?(:clj 78 | (defmacro top-level-error-thrown? [& body] 79 | `(with-ex-info-handlers 80 | [top-level-error? (constantly true)] 81 | ~@body 82 | false))) 83 | 84 | #?(:clj 85 | (defmacro tc-error-thrown? [& body] 86 | `(with-ex-info-handlers 87 | [tc-error? (constantly true)] 88 | ~@body 89 | false))) 90 | 91 | (defn tc-error? [exdata] 92 | (assert (not (instance? clojure.lang.ExceptionInfo exdata))) 93 | (isa? (:type-error exdata) tc-error-parent)) 94 | 95 | (let [parse-type (delay (impl/dynaload 'clojure.core.typed.checker.jvm.parse-unparse/parse-type))] 96 | (defn msg-fn-opts [] 97 | {:parse-type @parse-type})) 98 | 99 | (let [unparse-type (delay (impl/dynaload 'clojure.core.typed.checker.jvm.parse-unparse/unparse-type)) 100 | -error (delay (impl/dynaload 'clojure.core.typed.checker.type-rep/-error))] 101 | (defn tc-delayed-error [msg & {:keys [return form expected] :as opt}] 102 | (let [form (cond 103 | (contains? (:opts expected) :blame-form) (-> expected :opts :blame-form) 104 | (contains? opt :blame-form) (:blame-form opt) 105 | (contains? opt :form) form 106 | :else (ast-u/emit-form-fn uvs/*current-expr*)) 107 | msg (str (when-let [msg-fn (some-> (or (-> expected :opts :msg-fn) 108 | (:msg-fn opt)) 109 | eval)] 110 | (str (msg-fn (merge (msg-fn-opts) 111 | (when-let [[_ actual] (find opt :actual)] 112 | {:actual (@unparse-type actual)}))) 113 | (when msg 114 | (str 115 | "\n\n" 116 | "====================\n" 117 | " More information \n" 118 | "====================\n\n")))) 119 | msg) 120 | e (ex-info msg (merge {:type-error tc-error-parent} 121 | (when (or (contains? opt :form) 122 | uvs/*current-expr*) 123 | {:form form}) 124 | {:env (env-for-error 125 | (merge (or (when uvs/*current-expr* 126 | (:env uvs/*current-expr*)) 127 | *current-env*) 128 | (when (contains? (:opts expected) :blame-form) 129 | (meta (-> expected :opts :blame-form)))))}))] 130 | (cond 131 | ;can't delay here 132 | (not uvs/*delayed-errors*) 133 | (throw e) 134 | 135 | :else 136 | (do 137 | (if-let [delayed-errors uvs/*delayed-errors*] 138 | (swap! delayed-errors conj e) 139 | (throw (Exception. (str "*delayed-errors* not rebound")))) 140 | (or (when (contains? opt :return) 141 | return) 142 | @-error)))))) 143 | 144 | (defn tc-error 145 | [estr] 146 | (let [env *current-env*] 147 | (throw (ex-info (str "Type Error " 148 | "(" (:file env) ":" (or (:line env) "") 149 | (when-let [col (:column env)] 150 | (str ":" col)) 151 | ") " 152 | estr) 153 | (merge 154 | {:type-error tc-error-parent} 155 | {:env (env-for-error env)}))))) 156 | 157 | (defn warn [msg] 158 | (println (str "WARNING: " msg))) 159 | 160 | (defn deprecated-warn 161 | [msg] 162 | (let [env *current-env* 163 | file (:file env)] 164 | (println 165 | (str 166 | "DEPRECATED SYNTAX " 167 | "(" 168 | (cond 169 | (and file 170 | (not= "NO_SOURCE_PATH" file)) 171 | (str (:file env) 172 | (when-let [line (:line env)] 173 | (str ":" (:line env) 174 | (when-let [col (:column env)] 175 | (str ":" col))))) 176 | :else "NO_SOURCE_PATH") 177 | "): " 178 | msg)) 179 | (flush))) 180 | 181 | (defn nyi-error 182 | [estr] 183 | (let [env *current-env*] 184 | (throw (ex-info (str "core.typed Not Yet Implemented Error:" 185 | "(" (:file env) ":" (or (:line env) "") 186 | (when-let [col (:column env)] 187 | (str ":"col)) 188 | ") " 189 | estr) 190 | (merge {:type-error nyi-error-kw} 191 | {:env (env-for-error env)}))))) 192 | 193 | #?(:clj 194 | (defmacro with-ex-info-handlers 195 | "Handle an ExceptionInfo e thrown in body. The first handler whos left hand 196 | side returns true, then the right hand side is called passing (ex-info e) and e." 197 | [handlers & body] 198 | `(try 199 | (do ~@body) 200 | (catch clojure.lang.ExceptionInfo e# 201 | (let [found?# (atom false) 202 | result# (reduce (fn [_# [h?# hfn#]] 203 | (when (h?# (ex-data e#)) 204 | (reset! found?# true) 205 | (reduced (hfn# (ex-data e#) e#)))) 206 | nil 207 | ~(mapv vec (partition 2 handlers)))] 208 | (if @found?# 209 | result# 210 | (throw e#))))))) 211 | 212 | (defn var-for-impl [sym] 213 | {:pre [((some-fn string? symbol?) sym)] 214 | :post [(symbol? %)]} 215 | (symbol 216 | (impl/impl-case 217 | :clojure "clojure.core.typed" 218 | :cljs "cljs.core.typed") 219 | (str sym))) 220 | 221 | (defn deprecated-plain-op [old & [new]] 222 | {:pre [(symbol? old) 223 | ((some-fn symbol? nil?) new)]} 224 | (deprecated-warn (str old " syntax is deprecated, use " (var-for-impl (or new old))))) 225 | 226 | (defn deprecated-macro-syntax [form msg] 227 | (binding [*current-env* {:file (or (-> form meta :file) (ns-name *ns*)) 228 | :line (-> form meta :line) 229 | :colomn (-> form meta :column)}] 230 | (deprecated-warn msg))) 231 | 232 | (defn deprecated-renamed-macro [form old new] 233 | (deprecated-macro-syntax 234 | form 235 | (str "Renamed macro: clojure.core.typed/" old 236 | " -> clojure.core.typed/" new))) 237 | 238 | (defn 239 | print-errors! 240 | "Internal use only" 241 | [errors] 242 | {:pre [(seq errors) 243 | (every? #(instance? clojure.lang.ExceptionInfo %) errors)]} 244 | (binding [*out* *err*] 245 | (doseq [^Exception e errors] 246 | (let [{{:keys [file line column] :as env} :env :as data} (ex-data e)] 247 | (print "Type Error ") 248 | (print (str "(" (or file 249 | (let [nsym (-> env :ns)] 250 | (when (symbol? nsym) 251 | nsym)) 252 | "NO_SOURCE_FILE") 253 | (when line 254 | (str ":" line 255 | (when column 256 | (str ":" column)))) 257 | ") ")) 258 | (println) 259 | (print (.getMessage e)) 260 | (println) 261 | (flush) 262 | (let [[_ form :as has-form?] (find data :form)] 263 | (when has-form? 264 | (print "\n\nin:\n") 265 | (binding [*print-length* (when-not uvs/*verbose-forms* 266 | 10) 267 | *print-level* (when-not uvs/*verbose-forms* 268 | 10)] 269 | (pp/pprint form) 270 | (println)) 271 | (println) 272 | (println) 273 | (flush))) 274 | (flush)))) 275 | (throw (ex-info (str "Type Checker: Found " (count errors) " error" (when (< 1 (count errors)) "s")) 276 | {:type-error :top-level-error 277 | :errors errors}))) 278 | 279 | (defn ^:skip-wiki 280 | -init-delayed-errors 281 | "Internal use only" 282 | [] 283 | (atom [] :validator #(and (vector? %) 284 | (every? (fn [a] 285 | (instance? clojure.lang.ExceptionInfo a)) 286 | %)))) 287 | -------------------------------------------------------------------------------- /epl-v10.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Eclipse Public License - Version 1.0 8 | 25 | 26 | 27 | 28 | 29 | 30 |

Eclipse Public License - v 1.0

31 | 32 |

THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 33 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR 34 | DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS 35 | AGREEMENT.

36 | 37 |

1. DEFINITIONS

38 | 39 |

"Contribution" means:

40 | 41 |

a) in the case of the initial Contributor, the initial 42 | code and documentation distributed under this Agreement, and

43 |

b) in the case of each subsequent Contributor:

44 |

i) changes to the Program, and

45 |

ii) additions to the Program;

46 |

where such changes and/or additions to the Program 47 | originate from and are distributed by that particular Contributor. A 48 | Contribution 'originates' from a Contributor if it was added to the 49 | Program by such Contributor itself or anyone acting on such 50 | Contributor's behalf. Contributions do not include additions to the 51 | Program which: (i) are separate modules of software distributed in 52 | conjunction with the Program under their own license agreement, and (ii) 53 | are not derivative works of the Program.

54 | 55 |

"Contributor" means any person or entity that distributes 56 | the Program.

57 | 58 |

"Licensed Patents" mean patent claims licensable by a 59 | Contributor which are necessarily infringed by the use or sale of its 60 | Contribution alone or when combined with the Program.

61 | 62 |

"Program" means the Contributions distributed in accordance 63 | with this Agreement.

64 | 65 |

"Recipient" means anyone who receives the Program under 66 | this Agreement, including all Contributors.

67 | 68 |

2. GRANT OF RIGHTS

69 | 70 |

a) Subject to the terms of this Agreement, each 71 | Contributor hereby grants Recipient a non-exclusive, worldwide, 72 | royalty-free copyright license to reproduce, prepare derivative works 73 | of, publicly display, publicly perform, distribute and sublicense the 74 | Contribution of such Contributor, if any, and such derivative works, in 75 | source code and object code form.

76 | 77 |

b) Subject to the terms of this Agreement, each 78 | Contributor hereby grants Recipient a non-exclusive, worldwide, 79 | royalty-free patent license under Licensed Patents to make, use, sell, 80 | offer to sell, import and otherwise transfer the Contribution of such 81 | Contributor, if any, in source code and object code form. This patent 82 | license shall apply to the combination of the Contribution and the 83 | Program if, at the time the Contribution is added by the Contributor, 84 | such addition of the Contribution causes such combination to be covered 85 | by the Licensed Patents. The patent license shall not apply to any other 86 | combinations which include the Contribution. No hardware per se is 87 | licensed hereunder.

88 | 89 |

c) Recipient understands that although each Contributor 90 | grants the licenses to its Contributions set forth herein, no assurances 91 | are provided by any Contributor that the Program does not infringe the 92 | patent or other intellectual property rights of any other entity. Each 93 | Contributor disclaims any liability to Recipient for claims brought by 94 | any other entity based on infringement of intellectual property rights 95 | or otherwise. As a condition to exercising the rights and licenses 96 | granted hereunder, each Recipient hereby assumes sole responsibility to 97 | secure any other intellectual property rights needed, if any. For 98 | example, if a third party patent license is required to allow Recipient 99 | to distribute the Program, it is Recipient's responsibility to acquire 100 | that license before distributing the Program.

101 | 102 |

d) Each Contributor represents that to its knowledge it 103 | has sufficient copyright rights in its Contribution, if any, to grant 104 | the copyright license set forth in this Agreement.

105 | 106 |

3. REQUIREMENTS

107 | 108 |

A Contributor may choose to distribute the Program in object code 109 | form under its own license agreement, provided that:

110 | 111 |

a) it complies with the terms and conditions of this 112 | Agreement; and

113 | 114 |

b) its license agreement:

115 | 116 |

i) effectively disclaims on behalf of all Contributors 117 | all warranties and conditions, express and implied, including warranties 118 | or conditions of title and non-infringement, and implied warranties or 119 | conditions of merchantability and fitness for a particular purpose;

120 | 121 |

ii) effectively excludes on behalf of all Contributors 122 | all liability for damages, including direct, indirect, special, 123 | incidental and consequential damages, such as lost profits;

124 | 125 |

iii) states that any provisions which differ from this 126 | Agreement are offered by that Contributor alone and not by any other 127 | party; and

128 | 129 |

iv) states that source code for the Program is available 130 | from such Contributor, and informs licensees how to obtain it in a 131 | reasonable manner on or through a medium customarily used for software 132 | exchange.

133 | 134 |

When the Program is made available in source code form:

135 | 136 |

a) it must be made available under this Agreement; and

137 | 138 |

b) a copy of this Agreement must be included with each 139 | copy of the Program.

140 | 141 |

Contributors may not remove or alter any copyright notices contained 142 | within the Program.

143 | 144 |

Each Contributor must identify itself as the originator of its 145 | Contribution, if any, in a manner that reasonably allows subsequent 146 | Recipients to identify the originator of the Contribution.

147 | 148 |

4. COMMERCIAL DISTRIBUTION

149 | 150 |

Commercial distributors of software may accept certain 151 | responsibilities with respect to end users, business partners and the 152 | like. While this license is intended to facilitate the commercial use of 153 | the Program, the Contributor who includes the Program in a commercial 154 | product offering should do so in a manner which does not create 155 | potential liability for other Contributors. Therefore, if a Contributor 156 | includes the Program in a commercial product offering, such Contributor 157 | ("Commercial Contributor") hereby agrees to defend and 158 | indemnify every other Contributor ("Indemnified Contributor") 159 | against any losses, damages and costs (collectively "Losses") 160 | arising from claims, lawsuits and other legal actions brought by a third 161 | party against the Indemnified Contributor to the extent caused by the 162 | acts or omissions of such Commercial Contributor in connection with its 163 | distribution of the Program in a commercial product offering. The 164 | obligations in this section do not apply to any claims or Losses 165 | relating to any actual or alleged intellectual property infringement. In 166 | order to qualify, an Indemnified Contributor must: a) promptly notify 167 | the Commercial Contributor in writing of such claim, and b) allow the 168 | Commercial Contributor to control, and cooperate with the Commercial 169 | Contributor in, the defense and any related settlement negotiations. The 170 | Indemnified Contributor may participate in any such claim at its own 171 | expense.

172 | 173 |

For example, a Contributor might include the Program in a commercial 174 | product offering, Product X. That Contributor is then a Commercial 175 | Contributor. If that Commercial Contributor then makes performance 176 | claims, or offers warranties related to Product X, those performance 177 | claims and warranties are such Commercial Contributor's responsibility 178 | alone. Under this section, the Commercial Contributor would have to 179 | defend claims against the other Contributors related to those 180 | performance claims and warranties, and if a court requires any other 181 | Contributor to pay any damages as a result, the Commercial Contributor 182 | must pay those damages.

183 | 184 |

5. NO WARRANTY

185 | 186 |

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS 187 | PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 188 | OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, 189 | ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY 190 | OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely 191 | responsible for determining the appropriateness of using and 192 | distributing the Program and assumes all risks associated with its 193 | exercise of rights under this Agreement , including but not limited to 194 | the risks and costs of program errors, compliance with applicable laws, 195 | damage to or loss of data, programs or equipment, and unavailability or 196 | interruption of operations.

197 | 198 |

6. DISCLAIMER OF LIABILITY

199 | 200 |

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT 201 | NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, 202 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING 203 | WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF 204 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 205 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR 206 | DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED 207 | HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

208 | 209 |

7. GENERAL

210 | 211 |

If any provision of this Agreement is invalid or unenforceable under 212 | applicable law, it shall not affect the validity or enforceability of 213 | the remainder of the terms of this Agreement, and without further action 214 | by the parties hereto, such provision shall be reformed to the minimum 215 | extent necessary to make such provision valid and enforceable.

216 | 217 |

If Recipient institutes patent litigation against any entity 218 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 219 | Program itself (excluding combinations of the Program with other 220 | software or hardware) infringes such Recipient's patent(s), then such 221 | Recipient's rights granted under Section 2(b) shall terminate as of the 222 | date such litigation is filed.

223 | 224 |

All Recipient's rights under this Agreement shall terminate if it 225 | fails to comply with any of the material terms or conditions of this 226 | Agreement and does not cure such failure in a reasonable period of time 227 | after becoming aware of such noncompliance. If all Recipient's rights 228 | under this Agreement terminate, Recipient agrees to cease use and 229 | distribution of the Program as soon as reasonably practicable. However, 230 | Recipient's obligations under this Agreement and any licenses granted by 231 | Recipient relating to the Program shall continue and survive.

232 | 233 |

Everyone is permitted to copy and distribute copies of this 234 | Agreement, but in order to avoid inconsistency the Agreement is 235 | copyrighted and may only be modified in the following manner. The 236 | Agreement Steward reserves the right to publish new versions (including 237 | revisions) of this Agreement from time to time. No one other than the 238 | Agreement Steward has the right to modify this Agreement. The Eclipse 239 | Foundation is the initial Agreement Steward. The Eclipse Foundation may 240 | assign the responsibility to serve as the Agreement Steward to a 241 | suitable separate entity. Each new version of the Agreement will be 242 | given a distinguishing version number. The Program (including 243 | Contributions) may always be distributed subject to the version of the 244 | Agreement under which it was received. In addition, after a new version 245 | of the Agreement is published, Contributor may elect to distribute the 246 | Program (including its Contributions) under the new version. Except as 247 | expressly stated in Sections 2(a) and 2(b) above, Recipient receives no 248 | rights or licenses to the intellectual property of any Contributor under 249 | this Agreement, whether expressly, by implication, estoppel or 250 | otherwise. All rights in the Program not expressly granted under this 251 | Agreement are reserved.

252 | 253 |

This Agreement is governed by the laws of the State of New York and 254 | the intellectual property laws of the United States of America. No party 255 | to this Agreement will bring a legal action under this Agreement more 256 | than one year after the cause of action arose. Each party waives its 257 | rights to a jury trial in any resulting litigation.

258 | 259 | 260 | 261 | 262 | -------------------------------------------------------------------------------- /src/main/clojure/clojure/core/typed/rules.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Ambrose Bonnaire-Sergeant, Rich Hickey & contributors. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns clojure.core.typed.rules 10 | (:require [clojure.core.typed :as t] 11 | [clojure.core.typed.internal :as internal] 12 | [clojure.core.typed.analyzer :as ana2])) 13 | 14 | (t/defalias TCType t/Any) 15 | (t/defalias MsgFnOpts (t/HMap)) 16 | 17 | (t/defalias AST (t/Map t/Any t/Any)) 18 | 19 | (t/defalias ExprType 20 | (t/HMap :mandatory 21 | {;; the type 22 | :type TCType} 23 | ;; filter set 24 | :optional 25 | {:filters (t/HMap :optional 26 | {:then t/Any 27 | :else t/Any}) 28 | ;; the object 29 | :object t/Any 30 | ;; the flow filter 31 | :flow t/Any 32 | :opts (t/HMap :optional 33 | {:msg-fn [MsgFnOpts -> t/Str] 34 | :blame-form t/Any})})) 35 | 36 | (t/defalias ErrorOpts (t/HMap 37 | :optional 38 | {:expected (t/U nil ExprType)})) 39 | 40 | (t/defalias RuleOpts 41 | (t/HMap :mandatory 42 | {; FIXME docs 43 | :expr AST 44 | ; FIXME docs 45 | :opts t/Any 46 | ;; the fully qualified symbol of the current 47 | ;; macro being type checked 48 | :vsym t/Sym 49 | ;; Map of current tools.analyzer local scope 50 | :locals (t/Map t/Sym t/Any) 51 | ;; expected type of the current form 52 | :expected (t/U nil ExprType) 53 | ;; (fn [actual maybe-expected] ..) 54 | ;; if provided, checks actual is compatible with the expected type 55 | :maybe-check-expected [ExprType (t/U nil ExprType) -> ExprType] 56 | ;; (fn ([form] ..) ([form expected-type] ..)) 57 | ;; type checks form with an optional expected-type 58 | :check (t/IFn [t/Any -> ExprType] 59 | [t/Any (t/U nil ExprType) -> ExprType]) 60 | ;; (fn [vs f] ..) 61 | ;; FIXME docs 62 | ;:solve-subtype [(t/Vec t/Sym) [t/Sym * :-> [TCType TCType]] :-> (t/U nil (t/Map t/Sym TCType))] 63 | ;; (fn [t1 t2] ..) 64 | ;; true if t1 is a subtype of t2 65 | :subtype? [TCType TCType :-> Boolean] 66 | ;; given a tools.analyzer AST form, returns its Clojure representation 67 | :emit-form [t/Any :-> t/Any] 68 | ;; compacts a type so it is suitable to use in an error message 69 | :abbreviate-type [TCType :-> TCType] 70 | ;;TODO document 71 | :expected-error [TCType ExprType ErrorOpts :-> t/Any] 72 | :delayed-error [t/Str ErrorOpts :-> t/Any] 73 | :internal-error [t/Str ErrorOpts :-> t/Any] 74 | })) 75 | 76 | (t/ann typing-rule [RuleOpts -> '{:op t/Kw, ::expr-type ExprType}]) 77 | (defmulti typing-rule (fn [{:keys [vsym]}] vsym)) 78 | 79 | (defmulti macro-rule (fn [_ _ {:keys [vsym]}] vsym)) 80 | 81 | ;copied from clojure.core 82 | (defn- get-super-and-interfaces [bases] 83 | (if (. ^Class (first bases) (isInterface)) 84 | [Object bases] 85 | [(first bases) (next bases)])) 86 | 87 | (defmethod macro-rule 'clojure.core/proxy 88 | [[_ class-and-interfaces args & fs :as form] expected _] 89 | (let [bases (map #(or (resolve %) (throw (Exception. (str "Can't resolve: " %)))) 90 | class-and-interfaces) 91 | [super interfaces] (get-super-and-interfaces bases) 92 | ^Class pc-effect (apply get-proxy-class bases) 93 | pname (proxy-name super interfaces) 94 | super (.getSuperclass pc-effect) 95 | t `(t/I ~@(map (comp symbol #(.getName ^Class %)) bases))] 96 | {:form `(^::t/untyped proxy [] ~args ~@fs) 97 | ::expr-type {:type t}})) 98 | 99 | #_ 100 | (defmethod typing-rule 'clojure.core.typed.expand/gather-for-return-type 101 | [{[_ ret] :form, :keys [expected check solve]}] 102 | (assert nil "FIXME args etc.") 103 | (let [{:keys [::expr-type] :as m} (check ret) 104 | {:keys [x] :as solved?} (solve-subtype '[x] 105 | (fn [x] 106 | [(:type expr-type) `(t/U nil '[~x])])) 107 | _ (assert solved?) 108 | ret {:type `(t/Seq ~x) 109 | :filters {:else 'ff}}] 110 | (assoc m ::expr-type ret))) 111 | 112 | (defmethod typing-rule 'clojure.core.typed.expand/expected-type-as 113 | [{:keys [expr opts expected check delayed-error form with-updated-locals]}] 114 | (let [{:keys [sym msg-fn blame-form]} opts] 115 | (if expected 116 | (with-updated-locals {sym (:type expected)} 117 | #(check expr expected)) 118 | (do 119 | (delayed-error (if msg-fn 120 | ((eval msg-fn) {}) 121 | "Must provide expected to this expression") 122 | {:form (if (contains? opts :blame-form) 123 | blame-form 124 | form)}) 125 | (assoc expr ::expr-type {:type `t/TCError}))))) 126 | 127 | ;; (solve 128 | ;; coll 129 | ;; {:query (t/All [a] [(t/U nil (t/Seqable a)) :-> a]) 130 | ;; :msg-fn (fn [_#] 131 | ;; (str "Argument number " ~(inc i) 132 | ;; " to 'map' must be Seqable")) 133 | ;; :blame-form ~coll}) 134 | (defmethod typing-rule 'clojure.core.typed.expand/solve 135 | [{:keys [expr opts expected check solve delayed-error form maybe-check-expected]}] 136 | (let [{:keys [query msg-fn blame-form]} opts 137 | {::keys [expr-type] :as cexpr} (check expr) 138 | res (solve expr-type query)] 139 | (when-not res 140 | (let [form (if (contains? opts :blame-form) 141 | blame-form 142 | form)] 143 | ;; msg-fn should provide message 144 | (delayed-error nil (merge {:form form :actual (:type expr-type)} 145 | (select-keys opts [:msg-fn :blame-form]))))) 146 | (assoc cexpr 147 | ::expr-type (maybe-check-expected 148 | (or res {:type `t/TCError}) 149 | expected)))) 150 | 151 | (defmethod typing-rule 'clojure.core.typed.expand/require-expected 152 | [{:keys [expr opts expected check solve delayed-error form maybe-check-expected subtype?]}] 153 | (let [sub-check (:subtype opts) 154 | msg-fn (:msg-fn opts)] 155 | (cond 156 | (or (not expected) 157 | (and expected 158 | (contains? opts :subtype) 159 | (not (subtype? (:type expected) sub-check)))) 160 | (let [form (if-let [[_ bf] (find opts :blame-form)] 161 | bf 162 | form) 163 | msg (if msg-fn 164 | ((eval msg-fn) {}) 165 | (str "An expected type " 166 | (when (contains? opts :subtype) 167 | (str "which is a subtype of " (pr-str sub-check))) 168 | " is required for this expression."))] 169 | (delayed-error msg {:form form}) 170 | (assoc expr ::expr-type {:type `t/TCError})) 171 | 172 | :else (check expr expected)))) 173 | 174 | #_ 175 | (defmethod typing-rule 'clojure.core.typed.expand/check-for-expected 176 | [{[_ {:keys [expr expected-local] :as form-opts} :as form] :form, 177 | :keys [expr opts expected check locals solve-subtype subtype? delayed-error abbreviate-type 178 | emit-form] :as opt}] 179 | (assert nil "FIXME update args above and defmacro") 180 | (assert (not (:expected opt))) 181 | (let [{:keys [expected-local]} opts 182 | l (get locals expected-local) 183 | _ (assert l expected-local) 184 | [qut expected] (-> l :init emit-form) 185 | _ (assert (= 'quote qut)) 186 | {:syms [x] :as solved?} (when expected 187 | (solve-subtype '[x] 188 | (fn [x] 189 | [(:type expected) `(t/U nil (t/Seqable ~x))]))) 190 | ;; TODO check-below of filters/object/flow 191 | errored? (when expected 192 | (when-not (subtype? `(t/Seq t/Nothing) (:type expected)) 193 | (delayed-error (str "'for' expression returns a seq, but surrounding context expected it to return " 194 | (pr-str (abbreviate-type (:type expected)))) 195 | {:form (:form form-opts)}) 196 | true)) 197 | _ (assert (or solved? errored? (not expected)))] 198 | (check expr (when expected 199 | (when solved? 200 | (when (not errored?) 201 | {:type x})))))) 202 | 203 | (defn update-expected-with-check-expected-opts 204 | [expected opts] 205 | (assert (map? opts) (pr-str (class opts))) 206 | (when-let [expected (or expected 207 | (:default-expected opts) 208 | #_ 209 | {:type `^::t/infer t/Any 210 | :filters {:then 'no-filter 211 | :else 'no-filter} 212 | :flow 'no-filter 213 | :object 'no-object})] 214 | (update expected :opts 215 | ;; earlier messages override later ones 216 | #(merge 217 | (select-keys opts [:blame-form :msg-fn]) 218 | %)))) 219 | 220 | (defmethod typing-rule 'clojure.core.typed.expand/check-expected 221 | [{:keys [expr opts expected check]}] 222 | (check expr (update-expected-with-check-expected-opts expected opts))) 223 | 224 | (defmethod typing-rule 'clojure.core.typed.expand/check-if-empty-body 225 | [{:keys [expr opts expected check]}] 226 | (check expr (when expected 227 | (if (empty? (:original-body opts)) 228 | (update expected :opts 229 | ;; earlier messages override later ones 230 | #(merge 231 | (select-keys opts [:blame-form :msg-fn]) 232 | %)) 233 | expected)))) 234 | 235 | ;TODO use ana2/run-passes & ana2/unmark-eval-top-level 236 | (defmethod typing-rule 'clojure.core.typed.expand/type-error 237 | [{:keys [expr opts delayed-error]}] 238 | (let [{:keys [msg-fn form]} opts] 239 | (delayed-error ((eval msg-fn) {}) {:form form}) 240 | (assoc expr ::expr-type {:type `t/TCError}))) 241 | 242 | (defmethod typing-rule 'clojure.core.typed.expand/with-post-blame-context 243 | [{:keys [expr opts env expected check]} ] 244 | (let [ce (check expr expected)] 245 | (update-in ce [::expr-type :opts] 246 | ;; earlier messages override later ones 247 | #(merge 248 | (select-keys opts [:blame-form :msg-fn]) 249 | %)))) 250 | 251 | ;; FIXME use check-below!! 252 | (defn ann-form-typing-rule 253 | [{:keys [expr opts expected check subtype? expected-error]}] 254 | {:pre [(map? opts)]} 255 | #_ 256 | (prn "ann-form-typing-rule" opts expected (class expected)) 257 | (let [_ (assert (contains? opts :type)) 258 | {ty :type, :keys [inner-check-expected outer-check-expected]} opts 259 | _ (assert (map? inner-check-expected) inner-check-expected) 260 | _ (assert (map? outer-check-expected) outer-check-expected) 261 | _ (when expected 262 | ;; FIXME use check-below!! 263 | (when-not (subtype? ty (:type expected)) 264 | (expected-error ty expected 265 | {:expected (update-expected-with-check-expected-opts 266 | expected outer-check-expected)})))] 267 | (check expr (update-expected-with-check-expected-opts 268 | (merge expected {:type ty}) inner-check-expected)))) 269 | 270 | (defmethod typing-rule `t/ann-form [& args] (apply ann-form-typing-rule args)) 271 | (defmethod typing-rule 'clojure.core.typed.macros/ann-form [& args] (apply ann-form-typing-rule args)) 272 | 273 | (defn tc-ignore-typing-rule 274 | [{:keys [expr opts expected maybe-check-expected]}] 275 | {:pre [(map? opts)]} 276 | #_ 277 | (prn "tc-ignore-typing-rule" opts) 278 | (let [expr (-> expr 279 | ana2/run-passes 280 | ; ensure the main checking loop doesn't reevaluate this tc-ignore, 281 | ; since run-passes has already if this is top-level. 282 | ana2/unmark-eval-top-level)] 283 | 284 | (assoc expr 285 | ::expr-type (maybe-check-expected 286 | {:type `t/Any} 287 | (update-expected-with-check-expected-opts 288 | expected (:outer-check-expected opts)))))) 289 | 290 | (defmethod typing-rule `t/tc-ignore [& args] (apply tc-ignore-typing-rule args)) 291 | (defmethod typing-rule 'clojure.core.typed.macros/tc-ignore [& args] (apply tc-ignore-typing-rule args)) 292 | 293 | (defmethod typing-rule 'clojure.core.typed.expand/ignore-expected-if 294 | [{[_ ignore? body :as form] :form, :keys [expected check]}] 295 | {:pre [(boolean? ignore?)]} 296 | (assert nil "FIXME args etc.") 297 | (check body (when-not ignore? expected))) 298 | 299 | (defmethod typing-rule :default 300 | [{:keys [form internal-error]}] 301 | (internal-error (str "No such internal form: " form))) 302 | -------------------------------------------------------------------------------- /src/main/clojure/cljs/core/typed.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Ambrose Bonnaire-Sergeant, Rich Hickey & contributors. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns ^:no-doc cljs.core.typed 10 | "Macros for Clojurescript type checking" 11 | (:refer-clojure :exclude [fn loop let defn atom defprotocol]) 12 | (:require [clojure.core.typed.load-if-needed :as load] 13 | [clojure.core :as core] 14 | [clojure.core.typed.current-impl :as impl] 15 | [clojure.core.typed.util-vars :as vs] 16 | [clojure.core.typed.internal :as internal] 17 | [clojure.core.typed.errors :as err] 18 | [clojure.core.typed.special-form :as spec] 19 | [clojure.core.typed.import-macros :as import-m] 20 | [clojure.core.typed.macros :as macros] 21 | [clojure.pprint :as pprint])) 22 | 23 | (import-m/import-macros clojure.core.typed.macros 24 | [fn tc-ignore ann-form def loop let defn atom defprotocol]) 25 | 26 | (defn load-if-needed 27 | "Load and initialize all of core.typed if not already" 28 | [] 29 | (load/load-if-needed true)) 30 | 31 | (let [rc (delay (impl/dynaload 'clojure.core.typed.checker.jvm.reset-caches/reset-caches))] 32 | (defn reset-caches 33 | "Reset internal type caches." 34 | [] 35 | (load-if-needed) 36 | (@rc))) 37 | 38 | ; many of these macros resolve to CLJS functions in 39 | ; the CLJS ns cljs.core.typed 40 | 41 | (def ^:private parse-cljs (delay (impl/dynaload 'clojure.core.typed.checker.jvm.parse-unparse/parse-cljs))) 42 | (def ^:private cljs-ns (delay (impl/dynaload 'clojure.core.typed.util-cljs/cljs-ns))) 43 | (def ^:private with-parse-ns* (delay (impl/dynaload 'clojure.core.typed.checker.jvm.parse-unparse/with-parse-ns*))) 44 | 45 | (defmacro ^:private delay-tc-parse 46 | [t] 47 | `(let [t# ~t 48 | app-outer-context# (bound-fn [f#] (f#))] 49 | (delay 50 | (app-outer-context# 51 | (fn [] 52 | (@with-parse-ns* 53 | (@cljs-ns) 54 | #(@parse-cljs t#))))))) 55 | 56 | (defmacro ^:skip-wiki with-current-location 57 | [{:keys [form env]} & body] 58 | `(let [form# ~form 59 | env# ~env] 60 | (binding [vs/*current-env* {:ns (or (:ns env#) 61 | {:name (@cljs-ns)}) 62 | :line (or (-> form# meta :line) 63 | (:line env#) 64 | :column (or (-> form# meta :column) 65 | (:column env#)))}] 66 | ~@body))) 67 | 68 | (defn ^:skip-wiki 69 | ann*-macro-time 70 | "Internal use only. Use ann." 71 | [qsym typesyn check? form env] 72 | (let [_ (impl/with-impl impl/clojurescript 73 | (when (and (contains? (impl/var-env) qsym) 74 | (not (impl/check-var? qsym)) 75 | check?) 76 | (err/warn (str "Removing :no-check from var " qsym)) 77 | (impl/remove-nocheck-var qsym))) 78 | _ (impl/with-impl impl/clojurescript 79 | (when-not check? 80 | (impl/add-nocheck-var qsym))) 81 | #_#_ast (with-current-location {:form form :env env} 82 | (delay-rt-parse typesyn)) 83 | tc-type (with-current-location {:form form :env env} 84 | (delay-tc-parse typesyn))] 85 | #_(impl/with-impl impl/clojurescript 86 | (impl/add-var-env qsym ast)) 87 | (impl/with-impl impl/clojurescript 88 | (impl/add-tc-var-type qsym tc-type))) 89 | nil) 90 | 91 | (let [cljs-resolve (delay (impl/dynaload 'cljs.analyzer.api/resolve))] 92 | (defmacro ann 93 | "Annotate varsym with type. If unqualified, qualify in the current namespace. 94 | If varsym has metadata {:no-check true}, ignore definitions of varsym while type checking. 95 | 96 | eg. ; annotate the var foo in this namespace 97 | (ann foo [Number -> Number]) 98 | 99 | ; annotate a var in another namespace 100 | (ann another.ns/bar [-> nil]) 101 | 102 | ; don't check this var 103 | (ann ^:no-check foobar [Integer -> String])" 104 | [varsym typesyn] 105 | (let [{:keys [name]} (@cljs-resolve &env varsym) 106 | qsym name 107 | opts (meta varsym) 108 | check? (not (:no-check opts))] 109 | (ann*-macro-time qsym typesyn check? &form &env) 110 | `(tc-ignore (ann* '~qsym '~typesyn '~check? '~&form))))) 111 | 112 | (defmacro 113 | ^{:forms '[(ann-protocol vbnd varsym & methods) 114 | (ann-protocol varsym & methods)]} 115 | ann-protocol 116 | "Annotate a possibly polymorphic protocol var with method types. 117 | 118 | eg. (ann-protocol IFoo 119 | bar 120 | [IFoo -> Any] 121 | baz 122 | [IFoo -> Number]) 123 | 124 | ; polymorphic 125 | (ann-protocol [[x :variance :covariant]] 126 | IFoo 127 | bar 128 | [IFoo -> Any] 129 | baz 130 | [IFoo -> Number])" 131 | [& args] 132 | (let [bnd-provided? (vector? (first args)) 133 | vbnd (when bnd-provided? 134 | (first args)) 135 | varsym (if bnd-provided? 136 | (second args) 137 | (first args)) 138 | {:as mth} (if bnd-provided? 139 | (next (next args)) 140 | (next args))] 141 | `(ann-protocol* '~vbnd '~varsym '~mth))) 142 | 143 | (defmacro ann-jsnominal 144 | "Equivalent of TypeScript interface" 145 | [varsym jsnom] 146 | (let [qualsym (if (namespace varsym) 147 | varsym 148 | (symbol (str (ns-name *ns*)) (name varsym)))] 149 | `(ann-jsnominal* '~qualsym '~jsnom))) 150 | 151 | (defmacro 152 | ^{:forms '[(ann-datatype dname [field :- type*] opts*) 153 | (ann-datatype binder dname [field :- type*] opts*)]} 154 | ann-datatype 155 | "Annotate datatype Class name dname with expected fields. 156 | If unqualified, qualify in the current namespace. 157 | 158 | eg. (ann-datatype MyDatatype [a :- Number, 159 | b :- Long]) 160 | 161 | (ann-datatype another.ns.TheirDatatype 162 | [str :- String, 163 | vec :- (IPersistentVector Number)])" 164 | [& args] 165 | ;[dname fields & {ancests :unchecked-ancestors rplc :replace :as opts}] 166 | (let [bnd-provided? (vector? (first args)) 167 | vbnd (when bnd-provided? 168 | (first args)) 169 | [dname fields & {ancests :unchecked-ancestors rplc :replace :as opts}] 170 | (if bnd-provided? 171 | (next args) 172 | args)] 173 | (assert (not rplc) "Replace NYI") 174 | (assert (symbol? dname) 175 | (str "Must provide name symbol: " dname)) 176 | `(ann-datatype* '~vbnd '~dname '~fields '~opts))) 177 | 178 | (defmacro defalias 179 | "Define a type alias. Takes an optional doc-string as a second 180 | argument. 181 | 182 | Updates the corresponding var with documentation. 183 | 184 | eg. (defalias MyAlias 185 | \"Here is my alias\" 186 | (U nil String))" 187 | ([sym doc-str t] 188 | (assert (string? doc-str) "Doc-string passed to defalias must be a string") 189 | `(defalias ~sym ~t)) 190 | ([sym t] 191 | (assert (symbol? sym) (str "First argument to defalias must be a symbol: " sym)) 192 | `(do (def-alias* '~sym '~t) 193 | ~(when-not (namespace sym) 194 | `(def ~sym))))) 195 | 196 | (defmacro inst 197 | "Instantiate a polymorphic type with a number of types" 198 | [inst-of & types] 199 | `(inst-poly ~inst-of '~types)) 200 | 201 | (defmacro 202 | ^{:forms '[(letfn> [fn-spec-or-annotation*] expr*)]} 203 | letfn> 204 | "Like letfn, but each function spec must be annotated. 205 | 206 | eg. (letfn> [a :- [Number -> Number] 207 | (a [b] 2) 208 | 209 | c :- [Symbol -> nil] 210 | (c [s] nil)] 211 | ...)" 212 | [fn-specs-and-annotations & body] 213 | (let [bindings fn-specs-and-annotations 214 | ; (Vector (U '[Symbol TypeSyn] LetFnInit)) 215 | normalised-bindings 216 | (core/loop [[fbnd :as bindings] bindings 217 | norm []] 218 | (cond 219 | (empty? bindings) norm 220 | (symbol? fbnd) (do 221 | (assert (#{:-} (second bindings)) 222 | "letfn> annotations require :- separator") 223 | (assert (<= 3 (count bindings))) 224 | (recur 225 | (drop 3 bindings) 226 | (conj norm [(nth bindings 0) 227 | (nth bindings 2)]))) 228 | (list? fbnd) (recur 229 | (next bindings) 230 | (conj norm fbnd)) 231 | :else (throw (Exception. (str "Unknown syntax to letfn>: " fbnd))))) 232 | {anns false inits true} (group-by list? normalised-bindings) 233 | ; init-syn unquotes local binding references to be compatible with hygienic expansion 234 | init-syn (into {} 235 | (for [[lb type] anns] 236 | [lb `'~type]))] 237 | `(cljs.core/letfn ~(vec inits) 238 | ;unquoted to allow bindings to resolve with hygiene 239 | ~init-syn 240 | ;;preserve letfn empty body 241 | ;;nil 242 | ~@body))) 243 | 244 | (defmacro 245 | ^{:forms '[(loop> [binding :- type, init*] exprs*)]} 246 | ^{:deprecated "0.2.61"} 247 | loop> 248 | "DEPRECATED: use loop 249 | 250 | Like loop, except loop variables require annotation. 251 | 252 | Suggested idiom: use a comma between the type and the initial 253 | expression. 254 | 255 | eg. (loop> [a :- Number, 1 256 | b :- (U nil Number), nil] 257 | ...)" 258 | [bndings* & forms] 259 | (let [normalise-args 260 | (core/fn [seq-exprs] 261 | (core/loop [flat-result () 262 | seq-exprs seq-exprs] 263 | (cond 264 | (empty? seq-exprs) flat-result 265 | (and (vector? (first seq-exprs)) 266 | (#{:-} (-> seq-exprs first second))) (do 267 | (prn "DEPRECATED WARNING: loop> syntax has changed, use [b :- t i] for clauses" 268 | "ns: " *ns* " form:" &form) 269 | (recur (concat flat-result (take 2 seq-exprs)) 270 | (drop 2 seq-exprs))) 271 | :else (do (assert (#{:-} (second seq-exprs)) 272 | "Incorrect syntax in loop>.") 273 | (recur (concat flat-result [(vec (take 3 seq-exprs)) 274 | (nth seq-exprs 3)]) 275 | (drop 4 seq-exprs)))))) 276 | ;group args in flat pairs 277 | bndings* (normalise-args bndings*) 278 | bnds (partition 2 bndings*) 279 | ; [[lhs :- bnd-ann] rhs] 280 | lhs (map ffirst bnds) 281 | rhs (map second bnds) 282 | bnd-anns (map #(-> % first next second) bnds)] 283 | `(loop>-ann (cljs.core/loop ~(vec (mapcat vector lhs rhs)) 284 | ~@forms) 285 | '~bnd-anns))) 286 | 287 | (defmacro typed-deps 288 | "Declare namespaces which should be checked before the current namespace. 289 | Accepts any number of symbols. 290 | 291 | eg. (typed-deps clojure.core.typed.holes 292 | myns.types)" 293 | [& args] 294 | `(typed-deps* '~args)) 295 | 296 | (let [check-form-cljs (delay (impl/dynaload 'clojure.core.typed.checker.js.check-form-cljs/check-form-cljs))] 297 | (defn cf* 298 | "Check a single form with an optional expected type. 299 | Intended to be called from Clojure. For evaluation at the Clojurescript 300 | REPL see cf." 301 | [form expected expected-provided?] 302 | (load-if-needed) 303 | (@check-form-cljs form expected expected-provided?))) 304 | 305 | (let [chkfi (delay (impl/dynaload 'clojure.core.typed.checker.js.check-form-cljs/check-form-info))] 306 | (defn check-form-info 307 | [form & opts] 308 | (load-if-needed) 309 | (apply @chkfi form opts))) 310 | 311 | (defmacro cf 312 | "Check a single form with an optional expected type." 313 | ([form] `(cf* '~form nil nil)) 314 | ([form expected] `(cf* '~form '~expected true))) 315 | 316 | (let [chkni (delay (impl/dynaload 'clojure.core.typed.checker.js.check-ns-cljs/check-ns-info))] 317 | (defn check-ns-info 318 | "Check a Clojurescript namespace, or the current namespace. 319 | Intended to be called from Clojure. For evaluation at the Clojurescript 320 | REPL see check-ns." 321 | ([] 322 | (load-if-needed) 323 | (check-ns-info (@cljs-ns))) 324 | ([ns-or-syms & {:as opt}] 325 | (load-if-needed) 326 | (@chkni ns-or-syms opt)))) 327 | 328 | (let [chkns (delay (impl/dynaload 'clojure.core.typed.checker.js.check-ns-cljs/check-ns))] 329 | (defn check-ns* 330 | "Check a Clojurescript namespace, or the current namespace. 331 | Intended to be called from Clojure. For evaluation at the Clojurescript 332 | REPL see check-ns." 333 | ([] 334 | (load-if-needed) 335 | (check-ns* (@cljs-ns))) 336 | ([ns-or-syms & {:as opt}] 337 | (load-if-needed) 338 | (@chkns ns-or-syms opt)))) 339 | 340 | (defmacro check-ns 341 | "Check a Clojurescript namespace, or the current namespace. This macro 342 | is intended to be called at the Clojurescript REPL. For the equivalent function see 343 | check-ns*. 344 | 345 | The symbols *ns* and clojure.core/*ns* are special and refer to the current namespace. Useful if 346 | providing options for the current namespace." 347 | ([] 348 | (load-if-needed) 349 | `(check-ns *ns*)) 350 | ([ns-or-syms & args] 351 | (load-if-needed) 352 | (let [_ (when (and (list? ns-or-syms) 353 | (#{'quote} (first ns-or-syms))) 354 | (err/int-error "check-ns is a macro, do not quote the first argument")) 355 | ns-or-syms (if ('#{*ns* clojure.core/*ns*} ns-or-syms) 356 | (@cljs-ns) 357 | ns-or-syms)] 358 | `~(apply check-ns* ns-or-syms args)))) 359 | -------------------------------------------------------------------------------- /src/main/clojure/clojure/core/typed/async.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Ambrose Bonnaire-Sergeant, Rich Hickey & contributors. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns 10 | ^{:doc 11 | "This namespace contains annotations and helper macros for type 12 | checking core.async code. Ensure clojure.core.async is require'd 13 | before performing type checking. 14 | 15 | go 16 | use go 17 | 18 | chan 19 | use chan 20 | 21 | buffer 22 | use buffer (similar for other buffer constructors) 23 | "} 24 | clojure.core.typed.async 25 | (:require [clojure.core.typed :refer [ann ann-datatype defalias inst ann-protocol] 26 | :as t] 27 | [clojure.core.async :as async] 28 | [clojure.core.async.impl.protocols :as impl] 29 | [clojure.core.async.impl.channels :as channels] 30 | [clojure.core.async.impl.dispatch :as dispatch] 31 | [clojure.core.async.impl.ioc-macros :as ioc] 32 | ) 33 | (:import (java.util.concurrent Executor) 34 | (java.util.concurrent.locks Lock) 35 | (java.util.concurrent.atomic AtomicReferenceArray) 36 | (clojure.lang IDeref))) 37 | 38 | ;TODO how do we encode that nil is illegal to provide to Ports/Channels? 39 | ; Is it essential? 40 | 41 | ;;;;;;;;;;;;;;;;;;;; 42 | ;; Protocols 43 | 44 | (ann-protocol clojure.core.async.impl.protocols/Channel 45 | close! [impl/Channel -> nil]) 46 | 47 | (ann-protocol [[r :variance :covariant]] 48 | clojure.core.async.impl.protocols/ReadPort 49 | take! [(impl/ReadPort r) Lock 50 | -> (t/U nil (IDeref (t/U nil r)))]) 51 | 52 | (ann-protocol [[w :variance :contravariant]] 53 | clojure.core.async.impl.protocols/WritePort 54 | put! [(impl/WritePort w) w Lock 55 | -> (t/U nil (IDeref nil))]) 56 | 57 | (ann-protocol [[w :variance :contravariant] 58 | [r :variance :covariant]] 59 | clojure.core.async.impl.protocols/Buffer 60 | full? [(impl/Buffer w r) :-> t/Any] 61 | remove! [(impl/Buffer w r) :-> nil] 62 | add!* [(impl/Buffer w r) w :-> (impl/Buffer w r)] 63 | ) 64 | 65 | (ann-protocol clojure.core.async.impl.protocols/UnblockingBuffer) 66 | 67 | (ann-datatype [[w :variance :contravariant] 68 | [r :variance :covariant]] 69 | clojure.core.async.impl.channels.ManyToManyChannel 70 | [] 71 | :unchecked-ancestors [impl/Channel 72 | (impl/ReadPort r) 73 | (impl/WritePort w)]) 74 | 75 | ;;;;;;;;;;;;;;;;;;;; 76 | ;; Aliases 77 | 78 | (defalias 79 | ^{:forms '[(Port2 t t)]} 80 | Port2 81 | "A port that can write type w and read type r" 82 | (t/TFn [[w :variance :contravariant] 83 | [r :variance :covariant]] 84 | (t/I (impl/WritePort w) 85 | (impl/ReadPort r)))) 86 | 87 | (defalias 88 | ^{:forms '[(Port t)]} 89 | Port 90 | "A port that can read and write type x" 91 | (t/TFn [[x :variance :invariant]] 92 | (Port2 x x))) 93 | 94 | (defalias 95 | ^{:forms '[(Chan2 t t)]} 96 | Chan2 97 | "A core.async channel that can take type w and put type r" 98 | (t/TFn [[w :variance :contravariant] 99 | [r :variance :covariant]] 100 | (t/I (Port2 w r) 101 | impl/Channel))) 102 | 103 | (defalias 104 | ^{:forms '[(Chan t)]} 105 | Chan 106 | "A core.async channel" 107 | (t/TFn [[x :variance :invariant]] 108 | (Chan2 x x))) 109 | 110 | (defalias 111 | ^{:forms '[(ReadOnlyChan t)]} 112 | ReadOnlyChan 113 | "A core.async channel that statically disallows writes." 114 | (t/TFn [[r :variance :covariant]] 115 | (Chan2 t/Nothing r))) 116 | 117 | (defalias 118 | ^{:forms '[(ReadOnlyPort t)]} 119 | ReadOnlyPort 120 | "A read-only port that can read type x" 121 | (t/TFn [[t :variance :covariant]] 122 | (Port2 t/Nothing t))) 123 | 124 | (defalias 125 | ^{:forms '[(WriteOnlyPort t)]} 126 | WriteOnlyPort 127 | "A write-only port that can write type p" 128 | (t/TFn [[p :variance :contravariant]] 129 | (Port2 p t/Nothing))) 130 | 131 | (defalias 132 | ^{:forms '[TimeoutChan]} 133 | TimeoutChan 134 | "A timeout channel" 135 | (Chan t/Any)) 136 | 137 | (defalias 138 | ^{:forms '[(Buffer2 t t)]} 139 | Buffer2 140 | "A buffer of that can write type w and read type t." 141 | (t/TFn [[w :variance :contravariant] 142 | [r :variance :covariant]] 143 | (t/I (impl/Buffer w r) 144 | clojure.lang.Counted))) 145 | 146 | (defalias 147 | ^{:forms '[(Buffer t)]} 148 | Buffer 149 | "A buffer of type x." 150 | (t/TFn [[x :variance :invariant]] 151 | (Buffer2 x x))) 152 | 153 | (defalias 154 | ^{:forms '[(UnblockingBuffer2 t t)]} 155 | UnblockingBuffer2 156 | "An unblocking buffer that can write type w and read type t." 157 | (t/TFn [[w :variance :contravariant] 158 | [r :variance :covariant]] 159 | (t/I (Buffer2 w r) 160 | impl/UnblockingBuffer))) 161 | 162 | (defalias 163 | ^{:forms '[(UnblockingBuffer t)]} 164 | UnblockingBuffer 165 | "An unblocking buffer of type x." 166 | (t/TFn [[x :variance :invariant]] 167 | (UnblockingBuffer2 x x))) 168 | 169 | ;;;;;;;;;;;;;;;;;;;; 170 | ;; Var annotations 171 | 172 | (ann ^:no-check clojure.core.async/buffer (t/All [w r] [t/Int :-> (Buffer2 w r)])) 173 | (ann ^:no-check clojure.core.async/dropping-buffer (t/All [w r] [t/Int :-> (Buffer w r)])) 174 | (ann ^:no-check clojure.core.async/sliding-buffer (t/All [w r] [t/Int :-> (Buffer w r)])) 175 | 176 | (ann ^:no-check clojure.core.async/thread-call (t/All [x] [[:-> x] :-> (Chan x)])) 177 | 178 | (ann ^:no-check clojure.core.async/pipe 179 | (t/All [t] 180 | (t/IFn 181 | [(Chan t) (Chan t) :-> (Chan t)] 182 | [(Chan t) (Chan t) t/Any :-> (Chan t)]))) 183 | 184 | (ann ^:no-check clojure.core.async/timeout [t/Int :-> TimeoutChan]) 185 | 186 | ; TODO buffer must be supplied when xform is 187 | (ann ^:no-check clojure.core.async/chan 188 | (t/All [p t] 189 | (t/IFn [:-> (Chan2 p t)] 190 | [(t/U (Buffer2 p t) t/Int nil) :-> (Chan2 p t)] 191 | [(t/U (Buffer2 p t) t/Int nil) 192 | ; xform 193 | (t/U nil 194 | [[(Buffer2 p t) p :-> (Buffer2 p t)] 195 | :-> 196 | [(Buffer2 p t) p :-> (Buffer2 p t)]]) 197 | :-> (Chan2 p t)] 198 | [(t/U (Buffer2 p t) t/Int nil) 199 | ; xform 200 | (t/U nil 201 | [[(Buffer2 p t) p :-> (Buffer2 p t)] 202 | :-> 203 | [(Buffer2 p t) p :-> (Buffer2 p t)]]) 204 | ; ex-handler 205 | (t/U nil 206 | [Throwable :-> (t/U nil p)]) 207 | :-> (Chan2 p t)]))) 208 | 209 | (ann ^:no-check clojure.core.async.impl.ioc-macros/aget-object [AtomicReferenceArray t/Int :-> t/Any]) 210 | (ann ^:no-check clojure.core.async.impl.ioc-macros/aset-object [AtomicReferenceArray t/Any :-> nil]) 211 | (ann ^:no-check clojure.core.async.impl.ioc-macros/run-state-machine [AtomicReferenceArray :-> t/Any]) 212 | 213 | ;FIXME what is 2nd arg? 214 | (ann ^:no-check clojure.core.async.impl.ioc-macros/put! (t/All [x] [t/Int t/Any (Chan x) x :-> t/Any])) 215 | (ann ^:no-check clojure.core.async.impl.ioc-macros/return-chan (t/All [x] [AtomicReferenceArray x :-> (Chan x)])) 216 | 217 | (ann ^:no-check clojure.core.async/ (t/U nil t)])) 218 | ; should this use Port's? 219 | (ann ^:no-check clojure.core.async/ (t/U nil t)])) 220 | (ann ^:no-check clojure.core.async/>!! (t/All [p] [(Port2 p t/Any) p :-> t/Any])) 221 | (ann ^:no-check clojure.core.async/>! (t/All [p t] [(Port2 p t) p :-> (Port2 p t)])) 222 | (t/ann-many 223 | (t/All [x d] 224 | (t/IFn [(t/Seqable (t/U (Port x) '[(Port x) x])) 225 | & :mandatory {:default d} 226 | :optional {:priority (t/U nil true)} 227 | :-> (t/U '[d ':default] '[(t/U nil x) (Port x)])] 228 | [(t/Seqable (t/U (Port x) '[(Port x) x])) 229 | & :optional {:priority (t/U nil true)} 230 | :-> '[(t/U nil x) (Port x)]])) 231 | ^:no-check clojure.core.async/alts!! 232 | ^:no-check clojure.core.async/alts!) 233 | 234 | (ann ^:no-check clojure.core.async/close! [impl/Channel :-> nil]) 235 | 236 | (ann ^:no-check clojure.core.async.impl.dispatch/run [[:-> (ReadOnlyChan t/Any)] :-> Executor]) 237 | ;(ann clojure.core.async.impl.ioc-macros/async-chan-wrapper kV 238 | 239 | (ann ^:no-check clojure.core.async/put! 240 | (t/All [p] 241 | (t/IFn [(Port2 p t/Any) p :-> t/Any] 242 | [(Port2 p t/Any) p [t/Any :-> t/Any] :-> t/Any] 243 | [(Port2 p t/Any) p [t/Any :-> t/Any] t/Any :-> t/Any]))) 244 | 245 | (ann ^:no-check clojure.core.async/map< 246 | (t/All [t o] 247 | [[t -> o] 248 | (Chan2 t/Nothing t) 249 | :-> 250 | (Chan o)])) 251 | 252 | (ann ^:no-check clojure.core.async/map> 253 | (t/All [p t] 254 | [[t -> p] 255 | (Chan2 p t) 256 | :-> 257 | (Chan2 p t)])) 258 | 259 | ;(ann ^:no-check clojure.core.async/filter> 260 | ; (t/All [t t'] 261 | ; (t/IFn 262 | ; [[t :-> t/Any :filters {:then (is t' 0)}] (Chan2 t/Nothing t) :-> (Chan t')] 263 | ; [[t :-> t/Any] (Chan2 t/Nothing t) :-> (Chan t)]))) 264 | ; 265 | ;(ann ^:no-check clojure.core.async/remove> 266 | ; (t/All [p t] 267 | ; (t/IFn 268 | ; [[t :-> t/Any :filters {:then (! p 0)}] (Chan2 p t) :-> (Chan2 p t)] 269 | ; [[t :-> t/Any] (Chan2 p t) :-> (Chan2 p t)]))) 270 | ; 271 | ;(ann ^:no-check clojure.core.async/filter< 272 | ; (t/All [p t] 273 | ; (t/IFn 274 | ; [[t :-> t/Any :filters {:then (is p 0)}] (Chan2 t/Nothing t) :-> (Chan2 p t)] 275 | ; [[t :-> t/Any] (Chan2 t/Nothing t) :-> (Chan2 t t)]))) 276 | 277 | (ann ^:no-check clojure.core.async/onto-chan 278 | (t/All [x] 279 | [(Chan x) 280 | (t/U nil (t/Seqable x)) 281 | :-> 282 | (Chan t/Any)])) 283 | 284 | (ann ^:no-check clojure.core.async/to-chan 285 | (t/All [x] 286 | [(t/U nil (t/Seqable x)) 287 | :-> (Chan x)])) 288 | 289 | ;(ann ^:no-check clojure.core.async/map 290 | ; (All [x] 291 | ; [[x :-> y] 292 | ; (t/U nil (t/Seqable (Chan x))))) 293 | 294 | 295 | ;;;;;;;;;;;;;;;;;;;; 296 | ;; Typed wrappers 297 | 298 | (t/tc-ignore 299 | (defn ^:skip-wiki maybe-annotation [args] 300 | (let [t? (#{:-} (first args)) 301 | t (when t? (second args)) 302 | args (if t? 303 | (drop 2 args) 304 | args)] 305 | [t? t args])) 306 | ) 307 | 308 | (defmacro go 309 | "Like go but with optional annotations. Channel annotation defaults to Any. 310 | 311 | eg. 312 | (let [c (chan :- Str)] 313 | ;; same as (go :- t/Any ...) 314 | (go (a/>! c \"hello\")) 315 | (assert (= \"hello\" (a/ (f#) 336 | (ioc/aset-all! ioc/USER-START-IDX c# 337 | ioc/BINDINGS-IDX captured-bindings#))] 338 | (ioc/run-state-machine-wrapped state#))))) 339 | c#))) 340 | 341 | (defmacro go-loop 342 | "Like (go (t/loop ...))" 343 | [& body] 344 | (let [[t? t body] (maybe-annotation body)] 345 | (if t? 346 | `(go :- ~t (t/loop ~@body)) 347 | `(go (t/loop ~@body))))) 348 | 349 | (comment 350 | (t/cf 351 | (let [c (chan )] 352 | (go (a/>! c "hello")) 353 | (prn (a/!! c1 "hi") 363 | (a/>!! c2 "there"))) 364 | 365 | (t/cf 366 | (let [c1 (chan) 367 | c2 (chan :- t/Str)] 368 | (go (while true 369 | (let [[v ch] (a/alts! [c1 c2])] 370 | (println "Read" v "from" ch)))) 371 | (go (a/>! c1 "hi")) 372 | (go (a/>! c2 "there")))) 373 | 374 | ) 375 | 376 | (defmacro chan 377 | "Like chan but with optional type annotations. 378 | 379 | (chan :- t ...) creates a buffer that can read and write type t. 380 | Subsequent arguments are passed directly to clojure.core.async/chan. 381 | 382 | Note: 383 | (chan :- t ...) is the same as ((inst async/chan t) ...)" 384 | [& args] 385 | (let [[t? t args] (maybe-annotation args)] 386 | (if t? 387 | `((inst async/chan ~t ~t) ~@args) 388 | `(async/chan ~@args)))) 389 | 390 | (defmacro buffer 391 | "Like buffer but with optional type annotations. 392 | 393 | (buffer :- t ...) creates a buffer that can read and write type t. 394 | Subsequent arguments are passed directly to clojure.core.async/buffer. 395 | 396 | Note: (buffer :- t ...) is the same as ((inst buffer t) ...)" 397 | [& args] 398 | (let [[t? t args] (maybe-annotation args)] 399 | (if t? 400 | `((inst async/buffer ~t ~t) ~@args) 401 | `(async/buffer ~@args)))) 402 | 403 | (defmacro sliding-buffer 404 | "Like sliding-buffer but with optional type annotations. 405 | 406 | (sliding-buffer :- t ...) creates a sliding buffer that can read and write type t. 407 | Subsequent arguments are passed directly to clojure.core.async/sliding-buffer. 408 | 409 | Note: (sliding-buffer :- t ...) is the same as ((inst sliding-buffer t t) ...)" 410 | [& args] 411 | (let [[t? t args] (maybe-annotation args)] 412 | (if t? 413 | `((inst async/sliding-buffer ~t ~t) ~@args) 414 | `(async/sliding-buffer ~@args)))) 415 | 416 | 417 | (defmacro dropping-buffer 418 | "Like dropping-buffer but with optional type annotations. 419 | 420 | (dropping-buffer :- t ...) creates a dropping buffer that can read and write type t. 421 | Subsequent arguments are passed directly to clojure.core.async/dropping-buffer. 422 | 423 | Note: (dropping-buffer :- t ...) is the same as ((inst dropping-buffer t) ...)" 424 | [& args] 425 | (let [[t? t args] (maybe-annotation args)] 426 | (if t? 427 | `((inst async/dropping-buffer ~t ~t) ~@args) 428 | `(async/dropping-buffer ~@args)))) 429 | 430 | 431 | ;;;;;;;;;;;;;;;;;;;;;;;;;;; 432 | ;; Deprecated 433 | 434 | (defmacro chan> 435 | "DEPRECATED: use chan" 436 | [t & args] 437 | (prn "DEPRECATED: chan>, use chan") 438 | `((inst async/chan ~t) ~@args)) 439 | 440 | (defmacro buffer> 441 | "DEPRECATED: use buffer" 442 | [t & args] 443 | (prn "DEPRECATED: buffer>, use buffer") 444 | `((inst async/buffer ~t) ~@args)) 445 | 446 | (defmacro sliding-buffer> 447 | "DEPRECATED: use sliding-buffer" 448 | [t & args] 449 | (prn "DEPRECATED: sliding-buffer>, use sliding-buffer") 450 | `((inst async/sliding-buffer ~t) ~@args)) 451 | 452 | (defmacro dropping-buffer> 453 | "DEPRECATED: use dropping-buffer" 454 | [t & args] 455 | (prn "DEPRECATED: dropping-buffer>, use dropping-buffer") 456 | `((inst async/dropping-buffer ~t) ~@args)) 457 | 458 | (defmacro go> 459 | "DEPRECATED: use go" 460 | [& body] 461 | (prn "DEPRECATED: go>, use go") 462 | `(go ~@body)) 463 | -------------------------------------------------------------------------------- /src/main/clojure/clojure/core/typed/type_contract.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Ambrose Bonnaire-Sergeant, Rich Hickey & contributors. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | ;flat contracts only 10 | (ns ^:no-doc ^:skip-wiki clojure.core.typed.type-contract 11 | (:require [clojure.core.typed.parse-ast :as ast] 12 | [clojure.core.typed.errors :as err] 13 | [clojure.core.typed.current-impl :as impl] 14 | [clojure.core.typed.ast-ops :as ops] 15 | [clojure.core.typed.contract :as con] 16 | ;used in contracts 17 | [clojure.set :as set])) 18 | 19 | (defn keyword-singleton? [{:keys [op val]}] 20 | (when ('#{:singleton} op) 21 | (keyword? val))) 22 | 23 | (def ^:dynamic *inside-rec* #{}) 24 | 25 | (defn ast->pred 26 | "Returns syntax representing a runtime predicate on the 27 | given type ast." 28 | [t] 29 | (letfn [(gen-inner [{:keys [op] :as t} arg] 30 | (case op 31 | (:F) (err/int-error "Cannot generate predicate for free variable") 32 | (:Poly) (err/int-error "Cannot generate predicate for polymorphic type") 33 | (:PolyDots) (err/int-error "Cannot generate predicate for dotted polymorphic type") 34 | (:Fn) (err/int-error "Cannot generate predicate for function type") 35 | (:TApp) (let [{:keys [rator rands]} t] 36 | (cond 37 | ;needs resolving 38 | (#{:Name} (:op rator)) 39 | (gen-inner (update-in t [:rator] ops/resolve-Name) arg) 40 | ;polymorphic class 41 | (#{:Class} (:op rator)) 42 | (let [{:keys [args pred] :as rcls} (get (impl/rclass-env) (:name rator)) 43 | _ (when-not rcls 44 | (err/int-error (str "Class does not take arguments: " 45 | (:name rator)))) 46 | _ (when-not (args (count rands)) 47 | (err/int-error (str "Wrong number of arguments to " 48 | (:name rator) ", expected " args 49 | " actual " (count rands)))) 50 | rands-args (repeatedly (count rands) gensym) 51 | rands-p (mapv (fn [ast gsym] 52 | `(fn [~gsym] ~(gen-inner ast gsym))) 53 | rands rands-args)] 54 | `(and (instance? ~(:name rator) ~arg) 55 | ~(apply pred arg rands-p))) 56 | ;substitute 57 | (#{:TFn} (:op rator)) 58 | (gen-inner (ops/instantiate-TFn rator rands) arg) 59 | :else 60 | (err/int-error (str "Don't know how to apply type: " (:form t))))) 61 | (:Class) `(instance? ~(:name t) ~arg) 62 | (:Name) 63 | (impl/impl-case 64 | :clojure (gen-inner (ops/resolve-Name t) arg) 65 | :cljs (err/int-error (str "TODO CLJS Name"))) 66 | ; (cond 67 | ; (empty? (:poly? t)) `(instance? ~(:the-class t) ~arg) 68 | ; :else (err/int-error (str "Cannot generate predicate for polymorphic Class"))) 69 | (:Any) `true 70 | ;TODO special case for union of HMap, and unions of constants 71 | (:U) `(or ~@(mapv gen-inner (:types t) (repeat arg))) 72 | (:I) `(and ~@(mapv gen-inner (:types t) (repeat arg))) 73 | (:HVec) `(and (vector? ~arg) 74 | ~(cond 75 | (:rest t) 76 | `(<= ~(count (:types t)) (count ~arg)) 77 | (:drest t) 78 | (err/int-error (str "Cannot generate predicate for dotted HVec")) 79 | :else 80 | `(== ~(count (:types t)) (count ~arg))) 81 | ~@(doall 82 | (map-indexed 83 | (fn [i t*] 84 | (let [vlocal (gensym "vlocal")] 85 | `(let [~vlocal (nth ~arg ~i)] 86 | ~(gen-inner t* vlocal)))) 87 | (:types t))) 88 | ~@(when (:rest t) 89 | (let [nfixed (count (:types t))] 90 | [`(let [rstvec# (subvec ~arg ~nfixed)] 91 | (every? ~(let [vlocal (gensym "vlocal")] 92 | `(fn [~vlocal] 93 | ~(gen-inner (:rest t) vlocal))) 94 | rstvec#))]))) 95 | (:CountRange) (let [cnt (gensym "cnt")] 96 | `(and (or (nil? ~arg) 97 | (coll? ~arg)) 98 | (let [~cnt (count ~arg)] 99 | (<= ~@(let [{:keys [lower upper]} t] 100 | (concat [lower cnt] 101 | (when upper 102 | [upper]))))))) 103 | (:singleton) (let [v (:val t)] 104 | (cond 105 | (nil? v) `(nil? ~arg) 106 | (symbol? v) `(= '~v ~arg) 107 | (keyword? v) `(identical? '~v ~arg) 108 | ((some-fn true? false?) v) `(identical? '~v ~arg) 109 | (number? v) `(when (number? ~arg) 110 | ; I think = models the type system's behaviour better than == 111 | (= '~v ~arg)) 112 | :else (err/int-error 113 | (str "Cannot generate predicate for value type: " v)))) 114 | (:HMap) (let [mandatory (apply hash-map (:mandatory t)) 115 | optional (apply hash-map (:optional t)) 116 | absent-keys (:absent-keys t) 117 | valgen (fn [tmap] 118 | (zipmap (map :val (keys tmap)) 119 | (mapv (fn [tsyn gi] 120 | `(fn [~gi] 121 | ~(gen-inner tsyn gi))) 122 | (vals tmap) 123 | (repeatedly (count tmap) gensym))))] 124 | `((impl/hmap-c? :mandatory ~(valgen mandatory) 125 | :optional ~(valgen optional) 126 | :absent-keys ~(set (map :val absent-keys)) 127 | :complete? ~(:complete? t)) 128 | ~arg)) 129 | (:Rec) (cond 130 | ;we're already inside this rec 131 | (contains? *inside-rec* (:unwrap-id t)) 132 | (let [{:keys [unwrap-id]} t] 133 | `(~unwrap-id ~arg)) 134 | 135 | :else 136 | (let [unwrap-id (gensym 'Rec-id) 137 | body (ops/unwrap-rec t unwrap-id) 138 | garg (gensym 'garg)] 139 | (binding [*inside-rec* (conj *inside-rec* unwrap-id)] 140 | `((fn ~unwrap-id 141 | [~garg] 142 | ~(gen-inner body garg)) 143 | ~arg)))) 144 | (err/int-error (str op " not supported in type->pred: " (:form t)))))] 145 | (let [arg (gensym "arg")] 146 | `(fn [~arg] 147 | (boolean 148 | ~(gen-inner t arg)))))) 149 | 150 | (defn ast->contract 151 | "Returns syntax representing a runtime predicate on the 152 | given type ast." 153 | [t] 154 | (letfn [(gen-inner [{:keys [op] :as t} arg] 155 | (case op 156 | (:F) (err/int-error "Cannot generate predicate for free variable") 157 | (:Poly) (err/int-error "Cannot generate predicate for polymorphic type") 158 | (:PolyDots) (err/int-error "Cannot generate predicate for dotted polymorphic type") 159 | (:Fn) (cond 160 | (== 1 (count (:arities t))) 161 | (let [{:keys [dom rng filter object flow rest drest] :as method} 162 | (first (:arities t))] 163 | (if (or rest drest filter object flow) 164 | (err/int-error "Cannot generate predicate for this function type") 165 | `(con/ifn-c ~(mapv #(gen-inner % arg) dom) 166 | ~(gen-inner rng arg)))) 167 | :else (err/int-error "Cannot generate predicate for function type")) 168 | (:TApp) (let [{:keys [rator rands]} t] 169 | (cond 170 | ;needs resolving 171 | (#{:Name} (:op rator)) 172 | (gen-inner (update-in t [:rator] ops/resolve-Name) arg) 173 | ;polymorphic class 174 | ;(#{:Class} (:op rator)) 175 | ; (let [{:keys [args pred] :as rcls} (get (impl/rclass-env) (:name rator)) 176 | ; _ (when-not rcls 177 | ; (err/int-error (str "Class does not take arguments: " 178 | ; (:name rator)))) 179 | ; _ (when-not (args (count rands)) 180 | ; (err/int-error (str "Wrong number of arguments to " 181 | ; (:name rator) ", expected " args 182 | ; " actual " (count rands)))) 183 | ; rands-args (repeatedly (count rands) gensym) 184 | ; rands-p (mapv (fn [ast gsym] 185 | ; `(fn [~gsym] ~(gen-inner ast gsym))) 186 | ; rands rands-args)] 187 | ; `(and (instance? ~(:name rator) ~arg) 188 | ; ~(apply pred arg rands-p))) 189 | ;substitute 190 | (#{:TFn} (:op rator)) 191 | (gen-inner (ops/instantiate-TFn rator rands) arg) 192 | :else 193 | (err/int-error (str "Don't know how to apply type: " (:form t))))) 194 | (:Class) `(con/instance-c 195 | (Class/forName ~(str (:name t)))) 196 | (:Name) 197 | (impl/impl-case 198 | :clojure (gen-inner (ops/resolve-Name t) arg) 199 | :cljs (err/int-error (str "TODO CLJS Name"))) 200 | ; (cond 201 | ; (empty? (:poly? t)) `(instance? ~(:the-class t) ~arg) 202 | ; :else (err/int-error (str "Cannot generate predicate for polymorphic Class"))) 203 | (:Any) `con/any-c 204 | ;TODO special case for union of HMap, and unions of constants 205 | (:U) `(con/or-c 206 | ;; TODO flatten unions, ensuring Names are resolved 207 | ~@(mapv #(gen-inner % arg) (:types t))) 208 | (:I) `(con/and-c 209 | ~@(mapv #(gen-inner % arg) (:types t))) 210 | ;(:HVec) `(and (vector? ~arg) 211 | ; ~(cond 212 | ; (:rest t) 213 | ; `(<= ~(count (:types t)) (count ~arg)) 214 | ; (:drest t) 215 | ; (err/int-error (str "Cannot generate predicate for dotted HVec")) 216 | ; :else 217 | ; `(== ~(count (:types t)) (count ~arg))) 218 | ; ~@(doall 219 | ; (map-indexed 220 | ; (fn [i t*] 221 | ; (let [vlocal (gensym "vlocal")] 222 | ; `(let [~vlocal (nth ~arg ~i)] 223 | ; ~(gen-inner t* vlocal)))) 224 | ; (:types t))) 225 | ; ~@(when (:rest t) 226 | ; (let [nfixed (count (:types t))] 227 | ; [`(let [rstvec# (subvec ~arg ~nfixed)] 228 | ; (every? ~(let [vlocal (gensym "vlocal")] 229 | ; `(fn [~vlocal] 230 | ; ~(gen-inner (:rest t) vlocal))) 231 | ; rstvec#))]))) 232 | (:CountRange) `(con/count-range-c ~(:lower t) ~(:upper t)) 233 | (:singleton) (let [v (:val t)] 234 | (cond 235 | (nil? v) `con/nil-c 236 | (symbol? v) `(con/equiv-c ~v) 237 | (keyword? v) `(con/identical-c ~v) 238 | ((some-fn true? false?) v) `(con/identical-c ~v) 239 | (number? v) ; I think = models the type system's behaviour better than == 240 | `(con/equiv-c ~v) 241 | 242 | :else (err/int-error 243 | (str "Cannot generate predicate for value type: " v)))) 244 | 245 | (:HMap) (let [mandatory (apply hash-map (:mandatory t)) 246 | optional (apply hash-map (:optional t)) 247 | absent-keys (:absent-keys t) 248 | congen (fn [tmap] 249 | (zipmap (map :val (keys tmap)) 250 | (map #(gen-inner % arg) (vals tmap))))] 251 | `(con/hmap-c :mandatory ~(congen mandatory) 252 | :optional ~(congen optional) 253 | :absent-keys ~(set (map :val absent-keys)) 254 | :complete? ~(:complete? t))) 255 | 256 | ;(:Rec) (cond 257 | ; ;we're already inside this rec 258 | ; (contains? *inside-rec* (:unwrap-id t)) 259 | ; (let [{:keys [unwrap-id]} t] 260 | ; `(~unwrap-id ~arg)) 261 | ; 262 | ; :else 263 | ; (let [unwrap-id (gensym 'Rec-id) 264 | ; body (ops/unwrap-rec t unwrap-id) 265 | ; garg (gensym 'garg)] 266 | ; (binding [*inside-rec* (conj *inside-rec* unwrap-id)] 267 | ; `((fn ~unwrap-id 268 | ; [~garg] 269 | ; ~(gen-inner body garg)) 270 | ; ~arg)))) 271 | (err/int-error (str op " not supported in type->pred: " (:form t)))))] 272 | (gen-inner t nil))) 273 | 274 | (defn type-syntax->pred [t] 275 | (impl/with-impl impl/clojure 276 | (-> (ast/parse t) 277 | ast->pred))) 278 | 279 | (defn type-syntax->contract [t] 280 | (impl/with-impl impl/clojure 281 | (-> (ast/parse t) 282 | ast->contract))) 283 | 284 | (comment 285 | (type-syntax->pred 'Any) 286 | (type-syntax->pred 'Nothing) 287 | (type-syntax->pred '(U Number Boolean)) 288 | 289 | (con/contract (type-syntax->contract 'nil) 1) 290 | 291 | (clojure.pprint/pprint (type-syntax->pred '(HMap :optional {:c Number}))) 292 | (clojure.pprint/pprint (type-syntax->pred '(HMap :mandatory {:c Number}))) 293 | (clojure.pprint/pprint (type-syntax->pred ''[Number])) 294 | (clojure.pprint/pprint (type-syntax->pred '(Rec [x] (U '[x] Number)))) 295 | (clojure.pprint/pprint (type-syntax->pred '(clojure.core.typed/Option Number))) 296 | 297 | (walk (type-syntax->pred '(HMap :optional {:c Number})) 298 | (fn [e] (prn 'pre (:op e))) 299 | (fn [e] (prn 'post (:op e)))) 300 | 301 | (def ast (ast/parse-clj '(HMap :optional {:c Number}))) 302 | 303 | (:children ast) 304 | 305 | (ops/walk ast 306 | (fn f [e] (prn 'pre (:op e))) 307 | (fn [e] (prn 'post (:op e)))) 308 | (ops/unwrap-rec (ast/parse-clj '(Rec [x] (U '[x] Number))) 'abc) 309 | ) 310 | -------------------------------------------------------------------------------- /src/main/clojure/clojure/core/typed/internal.cljc: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Ambrose Bonnaire-Sergeant, Rich Hickey & contributors. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns ^:no-doc ^:skip-wiki clojure.core.typed.internal 10 | (:require [clojure.set :as set] 11 | [clojure.core.typed.contract-utils :as con])) 12 | 13 | (defn take-when 14 | "When pred is true of the head of seq, return [head tail]. Otherwise 15 | [nil seq]. Used as a helper for parsing optinal typed elements out 16 | of sequences. Say docstrings out of argument seqs." 17 | [pred seq] 18 | (if (pred (first seq)) 19 | ((juxt first rest) seq) 20 | [nil seq])) 21 | 22 | (defn parse-keyword-flat-map [forms] 23 | (loop [opts [] 24 | forms forms] 25 | (cond 26 | (keyword? (first forms)) 27 | (let [[kv forms] (split-at 2 forms)] 28 | (assert (#{2} (count kv)) 29 | (str "Missing keyword argument to: " (pr-str (first kv)))) 30 | (recur (apply conj opts kv) 31 | forms)) 32 | :else [opts forms]))) 33 | 34 | (defn parse-keyword-map [forms] 35 | (let [[flatopts forms] (parse-keyword-flat-map forms)] 36 | [(apply hash-map flatopts) forms])) 37 | 38 | (defn parse-fn* 39 | "(fn name? [[param :- type]* & [param :- type *]?] :- type? exprs*) 40 | (fn name? ([[param :- type]* & [param :- type *]?] :- type? exprs*)+)" 41 | [[_fn_ & forms :as form]] 42 | {:pre [(symbol? _fn_) 43 | #_(= "fn" (name _fn_))]} 44 | (let [[{poly :forall :as opts} forms] (parse-keyword-map forms) 45 | [name forms] (take-when symbol? forms) 46 | _ (assert (not (keyword? (first forms)))) 47 | single-arity-syntax? (vector? (first forms)) 48 | methods (if single-arity-syntax? 49 | (list forms) 50 | forms) 51 | parsed-methods (for [method methods] 52 | (merge-with merge 53 | (let [ann-params (first method)] 54 | (assert (vector? ann-params)) 55 | {:ann-params ann-params 56 | :original-method (vary-meta method #(merge (meta form) 57 | (meta ann-params) 58 | %))}) 59 | (loop [ann-params (first method) 60 | pvec (empty (first method)) ; an empty param vector with same metadata 61 | ann-info []] 62 | (cond 63 | (empty? ann-params) 64 | (let [[dom [amp rst]] (split-with (complement #{'&}) ann-info)] 65 | {:pvec pvec 66 | :ann (merge 67 | {:dom dom} 68 | (when (:rest rst) 69 | {:rest (:rest rst)}) 70 | (when (:drest rst) 71 | {:drest (:drest rst)}))}) 72 | 73 | ;rest param 74 | (#{'&} (first ann-params)) 75 | (let [[amp & ann-params] ann-params] 76 | (if (#{:-} (second ann-params)) 77 | (let [[p colon & rst-params] ann-params] 78 | (cond 79 | (#{'*} (second rst-params)) 80 | (let [[t star & after-rst] rst-params] 81 | (recur after-rst 82 | (conj pvec amp p) 83 | (conj ann-info amp {:rest {:type t}}))) 84 | 85 | (#{'...} (second rst-params)) 86 | (let [[pretype dots bound & after-rst] rst-params] 87 | (recur after-rst 88 | (conj pvec amp p) 89 | (conj ann-info amp {:drest {:pretype {:type pretype} 90 | :bound bound}}))) 91 | 92 | :else 93 | (throw (ex-info "Rest annotation must be followed with * or ..." {:form method})))) 94 | (let [[p & after-rst] ann-params] 95 | (recur after-rst 96 | (conj pvec amp p) 97 | (conj ann-info amp {:rest {:type 'clojure.core.typed/Any 98 | :default true}}))))) 99 | 100 | ;fixed param 101 | :else 102 | (if (#{:-} (second ann-params)) 103 | (let [[p colon t & rest-params] ann-params] 104 | (recur rest-params 105 | (conj pvec p) 106 | (conj ann-info {:type t}))) 107 | (let [[p & rest-params] ann-params] 108 | (recur rest-params 109 | (conj pvec p) 110 | (conj ann-info {:type 'clojure.core.typed/Any 111 | :default true})))))) 112 | (if (and (#{:-} (second method)) 113 | (<= 3 (count method))) 114 | (let [[param colon t & body] method] 115 | {:body body 116 | :ann {:rng {:type t}}}) 117 | (let [[param & body] method] 118 | {:body body 119 | :ann {:rng {:type 'clojure.core.typed/Any 120 | :default true}}})))) 121 | final-ann (mapv :ann parsed-methods)] 122 | #_(assert ((con/vec-c? 123 | (con/hmap-c? 124 | :dom (con/every-c? (con/hmap-c? :type (constantly true))) 125 | (con/optional :rest) (con/hmap-c? :type (constantly true)) 126 | :rng (some-fn (con/hmap-c? :default #{true}) 127 | (con/hmap-c? :type (constantly true))))) 128 | final-ann) 129 | final-ann) 130 | {:fn `(fn ~@(concat 131 | (when name 132 | [name]) 133 | (for [{:keys [body pvec]} parsed-methods] 134 | (apply list pvec body)))) 135 | :ann final-ann 136 | :poly poly 137 | :parsed-methods parsed-methods 138 | :name name 139 | :single-arity-syntax? single-arity-syntax?})) 140 | 141 | (defn parse-defn* [args] 142 | (let [[flatopt args] (parse-keyword-flat-map args) 143 | [name & args] args 144 | _ (assert (symbol? name) "defn name should be a symbol") 145 | [docstring args] (take-when string? args) 146 | [attr-map args] (take-when map? args)] 147 | {:name (vary-meta name merge 148 | {:arglists 149 | (list 'quote 150 | (if (vector? (first args)) ; arity = 1 151 | (list (first args)) 152 | (map first args)))} 153 | (when docstring {:doc docstring}) 154 | attr-map) 155 | :args (concat flatopt args)})) 156 | 157 | (defn parse-loop* 158 | [forms] 159 | (let [parsed-loop (merge 160 | (loop [ann-params (first forms) 161 | pvec [] 162 | ann-info []] 163 | (cond 164 | (empty? ann-params) 165 | {:pvec pvec 166 | :ann {:params ann-info}} 167 | 168 | :else 169 | (if (#{:-} (second ann-params)) 170 | (let [[p colon t init & rest-params] ann-params] 171 | (recur rest-params 172 | (conj pvec p init) 173 | (conj ann-info {:type t}))) 174 | (let [[p init & rest-params] ann-params] 175 | (recur rest-params 176 | (conj pvec p init) 177 | (conj ann-info {:type 'clojure.core.typed/Any 178 | :default true})))))) 179 | {:body (next forms)})] 180 | {:loop `(clojure.core/loop ~(:pvec parsed-loop) ~@(:body parsed-loop)) 181 | :ann (:ann parsed-loop)})) 182 | 183 | (defn binder-names [binder] 184 | {:post [(every? symbol? %)]} 185 | (map (fn [v] 186 | (if (vector? v) 187 | (first v) 188 | v)) 189 | binder)) 190 | 191 | (defn gen-ann-protocol [{:keys [name methods binder] :as dp-ann}] 192 | (let [tvars (set (binder-names binder)) 193 | this-type (if binder 194 | `(~name ~@(binder-names binder)) 195 | name)] 196 | `(clojure.core.typed/ann-protocol 197 | ~@(when binder 198 | [binder]) 199 | ~name 200 | ~@(mapcat (fn [{:keys [name arities poly]}] 201 | (let [localtvars (set (binder-names poly)) 202 | _ (assert (empty? (set/intersection localtvars 203 | tvars)) 204 | "Shadowing a protocol type variable in a method is disallowed") 205 | fn-type `(clojure.core.typed/IFn 206 | ~@(map (fn [{:keys [ptypes ret]}] 207 | (let [[provided-this & argts] ptypes 208 | ; if programmer provides the default 'this' type, use that, 209 | ; otherwise use the current protocol. 210 | actual-this (if (:default provided-this) 211 | this-type 212 | (:type provided-this))] 213 | `[~@(concat [actual-this] (map :type argts)) ~'-> ~(:type ret)])) 214 | arities))] 215 | [name (if poly 216 | `(clojure.core.typed/All ~poly ~fn-type) 217 | fn-type)])) 218 | methods)))) 219 | 220 | 221 | (defn parse-defprotocol* 222 | [forms] 223 | (let [[binder forms] (take-when vector? forms) 224 | [pname & typed-decl-methods] forms 225 | [pdoc typed-decl-methods] (take-when string? typed-decl-methods) 226 | parse-pvec (fn [pvec] ; parse parameter vectors 227 | {:pre [(vector? pvec)] 228 | :post [((con/hmap-c? :actual vector? 229 | :ptypes vector?) 230 | %)]} 231 | (loop [pvec pvec 232 | actual (empty pvec) ; empty vector with same metadata as pvec 233 | ptypes []] 234 | (assert (every? vector? [actual ptypes])) 235 | (cond 236 | (empty? pvec) {:ptypes ptypes :actual actual} 237 | :else (if (#{:-} (second pvec)) 238 | (let [_ (assert (#{3} (count (take 3 pvec))) 239 | "Missing type annotation after :-") 240 | [b colon t & rst] pvec] 241 | (recur rst 242 | (conj actual b) 243 | (conj ptypes {:type t}))) 244 | (let [_ (assert (seq pvec)) 245 | [b & rst] pvec] 246 | (recur rst 247 | (conj actual b) 248 | (conj ptypes {:type 'clojure.core.typed/Any 249 | :default true}))))))) 250 | actual-decl-methods (for [m typed-decl-methods] 251 | (let [[poly rst] (take-when vector? m) 252 | [name & dvecs] rst] 253 | (assert (symbol? name) (str "defprotocol method name must be a symbol: " pname)) 254 | (loop [dvecs dvecs 255 | arities []] 256 | (cond 257 | (or (empty? dvecs) 258 | (string? (first dvecs))) 259 | (merge {:poly poly 260 | :name name 261 | :arities arities} 262 | (when (string? (first dvecs)) 263 | {:doc (first dvecs)})) 264 | 265 | :else (if (#{:-} (second dvecs)) 266 | (let [_ (assert (#{3} (count (take 3 dvecs))) 267 | "Missing type annotation after :-") 268 | [v colon t & rst] dvecs 269 | {:keys [ptypes actual]} (parse-pvec v)] 270 | (recur rst 271 | (conj arities {:ret {:type t} 272 | :ptypes ptypes 273 | :actual actual}))) 274 | (let [_ (assert (seq dvecs)) 275 | [v & rst] dvecs 276 | {:keys [ptypes actual]} (parse-pvec v)] 277 | (recur rst 278 | (conj arities {:ret {:type 'clojure.core.typed/Any 279 | :default true} 280 | :ptypes ptypes 281 | :actual actual})))))))) 282 | ann {:binder binder 283 | :name pname 284 | :methods (map #(dissoc % :doc) actual-decl-methods)}] 285 | {:defprotocol `(clojure.core/defprotocol 286 | ~pname 287 | ~@(when pdoc [pdoc]) 288 | ~@(map (fn [{:keys [name arities doc]}] 289 | `(~name ~@(concat ; prefer left-most arities if grouped duplicates 290 | (reduce 291 | (fn [ret current] 292 | (if (= (count current) (count (last ret))) 293 | ret 294 | (conj ret current))) 295 | [] 296 | (map :actual arities)) 297 | (when doc 298 | [doc])))) 299 | actual-decl-methods)) 300 | :ann-protocol (gen-ann-protocol ann)})) 301 | 302 | (defn parse-let* 303 | [[bvec & forms]] 304 | (let [actual-bvec (loop [bvec bvec 305 | actual-bvec (empty bvec)] ; empty vector with same metadata as bvec 306 | (assert (vector? actual-bvec)) 307 | (cond 308 | (empty? bvec) actual-bvec 309 | :else (if (#{:-} (second bvec)) 310 | (let [_ (assert (#{4} (count (take 4 bvec))) 311 | "Incorrect forms following :-") 312 | [v colon t init & rst] bvec] 313 | (recur rst 314 | (conj actual-bvec v `(clojure.core.typed/ann-form ~init ~t)))) 315 | (let [_ (assert (#{2} (count (take 2 bvec))) 316 | "No init found for local binding") 317 | [v init & rst] bvec] 318 | (recur rst 319 | (conj actual-bvec v init))))))] 320 | {:let `(clojure.core/let ~actual-bvec ~@forms)})) 321 | -------------------------------------------------------------------------------- /src/main/clojure/clojure/core/typed/contract.cljc: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Ambrose Bonnaire-Sergeant, Rich Hickey & contributors. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns clojure.core.typed.contract 10 | "A contract system a la racket/contract. 11 | 12 | Main entry point is the `contract` macro." 13 | #?(:cljs (:require-macros [clojure.core.typed.contract :refer [contract instance-c]])) 14 | (:require [clojure.set :as set])) 15 | 16 | ;; A contract, the first argument to the `contract` macro 17 | ;; - name : Symbol 18 | ;; a name for the contract, eg. 'int-c 19 | ;; - first-order : [Any -> Any] 20 | ;; first order (flat) predicate for the current contract. 21 | ;; Must return true for all inputs that passes the projection, but 22 | ;; can also return true for values that fail the contract. 23 | ;; eg. ifn? for [Int -> Int] 24 | ;; - projection : [Blame -> [Any -> Any]] 25 | ;; A curried function that does the actual contract checking. 26 | ;; Takes a Blame object and a value, and returns a new value that 27 | ;; adheres to the current Contract object, otherwise blames Blame. 28 | ;; eg. for the int-c contract: 29 | ;; (fn [b] 30 | ;; (fn [x] 31 | ;; (if (integer? x) 32 | ;; x 33 | ;; (throw-blame b)))) 34 | ;; - flat? : Boolean 35 | ;; True if this is a flat contract, ie. first-order returns true 36 | ;; for exactly the same values that pass the projection function. 37 | (defrecord Contract [name first-order projection flat?]) 38 | 39 | ;; A Blame object 40 | ;; - positive : (U String Symbol) 41 | ;; Positive blame party. 42 | ;; eg. "clojure.core.typed" 43 | ;; - negative : (U String Symbol) 44 | ;; Negative blame party. 45 | ;; eg. "Not clojure.core.typed" 46 | ;; - name (unused) 47 | ;; - contract (unused) 48 | ;; - file : (U nil String) 49 | ;; File name where contract occurs. 50 | ;; - line, column : (U Integer nil) 51 | ;; Line/column positions to blame. 52 | (defrecord Blame [positive negative name contract file line column]) 53 | 54 | #_ (ann throw-blame [Blame -> Nothing]) 55 | (defn throw-blame 56 | "Throw a blame object 57 | 58 | [Blame -> Nothing]" 59 | [{:keys [message positive negative file line column] :as b}] 60 | (throw 61 | (ex-info 62 | (str message "\n" 63 | "Positive blame: " positive "\n" 64 | "Negative blame: " negative "\n" 65 | "File: " file "\n" 66 | "Line: " line "\n" 67 | "Column: " column "\n") 68 | {:blame b}))) 69 | 70 | #_(ann-many [& :optional {:name (U Symbol String) 71 | :first-order [Any :-> Any] 72 | :projection [Blame :-> [Any :-> Any]] 73 | :flat? Boolean} 74 | :-> Contract] 75 | make-contract 76 | make-flat-contract) 77 | (defn make-contract 78 | "Make a new contract. 79 | 80 | Keyword arguments: (see Contract datatype for more details) 81 | - :name Name of the contract, (U Symbol String) 82 | - :first-order First-order predicate for this contract, [Any -> Any] 83 | - :projection Curried function taking blame and the value to check, 84 | and returns a new checked value, or throws blame. 85 | [Blame -> [Any -> Any]] 86 | - :flat? True if this is a flat contract, Boolean" 87 | [& {:keys [name first-order projection flat?] 88 | :or {flat? false}}] 89 | (let [name (or name 90 | 'anonymous-contract) 91 | first-order (or first-order 92 | (fn [x] true)) 93 | projection (or projection 94 | (fn [b] 95 | (fn [x] 96 | (if (first-order x) 97 | x 98 | (throw-blame b)))))] 99 | (map->Contract 100 | {:name name 101 | :first-order first-order 102 | :projection projection 103 | :flat? flat?}))) 104 | 105 | 106 | (defn make-flat-contract 107 | "Calls `make-contract` but also passes `:flat? true` as the first arguments." 108 | [& args] 109 | (apply make-contract :flat? true args)) 110 | 111 | #_(ann make-blame [& :optional {:message String 112 | :positive (U String Symbol) 113 | :negative (U String Symbol) 114 | :file (U Str nil) 115 | :line (U Int nil) 116 | :column (U Int nil)} 117 | :-> Blame]) 118 | (defn make-blame 119 | "Make a new blame object. 120 | 121 | Keyword arguments: 122 | - :message A string message, String 123 | - :positive Positive blame party, (U String Symbol) 124 | - :negative Negative blame party, (U String Symbol) 125 | - :file File that contains contract, (U Str nil) 126 | - :line Line where contract occurs, (U Int nil) 127 | - :column Column where contract occurs, (U Int nil)" 128 | [& {:as bls}] 129 | (map->Blame bls)) 130 | 131 | #?(:clj 132 | (defmacro contract 133 | "Check a contract against a value, with an optional Blame object. 134 | 135 | (IFn [Contract Any -> Any] 136 | [Contract Any Blame -> Any])" 137 | ([c x] `(contract ~c ~x nil)) 138 | ([c x b] 139 | `(((:projection ~c) 140 | (or ~b 141 | (make-blame :positive ~(str (ns-name *ns*)) 142 | :negative ~(str "Not " (ns-name *ns*)) 143 | :file ~*file* 144 | :line ~(or (-> &form meta :line) 145 | @Compiler/LINE) 146 | :column ~(or (-> &form meta :column) 147 | @Compiler/COLUMN)))) 148 | ~x)))) 149 | 150 | #_(ann swap-blame [Blame :-> Blame]) 151 | (defn swap-blame 152 | "Swap a blame object's blame parties. 153 | 154 | [Blame -> Blame]" 155 | [x] 156 | {:pre [(instance? Blame x)] 157 | :post [(instance? Blame %)]} 158 | (-> x 159 | (assoc :positive (:negative x)) 160 | (assoc :negative (:positive x)))) 161 | 162 | #_(ann int-c Contract) 163 | (def int-c 164 | "Flat contract for values that pass `integer?`." 165 | (make-flat-contract :name 'int-c :first-order integer?)) 166 | 167 | ;; macro to allow instance? specialisation 168 | #?(:clj 169 | (defmacro instance-c 170 | "Flat contracts for instance? checks on Class's." 171 | [c] 172 | `(make-flat-contract :name (str ~c) 173 | :first-order #(instance? ~c %)))) 174 | 175 | #_(ann Object-c Contract) 176 | (def Object-c (instance-c Object)) 177 | 178 | #_(ann flat-val-c [Sym [Any -> Any] :-> Contract]) 179 | (defn flat-val-c 180 | "Contract generation for flat predicates." 181 | [name pred] 182 | (make-flat-contract :name name :first-order pred)) 183 | 184 | #_(ann-many Contract 185 | nil-c 186 | true-c 187 | false-c) 188 | (def nil-c 189 | "Contract that checks for `nil`." 190 | (flat-val-c 'nil-c nil?)) 191 | (def true-c 192 | "Contract that checks for `true`." 193 | (flat-val-c 'true-c true?)) 194 | (def false-c 195 | "Contract that checks for `false`." 196 | (flat-val-c 'false-c false?)) 197 | 198 | #_(ann any-c Contract) 199 | (def any-c 200 | "Contract that allows any value." 201 | (make-flat-contract :name any-c)) 202 | 203 | #_(ann count-range-c 204 | (IFn [Int -> Contract] 205 | [Int (U nil Int) -> Contract])) 206 | (defn count-range-c 207 | "Returns a flat contract that allows values with `count` 208 | greater-or-equal-to lower, and less-or-equal-to upper. 209 | Upper can be nil for positive infinity. 210 | 211 | (IFn [Int -> Contract] 212 | [Int (U nil Int) -> Contract]) 213 | 214 | eg. (count-range-c 0 10) 215 | (count-range-c 0 nil)" 216 | ([lower] (count-range-c lower nil)) 217 | ([lower upper] 218 | (make-flat-contract :name 'count-range-c 219 | :first-order (fn [x] 220 | (and (or (nil? x) 221 | (coll? x)) 222 | (if upper 223 | (<= lower (count x) upper) 224 | (<= lower (count x)))))))) 225 | 226 | #_(ann equiv-c [Any -> Contract]) 227 | (defn equiv-c 228 | "Returns a flat contract that returns true if a value is `=` 229 | to y. 230 | 231 | [Any -> Contract]" 232 | [y] 233 | (make-flat-contract :name 'equiv-c 234 | :first-order (fn [x] 235 | (= x y)))) 236 | 237 | #_(ann identical-c [Any -> Contract]) 238 | (defn identical-c 239 | "Returns a flat contract that returns true if a value is `identical?` 240 | to y. 241 | 242 | [Any -> Contract]" 243 | [y] 244 | (make-flat-contract :name 'identical-c 245 | :first-order (fn [x] 246 | (identical? x y)))) 247 | 248 | 249 | #_(ann ifn-c [(Vec Contract) Contract -> Contract]) 250 | (defn ifn-c 251 | "Returns a function contract that checks a function has 252 | fixed domain that passes contracts `cs` and return value 253 | that passes contact `c2`. 254 | 255 | [(Vec Contract) Contract -> Contract] 256 | 257 | eg. (ifn-c [int-c] int-c) ;; [Int -> Int] contract" 258 | [cs c2] 259 | {:pre [(every? #(instance? Contract %) cs) 260 | (instance? Contract c2)] 261 | :post [(instance? Contract %)]} 262 | (make-contract 263 | :name 'ifn-c 264 | :first-order ifn? 265 | :projection (fn [b] 266 | (fn [f] 267 | ; returning a contracted function 268 | (contract (make-flat-contract 269 | :name 'ifn? 270 | :first-order ifn?) 271 | f 272 | b) 273 | (with-meta 274 | (fn [& xs] 275 | (contract c2 276 | (apply f 277 | (map #(contract %1 278 | %2 279 | (swap-blame b)) 280 | cs 281 | xs)) 282 | b)) 283 | (if (fn? f) 284 | (meta f) 285 | nil)))))) 286 | 287 | (declare ->CheckedISeq) 288 | 289 | (deftype CheckedISeq [s c b] 290 | clojure.lang.Sequential 291 | clojure.lang.ISeq 292 | (first [this] 293 | (contract c (first s) b)) 294 | (next [this] 295 | (when-let [n (next s)] 296 | (->CheckedISeq n c b))) 297 | (cons [this x] 298 | (->CheckedISeq (conj s x) c b)) 299 | (empty [this] 300 | (empty s)) 301 | (seq [this] 302 | (when (seq s) 303 | this)) 304 | (equiv [this o] 305 | (if (or (not (instance? clojure.lang.Sequential o)) 306 | (not (instance? java.util.List o))) 307 | false 308 | (loop [ms this 309 | s (seq o)] 310 | (if (and s (= (first ms) 311 | (first s))) 312 | (recur (next ms) (next s)) 313 | (not ms)))))) 314 | 315 | 316 | #_(ann seqable-c [Contract :-> Contract]) 317 | (defn seqable-c 318 | "Alpha - subject to change. 319 | 320 | Returns a contract that checks Seqable things. 321 | 322 | [Contract -> Contract]" 323 | [c] 324 | {:pre [(instance? Contract c)] 325 | :post [(instance? Contract %)]} 326 | (make-contract 327 | :name 'seqable-c 328 | :projection (fn [b] 329 | (fn [s] 330 | (contract Object-c s b) 331 | (reify 332 | clojure.lang.Seqable 333 | (seq [this] 334 | (->CheckedISeq s c b))))))) 335 | 336 | #_(ann or-c [Contract * :-> Contract]) 337 | (defn or-c 338 | "Returns a contract that checks a value passes at least 339 | one of the contracts `cs`. 340 | 341 | Any number of flat contracts may be passed to or-c. However, 342 | if more than one higher-order contract is provided, each time 343 | this contract is used, at most *one* may pass its first-order 344 | predicate. 345 | 346 | For example, (or-c (ifn-c [int-c] int-c) (ifn-c [] int-c)) 347 | cannot be checked against `clojure.core/+` because 348 | the first-order check for both contracts (`ifn?`) passes. 349 | 350 | [Contract * -> Contract] 351 | 352 | eg. (or-c int-c nil-c) ;; (U Int nil) 353 | (or-c int-c (ifn-c [int-c] int-c)) ;; (U Int [Int -> Int]) 354 | " 355 | [& cs] 356 | {:pre [(every? #(instance? Contract %) cs)] 357 | :post [(instance? Contract %)]} 358 | (let [{flat true hoc false} (group-by :flat? cs) 359 | ;_ (prn "flat" (mapv :name flat)) 360 | ;_ (prn "hoc" (mapv :name hoc)) 361 | flat-checks (apply some-fn (or (seq (map :first-order flat)) 362 | ;; (U) always fails 363 | [(fn [_] false)])) 364 | choose-hoc 365 | (fn [x b] 366 | {:pre [(instance? Blame b)]} 367 | (let [hs (filter (fn [{:keys [first-order]}] 368 | (first-order x)) 369 | hoc)] 370 | ;; don't realise more than needed, though chunking will 371 | ;; probably negate most of the benefit. 372 | (cond 373 | ;; more than one higher-order contract matched 374 | (second hs) (throw-blame b) 375 | ;; exactly one matched 376 | (first hs) (contract (first hs) x b) 377 | ;; no contracts matched 378 | :else (throw-blame b))))] 379 | (make-contract 380 | :name 'or-c 381 | :flat? (not (seq hoc)) 382 | ; needed? 383 | :first-order (apply some-fn flat-checks (map :first-order hoc)) 384 | :projection (fn [b] 385 | (fn [x] 386 | (if (flat-checks x) 387 | x 388 | (choose-hoc x b))))))) 389 | 390 | #_(ann and-c [Contract * :-> Contract]) 391 | (defn and-c 392 | "Returns a contract that ensures a value passes each contract `cs`. 393 | 394 | At most *one* higher-order contract may be passed to `and-c`, and 395 | any number of flat contracts. 396 | 397 | [Contract * -> Contract] 398 | 399 | eg. (and-c (instance-c Boolean) true-c) ;; (I Boolean true)" 400 | [& cs] 401 | {:pre [(every? #(instance? Contract %) cs)] 402 | :post [(instance? Contract %)]} 403 | (let [{flat true hoc false} (group-by (comp boolean :flat?) cs) 404 | ;_ (prn "flat" (mapv :name flat)) 405 | ;_ (prn "hoc" (mapv :name hoc)) 406 | ] 407 | (if (< (count hoc) 2) 408 | (let [h (first hoc)] 409 | (make-contract 410 | :name 'and-c 411 | :flat? (not h) 412 | :first-order (apply every-pred (or (seq (map :first-order cs)) 413 | ;; (I) always passes 414 | (fn [_] true))) 415 | :projection (fn [b] 416 | (fn [x] 417 | (doseq [f flat] 418 | (contract f x b)) 419 | ;; could stage this conditional 420 | (if h 421 | (contract h x b) 422 | x))))) 423 | (throw (ex-info 424 | "Cannot create and-c contract with more than one higher-order contract" 425 | {:hoc (map :name hoc)}))))) 426 | 427 | #_(ann hmap-c [& :optional {:mandatory (Map Keyword Contract) 428 | :optional (Map Keyword Contract) 429 | :absent-keys (Set Keyword) 430 | :complete? Boolean} 431 | :-> Contract]) 432 | (defn hmap-c 433 | "Takes a map of mandatory and optional entry contracts, 434 | a set of absent keys, and :complete? true if this is a fully 435 | specified map. Intended to work with keyword keys, but should 436 | work with any keys looked up via =." 437 | [& {:keys [mandatory optional absent-keys complete?] 438 | :or {absent-keys #{} 439 | mandatory {} 440 | optional {} 441 | complete? false}}] 442 | (let [flat? (every? (comp :flat? val) (concat mandatory optional)) 443 | ;_ (prn "flat?" flat?) 444 | mkeys (set (keys mandatory)) 445 | okeys (set (keys optional)) 446 | check-absent? 447 | (if complete? 448 | (fn [m] 449 | {:pre [(map? m)]} 450 | true) 451 | (fn [m] 452 | {:pre [(map? m)]} 453 | (empty? (set/intersection (set (keys m)) absent-keys)))) 454 | check-completeness? 455 | (if complete? 456 | (fn [m] 457 | {:pre [(map? m)]} 458 | ;; only the mandatory or optional entries are allowed 459 | (empty? (set/difference (set (keys m)) 460 | mkeys 461 | okeys))) 462 | (fn [m] 463 | {:pre [(map? m)]} 464 | true))] 465 | (make-contract :name 'hmap-c 466 | :flat? flat? 467 | :first-order (fn [m] 468 | (and 469 | (map? m) 470 | (check-completeness? m) 471 | (check-absent? m) 472 | (every? (fn [[k {:keys [first-order]}]] 473 | (and (contains? m k) 474 | (first-order (get m k)))) 475 | mandatory) 476 | (every? (fn [[k {:keys [first-order]}]] 477 | (or (not (contains? m k)) 478 | (first-order (get m k)))) 479 | optional))) 480 | :projection (fn [b] 481 | (fn [m] 482 | (contract (make-flat-contract 483 | :name 'map? 484 | :first-order map?) 485 | m 486 | b) 487 | (contract (make-flat-contract 488 | :name 'hmap-completeness-check 489 | :first-order check-completeness?) 490 | m 491 | b) 492 | (contract (make-flat-contract 493 | :name 'hmap-absent-check 494 | :first-order check-absent?) 495 | m 496 | b) 497 | (as-> 498 | m ;; the expression 499 | m ;; the name to thread through 500 | 501 | ;; apply mandatory checks 502 | (reduce-kv (fn [m k c] 503 | {:pre [(map? m)] 504 | :post [(map? m)]} 505 | (if (not (contains? m k)) 506 | (throw-blame 507 | (assoc b 508 | :message (str k " key is missing"))) 509 | (if (:flat? c) 510 | (do (contract c (get m k) b) ;; could be done asynchronously 511 | m) 512 | (update m k #(contract c % b))))) 513 | m ;; the current map 514 | mandatory) 515 | 516 | ;; apply optional checks 517 | (reduce-kv (fn [m k c] 518 | {:pre [(map? m)] 519 | :post [(map? m)]} 520 | (if (not (contains? m k)) 521 | m 522 | (if (:flat? c) 523 | (do (contract c (get m k) b) 524 | m) 525 | (update m k #(contract c % b))))) 526 | m ;; the current map 527 | optional) 528 | 529 | ;; return the accumulated map 530 | m)))))) 531 | --------------------------------------------------------------------------------