Skip to content

Commit 866274e

Browse files
committed
Warn recursive deferred given impl selects itself
1 parent 0a7f843 commit 866274e

File tree

9 files changed

+142
-57
lines changed

9 files changed

+142
-57
lines changed

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

Lines changed: 59 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -3237,68 +3237,71 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
32373237
* parameters of the current class are also defined.
32383238
*/
32393239
def implementDeferredGivens(body: List[Tree]): List[Tree] =
3240+
def failFor(mbr: TermRef, why: String): false =
3241+
report.error(
3242+
em"""Cannot infer the implementation of the deferred ${mbr.symbol.showLocated}
3243+
|since $why. An implementing given needs to be written explicitly.""",
3244+
cdef.srcPos)
3245+
false
3246+
def isGivenValue(mbr: TermRef) = !mbr.symbol.is(Method) || failFor(mbr, "that given is parameterized")
3247+
3248+
def willBeImplementedInParentClass(m: TermRef) =
3249+
val superCls = cls.superClass
3250+
superCls.exists && superCls.asClass.baseClasses.contains(m.symbol.owner)
3251+
3252+
def givenImpl(mbr: TermRef): ValDef =
3253+
val dcl = mbr.symbol
3254+
val target = dcl.info.asSeenFrom(cls.thisType, dcl.owner)
3255+
val constr = cls.primaryConstructor
3256+
val usingParamAccessors = cls.paramAccessors.filter(_.is(Given))
3257+
val paramScope = newScopeWith(usingParamAccessors*)
3258+
val searchCtx = ctx.outer.fresh.setScope(paramScope)
3259+
3260+
// Before losing the reference to ctx.owner
3261+
// when calling implicitArgTree with searchCtx,
3262+
// let's store ctx.owner as the fallback "responsibleForImports"
3263+
// in DependencyRecorder. That way, if we end up recording any dependencies
3264+
// we use ctx.owner as the "fromClass" rather than emitting a warning
3265+
// (because ctx.compilationUnit.tpdTree is still EmptyTree during typer).
3266+
// For example, to record mirror dependencies, see i23049.
3267+
val depRecorder = ctx.compilationUnit.depRecorder
3268+
val responsibleForImports = depRecorder._responsibleForImports
3269+
if responsibleForImports == null then
3270+
depRecorder._responsibleForImports = ctx.owner
3271+
3272+
val rhs = implicitArgTree(target, cdef.span,
3273+
where = i"inferring the implementation of the deferred ${dcl.showLocated}"
3274+
)(using searchCtx)
3275+
val resolvedHere =
3276+
rhs.tpe match
3277+
case tp: NamedType => (tp.prefix.typeSymbol eq cls) && tp.name == mbr.name && !tp.typeSymbol.is(Method)
3278+
case _ => false
3279+
if resolvedHere then failFor(mbr, "the result is self-recursive")
3280+
3281+
if responsibleForImports == null then
3282+
depRecorder._responsibleForImports = null
3283+
3284+
val impl = dcl.copy(cls,
3285+
flags = dcl.flags &~ (HasDefault | Deferred) | Final | Override,
3286+
info = target,
3287+
coord = rhs.span).entered.asTerm
3288+
3289+
def anchorParams = new TreeMap:
3290+
override def transform(tree: Tree)(using Context): Tree = tree match
3291+
case id: Ident if usingParamAccessors.contains(id.symbol) =>
3292+
cpy.Select(id)(This(cls), id.name)
3293+
case _ =>
3294+
super.transform(tree)
3295+
ValDef(impl, anchorParams.transform(rhs)).withSpan(impl.span.endPos)
3296+
end givenImpl
3297+
32403298
if cls.is(Trait) || ctx.isAfterTyper then body
32413299
else
3242-
def isGivenValue(mbr: TermRef) =
3243-
val dcl = mbr.symbol
3244-
if dcl.is(Method) then
3245-
report.error(
3246-
em"""Cannnot infer the implementation of the deferred ${dcl.showLocated}
3247-
|since that given is parameterized. An implementing given needs to be written explicitly.""",
3248-
cdef.srcPos)
3249-
false
3250-
else true
3251-
3252-
def willBeimplementedInParentClass(m: TermRef) =
3253-
val superCls = cls.superClass
3254-
superCls.exists && superCls.asClass.baseClasses.contains(m.symbol.owner)
3255-
3256-
def givenImpl(mbr: TermRef): ValDef =
3257-
val dcl = mbr.symbol
3258-
val target = dcl.info.asSeenFrom(cls.thisType, dcl.owner)
3259-
val constr = cls.primaryConstructor
3260-
val usingParamAccessors = cls.paramAccessors.filter(_.is(Given))
3261-
val paramScope = newScopeWith(usingParamAccessors*)
3262-
val searchCtx = ctx.outer.fresh.setScope(paramScope)
3263-
3264-
// Before losing the reference to ctx.owner
3265-
// when calling implicitArgTree with searchCtx,
3266-
// let's store ctx.owner as the fallback "responsibleForImports"
3267-
// in DependencyRecorder. That way, if we end up recording any dependencies
3268-
// we use ctx.owner as the "fromClass" rather than emitting a warning
3269-
// (because ctx.compilationUnit.tpdTree is still EmptyTree during typer).
3270-
// For example, to record mirror dependencies, see i23049.
3271-
val depRecorder = ctx.compilationUnit.depRecorder
3272-
val responsibleForImports = depRecorder._responsibleForImports
3273-
if responsibleForImports == null then
3274-
depRecorder._responsibleForImports = ctx.owner
3275-
3276-
val rhs = implicitArgTree(target, cdef.span,
3277-
where = i"inferring the implementation of the deferred ${dcl.showLocated}"
3278-
)(using searchCtx)
3279-
3280-
if responsibleForImports == null then
3281-
depRecorder._responsibleForImports = null
3282-
3283-
val impl = dcl.copy(cls,
3284-
flags = dcl.flags &~ (HasDefault | Deferred) | Final | Override,
3285-
info = target,
3286-
coord = rhs.span).entered.asTerm
3287-
3288-
def anchorParams = new TreeMap:
3289-
override def transform(tree: Tree)(using Context): Tree = tree match
3290-
case id: Ident if usingParamAccessors.contains(id.symbol) =>
3291-
cpy.Select(id)(This(cls), id.name)
3292-
case _ =>
3293-
super.transform(tree)
3294-
ValDef(impl, anchorParams.transform(rhs)).withSpan(impl.span.endPos)
3295-
end givenImpl
3296-
32973300
val givenImpls =
32983301
cls.thisType.implicitMembers
32993302
//.showing(i"impl def givens for $cls/$result")
33003303
.filter(_.symbol.isAllOf(DeferredGivenFlags, butNot = Param))
3301-
.filter(!willBeimplementedInParentClass(_)) // only implement the given in the topmost class
3304+
.filter(!willBeImplementedInParentClass(_)) // only implement the given in the topmost class
33023305
//.showing(i"impl def filtered givens for $cls/$result")
33033306
.filter(isGivenValue)
33043307
.map(givenImpl)

