Skip to content

Commit 28782eb

Browse files
committed
Make parameterless givens lazy vals
We used to represent them as defs and converted some of them to lazy vals later, in CacheAliasImplicits. But that means a parameterless given value is never a stable prefix, so we cannot use it as a qualifier for an abstract type. We now change the scheme so that parameterless givens start out as lazy vals and some of them are changed to defs later in CacheAliasImplicits. This is not a uniform improvement, since lazy vals have the restriction that their types must be realizable. See i9103.scala, where we had to change a type field to a trait to keep it working. But I think overall this is an improvement to what we had before.
1 parent 7cf571e commit 28782eb

File tree

8 files changed

+99
-19
lines changed

8 files changed

+99
-19
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ object desugar {
166166
val mods = vdef.mods
167167

168168
val valName = normalizeName(vdef, tpt).asTermName
169+
val vdef1 = cpy.ValDef(vdef)(name = valName)
169170

170171
if (isSetterNeeded(vdef)) {
171172
// TODO: copy of vdef as getter needed?
@@ -182,9 +183,9 @@ object desugar {
182183
tpt = TypeTree(defn.UnitType),
183184
rhs = setterRhs
184185
).withMods((mods | Accessor) &~ (CaseAccessor | GivenOrImplicit | Lazy))
185-
Thicket(vdef, setter)
186+
Thicket(vdef1, setter)
186187
}
187-
else vdef
188+
else vdef1
188189
}
189190

