Skip to content

Commit 96180d2

Browse files
committed
Implement bounds for opaque types
1 parent 9f4fa00 commit 96180d2

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
@@ -832,6 +832,8 @@ class Definitions {
832832
def BodyAnnot(implicit ctx: Context): ClassSymbol = BodyAnnotType.symbol.asClass
833833
lazy val ChildAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.internal.Child")
834834
def ChildAnnot(implicit ctx: Context): ClassSymbol = ChildAnnotType.symbol.asClass
835+
lazy val WithBoundsAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.internal.WithBounds")
836+
def WithBoundsAnnot(implicit ctx: Context): ClassSymbol = WithBoundsAnnotType.symbol.asClass
835837
lazy val CovariantBetweenAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.internal.CovariantBetween")
836838
def CovariantBetweenAnnot(implicit ctx: Context): ClassSymbol = CovariantBetweenAnnotType.symbol.asClass
837839
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
@@ -370,7 +370,9 @@ object SymDenotations {
370370
case _ => unforcedDecls.openForMutations
371371
}
372372

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

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

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

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])
@@ -1469,9 +1470,9 @@ object Parsers {
14691470

14701471
/** `match' { TypeCaseClauses }
14711472
*/
1472-
def matchType(bound: Tree, t: Tree): MatchTypeTree =
1473-
atSpan((if (bound.isEmpty) t else bound).span.start, accept(MATCH)) {
1474-
inBraces(MatchTypeTree(bound, t, caseClauses(typeCaseClause)))
1473+
def matchType(t: Tree): MatchTypeTree =
1474+
atSpan(t.span.start, accept(MATCH)) {
1475+
inBraces(MatchTypeTree(EmptyTree, t, caseClauses(typeCaseClause)))
14751476
}
14761477

14771478
/** FunParams ::= Bindings
@@ -2063,6 +2064,7 @@ object Parsers {
20632064
* Modifier ::= LocalModifier
20642065
* | AccessModifier
20652066
* | override
2067+
* | opaque
20662068
* LocalModifier ::= abstract | final | sealed | implicit | lazy | erased | inline
20672069
*/
20682070
def modifiers(allowed: BitSet = modifierTokens, start: Modifiers = Modifiers()): Modifiers = {
@@ -2605,8 +2607,7 @@ object Parsers {
26052607
Block(stats, Literal(Constant(())))
26062608
}
26072609

2608-
/** TypeDcl ::= id [TypeParamClause] (TypeBounds | ‘=’ Type)
2609-
* | id [TypeParamClause] <: Type = MatchType
2610+
/** TypeDcl ::= id [TypeParamClause] TypeBounds [‘=’ Type]
26102611
*/
26112612
def typeDefOrDcl(start: Offset, mods: Modifiers): Tree = {
26122613
newLinesOpt()
@@ -2619,15 +2620,33 @@ object Parsers {
26192620
case EQUALS =>
26202621
in.nextToken()
26212622
makeTypeDef(toplevelTyp())
2622-
case SUBTYPE =>
2623-
in.nextToken()
2624-
val bound = toplevelTyp()
2623+
case SUBTYPE | SUPERTYPE =>
2624+
val bounds = typeBounds()
26252625
if (in.token == EQUALS) {
2626-
in.nextToken()
2627-
makeTypeDef(matchType(bound, infixType()))
2626+
val eqOffset = in.skipToken()
2627+
var rhs = toplevelTyp()
2628+
rhs match {
2629+
case mtt: MatchTypeTree =>
2630+
bounds match {
2631+
case TypeBoundsTree(EmptyTree, upper) =>
2632+
rhs = MatchTypeTree(upper, mtt.selector, mtt.cases)
2633+
case _ =>
2634+
syntaxError(i"cannot combine lower bound and match type alias", eqOffset)
2635+
}
2636+
case _ =>
2637+
if (mods.is(Opaque)) {
2638+
val annotType = AppliedTypeTree(
2639+
TypeTree(defn.WithBoundsAnnotType),
2640+
bounds.lo.orElse(TypeTree(defn.NothingType)) ::
2641+
bounds.hi.orElse(TypeTree(defn.AnyType)) :: Nil)
2642+
rhs = Annotated(rhs, ensureApplied(wrapNew(annotType)))
2643+
}
2644+
else syntaxError(i"cannot combine bound and alias", eqOffset)
2645+
}
2646+
makeTypeDef(rhs)
26282647
}
2629-
else makeTypeDef(TypeBoundsTree(EmptyTree, bound))
2630-
case SUPERTYPE | SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | EOF =>
2648+
else makeTypeDef(bounds)
2649+
case SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | EOF =>
26312650
makeTypeDef(typeBounds())
26322651
case _ =>
26332652
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
@@ -1820,8 +1820,15 @@ class Typer extends Namer
18201820
def typedAnnotated(tree: untpd.Annotated, pt: Type)(implicit ctx: Context): Tree = track("typedAnnotated") {
18211821
val annot1 = typedExpr(tree.annot, defn.AnnotationType)
18221822
val arg1 = typed(tree.arg, pt)
1823-
if (ctx.mode is Mode.Type)
1824-
assignType(cpy.Annotated(tree)(arg1, annot1), arg1, annot1)
1823+
if (ctx.mode is Mode.Type) {
1824+
val result = assignType(cpy.Annotated(tree)(arg1, annot1), arg1, annot1)
1825+
result.tpe match {
1826+
case AnnotatedType(rhs, Annotation.WithBounds(bounds)) =>
1827+
if (!bounds.contains(rhs)) ctx.error(em"type $rhs outside bounds $bounds", tree.sourcePos)
1828+
case _ =>
1829+
}
1830+
result
1831+
}
18251832
else {
18261833
val arg2 = arg1 match {
18271834
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

tests/neg/opaque-bounds.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
class Test { // error: class Test cannot be instantiated
2+
3+
opaque type FlagSet = Int
4+
5+
opaque type Flag <: FlagSet = String // error: type String outside bounds <: Test.this.FlagSet
6+
7+
object Flag {
8+
def make(s: String): Flag = s
9+
}
10+
11+
val f: Flag = Flag.make("hello")
12+
val g: FlagSet = f
13+
14+
}

tests/pos/opaque-bounds.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
object Test {
2+
3+
4+
opaque type FlagSet = Int
5+
6+
opaque type Flag <: FlagSet = Int
7+
8+
object Flag {
9+
def make(n: Int): Flag = n
10+
}
11+
12+
val f: Flag = Flag.make(1)
13+
val g: FlagSet = f
14+
15+
}

0 commit comments

Comments
 (0)