Skip to content

Port Constant Types for Literal Final Static Java Fields and Implicit Conversions from Scalac #7483

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down
67 changes: 65 additions & 2 deletions compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 = {
Expand Down
48 changes: 48 additions & 0 deletions compiler/src/dotty/tools/dotc/parsing/JavaScanners.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
33 changes: 33 additions & 0 deletions tests/pos/t3236/AnnotationTest.scala
Original file line number Diff line number Diff line change
@@ -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
}
7 changes: 7 additions & 0 deletions tests/pos/t3236/BooleanAnnotation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface BooleanAnnotation {
boolean value();
}
7 changes: 7 additions & 0 deletions tests/pos/t3236/ByteAnnotation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface ByteAnnotation {
byte value();
}
7 changes: 7 additions & 0 deletions tests/pos/t3236/CharAnnotation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface CharAnnotation {
char value();
}
34 changes: 34 additions & 0 deletions tests/pos/t3236/Constants.java
Original file line number Diff line number Diff line change
@@ -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%!%!$#@@@";
}
7 changes: 7 additions & 0 deletions tests/pos/t3236/DoubleAnnotation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface DoubleAnnotation {
double value();
}
7 changes: 7 additions & 0 deletions tests/pos/t3236/FloatAnnotation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface FloatAnnotation {
float value();
}
7 changes: 7 additions & 0 deletions tests/pos/t3236/IntAnnotation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface IntAnnotation {
int value();
}
7 changes: 7 additions & 0 deletions tests/pos/t3236/LongAnnotation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface LongAnnotation {
long value();
}
7 changes: 7 additions & 0 deletions tests/pos/t3236/ShortAnnotation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface ShortAnnotation {
short value();
}
7 changes: 7 additions & 0 deletions tests/pos/t3236/StringAnnotation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface StringAnnotation {
String value();
}
44 changes: 44 additions & 0 deletions tests/pos/t3236/Test.scala
Original file line number Diff line number Diff line change
@@ -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)
}