From 012253da501198ebbdcda38e6b80fb42788a3574 Mon Sep 17 00:00:00 2001
From: Timothy Baldridge <tbaldridge@gmail.com>
Date: Fri, 11 Oct 2013 08:21:14 -0600
Subject: [PATCH 1/3] Fix for CLJ-865

---
 src/jvm/clojure/lang/Compiler.java   |   32 +++++++++++++++++++++++++++++++-
 test/clojure/test_clojure/macros.clj |   24 +++++++++++++++++++++++-
 2 files changed, 54 insertions(+), 2 deletions(-)

diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java
index c961f2f..88dafd4 100644
--- a/src/jvm/clojure/lang/Compiler.java
+++ b/src/jvm/clojure/lang/Compiler.java
@@ -82,6 +82,7 @@ static final Keyword inlineKey = Keyword.intern(null, "inline");
 static final Keyword inlineAritiesKey = Keyword.intern(null, "inline-arities");
 static final Keyword staticKey = Keyword.intern(null, "static");
 static final Keyword arglistsKey = Keyword.intern(null, "arglists");
+static final Keyword explicitMetaKey = Keyword.intern(null, "explicit-meta");
 static final Symbol INVOKE_STATIC = Symbol.intern("invokeStatic");
 
 static final Keyword volatileKey = Keyword.intern(null, "volatile");
