Skip to content

Commit

Permalink
Make FieldType and @@ abstract
Browse files Browse the repository at this point in the history
aka. "You shall not dealias"

Fixes a bunch of problems caused by:
1. the compiler normalizing types behind your back;
2. the compiler failing to unify the normalized types.
  • Loading branch information
joroKr21 committed Oct 13, 2018
1 parent 1185bc2 commit 5eaf2fa
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 51 deletions.
20 changes: 7 additions & 13 deletions core/src/main/scala/shapeless/generic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ trait ReprTypes {

def atatTpe = typeOf[tag.@@[_,_]].typeConstructor
def fieldTypeTpe = typeOf[shapeless.labelled.FieldType[_, _]].typeConstructor
def keyTagTpe = typeOf[shapeless.labelled.KeyTag[_, _]].typeConstructor
def keyTagTpe = typeOf[shapeless.labelled.KeyTag[_]].typeConstructor
def symbolTpe = typeOf[Symbol]
}

Expand Down Expand Up @@ -516,20 +516,14 @@ trait CaseClassMacros extends ReprTypes with CaseClassMacrosVersionSpecifics {
}

object FieldType {
import internal._
val FieldTypeSym = fieldTypeTpe.typeSymbol

def apply(kTpe: Type, vTpe: Type): Type =
refinedType(List(vTpe, typeRef(prefix(keyTagTpe), keyTagTpe.typeSymbol, List(kTpe, vTpe))), NoSymbol)

def unapply(fTpe: Type): Option[(Type, Type)] = {
val KeyTagPre = prefix(keyTagTpe)
val KeyTagSym = keyTagTpe.typeSymbol
fTpe.dealias match {
case RefinedType(v0 :+ TypeRef(pre, KeyTagSym, List(k, v1)), scope)
if pre =:= KeyTagPre && refinedType(v0, NoSymbol, scope, NoPosition) =:= v1 =>
Some((k, v1))
case _ => None
}
appliedType(fieldTypeTpe, List(kTpe, vTpe))

def unapply(fTpe: Type): Option[(Type, Type)] = fTpe.dealias match {
case TypeRef(_, FieldTypeSym, List(k, v)) => Some((k, v))
case _ => None
}
}

Expand Down
18 changes: 13 additions & 5 deletions core/src/main/scala/shapeless/labelled.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,31 @@
package shapeless

import scala.language.experimental.macros
import scala.reflect._

import scala.reflect.macros.whitebox

object labelled {
private[shapeless] trait LabelledTypes {

/**
* The type of fields with keys of singleton type `K` and value type `V`.
*/
type FieldType[K, +V] = V with KeyTag[K, V]
trait KeyTag[K, +V]
type FieldType[K, +V] <: V with KeyTag[K]
trait KeyTag[K]
}

object labelled extends LabelledTypes {

implicit def fieldTypeClassTag[K, V](implicit tag: ClassTag[V]): ClassTag[FieldType[K, V]] =
ClassTag(tag.runtimeClass)

/**
* Yields a result encoding the supplied value with the singleton type `K' of its key.
*/
def field[K] = new FieldBuilder[K]
def field[K]: FieldBuilder[K] = new FieldBuilder

class FieldBuilder[K] {
def apply[V](v : V): FieldType[K, V] = v.asInstanceOf[FieldType[K, V]]
def apply[V](v: V): FieldType[K, V] = v.asInstanceOf[FieldType[K, V]]
}
}

Expand Down
28 changes: 12 additions & 16 deletions core/src/main/scala/shapeless/singletons.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@ package shapeless

import scala.language.dynamics
import scala.language.experimental.macros

import scala.reflect.macros.whitebox

import tag.@@
import scala.util.Try

/** Provides the value corresponding to a singleton type.
Expand Down Expand Up @@ -147,22 +144,21 @@ trait SingletonTypeUtils extends ReprTypes {
}

object SingletonSymbolType {
val atatTpe = typeOf[@@[_,_]].typeConstructor
val TaggedSym = typeOf[tag.Tagged[_]].typeConstructor.typeSymbol
val AtatSym = atatTpe.typeSymbol
val SymbolTpe = symbolTpe

def unrefine(t: Type): Type =
t.dealias match {
case RefinedType(List(t), scope) if scope.isEmpty => unrefine(t)
case t => t
}
def unrefine(t: Type): Type = t.dealias match {
case RefinedType(List(t), scope) if scope.isEmpty => unrefine(t)
case t => t
}

def apply(s: String): Type = appliedType(atatTpe, List(SymTpe, c.internal.constantType(Constant(s))))
def apply(s: String): Type =
appliedType(atatTpe, List(SymbolTpe, c.internal.constantType(Constant(s))))

def unapply(t: Type): Option[String] =
unrefine(t).dealias match {
case RefinedType(List(SymTpe, TypeRef(_, TaggedSym, List(ConstantType(Constant(s: String))))), _) => Some(s)
case _ => None
}
def unapply(t: Type): Option[String] = unrefine(t).dealias match {
case TypeRef(_, AtatSym, List(SymbolTpe, ConstantType(Constant(s: String)))) => Some(s)
case _ => None
}
}

def mkSingletonSymbol(s: String): Tree = {
Expand Down
17 changes: 12 additions & 5 deletions core/src/main/scala/shapeless/typeoperators.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,24 @@ package shapeless
import scala.language.dynamics
import scala.language.experimental.macros

import scala.reflect.ClassTag
import scala.reflect.macros.whitebox
import scala.util.{ Try, Success, Failure }

object tag {
def apply[U] = new Tagger[U]

private[shapeless] trait TagTypes {
type @@[+T, U] <: T with Tagged[U]
trait Tagged[U]
type @@[+T, U] = T with Tagged[U]
}

object tag extends TagTypes {

implicit def atatClassTag[T, U](implicit tag: ClassTag[T]): ClassTag[T @@ U] =
ClassTag(tag.runtimeClass)

def apply[U]: Tagger[U] = new Tagger

class Tagger[U] {
def apply[T](t : T) : T @@ U = t.asInstanceOf[T @@ U]
def apply[T](t: T): T @@ U = t.asInstanceOf[T @@ U]
}
}

Expand Down
9 changes: 0 additions & 9 deletions core/src/test/scala/shapeless/labelledgeneric.scala
Original file line number Diff line number Diff line change
Expand Up @@ -114,15 +114,6 @@ object ScalazTaggedAux {
def apply() = "HNil"
}

implicit def hconsTCTagged[K <: Symbol, H, HT, T <: HList](implicit
key: Witness.Aux[K],
headTC: Lazy[TC[H @@ HT]],
tailTC: Lazy[TC[T]]
): TC[FieldType[K, H @@ HT] :: T] =
new TC[FieldType[K, H @@ HT] :: T] {
def apply() = s"${key.value.name}: ${headTC.value()} :: ${tailTC.value()}"
}

implicit def hconsTC[K <: Symbol, H, T <: HList](implicit
key: Witness.Aux[K],
headTC: Lazy[TC[H]],
Expand Down
17 changes: 17 additions & 0 deletions core/src/test/scala/shapeless/lazy.scala
Original file line number Diff line number Diff line change
Expand Up @@ -316,4 +316,21 @@ class LazyStrictTests {
"lazily[W[String, Int]]", "No W\\[String, Int]"
)
}

@Test
def testInteractionWithTaggedTypes(): Unit = {
import tag._

class Readable[A]
trait IdTag
type Id = String @@ IdTag

implicit def taggedStringReadable[T, M[_, _]](
implicit ev: String @@ T =:= M[String, T]
): Readable[M[String, T]] = new Readable

implicitly[Readable[Id]]
implicitly[Lazy[Readable[Id]]]
implicitly[Strict[Readable[Id]]]
}
}
13 changes: 13 additions & 0 deletions core/src/test/scala/shapeless/records.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1154,4 +1154,17 @@ class RecordTests {

assertTypedEquals[Witness.`'a`.T](swapped.head, select(swapped))
}

@Test
def testFieldTypeAny(): Unit = {
val field = "foo" ->> (1: Any)
typed[FieldType[Witness.`"foo"`.T, Any]](field)
assertEquals(1, field)
}

@Test
def testFieldTypeArray(): Unit = {
val fields = Array("shapeless" ->> 42)
assertEquals(42, fields.head)
}
}
33 changes: 32 additions & 1 deletion core/src/test/scala/shapeless/typeoperators.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class TypeOperatorTests {
trait ATag

object ATag {
implicit def taggedToString[T](value: T with Tagged[ATag]): String = message
implicit def taggedToString[T](value: T @@ ATag): String = message

val message = "This object has ATag tag type"
}
Expand Down Expand Up @@ -213,6 +213,37 @@ class TypeOperatorTests {
val x = the[AValueClass]
typed[AValueClass](x)
}

@Test
def testAliasingTaggedType(): Unit = {
type Name = String @@ ATag
val name1 = tag[ATag]("ben")
val name2: Name = tag[ATag]("ben")
val name3: Name = tag.apply("ben")
assertTypedEquals[Name](name1, name2)
assertTypedEquals[Name](name1, name3)
}

@Test
def testTaggedAny(): Unit = {
val one = tag[ATag](1: Any)
typed[Any @@ ATag](one)
assertEquals(1, one)
}

@Test
def testTaggedBoxing(): Unit = {
val one = tag[ATag](1)
typed[Int @@ ATag](one)
assertEquals(1, one)
assert(!one.getClass.isPrimitive)
}

@Test
def testTaggedValueClass(): Unit = {
val x = tag[ATag](AValueClass(1L))
assertEquals(x, Array(x).head)
}
}

object TypeOperatorTests {
Expand Down
4 changes: 2 additions & 2 deletions examples/src/main/scala/shapeless/examples/partition.scala
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ object ADTPartitionExample extends App {
typed[List[Apple]](apples)
typed[List[Pear]](pears)

assert(apples == expectedApples)
assert(pears == expectedPears)
assert((apples: List[Apple]) == expectedApples)
assert((pears: List[Pear]) == expectedPears)

// Partition the list into a record of lists.
val basket = partitionRecord(fruits)
Expand Down

0 comments on commit 5eaf2fa

Please sign in to comment.