Skip to content

Commit 4155cf2

Browse files
committed
more informative documentation of numeric-literals
1 parent 181e450 commit 4155cf2

File tree

2 files changed

+37
-27
lines changed

2 files changed

+37
-27
lines changed

docs/docs/reference/changed-features/numeric-literals.md

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ the string is passed to `fromDigits`.
7070

7171
The companion object `FromDigits` also defines subclasses of `FromDigits` for
7272
whole numbers with a given radix, for numbers with a decimal point, and for
73-
numbers that can have both a decimal point and an exponent:
73+
numbers that can have both a decimal point and an exponent, along with methods to summon a given instance of each subclass:
7474
```scala
7575
object FromDigits {
7676

@@ -92,12 +92,32 @@ object FromDigits {
9292
* exponent `('e' | 'E')['+' | '-']digit digit*`.
9393
*/
9494
trait Floating[T] extends Decimal[T]
95+
96+
/** Does `T` have a given `FromDigits[T]` instance?
97+
*/
98+
inline def fromDigits[T](given x: FromDigits[T]): x.type = x
99+
100+
/** Does `T` have a given `FromDigits.WithRadix[T]` instance?
101+
*/
102+
inline def fromRadixDigits[T](given x: FromDigits.WithRadix[T]): x.type = x
103+
104+
/** Does `T` have a given `FromDigits.Decimal[T]` instance?
105+
*/
106+
inline def fromDecimalDigits[T](given x: FromDigits.Decimal[T]): x.type = x
107+
108+
/** Does `T` have a given `FromDigits.Floating[T]` instance?
109+
*/
110+
inline def fromFloatingDigits[T](given x: FromDigits.Floating[T]): x.type = x
95111
...
96112
}
97113
```
98-
A user-defined number type can implement one of those, which signals to the compiler
99-
that hexadecimal numbers, decimal points, or exponents are also accepted in literals
100-
for this type.
114+
A user-defined number type, that provides a given instance of one of the above subclasses, signals to the compiler
115+
that hexadecimal numbers, decimal points, or exponents are also accepted in literals for that type. A numeric literal with a concrete type `T` and digits `digits` is replaced as follows, when a given instance for `FromDigits[T]` is available:
116+
117+
- `fromDigits[T].fromDigits(digits)` if the digits form a decimal whole number.
118+
- `fromRadixDigits[T].fromDigits(digits, 16)` if the digits form a hexadecimal whole number.
119+
- `fromDecimalDigits[T].fromDigits(digits)` if the digits are decimal with a single decimal point `"."`.
120+
- `fromFloatingDigits[T].fromDigits(digits)` if the digits are decimal with an exponent and optionally a single decimal point `"."`.
101121

102122
### Error Handling
103123

@@ -112,37 +132,19 @@ class NumberTooSmall (msg: String = "number too small") extends FromDigi
112132
class MalformedNumber(msg: String = "malformed number literal") extends FromDigitsException(msg)
113133
```
114134

115-
### Compiler Expansion
116-
117-
The companion object `FromDigits` also defines four methods that may be used to provide a given instance of one of the subclasses of `FromDigits`:
118-
```scala
119-
inline def fromDigits[T](given x: FromDigits[T]): x.type = x
120-
121-
inline def fromRadixDigits[T](given x: FromDigits.WithRadix[T]): x.type = x
122-
123-
inline def fromDecimalDigits[T](given x: FromDigits.Decimal[T]): x.type = x
124-
125-
inline def fromFloatingDigits[T](given x: FromDigits.Floating[T]): x.type = x
126-
```
127-
128-
If a numeric literal has a concrete expected type `T` that is not one of the primitive numeric types, the compiler will search for a given instance of `FromDigits[T]`. If one exists, then the compiler will replace the numeric literal with an application of its digits to the `fromDigits` method on the result of the application of `T` to one of the above four methods, resulting in the following:
129-
130-
- `fromDigits[T].fromDigits(digits)` if the literal forms a whole number with base-10.
131-
- `fromRadixDigits[T].fromDigits(digits, 16)` if the literal forms a whole number with base-16.
132-
- `fromDecimalDigits[T].fromDigits(digits)` if the literal is base-10 with a single decimal point.
133-
- `fromFloatingDigits[T].fromDigits(digits)` if the literal is base-10 with an exponent and optionally a single decimal point.
135+
### Number Format Safety
134136

135-
<!-- on the `fromDigits` method on the result obtained from calling one of the -->
137+
A benefit of implementing one of the specialised subclasses of `FromDigits` is that the compiler preserves invariants about the format of digits passed to the `fromDigits` method.
136138

137-
As an example, the literal below has a nonsensical expected type `BigDecimal`, which can not be constructed with hex digits:
139+
As an example, the constructor for `BigDecimal` can not accept digits in hexadecimal format, so it does not make sense to allow hexadecimal number literals with expected type `BigDecimal`. Below is such an expression:
138140
```scala
139141
0xABC_123: BigDecimal
140142
```
141-
Upon the compiler finding a given instance for `FromDigits[BigDecimal]`, the hex literal above expands to the following, after removing numeric separators:
143+
We can protect against this poorly formed expression by restricting the domain of acceptable number literals. The `FromDigits` companion object provides a given instance of `FromDigits.Floating[BigDecimal]`, which does not accept non-decimal literals. As described above, because the compiler can see a given instance for `FromDigits[BigDecimal]`, the hex literal is replaced with the following, after removing numeric separators:
142144
```scala
143145
FromDigits.fromRadixDigits[BigDecimal].fromDigits("0ABC123", 16)
144146
```
145-
The given clause of `fromRadixDigits` asserts that the prior found `FromDigits` instance is a subtype of `FromDigits.WithRadix[BigDecimal]`, or else following error is issued:
147+
Because the given instance for `FromDigits[BigDecimal]` in the `FromDigits` companion object is not a subtype of `FromDigits.WithRadix[BigDecimal]`, as expected by the given clause of `fromRadixDigits`, the following error is issued:
146148
```scala
147149
1 |0xABC_123: BigDecimal
148150
|^

library/src/scala/util/FromDigits.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,20 @@ object FromDigits {
5151
@implicitNotFound("Type ${T} does not have a FromDigits instance for floating-point numbers.")
5252
trait Floating[T] extends Decimal[T]
5353

54+
/** Does `T` have a given `FromDigits[T]` instance?
55+
*/
5456
inline def fromDigits[T](given x: FromDigits[T]): x.type = x
5557

58+
/** Does `T` have a given `FromDigits.WithRadix[T]` instance?
59+
*/
5660
inline def fromRadixDigits[T](given x: FromDigits.WithRadix[T]): x.type = x
5761

62+
/** Does `T` have a given `FromDigits.Decimal[T]` instance?
63+
*/
5864
inline def fromDecimalDigits[T](given x: FromDigits.Decimal[T]): x.type = x
5965

66+
/** Does `T` have a given `FromDigits.Floating[T]` instance?
67+
*/
6068
inline def fromFloatingDigits[T](given x: FromDigits.Floating[T]): x.type = x
6169

6270
/** The base type for exceptions that can be thrown from

0 commit comments

Comments
 (0)