Skip to content

Commit c99e771

Browse files
authored
Merge pull request #6454 from dotty-staging/fix/type-member-inf-7
Fix #6199: Use a skolemized prefix in asSeenFrom when needed
2 parents f85877d + 502c7c9 commit c99e771

File tree

7 files changed

+116
-18
lines changed

7 files changed

+116
-18
lines changed

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

+44-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package core
55
import Contexts._, Types._, Symbols._, Names._, Flags._
66
import SymDenotations._
77
import util.Spans._
8+
import util.Stats
89
import util.SourcePosition
910
import NameKinds.DepParamName
1011
import Decorators._
@@ -26,11 +27,34 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
2627
/** The type `tp` as seen from prefix `pre` and owner `cls`. See the spec
2728
* for what this means.
2829
*/
29-
final def asSeenFrom(tp: Type, pre: Type, cls: Symbol): Type =
30+
final def asSeenFrom(tp: Type, pre: Type, cls: Symbol): Type = {
31+
pre match {
32+
case pre: QualSkolemType =>
33+
// When a selection has an unstable qualifier, the qualifier type gets
34+
// wrapped in a `QualSkolemType` so that it may appear soundly as the
35+
// prefix of a path in the selection type.
36+
// However, we'd like to avoid referring to skolems when possible since
37+
// they're an extra level of indirection we usually don't need, so we
38+
// compute the type as seen from the widened prefix, and in the rare
39+
// cases where this leads to an approximated type we recompute it with
40+
// the skolemized prefix. See the i6199* tests for usecases.
41+
val widenedAsf = new AsSeenFromMap(pre.info, cls)
42+
val ret = widenedAsf.apply(tp)
43+
44+
if (!widenedAsf.approximated)
45+
return ret
46+
47+
Stats.record("asSeenFrom skolem prefix required")
48+
case _ =>
49+
}
50+
3051
new AsSeenFromMap(pre, cls).apply(tp)
52+
}
3153

