Skip to content

Commit 73deadc

Browse files
committed
Implement deferredSummon
A definition like `given T = deferredSummon` 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 cf59585 commit 73deadc

18 files changed

+1391
-9
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@ object StdNames {
453453
val create: N = "create"
454454
val currentMirror: N = "currentMirror"
455455
val curried: N = "curried"
456+
val deferredSummon: N = "deferredSummon"
456457
val definitions: N = "definitions"
457458
val delayedInit: N = "delayedInit"
458459
val delayedInitArg: N = "delayedInit$body"

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -397,13 +397,12 @@ object Symbols extends SymUtils {
397397
flags: FlagSet = this.flags,
398398
info: Type = this.info,
399399
privateWithin: Symbol = this.privateWithin,
400-
coord: Coord = NoCoord, // Can be `= owner.coord` once we bootstrap
401-
compUnitInfo: CompilationUnitInfo | Null = null // Can be `= owner.associatedFile` once we bootstrap
400+
coord: Coord = NoCoord, // Can be `= owner.coord` once we have new default args
401+
compUnitInfo: CompilationUnitInfo | Null = null // Can be `= owner.compilationUnitInfo` once we have new default args
402402
): Symbol = {
403403
val coord1 = if (coord == NoCoord) owner.coord else coord
404404
val compilationUnitInfo1 = if (compilationUnitInfo == null) owner.compilationUnitInfo else compilationUnitInfo
405405

406-
407406
if isClass then
408407
newClassSymbol(owner, name.asTypeName, flags, _ => info, privateWithin, coord1, compilationUnitInfo1)
409408
else

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1805,6 +1805,18 @@ class Namer { typer: Typer =>
18051805
case _ =>
18061806
WildcardType
18071807
}
1808+
1809+
// translate `given T = deferredSummon` to an abstract given with HasDefault flag
1810+
if sym.is(Given, butNot = Method) then
1811+
mdef.rhs match
1812+
case Ident(nme.deferredSummon) if Feature.enabled(modularity) =>
1813+
if !sym.maybeOwner.is(Trait) then
1814+
report.error(em"`deferredSummon` can only be used for givens in traits", mdef.rhs.srcPos)
1815+
else
1816+
sym.resetFlag(Final | Lazy)
1817+
sym.setFlag(Deferred | HasDefault)
1818+
case _ =>
1819+
18081820
val mbrTpe = paramFn(checkSimpleKinded(typedAheadType(mdef.tpt, tptProto)).tpe)
18091821
if (ctx.explicitNulls && mdef.mods.is(JavaDefined))
18101822
JavaNullInterop.nullifyMember(sym, mbrTpe, mdef.mods.isAllOf(JavaEnumValue))

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

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2572,12 +2572,16 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
25722572
val ValDef(name, tpt, _) = vdef
25732573
checkNonRootName(vdef.name, vdef.nameSpan)
25742574
completeAnnotations(vdef, sym)
2575-
if (sym.isOneOf(GivenOrImplicit)) checkImplicitConversionDefOK(sym)
2575+
if sym.is(Implicit) then checkImplicitConversionDefOK(sym)
25762576
if sym.is(Module) then checkNoModuleClash(sym)
25772577
val tpt1 = checkSimpleKinded(typedType(tpt))
25782578
val rhs1 = vdef.rhs match {
2579-
case rhs @ Ident(nme.WILDCARD) => rhs withType tpt1.tpe
2580-
case rhs => typedExpr(rhs, tpt1.tpe.widenExpr)
2579+
case rhs @ Ident(nme.WILDCARD) =>
2580+
rhs.withType(tpt1.tpe)
2581+
case Ident(nme.deferredSummon) if sym.isAllOf(Given | Deferred | HasDefault, butNot = Param) =>
2582+
EmptyTree
2583+
case rhs =>
2584+
typedExpr(rhs, tpt1.tpe.widenExpr)
25812585
}
25822586
val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym)
25832587
postProcessInfo(vdef1, sym)
@@ -2821,6 +2825,34 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
28212825
case None =>
28222826
body
28232827

2828+
/** Implement givens that were declared with a `deferredSummon` rhs.
2829+
* The a given value matching the declared type is searched in a
2830+
* context directly enclosing the current class, in which all given
2831+
* parameters of the current class are also defined.
2832+
*/
2833+
def implementDeferredGivens(body: List[Tree]): List[Tree] =
2834+
if cls.is(Trait) then body
2835+
else
2836+
def givenImpl(mbr: TermRef): ValDef =
2837+
val dcl = mbr.symbol
2838+
val target = dcl.info.asSeenFrom(cls.thisType, dcl.owner)
2839+
val constr = cls.primaryConstructor
2840+
val paramScope = newScopeWith(cls.paramAccessors.filter(_.is(Given))*)
2841+
val searchCtx = ctx.outer.fresh.setScope(paramScope)
2842+
val rhs = implicitArgTree(target, cdef.span)(using searchCtx)
2843+
val impl = dcl.copy(cls,
2844+
flags = dcl.flags &~ (HasDefault | Deferred) | Final,
2845+
info = target,
2846+
coord = rhs.span).entered.asTerm
2847+
ValDef(impl, rhs)
2848+
2849+
val givenImpls =
2850+
cls.thisType.implicitMembers
2851+
.filter(_.symbol.isAllOf(Given | Deferred | HasDefault, butNot = Param))
2852+
.map(givenImpl)
2853+
body ++ givenImpls
2854+
end implementDeferredGivens
2855+
28242856
ensureCorrectSuperClass()
28252857
completeAnnotations(cdef, cls)
28262858
val constr1 = typed(constr).asInstanceOf[DefDef]
@@ -2842,9 +2874,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
28422874
else {
28432875
val dummy = localDummy(cls, impl)
28442876
val body1 =
2845-
addParentRefinements(
2846-
addAccessorDefs(cls,
2847-
typedStats(impl.body, dummy)(using ctx.inClassContext(self1.symbol))._1))
2877+
implementDeferredGivens(
2878+
addParentRefinements(
2879+
addAccessorDefs(cls,
2880+
typedStats(impl.body, dummy)(using ctx.inClassContext(self1.symbol))._1)))
28482881

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

tests/neg/deferredSummon.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//> using options -language:experimental.modularity
2+
3+
object Test:
4+
given Int = deferredSummon // error
5+
6+
abstract class C:
7+
given Int = deferredSummon // error
8+
9+
trait A:
10+
locally:
11+
given Int = deferredSummon // error
12+

tests/pos/deferredSummon.scala

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//> using options -language:experimental.modularity -source future
2+
trait Ord:
3+
type Self
4+
def less(x: Self, y: Self): Boolean
5+
6+
trait A:
7+
type Elem
8+
given Elem is Ord = deferredSummon
9+
def foo = summon[Elem is Ord]
10+
11+
object Inst:
12+
given Int is Ord:
13+
def less(x: Int, y: Int) = x < y
14+
15+
object Test:
16+
import Inst.given
17+
class C extends A:
18+
type Elem = Int
19+
object E extends A:
20+
type Elem = Int
21+
given A:
22+
type Elem = Int
23+
24+
class D[T: Ord] extends A:
25+
type Elem = T
26+
27+
28+
29+

tests/pos/hylolib-extract.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//> using options -language:experimental.modularity -source future
2+
package hylotest
3+
4+
trait Value[Self]
5+
6+
/** A collection of elements accessible by their position. */
7+
trait Collection[Self]:
8+
9+
/** The type of the elements in the collection. */
10+
type Element
11+
given elementIsValue: Value[Element] = deferredSummon
12+
13+
class BitArray
14+
15+
given Value[Boolean] {}
16+
17+
given Collection[BitArray] with
18+
type Element = Boolean

tests/pos/hylolib/AnyCollection.scala

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package hylo
2+
3+
/** A type-erased collection.
4+
*
5+
* A `AnyCollection` forwards its operations to a wrapped value, hiding its implementation.
6+
*/
7+
final class AnyCollection[Element] private (
8+
val _start: () => AnyValue,
9+
val _end: () => AnyValue,
10+
val _after: (AnyValue) => AnyValue,
11+
val _at: (AnyValue) => Element
12+
)
13+
14+
object AnyCollection {
15+
16+
/** Creates an instance forwarding its operations to `base`. */
17+
def apply[Base](using b: Collection[Base])(base: Base): AnyCollection[b.Element] =
18+
// NOTE: This evidence is redefined so the compiler won't report ambiguity between `intIsValue`
19+
// and `anyValueIsValue` when the method is called on a collection of `Int`s. None of these
20+
// choices is even correct! Note also that the ambiguity is suppressed if the constructor of
21+
// `AnyValue` is declared with a context bound rather than an implicit parameter.
22+
given Value[b.Position] = b.positionIsValue
23+
24+
def start(): AnyValue =
25+
AnyValue(base.startPosition)
26+
27+
def end(): AnyValue =
28+
AnyValue(base.endPosition)
29+
30+
def after(p: AnyValue): AnyValue =
31+
AnyValue(base.positionAfter(p.unsafelyUnwrappedAs[b.Position]))
32+
33+
def at(p: AnyValue): b.Element =
34+
base.at(p.unsafelyUnwrappedAs[b.Position])
35+
36+
new AnyCollection[b.Element](
37+
_start = start,
38+
_end = end,
39+
_after = after,
40+
_at = at
41+
)
42+
43+
}
44+
45+
given anyCollectionIsCollection[T](using tIsValue: Value[T]): Collection[AnyCollection[T]] with {
46+
47+
type Element = T
48+
//given elementIsValue: Value[Element] = tIsValue
49+
50+
type Position = AnyValue
51+
given positionIsValue: Value[Position] = anyValueIsValue
52+
53+
extension (self: AnyCollection[T]) {
54+
55+
def startPosition =
56+
self._start()
57+
58+
def endPosition =
59+
self._end()
60+
61+
def positionAfter(p: Position) =
62+
self._after(p)
63+
64+
def at(p: Position) =
65+
self._at(p)
66+
67+
}
68+
69+
}

tests/pos/hylolib/AnyValue.scala

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package hylo
2+
3+
/** A wrapper around an object providing a reference API. */
4+
private final class Ref[T](val value: T) {
5+
6+
override def toString: String =
7+
s"Ref($value)"
8+
9+
}
10+
11+
/** A type-erased value.
12+
*
13+
* An `AnyValue` forwards its operations to a wrapped value, hiding its implementation.
14+
*/
15+
final class AnyValue private (
16+
private val wrapped: AnyRef,
17+
private val _copy: (AnyRef) => AnyValue,
18+
private val _eq: (AnyRef, AnyRef) => Boolean,
19+
private val _hashInto: (AnyRef, Hasher) => Hasher
20+
) {
21+
22+
/** Returns a copy of `this`. */
23+
def copy(): AnyValue =
24+
_copy(this.wrapped)
25+
26+
/** Returns `true` iff `this` and `other` have an equivalent value. */
27+
def eq(other: AnyValue): Boolean =
28+
_eq(this.wrapped, other.wrapped)
29+
30+
/** Hashes the salient parts of `this` into `hasher`. */
31+
def hashInto(hasher: Hasher): Hasher =
32+
_hashInto(this.wrapped, hasher)
33+
34+
/** Returns the value wrapped in `this` as an instance of `T`. */
35+
def unsafelyUnwrappedAs[T]: T =
36+
wrapped.asInstanceOf[Ref[T]].value
37+
38+
/** Returns a textual description of `this`. */
39+
override def toString: String =
40+
wrapped.toString
41+
42+
}
43+
44+
object AnyValue {
45+
46+
/** Creates an instance wrapping `wrapped`. */
47+
def apply[T](using Value[T])(wrapped: T): AnyValue =
48+
def copy(a: AnyRef): AnyValue =
49+
AnyValue(a.asInstanceOf[Ref[T]].value.copy())
50+
51+
def eq(a: AnyRef, b: AnyRef): Boolean =
52+
a.asInstanceOf[Ref[T]].value `eq` b.asInstanceOf[Ref[T]].value
53+
54+
def hashInto(a: AnyRef, hasher: Hasher): Hasher =
55+
a.asInstanceOf[Ref[T]].value.hashInto(hasher)
56+
57+
new AnyValue(Ref(wrapped), copy, eq, hashInto)
58+
59+
}
60+
61+
given anyValueIsValue: Value[AnyValue] with {
62+
63+
extension (self: AnyValue) {
64+
65+
def copy(): AnyValue =
66+
self.copy()
67+
68+
def eq(other: AnyValue): Boolean =
69+
self `eq` other
70+
71+
def hashInto(hasher: Hasher): Hasher =
72+
self.hashInto(hasher)
73+
74+
}
75+
76+
}

0 commit comments

Comments
 (0)