From 2277a098e3db941b662383956a9626a612f4d2b6 Mon Sep 17 00:00:00 2001
From: Oron Port <soronpo@tx.technion.ac.il>
Date: Sun, 21 Jun 2020 00:35:05 +0300
Subject: [PATCH 01/11] Add OpIntercept to allow custom types and operations

---
 .../scala/singleton/ops/OpIntercept.scala     |  22 ++++
 .../singleton/ops/impl/GeneralMacros.scala    | 112 +++++++++++++-----
 src/main/scala/singleton/ops/impl/Op.scala    |   8 +-
 .../scala/singleton/ops/OpInterceptSpec.scala |  55 +++++++++
 4 files changed, 166 insertions(+), 31 deletions(-)
 create mode 100644 src/main/scala/singleton/ops/OpIntercept.scala
 create mode 100644 src/test/scala/singleton/ops/OpInterceptSpec.scala

diff --git a/src/main/scala/singleton/ops/OpIntercept.scala b/src/main/scala/singleton/ops/OpIntercept.scala
new file mode 100644
index 00000000..d15362f1
--- /dev/null
+++ b/src/main/scala/singleton/ops/OpIntercept.scala
@@ -0,0 +1,22 @@
+package singleton.ops
+import scala.reflect.macros.whitebox
+import impl._
+
+import scala.annotation.implicitNotFound
+
+trait OpIntercept[Op <: HasOut] extends HasOut
+object OpIntercept {
+  type Aux[Op <: HasOut, Out0] = OpIntercept[Op]{type Out = Out0}
+  @implicitNotFound("Failed to cache op ${Op} with result ${Out}")
+  trait CacheResult[Op <: HasOut, Out]
+  object CacheResult {
+    implicit def call[Op <: HasOut, Out] : CacheResult[Op, Out] = macro Macro.materializeCacheResult[Op, Out]
+    final class Macro(val c: whitebox.Context) extends GeneralMacros {
+      def materializeCacheResult[
+        Op : c.WeakTypeTag,
+        Out: c.WeakTypeTag,
+      ]: c.Tree = cacheOpInterceptResult[Op, Out]
+    }
+
+  }
+}
\ No newline at end of file
diff --git a/src/main/scala/singleton/ops/impl/GeneralMacros.scala b/src/main/scala/singleton/ops/impl/GeneralMacros.scala
index 6679139b..e0495ff0 100644
--- a/src/main/scala/singleton/ops/impl/GeneralMacros.scala
+++ b/src/main/scala/singleton/ops/impl/GeneralMacros.scala
@@ -1,13 +1,17 @@
 package singleton.ops.impl
 import singleton.twoface.impl.TwoFaceAny
 
