Skip to content

Commit 8595aeb

Browse files
committed
Allow contecxt bounds with abstract Self types
If a context bound type `T` for type parameter `A` does not have type parameters, demand evidence of type `T { type Self = A }` instead.
1 parent f38d3c7 commit 8595aeb

39 files changed

+2198
-53
lines changed

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

+1
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,7 @@ object StdNames {
385385
val RootPackage: N = "RootPackage"
386386
val RootClass: N = "RootClass"
387387
val Select: N = "Select"
388+
val Self: N = "Self"
388389
val Shape: N = "Shape"
389390
val StringContext: N = "StringContext"
390391
val This: N = "This"

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

+5-1
Original file line numberDiff line numberDiff line change
@@ -2283,9 +2283,13 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
22832283
val tparam = untpd.Ident(tree.paramName).withSpan(tree.span)
22842284
if tycon.tpe.typeParams.nonEmpty then
22852285
typed(untpd.AppliedTypeTree(tyconSplice, tparam :: Nil))
2286+
else if Feature.enabled(modularity) && tycon.tpe.member(tpnme.Self).symbol.isAbstractType then
2287+
val tparamSplice = untpd.TypedSplice(typedExpr(tparam))
2288+
typed(untpd.RefinedTypeTree(tyconSplice, List(untpd.TypeDef(tpnme.Self, tparamSplice))))
22862289
else
22872290
errorTree(tree,
2288-
em"""Illegal context bound: ${tycon.tpe} does not take type parameters.""")
2291+
em"""Illegal context bound: ${tycon.tpe} does not take type parameters and
2292+
|does not have an abstract type member named `Self` either.""")
22892293