3254
/** The TypeMap handling the asSeenFrom */
3355
class AsSeenFromMap(pre: Type, cls: Symbol) extends ApproximatingTypeMap {
56+
/** Set to true when the result of `apply` was approximated to avoid an unstable prefix. */
57+
var approximated: Boolean = false
3458

3559
def apply(tp: Type): Type = {
3660

@@ -44,7 +68,19 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
4468
case pre: SuperType => toPrefix(pre.thistpe, cls, thiscls)
4569
case _ =>
4670
if (thiscls.derivesFrom(cls) && pre.baseType(thiscls).exists)
47-
if (variance <= 0 && !isLegalPrefix(pre)) range(defn.NothingType, pre)
71+
if (variance <= 0 && !isLegalPrefix(pre)) {
72+
if (variance < 0) {
73+
approximated = true
74+
defn.NothingType
75+
}
76+
else
77+
// Don't set the `approximated` flag yet: if this is a prefix
78+
// of a path, we might be able to dealias the path instead
79+
// (this is handled in `ApproximatingTypeMap`). If dealiasing
80+
// is not possible, then `expandBounds` will end up being
81+
// called which we override to set the `approximated` flag.
82+
range(defn.NothingType, pre)
83+
}
4884
else pre
4985
else if ((pre.termSymbol is Package) && !(thiscls is Package))
5086
toPrefix(pre.select(nme.PACKAGE), cls, thiscls)
@@ -74,9 +110,14 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
74110
override def reapply(tp: Type): Type =
75111
// derived infos have already been subjected to asSeenFrom, hence to need to apply the map again.
76112
tp
113+
114+
override protected def expandBounds(tp: TypeBounds): Type = {
115+
approximated = true
116+
super.expandBounds(tp)
117+
}
77118
}
78119

79-
private def isLegalPrefix(pre: Type)(implicit ctx: Context) =
120+
def isLegalPrefix(pre: Type)(implicit ctx: Context): Boolean =
80121
pre.isStable || !ctx.phase.isTyper
81122

82123
/** Implementation of Types#simplified */

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

+21-5
Original file line numberDiff line numberDiff line change
@@ -549,7 +549,7 @@ object Types {
549549
* flags and no `excluded` flag and produce a denotation that contains
550550
* the type of the member as seen from given prefix `pre`.
551551
*/
552-
final def findMember(name: Name, pre: Type, required: FlagConjunction, excluded: FlagSet)(implicit ctx: Context): Denotation = {
552+
final def findMember(name: Name, pre: Type, required: FlagConjunction = EmptyFlagConjunction, excluded: FlagSet = EmptyFlags)(implicit ctx: Context): Denotation = {
553553
@tailrec def go(tp: Type): Denotation = tp match {
554554
case tp: TermRef =>
555555
go (tp.underlying match {
@@ -3729,6 +3729,21 @@ object Types {
37293729
override def toString: String = s"Skolem($hashCode)"
37303730
}
37313731

3732+
/** A skolem type used to wrap the type of the qualifier of a selection.
3733+
*
3734+
* When typing a selection `e.f`, if `e` is unstable then we unconditionally
3735+
* skolemize it. We use a subclass of `SkolemType` for this so that
3736+
* [[TypeOps#asSeenFrom]] may treat it specially for optimization purposes,
3737+
* see its implementation for more details.
3738+
*/
3739+
class QualSkolemType(info: Type) extends SkolemType(info) {
3740+
override def derivedSkolemType(info: Type)(implicit ctx: Context): SkolemType =
3741+
if (info eq this.info) this else QualSkolemType(info)
3742+
}
3743+
object QualSkolemType {
3744+
def apply(info: Type): QualSkolemType = new QualSkolemType(info)
3745+
}
3746+
37323747
// ------------ Type variables ----------------------------------------
37333748

37343749
/** In a TypeApply tree, a TypeVar is created for each argument type to be inferred.
@@ -4652,6 +4667,9 @@ object Types {
46524667
case _ => tp
46534668
}
46544669

4670+
protected def expandBounds(tp: TypeBounds): Type =
4671+
range(atVariance(-variance)(reapply(tp.lo)), reapply(tp.hi))
4672+
46554673
/** Try to widen a named type to its info relative to given prefix `pre`, where possible.
46564674
* The possible cases are listed inline in the code.
46574675
*/
@@ -4663,10 +4681,10 @@ object Types {
46634681
// if H#T = U, then for any x in L..H, x.T =:= U,
46644682
// hence we can replace with U under all variances
46654683
reapply(alias.rewrapAnnots(tp1))
4666-
case TypeBounds(lo, hi) =>
4684+
case tp: TypeBounds =>
46674685
// If H#T = _ >: S <: U, then for any x in L..H, S <: x.T <: U,
46684686
// hence we can replace with S..U under all variances
4669-
range(atVariance(-variance)(reapply(lo)), reapply(hi))
4687+
expandBounds(tp)
46704688
case info: SingletonType =>
46714689
// if H#x: y.type, then for any x in L..H, x.type =:= y.type,
46724690
// hence we can replace with y.type under all variances
@@ -4682,8 +4700,6 @@ object Types {
46824700
* underlying bounds to a range, otherwise return the expansion.
46834701
*/
46844702
def expandParam(tp: NamedType, pre: Type): Type = {
4685-
def expandBounds(tp: TypeBounds) =
4686-
range(atVariance(-variance)(reapply(tp.lo)), reapply(tp.hi))
46874703
tp.argForParam(pre) match {
46884704
case arg @ TypeRef(pre, _) if pre.isArgPrefixOf(arg.symbol) =>
46894705
arg.info match {

compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala

+10-9
Original file line numberDiff line numberDiff line change
@@ -1016,14 +1016,14 @@ class TreeUnpickler(reader: TastyReader,
10161016
val localCtx =
10171017
if (name == nme.CONSTRUCTOR) ctx.addMode(Mode.InSuperCall) else ctx
10181018
val qual = readTerm()(localCtx)
1019-
var pre = qual.tpe.widenIfUnstable
1020-
val denot = accessibleDenot(pre, name, sig)
1019+
var qualType = qual.tpe.widenIfUnstable
1020+
val denot = accessibleDenot(qualType, name, sig)
10211021
val owner = denot.symbol.maybeOwner
1022-
if (owner.isPackageObject && pre.termSymbol.is(Package))
1023-
pre = pre.select(owner.sourceModule)
1022+
if (owner.isPackageObject && qualType.termSymbol.is(Package))
1023+
qualType = qualType.select(owner.sourceModule)
10241024
val tpe = name match {
1025-
case name: TypeName => TypeRef(pre, name, denot)
1026-
case name: TermName => TermRef(pre, name, denot)
1025+
case name: TypeName => TypeRef(qualType, name, denot)
1026+
case name: TermName => TermRef(qualType, name, denot)
10271027
}
10281028
ConstFold(untpd.Select(qual, name).withType(tpe))
10291029
}
@@ -1033,10 +1033,11 @@ class TreeUnpickler(reader: TastyReader,
10331033
(untpd.Ident(qual.name).withSpan(qual.span), qual.tpe.asInstanceOf[TypeRef])
10341034
}
10351035

1036-
def accessibleDenot(pre: Type, name: Name, sig: Signature) = {
1037-
val d = pre.member(name).atSignature(sig)
1036+
def accessibleDenot(qualType: Type, name: Name, sig: Signature) = {
1037+
val pre = ctx.typeAssigner.maybeSkolemizePrefix(qualType, name)
1038+
val d = qualType.findMember(name, pre).atSignature(sig)
10381039
if (!d.symbol.exists || d.symbol.isAccessibleFrom(pre)) d
1039-
else pre.nonPrivateMember(name).atSignature(sig)
1040+
else qualType.findMember(name, pre, excluded = Private).atSignature(sig)
10401041
}
10411042

10421043
def readSimpleTerm(): Tree = tag match {

compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala

+14-1
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,17 @@ trait TypeAssigner {
226226
test(tpe, true)
227227
}
228228

229+
/** Return a potentially skolemized version of `qualTpe` to be used
230+
* as a prefix when selecting `name`.
231+
*
232+
* @see QualSkolemType, TypeOps#asSeenFrom
233+
*/
234+
def maybeSkolemizePrefix(qualType: Type, name: Name)(implicit ctx: Context): Type =
235+
if (name.isTermName && !ctx.isLegalPrefix(qualType))
236+
QualSkolemType(qualType)
237+
else
238+
qualType
239+
229240
/** The type of the selection `tree`, where `qual1` is the typed qualifier part. */
230241
def selectionType(tree: untpd.RefTree, qual1: Tree)(implicit ctx: Context): Type = {
231242
var qualType = qual1.tpe.widenIfUnstable
@@ -234,8 +245,10 @@ trait TypeAssigner {
234245
qualType = errorType(em"$qualType takes type parameters", qual1.sourcePos)
235246
else if (!qualType.isInstanceOf[TermType])
236247
qualType = errorType(em"$qualType is illegal as a selection prefix", qual1.sourcePos)
248+
237249
val name = tree.name
238-
val mbr = qualType.member(name)
250+
val pre = maybeSkolemizePrefix(qualType, name)
251+
val mbr = qualType.findMember(name, pre)
239252
if (reallyExists(mbr))
240253
qualType.select(name, mbr)
241254
else if (qualType.derivesFrom(defn.DynamicClass) && name.isTermName && !Dynamic.isDynamicMethod(name))

tests/pos/i6199a.scala

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
class Encoder[T] { def encode(v: T): String = v.toString }
2+
case class ValueWithEncoder[T](value: T, encoder: Encoder[T])
3+
4+
object Test {
5+
val a: Seq[ValueWithEncoder[_]] = Seq.empty
6+
val b = a.map((value, encoder) => encoder.encode(value))
7+
val c: Seq[String] = b
8+
}

tests/pos/i6199b.scala

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
trait Foo {
2+
type State
3+
def bar(f: Bar[State] => Bar[State]): Foo = this
4+
}
5+
object Foo {
6+
def unit: Foo = new Foo {
7+
type State = Any
8+
}
9+
def doBar: Foo = unit.bar(bar => bar)
10+
}
11+
class Bar[+A]

tests/pos/i6199c.scala

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
trait Foo[State] {
2+
def bar(f: Bar[State] => Bar[State]): Foo[_] = this
3+
}
4+
object Foo {
5+
def unit: Foo[_] = new Foo[Any] {}
6+
def doBar: Foo[_] = unit.bar(bar => bar)
7+
}
8+
class Bar[+A]

0 commit comments

Comments
 (0)