From 2277a098e3db941b662383956a9626a612f4d2b6 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Sun, 21 Jun 2020 00:35:05 +0300 Subject: [PATCH 1/9] 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 Date: Sun, 21 Jun 2020 00:54:09 +0300 Subject: [PATCH 2/9] 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 Date: Sun, 21 Jun 2020 00:58:54 +0300 Subject: [PATCH 3/9] 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 Date: Sun, 21 Jun 2020 01:18:31 +0300 Subject: [PATCH 4/9] 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 Date: Sun, 21 Jun 2020 01:28:57 +0300 Subject: [PATCH 5/9] 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 Date: Mon, 22 Jun 2020 02:01:32 +0300 Subject: [PATCH 6/9] 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 Date: Mon, 22 Jun 2020 02:37:37 +0300 Subject: [PATCH 7/9] 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 Date: Mon, 22 Jun 2020 02:46:37 +0300 Subject: [PATCH 8/9] 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 299e10f6aafcc0832cb0b48ff86642eabd8a3ab8 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Thu, 25 Jun 2020 23:19:12 +0300 Subject: [PATCH 9/9] wip --- src/main/scala/singleton/ops/Math.scala | 90 ++++++++ .../scala/singleton/ops/OpIntercept.scala | 4 +- .../singleton/ops/impl/GeneralMacros.scala | 101 ++++---- src/main/scala/singleton/ops/impl/Op.scala | 8 +- .../scala/singleton/ops/impl/OpMacros.scala | 32 +-- src/main/scala/singleton/ops/package.scala | 218 ++++++++++-------- src/main/scala/singleton/ops/rational.scala | 154 +++++++++++++ .../scala/singleton/ops/GetLHSArgSpec.scala | 2 +- .../scala/singleton/ops/OpInterceptSpec.scala | 116 +++++++++- 9 files changed, 546 insertions(+), 179 deletions(-) create mode 100644 src/main/scala/singleton/ops/Math.scala create mode 100644 src/main/scala/singleton/ops/rational.scala diff --git a/src/main/scala/singleton/ops/Math.scala b/src/main/scala/singleton/ops/Math.scala new file mode 100644 index 00000000..cf7e3194 --- /dev/null +++ b/src/main/scala/singleton/ops/Math.scala @@ -0,0 +1,90 @@ +package singleton.ops + +import singleton.ops.impl.OpMacro + +object Math { + trait NumericLiteral[T, UB] + implicit def __numericIsNumeric[T, UB]: OpInterceptAux[NumericLiteral[T, UB] => NumericLiteral[_,_], NumericLiteral[T, UB]] = ??? + implicit def __numericInt[T <: XInt] : OpInterceptAux[T => NumericLiteral[_,_], NumericLiteral[T, XInt]] = ??? + implicit def __numericLong[T <: XLong] : OpInterceptAux[T => NumericLiteral[_,_], NumericLiteral[T, XLong]] = ??? + implicit def __numericFloat[T <: XFloat] : OpInterceptAux[T => NumericLiteral[_,_], NumericLiteral[T, XFloat]] = ??? + implicit def __numericDouble[T <: XDouble] : OpInterceptAux[T => NumericLiteral[_,_], NumericLiteral[T, XDouble]] = ??? + + trait IntegralLiteral[T, UB] extends NumericLiteral[T, UB] + implicit def __integralInt[T] : OpInterceptAux[NumericLiteral[T, XInt] => IntegralLiteral[_,_], IntegralLiteral[T, XInt]] = ??? + implicit def __integralLong[T] : OpInterceptAux[NumericLiteral[T, XLong] => IntegralLiteral[_,_], IntegralLiteral[T, XLong]] = ??? + + trait Zero + implicit def __zeroInt : OpInterceptAux[Zero => XInt, W.`0`.T] = ??? + implicit def __zeroLong : OpInterceptAux[Zero => XLong, W.`0L`.T] = ??? + implicit def __zeroFloat : OpInterceptAux[Zero => XFloat, W.`0.0f`.T] = ??? + implicit def __zeroDouble : OpInterceptAux[Zero => XDouble, W.`0.0`.T] = ??? + +// implicit def `__numeric+`[L, NL[_,_], TL, R, NR[_,_], TR, UB]( +// implicit +// numericL : OpInterceptAux[L => NumericLiteral[_,_], NL[TL, UB]], +// numericR : OpInterceptAux[R => NumericLiteral[_,_], NR[TR, UB]], +// op : TL + TR +// ) : OpInterceptAux[L + R, NumericLiteral[op.Out, UB]] = ??? +// +// implicit def `__numeric-`[L, NL[_,_], TL, R, NR[_,_], TR, UB]( +// implicit +// numericL : OpInterceptAux[L => NumericLiteral[_,_], NL[TL, UB]], +// numericR : OpInterceptAux[R => NumericLiteral[_,_], NR[TR, UB]], +// op : TL - TR +// ) : OpInterceptAux[L - R, NumericLiteral[op.Out, UB]] = ??? +// +// implicit def `__numeric*`[L, NL[_,_], TL, R, NR[_,_], TR, UB]( +// implicit +// numericL : OpInterceptAux[L => NumericLiteral[_,_], NL[TL, UB]], +// numericR : OpInterceptAux[R => NumericLiteral[_,_], NR[TR, UB]], +// op : TL * TR +// ) : OpInterceptAux[L * R, NumericLiteral[op.Out, UB]] = ??? +// +// implicit def `__numeric/`[L, NL[_,_], TL, R, NR[_,_], TR, UB]( +// implicit +// numericL : OpInterceptAux[L => IntegralLiteral[_,_], NL[TL, UB]], +// numericR : OpInterceptAux[R => IntegralLiteral[_,_], NR[TR, UB]], +// op : TL / TR +// ) : OpInterceptAux[L / R, IntegralLiteral[op.Out, UB]] = ??? +// +// implicit def `__numeric%`[L, NL[_,_], TL, R, NR[_,_], TR, UB]( +// implicit +// numericL : OpInterceptAux[L => IntegralLiteral[_,_], NL[TL, UB]], +// numericR : OpInterceptAux[R => IntegralLiteral[_,_], NR[TR, UB]], +// op : TL % TR +// ) : OpInterceptAux[L % R, IntegralLiteral[op.Out, UB]] = ??? + + +// trait IsPositiveLiteral[T] +// implicit def __isPositive[T]( +// implicit +// positive : T > ZeroLiteralOf[T] +// ) : OpInterceptAux[IsPositiveLiteral[T], positive.Out] = ??? +// +// trait PositiveLiteral[T] extends NumericLiteral[T] +// implicit def __positiveNumeric[T]( +// implicit +// positive : RequireMsg[IsPositiveLiteral[T], W.`"Must be a positive number, but found "`.T + ToString[T]] +// ) : OpInterceptAux[NumericLiteral[T] => PositiveLiteral[_], PositiveLiteral[T]] = ??? + + + +// trait IsNegativeOpId +// type IsNegative[P] = OpMacro[IsNegativeOpId, P, W.`0`.T, W.`0`.T] +// implicit def doIsNegative[P](implicit +// tst: P < AlignType[0, P]): OpInterceptAux[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]): OpInterceptAux[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]): OpInterceptAux[IsNonZero[P], tst.Out] = ??? + + + +} diff --git a/src/main/scala/singleton/ops/OpIntercept.scala b/src/main/scala/singleton/ops/OpIntercept.scala index 35a03d64..7af307df 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 HasOutValue +trait OpIntercept[Op] extends HasOutValue object OpIntercept { - type Aux[Op <: HasOut, Out0] = OpIntercept[Op]{type Out = Out0} + type Aux[Op, 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 18687b56..15f135c4 100644 --- a/src/main/scala/singleton/ops/impl/GeneralMacros.scala +++ b/src/main/scala/singleton/ops/impl/GeneralMacros.scala @@ -8,10 +8,6 @@ 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} - var errorCache : String = "" - def clearErrorCache() : Unit = errorCache = "" - def setErrorCache(msg : String) : Unit = errorCache = msg - def getErrorMessage : String = errorCache } trait GeneralMacros { val c: whitebox.Context @@ -312,26 +308,37 @@ trait GeneralMacros { } object Key { implicit def fromType(key : Type) : Key = new Key(key, GetArgTree.argContext) + implicit def fromTypeNoArgs(key : Type) : Key = new Key(key, List()) } - val cache = MacroCache.cache.asInstanceOf[mutable.Map[Key, Calc]] - def get(key : Type) : Option[Calc] = { + val cache = MacroCache.cache.asInstanceOf[mutable.Map[Key, Either[String, Calc]]] + def get(key : Type) : Option[Either[String, Calc]] = { val k = Key.fromType(key) - cache.get(k).map {v => - VerboseTraversal(s"${YELLOW}${BOLD}fetching${RESET} $k, $v") - val cloned = v match { - case lit : CalcLit => CalcLit(lit.value) //reconstruct internal literal tree - case nlit : CalcNLit => CalcNLit(nlit.primitive, deepCopyTree(nlit.tree)) - case c => c - } - cloned + cache.get(k).map { + case Right(v) => + VerboseTraversal(s"${YELLOW}${BOLD}fetching${RESET} $k, $v") + val cloned = v match { + case lit : CalcLit => CalcLit(lit.value) //reconstruct internal literal tree + case nlit : CalcNLit => CalcNLit(nlit.primitive, deepCopyTree(nlit.tree)) + case c => c + } + Right(cloned) + case left @ Left(msg) => + VerboseTraversal(s"${YELLOW}${BOLD}fetching error${RESET} $k, $msg") + left } } def add[V <: Calc](key : Type, value : V) : V = { val k = Key.fromType(key) - cache += (k -> value) - VerboseTraversal(s"${GREEN}${BOLD}caching${RESET} $k -> $value") + cache += (k -> Right(value)) + VerboseTraversal(s"${GREEN}${BOLD}caching value${RESET} $k -> $value") value } + def add(key : Type, msg : String, argsAbort : Boolean) : String = { + val k = if (argsAbort) Key.fromTypeNoArgs(key) else Key.fromType(key) + cache += (k -> Left(msg)) + VerboseTraversal(s"${GREEN}${BOLD}caching error${RESET} $k -> $msg") + msg + } } //////////////////////////////////////////////////////////////////// @@ -452,24 +459,24 @@ trait GeneralMacros { // Calculates an Op //////////////////////////////////////////////////////////////////////// object OpCalc { - private val opMacroSym = symbolOf[OpMacro[_,_,_,_]] + private val opMacroSym = symbolOf[OpMacro[_,_]] private var uncachingReason : Int = 0 def setUncachingReason(arg : Int) : Unit = { uncachingReason = arg } def unapply(tp: Type): Option[Calc] = { tp match { - case TypeRef(_, sym, ft :: tp :: _) if sym == opMacroSym && ft.typeSymbol == funcTypes.GetType => - Some(CalcUnknown(tp, None, opIntercept = false)) + case TypeRef(_, sym, opSymId :: opArgs :: _) if sym == opMacroSym && opSymId.typeSymbol == funcTypes.GetType => + Some(CalcUnknown(opArgs.typeArgs.head, None, opIntercept = false)) case TypeRef(_, sym, args) if sym == opMacroSym => VerboseTraversal(s"@@OpCalc@@\nTP: $tp\nRAW: ${showRaw(tp)}") val funcType = args.head.typeSymbol.asType CalcCache.get(tp) match { case None => - val args = tp.typeArgs - lazy val aValue = TypeCalc(args(1)) - lazy val bValue = TypeCalc(args(2)) - lazy val cValue = TypeCalc(args(3)) + val args = tp.typeArgs(1).typeArgs + lazy val aValue = if (args.length < 1) CalcLit(0) else TypeCalc(args(0)) + lazy val bValue = if (args.length < 2) CalcLit(0) else TypeCalc(args(1)) + lazy val cValue = if (args.length < 3) CalcLit(0) else TypeCalc(args(2)) //If function is set/get variable we keep the original string, //otherwise we get the variable's value @@ -543,7 +550,7 @@ trait GeneralMacros { bValue match { //Checking the argument type case t : CalcLit => Some(t) //Literal argument is just a literal case _ => //Got a type, so returning argument name - TypeCalc.unapply(args(3)) match { + TypeCalc.unapply(args(2)) match { case Some(t: CalcType) => val term = TermName(s"arg$argNum") Some(CalcNLit(t, q"$term")) @@ -553,7 +560,7 @@ trait GeneralMacros { } case _ => //regular cases - opCalc(funcType, aValue, bValue, cValue) match { + opCalc(funcType, args.length, 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 @@ -563,7 +570,11 @@ trait GeneralMacros { if (uncachingReason > 0) VerboseTraversal(s"$uncachingReason:: Skipped caching of $tp") else retVal.foreach{rv => CalcCache.add(tp, rv)} retVal - case cached => cached + case cached => cached match { + case Some(Right(calc)) => Some(calc) + case Some(Left(msg)) => abort(msg) + case None => None + } } case _ => None } @@ -659,10 +670,11 @@ trait GeneralMacros { } //////////////////////////////////////////////////////////////////////// - def abort(msg: String, annotatedSym : Option[TypeSymbol] = defaultAnnotatedSym, position : Position = c.enclosingPosition): Nothing = { + def abort(msg: String, annotatedSym : Option[TypeSymbol] = defaultAnnotatedSym, argsAbort : Boolean = false, position : Position = c.enclosingPosition): Nothing = { VerboseTraversal(s"!!!!!!aborted with: $msg at $annotatedSym, $defaultAnnotatedSym") - if (annotatedSym.isDefined) setAnnotation(msg, annotatedSym.get) - MacroCache.setErrorCache(msg) //propagating the error in case this is an inner implicit call for OpIntercept + annotatedSym.foreach{a => setAnnotation(msg, a)} + if (c.enclosingImplicits.nonEmpty) + CalcCache.add(c.enclosingImplicits.last.pt, msg, argsAbort) c.abort(position, msg) } @@ -843,7 +855,7 @@ trait GeneralMacros { case Apply(TypeApply(Select(t, _), _), _) => getAllArgs(t, true) case TypeApply(Select(t, _), _) => getAllArgs(t, true) case Select(t, _) => getAllArgs(t, true) - case _ => abort("Left-hand-side tree not found") + case _ => abort("Left-hand-side tree not found", argsAbort = true) } private var argListUsed = false @@ -873,7 +885,7 @@ trait GeneralMacros { // println("<<<<<<< rawArgs" + showRaw(allArgs)) val argTree : Tree = if (argIdx < allArgs.length) c.typecheck(allArgs(argIdx)) - else abort(s"Argument index($argIdx) is not smaller than the total number of arguments(${allArgs.length})") + else abort(s"Argument index($argIdx) is not smaller than the total number of arguments(${allArgs.length})", argsAbort = true) val tpe = c.enclosingImplicits.last.pt match { case TypeRef(_,sym,tp :: _) if (sym == func1Sym) => tp //conversion, so get the type from last.pt @@ -900,28 +912,31 @@ trait GeneralMacros { 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(funcType : TypeSymbol, argsLength : Int, aCalc : => Calc, bCalc : => Calc, cCalc : => Calc) : Calc = { lazy val a = aCalc lazy val b = bCalc lazy val cArg = cCalc def unsupported() : Calc = { - val opMacroTpe = typeOf[OpMacro[_,_,_,_]].typeConstructor - val opTpe = appliedType(opMacroTpe, List(funcType.toType, a.tpe, b.tpe, cArg.tpe)) + val opMacroTpe = typeOf[OpMacro[_,_]].typeConstructor + val argsTpe = argsLength match { + case 3 => appliedType(typeOf[Tuple3[_,_,_]].typeConstructor, List(a.tpe, b.tpe, cArg.tpe)) + case 2 => appliedType(typeOf[Tuple2[_,_]].typeConstructor, List(a.tpe, b.tpe)) + case 1 => appliedType(typeOf[Tuple1[_]].typeConstructor, List(a.tpe)) + case _ => abort("unsupported number of arguments") + } + val opTpe = appliedType(opMacroTpe, List(funcType.toType, argsTpe)) val interceptTpe = typeOf[singleton.ops.OpIntercept[_]].typeConstructor - MacroCache.clearErrorCache() + val summonTpe = appliedType(interceptTpe, List(opTpe)) try { - val itree = c.inferImplicitValue ( - appliedType(interceptTpe, List(opTpe)), - silent = false - ) + val itree = c.inferImplicitValue(summonTpe, silent = false) TypeCalc(itree.tpe.decls.head.info) match { 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 { case TypecheckException(_, _) => - MacroCache.getErrorMessage match { - case m if m.nonEmpty => abort(m) + CalcCache.get(c.enclosingImplicits.last.pt) match { + case Some(Left(msg)) => abort(msg) case _ => (a, b) match { case (_ : CalcVal, _ : CalcVal) => abort(s"Unsupported operation $opTpe") case _ => CalcUnknown(funcType.toType, None, opIntercept = false) @@ -1526,7 +1541,7 @@ trait GeneralMacros { } } - val reqCalc = opCalc(funcTypes.Require, condCalc, msgCalc, CalcUnknown(typeOf[NoSym], None, opIntercept = false)) + val reqCalc = opCalc(funcTypes.Require, 3, condCalc, msgCalc, CalcUnknown(typeOf[NoSym], None, opIntercept = false)) q""" (new $chkSym[$condTpe, $msgTpe, $chkArgTpe]($outTree.asInstanceOf[$outTpe])) @@ -1592,7 +1607,7 @@ trait GeneralMacros { } } - val reqCalc = opCalc(funcTypes.Require, condCalc, msgCalc, CalcUnknown(typeOf[NoSym], None, opIntercept = false)) + val reqCalc = opCalc(funcTypes.Require, 3, 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 ca400f7c..4e172c76 100644 --- a/src/main/scala/singleton/ops/impl/Op.scala +++ b/src/main/scala/singleton/ops/impl/Op.scala @@ -26,11 +26,11 @@ trait Op extends HasOutValue { val valueWide: OutWide } -protected[singleton] trait OpGen[O <: Op] {type Out; val value : Out} +protected[singleton] trait OpGen[O] {type Out; val value : Out} protected[singleton] object OpGen { - type Aux[O <: Op, Ret_Out] = OpGen[O] {type Out = Ret_Out} - implicit def impl[O <: Op](implicit o: O) : Aux[O, o.Out] = new OpGen[O] {type Out = o.Out; val value = o.value.asInstanceOf[o.Out]} - implicit def getValue[O <: Op, Out](o : Aux[O, Out]) : Out = o.value + type Aux[O, Ret_Out] = OpGen[O] {type Out = Ret_Out} + implicit def impl[O <: Op](implicit o: O) : Aux[O, o.Out] = new OpGen[O] {type Out = o.Out; val value : Out = o.value} + implicit def getValue[O, Out](o : Aux[O, Out]) : Out = o.value } trait OpCast[T, O <: Op] extends HasOutValue {type Out <: T} diff --git a/src/main/scala/singleton/ops/impl/OpMacros.scala b/src/main/scala/singleton/ops/impl/OpMacros.scala index 3d8bb5c4..11238f2d 100644 --- a/src/main/scala/singleton/ops/impl/OpMacros.scala +++ b/src/main/scala/singleton/ops/impl/OpMacros.scala @@ -5,14 +5,12 @@ import scala.reflect.macros.whitebox * Three arguments type function macro *******************************************************************************************************/ @scala.annotation.implicitNotFound("Literal operation has failed.") -trait OpMacro[N, S1, S2, S3] extends Op +trait OpMacro[SymId, Args] extends Op object OpMacro { type Aux[ - N, - S1, - S2, - S3, + SymId, + Args, OutWide0, Out0, OutNat0 <: shapeless.Nat, @@ -23,7 +21,7 @@ object OpMacro { OutDouble0 <: Double with Singleton, OutString0 <: String with Singleton, OutBoolean0 <: Boolean with Singleton - ] = OpMacro[N, S1, S2, S3] { + ] = OpMacro[SymId, Args] { type OutWide = OutWide0 type Out = Out0 type OutNat = OutNat0 @@ -37,10 +35,8 @@ object OpMacro { } implicit def call[ - N, - S1, - S2, - S3, + SymId, + Args, OutWide, Out, OutNat <: shapeless.Nat, @@ -52,10 +48,8 @@ object OpMacro { OutString <: String with Singleton, OutBoolean <: Boolean with Singleton ]: Aux[ - N, - S1, - S2, - S3, + SymId, + Args, OutWide, Out, OutNat, @@ -66,16 +60,14 @@ object OpMacro { OutDouble, OutString, OutBoolean - ] = macro Macro.impl[N, S1, S2, S3] + ] = macro Macro.impl[SymId, Args] final class Macro(val c: whitebox.Context) extends GeneralMacros { def impl[ - N : c.WeakTypeTag, - S1: c.WeakTypeTag, - S2: c.WeakTypeTag, - S3: c.WeakTypeTag + SymId : c.WeakTypeTag, + Args : c.WeakTypeTag ]: c.Tree = - materializeOpGen[OpMacro[N, S1, S2, S3]].usingFuncName + materializeOpGen[OpMacro[SymId, Args]].usingFuncName } // implicit def valueOfOp[N, S1 : ValueOf, S2 : ValueOf, S3 : ValueOf] diff --git a/src/main/scala/singleton/ops/package.scala b/src/main/scala/singleton/ops/package.scala index bdfd8a0d..43d89dbe 100644 --- a/src/main/scala/singleton/ops/package.scala +++ b/src/main/scala/singleton/ops/package.scala @@ -37,19 +37,37 @@ package object ops { ///////////////////////////////////////////////// //Short aliases of auxiliary operation types ///////////////////////////////////////////////// - type OpAuxGen[O <: Op, Ret_Out] = OpGen.Aux[O, Ret_Out] + type OpGenAux[O, Ret_Out] = OpGen.Aux[O, Ret_Out] + type OpNatAux[O <: Op, Ret_Out <: Nat] = OpNat.Aux[O, Ret_Out] + type OpCharAux[O <: Op, Ret_Out <: XChar] = OpChar.Aux[O, Ret_Out] + type OpIntAux[O <: Op, Ret_Out <: XInt] = OpInt.Aux[O, Ret_Out] + type OpLongAux[O <: Op, Ret_Out <: XLong] = OpLong.Aux[O, Ret_Out] + type OpFloatAux[O <: Op, Ret_Out <: XFloat] = OpFloat.Aux[O, Ret_Out] + type OpDoubleAux[O <: Op, Ret_Out <: XDouble] = OpDouble.Aux[O, Ret_Out] + type OpStringAux[O <: Op, Ret_Out <: XString] = OpString.Aux[O, Ret_Out] + type OpBooleanAux[O <: Op, Ret_Out <: XBoolean] = OpBoolean.Aux[O, Ret_Out] + type OpInterceptAux[F, Out] = OpIntercept.Aux[F, Out] + + @deprecated("Please use `OpGenAux` instead") + type OpAuxGen[O, Ret_Out] = OpGen.Aux[O, Ret_Out] + @deprecated("Please use `OpNatAux` instead") type OpAuxNat[O <: Op, Ret_Out <: Nat] = OpNat.Aux[O, Ret_Out] + @deprecated("Please use `OpCharAux` instead") type OpAuxChar[O <: Op, Ret_Out <: XChar] = OpChar.Aux[O, Ret_Out] + @deprecated("Please use `OpIntAux` instead") type OpAuxInt[O <: Op, Ret_Out <: XInt] = OpInt.Aux[O, Ret_Out] + @deprecated("Please use `OpLongAux` instead") type OpAuxLong[O <: Op, Ret_Out <: XLong] = OpLong.Aux[O, Ret_Out] + @deprecated("Please use `OpFloatAux` instead") type OpAuxFloat[O <: Op, Ret_Out <: XFloat] = OpFloat.Aux[O, Ret_Out] + @deprecated("Please use `OpDoubleAux` instead") type OpAuxDouble[O <: Op, Ret_Out <: XDouble] = OpDouble.Aux[O, Ret_Out] + @deprecated("Please use `OpStringAux` instead") type OpAuxString[O <: Op, Ret_Out <: XString] = OpString.Aux[O, Ret_Out] + @deprecated("Please use `OpBooleanAux` instead") type OpAuxBoolean[O <: Op, Ret_Out <: XBoolean] = OpBoolean.Aux[O, Ret_Out] ///////////////////////////////////////////////// - private val NP = W(0) - private type NP = NP.T private val DefaultRequireMsg = W("Cannot prove requirement Require[...]") private type DefaultRequireMsg = DefaultRequireMsg.T protected[singleton] val True = W(true) @@ -62,83 +80,83 @@ package object ops { ///////////////////////////////////////////////// //Special control aliases ///////////////////////////////////////////////// - type ITE[Cond,TBody,EBody]= OpMacro[OpId.ITE, Cond, TBody, EBody] - type ==>[A,B] = OpMacro[OpId.==>, A, B, NP] + type ITE[Cond,TBody,EBody]= OpMacro[OpId.ITE, (Cond, TBody, EBody)] + type ==>[A,B] = OpMacro[OpId.==>, (A, B)] ///////////////////////////////////////////////// - protected[singleton] type Arg[Num, T, TWide] = OpMacro[OpId.Arg, Num, T, TWide] //Argument for real-time function creation - protected[singleton] type GetType[Sym] = OpMacro[OpId.GetType, Sym, NP, NP] //Argument for real-time function creation - type AcceptNonLiteral[P1] = OpMacro[OpId.AcceptNonLiteral, P1, NP, NP] - type GetArg[ArgIdx] = OpMacro[OpId.GetArg, ArgIdx, False, NP] //Use to get argument type of class/definition - type GetLHSArg[ArgIdx] = OpMacro[OpId.GetArg, ArgIdx, True, NP] //Use to get argument type of the left-hand-side - type ImplicitFound[Sym] = OpMacro[OpId.ImplicitFound, GetType[Sym], NP, NP] //Implicit Found boolean indication - type EnumCount[Sym] = OpMacro[OpId.EnumCount, GetType[Sym], NP, NP] //Number of direct subclasses + protected[singleton] type Arg[Num, T, TWide] = OpMacro[OpId.Arg, (Num, T, TWide)] //Argument for real-time function creation + protected[singleton] type GetType[Sym] = OpMacro[OpId.GetType, Tuple1[Sym]] //Argument for real-time function creation + type AcceptNonLiteral[P1] = OpMacro[OpId.AcceptNonLiteral, Tuple1[P1]] + type GetArg[ArgIdx] = OpMacro[OpId.GetArg, (ArgIdx, False)] //Use to get argument type of class/definition + type GetLHSArg[ArgIdx] = OpMacro[OpId.GetArg, (ArgIdx, True)] //Use to get argument type of the left-hand-side + type ImplicitFound[Sym] = OpMacro[OpId.ImplicitFound, Tuple1[GetType[Sym]]] //Implicit Found boolean indication + type EnumCount[Sym] = OpMacro[OpId.EnumCount, Tuple1[GetType[Sym]]] //Number of direct subclasses object GetArg { - type Aux[ArgIdx, Out] = OpAuxGen[GetArg[ArgIdx], Out] + type Aux[ArgIdx, Out] = OpGenAux[GetArg[ArgIdx], Out] } object GetLHSArg { - type Aux[ArgIdx, Out] = OpAuxGen[GetLHSArg[ArgIdx], Out] + type Aux[ArgIdx, Out] = OpGenAux[GetLHSArg[ArgIdx], Out] } - type GetArg0 = GetArg[W.`0`.T] + type GetArg0 = GetArg[W.`0`.T] object GetArg0 { - type Aux[Out] = OpAuxGen[GetArg0, Out] + type Aux[Out] = OpGenAux[GetArg0, Out] } - type GetLHSArg0 = GetLHSArg[W.`0`.T] + type GetLHSArg0 = GetLHSArg[W.`0`.T] object GetLHSArg0 { - type Aux[Out] = OpAuxGen[GetLHSArg0, Out] + type Aux[Out] = OpGenAux[GetLHSArg0, Out] } - type Id[P1] = OpMacro[OpId.Id, P1, NP, NP] - type ![P1] = OpMacro[OpId.!, P1, NP, NP] - type Require[Cond] = OpMacro[OpId.Require, Cond, DefaultRequireMsg, NoSym] - type RequireMsg[Cond,Msg] = OpMacro[OpId.Require, Cond, Msg, NoSym] - type RequireMsgSym[Cond,Msg,Sym] = OpMacro[OpId.Require, Cond, Msg, GetType[Sym]] - type Warn = impl.Warn - type ToNat[P1] = OpMacro[OpId.ToNat, P1, NP, NP] - type ToChar[P1] = OpMacro[OpId.ToChar, P1, NP, NP] - type ToInt[P1] = OpMacro[OpId.ToInt, P1, NP, NP] - type ToLong[P1] = OpMacro[OpId.ToLong, P1, NP, NP] - type ToFloat[P1] = OpMacro[OpId.ToFloat, P1, NP, NP] - type ToDouble[P1] = OpMacro[OpId.ToDouble, P1, NP, NP] - type ToString[P1] = OpMacro[OpId.ToString, P1, NP, NP] - type IsNat[P1] = OpMacro[OpId.IsNat, P1, NP, NP] - type IsChar[P1] = OpMacro[OpId.IsChar, P1, NP, NP] - type IsInt[P1] = OpMacro[OpId.IsInt, P1, NP, NP] - type IsLong[P1] = OpMacro[OpId.IsLong, P1, NP, NP] - type IsFloat[P1] = OpMacro[OpId.IsFloat, P1, NP, NP] - type IsDouble[P1] = OpMacro[OpId.IsDouble, P1, NP, NP] - type IsString[P1] = OpMacro[OpId.IsString, P1, NP, NP] - type IsBoolean[P1] = OpMacro[OpId.IsBoolean, P1, NP, NP] - type IsNonLiteral[P1] = OpMacro[OpId.IsNonLiteral, P1, NP, NP] - type Reverse[P1] = OpMacro[OpId.Reverse, P1, NP, NP] - type Negate[P1] = OpMacro[OpId.Negate, P1, NP, NP] - type NumberOfLeadingZeros[P1] = OpMacro[OpId.NumberOfLeadingZeros, P1, NP, NP] - - type +[P1, P2] = OpMacro[OpId.+, P1, P2, NP] - type -[P1, P2] = OpMacro[OpId.-, P1, P2, NP] - type *[P1, P2] = OpMacro[OpId.*, P1, P2, NP] - type /[P1, P2] = OpMacro[OpId./, P1, P2, NP] - type %[P1, P2] = OpMacro[OpId.%, P1, P2, NP] - type <[P1, P2] = OpMacro[OpId.<, P1, P2, NP] - type <=[P1, P2] = OpMacro[OpId.<=, P1, P2, NP] - type >=[P1, P2] = OpMacro[OpId.>=, P1, P2, NP] - type >[P1, P2] = OpMacro[OpId.>, P1, P2, NP] - type ==[P1, P2] = OpMacro[OpId.==, P1, P2, NP] - type !=[P1, P2] = OpMacro[OpId.!=, P1, P2, NP] - type &&[P1, P2] = OpMacro[OpId.&&, P1, P2, NP] - type ||[P1, P2] = OpMacro[OpId.||, P1, P2, NP] - type SubSequence[S, IBeg, IEnd] = OpMacro[OpId.SubSequence, S, IBeg, IEnd] - type Substring[S, I] = OpMacro[OpId.Substring, S, I, NP] - type StartsWith[S, Prefix] = OpMacro[OpId.StartsWith, S, Prefix, NP] - type EndsWith[S, Suffix] = OpMacro[OpId.EndsWith, S, Suffix, NP] - type Head[S] = OpMacro[OpId.Head, S, NP, NP] - type Tail[S] = OpMacro[OpId.Tail, S, NP, NP] - type Length[S] = OpMacro[OpId.Length, S, NP, NP] - type CharAt[S, I] = OpMacro[OpId.CharAt, S, I, NP] - type Matches[S, Regex] = OpMacro[OpId.Matches, S, Regex, NP] - type FirstMatch[S, Regex] = OpMacro[OpId.FirstMatch, S, Regex, NP] - type PrefixMatch[S, Regex] = OpMacro[OpId.PrefixMatch, S, Regex, NP] - type ReplaceFirstMatch[S, Regex, R] = OpMacro[OpId.ReplaceFirstMatch, S, Regex, R] - type ReplaceAllMatches[S, Regex, R] = OpMacro[OpId.ReplaceAllMatches, S, Regex, R] + type Id[P1] = OpMacro[OpId.Id, Tuple1[P1]] + type ![P1] = OpMacro[OpId.!, Tuple1[P1]] + type Require[Cond] = OpMacro[OpId.Require, (Cond, DefaultRequireMsg, NoSym)] + type RequireMsg[Cond,Msg] = OpMacro[OpId.Require, (Cond, Msg, NoSym)] + type RequireMsgSym[Cond,Msg,Sym] = OpMacro[OpId.Require, (Cond, Msg, GetType[Sym])] + type Warn = impl.Warn + type ToNat[P1] = OpMacro[OpId.ToNat, Tuple1[P1]] + type ToChar[P1] = OpMacro[OpId.ToChar, Tuple1[P1]] + type ToInt[P1] = OpMacro[OpId.ToInt, Tuple1[P1]] + type ToLong[P1] = OpMacro[OpId.ToLong, Tuple1[P1]] + type ToFloat[P1] = OpMacro[OpId.ToFloat, Tuple1[P1]] + type ToDouble[P1] = OpMacro[OpId.ToDouble, Tuple1[P1]] + type ToString[P1] = OpMacro[OpId.ToString, Tuple1[P1]] + type IsNat[P1] = OpMacro[OpId.IsNat, Tuple1[P1]] + type IsChar[P1] = OpMacro[OpId.IsChar, Tuple1[P1]] + type IsInt[P1] = OpMacro[OpId.IsInt, Tuple1[P1]] + type IsLong[P1] = OpMacro[OpId.IsLong, Tuple1[P1]] + type IsFloat[P1] = OpMacro[OpId.IsFloat, Tuple1[P1]] + type IsDouble[P1] = OpMacro[OpId.IsDouble, Tuple1[P1]] + type IsString[P1] = OpMacro[OpId.IsString, Tuple1[P1]] + type IsBoolean[P1] = OpMacro[OpId.IsBoolean, Tuple1[P1]] + type IsNonLiteral[P1] = OpMacro[OpId.IsNonLiteral, Tuple1[P1]] + type Reverse[P1] = OpMacro[OpId.Reverse, Tuple1[P1]] + type Negate[P1] = OpMacro[OpId.Negate, Tuple1[P1]] + type NumberOfLeadingZeros[P1] = OpMacro[OpId.NumberOfLeadingZeros, Tuple1[P1]] + + type +[L, R] = OpMacro[OpId.+, (L, R)] + type -[L, R] = OpMacro[OpId.-, (L, R)] + type *[L, R] = OpMacro[OpId.*, (L, R)] + type /[L, R] = OpMacro[OpId./, (L, R)] + type %[L, R] = OpMacro[OpId.%, (L, R)] + type <[L, R] = OpMacro[OpId.<, (L, R)] + type <=[L, R] = OpMacro[OpId.<=, (L, R)] + type >=[L, R] = OpMacro[OpId.>=, (L, R)] + type >[L, R] = OpMacro[OpId.>, (L, R)] + type ==[L, R] = OpMacro[OpId.==, (L, R)] + type !=[L, R] = OpMacro[OpId.!=, (L, R)] + type &&[L, R] = OpMacro[OpId.&&, (L, R)] + type ||[L, R] = OpMacro[OpId.||, (L, R)] + type SubSequence[S, IBeg, IEnd] = OpMacro[OpId.SubSequence, (S, IBeg, IEnd)] + type Substring[S, I] = OpMacro[OpId.Substring, (S, I)] + type StartsWith[S, Prefix] = OpMacro[OpId.StartsWith, (S, Prefix)] + type EndsWith[S, Suffix] = OpMacro[OpId.EndsWith, (S, Suffix)] + type Head[S] = OpMacro[OpId.Head, Tuple1[S]] + type Tail[S] = OpMacro[OpId.Tail, Tuple1[S]] + type Length[S] = OpMacro[OpId.Length, Tuple1[S]] + type CharAt[S, I] = OpMacro[OpId.CharAt, (S, I)] + type Matches[S, Regex] = OpMacro[OpId.Matches, (S, Regex)] + type FirstMatch[S, Regex] = OpMacro[OpId.FirstMatch, (S, Regex)] + type PrefixMatch[S, Regex] = OpMacro[OpId.PrefixMatch, (S, Regex)] + type ReplaceFirstMatch[S, Regex, R] = OpMacro[OpId.ReplaceFirstMatch, (S, Regex, R)] + type ReplaceAllMatches[S, Regex, R] = OpMacro[OpId.ReplaceAllMatches, (S, Regex, R)] type CompileTime[C] = Require[ITE[IsNonLiteral[C], W.`true`.T, C]] type RunTime[R] = SafeBoolean[IsNonLiteral[R]] @@ -146,19 +164,19 @@ package object ops { object math { type Pi = W.`3.141592653589793`.T type E = W.`2.718281828459045`.T - type Abs[P1] = OpMacro[OpId.Abs, P1, NP, NP] - type Min[P1, P2] = OpMacro[OpId.Min, P1, P2, NP] - type Max[P1, P2] = OpMacro[OpId.Max, P1, P2, NP] - type Pow[P1, P2] = OpMacro[OpId.Pow, P1, P2, NP] - type Floor[P1] = OpMacro[OpId.Floor, P1, NP, NP] - type Ceil[P1] = OpMacro[OpId.Ceil, P1, NP, NP] - type Round[P1] = OpMacro[OpId.Round, P1, NP, NP] - type Sin[P1] = OpMacro[OpId.Sin, P1, NP, NP] - type Cos[P1] = OpMacro[OpId.Cos, P1, NP, NP] - type Tan[P1] = OpMacro[OpId.Tan, P1, NP, NP] - type Sqrt[P1] = OpMacro[OpId.Sqrt, P1, NP, NP] - type Log[P1] = OpMacro[OpId.Log, P1, NP, NP] - type Log10[P1] = OpMacro[OpId.Log10, P1, NP, NP] + type Abs[P1] = OpMacro[OpId.Abs, Tuple1[P1]] + type Min[L, R] = OpMacro[OpId.Min, (L, R)] + type Max[L, R] = OpMacro[OpId.Max, (L, R)] + type Pow[L, R] = OpMacro[OpId.Pow, (L, R)] + type Floor[P1] = OpMacro[OpId.Floor, Tuple1[P1]] + type Ceil[P1] = OpMacro[OpId.Ceil, Tuple1[P1]] + type Round[P1] = OpMacro[OpId.Round, Tuple1[P1]] + type Sin[P1] = OpMacro[OpId.Sin, Tuple1[P1]] + type Cos[P1] = OpMacro[OpId.Cos, Tuple1[P1]] + type Tan[P1] = OpMacro[OpId.Tan, Tuple1[P1]] + type Sqrt[P1] = OpMacro[OpId.Sqrt, Tuple1[P1]] + type Log[P1] = OpMacro[OpId.Log, Tuple1[P1]] + type Log10[P1] = OpMacro[OpId.Log10, Tuple1[P1]] } @@ -168,13 +186,13 @@ package object ops { //E.g. val four : 2 + 2 = 4 ///////////////////////////////////////////////// implicit def singletonToOp[ - X <: Singleton, N, S1, S2, S3, OP_OUT + X <: Singleton, S, A, M, OP_OUT ](x: X) (implicit - op : OpMacro[N, S1, S2, S3], - opaux: OpAuxGen[OpMacro[N, S1, S2, S3], OP_OUT], + op : OpMacro[S, A], + opaux: OpGenAux[OpMacro[S, A], OP_OUT], check : Require[X == OP_OUT] - ) : OpMacro[N, S1, S2, S3] = op + ) : OpMacro[S, A] = op ///////////////////////////////////////////////// @@ -184,10 +202,10 @@ package object ops { //E.g. val four : 4 = implicitly[2 + 2] ///////////////////////////////////////////////// implicit def opToSingleton[ - N, S1, S2, S3, OP_OUT - ](op : OpMacro[N, S1, S2, S3]) + S, A, OP_OUT + ](op : OpMacro[S, A]) (implicit - opaux: OpAuxGen[OpMacro[N, S1, S2, S3], OP_OUT], + opaux: OpGenAux[OpMacro[S, A], OP_OUT], id : Id[OP_OUT] ) : OP_OUT = id.value.asInstanceOf[OP_OUT] ///////////////////////////////////////////////// @@ -199,16 +217,16 @@ package object ops { //E.g. val four : 1 + 3 = implicitly[2 + 2] ///////////////////////////////////////////////// implicit def opToOp[ - NA, SA1, SA2, SA3, - NB, SB1, SB2, SB3, - OP_OUTA, - OP_OUTB - ](opA : OpMacro[NA, SA1, SA2, SA3]) + S1, A1, + S2, A2, + OP_OUT1, + OP_OUT2 + ](opA : OpMacro[S1, A1]) (implicit - opauxA: OpAuxGen[OpMacro[NA, SA1, SA2, SA3], OP_OUTA], - opauxB: OpAuxGen[OpMacro[NB, SB1, SB2, SB3], OP_OUTB], - opB : OpMacro[NB, SB1, SB2, SB3], - check : Require[OP_OUTA == OP_OUTB] - ) : OpMacro[NB, SB1, SB2, SB3] = opB + opauxA: OpGenAux[OpMacro[S1, A1], OP_OUT1], + opauxB: OpGenAux[OpMacro[S2, A2], OP_OUT2], + opB : OpMacro[S2, A2], + check : Require[OP_OUT1 == OP_OUT2] + ) : OpMacro[S2, A2] = opB ///////////////////////////////////////////////// } diff --git a/src/main/scala/singleton/ops/rational.scala b/src/main/scala/singleton/ops/rational.scala new file mode 100644 index 00000000..bd4967ba --- /dev/null +++ b/src/main/scala/singleton/ops/rational.scala @@ -0,0 +1,154 @@ +package singleton.ops + +import singleton.ops.impl.OpMacro + +object rational { + /** Represents a rational number + * + * @tparam N the numerator + * @tparam D the denominator + */ + trait Rational[N, D] { + 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})" + } + + implicit def __rational2rational[N, D] : OpInterceptAux[Rational[N, D] ==> Rational[_,_], Rational[N, D]] = ??? + implicit def __int2rational[I <: XInt] : OpInterceptAux[I ==> Rational[_,_], Rational[I, W.`1`.T]] = ??? + implicit def __long2rational[L <: XLong] : OpInterceptAux[L ==> Rational[_,_], Rational[L, W.`1L`.T]] = ??? + + implicit def __negateRational[N, D](implicit negateN : Negate[N]) + : OpInterceptAux[Negate[Rational[N, D]], Rational[negateN.Out, D]] = ??? + + implicit def `__rational+`[L, LN, LD, LRat[_,_], R, RN, RD, RRat[_,_], ON, OD] ( + implicit + rationalL : OpInterceptAux[L ==> Rational[_,_], LRat[LN, LD]], + rationalR : OpInterceptAux[R ==> Rational[_,_], RRat[RN, RD]], + nom : OpGenAux[(LN * RD) + (RN * LD), ON], + den : OpGenAux[LD * RD, OD] + ) : OpInterceptAux[L + R, Rational[nom.Out, den.Out]] = ??? + + + implicitly[Rational[1, 2] + 1] + + // +// implicit def doRationalSubtract[ +// LHS, RHS, +// 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: OpAuxGen[Negate[RN], RNN], +// add: OpAuxGen[Rational[LN, LD] + Rational[RNN, RD], Rational[SN, SD]] +// ): OpInterceptAux[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, 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: OpAuxGen[LN * RN, N], +// ev1: OpAuxGen[LD * RD, D], +// ev2: OpAuxGen[Simplify[Rational[N, D]], Rational[SN, SD]] +// ): OpInterceptAux[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, LD, +// RN, RD, +// SN, SD]( +// implicit +// rat: Require[IsRational[LHS] || IsRational[RHS]], +// 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]] +// ): OpInterceptAux[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 integral arguments"`.T +// +// 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]]): OpInterceptAux[GCD[A, B], B] = ??? +// +// 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]): OpInterceptAux[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]): OpInterceptAux[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, 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] +// ): OpInterceptAux[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, 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]] +// ): OpInterceptAux[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[Z, D](implicit +// zro: RequireMsg[IsZero[Z] && IsPositive[D], simplifyErrorMsg], +// v1: AlignType[1, D] +// ): OpInterceptAux[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, 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]] +// ): OpInterceptAux[Simplify[Rational[N, D]], Rational[SN, SD]] = +// new OpIntercept[Simplify[Rational[N, D]]] { +// type Out = Rational[SN, SD] +// val value = new Rational[SN, SD] {} +// } +} diff --git a/src/test/scala/singleton/ops/GetLHSArgSpec.scala b/src/test/scala/singleton/ops/GetLHSArgSpec.scala index f3fd1e84..f7b278f2 100644 --- a/src/test/scala/singleton/ops/GetLHSArgSpec.scala +++ b/src/test/scala/singleton/ops/GetLHSArgSpec.scala @@ -11,7 +11,7 @@ class GetLHSArgSpec extends Properties("GetLHSArgSpec") { implicit class ConvInt(val int : Int) { def ext(implicit g : GetLHSArg0) : g.Out = g.value - def <= [T, Out](that : Conv[T])(implicit g : OpAuxGen[AcceptNonLiteral[GetLHSArg0], Out]) : Conv[Out] = new Conv[Out](g.value){} + def <= [T, Out](that : Conv[T])(implicit g : OpGenAux[AcceptNonLiteral[GetLHSArg0], Out]) : Conv[Out] = new Conv[Out](g.value){} def := [T, Out](that : Conv[T])(implicit g : GetLHSArg.Aux[W.`0`.T, Out]) : Conv[Out] = new Conv[Out](g){} } diff --git a/src/test/scala/singleton/ops/OpInterceptSpec.scala b/src/test/scala/singleton/ops/OpInterceptSpec.scala index 883bda45..36fbead1 100644 --- a/src/test/scala/singleton/ops/OpInterceptSpec.scala +++ b/src/test/scala/singleton/ops/OpInterceptSpec.scala @@ -7,23 +7,121 @@ import singleton.TestUtils._ class OpInterceptSpec extends Properties("OpInterceptSpec") { trait Vec[A0, A1] { - def show(implicit a0 : Id[A0], a1 : Id[A1]) : String = s"Vec[${a0.value}, ${a1.value}]" + def show(implicit a0 : Id[A0], a1 : Id[A1]) : String = s"Vec[${a0}, ${a1}]" } - implicit def `Vec+`[VL0, VL1, VR0, VR1]( + implicit def VecToVec[A0, A1] : OpInterceptAux[Vec[A0, A1] => Vec[_, _], Vec[A0, A1]] = + new OpIntercept[Vec[A0, A1] => Vec[_,_]] { + val value : Out = new Vec[A0, A1]{} + type Out = Vec[A0, A1] + } + implicit def ScalarIntToVec[I <: XInt] : OpInterceptAux[I => Vec[_, _], Vec[I, I]] = + new OpIntercept[I => Vec[_,_]] { + val value : Out = new Vec[I, I]{} + type Out = Vec[I, I] + } + + implicit def `Vec+`[L, VL0, VL1, VL[_,_], R, VR0, VR1, VR[_,_]]( implicit + convL : OpInterceptAux[L => Vec[_, _], VL[VL0, VL1]], + convR : OpInterceptAux[R => Vec[_, _], VR[VR0, VR1]], opL : VL0 + VR0, - opR : VL1 + VR1 - ) : 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]] { + opR : VL1 + VR1, + maxVecSize : RequireMsg[(VL0 + VR0 + VL1 + VR1) < 100, "Reached maximum vector size allowed"] + ) : OpInterceptAux[L + R, Vec[opL.Out, opR.Out]] = //Vec is not a singleton value, so we need to instantiate OpIntercept + new OpIntercept[L + R] { type Out = Vec[opL.Out, opR.Out] val value : Out = new Vec[opL.Out, opR.Out]{} } + val vecPlusVec = shapeless.the[Vec[1, 2] + 10] + val vecPlusScalar = shapeless.the[1 + Vec[1, 2] + Vec[10, 10]] + illTyped("shapeless.the[Vec[1, 2] + Vec[10, 10] + 50]", "Reached maximum vector size allowed") + + + trait Time[H, M] + trait Hours[H] + trait Minutes[M] + + implicit def Time2Time[H, M] : OpInterceptAux[Time[H, M] => Time[_, _], Time[H, M]] = ??? + implicit def Hours2Time[H] : OpInterceptAux[Hours[H] => Time[_, _], Time[H, 0]] = ??? + implicit def Minutes2Time[M](implicit h : M / 60, m : M % 60) : + OpInterceptAux[Minutes[M] => Time[_, _], Time[h.Out, m.Out]] = ??? + + implicit def `Time+`[L, R, LH, LM, RH, RM, OH, OM, TL[_,_], TR[_,_]]( + implicit + convL : OpInterceptAux[L => Time[_, _], TL[LH, LM]], + convR : OpInterceptAux[R => Time[_, _], TR[RH, RM]], + opM : OpGenAux[(LM + RM) % 60, OM], + opH : OpGenAux[LH + RH + ((LM + RM) / 60), OH] + ) : OpInterceptAux[L + R, Time[OH, OM]] = ??? + + val totalTime = shapeless.the[Hours[2] + Time[1, 20] + Time[3, 40] + Hours[2] + Minutes[15]] + implicitly[totalTime.Out =:= Time[9, 15]] + + sealed trait Cast + object Cast { + trait Char extends Cast + trait Int extends Cast + trait Long extends Cast + trait Float extends Cast + trait Double extends Cast + trait Boolean extends Cast + trait String extends Cast + } + implicit def castCI[L <: XChar, R <: XInt] : OpInterceptAux[(L, R) => Cast, Cast.Int] = ??? + implicit def castCL[L <: XChar, R <: XLong] : OpInterceptAux[(L, R) => Cast, Cast.Long] = ??? + implicit def castIC[L <: XInt, R <: XChar] : OpInterceptAux[(L, R) => Cast, Cast.Int] = ??? + implicit def castIL[L <: XInt, R <: XLong] : OpInterceptAux[(L, R) => Cast, Cast.Long] = ??? + implicit def castLC[L <: XLong, R <: XChar] : OpInterceptAux[(L, R) => Cast, Cast.Long] = ??? + implicit def castLI[L <: XLong, R <: XInt] : OpInterceptAux[(L, R) => Cast, Cast.Long] = ??? + + implicit def Char2Char[C <: XChar] : OpInterceptAux[C => Cast.Char, C] = ??? + implicit def Char2Int[C <: XChar](implicit op : ToInt[C]) : OpInterceptAux[C => Cast.Int, op.Out] = ??? + implicit def Char2Long[C <: XChar](implicit op : ToLong[C]) : OpInterceptAux[C => Cast.Long, op.Out] = ??? + implicit def Int2Int[I <: XInt] : OpInterceptAux[I => Cast.Int, I] = ??? + implicit def Int2Long[I <: XInt](implicit op : ToLong[I]) : OpInterceptAux[I => Cast.Long, op.Out] = ??? + implicit def Long2Long[L <: XLong] : OpInterceptAux[L => Cast.Long, L] = ??? + + implicit def `+Ext`[L, R, BO, LL, RL]( + implicit + basicOp : OpInterceptAux[(L, R) => Cast, BO], + convL : OpInterceptAux[L => BO, LL], + convR : OpInterceptAux[R => BO, RL], + op : LL + RL + ) : OpInterceptAux[L + R, op.Out] = ??? + + implicit def `-Ext`[L, R, BO, LL, RL]( + implicit + basicOp : OpInterceptAux[(L, R) => Cast, BO], + convL : OpInterceptAux[L => BO, LL], + convR : OpInterceptAux[R => BO, RL], + op : LL - RL + ) : OpInterceptAux[L - R, op.Out] = ??? + + implicit def `*Ext`[L, R, BO, LL, RL]( + implicit + basicOp : OpInterceptAux[(L, R) => Cast, BO], + convL : OpInterceptAux[L => BO, LL], + convR : OpInterceptAux[R => BO, RL], + op : LL * RL + ) : OpInterceptAux[L * R, op.Out] = ??? + + implicit def `/Ext`[L, R, BO, LL, RL]( + implicit + basicOp : OpInterceptAux[(L, R) => Cast, BO], + convL : OpInterceptAux[L => BO, LL], + convR : OpInterceptAux[R => BO, RL], + op : LL / RL + ) : OpInterceptAux[L / R, op.Out] = ??? + + implicitly[20/4 + 1L + 1 * 5L + 'c'] + + implicit def `Vec==`[VL0, VL1, VR0, VR1]( implicit op : (VL0 == VR0) && (VL1 == VR1) - ) : OpIntercept.Aux[Vec[VL0, VL1] == Vec[VR0, VR1], op.Out] = ??? //No need to instantiate when a singleton value is returned + ) : OpInterceptAux[Vec[VL0, VL1] == Vec[VR0, VR1], op.Out] = ??? //No need to instantiate when a singleton value is returned property("Custom Vec Equality OK") = { val eq1 = shapeless.the[Vec[W.`1`.T, W.`2`.T] == Vec[W.`1`.T, W.`2`.T]] @@ -45,11 +143,11 @@ class OpInterceptSpec extends Properties("OpInterceptSpec") { trait FibId - type Fib[P] = impl.OpMacro[FibId, P, W.`0`.T, W.`0`.T] + type Fib[P] = impl.OpMacro[FibId, Tuple1[P]] 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] = ??? //No need to instantiate when a singleton value is returned + ) : OpInterceptAux[Fib[P], op.Out] = ??? //No need to instantiate when a singleton value is returned property("Custom Fibonacci Op OK") = { val fib4 = shapeless.the[Fib[W.`4`.T]] @@ -61,7 +159,7 @@ class OpInterceptSpec extends Properties("OpInterceptSpec") { trait FooOpId - type FooOp[C, M] = impl.OpMacro[FooOpId, C, M, W.`0`.T] + type FooOp[C, M] = impl.OpMacro[FooOpId, (C, M)] implicit def FooOp[C, M]( implicit r : RequireMsg[C, M]