Skip to content

Commit

Permalink
Populate children of Java sealed hierarchy
Browse files Browse the repository at this point in the history
Required for checking permitted subclasses
and for exhaustiveness of patterns.

Co-authored-by: Lukas Rytz <lukas.rytz@gmail.com>
  • Loading branch information
som-snytt and lrytz committed May 1, 2023
1 parent ab8feac commit a0f546d
Show file tree
Hide file tree
Showing 17 changed files with 195 additions and 4 deletions.
6 changes: 5 additions & 1 deletion src/compiler/scala/tools/nsc/javac/JavaParsers.scala
Expand Up @@ -19,6 +19,7 @@ package javac
import symtab.Flags
import JavaTokens._
import scala.annotation._
import scala.collection.mutable
import scala.collection.mutable.ListBuffer
import scala.language.implicitConversions
import scala.reflect.internal.util.{ListOfNil, Position}
Expand All @@ -45,6 +46,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {

abstract class JavaParser extends ParserCommon {
val in: JavaScanner
def unit: CompilationUnit

def freshName(prefix : String): Name
protected implicit def i2p(offset : Int) : Position
Expand Down Expand Up @@ -494,7 +496,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
in.nextToken()
case _ =>
val unsealed = 0L // no flag for UNSEALED
def consume(added: FlagSet): false = { in.nextToken(); /*flags |= added;*/ false }
def consume(added: FlagSet): false = { in.nextToken(); flags |= added; false }
def lookingAhead(s: String): Boolean = {
import scala.reflect.internal.Chars._
var i = 0
Expand Down Expand Up @@ -838,6 +840,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
else Nil

def classDecl(mods: Modifiers): List[Tree] = {
if (mods.hasFlag(SEALED)) patmat.javaClassesByUnit(unit.source) = mutable.Set.empty
accept(CLASS)
val pos = in.currentPos
val name = identForType()
Expand Down Expand Up @@ -904,6 +907,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
}

def interfaceDecl(mods: Modifiers): List[Tree] = {
if (mods.hasFlag(SEALED)) patmat.javaClassesByUnit(unit.source) = mutable.Set.empty
accept(INTERFACE)
val pos = in.currentPos
val name = identForType()
Expand Down
Expand Up @@ -13,9 +13,10 @@
package scala.tools.nsc.transform.patmat

import scala.annotation.tailrec
import scala.collection.mutable
import scala.collection.mutable.ListBuffer
import scala.reflect.internal.{Mode, Types}
import scala.reflect.internal.util.Statistics
import scala.reflect.internal.util.{SourceFile, Statistics}
import scala.tools.nsc.Global
import scala.tools.nsc.ast
import scala.tools.nsc.transform.{Transform, TypingTransformers}
Expand Down Expand Up @@ -58,6 +59,9 @@ trait PatternMatching extends Transform

val phaseName: String = "patmat"

/** Symbols to force for determining children of sealed Java classes. */
val javaClassesByUnit = perRunCaches.newMap[SourceFile, mutable.Set[Symbol]]()

def newTransformer(unit: CompilationUnit): AstTransformer = new MatchTransformer(unit)

class MatchTransformer(unit: CompilationUnit) extends TypingTransformer(unit) {
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/scala/tools/nsc/typechecker/Namers.scala
Expand Up @@ -795,6 +795,8 @@ trait Namers extends MethodSynthesis {
tree.symbol = enterClassSymbol(tree)
tree.symbol setInfo completerOf(tree)

if (tree.symbol.isJava) patmat.javaClassesByUnit.get(tree.symbol.pos.source).foreach(_.addOne(tree.symbol))

if (mods.isCase) {
val m = ensureCompanionObject(tree, caseModuleDef)
m.moduleClass.updateAttachment(new ClassForCaseCompanionAttachment(tree))
Expand Down
28 changes: 26 additions & 2 deletions src/compiler/scala/tools/nsc/typechecker/Typers.scala
Expand Up @@ -15,12 +15,11 @@ package tools.nsc
package typechecker

import scala.annotation.{tailrec, unused}
import scala.collection.mutable
import scala.collection.mutable, mutable.ListBuffer
import scala.reflect.internal.{Chars, TypesStats}
import scala.reflect.internal.util.{FreshNameCreator, ListOfNil, Statistics, StringContextStripMarginOps}
import scala.tools.nsc.Reporting.{MessageFilter, Suppression, WConf, WarningCategory}
import scala.util.chaining._
import mutable.ListBuffer
import symtab.Flags._
import Mode._

Expand Down Expand Up @@ -2653,6 +2652,31 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
val selectorTp = packCaptured(selector1.tpe.widen).skolemizeExistential(context.owner, selector)
val casesTyped = typedCases(cases, selectorTp, pt)

def initChildren(sym: Symbol): Unit =
if (sym.isJava && sym.isSealed)
sym.attachments.get[PermittedSubclassSymbols] match {
case Some(PermittedSubclassSymbols(permits)) =>
for (child <- permits if child.isJava)
initChildren(child.initialize)
case _ =>
val seen = mutable.HashSet.empty[Symbol]
def populate(): Unit =
patmat.javaClassesByUnit.get(sym.pos.source) match {
case Some(classes) =>
classes.find(!seen(_)) match {
case Some(unseen) =>
seen += unseen
unseen.initialize.companionSymbol.moduleClass.initialize
if (unseen.hasAttachment[PermittedSubclassSymbols]) initChildren(unseen)
populate()
case _ =>
}
case _ =>
}
populate()
}
initChildren(selectorTp.typeSymbol)

def finish(cases: List[CaseDef], matchType: Type) =
treeCopy.Match(tree, selector1, cases) setType matchType

Expand Down
7 changes: 7 additions & 0 deletions test/files/neg/t12159d.check
@@ -0,0 +1,7 @@
t.scala:7: warning: match may not be exhaustive.
It would fail on the following input: W()
x match {
^
error: No warnings can be incurred under -Werror.
1 warning
1 error
14 changes: 14 additions & 0 deletions test/files/neg/t12159d/X.java
@@ -0,0 +1,14 @@
// javaVersion: 17+
package p;

sealed abstract public class X {
}

final class W extends X {
}

final class Y extends X {
}

final class Z extends X {
}
12 changes: 12 additions & 0 deletions test/files/neg/t12159d/t.scala
@@ -0,0 +1,12 @@
// javaVersion: 17+
// scalac: -Werror
package p

class C {
def f(x: X) =
x match {
case y: Y => y.toString
case z: Z => z.toString
}
}

11 changes: 11 additions & 0 deletions test/files/neg/t12159e.check
@@ -0,0 +1,11 @@
t.scala:7: warning: match may not be exhaustive.
It would fail on the following input: W()
x match {
^
t.scala:12: warning: match may not be exhaustive.
It would fail on the following inputs: Z(), Z2()
x match {
^
error: No warnings can be incurred under -Werror.
2 warnings
1 error
20 changes: 20 additions & 0 deletions test/files/neg/t12159e/X.java
@@ -0,0 +1,20 @@
// javaVersion: 17+
package p;

sealed abstract public class X {
}

final class W extends X {
}

final class Y extends X {
}

sealed class Z extends X permits Z1, Z2 {
}

final class Z1 extends Z {
}

final class Z2 extends Z {
}
18 changes: 18 additions & 0 deletions test/files/neg/t12159e/t.scala
@@ -0,0 +1,18 @@
// javaVersion: 17+
// scalac: -Werror
package p

class C {
def f(x: X) =
x match {
case y: Y => y.toString
case z: Z => z.toString
}
def g(x: X) =
x match {
case w: W => w.toString
case y: Y => y.toString
case z: Z1 => z.toString
}
}

11 changes: 11 additions & 0 deletions test/files/neg/t12159f.check
@@ -0,0 +1,11 @@
t.scala:7: warning: match may not be exhaustive.
It would fail on the following input: W()
x match {
^
t.scala:12: warning: match may not be exhaustive.
It would fail on the following inputs: Z(), Z2()
x match {
^
error: No warnings can be incurred under -Werror.
2 warnings
1 error
14 changes: 14 additions & 0 deletions test/files/neg/t12159f/X.java
@@ -0,0 +1,14 @@
// javaVersion: 17+
package p;

sealed abstract public class X {
}

final class W extends X {
}

final class Y extends X {
}

sealed class Z extends X permits Z1, Z2 {
}
8 changes: 8 additions & 0 deletions test/files/neg/t12159f/Z.java
@@ -0,0 +1,8 @@
// javaVersion: 17+
package p;

final class Z1 extends Z {
}

final class Z2 extends Z {
}
18 changes: 18 additions & 0 deletions test/files/neg/t12159f/t.scala
@@ -0,0 +1,18 @@
// javaVersion: 17+
// scalac: -Werror
package p

class C {
def f(x: X) =
x match {
case y: Y => y.toString
case z: Z => z.toString
}
def g(x: X) =
x match {
case w: W => w.toString
case y: Y => y.toString
case z: Z1 => z.toString
}
}

7 changes: 7 additions & 0 deletions test/files/neg/t12159g.check
@@ -0,0 +1,7 @@
t.scala:4: warning: match may not be exhaustive.
It would fail on the following inputs: Oz(), Z()
def n(a: X) = a match { case _: Y => 42 }
^
error: No warnings can be incurred under -Werror.
1 warning
1 error
12 changes: 12 additions & 0 deletions test/files/neg/t12159g/X.java
@@ -0,0 +1,12 @@

package p;
public sealed interface X {
public default int x() { return 27; }
}
final class Y implements X { }
final class O {
final static class Z implements X { }
final static class Inner {
final static class Oz implements X { }
}
}
5 changes: 5 additions & 0 deletions test/files/neg/t12159g/t.scala
@@ -0,0 +1,5 @@
// scalac: -Werror -Xlint
package p
class T {
def n(a: X) = a match { case _: Y => 42 }
}

0 comments on commit a0f546d

Please sign in to comment.