Skip to content

Commit 1fdb76d

Browse files
committed
Implement deferred givens
A definition like `given T = deferred` in a trait will be expanded to an abstract given in the trait that is implemented automatically in all classes inheriting the trait.
1 parent 2903fd1 commit 1fdb76d

28 files changed

+1545
-14
lines changed

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ class Definitions {
240240
@tu lazy val Compiletime_codeOf: Symbol = CompiletimePackageClass.requiredMethod("codeOf")
241241
@tu lazy val Compiletime_erasedValue : Symbol = CompiletimePackageClass.requiredMethod("erasedValue")
242242
@tu lazy val Compiletime_uninitialized: Symbol = CompiletimePackageClass.requiredMethod("uninitialized")
243+
@tu lazy val Compiletime_deferred : Symbol = CompiletimePackageClass.requiredMethod("deferred")
243244
@tu lazy val Compiletime_error : Symbol = CompiletimePackageClass.requiredMethod(nme.error)
244245
@tu lazy val Compiletime_requireConst : Symbol = CompiletimePackageClass.requiredMethod("requireConst")
245246
@tu lazy val Compiletime_constValue : Symbol = CompiletimePackageClass.requiredMethod("constValue")

compiler/src/dotty/tools/dotc/core/Flags.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,7 @@ object Flags {
570570
val DeferredOrLazyOrMethod: FlagSet = Deferred | Lazy | Method
571571
val DeferredOrTermParamOrAccessor: FlagSet = Deferred | ParamAccessor | TermParam // term symbols without right-hand sides
572572
val DeferredOrTypeParam: FlagSet = Deferred | TypeParam // type symbols without right-hand sides
573+
val DeferredGivenFlags = Deferred | Given | HasDefault
573574
val EnumValue: FlagSet = Enum | StableRealizable // A Scala enum value
574575
val FinalOrInline: FlagSet = Final | Inline
575576
val FinalOrModuleClass: FlagSet = Final | ModuleClass // A module class or a final class

compiler/src/dotty/tools/dotc/core/StdNames.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,7 @@ object StdNames {
452452
val create: N = "create"
453453
val currentMirror: N = "currentMirror"
454454
val curried: N = "curried"
455+
val deferred: N = "deferred"
455456
val definitions: N = "definitions"
456457
val delayedInit: N = "delayedInit"
457458
val delayedInitArg: N = "delayedInit$body"

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,13 @@ object Erasure {
567567
case Some(annot) =>
568568
val message = annot.argumentConstant(0) match
569569
case Some(c) =>
570-
c.stringValue.toMessage
570+
val addendum = tree match
571+
case tree: RefTree
572+
if tree.symbol == defn.Compiletime_deferred && tree.name != nme.deferred =>
573+
i".\nNote that `deferred` can only be used under its own name when implementing a given in a trait; `${tree.name}` is not accepted."
574+
case _ =>
575+
""
576+
(c.stringValue ++ addendum).toMessage
571577
case _ =>
572578
em"""Reference to ${tree.symbol.showLocated} should not have survived,
573579
|it should have been processed and eliminated during expansion of an enclosing macro or term erasure."""

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -922,10 +922,10 @@ trait Implicits:
922922

923923

924924
/** Search an implicit argument and report error if not found */
925-
def implicitArgTree(formal: Type, span: Span)(using Context): Tree = {
925+
def implicitArgTree(formal: Type, span: Span, where: => String = "")(using Context): Tree = {
926926
val arg = inferImplicitArg(formal, span)
927927
if (arg.tpe.isInstanceOf[SearchFailureType])
928-
report.error(missingArgMsg(arg, formal, ""), ctx.source.atSpan(span))
928+
report.error(missingArgMsg(arg, formal, where), ctx.source.atSpan(span))
929929
arg
930930
}
931931

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1817,6 +1817,18 @@ class Namer { typer: Typer =>
18171817
case _ =>
18181818
WildcardType
18191819
}
1820+
1821+
// translate `given T = deferred` to an abstract given with HasDefault flag
1822+
if sym.is(Given) then
1823+
mdef.rhs match
1824+
case rhs: RefTree
1825+
if rhs.name == nme.deferred
1826+
&& typedAheadExpr(rhs).symbol == defn.Compiletime_deferred
1827+
&& sym.maybeOwner.is(Trait) =>
1828+
sym.resetFlag(Final)
1829+
sym.setFlag(Deferred | HasDefault)
1830+
case _ =>
1831+
18201832
val mbrTpe = paramFn(checkSimpleKinded(typedAheadType(mdef.tpt, tptProto)).tpe)
18211833
if (ctx.explicitNulls && mdef.mods.is(JavaDefined))
18221834
JavaNullInterop.nullifyMember(sym, mbrTpe, mdef.mods.isAllOf(JavaEnumValue))

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,7 @@ object RefChecks {
552552
overrideError("is an extension method, cannot override a normal method")
553553
else if (other.is(ExtensionMethod) && !member.is(ExtensionMethod)) // (1.3)
554554
overrideError("is a normal method, cannot override an extension method")
555-
else if !other.is(Deferred)
555+
else if (!other.is(Deferred) || other.isAllOf(Given | HasDefault))
556556
&& !member.is(Deferred)
557557
&& !other.name.is(DefaultGetterName)
558558
&& !member.isAnyOverride

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

Lines changed: 72 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2569,12 +2569,17 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
25692569
val ValDef(name, tpt, _) = vdef
25702570
checkNonRootName(vdef.name, vdef.nameSpan)
25712571
completeAnnotations(vdef, sym)
2572-
if (sym.isOneOf(GivenOrImplicit)) checkImplicitConversionDefOK(sym)
2572+
if sym.is(Implicit) then checkImplicitConversionDefOK(sym)
25732573
if sym.is(Module) then checkNoModuleClash(sym)
25742574
val tpt1 = checkSimpleKinded(typedType(tpt))
25752575
val rhs1 = vdef.rhs match {
2576-
case rhs @ Ident(nme.WILDCARD) => rhs withType tpt1.tpe
2577-
case rhs => typedExpr(rhs, tpt1.tpe.widenExpr)
2576+
case rhs @ Ident(nme.WILDCARD) =>
2577+
rhs.withType(tpt1.tpe)
2578+
case rhs: RefTree
2579+
if rhs.name == nme.deferred && sym.isAllOf(DeferredGivenFlags, butNot = Param) =>
2580+
EmptyTree
2581+
case rhs =>
2582+
typedExpr(rhs, tpt1.tpe.widenExpr)
25782583
}
25792584
val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym)
25802585
postProcessInfo(vdef1, sym)
@@ -2635,9 +2640,13 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
26352640

26362641
if sym.isInlineMethod then rhsCtx.addMode(Mode.InlineableBody)
26372642
if sym.is(ExtensionMethod) then rhsCtx.addMode(Mode.InExtensionMethod)
2638-
val rhs1 = PrepareInlineable.dropInlineIfError(sym,
2639-
if sym.isScala2Macro then typedScala2MacroBody(ddef.rhs)(using rhsCtx)
2640-
else typedExpr(ddef.rhs, tpt1.tpe.widenExpr)(using rhsCtx))
2643+
val rhs1 = ddef.rhs match
2644+
case Ident(nme.deferred) if sym.isAllOf(DeferredGivenFlags) =>
2645+
EmptyTree
2646+
case rhs =>
2647+
PrepareInlineable.dropInlineIfError(sym,
2648+
if sym.isScala2Macro then typedScala2MacroBody(ddef.rhs)(using rhsCtx)
2649+
else typedExpr(ddef.rhs, tpt1.tpe.widenExpr)(using rhsCtx))
26412650

26422651
if sym.isInlineMethod then
26432652
if StagingLevel.level > 0 then
@@ -2818,6 +2827,59 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
28182827
case None =>
28192828
body
28202829

2830+
/** Implement givens that were declared with a `deferred` rhs.
2831+
* The a given value matching the declared type is searched in a
2832+
* context directly enclosing the current class, in which all given
2833+
* parameters of the current class are also defined.
2834+
*/
2835+
def implementDeferredGivens(body: List[Tree]): List[Tree] =
2836+
if cls.is(Trait) || ctx.isAfterTyper then body
2837+
else
2838+
def isGivenValue(mbr: TermRef) =
2839+
val dcl = mbr.symbol
2840+
if dcl.is(Method) then
2841+
report.error(
2842+
em"""Cannnot infer the implementation of the deferred ${dcl.showLocated}
2843+
|since that given is parameterized. An implementing given needs to be written explicitly.""",
2844+
cdef.srcPos)
2845+
false
2846+
else true
2847+
2848+
def givenImpl(mbr: TermRef): ValDef =
2849+
val dcl = mbr.symbol
2850+
val target = dcl.info.asSeenFrom(cls.thisType, dcl.owner)
2851+
val constr = cls.primaryConstructor
2852+
val usingParamAccessors = cls.paramAccessors.filter(_.is(Given))
2853+
val paramScope = newScopeWith(usingParamAccessors*)
2854+
val searchCtx = ctx.outer.fresh.setScope(paramScope)
2855+
val rhs = implicitArgTree(target, cdef.span,
2856+
where = i"inferring the implementation of the deferred ${dcl.showLocated}"
2857+
)(using searchCtx)
2858+
2859+
val impl = dcl.copy(cls,
2860+
flags = dcl.flags &~ (HasDefault | Deferred) | Final | Override,
2861+
info = target,
2862+
coord = rhs.span).entered.asTerm
2863+
2864+
def anchorParams = new TreeMap:
2865+
override def transform(tree: Tree)(using Context): Tree = tree match
2866+
case id: Ident if usingParamAccessors.contains(id.symbol) =>
2867+
cpy.Select(id)(This(cls), id.name)
2868+
case _ =>
2869+
super.transform(tree)
2870+
ValDef(impl, anchorParams.transform(rhs))
2871+
end givenImpl
2872+
2873+
val givenImpls =
2874+
cls.thisType.implicitMembers
2875+
//.showing(i"impl def givens for $cls/$result")
2876+
.filter(_.symbol.isAllOf(DeferredGivenFlags, butNot = Param))
2877+
//.showing(i"impl def filtered givens for $cls/$result")
2878+
.filter(isGivenValue)
2879+
.map(givenImpl)
2880+
body ++ givenImpls
2881+
end implementDeferredGivens
2882+
28212883
ensureCorrectSuperClass()
28222884
completeAnnotations(cdef, cls)
28232885
val constr1 = typed(constr).asInstanceOf[DefDef]
@@ -2839,9 +2901,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
28392901
else {
28402902
val dummy = localDummy(cls, impl)
28412903
val body1 =
2842-
addParentRefinements(
2843-
addAccessorDefs(cls,
2844-
typedStats(impl.body, dummy)(using ctx.inClassContext(self1.symbol))._1))
2904+
implementDeferredGivens(
2905+
addParentRefinements(
2906+
addAccessorDefs(cls,
2907+
typedStats(impl.body, dummy)(using ctx.inClassContext(self1.symbol))._1)))
28452908

28462909
checkNoDoubleDeclaration(cls)
28472910
val impl1 = cpy.Template(impl)(constr1, parents1, Nil, self1, body1)

compiler/test/dotc/pos-test-pickling.blacklist

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ i13842.scala
100100
# Position change under captureChecking
101101
boxmap-paper.scala
102102

103-
# Function types print differnt after unpickling since test mispredicts Feature.preFundsEnabled
103+
# Function types print different after unpickling since test mispredicts Feature.preFundsEnabled
104104
caps-universal.scala
105105

106106
# GADT cast applied to singleton type difference
@@ -122,6 +122,8 @@ i15525.scala
122122
parsercombinators-givens.scala
123123
parsercombinators-givens-2.scala
124124
parsercombinators-arrow.scala
125+
hylolib-deferred-given
126+
125127

126128

127129

library/src/scala/compiletime/package.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,19 @@ def erasedValue[T]: T = erasedValue[T]
4242
@compileTimeOnly("`uninitialized` can only be used as the right hand side of a mutable field definition")
4343
def uninitialized: Nothing = ???
4444

45+
/** Used as the right hand side of a given in a trait, like this
46+
*
47+
* ```
48+
* given T = deferred
49+
* ```
50+
*
51+
* This signifies that the given will get a synthesized definition in all classes
52+
* that implement the enclosing trait and that do not contain an explicit overriding
53+
* definition of that given.
54+
*/
55+
@compileTimeOnly("`deferred` can only be used as the right hand side of a given definition in a trait")
56+
def deferred: Nothing = ???
57+
4558
/** The error method is used to produce user-defined compile errors during inline expansion.
4659
* If an inline expansion results in a call error(msgStr) the compiler produces an error message containing the given msgStr.
4760
*

tests/neg/deferred-givens.check

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
-- [E172] Type Error: tests/neg/deferred-givens.scala:11:6 -------------------------------------------------------------
2+
11 |class B extends A // error
3+
|^^^^^^^^^^^^^^^^^
4+
|No given instance of type Ctx was found for inferring the implementation of the deferred given instance ctx in trait A
5+
-- [E172] Type Error: tests/neg/deferred-givens.scala:13:15 ------------------------------------------------------------
6+
13 |abstract class C extends A // error
7+
|^^^^^^^^^^^^^^^^^^^^^^^^^^
8+
|No given instance of type Ctx was found for inferring the implementation of the deferred given instance ctx in trait A
9+
-- Error: tests/neg/deferred-givens.scala:26:8 -------------------------------------------------------------------------
10+
26 | class E extends A2 // error, can't summon polymorphic given
11+
| ^^^^^^^^^^^^^^^^^^
12+
| Cannnot infer the implementation of the deferred given instance given_Ctx3_T in trait A2
13+
| since that given is parameterized. An implementing given needs to be written explicitly.

tests/neg/deferred-givens.scala

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//> using options -language:experimental.modularity -source future
2+
import compiletime.deferred
3+
4+
class Ctx
5+
class Ctx2
6+
7+
trait A:
8+
given Ctx as ctx = deferred
9+
given Ctx2 = deferred
10+
11+
class B extends A // error
12+
13+
abstract class C extends A // error
14+
15+
class D extends A:
16+
given Ctx as ctx = Ctx() // ok, was implemented
17+
given Ctx2 = Ctx2() // ok
18+
19+
class Ctx3[T]
20+
21+
trait A2:
22+
given [T] => Ctx3[T] = deferred
23+
24+
object O:
25+
given [T] => Ctx3[T] = Ctx3[T]()
26+
class E extends A2 // error, can't summon polymorphic given
27+
28+
class E extends A2:
29+
given [T] => Ctx3[T] = Ctx3[T]() // ok
30+

tests/neg/deferredSummon.check

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
-- Error: tests/neg/deferredSummon.scala:4:26 --------------------------------------------------------------------------
2+
4 | given Int = compiletime.deferred // error
3+
| ^^^^^^^^^^^^^^^^^^^^
4+
| `deferred` can only be used as the right hand side of a given definition in a trait
5+
-- Error: tests/neg/deferredSummon.scala:7:26 --------------------------------------------------------------------------
6+
7 | given Int = compiletime.deferred // error
7+
| ^^^^^^^^^^^^^^^^^^^^
8+
| `deferred` can only be used as the right hand side of a given definition in a trait
9+
-- Error: tests/neg/deferredSummon.scala:12:16 -------------------------------------------------------------------------
10+
12 | given Int = deferred // error
11+
| ^^^^^^^^
12+
| `deferred` can only be used as the right hand side of a given definition in a trait
13+
-- Error: tests/neg/deferredSummon.scala:16:14 -------------------------------------------------------------------------
14+
16 | given Int = defered // error
15+
| ^^^^^^^
16+
|`deferred` can only be used as the right hand side of a given definition in a trait.
17+
|Note that `deferred` can only be used under its own name when implementing a given in a trait; `defered` is not accepted.

tests/neg/deferredSummon.scala

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//> using options -language:experimental.modularity
2+
3+
object Test:
4+
given Int = compiletime.deferred // error
5+
6+
abstract class C:
7+
given Int = compiletime.deferred // error
8+
9+
trait A:
10+
import compiletime.deferred
11+
locally:
12+
given Int = deferred // error
13+
14+
trait B:
15+
import compiletime.deferred as defered
16+
given Int = defered // error
17+
18+
19+

tests/pos/deferred-givens.scala

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//> using options -language:experimental.modularity -source future
2+
import compiletime.*
3+
class Ord[Elem]
4+
5+
given Ord[Double]
6+
7+
trait B:
8+
type Elem
9+
given Ord[Elem] = deferred
10+
def foo = summon[Ord[Elem]]
11+
12+
class C extends B:
13+
type Elem = String
14+
override given Ord[Elem] = ???
15+
16+
def bar(using Ord[String]) = 1
17+
18+
class D(using Ord[String]) extends B:
19+
type Elem = String
20+
21+
class E(using x: Ord[String]) extends B:
22+
type Elem = String
23+
override given Ord[Elem] = x
24+
25+
class F[X: Ord] extends B:
26+
type Elem = X

tests/pos/deferredSummon.scala

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//> using options -language:experimental.modularity -source future
2+
import compiletime.deferred
3+
4+
trait Ord[Self]:
5+
def less(x: Self, y: Self): Boolean
6+
7+
trait A:
8+
type Elem
9+
given Ord[Elem] = deferred
10+
def foo = summon[Ord[Elem]]
11+
12+
object Inst:
13+
given Ord[Int]:
14+
def less(x: Int, y: Int) = x < y
15+
16+
object Test:
17+
import Inst.given
18+
class C extends A:
19+
type Elem = Int
20+
object E extends A:
21+
type Elem = Int
22+
given A:
23+
type Elem = Int
24+
25+
class D[T: Ord] extends A:
26+
type Elem = T
27+
28+
29+
30+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//> using options -language:experimental.modularity -source future
2+
package hylotest
3+
import compiletime.deferred
4+
5+
trait Value[Self]
6+
7+
/** A collection of elements accessible by their position. */
8+
trait Collection[Self]:
9+
10+
/** The type of the elements in the collection. */
11+
type Element
12+
given elementIsValue: Value[Element] = compiletime.deferred
13+
14+
class BitArray
15+
16+
given Value[Boolean] {}
17+
18+
given Collection[BitArray] with
19+
type Element = Boolean

0 commit comments

Comments
 (0)