Skip to content

Commit ac74982

Browse files
authored
Refine AsSeenFrom approximation scheme (#15957)
The previous scheme did not set the approximated flag in some cases, which led to the appearance of unhelpful Nothing types in error messages. We now always mark the AsSeenFrom map as approximated when encountering an illegal prefix at variance <= 0. But we reset it again when a range in a prefix is dropped because we can follow a type alias or use a singleton type info. Furthermore, we use an `Int`, `approxCount` instead of `approximated` to keep track of multiple approximation points. Fixes #15939 in the sense that the error message now makes more sense. We still cannot find the implicit conversion; that would require a more global measure to fix the problem (such as going to ANF) or existential types.
2 parents 5667bb1 + 7649eaf commit ac74982

File tree

4 files changed

+95
-22
lines changed

4 files changed

+95
-22
lines changed

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

+15-18
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ object TypeOps:
4545
val widenedAsf = new AsSeenFromMap(pre.info, cls)
4646
val ret = widenedAsf.apply(tp)
4747

48-
if (!widenedAsf.approximated)
48+
if widenedAsf.approxCount == 0 then
4949
return ret
5050

5151
Stats.record("asSeenFrom skolem prefix required")
@@ -57,8 +57,14 @@ object TypeOps:
5757

5858
/** The TypeMap handling the asSeenFrom */
5959
class AsSeenFromMap(pre: Type, cls: Symbol)(using Context) extends ApproximatingTypeMap, IdempotentCaptRefMap {
60-
/** Set to true when the result of `apply` was approximated to avoid an unstable prefix. */
61-
var approximated: Boolean = false
60+
61+
/** The number of range approximations in invariant or contravariant positions
62+
* performed by this TypeMap.
63+
* - Incremented each time we produce a range.
64+
* - Decremented each time we drop a prefix range by forwarding to a type alias
65+
* or singleton type.
66+
*/
67+
private[TypeOps] var approxCount: Int = 0
6268

6369
def apply(tp: Type): Type = {
6470

@@ -76,17 +82,8 @@ object TypeOps:
7682
case _ =>
7783
if (thiscls.derivesFrom(cls) && pre.baseType(thiscls).exists)
7884
if (variance <= 0 && !isLegalPrefix(pre))
79-
if (variance < 0) {
80-
approximated = true
81-
defn.NothingType
82-
}
83-
else
84-
// Don't set the `approximated` flag yet: if this is a prefix
85-
// of a path, we might be able to dealias the path instead
86-
// (this is handled in `ApproximatingTypeMap`). If dealiasing
87-
// is not possible, then `expandBounds` will end up being
88-
// called which we override to set the `approximated` flag.
89-
range(defn.NothingType, pre)
85+
approxCount += 1
86+
range(defn.NothingType, pre)
9087
else pre
9188
else if (pre.termSymbol.is(Package) && !thiscls.is(Package))
9289
toPrefix(pre.select(nme.PACKAGE), cls, thiscls)
@@ -119,10 +116,10 @@ object TypeOps:
119116
// derived infos have already been subjected to asSeenFrom, hence to need to apply the map again.
120117
tp
121118

122-
override protected def expandBounds(tp: TypeBounds): Type = {
123-
approximated = true
124-
super.expandBounds(tp)
125-
}
119+
override protected def useAlternate(tp: Type): Type =
120+
assert(approxCount > 0)
121+
approxCount -= 1
122+
tp
126123
}
127124

128125
def isLegalPrefix(pre: Type)(using Context): Boolean =

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

+11-4
Original file line numberDiff line numberDiff line change
@@ -5766,6 +5766,13 @@ object Types {
57665766

57675767
private var expandingBounds: Boolean = false
57685768

5769+
/** Use an alterate type `tp` that replaces a range. This can happen if the
5770+
* prefix of a Select is a range and the selected symbol is an alias type
5771+
* or a value with a singleton type. In both cases we can forget the prefix
5772+
* and use the symbol's type.
5773+
*/
5774+
protected def useAlternate(tp: Type): Type = reapply(tp)
5775+
57695776
/** Whether it is currently expanding bounds
57705777
*
57715778
* It is used to avoid following LazyRef in F-Bounds
@@ -5789,15 +5796,15 @@ object Types {
57895796
case TypeAlias(alias) =>
57905797
// if H#T = U, then for any x in L..H, x.T =:= U,
57915798
// hence we can replace with U under all variances
5792-
reapply(alias.rewrapAnnots(tp1))
5799+
useAlternate(alias.rewrapAnnots(tp1))
57935800
case bounds: TypeBounds =>
57945801
// If H#T = ? >: S <: U, then for any x in L..H, S <: x.T <: U,
57955802
// hence we can replace with S..U under all variances
57965803
expandBounds(bounds)
57975804
case info: SingletonType =>
57985805
// if H#x: y.type, then for any x in L..H, x.type =:= y.type,
57995806
// hence we can replace with y.type under all variances
5800-
reapply(info)
5807+
useAlternate(info)
58015808
case _ =>
58025809
NoType
58035810
}
@@ -5813,10 +5820,10 @@ object Types {
58135820
case arg @ TypeRef(pre, _) if pre.isArgPrefixOf(arg.symbol) =>
58145821
arg.info match {
58155822
case argInfo: TypeBounds => expandBounds(argInfo)
5816-
case argInfo => reapply(arg)
5823+
case argInfo => useAlternate(arg)
58175824
}
58185825
case arg: TypeBounds => expandBounds(arg)
5819-
case arg => reapply(arg)
5826+
case arg => useAlternate(arg)
58205827
}
58215828

58225829
/** Derived selection.

tests/neg/i15939.check

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
-- [E007] Type Mismatch Error: tests/neg/i15939.scala:19:16 ------------------------------------------------------------
2+
19 | mkFoo.andThen(mkBarString) // error
3+
| ^^^^^^^^^^^
4+
| Found: String
5+
| Required: ?1.Bar
6+
|
7+
| where: ?1 is an unknown value of type Test.Foo
8+
|
9+
| longer explanation available when compiling with `-explain`
10+
-- [E007] Type Mismatch Error: tests/neg/i15939.scala:20:2 -------------------------------------------------------------
11+
20 | mkBarString andThen_: mkFoo // error
12+
| ^^^^^^^^^^^
13+
| Found: String
14+
| Required: ?2.Bar
15+
|
16+
| where: ?2 is an unknown value of type Test.Foo
17+
|
18+
| longer explanation available when compiling with `-explain`
19+
-- [E007] Type Mismatch Error: tests/neg/i15939.scala:21:18 ------------------------------------------------------------
20+
21 | mkFoo.andThen_:(mkBarString) // error
21+
| ^^^^^^^^^^^
22+
| Found: String
23+
| Required: ?3.Bar
24+
|
25+
| where: ?3 is an unknown value of type Test.Foo
26+
|
27+
| longer explanation available when compiling with `-explain`
28+
-- [E007] Type Mismatch Error: tests/neg/i15939.scala:22:2 -------------------------------------------------------------
29+
22 | mkBarString andThenByName_: mkFoo // error
30+
| ^^^^^^^^^^^
31+
| Found: String
32+
| Required: ?4.Bar
33+
|
34+
| where: ?4 is an unknown value of type Test.Foo
35+
|
36+
| longer explanation available when compiling with `-explain`
37+
-- [E007] Type Mismatch Error: tests/neg/i15939.scala:23:24 ------------------------------------------------------------
38+
23 | mkFoo.andThenByName_:(mkBarString) // error
39+
| ^^^^^^^^^^^
40+
| Found: String
41+
| Required: ?5.Bar
42+
|
43+
| where: ?5 is an unknown value of type Test.Foo
44+
|
45+
| longer explanation available when compiling with `-explain`

tests/neg/i15939.scala

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import scala.language.implicitConversions
2+
3+
object Test {
4+
class Foo {
5+
class Bar {
6+
override def toString() = "bar"
7+
}
8+
object Bar {
9+
implicit def fromString(a: String): Bar = { println("convert bar") ; new Bar }
10+
}
11+
12+
def andThen(b: Bar): Unit = { println("pre") ; println(s"use $b") ; println("post") }
13+
def andThen_:(b: Bar) = { println("pre") ; println(s"use $b") ; println("post") }
14+
def andThenByName_:(b: => Bar) = { println("pre") ; println(s"use $b") ; println(s"use $b") ; println("post") }
15+
}
16+
17+
def mkFoo: Foo = ???
18+
def mkBarString: String = ???
19+
mkFoo.andThen(mkBarString) // error
20+
mkBarString andThen_: mkFoo // error
21+
mkFoo.andThen_:(mkBarString) // error
22+
mkBarString andThenByName_: mkFoo // error
23+
mkFoo.andThenByName_:(mkBarString) // error
24+
}

0 commit comments

Comments
 (0)