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 2 commits 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
126 changes: 65 additions & 61 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3183,72 +3183,76 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
* parameters of the current class are also defined.
*/
def implementDeferredGivens(body: List[Tree]): List[Tree] =
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)

// Before losing the reference to ctx.owner
// when calling implicitArgTree with searchCtx,
// let's store ctx.owner as the fallback "responsibleForImports"
// in DependencyRecorder. That way, if we end up recording any dependencies
// we use ctx.owner as the "fromClass" rather than emitting a warning
// (because ctx.compilationUnit.tpdTree is still EmptyTree during typer).
// For example, to record mirror dependencies, see i23049.
val depRecorder = ctx.compilationUnit.depRecorder
val responsibleForImports = depRecorder._responsibleForImports
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)

// Before losing the reference to ctx.owner
// when calling implicitArgTree with searchCtx,
// let's store ctx.owner as the fallback "responsibleForImports"
// in DependencyRecorder. That way, if we end up recording any dependencies
// we use ctx.owner as the "fromClass" rather than emitting a warning
// (because ctx.compilationUnit.tpdTree is still EmptyTree during typer).
// For example, to record mirror dependencies, see i23049.
inline def withOwnerResponsibleForImports[A](inline op: A): A =
val depRecorder = ctx.compilationUnit.depRecorder
val responsibleForImports = depRecorder._responsibleForImports
if responsibleForImports == null then
depRecorder._responsibleForImports = ctx.owner
op.tap: _ =>
if responsibleForImports == null then
depRecorder._responsibleForImports = ctx.owner
depRecorder._responsibleForImports = null

val rhs = implicitArgTree(target, cdef.span,
where = i"inferring the implementation of the deferred ${dcl.showLocated}"
)(using searchCtx)
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 = withOwnerResponsibleForImports:
implicitArgTree(target, cdef.span,
where = i"inferring the implementation of the deferred ${dcl.showLocated}"
)(using searchCtx)
.tap:
_.tpe match
case tp: NamedType =>
val resolvedHere = tp.prefix.typeSymbol == cls && tp.name == mbr.name && !tp.typeSymbol.is(Method)
if resolvedHere then failFor(mbr, "the result is self-recursive")
case _ =>

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

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

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
//.showing(i"impl def filtered givens for $cls/$result")
.filter(isGivenValue)
.map(givenImpl)
body ++ givenImpls
if cls.is(Trait) || ctx.isAfterTyper then body
else
body ++ 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
//.showing(i"impl def filtered givens for $cls/$result")
.filter(isGivenValue)
.map(givenImpl)
end implementDeferredGivens

ensureCorrectSuperClass()
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