Skip to content

Commit

Permalink
Fix scalacenter#423: Merge the TypeBounds of type members without nee…
Browse files Browse the repository at this point in the history
…ding subtyping.

There was an infinite recursion between looking up a type member
of a refinement and subtyping of that member against the same
refinement. This came from computing the merged TypeBounds of the
type member during subtyping, which used subtyping to get rid of
useless bounds.

We break the cycle by not using subtyping when merging the
TypeBounds of a type member anymore. Instead, we manually dive into
possibly-higher-kinded bounds (`TypeLambda`s themselves, but also
`Nothing` and `AnyKind`), and otherwise construction a union or
intersection type.

Unwrapping higher-kinded bounds is necessary because constructing
a uniont or intersection requires proper types.
  • Loading branch information
sjrd committed Dec 18, 2023
1 parent e711b4a commit 539dd7f
Showing 1 changed file with 49 additions and 1 deletion.
50 changes: 49 additions & 1 deletion tasty-query/shared/src/main/scala/tastyquery/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ object Types {
ResolveMemberResult.TypeMember(rightSyms, rightBounds)
) =>
val syms = mergeSyms(leftSyms, rightSyms)
val bounds = leftBounds.intersect(rightBounds)
val bounds = mergeTypeMemberTypeBounds(leftBounds, rightBounds)
ResolveMemberResult.TypeMember(syms, bounds)

// Cases that cannot happen -- list them to preserve exhaustivity checking of every other case
Expand Down Expand Up @@ -228,6 +228,54 @@ object Types {
case _ =>
throw InvalidProgramStructureException(s"Cannot merge types $tp1 and $tp2")
end mergeTermMemberTypes

private def mergeTypeMemberTypeBounds(bounds1: TypeBounds, bounds2: TypeBounds)(using Context): TypeBounds =
// This implementation assumes that the program structure is valid
(bounds1, bounds2) match
case _ if bounds1 eq bounds2 =>
bounds1
case (bounds1: TypeAlias, _) =>
bounds1
case (_, bounds2: TypeAlias) =>
bounds2

case (bounds1 @ AbstractTypeBounds(low1, high1), bounds2 @ AbstractTypeBounds(low2, high2)) =>
val mergedLow = mergeTypeMemberLowBounds(low1, low2)
val mergedHigh = mergeTypeMemberHighBounds(high1, high2)
bounds1.derivedTypeBounds(mergedLow, mergedHigh)
end mergeTypeMemberTypeBounds

private def mergeTypeMemberLowBounds(low1: Type, low2: Type)(using Context): Type =
(low1.dealias, low2.dealias) match
case (low1: TypeLambda, low2: TypeLambda) if low1.paramNames.sizeCompare(low2.paramNames) == 0 =>
low1.derivedLambdaType(
low1.paramNames,
low1.paramTypeBounds,
mergeTypeMemberLowBounds(low1.resultType, low2.instantiate(low1.paramRefs))
)
case (_: NothingType, _) | (_, _: AnyKindType) =>
low2
case (_, _: NothingType) | (_: AnyKindType, _) =>
low1
case _ =>
low1 | low2
end mergeTypeMemberLowBounds

private def mergeTypeMemberHighBounds(high1: Type, high2: Type)(using Context): Type =
(high1.dealias, high2.dealias) match
case (high1: TypeLambda, high2: TypeLambda) if high1.paramNames.sizeCompare(high2.paramNames) == 0 =>
high1.derivedLambdaType(
high1.paramNames,
high1.paramTypeBounds,
mergeTypeMemberHighBounds(high1.resultType, high2.instantiate(high1.paramRefs))
)
case (_: AnyKindType, _) | (_, _: NothingType) =>
high2
case (_, _: AnyKindType) | (_: NothingType, _) =>
high1
case _ =>
high1 & high2
end mergeTypeMemberHighBounds
end ResolveMemberResult

/** A type parameter of a type constructor.
Expand Down

0 comments on commit 539dd7f

Please sign in to comment.