Skip to content

Commit 886081a

Browse files
committed
Implement bounds for opaque types
1 parent 51394e3 commit 886081a

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
@@ -823,6 +823,8 @@ class Definitions {
823823
def BodyAnnot(implicit ctx: Context): ClassSymbol = BodyAnnotType.symbol.asClass
824824
lazy val ChildAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.internal.Child")
825825
def ChildAnnot(implicit ctx: Context): ClassSymbol = ChildAnnotType.symbol.asClass
826+
lazy val WithBoundsAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.internal.WithBounds")
827+
def WithBoundsAnnot(implicit ctx: Context): ClassSymbol = WithBoundsAnnotType.symbol.asClass
826828
lazy val CovariantBetweenAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.internal.CovariantBetween")
827829
def CovariantBetweenAnnot(implicit ctx: Context): ClassSymbol = CovariantBetweenAnnotType.symbol.asClass
828830
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
@@ -2024,6 +2025,7 @@ object Parsers {
20242025
* Modifier ::= LocalModifier
20252026
* | AccessModifier
20262027
* | override
2028+
* | opaque
20272029
* LocalModifier ::= abstract | final | sealed | implicit | lazy | erased | inline
20282030
*/
20292031
def modifiers(allowed: BitSet = modifierTokens, start: Modifiers = Modifiers()): Modifiers = {
@@ -2552,8 +2554,7 @@ object Parsers {
25522554
Block(stats, Literal(Constant(())))
25532555
}
25542556

2555-
/** TypeDcl ::= id [TypeParamClause] (TypeBounds | ‘=’ Type)
2556-
* | id [TypeParamClause] <: Type = MatchType
2557+
/** TypeDcl ::= id [TypeParamClause] TypeBounds [‘=’ Type]
25572558
*/
25582559
def typeDefOrDcl(start: Offset, mods: Modifiers): Tree = {
25592560
newLinesOpt()
@@ -2566,15 +2567,33 @@ object Parsers {
25662567
case EQUALS =>
25672568
in.nextToken()
25682569
makeTypeDef(toplevelTyp())
2569-
case SUBTYPE =>
2570-
in.nextToken()
2571-
val bound = toplevelTyp()
2570+
case SUBTYPE | SUPERTYPE =>
2571+
val bounds = typeBounds()
25722572
if (in.token == EQUALS) {
2573-
in.nextToken()
2574-
makeTypeDef(matchType(bound, infixType()))
2573+
val eqOffset = in.skipToken()
2574+
var rhs = toplevelTyp()
2575+
rhs match {
2576+
case mtt: MatchTypeTree =>
2577+
bounds match {
2578+
case TypeBoundsTree(EmptyTree, upper) =>
2579+
rhs = MatchTypeTree(upper, mtt.selector, mtt.cases)
2580+
case _ =>
2581+
syntaxError(i"cannot combine lower bound and match type alias", eqOffset)
2582+
}
2583+
case _ =>
2584+
if (mods.is(Opaque)) {
2585+
val annotType = AppliedTypeTree(
2586+
TypeTree(defn.WithBoundsAnnotType),
2587+
bounds.lo.orElse(TypeTree(defn.NothingType)) ::
2588+
bounds.hi.orElse(TypeTree(defn.AnyType)) :: Nil)
2589+
rhs = Annotated(rhs, ensureApplied(wrapNew(annotType)))
2590+
}
2591+
else syntaxError(i"cannot combine bound and alias", eqOffset)
2592+
}
2593+
makeTypeDef(rhs)
25752594
}
2576-
else makeTypeDef(TypeBoundsTree(EmptyTree, bound))
2577-
case SUPERTYPE | SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | EOF =>
2595+
else makeTypeDef(bounds)
2596+
case SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | EOF =>
25782597
makeTypeDef(typeBounds())
25792598
case _ =>
25802599
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)