Skip to content

Commit 424db94

Browse files
committed
Implement bounds for opaque types
1 parent dffb0b0 commit 424db94

File tree

12 files changed

+108
-23
lines changed

12 files changed

+108
-23
lines changed

compiler/src/dotty/tools/dotc/ast/Trees.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,8 @@ object Trees {
818818
extends ProxyTree[T] {
819819
type ThisTree[-T >: Untyped] = Annotated[T]
820820
def forwardTo: Tree[T] = arg
821+
override def disableOverlapChecks = true
822+
// disable overlaps checks since the WithBounds annotation swaps type and annotation.
821823
}
822824

823825
trait WithoutTypeOrPos[-T >: Untyped] extends Tree[T] {

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,12 +171,22 @@ object Annotations {
171171

172172
def unapply(ann: Annotation)(implicit ctx: Context): Option[Symbol] =
173173
if (ann.symbol == defn.ChildAnnot) {
174-
val AppliedType(tycon, (arg: NamedType) :: Nil) = ann.tree.tpe
174+
val AppliedType(_, (arg: NamedType) :: Nil) = ann.tree.tpe
175175
Some(arg.symbol)
176176
}
177177
else None
178178
}
179179

180+
/** Extractor for WithBounds[T] annotations */
181+
object WithBounds {
182+
def unapply(ann: Annotation)(implicit ctx: Context): Option[TypeBounds] =
183+
if (ann.symbol == defn.WithBoundsAnnot) {
184+
val AppliedType(_, lo :: hi :: Nil) = ann.tree.tpe
185+
Some(TypeBounds(lo, hi))
186+
}
187+
else None
188+
}
189+
180190
def makeSourceFile(path: String)(implicit ctx: Context): Annotation =
181191
apply(defn.SourceFileAnnot, Literal(Constant(path)))
182192
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,8 @@ class Definitions {
877877
def BodyAnnot(implicit ctx: Context): ClassSymbol = BodyAnnotType.symbol.asClass
878878
@threadUnsafe lazy val ChildAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.internal.Child")
879879
def ChildAnnot(implicit ctx: Context): ClassSymbol = ChildAnnotType.symbol.asClass
880+
@threadUnsafe lazy val WithBoundsAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.internal.WithBounds")
881+
def WithBoundsAnnot(implicit ctx: Context): ClassSymbol = WithBoundsAnnotType.symbol.asClass
880882
@threadUnsafe lazy val CovariantBetweenAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.internal.CovariantBetween")
881883
def CovariantBetweenAnnot(implicit ctx: Context): ClassSymbol = CovariantBetweenAnnotType.symbol.asClass
882884
@threadUnsafe lazy val ContravariantBetweenAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.internal.ContravariantBetween")

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,9 @@ object SymDenotations {
372372
case _ => unforcedDecls.openForMutations
373373
}
374374

375-
/** If this is a synthetic opaque type alias, mark it as Deferred with empty bounds.
375+
/** If this is a synthetic opaque type alias, mark it as Deferred with bounds
376+
* as given by the right hand side's `WithBounds` annotation, if one is present,
377+
* or with empty bounds of the right kind, otherwise.
376378
* At the same time, integrate the original alias as a refinement of the
377379
* self type of the enclosing class.
378380
*/
@@ -384,16 +386,22 @@ object SymDenotations {
384386
if (isOpaqueAlias) {
385387
info match {
386388
case TypeAlias(alias) =>
389+
val (refiningAlias, bounds) = alias match {
390+
case AnnotatedType(alias1, Annotation.WithBounds(bounds)) =>
391+
(alias1, bounds)
392+
case _ =>
393+
(alias, TypeBounds(defn.NothingType, abstractRHS(alias)))
394+
}
387395
def refineSelfType(selfType: Type) =
388-
RefinedType(selfType, name, TypeAlias(alias))
396+
RefinedType(selfType, name, TypeAlias(refiningAlias))
389397
val enclClassInfo = owner.asClass.classInfo
390398
enclClassInfo.selfInfo match {
391399
case self: Type =>
392400
owner.info = enclClassInfo.derivedClassInfo(selfInfo = refineSelfType(self))
393401
case self: Symbol =>
394402
self.info = refineSelfType(self.info)
395403
}
396-
info = TypeBounds(defn.NothingType, abstractRHS(alias))
404+
info = bounds
397405
setFlag(Deferred)
398406
typeRef.recomputeDenot()
399407
case _ =>

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2313,7 +2313,8 @@ object Types {
23132313
override def translucentSuperType(implicit ctx: Context) = info match {
23142314
case TypeAlias(aliased) => aliased
23152315
case TypeBounds(_, hi) =>
2316-
if (symbol.isOpaqueAlias) symbol.opaqueAlias.asSeenFrom(prefix, symbol.owner)
2316+
if (symbol.isOpaqueAlias)
2317+
symbol.opaqueAlias.asSeenFrom(prefix, symbol.owner).orElse(hi) // orElse can happen for malformed input
23172318
else hi
23182319
case _ => underlying
23192320
}

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import ast.Trees._
1919
import StdNames._
2020
import util.Spans._
2121
import Constants._
22+
import Symbols.defn
2223
import ScriptParsers._
2324
import Decorators._
2425
import scala.tasty.util.Chars.isIdentifierStart
@@ -955,7 +956,7 @@ object Parsers {
955956

956957
in.token match {
957958
case ARROW => functionRest(t :: Nil)
958-
case MATCH => matchType(EmptyTree, t)
959+
case MATCH => matchType(t)
959960
case FORSOME => syntaxError(ExistentialTypesNoLongerSupported()); t
960961
case _ =>
961962
if (imods.is(ImplicitOrGiven) && !t.isInstanceOf[FunctionWithMods])
@@ -1481,9 +1482,9 @@ object Parsers {
14811482

14821483
/** `match' { TypeCaseClauses }
14831484
*/
1484-
def matchType(bound: Tree, t: Tree): MatchTypeTree =
1485-
atSpan((if (bound.isEmpty) t else bound).span.start, accept(MATCH)) {
1486-
inBraces(MatchTypeTree(bound, t, caseClauses(typeCaseClause)))
1485+
def matchType(t: Tree): MatchTypeTree =
1486+
atSpan(t.span.start, accept(MATCH)) {
1487+
inBraces(MatchTypeTree(EmptyTree, t, caseClauses(typeCaseClause)))
14871488
}
14881489

14891490
/** FunParams ::= Bindings
@@ -2075,6 +2076,7 @@ object Parsers {
20752076
* Modifier ::= LocalModifier
20762077
* | AccessModifier
20772078
* | override
2079+
* | opaque
20782080
* LocalModifier ::= abstract | final | sealed | implicit | lazy | erased | inline
20792081
*/
20802082
def modifiers(allowed: BitSet = modifierTokens, start: Modifiers = Modifiers()): Modifiers = {
@@ -2617,8 +2619,7 @@ object Parsers {
26172619
Block(stats, Literal(Constant(())))
26182620
}
26192621

2620-
/** TypeDcl ::= id [TypeParamClause] (TypeBounds | ‘=’ Type)
2621-
* | id [TypeParamClause] <: Type = MatchType
2622+
/** TypeDcl ::= id [TypeParamClause] TypeBounds [‘=’ Type]
26222623
*/
26232624
def typeDefOrDcl(start: Offset, mods: Modifiers): Tree = {
26242625
newLinesOpt()
@@ -2631,15 +2632,33 @@ object Parsers {
26312632
case EQUALS =>
26322633
in.nextToken()
26332634
makeTypeDef(toplevelTyp())
2634-
case SUBTYPE =>
2635-
in.nextToken()
2636-
val bound = toplevelTyp()
2635+
case SUBTYPE | SUPERTYPE =>
2636+
val bounds = typeBounds()
26372637
if (in.token == EQUALS) {
2638-
in.nextToken()
2639-
makeTypeDef(matchType(bound, infixType()))
2638+
val eqOffset = in.skipToken()
2639+
var rhs = toplevelTyp()
2640+
rhs match {
2641+
case mtt: MatchTypeTree =>
2642+
bounds match {
2643+
case TypeBoundsTree(EmptyTree, upper) =>
2644+
rhs = MatchTypeTree(upper, mtt.selector, mtt.cases)
2645+
case _ =>
2646+
syntaxError(i"cannot combine lower bound and match type alias", eqOffset)
2647+
}
2648+
case _ =>
2649+
if (mods.is(Opaque)) {
2650+
val annotType = AppliedTypeTree(
2651+
TypeTree(defn.WithBoundsAnnotType),
2652+
bounds.lo.orElse(TypeTree(defn.NothingType)) ::
2653+
bounds.hi.orElse(TypeTree(defn.AnyType)) :: Nil)
2654+
rhs = Annotated(rhs, ensureApplied(wrapNew(annotType)))
2655+
}
2656+
else syntaxError(i"cannot combine bound and alias", eqOffset)
2657+
}
2658+
makeTypeDef(rhs)
26402659
}
2641-
else makeTypeDef(TypeBoundsTree(EmptyTree, bound))
2642-
case SUPERTYPE | SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | EOF =>
2660+
else makeTypeDef(bounds)
2661+
case SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | EOF =>
26432662
makeTypeDef(typeBounds())
26442663
case _ =>
26452664
syntaxErrorOrIncomplete(ExpectedTypeBoundOrEquals(in.token))

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
450450
toTextLocal(tpt) ~ "[" ~ Text(args map argText, ", ") ~ "]"
451451
case LambdaTypeTree(tparams, body) =>
452452
changePrec(GlobalPrec) {
453-
tparamsText(tparams) ~ " -> " ~ toText(body)
453+
tparamsText(tparams) ~ " =>> " ~ toText(body)
454454
}
455455
case MatchTypeTree(bound, sel, cases) =>
456456
changePrec(GlobalPrec) {

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1810,8 +1810,15 @@ class Typer extends Namer
18101810
def typedAnnotated(tree: untpd.Annotated, pt: Type)(implicit ctx: Context): Tree = track("typedAnnotated") {
18111811
val annot1 = typedExpr(tree.annot, defn.AnnotationType)
18121812
val arg1 = typed(tree.arg, pt)
1813-
if (ctx.mode is Mode.Type)
1814-
assignType(cpy.Annotated(tree)(arg1, annot1), arg1, annot1)
1813+
if (ctx.mode is Mode.Type) {
1814+
val result = assignType(cpy.Annotated(tree)(arg1, annot1), arg1, annot1)
1815+
result.tpe match {
1816+
case AnnotatedType(rhs, Annotation.WithBounds(bounds)) =>
1817+
if (!bounds.contains(rhs)) ctx.error(em"type $rhs outside bounds $bounds", tree.sourcePos)
1818+
case _ =>
1819+
}
1820+
result
1821+
}
18151822
else {
18161823
val arg2 = arg1 match {
18171824
case Typed(arg2, tpt: TypeTree) =>

docs/docs/internals/syntax.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -360,8 +360,7 @@ VarDcl ::= ids ‘:’ Type
360360
DefDcl ::= DefSig [‘:’ Type] DefDef(_, name, tparams, vparamss, tpe, EmptyTree)
361361
DefSig ::= ‘(’ DefParam ‘)’ [nl] id
362362
[DefTypeParamClause] DefParamClauses
363-
TypeDcl ::= id [TypeParamClause] (SubtypeBounds | ‘=’ Type) TypeDefTree(_, name, tparams, bounds)
364-
| id [TypeParamClause] <: Type = MatchType
363+
TypeDcl ::= id [TypeParamClause] SubtypeBounds [‘=’ Type] TypeDefTree(_, name, tparams, bound
365364
366365
Def ::= ‘val’ PatDef
367366
| ‘var’ VarDef
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package scala.annotation.internal
2+
3+
import scala.annotation.Annotation
4+
5+
/** An annotation to indicate a pair of type bounds that comes with a type.
6+
* Used to indicate optional bounds of an opaque type
7+
*/
8+
class WithBounds[Lo <: AnyKind, Hi <: AnyKind] extends Annotation

0 commit comments

Comments
 (0)