Skip to content

Commit 990b3c7

Browse files
committed
SI-6380 #1 Add @throws[Exception]
This change allows an additional notation of the @throws annotation: Old-style: @throws(classOf[Exception]) New-style: @throws[Exception] The optional String argument moves @throws in line with @deprecated, @migration, etc. and prevents confusion caused by the default inheritance of ScalaDoc comments and the non-inheritance of annotations. Before: /** This method does ... * @throws IllegalArgumentException if `a` is less than 0. */ @throws(classOf[IllegalArgumentException]) def foo(a: Int) = ... Now: /** This method does ... */ @throws[IllegalArgumentException]("if `a` is less than 0") def foo(a: Int) = ... ScalaDoc @throws tags remain supported for cases where documentation of thrown exceptions is needed, but are not supposed to be added to the exception attribute of the class file. In this commit the necessary compiler support is added. The code to extract exceptions from annotations is now shared instead of being duplicated all over the place. The change is completely source and binary compatible, except that the code is now enforcing that the type thrown is a subtype of Throwable as mandated by the JVM spec instead of allowing something like @throws(classOf[String]). Not in this commit: - ScalaDoc support to add the String argument to ScalaDoc's exception list - Adaption of the library
1 parent b0a4d53 commit 990b3c7

File tree

7 files changed

+57
-15
lines changed

7 files changed

+57
-15
lines changed

src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -835,15 +835,10 @@ abstract class GenASM extends SubComponent with BytecodeWriters {
835835
*
836836
* The contents of that attribute are determined by the `String[] exceptions` argument to ASM's ClassVisitor.visitMethod()
837837
* This method returns such list of internal names.
838-
*
839838
*/
840-
def getExceptions(excs: List[AnnotationInfo]): List[String] = {
841-
for (AnnotationInfo(tp, List(exc), _) <- excs.distinct if tp.typeSymbol == ThrowsClass)
842-
yield {
843-
val Literal(const) = exc
844-
javaName(const.typeValue.typeSymbol)
845-
}
846-
}
839+
def getExceptions(excs: List[AnnotationInfo]): List[String] =
840+
for (ThrownException(exc) <- excs.distinct)
841+
yield javaName(exc)
847842

848843
/** Whether an annotation should be emitted as a Java annotation
849844
* .initialize: if 'annot' is read from pickle, atp might be un-initialized

src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -606,11 +606,10 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with
606606
// put some random value; the actual number is determined at the end
607607
buf putShort 0xbaba.toShort
608608

609-
for (AnnotationInfo(tp, List(exc), _) <- excs.distinct if tp.typeSymbol == ThrowsClass) {
610-
val Literal(const) = exc
609+
for (ThrownException(exc) <- excs.distinct) {
611610
buf.putShort(
612611
cpool.addClass(
613-
javaName(const.typeValue.typeSymbol)).shortValue)
612+
javaName(exc)).shortValue)
614613
nattr += 1
615614
}
616615

src/library/scala/throws.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ package scala
1414
* {{{
1515
* class Reader(fname: String) {
1616
* private val in = new BufferedReader(new FileReader(fname))
17-
* @throws(classOf[IOException])
17+
* @throws[IOException]("if the file doesn't exist")
1818
* def read() = in.read()
1919
* }
2020
* }}}
@@ -23,4 +23,6 @@ package scala
2323
* @version 1.0, 19/05/2006
2424
* @since 2.1
2525
*/
26-
class throws(clazz: Class[_]) extends scala.annotation.StaticAnnotation
26+
class throws[T <: Throwable](cause: String = "") extends scala.annotation.StaticAnnotation {
27+
def this(clazz: Class[T]) = this()
28+
}

src/reflect/scala/reflect/internal/AnnotationInfos.scala

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ trait AnnotationInfos extends api.Annotations { self: SymbolTable =>
3030
/** Symbols of any @throws annotations on this symbol.
3131
*/
3232
def throwsAnnotations(): List[Symbol] = annotations collect {
33-
case AnnotationInfo(tp, Literal(Constant(tpe: Type)) :: Nil, _) if tp.typeSymbol == ThrowsClass => tpe.typeSymbol
33+
case ThrownException(exc) => exc
3434
}
3535

3636
/** Tests for, get, or remove an annotation */
@@ -325,4 +325,23 @@ trait AnnotationInfos extends api.Annotations { self: SymbolTable =>
325325
implicit val AnnotationTag = ClassTag[AnnotationInfo](classOf[AnnotationInfo])
326326

327327
object UnmappableAnnotation extends CompleteAnnotationInfo(NoType, Nil, Nil)
328+
329+
/** Extracts symbol of thrown exception from AnnotationInfo.
330+
*
331+
* Supports both “old-style” `@throws(classOf[Exception])`
332+
* as well as “new-stye” `@throws[Exception]("cause")` annotations.
333+
*/
334+
object ThrownException {
335+
def unapply(ann: AnnotationInfo): Option[Symbol] =
336+
ann match {
337+
case AnnotationInfo(tpe, _, _) if tpe.typeSymbol != ThrowsClass =>
338+
None
339+
// old-style: @throws(classOf[Exception]) (which is throws[T](classOf[Exception]))
340+
case AnnotationInfo(_, List(Literal(Constant(tpe: Type))), _) =>
341+
Some(tpe.typeSymbol)
342+
// new-style: @throws[Exception], @throws[Exception]("cause")
343+
case AnnotationInfo(TypeRef(_, _, args), _, _) =>
344+
Some(args.head.typeSymbol)
345+
}
346+
}
328347
}

src/reflect/scala/reflect/internal/Definitions.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -948,7 +948,7 @@ trait Definitions extends api.StandardDefinitions {
948948
lazy val ScalaNoInlineClass = requiredClass[scala.noinline]
949949
lazy val SerialVersionUIDAttr = requiredClass[scala.SerialVersionUID]
950950
lazy val SpecializedClass = requiredClass[scala.specialized]
951-
lazy val ThrowsClass = requiredClass[scala.throws]
951+
lazy val ThrowsClass = requiredClass[scala.throws[_]]
952952
lazy val TransientAttr = requiredClass[scala.transient]
953953
lazy val UncheckedClass = requiredClass[scala.unchecked]
954954
lazy val UnspecializedClass = requiredClass[scala.annotation.unspecialized]

test/files/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)

test/files/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+
}

0 commit comments

Comments
 (0)