Skip to content

Commit 8a1edce

Browse files
committed
Require language import for ad hoc extensions
1 parent b70316b commit 8a1edce

File tree

11 files changed

+65
-22
lines changed

11 files changed

+65
-22
lines changed

compiler/src/dotty/tools/dotc/core/StdNames.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,8 +367,8 @@ object StdNames {
367367
val TypeApply: N = "TypeApply"
368368
val TypeRef: N = "TypeRef"
369369
val UNIT : N = "UNIT"
370-
val add_ : N = "add"
371370
val acc: N = "acc"
371+
val adhocExtensions: N = "adhocExtensions"
372372
val annotation: N = "annotation"
373373
val any2stringadd: N = "any2stringadd"
374374
val anyHash: N = "anyHash"

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,13 +108,13 @@ trait Reporting { this: Context =>
108108
else {
109109
reporter.reportNewFeatureUseSite(featureUseSite)
110110
s"""
111-
|This can be achieved by adding the import clause 'import $fqname'
112-
|or by setting the compiler option -language:$feature.
113111
|See the Scala docs for value $fqname for a discussion
114112
|why the feature $req be explicitly enabled.""".stripMargin
115113
}
116114

117-
val msg = s"$featureDescription $req be enabled\nby making the implicit value $fqname visible.$explain"
115+
val msg = s"""$featureDescription $req be enabled
116+
|by adding the import clause 'import $fqname'
117+
|or by setting the compiler option -language:$feature.$explain""".stripMargin
118118
if (required) error(msg, pos)
119119
else reportWarning(new FeatureWarning(msg, pos))
120120
}

compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1544,6 +1544,7 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend
15441544
def Flags_Private: Flags = core.Flags.Private
15451545
def Flags_Protected: Flags = core.Flags.Protected
15461546
def Flags_Abstract: Flags = core.Flags.Abstract
1547+
def Flags_Open: Flags = core.Flags.Open
15471548
def Flags_Final: Flags = core.Flags.Final
15481549
def Flags_Sealed: Flags = core.Flags.Sealed
15491550
def Flags_Case: Flags = core.Flags.Case

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -884,7 +884,7 @@ class Namer { typer: Typer =>
884884
else if !cls.is(ChildrenQueried) then
885885
addChild(cls, child)
886886
else
887-
ctx.error(em"""children of $cls were already queried before $sym / ${sym.ownersIterator.toList} was discovered.
887+
ctx.error(em"""children of $cls were already queried before $sym was discovered.
888888
|As a remedy, you could move $sym on the same nesting level as $cls.""",
889889
child.sourcePos)
890890

@@ -1163,10 +1163,16 @@ class Namer { typer: Typer =>
11631163
}
11641164
else {
11651165
val pclazz = pt.typeSymbol
1166-
if (pclazz.is(Final))
1166+
if pclazz.is(Final) then
11671167
ctx.error(ExtendFinalClass(cls, pclazz), cls.sourcePos)
1168-
if (pclazz.is(Sealed) && pclazz.associatedFile != cls.associatedFile)
1169-
ctx.error(UnableToExtendSealedClass(pclazz), cls.sourcePos)
1168+
else if pclazz.isEffectivelySealed && pclazz.associatedFile != cls.associatedFile then
1169+
if pclazz.is(Sealed) then
1170+
ctx.error(UnableToExtendSealedClass(pclazz), cls.sourcePos)
1171+
else if ctx.settings.strict.value then
1172+
checkFeature(nme.adhocExtensions,
1173+
i"Unless $pclazz with flags ${pclazz.flagsString} is declared 'open', its extension in a separate file",
1174+
cls.topLevelClass,
1175+
parent.sourcePos)
11701176
pt
11711177
}
11721178
}

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ class CompilationTests extends ParallelTesting {
5858
),
5959
compileFile("tests/pos-special/typeclass-scaling.scala", defaultOptions.and("-Xmax-inlines", "40")),
6060
compileFile("tests/pos-special/indent-colons.scala", defaultOptions.and("-Yindent-colons")),
61-
compileFile("tests/pos-special/i7296.scala", defaultOptions.and("-strict", "-deprecation", "-Xfatal-warnings"))
61+
compileFile("tests/pos-special/i7296.scala", defaultOptions.and("-strict", "-deprecation", "-Xfatal-warnings")),
62+
compileDir("tests/pos-special/adhoc-extension", defaultOptions.and("-strict", "-feature", "-Xfatal-warnings"))
6263
).checkCompile()
6364
}
6465

@@ -142,7 +143,8 @@ class CompilationTests extends ParallelTesting {
142143
compileFile("tests/neg-custom-args/missing-alpha.scala", defaultOptions.and("-strict", "-deprecation", "-Xfatal-warnings")),
143144
compileFile("tests/neg-custom-args/wildcards.scala", defaultOptions.and("-strict", "-deprecation", "-Xfatal-warnings")),
144145
compileFile("tests/neg-custom-args/indentRight.scala", defaultOptions.and("-noindent", "-Xfatal-warnings")),
145-
compileFile("tests/neg-custom-args/extmethods-tparams.scala", defaultOptions.and("-deprecation", "-Xfatal-warnings"))
146+
compileFile("tests/neg-custom-args/extmethods-tparams.scala", defaultOptions.and("-deprecation", "-Xfatal-warnings")),
147+
compileDir("tests/neg-custom-args/adhoc-extension", defaultOptions.and("-strict", "-feature", "-Xfatal-warnings"))
146148
).checkExpectedErrors()
147149
}
148150

library/src/scalaShadowing/language.scala

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ object language {
6262
*
6363
* @group production
6464
*/
65-
@volatile implicit lazy val dynamics: dynamics = languageFeature.dynamics
65+
implicit lazy val dynamics: dynamics = languageFeature.dynamics
6666

6767
/** Only where enabled, postfix operator notation `(expr op)` will be allowed.
6868
*
@@ -73,7 +73,7 @@ object language {
7373
*
7474
* @group production
7575
*/
76-
@volatile implicit lazy val postfixOps: postfixOps = languageFeature.postfixOps
76+
implicit lazy val postfixOps: postfixOps = languageFeature.postfixOps
7777

7878
/** Only where enabled, accesses to members of structural types that need
7979
* reflection are supported. Reminder: A structural type is a type of the form
@@ -91,7 +91,7 @@ object language {
9191
*
9292
* @group production
9393
*/
94-
@volatile implicit lazy val reflectiveCalls: reflectiveCalls = languageFeature.reflectiveCalls
94+
implicit lazy val reflectiveCalls: reflectiveCalls = languageFeature.reflectiveCalls
9595

9696
/** Only where enabled, definitions of legacy implicit conversions and certain uses
9797
* of implicit conversions are allowed.
@@ -139,7 +139,7 @@ object language {
139139
*
140140
* @group production
141141
*/
142-
@volatile implicit lazy val implicitConversions: implicitConversions = languageFeature.implicitConversions
142+
implicit lazy val implicitConversions: implicitConversions = languageFeature.implicitConversions
143143

144144
/** Only where this flag is enabled, higher-kinded types can be written.
145145
*
@@ -162,7 +162,7 @@ object language {
162162
*
163163
* @group production
164164
*/
165-
@volatile implicit lazy val higherKinds: higherKinds = languageFeature.higherKinds
165+
implicit lazy val higherKinds: higherKinds = languageFeature.higherKinds
166166

167167
/** Only where enabled, existential types that cannot be expressed as wildcard
168168
* types can be written and are allowed in inferred types of values or return
@@ -180,7 +180,7 @@ object language {
180180
*
181181
* @group production
182182
*/
183-
@volatile implicit lazy val existentials: existentials = languageFeature.existentials
183+
implicit lazy val existentials: existentials = languageFeature.existentials
184184

185185
/** The experimental object contains features that have been recently added but have not
186186
* been thoroughly tested in production yet.
@@ -209,7 +209,7 @@ object language {
209209
* '''Why control it?''' For their very power, macros can lead to code that is hard
210210
* to debug and understand.
211211
*/
212-
@volatile implicit lazy val macros: macros = languageFeature.experimental.macros
212+
implicit lazy val macros: macros = languageFeature.experimental.macros
213213
}
214214

215215
/** Where imported, a backwards compatibility mode for Scala2 is enabled */
@@ -218,6 +218,24 @@ object language {
218218
/** Where imported, auto-tupling is disabled */
219219
object noAutoTupling
220220

221-
/** Where imported loose equality using eqAny is disabled */
221+
/** Where imported, loose equality using eqAny is disabled */
222222
object strictEquality
223+
224+
/** Where imported, ad hoc extensions of non-open classes in other
225+
* compilation units are allowed.
226+
*
227+
* '''Why control the feature?''' Ad-hoc extensions should usually be avoided
228+
* since they typically cannot reply on an "internal" contract between a class
229+
* and its extensions. Only open classes need to specify such a contract.
230+
* Ad-hoc extensions might break for future versions of the extended class,
231+
* since the extended class is free to change its implementation without
232+
* being constrained by an internal contract.
233+
*
234+
* '''Why allow it?''' An ad-hoc extension can sometimes be necessary,
235+
* for instance when mocking a class in a testing framework, or to work
236+
* around a bug or missing feature in the original class. Nevertheless,
237+
* such extensions should be limited in scope and clearly documented.
238+
* That's why the language import is required for them.
239+
*/
240+
object adhocExtensions
223241
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
package adhoc
2+
class A
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package adhoc
2+
class B extends A // error: adhoc-extension (under -strict -feature -Xfatal-warnings)
3+
class C extends A // error
4+
5+
object O {
6+
val a = new A {} // error
7+
object E extends A // error
8+
}

tests/neg/i5455.scala

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,9 @@ object Library {
1111
def toInt(n: Nat): Int = n
1212

1313
}
14-
given {
15-
def (x: Nat) * (y: Nat): Nat = x * y
16-
def (x: Nat) toInt: Int = x
17-
}
14+
given (x: Nat)
15+
def * (y: Nat): Nat = x * y
16+
def toInt: Int = x
1817
}
1918

2019
object User extends App {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package adhoc
2+
class A
3+
abstract class Abs
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package adhoc
2+
import language.adhocExtensions
3+
class B extends A
4+
class B2 extends Abs

0 commit comments

Comments
 (0)