git » blacktide.git » commit 2014360

Consolidate assignment lowering and remove redundant scaffolding

author Alan Dipert
2026-11-11 03:23:48 UTC
committer Alan Dipert
2026-11-11 03:23:48 UTC
parent 7b795a3d30fb450e4f9638e9f2b5e831ee9ad04c

Consolidate assignment lowering and remove redundant scaffolding

doc/compiler.md +1 -1
doc/tutorial.md +3 -9
src/com/tailrecursion/blacktide/compiler.clj +1 -2
src/com/tailrecursion/blacktide/compiler/languages.clj +3 -6
src/com/tailrecursion/blacktide/compiler/passes/assignment.clj +46 -83
src/com/tailrecursion/blacktide/compiler/passes/collections.clj +8 -0
src/com/tailrecursion/blacktide/compiler/passes/emit.clj +1 -4
src/com/tailrecursion/blacktide/compiler/passes/invariants.clj +1 -6
src/com/tailrecursion/blacktide/compiler/passes/source.clj +4 -10
src/com/tailrecursion/blacktide/compiler/passes/source_host.clj +5 -5
src/com/tailrecursion/blacktide/compiler/passes/structured_emit.clj +1 -4
test/com/tailrecursion/blacktide/check.clj +5 -5

diff --git a/doc/compiler.md b/doc/compiler.md
index 463dde5..6bf5e9d 100644
--- a/doc/compiler.md
+++ b/doc/compiler.md
@@ -24,7 +24,7 @@ For a walkthrough with actual inputs, intermediate data and generated code, see
 `compiler/compile-form` orchestrates source analysis and lowering:
 
 ```text
-Bound → Classified → Sequential → Assigned → Collections
+Bound → Classified → Assigned → Collections
       → Prepared → Ordered → Symbolic → CFG
 ```
 
diff --git a/doc/tutorial.md b/doc/tutorial.md
index c54a58a..a1cd658 100644
--- a/doc/tutorial.md
+++ b/doc/tutorial.md
@@ -118,14 +118,9 @@ The [pipeline](compiler.md) follows the [nanopass approach](https://nanopass.org
 
 The representations here are ordinary Clojure maps and vectors. These are _intermediate representations_, or IRs: data describing the program between source and output. Each tree node's `:op` identifies its kind; other fields hold its operands, bindings or body.
 
-_Lowering_ rewrites a richer construct into simpler operations. For example, assignment lowering takes two passes:
+_Lowering_ rewrites a richer construct into simpler operations. The assignment pass takes `Classified` to `Assigned`, replacing both assignment forms with individual `:write` nodes. For parallel batches, it introduces bindings to save all right-hand-side values before writing.
 
-| Pass | Input → output language | What the next pass can rely on |
-| --- | --- | --- |
-| Lower sequential assignment | `Classified` → `Sequential` | `:setq` nodes are gone; their pairs have become individual `:write` nodes. |
-| Lower parallel assignment | `Sequential` → `Assigned` | `:psetq` nodes are gone; each batch's right-hand-side values are saved before its pending writes. |
-
-Later passes can work with individual writes without having to understand the two assignment forms again. The [language definitions](../src/com/tailrecursion/blacktide/compiler/languages.clj) record that progression: `Sequential` removes `:setq` and adds `:write`; `Assigned` then removes `:psetq`. These are contracts the machinery can use.
+Later passes can work with individual writes without understanding `setq` and `psetq`. The [language definitions](../src/com/tailrecursion/blacktide/compiler/languages.clj) express that contract: `Assigned` removes both assignment forms and adds `:write`.
 
 Blacktide's [nanopass library](nanopass.md) generates code to walk unchanged node kinds and derives [Typed Clojure](https://github.com/typedclojure/typedclojure) types from the language descriptions. Development checks verify pass bodies against those contracts. A pass can still produce a beautifully well-typed wrong answer, so we also need tests of the program's behavior.
 
@@ -160,8 +155,7 @@ The remaining examples use `stages` to run the pipeline and retain intermediate
              :site {:form form :file nil :line nil :column nil}}
         [supply bound] (source/parse-scope {:prefix "demo" :next 0} ctx true (vec form))
         classified (boundaries/classify bound)
