diff --git a/src/compiler/scala/tools/nsc/javac/JavaParsers.scala b/src/compiler/scala/tools/nsc/javac/JavaParsers.scala index c6894afba4df..e77576c804ae 100644 --- a/src/compiler/scala/tools/nsc/javac/JavaParsers.scala +++ b/src/compiler/scala/tools/nsc/javac/JavaParsers.scala @@ -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} @@ -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 @@ -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 @@ -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() @@ -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() diff --git a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala index b9d562ff9756..958ffad37c3d 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala @@ -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} @@ -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) { diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index f4487e2bfa5e..6323357a5c5b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -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)) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 1c66e180e85c..f211616c271b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -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._ @@ -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 diff --git a/test/files/neg/t12159d.check b/test/files/neg/t12159d.check new file mode 100644 index 000000000000..1efb798c80d7 --- /dev/null +++ b/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 diff --git a/test/files/neg/t12159d/X.java b/test/files/neg/t12159d/X.java new file mode 100644 index 000000000000..0812a6fd21cd --- /dev/null +++ b/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 { +} diff --git a/test/files/neg/t12159d/t.scala b/test/files/neg/t12159d/t.scala new file mode 100644 index 000000000000..e57c9cd62ddc --- /dev/null +++ b/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 + } +} + diff --git a/test/files/neg/t12159e.check b/test/files/neg/t12159e.check new file mode 100644 index 000000000000..86807203f124 --- /dev/null +++ b/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 diff --git a/test/files/neg/t12159e/X.java b/test/files/neg/t12159e/X.java new file mode 100644 index 000000000000..6b770b115ea6 --- /dev/null +++ b/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 { +} diff --git a/test/files/neg/t12159e/t.scala b/test/files/neg/t12159e/t.scala new file mode 100644 index 000000000000..1d5810a09f85 --- /dev/null +++ b/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 + } +} + diff --git a/test/files/neg/t12159f.check b/test/files/neg/t12159f.check new file mode 100644 index 000000000000..86807203f124 --- /dev/null +++ b/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 diff --git a/test/files/neg/t12159f/X.java b/test/files/neg/t12159f/X.java new file mode 100644 index 000000000000..bc1043b66039 --- /dev/null +++ b/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 { +} diff --git a/test/files/neg/t12159f/Z.java b/test/files/neg/t12159f/Z.java new file mode 100644 index 000000000000..55fcc661a182 --- /dev/null +++ b/test/files/neg/t12159f/Z.java @@ -0,0 +1,8 @@ +// javaVersion: 17+ +package p; + +final class Z1 extends Z { +} + +final class Z2 extends Z { +} diff --git a/test/files/neg/t12159f/t.scala b/test/files/neg/t12159f/t.scala new file mode 100644 index 000000000000..1d5810a09f85 --- /dev/null +++ b/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 + } +} + diff --git a/test/files/neg/t12159g.check b/test/files/neg/t12159g.check new file mode 100644 index 000000000000..f268b3430cdc --- /dev/null +++ b/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 diff --git a/test/files/neg/t12159g/X.java b/test/files/neg/t12159g/X.java new file mode 100644 index 000000000000..8687ede65157 --- /dev/null +++ b/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 { } + } +} diff --git a/test/files/neg/t12159g/t.scala b/test/files/neg/t12159g/t.scala new file mode 100644 index 000000000000..f92009287f1b --- /dev/null +++ b/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 } +}