Skip to content

Commit b814e77

Browse files
committed
Fix #8593: Implement support for @throws
The implementation and tests are based on the scalac implementation, the implementation relies on the classOf rewrite in the previous commit.
1 parent 4645c08 commit b814e77

File tree

8 files changed

+153
-24
lines changed

8 files changed

+153
-24
lines changed

compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import scala.collection.mutable
1111
import dotty.tools.dotc.CompilationUnit
1212
import dotty.tools.dotc.ast.tpd
1313
import dotty.tools.dotc.ast.Trees
14-
import dotty.tools.dotc.core.Annotations.Annotation
14+
import dotty.tools.dotc.core.Annotations._
1515
import dotty.tools.dotc.core.Constants._
1616
import dotty.tools.dotc.core.Contexts.Context
1717
import dotty.tools.dotc.core.Decorators._
@@ -512,7 +512,7 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
512512

513513
// TODO needed? for(ann <- m.annotations) { ann.symbol.initialize }
514514
val jgensig = getStaticForwarderGenericSignature(m, module)
515-
val (throws, others) = m.annotations partition (_.tree.symbol == defn.ThrowsAnnot)
515+
val (throws, others) = m.annotations.partition(_.symbol eq defn.ThrowsAnnot)
516516
val thrownExceptions: List[String] = getExceptions(throws)
517517

518518
val jReturnType = toTypeKind(methodInfo.resultType)
@@ -612,12 +612,9 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
612612
* must-single-thread
613613
*/
614614
def getExceptions(excs: List[Annotation]): List[String] = {
615-
// TODO: implement ThrownException
616-
// for (ThrownException(exc) <- excs.distinct)
617-
// yield internalName(exc)
618-
Nil
615+
for (case ThrownException(exc) <- excs.distinct)
616+
yield internalName(TypeErasure.erasure(exc).classSymbol)
619617
}
620-
621618
} // end of trait BCForwardersGen
622619

