Skip to content

Commit 1c59175

Browse files
committed
Favour error over implicit conversions with generic number literals
1 parent d06a5ff commit 1c59175

File tree

6 files changed

+106
-15
lines changed

6 files changed

+106
-15
lines changed

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -689,9 +689,12 @@ class Definitions {
689689
@tu lazy val Stats_doRecord: Symbol = StatsModule.requiredMethod("doRecord")
690690

691691
@tu lazy val FromDigitsClass: ClassSymbol = ctx.requiredClass("scala.util.FromDigits")
692-
@tu lazy val FromDigits_WithRadixClass: ClassSymbol = ctx.requiredClass("scala.util.FromDigits.WithRadix")
693-
@tu lazy val FromDigits_DecimalClass: ClassSymbol = ctx.requiredClass("scala.util.FromDigits.Decimal")
694-
@tu lazy val FromDigits_FloatingClass: ClassSymbol = ctx.requiredClass("scala.util.FromDigits.Floating")
692+
@tu lazy val FromDigits_WithRadixClass: ClassSymbol = ctx.requiredClass("scala.util.FromDigits.WithRadix")
693+
@tu lazy val FromDigitsModule: TermSymbol = ctx.requiredModule("scala.util.FromDigits")
694+
@tu lazy val FromDigits_fromDigits: Symbol = FromDigitsModule.requiredMethod(nme.fromDigits)
695+
@tu lazy val FromDigits_fromRadixDigits: Symbol = FromDigitsModule.requiredMethod(nme.fromRadixDigits)
696+
@tu lazy val FromDigits_fromDecimalDigits: Symbol = FromDigitsModule.requiredMethod(nme.fromDecimalDigits)
697+
@tu lazy val FromDigits_fromFloatingDigits: Symbol = FromDigitsModule.requiredMethod(nme.fromFloatingDigits)
695698

696699
@tu lazy val XMLTopScopeModule: Symbol = ctx.requiredModule("scala.xml.TopScope")
697700

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,9 @@ object StdNames {
444444
val flatMap: N = "flatMap"
445445
val foreach: N = "foreach"
446446
val fromDigits: N = "fromDigits"
447+
val fromRadixDigits: N = "fromRadixDigits"
448+
val fromDecimalDigits: N = "fromDecimalDigits"
449+
val fromFloatingDigits: N = "fromFloatingDigits"
447450
val fromProduct: N = "fromProduct"
448451
val genericArrayOps: N = "genericArrayOps"
449452
val genericClass: N = "genericClass"

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

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -528,20 +528,23 @@ class Typer extends Namer
528528
return lit(doubleFromDigits(digits))
529529
else if (target.isValueType && isFullyDefined(target, ForceDegree.none)) {
530530
// If expected type is defined with a FromDigits instance, use that one
531-
val fromDigitsCls = tree.kind match {
532-
case Whole(10) => defn.FromDigitsClass
533-
case Whole(_) => defn.FromDigits_WithRadixClass
534-
case Decimal => defn.FromDigits_DecimalClass
535-
case Floating => defn.FromDigits_FloatingClass
536-
}
537-
inferImplicit(fromDigitsCls.typeRef.appliedTo(target), EmptyTree, tree.span) match {
531+
val summoner = tree.kind match
532+
case Whole(10) => defn.FromDigits_fromDigits
533+
case Whole(_) => defn.FromDigits_fromRadixDigits
534+
case Decimal => defn.FromDigits_fromDecimalDigits
535+
case Floating => defn.FromDigits_fromFloatingDigits
536+
inferImplicit(defn.FromDigitsClass.typeRef.appliedTo(target), EmptyTree, tree.span) match {
538537
case SearchSuccess(arg, _, _) =>
539-
val fromDigits = untpd.Select(untpd.TypedSplice(arg), nme.fromDigits).withSpan(tree.span)
538+
val summoned = untpd.Apply(untpd.TypedSplice(ref(summoner)), untpd.TypedSplice(arg) :: Nil).setGivenApply()
539+
var fromDigits: untpd.Tree = untpd.Select(summoned, nme.fromDigits).withSpan(tree.span)
540540
val firstArg = Literal(Constant(digits))
541-
val otherArgs = tree.kind match {
542-
case Whole(r) if r != 10 => Literal(Constant(r)) :: Nil
543-
case _ => Nil
544-
}
541+
val otherArgs =
542+
if arg.tpe.widen.classSymbol.isSubClass(defn.FromDigits_WithRadixClass) then
543+
tree.kind match
544+
case Whole(r) if r != 10 => Literal(Constant(r)) :: Nil
545+
case _ => Nil
546+
else
547+
Nil
545548
var app: untpd.Tree = untpd.Apply(fromDigits, firstArg :: otherArgs)
546549
if (ctx.mode.is(Mode.Pattern)) app = untpd.Block(Nil, app)
547550
return typed(app, pt)

library/src/scala/util/FromDigits.scala

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,30 @@ object FromDigits {
4747
*/
4848
trait Floating[T] extends Decimal[T]
4949

50+
inline def fromDigits[T](given x: FromDigits[T]): x.type = x
51+
52+
inline def fromRadixDigits[T](given x: FromDigits[T]): x.type =
53+
${summonDigitsImpl[x.type, FromDigits.WithRadix[T]]('x)}
54+
55+
inline def fromDecimalDigits[T](given x: FromDigits[T]): x.type =
56+
${summonDigitsImpl[x.type, FromDigits.Decimal[T]]('x)}
57+
58+
inline def fromFloatingDigits[T](given x: FromDigits[T]): x.type =
59+
${summonDigitsImpl[x.type, FromDigits.Floating[T]]('x)}
60+
61+
private def summonDigitsImpl[T <: FromDigits[_], U <: FromDigits[_]](x: Expr[T])(given
62+
qctx: QuoteContext, t: Type[T], u: Type[U]): Expr[T] = {
63+
import qctx.tasty.{Type => _, _, given}
64+
def makeError = s"""|FromDigits instance is incompatible with the expected numeric kind.
65+
| Found: ${t.show}(${x.show})
66+
| Expected: ${u.show}""".stripMargin
67+
if typeOf[T] <:< typeOf[U] then
68+
x
69+
else
70+
qctx.error(makeError, x)
71+
Expr.nullExpr.cast[T]
72+
}
73+
5074
/** The base type for exceptions that can be thrown from
5175
* `fromDigits` conversions
5276
*/
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
-- Error: tests/neg-macros/generic-num-lits.scala:2:8 ------------------------------------------------------------------
2+
2 |val b = 0xcafebabe: BigDecimal // error
3+
| ^
4+
| FromDigits instance is incompatible with the expected numeric kind.
5+
| Found: scala.util.FromDigits.BigDecimalFromDigits.type(scala.util.FromDigits.BigDecimalFromDigits)
6+
| Expected: scala.util.FromDigits.WithRadix[scala.math.BigDecimal]
7+
| This location is in code that was inlined at generic-num-lits.scala:2
8+
-- Error: tests/neg-macros/generic-num-lits.scala:3:8 ------------------------------------------------------------------
9+
3 |val c = 1.3: BigInt // error
10+
| ^
11+
| FromDigits instance is incompatible with the expected numeric kind.
12+
| Found: scala.util.FromDigits.BigIntFromDigits.type(scala.util.FromDigits.BigIntFromDigits)
13+
| Expected: scala.util.FromDigits.Decimal[scala.math.BigInt]
14+
| This location is in code that was inlined at generic-num-lits.scala:3
15+
-- Error: tests/neg-macros/generic-num-lits.scala:4:8 ------------------------------------------------------------------
16+
4 |val d = 2e500: BigInt // error
17+
| ^
18+
| FromDigits instance is incompatible with the expected numeric kind.
19+
| Found: scala.util.FromDigits.BigIntFromDigits.type(scala.util.FromDigits.BigIntFromDigits)
20+
| Expected: scala.util.FromDigits.Floating[scala.math.BigInt]
21+
| This location is in code that was inlined at generic-num-lits.scala:4
22+
-- Error: tests/neg-macros/generic-num-lits.scala:7:7 ------------------------------------------------------------------
23+
7 | case 0xcafebabe: BigDecimal => // error
24+
| ^
25+
| FromDigits instance is incompatible with the expected numeric kind.
26+
| Found: scala.util.FromDigits.BigDecimalFromDigits.type(scala.util.FromDigits.BigDecimalFromDigits)
27+
| Expected: scala.util.FromDigits.WithRadix[scala.math.BigDecimal]
28+
| This location is in code that was inlined at generic-num-lits.scala:7
29+
-- Error: tests/neg-macros/generic-num-lits.scala:11:7 -----------------------------------------------------------------
30+
11 | case 1.3: BigInt => // error
31+
| ^
32+
| FromDigits instance is incompatible with the expected numeric kind.
33+
| Found: scala.util.FromDigits.BigIntFromDigits.type(scala.util.FromDigits.BigIntFromDigits)
34+
| Expected: scala.util.FromDigits.Decimal[scala.math.BigInt]
35+
| This location is in code that was inlined at generic-num-lits.scala:11
36+
-- Error: tests/neg-macros/generic-num-lits.scala:15:7 -----------------------------------------------------------------
37+
15 | case 2e500: BigInt => // error
38+
| ^
39+
| FromDigits instance is incompatible with the expected numeric kind.
40+
| Found: scala.util.FromDigits.BigIntFromDigits.type(scala.util.FromDigits.BigIntFromDigits)
41+
| Expected: scala.util.FromDigits.Floating[scala.math.BigInt]
42+
| This location is in code that was inlined at generic-num-lits.scala:15
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
val a = 0.25: BigDecimal
2+
val b = 0xcafebabe: BigDecimal // error
3+
val c = 1.3: BigInt // error
4+
val d = 2e500: BigInt // error
5+
6+
val e = (??? : Any) match
7+
case 0xcafebabe: BigDecimal => // error
8+
()
9+
10+
val f = (??? : Any) match
11+
case 1.3: BigInt => // error
12+
()
13+
14+
val g = (??? : Any) match
15+
case 2e500: BigInt => // error
16+
()

0 commit comments

Comments
 (0)