Skip to content

Commit b48fb99

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 a61d2bc commit b48fb99

28 files changed

+1545
-14
lines changed

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

+1
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

+1
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,7 @@ object Flags {
573573
val DeferredOrLazyOrMethod: FlagSet = Deferred | Lazy | Method
574574
val DeferredOrTermParamOrAccessor: FlagSet = Deferred | ParamAccessor | TermParam // term symbols without right-hand sides
575575
val DeferredOrTypeParam: FlagSet = Deferred | TypeParam // type symbols without right-hand sides
576+
val DeferredGivenFlags = Deferred | Given | HasDefault
576577
val EnumValue: FlagSet = Enum | StableRealizable // A Scala enum value
577578
val FinalOrInline: FlagSet = Final | Inline
578579
val FinalOrModuleClass: FlagSet = Final | ModuleClass // A module class or a final class

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

+1
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,7 @@ object StdNames {
455455
val create: N = "create"
456456
val currentMirror: N = "currentMirror"
457457
val curried: N = "curried"
458+
val deferred: N = "deferred"
458459
val definitions: N = "definitions"
459460
val delayedInit: N = "delayedInit"
460461
val delayedInitArg: N = "delayedInit$body"

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

+7-1
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

+2-2
Original file line numberDiff line numberDiff line change
@@ -924,10 +924,10 @@ trait Implicits:
924924

925925

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

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

+12
Original file line numberDiff line numberDiff line change
@@ -1830,6 +1830,18 @@ class Namer { typer: Typer =>
18301830
case _ =>
18311831
WildcardType
18321832
}
1833+
1834+
// translate `given T = deferred` to an abstract given with HasDefault flag
1835+
if sym.is(Given) then
1836+
mdef.rhs match
1837+
case rhs: RefTree
1838+
if rhs.name == nme.deferred
1839+
&& typedAheadExpr(rhs).symbol == defn.Compiletime_deferred
1840+
&& sym.maybeOwner.is(Trait) =>
1841+
sym.resetFlag(Final)
1842+
sym.setFlag(Deferred | HasDefault)
1843+
case _ =>
1844+
18331845
val mbrTpe = paramFn(checkSimpleKinded(typedAheadType(mdef.tpt, tptProto)).tpe)
18341846
if (ctx.explicitNulls && mdef.mods.is(JavaDefined))
18351847
JavaNullInterop.nullifyMember(sym, mbrTpe, mdef.mods.isAllOf(JavaEnumValue))

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

+1-1
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

+72-9
Original file line numberDiff line numberDiff line change
@@ -2649,12 +2649,17 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
26492649
val ValDef(name, tpt, _) = vdef
26502650
checkNonRootName(vdef.name, vdef.nameSpan)
26512651
completeAnnotations(vdef, sym)
2652-
if (sym.isOneOf(GivenOrImplicit)) checkImplicitConversionDefOK(sym)
2652+
if sym.is(Implicit) then checkImplicitConversionDefOK(sym)
26532653
if sym.is(Module) then checkNoModuleClash(sym)
26542654
val tpt1 = checkSimpleKinded(typedType(tpt))
26552655
val rhs1 = vdef.rhs match {
2656-
case rhs @ Ident(nme.WILDCARD) => rhs withType tpt1.tpe
2657-
case rhs => typedExpr(rhs, tpt1.tpe.widenExpr)
2656+
case rhs @ Ident(nme.WILDCARD) =>
2657+
rhs.withType(tpt1.tpe)
2658+
case rhs: RefTree
2659+
if rhs.name == nme.deferred && sym.isAllOf(DeferredGivenFlags, butNot = Param) =>
2660+
EmptyTree
2661+
case rhs =>
2662+
typedExpr(rhs, tpt1.tpe.widenExpr)
26582663
}
26592664
val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym)
26602665
postProcessInfo(vdef1, sym)
@@ -2715,9 +2720,13 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
27152720

27162721
if sym.isInlineMethod then rhsCtx.addMode(Mode.InlineableBody)
27172722
if sym.is(ExtensionMethod) then rhsCtx.addMode(Mode.InExtensionMethod)
2718-
val rhs1 = PrepareInlineable.dropInlineIfError(sym,
2719-
if sym.isScala2Macro then typedScala2MacroBody(ddef.rhs)(using rhsCtx)
2720-
else typedExpr(ddef.rhs, tpt1.tpe.widenExpr)(using rhsCtx))
2723+
val rhs1 = ddef.rhs match
2724+
case Ident(nme.deferred) if sym.isAllOf(DeferredGivenFlags) =>
2725+
EmptyTree
2726+
case rhs =>
2727+
PrepareInlineable.dropInlineIfError(sym,
2728+
if sym.isScala2Macro then typedScala2MacroBody(ddef.rhs)(using rhsCtx)
2729+
else typedExpr(ddef.rhs, tpt1.tpe.widenExpr)(using rhsCtx))
27212730

27222731
if sym.isInlineMethod then
27232732
if StagingLevel.level > 0 then
@@ -2898,6 +2907,59 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
28982907
case None =>
28992908
body
29002909

2910+
/** Implement givens that were declared with a `deferred` rhs.
2911+
* The a given value matching the declared type is searched in a
2912+
* context directly enclosing the current class, in which all given
2913+
* parameters of the current class are also defined.
2914+
*/
2915+
def implementDeferredGivens(body: List[Tree]): List[Tree] =
2916+
if cls.is(Trait) || ctx.isAfterTyper then body
2917+
else
2918+
def isGivenValue(mbr: TermRef) =
2919+
val dcl = mbr.symbol
2920+
if dcl.is(Method) then
2921+
report.error(
2922+
em"""Cannnot infer the implementation of the deferred ${dcl.showLocated}
2923+
|since that given is parameterized. An implementing given needs to be written explicitly.""",
2924+
cdef.srcPos)
2925+
false
2926+
else true
2927+
2928+
def givenImpl(mbr: TermRef): ValDef =
2929+
val dcl = mbr.symbol
2930+
val target = dcl.info.asSeenFrom(cls.thisType, dcl.owner)
2931+
val constr = cls.primaryConstructor
2932+
val usingParamAccessors = cls.paramAccessors.filter(_.is(Given))
2933+
val paramScope = newScopeWith(usingParamAccessors*)
2934+
val searchCtx = ctx.outer.fresh.setScope(paramScope)
2935+
val rhs = implicitArgTree(target, cdef.span,
2936+
where = i"inferring the implementation of the deferred ${dcl.showLocated}"
2937+
)(using searchCtx)
2938+
2939+
val impl = dcl.copy(cls,
2940+
flags = dcl.flags &~ (HasDefault | Deferred) | Final | Override,
2941+
info = target,
2942+
coord = rhs.span).entered.asTerm
2943+
2944+
def anchorParams = new TreeMap:
2945+
override def transform(tree: Tree)(using Context): Tree = tree match
2946+
case id: Ident if usingParamAccessors.contains(id.symbol) =>
2947+
cpy.Select(id)(This(cls), id.name)
2948+
case _ =>
2949+
super.transform(tree)
2950+
ValDef(impl, anchorParams.transform(rhs))
2951+
end givenImpl
2952+
2953+
val givenImpls =
2954+
cls.thisType.implicitMembers
2955+
//.showing(i"impl def givens for $cls/$result")
2956+
.filter(_.symbol.isAllOf(DeferredGivenFlags, butNot = Param))
2957+
//.showing(i"impl def filtered givens for $cls/$result")
2958+
.filter(isGivenValue)
2959+
.map(givenImpl)
2960+
body ++ givenImpls
2961+
end implementDeferredGivens
2962+
29012963
ensureCorrectSuperClass()
29022964
completeAnnotations(cdef, cls)
29032965
val constr1 = typed(constr).asInstanceOf[DefDef]
@@ -2919,9 +2981,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
29192981
else {
29202982
val dummy = localDummy(cls, impl)
29212983
val body1 =
2922-
addParentRefinements(
2923-
addAccessorDefs(cls,
2924-
typedStats(impl.body, dummy)(using ctx.inClassContext(self1.symbol))._1))
2984+
implementDeferredGivens(
2985+
addParentRefinements(
2986+
addAccessorDefs(cls,
2987+
typedStats(impl.body, dummy)(using ctx.inClassContext(self1.symbol))._1)))
29252988

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

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ i13842.scala
103103
# Position change under captureChecking
104104
boxmap-paper.scala
105105

106-
# Function types print differnt after unpickling since test mispredicts Feature.preFundsEnabled
106+
# Function types print different after unpickling since test mispredicts Feature.preFundsEnabled
107107
caps-universal.scala
108108

109109
# GADT cast applied to singleton type difference
@@ -128,6 +128,8 @@ i20053b.scala
128128
parsercombinators-givens.scala
129129
parsercombinators-givens-2.scala
130130
parsercombinators-arrow.scala
131+
hylolib-deferred-given
132+
131133

132134

133135

library/src/scala/compiletime/package.scala

+13
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

+13
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

+30
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

+17
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

+19
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

+26
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

+30
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+
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)