diff --git a/sourcecode/src-2/sourcecode/Macros.scala b/sourcecode/src-2/sourcecode/Macros.scala index 98e4a80..a2a8ae4 100644 --- a/sourcecode/src-2/sourcecode/Macros.scala +++ b/sourcecode/src-2/sourcecode/Macros.scala @@ -51,6 +51,10 @@ trait ArgsMacros { implicit def generate: Args = macro Macros.argsImpl } +trait UUIDMacros { + implicit def generate: SourceUUID = macro Macros.uuidImpl +} + object Util{ def isSynthetic(c: Compat.Context)(s: c.Symbol) = isSyntheticName(getName(c)(s)) def isSyntheticName(name: String) = { @@ -195,4 +199,12 @@ object Macros { }.mkString.dropRight(1) c.Expr[T](q"""${c.prefix}($renderedPath)""") } + + def uuidImpl(c: Compat.Context): c.Expr[SourceUUID] = { + import c.universe._ + import java.util.UUID + implicit val l: Liftable[UUID] = Liftable((in: UUID) => q"_root_.java.util.UUID.fromString(${in.toString})") + val uuid = java.util.UUID.randomUUID() + c.Expr[SourceUUID](q"""${c.prefix}($uuid)""") + } } diff --git a/sourcecode/src-3/sourcecode/Macros.scala b/sourcecode/src-3/sourcecode/Macros.scala index 69488c4..4448c33 100644 --- a/sourcecode/src-3/sourcecode/Macros.scala +++ b/sourcecode/src-3/sourcecode/Macros.scala @@ -63,6 +63,11 @@ trait ArgsMacros { ${ Macros.argsImpl } } +trait UUIDMacros { + inline implicit def generate: SourceUUID = + ${ Macros.uuidImpl } +} + object Util{ def isSynthetic(using Quotes)(s: quotes.reflect.Symbol) = isSyntheticName(getName(s)) def isSyntheticName(name: String) = { @@ -167,6 +172,16 @@ object Macros { '{Enclosing(${Expr(path)})} } + def uuidImpl(using Quotes): Expr[SourceUUID] = { + import quotes.reflect._ + import java.util.UUID + given ToExpr[UUID] with { + def apply(x: UUID)(using Quotes) = '{ _root_.java.util.UUID.fromString(${ Expr(x.toString) }.toString) } + } + val uuid = UUID.randomUUID() + '{ sourcecode.SourceUUID(${ Expr(uuid) }) } + } + def enclosingMachineImpl(using Quotes): Expr[Enclosing.Machine] = { val path = enclosing(machine = true)(_ => true) '{Enclosing.Machine(${Expr(path)})} diff --git a/sourcecode/src/sourcecode/SourceContext.scala b/sourcecode/src/sourcecode/SourceContext.scala index c11a2e5..13d77c2 100644 --- a/sourcecode/src/sourcecode/SourceContext.scala +++ b/sourcecode/src/sourcecode/SourceContext.scala @@ -43,3 +43,10 @@ object Text extends TextMacros case class Args(value: Seq[Seq[Text[_]]]) extends SourceValue[Seq[Seq[Text[_]]]] object Args extends SourceCompanion[Seq[Seq[Text[_]]], Args](new Args(_)) with ArgsMacros + +// Unique identifiers, labels, and other IDs generated at compile time. +import java.util.UUID + +case class SourceUUID(value: UUID) extends SourceValue[UUID] + +object SourceUUID extends SourceCompanion[UUID, SourceUUID](new SourceUUID(_)) with UUIDMacros diff --git a/sourcecode/test/src/sourcecode/EnumExaple.scala b/sourcecode/test/src/sourcecode/EnumExample.scala similarity index 95% rename from sourcecode/test/src/sourcecode/EnumExaple.scala rename to sourcecode/test/src/sourcecode/EnumExample.scala index 922ec19..5b3aede 100644 --- a/sourcecode/test/src/sourcecode/EnumExaple.scala +++ b/sourcecode/test/src/sourcecode/EnumExample.scala @@ -1,6 +1,6 @@ package sourcecode -object EnumExaple { +object EnumExample { def run() = { case class EnumValue(name: String){ override def toString = name diff --git a/sourcecode/test/src/sourcecode/SourceUUIDTests.scala b/sourcecode/test/src/sourcecode/SourceUUIDTests.scala new file mode 100644 index 0000000..6136ecb --- /dev/null +++ b/sourcecode/test/src/sourcecode/SourceUUIDTests.scala @@ -0,0 +1,47 @@ +package sourcecode + +import java.util.UUID + +object SourceUUIDTests { + case class DataWithId(x: Int, id: UUID) + + case class DataWithImplicitId(x: Int)(implicit val id: sourcecode.SourceUUID) + + def run() = { + // Verify that UUIDs are different and are generated at compile time, not at run time. + def generateUUIDs() = { + val uuid1 = implicitly[sourcecode.SourceUUID].value + val uuid2 = implicitly[sourcecode.SourceUUID].value + (uuid1, uuid2) + } + + val (u1a, u2a) = generateUUIDs() // Generate a pair of UUIDs. + val (u1b, u2b) = generateUUIDs() // This will have the same UUIDs. + assert(u1a != u2a && u1a.toString != u2a.toString, "the two UUIDs are different") + assert(u1a == u1b && u1a.toString == u1b.toString, "the UUIDs are generated at compile time") + assert(u2a == u2b && u2a.toString == u2b.toString, "calling `generateUUIDs()` several times will produce the same results") + + def generateData1(x: Int)(implicit uuid: sourcecode.SourceUUID) = DataWithId(x, uuid.value) + + def generateData2(x: Int) = DataWithId(x, implicitly[sourcecode.SourceUUID].value) + + val u10 = generateData1(10) + val u20 = generateData2(20) + val u30 = generateData1(30) + val u40 = generateData2(40) + + assert(u10.id.toString != u30.id.toString, "generateData1() produces different IDs each time") + assert(u20.id.toString == u40.id.toString, "generateData2() produces the same IDs each time") + assert(u10.id.toString != u20.id.toString && u30.id.toString != u40.id.toString, "generateData1() and generateData2() produce different IDs") + + val u50 = DataWithImplicitId(50) + val u60 = DataWithImplicitId(60) + assert(u50.id.toString != u60.id.toString, "DataWithImplicitId() produces different IDs each time") + + def generateData3(x: Int) = DataWithImplicitId(x) + + val u70 = generateData3(70) + val u80 = generateData3(80) + assert(u70.id.toString == u80.id.toString, "generateData3() produces the same IDs each time") + } +} diff --git a/sourcecode/test/src/sourcecode/Tests.scala b/sourcecode/test/src/sourcecode/Tests.scala index 4bfd38b..7a86804 100644 --- a/sourcecode/test/src/sourcecode/Tests.scala +++ b/sourcecode/test/src/sourcecode/Tests.scala @@ -14,7 +14,7 @@ object Tests{ println("================Test Begin================") Apply.applyRun() Implicits.implicitRun() - EnumExaple.run() + EnumExample.run() EnumFull.run() NoSynthetic.run() Synthetic.run() @@ -30,6 +30,8 @@ object Tests{ DebugName.main() println("================Debug Lite================") DebugLite.main() + println("================Unique IDs================") + SourceUUIDTests.run() println("================Regressions===============") Regressions.main() println("================Test Ended================")