Skip to content

Commit adefa48

Browse files
authored
Merge pull request #13417 from dotty-staging/allow-experimental-imports-in-experimetnal-scopes
2 parents f8dec07 + 3fb1b6d commit adefa48

22 files changed

+544
-296
lines changed

compiler/src/dotty/tools/dotc/config/Feature.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,9 @@ object Feature:
9898
else
9999
false
100100

101-
def checkExperimentalFeature(which: String, srcPos: SrcPos)(using Context) =
101+
def checkExperimentalFeature(which: String, srcPos: SrcPos, note: => String = "")(using Context) =
102102
if !isExperimentalEnabled then
103-
report.error(i"Experimental $which may only be used with a nightly or snapshot version of the compiler", srcPos)
103+
report.error(i"Experimental $which may only be used with a nightly or snapshot version of the compiler$note", srcPos)
104104

105105
def checkExperimentalDef(sym: Symbol, srcPos: SrcPos)(using Context) =
106106
if !isExperimentalEnabled then

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3114,10 +3114,6 @@ object Parsers {
31143114
languageImport(tree) match
31153115
case Some(prefix) =>
31163116
in.languageImportContext = in.languageImportContext.importContext(imp, NoSymbol)
3117-
if prefix == nme.experimental
3118-
&& selectors.exists(sel => Feature.experimental(sel.name) != Feature.scala2macros && Feature.experimental(sel.name) != Feature.erasedDefinitions)
3119-
then
3120-
Feature.checkExperimentalFeature("features", imp.srcPos)
31213117
for
31223118
case ImportSelector(id @ Ident(imported), EmptyTree, _) <- selectors
31233119
if allSourceVersionNames.contains(imported)

compiler/src/dotty/tools/dotc/transform/PostTyper.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,10 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
448448
throw ex
449449
}
450450

451+
override def transformStats(trees: List[Tree], exprOwner: Symbol)(using Context): List[Tree] =
452+
try super.transformStats(trees, exprOwner)
453+
finally Checking.checkExperimentalImports(trees)
454+
451455
/** Transforms the rhs tree into a its default tree if it is in an `erased` val/def.
452456
* Performed to shrink the tree that is known to be erased later.
453457
*/

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

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,50 @@ object Checking {
721721
checkValue(tree)
722722
case _ =>
723723
tree
724+
725+
/** Check that experimental language imports in `trees`
726+
* are done only in experimental scopes, or in a top-level
727+
* scope with only @experimental definitions.
728+
*/
729+
def checkExperimentalImports(trees: List[Tree])(using Context): Unit =
730+
731+
def nonExperimentalStat(trees: List[Tree]): Tree = trees match
732+
case (_: Import | EmptyTree) :: rest =>
733+
nonExperimentalStat(rest)
734+
case (tree @ TypeDef(_, impl: Template)) :: rest if tree.symbol.isPackageObject =>
735+
nonExperimentalStat(impl.body).orElse(nonExperimentalStat(rest))
736+
case (tree: PackageDef) :: rest =>
737+
nonExperimentalStat(tree.stats).orElse(nonExperimentalStat(rest))
738+
case (tree: MemberDef) :: rest =>
739+
if tree.symbol.isExperimental || tree.symbol.is(Synthetic) then
740+
nonExperimentalStat(rest)
741+
else
742+
tree
743+
case tree :: rest =>
744+
tree
745+
case Nil =>
746+
EmptyTree
747+
748+
for case imp @ Import(qual, selectors) <- trees do
749+
def isAllowedImport(sel: untpd.ImportSelector) =
750+
val name = Feature.experimental(sel.name)
751+
name == Feature.scala2macros || name == Feature.erasedDefinitions
752+
753+
languageImport(qual) match
754+
case Some(nme.experimental)
755+
if !ctx.owner.isInExperimentalScope && !selectors.forall(isAllowedImport) =>
756+
def check(stable: => String) =
757+
Feature.checkExperimentalFeature("features", imp.srcPos,
758+
s"\n\nNote: the scope enclosing the import is not considered experimental because it contains the\nnon-experimental $stable")
759+
if ctx.owner.is(Package) then
760+
// allow top-level experimental imports if all definitions are @experimental
761+
nonExperimentalStat(trees) match
762+
case EmptyTree =>
763+
case tree: MemberDef => check(i"${tree.symbol}")
764+
case tree => check(i"expression ${tree}")
765+
else Feature.checkExperimentalFeature("features", imp.srcPos)
766+
case _ =>
767+
end checkExperimentalImports
724768
}
725769

