Skip to content

Commit

Permalink
Fix 'weak' abstract type detection on Scala 3 (#387)
Browse files Browse the repository at this point in the history
* Fix 'weak' abstract type detection on Scala 3 - work around scala/scala3#16107 - check if this-type prefix is present in the owner chain

* fix 2.11 build
  • Loading branch information
neko-kai authored Apr 24, 2023
1 parent 3f2ec7d commit 2c7cf2c
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ final class TagMacro(using override val qctx: Quotes) extends InspectorBase {
override def shift: Int = 0

def createTagExpr[A <: AnyKind: Type]: Expr[Tag[A]] = {
val owners = getClassDefOwners(Symbol.spliceOwner)
val typeRepr = TypeRepr.of[A].dealias
if (allPartsStrong(typeRepr)) {
if (allPartsStrong(owners, typeRepr)) {
createTag[A](typeRepr)
} else {
summonCombinedTag[A](typeRepr)
summonCombinedTag[A](owners, typeRepr)
}
}

Expand All @@ -38,10 +39,10 @@ final class TagMacro(using override val qctx: Quotes) extends InspectorBase {
}
}

private def summonCombinedTag[T <: AnyKind: Type](typeRepr: TypeRepr): Expr[Tag[T]] = {
private def summonCombinedTag[T <: AnyKind: Type](owners: Set[Symbol], typeRepr: TypeRepr): Expr[Tag[T]] = {

def summonLTTAndFastTrackIfNotTypeParam(typeRepr: TypeRepr): Expr[LightTypeTag] = {
if (allPartsStrong(typeRepr)) {
if (allPartsStrong(owners, typeRepr)) {
typeRepr.asType match {
// case given Type[a] => Inspect.inspectAny[a]
case given Type[a] => '{ Inspect.inspect[a] }
Expand All @@ -53,7 +54,7 @@ final class TagMacro(using override val qctx: Quotes) extends InspectorBase {
}

def summonTagAndFastTrackIfNotTypeParam(typeRepr: TypeRepr): Expr[Tag[Any]] = {
if (allPartsStrong(typeRepr)) {
if (allPartsStrong(owners, typeRepr)) {
createTag[Any](typeRepr)
} else {
summonTag[T, Any](typeRepr)
Expand Down Expand Up @@ -206,7 +207,7 @@ final class TagMacro(using override val qctx: Quotes) extends InspectorBase {

// error: the entire type is just a proper type parameter with no type arguments
// it cannot be resolved further
case x if ReflectionUtil.topLevelWeakType(Set.empty, x) =>
case x if ReflectionUtil.topLevelWeakType(owners, Set.empty, x) =>
val tStr = x.show
val implicitMessage = defaultImplicitError.replace("${T}", tStr)
report.errorAndAbort(s"""$tStr is a type parameter without an implicit Tag!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ object Inspect {
def inspectStrong[T <: AnyKind: Type](using qctx: Quotes): Expr[LightTypeTag] = {
import qctx.reflect.*
val tpe = TypeRepr.of[T]
if (ReflectionUtil.allPartsStrong(0, Set.empty, tpe)) {
val owners = ReflectionUtil.getClassDefOwners(Symbol.spliceOwner)
if (ReflectionUtil.allPartsStrong(0, owners, Set.empty, tpe)) {
inspectAny[T]
} else {
report.errorAndAbort(s"Can't materialize LTag[$tpe]: found unresolved type parameters in $tpe")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,12 @@ private[dottyreflection] trait ReflectionUtil { this: InspectorBase =>
}
}

protected def allPartsStrong(typeRepr: TypeRepr): Boolean = {
ReflectionUtil.allPartsStrong(using qctx)(shift, Set.empty, typeRepr)
protected def allPartsStrong(outerOwnerClassDefs: Set[Symbol], typeRepr: TypeRepr): Boolean = {
ReflectionUtil.allPartsStrong(using qctx)(shift, outerOwnerClassDefs, Set.empty, typeRepr)
}

protected def getClassDefOwners(symbol: Symbol): Set[Symbol] = {
ReflectionUtil.getClassDefOwners(using qctx)(symbol)
}

import ReflectionUtil.reflectiveUncheckedNonOverloadedSelectable
Expand Down Expand Up @@ -121,43 +125,75 @@ private[reflect] object ReflectionUtil {
* Returns true if the given type contains no type parameters
* (this means the type is not "weak" https://stackoverflow.com/questions/29435985/weaktypetag-v-typetag)
*/
private[reflect] def allPartsStrong(using qctx: Quotes)(shift: Int, lambdas: Set[qctx.reflect.TypeRepr], typeRepr: qctx.reflect.TypeRepr): Boolean = {
private[reflect] def allPartsStrong(
using qctx: Quotes
)(shift: Int,
outerOwnerClassDefs: Set[qctx.reflect.Symbol],
outerLambdas: Set[qctx.reflect.TypeRepr],
typeRepr: qctx.reflect.TypeRepr
): Boolean = {
import qctx.reflect.*
typeRepr.dealias match {
case x if topLevelWeakType(lambdas, x) => false
case AppliedType(tpe, args) => allPartsStrong(shift, lambdas, tpe) && args.forall(allPartsStrong(shift, lambdas, _))
case AndType(lhs, rhs) => allPartsStrong(shift, lambdas, lhs) && allPartsStrong(shift, lambdas, rhs)
case OrType(lhs, rhs) => allPartsStrong(shift, lambdas, lhs) && allPartsStrong(shift, lambdas, rhs)
case TypeRef(tpe, _) => allPartsStrong(shift, lambdas, tpe)
case TermRef(tpe, _) => allPartsStrong(shift, lambdas, tpe)
case ThisType(tpe) => allPartsStrong(shift, lambdas, tpe)
case x if topLevelWeakType(outerOwnerClassDefs, outerLambdas, x) => false
case AppliedType(tpe, args) =>
allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, tpe) && args.forall(allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, _))
case AndType(lhs, rhs) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, lhs) && allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, rhs)
case OrType(lhs, rhs) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, lhs) && allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, rhs)
case TypeRef(tpe, _) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, tpe)
case TermRef(tpe, _) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, tpe)
case ThisType(tpe) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, tpe)
case NoPrefix() => true
case TypeBounds(lo, hi) => allPartsStrong(shift, lambdas, lo) && allPartsStrong(shift, lambdas, hi)
case lam @ TypeLambda(_, _, body) => allPartsStrong(shift, lambdas + lam, body)
case Refinement(parent, _, tpe) => allPartsStrong(shift, lambdas, tpe) && allPartsStrong(shift, lambdas, parent)
case ByNameType(tpe) => allPartsStrong(shift, lambdas, tpe)
case TypeBounds(lo, hi) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, lo) && allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, hi)
case lam @ TypeLambda(_, _, body) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas + lam, body)
case Refinement(parent, _, tpe) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, tpe) && allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, parent)
case ByNameType(tpe) => allPartsStrong(shift, outerOwnerClassDefs, outerLambdas, tpe)
case strange =>
InspectorBase.log(shift, s"Got unknown type component when checking strength: $strange")
true
}
}

private[reflect] def topLevelWeakType(using qctx: Quotes)(lambdas: Set[qctx.reflect.TypeRepr], typeRepr: qctx.reflect.TypeRepr): Boolean = {
private[reflect] def topLevelWeakType(
using qctx: Quotes
)(outerOwnerClassDefs: Set[qctx.reflect.Symbol],
outerLambdas: Set[qctx.reflect.TypeRepr],
typeRepr: qctx.reflect.TypeRepr
): Boolean = {
import qctx.reflect.*
typeRepr match {
case x if x.typeSymbol.isTypeParam =>
x match {
case t: ParamRef if lambdas.contains(t.binder) => false
case t: ParamRef if outerLambdas.contains(t.binder) => false
case _ => true
}
// we regard abstract types like T in trait X { type T; Tag[this.T] } - when we are _inside_ the definition template
// as 'type parameters' too. So that you could define `implicit def tagForT: Tag[this.T]` and the tag would be resolved
// to this implicit correctly, instead of generating a useless `X::this.type::T` tag.
case x @ TypeRef(ThisType(_), _) if x.typeSymbol.isAbstractType && !x.typeSymbol.isClassDef => true
// TODO: Due to /~https://github.com/lampepfl/dotty/issues/16107 not being fixed we have to make sure we're actually
// inside the definition of the this-type prefix to count it as 'weak' - unlike Scala 2 we're not protected
// from this-types leaking in and have to carry the owner chain here - until that issue is fixed.
case x @ TypeRef(ThisType(prefix), _) if x.typeSymbol.isAbstractType && !x.typeSymbol.isClassDef && outerOwnerClassDefs.contains(prefix.typeSymbol) =>
true
case _ => false
}
}

private[reflect] def getClassDefOwners(using qctx: Quotes)(symbol: qctx.reflect.Symbol): Set[qctx.reflect.Symbol] = {
Iterator
.iterate(symbol) {
s =>
val owner = s.owner
if (owner == null || owner.isNoSymbol || owner == qctx.reflect.defn.RootClass) {
null.asInstanceOf[qctx.reflect.Symbol]
} else {
owner
}
}
.takeWhile(_ ne null)
.filter(s => s.isClassDef && !s.isAbstractType)
.toSet
}

private[reflect] final class UncheckedNonOverloadedSelectable(private val selectable: Any) extends AnyVal with Selectable {

inline def selectDynamic(name: String): Any = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -965,6 +965,18 @@ abstract class SharedTagTest extends AnyWordSpec with XY[String] with TagAsserti
assert(t3InhBases.contains(tres1.tag.ref.withoutArgs.asInstanceOf[LightTypeTagRef.NameReference]))
}

"regression test: do not be confused by a type alias of Set of an abstract type referred via this-prefix on Scala 3" in {
val t1 = Tag[RoleDep.RoleDeps[Int, Int]].tag
val t2 = Tag[Set[RoleDep.RoleDep[Int, Int]]].tag
val t3 = LTT[RoleDep.RoleDeps[Int, Int]]

assertSameStrict(t1, t2)
assertSameStrict(t1, t3)

assertDebugSame(t1, t2)
assertDebugSame(t1, t3)
}

}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,4 +192,9 @@ object TestModel {
type G[F[_], A] >: Unit <: T[F, A]
}

object RoleDep {
type RoleDep[RoleId, C] >: Any
type RoleDeps[RoleId, C] = Set[this.RoleDep[RoleId, C]] // this-prefix is important, do not remove
}

}

0 comments on commit 2c7cf2c

Please sign in to comment.