-import scala.reflect.macros.whitebox
+import scala.reflect.macros.{TypecheckException, whitebox}
 
 private object MacroCache {
   import scala.collection.mutable
   val cache = mutable.Map.empty[Any, Any]
   def get(key : Any) : Option[Any] = cache.get(key)
   def add[V <: Any](key : Any, value : V) : V = {cache += (key -> value); value}
+  private var opInterceptValue : Option[Any] = None
+  def setOpInterceptValue(value : Any) : Unit = opInterceptValue = Some(value)
+  def getOpInterceptValue : Any = opInterceptValue
+  def clearOpInterceptValue() : Unit = opInterceptValue = None
 }
 trait GeneralMacros {
   val c: whitebox.Context
@@ -261,7 +265,7 @@ trait GeneralMacros {
 
     def unapply(arg: CalcType): Option[Primitive] = Some(arg.primitive)
   }
-  case class CalcUnknown(tpe : Type, treeOption : Option[Tree]) extends Calc {
+  case class CalcUnknown(tpe : Type, treeOption : Option[Tree], opIntercept : Boolean) extends Calc {
     override val primitive: Primitive = Primitive.Unknown(tpe, "Unknown")
   }
   object NonLiteralCalc {
@@ -328,6 +332,19 @@ trait GeneralMacros {
       VerboseTraversal(s"${GREEN}${BOLD}caching${RESET} $k -> $value")
       value
     }
+    def setOpInterceptCalc(calc : Calc) : Unit = MacroCache.setOpInterceptValue(Left(calc))
+    def setOpInterceptError(msg : String) : Unit = MacroCache.setOpInterceptValue(Right(msg))
+    def clearOpInterceptCalc() : Unit = MacroCache.clearOpInterceptValue()
+    def getOpInterceptCalc : Option[Either[Calc, String]] = {
+      MacroCache.getOpInterceptValue.asInstanceOf[Option[Either[Calc, String]]] match {
+        case Some(Left(v)) => Some(Left(v match {
+          case lit : CalcLit => CalcLit(lit.value) //reconstruct internal literal tree
+          case nlit : CalcNLit => CalcNLit(nlit.primitive, deepCopyTree(nlit.tree))
+          case c => c
+        }))
+        case v => v
+      }
+    }
   }
   ////////////////////////////////////////////////////////////////////
 
@@ -456,7 +473,7 @@ trait GeneralMacros {
       def unapply(tp: Type): Option[Calc] = {
         tp match {
           case TypeRef(_, sym, ft :: tp :: _) if sym == opMacroSym && ft.typeSymbol == funcTypes.GetType =>
-            Some(CalcUnknown(tp, None))
+            Some(CalcUnknown(tp, None, opIntercept = false))
           case TypeRef(_, sym, args) if sym == opMacroSym =>
             VerboseTraversal(s"@@OpCalc@@\nTP: $tp\nRAW: ${showRaw(tp)}")
             val funcType = args.head.typeSymbol.asType
@@ -473,7 +490,7 @@ trait GeneralMacros {
                   case (funcTypes.ImplicitFound, _) =>
                     setUncachingReason(1)
                     aValue match {
-                      case CalcUnknown(t, _) => try {
+                      case CalcUnknown(t, _, false) => try {
                         c.typecheck(q"implicitly[$t]")
                         Some(CalcLit(true))
                       } catch {
@@ -484,7 +501,7 @@ trait GeneralMacros {
                     }
                   case (funcTypes.EnumCount, _) =>
                     aValue match {
-                      case CalcUnknown(t, _) => Some(CalcLit(t.typeSymbol.asClass.knownDirectSubclasses.size))
+                      case CalcUnknown(t, _, false) => Some(CalcLit(t.typeSymbol.asClass.knownDirectSubclasses.size))
                       case _ => Some(CalcLit(0))
                     }
                   case (funcTypes.IsNat, _) =>
@@ -549,9 +566,10 @@ trait GeneralMacros {
                     }
 
                   case _ => //regular cases
-                    opCalc(funcType, aValue, bValue, cValue) match {
+                    opCalc(Some(tp), funcType, aValue, bValue, cValue) match {
                       case (res : CalcVal) => Some(res)
-                      case u @ CalcUnknown(_,Some(_)) => Some(u) //Accept unknown values with a tree
+                      case u @ CalcUnknown(_,Some(_), _) => Some(u) //Accept unknown values with a tree
+                      case oi @ CalcUnknown(_,_, true) => Some(oi) //Accept unknown op interception
                       case _ => None
                     }
                 }
@@ -575,7 +593,7 @@ trait GeneralMacros {
         case Some(t : CalcUnknown) => t
         case _ =>
           VerboseTraversal(s"@@Unknown@@\nTP: $tp\nRAW: ${showRaw(tp)}")
-          CalcUnknown(tp, None)
+          CalcUnknown(tp, None, opIntercept = false)
       }
     }
 
@@ -654,10 +672,11 @@ trait GeneralMacros {
   }
   ////////////////////////////////////////////////////////////////////////
 
-  def abort(msg: String, annotatedSym : Option[TypeSymbol] = defaultAnnotatedSym): Nothing = {
+  def abort(msg: String, annotatedSym : Option[TypeSymbol] = defaultAnnotatedSym, position : Position = c.enclosingPosition): Nothing = {
     VerboseTraversal(s"!!!!!!aborted with: $msg at $annotatedSym, $defaultAnnotatedSym")
     if (annotatedSym.isDefined) setAnnotation(msg, annotatedSym.get)
-    c.abort(c.enclosingPosition, msg)
+    CalcCache.setOpInterceptError(msg) //propagating the error in case this is an inner implicit call for OpIntercept
+    c.abort(position, msg)
   }
 
   def buildWarningMsgLoc : String = s"${c.enclosingPosition.source.path}:${c.enclosingPosition.line}:${c.enclosingPosition.column}"
@@ -734,11 +753,11 @@ trait GeneralMacros {
       case None =>
         q"""
           new $opTpe {
-            type OutWide = Option[$outTpe]
-            type Out = Option[$outTpe]
-            final val value: Option[$outTpe] = None
+            type OutWide = $outTpe
+            type Out = $outTpe
+            final lazy val value: $outTpe = throw new IllegalArgumentException("This operation does not produce a value.")
             final val isLiteral = false
-            final val valueWide: Option[$outTpe] = None
+            final lazy val valueWide: $outTpe = throw new IllegalArgumentException("This operation does not produce a value.")
           }
         """
     }
@@ -771,11 +790,11 @@ trait GeneralMacros {
     }
 
     opTree match {
-      case q"""{
-        $mods class $tpname[..$tparams] $ctorMods(...$paramss) extends ..$parents { $self => ..$opClsBlk }
-        $expr(...$exprss)
-      }""" => getOut(opClsBlk)
-      case _ => extractionFailed(opTree)
+        case q"""{
+                $mods class $tpname[..$tparams] $ctorMods(...$paramss) extends ..$parents { $self => ..$opClsBlk }
+                $expr(...$exprss)
+              }""" => getOut(opClsBlk)
+        case _ => extractionFailed(opTree)
     }
   }
 
@@ -881,27 +900,61 @@ trait GeneralMacros {
     val (typedTree, tpe) = GetArgTree(argIdx, lhs)
     VerboseTraversal(s"@@extractFromArg@@\nTP: $tpe\nRAW: ${showRaw(tpe)}\nTree: $typedTree")
     TypeCalc(tpe) match {
-      case _ : CalcUnknown => CalcUnknown(tpe, Some(c.untypecheck(typedTree)))
+      case _ : CalcUnknown => CalcUnknown(tpe, Some(c.untypecheck(typedTree)), opIntercept = false)
       case t : CalcNLit => CalcNLit(t, typedTree)
       case t => t
     }
   }
   ///////////////////////////////////////////////////////////////////////////////////////////
 
+  ///////////////////////////////////////////////////////////////////////////////////////////
+  // OpInterept Result Caching
+  ///////////////////////////////////////////////////////////////////////////////////////////
+  def cacheOpInterceptResult[Op, Out](implicit ev0: c.WeakTypeTag[Op], ev1: c.WeakTypeTag[Out]) : Tree  = {
+    val opTpe = weakTypeOf[Op]
+    val outTpe = weakTypeOf[Out]
+    val outCalc = TypeCalc(outTpe)
+    CalcCache.setOpInterceptCalc(outCalc)
+    q"new _root_.singleton.ops.OpIntercept.CacheResult[$opTpe, $outTpe]{}"
+  }
+  ///////////////////////////////////////////////////////////////////////////////////////////
+
   ///////////////////////////////////////////////////////////////////////////////////////////
   // Three operands (Generic)
   ///////////////////////////////////////////////////////////////////////////////////////////
   def materializeOpGen[F](implicit ev0: c.WeakTypeTag[F]): MaterializeOpAuxGen =
     new MaterializeOpAuxGen(weakTypeOf[F])
 
-  def opCalc(funcType : TypeSymbol, aCalc : => Calc, bCalc : => Calc, cCalc : => Calc) : Calc = {
+  def opCalc(opTpe : Option[Type], funcType : TypeSymbol, aCalc : => Calc, bCalc : => Calc, cCalc : => Calc) : Calc = {
     lazy val a = aCalc
     lazy val b = bCalc
     lazy val cArg = cCalc
     def unsupported() : Calc = {
-      (a, b) match {
-        case (aArg : CalcVal, bArg : CalcVal) => abort(s"Unsupported $funcType[$a, $b, $cArg]")
-        case _ => CalcUnknown(funcType.toType, None)
+      val cachedTpe = opTpe.get match {
+        case TypeRef(pre, sym, args) => c.internal.typeRef(pre, sym, List(funcType.toType, a.tpe, b.tpe, cArg.tpe))
+      }
+      //calling OpIntercept for the operation should cache the expected result if executed correctly
+      CalcCache.clearOpInterceptCalc()
+      val implicitlyTree = q"implicitly[_root_.singleton.ops.OpIntercept[$cachedTpe]]"
+      try {
+        c.typecheck(implicitlyTree, silent = false)
+        val cachedCalc = CalcCache.getOpInterceptCalc match {
+          case Some(calc) => calc
+          case None => abort("Missing a result cache for OpIntercept. Make sure you set `OpIntercept.CacheResult`")
+        }
+        CalcCache.clearOpInterceptCalc()
+        cachedCalc match {
+          case Left(t : CalcUnknown) =>
+            t.copy(opIntercept = true) //the unknown result must be marked properly so we allow it later
+          case Left(t) => t
+          case Right(msg) => abort(msg)
+        }
+      } catch {
+        case TypecheckException(pos, msg) =>
+          CalcCache.getOpInterceptCalc match {
+            case Some(Right(msg)) => abort(msg)
+            case _ => abort(s"Unsupported operation $cachedTpe")
+          }
       }
     }
 
@@ -1055,7 +1108,7 @@ trait GeneralMacros {
           }
         //directly using the java lib `require` resulted in compiler crash, so we use wrapped require instead
         case CalcNLit(Primitive.String, msg, _) => cArg match {
-          case CalcUnknown(t, _) if t.typeSymbol == symbolOf[Warn] =>
+          case CalcUnknown(t, _, false) if t.typeSymbol == symbolOf[Warn] =>
             CalcNLit(Primitive.Boolean, q"""{println(${buildWarningMsg(msg)}); false}""")
           case _ =>
             CalcNLit(Primitive.Boolean, q"{_root_.singleton.ops.impl._require(false, $msg); false}")
@@ -1065,7 +1118,7 @@ trait GeneralMacros {
       case CalcNLit(Primitive.Boolean, cond, _) => b match {
         //directly using the java lib `require` resulted in compiler crash, so we use wrapped require instead
         case CalcVal(msg : String, msgt) => cArg match {
-          case CalcUnknown(t, _) if t == symbolOf[Warn] =>
+          case CalcUnknown(t, _, false) if t == symbolOf[Warn] =>
             CalcNLit(Primitive.Boolean,
               q"""{
                   if ($cond) true
@@ -1366,7 +1419,7 @@ trait GeneralMacros {
       case funcTypes.PrefixMatch => PrefixMatch
       case funcTypes.ReplaceFirstMatch => ReplaceFirstMatch
       case funcTypes.ReplaceAllMatches => ReplaceAllMatches
-      case _ => abort(s"Unsupported $funcType[$a, $b, $cArg]")
+      case _ => unsupported()
     }
   }
 
@@ -1381,6 +1434,7 @@ trait GeneralMacros {
           else genOpTreeNat(opTpe, t)
         case (_, CalcLit(_, t)) => genOpTreeLit(opTpe, t)
         case (funcTypes.AcceptNonLiteral | funcTypes.GetArg, t : CalcNLit) => genOpTreeNLit(opTpe, t)
+        case (_, t @ CalcUnknown(_,_,true)) => genOpTreeUnknown(opTpe, t)
         case (funcTypes.GetArg, t : CalcUnknown) => genOpTreeUnknown(opTpe, t)
         case (_, t: CalcNLit) =>
           abort("Calculation has returned a non-literal type/value.\nTo accept non-literal values, use `AcceptNonLiteral[T]`.")
@@ -1500,7 +1554,7 @@ trait GeneralMacros {
         }
       }
 
-      val reqCalc = opCalc(funcTypes.Require, condCalc, msgCalc, CalcUnknown(typeOf[NoSym], None))
+      val reqCalc = opCalc(None, funcTypes.Require, condCalc, msgCalc, CalcUnknown(typeOf[NoSym], None, opIntercept = false))
 
       q"""
          (new $chkSym[$condTpe, $msgTpe, $chkArgTpe]($outTree.asInstanceOf[$outTpe]))
@@ -1566,7 +1620,7 @@ trait GeneralMacros {
         }
       }
 
-      val reqCalc = opCalc(funcTypes.Require, condCalc, msgCalc, CalcUnknown(typeOf[NoSym], None))
+      val reqCalc = opCalc(None, funcTypes.Require, condCalc, msgCalc, CalcUnknown(typeOf[NoSym], None, opIntercept = false))
 
       q"""
          (new $chkSym[$condTpe, $msgTpe, $chkArgTpe, $paramFaceTpe, $paramTpe]($outTree.asInstanceOf[$outTpe]))
diff --git a/src/main/scala/singleton/ops/impl/Op.scala b/src/main/scala/singleton/ops/impl/Op.scala
index 1544434e..ca400f7c 100644
--- a/src/main/scala/singleton/ops/impl/Op.scala
+++ b/src/main/scala/singleton/ops/impl/Op.scala
@@ -6,7 +6,11 @@ trait HasOut extends Any with Serializable {
   type Out
 }
 
-trait Op extends HasOut {
+trait HasOutValue extends HasOut {
+  val value : Out
+}
+
+trait Op extends HasOutValue {
   type OutWide
   type Out
   type OutNat <: Nat
@@ -29,7 +33,7 @@ protected[singleton] object OpGen {
   implicit def getValue[O <: Op, Out](o : Aux[O, Out]) : Out = o.value
 }
 
-trait OpCast[T, O <: Op] extends HasOut {type Out <: T; val value : Out}
+trait OpCast[T, O <: Op] extends HasOutValue {type Out <: T}
 
 
 @scala.annotation.implicitNotFound(msg = "Unable to prove type argument is a Nat.")
diff --git a/src/test/scala/singleton/ops/OpInterceptSpec.scala b/src/test/scala/singleton/ops/OpInterceptSpec.scala
new file mode 100644
index 00000000..cf772e25
--- /dev/null
+++ b/src/test/scala/singleton/ops/OpInterceptSpec.scala
@@ -0,0 +1,55 @@
+package singleton.ops
+
+import org.scalacheck.Properties
+import singleton.TestUtils._
+
+class OpInterceptSpec extends Properties("OpInterceptSpec") {
+
+  trait Vec[A0, A1]
+
+  implicit def `Vec+`[VL0, VL1, VR0, VR1, VO0, VO1](
+    implicit
+    opL : OpAuxGen[VL0 + VR0, VO0],
+    opR : OpAuxGen[VL1 + VR1, VO1],
+    result : OpIntercept.CacheResult[Vec[VL0, VL1] + Vec[VR0, VR1], Vec[VO0, VO1]]
+  ) : OpIntercept[Vec[VL0, VL1] + Vec[VR0, VR1]] = ???
+
+  implicit def `Vec==`[VL0, VL1, VR0, VR1, EqOut](
+    implicit
+    op : OpAuxGen[(VL0 == VR0) && (VL1 == VR1), EqOut],
+    result : OpIntercept.CacheResult[Vec[VL0, VL1] == Vec[VR0, VR1], EqOut]
+  ) : OpIntercept[Vec[VL0, VL1] == Vec[VR0, VR1]] = ???
+
+
+  property("Custom Vec Equality OK") = wellTyped {
+    val eq1 = shapeless.the[Vec[1, 2] == Vec[1, 2]]
+    val eq2 = shapeless.the[Vec[1, 2] == Vec[1, 1]]
+    implicitly[eq1.Out =:= true]
+    implicitly[eq2.Out =:= false]
+  }
+
+  property("Custom Vec Addition OK") = wellTyped {
+    val add2 = shapeless.the[Vec[1, 2] + Vec[3, 8]]
+    val add3 = shapeless.the[Vec[1, 2] + Vec[3, 8] + Vec[20, 20]]
+    implicitly[add2.Out =:= Vec[4, 10]]
+    implicitly[add3.Out =:= Vec[24, 30]]
+    val add23 = shapeless.the[add2.Out + add3.Out]
+    implicitly[add23.Out =:= Vec[28, 40]]
+  }
+
+  trait FibId
+  type Fib[P] = impl.OpMacro[FibId, P, 0, 0]
+  implicit def doFib[P, Out](
+    implicit
+    op : OpAuxGen[ITE[P == 0, 0, ITE[P == 1, 1, Fib[P - 1] + Fib[P - 2]]], Out],
+    result : OpIntercept.CacheResult[Fib[P], Out]
+  ) : OpIntercept[Fib[P]] = ???
+
+
+  property("Custom Fibonacci Op OK") = wellTyped {
+    val fib4 = shapeless.the[Fib[4]]
+    implicitly[fib4.Out =:= 3]
+    val fib10 = shapeless.the[Fib[10]]
+    implicitly[fib10.Out =:= 55]
+  }
+}

From a56f62b293df88e1f974d2b5b14af8565a7265b2 Mon Sep 17 00:00:00 2001
From: Oron Port <soronpo@tx.technion.ac.il>
Date: Sun, 21 Jun 2020 00:54:09 +0300
Subject: [PATCH 02/11] Assume caching to be unique to the running operation
 without specifying it

---
 src/main/scala/singleton/ops/OpIntercept.scala        | 9 ++++-----
 src/main/scala/singleton/ops/impl/GeneralMacros.scala | 5 ++---
 src/test/scala/singleton/ops/OpInterceptSpec.scala    | 6 +++---
 3 files changed, 9 insertions(+), 11 deletions(-)

diff --git a/src/main/scala/singleton/ops/OpIntercept.scala b/src/main/scala/singleton/ops/OpIntercept.scala
index d15362f1..32750534 100644
--- a/src/main/scala/singleton/ops/OpIntercept.scala
+++ b/src/main/scala/singleton/ops/OpIntercept.scala
@@ -8,14 +8,13 @@ trait OpIntercept[Op <: HasOut] extends HasOut
 object OpIntercept {
   type Aux[Op <: HasOut, Out0] = OpIntercept[Op]{type Out = Out0}
   @implicitNotFound("Failed to cache op ${Op} with result ${Out}")
-  trait CacheResult[Op <: HasOut, Out]
+  trait CacheResult[Out]
   object CacheResult {
-    implicit def call[Op <: HasOut, Out] : CacheResult[Op, Out] = macro Macro.materializeCacheResult[Op, Out]
+    implicit def call[Out] : CacheResult[Out] = macro Macro.materializeCacheResult[Out]
     final class Macro(val c: whitebox.Context) extends GeneralMacros {
       def materializeCacheResult[
-        Op : c.WeakTypeTag,
-        Out: c.WeakTypeTag,
-      ]: c.Tree = cacheOpInterceptResult[Op, Out]
+        Out: c.WeakTypeTag
+      ]: c.Tree = cacheOpInterceptResult[Out]
     }
 
   }
diff --git a/src/main/scala/singleton/ops/impl/GeneralMacros.scala b/src/main/scala/singleton/ops/impl/GeneralMacros.scala
index e0495ff0..c34a6a9e 100644
--- a/src/main/scala/singleton/ops/impl/GeneralMacros.scala
+++ b/src/main/scala/singleton/ops/impl/GeneralMacros.scala
@@ -910,12 +910,11 @@ trait GeneralMacros {
   ///////////////////////////////////////////////////////////////////////////////////////////
   // OpInterept Result Caching
   ///////////////////////////////////////////////////////////////////////////////////////////
-  def cacheOpInterceptResult[Op, Out](implicit ev0: c.WeakTypeTag[Op], ev1: c.WeakTypeTag[Out]) : Tree  = {
-    val opTpe = weakTypeOf[Op]
+  def cacheOpInterceptResult[Out](implicit ev0: c.WeakTypeTag[Out]) : Tree  = {
     val outTpe = weakTypeOf[Out]
     val outCalc = TypeCalc(outTpe)
     CalcCache.setOpInterceptCalc(outCalc)
-    q"new _root_.singleton.ops.OpIntercept.CacheResult[$opTpe, $outTpe]{}"
+    q"new _root_.singleton.ops.OpIntercept.CacheResult[$outTpe]{}"
   }
   ///////////////////////////////////////////////////////////////////////////////////////////
 
diff --git a/src/test/scala/singleton/ops/OpInterceptSpec.scala b/src/test/scala/singleton/ops/OpInterceptSpec.scala
index cf772e25..129a553b 100644
--- a/src/test/scala/singleton/ops/OpInterceptSpec.scala
+++ b/src/test/scala/singleton/ops/OpInterceptSpec.scala
@@ -11,13 +11,13 @@ class OpInterceptSpec extends Properties("OpInterceptSpec") {
     implicit
     opL : OpAuxGen[VL0 + VR0, VO0],
     opR : OpAuxGen[VL1 + VR1, VO1],
-    result : OpIntercept.CacheResult[Vec[VL0, VL1] + Vec[VR0, VR1], Vec[VO0, VO1]]
+    result : OpIntercept.CacheResult[Vec[VO0, VO1]]
   ) : OpIntercept[Vec[VL0, VL1] + Vec[VR0, VR1]] = ???
 
   implicit def `Vec==`[VL0, VL1, VR0, VR1, EqOut](
     implicit
     op : OpAuxGen[(VL0 == VR0) && (VL1 == VR1), EqOut],
-    result : OpIntercept.CacheResult[Vec[VL0, VL1] == Vec[VR0, VR1], EqOut]
+    result : OpIntercept.CacheResult[EqOut]
   ) : OpIntercept[Vec[VL0, VL1] == Vec[VR0, VR1]] = ???
 
 
@@ -42,7 +42,7 @@ class OpInterceptSpec extends Properties("OpInterceptSpec") {
   implicit def doFib[P, Out](
     implicit
     op : OpAuxGen[ITE[P == 0, 0, ITE[P == 1, 1, Fib[P - 1] + Fib[P - 2]]], Out],
-    result : OpIntercept.CacheResult[Fib[P], Out]
+    result : OpIntercept.CacheResult[Out]
   ) : OpIntercept[Fib[P]] = ???
 
 

From 14d3c9fb3a61b7035233fe93905adcf9474e33b4 Mon Sep 17 00:00:00 2001
From: Oron Port <soronpo@tx.technion.ac.il>
Date: Sun, 21 Jun 2020 00:58:54 +0300
Subject: [PATCH 03/11] fix calculation missing unknown return

---
 src/main/scala/singleton/ops/impl/GeneralMacros.scala | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/main/scala/singleton/ops/impl/GeneralMacros.scala b/src/main/scala/singleton/ops/impl/GeneralMacros.scala
index c34a6a9e..15ccebb4 100644
--- a/src/main/scala/singleton/ops/impl/GeneralMacros.scala
+++ b/src/main/scala/singleton/ops/impl/GeneralMacros.scala
@@ -952,7 +952,11 @@ trait GeneralMacros {
         case TypecheckException(pos, msg) =>
           CalcCache.getOpInterceptCalc match {
             case Some(Right(msg)) => abort(msg)
-            case _ => abort(s"Unsupported operation $cachedTpe")
+            case _ =>
+              (a, b) match {
+                case (_ : CalcVal, _ : CalcVal) => abort(s"Unsupported operation $cachedTpe")
+                case _ => CalcUnknown(funcType.toType, None, opIntercept = false)
+              }
           }
       }
     }

From 4daee9a29e597b9485ac23ba6d9b4f6838f97ca6 Mon Sep 17 00:00:00 2001
From: Oron Port <soronpo@tx.technion.ac.il>
Date: Sun, 21 Jun 2020 01:18:31 +0300
Subject: [PATCH 04/11] Added error cases coverage and fixed literals for
 pre-2.13 scala

---
 .../scala/singleton/ops/OpInterceptSpec.scala | 56 ++++++++++++++-----
 1 file changed, 41 insertions(+), 15 deletions(-)

diff --git a/src/test/scala/singleton/ops/OpInterceptSpec.scala b/src/test/scala/singleton/ops/OpInterceptSpec.scala
index 129a553b..f3a6932d 100644
--- a/src/test/scala/singleton/ops/OpInterceptSpec.scala
+++ b/src/test/scala/singleton/ops/OpInterceptSpec.scala
@@ -1,6 +1,7 @@
 package singleton.ops
 
 import org.scalacheck.Properties
+import shapeless.test.illTyped
 import singleton.TestUtils._
 
 class OpInterceptSpec extends Properties("OpInterceptSpec") {
@@ -22,34 +23,59 @@ class OpInterceptSpec extends Properties("OpInterceptSpec") {
 
 
   property("Custom Vec Equality OK") = wellTyped {
-    val eq1 = shapeless.the[Vec[1, 2] == Vec[1, 2]]
-    val eq2 = shapeless.the[Vec[1, 2] == Vec[1, 1]]
-    implicitly[eq1.Out =:= true]
-    implicitly[eq2.Out =:= false]
+    val eq1 = shapeless.the[Vec[W.`1`.T, W.`2`.T] == Vec[W.`1`.T, W.`2`.T]]
+    val eq2 = shapeless.the[Vec[W.`1`.T, W.`2`.T] == Vec[W.`1`.T, W.`1`.T]]
+    implicitly[eq1.Out =:= W.`true`.T]
+    implicitly[eq2.Out =:= W.`false`.T]
   }
 
   property("Custom Vec Addition OK") = wellTyped {
-    val add2 = shapeless.the[Vec[1, 2] + Vec[3, 8]]
-    val add3 = shapeless.the[Vec[1, 2] + Vec[3, 8] + Vec[20, 20]]
-    implicitly[add2.Out =:= Vec[4, 10]]
-    implicitly[add3.Out =:= Vec[24, 30]]
+    val add2 = shapeless.the[Vec[W.`1`.T, W.`2`.T] + Vec[W.`3`.T, W.`8`.T]]
+    val add3 = shapeless.the[Vec[W.`1`.T, W.`2`.T] + Vec[W.`3`.T, W.`8`.T] + Vec[W.`20`.T, W.`20`.T]]
+    implicitly[add2.Out =:= Vec[W.`4`.T, W.`10`.T]]
+    implicitly[add3.Out =:= Vec[W.`24`.T, W.`30`.T]]
     val add23 = shapeless.the[add2.Out + add3.Out]
-    implicitly[add23.Out =:= Vec[28, 40]]
+    implicitly[add23.Out =:= Vec[W.`28`.T, W.`40`.T]]
   }
 
   trait FibId
-  type Fib[P] = impl.OpMacro[FibId, P, 0, 0]
+  type Fib[P] = impl.OpMacro[FibId, P, W.`0`.T, W.`0`.T]
   implicit def doFib[P, Out](
     implicit
-    op : OpAuxGen[ITE[P == 0, 0, ITE[P == 1, 1, Fib[P - 1] + Fib[P - 2]]], Out],
+    op : OpAuxGen[ITE[P == W.`0`.T, W.`0`.T, ITE[P == W.`1`.T, W.`1`.T, Fib[P - W.`1`.T] + Fib[P - W.`2`.T]]], Out],
     result : OpIntercept.CacheResult[Out]
   ) : OpIntercept[Fib[P]] = ???
 
 
   property("Custom Fibonacci Op OK") = wellTyped {
-    val fib4 = shapeless.the[Fib[4]]
-    implicitly[fib4.Out =:= 3]
-    val fib10 = shapeless.the[Fib[10]]
-    implicitly[fib10.Out =:= 55]
+    val fib4 = shapeless.the[Fib[W.`4`.T]]
+    implicitly[fib4.Out =:= W.`3`.T]
+    val fib10 = shapeless.the[Fib[W.`10`.T]]
+    implicitly[fib10.Out =:= W.`55`.T]
   }
+
+
+  trait FooOpId
+  type FooOp[C, M] = impl.OpMacro[FooOpId, C, M, W.`0`.T]
+  implicit def FooOp[C, M](
+    implicit
+    r : RequireMsg[C, M],
+    result : OpIntercept.CacheResult[W.`true`.T]
+  ) : OpIntercept[FooOp[C, M]] = ???
+
+  property("Error Message Propagation") = wellTyped {
+    illTyped("""shapeless.the[FooOp[W.`false`.T, W.`"this is a test"`.T]]""", "this is a test")
+  }
+
+  trait BarOpId
+  type BarOp[C, M] = impl.OpMacro[BarOpId, C, M, W.`0`.T]
+  implicit def BarOp[C, M](
+    implicit
+    op : C + M
+  ) : OpIntercept[BarOp[C, M]] = ???
+
+  property("Missing Caching Error") = wellTyped {
+    illTyped("""shapeless.the[BarOp[W.`1`.T, W.`2`.T]]""", "Missing a result cache for OpIntercept. Make sure you set `OpIntercept.CacheResult`")
+  }
+  
 }

From b87d834fc9084c09d0fea5bddcb6753553fcc368 Mon Sep 17 00:00:00 2001
From: Oron Port <soronpo@tx.technion.ac.il>
Date: Sun, 21 Jun 2020 01:28:57 +0300
Subject: [PATCH 05/11] fix minor issue

---
 src/main/scala/singleton/ops/OpIntercept.scala | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/src/main/scala/singleton/ops/OpIntercept.scala b/src/main/scala/singleton/ops/OpIntercept.scala
index 32750534..a30f13e4 100644
--- a/src/main/scala/singleton/ops/OpIntercept.scala
+++ b/src/main/scala/singleton/ops/OpIntercept.scala
@@ -4,10 +4,9 @@ import impl._
 
 import scala.annotation.implicitNotFound
 
-trait OpIntercept[Op <: HasOut] extends HasOut
+trait OpIntercept[Op <: HasOut]
 object OpIntercept {
-  type Aux[Op <: HasOut, Out0] = OpIntercept[Op]{type Out = Out0}
-  @implicitNotFound("Failed to cache op ${Op} with result ${Out}")
+  @implicitNotFound("Failed to cache with result ${Out}")
   trait CacheResult[Out]
   object CacheResult {
     implicit def call[Out] : CacheResult[Out] = macro Macro.materializeCacheResult[Out]

From 6a278b7b8e4b09607a2284f2539222dc29f5c0de Mon Sep 17 00:00:00 2001
From: Oron Port <soronpo@tx.technion.ac.il>
Date: Mon, 22 Jun 2020 02:01:32 +0300
Subject: [PATCH 06/11] changed method of procuring the Out for OpIntercept

---
 .../scala/singleton/ops/OpIntercept.scala     | 17 +---
 .../singleton/ops/impl/GeneralMacros.scala    | 85 ++++++-------------
 .../scala/singleton/ops/OpInterceptSpec.scala | 41 +++------
 3 files changed, 42 insertions(+), 101 deletions(-)

diff --git a/src/main/scala/singleton/ops/OpIntercept.scala b/src/main/scala/singleton/ops/OpIntercept.scala
index a30f13e4..791b1dcd 100644
--- a/src/main/scala/singleton/ops/OpIntercept.scala
+++ b/src/main/scala/singleton/ops/OpIntercept.scala
@@ -1,20 +1,9 @@
 package singleton.ops
-import scala.reflect.macros.whitebox
 import impl._
 
 import scala.annotation.implicitNotFound
-
-trait OpIntercept[Op <: HasOut]
+@implicitNotFound("Missing an `OpIntercept` implicit for the operation ${Op}")
+trait OpIntercept[Op <: HasOut] extends HasOut
 object OpIntercept {
-  @implicitNotFound("Failed to cache with result ${Out}")
-  trait CacheResult[Out]
-  object CacheResult {
-    implicit def call[Out] : CacheResult[Out] = macro Macro.materializeCacheResult[Out]
-    final class Macro(val c: whitebox.Context) extends GeneralMacros {
-      def materializeCacheResult[
-        Out: c.WeakTypeTag
-      ]: c.Tree = cacheOpInterceptResult[Out]
-    }
-
-  }
+  type Aux[Op <: HasOut, Out0] = OpIntercept[Op]{type Out = Out0}
 }
\ No newline at end of file
diff --git a/src/main/scala/singleton/ops/impl/GeneralMacros.scala b/src/main/scala/singleton/ops/impl/GeneralMacros.scala
index 15ccebb4..f0320d7c 100644
--- a/src/main/scala/singleton/ops/impl/GeneralMacros.scala
+++ b/src/main/scala/singleton/ops/impl/GeneralMacros.scala
@@ -8,10 +8,10 @@ private object MacroCache {
   val cache = mutable.Map.empty[Any, Any]
   def get(key : Any) : Option[Any] = cache.get(key)
   def add[V <: Any](key : Any, value : V) : V = {cache += (key -> value); value}
-  private var opInterceptValue : Option[Any] = None
-  def setOpInterceptValue(value : Any) : Unit = opInterceptValue = Some(value)
-  def getOpInterceptValue : Any = opInterceptValue
-  def clearOpInterceptValue() : Unit = opInterceptValue = None
+  var errorCache : String = ""
+  def clearErrorCache() : Unit = errorCache = ""
+  def setErrorCache(msg : String) : Unit = errorCache = msg
+  def getErrorMessage : String = errorCache
 }
 trait GeneralMacros {
   val c: whitebox.Context
@@ -332,19 +332,6 @@ trait GeneralMacros {
       VerboseTraversal(s"${GREEN}${BOLD}caching${RESET} $k -> $value")
       value
     }
-    def setOpInterceptCalc(calc : Calc) : Unit = MacroCache.setOpInterceptValue(Left(calc))
-    def setOpInterceptError(msg : String) : Unit = MacroCache.setOpInterceptValue(Right(msg))
-    def clearOpInterceptCalc() : Unit = MacroCache.clearOpInterceptValue()
-    def getOpInterceptCalc : Option[Either[Calc, String]] = {
-      MacroCache.getOpInterceptValue.asInstanceOf[Option[Either[Calc, String]]] match {
-        case Some(Left(v)) => Some(Left(v match {
-          case lit : CalcLit => CalcLit(lit.value) //reconstruct internal literal tree
-          case nlit : CalcNLit => CalcNLit(nlit.primitive, deepCopyTree(nlit.tree))
-          case c => c
-        }))
-        case v => v
-      }
-    }
   }
   ////////////////////////////////////////////////////////////////////
 
@@ -566,7 +553,7 @@ trait GeneralMacros {
                     }
 
                   case _ => //regular cases
-                    opCalc(Some(tp), funcType, aValue, bValue, cValue) match {
+                    opCalc(funcType, aValue, bValue, cValue) match {
                       case (res : CalcVal) => Some(res)
                       case u @ CalcUnknown(_,Some(_), _) => Some(u) //Accept unknown values with a tree
                       case oi @ CalcUnknown(_,_, true) => Some(oi) //Accept unknown op interception
@@ -675,7 +662,7 @@ trait GeneralMacros {
   def abort(msg: String, annotatedSym : Option[TypeSymbol] = defaultAnnotatedSym, position : Position = c.enclosingPosition): Nothing = {
     VerboseTraversal(s"!!!!!!aborted with: $msg at $annotatedSym, $defaultAnnotatedSym")
     if (annotatedSym.isDefined) setAnnotation(msg, annotatedSym.get)
-    CalcCache.setOpInterceptError(msg) //propagating the error in case this is an inner implicit call for OpIntercept
+    MacroCache.setErrorCache(msg) //propagating the error in case this is an inner implicit call for OpIntercept
     c.abort(position, msg)
   }
 
@@ -907,56 +894,38 @@ trait GeneralMacros {
   }
   ///////////////////////////////////////////////////////////////////////////////////////////
 
-  ///////////////////////////////////////////////////////////////////////////////////////////
-  // OpInterept Result Caching
-  ///////////////////////////////////////////////////////////////////////////////////////////
-  def cacheOpInterceptResult[Out](implicit ev0: c.WeakTypeTag[Out]) : Tree  = {
-    val outTpe = weakTypeOf[Out]
-    val outCalc = TypeCalc(outTpe)
-    CalcCache.setOpInterceptCalc(outCalc)
-    q"new _root_.singleton.ops.OpIntercept.CacheResult[$outTpe]{}"
-  }
-  ///////////////////////////////////////////////////////////////////////////////////////////
-
   ///////////////////////////////////////////////////////////////////////////////////////////
   // Three operands (Generic)
   ///////////////////////////////////////////////////////////////////////////////////////////
   def materializeOpGen[F](implicit ev0: c.WeakTypeTag[F]): MaterializeOpAuxGen =
     new MaterializeOpAuxGen(weakTypeOf[F])
 
-  def opCalc(opTpe : Option[Type], funcType : TypeSymbol, aCalc : => Calc, bCalc : => Calc, cCalc : => Calc) : Calc = {
+  def opCalc(funcType : TypeSymbol, aCalc : => Calc, bCalc : => Calc, cCalc : => Calc) : Calc = {
     lazy val a = aCalc
     lazy val b = bCalc
     lazy val cArg = cCalc
     def unsupported() : Calc = {
-      val cachedTpe = opTpe.get match {
-        case TypeRef(pre, sym, args) => c.internal.typeRef(pre, sym, List(funcType.toType, a.tpe, b.tpe, cArg.tpe))
-      }
-      //calling OpIntercept for the operation should cache the expected result if executed correctly
-      CalcCache.clearOpInterceptCalc()
-      val implicitlyTree = q"implicitly[_root_.singleton.ops.OpIntercept[$cachedTpe]]"
+      val opMacroTpe = typeOf[OpMacro[_,_,_,_]].typeConstructor
+      val opTpe = appliedType(opMacroTpe, List(funcType.toType, a.tpe, b.tpe, cArg.tpe))
+      val interceptTpe = typeOf[singleton.ops.OpIntercept[_]].typeConstructor
+      MacroCache.clearErrorCache()
       try {
-        c.typecheck(implicitlyTree, silent = false)
-        val cachedCalc = CalcCache.getOpInterceptCalc match {
-          case Some(calc) => calc
-          case None => abort("Missing a result cache for OpIntercept. Make sure you set `OpIntercept.CacheResult`")
-        }
-        CalcCache.clearOpInterceptCalc()
-        cachedCalc match {
-          case Left(t : CalcUnknown) =>
-            t.copy(opIntercept = true) //the unknown result must be marked properly so we allow it later
-          case Left(t) => t
-          case Right(msg) => abort(msg)
+        val itree = c.inferImplicitValue (
+          appliedType(interceptTpe, List(opTpe)),
+          silent = false
+        )
+        TypeCalc(itree.tpe.decls.head.info) match {
+          case t : CalcUnknown => t.copy(opIntercept = true) //the unknown result must be marked properly so we allow it later
+          case t => t
         }
       } catch {
-        case TypecheckException(pos, msg) =>
-          CalcCache.getOpInterceptCalc match {
-            case Some(Right(msg)) => abort(msg)
-            case _ =>
-              (a, b) match {
-                case (_ : CalcVal, _ : CalcVal) => abort(s"Unsupported operation $cachedTpe")
-                case _ => CalcUnknown(funcType.toType, None, opIntercept = false)
-              }
+        case TypecheckException(_, _) =>
+          MacroCache.getErrorMessage match {
+            case m if m.nonEmpty => abort(m)
+            case _ => (a, b) match {
+              case (_ : CalcVal, _ : CalcVal) => abort(s"Unsupported operation $opTpe")
+              case _ => CalcUnknown(funcType.toType, None, opIntercept = false)
+            }
           }
       }
     }
@@ -1557,7 +1526,7 @@ trait GeneralMacros {
         }
       }
 
-      val reqCalc = opCalc(None, funcTypes.Require, condCalc, msgCalc, CalcUnknown(typeOf[NoSym], None, opIntercept = false))
+      val reqCalc = opCalc(funcTypes.Require, condCalc, msgCalc, CalcUnknown(typeOf[NoSym], None, opIntercept = false))
 
       q"""
          (new $chkSym[$condTpe, $msgTpe, $chkArgTpe]($outTree.asInstanceOf[$outTpe]))
@@ -1623,7 +1592,7 @@ trait GeneralMacros {
         }
       }
 
-      val reqCalc = opCalc(None, funcTypes.Require, condCalc, msgCalc, CalcUnknown(typeOf[NoSym], None, opIntercept = false))
+      val reqCalc = opCalc(funcTypes.Require, condCalc, msgCalc, CalcUnknown(typeOf[NoSym], None, opIntercept = false))
 
       q"""
          (new $chkSym[$condTpe, $msgTpe, $chkArgTpe, $paramFaceTpe, $paramTpe]($outTree.asInstanceOf[$outTpe]))
diff --git a/src/test/scala/singleton/ops/OpInterceptSpec.scala b/src/test/scala/singleton/ops/OpInterceptSpec.scala
index f3a6932d..2d237f0c 100644
--- a/src/test/scala/singleton/ops/OpInterceptSpec.scala
+++ b/src/test/scala/singleton/ops/OpInterceptSpec.scala
@@ -8,19 +8,16 @@ class OpInterceptSpec extends Properties("OpInterceptSpec") {
 
   trait Vec[A0, A1]
 
-  implicit def `Vec+`[VL0, VL1, VR0, VR1, VO0, VO1](
+  implicit def `Vec+`[VL0, VL1, VR0, VR1](
     implicit
-    opL : OpAuxGen[VL0 + VR0, VO0],
-    opR : OpAuxGen[VL1 + VR1, VO1],
-    result : OpIntercept.CacheResult[Vec[VO0, VO1]]
-  ) : OpIntercept[Vec[VL0, VL1] + Vec[VR0, VR1]] = ???
+    opL : VL0 + VR0,
+    opR : VL1 + VR1
+  ) : OpIntercept.Aux[Vec[VL0, VL1] + Vec[VR0, VR1], Vec[opL.Out, opR.Out]] = ???
 
-  implicit def `Vec==`[VL0, VL1, VR0, VR1, EqOut](
+  implicit def `Vec==`[VL0, VL1, VR0, VR1](
     implicit
-    op : OpAuxGen[(VL0 == VR0) && (VL1 == VR1), EqOut],
-    result : OpIntercept.CacheResult[EqOut]
-  ) : OpIntercept[Vec[VL0, VL1] == Vec[VR0, VR1]] = ???
-
+    op : (VL0 == VR0) && (VL1 == VR1)
+  ) : OpIntercept.Aux[Vec[VL0, VL1] == Vec[VR0, VR1], op.Out] = ???
 
   property("Custom Vec Equality OK") = wellTyped {
     val eq1 = shapeless.the[Vec[W.`1`.T, W.`2`.T] == Vec[W.`1`.T, W.`2`.T]]
@@ -38,14 +35,13 @@ class OpInterceptSpec extends Properties("OpInterceptSpec") {
     implicitly[add23.Out =:= Vec[W.`28`.T, W.`40`.T]]
   }
 
+
   trait FibId
   type Fib[P] = impl.OpMacro[FibId, P, W.`0`.T, W.`0`.T]
-  implicit def doFib[P, Out](
+  implicit def doFib[P](
     implicit
-    op : OpAuxGen[ITE[P == W.`0`.T, W.`0`.T, ITE[P == W.`1`.T, W.`1`.T, Fib[P - W.`1`.T] + Fib[P - W.`2`.T]]], Out],
-    result : OpIntercept.CacheResult[Out]
-  ) : OpIntercept[Fib[P]] = ???
-
+    op : ITE[P == W.`0`.T, W.`0`.T, ITE[P == W.`1`.T, W.`1`.T, Fib[P - W.`1`.T] + Fib[P - W.`2`.T]]]
+  ) : OpIntercept.Aux[Fib[P], op.Out] = ???
 
   property("Custom Fibonacci Op OK") = wellTyped {
     val fib4 = shapeless.the[Fib[W.`4`.T]]
@@ -59,23 +55,10 @@ class OpInterceptSpec extends Properties("OpInterceptSpec") {
   type FooOp[C, M] = impl.OpMacro[FooOpId, C, M, W.`0`.T]
   implicit def FooOp[C, M](
     implicit
-    r : RequireMsg[C, M],
-    result : OpIntercept.CacheResult[W.`true`.T]
+    r : RequireMsg[C, M]
   ) : OpIntercept[FooOp[C, M]] = ???
 
   property("Error Message Propagation") = wellTyped {
     illTyped("""shapeless.the[FooOp[W.`false`.T, W.`"this is a test"`.T]]""", "this is a test")
   }
-
-  trait BarOpId
-  type BarOp[C, M] = impl.OpMacro[BarOpId, C, M, W.`0`.T]
-  implicit def BarOp[C, M](
-    implicit
-    op : C + M
-  ) : OpIntercept[BarOp[C, M]] = ???
-
-  property("Missing Caching Error") = wellTyped {
-    illTyped("""shapeless.the[BarOp[W.`1`.T, W.`2`.T]]""", "Missing a result cache for OpIntercept. Make sure you set `OpIntercept.CacheResult`")
-  }
-  
 }

From aa14d5edb80677859c6dbd31ec33f8139cc50d85 Mon Sep 17 00:00:00 2001
From: Oron Port <soronpo@tx.technion.ac.il>
Date: Mon, 22 Jun 2020 02:37:37 +0300
Subject: [PATCH 07/11] add value support

---
 .../scala/singleton/ops/OpIntercept.scala     |  2 +-
 .../singleton/ops/impl/GeneralMacros.scala    |  2 +-
 .../scala/singleton/ops/OpInterceptSpec.scala | 23 +++++++++++++------
 3 files changed, 18 insertions(+), 9 deletions(-)

diff --git a/src/main/scala/singleton/ops/OpIntercept.scala b/src/main/scala/singleton/ops/OpIntercept.scala
index 791b1dcd..35a03d64 100644
--- a/src/main/scala/singleton/ops/OpIntercept.scala
+++ b/src/main/scala/singleton/ops/OpIntercept.scala
@@ -3,7 +3,7 @@ import impl._
 
 import scala.annotation.implicitNotFound
 @implicitNotFound("Missing an `OpIntercept` implicit for the operation ${Op}")
-trait OpIntercept[Op <: HasOut] extends HasOut
+trait OpIntercept[Op <: HasOut] extends HasOutValue
 object OpIntercept {
   type Aux[Op <: HasOut, Out0] = OpIntercept[Op]{type Out = Out0}
 }
\ No newline at end of file
diff --git a/src/main/scala/singleton/ops/impl/GeneralMacros.scala b/src/main/scala/singleton/ops/impl/GeneralMacros.scala
index f0320d7c..18687b56 100644
--- a/src/main/scala/singleton/ops/impl/GeneralMacros.scala
+++ b/src/main/scala/singleton/ops/impl/GeneralMacros.scala
@@ -915,7 +915,7 @@ trait GeneralMacros {
           silent = false
         )
         TypeCalc(itree.tpe.decls.head.info) match {
-          case t : CalcUnknown => t.copy(opIntercept = true) //the unknown result must be marked properly so we allow it later
+          case t : CalcUnknown => t.copy(treeOption = Some(c.untypecheck(q"$itree.value")),opIntercept = true) //the unknown result must be marked properly so we allow it later
           case t => t
         }
       } catch {
diff --git a/src/test/scala/singleton/ops/OpInterceptSpec.scala b/src/test/scala/singleton/ops/OpInterceptSpec.scala
index 2d237f0c..9a1bd173 100644
--- a/src/test/scala/singleton/ops/OpInterceptSpec.scala
+++ b/src/test/scala/singleton/ops/OpInterceptSpec.scala
@@ -6,33 +6,41 @@ import singleton.TestUtils._
 
 class OpInterceptSpec extends Properties("OpInterceptSpec") {
 
-  trait Vec[A0, A1]
+  trait Vec[A0, A1] {
+    def show(implicit a0 : ValueOf[A0], a1 : ValueOf[A1]) : String = s"Vec[${valueOf[A0]}, ${valueOf[A1]}]"
+  }
 
   implicit def `Vec+`[VL0, VL1, VR0, VR1](
     implicit
     opL : VL0 + VR0,
     opR : VL1 + VR1
-  ) : OpIntercept.Aux[Vec[VL0, VL1] + Vec[VR0, VR1], Vec[opL.Out, opR.Out]] = ???
+  ) : OpIntercept.Aux[Vec[VL0, VL1] + Vec[VR0, VR1], Vec[opL.Out, opR.Out]] = //Vec is not a singleton value, so we need to instantiate OpIntercept
+    new OpIntercept[Vec[VL0, VL1] + Vec[VR0, VR1]] {
+      type Out = Vec[opL.Out, opR.Out]
+      val value : Out = new Vec[opL.Out, opR.Out]{}
+    }
 
   implicit def `Vec==`[VL0, VL1, VR0, VR1](
     implicit
     op : (VL0 == VR0) && (VL1 == VR1)
-  ) : OpIntercept.Aux[Vec[VL0, VL1] == Vec[VR0, VR1], op.Out] = ???
+  ) : OpIntercept.Aux[Vec[VL0, VL1] == Vec[VR0, VR1], op.Out] = ??? //No need to instantiate when a singleton value is returned
 
-  property("Custom Vec Equality OK") = wellTyped {
+  property("Custom Vec Equality OK") = {
     val eq1 = shapeless.the[Vec[W.`1`.T, W.`2`.T] == Vec[W.`1`.T, W.`2`.T]]
     val eq2 = shapeless.the[Vec[W.`1`.T, W.`2`.T] == Vec[W.`1`.T, W.`1`.T]]
     implicitly[eq1.Out =:= W.`true`.T]
     implicitly[eq2.Out =:= W.`false`.T]
+    eq1.value == true
   }
 
-  property("Custom Vec Addition OK") = wellTyped {
+  property("Custom Vec Addition OK") = {
     val add2 = shapeless.the[Vec[W.`1`.T, W.`2`.T] + Vec[W.`3`.T, W.`8`.T]]
     val add3 = shapeless.the[Vec[W.`1`.T, W.`2`.T] + Vec[W.`3`.T, W.`8`.T] + Vec[W.`20`.T, W.`20`.T]]
     implicitly[add2.Out =:= Vec[W.`4`.T, W.`10`.T]]
     implicitly[add3.Out =:= Vec[W.`24`.T, W.`30`.T]]
     val add23 = shapeless.the[add2.Out + add3.Out]
     implicitly[add23.Out =:= Vec[W.`28`.T, W.`40`.T]]
+    add2.value.show == "Vec[4, 10]"
   }
 
 
@@ -41,13 +49,14 @@ class OpInterceptSpec extends Properties("OpInterceptSpec") {
   implicit def doFib[P](
     implicit
     op : ITE[P == W.`0`.T, W.`0`.T, ITE[P == W.`1`.T, W.`1`.T, Fib[P - W.`1`.T] + Fib[P - W.`2`.T]]]
-  ) : OpIntercept.Aux[Fib[P], op.Out] = ???
+  ) : OpIntercept.Aux[Fib[P], op.Out] = ??? //No need to instantiate when a singleton value is returned
 
-  property("Custom Fibonacci Op OK") = wellTyped {
+  property("Custom Fibonacci Op OK") = {
     val fib4 = shapeless.the[Fib[W.`4`.T]]
     implicitly[fib4.Out =:= W.`3`.T]
     val fib10 = shapeless.the[Fib[W.`10`.T]]
     implicitly[fib10.Out =:= W.`55`.T]
+    fib10.value == 55
   }
 
 

From 6e5868a8ab156d7c58301d43dfdbf21f04f0d002 Mon Sep 17 00:00:00 2001
From: Oron Port <soronpo@tx.technion.ac.il>
Date: Mon, 22 Jun 2020 02:46:37 +0300
Subject: [PATCH 08/11] fixed test-case for scala pre-2.13

---
 src/test/scala/singleton/ops/OpInterceptSpec.scala | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/scala/singleton/ops/OpInterceptSpec.scala b/src/test/scala/singleton/ops/OpInterceptSpec.scala
index 9a1bd173..883bda45 100644
--- a/src/test/scala/singleton/ops/OpInterceptSpec.scala
+++ b/src/test/scala/singleton/ops/OpInterceptSpec.scala
@@ -7,7 +7,7 @@ import singleton.TestUtils._
 class OpInterceptSpec extends Properties("OpInterceptSpec") {
 
   trait Vec[A0, A1] {
-    def show(implicit a0 : ValueOf[A0], a1 : ValueOf[A1]) : String = s"Vec[${valueOf[A0]}, ${valueOf[A1]}]"
+    def show(implicit a0 : Id[A0], a1 : Id[A1]) : String = s"Vec[${a0.value}, ${a1.value}]"
   }
 
   implicit def `Vec+`[VL0, VL1, VR0, VR1](

From a9db1b8447a6a5c1b2df48cdfdebae3824bc5c47 Mon Sep 17 00:00:00 2001
From: Erik Erlandson <eerlands@redhat.com>
Date: Tue, 23 Jun 2020 17:48:07 -0700
Subject: [PATCH 09/11] initial draft for Rational support

---
 src/main/scala/singleton/ops/rational.scala | 222 ++++++++++++++++++++
 1 file changed, 222 insertions(+)
 create mode 100644 src/main/scala/singleton/ops/rational.scala

diff --git a/src/main/scala/singleton/ops/rational.scala b/src/main/scala/singleton/ops/rational.scala
new file mode 100644
index 00000000..7d0728ee
--- /dev/null
+++ b/src/main/scala/singleton/ops/rational.scala
@@ -0,0 +1,222 @@
+package singleton.ops
+
+import singleton.ops._
+import singleton.ops.impl.{OpCast, OpGen, OpInt, OpMacro}
+
+object rational {
+  /** Represents a rational number
+    *
+    * @tparam N the numerator
+    * @tparam D the denominator
+    */
+  trait Rational[N, D] {
+    // currently only XInt is supported,
+    // other types such as XLong could be added with additional implicit rules
+    def n(implicit nv: Id[N]): nv.Out = nv.value
+    def d(implicit dv: Id[D]): dv.Out = dv.value
+    def show(implicit nv: Id[N], dv: Id[D]): String = s"Rational(${n}, ${d})"
+  }
+
+  private trait IsRationalImpl[P] {
+    type Out
+  }
+  private trait IsRationalImplDefault {
+    type Aux[P, O] = IsRationalImpl[P] { type Out = O }
+    implicit def isRationalFalse[P]: Aux[P, false] =
+      new IsRationalImpl[P] {
+        type Out = false
+      }
+  }
+  private object IsRationalImpl extends IsRationalImplDefault {
+    implicit def isRationalTrue[N, D]: Aux[Rational[N, D], true] =
+      new IsRationalImpl[Rational[N, D]] {
+        type Out = true
+      }    
+  }
+
+  trait IsRationalOpId
+  type IsRational[P] = OpMacro[IsRationalOpId, P, W.`0`.T, W.`0`.T]
+
+  implicit def doIsRational[P, T](implicit
+      tst: IsRationalImpl.Aux[P, T]): OpIntercept.Aux[IsRational[P], T] = ???
+
+  trait ToRationalOpId
+  type ToRational[P] = OpMacro[ToRationalOpId, P, W.`0`.T, W.`0`.T]
+
+  implicit def toRationalFromRat[
+      N <: XInt, D <: XInt,
+      SN <: XInt, SD <: XInt](
+    implicit
+      sim: OpGen.Aux[Simplify[Rational[N, D]], Rational[SN, SD]]
+    ): OpIntercept.Aux[ToRational[Rational[N, D]], Rational[SN, SD]] =
+    new OpIntercept[ToRational[Rational[N, D]]] {
+      type Out = Rational[SN, SD]
+      val value: Out = new Rational[SN, SD] {}
+    }
+
+  implicit def toRationalFromInt[N <: XInt]: OpIntercept.Aux[ToRational[N], Rational[N, W.`1`.T]] =
+    new OpIntercept[ToRational[N]] {
+      type Out = Rational[N, W.`1`.T]
+      val value: Out = new Rational[N, W.`1`.T] {}
+    }
+
+  implicit def doRationalNegate[N <: XInt, D <: XInt, NN <: XInt](implicit
+      neg: OpInt.Aux[Negate[N], NN]): OpIntercept.Aux[Negate[Rational[N, D]], Rational[NN, D]] =
+    new OpIntercept[Negate[Rational[N, D]]] {
+      type Out = Rational[NN, D]
+      val value: Out = new Rational[NN, D] {}
+    }
+
+  implicit def doRationalAdd[
+      LHS, RHS,
+      LN <: XInt, LD <: XInt,
+      RN <: XInt, RD <: XInt,
+      LNRD <: XInt, RNLD <: XInt,
+      N <: XInt, D <: XInt,
+      SN <: XInt, SD <: XInt](
+    implicit
+      rat: Require[IsRational[LHS] || IsRational[RHS]],
+      lhs: OpGen.Aux[ToRational[LHS], Rational[LN, LD]],
+      rhs: OpGen.Aux[ToRational[RHS], Rational[RN, RD]],
+      ev0: OpInt.Aux[LN * RD, LNRD],
+      ev1: OpInt.Aux[RN * LD, RNLD],
+      ev2: OpInt.Aux[LNRD + RNLD, N],
+      ev3: OpInt.Aux[LD * RD, D],
+      ev4: OpGen.Aux[Simplify[Rational[N, D]], Rational[SN, SD]],
+    ): OpIntercept.Aux[LHS + RHS, Rational[SN, SD]] =
+  new OpIntercept[LHS + RHS] {
+    type Out = Rational[SN, SD]
+    val value: Out = new Rational[SN, SD] {}
+  }
+
+  implicit def doRationalSubtract[
+      LHS, RHS,
+      LN <: XInt, LD <: XInt,
+      RN <: XInt, RD <: XInt, RNN <: XInt,
+      SN <: XInt, SD <: XInt](
+    implicit
+      rat: Require[IsRational[LHS] || IsRational[RHS]],
+      lhs: OpGen.Aux[ToRational[LHS], Rational[LN, LD]],
+      rhs: OpGen.Aux[ToRational[RHS], Rational[RN, RD]],
+      neg: OpInt.Aux[Negate[RN], RNN],
+      add: OpGen.Aux[Rational[LN, LD] + Rational[RNN, RD], Rational[SN, SD]]
+    ): OpIntercept.Aux[LHS - RHS, Rational[SN, SD]] =
+  new OpIntercept[LHS - RHS] {
+    type Out = Rational[SN, SD]
+    val value: Out = new Rational[SN, SD] {}
+  }
+
+  implicit def doRationalMultiply[
+      LHS, RHS,
+      LN <: XInt, LD <: XInt,
+      RN <: XInt, RD <: XInt,
+      N <: XInt, D <: XInt,
+      SN <: XInt, SD <: XInt](
+    implicit
+      rat: Require[IsRational[LHS] || IsRational[RHS]],
+      lhs: OpGen.Aux[ToRational[LHS], Rational[LN, LD]],
+      rhs: OpGen.Aux[ToRational[RHS], Rational[RN, RD]],
+      ev0: OpInt.Aux[LN * RN, N],
+      ev1: OpInt.Aux[LD * RD, D],
+      ev2: OpGen.Aux[Simplify[Rational[N, D]], Rational[SN, SD]]
+    ): OpIntercept.Aux[LHS * RHS, Rational[SN, SD]] =
+    new OpIntercept[LHS * RHS] {
+      type Out = Rational[SN, SD]
+      val value: Out = new Rational[SN, SD] {}
+    }
+
+  implicit def doRationalDivide[
+      LHS, RHS,
+      LN <: XInt, LD <: XInt,
+      RN <: XInt, RD <: XInt,
+      SN <: XInt, SD <: XInt](
+    implicit
+      rat: Require[IsRational[LHS] || IsRational[RHS]],
+      lhs: OpGen.Aux[ToRational[LHS], Rational[LN, LD]],
+      rhs: OpGen.Aux[ToRational[RHS], Rational[RN, RD]],
+      mul: OpGen.Aux[Rational[LN, LD] * Rational[RD, RN], Rational[SN, SD]]
+    ): OpIntercept.Aux[LHS / RHS, Rational[SN, SD]] =
+    new OpIntercept[LHS / RHS] {
+      type Out = Rational[SN, SD]
+      val value: Out = new Rational[SN, SD] {}
+    }
+
+  trait GCDOpId
+  type GCD[A, B] = OpMacro[GCDOpId, A, B, W.`0`.T]
+
+  private type gcdErrorMsg = W.`"GCD requires positive integers"`.T
+
+  implicit def doGCDforBasisCase[A <: XInt, B <: XInt, Rem <: XInt](implicit
+      ev0: RequireMsg[(A >= B) && (B > W.`0`.T), gcdErrorMsg],
+      ev1: OpInt.Aux[A % B, Rem],
+      ev2: Require[Rem == W.`0`.T]): OpIntercept.Aux[GCD[A, B], B] = ???
+
+  implicit def doGCDforAgeB[A <: XInt, B <: XInt,Rem <: XInt, D <: XInt](implicit
+      ev0: RequireMsg[(A >= B) && (B > W.`0`.T), gcdErrorMsg],
+      ev1: OpInt.Aux[A % B, Rem],
+      ev2: Require[Rem != W.`0`.T],
+      ev3: OpInt.Aux[GCD[B, Rem], D]): OpIntercept.Aux[GCD[A, B], D] = ???
+
+  implicit def doGCDforAltB[A <: XInt, B <: XInt, Rem <: XInt, D <: XInt](implicit
+      ev0: RequireMsg[(A < B) && (A > W.`0`.T), gcdErrorMsg],
+      ev1: OpInt.Aux[GCD[B, A], D]): OpIntercept.Aux[GCD[A, B], D] = ???
+
+  trait SimplifyOpId
+  type Simplify[F] = OpMacro[SimplifyOpId, F, W.`0`.T, W.`0`.T]
+
+  private type simplifyErrorMsg = W.`"Simplify requires non-zero denominator"`.T
+
+  implicit def doSimplifyPositive[
+      N <: XInt, D <: XInt,
+      C <: XInt,
+      SN <: XInt, SD <: XInt](
+    implicit
+      ev0: RequireMsg[(N > W.`0`.T) && (D > W.`0`.T), simplifyErrorMsg],
+      gcd: OpInt.Aux[GCD[N, D], C],
+      n: OpInt.Aux[N / C, SN],
+      d: OpInt.Aux[D / C, SD]
+    ): OpIntercept.Aux[Simplify[Rational[N, D]], Rational[SN, SD]] =
+    new OpIntercept[Simplify[Rational[N, D]]] {
+      type Out = Rational[SN, SD]
+      val value = new Rational[SN, SD] {}
+    }
+
+  implicit def doSimplifyNegative[
+      N <: XInt, D <: XInt,
+      F <: Rational[_, _],
+      SNF <: Rational[_, _],
+      SN <: XInt,  SD <: XInt](
+    implicit
+      ev0: RequireMsg[(N < W.`0`.T) && (D > W.`0`.T), simplifyErrorMsg],
+      ev1: OpGen.Aux[Negate[Rational[N, D]], F],
+      ev2: OpGen.Aux[Simplify[F], SNF],
+      ev3: OpGen.Aux[Negate[SNF], Rational[SN, SD]]
+    ): OpIntercept.Aux[Simplify[Rational[N, D]], Rational[SN, SD]] =
+    new OpIntercept[Simplify[Rational[N, D]]] {
+      type Out = Rational[SN, SD]
+      val value = new Rational[SN, SD] {}
+    }
+
+  implicit def doSimplifyZero[D <: XInt](implicit
+      nz: RequireMsg[D > W.`0`.T, simplifyErrorMsg]
+    ): OpIntercept.Aux[Simplify[Rational[W.`0`.T, D]], Rational[W.`0`.T, W.`1`.T]] =
+    new OpIntercept[Simplify[Rational[W.`0`.T, D]]] {
+      type Out = Rational[W.`0`.T, W.`1`.T]
+      val value = new Rational[W.`0`.T, W.`1`.T] {}
+    }
+
+  implicit def doSimplifyNegDenom[
+      N <: XInt, D <: XInt,
+      NN <: XInt, ND <: XInt,
+      SN <: XInt, SD <: XInt](
+    implicit
+      bn: RequireMsg[D < W.`0`.T, simplifyErrorMsg],
+      nn: OpInt.Aux[Negate[N], NN],
+      nd: OpInt.Aux[Negate[D], ND],
+      sf: OpGen.Aux[Simplify[Rational[NN, ND]], Rational[SN, SD]]
+    ): OpIntercept.Aux[Simplify[Rational[N, D]], Rational[SN, SD]] =
+    new OpIntercept[Simplify[Rational[N, D]]] {
+      type Out = Rational[SN, SD]
+      val value = new Rational[SN, SD] {}
+    }
+}

From 14abaa44fc0a9a24002b46957c8aa7efdfca7f04 Mon Sep 17 00:00:00 2001
From: Erik Erlandson <eerlands@redhat.com>
Date: Wed, 24 Jun 2020 17:42:23 -0700
Subject: [PATCH 10/11] remove type bounds for GCD

---
 src/main/scala/singleton/ops/rational.scala | 107 +++++++++++++-------
 1 file changed, 72 insertions(+), 35 deletions(-)

diff --git a/src/main/scala/singleton/ops/rational.scala b/src/main/scala/singleton/ops/rational.scala
index 7d0728ee..75007587 100644
--- a/src/main/scala/singleton/ops/rational.scala
+++ b/src/main/scala/singleton/ops/rational.scala
@@ -17,6 +17,42 @@ object rational {
     def show(implicit nv: Id[N], dv: Id[D]): String = s"Rational(${n}, ${d})"
   }
 
+  trait AlignTypeOpId
+  type AlignType[V, P] = OpMacro[AlignTypeOpId, V, P, W.`0`.T]
+  implicit def doAlignTypeInt[V, P <: XInt](implicit
+      v: ToInt[V]): OpIntercept.Aux[AlignType[V, P], v.Out] = ???
+  implicit def doAlignTypeLong[V, P <: XLong](implicit
+      v: ToLong[V]): OpIntercept.Aux[AlignType[V, P], v.Out] = ???
+  implicit def doAlignTypeFloat[V, P <: XFloat](implicit
+      v: ToFloat[V]): OpIntercept.Aux[AlignType[V, P], v.Out] = ???
+  implicit def doAlignTypeDouble[V, P <: XDouble](implicit
+      v: ToDouble[V]): OpIntercept.Aux[AlignType[V, P], v.Out] = ???
+
+  trait IsPositiveOpId
+  type IsPositive[P] = OpMacro[IsPositiveOpId, P, W.`0`.T, W.`0`.T]
+  implicit def doIsPositive[P](implicit
+      tst: P > AlignType[0, P]): OpIntercept.Aux[IsPositive[P], tst.Out] = ???
+
+  trait IsNegativeOpId
+  type IsNegative[P] = OpMacro[IsNegativeOpId, P, W.`0`.T, W.`0`.T]
+  implicit def doIsNegative[P](implicit
+      tst: P < AlignType[0, P]): OpIntercept.Aux[IsNegative[P], tst.Out] = ???
+
+  trait IsZeroOpId
+  type IsZero[P] = OpMacro[IsZeroOpId, P, W.`0`.T, W.`0`.T]
+  implicit def doIsZero[P](implicit
+      tst: P == AlignType[0, P]): OpIntercept.Aux[IsZero[P], tst.Out] = ???
+
+  trait IsNonZeroOpId
+  type IsNonZero[P] = OpMacro[IsNonZeroOpId, P, W.`0`.T, W.`0`.T]
+  implicit def doIsNonZero[P](implicit
+      tst: P != AlignType[0, P]): OpIntercept.Aux[IsNonZero[P], tst.Out] = ???
+
+  trait IsIntegralOpId
+  type IsIntegral[P] = OpMacro[IsIntegralOpId, P, W.`0`.T, W.`0`.T]
+  implicit def doIsIntegral[P](implicit
+      tst: IsInt[P] || IsLong[P]): OpIntercept.Aux[IsIntegral[P], tst.Out] = ???
+
   private trait IsRationalImpl[P] {
     type Out
   }
@@ -47,21 +83,22 @@ object rational {
       N <: XInt, D <: XInt,
       SN <: XInt, SD <: XInt](
     implicit
-      sim: OpGen.Aux[Simplify[Rational[N, D]], Rational[SN, SD]]
+      sim: OpAuxGen[Simplify[Rational[N, D]], Rational[SN, SD]]
     ): OpIntercept.Aux[ToRational[Rational[N, D]], Rational[SN, SD]] =
     new OpIntercept[ToRational[Rational[N, D]]] {
       type Out = Rational[SN, SD]
       val value: Out = new Rational[SN, SD] {}
     }
 
-  implicit def toRationalFromInt[N <: XInt]: OpIntercept.Aux[ToRational[N], Rational[N, W.`1`.T]] =
+  implicit def toRationalFromInt[N, One](implicit
+      one: OpAuxGen[AlignType[1, N], One]): OpIntercept.Aux[ToRational[N], Rational[N, One]] =
     new OpIntercept[ToRational[N]] {
-      type Out = Rational[N, W.`1`.T]
-      val value: Out = new Rational[N, W.`1`.T] {}
+      type Out = Rational[N, One]
+      val value: Out = new Rational[N, One] {}
     }
 
-  implicit def doRationalNegate[N <: XInt, D <: XInt, NN <: XInt](implicit
-      neg: OpInt.Aux[Negate[N], NN]): OpIntercept.Aux[Negate[Rational[N, D]], Rational[NN, D]] =
+  implicit def doRationalNegate[N, D, NN](implicit
+      neg: OpAuxGen[Negate[N], NN]): OpIntercept.Aux[Negate[Rational[N, D]], Rational[NN, D]] =
     new OpIntercept[Negate[Rational[N, D]]] {
       type Out = Rational[NN, D]
       val value: Out = new Rational[NN, D] {}
@@ -76,13 +113,13 @@ object rational {
       SN <: XInt, SD <: XInt](
     implicit
       rat: Require[IsRational[LHS] || IsRational[RHS]],
-      lhs: OpGen.Aux[ToRational[LHS], Rational[LN, LD]],
-      rhs: OpGen.Aux[ToRational[RHS], Rational[RN, RD]],
+      lhs: OpAuxGen[ToRational[LHS], Rational[LN, LD]],
+      rhs: OpAuxGen[ToRational[RHS], Rational[RN, RD]],
       ev0: OpInt.Aux[LN * RD, LNRD],
       ev1: OpInt.Aux[RN * LD, RNLD],
       ev2: OpInt.Aux[LNRD + RNLD, N],
       ev3: OpInt.Aux[LD * RD, D],
-      ev4: OpGen.Aux[Simplify[Rational[N, D]], Rational[SN, SD]],
+      ev4: OpAuxGen[Simplify[Rational[N, D]], Rational[SN, SD]],
     ): OpIntercept.Aux[LHS + RHS, Rational[SN, SD]] =
   new OpIntercept[LHS + RHS] {
     type Out = Rational[SN, SD]
@@ -96,10 +133,10 @@ object rational {
       SN <: XInt, SD <: XInt](
     implicit
       rat: Require[IsRational[LHS] || IsRational[RHS]],
-      lhs: OpGen.Aux[ToRational[LHS], Rational[LN, LD]],
-      rhs: OpGen.Aux[ToRational[RHS], Rational[RN, RD]],
+      lhs: OpAuxGen[ToRational[LHS], Rational[LN, LD]],
+      rhs: OpAuxGen[ToRational[RHS], Rational[RN, RD]],
       neg: OpInt.Aux[Negate[RN], RNN],
-      add: OpGen.Aux[Rational[LN, LD] + Rational[RNN, RD], Rational[SN, SD]]
+      add: OpAuxGen[Rational[LN, LD] + Rational[RNN, RD], Rational[SN, SD]]
     ): OpIntercept.Aux[LHS - RHS, Rational[SN, SD]] =
   new OpIntercept[LHS - RHS] {
     type Out = Rational[SN, SD]
@@ -114,11 +151,11 @@ object rational {
       SN <: XInt, SD <: XInt](
     implicit
       rat: Require[IsRational[LHS] || IsRational[RHS]],
-      lhs: OpGen.Aux[ToRational[LHS], Rational[LN, LD]],
-      rhs: OpGen.Aux[ToRational[RHS], Rational[RN, RD]],
+      lhs: OpAuxGen[ToRational[LHS], Rational[LN, LD]],
+      rhs: OpAuxGen[ToRational[RHS], Rational[RN, RD]],
       ev0: OpInt.Aux[LN * RN, N],
       ev1: OpInt.Aux[LD * RD, D],
-      ev2: OpGen.Aux[Simplify[Rational[N, D]], Rational[SN, SD]]
+      ev2: OpAuxGen[Simplify[Rational[N, D]], Rational[SN, SD]]
     ): OpIntercept.Aux[LHS * RHS, Rational[SN, SD]] =
     new OpIntercept[LHS * RHS] {
       type Out = Rational[SN, SD]
@@ -132,9 +169,9 @@ object rational {
       SN <: XInt, SD <: XInt](
     implicit
       rat: Require[IsRational[LHS] || IsRational[RHS]],
-      lhs: OpGen.Aux[ToRational[LHS], Rational[LN, LD]],
-      rhs: OpGen.Aux[ToRational[RHS], Rational[RN, RD]],
-      mul: OpGen.Aux[Rational[LN, LD] * Rational[RD, RN], Rational[SN, SD]]
+      lhs: OpAuxGen[ToRational[LHS], Rational[LN, LD]],
+      rhs: OpAuxGen[ToRational[RHS], Rational[RN, RD]],
+      mul: OpAuxGen[Rational[LN, LD] * Rational[RD, RN], Rational[SN, SD]]
     ): OpIntercept.Aux[LHS / RHS, Rational[SN, SD]] =
     new OpIntercept[LHS / RHS] {
       type Out = Rational[SN, SD]
@@ -144,22 +181,22 @@ object rational {
   trait GCDOpId
   type GCD[A, B] = OpMacro[GCDOpId, A, B, W.`0`.T]
 
-  private type gcdErrorMsg = W.`"GCD requires positive integers"`.T
+  private type gcdErrorMsg = W.`"GCD requires positive integral arguments"`.T
 
-  implicit def doGCDforBasisCase[A <: XInt, B <: XInt, Rem <: XInt](implicit
-      ev0: RequireMsg[(A >= B) && (B > W.`0`.T), gcdErrorMsg],
-      ev1: OpInt.Aux[A % B, Rem],
-      ev2: Require[Rem == W.`0`.T]): OpIntercept.Aux[GCD[A, B], B] = ???
+  implicit def doGCDforBasisCase[A, B, Rem](implicit
+      ev0: RequireMsg[IsIntegral[A] && IsIntegral[B] && (A >= B) && IsPositive[B], gcdErrorMsg],
+      ev1: OpAuxGen[A % B, Rem],
+      ev2: Require[IsZero[Rem]]): OpIntercept.Aux[GCD[A, B], B] = ???
 
-  implicit def doGCDforAgeB[A <: XInt, B <: XInt,Rem <: XInt, D <: XInt](implicit
-      ev0: RequireMsg[(A >= B) && (B > W.`0`.T), gcdErrorMsg],
-      ev1: OpInt.Aux[A % B, Rem],
-      ev2: Require[Rem != W.`0`.T],
-      ev3: OpInt.Aux[GCD[B, Rem], D]): OpIntercept.Aux[GCD[A, B], D] = ???
+  implicit def doGCDforAgeB[A, B, Rem, D](implicit
+      ev0: RequireMsg[IsIntegral[A] && IsIntegral[B] && (A >= B) && IsPositive[B], gcdErrorMsg],
+      ev1: OpAuxGen[A % B, Rem],
+      ev2: Require[IsNonZero[Rem]],
+      ev3: OpAuxGen[GCD[B, Rem], D]): OpIntercept.Aux[GCD[A, B], D] = ???
 
-  implicit def doGCDforAltB[A <: XInt, B <: XInt, Rem <: XInt, D <: XInt](implicit
-      ev0: RequireMsg[(A < B) && (A > W.`0`.T), gcdErrorMsg],
-      ev1: OpInt.Aux[GCD[B, A], D]): OpIntercept.Aux[GCD[A, B], D] = ???
+  implicit def doGCDforAltB[A, B, Rem, D](implicit
+      ev0: RequireMsg[IsIntegral[A] && IsIntegral[B] && (A < B) && IsPositive[A], gcdErrorMsg],
+      ev1: OpAuxGen[GCD[B, A], D]): OpIntercept.Aux[GCD[A, B], D] = ???
 
   trait SimplifyOpId
   type Simplify[F] = OpMacro[SimplifyOpId, F, W.`0`.T, W.`0`.T]
@@ -188,9 +225,9 @@ object rational {
       SN <: XInt,  SD <: XInt](
     implicit
       ev0: RequireMsg[(N < W.`0`.T) && (D > W.`0`.T), simplifyErrorMsg],
-      ev1: OpGen.Aux[Negate[Rational[N, D]], F],
-      ev2: OpGen.Aux[Simplify[F], SNF],
-      ev3: OpGen.Aux[Negate[SNF], Rational[SN, SD]]
+      ev1: OpAuxGen[Negate[Rational[N, D]], F],
+      ev2: OpAuxGen[Simplify[F], SNF],
+      ev3: OpAuxGen[Negate[SNF], Rational[SN, SD]]
     ): OpIntercept.Aux[Simplify[Rational[N, D]], Rational[SN, SD]] =
     new OpIntercept[Simplify[Rational[N, D]]] {
       type Out = Rational[SN, SD]
@@ -213,7 +250,7 @@ object rational {
       bn: RequireMsg[D < W.`0`.T, simplifyErrorMsg],
       nn: OpInt.Aux[Negate[N], NN],
       nd: OpInt.Aux[Negate[D], ND],
-      sf: OpGen.Aux[Simplify[Rational[NN, ND]], Rational[SN, SD]]
+      sf: OpAuxGen[Simplify[Rational[NN, ND]], Rational[SN, SD]]
     ): OpIntercept.Aux[Simplify[Rational[N, D]], Rational[SN, SD]] =
     new OpIntercept[Simplify[Rational[N, D]]] {
       type Out = Rational[SN, SD]

From eedb6306229f044a04743b5f7aed4f67aa7503bc Mon Sep 17 00:00:00 2001
From: Erik Erlandson <eerlands@redhat.com>
Date: Thu, 25 Jun 2020 07:13:57 -0700
Subject: [PATCH 11/11] abstract away from XInt, use OpAuxGen

---
 src/main/scala/singleton/ops/rational.scala | 94 +++++++++------------
 1 file changed, 40 insertions(+), 54 deletions(-)

diff --git a/src/main/scala/singleton/ops/rational.scala b/src/main/scala/singleton/ops/rational.scala
index 75007587..c48412ff 100644
--- a/src/main/scala/singleton/ops/rational.scala
+++ b/src/main/scala/singleton/ops/rational.scala
@@ -10,8 +10,6 @@ object rational {
     * @tparam D the denominator
     */
   trait Rational[N, D] {
-    // currently only XInt is supported,
-    // other types such as XLong could be added with additional implicit rules
     def n(implicit nv: Id[N]): nv.Out = nv.value
     def d(implicit dv: Id[D]): dv.Out = dv.value
     def show(implicit nv: Id[N], dv: Id[D]): String = s"Rational(${n}, ${d})"
@@ -106,19 +104,19 @@ object rational {
 
   implicit def doRationalAdd[
       LHS, RHS,
-      LN <: XInt, LD <: XInt,
-      RN <: XInt, RD <: XInt,
-      LNRD <: XInt, RNLD <: XInt,
-      N <: XInt, D <: XInt,
-      SN <: XInt, SD <: XInt](
+      LN, LD,
+      RN, RD,
+      LNRD, RNLD,
+      N, D,
+      SN, SD](
     implicit
       rat: Require[IsRational[LHS] || IsRational[RHS]],
       lhs: OpAuxGen[ToRational[LHS], Rational[LN, LD]],
       rhs: OpAuxGen[ToRational[RHS], Rational[RN, RD]],
-      ev0: OpInt.Aux[LN * RD, LNRD],
-      ev1: OpInt.Aux[RN * LD, RNLD],
-      ev2: OpInt.Aux[LNRD + RNLD, N],
-      ev3: OpInt.Aux[LD * RD, D],
+      ev0: OpAuxGen[LN * RD, LNRD],
+      ev1: OpAuxGen[RN * LD, RNLD],
+      ev2: OpAuxGen[LNRD + RNLD, N],
+      ev3: OpAuxGen[LD * RD, D],
       ev4: OpAuxGen[Simplify[Rational[N, D]], Rational[SN, SD]],
     ): OpIntercept.Aux[LHS + RHS, Rational[SN, SD]] =
   new OpIntercept[LHS + RHS] {
@@ -128,14 +126,14 @@ object rational {
 
   implicit def doRationalSubtract[
       LHS, RHS,
-      LN <: XInt, LD <: XInt,
-      RN <: XInt, RD <: XInt, RNN <: XInt,
-      SN <: XInt, SD <: XInt](
+      LN, LD,
+      RN, RD, RNN,
+      SN, SD](
     implicit
       rat: Require[IsRational[LHS] || IsRational[RHS]],
       lhs: OpAuxGen[ToRational[LHS], Rational[LN, LD]],
       rhs: OpAuxGen[ToRational[RHS], Rational[RN, RD]],
-      neg: OpInt.Aux[Negate[RN], RNN],
+      neg: OpAuxGen[Negate[RN], RNN],
       add: OpAuxGen[Rational[LN, LD] + Rational[RNN, RD], Rational[SN, SD]]
     ): OpIntercept.Aux[LHS - RHS, Rational[SN, SD]] =
   new OpIntercept[LHS - RHS] {
@@ -145,16 +143,16 @@ object rational {
 
   implicit def doRationalMultiply[
       LHS, RHS,
-      LN <: XInt, LD <: XInt,
-      RN <: XInt, RD <: XInt,
-      N <: XInt, D <: XInt,
-      SN <: XInt, SD <: XInt](
+      LN, LD,
+      RN, RD,
+      N, D,
+      SN, SD](
     implicit
       rat: Require[IsRational[LHS] || IsRational[RHS]],
       lhs: OpAuxGen[ToRational[LHS], Rational[LN, LD]],
       rhs: OpAuxGen[ToRational[RHS], Rational[RN, RD]],
-      ev0: OpInt.Aux[LN * RN, N],
-      ev1: OpInt.Aux[LD * RD, D],
+      ev0: OpAuxGen[LN * RN, N],
+      ev1: OpAuxGen[LD * RD, D],
       ev2: OpAuxGen[Simplify[Rational[N, D]], Rational[SN, SD]]
     ): OpIntercept.Aux[LHS * RHS, Rational[SN, SD]] =
     new OpIntercept[LHS * RHS] {
@@ -164,9 +162,9 @@ object rational {
 
   implicit def doRationalDivide[
       LHS, RHS,
-      LN <: XInt, LD <: XInt,
-      RN <: XInt, RD <: XInt,
-      SN <: XInt, SD <: XInt](
+      LN, LD,
+      RN, RD,
+      SN, SD](
     implicit
       rat: Require[IsRational[LHS] || IsRational[RHS]],
       lhs: OpAuxGen[ToRational[LHS], Rational[LN, LD]],
@@ -203,28 +201,19 @@ object rational {
 
   private type simplifyErrorMsg = W.`"Simplify requires non-zero denominator"`.T
 
-  implicit def doSimplifyPositive[
-      N <: XInt, D <: XInt,
-      C <: XInt,
-      SN <: XInt, SD <: XInt](
-    implicit
-      ev0: RequireMsg[(N > W.`0`.T) && (D > W.`0`.T), simplifyErrorMsg],
-      gcd: OpInt.Aux[GCD[N, D], C],
-      n: OpInt.Aux[N / C, SN],
-      d: OpInt.Aux[D / C, SD]
+  implicit def doSimplifyPositive[N, D, C, SN, SD](implicit
+      pos: RequireMsg[IsPositive[N] && IsPositive[D], simplifyErrorMsg],
+      gcd: OpAuxGen[GCD[N, D], C],
+      n: OpAuxGen[N / C, SN],
+      d: OpAuxGen[D / C, SD]
     ): OpIntercept.Aux[Simplify[Rational[N, D]], Rational[SN, SD]] =
     new OpIntercept[Simplify[Rational[N, D]]] {
       type Out = Rational[SN, SD]
       val value = new Rational[SN, SD] {}
     }
 
-  implicit def doSimplifyNegative[
-      N <: XInt, D <: XInt,
-      F <: Rational[_, _],
-      SNF <: Rational[_, _],
-      SN <: XInt,  SD <: XInt](
-    implicit
-      ev0: RequireMsg[(N < W.`0`.T) && (D > W.`0`.T), simplifyErrorMsg],
+  implicit def doSimplifyNegative[N, D, F, SNF, SN, SD](implicit
+      neg: RequireMsg[IsNegative[N] && IsPositive[D], simplifyErrorMsg],
       ev1: OpAuxGen[Negate[Rational[N, D]], F],
       ev2: OpAuxGen[Simplify[F], SNF],
       ev3: OpAuxGen[Negate[SNF], Rational[SN, SD]]
@@ -234,22 +223,19 @@ object rational {
       val value = new Rational[SN, SD] {}
     }
 
-  implicit def doSimplifyZero[D <: XInt](implicit
-      nz: RequireMsg[D > W.`0`.T, simplifyErrorMsg]
-    ): OpIntercept.Aux[Simplify[Rational[W.`0`.T, D]], Rational[W.`0`.T, W.`1`.T]] =
-    new OpIntercept[Simplify[Rational[W.`0`.T, D]]] {
-      type Out = Rational[W.`0`.T, W.`1`.T]
-      val value = new Rational[W.`0`.T, W.`1`.T] {}
+  implicit def doSimplifyZero[Z, D](implicit
+      zro: RequireMsg[IsZero[Z] && IsPositive[D], simplifyErrorMsg],
+      v1: AlignType[1, D]
+    ): OpIntercept.Aux[Simplify[Rational[Z, D]], Rational[Z, v1.Out]] =
+    new OpIntercept[Simplify[Rational[Z, D]]] {
+      type Out = Rational[Z, v1.Out]
+      val value = new Rational[Z, v1.Out] {}
     }
 
-  implicit def doSimplifyNegDenom[
-      N <: XInt, D <: XInt,
-      NN <: XInt, ND <: XInt,
-      SN <: XInt, SD <: XInt](
-    implicit
-      bn: RequireMsg[D < W.`0`.T, simplifyErrorMsg],
-      nn: OpInt.Aux[Negate[N], NN],
-      nd: OpInt.Aux[Negate[D], ND],
+  implicit def doSimplifyNegDenom[N, D, NN, ND, SN, SD](implicit
+      neg: RequireMsg[IsNegative[D], simplifyErrorMsg],
+      nn: OpAuxGen[Negate[N], NN],
+      nd: OpAuxGen[Negate[D], ND],
       sf: OpAuxGen[Simplify[Rational[NN, ND]], Rational[SN, SD]]
     ): OpIntercept.Aux[Simplify[Rational[N, D]], Rational[SN, SD]] =
     new OpIntercept[Simplify[Rational[N, D]]] {