726770
trait Checking {

compiler/test/dotty/tools/dotc/CompilationTests.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class CompilationTests {
4242
compileFilesInDir("tests/pos-custom-args/erased", defaultOptions.and("-language:experimental.erasedDefinitions")),
4343
compileFilesInDir("tests/pos", defaultOptions.and("-Ysafe-init")),
4444
compileFilesInDir("tests/pos-deep-subtype", allowDeepSubtypes),
45+
compileFilesInDir("tests/pos-custom-args/no-experimental", defaultOptions.and("-Yno-experimental")),
4546
compileDir("tests/pos-special/java-param-names", defaultOptions.withJavacOnlyOptions("-parameters")),
4647
compileFile(
4748
// succeeds despite -Xfatal-warnings because of -nowarn
@@ -178,7 +179,7 @@ class CompilationTests {
178179
compileFile("tests/neg-custom-args/matchable.scala", defaultOptions.and("-Xfatal-warnings", "-source", "future")),
179180
compileFile("tests/neg-custom-args/i7314.scala", defaultOptions.and("-Xfatal-warnings", "-source", "future")),
180181
compileFile("tests/neg-custom-args/feature-shadowing.scala", defaultOptions.and("-Xfatal-warnings", "-feature")),
181-
compileDir("tests/neg-custom-args/hidden-type-errors", defaultOptions.and("-explain")),
182+
compileDir("tests/neg-custom-args/hidden-type-errors", defaultOptions.and("-explain")),
182183
).checkExpectedErrors()
183184
}
184185

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

Lines changed: 1 addition & 256 deletions
Original file line numberDiff line numberDiff line change
@@ -4,259 +4,4 @@ title: "Numeric Literals"
44
movedTo: https://docs.scala-lang.org/scala3/reference/changed-features/numeric-literals.html
55
---
66

7-
**Note**: This feature is not yet part of the Scala 3 language definition. It can be made available by a language import:
8-
9-
```scala
10-
import scala.language.experimental.genericNumberLiterals
11-
```
12-
13-
In Scala 2, numeric literals were confined to the primitive numeric types `Int`, `Long`, `Float`, and `Double`. Scala 3 allows to write numeric literals also for user-defined types. Example:
14-
15-
```scala
16-
val x: Long = -10_000_000_000
17-
val y: BigInt = 0x123_abc_789_def_345_678_901
18-
val z: BigDecimal = 110_222_799_799.99
19-
20-
(y: BigInt) match
21-
case 123_456_789_012_345_678_901 =>
22-
```
23-
24-
The syntax of numeric literals is the same as before, except there are no pre-set limits
25-
how large they can be.
26-
27-
### Meaning of Numeric Literals
28-
29-
The meaning of a numeric literal is determined as follows:
30-
31-
- If the literal ends with `l` or `L`, it is a `Long` integer (and must fit in its legal range).
32-
- If the literal ends with `f` or `F`, it is a single precision floating point number of type `Float`.
33-
- If the literal ends with `d` or `D`, it is a double precision floating point number of type `Double`.
34-
35-
In each of these cases the conversion to a number is exactly as in Scala 2 or in Java. If a numeric literal does _not_ end in one of these suffixes, its meaning is determined by the expected type:
36-
37-
1. If the expected type is `Int`, `Long`, `Float`, or `Double`, the literal is
38-
treated as a standard literal of that type.
39-
2. If the expected type is a fully defined type `T` that has a given instance of type
40-
`scala.util.FromDigits[T]`, the literal is converted to a value of type `T` by passing it as an argument to
41-
the `fromDigits` method of that instance (more details below).
42-
3. Otherwise, the literal is treated as a `Double` literal (if it has a decimal point or an
43-
exponent), or as an `Int` literal (if not). (This last possibility is again as in Scala 2 or Java.)
44-
45-
With these rules, the definition
46-
47-
```scala
48-
val x: Long = -10_000_000_000
49-
```
50-
51-
is legal by rule (1), since the expected type is `Long`. The definitions
52-
53-
```scala
54-
val y: BigInt = 0x123_abc_789_def_345_678_901
55-
val z: BigDecimal = 111222333444.55
56-
```
57-
58-
are legal by rule (2), since both `BigInt` and `BigDecimal` have `FromDigits` instances
59-
(which implement the `FromDigits` subclasses `FromDigits.WithRadix` and `FromDigits.Decimal`, respectively).
60-
On the other hand,
61-
62-
```scala
63-
val x = -10_000_000_000
64-
```
65-
66-
gives a type error, since without an expected type `-10_000_000_000` is treated by rule (3) as an `Int` literal, but it is too large for that type.
67-
68-
### The FromDigits Trait
69-
70-
To allow numeric literals, a type simply has to define a `given` instance of the
71-
`scala.util.FromDigits` type class, or one of its subclasses. `FromDigits` is defined
72-
as follows:
73-
74-
```scala
75-
trait FromDigits[T]:
76-
def fromDigits(digits: String): T
77-
```
78-
79-
Implementations of the `fromDigits` convert strings of digits to the values of the
80-
implementation type `T`.
81-
The `digits` string consists of digits between `0` and `9`, possibly preceded by a
82-
sign ("+" or "-"). Number separator characters `_` are filtered out before
83-
the string is passed to `fromDigits`.
84-
85-
The companion object `FromDigits` also defines subclasses of `FromDigits` for
86-
whole numbers with a given radix, for numbers with a decimal point, and for
87-
numbers that can have both a decimal point and an exponent:
88-
89-
```scala
90-
object FromDigits:
91-
92-
/** A subclass of `FromDigits` that also allows to convert whole
93-
* number literals with a radix other than 10
94-
*/
95-
trait WithRadix[T] extends FromDigits[T]:
96-
def fromDigits(digits: String): T = fromDigits(digits, 10)
97-
def fromDigits(digits: String, radix: Int): T
98-
99-
/** A subclass of `FromDigits` that also allows to convert number
100-
* literals containing a decimal point ".".
101-
*/
102-
trait Decimal[T] extends FromDigits[T]
103-
104-
/** A subclass of `FromDigits`that allows also to convert number
105-
* literals containing a decimal point "." or an
106-
* exponent `('e' | 'E')['+' | '-']digit digit*`.
107-
*/
108-
trait Floating[T] extends Decimal[T]
109-
```
110-
111-
A user-defined number type can implement one of those, which signals to the compiler
112-
that hexadecimal numbers, decimal points, or exponents are also accepted in literals
113-
for this type.
114-
115-
### Error Handling
116-
117-
`FromDigits` implementations can signal errors by throwing exceptions of some subtype
118-
of `FromDigitsException`. `FromDigitsException` is defined with three subclasses in the
119-
`FromDigits` object as follows:
120-
121-
```scala
122-
abstract class FromDigitsException(msg: String) extends NumberFormatException(msg)
123-
124-
class NumberTooLarge (msg: String = "number too large") extends FromDigitsException(msg)
125-
class NumberTooSmall (msg: String = "number too small") extends FromDigitsException(msg)
126-
class MalformedNumber(msg: String = "malformed number literal") extends FromDigitsException(msg)
127-
```
128-
129-
### Example
130-
131-
As a fully worked out example, here is an implementation of a new numeric class, `BigFloat`, that accepts numeric literals. `BigFloat` is defined in terms of a `BigInt` mantissa and an `Int` exponent:
132-
133-
```scala
134-
case class BigFloat(mantissa: BigInt, exponent: Int):
135-
override def toString = s"${mantissa}e${exponent}"
136-
```
137-
138-
`BigFloat` literals can have a decimal point as well as an exponent. E.g. the following expression
139-
should produce the `BigFloat` number `BigFloat(-123, 997)`:
140-
141-
```scala
142-
-0.123E+1000: BigFloat
143-
```
144-
145-
The companion object of `BigFloat` defines an `apply` constructor method to construct a `BigFloat`
146-
from a `digits` string. Here is a possible implementation:
147-
148-
```scala
149-
object BigFloat:
150-
import scala.util.FromDigits
151-
152-
def apply(digits: String): BigFloat =
153-
val (mantissaDigits, givenExponent) =
154-
digits.toUpperCase.split('E') match
155-
case Array(mantissaDigits, edigits) =>
156-
val expo =
157-
try FromDigits.intFromDigits(edigits)
158-
catch case ex: FromDigits.NumberTooLarge =>
159-
throw FromDigits.NumberTooLarge(s"exponent too large: $edigits")
160-
(mantissaDigits, expo)
161-
case Array(mantissaDigits) =>
162-
(mantissaDigits, 0)
163-
val (intPart, exponent) =
164-
mantissaDigits.split('.') match
165-
case Array(intPart, decimalPart) =>
166-
(intPart ++ decimalPart, givenExponent - decimalPart.length)
167-
case Array(intPart) =>
168-
(intPart, givenExponent)
169-
BigFloat(BigInt(intPart), exponent)
170-
```
171-
172-
To accept `BigFloat` literals, all that's needed in addition is a `given` instance of type
173-
`FromDigits.Floating[BigFloat]`:
174-
175-
```scala
176-
given FromDigits: FromDigits.Floating[BigFloat] with
177-
def fromDigits(digits: String) = apply(digits)
178-
end BigFloat
179-
```
180-
181-
Note that the `apply` method does not check the format of the `digits` argument. It is
182-
assumed that only valid arguments are passed. For calls coming from the compiler
183-
that assumption is valid, since the compiler will first check whether a numeric
184-
literal has the correct format before it gets passed on to a conversion method.
185-
186-
### Compile-Time Errors
187-
188-
With the setup of the previous section, a literal like
189-
190-
```scala
191-
1e10_0000_000_000: BigFloat
192-
```
193-
194-
would be expanded by the compiler to
195-
196-
```scala
197-
BigFloat.FromDigits.fromDigits("1e100000000000")
198-
```
199-
200-
Evaluating this expression throws a `NumberTooLarge` exception at run time. We would like it to
201-
produce a compile-time error instead. We can achieve this by tweaking the `BigFloat` class
202-
with a small dose of metaprogramming. The idea is to turn the `fromDigits` method
203-
into a macro, i.e. make it an inline method with a splice as right-hand side.
204-
To do this, replace the `FromDigits` instance in the `BigFloat` object by the following two definitions:
205-
206-
```scala
207-
object BigFloat:
208-
...
209-
210-
class FromDigits extends FromDigits.Floating[BigFloat]:
211-
def fromDigits(digits: String) = apply(digits)
212-
213-
given FromDigits with
214-
override inline def fromDigits(digits: String) = ${
215-
fromDigitsImpl('digits)
216-
}
217-
```
218-
219-
Note that an inline method cannot directly fill in for an abstract method, since it produces
220-
no code that can be executed at runtime. That is why we define an intermediary class
221-
`FromDigits` that contains a fallback implementation which is then overridden by the inline
222-
method in the `FromDigits` given instance. That method is defined in terms of a macro
223-
implementation method `fromDigitsImpl`. Here is its definition:
224-
225-
```scala
226-
private def fromDigitsImpl(digits: Expr[String])(using ctx: Quotes): Expr[BigFloat] =
227-
digits.value match
228-
case Some(ds) =>
229-
try
230-
val BigFloat(m, e) = apply(ds)
231-
'{BigFloat(${Expr(m)}, ${Expr(e)})}
232-
catch case ex: FromDigits.FromDigitsException =>
233-
ctx.error(ex.getMessage)
234-
'{BigFloat(0, 0)}
235-
case None =>
236-
'{apply($digits)}
237-
end BigFloat
238-
```
239-
240-
The macro implementation takes an argument of type `Expr[String]` and yields
241-
a result of type `Expr[BigFloat]`. It tests whether its argument is a constant
242-
string. If that is the case, it converts the string using the `apply` method
243-
and lifts the resulting `BigFloat` back to `Expr` level. For non-constant
244-
strings `fromDigitsImpl(digits)` is simply `apply(digits)`, i.e. everything is
245-
evaluated at runtime in this case.
246-
247-
The interesting part is the `catch` part of the case where `digits` is constant.
248-
If the `apply` method throws a `FromDigitsException`, the exception's message is issued as a compile time error in the `ctx.error(ex.getMessage)` call.
249-
250-
With this new implementation, a definition like
251-
252-
```scala
253-
val x: BigFloat = 1234.45e3333333333
254-
```
255-
256-
would give a compile time error message:
257-
258-
```scala
259-
3 | val x: BigFloat = 1234.45e3333333333
260-
| ^^^^^^^^^^^^^^^^^^
261-
| exponent too large: 3333333333
262-
```
7+
[Document was moved](../experimental/numeric-literals.md)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
layout: doc-page
3+
title: "Named Type Arguments"
4+
---
5+
6+
**Note:** This feature is implemented in Scala 3, but is not expected to be part of Scala 3.0.
7+
8+
Type arguments of methods can now be specified by name as well as by position. Example:
9+
10+
``` scala
11+
def construct[Elem, Coll[_]](xs: Elem*): Coll[Elem] = ???
12+
13+
val xs1 = construct[Coll = List, Elem = Int](1, 2, 3)
14+
val xs2 = construct[Coll = List](1, 2, 3)
15+
```
16+
17+
Similar to a named value argument `(x = e)`, a named type argument
18+
`[X = T]` instantiates the type parameter `X` to the type `T`.
19+
Named type arguments do not have to be in order (see `xs1` above) and
20+
unspecified arguments are inferred by the compiler (see `xs2` above).
21+
Type arguments must be all named or un-named, mixtures of named and
22+
positional type arguments are not supported.
23+
24+
## Motivation
25+
26+
The main benefit of named type arguments is that unlike positional arguments,
27+
you are allowed to omit passing arguments for some parameters, like in the
28+
definition of `xs2` above. A missing type argument is inferred as usual by
29+
local type inference. This is particularly useful in situations where some type
30+
arguments can be easily inferred from others.
31+
32+
[More details](./named-typeargs-spec.md)

0 commit comments

Comments
 (0)