Skip to content

Introduce open modifier on classes #7471

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Nov 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {

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

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

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

case class Abstract()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Abstract)
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ class Definitions {
ctx.newSymbol(owner, name, flags | Permanent, info)

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

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

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

// The companion object doesn't really exist, so it needs to be marked as
// absent. Here we need to set it before completing attempt to load Object's
Expand Down
14 changes: 9 additions & 5 deletions compiler/src/dotty/tools/dotc/core/Flags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ object Flags {
else {
val tbits = x.bits & y.bits & KINDFLAGS
if (tbits == 0)
assert(false, s"illegal flagset combination: $x and $y")
assert(false, s"illegal flagset combination: ${x.flagsString} and ${y.flagsString}")
FlagSet(tbits | ((x.bits | y.bits) & ~KINDFLAGS))
}

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

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

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

val TypeSourceModifierFlags: FlagSet =
CommonSourceModifierFlags.toTypeFlags | Abstract | Sealed | Opaque
CommonSourceModifierFlags.toTypeFlags | Abstract | Sealed | Opaque | Open

val TermSourceModifierFlags: FlagSet =
CommonSourceModifierFlags.toTermFlags | Inline | AbsOverride | Lazy | Erased
Expand All @@ -439,7 +439,7 @@ object Flags {
val FromStartFlags: FlagSet = commonFlags(
Module, Package, Deferred, Method, Case,
HigherKinded, Param, ParamAccessor,
Scala2ExistentialCommon, Mutable, Opaque, Touched, JavaStatic,
Scala2ExistentialCommon, MutableOrOpen, Opaque, Touched, JavaStatic,
OuterOrCovariant, LabelOrContravariant, CaseAccessor,
Extension, NonMember, Implicit, Given, Permanent, Synthetic,
SuperAccessorOrScala2x, Inline, Macro)
Expand Down Expand Up @@ -509,12 +509,16 @@ object Flags {
/** Flags retained in export forwarders */
val RetainedExportFlags = Given | Implicit | Extension

/** Flags that apply only to classes */
val ClassOnlyFlags = Sealed | Open | Abstract.toTypeFlags

// ------- Other flag sets -------------------------------------

val AbstractFinal: FlagSet = Abstract | Final
val AbstractOverride: FlagSet = Abstract | Override
val AbstractSealed: FlagSet = Abstract | Sealed
val AbstractOrTrait: FlagSet = Abstract | Trait
val EffectivelyOpenFlags = Abstract | JavaDefined | Open | Scala2x | Trait
val PrivateAccessor: FlagSet = Accessor | Private
val AccessorOrSynthetic: FlagSet = Accessor | Synthetic
val EnumCase: FlagSet = Case | Enum
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -367,8 +367,8 @@ object StdNames {
val TypeApply: N = "TypeApply"
val TypeRef: N = "TypeRef"
val UNIT : N = "UNIT"
val add_ : N = "add"
val acc: N = "acc"
val adhocExtensions: N = "adhocExtensions"
val annotation: N = "annotation"
val any2stringadd: N = "any2stringadd"
val anyHash: N = "anyHash"
Expand Down Expand Up @@ -506,6 +506,7 @@ object StdNames {
val nullExpr: N = "nullExpr"
val ofDim: N = "ofDim"
val opaque: N = "opaque"
val open: N = "open"
val ordinal: N = "ordinal"
val ordinalDollar: N = "$ordinal"
val ordinalDollar_ : N = "_$ordinal"
Expand Down
26 changes: 16 additions & 10 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,12 @@ object SymDenotations {
final def isEffectivelyFinal(implicit ctx: Context): Boolean =
isOneOf(EffectivelyFinalFlags) || !owner.isClass || owner.isOneOf(FinalOrModuleClass) || owner.isAnonymousClass

/** A class is effectively sealed if has the `final` or `sealed` modifier, or it
* is defined in Scala 3 and is neither abstract nor open.
*/
final def isEffectivelySealed(given Context): Boolean =
isOneOf(FinalOrSealed) || isClass && !isOneOf(EffectivelyOpenFlags)

/** The class containing this denotation which has the given effective name. */
final def enclosingClassNamed(name: Name)(implicit ctx: Context): Symbol = {
val cls = enclosingClass
Expand Down Expand Up @@ -1369,16 +1375,16 @@ object SymDenotations {
*/
def typeParamCreationFlags: FlagSet = TypeParam

override def toString: String = {
val kindString =
if (myFlags.is(ModuleClass)) "module class"
else if (isClass) "class"
else if (isType) "type"
else if (myFlags.is(Module)) "module"
else if (myFlags.is(Method)) "method"
else "val"
s"$kindString $name"
}
def kindString: String =
if myFlags.is(ModuleClass) then "module class"
else if myFlags.is(Trait) then "trait"
else if isClass then "class"
else if isType then "type"
else if myFlags.is(Module) then "module"
else if myFlags.is(Method) then "method"
else "val"

override def toString: String = s"$kindString $name"

// ----- Sanity checks and debugging */

Expand Down
6 changes: 5 additions & 1 deletion compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ Standard-Section: "ASTs" TopLevelStat*
EXTENSION -- An extension method
PARAMsetter -- The setter part `x_=` of a var parameter `x` which itself is pickled as a PARAM
EXPORTED -- An export forwarder
OPEN -- an open class
Annotation

Annotation = ANNOTATION Length tycon_Type fullAnnotation_Term -- An annotation, given (class) type of constructor, and full application tree
Expand Down Expand Up @@ -332,6 +333,7 @@ object TastyFormat {
final val GIVEN = 37
final val PARAMsetter = 38
final val EXPORTED = 39
final val OPEN = 40

// Cat. 2: tag Nat

Expand Down Expand Up @@ -460,7 +462,7 @@ object TastyFormat {

/** Useful for debugging */
def isLegalTag(tag: Int): Boolean =
firstSimpleTreeTag <= tag && tag <= EXPORTED ||
firstSimpleTreeTag <= tag && tag <= OPEN ||
firstNatTreeTag <= tag && tag <= RENAMED ||
firstASTTreeTag <= tag && tag <= BOUNDED ||
firstNatASTTreeTag <= tag && tag <= NAMEDARG ||
Expand Down Expand Up @@ -505,6 +507,7 @@ object TastyFormat {
| GIVEN
| PARAMsetter
| EXPORTED
| OPEN
| ANNOTATION
| PRIVATEqualified
| PROTECTEDqualified => true
Expand Down Expand Up @@ -565,6 +568,7 @@ object TastyFormat {
case GIVEN => "GIVEN"
case PARAMsetter => "PARAMsetter"
case EXPORTED => "EXPORTED"
case OPEN => "OPEN"

case SHAREDterm => "SHAREDterm"
case SHAREDtype => "SHAREDtype"
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,7 @@ class TreePickler(pickler: TastyPickler) {
if (flags.is(Covariant)) writeModTag(COVARIANT)
if (flags.is(Contravariant)) writeModTag(CONTRAVARIANT)
if (flags.is(Opaque)) writeModTag(OPAQUE)
if (flags.is(Open)) writeModTag(OPEN)
}
}

Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,7 @@ class TreeUnpickler(reader: TastyReader,
case GIVEN => addFlag(Given)
case PARAMsetter => addFlag(ParamAccessor)
case EXPORTED => addFlag(Exported)
case OPEN => addFlag(Open)
case PRIVATEqualified =>
readByte()
privateWithin = readWithin(ctx)
Expand Down Expand Up @@ -890,9 +891,9 @@ class TreeUnpickler(reader: TastyReader,
untpd.ValDef(readName(), readTpt(), EmptyTree).withType(NoType)
}
else EmptyValDef
cls.setNoInitsFlags(parentsKind(parents), bodyFlags)
cls.info = ClassInfo(cls.owner.thisType, cls, parentTypes, cls.unforcedDecls,
if (self.isEmpty) NoType else self.tpt.tpe)
cls.setNoInitsFlags(parentsKind(parents), bodyFlags)
val constr = readIndexedDef().asInstanceOf[DefDef]
val mappedParents = parents.map(_.changeOwner(localDummy, constr.symbol))

Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2590,6 +2590,7 @@ object Parsers {
name match {
case nme.inline => Mod.Inline()
case nme.opaque => Mod.Opaque()
case nme.open => Mod.Open()
}
}

Expand Down Expand Up @@ -2661,7 +2662,7 @@ object Parsers {
* | AccessModifier
* | override
* | opaque
* LocalModifier ::= abstract | final | sealed | implicit | lazy | erased | inline
* LocalModifier ::= abstract | final | sealed | open | implicit | lazy | erased | inline
*/
def modifiers(allowed: BitSet = modifierTokens, start: Modifiers = Modifiers()): Modifiers = {
@tailrec
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/parsing/Tokens.scala
Original file line number Diff line number Diff line change
Expand Up @@ -286,5 +286,5 @@ object Tokens extends TokensCommon {

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

final val softModifierNames = Set(nme.inline, nme.opaque)
final val softModifierNames = Set(nme.inline, nme.opaque, nme.open)
}
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/reporting/Reporter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -108,13 +108,13 @@ trait Reporting { this: Context =>
else {
reporter.reportNewFeatureUseSite(featureUseSite)
s"""
|This can be achieved by adding the import clause 'import $fqname'
|or by setting the compiler option -language:$feature.
|See the Scala docs for value $fqname for a discussion
|why the feature $req be explicitly enabled.""".stripMargin
}

val msg = s"$featureDescription $req be enabled\nby making the implicit value $fqname visible.$explain"
val msg = s"""$featureDescription $req be enabled
|by adding the import clause 'import $fqname'
|or by setting the compiler option -language:$feature.$explain""".stripMargin
if (required) error(msg, pos)
else reportWarning(new FeatureWarning(msg, pos))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1790,13 +1790,10 @@ object messages {
extends Message(ClassAndCompanionNameClashID) {
val kind: String = "Naming"
val msg: String = em"Name clash: both ${cls.owner} and its companion object defines ${cls.name.stripModuleClassSuffix}"
val explanation: String = {
val kind = if (cls.owner.is(Flags.Trait)) "trait" else "class"

em"""|A $kind and its companion object cannot both define a ${hl("class")}, ${hl("trait")} or ${hl("object")} with the same name:
val explanation: String =
em"""|A ${cls.kindString} and its companion object cannot both define a ${hl("class")}, ${hl("trait")} or ${hl("object")} with the same name:
| - ${cls.owner} defines ${cls}
| - ${other.owner} defines ${other}"""
}
}

case class TailrecNotApplicable(symbol: Symbol)(implicit ctx: Context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1544,6 +1544,7 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend
def Flags_Private: Flags = core.Flags.Private
def Flags_Protected: Flags = core.Flags.Protected
def Flags_Abstract: Flags = core.Flags.Abstract
def Flags_Open: Flags = core.Flags.Open
def Flags_Final: Flags = core.Flags.Final
def Flags_Sealed: Flags = core.Flags.Sealed
def Flags_Case: Flags = core.Flags.Case
Expand Down
18 changes: 12 additions & 6 deletions compiler/src/dotty/tools/dotc/transform/SymUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ class SymUtils(val self: Symbol) extends AnyVal {
*/
def whyNotGenericSum(implicit ctx: Context): String =
if (!self.is(Sealed))
s"it is not a sealed ${if (self.is(Trait)) "trait" else "class"}"
s"it is not a sealed ${self.kindString}"
else {
val children = self.children
val companion = self.linkedClass
Expand Down Expand Up @@ -175,12 +175,18 @@ class SymUtils(val self: Symbol) extends AnyVal {
def isEnumAnonymClass(implicit ctx: Context): Boolean =
self.isAnonymousClass && (self.owner.name.eq(nme.DOLLAR_NEW) || self.owner.is(CaseVal))

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

/** If this is a sealed class, its known children in the order of textual occurrence */
def children(implicit ctx: Context): List[Symbol] = {
Expand Down
6 changes: 6 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,10 @@ object Checking {
}
if (!sym.isClass && sym.is(Abstract))
fail(OnlyClassesCanBeAbstract(sym))
// note: this is not covered by the next test since terms can be abstract (which is a dual-mode flag)
// but they can never be one of ClassOnlyFlags
if !sym.isClass && sym.isOneOf(ClassOnlyFlags) then
fail(em"only classes can be ${(sym.flags & ClassOnlyFlags).flagsString}")
if (sym.is(AbsOverride) && !sym.owner.is(Trait))
fail(AbstractOverrideOnlyInTraits(sym))
if (sym.is(Trait) && sym.is(Final))
Expand All @@ -437,6 +441,8 @@ object Checking {
}
if (sym.isValueClass && sym.is(Trait) && !sym.isRefinementClass)
fail(CannotExtendAnyVal(sym))
checkCombination(Final, Open)
checkCombination(Sealed, Open)
checkCombination(Final, Sealed)
checkCombination(Private, Protected)
checkCombination(Abstract, Override)
Expand Down
12 changes: 9 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1162,10 +1162,16 @@ class Namer { typer: Typer =>
}
else {
val pclazz = pt.typeSymbol
if (pclazz.is(Final))
if pclazz.is(Final) then
ctx.error(ExtendFinalClass(cls, pclazz), cls.sourcePos)
if (pclazz.is(Sealed) && pclazz.associatedFile != cls.associatedFile)
ctx.error(UnableToExtendSealedClass(pclazz), cls.sourcePos)
else if pclazz.isEffectivelySealed && pclazz.associatedFile != cls.associatedFile then
if pclazz.is(Sealed) then
ctx.error(UnableToExtendSealedClass(pclazz), cls.sourcePos)
else if ctx.settings.strict.value then
checkFeature(nme.adhocExtensions,
i"Unless $pclazz is declared 'open', its extension in a separate file",
cls.topLevelClass,
parent.sourcePos)
pt
}
}
Expand Down
25 changes: 13 additions & 12 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1756,7 +1756,6 @@ class Typer extends Namer
// check value class constraints
checkDerivedValueClass(cls, body1)


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

val ifpt = defn.asImplicitFunctionType(pt)
val result = if (ifpt.exists &&
xtree.isTerm &&
!untpd.isContextualClosure(xtree) &&
!ctx.mode.is(Mode.Pattern) &&
!ctx.isAfterTyper &&
!ctx.isInlineContext)
makeContextualFunction(xtree, ifpt)
else xtree match {
case xtree: untpd.NameTree => typedNamed(xtree, pt)
case xtree => typedUnnamed(xtree)
}
val result =
if ifpt.exists
&& xtree.isTerm
&& !untpd.isContextualClosure(xtree)
&& !ctx.mode.is(Mode.Pattern)
&& !ctx.isAfterTyper
&& !ctx.isInlineContext
then
makeContextualFunction(xtree, ifpt)
else xtree match
case xtree: untpd.NameTree => typedNamed(xtree, pt)
case xtree => typedUnnamed(xtree)

simplify(result, pt, locked)
}
}
Expand Down
Loading