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 Apr 24, 2023
1 parent 50eef2f commit 2bf8131
Show file tree
Hide file tree
Showing 14 changed files with 158 additions and 8 deletions.
4 changes: 4 additions & 0 deletions 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 @@ -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
14 changes: 14 additions & 0 deletions src/compiler/scala/tools/nsc/typechecker/Typers.scala
Expand Up @@ -2653,6 +2653,20 @@ 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].foreach(_.permits.foreach { child =>
child.initialize
initChildren(child)
})
patmat.javaClassesByUnit.get(sym.pos.source).foreach(_.foreach { c =>
if (c.parentSymbols.contains(sym)) {
c.initialize
initChildren(c)
}
})
}
initChildren(selectorTp.typeSymbol)

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

Expand Down
14 changes: 7 additions & 7 deletions test/files/neg/t12159d.check
@@ -1,7 +1,7 @@
s.scala:5: error: illegal inheritance from sealed class H
class S extends H {
^
s.scala:8: error: illegal inheritance from sealed trait I
trait T extends I {
^
2 errors
t_3.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_3.java
@@ -0,0 +1,14 @@
// javaVersion: 17+
package p;

sealed abstract public class X_3 {
}

final class W extends X_3 {
}

final class Y extends X_3 {
}

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

class C {
def f(x: X_3) =
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
}
}

0 comments on commit 2bf8131

Please sign in to comment.