Skip to content

Commit

Permalink
Merge pull request #283 from sjrd/wildcard-capture
Browse files Browse the repository at this point in the history
Implement capture conversion in subtyping.
  • Loading branch information
sjrd authored Apr 6, 2023
2 parents 6d0923a + 0dbd2d8 commit c5704a7
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 25 deletions.
38 changes: 25 additions & 13 deletions tasty-query/shared/src/main/scala/tastyquery/Subtyping.scala
Original file line number Diff line number Diff line change
Expand Up @@ -235,21 +235,21 @@ private[tastyquery] object Subtyping:
throw InvalidProgramStructureException(s"found type constructor $tycon2 without type params in AppliedType")

def isMatchingApply(tp1: Type): Boolean = tp1.widen match
case tp1: AppliedType =>
tp1.tycon match
case tp1Applied: AppliedType =>
tp1Applied.tycon match
case tycon1: TypeRef =>
tp2.tycon match
case tycon2: TypeRef =>
val tycon1Sym = tycon1.optSymbol
val tycon2Sym = tycon2.optSymbol
if tycon1Sym.isDefined && tycon1Sym == tycon2Sym && isSubprefix(tycon1.prefix, tycon2.prefix) then
isSubArgs(tp1.args, tp2.args, tparams)
isSubArgs(tp1, tp1Applied.args, tp2.args, tparams)
else false
case _ =>
false

case tycon1: TypeParamRef =>
tycon1 == tycon2 && isSubArgs(tp1.args, tp2.args, tparams)
tycon1 == tycon2 && isSubArgs(tp1, tp1Applied.args, tp2.args, tparams)

case _ =>
false
Expand Down Expand Up @@ -282,30 +282,42 @@ private[tastyquery] object Subtyping:
false
end compareAppliedType2

private def isSubArgs(args1: List[Type], args2: List[Type], tparams2: List[TypeParamInfo])(using Context): Boolean =
def isSubArg(arg1: Type, arg2: Type, tparam2: TypeParamInfo): Boolean =
val variance = tparam2.paramVariance
private def isSubArgs(tp1: Type, args1: List[Type], args2: List[Type], tparams: List[TypeParamInfo])(
using Context
): Boolean =
def isSubArg(arg1: Type, arg2: Type, tparam: TypeParamInfo): Boolean =
val variance = tparam.paramVariance

arg2 match
case arg2: WildcardTypeBounds =>
arg2.bounds.contains(arg1)
case _ =>
arg1 match
case arg1: WildcardTypeBounds =>
// TODO? Capture conversion
false
// Attempt capture conversion
tp1 match
case tp1: SingletonType =>
tparam match
case tparam: ClassTypeParamSymbol =>
val wildcardConverted = TypeRef(tp1, tparam)
isSubArg(wildcardConverted, arg2, tparam)
case _ =>
false
case _ =>
// TODO Approximate if co- or contravariant
false
case _ =>
variance.sign match
case 1 => isSubtype(arg1, arg2)
case -1 => isSubtype(arg2, arg1)
case 0 => isSameType(arg1, arg2)
end isSubArg

if args1.sizeCompare(args2) != 0 || args2.sizeCompare(tparams2) != 0 then
throw InvalidProgramStructureException(s"argument count mismatch: isSubArgs($args1, $args2, $tparams2)")
if args1.sizeCompare(args2) != 0 || args2.sizeCompare(tparams) != 0 then
throw InvalidProgramStructureException(s"argument count mismatch: isSubArgs($args1, $args2, $tparams)")

