Skip to content

Commit 009eba9

Browse files
committed
Implement bounds for opaque types
1 parent c22a180 commit 009eba9

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
@@ -817,6 +817,8 @@ object Trees {
817817
extends ProxyTree[T] {
818818
type ThisTree[-T >: Untyped] = Annotated[T]
819819
def forwardTo: Tree[T] = arg
820+
override def disableOverlapChecks = true
821+
// disable overlaps checks since the WithBounds annotation swaps type and annotation.
820822
}
821823

822824
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
@@ -814,6 +814,8 @@ class Definitions {
814814
def BodyAnnot(implicit ctx: Context): ClassSymbol = BodyAnnotType.symbol.asClass
815815
lazy val ChildAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.internal.Child")
816816
def ChildAnnot(implicit ctx: Context): ClassSymbol = ChildAnnotType.symbol.asClass
817+
lazy val WithBoundsAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.internal.WithBounds")
818+
def WithBoundsAnnot(implicit ctx: Context): ClassSymbol = WithBoundsAnnotType.symbol.asClass
817819
lazy val CovariantBetweenAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.internal.CovariantBetween")
818820
def CovariantBetweenAnnot(implicit ctx: Context): ClassSymbol = CovariantBetweenAnnotType.symbol.asClass
819821
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
@@ -930,7 +931,7 @@ object Parsers {
930931

931932
in.token match {
932933
case ARROW => functionRest(t :: Nil)
933-
case MATCH => matchType(EmptyTree, t)
934+
case MATCH => matchType(t)
934935
case FORSOME => syntaxError(ExistentialTypesNoLongerSupported()); t
935936
case _ =>
936937
if (imods.is(ImplicitOrGiven) && !t.isInstanceOf[FunctionWithMods])
@@ -1430,9 +1431,9 @@ object Parsers {
14301431

14311432
/** `match' { TypeCaseClauses }
14321433
*/
1433-
def matchType(bound: Tree, t: Tree): MatchTypeTree =
1434-
atSpan((if (bound.isEmpty) t else bound).span.start, accept(MATCH)) {
1435-
inBraces(MatchTypeTree(bound, t, caseClauses(typeCaseClause)))
1434+
def matchType(t: Tree): MatchTypeTree =
1435+
atSpan(t.span.start, accept(MATCH)) {
1436+
inBraces(MatchTypeTree(EmptyTree, t, caseClauses(typeCaseClause)))
14361437
}
14371438

14381439
/** FunParams ::= Bindings
@@ -2022,6 +2023,7 @@ object Parsers {
20222023
* Modifier ::= LocalModifier
20232024
* | AccessModifier
20242025
* | override
2026+
* | opaque
20252027
* LocalModifier ::= abstract | final | sealed | implicit | lazy | erased | inline
20262028
*/
20272029
def modifiers(allowed: BitSet = modifierTokens, start: Modifiers = Modifiers()): Modifiers = {
@@ -2550,8 +2552,7 @@ object Parsers {
25502552
Block(stats, Literal(Constant(())))
25512553
}
25522554

2553-
/** TypeDcl ::= id [TypeParamClause] (TypeBounds | ‘=’ Type)
2554-
* | id [TypeParamClause] <: Type = MatchType
2555+
/** TypeDcl ::= id [TypeParamClause] TypeBounds [‘=’ Type]
25552556
*/
25562557
def typeDefOrDcl(start: Offset, mods: Modifiers): Tree = {
25572558
newLinesOpt()
@@ -2564,15 +2565,33 @@ object Parsers {
25642565
case EQUALS =>
25652566
in.nextToken()
25662567
makeTypeDef(toplevelTyp())
2567-
case SUBTYPE =>
2568-
in.nextToken()
2569-
val bound = toplevelTyp()
2568+
case SUBTYPE | SUPERTYPE =>
2569+
val bounds = typeBounds()
25702570
if (in.token == EQUALS) {
2571-
in.nextToken()
2572-
makeTypeDef(matchType(bound, infixType()))
2571+
val eqOffset = in.skipToken()
2572+
var rhs = toplevelTyp()
2573+
rhs match {
2574+
case mtt: MatchTypeTree =>
2575+
bounds match {
2576+
case TypeBoundsTree(EmptyTree, upper) =>
2577+
rhs = MatchTypeTree(upper, mtt.selector, mtt.cases)
2578+
case _ =>
2579+
syntaxError(i"cannot combine lower bound and match type alias", eqOffset)
2580+
}
2581+
case _ =>
2582+
if (mods.is(Opaque)) {
2583+
val annotType = AppliedTypeTree(
2584+
TypeTree(defn.WithBoundsAnnotType),
2585+
bounds.lo.orElse(TypeTree(defn.NothingType)) ::
2586+
bounds.hi.orElse(TypeTree(defn.AnyType)) :: Nil)
2587+
rhs = Annotated(rhs, ensureApplied(wrapNew(annotType)))
2588+
}
2589+
else syntaxError(i"cannot combine bound and alias", eqOffset)
2590+
}
2591+
makeTypeDef(rhs)
25732592
}
2574-
else makeTypeDef(TypeBoundsTree(EmptyTree, bound))
2575-
case SUPERTYPE | SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | EOF =>
2593+
else makeTypeDef(bounds)
2594+
case SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | EOF =>
25762595
makeTypeDef(typeBounds())
25772596
case _ =>
25782597
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
@@ -353,8 +353,7 @@ VarDcl ::= ids ‘:’ Type
353353
DefDcl ::= DefSig [‘:’ Type] DefDef(_, name, tparams, vparamss, tpe, EmptyTree)
354354
DefSig ::= ‘(’ DefParam ‘)’ [nl] id
355355
[DefTypeParamClause] DefParamClauses
356-
TypeDcl ::= id [TypeParamClause] (SubtypeBounds | ‘=’ Type) TypeDefTree(_, name, tparams, bounds)
357-
| id [TypeParamClause] <: Type = MatchType
356+
TypeDcl ::= id [TypeParamClause] SubtypeBounds [‘=’ Type] TypeDefTree(_, name, tparams, bound
358357
359358
Def ::= ‘val’ PatDef
360359
| ‘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)