Skip to content

Commit 9cbe994

Browse files
committed
Merge pull request #926 from dotty-staging/stdlib-variance
Handle Scalac variance unsoundness with regards to constructors.
2 parents c16c7f6 + 93753dc commit 9cbe994

File tree

9 files changed

+91
-13
lines changed

9 files changed

+91
-13
lines changed

src/dotty/tools/dotc/config/ScalaSettings.scala

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class ScalaSettings extends Settings.SettingGroup {
1818
*/
1919
val dependencyfile = StringSetting("-dependencyfile", "file", "Set dependency tracking file.", ".scala_dependencies")
2020
val deprecation = BooleanSetting("-deprecation", "Emit warning and location for usages of deprecated APIs.")
21+
val migration = BooleanSetting("-migration", "Emit warning and location for migration issues from Scala 2.")
2122
val encoding = StringSetting("-encoding", "encoding", "Specify character encoding used by source files.", Properties.sourceEncoding)
2223
val explaintypes = BooleanSetting("-explaintypes", "Explain type errors in more detail.")
2324
val feature = BooleanSetting("-feature", "Emit warning and location for usages of features that should be imported explicitly.")

src/dotty/tools/dotc/core/TypeOps.scala

+10
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package core
55
import Contexts._, Types._, Symbols._, Names._, Flags._, Scopes._
66
import SymDenotations._, Denotations.Denotation
77
import config.Printers._
8+
import util.Positions._
89
import Decorators._
910
import StdNames._
1011
import util.SimpleMap
@@ -572,6 +573,15 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
572573
/** Is auto-tupling enabled? */
573574
def canAutoTuple =
574575
!featureEnabled(defn.LanguageModuleClass, nme.noAutoTupling)
576+
577+
def scala2Mode =
578+
featureEnabled(defn.LanguageModuleClass, nme.Scala2)
579+
580+
def testScala2Mode(msg: String, pos: Position) = {
581+
if (scala2Mode) migrationWarning(msg, pos)
582+
scala2Mode
583+
}
584+
575585
}
576586

