Skip to content

Commit 9eed6e6

Browse files
authored
Merge pull request #5371 from dotty-staging/drop-weak-conformance
Tweak and implement weak conformance replacement
2 parents d9c2335 + c13aa58 commit 9eed6e6

File tree

8 files changed

+126
-61
lines changed

8 files changed

+126
-61
lines changed

compiler/src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 32 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,8 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
509509
addTyped(arg, formal)
510510
case _ =>
511511
val elemFormal = formal.widenExpr.argTypesLo.head
512-
val typedArgs = harmonic(harmonizeArgs)(args.map(typedArg(_, elemFormal)))
512+
val typedArgs =
513+
harmonic(harmonizeArgs, elemFormal)(args.map(typedArg(_, elemFormal)))
513514
typedArgs.foreach(addArg(_, elemFormal))
514515
makeVarArg(args.length, elemFormal)
515516
}
@@ -1555,38 +1556,33 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
15551556
}
15561557

15571558
private def harmonizeWith[T <: AnyRef](ts: List[T])(tpe: T => Type, adapt: (T, Type) => T)(implicit ctx: Context): List[T] = {
1558-
def numericClasses(ts: List[T], acc: Set[Symbol]): Set[Symbol] = ts match {
1559+
def targetClass(ts: List[T], cls: Symbol, intLitSeen: Boolean): Symbol = ts match {
15591560
case t :: ts1 =>
1560-
val sym = tpe(t).widen.classSymbol
1561-
if (sym.isNumericValueClass) numericClasses(ts1, acc + sym)
1562-
else Set()
1561+
tpe(t).widenTermRefExpr match {
1562+
case ConstantType(c: Constant) if c.tag == IntTag =>
1563+
targetClass(ts1, cls, true)
1564+
case t =>
1565+
val sym = t.widen.classSymbol
1566+
if (!sym.isNumericValueClass || cls.exists && cls != sym) NoSymbol
1567+
else targetClass(ts1, sym, intLitSeen)
1568+
}
15631569
case Nil =>
1564-
acc
1570+
if (cls != defn.IntClass && intLitSeen) cls else NoSymbol
15651571
}
1566-
val clss = numericClasses(ts, Set())
1567-
if (clss.size > 1) {
1568-
def isCompatible(cls: Symbol, sup: TypeRef) =
1569-
defn.isValueSubType(cls.typeRef, sup) &&
1570-
!(cls == defn.LongClass && sup.isRef(defn.FloatClass))
1571-
// exclude Long <: Float from list of allowable widenings
1572-
// TODO: should we do this everywhere we ask for isValueSubType?
1573-
1574-
val lub = defn.ScalaNumericValueTypeList.find(lubTpe =>
1575-
clss.forall(cls => isCompatible(cls, lubTpe))).get
1576-
1577-
def lossOfPrecision(ct: Constant): Boolean =
1578-
ct.tag == IntTag && lub.isRef(defn.FloatClass) &&
1579-
ct.intValue.toFloat.toInt != ct.intValue ||
1580-
ct.tag == LongTag && lub.isRef(defn.DoubleClass) &&
1581-
ct.longValue.toDouble.toLong != ct.longValue
1582-
1572+
val cls = targetClass(ts, NoSymbol, false)
1573+
if (cls.exists) {
1574+
def lossOfPrecision(n: Int): Boolean =
1575+
cls == defn.FloatClass && n.toFloat.toInt != n
1576+
var canAdapt = true
15831577
val ts1 = ts.mapConserve { t =>
15841578
tpe(t).widenTermRefExpr match {
1585-
case ct: ConstantType if !lossOfPrecision(ct.value) => adapt(t, lub)
1579+
case ConstantType(c: Constant) if c.tag == IntTag =>
1580+
canAdapt &= c.convertTo(cls.typeRef) != null && !lossOfPrecision(c.intValue)
1581+
if (canAdapt) adapt(t, cls.typeRef) else t
15861582
case _ => t
15871583
}
15881584
}
1589-
if (numericClasses(ts1, Set()).size == 1) ts1 else ts
1585+
if (canAdapt) ts1 else ts
15901586
}
15911587
else ts
15921588
}
@@ -1603,7 +1599,8 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
16031599
if (ctx.isAfterTyper) trees else harmonizeWith(trees)(_.tpe, adaptDeep)
16041600
}
16051601

1606-
/** Apply a transformation `harmonize` on the results of operation `op`.
1602+
/** Apply a transformation `harmonize` on the results of operation `op`,
1603+
* unless the expected type `pt` is fully defined.
16071604
* If the result is different (wrt eq) from the original results of `op`,
16081605
* revert back to the constraint in force before computing `op`.
16091606
* This reset is needed because otherwise the original results might
@@ -1612,13 +1609,15 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
16121609
* the result of harmomization will be compared again with the expected type.
16131610
* Test cases where this matters are in pos/harmomize.scala.
16141611
*/
1615-
def harmonic[T](harmonize: List[T] => List[T])(op: => List[T])(implicit ctx: Context): List[T] = {
1616-
val origConstraint = ctx.typerState.constraint
1617-
val origElems = op
1618-
val harmonizedElems = harmonize(origElems)
1619-
if (harmonizedElems ne origElems) ctx.typerState.constraint = origConstraint
1620-
harmonizedElems
1621-
}
1612+
def harmonic[T](harmonize: List[T] => List[T], pt: Type)(op: => List[T])(implicit ctx: Context): List[T] =
1613+
if (!isFullyDefined(pt, ForceDegree.none)) {
1614+
val origConstraint = ctx.typerState.constraint
1615+
val origElems = op
1616+
val harmonizedElems = harmonize(origElems)
1617+
if (harmonizedElems ne origElems) ctx.typerState.constraint = origConstraint
1618+
harmonizedElems
1619+
}
1620+
else op
16221621

16231622
/** If all `types` are numeric value types, and they are not all the same type,
16241623
* pick a common numeric supertype and widen any constant types in `tpes` to it.

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -706,7 +706,7 @@ class Typer extends Namer
706706

707707
def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context): Tree = track("typedIf") {
708708
val cond1 = typed(tree.cond, defn.BooleanType)
709-
val thenp2 :: elsep2 :: Nil = harmonic(harmonize) {
709+
val thenp2 :: elsep2 :: Nil = harmonic(harmonize, pt) {
710710
val thenp1 = typed(tree.thenp, pt.notApplied)
711711
val elsep1 = typed(tree.elsep orElse (untpd.unitLiteral withPos tree.pos), pt.notApplied)
712712
thenp1 :: elsep1 :: Nil
@@ -979,7 +979,7 @@ class Typer extends Namer
979979

980980
// Overridden in InlineTyper for inline matches
981981
def typedMatchFinish(tree: untpd.Match, sel: Tree, selType: Type, pt: Type)(implicit ctx: Context): Tree = {
982-
val cases1 = harmonic(harmonize)(typedCases(tree.cases, selType, pt.notApplied))
982+
val cases1 = harmonic(harmonize, pt)(typedCases(tree.cases, selType, pt.notApplied))
983983
.asInstanceOf[List[CaseDef]]
984984
assignType(cpy.Match(tree)(sel, cases1), sel, cases1)
985985
}
@@ -1135,7 +1135,7 @@ class Typer extends Namer
11351135
}
11361136

11371137
def typedTry(tree: untpd.Try, pt: Type)(implicit ctx: Context): Try = track("typedTry") {
1138-
val expr2 :: cases2x = harmonic(harmonize) {
1138+
val expr2 :: cases2x = harmonic(harmonize, pt) {
11391139
val expr1 = typed(tree.expr, pt.notApplied)
11401140
val cases1 = typedCases(tree.cases, defn.ThrowableType, pt.notApplied)
11411141
expr1 :: cases1
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
# Closure type miss match
22
eff-dependent.scala
3+
4+
# It seems we still harmonize types in fromTasty. Not sure where this happens
5+
puzzle.scala

docs/docs/reference/dropped/weak-conformance-spec.md

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,16 @@ assigning a type to a constant expression. The new rule is:
1616

1717
and all expressions have primitive numeric types, but they do not
1818
all have the same type, then the following is attempted:
19-
20-
- the expressions `Es` are partitioned into `Int` literals on the
21-
one hand, and all other expressions on the other hand
19+
20+
- the expressions `Es` are partitioned into `Int` constants on the
21+
one hand, and all other expressions on the other hand,
2222
- if all the other expressions have the same numeric type `T`
23-
(which can be one of `Byte`, `Short`, `Int`, `Long`, `Float`,
23+
(which can be one of `Byte`, `Short`, `Char`, `Int`, `Long`, `Float`,
2424
`Double`), possibly after widening, and if none of the `Int`
2525
literals would incur a loss of precision when converted to `T`,
2626
then they are thus converted (the other expressions are left
27-
unchanged regardless)
28-
- otherwise, the expressions `Es` are used unchanged
27+
unchanged regardless),
28+
- otherwise, the expressions `Es` are used unchanged.
2929

3030
A loss of precision occurs for an `Int -> Float` conversion of a constant
3131
`c` if `c.toFloat.toInt != c`. For an `Int -> Byte` conversion it occurs
@@ -36,11 +36,11 @@ __Examples:__
3636

3737
inline val b = 33
3838
def f(): Int = b + 1
39-
List(b, 33, 5.5) : List[Double] // b is an inline val
40-
List(f(), 33, 5.5) : List[AnyVal] // f() is not a constant
41-
List(5, 11L) : List[Long]
42-
List(5, 11L, 5.5) : List[AnyVal] // Long and Double found
43-
List(1.0f, 2) : List[Float]
44-
List(1.0f, 1234567890): List[AnyVal] // loss of precision
45-
List(b, 33, 'a') : List[AnyVal] // Char is not a numeric
46-
List(5.toByte, 11) : List[Byte]
39+
Array(b, 33, 5.5) : Array[Double] // b is an inline val
40+
Array(f(), 33, 5.5) : Array[AnyVal] // f() is not a constant
41+
Array(5, 11L) : Array[Long]
42+
Array(5, 11L, 5.5) : Array[AnyVal] // Long and Double found
43+
Array(1.0f, 2) : Array[Float]
44+
Array(1.0f, 1234567890): Array[AnyVal] // loss of precision
45+
Array(b, 33, 'a') : Array[Char]
46+
Array(5.toByte, 11) : Array[Byte]

tests/pos/harmonize.scala renamed to tests/neg/harmonize.scala

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ object Test {
55
val n = 1
66
inline val nn = 2
77
val y = if (x) 'A' else n
8-
val z: Int = y
8+
val z: Int = y // error: no widening
99

1010
val yy1 = n match {
1111
case 1 => 'A'
@@ -19,38 +19,47 @@ object Test {
1919
case 2 => nn
2020
case 3 => 1.0f
2121
}
22-
val zz2: Float = yy2 // widening to Float
22+
val zz2: Float = yy2 // error: no widening from Char to Float
2323

2424
val yy3 = n match {
2525
case 1 => 'A'
2626
case 2 => 3L
2727
case 3 => 1.0f
2828
}
29-
val zz3: Double = yy3 // widening to Double
29+
val zz3: Double = yy3 // error: no widening from Char to Double
3030

3131
val a = try {
3232
'A'
3333
} catch {
3434
case ex: Exception => nn
3535
case ex: Error => 3L
3636
}
37-
val b: Long = a
37+
val b: Long = a // error: no widening
38+
39+
val a_ok = try {
40+
1
41+
} catch {
42+
case ex: Exception => nn
43+
case ex: Error => 3L
44+
}
45+
46+
val b_ok: Long = a_ok // ok
3847

3948
val xs = List(1.0, nn, 'c')
40-
val ys: List[Double] = xs
49+
val ys: List[Double] = xs // error: no widening
4150

4251
}
4352

4453
inline val b = 33
4554
def f(): Int = b + 1
4655
val a1 = Array(b, 33, 'a')
47-
val b1: Array[Int] = a1
56+
val b1: Array[Int] = a1 // error: no widening
4857
val a2 = Array(b, 33, 'a', f())
49-
val b2: Array[Int] = a2
58+
val b2: Array[Int] = a2 // error: no widening
5059
val a3 = Array(1.0f, 'a', 0)
51-
val b3: Array[Float] = a3
60+
val b3: Array[Float] = a3 // error: no widening
5261
val a4 = Array(1.0f, 1L)
53-
val b4: Array[Double] = a4
62+
val b4: Array[Double] = a4 // error: no widening
5463
val a5 = Array(1.0f, 1L, f())
5564
val b5: Array[AnyVal] = a5
5665
val a6 = Array(1.0f, 1234567890)

tests/run/puzzle.check

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
53.0
2-
53.0
1+
5
2+
5

tests/run/puzzle.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// no longer a puzzler, now prints 5, 5
12
object Test {
23

34
def main(args: Array[String]): Unit = {

tests/run/weak-conformance.scala

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
object Test extends App {
2+
inline val b = 33
3+
4+
locally {
5+
def f(): Int = b + 1
6+
val x1 = List(b, 33, 5.5) ; x1: List[Double] // b is an inline val
7+
val x2 = List(f(), 33, 5.5) ; x2: List[AnyVal] // f() is not a constant
8+
val x3 = List(5, 11L) ; x3: List[Long]
9+
val x4 = List(5, 11L, 5.5) ; x4: List[AnyVal] // Long and Double found
10+
val x5 = List(1.0f, 2) ; x5: List[Float]
11+
val x6 = List(1.0f, 1234567890); x6: List[AnyVal] // loss of precision
12+
val x7 = List(b, 33, 'a') ; x7: List[Char]
13+
val x8 = List(5.toByte, 11) ; x8: List[Byte]
14+
15+
val x9: List[AnyVal] = List(1.0f, 0)
16+
assert(x9(0).getClass == classOf[java.lang.Float])
17+
assert(x9(1).getClass == classOf[java.lang.Float]) // expected type not fully defined, since `List` is covariant
18+
val x10 = List[Any](1.0f, 0)
19+
assert(x10(0).getClass == classOf[java.lang.Float])
20+
assert(x10(1).getClass == classOf[java.lang.Integer])
21+
}
22+
23+
locally {
24+
def f(): Int = b + 1
25+
val x1 = Array(b, 33, 5.5) ; x1: Array[Double] // b is an inline val
26+
val x2 = Array(f(), 33, 5.5) ; x2: Array[AnyVal] // f() is not a constant
27+
val x3 = Array(5, 11L) ; x3: Array[Long]
28+
val x4 = Array(5, 11L, 5.5) ; x4: Array[AnyVal] // Long and Double found
29+
val x5 = Array(1.0f, 2) ; x5: Array[Float]
30+
val x6 = Array(1.0f, 1234567890); x6: Array[AnyVal] // loss of precision
31+
val x7 = Array(b, 33, 'a') ; x7: Array[Char]
32+
val x8 = Array(5.toByte, 11) ; x8: Array[Byte]
33+
34+
val x9: Array[AnyVal] = Array(1.0f, 0)
35+
assert(x9(0).getClass == classOf[java.lang.Float])
36+
assert(x9(1).getClass == classOf[java.lang.Integer]) // expected type fully defined since Array is nonvariant
37+
val x10 = Array[Any](1.0f, 0)
38+
assert(x10(0).getClass == classOf[java.lang.Float])
39+
assert(x10(1).getClass == classOf[java.lang.Integer])
40+
}
41+
42+
locally {
43+
def f(): Int = b + 1
44+
val x1 = if (true) b else if (true) 33 else 5.5 ; x1: Double // b is an inline val
45+
val x2 = if (true) f() else if (true) 33 else 5.5) ; x2: AnyVal // f() is not a constant
46+
val x3 = if (true) 5 else 11L) ; x3: Long
47+
val x4 = if (true) 5 else if (true) 11L else 5.5) ; x4: AnyVal // Long and Double found
48+
val x5 = if (true) 1.0f else 2) ; x5: Float
49+
val x6 = if (true) 1.0f else 1234567890) ; x6: AnyVal // loss of precision
50+
val x7 = if (true) b else if (true) 33 else 'a') ; x7: Char
51+
val x8 = if (true) 5.toByte else 11) ; x8: Byte
52+
}
53+
}

0 commit comments

Comments
 (0)