From f7e38af4b67de4adec6b57b752ac8440eb7f0313 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 23 Mar 2022 19:10:41 +0100 Subject: [PATCH 1/3] Fix hasKnownMembers As observed in #13900, hasKnownMembers gives problematic false positives. What we are really after is a (nontrivial) upper approximation of known members. Even uninstantiated type variables have such an approximation if their lower bound is defined and different from Nothing. Fixes #13900 --- .../dotty/tools/dotc/typer/ProtoTypes.scala | 35 +++++++++++-------- tests/neg/i13900.scala | 9 ----- tests/pos/i13900.scala | 14 ++++++++ 3 files changed, 35 insertions(+), 23 deletions(-) delete mode 100644 tests/neg/i13900.scala create mode 100644 tests/pos/i13900.scala diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 1da11ca71992..4f49ec9e8c3e 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -157,28 +157,35 @@ object ProtoTypes { abstract case class SelectionProto(name: Name, memberProto: Type, compat: Compatibility, privateOK: Boolean) extends CachedProxyType with ProtoType with ValueTypeOrProto { - /** Is the set of members of this type unknown? This is the case if: - * 1. The type has Nothing or Wildcard as a prefix or underlying type - * 2. The type has an uninstantiated TypeVar as a prefix or underlying type, - * or as an upper bound of a prefix or underlying type. + /** Is the set of members of this type unknown, in the sense that we + * cannot compute a non-trivial upper approximation? This is the case if: + * 1. The type has Nothing or Wildcard as a prefix or underlying type + * 2. The type is an abstract type with a lower bound that has a unknown + * members and an upper bound that is both provisional and has unknown members. + * 3. The type is a type param ref or uninstiated type var with a lower + * that has unknown members. + * 4. Type proxies have unknown members if their super types do */ - private def hasUnknownMembers(tp: Type)(using Context): Boolean = tp match { - case tp: TypeVar => !tp.isInstantiated + private def hasUnknownMembers(tp: Type)(using Context): Boolean = tp match case tp: WildcardType => true case NoType => true case tp: TypeRef => val sym = tp.symbol - sym == defn.NothingClass || - !sym.isStatic && { - hasUnknownMembers(tp.prefix) || { - val bound = tp.info.hiBound - bound.isProvisional && hasUnknownMembers(bound) - } - } + defn.isBottomClass(sym) + || !sym.isClass + && !sym.isStatic + && { + hasUnknownMembers(tp.prefix) + || { val bound = tp.info.hiBound + bound.isProvisional && hasUnknownMembers(bound) + } && hasUnknownMembers(tp.info.loBound) + } + case tp: TypeParamRef => hasUnknownMembers(TypeComparer.bounds(tp).lo) case tp: AppliedType => hasUnknownMembers(tp.tycon) || hasUnknownMembers(tp.superType) case tp: TypeProxy => hasUnknownMembers(tp.superType) + // It woukd make sense to also include And/OrTypes, but that leads to + // infinite recursions, as observed for instance for t2399.scala. case _ => false - } override def isMatchedBy(tp1: Type, keepConstraint: Boolean)(using Context): Boolean = name == nme.WILDCARD || hasUnknownMembers(tp1) || diff --git a/tests/neg/i13900.scala b/tests/neg/i13900.scala deleted file mode 100644 index 175adf6e5d2c..000000000000 --- a/tests/neg/i13900.scala +++ /dev/null @@ -1,9 +0,0 @@ -opaque type Inlined[T] = T - -object Inlined: - - given fromValueWide[Wide]: Conversion[Wide, Inlined[Wide]] = ??? - - // TODO: This used to make the compiler run into an infinite loop. - // Now it fails instead but shouldn't, see discussion in https://github.com/lampepfl/dotty/issues/13900#issuecomment-1075580792 - def myMax: Int = 1 max 2 // error diff --git a/tests/pos/i13900.scala b/tests/pos/i13900.scala new file mode 100644 index 000000000000..e6197d7f3f33 --- /dev/null +++ b/tests/pos/i13900.scala @@ -0,0 +1,14 @@ +import compiletime.ops.int.Max +import scala.annotation.targetName +opaque type Inlined[T] = T +object Inlined: + extension [T](inlined: Inlined[T]) def value: T = inlined + inline given fromValue[T <: Singleton]: Conversion[T, Inlined[T]] = + value => value + @targetName("fromValueWide") + given fromValue[Wide]: Conversion[Wide, Inlined[Wide]] = value => value + + def forced[T](value: Any): Inlined[T] = value.asInstanceOf[T] + extension [T <: Int](lhs: Inlined[T]) + def max[R <: Int](rhs: Inlined[R]) = + forced[Max[T, R]](lhs.value max rhs.value) \ No newline at end of file From e6c7f272e185926e3ef8ef2e2317287fe77e0b20 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 24 Mar 2022 09:05:17 +0100 Subject: [PATCH 2/3] Another tweak to unknownMembers Turns out we cannot make TypeParamRefs have unknown members directly. scala.concurrent.TrieMap fails if we do that. --- compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 4f49ec9e8c3e..64d0acef1798 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -162,8 +162,7 @@ object ProtoTypes { * 1. The type has Nothing or Wildcard as a prefix or underlying type * 2. The type is an abstract type with a lower bound that has a unknown * members and an upper bound that is both provisional and has unknown members. - * 3. The type is a type param ref or uninstiated type var with a lower - * that has unknown members. + * 3. The type is an uninstiated type var with a lower that has unknown members. * 4. Type proxies have unknown members if their super types do */ private def hasUnknownMembers(tp: Type)(using Context): Boolean = tp match @@ -171,8 +170,8 @@ object ProtoTypes { case NoType => true case tp: TypeRef => val sym = tp.symbol - defn.isBottomClass(sym) - || !sym.isClass + sym == defn.NothingClass + || !sym.isClass && !sym.isStatic && { hasUnknownMembers(tp.prefix) @@ -180,7 +179,8 @@ object ProtoTypes { bound.isProvisional && hasUnknownMembers(bound) } && hasUnknownMembers(tp.info.loBound) } - case tp: TypeParamRef => hasUnknownMembers(TypeComparer.bounds(tp).lo) + case tp: TypeVar => + !tp.isInstantiated && hasUnknownMembers(TypeComparer.bounds(tp.origin).lo) case tp: AppliedType => hasUnknownMembers(tp.tycon) || hasUnknownMembers(tp.superType) case tp: TypeProxy => hasUnknownMembers(tp.superType) // It woukd make sense to also include And/OrTypes, but that leads to From 7f7d83f88ff93b8bb28077e21046d5b8e2171c31 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 24 Mar 2022 13:42:48 +0100 Subject: [PATCH 3/3] Update compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala Co-authored-by: Guillaume Martres --- compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 64d0acef1798..1433045c31ff 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -179,8 +179,8 @@ object ProtoTypes { bound.isProvisional && hasUnknownMembers(bound) } && hasUnknownMembers(tp.info.loBound) } - case tp: TypeVar => - !tp.isInstantiated && hasUnknownMembers(TypeComparer.bounds(tp.origin).lo) + case tp: TypeVar if !tp.isInstantiated => + hasUnknownMembers(TypeComparer.bounds(tp.origin).lo) case tp: AppliedType => hasUnknownMembers(tp.tycon) || hasUnknownMembers(tp.superType) case tp: TypeProxy => hasUnknownMembers(tp.superType) // It woukd make sense to also include And/OrTypes, but that leads to