Skip to content

Commit 3cb93f3

Browse files
authored
Merge pull request #7483 from noti0na1/master
Port Constant Types for Literal Final Static Java Fields and Implicit Conversions from Scalac
2 parents a923e6a + 30cc3e9 commit 3cb93f3

15 files changed

+289
-2
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,7 @@ object StdNames {
807807
final val ELSEkw: N = kw("else")
808808
final val ENUMkw: N = kw("enum")
809809
final val EXTENDSkw: N = kw("extends")
810+
final val FALSEkw: N = kw("false")
810811
final val FINALkw: N = kw("final")
811812
final val FINALLYkw: N = kw("finally")
812813
final val FLOATkw: N = kw("float")
@@ -836,6 +837,7 @@ object StdNames {
836837
final val THROWkw: N = kw("throw")
837838
final val THROWSkw: N = kw("throws")
838839
final val TRANSIENTkw: N = kw("transient")
840+
final val TRUEkw: N = kw("true")
839841
final val TRYkw: N = kw("try")
840842
final val VOIDkw: N = kw("void")
841843
final val VOLATILEkw: N = kw("volatile")

compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -601,9 +601,53 @@ object JavaParsers {
601601

602602
def varDecl(mods: Modifiers, tpt: Tree, name: TermName): ValDef = {
603603
val tpt1 = optArrayBrackets(tpt)
604-
if (in.token == EQUALS && !mods.is(Flags.Param)) skipTo(COMMA, SEMI)
604+
/** Tries to detect final static literals syntactically and returns a constant type replacement */
605+
def optConstantTpe(): Tree = {
606+
def constantTpe(const: Constant): Tree = TypeTree(ConstantType(const))
607+
608+
def forConst(const: Constant): Tree = {
609+
if (in.token != SEMI) tpt1
610+
else {
611+
def isStringTyped = tpt1 match {
612+
case Ident(n: TypeName) => "String" == n.toString
613+
case _ => false
614+
}
615+
if (const.tag == Constants.StringTag && isStringTyped) constantTpe(const)
616+
else tpt1 match {
617+
case TypedSplice(tpt2) =>
618+
if (const.tag == Constants.BooleanTag || const.isNumeric) {
619+
//for example, literal 'a' is ok for float. 127 is ok for byte, but 128 is not.
620+
val converted = const.convertTo(tpt2.tpe)
621+
if (converted == null) tpt1
622+
else constantTpe(converted)
623+
}
624+
else tpt1
625+
case _ => tpt1
626+
}
627+
}
628+
}
629+
630+
in.nextToken() // EQUALS
631+
if (mods.is(Flags.JavaStatic) && mods.is(Flags.Final)) {
632+
val neg = in.token match {
633+
case MINUS | BANG => in.nextToken(); true
634+
case _ => false
635+
}
636+
tryLiteral(neg).map(forConst).getOrElse(tpt1)
637+
}
638+
else tpt1
639+
}
640+
641+
val tpt2: Tree =
642+
if (in.token == EQUALS && !mods.is(Flags.Param)) {
643+
val res = optConstantTpe()
644+
skipTo(COMMA, SEMI)
645+
res
646+
}
647+
else tpt1
648+
605649
val mods1 = if (mods.is(Flags.Final)) mods else mods | Flags.Mutable
606-
ValDef(name, tpt1, if (mods.is(Flags.Param)) EmptyTree else unimplementedExpr).withMods(mods1)
650+
ValDef(name, tpt2, if (mods.is(Flags.Param)) EmptyTree else unimplementedExpr).withMods(mods1)
607651
}
608652

609653
def memberDecl(start: Offset, mods: Modifiers, parentToken: Int, parentTParams: List[TypeDef]): List[Tree] = in.token match {
@@ -881,6 +925,25 @@ object JavaParsers {
881925
case _ => in.nextToken(); syntaxError("illegal start of type declaration", skipIt = true); List(errorTypeTree)
882926
}
883927

928+
def tryLiteral(negate: Boolean = false): Option[Constant] = {
929+
val l = in.token match {
930+
case TRUE => !negate
931+
case FALSE => negate
932+
case CHARLIT => in.strVal.charAt(0)
933+
case INTLIT => in.intVal(negate).toInt
934+
case LONGLIT => in.intVal(negate)
935+
case FLOATLIT => in.floatVal(negate).toFloat
936+
case DOUBLELIT => in.floatVal(negate)
937+
case STRINGLIT => in.strVal
938+
case _ => null
939+
}
940+
if (l == null) None
941+
else {
942+
in.nextToken()
943+
Some(Constant(l))
944+
}
945+
}
946+
884947
/** CompilationUnit ::= [package QualId semi] TopStatSeq
885948
*/
886949
def compilationUnit(): Tree = {

compiler/src/dotty/tools/dotc/parsing/JavaScanners.scala

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,54 @@ object JavaScanners {
479479
setStrVal()
480480
}
481481

482+
/** convert name to long value
483+
*/
484+
def intVal(negated: Boolean): Long =
485+
if (token == CHARLIT && !negated)
486+
if (strVal.length > 0) strVal.charAt(0).toLong else 0
487+
else {
488+
var value: Long = 0
489+
val divider = if (base == 10) 1 else 2
490+
val limit: Long =
491+
if (token == LONGLIT) Long.MaxValue else Int.MaxValue
492+
var i = 0
493+
val len = strVal.length
494+
while (i < len) {
495+
val d = digit2int(strVal.charAt(i), base)
496+
if (d < 0) {
497+
error("malformed integer number")
498+
return 0
499+
}
500+
if (value < 0 ||
501+
limit / (base / divider) < value ||
502+
limit - (d / divider) < value * (base / divider) &&
503+
!(negated && limit == value * base - 1 + d)) {
504+
error("integer number too large")
505+
return 0
506+
}
507+
value = value * base + d
508+
i += 1
509+
}
510+
if (negated) -value else value
511+
}
512+
513+
/** convert name, base to double value
514+
*/
515+
def floatVal(negated: Boolean): Double = {
516+
val limit: Double =
517+
if (token == DOUBLELIT) Double.MaxValue else Float.MaxValue
518+
try {
519+
val value: Double = java.lang.Double.valueOf(strVal.toString).doubleValue()
520+
if (value > limit)
521+
error("floating point number too large")
522+
if (negated) -value else value
523+
} catch {
524+
case _: NumberFormatException =>
525+
error("malformed floating point number")
526+
0.0
527+
}
528+
}
529+
482530
/** read a number into name and set base
483531
*/
484532
protected def getNumber(): Unit = {

tests/pos/t3236/AnnotationTest.scala

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
trait AnnotationTest {
2+
@BooleanAnnotation(Constants.BooleanTrue)
3+
@ByteAnnotation(Constants.Byte)
4+
@CharAnnotation(Constants.Char)
5+
@ShortAnnotation(Constants.Short)
6+
@IntAnnotation(Constants.Int)
7+
@LongAnnotation(Constants.Long)
8+
@FloatAnnotation(Constants.Float)
9+
@DoubleAnnotation(Constants.Double)
10+
@StringAnnotation(Constants.String)
11+
def test1: Unit
12+
13+
@BooleanAnnotation(Constants.InvertedBoolean)
14+
@ByteAnnotation(Constants.NegativeByte)
15+
@ShortAnnotation(Constants.NegativeShort)
16+
@IntAnnotation(Constants.NegativeInt)
17+
@LongAnnotation(Constants.NegativeLong)
18+
@FloatAnnotation(Constants.NegativeFloat)
19+
@DoubleAnnotation(Constants.NegativeDouble)
20+
@StringAnnotation(Constants.NegativeString)
21+
def test2: Unit
22+
23+
@BooleanAnnotation(Constants.BooleanFalse)
24+
@ByteAnnotation(Constants.LiteralCharAsByte)
25+
@CharAnnotation(Constants.LiteralChar)
26+
@ShortAnnotation(Constants.LiteralCharAsShort)
27+
@IntAnnotation(Constants.LiteralCharAsInt)
28+
@LongAnnotation(Constants.LiteralCharAsLong)
29+
def test3: Unit
30+
31+
@LongAnnotation(Constants.LiteralIntAsLong)
32+
def test4: Unit
33+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import java.lang.annotation.Retention;
2+
import java.lang.annotation.RetentionPolicy;
3+
4+
@Retention(RetentionPolicy.RUNTIME)
5+
public @interface BooleanAnnotation {
6+
boolean value();
7+
}

tests/pos/t3236/ByteAnnotation.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import java.lang.annotation.Retention;
2+
import java.lang.annotation.RetentionPolicy;
3+
4+
@Retention(RetentionPolicy.RUNTIME)
5+
public @interface ByteAnnotation {
6+
byte value();
7+
}

tests/pos/t3236/CharAnnotation.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import java.lang.annotation.Retention;
2+
import java.lang.annotation.RetentionPolicy;
3+
4+
@Retention(RetentionPolicy.RUNTIME)
5+
public @interface CharAnnotation {
6+
char value();
7+
}

tests/pos/t3236/Constants.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
public class Constants {
2+
public static final boolean BooleanTrue = true;
3+
public static final boolean BooleanFalse = false;
4+
public static final boolean InvertedBoolean = !true;
5+
6+
public static final byte Byte = 23;
7+
public static final byte NegativeByte = -42;
8+
public static final byte LiteralCharAsByte = 'a';
9+
10+
public static final char Char = 33;
11+
public static final char LiteralChar = 'b';
12+
13+
public static final short Short = 0x1234;
14+
public static final short NegativeShort= -0x5678;
15+
public static final short LiteralCharAsShort = 'c';
16+
17+
public static final int Int = 0xabcdef;
18+
public static final int NegativeInt = -12345678;
19+
public static final int LiteralCharAsInt = 'd';
20+
21+
public static final long Long = 0x1234567890abcdefL;
22+
public static final long NegativeLong = -0xfedcba09876L;
23+
public static final long LiteralCharAsLong = 'e';
24+
public static final long LiteralIntAsLong = 0x12345678;
25+
26+
public static final float Float = 42.232323f;
27+
public static final float NegativeFloat = -3.1415f;
28+
29+
public static final double Double = 23.4243598374594d;
30+
public static final double NegativeDouble = -42.2324358934589734859d;
31+
32+
public static final String String = "testConstant";
33+
public static final String NegativeString = "!#!$!grml%!%!$#@@@";
34+
}

tests/pos/t3236/DoubleAnnotation.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import java.lang.annotation.Retention;
2+
import java.lang.annotation.RetentionPolicy;
3+
4+
@Retention(RetentionPolicy.RUNTIME)
5+
public @interface DoubleAnnotation {
6+
double value();
7+
}

tests/pos/t3236/FloatAnnotation.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import java.lang.annotation.Retention;
2+
import java.lang.annotation.RetentionPolicy;
3+
4+
@Retention(RetentionPolicy.RUNTIME)
5+
public @interface FloatAnnotation {
6+
float value();
7+
}

tests/pos/t3236/IntAnnotation.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import java.lang.annotation.Retention;
2+
import java.lang.annotation.RetentionPolicy;
3+
4+
@Retention(RetentionPolicy.RUNTIME)
5+
public @interface IntAnnotation {
6+
int value();
7+
}

tests/pos/t3236/LongAnnotation.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import java.lang.annotation.Retention;
2+
import java.lang.annotation.RetentionPolicy;
3+
4+
@Retention(RetentionPolicy.RUNTIME)
5+
public @interface LongAnnotation {
6+
long value();
7+
}

tests/pos/t3236/ShortAnnotation.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import java.lang.annotation.Retention;
2+
import java.lang.annotation.RetentionPolicy;
3+
4+
@Retention(RetentionPolicy.RUNTIME)
5+
public @interface ShortAnnotation {
6+
short value();
7+
}

tests/pos/t3236/StringAnnotation.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import java.lang.annotation.Retention;
2+
import java.lang.annotation.RetentionPolicy;
3+
4+
@Retention(RetentionPolicy.RUNTIME)
5+
public @interface StringAnnotation {
6+
String value();
7+
}

tests/pos/t3236/Test.scala

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import scala.reflect.Selectable.reflectiveSelectable
2+
3+
object Test extends App {
4+
val theClass = classOf[AnnotationTest]
5+
6+
def annotation[T <: java.lang.annotation.Annotation](annotationClass: Class[T], methodName: String): T =
7+
theClass.getDeclaredMethod(methodName)
8+
.getAnnotation[T](annotationClass)
9+
10+
def check[T, U <: java.lang.annotation.Annotation & { def value(): T } ](annotationClass: Class[U], methodName: String, expected: T): Unit = {
11+
val a = annotation(annotationClass, methodName)
12+
assert(a != null, s"No annotation of type $annotationClass found on method $methodName")
13+
assert(a.value() == expected, s"Actual value of annotation $a on $methodName was not of expected value $expected")
14+
}
15+
16+
check(classOf[BooleanAnnotation], "test1", Constants.BooleanTrue)
17+
check(classOf[ByteAnnotation], "test1", Constants.Byte)
18+
check(classOf[CharAnnotation], "test1", Constants.Char)
19+
check(classOf[ShortAnnotation], "test1", Constants.Short)
20+
check(classOf[IntAnnotation], "test1", Constants.Int)
21+
check(classOf[LongAnnotation], "test1", Constants.Long)
22+
check(classOf[FloatAnnotation], "test1", Constants.Float)
23+
check(classOf[DoubleAnnotation], "test1", Constants.Double)
24+
check(classOf[StringAnnotation], "test1", Constants.String)
25+
26+
check(classOf[BooleanAnnotation], "test2", Constants.InvertedBoolean)
27+
check(classOf[ByteAnnotation], "test2", Constants.NegativeByte)
28+
// no negative char possible
29+
check(classOf[ShortAnnotation], "test2", Constants.NegativeShort)
30+
check(classOf[IntAnnotation], "test2", Constants.NegativeInt)
31+
check(classOf[LongAnnotation], "test2", Constants.NegativeLong)
32+
check(classOf[FloatAnnotation], "test2", Constants.NegativeFloat)
33+
check(classOf[DoubleAnnotation], "test2", Constants.NegativeDouble)
34+
check(classOf[StringAnnotation], "test2", Constants.NegativeString)
35+
36+
check(classOf[BooleanAnnotation], "test3", Constants.BooleanFalse)
37+
check(classOf[ByteAnnotation], "test3", Constants.LiteralCharAsByte)
38+
check(classOf[CharAnnotation], "test3", Constants.LiteralChar)
39+
check(classOf[ShortAnnotation], "test3", Constants.LiteralCharAsShort)
40+
check(classOf[IntAnnotation], "test3", Constants.LiteralCharAsInt)
41+
check(classOf[LongAnnotation], "test3", Constants.LiteralCharAsLong)
42+
43+
check(classOf[LongAnnotation], "test4", Constants.LiteralIntAsLong)
44+
}

0 commit comments

Comments
 (0)