tests/neg/deferred-givens.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@
99
-- Error: tests/neg/deferred-givens.scala:26:8 -------------------------------------------------------------------------
1010
26 | class E extends A2 // error, can't summon polymorphic given
1111
| ^^^^^^^^^^^^^^^^^^
12-
| Cannnot infer the implementation of the deferred given instance given_Ctx3_T in trait A2
12+
| Cannot infer the implementation of the deferred given instance given_Ctx3_T in trait A2
1313
| since that given is parameterized. An implementing given needs to be written explicitly.

tests/neg/i22589.check

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- Error: tests/neg/i22589.scala:15:7 ----------------------------------------------------------------------------------
2+
15 |object Person extends CompanionEssentials[Person]: // error
3+
|^
4+
|Cannot infer the implementation of the deferred given instance given_MyCodec_E in trait CompanionEssentials
5+
|since the result is self-recursive. An implementing given needs to be written explicitly.
6+
16 | //override final lazy given given_MyCodec_E: MyCodec[Person] = Person.given_MyCodec_E
7+
17 | override def toString = ""

tests/neg/i22589.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
//> using options -Wsafe-init -Ysafe-init-global
3+
4+
import scala.compiletime.deferred
5+
6+
trait MyCodec[E]
7+
8+
object auto:
9+
trait CompanionEssentials[E]:
10+
given MyCodec[E] = deferred
11+
12+
import auto.CompanionEssentials
13+
14+
case class Person(name: String, age: Int)
15+
object Person extends CompanionEssentials[Person]: // error
16+
//override final lazy given given_MyCodec_E: MyCodec[Person] = Person.given_MyCodec_E
17+
override def toString = ""

tests/neg/i22589b.check

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-- Error: tests/neg/i22589b.scala:13:7 ---------------------------------------------------------------------------------
2+
13 |object Person extends CompanionEssentials[Person]: // error
3+
|^
4+
|Cannot infer the implementation of the deferred given instance myc in trait CompanionEssentials
5+
|since the result is self-recursive. An implementing given needs to be written explicitly.
6+
14 | given String = "hw"
7+
15 | given myc(using String): MyCodec[Person] = new MyCodec[Person] {}
8+
16 | override def toString = ""

tests/neg/i22589b.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
import scala.compiletime.deferred
3+
4+
trait MyCodec[E]
5+
6+
object auto:
7+
trait CompanionEssentials[E]:
8+
given myc: MyCodec[E] = deferred
9+
10+
import auto.CompanionEssentials
11+
12+
case class Person(name: String, age: Int)
13+
object Person extends CompanionEssentials[Person]: // error
14+
given String = "hw"
15+
given myc(using String): MyCodec[Person] = new MyCodec[Person] {}
16+
override def toString = ""

tests/neg/i22589c.check

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- Error: tests/neg/i22589c.scala:8:7 ----------------------------------------------------------------------------------
2+
8 |object A extends Base[A.P] { // error
3+
|^
4+
|Cannot infer the implementation of the deferred given instance given_TC_T in trait Base
5+
|since the result is self-recursive. An implementing given needs to be written explicitly.
6+
9 | case class P()
7+
10 |}

tests/neg/i22589c.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
trait TC[T]
3+
4+
trait Base[T] {
5+
given TC[T] = scala.compiletime.deferred
6+
}
7+
8+
object A extends Base[A.P] { // error
9+
case class P()
10+
}

tests/pos/i22589.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
import scala.compiletime.deferred
3+
4+
trait MyCodec[E]
5+
6+
object auto:
7+
trait CompanionEssentials[E]:
8+
//given [E] => MyCodec[E] = deferred
9+
given MyCodec[E] = deferred
10+
11+
import auto.CompanionEssentials
12+
13+
case class Person(name: String, age: Int)
14+
object Person extends CompanionEssentials[Person]:
15+
//given something: [E] => MyCodec[E] = new MyCodec[E] {}
16+
given something: MyCodec[Person] = new MyCodec[Person] {}
17+
override def toString = ""

0 commit comments

Comments
 (0)