Skip to content

Commit 5b06d3b

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 c4cfa44 commit 5b06d3b

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
@@ -1816,6 +1816,18 @@ class Namer { typer: Typer =>
18161816
case _ =>
18171817
WildcardType
18181818
}
1819+
1820+
// translate `given T = deferred` to an abstract given with HasDefault flag
1821+
if sym.is(Given) then
1822+
mdef.rhs match
1823+
case rhs: RefTree
1824+
if rhs.name == nme.deferred
1825+
&& typedAheadExpr(rhs).symbol == defn.Compiletime_deferred
1826+
&& sym.maybeOwner.is(Trait) =>
1827+
sym.resetFlag(Final)
1828+
sym.setFlag(Deferred | HasDefault)
1829+
case _ =>
1830+
18191831
val mbrTpe = paramFn(checkSimpleKinded(typedAheadType(mdef.tpt, tptProto)).tpe)
18201832
if (ctx.explicitNulls && mdef.mods.is(JavaDefined))
18211833
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
@@ -2565,12 +2565,17 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
25652565
val ValDef(name, tpt, _) = vdef
25662566
checkNonRootName(vdef.name, vdef.nameSpan)
25672567
completeAnnotations(vdef, sym)
2568-
if (sym.isOneOf(GivenOrImplicit)) checkImplicitConversionDefOK(sym)
2568+
if sym.is(Implicit) then checkImplicitConversionDefOK(sym)
25692569
if sym.is(Module) then checkNoModuleClash(sym)
25702570
val tpt1 = checkSimpleKinded(typedType(tpt))
25712571
val rhs1 = vdef.rhs match {
2572-
case rhs @ Ident(nme.WILDCARD) => rhs withType tpt1.tpe
2573-
case rhs => typedExpr(rhs, tpt1.tpe.widenExpr)
2572+
case rhs @ Ident(nme.WILDCARD) =>
2573+
rhs.withType(tpt1.tpe)
2574+
case rhs: RefTree
2575+
if rhs.name == nme.deferred && sym.isAllOf(DeferredGivenFlags, butNot = Param) =>
2576+
EmptyTree
2577+
case rhs =>
2578+
typedExpr(rhs, tpt1.tpe.widenExpr)
25742579
}
25752580
val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym)
25762581
postProcessInfo(vdef1, sym)
@@ -2631,9 +2636,13 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
26312636

26322637
if sym.isInlineMethod then rhsCtx.addMode(Mode.InlineableBody)
26332638
if sym.is(ExtensionMethod) then rhsCtx.addMode(Mode.InExtensionMethod)
2634-
val rhs1 = PrepareInlineable.dropInlineIfError(sym,
2635-
if sym.isScala2Macro then typedScala2MacroBody(ddef.rhs)(using rhsCtx)
2636-
else typedExpr(ddef.rhs, tpt1.tpe.widenExpr)(using rhsCtx))
2639+
val rhs1 = ddef.rhs match
2640+
case Ident(nme.deferred) if sym.isAllOf(DeferredGivenFlags) =>
2641+
EmptyTree
2642+
case rhs =>
2643+
PrepareInlineable.dropInlineIfError(sym,
2644+
if sym.isScala2Macro then typedScala2MacroBody(ddef.rhs)(using rhsCtx)
2645+
else typedExpr(ddef.rhs, tpt1.tpe.widenExpr)(using rhsCtx))
26372646

26382647
if sym.isInlineMethod then
26392648
if StagingLevel.level > 0 then
@@ -2814,6 +2823,59 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
28142823
case None =>
28152824
body
28162825

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

28422905
checkNoDoubleDeclaration(cls)
28432906
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
*

0 commit comments

Comments
 (0)