Skip to content

Commit

Permalink
Scala 3: fix prefix resolution, use type qualifier as prefix, not def…
Browse files Browse the repository at this point in the history
…inition template (except for this-type prefixes)
  • Loading branch information
neko-kai committed Nov 21, 2022
1 parent 5f914cc commit af014c5
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 125 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ abstract class Inspector(protected val shift: Int) extends InspectorBase {
makeNameReferenceFromType(term)

case r: TypeRef =>
next().inspectSymbol(r.typeSymbol, Some(r))
next().inspectSymbol(r.typeSymbol, Some(r), Some(r))

case tb: TypeBounds =>
inspectBounds(outerTypeRef, tb, isParam = false)
Expand All @@ -94,7 +94,7 @@ abstract class Inspector(protected val shift: Int) extends InspectorBase {
}
}

private def inspectBounds(outerTypeRef: Option[qctx.reflect.TypeRef], tb: TypeBounds, isParam: Boolean): AbstractReference = {
private def inspectBounds(outerTypeRef: Option[TypeRef], tb: TypeBounds, isParam: Boolean): AbstractReference = {
val hi = next().inspectTypeRepr(tb.hi)
val low = next().inspectTypeRepr(tb.low)
if (hi == low) {
Expand All @@ -112,17 +112,17 @@ abstract class Inspector(protected val shift: Int) extends InspectorBase {
} else {
// Boundaries which are not parameters are named types (e.g. type members) and are NOT wildcards
// if hi and low boundaries are defined and distinct, type is not reducible to one of them
val typeSymbol = outerTypeRef.getOrElse(tb).typeSymbol
val symref = makeNameReferenceFromSymbol(typeSymbol).copy(boundaries = boundaries)
val typeRepr = outerTypeRef.getOrElse(tb)
val symref = makeNameReferenceFromType(typeRepr).copy(boundaries = boundaries)
symref
}
}
}

private[dottyreflection] def inspectSymbol(symbol: Symbol, outerTypeRef: Option[TypeRef] = None): AbstractReference = {
private[dottyreflection] def inspectSymbol(symbol: Symbol, outerTypeRef: Option[TypeRef], prefixSource: Option[NamedType]): AbstractReference = {
symbol match {
case s if s.isClassDef || s.isValDef || s.isBind =>
makeNameReferenceFromSymbol(symbol)
makeNameReferenceFromSymbol(symbol, prefixSource)

case s if s.isTypeDef =>
log(s"inspectSymbol: Found TypeDef symbol $s")
Expand Down Expand Up @@ -177,30 +177,30 @@ abstract class Inspector(protected val shift: Int) extends InspectorBase {
t match {
case ref: TypeRef =>
log(s"make name reference from type $ref termSymbol=${ref.termSymbol}")
makeNameReferenceFromSymbol(ref.typeSymbol)
makeNameReferenceFromSymbol(ref.typeSymbol, Some(ref))
case term: TermRef =>
log(s"make name reference from term $term")
makeNameReferenceFromSymbol(term.termSymbol)
makeNameReferenceFromSymbol(term.termSymbol, Some(term))
case t: ParamRef =>
NameReference(tpeName = t.binder.asInstanceOf[LambdaType].paramNames(t.paramNum).toString)
case constant: ConstantType =>
NameReference(SymName.SymLiteral(constant.constant.value), Boundaries.Empty, prefix = None)
case ref =>
log(s"make name reference from what? $ref ${ref.getClass} ${ref.termSymbol}")
makeNameReferenceFromSymbol(ref.typeSymbol)
makeNameReferenceFromSymbol(ref.typeSymbol, None)
}
}

private[dottyreflection] def makeNameReferenceFromSymbol(symbol: Symbol): NameReference = {
private[dottyreflection] def makeNameReferenceFromSymbol(symbol: Symbol, prefixSource1: Option[NamedType]): NameReference = {
def default: NameReference = {
val (symName, s) = if (symbol.isTerm || symbol.isBind || symbol.isValDef) {
SymName.SymTermName(symbol.fullName) -> symbol
val (symName, s, prefixSource2) = if (symbol.isTerm || symbol.isBind || symbol.isValDef) {
(SymName.SymTermName(symbol.fullName), symbol, symbol.termRef)
} else if (symbol.flags.is(Flags.Module)) { // Handle ModuleClasses (can creep in from ThisType)
SymName.SymTermName(symbol.companionModule.fullName) -> symbol.companionModule
(SymName.SymTermName(symbol.companionModule.fullName), symbol.companionModule, symbol.companionModule.termRef)
} else {
SymName.SymTypeName(symbol.fullName) -> symbol
(SymName.SymTypeName(symbol.fullName), symbol, symbol.termRef)
}
val prefix = getPrefixFromDefinitionOwner(s) // FIXME: should get prefix from type qualifier (prefix), not from owner
val prefix = getPrefixFromQualifier(prefixSource1.getOrElse(prefixSource2))
NameReference(symName, Boundaries.Empty, prefix)
}

Expand All @@ -212,7 +212,7 @@ abstract class Inspector(protected val shift: Int) extends InspectorBase {
case constant: ConstantType => // constant type vals are aliases to constant types
makeNameReferenceFromType(constant)
case t: TermRef => // singleton type vals are aliases to their singleton
makeNameReferenceFromSymbol(t.termSymbol)
makeNameReferenceFromSymbol(t.termSymbol, Some(t))
case other =>
log(s"inspectSymbol: found UNKNOWN symbol in ValDef $other")
default
Expand All @@ -222,37 +222,43 @@ abstract class Inspector(protected val shift: Int) extends InspectorBase {
}
}

// FIXME: should get prefix from type qualifier (prefix), not from owner
private def getPrefixFromDefinitionOwner(symbol: Symbol): Option[AppliedReference] = {
val maybeOwner = symbol.maybeOwner
if (!maybeOwner.exists || maybeOwner.isNoSymbol || maybeOwner.isPackageDef || maybeOwner.isDefDef || maybeOwner.isTypeDef) {
None
} else {
inspectSymbol(maybeOwner) match {
case a: AppliedReference =>
log(s"Constructed prefix=$a from owner=$maybeOwner")
Some(a)
case _ =>
private def getPrefixFromQualifier(tpe: NamedType): Option[AppliedReference] = {
tpe.qualifier match {
// Support https://github.com/zio/izumi-reflect/issues/35
// consider class member's this-prefix to be the defining template, not the most specific prefix from where the call is happening
case _: ThisType | _: SuperType | _: RecursiveThis =>
val s = if (!tpe.termSymbol.isNoSymbol) {
tpe.termSymbol
} else {
tpe.typeSymbol
}

val maybeOwner = s.maybeOwner
if (!maybeOwner.exists || maybeOwner.isNoSymbol || maybeOwner.isPackageDef || maybeOwner.isDefDef || maybeOwner.isTypeDef || maybeOwner.isLocalDummy) {
None
}
} else {
inspectSymbol(maybeOwner, None, None) match {
case a: AppliedReference =>
log(s"Constructed prefix=$a from owner=$maybeOwner")
Some(a)
case _ =>
None
}
}

case prefix =>
val typeSymbol = prefix.typeSymbol
if (!typeSymbol.exists || typeSymbol.isNoSymbol || typeSymbol.isPackageDef || typeSymbol.isDefDef || typeSymbol.isTypeDef || typeSymbol.isLocalDummy) {
None
} else {
inspectTypeRepr(prefix) match {
case reference: AppliedReference =>
log(s"Constructed prefix=$reference from prefix=$prefix")
Some(reference)
case _ => None
}
}
}
}
// private def getPrefixFromQualifier(t: TypeRepr): Option[AppliedReference] = {
// @tailrec def unpack(tpe: TypeRepr): Option[TypeRepr] = tpe match {
// case t: ThisType => unpack(t.tref)
// case _: NoPrefix => None
// case t =>
// ??? // nonsense below
// val typeSymbol = t.typeSymbol
// if (!typeSymbol.exists || typeSymbol.isNoSymbol || typeSymbol.isPackageDef || typeSymbol.isDefDef) {
// None
// } else {
// Some(t)
// }
// }
// unpack(t).flatMap(inspectTypeRepr(_) match {
// case reference: AppliedReference => Some(reference)
// case _ => None
// })
// }

}
Original file line number Diff line number Diff line change
Expand Up @@ -124,21 +124,25 @@ abstract class SharedLightTypeTagProgressionTest extends TagAssertions with TagP

"progression test: what about non-empty refinements with intersections" in {
val ltt = LTT[Int with Object with Option[String] { def a: Boolean }]
println(ltt.debug())
assert(!ltt.debug().contains("<refinement>"))
assert(!ltt.debug().contains("* String"))
val debug = ltt.debug()
assert(!debug.contains("<refinement>"))
doesntWorkYetOnDotty {
assert(!debug.contains("<none>"))
}
assert(!debug.contains("* String"))
doesntWorkYetOnDotty {
assert(debug.contains("- java.lang.String"))
}
}

"progression test: can't support subtyping of type prefixes" in {
val a = new C {}

doesntWorkYetOnScala2 {
doesntWorkYet {
assertChild(LTT[a.A], LTT[C#A])
}
doesntWorkYetOnDotty {
assertDifferent(LTT[a.A], LTT[C#A])
assertNotChild(LTT[C#A], LTT[a.A])
}
assertDifferent(LTT[a.A], LTT[C#A])
assertNotChild(LTT[C#A], LTT[a.A])
}

"progression test: can't support subtyping of concrete type projections" in {
Expand All @@ -151,10 +155,8 @@ abstract class SharedLightTypeTagProgressionTest extends TagAssertions with TagP
val tagB = LTT[B#T]

assertSame(LTT[A#T], LTT[A#T])
doesntWorkYetOnDotty {
assertDifferent(LTT[B#T], LTT[A#T])
}
doesntWorkYetOnScala2 {
assertDifferent(LTT[B#T], LTT[A#T])
doesntWorkYet {
assertChild(tagB, tagA)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,6 @@ import scala.util.Try

abstract class SharedTagProgressionTest extends AnyWordSpec with TagAssertions with TagProgressions with InheritedModel {

trait DockerContainer[T]
trait ContainerDef {
type T

def make(implicit t: Tag[T]) = {
val _ = t
Tag[DockerContainer[T]]
}
}

"[progression] Tag (all versions)" should {

"progression test: can't handle parameters in defs/vals in structural types" in {
Expand Down Expand Up @@ -110,7 +100,7 @@ abstract class SharedTagProgressionTest extends AnyWordSpec with TagAssertions w
)
}

"progression test: projections into singletons are not handled properly" in {
"progression test: projections into singletons are not handled properly (on Scala 2)" in {
trait A {
class X

Expand Down Expand Up @@ -152,10 +142,7 @@ abstract class SharedTagProgressionTest extends AnyWordSpec with TagAssertions w
assertSame(Tag[A#S1].tag, B.s1b1)

// progression: this still fails; see https://github.com/zio/izumi-reflect/issues/192
// projection into singleton generates a form `_1.singleton2.type forSome { val _1: A }` which is not handled
doesntWorkYetOnDotty {
assert(!Tag[A#S2].tag.debug().contains("::_$A::singleton2"))
}
// projection into singleton generates a form `_1.singleton2.type forSome { val _1: A }` which is not handled on Scala 2
doesntWorkYetOnScala2 {
assertSame(Tag[A#S2].tag, B.s2a)
}
Expand Down Expand Up @@ -283,23 +270,6 @@ abstract class SharedTagProgressionTest extends AnyWordSpec with TagAssertions w
}
}

"progression test: Dotty fails to correctly resolve abstract types inside traits when summoned inside trait" in {
val a = new ContainerDef {}
val b = new ContainerDef {}

assert(a.make.tag == Tag[DockerContainer[a.T]].tag)
doesntWorkYetOnDotty {
assertDifferent(a.make.tag, Tag[DockerContainer[b.T]].tag)
}
assert(Tag[DockerContainer[a.T]].tag == Tag[DockerContainer[a.T]].tag)

val zy = new ZY {}
assert(zy.tagT.getMessage contains "could not find implicit value")
assert(zy.tagU.getMessage contains "could not find implicit value")
assert(zy.tagV.getMessage contains "could not find implicit value")
assert(zy.tagA.isSuccess)
}

"progression test: Dotty fails to can resolve parameters in structural types" in {
def t[X: Tag]: Tag[{ type T = X }] = Tag[{ type T = X }]

Expand Down Expand Up @@ -389,28 +359,6 @@ abstract class SharedTagProgressionTest extends AnyWordSpec with TagAssertions w
}
}

"Work with term type prefixes" in {
val zy = new ZY {}
val zx = new ZY {}

assertSameStrict(Tag[zy.T].tag, fromLTag[zy.T])
doesntWorkYetOnDotty {
assertNotChildStrict(Tag[zy.T].tag, fromLTag[zx.T])
}
assertSameStrict(Tag[zy.x.type].tag, fromLTag[zy.x.type])
assertChild(Tag[zy.x.type].tag, fromLTag[String])
assertChild(Tag[zy.x.type].tag, fromLTag[java.io.Serializable])
doesntWorkYetOnDotty {
assertNotChildStrict(Tag[zy.x.type].tag, fromLTag[zx.x.type])
}
assertSameStrict(Tag[zy.y.type].tag, fromLTag[zy.y.type])
assertChild(Tag[zy.y.type].tag, fromLTag[java.lang.Object])
doesntWorkYetOnDotty {
assertNotChildStrict(Tag[zy.y.type].tag, fromLTag[zx.y.type])
}
assertNotChildStrict(Tag[zy.y.type].tag, fromLTag[zx.x.type])
}

"Work for any abstract type with available Tag while preserving additional type refinement" in {
def testTag[T: Tag] = Tag[T { type X = Int; type Y = String }]

Expand Down Expand Up @@ -446,6 +394,4 @@ abstract class SharedTagProgressionTest extends AnyWordSpec with TagAssertions w

}

def fromLTag[T: LTag]: LightTypeTag = LTag[T].tag

}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ abstract class SharedTagTest extends AnyWordSpec with XY[String] with TagAsserti
trait T1[A, B, C, D, E, F[_]]
trait YX[V] extends XY[V]

trait DockerContainer[T]
trait ContainerDef {
type T

def make(implicit t: Tag[T]) = {
val _ = t
Tag[DockerContainer[T]]
}
}

"Tag (all versions)" should {

"Work for any concrete type" in {
Expand Down Expand Up @@ -309,17 +319,17 @@ abstract class SharedTagTest extends AnyWordSpec with XY[String] with TagAsserti

object B extends B

assertSame(B.xa, B.xb)
assertSame(B.s1a, B.s1b)
assertSame(B.s1a1, B.s1b1)
assertSame(B.s2a, B.s2b)
assertSame(B.s2a1, B.s2b1)
assertSameStrict(B.xa, B.xb)
assertSameStrict(B.s1a, B.s1b)
assertSameStrict(B.s1a1, B.s1b1)
assertSameStrict(B.s2a, B.s2b)
assertSameStrict(B.s2a1, B.s2b1)

assertSame(Tag[A#X].tag, B.xa)
assertSameStrict(Tag[A#X].tag, B.xa)

assertSame(B.s1b, B.s1a)
assertSame(B.s1a, B.s1a1)
assertSame(B.s1b, B.s1b1)
assertSameStrict(B.s1b, B.s1a)
assertSameStrict(B.s1a, B.s1a1)
assertSameStrict(B.s1b, B.s1b1)
}

"Does NOT synthesize Tags for abstract types, but recursively summons Tag[this.Abstract]" in {
Expand Down Expand Up @@ -642,6 +652,38 @@ abstract class SharedTagTest extends AnyWordSpec with XY[String] with TagAsserti
assert(Tag[ZY#Y].hasPreciseClass)
}

"Work with term type prefixes" in {
val zy = new ZY {}
val zx = new ZY {}

assertSameStrict(Tag[zy.T].tag, LTT[zy.T])
assertNotChildStrict(Tag[zy.T].tag, LTT[zx.T])
assertSameStrict(Tag[zy.x.type].tag, LTT[zy.x.type])
assertChild(Tag[zy.x.type].tag, LTT[String])
assertChild(Tag[zy.x.type].tag, LTT[java.io.Serializable])
assertNotChildStrict(Tag[zy.x.type].tag, LTT[zx.x.type])
assertSameStrict(Tag[zy.y.type].tag, LTT[zy.y.type])
assertChild(Tag[zy.y.type].tag, LTT[java.lang.Object])
assertNotChildStrict(Tag[zy.y.type].tag, LTT[zx.y.type])
assertNotChildStrict(Tag[zy.y.type].tag, LTT[zx.x.type])
}

"correctly resolve abstract types inside traits when summoned inside trait" in {
val a = new ContainerDef {}
val b = new ContainerDef {}

assert(a.make.tag == Tag[DockerContainer[a.T]].tag)
assertDifferent(a.make.tag, Tag[DockerContainer[b.T]].tag)
assertSameStrict(Tag[DockerContainer[a.T]].tag, Tag[DockerContainer[a.T]].tag)
assertDifferent(Tag[DockerContainer[a.T]].tag, Tag[DockerContainer[b.T]].tag)

val zy = new ZY {}
assert(zy.tagT.getMessage contains "could not find implicit value")
assert(zy.tagU.getMessage contains "could not find implicit value")
assert(zy.tagV.getMessage contains "could not find implicit value")
assert(zy.tagA.isSuccess)
}

}

}
Expand Down
Loading

0 comments on commit af014c5

Please sign in to comment.