-        sequential (assignment/sequential classified)
-        [supply assigned] (assignment/parallel supply sequential)
+        [supply assigned] (assignment/lower supply classified)
         constructed (collections/normalize assigned)
         prepared (prepare/prepare constructed)
         [supply ordered] (order/normalize supply prepared)
diff --git a/src/com/tailrecursion/blacktide/compiler.clj b/src/com/tailrecursion/blacktide/compiler.clj
index cb95f37..0e02da3 100644
--- a/src/com/tailrecursion/blacktide/compiler.clj
+++ b/src/com/tailrecursion/blacktide/compiler.clj
@@ -52,8 +52,7 @@
   [compilation]
   (-> compilation
       boundaries/stage
-      assignment/sequential-stage
-      assignment/parallel-stage
+      assignment/stage
       collections/stage
       prepare/stage
       order/stage
diff --git a/src/com/tailrecursion/blacktide/compiler/languages.clj b/src/com/tailrecursion/blacktide/compiler/languages.clj
index 26568c2..a613837 100644
--- a/src/com/tailrecursion/blacktide/compiler/languages.clj
+++ b/src/com/tailrecursion/blacktide/compiler/languages.clj
@@ -38,7 +38,7 @@
    Plan (t/HMap :mandatory {:operation Operation :conversions (t/Vec (t/Option Conversion))} :complete? true)
    Selection (t/HMap :mandatory {:method (t/Option Method) :exact? t/Bool :tied? t/Bool} :complete? true)})
 