@@ -6486,9 +6487,38 @@ public static Object macroexpand1(Object x) {
 		Var v = isMacro(op);
 		if(v != null)
 			{
+				IPersistentMap preMeta = null;
+				if (form instanceof IMeta)
+					{
+						preMeta = ((IMeta)form).meta();
+					}
 				try
 					{
-						return v.applyTo(RT.cons(form,RT.cons(LOCAL_ENV.get(),form.next())));
+						Object expansion = v.applyTo(RT.cons(form,RT.cons(LOCAL_ENV.get(),form.next())));
+						if (preMeta == null || !(expansion instanceof IObj))
+							{
+								return expansion;
+							}
+						IObj ret = (IObj)expansion;
+						IPersistentMap mergedMeta = ret.meta();
+						if (mergedMeta == null)
+							{
+								mergedMeta = PersistentArrayMap.EMPTY;
+							}
+
+                                                if (RT.booleanCast(RT.get(mergedMeta, explicitMetaKey)))
+                                                        {	// macro author has already considered &form meta
+                                                                return ret;
+                                                        }
+						ISeq pairs = preMeta.without(RT.LINE_KEY)
+                                                                    .without(RT.FILE_KEY)
+                                                                    .without(RT.COLUMN_KEY).seq();
+						while (pairs != null)
+							{
+								mergedMeta = (IPersistentMap)mergedMeta.cons(pairs.first());
+								pairs = pairs.next();
+							}
+						return ret.withMeta(mergedMeta);
 					}
 				catch(ArityException e)
 					{
diff --git a/test/clojure/test_clojure/macros.clj b/test/clojure/test_clojure/macros.clj
index e832b7e..43cc672 100644
--- a/test/clojure/test_clojure/macros.clj
+++ b/test/clojure/test_clojure/macros.clj
@@ -9,13 +9,35 @@
 ; Author: Frantisek Sodomka
 
 (ns clojure.test-clojure.macros
-  (:use clojure.test))
+  (:use clojure.test)
+  (:import (java.util Map ArrayList)))
 
 ; http://clojure.org/macros
 
 ; ->
 ; defmacro definline macroexpand-1 macroexpand
 
+(defmacro simple-macro [f arg]
+  `(~f ~arg))
+
+(defmacro domineering-macro [arg]
+  (with-meta arg {:tag 'Integer, :explicit-meta true}))
+
+(let [expanded (macroexpand ' ^Integer (simple-macro inc 1))]
+  ;; calling macroexpand inside of deftest is broken, so do it outside
+  (deftest preserve-meta
+    (testing "These macros get applied"
+      (is (= 2 (simple-macro inc 1))))
+    (testing "Manual expansion preserves metadata"
+      (is (= 'Integer (:tag (meta expanded)))))
+    (testing "Compiler preserves metadata"
+      (is (thrown? ClassCastException (.size ^Map (simple-macro identity (ArrayList.))))))))
+
+(let [expanded (macroexpand ' ^String (domineering-macro (inc 1)))]
+  (deftest explicit-meta
+    (is (= '(inc 1) expanded))
+    (is (= 2 (domineering-macro (inc 1))))
+    (is (= 'Integer (:tag (meta expanded))))))
 
 ;; -> and ->> should not be dependent on the meaning of their arguments
 
-- 
1.7.9.5


From d86af6c8c16ab1b88a7d5dd08158eff91ee0c3af Mon Sep 17 00:00:00 2001
From: Timothy Baldridge <tbaldridge@gmail.com>
Date: Fri, 11 Oct 2013 11:47:36 -0600
Subject: [PATCH 2/3] FIX CLJ-865 tests for line numbers

---
 test/clojure/test_clojure/macros.clj |   13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/test/clojure/test_clojure/macros.clj b/test/clojure/test_clojure/macros.clj
index 43cc672..7e304c1 100644
--- a/test/clojure/test_clojure/macros.clj
+++ b/test/clojure/test_clojure/macros.clj
@@ -20,6 +20,9 @@ (ns clojure.test-clojure.macros
 (defmacro simple-macro [f arg]
   `(~f ~arg))
 
+(defmacro identity-macro [x]
+  x)
+
 (defmacro domineering-macro [arg]
   (with-meta arg {:tag 'Integer, :explicit-meta true}))
 
@@ -39,6 +42,16 @@ (let [expanded (macroexpand ' ^String (domineering-macro (inc 1)))]
     (is (= 2 (domineering-macro (inc 1))))
     (is (= 'Integer (:tag (meta expanded))))))
 
+(let [expanded1 (macroexpand '(identity-macro (+ 1 2)))
+      expanded2 (macroexpand '(+ 1 2))]
+  (deftest line-number-tests
+    (is (= (:file (meta expanded1))
+           (:file (meta expanded2))))
+    (is (= (inc (:line (meta expanded1)))
+           (:line (meta expanded2))))
+    (is (= (- (:column (meta expanded1)) 16)
+           (:column (meta expanded2))))))
+
 ;; -> and ->> should not be dependent on the meaning of their arguments
 
 (defmacro c
-- 
1.7.9.5


From 056fa036c2eb9619134d00ce86b93c33817275b5 Mon Sep 17 00:00:00 2001
From: Alan Malloy <alan@malloys.org>
Date: Mon, 2 Dec 2013 23:56:10 -0800
Subject: [PATCH 3/3] Make the metadata-copying from CLJ-865 opt-in rather
 than opt-out

---
 src/jvm/clojure/lang/Compiler.java   |   26 +++++---------------------
 test/clojure/test_clojure/macros.clj |    8 ++++----
 2 files changed, 9 insertions(+), 25 deletions(-)

diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java
index 88dafd4..8521930 100644
--- a/src/jvm/clojure/lang/Compiler.java
+++ b/src/jvm/clojure/lang/Compiler.java
@@ -82,7 +82,7 @@ static final Keyword inlineKey = Keyword.intern(null, "inline");
 static final Keyword inlineAritiesKey = Keyword.intern(null, "inline-arities");
 static final Keyword staticKey = Keyword.intern(null, "static");
 static final Keyword arglistsKey = Keyword.intern(null, "arglists");
-static final Keyword explicitMetaKey = Keyword.intern(null, "explicit-meta");
+static final Keyword keepMetaKey = Keyword.intern(null, "keep-meta");
 static final Symbol INVOKE_STATIC = Symbol.intern("invokeStatic");
 
 static final Keyword volatileKey = Keyword.intern(null, "volatile");
@@ -394,19 +394,6 @@ static class DefExpr implements Expr{
 		this.initProvided = initProvided;
 	}
 
-    private boolean includesExplicitMetadata(MapExpr expr) {
-        for(int i=0; i < expr.keyvals.count(); i += 2)
-            {
-                Keyword k  = ((KeywordExpr) expr.keyvals.nth(i)).k;
-                if ((k != RT.FILE_KEY) &&
-                    (k != RT.DECLARED_KEY) &&
-                    (k != RT.LINE_KEY) &&
-                    (k != RT.COLUMN_KEY))
-                    return true;
-            }
-        return false;
-    }
-
     public Object eval() {
 		try
 			{
@@ -420,7 +407,7 @@ static class DefExpr implements Expr{
 			if(meta != null)
 				{
                 IPersistentMap metaMap = (IPersistentMap) meta.eval();
-                if (initProvided || true)//includesExplicitMetadata((MapExpr) meta))
+                if (initProvided)
 				    var.setMeta((IPersistentMap) meta.eval());
 				}
 			return var.setDynamic(isDynamic);
@@ -443,7 +430,7 @@ static class DefExpr implements Expr{
 			}
 		if(meta != null)
 			{
-            if (initProvided || true)//includesExplicitMetadata((MapExpr) meta))
+            if (initProvided)
                 {
                 gen.dup();
                 meta.emit(C.EXPRESSION, objx, gen);
@@ -6487,6 +6474,7 @@ public static Object macroexpand1(Object x) {
 		Var v = isMacro(op);
 		if(v != null)
 			{
+				boolean keepMeta = RT.booleanCast(RT.get(v.meta(), keepMetaKey, false));
 				IPersistentMap preMeta = null;
 				if (form instanceof IMeta)
 					{
@@ -6495,7 +6483,7 @@ public static Object macroexpand1(Object x) {
 				try
 					{
 						Object expansion = v.applyTo(RT.cons(form,RT.cons(LOCAL_ENV.get(),form.next())));
-						if (preMeta == null || !(expansion instanceof IObj))
+						if (!keepMeta || preMeta == null || !(expansion instanceof IObj))
 							{
 								return expansion;
 							}
@@ -6506,10 +6494,6 @@ public static Object macroexpand1(Object x) {
 								mergedMeta = PersistentArrayMap.EMPTY;
 							}
 
-                                                if (RT.booleanCast(RT.get(mergedMeta, explicitMetaKey)))
-                                                        {	// macro author has already considered &form meta
-                                                                return ret;
-                                                        }
 						ISeq pairs = preMeta.without(RT.LINE_KEY)
                                                                     .without(RT.FILE_KEY)
                                                                     .without(RT.COLUMN_KEY).seq();
diff --git a/test/clojure/test_clojure/macros.clj b/test/clojure/test_clojure/macros.clj
index 7e304c1..a890d24 100644
--- a/test/clojure/test_clojure/macros.clj
+++ b/test/clojure/test_clojure/macros.clj
@@ -17,14 +17,14 @@ (ns clojure.test-clojure.macros
 ; ->
 ; defmacro definline macroexpand-1 macroexpand
 
-(defmacro simple-macro [f arg]
+(defmacro ^:keep-meta simple-macro [f arg]
   `(~f ~arg))
 
-(defmacro identity-macro [x]
+(defmacro ^:keep-meta identity-macro [x]
   x)
 
 (defmacro domineering-macro [arg]
-  (with-meta arg {:tag 'Integer, :explicit-meta true}))
+  (with-meta arg {:tag 'Integer}))
 
 (let [expanded (macroexpand ' ^Integer (simple-macro inc 1))]
   ;; calling macroexpand inside of deftest is broken, so do it outside
@@ -37,7 +37,7 @@ (let [expanded (macroexpand ' ^Integer (simple-macro inc 1))]
       (is (thrown? ClassCastException (.size ^Map (simple-macro identity (ArrayList.))))))))
 
 (let [expanded (macroexpand ' ^String (domineering-macro (inc 1)))]
-  (deftest explicit-meta
+  (deftest overwrite-meta-unless-kept
     (is (= '(inc 1) expanded))
     (is (= 2 (domineering-macro (inc 1))))
     (is (= 'Integer (:tag (meta expanded))))))
-- 
1.7.9.5