diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index b4983df55b0b..58fdc137e6bf 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -807,6 +807,7 @@ object StdNames { final val ELSEkw: N = kw("else") final val ENUMkw: N = kw("enum") final val EXTENDSkw: N = kw("extends") + final val FALSEkw: N = kw("false") final val FINALkw: N = kw("final") final val FINALLYkw: N = kw("finally") final val FLOATkw: N = kw("float") @@ -836,6 +837,7 @@ object StdNames { final val THROWkw: N = kw("throw") final val THROWSkw: N = kw("throws") final val TRANSIENTkw: N = kw("transient") + final val TRUEkw: N = kw("true") final val TRYkw: N = kw("try") final val VOIDkw: N = kw("void") final val VOLATILEkw: N = kw("volatile") diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala index 9e41cff04106..fed445077d8a 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -601,9 +601,53 @@ object JavaParsers { def varDecl(mods: Modifiers, tpt: Tree, name: TermName): ValDef = { val tpt1 = optArrayBrackets(tpt) - if (in.token == EQUALS && !mods.is(Flags.Param)) skipTo(COMMA, SEMI) + /** Tries to detect final static literals syntactically and returns a constant type replacement */ + def optConstantTpe(): Tree = { + def constantTpe(const: Constant): Tree = TypeTree(ConstantType(const)) + + def forConst(const: Constant): Tree = { + if (in.token != SEMI) tpt1 + else { + def isStringTyped = tpt1 match { + case Ident(n: TypeName) => "String" == n.toString + case _ => false + } + if (const.tag == Constants.StringTag && isStringTyped) constantTpe(const) + else tpt1 match { + case TypedSplice(tpt2) => + if (const.tag == Constants.BooleanTag || const.isNumeric) { + //for example, literal 'a' is ok for float. 127 is ok for byte, but 128 is not. + val converted = const.convertTo(tpt2.tpe) + if (converted == null) tpt1 + else constantTpe(converted) + } + else tpt1 + case _ => tpt1 + } + } + } + + in.nextToken() // EQUALS + if (mods.is(Flags.JavaStatic) && mods.is(Flags.Final)) { + val neg = in.token match { + case MINUS | BANG => in.nextToken(); true + case _ => false + } + tryLiteral(neg).map(forConst).getOrElse(tpt1) + } + else tpt1 + } + + val tpt2: Tree = + if (in.token == EQUALS && !mods.is(Flags.Param)) { + val res = optConstantTpe() + skipTo(COMMA, SEMI) + res + } + else tpt1 + val mods1 = if (mods.is(Flags.Final)) mods else mods | Flags.Mutable - ValDef(name, tpt1, if (mods.is(Flags.Param)) EmptyTree else unimplementedExpr).withMods(mods1) + ValDef(name, tpt2, if (mods.is(Flags.Param)) EmptyTree else unimplementedExpr).withMods(mods1) } def memberDecl(start: Offset, mods: Modifiers, parentToken: Int, parentTParams: List[TypeDef]): List[Tree] = in.token match { @@ -881,6 +925,25 @@ object JavaParsers { case _ => in.nextToken(); syntaxError("illegal start of type declaration", skipIt = true); List(errorTypeTree) } + def tryLiteral(negate: Boolean = false): Option[Constant] = { + val l = in.token match { + case TRUE => !negate + case FALSE => negate + case CHARLIT => in.strVal.charAt(0) + case INTLIT => in.intVal(negate).toInt + case LONGLIT => in.intVal(negate) + case FLOATLIT => in.floatVal(negate).toFloat + case DOUBLELIT => in.floatVal(negate) + case STRINGLIT => in.strVal + case _ => null + } + if (l == null) None + else { + in.nextToken() + Some(Constant(l)) + } + } + /** CompilationUnit ::= [package QualId semi] TopStatSeq */ def compilationUnit(): Tree = { diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaScanners.scala b/compiler/src/dotty/tools/dotc/parsing/JavaScanners.scala index 0e437d9d0a49..bef78f06e9f0 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaScanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaScanners.scala @@ -479,6 +479,54 @@ object JavaScanners { setStrVal() } + /** convert name to long value + */ + def intVal(negated: Boolean): Long = + if (token == CHARLIT && !negated) + if (strVal.length > 0) strVal.charAt(0).toLong else 0 + else { + var value: Long = 0 + val divider = if (base == 10) 1 else 2 + val limit: Long = + if (token == LONGLIT) Long.MaxValue else Int.MaxValue + var i = 0 + val len = strVal.length + while (i < len) { + val d = digit2int(strVal.charAt(i), base) + if (d < 0) { + error("malformed integer number") + return 0 + } + if (value < 0 || + limit / (base / divider) < value || + limit - (d / divider) < value * (base / divider) && + !(negated && limit == value * base - 1 + d)) { + error("integer number too large") + return 0 + } + value = value * base + d + i += 1 + } + if (negated) -value else value + } + + /** convert name, base to double value + */ + def floatVal(negated: Boolean): Double = { + val limit: Double = + if (token == DOUBLELIT) Double.MaxValue else Float.MaxValue + try { + val value: Double = java.lang.Double.valueOf(strVal.toString).doubleValue() + if (value > limit) + error("floating point number too large") + if (negated) -value else value + } catch { + case _: NumberFormatException => + error("malformed floating point number") + 0.0 + } + } + /** read a number into name and set base */ protected def getNumber(): Unit = { diff --git a/tests/pos/t3236/AnnotationTest.scala b/tests/pos/t3236/AnnotationTest.scala new file mode 100644 index 000000000000..c2f9ae7837f8 --- /dev/null +++ b/tests/pos/t3236/AnnotationTest.scala @@ -0,0 +1,33 @@ +trait AnnotationTest { + @BooleanAnnotation(Constants.BooleanTrue) + @ByteAnnotation(Constants.Byte) + @CharAnnotation(Constants.Char) + @ShortAnnotation(Constants.Short) + @IntAnnotation(Constants.Int) + @LongAnnotation(Constants.Long) + @FloatAnnotation(Constants.Float) + @DoubleAnnotation(Constants.Double) + @StringAnnotation(Constants.String) + def test1: Unit + + @BooleanAnnotation(Constants.InvertedBoolean) + @ByteAnnotation(Constants.NegativeByte) + @ShortAnnotation(Constants.NegativeShort) + @IntAnnotation(Constants.NegativeInt) + @LongAnnotation(Constants.NegativeLong) + @FloatAnnotation(Constants.NegativeFloat) + @DoubleAnnotation(Constants.NegativeDouble) + @StringAnnotation(Constants.NegativeString) + def test2: Unit + + @BooleanAnnotation(Constants.BooleanFalse) + @ByteAnnotation(Constants.LiteralCharAsByte) + @CharAnnotation(Constants.LiteralChar) + @ShortAnnotation(Constants.LiteralCharAsShort) + @IntAnnotation(Constants.LiteralCharAsInt) + @LongAnnotation(Constants.LiteralCharAsLong) + def test3: Unit + + @LongAnnotation(Constants.LiteralIntAsLong) + def test4: Unit +} diff --git a/tests/pos/t3236/BooleanAnnotation.java b/tests/pos/t3236/BooleanAnnotation.java new file mode 100644 index 000000000000..7e57a5e0dbd9 --- /dev/null +++ b/tests/pos/t3236/BooleanAnnotation.java @@ -0,0 +1,7 @@ +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface BooleanAnnotation { + boolean value(); +} diff --git a/tests/pos/t3236/ByteAnnotation.java b/tests/pos/t3236/ByteAnnotation.java new file mode 100644 index 000000000000..c986fa5d27d5 --- /dev/null +++ b/tests/pos/t3236/ByteAnnotation.java @@ -0,0 +1,7 @@ +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface ByteAnnotation { + byte value(); +} diff --git a/tests/pos/t3236/CharAnnotation.java b/tests/pos/t3236/CharAnnotation.java new file mode 100644 index 000000000000..1715f1b7de39 --- /dev/null +++ b/tests/pos/t3236/CharAnnotation.java @@ -0,0 +1,7 @@ +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface CharAnnotation { + char value(); +} diff --git a/tests/pos/t3236/Constants.java b/tests/pos/t3236/Constants.java new file mode 100644 index 000000000000..16b4001f7694 --- /dev/null +++ b/tests/pos/t3236/Constants.java @@ -0,0 +1,34 @@ +public class Constants { + public static final boolean BooleanTrue = true; + public static final boolean BooleanFalse = false; + public static final boolean InvertedBoolean = !true; + + public static final byte Byte = 23; + public static final byte NegativeByte = -42; + public static final byte LiteralCharAsByte = 'a'; + + public static final char Char = 33; + public static final char LiteralChar = 'b'; + + public static final short Short = 0x1234; + public static final short NegativeShort= -0x5678; + public static final short LiteralCharAsShort = 'c'; + + public static final int Int = 0xabcdef; + public static final int NegativeInt = -12345678; + public static final int LiteralCharAsInt = 'd'; + + public static final long Long = 0x1234567890abcdefL; + public static final long NegativeLong = -0xfedcba09876L; + public static final long LiteralCharAsLong = 'e'; + public static final long LiteralIntAsLong = 0x12345678; + + public static final float Float = 42.232323f; + public static final float NegativeFloat = -3.1415f; + + public static final double Double = 23.4243598374594d; + public static final double NegativeDouble = -42.2324358934589734859d; + + public static final String String = "testConstant"; + public static final String NegativeString = "!#!$!grml%!%!$#@@@"; +} diff --git a/tests/pos/t3236/DoubleAnnotation.java b/tests/pos/t3236/DoubleAnnotation.java new file mode 100644 index 000000000000..1eb8223f4e58 --- /dev/null +++ b/tests/pos/t3236/DoubleAnnotation.java @@ -0,0 +1,7 @@ +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface DoubleAnnotation { + double value(); +} diff --git a/tests/pos/t3236/FloatAnnotation.java b/tests/pos/t3236/FloatAnnotation.java new file mode 100644 index 000000000000..c723a25fada5 --- /dev/null +++ b/tests/pos/t3236/FloatAnnotation.java @@ -0,0 +1,7 @@ +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface FloatAnnotation { + float value(); +} diff --git a/tests/pos/t3236/IntAnnotation.java b/tests/pos/t3236/IntAnnotation.java new file mode 100644 index 000000000000..2ffad8890cd3 --- /dev/null +++ b/tests/pos/t3236/IntAnnotation.java @@ -0,0 +1,7 @@ +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface IntAnnotation { + int value(); +} diff --git a/tests/pos/t3236/LongAnnotation.java b/tests/pos/t3236/LongAnnotation.java new file mode 100644 index 000000000000..9f80b4139859 --- /dev/null +++ b/tests/pos/t3236/LongAnnotation.java @@ -0,0 +1,7 @@ +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface LongAnnotation { + long value(); +} diff --git a/tests/pos/t3236/ShortAnnotation.java b/tests/pos/t3236/ShortAnnotation.java new file mode 100644 index 000000000000..f0a35892c750 --- /dev/null +++ b/tests/pos/t3236/ShortAnnotation.java @@ -0,0 +1,7 @@ +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface ShortAnnotation { + short value(); +} diff --git a/tests/pos/t3236/StringAnnotation.java b/tests/pos/t3236/StringAnnotation.java new file mode 100644 index 000000000000..0fdc1ead3815 --- /dev/null +++ b/tests/pos/t3236/StringAnnotation.java @@ -0,0 +1,7 @@ +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface StringAnnotation { + String value(); +} diff --git a/tests/pos/t3236/Test.scala b/tests/pos/t3236/Test.scala new file mode 100644 index 000000000000..efb24119d92c --- /dev/null +++ b/tests/pos/t3236/Test.scala @@ -0,0 +1,44 @@ +import scala.reflect.Selectable.reflectiveSelectable + +object Test extends App { + val theClass = classOf[AnnotationTest] + + def annotation[T <: java.lang.annotation.Annotation](annotationClass: Class[T], methodName: String): T = + theClass.getDeclaredMethod(methodName) + .getAnnotation[T](annotationClass) + + def check[T, U <: java.lang.annotation.Annotation & { def value(): T } ](annotationClass: Class[U], methodName: String, expected: T): Unit = { + val a = annotation(annotationClass, methodName) + assert(a != null, s"No annotation of type $annotationClass found on method $methodName") + assert(a.value() == expected, s"Actual value of annotation $a on $methodName was not of expected value $expected") + } + + check(classOf[BooleanAnnotation], "test1", Constants.BooleanTrue) + check(classOf[ByteAnnotation], "test1", Constants.Byte) + check(classOf[CharAnnotation], "test1", Constants.Char) + check(classOf[ShortAnnotation], "test1", Constants.Short) + check(classOf[IntAnnotation], "test1", Constants.Int) + check(classOf[LongAnnotation], "test1", Constants.Long) + check(classOf[FloatAnnotation], "test1", Constants.Float) + check(classOf[DoubleAnnotation], "test1", Constants.Double) + check(classOf[StringAnnotation], "test1", Constants.String) + + check(classOf[BooleanAnnotation], "test2", Constants.InvertedBoolean) + check(classOf[ByteAnnotation], "test2", Constants.NegativeByte) + // no negative char possible + check(classOf[ShortAnnotation], "test2", Constants.NegativeShort) + check(classOf[IntAnnotation], "test2", Constants.NegativeInt) + check(classOf[LongAnnotation], "test2", Constants.NegativeLong) + check(classOf[FloatAnnotation], "test2", Constants.NegativeFloat) + check(classOf[DoubleAnnotation], "test2", Constants.NegativeDouble) + check(classOf[StringAnnotation], "test2", Constants.NegativeString) + + check(classOf[BooleanAnnotation], "test3", Constants.BooleanFalse) + check(classOf[ByteAnnotation], "test3", Constants.LiteralCharAsByte) + check(classOf[CharAnnotation], "test3", Constants.LiteralChar) + check(classOf[ShortAnnotation], "test3", Constants.LiteralCharAsShort) + check(classOf[IntAnnotation], "test3", Constants.LiteralCharAsInt) + check(classOf[LongAnnotation], "test3", Constants.LiteralCharAsLong) + + check(classOf[LongAnnotation], "test4", Constants.LiteralIntAsLong) +}