diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index bc0b295c421f..4d3d8172e856 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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() diff --git a/tests/neg/deferred-givens.check b/tests/neg/deferred-givens.check index cc15901d087f..7195990de3fc 100644 --- a/tests/neg/deferred-givens.check +++ b/tests/neg/deferred-givens.check @@ -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. diff --git a/tests/neg/i22589.check b/tests/neg/i22589.check new file mode 100644 index 000000000000..3743c0496f77 --- /dev/null +++ b/tests/neg/i22589.check @@ -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 = "" diff --git a/tests/neg/i22589.scala b/tests/neg/i22589.scala new file mode 100644 index 000000000000..df989dbf1800 --- /dev/null +++ b/tests/neg/i22589.scala @@ -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 = "" diff --git a/tests/neg/i22589b.check b/tests/neg/i22589b.check new file mode 100644 index 000000000000..023b85a6724d --- /dev/null +++ b/tests/neg/i22589b.check @@ -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 = "" diff --git a/tests/neg/i22589b.scala b/tests/neg/i22589b.scala new file mode 100644 index 000000000000..da428d0e6470 --- /dev/null +++ b/tests/neg/i22589b.scala @@ -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 = "" diff --git a/tests/pos/i22589.scala b/tests/pos/i22589.scala new file mode 100644 index 000000000000..cd5a7f179180 --- /dev/null +++ b/tests/pos/i22589.scala @@ -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 = ""