Skip to content

Commit 9a0952c

Browse files
committed
support check java enum from classfile and source file
1 parent 5cdd42f commit 9a0952c

File tree

11 files changed

+189
-91
lines changed

11 files changed

+189
-91
lines changed

src/dotty/tools/dotc/Compiler.scala

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,8 @@ import Symbols._
88
import Types._
99
import Scopes._
1010
import typer.{FrontEnd, Typer, ImportInfo, RefChecks}
11-
import reporting.{Reporter, ConsoleReporter}
1211
import Phases.Phase
1312
import transform._
14-
import transform.TreeTransforms.{TreeTransform, TreeTransformer}
15-
import core.DenotTransformers.DenotTransformer
16-
import core.Denotations.SingleDenotation
1713

1814
import dotty.tools.backend.jvm.{LabelDefs, GenBCode}
1915
import dotty.tools.backend.sjs.GenSJSIR

src/dotty/tools/dotc/core/Phases.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
package dotty.tools.dotc
22
package core
33

4-
import Periods._
54
import Contexts._
6-
import dotty.tools.backend.jvm.{LabelDefs, GenBCode}
5+
import dotty.tools.backend.jvm.{GenBCode, LabelDefs}
76
import dotty.tools.dotc.core.Symbols.ClassSymbol
87
import util.DotClass
98
import DenotTransformers._
109
import Denotations._
1110
import Decorators._
1211
import config.Printers._
13-
import scala.collection.mutable.{ListBuffer, ArrayBuffer}
14-
import dotty.tools.dotc.transform.TreeTransforms.{TreeTransformer, MiniPhase, TreeTransform}
12+
13+
import scala.collection.mutable.ListBuffer
14+
import dotty.tools.dotc.transform.TreeTransforms.{MiniPhase, TreeTransformer}
1515
import dotty.tools.dotc.transform._
1616
import Periods._
1717
import typer.{FrontEnd, RefChecks}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,13 @@ object Types {
810810
case _ => this
811811
}
812812

813+
/** Eliminate anonymous classes */
814+
final def elimAnonymousClass(implicit ctx: Context): Type = this match {
815+
case tp:TypeRef if tp.symbol.isAnonymousClass =>
816+
tp.symbol.asClass.typeRef.asSeenFrom(tp.prefix, tp.symbol.owner)
817+
case tp => tp
818+
}
819+
813820
/** Follow aliases and dereferences LazyRefs and instantiated TypeVars until type
814821
* is no longer alias type, LazyRef, or instantiated type variable.
815822
*/

src/dotty/tools/dotc/core/classfile/ClassfileParser.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ class ClassfileParser(
9090
val jflags = in.nextChar
9191
val isAnnotation = hasAnnotation(jflags)
9292
val sflags = classTranslation.flags(jflags)
93+
val isEnum = (jflags & JAVA_ACC_ENUM) != 0
9394
val nameIdx = in.nextChar
9495
currentClassName = pool.getClassName(nameIdx)
9596

@@ -145,6 +146,15 @@ class ClassfileParser(
145146
setClassInfo(classRoot, classInfo)
146147
setClassInfo(moduleRoot, staticInfo)
147148
}
149+
150+
// eager load java enum definitions for exhaustivity check of pattern match
151+
if (isEnum) {
152+
instanceScope.toList.map(_.ensureCompleted())
153+
staticScope.toList.map(_.ensureCompleted())
154+
classRoot.setFlag(Flags.Enum)
155+
moduleRoot.setFlag(Flags.Enum)
156+
}
157+
148158
result
149159
}
150160

src/dotty/tools/dotc/transform/PatternMatcher.scala

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import Applications._
2424
import TypeApplications._
2525
import SymUtils._, core.NameOps._
2626
import core.Mode
27+
import patmat._
2728

2829
import dotty.tools.dotc.util.Positions.Position
2930
import dotty.tools.dotc.core.Decorators._
@@ -56,9 +57,9 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans
5657