22902294
def typedSingletonTypeTree(tree: untpd.SingletonTypeTree)(using Context): SingletonTypeTree = {
22912295
val ref1 = typedExpr(tree.ref, SingletonTypeProto)

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

+4-3
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,11 @@ i15525.scala
121121
# alias types at different levels of dereferencing
122122
parsercombinators-givens.scala
123123
parsercombinators-givens-2.scala
124+
parsercombinators-ctx-bounds.scala
125+
parsercombinators-this.scala
124126
parsercombinators-arrow.scala
127+
parsercombinators-new-syntax.scala
125128
hylolib-deferred-given
126129
hylolib-cb
127-
128-
129-
130+
hylolib
130131

library/src/scala/runtime/stdLibPatches/Predef.scala

+12
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,16 @@ object Predef:
6161
inline def ne(inline y: AnyRef | Null): Boolean =
6262
!(x eq y)
6363

64+
/** A type supporting Self-based type classes.
65+
*
66+
* A is TC
67+
*
68+
* expands to
69+
*
70+
* TC { type Self = A }
71+
*
72+
* which is what is needed for a context bound `[A: TC]`.
73+
*/
74+
infix type is[A <: AnyKind, B <: {type Self <: AnyKind}] = B { type Self = A }
75+
6476
end Predef

tests/pos/FromString.scala

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//> using options -language:experimental.modularity -source future
2+
3+
trait FromString:
4+
type Self
5+
def fromString(s: String): Self
6+
7+
given Int is FromString = _.toInt
8+
9+
given Double is FromString = _.toDouble
10+
11+
def add[N: {FromString, Numeric as num}](a: String, b: String): N =
12+
N.plus(
13+
num.plus(N.fromString(a), N.fromString(b)),
14+
N.fromString(a)
15+
)

tests/pos/deferred-givens.scala

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
//> using options -language:experimental.modularity -source future
22
import compiletime.*
33
class Ord[Elem]
4-
54
given Ord[Double]
65

6+
trait A:
7+
type Elem : Ord
8+
def foo = summon[Ord[Elem]]
9+
10+
class AC extends A:
11+
type Elem = Double
12+
override given Ord[Elem] = ???
13+
14+
class AD extends A:
15+
type Elem = Double
16+
717
trait B:
818
type Elem
919
given Ord[Elem] = deferred

tests/pos/deferredSummon.scala

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
//> using options -language:experimental.modularity -source future
22
import compiletime.deferred
33

4-
trait Ord[Self]:
4+
trait Ord:
5+
type Self
56
def less(x: Self, y: Self): Boolean
67

78
trait A:
89
type Elem
9-
given Ord[Elem] = deferred
10-
def foo = summon[Ord[Elem]]
10+
given Elem is Ord = deferred
11+
def foo = summon[Elem is Ord]
1112

1213
trait B:
1314
type Elem: Ord
14-
def foo = summon[Ord[Elem]]
15+
def foo = summon[Elem is Ord]
1516

1617
object Inst:
17-
given Ord[Int]:
18+
given Int is Ord:
1819
def less(x: Int, y: Int) = x < y
1920

2021
object Test1:

tests/pos/dep-context-bounds.scala

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
//> using options -language:experimental.modularity -source future
2-
trait A[X]:
3-
type Self = X
2+
trait A:
3+
type Self
4+
5+
object Test1:
6+
def foo[X: A](x: X.Self) = ???
7+
8+
def bar[X: A](a: Int) = ???
9+
10+
def baz[X: A](a: Int)(using String) = ???
411

512
object Test2:
613
def foo[X: A as x](a: x.Self) = ???

tests/pos/hylolib-extract.scala

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//> using options -language:experimental.modularity -source future
2+
package hylotest
3+
4+
trait Value:
5+
type Self
6+
extension (self: Self) def eq(other: Self): Boolean
7+
8+
/** A collection of elements accessible by their position. */
9+
trait Collection:
10+
type Self
11+
12+
/** The type of the elements in the collection. */
13+
type Element: Value
14+
15+
class BitArray
16+
17+
given Boolean is Value:
18+
extension (self: Self) def eq(other: Self): Boolean =
19+
self == other
20+
21+
given BitArray is Collection:
22+
type Element = Boolean
23+
24+
extension [Self: Value](self: Self)
25+
def neq(other: Self): Boolean = !self.eq(other)
26+
27+
extension [Self: Collection](self: Self)
28+
def elementsEqual[T: Collection { type Element = Self.Element } ](other: T): Boolean =
29+
???

tests/pos/hylolib/AnyCollection.scala

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//> using options -language:experimental.modularity -source future
2+
package hylo
3+
4+
/** A type-erased collection.
5+
*
6+
* A `AnyCollection` forwards its operations to a wrapped value, hiding its implementation.
7+
*/
8+
final class AnyCollection[Element] private (
9+
val _start: () => AnyValue,
10+
val _end: () => AnyValue,
11+
val _after: (AnyValue) => AnyValue,
12+
val _at: (AnyValue) => Element
13+
)
14+
15+
object AnyCollection {
16+
17+
/** Creates an instance forwarding its operations to `base`. */
18+
def apply[Base: Collection](base: Base): AnyCollection[Base.Element] =
19+
20+
def start(): AnyValue =
21+
AnyValue(base.startPosition)
22+
23+
def end(): AnyValue =
24+
AnyValue(base.endPosition)
25+
26+
def after(p: AnyValue): AnyValue =
27+
AnyValue(base.positionAfter(p.unsafelyUnwrappedAs[Base.Position]))
28+
29+
def at(p: AnyValue): Base.Element =
30+
base.at(p.unsafelyUnwrappedAs[Base.Position])
31+
32+
new AnyCollection[Base.Element](
33+
_start = start,
34+
_end = end,
35+
_after = after,
36+
_at = at
37+
)
38+
39+
}
40+
41+
given [T: Value] => AnyCollection[T] is Collection:
42+
43+
type Element = T
44+
type Position = AnyValue
45+
46+
extension (self: AnyCollection[T])
47+
def startPosition = self._start()
48+
def endPosition = self._end()
49+
def positionAfter(p: Position) = self._after(p)
50+
def at(p: Position) = self._at(p)
51+

tests/pos/hylolib/AnyValue.scala

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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: Value](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 AnyValue is Value:
62+
63+
extension (self: AnyValue)
64+
def copy(): AnyValue = self.copy()
65+
def eq(other: AnyValue): Boolean = self `eq` other
66+
def hashInto(hasher: Hasher): Hasher = self.hashInto(hasher)
67+

tests/pos/hylolib/AnyValueTests.scala

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//> using options -language:experimental.modularity -source future
2+
import hylo.*
3+
import hylo.given
4+
5+
class AnyValueTests extends munit.FunSuite:
6+
7+
test("eq"):
8+
val a = AnyValue(1)
9+
assert(a `eq` a)
10+
assert(!(a `neq` a))
11+
12+
val b = AnyValue(2)
13+
assert(!(a `eq` b))
14+
assert(a `neq` b)
15+

0 commit comments

Comments
 (0)