├── .gitignore
├── project
├── build.properties
├── project
│ └── Build.scala
└── Build.scala
├── readme.txt
└── src
├── main
└── scala
│ ├── TMap.scala
│ ├── MacroHelpers.scala
│ ├── select.scala
│ ├── RecordType.scala
│ └── Record.scala
└── test
└── scala
├── TMapTest.scala
└── RecordTest.scala
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | .flooignore
3 | .floo
4 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version = 0.13.8
2 |
--------------------------------------------------------------------------------
/project/project/Build.scala:
--------------------------------------------------------------------------------
1 | import sbt._
2 | import Keys._
3 | object BuildBuild extends Build{
4 | override lazy val settings = super.settings ++ Seq(
5 | scalacOptions ++= Seq(
6 | "-feature", "-deprecation", "-unchecked"
7 | )
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/readme.txt:
--------------------------------------------------------------------------------
1 | Compossible: Extensible records and type-indexed maps.
2 |
3 | Compossible is a new, easy to use library implementing extensible
4 | records and type-indexed maps (TMaps) based on intersection types.
5 | Extensible records are a more modular alternative to case classes.
6 | Type-indexed maps are type-safe maps without the need for key names.
7 |
8 | Example usage:
9 | src/test/scala/RecordTest.scala
10 |
11 | src/test/scala/TMapTest.scala
12 |
13 | Sbt setup:
14 |
15 | resolvers += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"
16 |
17 | libraryDependencies += "org.cvogt" %% "compossible" % "0.2"
18 |
--------------------------------------------------------------------------------
/src/main/scala/TMap.scala:
--------------------------------------------------------------------------------
1 | package org.cvogt.compossible
2 | import scala.reflect.runtime.universe._
3 |
4 | /**
5 | A type-indexed map implementation with no tricks.
6 | You get what the type-system gives you, which already
7 | covers many use cases.
8 | */
9 | class TMap[+T] private(private val values: List[(Type, Any)]) {
10 | def apply[E >: T](implicit tt: TypeTag[E]): E = {
11 | // runtime reflection doesn't allow constant time element lookup
12 | values.find(_._1 <:< tt.tpe).get._2.asInstanceOf[E]
13 | }
14 |
15 | /**
16 | Concatenate two TMaps.
17 | Elements of the left one override
18 | */
19 | def ++[S](other: TMap[S])
20 | = new TMap[T with S](other.values ++ values)
21 |
22 | override def toString = "TMap("+values.toString+")"
23 | }
24 |
25 | object TMap {
26 | def apply[T](values: T)(implicit tt: TypeTag[T]) =
27 | new TMap[T](List(tt.tpe -> values))
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/scala/MacroHelpers.scala:
--------------------------------------------------------------------------------
1 | package org.cvogt.compossible
2 | import scala.reflect.macros.whitebox.Context
3 | import scala.language.experimental.macros
4 |
5 | trait MacroHelpers{
6 | val c: Context
7 | import c.universe._
8 |
9 | protected def error(msg: String) = c.error(c.enclosingPosition, msg)
10 |
11 | protected def firstTypeArg(tree: Tree) = {
12 | tree.tpe.widen.dealias.typeArgs.head
13 | }
14 |
15 | protected def prefixTypeArg
16 | = firstTypeArg(c.prefix.tree)
17 |
18 | protected def splitRefinedTypes(t: Type): Seq[Type] = t match {
19 | case RefinedType(types,scope) => types.map(splitRefinedTypes(_)).flatten
20 | case t => Seq(t)
21 | }
22 |
23 | protected def singletonConstant(tpe: Type): Constant
24 | = tpe match {
25 | case ConstantType(c:Constant) => c
26 | }
27 |
28 | protected def splitPair(t: Type) = {
29 | val args = t.typeArgs
30 | (args(0),args(1))
31 | }
32 |
33 | protected def intersectTypes(types: Seq[Type]): Type
34 | = internal.refinedType( // TODO: can this be done with Quasi-Quotes?
35 | types.toList, internal.newScopeWith()
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/scala/select.scala:
--------------------------------------------------------------------------------
1 | package org.cvogt.compossible
2 | import scala.reflect.macros.whitebox.Context
3 | import scala.language.experimental.macros
4 | import scala.language.dynamics
5 |
6 | // structural by name selectors that allow
7 | // to describe sub-records for projections
8 |
9 | class select[T] extends Dynamic{
10 | def &[Q](select: select[Q]): Any
11 | = macro selectMacros.unionMacro[Q]
12 |
13 | def applyDynamic(key: String)
14 | (value: org.cvogt.compossible.&.type)
15 | : Any
16 | = macro selectMacros.applyUnionMacro
17 |
18 | def selectDynamic(key: String): Any
19 | = macro selectMacros.selectUnionMacro
20 | }
21 |
22 | object &
23 | object select extends Dynamic{
24 | def applyDynamic(key: String)
25 | (value: org.cvogt.compossible.&.type)
26 | : Any
27 | = macro selectMacros.applyCreateMacro
28 |
29 | def selectDynamic(key: String): Any
30 | = macro selectMacros.selectCreateMacro
31 | }
32 |
33 | class selectMacros(val c: Context) extends MacroHelpers{
34 | import c.universe._
35 |
36 | private def create(T: Type) = q"""new select[$T]"""
37 |
38 | private def union(Q: Type)
39 | = q"""new select[$prefixTypeArg with $Q]"""
40 |
41 | def unionMacro[Q:c.WeakTypeTag](select: Tree)
42 | = union(c.weakTypeTag[Q].tpe)
43 |
44 | def selectUnionMacro(key: Tree)
45 | = union(key.tpe)
46 |
47 | def applyUnionMacro(key: Tree)(value: Tree)
48 | = union(key.tpe)
49 |
50 | def selectCreateMacro(key: Tree)
51 | = create(key.tpe)
52 |
53 | def applyCreateMacro(key: Tree)(value: Tree)
54 | = create(key.tpe)
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/scala/RecordType.scala:
--------------------------------------------------------------------------------
1 | package org.cvogt.compossible
2 |
3 | import scala.reflect.macros.whitebox.Context
4 | import scala.language.experimental.macros
5 | import scala.language.dynamics
6 |
7 | // Workaround the current limitation of not being
8 | // able to express singleton types
9 |
10 | class RecordType[T <: (String, Any)] extends Dynamic{
11 | type Type = Record[T]
12 | def &[Q <: (String, Any)](recordType: RecordType[Q]): Any
13 | = macro RecordTypeMacros.unionMacro[Q]
14 |
15 | def applyDynamic[V](key: String)
16 | (& : org.cvogt.compossible.&.type)
17 | : Any
18 | = macro RecordTypeMacros.applyUnionMacro[V]
19 |
20 | def selectDynamic[V](key: String): Any
21 | = macro RecordTypeMacros.selectUnionMacro[V]
22 | }
23 |
24 | object RecordType extends Dynamic{
25 | def applyDynamic[V](key: String)
26 | (& : org.cvogt.compossible.&.type)
27 | : Any
28 | = macro RecordTypeMacros.applyCreateMacro[V]
29 |
30 | def selectDynamic[V](key: String): Any
31 | = macro RecordTypeMacros.selectCreateMacro[V]
32 | }
33 |
34 | class RecordTypeMacros(val c: Context) extends MacroHelpers{
35 | import c.universe._
36 |
37 | private def create(T: Type,V: Type)
38 | = q"""new RecordType[($T, $V)]"""
39 |
40 | private def union(Q: Type)
41 | = q"""new RecordType[$prefixTypeArg with $Q]"""
42 |
43 | private def append(K: Type, V: Type)
44 | = q"""new RecordType[$prefixTypeArg with ($K, $V)]"""
45 |
46 | def unionMacro[Q <: (String, Any):c.WeakTypeTag](recordType: Tree)
47 | = union(c.weakTypeTag[Q].tpe)
48 |
49 | def selectUnionMacro[V:c.WeakTypeTag](key: Tree)
50 | = append(key.tpe,c.weakTypeTag[V].tpe)
51 |
52 | def applyUnionMacro[V:c.WeakTypeTag](key: Tree)(& : Tree)
53 | = append(key.tpe,c.weakTypeTag[V].tpe)
54 |
55 | def selectCreateMacro[V:c.WeakTypeTag](key: Tree)
56 | = create(key.tpe,c.weakTypeTag[V].tpe)
57 |
58 | def applyCreateMacro[V:c.WeakTypeTag](key: Tree)(& : Tree)
59 | = create(key.tpe,c.weakTypeTag[V].tpe)
60 | }
61 |
--------------------------------------------------------------------------------
/src/test/scala/TMapTest.scala:
--------------------------------------------------------------------------------
1 | package org.cvogt.test.compossible
2 |
3 | import org.scalautils.TypeCheckedTripleEquals._
4 | import org.scalatest.FunSuite
5 |
6 | import org.cvogt.compossible.TMap
7 | import scala.reflect.runtime.universe._
8 |
9 | class NativeTest extends FunSuite{
10 | test("basic"){
11 | assert(5 === TMap(5).apply[Int])
12 | val t1 = TMap(5).apply[Int]
13 | assert(5 === t1)
14 |
15 | assert("test" === TMap("test").apply[String])
16 | val t2 = TMap("test").apply[String]
17 | assert("test" === t2)
18 | }
19 |
20 | test("duplicate elements get last inserted"){
21 | val t3 = TMap(5) ++ TMap(6)
22 | assert(6 == t3[Int])
23 | assert(6 == t3[AnyVal])
24 | assert(6 == t3[Any])
25 |
26 | val t4 = TMap(6) ++ TMap(5)
27 | assert(5 == t4[Int])
28 | assert(5 == t4[AnyVal])
29 | assert(5 == t4[Any])
30 | }
31 |
32 | case class Name(value: String)
33 | case class Age(value: Int)
34 |
35 | case class Model(value: String)
36 | test("test struct"){
37 | type Person = TMap[Name with Age]
38 | val p: Person = TMap(Name("Chris")) ++ TMap(Age(99))
39 | assert("Chris" === p[Name].value)
40 | assert(99 === p[Age].value)
41 |
42 | // subtyping property TMaps rely on for reader
43 | implicitly[TMap[Model with Age with Name] <:< TMap[Model] with TMap[Name with Age]]
44 | }
45 |
46 | case class _1(value: String)
47 | case class _2(value: Int)
48 | test("test tuple"){
49 | type Tuple = TMap[_1 with _2]
50 | val p: Tuple = TMap(_1("Chris")) ++ TMap(_2(99))
51 | assert("Chris" === p[_1].value)
52 | assert(99 === p[_2].value)
53 | }
54 |
55 | case class left(value: Int)
56 | case class right(value: Int)
57 | test("test named arguments"){
58 | def plus(args: TMap[left with right])
59 | = args[left].value + args[right].value
60 |
61 | assert{
62 | val args = TMap(left(4)) ++ TMap(right(5))
63 | 9 === plus(args)
64 | }
65 | }
66 |
67 | trait Database{ def query: Any }
68 | trait Logger { def log: Any }
69 | test("dependency injection test"){
70 | implicit class FunctionReaderMonad[-T,+R](f: TMap[T] => R){
71 | def map[Q](g: R => Q) = (t: TMap[T]) => g(f(t))
72 | def flatMap[S,Q](g: R => (TMap[S] => Q)) = (ts: TMap[T with S]) => g(f(ts))(ts)
73 | }
74 | def use[T:TypeTag,R](f: T => R): TMap[T] => R = (c: TMap[T]) => f(c[T])
75 |
76 | val f: TMap[Database with Logger] => Unit
77 | = for{
78 | _ <- use{db: Database => db.query}
79 | _ <- use{logger: Logger => logger.log}
80 | } yield ()
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/project/Build.scala:
--------------------------------------------------------------------------------
1 | import sbt._
2 | import Keys._
3 |
4 | object MyBuild extends Build{
5 | val repoKind = SettingKey[String]("repo-kind", "Maven repository kind (\"snapshots\" or \"releases\")")
6 |
7 | lazy val aRootProject = Project(id = "compossible", base = file("."),
8 | settings = Seq(
9 | name := "compossible",
10 | scalaVersion := "2.11.7",
11 | description := "Composable Records and type-indexed Maps for Scala",
12 | libraryDependencies ++= Seq(
13 | "org.scalatest" %% "scalatest" % "2.2.5" % "test",
14 | "org.scalautils" %% "scalautils" % "2.1.7" % "test"
15 | ),
16 | libraryDependencies <+= scalaVersion(
17 | "org.scala-lang" % "scala-reflect" % _ //% "optional"
18 | ),
19 | scalacOptions ++= Seq("-feature", "-deprecation", "-unchecked"),
20 | //scalacOptions ++= Seq("-Xprint:patmat", "-Xshow-phases"),
21 | testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-oFD"),
22 | parallelExecution := false, // <- until TMap thread-safety issues are resolved
23 | version := "0.2-SNAPSHOT",
24 | organizationName := "Jan Christopher Vogt",
25 | organization := "org.cvogt",
26 | scalacOptions in (Compile, doc) <++= (version,sourceDirectory in Compile,name).map((v,src,n) => Seq(
27 | "-doc-title", n,
28 | "-doc-version", v,
29 | "-doc-footer", "Compossible is developed by Jan Christopher Vogt.",
30 | "-sourcepath", src.getPath, // needed for scaladoc to strip the location of the linked source path
31 | "-doc-source-url", "https://github.com/cvogt/compossible/blob/"+v+"/src/main€{FILE_PATH}.scala",
32 | "-implicits",
33 | "-diagrams", // requires graphviz
34 | "-groups"
35 | )),
36 | repoKind <<= (version)(v => if(v.trim.endsWith("SNAPSHOT")) "snapshots" else "releases"),
37 | //publishTo <<= (repoKind)(r => Some(Resolver.file("test", file("c:/temp/repo/"+r)))),
38 | publishTo <<= (repoKind){
39 | case "snapshots" => Some("snapshots" at "https://oss.sonatype.org/content/repositories/snapshots")
40 | case "releases" => Some("releases" at "https://oss.sonatype.org/service/local/staging/deploy/maven2")
41 | },
42 | publishMavenStyle := true,
43 | publishArtifact in Test := false,
44 | pomIncludeRepository := { _ => false },
45 | makePomConfiguration ~= { _.copy(configurations = Some(Seq(Compile, Runtime, Optional))) },
46 | licenses += ("Creative Commons Attribution-ShareAlike 4.0 International", url("https://creativecommons.org/licenses/by-sa/4.0/")),
47 | homepage := Some(url("http://github.com/cvogt/compossible")),
48 | startYear := Some(2015),
49 | pomExtra :=
50 |
51 |
52 | cvogt
53 | Jan Christopher Vogt
54 | -5
55 | https://github.com/cvogt/
56 |
57 |
58 |
59 | git@github.com:cvogt/compossible.git
60 | scm:git:git@github.com:cvogt/compossible.git
61 |
62 | )
63 | )
64 | }
65 |
--------------------------------------------------------------------------------
/src/test/scala/RecordTest.scala:
--------------------------------------------------------------------------------
1 | package org.cvogt.test.records
2 |
3 | import org.scalautils.TypeCheckedTripleEquals._
4 | import org.scalatest.FunSuite
5 | import org.cvogt.compossible._
6 | import org.cvogt.compossible.{Record => R}
7 | import scala.language.postfixOps
8 |
9 | class RecordTest extends FunSuite {
10 | test("basic") {
11 | val r /*: Record[("name".type -> String)]*/
12 | = Record name "Chris"
13 |
14 | assert("Chris" === r.name)
15 |
16 | val r2 = r.name = "Miguel"
17 | assert("Miguel" === r2.name)
18 |
19 | val r3 = r2 & (Record age 99)
20 |
21 | assert("Miguel" === r3.name)
22 | assert(99 === r3.age)
23 |
24 |
25 | val person = (Record name "Chris"
26 | age 99
27 | dob new java.util.Date())
28 |
29 | val name = person.name
30 | val age = person.age
31 | val dob = person.dob
32 |
33 | (name: String, age: Int, dob: java.util.Date)
34 |
35 |
36 | val car = (Record owner "Chris"
37 | model "Mercedes")
38 |
39 | {
40 | val merged = for{
41 | p <- List(person)
42 | c <- List(car) if c.owner == p.name
43 | } yield p & c
44 |
45 | merged.map{
46 | r =>
47 | assert(r.name === "Chris")
48 | assert(r.owner === "Chris")
49 | assert(r.model === "Mercedes")
50 | assert(r.age === 99)
51 | (r.name: String, r.age: Int, r.dob: java.util.Date, r.owner: String, r.model: String)
52 | }
53 | };
54 |
55 | {
56 | val merged = for{
57 | p <- List(person)
58 | c <- List(car) if c.owner == p.name
59 | } yield p(select name & age) &
60 | c(select.owner)
61 |
62 | merged.map{
63 | r =>
64 | assert(r.name === "Chris")
65 | assert(r.owner === "Chris")
66 | assertTypeError("""assert(r.model === "Mercedes")""")
67 | //assertTypeError("""r.dob: java.util.Date""")
68 | //assertTypeError("""r.model: String""")
69 | assert(r.age === 99)
70 | (r.name: String, r.age: Int, r.owner: String)
71 | }
72 | };
73 |
74 | {
75 | val personWithCar =
76 | (Record name "Chris"
77 | age 99
78 | dob new java.util.Date()
79 | car (Record owner "Chris"
80 | model "Mercedes"))
81 |
82 | assert("Chris" === personWithCar.car.owner)
83 |
84 | //new Bar.Foo[String]().name[Int](5)
85 |
86 | val recordType = (RecordType age [Int] &
87 | name[String] &
88 | dob [java.util.Date] &)
89 |
90 | def foo(record: recordType.Type) = record.name
91 |
92 | assert("Chris" === foo(person))
93 | assert("Chris" === foo(personWithCar))
94 |
95 | //personWithCar.§.car(owner = "Miguel")
96 | };
97 |
98 | {
99 | case class Person(name: String, age: Int, dob: java.util.Date)
100 | val t = Record.tuple(person)
101 | (t: (String, Int, java.util.Date),())
102 | val p = Person.tupled(Record.tuple(person))
103 | val r = Record.fromCaseClass(p)
104 | assert(r.name === "Chris")
105 | assert(r.age === 99)
106 | (r.dob,())
107 | };
108 |
109 | {
110 | // Good Example Use Case
111 | case class Person(name: String, age: Int)
112 | case class PersonWithDob(name: String, age: Int, dob: java.util.Date)
113 | val p1 = Person("Chris",99)
114 | val r = Record.fromCaseClass(p1) &
115 | (Record dob new java.util.Date)
116 | val p2 = PersonWithDob.tupled(Record.tuple(r))
117 | };
118 |
119 | {
120 | val r =
121 | (Record name "Chris") &
122 | (Record age 99) &
123 | (Record dob new java.util.Date())
124 |
125 | val name = r.name
126 | val age = r.age
127 | val dob = r.dob
128 |
129 | r.age - 1
130 |
131 | assert("Chris" == r.name)
132 | assert(99 == r.age)
133 |
134 | assert("Chris" === r.name)
135 | assert(99 === r.age)
136 |
137 | val r2 = (r.name = "Miguel").age = 98
138 | assert("Miguel" === r2.name)
139 | assert(98 === r2.age)
140 |
141 | // val r3 = r(age = 98, name = Miguel)
142 |
143 | // val r3 = name = "Miguel").age = 98
144 | assert("Miguel" === r2.name)
145 | assert(98 === r2.age)
146 |
147 | //val r2 = r(update name & age) = ("")
148 |
149 | (name: String, age: Int, dob: java.util.Date)
150 | };
151 |
152 | { // contrast what you can and can't do with a map
153 | val m = Map[String, Any](
154 | "name" -> "Chris", "age" -> 99)
155 |
156 | assert("Chris" === m("name"))
157 | assert(99 === m("age"))
158 |
159 | // does not compile with a Map
160 | assertTypeError{
161 | """
162 | m("age") - 1
163 | """
164 | }
165 | }
166 | /*
167 | {
168 | val name = person.extract("name").name
169 | val age = person.extract("age").age
170 | val dob = person.extract("dob").dob
171 |
172 | (name: String, age: Int, dob: java.util.Date)
173 | };
174 | */
175 | /*
176 | {
177 | val r =
178 | (((Record.name = "Chris")
179 | .age = 99)
180 | .dob = new java.util.Date())
181 |
182 | val name = r.name
183 | val age = r.age
184 | val dob = r.dob
185 |
186 | (name: String, age: Int, dob: java.util.Date)
187 | };
188 | */
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/src/main/scala/Record.scala:
--------------------------------------------------------------------------------
1 | package org.cvogt.compossible
2 |
3 | import scala.reflect.macros.whitebox.Context
4 | import scala.language.experimental.macros
5 | import scala.language.dynamics
6 |
7 | // Extensible records for Scala based on intersection types
8 |
9 | object Record extends Dynamic{
10 | def applyDynamic[K <: String](key: K)(value:Any): Record[(String, Any)]
11 | = macro RecordMacros.createMacro[K]
12 |
13 | def tuple(record: Record[_]): Product
14 | = macro RecordMacros.tupleMacro
15 |
16 | def fromCaseClass(obj: Product): Record[(String, Any)]
17 | = macro RecordMacros.fromCaseClassMacro
18 | //def updateDynamic[K <: String](key: K)(value:Any): Any = macro createMacro2[K]
19 | }
20 |
21 | // TODO make invariant and use implicit conversion macro freeing items from internal map
22 | class Record[+T <: (String,Any)](
23 | val values: Map[String, Any]
24 | ) extends Dynamic{
25 | override def toString = "Record("+values.toString+")"
26 |
27 | /** Combine two records into one.
28 | The combined one will have the keys of both. */
29 | def &[O <: (String,Any)](other: Record[O])
30 | = new Record[T with O](values ++ other.values)
31 |
32 | def selectDynamic[K <: String](key: K): Any
33 | = macro RecordMacros.lookupMacro[K]
34 |
35 | def apply[K <: String](select: select[K]): Record[(String, Any)]
36 | = macro RecordMacros.selectMacro[K]
37 |
38 | def updateDynamic[K <: String](key: K)(value:Any): Record[T]
39 | = new Record[T](values ++ Map(key -> value))
40 |
41 | def applyDynamic[K <: String](key: K)(value:Any): Record[(String, Any)]
42 | = macro RecordMacros.appendFieldMacro[K]
43 |
44 | //def applyDynamicNamed[K <: String](key: K)(value:Any): Record[_] = macro RecordMacros.
45 | /*
46 | def selectDynamic[K <: String](k: K): Any
47 | = macro Record.extractMacro[K]
48 |
49 | def value: Any
50 | = macro Record.valueMacro
51 | */
52 | //def updateDynamic[K <: String](key: K)(value:Any): Any = macro Record.createMacro3[K]
53 | }
54 |
55 | class RecordMacros(val c: Context) extends MacroHelpers{
56 | import c.universe._
57 |
58 | private def lookup(record: Tree, key: Tree, valueType: Type)
59 | = q"$record.values($key).asInstanceOf[$valueType]"
60 |
61 | private def byKey(typePairs: Seq[Type])
62 | = typePairs.map(p => splitPair(p)._1 -> p).toMap
63 |
64 | private def keyValues(record: Tree)
65 | = splitRefinedTypes(firstTypeArg(record))
66 |
67 | private def newRecord(tpe: Tree, keyValues: Seq[Tree])
68 | = q"""new Record[$tpe](Map(..$keyValues))"""
69 |
70 | private def keyString(key: Tree) =
71 | key match{
72 | case Literal(Constant("apply")) =>
73 | error("Error: .apply is prohibited for Records.")
74 | ???
75 | //q"""${c.prefix}"""
76 | case Literal(Constant(const)) => const
77 | case _ =>
78 | error("Only string literals are allows as keys, not: "+key)
79 | ???
80 | }
81 |
82 | def createRecord(key: Tree, value: Tree)
83 | = newRecord(
84 | tq"(${key.tpe}, ${value.tpe.widen})",
85 | Seq(q"${key} -> ${value}")
86 | )
87 |
88 | def selectMacro[K <: String:c.WeakTypeTag]
89 | (select: Tree)
90 | = {
91 | val selectedTypes = splitRefinedTypes(c.weakTypeTag[K].tpe)
92 |
93 | newRecord(
94 | TypeTree(intersectTypes(
95 | selectedTypes.map(byKey(keyValues(c.prefix.tree)))
96 | )),
97 |
98 | selectedTypes.map{
99 | case ConstantType(key:Constant) =>
100 | q"$key -> ${c.prefix}.values($key)"
101 | }
102 | )
103 | }
104 |
105 |
106 | def appendFieldMacro[K <: String:c.WeakTypeTag](key: Tree)(value: Tree)
107 | = {
108 | keyString(key)
109 | q"""${c.prefix.tree} & ${createRecord(key, value)}"""
110 | }
111 |
112 | def createMacro[K <: String:c.WeakTypeTag](key: Tree)(value: Tree)
113 | = createRecord(key, value)
114 |
115 | def lookupMacro[K <: String:c.WeakTypeTag](key: Tree)
116 | = {
117 | val valueType =
118 | keyValues(c.prefix.tree)
119 | .map(splitPair)
120 | .toMap
121 | .get(key.tpe)
122 | .getOrElse{
123 | error(s"""Record has no key .${keyString(key)}""")
124 | ???
125 | }
126 | lookup(c.prefix.tree, key, valueType)
127 | }
128 | /*
129 | def valueMacro: c.Expr[Any]
130 | = {
131 | import c.universe._
132 | val recordTypeArg = c.prefix.actualType.widen.typeArgs.head
133 |
134 | def splitTypes(t: Type): Seq[Type] = t match {
135 | case RefinedType(types,scope) => types.map(splitTypes(_)).flatten
136 | case t => Seq(t)
137 | }
138 | val keyValuePairTypes: Seq[Type] = splitTypes(recordTypeArg)
139 | if(keyValuePairTypes.size >1){
140 | c.error(
141 | c.enclosingPosition,
142 | ".value can only be called on single-element Records"
143 | )
144 | }
145 |
146 | val Seq(ConstantType(key:Constant),v) = keyValuePairTypes.head.typeArgs
147 | c.Expr[Any](q"""${c.prefix}.values($key).asInstanceOf[$v]""")
148 | }
149 |
150 | def extractMacro[K <: String:c.WeakTypeTag]
151 | (k: c.Expr[K]): c.Expr[Any]
152 | = {
153 | val recordTypeArg = c.prefix.actualType.widen.typeArgs.head
154 |
155 | def splitTypes(t: Type): Seq[Type] = t match {
156 | case RefinedType(types,scope) => types.map(splitTypes(_)).flatten
157 | case t => Seq(t)
158 | }
159 | val keyValuePairTypes: Seq[Type] = splitTypes(recordTypeArg)
160 |
161 | val keyValueMap = keyValuePairTypes.map{
162 | t =>
163 | val args = t.typeArgs
164 | (args(0),args(1))
165 | }.toMap
166 |
167 | val keyString = k.tree match{
168 | case Literal(Constant("apply")) => c.error(
169 | c.enclosingPosition,
170 | "Error: You are trying to use \"apply\" as Record key or call a Record's apply method. Both are prohibited."
171 | )
172 | case Literal(Constant(s)) => s
173 | case _ => c.error(c.enclosingPosition, "Only string literals are allows as keys, not: "+k.tree)
174 | }
175 | val kt = c.weakTypeTag[K]
176 | val v = keyValueMap.get(k.tree.tpe).getOrElse{
177 | c.error(
178 | c.enclosingPosition,
179 | s"""Record has no key .$keyString"""
180 | )
181 | ???
182 | }
183 | c.Expr[Any](q"""new Record[(${k.tree.tpe},$v)](Map(${k.tree} -> ${c.prefix}.values(${k.tree}).asInstanceOf[$v]))""")
184 | }
185 | */
186 | def tupleMacro(record: Tree)
187 | = {
188 | val accessors =
189 | keyValues(record)
190 | .map(splitPair)
191 | .map{
192 | case (ConstantType(key:Constant),valueType) =>
193 | lookup(record,Literal(key),valueType)
194 | }
195 | q"""(..$accessors)"""
196 | }
197 |
198 | def fromCaseClassMacro(obj: Tree)
199 | = {
200 | val tpe = obj.tpe.widen.dealias
201 | assert(tpe.typeSymbol.asInstanceOf[ClassSymbol].isCaseClass)
202 |
203 | val params =
204 | tpe.decls.collectFirst {
205 | case m: MethodSymbol if m.isPrimaryConstructor => m
206 | }.get.paramLists.head
207 |
208 | val names = params.map{ field =>
209 | ( field.name.toTermName.decodedName.toString,
210 | field.typeSignature)
211 | }
212 |
213 | val keyValues = names.map{
214 | case (name,tpe) => (
215 | q"""${Constant(name)} -> ${obj}.${TermName(name)}"""
216 | )
217 | }
218 |
219 | val types = names.map{
220 | case (name,tpe) => (
221 | tq"""(${internal.constantType(Constant(name))},${tpe})"""
222 | )
223 | }.reduce((a,b) => tq"""$a with $b""")
224 |
225 | newRecord(types, keyValues)
226 | }
227 | }
228 |
--------------------------------------------------------------------------------