577587
object TypeOps {

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

+9-4
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,11 @@ object Parsers {
259259
}
260260

261261
/** Cannot use ctx.featureEnabled because accessing the context would force too much */
262-
private def scala2mode = ctx.settings.language.value.contains(nme.Scala2.toString)
262+
private def testScala2Mode(msg: String, pos: Position = Position(in.offset)) = {
263+
val s2 = ctx.settings.language.value.contains(nme.Scala2.toString)
264+
if (s2) ctx.migrationWarning(msg, source atPos pos)
265+
s2
266+
}
263267

264268
/* ---------- TREE CONSTRUCTION ------------------------------------------- */
265269

@@ -1726,12 +1730,13 @@ object Parsers {
17261730
* DefSig ::= id [DefTypeParamClause] ParamClauses
17271731
*/
17281732
def defDefOrDcl(mods: Modifiers): Tree = atPos(tokenRange) {
1729-
def atScala2Brace = scala2mode && in.token == LBRACE
1733+
def scala2ProcedureSyntax =
1734+
testScala2Mode("Procedure syntax no longer supported; `=' should be inserted here")
17301735
if (in.token == THIS) {
17311736
in.nextToken()
17321737
val vparamss = paramClauses(nme.CONSTRUCTOR)
17331738
val rhs = {
1734-
if (!atScala2Brace) accept(EQUALS)
1739+
if (!(in.token == LBRACE && scala2ProcedureSyntax)) accept(EQUALS)
17351740
atPos(in.offset) { constrExpr() }
17361741
}
17371742
makeConstructor(Nil, vparamss, rhs).withMods(mods)
@@ -1748,7 +1753,7 @@ object Parsers {
17481753
}
17491754
else if (!tpt.isEmpty)
17501755
EmptyTree
1751-
else if (scala2mode) {
1756+
else if (scala2ProcedureSyntax) {
17521757
tpt = scalaUnit
17531758
if (in.token == LBRACE) expr()
17541759
else EmptyTree

src/dotty/tools/dotc/reporting/Reporter.scala

+6
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ object Reporter {
6666
class DeprecationWarning(msgFn: => String, pos: SourcePosition) extends ConditionalWarning(msgFn, pos) {
6767
def enablingOption(implicit ctx: Context) = ctx.settings.deprecation
6868
}
69+
class MigrationWarning(msgFn: => String, pos: SourcePosition) extends ConditionalWarning(msgFn, pos) {
70+
def enablingOption(implicit ctx: Context) = ctx.settings.migration
71+
}
6972
}
7073

7174
import Reporter._
@@ -82,6 +85,9 @@ trait Reporting { this: Context =>
8285
def deprecationWarning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit =
8386
reporter.report(new DeprecationWarning(msg, pos))
8487

88+
def migrationWarning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit =
89+
reporter.report(new MigrationWarning(msg, pos))
90+
8591
def uncheckedWarning(msg: => String, pos: SourcePosition = NoSourcePosition): Unit =
8692
reporter.report(new UncheckedWarning(msg, pos))
8793

src/dotty/tools/dotc/typer/VarianceChecker.scala

+8-4
Original file line numberDiff line numberDiff line change
@@ -110,16 +110,20 @@ class VarianceChecker()(implicit ctx: Context) {
110110
private object Traverser extends TreeTraverser {
111111
def checkVariance(sym: Symbol) = Validator.validateDefinition(sym) match {
112112
case Some(VarianceError(tvar, required)) =>
113-
ctx.error(
114-
i"${varianceString(tvar.flags)} $tvar occurs in ${varianceString(required)} position in type ${sym.info} of $sym",
115-
sym.pos)
113+
def msg = i"${varianceString(tvar.flags)} $tvar occurs in ${varianceString(required)} position in type ${sym.info} of $sym"
114+
if (ctx.scala2Mode && sym.owner.isConstructor)
115+
ctx.migrationWarning(s"According to new variance rules, this is no longer accepted; need to annotate with @uncheckedVariance:\n$msg", sym.pos)
116+
else ctx.error(msg, sym.pos)
116117
case None =>
117118
}
118119

119120
override def traverse(tree: Tree)(implicit ctx: Context) = {
120121
def sym = tree.symbol
121122
// No variance check for private/protected[this] methods/values.
122-
def skip = !sym.exists || sym.is(Local)
123+
def skip =
124+
!sym.exists ||
125+
sym.is(Local) || // !!! watch out for protected local!
126+
sym.is(TypeParam) && sym.owner.isClass // already taken care of in primary constructor of class
123127
tree match {
124128
case defn: MemberDef if skip =>
125129
ctx.debuglog(s"Skipping variance check of ${sym.showDcl}")

test/dotc/scala-collections.whitelist

+1-3
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,7 @@
9797
# ./scala-scala/src/library/scala/collection/IndexedSeqOptimized.scala
9898

9999
./scala-scala/src/library/scala/collection/IterableLike.scala
100-
101-
# https://github.com/lampepfl/dotty/issues/913
102-
#./scala-scala/src/library/scala/collection/Iterator.scala
100+
./scala-scala/src/library/scala/collection/Iterator.scala
103101
./scala-scala/src/library/scala/collection/LinearSeqOptimized.scala
104102

105103
# https://github.com/lampepfl/dotty/issues/914

test/dotc/tests.scala

+3-2
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ class tests extends CompilerTest {
102102

103103
@Test def pos_t2613 = compileFile(posSpecialDir, "t2613")(allowDeepSubtypes)
104104
@Test def pos_i871 = compileFile(posSpecialDir, "i871", scala2mode)
105+
@Test def pos_variancesConstr = compileFile(posSpecialDir, "variances-constr", scala2mode)
105106

106107
@Test def new_all = compileFiles(newDir, twice)
107108

@@ -131,12 +132,12 @@ class tests extends CompilerTest {
131132
@Test def neg_tailcall2 = compileFile(negTailcallDir, "tailrec-2", xerrors = 2)
132133
@Test def neg_tailcall3 = compileFile(negTailcallDir, "tailrec-3", xerrors = 2)
133134

134-
@Test def neg_t1279a = compileFile(negDir, "t1279a", xerrors = 1)
135135
@Test def neg_t1843_variances = compileFile(negDir, "t1843-variances", xerrors = 1)
136136
@Test def neg_t2660_ambi = compileFile(negDir, "t2660", xerrors = 2)
137137
@Test def neg_t2994 = compileFile(negDir, "t2994", xerrors = 2)
138138
@Test def neg_subtyping = compileFile(negDir, "subtyping", xerrors = 5)
139139
@Test def neg_variances = compileFile(negDir, "variances", xerrors = 2)
140+
@Test def neg_variancesConstr = compileFile(negDir, "variances-constr", xerrors = 2)
140141
@Test def neg_i871_missingReturnType = compileFile(negDir, "i871", xerrors = 2)
141142
@Test def neg_badAuxConstr = compileFile(negDir, "badAuxConstr", xerrors = 2)
142143
@Test def neg_typetest = compileFile(negDir, "typetest", xerrors = 1)
@@ -176,7 +177,7 @@ class tests extends CompilerTest {
176177
.filter(_.nonEmpty)
177178
.toList
178179

179-
@Test def compileStdLib = compileList("compileStdLib", stdlibFiles)
180+
@Test def compileStdLib = compileList("compileStdLib", stdlibFiles, "-migration" :: scala2mode)
180181
@Test def dotty = compileDir(dottyDir, ".", "-deep" :: "-Ycheck-reentrant" :: allowDeepSubtypes) // note the -deep argument
181182

182183
@Test def dotc_ast = compileDir(dotcDir, "ast")

tests/neg/variances-constr.scala

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
class C[+A] {
2+
3+
private[this] var y: A = _
4+
def getY: A = y
5+
6+
class Inner(x: A) { // error A appears contravariantly
7+
y = x
8+
}
9+
class Inner2[B <: A](x: B) { // error A appears contravariantly
10+
y = x
11+
}
12+
}
13+
14+
object Test {
15+
16+
def main(args: Array[String]) = {
17+
val x = new C[String]
18+
val y: C[Any] = x
19+
val i = new y.Inner(1)
20+
val s: String = x.getY
21+
val i2 = new y.Inner2(1)
22+
val s2: String = x.getY
23+
println(s)
24+
}
25+
}
26+
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
class C[+A] {
2+
3+
private[this] var y: A = _
4+
def getY: A = y
5+
6+
class Inner(x: A) {
7+
y = x
8+
}
9+
}
10+
11+
object Test {
12+
13+
def main(args: Array[String]) = {
14+
val x = new C[String]
15+
val y: C[Any] = x
16+
val i = new y.Inner(1)
17+
val s: String = x.getY
18+
println(s)
19+
}
20+
}
21+
22+
class CC[+A] {
23+
class Inner {
24+
def this(a: A) = this()
25+
}
26+
}
27+

0 commit comments

Comments
 (0)