-;; Source normalization: Bound -> Classified -> Sequential -> Assigned ->
+;; Source normalization: Bound -> Classified -> Assigned ->
 ;; Collections -> Prepared -> Ordered. Native is a restricted subtree.
 (np/deflanguage Bound
   {Raw t/Any
@@ -97,13 +97,10 @@
   {:add {Node [(t/HMap :mandatory {:op ':native :value l/Native-Expr}
                        :complete? true)]}})
 
-(np/derive-language Sequential Classified
-  {:remove {Node #{:setq}}
+(np/derive-language Assigned Classified
+  {:remove {Node #{:setq :psetq}}
    :add {Node [(t/HMap :mandatory {:op ':write :target Local :value Expr} :complete? true)]}})
 
-(np/derive-language Assigned Sequential
-  {:remove {Node #{:psetq}}})
-
 (np/derive-language Collections Assigned
   {:remove {Node #{:collection}}
    :add {Node [(t/HMap :mandatory {:op ':build :kind CollectionKind :items Exprs :unique? t/Bool
diff --git a/src/com/tailrecursion/blacktide/compiler/passes/assignment.clj b/src/com/tailrecursion/blacktide/compiler/passes/assignment.clj
index e53aa06..9f9bdcf 100644
--- a/src/com/tailrecursion/blacktide/compiler/passes/assignment.clj
+++ b/src/com/tailrecursion/blacktide/compiler/passes/assignment.clj
@@ -1,78 +1,43 @@
 (ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.assignment
-  "Lower sequential batches to writes, then parallel batches to saved RHSs and
-  writes. Pending parallel writes do not execute if an RHS abandons evaluation."
+  "Eliminate assignment batches. Sequential pairs become writes; parallel pairs
+  save all RHSs before writing. An abandoned RHS leaves pending writes undone."
   (:require [com.tailrecursion.nanopass :as np]
-            [com.tailrecursion.nanopass.pipeline :as pipeline]
             [com.tailrecursion.blacktide.compiler.languages :as l]
             [com.tailrecursion.blacktide.compiler.passes.source :as source]
             [typed.clojure :as-alias t]))
 
 (np/defpasses
-  (sequential-pair
-    {:from [l/Classified Assignment] :to [l/Sequential Assignment]
-     :processors {Local clojure.core/identity Expr sequential}}
-    [pair])
-
-  (sequential-pairs
-    {:from [l/Classified Assignments] :to [l/Sequential Assignments] :processors {Assignment sequential-pair}}
-    [pairs])
-
-  (sequential-forms
-    {:from [l/Classified Exprs] :to [l/Sequential Exprs] :processors {Expr sequential}}
-    [forms])
-
-  (sequential-node
-    {:from [l/Classified Node] :to [l/Sequential Node]
-     :processors {Local clojure.core/identity Raw clojure.core/identity CollectionKind clojure.core/identity RecurTarget clojure.core/identity CaseSpec clojure.core/identity
-                  Expr sequential Exprs sequential-forms Binding sequential-binding Assignments sequential-pairs}}
-    [node]
-    :setq {:op :do :forms
-           (mapv (fn [pair]
-                   (let [value (sequential (:value pair))]
-                     (assoc value :node {:op :write :target (:target pair) :value value}))) (:pairs node))})
-
-  (sequential-binding
-    {:from [l/Classified Binding] :to [l/Sequential Binding]
-     :processors {Local clojure.core/identity Expr sequential}}
-    [binding])
-
-  (sequential
-    {:from [l/Classified Expr] :to [l/Sequential Expr] :body true}
-    [expr]
-    (assoc expr :node (sequential-node (:node expr)))))
-
-(np/defpasses
-  (parallel-forms
-    {:from [l/Sequential Exprs] :to (t/HVec [l/Bound-Supply l/Assigned-Exprs])
+  (lower-forms
+    {:from [l/Classified Exprs] :to (t/HVec [l/Bound-Supply l/Assigned-Exprs])
      :args [l/Bound-Supply] :body true}
     [supply forms]
-    (reduce (fn [^{::t/- (t/HVec [l/Bound-Supply l/Assigned-Exprs])} acc ^{::t/- l/Sequential-Expr} expr]
-              (let [[supply out] acc [supply expr] (parallel supply expr)] [supply (conj out expr)]))
+    (reduce (fn [^{::t/- (t/HVec [l/Bound-Supply l/Assigned-Exprs])} acc ^{::t/- l/Classified-Expr} expr]
+              (let [[supply out] acc [supply expr] (lower supply expr)] [supply (conj out expr)]))
             [supply []] forms))
 
-  (parallel-bindings
-    {:from [l/Sequential Bindings] :to (t/HVec [l/Bound-Supply l/Assigned-Bindings])
+  (lower-bindings
+    {:from [l/Classified Bindings] :to (t/HVec [l/Bound-Supply l/Assigned-Bindings])
      :args [l/Bound-Supply] :body true}
     [supply bindings]
-    (reduce (fn [^{::t/- (t/HVec [l/Bound-Supply l/Assigned-Bindings])} acc ^{::t/- l/Sequential-Binding} binding]
-              (let [[supply out] acc [supply init] (parallel supply (:init binding))]
+    (reduce (fn [^{::t/- (t/HVec [l/Bound-Supply l/Assigned-Bindings])} acc ^{::t/- l/Classified-Binding} binding]
+              (let [[supply out] acc [supply init] (lower supply (:init binding))]
                 [supply (conj out (assoc binding :init init))])) [supply []] bindings))
 
-  (save-parallel-values
-    {:from [l/Sequential Assignments]
+  (save-values
+    {:from [l/Classified Assignments]
      :to (t/HVec [l/Bound-Supply l/Assigned-Bindings l/Assigned-Exprs]) :args [l/Bound-Supply] :body true}
     [supply pairs]
     (reduce (fn [^{::t/- (t/HVec [l/Bound-Supply l/Assigned-Bindings l/Assigned-Exprs])} acc
-                 ^{::t/- l/Sequential-Assignment} pair]
-              (let [[supply bindings writes] acc [supply value] (parallel supply (:value pair))
+                 ^{::t/- l/Classified-Assignment} pair]
+              (let [[supply bindings writes] acc [supply value] (lower supply (:value pair))
                     [supply id] (source/fresh "parallel" supply)
                     local {:id id :name id :mutable? false :external? false :jvm (:jvm value)}
                     read (assoc value :node {:op :local :local local})
                     write (assoc value :node {:op :write :target (:target pair) :value read})]
                 [supply (conj bindings {:local local :init value}) (conj writes write)])) [supply [] []] pairs))
 
-  (parallel-node
-    {:from [l/Sequential Node] :to (t/HVec [l/Bound-Supply l/Assigned-Node]) :args [l/Bound-Supply]}
+  (lower-node
+    {:from [l/Classified Node] :to (t/HVec [l/Bound-Supply l/Assigned-Node]) :args [l/Bound-Supply]}
     [supply node]
     :literal [supply node]
     :local [supply node]
@@ -81,54 +46,52 @@
     :label [supply node]
     :goto [supply node]
     :native [supply node]
-    :dispatch (let [[supply value] (parallel supply (:value node))]
+    :dispatch (let [[supply value] (lower supply (:value node))]
                 [supply {:op :dispatch :value value :spec (:spec node)}])
-    :case (let [[supply selector] (parallel supply (:selector node))
-                [supply arms] (parallel-forms supply (:arms node))]
+    :case (let [[supply selector] (lower supply (:selector node))
+                [supply arms] (lower-forms supply (:arms node))]
             [supply {:op :case :selector selector :arms arms}])
-    :recur (let [[supply args] (parallel-forms supply (:args node))]
+    :recur (let [[supply args] (lower-forms supply (:args node))]
              [supply {:op :recur :target (:target node) :args args}])
-    :loop (let [[supply bindings] (parallel-bindings supply (:bindings node))
-                [supply body] (parallel supply (:body node))]
+    :loop (let [[supply bindings] (lower-bindings supply (:bindings node))
+                [supply body] (lower supply (:body node))]
             [supply {:op :loop :id (:id node) :bindings bindings :body body}])
-    :if (let [[supply test] (parallel supply (:test node))
-              [supply yes] (parallel supply (:then node))
-              [supply no] (parallel supply (:else node))]
+    :if (let [[supply test] (lower supply (:test node))
+              [supply yes] (lower supply (:then node))
+              [supply no] (lower supply (:else node))]
           [supply {:op :if :test test :then yes :else no}])
-    :throw (let [[supply value] (parallel supply (:value node))] [supply {:op :throw :value value}])
-    :write (let [[supply value] (parallel supply (:value node))]
-             [supply {:op :write :target (:target node) :value value}])
-    :do (let [[supply forms] (parallel-forms supply (:forms node))] [supply {:op :do :forms forms}])
-    :host (let [[supply args] (parallel-forms supply (:args node))]
+    :throw (let [[supply value] (lower supply (:value node))] [supply {:op :throw :value value}])
+    :do (let [[supply forms] (lower-forms supply (:forms node))] [supply {:op :do :forms forms}])
+    :host (let [[supply args] (lower-forms supply (:args node))]
             [supply {:op :host :operation (:operation node) :args args}])
-    :invoke (let [[supply function] (parallel supply (:function node))
-                  [supply args] (parallel-forms supply (:args node))]
+    :invoke (let [[supply function] (lower supply (:function node))
+                  [supply args] (lower-forms supply (:args node))]
               [supply {:op :invoke :function function :args args}])
-    :collection (let [[supply items] (parallel-forms supply (:items node))
-                      [supply metadata] (if-let [metadata (:metadata node)] (parallel supply metadata) [supply nil])]
+    :collection (let [[supply items] (lower-forms supply (:items node))
+                      [supply metadata] (if-let [metadata (:metadata node)] (lower supply metadata) [supply nil])]
                   [supply {:op :collection :kind (:kind node) :items items :metadata metadata}])
-    :let (let [[supply bindings] (parallel-bindings supply (:bindings node))
-               [supply body] (parallel supply (:body node))]
+    :let (let [[supply bindings] (lower-bindings supply (:bindings node))
+               [supply body] (lower supply (:body node))]
            [supply {:op :let :bindings bindings :body body}])
-    :psetq (let [[supply bindings writes] (save-parallel-values supply (:pairs node))
+    :setq (let [[supply values] (lower-forms supply (mapv :value (:pairs node)))]
+            [supply {:op :do :forms
+                     (mapv (fn [pair value]
+                             (assoc value :node {:op :write :target (:target pair) :value value}))
+                           (:pairs node) values)}])
+    :psetq (let [[supply bindings writes] (save-values supply (:pairs node))
                  template (first writes)]
              (if template
                [supply {:op :let :bindings bindings
                         :body (assoc template :node {:op :do :forms (conj writes (assoc template :node {:op :literal :value nil}))})}]
                [supply {:op :literal :value nil}])))
 
-  (parallel
-    {:from [l/Sequential Expr] :to (t/HVec [l/Bound-Supply l/Assigned-Expr])
+  (lower
+    {:from [l/Classified Expr] :to (t/HVec [l/Bound-Supply l/Assigned-Expr])
      :args [l/Bound-Supply] :body true}
     [supply expr]
-    (let [[supply node] (parallel-node supply (:node expr))] [supply (assoc expr :node node)])))
-
-(np/defpass sequential-stage
-  {:from [l/Classified Compilation] :to [l/Sequential Compilation] :body true}
-  [compilation]
-  (pipeline/map-value sequential compilation))
+    (let [[supply node] (lower-node supply (:node expr))] [supply (assoc expr :node node)])))
 
-(np/defpass parallel-stage
-  {:from [l/Sequential Compilation] :to [l/Assigned Compilation] :body true}
+(np/defpass stage
+  {:from [l/Classified Compilation] :to [l/Assigned Compilation] :body true}
   [compilation]
-  (apply parallel compilation))
+  (apply lower compilation))
diff --git a/src/com/tailrecursion/blacktide/compiler/passes/collections.clj b/src/com/tailrecursion/blacktide/compiler/passes/collections.clj
index 5029433..e21d2d7 100644
--- a/src/com/tailrecursion/blacktide/compiler/passes/collections.clj
+++ b/src/com/tailrecursion/blacktide/compiler/passes/collections.clj
@@ -38,6 +38,14 @@
               (if (and values (= :literal (:op node))) (conj values (:value node)) (reduced nil))))
           [] items))
 
+(np/defpass metadata-form
+  {:from [l/Machine Code] :to [l/Machine Code] :args [l/Machine-Code] :body true}
+  [value metadata]
+  (let [v (gensym "value__") m (gensym "metadata__")]
+    (list 'let* [(with-meta v {:tag 'clojure.lang.IObj}) value
+                 (with-meta m {:tag 'clojure.lang.IPersistentMap}) metadata]
+          (list '. v 'withMeta m))))
+
 (np/defpasses
   (normalize-forms
     {:from [l/Assigned Exprs] :to [l/Collections Exprs] :processors {Expr normalize}}
diff --git a/src/com/tailrecursion/blacktide/compiler/passes/emit.clj b/src/com/tailrecursion/blacktide/compiler/passes/emit.clj
index 7960dac..5b850b8 100644
--- a/src/com/tailrecursion/blacktide/compiler/passes/emit.clj
+++ b/src/com/tailrecursion/blacktide/compiler/passes/emit.clj
@@ -23,10 +23,7 @@
   :native (native/form #{} (:value node))
   :dispatch (cases/selector-form (host/typed-form (:jvm (:value node)) (atom-form (:value (:value node)))) (:spec node))
   :construct (collections/form (mapv atom-form (:items node)) (:unique? node) (:kind node))
-  :metadata (let [value (gensym "value__") metadata (gensym "metadata__")]
-              (list 'let* [(with-meta value {:tag 'clojure.lang.IObj}) (atom-form (:value node))
-                           (with-meta metadata {:tag 'clojure.lang.IPersistentMap}) (atom-form (:metadata node))]
-                    (list '. value 'withMeta metadata)))
+  :metadata (collections/metadata-form (atom-form (:value node)) (atom-form (:metadata node)))
   :convert (host/conversion-form (:conversion node) (:jvm (:value node)) (atom-form (:value (:value node))))
   :host (host/host-form
           (mapv (fn [arg] (host/typed-form (assoc (:jvm arg) :primitive? false) (atom-form (:value arg))))
diff --git a/src/com/tailrecursion/blacktide/compiler/passes/invariants.clj b/src/com/tailrecursion/blacktide/compiler/passes/invariants.clj
index 43d29ff..851c8d4 100644
--- a/src/com/tailrecursion/blacktide/compiler/passes/invariants.clj
+++ b/src/com/tailrecursion/blacktide/compiler/passes/invariants.clj
@@ -17,12 +17,7 @@
 
 (np/defpass expression {:from [l/Joined Expr] :to [l/Joined Expr] :args [(t/Set t/Sym)]}
   [invariants node]
-  :bind (assoc node :body (expression invariants (:body node)))
-  :join (assoc node :value (expression invariants (:value node)) :body (expression invariants (:body node)))
-  :branch (assoc node :yes (expression invariants (:yes node)) :no (expression invariants (:no node)))
-  :switch (assoc node :arms (mapv #(expression invariants %) (:arms node)))
   :loop (let [invariants (set/difference (set (:slots node)) (assigned (:body node)))]
           (assoc node :slots (filterv #(not (contains? invariants %)) (:slots node))
                       :body (expression invariants (:body node))))
-  :recur (assoc node :slots (filterv #(not (contains? invariants %)) (:slots node)))
-  :return node :throw node)
+  :recur (assoc node :slots (filterv #(not (contains? invariants %)) (:slots node))))
diff --git a/src/com/tailrecursion/blacktide/compiler/passes/source.clj b/src/com/tailrecursion/blacktide/compiler/passes/source.clj
index 50cbb46..448bf47 100644
--- a/src/com/tailrecursion/blacktide/compiler/passes/source.clj
+++ b/src/com/tailrecursion/blacktide/compiler/passes/source.clj
@@ -70,12 +70,6 @@
                          :macro-env (assoc (:macro-env ctx) name {:name name :tag tag})) local])
     (errors/fail :invalid-binding "Binding targets must be simple symbols" {:binding name} (:site ctx))))
 
-(np/defpass case-expression
-  {:from [l/Bound Exprs] :to (t/HVec [l/Bound-Supply l/Bound-Expr])
-   :args [l/Bound-Supply l/Bound-Context l/Bound-Expr] :body true}
-  [supply ctx selector arms]
-  [supply (expression ctx {:hint nil :var-meta {}} {:op :case :selector selector :arms arms})])
-
 (np/defpass protocol-argument
   {:from [l/Bound Expr] :to (t/HVec [l/Bound-Supply l/Bound-Expr])
    :args [l/Bound-Supply l/Bound-Context l/Bound-Expr java.lang.Class] :body true}
@@ -238,7 +232,7 @@
           (let [[supply inner bindings] (parse-bindings supply ctx :immutable (second selector))
                 [supply selector] (parse-operand supply inner (first (nnext selector)))
                 [supply arms] (parse-arms supply inner arms)
-                [supply body] (case-expression supply ctx selector arms)]
+                body (expression ctx {:hint nil :var-meta {}} {:op :case :selector selector :arms arms})]
             [supply (expression ctx {:hint nil :var-meta {}} {:op :let :bindings bindings :body body})])))))
 
   (parse-dispatch
@@ -251,7 +245,7 @@
         (errors/fail :invalid-case "case* dispatch must reference a lexical local" {} (:site ctx)))
       (let [selector (expression ctx {:hint nil :var-meta {}} {:op :dispatch :value value :spec spec})
             [supply arms] (parse-arms supply ctx arms)]
-        (case-expression supply ctx selector arms))))
+        [supply (expression ctx {:hint nil :var-meta {}} {:op :case :selector selector :arms arms})])))
 
   (parse-call
     {:from [l/Bound Forms] :to (t/HVec [l/Bound-Supply l/Bound-Expr])
@@ -341,7 +335,7 @@
             (errors/fail :invalid-var "var requires a resolvable Var name" {} (:site ctx))))
         (#{'fn* 'letfn* 'try 'set! 'monitor-enter 'monitor-exit} op)
         (errors/fail :unsupported-form "Form is unsupported inside loophole; place it outside the region" {:operator op} (:site ctx))
-        (#{'new '.} op) (parse-host supply ctx (or (host/syntax ctx forms)
+        (#{'new '.} op) (parse-host supply ctx (or (host/syntax forms)
                                                   (errors/fail :invalid-interop "Missing host syntax" {} (:site ctx))))
         (= op 'do) (parse-body supply ctx (vec (rest forms)))
         (= op 'let*) (parse-scope supply ctx false forms)
@@ -378,7 +372,7 @@
               expanded (if (instance? clojure.lang.IObj expanded)
                          (with-meta expanded (merge (meta expanded) (meta form))) expanded)]
           (parse supply ctx expanded))
-        :else (if-let [syntax (host/syntax ctx forms)]
+        :else (if-let [syntax (host/syntax forms)]
                 (parse-host supply ctx syntax)
                 (parse-call supply ctx forms)))))
 
diff --git a/src/com/tailrecursion/blacktide/compiler/passes/source_host.clj b/src/com/tailrecursion/blacktide/compiler/passes/source_host.clj
index 431e186..1cdfb88 100644
--- a/src/com/tailrecursion/blacktide/compiler/passes/source_host.clj
+++ b/src/com/tailrecursion/blacktide/compiler/passes/source_host.clj
@@ -1,5 +1,5 @@
 (ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.source-host
-  "Recognize native JVM syntax with the source lexical environment. Return host
+  "Recognize native JVM syntax in the caller namespace. Return host
   descriptors and ordered source operands; none of those operands is evaluated."
   (:require [com.tailrecursion.nanopass :as np]
             [com.tailrecursion.blacktide.compiler.languages :as l]
@@ -27,8 +27,8 @@
               :else {:op :static :class class :member (clojure.core/symbol member) :tags tags})))))
 
 (np/defpass syntax
-  {:from [l/Bound Forms] :to (t/Option l/JVM-Syntax) :args [l/Bound-Context] :body true}
-  [ctx forms]
+  {:from [l/Bound Forms] :to (t/Option l/JVM-Syntax) :body true}
+  [forms]
   (let [op (first forms) args (subvec forms 1)]
     (cond
       (= op 'new)
@@ -60,9 +60,9 @@
               target (if (jvm/resolved-class target)
                        (with-meta (list 'clojure.core/identity target) {:tag 'java.lang.Class}) target)]
           (when (empty? args) (throw (IllegalArgumentException. "Instance member requires a receiver")))
-          (syntax ctx (into ['. target (symbol (subs (name op) 1))] (rest args))))
+          (syntax (into ['. target (symbol (subs (name op) 1))] (rest args))))
         (.endsWith (name op) ".")
-        (syntax ctx (into ['new (symbol (subs (str op) 0 (dec (count (str op)))))] args))
+        (syntax (into ['new (symbol (subs (str op) 0 (dec (count (str op)))))] args))
         :else (when-let [operation (qualified op)]
                 (when (and (= :instance (:op operation)) (empty? args))
                   (throw (IllegalArgumentException. "Qualified instance call requires a receiver")))
diff --git a/src/com/tailrecursion/blacktide/compiler/passes/structured_emit.clj b/src/com/tailrecursion/blacktide/compiler/passes/structured_emit.clj
index 723f7ce..0cc1943 100644
--- a/src/com/tailrecursion/blacktide/compiler/passes/structured_emit.clj
+++ b/src/com/tailrecursion/blacktide/compiler/passes/structured_emit.clj
@@ -53,10 +53,7 @@
     :value (atom-form types (:value rhs))
     :global (emit/rhs-form rhs) :external (emit/rhs-form rhs) :var (emit/rhs-form rhs) :native (emit/rhs-form rhs)
     :construct (collections/form (mapv #(atom-form types %) (:items rhs)) (:unique? rhs) (:kind rhs))
-    :metadata (let [value (gensym "value__") metadata (gensym "metadata__")]
-                (list 'let* [(with-meta value {:tag 'clojure.lang.IObj}) (atom-form types (:value rhs))
-                             (with-meta metadata {:tag 'clojure.lang.IPersistentMap}) (atom-form types (:metadata rhs))]
-                      (list '. value 'withMeta metadata)))
+    :metadata (collections/metadata-form (atom-form types (:value rhs)) (atom-form types (:metadata rhs)))
     :invoke (list* (atom-form types (:function rhs)) (mapv #(atom-form types %) (:args rhs)))))
 
 (np/defpass rhs-type
diff --git a/test/com/tailrecursion/blacktide/check.clj b/test/com/tailrecursion/blacktide/check.clj
index 6c6553c..4aab17c 100644
--- a/test/com/tailrecursion/blacktide/check.clj
+++ b/test/com/tailrecursion/blacktide/check.clj
@@ -15,9 +15,9 @@
 
 (deftest compilation-stage-contracts
   (doseq [[accepted stages]
-          [[true `[boundaries/stage assignment/sequential-stage assignment/parallel-stage]]
-           [false `[boundaries/stage assignment/parallel-stage assignment/sequential-stage]]
-           [false `[boundaries/stage assignment/sequential-stage]]]]
+          [[true `[boundaries/stage assignment/stage]]
+           [false `[assignment/stage boundaries/stage]]
+           [false `[boundaries/stage]]]]
     (let [form `(fn [compilation#] (-> compilation# ~@stages))
           {:keys [ex delayed-errors]}
           (probes/check-form form `[l/Bound-Compilation :-> l/Assigned-Compilation])
@@ -32,8 +32,8 @@
         batch {:op :psetq :pairs []}
         assigned 'com.tailrecursion.blacktide.compiler.languages/Assigned-Expr]
     (probes/accepts? 'com.tailrecursion.blacktide.compiler.languages/Bound-Node {:op :setq :pairs []} true)
-    (probes/accepts? 'com.tailrecursion.blacktide.compiler.languages/Sequential-Node {:op :setq :pairs []} false)
-    (probes/accepts? 'com.tailrecursion.blacktide.compiler.languages/Sequential-Node batch true)
+    (probes/accepts? 'com.tailrecursion.blacktide.compiler.languages/Assigned-Node {:op :setq :pairs []} false)
+    (probes/accepts? 'com.tailrecursion.blacktide.compiler.languages/Bound-Node batch true)
     (probes/accepts? assigned (expr batch) false)
     (probes/accepts? assigned (expr {:op :do :forms [(expr batch)]}) false)
     (probes/accepts? 'com.tailrecursion.blacktide.compiler.languages/Native-Expr