Skip to content

Commit 193fb95

Browse files
committed
deprecate 3.1-migration, err on import
1 parent 9ed0762 commit 193fb95

File tree

8 files changed

+67
-33
lines changed

8 files changed

+67
-33
lines changed

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,12 @@ enum SourceVersion:
1717

1818
object SourceVersion extends Property.Key[SourceVersion]:
1919

20-
val allSourceVersionNames = values.toList.map(_.toString.toTermName)
20+
/** language versions that may appear in a language import, are deprecated, but not removed from the standard library. */
21+
val illegalSourceVersionNames = List("3.1-migration").map(_.toTermName)
22+
23+
/** language versions that the compiler recognises. */
24+
val validSourceVersionNames = values.toList.map(_.toString.toTermName)
25+
26+
/** All source versions that can be recognised from a language import. e.g. `import language.3.1` */
27+
val allSourceVersionNames = validSourceVersionNames ::: illegalSourceVersionNames
2128
end SourceVersion

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3150,6 +3150,18 @@ object Parsers {
31503150
syntaxError(i"source version import is only allowed at the toplevel", id.span)
31513151
else if ctx.compilationUnit.sourceVersion.isDefined then
31523152
syntaxError(i"duplicate source version import", id.span)
3153+
else if illegalSourceVersionNames.contains(imported) then
3154+
// find closest non-migration versions
3155+
val closest = closestNamed(
3156+
candidates = validSourceVersionNames,
3157+
missing = imported.show.replace("-migration", ""),
3158+
format = _.show.replace("-migration", "")
3159+
)
3160+
val baseMsg = i"`$imported` is not a valid source version"
3161+
val msg = closest match
3162+
case (_, member) :: _ => i"$baseMsg, did you mean language.`$member`?"
3163+
case _ => baseMsg
3164+
syntaxError(msg, id.span)
31533165
else
31543166
ctx.compilationUnit.sourceVersion = Some(SourceVersion.valueOf(imported.toString))
31553167
case None =>

compiler/src/dotty/tools/dotc/reporting/messages.scala

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,43 @@ import ast.untpd
2626
import ast.tpd
2727
import transform.SymUtils._
2828

29-
/** Messages
30-
* ========
31-
* The role of messages is to provide the necessary details for a simple to
32-
* understand diagnostic event. Each message can be turned into a message
33-
* container (one of the above) by calling the appropriate method on them.
34-
* For instance:
35-
*
36-
* ```scala
37-
* EmptyCatchBlock(tree).error(pos) // res: Error
38-
* EmptyCatchBlock(tree).warning(pos) // res: Warning
39-
* ```
29+
/* Messages
30+
* ========
31+
* The role of messages is to provide the necessary details for a simple to
32+
* understand diagnostic event. Each message can be turned into a message
33+
* container (one of the above) by calling the appropriate method on them.
34+
* For instance:
35+
*
36+
* ```scala
37+
* EmptyCatchBlock(tree).error(pos) // res: Error
38+
* EmptyCatchBlock(tree).warning(pos) // res: Warning
39+
* ```
40+
*/
41+
42+
/** A list of possible candidate options with their Levenstein distances
43+
* to the name of the missing member.
4044
*/
45+
def closestNamed[T](candidates: List[T], missing: String, format: T => String, maxDist: Int = 3): List[(Int, T)] =
46+
47+
def levenshteinDistance(s1: String, s2: String): Int =
48+
val dist = Array.ofDim[Int](s2.length + 1, s1.length + 1)
49+
for
50+
j <- 0 to s2.length
51+
i <- 0 to s1.length
52+
do
53+
dist(j)(i) =
54+
if j == 0 then i
55+
else if i == 0 then j
56+
else if s2(j - 1) == s1(i - 1) then dist(j - 1)(i - 1)
57+
else (dist(j - 1)(i) min dist(j)(i - 1) min dist(j - 1)(i - 1)) + 1
58+
dist(s2.length)(s1.length)
59+
end levenshteinDistance
60+
61+
candidates
62+
.map(candidate => (levenshteinDistance(format(candidate), missing), candidate))
63+
.filter((d, candidate) => d <= maxDist && d < missing.length && d < format(candidate).length)
64+
.sortBy((d, candidate) => (d, format(candidate))) // sort by distance first, alphabetically second
65+
end closestNamed
4166

4267
abstract class SyntaxMsg(errorId: ErrorMessageID) extends Message(errorId):
4368
def kind = MessageKind.Syntax
@@ -318,27 +343,10 @@ import transform.SymUtils._
318343
&& !sym.flagsUNSAFE.isOneOf(Synthetic | Private))
319344
yield sym
320345

321-
// Calculate Levenshtein distance
322-
def distance(s1: String, s2: String): Int =
323-
val dist = Array.ofDim[Int](s2.length + 1, s1.length + 1)
324-
for
325-
j <- 0 to s2.length
326-
i <- 0 to s1.length
327-
do
328-
dist(j)(i) =
329-
if j == 0 then i
330-
else if i == 0 then j
331-
else if s2(j - 1) == s1(i - 1) then dist(j - 1)(i - 1)
332-
else (dist(j - 1)(i) min dist(j)(i - 1) min dist(j - 1)(i - 1)) + 1
333-
dist(s2.length)(s1.length)
334-
335346
// A list of possible candidate symbols with their Levenstein distances
336347
// to the name of the missing member
337-
def closest: List[(Int, Symbol)] = candidates
338-
.toList
339-
.map(sym => (distance(sym.name.show, missing), sym))
340-
.filter((d, sym) => d <= maxDist && d < missing.length && d < sym.name.show.length)
341-
.sortBy((d, sym) => (d, sym.name.show)) // sort by distance first, alphabetically second
348+
def closest: List[(Int, Symbol)] =
349+
closestNamed(candidates.toList, missing, format = _.name.show, maxDist)
342350

343351
val enumClause =
344352
if ((name eq nme.values) || (name eq nme.valueOf)) && site.classSymbol.companionClass.isEnumClass then

library/src/scala/runtime/stdLibPatches/language.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,13 @@ object language:
149149
object `3.0`
150150

151151
/** Set source version to 3.1-migration.
152+
*
153+
* This is a no-op, and should not be used. A syntax error will be reported upon import.
152154
*
153155
* @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]]
154156
*/
155157
@compileTimeOnly("`3.1-migration` can only be used at compile time in import statements")
158+
@deprecated("`3.1-migration` is not valid, use `3.1` instead", since = "3.2")
156159
object `3.1-migration`
157160

158161
/** Set source version to 3.1

tests/neg/i12457.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
import language.`3.1-migration`
1+
import language.`3.1`
22

33
trait X [ X <: Z , Z >: X [ R ] ] // error
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- Error: tests/neg/source-import-3-1-migration.scala:1:16 -------------------------------------------------------------
2+
1 |import language.`3.1-migration` // error
3+
| ^^^^^^^^^^^^^^^
4+
| `3.1-migration` is not a valid source version, did you mean language.`3.1`?
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import language.`3.1-migration` // error

tests/pos/source-import-3-1-migration.scala

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)