Skip to content

Commit 421f295

Browse files
committed
Introduce harmonization of numeric arguments
Harmonization is Dotty's alternative to Scala 2's notion of weak conformance. It is less powerful but also less entangled with the core type system. The idea is that in some specific contexts trees that all have primitive numeric types will be converted as necessary so that they all have the same numeric type. These tree sets are: - the two branches of an if - the alternatives of a match - the body together with the catch blocks of a try - the arguments of a vararg parameter Examples are in the test file, harmonize.scala.
1 parent 8dd3466 commit 421f295

File tree

3 files changed

+70
-15
lines changed

3 files changed

+70
-15
lines changed

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

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ trait Applications extends Compatibility { self: Typer =>
127127
*/
128128
protected def makeVarArg(n: Int, elemFormal: Type): Unit
129129

130+
/** If all `args` have primitive numeric types, make sure it's the same one */
131+
protected def harmonizeArgs(args: List[TypedArg]): List[TypedArg]
132+
130133
/** Signal failure with given message at position of given argument */
131134
protected def fail(msg: => String, arg: Arg): Unit
132135

@@ -334,7 +337,14 @@ trait Applications extends Compatibility { self: Typer =>
334337
addTyped(arg, formal)
335338
case _ =>
336339
val elemFormal = formal.widenExpr.argTypesLo.head
337-
args foreach (addTyped(_, elemFormal))
340+
val origConstraint = ctx.typerState.constraint
341+
var typedArgs = args.map(typedArg(_, elemFormal))
342+
val harmonizedArgs = harmonizeArgs(typedArgs)
343+
if (harmonizedArgs ne typedArgs) {
344+
ctx.typerState.constraint = origConstraint
345+
typedArgs = harmonizedArgs
346+
}
347+
typedArgs.foreach(addArg(_, elemFormal))
338348
makeVarArg(args.length, elemFormal)
339349
}
340350
else args match {
@@ -389,6 +399,7 @@ trait Applications extends Compatibility { self: Typer =>
389399
def argType(arg: Tree, formal: Type): Type = normalize(arg.tpe, formal)
390400
def treeToArg(arg: Tree): Tree = arg
391401
def isVarArg(arg: Tree): Boolean = tpd.isWildcardStarArg(arg)
402+
def harmonizeArgs(args: List[Tree]) = harmonize(args)
392403
}
393404

394405
/** Subclass of Application for applicability tests with type arguments and value
@@ -405,6 +416,7 @@ trait Applications extends Compatibility { self: Typer =>
405416
def argType(arg: Type, formal: Type): Type = arg
406417
def treeToArg(arg: Tree): Type = arg.tpe
407418
def isVarArg(arg: Type): Boolean = arg.isRepeatedParam
419+
def harmonizeArgs(args: List[Type]) = harmonizeTypes(args)
408420
}
409421

410422
/** Subclass of Application for type checking an Apply node, where
@@ -430,6 +442,8 @@ trait Applications extends Compatibility { self: Typer =>
430442
typedArgBuf += seqToRepeated(seqLit)
431443
}
432444

445+
def harmonizeArgs(args: List[TypedArg]) = harmonize(args)
446+
433447
override def appPos = app.pos
434448

435449
def fail(msg: => String, arg: Trees.Tree[T]) = {
@@ -1025,25 +1039,34 @@ trait Applications extends Compatibility { self: Typer =>
10251039
}
10261040
}
10271041

1028-
def harmonize(trees: List[Tree])(implicit ctx: Context): List[Tree] = {
1029-
def numericClasses(trees: List[Tree], acc: Set[Symbol]): Set[Symbol] = trees match {
1030-
case tree :: trees1 =>
1031-
val sym = tree.tpe.typeSymbol
1032-
if (sym.isNumericValueClass && tree.tpe.isRef(sym))
1033-
numericClasses(trees1, acc + sym)
1034-
else
1035-
Set()
1042+
private def harmonizeWith[T <: AnyRef](ts: List[T])(tpe: T => Type, adapt: (T, Type) => T)(implicit ctx: Context): List[T] = {
1043+
def numericClasses(ts: List[T], acc: Set[Symbol]): Set[Symbol] = ts match {
1044+
case t :: ts1 =>
1045+
val sym = tpe(t).widen.classSymbol
1046+
if (sym.isNumericValueClass) numericClasses(ts1, acc + sym)
1047+
else Set()
10361048
case Nil =>
10371049
acc
10381050
}
1039-
val clss = numericClasses(trees, Set())
1051+
val clss = numericClasses(ts, Set())
10401052
if (clss.size > 1) {
10411053
val lub = defn.ScalaNumericValueClassList.find(lubCls =>
10421054
clss.forall(defn.isValueSubClass(_, lubCls))).get.typeRef
1043-
trees.mapConserve(tree => adaptInterpolated(tree, lub, tree))
1055+
ts.mapConserve(adapt(_, lub))
10441056
}
1045-
else trees
1057+
else ts
10461058
}
1059+
1060+
def harmonize(trees: List[Tree])(implicit ctx: Context): List[Tree] = {
1061+
def adapt(tree: Tree, pt: Type): Tree = tree match {
1062+
case cdef: CaseDef => tpd.cpy.CaseDef(cdef)(body = adapt(cdef.body, pt))
1063+
case _ => adaptInterpolated(tree, pt, tree)
1064+
}
1065+
harmonizeWith(trees)(_.tpe, adapt)
1066+
}
1067+
1068+
def harmonizeTypes(tpes: List[Type])(implicit ctx: Context): List[Type] =
1069+
harmonizeWith(tpes)(identity, (tp, pt) => pt)
10471070
}
10481071

10491072
/*

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
493493
val cond1 = typed(tree.cond, defn.BooleanType)
494494
val thenp1 = typed(tree.thenp, pt)
495495
val elsep1 = typed(tree.elsep orElse untpd.unitLiteral withPos tree.pos, pt)
496-
assignType(cpy.If(tree)(cond1, thenp1, elsep1), thenp1, elsep1)
496+
val thenp2 :: elsep2 :: Nil = harmonize(thenp1 :: elsep1 :: Nil)
497+
assignType(cpy.If(tree)(cond1, thenp2, elsep2), thenp2, elsep2)
497498
}
498499

499500
def typedFunction(tree: untpd.Function, pt: Type)(implicit ctx: Context) = track("typedFunction") {
@@ -629,7 +630,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
629630
fullyDefinedType(sel1.tpe, "pattern selector", tree.pos))
630631

631632
val cases1 = typedCases(tree.cases, selType, pt)
632-
assignType(cpy.Match(tree)(sel1, cases1), cases1)
633+
val cases2 = harmonize(cases1).asInstanceOf[List[CaseDef]]
634+
assignType(cpy.Match(tree)(sel1, cases2), cases2)
633635
}
634636
}
635637

@@ -737,7 +739,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
737739
val expr1 = typed(tree.expr, pt)
738740
val cases1 = typedCases(tree.cases, defn.ThrowableType, pt)
739741
val finalizer1 = typed(tree.finalizer, defn.UnitType)
740-
assignType(cpy.Try(tree)(expr1, cases1, finalizer1), expr1, cases1)
742+
val expr2 :: cases2x = harmonize(expr1 :: cases1)
743+
val cases2 = cases2x.asInstanceOf[List[CaseDef]]
744+
assignType(cpy.Try(tree)(expr2, cases2, finalizer1), expr2, cases2)
741745
}
742746

743747
def typedThrow(tree: untpd.Throw)(implicit ctx: Context): Tree = track("typedThrow") {

tests/pos/harmonize.scala

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
object Test {
2+
3+
def main(args: Array[String]) = {
4+
val x = true
5+
val n = 1
6+
/* val y = if (x) 'A' else n
7+
val z: Int = y
8+
9+
val yy = n match {
10+
case 1 => 'A'
11+
case 2 => n
12+
case 3 => 1.0
13+
}
14+
val zz: Double = yy
15+
16+
val a = try {
17+
'A'
18+
} catch {
19+
case ex: Exception => n
20+
case ex: Error => 3L
21+
}
22+
val b: Long = a
23+
*/
24+
val xs = List(1.0, n, 'c')
25+
val ys: List[Double] = xs
26+
}
27+
28+
}

0 commit comments

Comments
 (0)