Skip to content

Commit

Permalink
Be still more careful when computing denotations of class parameters
Browse files Browse the repository at this point in the history
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 scala#14816 to fix
scala#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.
  • Loading branch information
odersky authored and mpollmeier committed Oct 16, 2022
1 parent cd9ae53 commit d54157d
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 18 deletions.
33 changes: 23 additions & 10 deletions compiler/src/dotty/tools/dotc/core/Denotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,7 @@ object Denotations {
def aggregate[T](f: SingleDenotation => T, g: (T, T) => T): T = f(this)

type AsSeenFromResult = SingleDenotation

protected def computeAsSeenFrom(pre: Type)(using Context): SingleDenotation = {
val symbol = this.symbol
val owner = this match {
Expand Down Expand Up @@ -1120,19 +1121,31 @@ object Denotations {
then this
else if symbol.isAllOf(ClassTypeParam) then
val arg = symbol.typeRef.argForParam(pre, widenAbstract = true)
if arg.exists then
// take the argument bounds, but intersect with the symbols bounds if
// this forces nothing and gives a non-empty type.
val newBounds =
if symbol.isCompleted && !symbol.info.containsLazyRefs then
val combined @ TypeBounds(lo, hi) = symbol.info.bounds & arg.bounds
if lo frozen_<:< hi then combined
else arg.bounds
else arg.bounds
derivedSingleDenotation(symbol, newBounds, pre)
if arg.exists
then derivedSingleDenotation(symbol, normalizedArgBounds(arg.bounds), pre)
else derived(symbol.info)
else derived(symbol.info)
}

/** The argument bounds, possibly intersected with the parameter's info TypeBounds,
* if the latter is not F-bounded and does not refer to other type parameters
* of the same class, and the intersection is provably nonempty.
*/
private def normalizedArgBounds(argBounds: TypeBounds)(using Context): TypeBounds =
if symbol.isCompleted && !hasBoundsDependingOnParamsOf(symbol.owner) then
val combined @ TypeBounds(lo, hi) = symbol.info.bounds & argBounds
if (lo frozen_<:< hi) then combined
else argBounds
else argBounds

private def hasBoundsDependingOnParamsOf(cls: Symbol)(using Context): Boolean =
val acc = new TypeAccumulator[Boolean]:
def apply(x: Boolean, tp: Type): Boolean = tp match
case _: LazyRef => true
case tp: TypeRef
if tp.symbol.isAllOf(ClassTypeParam) && tp.symbol.owner == cls => true
case _ => foldOver(x, tp)
acc(false, symbol.info)
}

abstract class NonSymSingleDenotation(symbol: Symbol, initInfo: Type, override val prefix: Type) extends SingleDenotation(symbol, initInfo) {
Expand Down
8 changes: 0 additions & 8 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -442,14 +442,6 @@ object Types {
final def containsWildcardTypes(using Context) =
existsPart(_.isInstanceOf[WildcardType], StopAt.Static, forceLazy = false)

/** Does this type contain LazyRef types? */
final def containsLazyRefs(using Context) =
val acc = new TypeAccumulator[Boolean]:
def apply(x: Boolean, tp: Type): Boolean = tp match
case _: LazyRef => true
case _ => x || foldOver(x, tp)
acc(false, this)

// ----- Higher-order combinators -----------------------------------

/** Returns true if there is a part of this type that satisfies predicate `p`.
Expand Down
12 changes: 12 additions & 0 deletions tests/pos/i16105.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
trait SQLSyntaxSupport[A]

trait ResultNameSQLSyntaxProvider[S <: SQLSyntaxSupport[A], A]
trait QuerySQLSyntaxProvider[S <: SQLSyntaxSupport[A], A]{
def resultName: ResultNameSQLSyntaxProvider[S, A] = ???
}

def include(syntaxProviders: QuerySQLSyntaxProvider[_, _]*) = {
syntax(syntaxProviders.map(_.resultName): _*)
}

def syntax(resultNames: ResultNameSQLSyntaxProvider[_, _]*) = ???

0 comments on commit d54157d

Please sign in to comment.