190191
def makeImplicitParameters(tpts: List[Tree], implicitFlag: FlagSet, forPrimaryConstructor: Boolean = false)(using Context): List[ValDef] =

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3536,13 +3536,18 @@ object Parsers {
35363536
then paramClauses(givenOnly = true)
35373537
else Nil
35383538
newLinesOpt()
3539-
if !name.isEmpty || !tparams.isEmpty || !vparamss.isEmpty then
3539+
val noParams = tparams.isEmpty && vparamss.isEmpty
3540+
if !(name.isEmpty && noParams) then
35403541
accept(nme.as)
35413542
val parents = constrApps(commaOK = true, templateCanFollow = true)
35423543
if in.token == EQUALS && parents.length == 1 && parents.head.isType then
35433544
accept(EQUALS)
35443545
mods1 |= Final
3545-
DefDef(name, tparams, vparamss, parents.head, subExpr())
3546+
if noParams && !mods.is(Inline) then
3547+
mods1 |= Lazy
3548+
ValDef(name, parents.head, subExpr())
3549+
else
3550+
DefDef(name, tparams, vparamss, parents.head, subExpr())
35463551
else
35473552
possibleTemplateStart()
35483553
val tparams1 = tparams.map(tparam => tparam.withMods(tparam.mods | PrivateLocal))

compiler/src/dotty/tools/dotc/transform/CacheAliasImplicits.scala

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,36 @@ class CacheAliasImplicits extends MiniPhase with IdentityDenotTransformer { this
4646

4747
override def phaseName: String = CacheAliasImplicits.name
4848

49+
private def needsCache(sym: Symbol, rhs: Tree)(using Context): Boolean = rhs.tpe match
50+
case rhsTpe @ TermRef(NoPrefix, _)
51+
if rhsTpe.isStable => false
52+
case rhsTpe @ TermRef(pre: ThisType, _)
53+
if rhsTpe.isStable && pre.cls == sym.owner.enclosingClass => false
54+
case rhsTpe: ThisType => false
55+
case _ => true
56+
57+
/** Transform
58+
*
59+
* given def x = rhs
60+
*
61+
* to
62+
*
63+
* lazy val x = rhs
64+
*
65+
* unless `rhs` has a stable type and is of one of them forms
66+
*
67+
* this
68+
* this.y
69+
* y
70+
*
71+
* Parameterless given defs are generated during typeclass derivation.
72+
*/
4973
override def transformDefDef(tree: DefDef)(using Context): Tree = {
5074
val sym = tree.symbol
5175
val isCached = !sym.is(Inline) && {
5276
sym.info match {
5377
case ExprType(resTpe) if sym.is(Given, butNot = CacheAliasImplicits.NoCacheFlags) =>
54-
tree.rhs.tpe match {
55-
case rhsTpe @ TermRef(NoPrefix, _)
56-
if rhsTpe.isStable => false
57-
case rhsTpe @ TermRef(pre: ThisType, _)
58-
if rhsTpe.isStable && pre.cls == sym.owner.enclosingClass => false
59-
case rhsTpe: ThisType => false
60-
case _ => true
61-
}
78+
needsCache(sym, tree.rhs)
6279
case _ => false
6380
}
6481
}
@@ -71,6 +88,30 @@ class CacheAliasImplicits extends MiniPhase with IdentityDenotTransformer { this
7188
}
7289
else tree
7390
}
91+
92+
/** Transform
93+
*
94+
* lazy given val x = rhs
95+
*
96+
* to
97+
*
98+
* def x = rhs
99+
*
100+
* provided `rhs` has a stable type and is of one of them forms
101+
*
102+
* this
103+
* this.y
104+
* y
105+
*/
106+
override def transformValDef(tree: ValDef)(using Context): Tree =
107+
val sym = tree.symbol
108+
if sym.isAllOf(Given, Lazy) && !needsCache(sym, tree.rhs) then
109+
sym.copySymDenotation(
110+
initFlags = sym.flags &~ Lazy | Method,
111+
info = ExprType(sym.info))
112+
.installAfter(thisPhase)
113+
cpy.DefDef(tree)(tree.name, Nil, Nil, tree.tpt, tree.rhs)
114+
else tree
74115
}
75116

76117

compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ object ExplicitOuter {
353353
*/
354354
class OuterOps(val ictx: Context) extends AnyVal {
355355
/** The context of all operations of this class */
356-
given Context = ictx
356+
given [Dummy] as Context = ictx
357357

358358
/** If `cls` has an outer parameter add one to the method type `tp`. */
359359
def addParam(cls: ClassSymbol, tp: Type): Type =

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -737,7 +737,7 @@ trait Checking {
737737
defn.ObjectType
738738
}
739739

740-
/** If `sym` is an implicit conversion, check that implicit conversions are enabled.
740+
/** If `sym` is an old-style implicit conversion, check that implicit conversions are enabled.
741741
* @pre sym.is(GivenOrImplicit)
742742
*/
743743
def checkImplicitConversionDefOK(sym: Symbol)(using Context): Unit = {
@@ -750,10 +750,7 @@ trait Checking {
750750

751751
sym.info.stripPoly match {
752752
case mt @ MethodType(_ :: Nil)
753-
if !mt.isImplicitMethod && !sym.is(Synthetic) => // it's a conversion
754-
check()
755-
case AppliedType(tycon, _)
756-
if tycon.derivesFrom(defn.ConversionClass) && !sym.is(Synthetic) =>
753+
if !mt.isImplicitMethod && !sym.is(Synthetic) => // it's an old-styleconversion
757754
check()
758755
case _ =>
759756
}

tests/neg/i8623a.scala

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
object Test {
2+
trait A {
3+
type X = String
4+
}
5+
trait B {
6+
type X = Int
7+
}
8+
9+
def foo: A & B = o
10+
given o as (A & B) = foo
11+
12+
def xToString(x: o.X): String = x // error
13+
14+
def intToString(i: Int): String = xToString(i)
15+
16+
def main(args: Array[String]): Unit = {
17+
val z: String = intToString(1)
18+
}
19+
}

tests/pos/i8623.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
trait QC:
3+
object tasty:
4+
type Tree
5+
extension (tree: Tree)
6+
def pos: Tree = ???
7+
8+
def test1 =
9+
given QC = ???
10+
def unseal(using qctx: QC): qctx.tasty.Tree = ???
11+
unseal.pos
12+
13+
def test2 =
14+
given QC
15+
def unseal(using qctx: QC): qctx.tasty.Tree = ???
16+
unseal.pos
17+

tests/pos/i9103.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
object a:
2-
type Foo[T]
2+
trait Foo[T]
33
given Foo[Unit] = ???
44

55
val b = a

0 commit comments

Comments
 (0)