args1.lazyZip(args2).lazyZip(tparams2).forall { (arg1, arg2, tparam2) =>
isSubArg(arg1, arg2, tparam2)
args1.lazyZip(args2).lazyZip(tparams).forall { (arg1, arg2, tparam) =>
isSubArg(arg1, arg2, tparam)
}
end isSubArgs

Expand Down
34 changes: 28 additions & 6 deletions tasty-query/shared/src/main/scala/tastyquery/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import tastyquery.Trees.*
import tastyquery.Variances.*

object Types {
private[tastyquery] final case class LookupIn(pre: TypeRef, sel: SignedName)
private[tastyquery] final case class LookupIn(ownerRef: TypeRef, sel: SignedName)
private[tastyquery] final case class LookupTypeIn(ownerRef: TypeRef, name: TypeName)

private[tastyquery] final case class Scala2ExternalSymRef(owner: Symbol, path: List[Name]) {
val name = path.last
Expand Down Expand Up @@ -588,17 +589,19 @@ object Types {
// ----- Type Proxies -------------------------------------------------

sealed abstract class NamedType extends PathType {
protected type AnyDesignatorType = TermOrTypeSymbol | Name | LookupIn | LookupTypeIn | Scala2ExternalSymRef

type ThisName <: Name
type ThisSymbolType <: TermOrTypeSymbol { type ThisNameType = ThisName }
type ThisNamedType >: this.type <: NamedType
protected type ThisDesignatorType >: ThisSymbolType <: TermOrTypeSymbol | Name | LookupIn | Scala2ExternalSymRef
protected type ThisDesignatorType >: ThisSymbolType <: AnyDesignatorType

val prefix: Prefix

protected def designator: ThisDesignatorType

// For tests
private[tastyquery] final def designatorInternal: TermOrTypeSymbol | Name | LookupIn | Scala2ExternalSymRef =
private[tastyquery] final def designatorInternal: AnyDesignatorType =
designator

private var myName: ThisName | Null = null
Expand Down Expand Up @@ -638,6 +641,7 @@ object Types {
case name: Name => name
case sym: TermOrTypeSymbol => sym.name
case LookupIn(_, sel) => sel
case LookupTypeIn(_, name) => name
case designator: Scala2ExternalSymRef => designator.name
}).asInstanceOf[ThisName]

Expand Down Expand Up @@ -825,7 +829,7 @@ object Types {
new TermRef(prefix, resolved)

private[tastyquery] def resolveLookupIn(designator: LookupIn)(using Context): TermSymbol =
val cls = designator.pre.classSymbol.getOrElse {
val cls = designator.ownerRef.classSymbol.getOrElse {
throw InvalidProgramStructureException(s"Owner of SelectIn($designator) does not refer a class")
}
cls
Expand Down Expand Up @@ -893,13 +897,13 @@ object Types {

final class TypeRef private (
val prefix: Prefix,
private var myDesignator: TypeName | TypeSymbol | Scala2ExternalSymRef
private var myDesignator: TypeName | TypeSymbol | LookupTypeIn | Scala2ExternalSymRef
) extends NamedType {

type ThisName = TypeName
type ThisSymbolType = TypeSymbol
type ThisNamedType = TypeRef
type ThisDesignatorType = TypeName | TypeSymbol | Scala2ExternalSymRef
type ThisDesignatorType = TypeName | TypeSymbol | LookupTypeIn | Scala2ExternalSymRef

// Cache fields
private var myOptSymbol: Option[TypeSymbol] | Null = null
Expand Down Expand Up @@ -943,6 +947,9 @@ object Types {
designator match
case sym: TypeSymbol =>
storeSymbol(sym)
case lookupTypeIn: LookupTypeIn =>
val sym = TypeRef.resolveLookupTypeIn(lookupTypeIn)
storeSymbol(sym)
case externalRef: Scala2ExternalSymRef =>
val sym = NamedType.resolveScala2ExternalRef(externalRef).asType
storeSymbol(sym)
Expand Down Expand Up @@ -1044,6 +1051,9 @@ object Types {
def apply(prefix: Type, name: TypeName): TypeRef = new TypeRef(prefix, name)
def apply(prefix: Prefix, symbol: TypeSymbol): TypeRef = new TypeRef(prefix, symbol)

private[tastyquery] def apply(prefix: Type, lookupTypeIn: LookupTypeIn): TypeRef =
new TypeRef(prefix, lookupTypeIn)

private[tastyquery] def apply(prefix: Prefix, external: Scala2ExternalSymRef): TypeRef =
new TypeRef(prefix, external)

Expand All @@ -1059,6 +1069,18 @@ object Types {
case Some(cls: ClassSymbol) => Some(cls)
case _ => None
end OfClass

private[tastyquery] def resolveLookupTypeIn(designator: LookupTypeIn)(using Context): TypeSymbol =
val cls = designator.ownerRef.classSymbol.getOrElse {
throw InvalidProgramStructureException(s"Owner of LookupTypeIn($designator) does not refer a class")
}
cls.typeParams
.find(_.name == designator.name) // typical case, resulting from persisted capture conversion
.orElse(cls.getDecl(designator.name)) // reference to private class member, or shadowed class member
.getOrElse {
throw MemberNotFoundException(cls, designator.name)
}
end resolveLookupTypeIn
end TypeRef

final class ThisType(val tref: TypeRef) extends PathType with SingletonType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1036,9 +1036,8 @@ private[tasties] class TreeUnpickler(
reader.readEnd()
val name = readName.toTypeName
val prefix = readType
// TODO: use the namespace
val namespace = readType
TypeRef(prefix, name)
val ownerRef = readTypeRef()
TypeRef(prefix, LookupTypeIn(ownerRef, name))
case TYPEREFdirect =>
reader.readByte()
val sym = readSymRef.asType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class ReadTreeSuite extends RestrictedUnpicklingSuite {
private object SimpleTreesPackageRef:
def unapply(tree: PackageRef): Boolean = tree.fullyQualifiedName.path == List(termName("simple_trees"))

private type AnyDesignator = Symbol | Name | LookupIn | Scala2ExternalSymRef
private type AnyDesignator = Symbol | Name | LookupIn | LookupTypeIn | Scala2ExternalSymRef

private object TypeRefInternal:
def unapply(tpe: TypeRef): Some[(Prefix, AnyDesignator)] = Some((tpe.prefix, tpe.designatorInternal))
Expand Down Expand Up @@ -2153,9 +2153,11 @@ class ReadTreeSuite extends RestrictedUnpicklingSuite {
val typerefCheck: StructureCheck = {
case TypeApply(
Select(qualifier, SignedName(SimpleName("withArray"), _, _)),
// TODO: check the namespace ("in") once its taken into account
TypeWrapper(
TypeRefInternal(TermRefInternal(NoPrefix, SymbolWithName(SimpleName("arr"))), TypeName(SimpleName("T")))
TypeRefInternal(
TermRefInternal(NoPrefix, SymbolWithName(SimpleName("arr"))),
LookupTypeIn(TypeRefInternal(ScalaPackageRef(), SimpleTypeName("Array")), SimpleTypeName("T"))
)
) :: Nil
) =>
}
Expand Down
35 changes: 35 additions & 0 deletions tasty-query/shared/src/test/scala/tastyquery/SubtypingSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1253,4 +1253,39 @@ class SubtypingSuite extends UnrestrictedUnpicklingSuite:
.withRef[MatchTypeOldStyleEnums[OldStyleEnum.Parametric[String]], String]
}

testWithContext("capture-conversion") {
val TypeRefInClass = ctx.findTopLevelClass("simple_trees.TypeRefIn")

def finalResultType(tpe: Type): Type = tpe match
case tpe: MethodType => finalResultType(tpe.resultType)
case tpe: PolyType => finalResultType(tpe.resultType)
case _ => tpe

var applyBodyCount = 0

for
case (decl: TermSymbol) <- TypeRefInClass.declarations
if decl.is(Method) && decl.name != nme.Constructor
do
val tree = decl.tree.get.asInstanceOf[DefDef].rhs.get
assert(tree.tpe.isSubtype(finalResultType(decl.declaredType)))

tree match
case Apply(fun, List(arg)) =>
val methodType = fun.tpe.widen.asInstanceOf[MethodType]
assert(clue(arg.tpe).isSubtype(clue(methodType.paramTypes.head)))
applyBodyCount += 1

case Literal(_) =>
// Nothing to check here
()

case _ =>
fail(s"Unexpected method body $tree")
end match
end for

assert(clue(applyBodyCount) == 4)
}

end SubtypingSuite

0 comments on commit c5704a7

Please sign in to comment.