Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ final class LightTypeTagImpl[U <: Universe with Singleton](val u: U, withCache:
val appliedParents = tpeBases(component).filterNot(isHKTOrPolyTypeOrResultTypeArtifact)
val componentRef = makeRef(component)

appliedParents.map {
appliedParents.flatMap {
parentTpe =>
val parentRef = makeRefTop(parentTpe, terminalNames = lambdaParams, isLambdaOutput = lambdaParams.nonEmpty) match {
case unapplied: Lambda =>
Expand All @@ -258,7 +258,8 @@ final class LightTypeTagImpl[U <: Universe with Singleton](val u: U, withCache:
case applied: AppliedReference =>
applied
}
(componentRef, parentRef)
val (refinedParent, typeMemberBaseRefs) = typeMemberRefinementParentAndBases(component, parentTpe, parentRef)
((componentRef, parentRef) :: refinedParent.map(componentRef -> _).toList) ::: typeMemberBaseRefs
}
}
.filterNot {
Expand All @@ -271,6 +272,113 @@ final class LightTypeTagImpl[U <: Universe with Singleton](val u: U, withCache:
appliedBases
}

private[this] def typeMemberRefinementParentAndBases(
tpe: Type,
baseType: Type,
parentRef: AbstractReference
): (Option[AbstractReference], List[(AbstractReference, AbstractReference)]) = {
val (typeMemberDecls, typeMemberBaseRefs) = baseType.decls.toList
.filter(symbol => symbol.isType && !symbol.isClass && !symbol.isParameter && symbol.isAbstract)
.flatMap {
member =>
typeMemberRefinementDecl(tpe, member)
}
.unzip

val decls: Set[RefinementDecl] = typeMemberDecls.toSet

parentRef match {
case parent: AppliedReference if decls.nonEmpty =>
(Some(Refinement(parent, decls)), typeMemberBaseRefs.flatten)
case _ =>
(None, typeMemberBaseRefs.flatten)
}
}

private[this] def typeMemberRefinementDecl(tpe: Type, member: Symbol): Option[(RefinementDecl.TypeMember, List[(AbstractReference, AbstractReference)])] = {
resolveTypeMember(tpe, member.name).flatMap {
member =>
val memberTpe = UniRefinement.typeOfTypeMember(member)
inspectTypeMember(member.name.decodedName.toString, memberTpe)
}
}

private[this] def resolveTypeMember(tpe: Type, name: Name): Option[Symbol] = {
val candidates = (tpe.decls.toList ++ tpe.baseClasses.map(tpe.baseType).flatMap(_.decls.toList))
.filter(symbol => symbol.isType && !symbol.isParameter && symbol.name == name)

candidates.find {
symbol =>
val memberTpe = UniRefinement.typeOfTypeMember(symbol)
nonEmptyTypeMember(memberTpe)
}
}

private[this] def nonEmptyTypeMember(memberTpe: Type): Boolean = {
memberTpe match {
case TypeBounds(lo, hi) =>
val low = makeRef(lo)
val high = makeRef(hi)
low != LightTypeTagInheritance.tpeNothing || high != LightTypeTagInheritance.tpeAny
case _ =>
true
}
}

private[this] def inspectTypeMember(name: String, memberTpe: Type): Option[(RefinementDecl.TypeMember, List[(AbstractReference, AbstractReference)])] = {
val memberBaseRefs = typeMemberComponentTypes(memberTpe).flatMap(typeMemberBases)

memberTpe match {
case TypeBounds(lo, hi) =>
val low = makeRef(lo)
val high = makeRef(hi)
val boundaries = if (low == LightTypeTagInheritance.tpeNothing && high == LightTypeTagInheritance.tpeAny) {
Boundaries.Empty
} else {
Boundaries.Defined(low, high)
}
boundaries match {
case Boundaries.Defined(boundLow, boundHigh) if boundLow == boundHigh =>
Some((RefinementDecl.TypeMember(name, boundHigh), memberBaseRefs))
case defined @ Boundaries.Defined(_, _) =>
Some((RefinementDecl.TypeMember(name, NameReference(SymTypeName(name), defined, None)), memberBaseRefs))
case Boundaries.Empty =>
None
}
case concrete =>
Some((RefinementDecl.TypeMember(name, makeRef(concrete)), memberBaseRefs))
}
}

private[this] def typeMemberComponentTypes(tpe: Type): List[Type] = {
tpe match {
case TypeBounds(lo, hi) =>
List(lo, hi).filterNot(ignored)
case concrete =>
List(concrete)
}
}

private[this] def typeMemberBases(tpe: Type): List[(AbstractReference, AbstractReference)] = {
def directBases(component0: Type): List[(AbstractReference, AbstractReference)] = {
val component = Dealias.fullNormDealias(component0)
val componentRef = makeRef(component)
tpeBases(component)
.filterNot(isHKTOrPolyTypeOrResultTypeArtifact)
.map(parent => componentRef -> makeRef(parent))
}

typeMemberComponentTypes(tpe)
.flatMap {
component =>
directBases(component) ++ component.typeArgs.flatMap(typeMemberComponentTypes).flatMap(directBases)
}
.filterNot {
case (t, parent) =>
parent == t
}
}

private[this] def makeLambdaOnlyBases(mainTpe: Type, allReferenceComponents: Iterator[Type]): List[(AbstractReference, AbstractReference)] = {

@inline def result(): List[(AbstractReference, AbstractReference)] = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package izumi.reflect.dottyreflection

import izumi.reflect.internal.fundamentals.collections.IzCollections.toRich
import izumi.reflect.macrortti.LightTypeTagInheritance
import izumi.reflect.macrortti.LightTypeTagRef
import izumi.reflect.macrortti.LightTypeTagRef.*

Expand Down Expand Up @@ -174,10 +175,11 @@ abstract class FullDbInspector(protected val shift: Int) extends InspectorBase {
val directBaseRefs = if (onlyIndirect) {
Nil
} else {
baseTypes.map {
baseTypes.flatMap {
bt =>
val parentRef = inspector.inspectTypeRepr(bt)
(selfRef, parentRef)
val (refinedParent, typeMemberBaseRefs) = typeMemberRefinementParentAndBases(tpe, bt, parentRef)
((selfRef, parentRef) :: refinedParent.map(selfRef -> _).toList) ::: typeMemberBaseRefs
}
}

Expand All @@ -186,6 +188,72 @@ abstract class FullDbInspector(protected val shift: Int) extends InspectorBase {
argBasesRefs ::: mainBasesRefs
}

private def typeMemberRefinementParentAndBases(
tpe: TypeRepr,
baseType: TypeRepr,
parentRef: AbstractReference
): (Option[AbstractReference], List[(AbstractReference, AbstractReference)]) = {
val (typeMemberDecls, typeMemberBaseRefs) = baseType.typeSymbol.declaredTypes
.filter(member => !member.isTypeParam && !member.isClassDef && member.flags.is(Flags.Deferred))
.flatMap {
member =>
typeMemberRefinementDecl(tpe, member)
}.unzip

val decls: Set[RefinementDecl] = typeMemberDecls.toSet

parentRef match {
case parent: AppliedReference if decls.nonEmpty =>
(Some(LightTypeTagRef.Refinement(parent, decls)), typeMemberBaseRefs.flatten)
case _ =>
(None, typeMemberBaseRefs.flatten)
}
}

private def typeMemberRefinementDecl(tpe: TypeRepr, member: Symbol): Option[(RefinementDecl.TypeMember, List[(AbstractReference, AbstractReference)])] = {
resolveTypeMember(tpe, member.name).flatMap {
member =>
tpe.memberType(member) match {
case bounds: TypeBounds =>
val boundaries = inspectTypeBounds(bounds)
boundaries match {
case Boundaries.Defined(low, high) if low == high =>
Some((RefinementDecl.TypeMember(member.name, high), processTypeBounds(bounds)))
case defined @ Boundaries.Defined(_, _) =>
Some((RefinementDecl.TypeMember(member.name, NameReference(SymName.SymTypeName(member.name), defined, None)), processTypeBounds(bounds)))
case Boundaries.Empty =>
None
}
case _ =>
None
}
}
}

private def resolveTypeMember(tpe: TypeRepr, name: String): Option[Symbol] = {
val candidates = (tpe.typeSymbol.declaredTypes ++ tpe.baseClasses.map(tpe.baseType).flatMap(_.typeSymbol.declaredTypes))
.filterNot(_.isTypeParam)
.filter(_.name == name)

candidates.find {
symbol =>
tpe.memberType(symbol) match {
case bounds: TypeBounds => inspectTypeBounds(bounds) != Boundaries.Empty
case _ => false
}
}
}

private def inspectTypeBounds(bounds: TypeBounds): Boundaries = {
val low = inspector.inspectTypeRepr(bounds.low)
val high = inspector.inspectTypeRepr(bounds.hi)
if (low == LightTypeTagInheritance.tpeNothing && high == LightTypeTagInheritance.tpeAny) {
Boundaries.Empty
} else {
Boundaries.Defined(low, high)
}
}

private def inspectTypeBoundsToFull(tpe: TypeRepr): List[(AbstractReference, AbstractReference)] = {
tpe._dealiasSimplifiedFull match {
case t: TypeBounds =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,14 +217,14 @@ final class LightTypeTagInheritance(self: LightTypeTag, other: LightTypeTag) {
private def compareDecl(ctx: Ctx)(s: RefinementDecl, t: RefinementDecl): Boolean = (s, t) match {
case (
RefinementDecl.TypeMember(ln, lref),
RefinementDecl.TypeMember(rn, NameReference(SymName.SymTypeName(rn1), rBounds, None))
RefinementDecl.TypeMember(rn, NameReference(SymName.SymTypeName(rn1), rBounds, _))
) if rn == rn1 =>
// we're comparing two abstract types type X = X|>:A<:B|
// We know that the type is abstract if its name matches the type member's name
ln == rn && compareBounds(ctx)(lref, rBounds)
case (RefinementDecl.TypeMember(ln, lref), RefinementDecl.TypeMember(rn, rref)) =>
// if the rhs type is not abstract (has form `type X = Int`), then lhs must be exactly equal to it, not <:
ln == rn && lref == rref
ln == rn && (lref == rref || (ctx.isChild(lref, rref) && ctx.isChild(rref, lref)))
case (RefinementDecl.Signature(ln, lins, lout), RefinementDecl.Signature(rn, rins, rout)) =>
(ln == rn
&& lins.iterator.zipAll(rins.iterator, null, null).forall {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,17 @@ abstract class SharedLightTypeTagTest extends TagAssertions {
assertNotChildStrict(LTT[{ def T: Int }], LTT[{ type T }])
}

"support subtype checks against structural refinements of inherited type members" in {
assertChild(LTT[Issue481AInt], LTT[Issue481A { type T = Int }])
assertChild(LTT[Issue481AString], LTT[Issue481A { type T = String }])

assertNotChild(LTT[Issue481AInt], LTT[Issue481A { type T = String }])
assertNotChild(LTT[Issue481AString], LTT[Issue481A { type T = Int }])

assertChild(LTT[Issue481AInt], LTT[Issue481A { type T <: AnyVal }])
assertNotChild(LTT[Issue481AString], LTT[Issue481A { type T <: AnyVal }])
}

"what about non-empty refinements with intersections" in {
val ltt = LTT[Int with Object with Option[String] { def a: Boolean }]
val debug = ltt.debug()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,16 @@ object TestModel {
type WithX = { type X }
type FXS <: { type F[A] = A }

trait Issue481A {
type T
}
trait Issue481AInt extends Issue481A {
override type T = Int
}
trait Issue481AString extends Issue481A {
override type T = String
}

trait H1
trait H2 extends H1
trait H3 extends H2
Expand Down
Loading