From 9f4179a6d261784502deca5febff89548f612f56 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Tue, 15 Jul 2025 16:24:19 +0200 Subject: [PATCH] Don't approximate a type using `Nothing` as prefix Approximate range(Nothing, hi) to Nothing & hi instead of Nothing. This avoids creating TypeRefs with a Nothing prefix which manifest themselves down the line with an error like: Cannot resolve reference to type path.type.AbsMember. The classfile defining the type might be missing from the classpath. val z1 = transition(di).ext(1) // error ^ Due to a MissingType thrown from TypeErasure#sigName. This change required tweaking Type#findMember to handle WildcardTypes instead of returning NoDenotation. This was only required for two tests (tests/pos/i7414.scala and tests/pos/i13842.scala) where `wildApprox` ends up creating a TermRef whose underlying type is a WildcardType, because of a type parameter being substituted by a wildcard, which seems legitimate when we're using `wildApprox`. The presentation compiler also had a special case for Nothing appearing in completion which had to be adapted. Fixes #23530 --- .../src/dotty/tools/dotc/core/Types.scala | 23 ++++++++++++++++--- .../pc/printer/ShortenedTypePrinter.scala | 8 +++---- tests/pos/i23530.scala | 18 +++++++++++++++ 3 files changed, 41 insertions(+), 8 deletions(-) create mode 100644 tests/pos/i23530.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index c9f48657621c..bf3769e742bf 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -849,6 +849,8 @@ object Types extends TypeUtils { goOr(tp) case tp: JavaArrayType => defn.ObjectType.findMember(name, pre, required, excluded) + case tp: WildcardType => + go(tp.bounds) case err: ErrorType => newErrorSymbol(pre.classSymbol.orElse(defn.RootClass), name, err.msg) case _ => @@ -6482,9 +6484,24 @@ object Types extends TypeUtils { abstract class ApproximatingTypeMap(using Context) extends TypeMap { thisMap => protected def range(lo: Type, hi: Type): Type = - if (variance > 0) hi - else if (variance < 0) lo - else if (lo `eq` hi) lo + if variance > 0 then hi + else if variance < 0 then + if (lo eq defn.NothingType) && hi.hasSimpleKind then + // Approximate by Nothing & hi instead of just Nothing, in case the + // approximated type is used as the prefix of another type (this would + // lead to a type with a `NoDenotation` denot and a possible + // MissingType in `TypeErasure#sigName`). + // + // Note that we cannot simply check for a `Nothing` prefix in + // `derivedSelect`, because the substitution might be done lazily (for + // example if Nothing is the type of a parameter being depended on in + // a MethodType) + // + // Test case in tests/pos/i23530.scala + AndType(lo, hi) + else + lo + else if lo `eq` hi then lo else Range(lower(lo), upper(hi)) protected def emptyRange = range(defn.NothingType, defn.AnyType) diff --git a/presentation-compiler/src/main/dotty/tools/pc/printer/ShortenedTypePrinter.scala b/presentation-compiler/src/main/dotty/tools/pc/printer/ShortenedTypePrinter.scala index 99a32e42d8a4..5dee96c6133c 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/printer/ShortenedTypePrinter.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/printer/ShortenedTypePrinter.scala @@ -17,8 +17,7 @@ import dotty.tools.dotc.core.Names import dotty.tools.dotc.core.Names.Name import dotty.tools.dotc.core.Names.NameOrdering import dotty.tools.dotc.core.StdNames -import dotty.tools.dotc.core.Symbols.NoSymbol -import dotty.tools.dotc.core.Symbols.Symbol +import dotty.tools.dotc.core.Symbols.* import dotty.tools.dotc.core.Types.* import dotty.tools.dotc.core.Types.Type import dotty.tools.dotc.printing.RefinedPrinter @@ -256,7 +255,6 @@ class ShortenedTypePrinter( end hoverSymbol def isImportedByDefault(sym: Symbol): Boolean = - import dotty.tools.dotc.core.Symbols.defn lazy val effectiveOwner = sym.effectiveOwner sym.isType && (effectiveOwner == defn.ScalaPackageClass || effectiveOwner == defn.ScalaPredefModuleClass) @@ -498,9 +496,9 @@ class ShortenedTypePrinter( val info = nameToInfo .get(param.name) .flatMap { info => - // In some cases, paramInfo becomes Nothing (e.g. CompletionOverrideSuite#cake) + // In some cases, paramInfo becomes `... & Nothing` (e.g. CompletionOverrideSuite#cake) // which is meaningless, in that case, fallback to param.info - if info.isNothingType then None + if info <:< defn.NothingType then None else Some(info) } .getOrElse(param.info) diff --git a/tests/pos/i23530.scala b/tests/pos/i23530.scala new file mode 100644 index 000000000000..822526f7562d --- /dev/null +++ b/tests/pos/i23530.scala @@ -0,0 +1,18 @@ +trait TestContainer: + trait TestPath: + type AbsMember + + extension (path: TestPath) + infix def ext(color: path.AbsMember): Unit = ??? + infix def ext(other: Int): Unit = ??? + +object Repro: + val dc2: TestContainer = ??? + import dc2.TestPath + + def transition(path: TestPath)(using DummyImplicit): TestPath = ??? + + def test: Unit = + val di: TestPath = ??? + // error + val z1 = transition(di).ext(1)