Skip to content

Commit 879517c

Browse files
authored
Merge pull request #10371 from dotty-staging/fix-#8623
2 parents 7f2c4f6 + 8c3f80a commit 879517c

File tree

15 files changed

+173
-44
lines changed

15 files changed

+173
-44
lines changed

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

+3-2
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

+7-2
Original file line numberDiff line numberDiff line change
@@ -3533,13 +3533,18 @@ object Parsers {
35333533
then paramClauses(givenOnly = true)
35343534
else Nil
35353535
newLinesOpt()
3536-
if !name.isEmpty || !tparams.isEmpty || !vparamss.isEmpty then
3536+
val noParams = tparams.isEmpty && vparamss.isEmpty
3537+
if !(name.isEmpty && noParams) then
35373538
accept(nme.as)
35383539
val parents = constrApps(commaOK = true, templateCanFollow = true)
35393540
if in.token == EQUALS && parents.length == 1 && parents.head.isType then
35403541
accept(EQUALS)
35413542
mods1 |= Final
3542-
DefDef(name, tparams, vparamss, parents.head, subExpr())
3543+
if noParams && !mods.is(Inline) then
3544+
mods1 |= Lazy
3545+
ValDef(name, parents.head, subExpr())
3546+
else
3547+
DefDef(name, tparams, vparamss, parents.head, subExpr())
35433548
else
35443549
possibleTemplateStart()
35453550
val tparams1 = tparams.map(tparam => tparam.withMods(tparam.mods | PrivateLocal))

compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID] {
150150
UnknownNamedEnclosingClassOrObjectID,
151151
IllegalCyclicTypeReferenceID,
152152
MissingTypeParameterInTypeAppID,
153-
UNUSED_ImplicitTypesCanOnlyBeFunctionTypesID,
153+
SkolemInInferredID,
154154
ErasedTypesCanOnlyBeFunctionTypesID,
155155
CaseClassMissingNonImplicitParamListID,
156156
EnumerationsShouldNotBeEmptyID,

compiler/src/dotty/tools/dotc/reporting/messages.scala

+19
Original file line numberDiff line numberDiff line change
@@ -1244,6 +1244,25 @@ import transform.SymUtils._
12441244
|""".stripMargin
12451245
}
12461246

1247+
class SkolemInInferred(tree: tpd.Tree, pt: Type, argument: tpd.Tree)(using Context)
1248+
extends TypeMsg(SkolemInInferredID):
1249+
private def argStr =
1250+
if argument.isEmpty then ""
1251+
else i" from argument of type ${argument.tpe.widen}"
1252+
def msg =
1253+
em"""Failure to generate given instance for type $pt$argStr)
1254+
|
1255+
|I found: $tree
1256+
|But the part corresponding to `<skolem>` is not a reference that can be generated.
1257+
|This might be because resolution yielded as given instance a function that is not
1258+
|known to be total and side-effect free."""
1259+
def explain =
1260+
em"""The part of given resolution that corresponds to `<skolem>` produced a term that
1261+
|is not a stable reference. Therefore a given instance could not be generated.
1262+
|
1263+
|To trouble-shoot the problem, try to supply an explicit expression instead of
1264+
|relying on implicit search at this point."""
1265+
12471266
class SuperQualMustBeParent(qual: untpd.Ident, cls: ClassSymbol)(using Context)
12481267
extends ReferenceMsg(SuperQualMustBeParentID) {
12491268
def msg = em"""|$qual does not name a parent of $cls"""

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

+58-25
Original file line numberDiff line numberDiff line change
@@ -18,47 +18,56 @@ object CacheAliasImplicits {
1818

1919
/** Flags that disable caching */
2020
val NoCacheFlags =
21-
StableRealizable | // It's a simple forwarder, leave it as one
22-
Exported // Export forwarders are never cached
21+
StableRealizable // It's a simple forwarder, leave it as one
22+
| Exported // Export forwarders are never cached
2323
}
2424

2525
/** This phase ensures that the right hand side of parameterless alias implicits
26-
* is cached. It applies to all alias implicits that have neither type parameters
27-
* nor a given clause. Example: The alias
26+
* is cached if necessary. Dually, it optimizes lazy vak alias implicit to be uncached
27+
* if that does not change runtime behavior.
2828
*
29-
* TC = rhs
29+
* A definition does not need to be cached if its right hand side has a stable type
30+
* and is of one of them forms
3031
*
31-
* is expanded before this phase to:
32-
*
33-
* implicit def a: TC = rhs
34-
*
35-
* It is then expanded further as follows:
36-
*
37-
* 1. If `rhs` is a simple name `x` (possibly with a `this.` prefix) that
38-
* refers to a value, leave it as is.
39-
*
40-
* 2. Otherwise, replace the definition with
41-
*
42-
* lazy implicit val a: TC = rhs
32+
* this
33+
* this.y
34+
* y
4335
*/
4436
class CacheAliasImplicits extends MiniPhase with IdentityDenotTransformer { thisPhase =>
4537
import tpd._
4638

4739
override def phaseName: String = CacheAliasImplicits.name
4840

41+
private def needsCache(sym: Symbol, rhs: Tree)(using Context): Boolean = rhs.tpe match
42+
case rhsTpe @ TermRef(NoPrefix, _)
43+
if rhsTpe.isStable => false
44+
case rhsTpe @ TermRef(pre: ThisType, _)
45+
if rhsTpe.isStable && pre.cls == sym.owner.enclosingClass => false
46+
case rhsTpe: ThisType => false
47+
case _ => true
48+
49+
/** Transform
50+
*
51+
* given def x = rhs
52+
*
53+
* to
54+
*
55+
* lazy val x = rhs
56+
*
57+
* unless `rhs` has a stable type and is of one of them forms
58+
*
59+
* this
60+
* this.y
61+
* y
62+
*
63+
* Parameterless given defs are generated during typeclass derivation.
64+
*/
4965
override def transformDefDef(tree: DefDef)(using Context): Tree = {
5066
val sym = tree.symbol
5167
val isCached = !sym.is(Inline) && {
5268
sym.info match {
5369
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-
}
70+
needsCache(sym, tree.rhs)
6271
case _ => false
6372
}
6473
}
@@ -71,6 +80,30 @@ class CacheAliasImplicits extends MiniPhase with IdentityDenotTransformer { this
7180
}
7281
else tree
7382
}
83+
84+
/** Transform
85+
*
86+
* lazy given val x = rhs
87+
*
88+
* to
89+
*
90+
* def x = rhs
91+
*
92+
* provided `rhs` has a stable type and is of one of them forms
93+
*
94+
* this
95+
* this.y
96+
* y
97+
*/
98+
override def transformValDef(tree: ValDef)(using Context): Tree =
99+
val sym = tree.symbol
100+
if sym.isAllOf(Given, Lazy) && !needsCache(sym, tree.rhs) then
101+
sym.copySymDenotation(
102+
initFlags = sym.flags &~ Lazy | Method,
103+
info = ExprType(sym.info))
104+
.installAfter(thisPhase)
105+
cpy.DefDef(tree)(tree.name, Nil, Nil, tree.tpt, tree.rhs)
106+
else tree
74107
}
75108

76109

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

+1-1
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

+2-5
Original file line numberDiff line numberDiff line change
@@ -742,7 +742,7 @@ trait Checking {
742742
defn.ObjectType
743743
}
744744

745-
/** If `sym` is an implicit conversion, check that implicit conversions are enabled.
745+
/** If `sym` is an old-style implicit conversion, check that implicit conversions are enabled.
746746
* @pre sym.is(GivenOrImplicit)
747747
*/
748748
def checkImplicitConversionDefOK(sym: Symbol)(using Context): Unit = {
@@ -755,10 +755,7 @@ trait Checking {
755755

756756
sym.info.stripPoly match {
757757
case mt @ MethodType(_ :: Nil)
758-
if !mt.isImplicitMethod && !sym.is(Synthetic) => // it's a conversion
759-
check()
760-
case AppliedType(tycon, _)
761-
if tycon.derivesFrom(defn.ConversionClass) && !sym.is(Synthetic) =>
758+
if !mt.isImplicitMethod && !sym.is(Synthetic) => // it's an old-styleconversion
762759
check()
763760
case _ =>
764761
}

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

+15
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,19 @@ trait Implicits:
917917
implicits.println(i"CanEqual witness found for $ltp / $rtp: $res: ${res.tpe}")
918918
}
919919

920+
object hasSkolem extends TreeAccumulator[Boolean]:
921+
def apply(x: Boolean, tree: Tree)(using Context): Boolean =
922+
x || {
923+
tree match
924+
case tree: Ident => tree.symbol.isSkolem
925+
case Select(qual, _) => apply(x, qual)
926+
case Apply(fn, _) => apply(x, fn)
927+
case TypeApply(fn, _) => apply(x, fn)
928+
case tree: Applications.IntegratedTypeArgs => apply(x, tree.app)
929+
case _: This => false
930+
case _ => foldOver(x, tree)
931+
}
932+
920933
/** Find an implicit parameter or conversion.
921934
* @param pt The expected type of the parameter or conversion.
922935
* @param argument If an implicit conversion is searched, the argument to which
@@ -940,6 +953,8 @@ trait Implicits:
940953
case result: SearchSuccess =>
941954
result.tstate.commit()
942955
ctx.gadt.restore(result.gstate)
956+
if hasSkolem(false, result.tree) then
957+
report.error(SkolemInInferred(result.tree, pt, argument), ctx.source.atSpan(span))
943958
implicits.println(i"success: $result")
944959
implicits.println(i"committing ${result.tstate.constraint} yielding ${ctx.typerState.constraint} in ${ctx.typerState}")
945960
result

compiler/test/dotty/tools/repl/ReplCompilerTests.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ class ReplCompilerTests extends ReplTest {
155155
fromInitialState { implicit state => run("given Int = 10") }
156156
.andThen { implicit state =>
157157
assertEquals(
158-
"def given_Int: Int",
158+
"lazy val given_Int: Int",
159159
storedOutput().trim
160160
)
161161
run("implicitly[Int]")

tests/neg-macros/i7048e.scala

+6-6
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,31 @@ abstract class Test {
99

1010
def foo(using Quotes): Expr[Any] = {
1111

12-
val r = '{Option.empty[T]} // error: is not stable
12+
val r = '{Option.empty[T]} // works
1313

1414
{
1515
val t: Test = this
1616
import t.given
1717
println(Type.show[t.T])
18-
// val r = '{Option.empty[t.T]} // access to value t from wrong staging level
18+
val r = '{Option.empty[t.T]} // works
1919
val r2 = '{Option.empty[t.T.Underlying]} // works
2020
}
2121

2222
{
2323
val r1 = '{Option.empty[T.Underlying]} // works
2424
val r2 = '{Option.empty[List[T.Underlying]]} // works
25-
val r3 = '{summon[Type[T.Underlying]]} // error: is not stable
26-
val r4 = '{summon[T.Underlying <:< Any]} // error: is not stable
25+
val r3 = '{summon[Type[T.Underlying]]} // error: access to value t from wrong staging level
26+
val r4 = '{summon[T.Underlying <:< Any]} // works
2727
}
2828

2929
{
3030
val s = '{Option.empty[T.Underlying]} // works
3131
val r = '{identity($s)} // works
32-
val r2 = '{identity(${s: Expr[Option[T]]})} // error // error : is not stable
32+
val r2 = '{identity(${s: Expr[Option[T]]})} //works
3333
}
3434

3535
r
3636
}
3737
}
3838

39-
@main def main = ()
39+
@main def tester = ()

tests/neg/i8623.check

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-- [E142] Type Error: tests/neg/i8623.scala:11:2 -----------------------------------------------------------------------
2+
11 | unseal.pos // error
3+
| ^^^^^^
4+
| Failure to generate given instance for type ?{ pos: ? } from argument of type ?1.tasty.Tree)
5+
|
6+
| I found: <skolem>.tasty.pos(unseal(given_QC[Any]))
7+
| But the part corresponding to `<skolem>` is not a reference that can be generated.
8+
| This might be because resolution yielded as given instance a function that is not
9+
| known to be total and side-effect free.
10+
11+
longer explanation available when compiling with `-explain`

tests/neg/i8623.scala

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
trait QC:
3+
object tasty:
4+
type Tree
5+
extension (tree: Tree)
6+
def pos: Tree = ???
7+
8+
def test =
9+
given [T] as QC = ???
10+
def unseal(using qctx: QC): qctx.tasty.Tree = ???
11+
unseal.pos // error
12+

tests/neg/i8623a.scala

+19
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

+17
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

+1-1
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)