Skip to content

[WIP] Support a rational type #146

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
9 changes: 9 additions & 0 deletions src/main/scala/singleton/ops/OpIntercept.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package singleton.ops
import impl._

import scala.annotation.implicitNotFound
@implicitNotFound("Missing an `OpIntercept` implicit for the operation ${Op}")
trait OpIntercept[Op <: HasOut] extends HasOutValue
object OpIntercept {
type Aux[Op <: HasOut, Out0] = OpIntercept[Op]{type Out = Out0}
}
80 changes: 53 additions & 27 deletions src/main/scala/singleton/ops/impl/GeneralMacros.scala
Original file line number Diff line number Diff line change
@@ -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}
var errorCache : String = ""
def clearErrorCache() : Unit = errorCache = ""
def setErrorCache(msg : String) : Unit = errorCache = msg
def getErrorMessage : String = errorCache
}
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 {
@@ -456,7 +460,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 +477,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 +488,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, _) =>
@@ -551,7 +555,8 @@ trait GeneralMacros {
case _ => //regular cases
opCalc(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 +580,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 +659,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)
MacroCache.setErrorCache(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 +740,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 +777,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,7 +887,7 @@ 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
}
@@ -899,9 +905,28 @@ trait GeneralMacros {
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 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 {
val itree = c.inferImplicitValue (
appliedType(interceptTpe, List(opTpe)),
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)
case _ => (a, b) match {
case (_ : CalcVal, _ : CalcVal) => abort(s"Unsupported operation $opTpe")
case _ => CalcUnknown(funcType.toType, None, opIntercept = false)
}
}
}
}

@@ -1055,7 +1080,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 +1090,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 +1391,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 +1406,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 +1526,7 @@ trait GeneralMacros {
}
}

val reqCalc = opCalc(funcTypes.Require, condCalc, msgCalc, CalcUnknown(typeOf[NoSym], None))
val reqCalc = opCalc(funcTypes.Require, condCalc, msgCalc, CalcUnknown(typeOf[NoSym], None, opIntercept = false))

q"""
(new $chkSym[$condTpe, $msgTpe, $chkArgTpe]($outTree.asInstanceOf[$outTpe]))
@@ -1566,7 +1592,7 @@ trait GeneralMacros {
}
}

val reqCalc = opCalc(funcTypes.Require, condCalc, msgCalc, CalcUnknown(typeOf[NoSym], None))
val reqCalc = opCalc(funcTypes.Require, condCalc, msgCalc, CalcUnknown(typeOf[NoSym], None, opIntercept = false))

q"""
(new $chkSym[$condTpe, $msgTpe, $chkArgTpe, $paramFaceTpe, $paramTpe]($outTree.asInstanceOf[$outTpe]))
8 changes: 6 additions & 2 deletions src/main/scala/singleton/ops/impl/Op.scala
Original file line number Diff line number Diff line change
@@ -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.")
Loading