5758
// check exhaustivity and unreachability
5859
val engine = new SpaceEngine
59-
if (!engine.skipCheck(sel.tpe)) {
60-
engine.exhaustivity(tree)
61-
engine.redundancy(tree)
60+
if (engine.checkable(sel.tpe.widen.elimAnonymousClass)) {
61+
engine.checkExhaustivity(tree)
62+
engine.checkRedundancy(tree)
6263
}
6364

6465
translated.ensureConforms(tree.tpe)
@@ -1275,7 +1276,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans
12751276
def translateMatch(match_ : Match): Tree = {
12761277
val Match(sel, cases) = match_
12771278

1278-
val selectorTp = elimAnonymousClass(sel.tpe.widen/*withoutAnnotations*/)
1279+
val selectorTp = sel.tpe.widen.elimAnonymousClass/*withoutAnnotations*/
12791280

12801281
val selectorSym = freshSym(sel.pos, selectorTp, "selector")
12811282

src/dotty/tools/dotc/transform/Space.scala renamed to src/dotty/tools/dotc/transform/patmat/Space.scala

Lines changed: 78 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package dotty.tools.dotc
22
package transform
3+
package patmat
34

45
import core.Types._
56
import core.Contexts._
@@ -12,8 +13,26 @@ import core.Symbols._
1213
import core.NameOps._
1314
import core.Constants._
1415

15-
/** Space logic for checking exhaustivity and unreachability of
16-
* pattern matching.
16+
/** Space logic for checking exhaustivity and unreachability of pattern matching.
17+
*
18+
* The core idea of the algorithm is that patterns and types are value
19+
* spaces, which is recursively defined as follows:
20+
*
21+
* 1. `Empty` is a space
22+
* 2. For a type T, `Typ(T)` is a space
23+
* 3. A union of spaces `S1 | S2 | ...` is a space
24+
* 4. For a case class Kon(x1: T1, x2: T2, .., xn: Tn), if S1, S2, ..., Sn
25+
* are spaces, then `Kon(S1, S2, ..., Sn)` is a space.
26+
* 5. A constant `Const(value, T)` is a point in space
27+
* 6. A stable identifier `Var(sym, T)` is a space
28+
*
29+
* For the problem of exhaustivity check, its formulation in terms of space is as follows:
30+
*
31+
* Is the space Typ(T) a subspace of the union of space covered by all the patterns?
32+
*
33+
* The problem of unreachable patterns can be formulated as follows:
34+
*
35+
* Is the space covered by a pattern a subspace of the space covered by previous patterns?
1736
*/
1837

1938

@@ -41,7 +60,7 @@ trait SpaceLogic {
4160
/** Is the type `tp` decomposable? i.e. all values of the type can be covered
4261
* by its decomposed types.
4362
*
44-
* Abstract sealed class, OrType and Boolean can be decomposed.
63+
* Abstract sealed class, OrType, Boolean and Java enums can be decomposed.
4564
*/
4665
def canDecompose(tp: Type): Boolean
4766

@@ -197,7 +216,7 @@ trait SpaceLogic {
197216
case (Const(_, tp1), Typ(tp2, _)) =>
198217
if (isSubType(tp1, tp2)) Empty else a
199218
case (Const(_, _), _) => a
200-
case (Typ(tp1, _), Const(_, tp2)) => // Boolean
219+
case (Typ(tp1, _), Const(_, tp2)) => // Boolean & Java enum
201220
if (isSubType(tp2, tp1) && canDecompose(tp1))
202221
minus(Or(partitions(tp1)), b)
203222
else a
@@ -215,10 +234,6 @@ trait SpaceLogic {
215234
class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
216235
import tpd._
217236

218-
def debug(s: String): Unit = {
219-
if (ctx.debug) println(s)
220-
}
221-
222237
/** Return the space that represents the pattern `pat`
223238
*
224239
* If roundUp is true, approximate extractors to its type,
@@ -227,10 +242,13 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
227242
def project(pat: Tree, roundUp: Boolean = true)(implicit ctx: Context): Space = pat match {
228243
case Literal(c) => Const(c, c.tpe)
229244
case _: BackquotedIdent => Var(pat.symbol, pat.tpe)
230-
case Ident(_) => Typ(pat.tpe.stripAnnots, false)
231-
case Select(_, _) =>
245+
case Ident(_) =>
246+
Typ(pat.tpe.stripAnnots, false)
247+
case Select(_, _) =>
232248
if (pat.symbol.is(Module))
233249
Typ(pat.tpe.stripAnnots, false)
250+
else if (pat.symbol.is(Enum))
251+
Const(Constant(pat.symbol), pat.tpe)
234252
else
235253
Var(pat.symbol, pat.tpe)
236254
case Alternative(trees) => Or(trees.map(project(_, roundUp)))
@@ -243,7 +261,6 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
243261
case Typed(pat @ UnApply(_, _, _), _) => project(pat)
244262
case Typed(expr, _) => Typ(expr.tpe.stripAnnots, true)
245263
case _ =>
246-
debug(s"========unkown tree: $pat========")
247264
Empty
248265
}
249266

@@ -255,71 +272,61 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
255272
case (tp1: RefinedType, tp2: RefinedType) => isSubType(tp1.parent, tp2.parent)
256273
case (tp1: RefinedType, _) => isSubType(tp1.parent, tp2)
257274
case (_, tp2: RefinedType) => isSubType(tp1, tp2.parent)
258-
case (_, _) =>
259-
val res = tp1 <:< tp2
260-
debug(s"$tp1 <: $tp2 ? $res")
261-
res
275+
case (_, _) => tp1 <:< tp2
262276
}
263277

264-
def isEqualType(tp1: Type, tp2: Type): Boolean = {
265-
val res = tp1 =:= tp2
266-
debug(s"$tp1 == $tp2 ? $res")
267-
res
268-
}
278+
def isEqualType(tp1: Type, tp2: Type): Boolean = tp1 =:= tp2
269279

270280
def signature(tp: Type): List[Type] = {
271281
val ktor = tp.classSymbol.primaryConstructor.info
272282

273-
debug(s"=======ktor: $ktor")
274-
275283
val meth =
276284
if (ktor.isInstanceOf[MethodType]) ktor
277285
else
278286
tp match {
279287
case AppliedType(_, params) =>
280-
debug(s"=======params: $params")
281288
val refined = params.map {
282289
// TypeBounds would generate an exception
283290
case tp: TypeBounds => tp.underlying
284291
case tp => tp
285292
}
286-
debug(s"=======refined params: $refined")
287293
ktor.appliedTo(refined)
288294
case _ =>
289295
ktor
290296
}
291297

292-
val sign = meth.firstParamTypes.map(_.stripTypeVar).map(paramTp => refine(tp, paramTp))
293-
294-
debug(s"====signature of $tp: $sign")
295-
sign
298+
meth.firstParamTypes.map(_.stripTypeVar).map(refine(tp, _))
296299
}
297300

298-
def partitions(tp: Type): List[Space] = tp match {
299-
case OrType(tp1, tp2) => List(Typ(tp1, true), Typ(tp2, true))
300-
case _ if tp =:= ctx.definitions.BooleanType =>
301-
List(
302-
Const(Constant(true), ctx.definitions.BooleanType),
303-
Const(Constant(false), ctx.definitions.BooleanType)
304-
)
305-
case _ =>
306-
val children = tp.classSymbol.annotations.filter(_.symbol == ctx.definitions.ChildAnnot).map { annot =>
307-
// refer to definition of Annotation.makeChild
308-
val sym = annot.tree match {
309-
case Apply(TypeApply(_, List(tpTree)), _) => tpTree.symbol.asClass
310-
}
311-
312-
if (sym.is(ModuleClass))
313-
sym.classInfo.selfType
314-
else if (sym.info.typeParams.length > 0 || tp.isInstanceOf[TypeRef])
315-
refine(tp, sym.classInfo.symbolicTypeRef)
316-
else
317-
sym.info
318-
} filter(_ <:< tp) // child may not always be subtype of parent: SI-4020
319-
320-
debug(s"=========child of ${tp.show}: ${children.map(_.show).mkString(", ")}")
301+
def partitions(tp: Type): List[Space] = {
302+
val children = tp.classSymbol.annotations.filter(_.symbol == ctx.definitions.ChildAnnot).map { annot =>
303+
// refer to definition of Annotation.makeChild
304+
annot.tree match {
305+
case Apply(TypeApply(_, List(tpTree)), _) => tpTree.symbol
306+
}
307+
}
321308

322-
children.map(tp => Typ(tp, true))
309+
tp match {
310+
case OrType(tp1, tp2) => List(Typ(tp1, true), Typ(tp2, true))
311+
case _ if tp =:= ctx.definitions.BooleanType =>
312+
List(
313+
Const(Constant(true), ctx.definitions.BooleanType),
314+
Const(Constant(false), ctx.definitions.BooleanType)
315+
)
316+
case _ if tp.classSymbol.is(Enum) =>
317+
children.map(sym => Const(Constant(sym), tp))
318+
case _ =>
319+
val parts = children.map { sym =>
320+
if (sym.is(ModuleClass))
321+
sym.asClass.classInfo.selfType
322+
else if (sym.info.typeParams.length > 0 || tp.isInstanceOf[TypeRef])
323+
refine(tp, sym.asClass.classInfo.symbolicTypeRef)
324+
else
325+
sym.info
326+
} filter(_ <:< tp) // child may not always be subtype of parent: SI-4020
327+
328+
parts.map(tp => Typ(tp, true))
329+
}
323330
}
324331

325332
/** Refine tp2 based on tp1
@@ -337,13 +344,13 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
337344
case _ => tp2
338345
}
339346

340-
/** Abstract sealed types, or-types and Boolean can be decomposed */
341-
def canDecompose(tp: Type): Boolean = {
347+
/** Abstract sealed types, or-types, Boolean and Java enums can be decomposed */
348+
def canDecompose(tp: Type): Boolean =
342349
tp.typeSymbol.is(allOf(Abstract, Sealed)) ||
343350
tp.typeSymbol.is(allOf(Trait, Sealed)) ||
344351
tp.isInstanceOf[OrType] ||
345-
tp =:= ctx.definitions.BooleanType
346-
}
352+
tp =:= ctx.definitions.BooleanType ||
353+
tp.typeSymbol.is(Enum)
347354

348355
def isCaseClass(tp: Type): Boolean = tp.classSymbol.isClass && tp.classSymbol.is(CaseClass)
349356

@@ -428,27 +435,24 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
428435
flatten(s).map(doShow(_, false)).distinct.mkString(", ")
429436
}
430437

431-
/** Does the type t have @unchecked annotation? */
432-
def skipCheck(tp: Type): Boolean = tp match {
433-
case AnnotatedType(tp, annot) => ctx.definitions.UncheckedAnnot == annot.symbol
434-
case _ => false
435-
}
436-
437-
/** Widen a type and eliminate anonymous classes */
438-
private def widen(tp: Type): Type = tp.widen match {
439-
case tp:TypeRef if tp.symbol.isAnonymousClass =>
440-
tp.symbol.asClass.typeRef.asSeenFrom(tp.prefix, tp.symbol.owner)
441-
case tp => tp
438+
def checkable(tp: Type): Boolean = tp match {
439+
case AnnotatedType(tp, annot) =>
440+
(ctx.definitions.UncheckedAnnot != annot.symbol) && checkable(tp)
441+
case _ => true // actually everything is checkable unless @unchecked
442+
443+
// tp.classSymbol.is(Sealed) ||
444+
// tp.isInstanceOf[OrType] ||
445+
// tp.classSymbol.is(Enum) ||
446+
// Boolean
447+
// Int
448+
// ...
442449
}
443450

444-
def exhaustivity(_match: Match): Unit = {
451+
def checkExhaustivity(_match: Match): Unit = {
445452
val Match(sel, cases) = _match
446-
val selTyp = widen(sel.tpe)
453+
val selTyp = sel.tpe.widen.elimAnonymousClass
447454

448-
debug(s"====patterns:\n${cases.map(_.pat).mkString("\n")}")
449455
val patternSpace = cases.map(x => project(x.pat)).reduce((a, b) => Or(List(a, b)))
450-
debug(s"====selector:\n" + selTyp)
451-
debug("====pattern space:\n" + show(patternSpace))
452456
val uncovered = simplify(minus(Typ(selTyp, true), patternSpace))
453457

454458
if (uncovered != Empty) {
@@ -460,9 +464,9 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
460464
}
461465
}
462466

463-
def redundancy(_match: Match): Unit = {
467+
def checkRedundancy(_match: Match): Unit = {
464468
val Match(sel, cases) = _match
465-
val selTyp = widen(sel.tpe)
469+
val selTyp = sel.tpe.widen.elimAnonymousClass
466470

467471
// starts from the second, the first can't be redundant
468472
(1 until cases.length).foreach { i =>

0 commit comments

Comments
 (0)