Skip to content

Commit 15f33fa

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 5d6efa7 commit 15f33fa

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
@@ -1827,6 +1827,18 @@ class Namer { typer: Typer =>
18271827
case _ =>
18281828
WildcardType
18291829
}
1830+
1831+
// translate `given T = deferred` to an abstract given with HasDefault flag
1832+
if sym.is(Given) then
1833+
mdef.rhs match
1834+
case rhs: RefTree
1835+
if rhs.name == nme.deferred
1836+
&& typedAheadExpr(rhs).symbol == defn.Compiletime_deferred
1837+
&& sym.maybeOwner.is(Trait) =>
1838+
sym.resetFlag(Final)
1839+
sym.setFlag(Deferred | HasDefault)
1840+
case _ =>
1841+
18301842
val mbrTpe = paramFn(checkSimpleKinded(typedAheadType(mdef.tpt, tptProto)).tpe)
18311843
if (ctx.explicitNulls && mdef.mods.is(JavaDefined))
18321844
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
@@ -102,7 +102,7 @@ i13842.scala
102102
# Position change under captureChecking
103103
boxmap-paper.scala
104104

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

108108
# GADT cast applied to singleton type difference
@@ -124,6 +124,8 @@ i15525.scala
124124
parsercombinators-givens.scala
125125
parsercombinators-givens-2.scala
126126
parsercombinators-arrow.scala
127+
hylolib-deferred-given
128+
127129

128130

129131

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)