Skip to content

Commit 7577342

Browse files
authored
Merge pull request #7471 from dotty-staging/add-open-class
Introduce `open` modifier on classes
2 parents 826ac10 + 921ddeb commit 7577342

File tree

29 files changed

+243
-67
lines changed

29 files changed

+243
-67
lines changed

compiler/src/dotty/tools/dotc/ast/untpd.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
182182

183183
case class Opaque()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Opaque)
184184

185+
case class Open()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Open)
186+
185187
case class Override()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Override)
186188

187189
case class Abstract()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Abstract)

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,10 @@ class Definitions {
4343
ctx.newSymbol(owner, name, flags | Permanent, info)
4444

4545
private def newClassSymbol(owner: Symbol, name: TypeName, flags: FlagSet, infoFn: ClassSymbol => Type) =
46-
ctx.newClassSymbol(owner, name, flags | Permanent | NoInits, infoFn)
46+
ctx.newClassSymbol(owner, name, flags | Permanent | NoInits | Open, infoFn)
4747

4848
private def enterCompleteClassSymbol(owner: Symbol, name: TypeName, flags: FlagSet, parents: List[TypeRef], decls: Scope = newScope) =
49-
ctx.newCompleteClassSymbol(owner, name, flags | Permanent | NoInits, parents, decls).entered
49+
ctx.newCompleteClassSymbol(owner, name, flags | Permanent | NoInits | Open, parents, decls).entered
5050

5151
private def enterTypeField(cls: ClassSymbol, name: TypeName, flags: FlagSet, scope: MutableScope) =
5252
scope.enter(newSymbol(cls, name, flags, TypeBounds.empty))
@@ -279,7 +279,7 @@ class Definitions {
279279
val cls = ctx.requiredClass("java.lang.Object")
280280
assert(!cls.isCompleted, "race for completing java.lang.Object")
281281
cls.info = ClassInfo(cls.owner.thisType, cls, AnyClass.typeRef :: Nil, newScope)
282-
cls.setFlag(NoInits)
282+
cls.setFlag(NoInits | JavaDefined)
283283

284284
// The companion object doesn't really exist, so it needs to be marked as
285285
// absent. Here we need to set it before completing attempt to load Object's

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

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ object Flags {
3737
else {
3838
val tbits = x.bits & y.bits & KINDFLAGS
3939
if (tbits == 0)
40-
assert(false, s"illegal flagset combination: $x and $y")
40+
assert(false, s"illegal flagset combination: ${x.flagsString} and ${y.flagsString}")
4141
FlagSet(tbits | ((x.bits | y.bits) & ~KINDFLAGS))
4242
}
4343

@@ -237,8 +237,8 @@ object Flags {
237237
/** A value or variable accessor (getter or setter) */
238238
val (AccessorOrSealed @ _, Accessor @ _, Sealed @ _) = newFlags(11, "<accessor>", "sealed")
239239

240-
/** A mutable var */
241-
val (_, Mutable @ _, _) = newFlags(12, "mutable")
240+
/** A mutable var, an open class */
241+
val (MutableOrOpen @ __, Mutable @ _, Open @ _) = newFlags(12, "mutable", "open")
242242

243243
/** Symbol is local to current class (i.e. private[this] or protected[this]
244244
* pre: Private or Protected are also set
@@ -422,7 +422,7 @@ object Flags {
422422
commonFlags(Private, Protected, Final, Case, Implicit, Given, Override, JavaStatic)
423423

424424
val TypeSourceModifierFlags: FlagSet =
425-
CommonSourceModifierFlags.toTypeFlags | Abstract | Sealed | Opaque
425+
CommonSourceModifierFlags.toTypeFlags | Abstract | Sealed | Opaque | Open
426426

427427
val TermSourceModifierFlags: FlagSet =
428428
CommonSourceModifierFlags.toTermFlags | Inline | AbsOverride | Lazy | Erased
@@ -439,7 +439,7 @@ object Flags {
439439
val FromStartFlags: FlagSet = commonFlags(
440440
Module, Package, Deferred, Method, Case,
441441
HigherKinded, Param, ParamAccessor,
442-
Scala2ExistentialCommon, Mutable, Opaque, Touched, JavaStatic,
442+
Scala2ExistentialCommon, MutableOrOpen, Opaque, Touched, JavaStatic,
443443
OuterOrCovariant, LabelOrContravariant, CaseAccessor,
444444
Extension, NonMember, Implicit, Given, Permanent, Synthetic,
445445
SuperAccessorOrScala2x, Inline, Macro)
@@ -509,12 +509,16 @@ object Flags {
509509
/** Flags retained in export forwarders */
510510
val RetainedExportFlags = Given | Implicit | Extension
511511

512+
/** Flags that apply only to classes */
513+
val ClassOnlyFlags = Sealed | Open | Abstract.toTypeFlags
514+
512515
// ------- Other flag sets -------------------------------------
513516

514517
val AbstractFinal: FlagSet = Abstract | Final
515518
val AbstractOverride: FlagSet = Abstract | Override
516519
val AbstractSealed: FlagSet = Abstract | Sealed
517520
val AbstractOrTrait: FlagSet = Abstract | Trait
521+
val EffectivelyOpenFlags = Abstract | JavaDefined | Open | Scala2x | Trait
518522
val PrivateAccessor: FlagSet = Accessor | Private
519523
val AccessorOrSynthetic: FlagSet = Accessor | Synthetic
520524
val EnumCase: FlagSet = Case | Enum

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

Lines changed: 2 additions & 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"
@@ -506,6 +506,7 @@ object StdNames {
506506
val nullExpr: N = "nullExpr"
507507
val ofDim: N = "ofDim"
508508
val opaque: N = "opaque"
509+
val open: N = "open"
509510
val ordinal: N = "ordinal"
510511
val ordinalDollar: N = "$ordinal"
511512
val ordinalDollar_ : N = "_$ordinal"

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

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,6 +1064,12 @@ object SymDenotations {
10641064
final def isEffectivelyFinal(implicit ctx: Context): Boolean =
10651065
isOneOf(EffectivelyFinalFlags) || !owner.isClass || owner.isOneOf(FinalOrModuleClass) || owner.isAnonymousClass
10661066

1067+
/** A class is effectively sealed if has the `final` or `sealed` modifier, or it
1068+
* is defined in Scala 3 and is neither abstract nor open.
1069+
*/
1070+
final def isEffectivelySealed(given Context): Boolean =
1071+
isOneOf(FinalOrSealed) || isClass && !isOneOf(EffectivelyOpenFlags)
1072+
10671073
/** The class containing this denotation which has the given effective name. */
10681074
final def enclosingClassNamed(name: Name)(implicit ctx: Context): Symbol = {
10691075
val cls = enclosingClass
@@ -1362,16 +1368,16 @@ object SymDenotations {
13621368
*/
13631369
def typeParamCreationFlags: FlagSet = TypeParam
13641370

1365-
override def toString: String = {
1366-
val kindString =
1367-
if (myFlags.is(ModuleClass)) "module class"
1368-
else if (isClass) "class"
1369-
else if (isType) "type"
1370-
else if (myFlags.is(Module)) "module"
1371-
else if (myFlags.is(Method)) "method"
1372-
else "val"
1373-
s"$kindString $name"
1374-
}
1371+
def kindString: String =
1372+
if myFlags.is(ModuleClass) then "module class"
1373+
else if myFlags.is(Trait) then "trait"
1374+
else if isClass then "class"
1375+
else if isType then "type"
1376+
else if myFlags.is(Module) then "module"
1377+
else if myFlags.is(Method) then "method"
1378+
else "val"
1379+
1380+
override def toString: String = s"$kindString $name"
13751381

13761382
// ----- Sanity checks and debugging */
13771383

compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ Standard-Section: "ASTs" TopLevelStat*
211211
EXTENSION -- An extension method
212212
PARAMsetter -- The setter part `x_=` of a var parameter `x` which itself is pickled as a PARAM
213213
EXPORTED -- An export forwarder
214+
OPEN -- an open class
214215
Annotation
215216
216217
Annotation = ANNOTATION Length tycon_Type fullAnnotation_Term -- An annotation, given (class) type of constructor, and full application tree
@@ -332,6 +333,7 @@ object TastyFormat {
332333
final val GIVEN = 37
333334
final val PARAMsetter = 38
334335
final val EXPORTED = 39
336+
final val OPEN = 40
335337

336338
// Cat. 2: tag Nat
337339

@@ -460,7 +462,7 @@ object TastyFormat {
460462

461463
/** Useful for debugging */
462464
def isLegalTag(tag: Int): Boolean =
463-
firstSimpleTreeTag <= tag && tag <= EXPORTED ||
465+
firstSimpleTreeTag <= tag && tag <= OPEN ||
464466
firstNatTreeTag <= tag && tag <= RENAMED ||
465467
firstASTTreeTag <= tag && tag <= BOUNDED ||
466468
firstNatASTTreeTag <= tag && tag <= NAMEDARG ||
@@ -505,6 +507,7 @@ object TastyFormat {
505507
| GIVEN
506508
| PARAMsetter
507509
| EXPORTED
510+
| OPEN
508511
| ANNOTATION
509512
| PRIVATEqualified
510513
| PROTECTEDqualified => true
@@ -565,6 +568,7 @@ object TastyFormat {
565568
case GIVEN => "GIVEN"
566569
case PARAMsetter => "PARAMsetter"
567570
case EXPORTED => "EXPORTED"
571+
case OPEN => "OPEN"
568572

569573
case SHAREDterm => "SHAREDterm"
570574
case SHAREDtype => "SHAREDtype"

compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,7 @@ class TreePickler(pickler: TastyPickler) {
669669
if (flags.is(Covariant)) writeModTag(COVARIANT)
670670
if (flags.is(Contravariant)) writeModTag(CONTRAVARIANT)
671671
if (flags.is(Opaque)) writeModTag(OPAQUE)
672+
if (flags.is(Open)) writeModTag(OPEN)
672673
}
673674
}
674675

compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,7 @@ class TreeUnpickler(reader: TastyReader,
631631
case GIVEN => addFlag(Given)
632632
case PARAMsetter => addFlag(ParamAccessor)
633633
case EXPORTED => addFlag(Exported)
634+
case OPEN => addFlag(Open)
634635
case PRIVATEqualified =>
635636
readByte()
636637
privateWithin = readWithin(ctx)
@@ -890,9 +891,9 @@ class TreeUnpickler(reader: TastyReader,
890891
untpd.ValDef(readName(), readTpt(), EmptyTree).withType(NoType)
891892
}
892893
else EmptyValDef
894+
cls.setNoInitsFlags(parentsKind(parents), bodyFlags)
893895
cls.info = ClassInfo(cls.owner.thisType, cls, parentTypes, cls.unforcedDecls,
894896
if (self.isEmpty) NoType else self.tpt.tpe)
895-
cls.setNoInitsFlags(parentsKind(parents), bodyFlags)
896897
val constr = readIndexedDef().asInstanceOf[DefDef]
897898
val mappedParents = parents.map(_.changeOwner(localDummy, constr.symbol))
898899

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2590,6 +2590,7 @@ object Parsers {
25902590
name match {
25912591
case nme.inline => Mod.Inline()
25922592
case nme.opaque => Mod.Opaque()
2593+
case nme.open => Mod.Open()
25932594
}
25942595
}
25952596

@@ -2661,7 +2662,7 @@ object Parsers {
26612662
* | AccessModifier
26622663
* | override
26632664
* | opaque
2664-
* LocalModifier ::= abstract | final | sealed | implicit | lazy | erased | inline
2665+
* LocalModifier ::= abstract | final | sealed | open | implicit | lazy | erased | inline
26652666
*/
26662667
def modifiers(allowed: BitSet = modifierTokens, start: Modifiers = Modifiers()): Modifiers = {
26672668
@tailrec

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,5 +286,5 @@ object Tokens extends TokensCommon {
286286

287287
final val scala3keywords = BitSet(ENUM, ERASED, GIVEN)
288288

289-
final val softModifierNames = Set(nme.inline, nme.opaque)
289+
final val softModifierNames = Set(nme.inline, nme.opaque, nme.open)
290290
}

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/reporting/diagnostic/messages.scala

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1790,13 +1790,10 @@ object messages {
17901790
extends Message(ClassAndCompanionNameClashID) {
17911791
val kind: String = "Naming"
17921792
val msg: String = em"Name clash: both ${cls.owner} and its companion object defines ${cls.name.stripModuleClassSuffix}"
1793-
val explanation: String = {
1794-
val kind = if (cls.owner.is(Flags.Trait)) "trait" else "class"
1795-
1796-
em"""|A $kind and its companion object cannot both define a ${hl("class")}, ${hl("trait")} or ${hl("object")} with the same name:
1793+
val explanation: String =
1794+
em"""|A ${cls.kindString} and its companion object cannot both define a ${hl("class")}, ${hl("trait")} or ${hl("object")} with the same name:
17971795
| - ${cls.owner} defines ${cls}
17981796
| - ${other.owner} defines ${other}"""
1799-
}
18001797
}
18011798

18021799
case class TailrecNotApplicable(symbol: Symbol)(implicit ctx: Context)

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/transform/SymUtils.scala

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ class SymUtils(val self: Symbol) extends AnyVal {
9292
*/
9393
def whyNotGenericSum(implicit ctx: Context): String =
9494
if (!self.is(Sealed))
95-
s"it is not a sealed ${if (self.is(Trait)) "trait" else "class"}"
95+
s"it is not a sealed ${self.kindString}"
9696
else {
9797
val children = self.children
9898
val companion = self.linkedClass
@@ -175,12 +175,18 @@ class SymUtils(val self: Symbol) extends AnyVal {
175175
def isEnumAnonymClass(implicit ctx: Context): Boolean =
176176
self.isAnonymousClass && (self.owner.name.eq(nme.DOLLAR_NEW) || self.owner.is(CaseVal))
177177

178-
/** Is this symbol defined locally (i.e. at some level owned by a term) and
179-
* defined in a different toplevel class than its supposed parent class `cls`?
180-
* Such children are not pickled, and have to be reconstituted manually.
178+
/** Is this symbol defined locally (i.e. at some level owned by a term) so that
179+
* it cannot be seen from parent class `cls`?
181180
*/
182-
def isInaccessibleChildOf(cls: Symbol)(implicit ctx: Context): Boolean =
183-
self.isLocal && !cls.topLevelClass.isLinkedWith(self.topLevelClass)
181+
def isInaccessibleChildOf(cls: Symbol)(given Context): Boolean =
182+
def isAccessible(sym: Symbol, cls: Symbol): Boolean =
183+
if cls.isType && !cls.is(Package) then
184+
isAccessible(sym, cls.owner)
185+
else
186+
sym == cls
187+
|| sym.is(Package)
188+
|| sym.isType && isAccessible(sym.owner, cls)
189+
!isAccessible(self.owner, cls)
184190

185191
/** If this is a sealed class, its known children in the order of textual occurrence */
186192
def children(implicit ctx: Context): List[Symbol] = {

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,10 @@ object Checking {
417417
}
418418
if (!sym.isClass && sym.is(Abstract))
419419
fail(OnlyClassesCanBeAbstract(sym))
420+
// note: this is not covered by the next test since terms can be abstract (which is a dual-mode flag)
421+
// but they can never be one of ClassOnlyFlags
422+
if !sym.isClass && sym.isOneOf(ClassOnlyFlags) then
423+
fail(em"only classes can be ${(sym.flags & ClassOnlyFlags).flagsString}")
420424
if (sym.is(AbsOverride) && !sym.owner.is(Trait))
421425
fail(AbstractOverrideOnlyInTraits(sym))
422426
if (sym.is(Trait) && sym.is(Final))
@@ -437,6 +441,8 @@ object Checking {
437441
}
438442
if (sym.isValueClass && sym.is(Trait) && !sym.isRefinementClass)
439443
fail(CannotExtendAnyVal(sym))
444+
checkCombination(Final, Open)
445+
checkCombination(Sealed, Open)
440446
checkCombination(Final, Sealed)
441447
checkCombination(Private, Protected)
442448
checkCombination(Abstract, Override)

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,10 +1160,16 @@ class Namer { typer: Typer =>
11601160
}
11611161
else {
11621162
val pclazz = pt.typeSymbol
1163-
if (pclazz.is(Final))
1163+
if pclazz.is(Final) then
11641164
ctx.error(ExtendFinalClass(cls, pclazz), cls.sourcePos)
1165-
if (pclazz.is(Sealed) && pclazz.associatedFile != cls.associatedFile)
1166-
ctx.error(UnableToExtendSealedClass(pclazz), cls.sourcePos)
1165+
else if pclazz.isEffectivelySealed && pclazz.associatedFile != cls.associatedFile then
1166+
if pclazz.is(Sealed) then
1167+
ctx.error(UnableToExtendSealedClass(pclazz), cls.sourcePos)
1168+
else if ctx.settings.strict.value then
1169+
checkFeature(nme.adhocExtensions,
1170+
i"Unless $pclazz is declared 'open', its extension in a separate file",
1171+
cls.topLevelClass,
1172+
parent.sourcePos)
11671173
pt
11681174
}
11691175
}

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

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1756,7 +1756,6 @@ class Typer extends Namer
17561756
// check value class constraints
17571757
checkDerivedValueClass(cls, body1)
17581758

1759-
17601759
// Temporarily set the typed class def as root tree so that we have at least some
17611760
// information in the IDE in case we never reach `SetRootTree`.
17621761
if (ctx.mode.is(Mode.Interactive) && ctx.settings.YretainTrees.value)
@@ -2104,17 +2103,19 @@ class Typer extends Namer
21042103
}
21052104

21062105
val ifpt = defn.asImplicitFunctionType(pt)
2107-
val result = if (ifpt.exists &&
2108-
xtree.isTerm &&
2109-
!untpd.isContextualClosure(xtree) &&
2110-
!ctx.mode.is(Mode.Pattern) &&
2111-
!ctx.isAfterTyper &&
2112-
!ctx.isInlineContext)
2113-
makeContextualFunction(xtree, ifpt)
2114-
else xtree match {
2115-
case xtree: untpd.NameTree => typedNamed(xtree, pt)
2116-
case xtree => typedUnnamed(xtree)
2117-
}
2106+
val result =
2107+
if ifpt.exists
2108+
&& xtree.isTerm
2109+
&& !untpd.isContextualClosure(xtree)
2110+
&& !ctx.mode.is(Mode.Pattern)
2111+
&& !ctx.isAfterTyper
2112+
&& !ctx.isInlineContext
2113+
then
2114+
makeContextualFunction(xtree, ifpt)
2115+
else xtree match
2116+
case xtree: untpd.NameTree => typedNamed(xtree, pt)
2117+
case xtree => typedUnnamed(xtree)
2118+
21182119
simplify(result, pt, locked)
21192120
}
21202121
}

0 commit comments

Comments
 (0)