Skip to content

Commit

Permalink
Allow context bounds in type declarations
Browse files Browse the repository at this point in the history
Expand them to deferred givens
  • Loading branch information
odersky committed May 6, 2024
1 parent 9bfe694 commit 0ad81e2
Show file tree
Hide file tree
Showing 20 changed files with 1,355 additions and 34 deletions.
16 changes: 12 additions & 4 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -237,12 +237,13 @@ object desugar {

def desugarRhs(rhs: Tree): Tree = rhs match
case ContextBounds(tbounds, cxbounds) =>
val isMember = flags.isAllOf(DeferredGivenFlags)
for bound <- cxbounds do
val evidenceName = bound match
case ContextBoundTypeTree(_, _, ownName) if !ownName.isEmpty =>
ownName
case _ if Config.nameSingleContextBounds && cxbounds.tail.isEmpty
&& Feature.enabled(Feature.modularity) =>
case _ if Config.nameSingleContextBounds && !isMember
&& cxbounds.tail.isEmpty && Feature.enabled(Feature.modularity) =>
tdef.name.toTermName
case _ =>
freshName(bound)
Expand All @@ -262,7 +263,6 @@ object desugar {
private def elimContextBounds(meth: DefDef, isPrimaryConstructor: Boolean)(using Context): DefDef =
val DefDef(_, paramss, tpt, rhs) = meth
val evidenceParamBuf = ListBuffer[ValDef]()

var seenContextBounds: Int = 0
def freshName(unused: Tree) =
seenContextBounds += 1 // Start at 1 like FreshNameCreator.
Expand Down Expand Up @@ -492,6 +492,14 @@ object desugar {
Apply(fn, params.map(refOfDef))
}

def typeDef(tdef: TypeDef)(using Context): Tree =
val evidenceBuf = new ListBuffer[ValDef]
val result = desugarContextBounds(
tdef, evidenceBuf,
(tdef.mods.flags.toTermFlags & AccessFlags) | Lazy | DeferredGivenFlags,
inventGivenOrExtensionName, Nil)
if evidenceBuf.isEmpty then result else Thicket(result :: evidenceBuf.toList)

/** The expansion of a class definition. See inline comments for what is involved */
def classDef(cdef: TypeDef)(using Context): Tree = {
val impl @ Template(constr0, _, self, _) = cdef.rhs: @unchecked
Expand Down Expand Up @@ -1425,7 +1433,7 @@ object desugar {
case tree: TypeDef =>
if (tree.isClassDef) classDef(tree)
else if (ctx.mode.isQuotedPattern) quotedPatternTypeDef(tree)
else tree
else typeDef(tree)
case tree: DefDef =>
if (tree.name.isConstructorName) tree // was already handled by enclosing classDef
else defDef(tree)
Expand Down
53 changes: 28 additions & 25 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3908,51 +3908,54 @@ object Parsers {
argumentExprss(mkApply(Ident(nme.CONSTRUCTOR), argumentExprs()))
}

/** TypeDef ::= id [TypeParamClause] {FunParamClause} TypeBounds [‘=’ Type]
/** TypeDef ::= id [TypeParamClause] {FunParamClause} TypeAndCtxBounds [‘=’ Type]
*/
def typeDefOrDcl(start: Offset, mods: Modifiers): Tree = {
newLinesOpt()
atSpan(start, nameStart) {
val nameIdent = typeIdent()
val tname = nameIdent.name.asTypeName
val tparams = typeParamClauseOpt(ParamOwner.Type)
val vparamss = funParamClauses()

def makeTypeDef(rhs: Tree): Tree = {
val rhs1 = lambdaAbstractAll(tparams :: vparamss, rhs)
val tdef = TypeDef(nameIdent.name.toTypeName, rhs1)
if (nameIdent.isBackquoted)
tdef.pushAttachment(Backquoted, ())
finalizeDef(tdef, mods, start)
}

in.token match {
case EQUALS =>
in.nextToken()
makeTypeDef(toplevelTyp())
case SUBTYPE | SUPERTYPE =>
val bounds = typeBounds()
if (in.token == EQUALS) {
val eqOffset = in.skipToken()
var rhs = toplevelTyp()
rhs match {
case mtt: MatchTypeTree =>
bounds match {
case TypeBoundsTree(EmptyTree, upper, _) =>
rhs = MatchTypeTree(upper, mtt.selector, mtt.cases)
case _ =>
syntaxError(em"cannot combine lower bound and match type alias", eqOffset)
}
case _ =>
if mods.is(Opaque) then
rhs = TypeBoundsTree(bounds.lo, bounds.hi, rhs)
else
syntaxError(em"cannot combine bound and alias", eqOffset)
}
makeTypeDef(rhs)
}
else makeTypeDef(bounds)
typeAndCtxBounds(tname) match
case bounds: TypeBoundsTree if in.token == EQUALS =>
val eqOffset = in.skipToken()
var rhs = toplevelTyp()
rhs match {
case mtt: MatchTypeTree =>
bounds match {
case TypeBoundsTree(EmptyTree, upper, _) =>
rhs = MatchTypeTree(upper, mtt.selector, mtt.cases)
case _ =>
syntaxError(em"cannot combine lower bound and match type alias", eqOffset)
}
case _ =>
if mods.is(Opaque) then
rhs = TypeBoundsTree(bounds.lo, bounds.hi, rhs)
else
syntaxError(em"cannot combine bound and alias", eqOffset)
}
makeTypeDef(rhs)
case bounds => makeTypeDef(bounds)
case SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | OUTDENT | EOF =>
makeTypeDef(typeBounds())
case _ if (staged & StageKind.QuotedPattern) != 0 =>
makeTypeDef(typeBounds())
makeTypeDef(typeAndCtxBounds(tname))
case _ if (staged & StageKind.QuotedPattern) != 0
|| in.featureEnabled(Feature.modularity) && in.isColon =>
makeTypeDef(typeAndCtxBounds(tname))
case _ =>
syntaxErrorOrIncomplete(ExpectedTypeBoundOrEquals(in.token))
return EmptyTree // return to avoid setting the span to EmptyTree
Expand Down
1 change: 1 addition & 0 deletions compiler/test/dotc/pos-test-pickling.blacklist
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ parsercombinators-givens.scala
parsercombinators-givens-2.scala
parsercombinators-arrow.scala
hylolib-deferred-given
hylolib-cb



Expand Down
2 changes: 1 addition & 1 deletion docs/_docs/internals/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,7 @@ PatDef ::= ids [‘:’ Type] [‘=’ Expr]
DefDef ::= DefSig [‘:’ Type] [‘=’ Expr] DefDef(_, name, paramss, tpe, expr)
| ‘this’ TypelessClauses [DefImplicitClause] ‘=’ ConstrExpr DefDef(_, <init>, vparamss, EmptyTree, expr | Block)
DefSig ::= id [DefParamClauses] [DefImplicitClause]
TypeDef ::= id [TypeParamClause] {FunParamClause} TypeBounds TypeDefTree(_, name, tparams, bound
TypeDef ::= id [TypeParamClause] {FunParamClause} TypeAndCtxBounds TypeDefTree(_, name, tparams, bound
[‘=’ Type]
TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef
Expand Down
21 changes: 19 additions & 2 deletions tests/pos/deferredSummon.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ trait A:
given Ord[Elem] = deferred
def foo = summon[Ord[Elem]]

trait B:
type Elem: Ord
def foo = summon[Ord[Elem]]

object Inst:
given Ord[Int]:
def less(x: Int, y: Int) = x < y

object Test:
object Test1:
import Inst.given
class C extends A:
type Elem = Int
Expand All @@ -22,9 +26,22 @@ object Test:
given A:
type Elem = Int

class D[T: Ord] extends A:
class D1[T: Ord] extends B:
type Elem = T

object Test2:
import Inst.given
class C extends B:
type Elem = Int
object E extends B:
type Elem = Int
given B:
type Elem = Int

class D2[T: Ord] extends B:
type Elem = T





10 changes: 10 additions & 0 deletions tests/pos/dep-context-bounds.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//> using options -language:experimental.modularity -source future
trait A[X]:
type Self = X

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

def bar[X: A as x](a: Int) = ???

def baz[X: A as x](a: Int)(using String) = ???
18 changes: 18 additions & 0 deletions tests/pos/hylolib-cb-extract.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//> using options -language:experimental.modularity -source future
package hylotest
import compiletime.deferred

trait Value[Self]

/** A collection of elements accessible by their position. */
trait Collection[Self]:

/** The type of the elements in the collection. */
type Element: Value

class BitArray

given Value[Boolean] {}

given Collection[BitArray] with
type Element = Boolean
66 changes: 66 additions & 0 deletions tests/pos/hylolib-cb/AnyCollection.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package hylo

/** A type-erased collection.
*
* A `AnyCollection` forwards its operations to a wrapped value, hiding its implementation.
*/
final class AnyCollection[Element] private (
val _start: () => AnyValue,
val _end: () => AnyValue,
val _after: (AnyValue) => AnyValue,
val _at: (AnyValue) => Element
)

object AnyCollection {

/** Creates an instance forwarding its operations to `base`. */
def apply[Base](using b: Collection[Base])(base: Base): AnyCollection[b.Element] =
// NOTE: This evidence is redefined so the compiler won't report ambiguity between `intIsValue`
// and `anyValueIsValue` when the method is called on a collection of `Int`s. None of these
// choices is even correct! Note also that the ambiguity is suppressed if the constructor of
// `AnyValue` is declared with a context bound rather than an implicit parameter.
given Value[b.Position] = b.positionIsValue

def start(): AnyValue =
AnyValue(base.startPosition)

def end(): AnyValue =
AnyValue(base.endPosition)

def after(p: AnyValue): AnyValue =
AnyValue(base.positionAfter(p.unsafelyUnwrappedAs[b.Position]))

def at(p: AnyValue): b.Element =
base.at(p.unsafelyUnwrappedAs[b.Position])

new AnyCollection[b.Element](
_start = start,
_end = end,
_after = after,
_at = at
)

}

given anyCollectionIsCollection[T](using tIsValue: Value[T]): Collection[AnyCollection[T]] with {

type Element = T
type Position = AnyValue

extension (self: AnyCollection[T]) {

def startPosition =
self._start()

def endPosition =
self._end()

def positionAfter(p: Position) =
self._after(p)

def at(p: Position) =
self._at(p)

}

}
76 changes: 76 additions & 0 deletions tests/pos/hylolib-cb/AnyValue.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package hylo

/** A wrapper around an object providing a reference API. */
private final class Ref[T](val value: T) {

override def toString: String =
s"Ref($value)"

}

/** A type-erased value.
*
* An `AnyValue` forwards its operations to a wrapped value, hiding its implementation.
*/
final class AnyValue private (
private val wrapped: AnyRef,
private val _copy: (AnyRef) => AnyValue,
private val _eq: (AnyRef, AnyRef) => Boolean,
private val _hashInto: (AnyRef, Hasher) => Hasher
) {

/** Returns a copy of `this`. */
def copy(): AnyValue =
_copy(this.wrapped)

/** Returns `true` iff `this` and `other` have an equivalent value. */
def eq(other: AnyValue): Boolean =
_eq(this.wrapped, other.wrapped)

/** Hashes the salient parts of `this` into `hasher`. */
def hashInto(hasher: Hasher): Hasher =
_hashInto(this.wrapped, hasher)

/** Returns the value wrapped in `this` as an instance of `T`. */
def unsafelyUnwrappedAs[T]: T =
wrapped.asInstanceOf[Ref[T]].value

/** Returns a textual description of `this`. */
override def toString: String =
wrapped.toString

}

object AnyValue {

/** Creates an instance wrapping `wrapped`. */
def apply[T](using Value[T])(wrapped: T): AnyValue =
def copy(a: AnyRef): AnyValue =
AnyValue(a.asInstanceOf[Ref[T]].value.copy())

def eq(a: AnyRef, b: AnyRef): Boolean =
a.asInstanceOf[Ref[T]].value `eq` b.asInstanceOf[Ref[T]].value

def hashInto(a: AnyRef, hasher: Hasher): Hasher =
a.asInstanceOf[Ref[T]].value.hashInto(hasher)

new AnyValue(Ref(wrapped), copy, eq, hashInto)

}

given anyValueIsValue: Value[AnyValue] with {

extension (self: AnyValue) {

def copy(): AnyValue =
self.copy()

def eq(other: AnyValue): Boolean =
self `eq` other

def hashInto(hasher: Hasher): Hasher =
self.hashInto(hasher)

}

}
Loading

0 comments on commit 0ad81e2

Please sign in to comment.