623620
trait BCClassGen extends BCInnerClassGen {

compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,7 @@ trait BCodeSkelBuilder extends BCodeHelpers {
513513
def initJMethod(flags: Int, paramAnnotations: List[List[Annotation]]): Unit = {
514514

515515
val jgensig = getGenericSignature(methSymbol, claszSymbol)
516-
val (excs, others) = methSymbol.annotations partition (_.tree.symbol == defn.ThrowsAnnot)
516+
val (excs, others) = methSymbol.annotations.partition(_.symbol eq defn.ThrowsAnnot)
517517
val thrownExceptions: List[String] = getExceptions(excs)
518518

519519
val bytecodeName =

compiler/src/dotty/tools/dotc/core/Annotations.scala

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,4 +235,28 @@ object Annotations {
235235
}
236236
yield ScalaVersion.parse(arg.stringValue)
237237
}
238+
239+
/** Extracts the type of the thrown exception from an annotation.
240+
*
241+
* Supports both "old-style" `@throws(classOf[Exception])`
242+
* as well as "new-style" `@throws[Exception]("cause")` annotations.
243+
*/
244+
object ThrownException {
245+
def unapply(a: Annotation)(using Context): Option[Type] =
246+
if (a.symbol ne defn.ThrowsAnnot)
247+
None
248+
else a.argumentConstant(0) match {
249+
// old-style: @throws(classOf[Exception]) (which is throws[T](classOf[Exception]))
250+
case Some(Constant(tpe: Type)) =>
251+
Some(tpe)
252+
// new-style: @throws[Exception], @throws[Exception]("cause")
253+
case _ =>
254+
stripApply(a.tree) match {
255+
case TypeApply(_, List(tpt)) =>
256+
Some(tpt.tpe)
257+
case _ =>
258+
None
259+
}
260+
}
261+
}
238262
}

compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package dotty.tools
22
package dotc
33
package transform
44

5-
import core.Annotations.Annotation
5+
import core.Annotations._
66
import core.Contexts.Context
77
import core.Definitions
88
import core.Flags._
@@ -381,21 +381,6 @@ object GenericSignatures {
381381
(initialSymbol.is(Method) && initialSymbol.typeParams.contains(sym))
382382
)
383383

384-
/** Extracts the type of the thrown exception from an AnnotationInfo.
385-
*
386-
* Supports both “old-style” `@throws(classOf[Exception])`
387-
* as well as “new-style” `@throws[Exception]("cause")` annotations.
388-
*/
389-
private object ThrownException {
390-
def unapply(ann: Annotation)(implicit ctx: Context): Option[Type] =
391-
ann.tree match {
392-
case Apply(TypeApply(fun, List(tpe)), _) if tpe.isType && fun.symbol.owner == defn.ThrowsAnnot && fun.symbol.isConstructor =>
393-
Some(tpe.typeOpt)
394-
case _ =>
395-
None
396-
}
397-
}
398-
399384
// @M #2585 when generating a java generic signature that includes
400385
// a selection of an inner class p.I, (p = `pre`, I = `cls`) must
401386
// rewrite to p'.I, where p' refers to the class that directly defines

tests/run/t6380.check

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
List(class java.lang.Exception)
2+
List(class java.lang.Throwable)
3+
List(class java.lang.RuntimeException)
4+
List(class java.lang.IllegalArgumentException, class java.util.NoSuchElementException)
5+
List(class java.lang.IndexOutOfBoundsException, class java.lang.IndexOutOfBoundsException)
6+
List(class java.lang.IllegalStateException, class java.lang.IllegalStateException)
7+
List(class java.lang.NullPointerException, class java.lang.NullPointerException)

tests/run/t6380.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
object Test extends App {
2+
classOf[Foo].getDeclaredMethods().sortBy(_.getName).map(_.getExceptionTypes.sortBy(_.getName).toList).toList.foreach(println)
3+
}
4+
5+
class Foo {
6+
@throws[Exception]
7+
def bar1 = ???
8+
@throws[Throwable]("always")
9+
def bar2 = ???
10+
@throws(classOf[RuntimeException])
11+
def bar3 = ???
12+
@throws[IllegalArgumentException] @throws[NoSuchElementException]
13+
def bar4 = ???
14+
@throws(classOf[IndexOutOfBoundsException]) @throws(classOf[IndexOutOfBoundsException])
15+
def bar5 = ???
16+
@throws[IllegalStateException]("Cause") @throws[IllegalStateException]
17+
def bar6 = ???
18+
@throws[NullPointerException]("Cause A") @throws[NullPointerException]("Cause B")
19+
def bar7 = ???
20+
}

tests/run/throws-annot.check

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
read throws: class java.io.IOException
2+
read annotations:
3+
readWith2 throws: class java.io.IOException, class java.lang.ClassCastException
4+
readWith2 annotations:
5+
readMixed throws: class java.lang.NullPointerException, class java.io.IOException
6+
readMixed annotations: interface java.lang.Deprecated
7+
readMixed2 throws: class java.lang.NullPointerException, class java.io.IOException
8+
readMixed2 annotations: interface java.lang.Deprecated
9+
readNoEx throws:
10+
readNoEx annotations: interface java.lang.Deprecated
11+
Testing mirror class
12+
read throws: class java.io.IOException
13+
read annotations:
14+
readWith2 throws: class java.io.IOException, class java.lang.ClassCastException
15+
readWith2 annotations:
16+
readMixed throws: class java.lang.NullPointerException, class java.io.IOException
17+
readMixed annotations: interface java.lang.Deprecated
18+
readMixed2 throws: class java.lang.NullPointerException, class java.io.IOException
19+
readMixed2 annotations: interface java.lang.Deprecated
20+
readNoEx throws:
21+
readNoEx annotations: interface java.lang.Deprecated

tests/run/throws-annot.scala

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/** Test the @throws annotation */
2+
import java.io.IOException
3+
4+
object TestThrows {
5+
6+
abstract class Foo {
7+
8+
@throws(classOf[IOException])
9+
def read(): Int
10+
11+
@throws(classOf[ClassCastException])
12+
@throws(classOf[IOException])
13+
def readWith2(): Int
14+
15+
@throws(classOf[IOException])
16+
@Deprecated
17+
@throws(classOf[NullPointerException])
18+
def readMixed(): Int
19+
20+
@Deprecated
21+
@throws(classOf[IOException])
22+
@throws(classOf[NullPointerException])
23+
def readMixed2(): Int
24+
25+
@Deprecated
26+
def readNoEx(): Int
27+
}
28+
29+
def checkMethod(cls: Class[_], name: String): Unit = {
30+
val method = cls.getMethod(name)
31+
println(name + " throws: " + method.getExceptionTypes.mkString("", ", ", ""))
32+
val annots = method.getDeclaredAnnotations.map(_.annotationType)
33+
println(name + " annotations: " + annots.mkString("", ", ", ""))
34+
}
35+
36+
def run(cls: Class[_]): Unit = {
37+
checkMethod(cls, "read")
38+
checkMethod(cls, "readWith2")
39+
checkMethod(cls, "readMixed")
40+
checkMethod(cls, "readMixed2")
41+
checkMethod(cls, "readNoEx")
42+
}
43+
}
44+
45+
/** Test the top-level mirror that is has the annotations. */
46+
object TL {
47+
48+
@throws(classOf[IOException])
49+
def read(): Int = 0
50+
51+
@throws(classOf[ClassCastException])
52+
@throws(classOf[IOException])
53+
def readWith2(): Int = 0
54+
55+
@throws(classOf[IOException])
56+
@Deprecated
57+
@throws(classOf[NullPointerException])
58+
def readMixed(): Int = 0
59+
60+
@Deprecated
61+
@throws(classOf[IOException])
62+
@throws(classOf[NullPointerException])
63+
def readMixed2(): Int = 0
64+
65+
@Deprecated
66+
def readNoEx(): Int = 0
67+
}
68+
69+
object Test {
70+
def main(args: Array[String]): Unit = {
71+
TestThrows.run(classOf[TestThrows.Foo])
72+
println("Testing mirror class")
73+
TestThrows.run(Class.forName("TL"))
74+
}
75+
}

0 commit comments

Comments
 (0)