Skip to content

Commit ccf6948

Browse files
committed
Specify source versions by top-level language imports
1 parent 754d603 commit ccf6948

File tree

11 files changed

+132
-84
lines changed

11 files changed

+132
-84
lines changed

compiler/src/dotty/tools/dotc/CompilationUnit.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import typer.PrepareInlineable.InlineAccessors
1313
import typer.Nullables
1414
import transform.SymUtils._
1515
import core.Decorators.{given _}
16+
import config.SourceVersion
1617

1718
class CompilationUnit protected (val source: SourceFile) {
1819

@@ -24,6 +25,9 @@ class CompilationUnit protected (val source: SourceFile) {
2425

2526
def isJava: Boolean = source.file.name.endsWith(".java")
2627

28+
/** The source version for this unit, as determined by a language import */
29+
var sourceVersion: Option[SourceVersion] = None
30+
2731
/** Pickled TASTY binaries, indexed by class. */
2832
var pickled: Map[ClassSymbol, Array[Byte]] = Map()
2933

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ object Feature:
6161
SourceVersion.valueOf(ctx.settings.source.value)
6262

6363
def sourceVersion(using Context): SourceVersion =
64-
if ctx.importInfo == null then sourceVersionSetting
65-
else ctx.importInfo.sourceVersion.getOrElse(sourceVersionSetting)
64+
if ctx.compilationUnit == null then sourceVersionSetting
65+
else ctx.compilationUnit.sourceVersion.getOrElse(sourceVersionSetting)
6666

6767
def migrateTo3(using Context): Boolean =
6868
sourceVersion == `3.0-migration` || enabledBySetting(nme.Scala2Compat)

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

Lines changed: 64 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ import scala.annotation.{tailrec, switch}
2828
import rewrites.Rewrites.{patch, overlapsPatch}
2929
import reporting.Message
3030
import reporting.messages._
31-
import config.Feature.sourceVersion
32-
import config.SourceVersion.`3.1`
31+
import config.Feature.{sourceVersion, migrateTo3}
32+
import config.SourceVersion._
33+
import config.SourceVersion
3334

3435
object Parsers {
3536

@@ -454,9 +455,10 @@ object Parsers {
454455
case Tuple(ts) =>
455456
ts.map(convertToParam(_, mods))
456457
case t: Typed =>
457-
in.errorOrMigrationWarning(
458-
em"parentheses are required around the parameter of a lambda$rewriteNotice")
459-
if in.migrateTo3 then
458+
ctx.errorOrMigrationWarning(
459+
em"parentheses are required around the parameter of a lambda${rewriteNotice()}",
460+
in.sourcePos())
461+
if migrateTo3 then
460462
patch(source, t.span.startPos, "(")
461463
patch(source, t.span.endPos, ")")
462464
convertToParam(t, mods) :: Nil
@@ -1188,10 +1190,12 @@ object Parsers {
11881190
Quote(t)
11891191
}
11901192
else {
1191-
in.errorOrMigrationWarning(em"""symbol literal '${in.name} is no longer supported,
1192-
|use a string literal "${in.name}" or an application Symbol("${in.name}") instead,
1193-
|or enclose in braces '{${in.name}} if you want a quoted expression.""")
1194-
if in.migrateTo3 then
1193+
ctx.errorOrMigrationWarning(
1194+
em"""symbol literal '${in.name} is no longer supported,
1195+
|use a string literal "${in.name}" or an application Symbol("${in.name}") instead,
1196+
|or enclose in braces '{${in.name}} if you want a quoted expression.""",
1197+
in.sourcePos())
1198+
if migrateTo3 then
11951199
patch(source, Span(in.offset, in.offset + 1), "Symbol(\"")
11961200
patch(source, Span(in.charOffset - 1), "\")")
11971201
atSpan(in.skipToken()) { SymbolLit(in.strVal) }
@@ -1729,7 +1733,9 @@ object Parsers {
17291733
AppliedTypeTree(toplevelTyp(), Ident(pname))
17301734
} :: contextBounds(pname)
17311735
case VIEWBOUND =>
1732-
in.errorOrMigrationWarning("view bounds `<%' are deprecated, use a context bound `:' instead")
1736+
ctx.errorOrMigrationWarning(
1737+
"view bounds `<%' are deprecated, use a context bound `:' instead",
1738+
in.sourcePos())
17331739
atSpan(in.skipToken()) {
17341740
Function(Ident(pname) :: Nil, toplevelTyp())
17351741
} :: contextBounds(pname)
@@ -1759,7 +1765,7 @@ object Parsers {
17591765
* the initially parsed (...) region?
17601766
*/
17611767
def toBeContinued(altToken: Token): Boolean =
1762-
if in.token == altToken || in.isNewLine || in.migrateTo3 then
1768+
if in.token == altToken || in.isNewLine || migrateTo3 then
17631769
false // a newline token means the expression is finished
17641770
else if !in.canStartStatTokens.contains(in.token)
17651771
|| in.isLeadingInfixOperator(inConditional = true)
@@ -1883,17 +1889,18 @@ object Parsers {
18831889
}
18841890
}
18851891
case DO =>
1886-
in.errorOrMigrationWarning(
1892+
ctx.errorOrMigrationWarning(
18871893
i"""`do <body> while <cond>` is no longer supported,
1888-
|use `while ({<body> ; <cond>}) ()` instead.$rewriteNotice""")
1894+
|use `while <body> ; <cond>} do ()` instead.${rewriteNotice()}""",
1895+
in.sourcePos())
18891896
val start = in.skipToken()
18901897
atSpan(start) {
18911898
val body = expr()
18921899
if (isStatSep) in.nextToken()
18931900
val whileStart = in.offset
18941901
accept(WHILE)
18951902
val cond = expr()
1896-
if in.migrateTo3 then
1903+
if migrateTo3 then
18971904
patch(source, Span(start, start + 2), "while ({")
18981905
patch(source, Span(whileStart, whileStart + 5), ";")
18991906
cond match {
@@ -2080,10 +2087,12 @@ object Parsers {
20802087
if sourceVersion.isAtLeast(`3.1`)
20812088
// Don't error in non-strict mode, as the alternative syntax "implicit (x: T) => ... "
20822089
// is not supported by Scala2.x
2083-
in.errorOrMigrationWarning(s"This syntax is no longer supported; parameter needs to be enclosed in (...)")
2090+
ctx.errorOrMigrationWarning(
2091+
s"This syntax is no longer supported; parameter needs to be enclosed in (...)",
2092+
in.sourcePos())
20842093
in.nextToken()
20852094
val t = infixType()
2086-
if (false && in.migrateTo3) {
2095+
if (false && migrateTo3) {
20872096
patch(source, Span(start), "(")
20882097
patch(source, Span(in.lastOffset), ")")
20892098
}
@@ -2576,15 +2585,19 @@ object Parsers {
25762585
infixPattern() match {
25772586
case pt @ Ident(tpnme.WILDCARD_STAR) =>
25782587
if sourceVersion.isAtLeast(`3.1`) then
2579-
in.errorOrMigrationWarning("The syntax `x @ _*` is no longer supported; use `x : _*` instead", Span(startOffset(p)))
2588+
ctx.errorOrMigrationWarning(
2589+
"The syntax `x @ _*` is no longer supported; use `x : _*` instead",
2590+
in.sourcePos(startOffset(p)))
25802591
atSpan(startOffset(p), offset) { Typed(p, pt) }
25812592
case pt =>
25822593
atSpan(startOffset(p), 0) { Bind(name, pt) }
25832594
}
25842595
case p @ Ident(tpnme.WILDCARD_STAR) =>
25852596
// compatibility for Scala2 `_*` syntax
25862597
if sourceVersion.isAtLeast(`3.1`) then
2587-
in.errorOrMigrationWarning("The syntax `_*` is no longer supported; use `x : _*` instead", Span(startOffset(p)))
2598+
ctx.errorOrMigrationWarning(
2599+
"The syntax `_*` is no longer supported; use `x : _*` instead",
2600+
in.sourcePos(startOffset(p)))
25882601
atSpan(startOffset(p)) { Typed(Ident(nme.WILDCARD), p) }
25892602
case p =>
25902603
p
@@ -3012,6 +3025,25 @@ object Parsers {
30123025
}
30133026
}
30143027

3028+
/** Create an import node and handle source version imports */
3029+
def mkImport(outermost: Boolean = false): ImportConstr = (tree, selectors) =>
3030+
val isLanguageImport = tree match
3031+
case Ident(nme.language) => true
3032+
case Select(Ident(nme.scala), nme.language) => true
3033+
case _ => false
3034+
if isLanguageImport then
3035+
for
3036+
case ImportSelector(id @ Ident(imported), EmptyTree, _) <- selectors
3037+
if allSourceVersionNames.contains(imported)
3038+
do
3039+
if !outermost then
3040+
syntaxError(i"source version import is only allowed at the toplevel", id.span)
3041+
else if ctx.compilationUnit.sourceVersion.isDefined then
3042+
syntaxError(i"duplicate source version import", id.span)
3043+
else
3044+
ctx.compilationUnit.sourceVersion = Some(SourceVersion.valueOf(imported.toString))
3045+
Import(tree, selectors)
3046+
30153047
/** ImportExpr ::= StableId ‘.’ ImportSpec
30163048
* ImportSpec ::= id
30173049
* | ‘_’
@@ -3181,8 +3213,10 @@ object Parsers {
31813213
def toInsert =
31823214
if in.token == LBRACE then s"$resultTypeStr ="
31833215
else ": Unit " // trailing space ensures that `def f()def g()` works.
3184-
if in.migrateTo3 then
3185-
in.errorOrMigrationWarning(s"Procedure syntax no longer supported; `$toInsert` should be inserted here")
3216+
if migrateTo3 then
3217+
ctx.errorOrMigrationWarning(
3218+
s"Procedure syntax no longer supported; `$toInsert` should be inserted here",
3219+
in.sourcePos())
31863220
patch(source, Span(in.lastOffset), toInsert)
31873221
true
31883222
else
@@ -3197,7 +3231,7 @@ object Parsers {
31973231
case EOF => incompleteInputError(AuxConstructorNeedsNonImplicitParameter())
31983232
case _ => syntaxError(AuxConstructorNeedsNonImplicitParameter(), nameStart)
31993233
}
3200-
if (in.migrateTo3) newLineOptWhenFollowedBy(LBRACE)
3234+
if (migrateTo3) newLineOptWhenFollowedBy(LBRACE)
32013235
val rhs = {
32023236
if (!(in.token == LBRACE && scala2ProcedureSyntax(""))) accept(EQUALS)
32033237
atSpan(in.offset) { subPart(constrExpr) }
@@ -3249,7 +3283,7 @@ object Parsers {
32493283
toplevelTyp()
32503284
else typedOpt()
32513285
}
3252-
if (in.migrateTo3) newLineOptWhenFollowedBy(LBRACE)
3286+
if (migrateTo3) newLineOptWhenFollowedBy(LBRACE)
32533287
val rhs =
32543288
if (in.token == EQUALS)
32553289
in.endMarkerScope(name) {
@@ -3594,7 +3628,9 @@ object Parsers {
35943628
if (in.token == EXTENDS) {
35953629
in.nextToken()
35963630
if (in.token == LBRACE || in.token == COLONEOL) {
3597-
in.errorOrMigrationWarning("`extends` must be followed by at least one parent")
3631+
ctx.errorOrMigrationWarning(
3632+
"`extends` must be followed by at least one parent",
3633+
in.sourcePos())
35983634
Nil
35993635
}
36003636
else constrApps(commaOK = true, templateCanFollow = true)
@@ -3675,7 +3711,7 @@ object Parsers {
36753711
* | package object objectDef
36763712
* |
36773713
*/
3678-
def topStatSeq(): List[Tree] = {
3714+
def topStatSeq(outermost: Boolean = false): List[Tree] = {
36793715
val stats = new ListBuffer[Tree]
36803716
while (!isStatSeqEnd) {
36813717
setLastStatOffset()
@@ -3688,7 +3724,7 @@ object Parsers {
36883724
else stats += packaging(start)
36893725
}
36903726
else if (in.token == IMPORT)
3691-
stats ++= importClause(IMPORT, Import)
3727+
stats ++= importClause(IMPORT, mkImport(outermost))
36923728
else if (in.token == EXPORT)
36933729
stats ++= importClause(EXPORT, Export.apply)
36943730
else if (in.token == AT || isDefIntro(modifierTokens))
@@ -3739,7 +3775,7 @@ object Parsers {
37393775
while (!isStatSeqEnd && !exitOnError) {
37403776
setLastStatOffset()
37413777
if (in.token == IMPORT)
3742-
stats ++= importClause(IMPORT, Import)
3778+
stats ++= importClause(IMPORT, mkImport())
37433779
else if (in.token == EXPORT)
37443780
stats ++= importClause(EXPORT, Export.apply)
37453781
else if (isDefIntro(modifierTokensOrCase))
@@ -3816,7 +3852,7 @@ object Parsers {
38163852
while (!isStatSeqEnd && in.token != CASE && !exitOnError) {
38173853
setLastStatOffset()
38183854
if (in.token == IMPORT)
3819-
stats ++= importClause(IMPORT, Import)
3855+
stats ++= importClause(IMPORT, mkImport())
38203856
else if (isExprIntro)
38213857
stats += expr(Location.InBlock)
38223858
else if in.token == IMPLICIT && !in.inModifierPosition() then
@@ -3868,7 +3904,7 @@ object Parsers {
38683904
ts ++= topStatSeq()
38693905
}
38703906
else
3871-
ts ++= topStatSeq()
3907+
ts ++= topStatSeq(outermost = true)
38723908

38733909
ts.toList
38743910
}
@@ -3881,7 +3917,6 @@ object Parsers {
38813917
}
38823918
}
38833919

3884-
38853920
/** OutlineParser parses top-level declarations in `source` to find declared classes, ignoring their bodies (which
38863921
* must only have balanced braces). This is used to map class names to defining sources.
38873922
*/

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

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ import scala.annotation.{switch, tailrec}
1717
import scala.collection.mutable
1818
import scala.collection.immutable.{SortedMap, BitSet}
1919
import rewrites.Rewrites.patch
20-
import config.{Feature, SourceVersion}
21-
import SourceVersion._
20+
import config.Feature.migrateTo3
21+
import config.SourceVersion._
2222
import reporting.Message
2323

2424
object Cbufs {
@@ -181,16 +181,6 @@ object Scanners {
181181
/** A switch whether operators at the start of lines can be infix operators */
182182
private[Scanners] var allowLeadingInfixOperators = true
183183

184-
var sourceVersion: SourceVersion = // TODO: overwrite when parsing language imports
185-
if Feature.enabledBySetting(nme.Scala2Compat) then `3.0-migration`
186-
else Feature.sourceVersion
187-
188-
def migrateTo3 = sourceVersion == `3.0-migration`
189-
190-
def errorOrMigrationWarning(msg: Message, span: Span = Span(offset))(using Context) =
191-
if migrateTo3 then ctx.migrationWarning(msg, source.atSpan(span))
192-
else ctx.error(msg, source.atSpan(span))
193-
194184
val rewrite = ctx.settings.rewrite.value.isDefined
195185
val oldSyntax = ctx.settings.oldSyntax.value
196186
val newSyntax = ctx.settings.newSyntax.value
@@ -252,7 +242,9 @@ object Scanners {
252242
else keyword
253243

254244
private def treatAsIdent(): Token =
255-
errorOrMigrationWarning(i"$name is now a keyword, write `$name` instead of $name to keep it as an identifier")
245+
ctx.errorOrMigrationWarning(
246+
i"$name is now a keyword, write `$name` instead of $name to keep it as an identifier",
247+
sourcePos())
256248
patch(source, Span(offset), "`")
257249
patch(source, Span(offset + name.length), "`")
258250
IDENTIFIER
@@ -435,9 +427,11 @@ object Scanners {
435427
val (what, previous) =
436428
if inConditional then ("Rest of line", "previous expression in parentheses")
437429
else ("Line", "expression on the previous line")
438-
errorOrMigrationWarning(em"""$what starts with an operator;
439-
|it is now treated as a continuation of the $previous,
440-
|not as a separate statement.""")
430+
ctx.errorOrMigrationWarning(
431+
em"""$what starts with an operator;
432+
|it is now treated as a continuation of the $previous,
433+
|not as a separate statement.""",
434+
sourcePos())
441435
true
442436
}
443437
)

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

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -169,16 +169,10 @@ class ImportInfo(symf: Context ?=> Symbol,
169169
assert(myUnimported != null)
170170
myUnimported
171171

172-
private def nextOuter(using Context): Context =
173-
var c = ctx.outer
174-
while c.importInfo eq ctx.importInfo do c = c.outer
175-
c
176-
177172
private var myUnimported: Symbol = _
178173

179174
private var myOwner: Symbol = null
180175
private var myResults: SimpleIdentityMap[TermName, java.lang.Boolean] = SimpleIdentityMap.Empty
181-
private var mySourceVersion: Option[SourceVersion] | Null = null
182176

183177
/** Does this import clause or a preceding import clause import `owner.feature`?
184178
* if `feature` is empty, we are looking for a source designator instead.
@@ -190,7 +184,8 @@ class ImportInfo(symf: Context ?=> Symbol,
190184
if isImportOwner && forwardMapping.contains(feature) then true
191185
else if isImportOwner && excluded.contains(feature) then false
192186
else
193-
val c = nextOuter
187+
var c = ctx.outer
188+
while c.importInfo eq ctx.importInfo do c = c.outer
194189
(c.importInfo != null) && c.importInfo.featureImported(feature, owner)(using c)
195190

196191
if myOwner.ne(owner) || !myResults.contains(feature) then
@@ -199,32 +194,5 @@ class ImportInfo(symf: Context ?=> Symbol,
199194
myResults(feature)
200195
end featureImported
201196

202-
/** The language source version that's implied by imports. E.g. an import
203-
*
204-
* import scala.language.`3.1`
205-
*
206-
* would return SourceVersion.`3.1` (unless shadowed by an inner source import).
207-
*/
208-
def sourceVersion(using Context): Option[SourceVersion] =
209-
if mySourceVersion == null then
210-
val isLanguageImport =
211-
symNameOpt match
212-
case Some(nme.language) =>
213-
mySym == null // don't force the import, assume it is the right one
214-
|| site.widen.typeSymbol.eq(defn.LanguageModule.moduleClass)
215-
case _ =>
216-
false
217-
if isLanguageImport then
218-
forwardMapping.keys.filter(SourceVersion.allSourceVersionNames.contains) match
219-
case src :: rest =>
220-
mySourceVersion = Some(SourceVersion.valueOf(src.toString))
221-
if rest.nonEmpty then throw TypeError("ambiguous source specifiers in language import")
222-
case _ =>
223-
if mySourceVersion == null then
224-
val c = nextOuter
225-
mySourceVersion = if c.importInfo == null then None else c.importInfo.sourceVersion(using c)
226-
mySourceVersion
227-
end sourceVersion
228-
229197
def toText(printer: Printer): Text = printer.toText(this)
230198
}

library/src/scalaShadowing/language.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,4 +238,10 @@ object language {
238238
* That's why the language import is required for them.
239239
*/
240240
object adhocExtensions
241+
242+
/** Source version */
243+
object `3.0-migration`
244+
object `3.0`
245+
object `3.1-migration`
246+
object `3.1`
241247
}

0 commit comments

Comments
 (0)