From 0bf7a946e4fa4540b3ee8e4a0af4cb51677b33b8 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 27 Sep 2022 13:03:37 +0200 Subject: [PATCH] Be still more careful when computing denotations of class parameters 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. --- .../dotty/tools/dotc/core/Denotations.scala | 33 +++++++++++++------ .../src/dotty/tools/dotc/core/Types.scala | 8 ----- tests/pos/i16105.scala | 12 +++++++ 3 files changed, 35 insertions(+), 18 deletions(-) create mode 100644 tests/pos/i16105.scala diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 4366515b8ce3..f267e6c85e03 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -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 { @@ -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) { diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 483745928f25..f60bba88dde2 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -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`. diff --git a/tests/pos/i16105.scala b/tests/pos/i16105.scala new file mode 100644 index 000000000000..477e47e98aa7 --- /dev/null +++ b/tests/pos/i16105.scala @@ -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[_, _]*) = ??? \ No newline at end of file