Skip to content

Commit 731522a

Browse files
authored
Be still more careful when computing denotations of class parameters (#16112)
Be still more careful when intersecting info and arguments of a class type parameter. This is the latest installment of a never-ending story. The problem is this: Given ```scala class C[T >: L1 <: H1] { val x: T } def f(): C[? >: L2 <: H2] ``` what is the type of `f().x`? With capture conversion (an extremely tricky aspect of the type system forced on us since Java does it), the type is something like `?1.T` where `?1` is a skolem variable of type `C[? >: L2 <: H2]`. OK, but what is the underlying (widened) type of `?1.T`? We used to say it's `C[T >: L1 <: H1]`. I.e. we forgot about the actual arguments. But then we had to change untupling desugarings from defs to vals in #14816 to fix #14783 and it turned out that was not good enough, we needed the information of the actual arguments, i.e. the type should be `C[T >: L2 <: H2]`. Then something else started failing which relied on the formal arguiment bounds being preserved. So the new resolution was that the type would be the intersection of the formal parameter bounds and the actual bounds, i.e. `C[T >: L1 | L2 <: H1 & H2]`. Then there was a series of problems where _that_ failed, either because the parameter bound was F-bounded or because the intersection was an empty type. The latest installment is that the parameter bounds refer to another parameter in the same class, which requires a simultaneous substitution of all skolemized bounds for all dependent parameter references in the parameter bounds. But that's impossible or at least very hard to achieve since we are looking at the type of a single parameter after capture conversion here. So the current solution is to also back out of the intersection if there are cross-parameter references and to use just the argument bounds instead.
2 parents 8d723a9 + 0bf7a94 commit 731522a

File tree

3 files changed

+35
-18
lines changed

3 files changed

+35
-18
lines changed

compiler/src/dotty/tools/dotc/core/Denotations.scala

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,6 +1076,7 @@ object Denotations {
10761076
def aggregate[T](f: SingleDenotation => T, g: (T, T) => T): T = f(this)
10771077

10781078
type AsSeenFromResult = SingleDenotation
1079+
10791080
protected def computeAsSeenFrom(pre: Type)(using Context): SingleDenotation = {
10801081
val symbol = this.symbol
10811082
val owner = this match {
@@ -1120,19 +1121,31 @@ object Denotations {
11201121
then this
11211122
else if symbol.isAllOf(ClassTypeParam) then
11221123
val arg = symbol.typeRef.argForParam(pre, widenAbstract = true)
1123-
if arg.exists then
1124-
// take the argument bounds, but intersect with the symbols bounds if
1125-
// this forces nothing and gives a non-empty type.
1126-
val newBounds =
1127-
if symbol.isCompleted && !symbol.info.containsLazyRefs then
1128-
val combined @ TypeBounds(lo, hi) = symbol.info.bounds & arg.bounds
1129-
if lo frozen_<:< hi then combined
1130-
else arg.bounds
1131-
else arg.bounds
1132-
derivedSingleDenotation(symbol, newBounds, pre)
1124+
if arg.exists
1125+
then derivedSingleDenotation(symbol, normalizedArgBounds(arg.bounds), pre)
11331126
else derived(symbol.info)
11341127
else derived(symbol.info)
11351128
}
1129+
1130+
/** The argument bounds, possibly intersected with the parameter's info TypeBounds,
1131+
* if the latter is not F-bounded and does not refer to other type parameters
1132+
* of the same class, and the intersection is provably nonempty.
1133+
*/
1134+
private def normalizedArgBounds(argBounds: TypeBounds)(using Context): TypeBounds =
1135+
if symbol.isCompleted && !hasBoundsDependingOnParamsOf(symbol.owner) then
1136+
val combined @ TypeBounds(lo, hi) = symbol.info.bounds & argBounds
1137+
if (lo frozen_<:< hi) then combined
1138+
else argBounds
1139+
else argBounds
1140+
1141+
private def hasBoundsDependingOnParamsOf(cls: Symbol)(using Context): Boolean =
1142+
val acc = new TypeAccumulator[Boolean]:
1143+
def apply(x: Boolean, tp: Type): Boolean = tp match
1144+
case _: LazyRef => true
1145+
case tp: TypeRef
1146+
if tp.symbol.isAllOf(ClassTypeParam) && tp.symbol.owner == cls => true
1147+
case _ => foldOver(x, tp)
1148+
acc(false, symbol.info)
11361149
}
11371150

11381151
abstract class NonSymSingleDenotation(symbol: Symbol, initInfo: Type, override val prefix: Type) extends SingleDenotation(symbol, initInfo) {

compiler/src/dotty/tools/dotc/core/Types.scala

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -442,14 +442,6 @@ object Types {
442442
final def containsWildcardTypes(using Context) =
443443
existsPart(_.isInstanceOf[WildcardType], StopAt.Static, forceLazy = false)
444444

445-
/** Does this type contain LazyRef types? */
446-
final def containsLazyRefs(using Context) =
447-
val acc = new TypeAccumulator[Boolean]:
448-
def apply(x: Boolean, tp: Type): Boolean = tp match
449-
case _: LazyRef => true
450-
case _ => x || foldOver(x, tp)
451-
acc(false, this)
452-
453445
// ----- Higher-order combinators -----------------------------------
454446

455447
/** Returns true if there is a part of this type that satisfies predicate `p`.

tests/pos/i16105.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
trait SQLSyntaxSupport[A]
2+
3+
trait ResultNameSQLSyntaxProvider[S <: SQLSyntaxSupport[A], A]
4+
trait QuerySQLSyntaxProvider[S <: SQLSyntaxSupport[A], A]{
5+
def resultName: ResultNameSQLSyntaxProvider[S, A] = ???
6+
}
7+
8+
def include(syntaxProviders: QuerySQLSyntaxProvider[_, _]*) = {
9+
syntax(syntaxProviders.map(_.resultName): _*)
10+
}
11+
12+
def syntax(resultNames: ResultNameSQLSyntaxProvider[_, _]*) = ???

0 commit comments

Comments
 (0)