Skip to content

Fail self-recursive deferred given impl #22595

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 43 additions & 40 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3143,52 +3143,55 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
* parameters of the current class are also defined.
*/
def implementDeferredGivens(body: List[Tree]): List[Tree] =
def failFor(mbr: TermRef, why: String): false =
report.error(
em"""Cannot infer the implementation of the deferred ${mbr.symbol.showLocated}
|since $why. An implementing given needs to be written explicitly.""",
cdef.srcPos)
false
def isGivenValue(mbr: TermRef) = !mbr.symbol.is(Method) || failFor(mbr, "that given is parameterized")

def willBeImplementedInParentClass(m: TermRef) =
val superCls = cls.superClass
superCls.exists && superCls.asClass.baseClasses.contains(m.symbol.owner)

def givenImpl(mbr: TermRef): ValDef =
val dcl = mbr.symbol
val target = dcl.info.asSeenFrom(cls.thisType, dcl.owner)
val constr = cls.primaryConstructor
val usingParamAccessors = cls.paramAccessors.filter(_.is(Given))
val paramScope = newScopeWith(usingParamAccessors*)
val searchCtx = ctx.outer.fresh.setScope(paramScope)
val rhs = implicitArgTree(target, cdef.span,
where = i"inferring the implementation of the deferred ${dcl.showLocated}"
)(using searchCtx)
val resolvedHere =
rhs.tpe match
case tp: NamedType => (tp.prefix.typeSymbol eq cls) && tp.name == mbr.name && !tp.typeSymbol.is(Method)
case _ => false
if resolvedHere then failFor(mbr, "the result is self-recursive")

val impl = dcl.copy(cls,
flags = dcl.flags &~ (HasDefault | Deferred) | Final | Override,
info = target,
coord = rhs.span).entered.asTerm

def anchorParams = new TreeMap:
override def transform(tree: Tree)(using Context): Tree = tree match
case id: Ident if usingParamAccessors.contains(id.symbol) =>
cpy.Select(id)(This(cls), id.name)
case _ =>
super.transform(tree)
ValDef(impl, anchorParams.transform(rhs)).withSpan(impl.span.endPos)
end givenImpl

if cls.is(Trait) || ctx.isAfterTyper then body
else
def isGivenValue(mbr: TermRef) =
val dcl = mbr.symbol
if dcl.is(Method) then
report.error(
em"""Cannnot infer the implementation of the deferred ${dcl.showLocated}
|since that given is parameterized. An implementing given needs to be written explicitly.""",
cdef.srcPos)
false
else true

def willBeimplementedInParentClass(m: TermRef) =
val superCls = cls.superClass
superCls.exists && superCls.asClass.baseClasses.contains(m.symbol.owner)

def givenImpl(mbr: TermRef): ValDef =
val dcl = mbr.symbol
val target = dcl.info.asSeenFrom(cls.thisType, dcl.owner)
val constr = cls.primaryConstructor
val usingParamAccessors = cls.paramAccessors.filter(_.is(Given))
val paramScope = newScopeWith(usingParamAccessors*)
val searchCtx = ctx.outer.fresh.setScope(paramScope)
val rhs = implicitArgTree(target, cdef.span,
where = i"inferring the implementation of the deferred ${dcl.showLocated}"
)(using searchCtx)

val impl = dcl.copy(cls,
flags = dcl.flags &~ (HasDefault | Deferred) | Final | Override,
info = target,
coord = rhs.span).entered.asTerm

def anchorParams = new TreeMap:
override def transform(tree: Tree)(using Context): Tree = tree match
case id: Ident if usingParamAccessors.contains(id.symbol) =>
cpy.Select(id)(This(cls), id.name)
case _ =>
super.transform(tree)
ValDef(impl, anchorParams.transform(rhs)).withSpan(impl.span.endPos)
end givenImpl

val givenImpls =
cls.thisType.implicitMembers
//.showing(i"impl def givens for $cls/$result")
.filter(_.symbol.isAllOf(DeferredGivenFlags, butNot = Param))
.filter(!willBeimplementedInParentClass(_)) // only implement the given in the topmost class
.filter(!willBeImplementedInParentClass(_)) // only implement the given in the topmost class
//.showing(i"impl def filtered givens for $cls/$result")
.filter(isGivenValue)
.map(givenImpl)
Expand Down
2 changes: 1 addition & 1 deletion tests/neg/deferred-givens.check
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
-- Error: tests/neg/deferred-givens.scala:26:8 -------------------------------------------------------------------------
26 | class E extends A2 // error, can't summon polymorphic given
| ^^^^^^^^^^^^^^^^^^
| Cannnot infer the implementation of the deferred given instance given_Ctx3_T in trait A2
| Cannot infer the implementation of the deferred given instance given_Ctx3_T in trait A2
| since that given is parameterized. An implementing given needs to be written explicitly.
7 changes: 7 additions & 0 deletions tests/neg/i22589.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- Error: tests/neg/i22589.scala:15:7 ----------------------------------------------------------------------------------
15 |object Person extends CompanionEssentials[Person]: // error
|^
|Cannot infer the implementation of the deferred given instance given_MyCodec_E in trait CompanionEssentials
|since the result is self-recursive. An implementing given needs to be written explicitly.
16 | //override final lazy given given_MyCodec_E: MyCodec[Person] = Person.given_MyCodec_E
17 | override def toString = ""
17 changes: 17 additions & 0 deletions tests/neg/i22589.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

//> using options -Wsafe-init -Ysafe-init-global

import scala.compiletime.deferred

trait MyCodec[E]

object auto:
trait CompanionEssentials[E]:
given MyCodec[E] = deferred

import auto.CompanionEssentials

case class Person(name: String, age: Int)
object Person extends CompanionEssentials[Person]: // error
//override final lazy given given_MyCodec_E: MyCodec[Person] = Person.given_MyCodec_E
override def toString = ""
8 changes: 8 additions & 0 deletions tests/neg/i22589b.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- Error: tests/neg/i22589b.scala:13:7 ---------------------------------------------------------------------------------
13 |object Person extends CompanionEssentials[Person]: // error
|^
|Cannot infer the implementation of the deferred given instance myc in trait CompanionEssentials
|since the result is self-recursive. An implementing given needs to be written explicitly.
14 | given String = "hw"
15 | given myc(using String): MyCodec[Person] = new MyCodec[Person] {}
16 | override def toString = ""
16 changes: 16 additions & 0 deletions tests/neg/i22589b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

import scala.compiletime.deferred

trait MyCodec[E]

object auto:
trait CompanionEssentials[E]:
given myc: MyCodec[E] = deferred

import auto.CompanionEssentials

case class Person(name: String, age: Int)
object Person extends CompanionEssentials[Person]: // error
given String = "hw"
given myc(using String): MyCodec[Person] = new MyCodec[Person] {}
override def toString = ""
17 changes: 17 additions & 0 deletions tests/pos/i22589.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

import scala.compiletime.deferred

trait MyCodec[E]

object auto:
trait CompanionEssentials[E]:
//given [E] => MyCodec[E] = deferred
given MyCodec[E] = deferred

import auto.CompanionEssentials

case class Person(name: String, age: Int)
object Person extends CompanionEssentials[Person]:
//given something: [E] => MyCodec[E] = new MyCodec[E] {}
given something: MyCodec[Person] = new MyCodec[Person] {}
override def toString = ""
Loading