Skip to content

Fix #9028: Introduce super traits #9032

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 9 commits into from
Jun 10, 2020
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 @@ -200,6 +200,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
case class Inline()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Inline)

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

case class Super()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.SuperTrait)
}

/** Modifiers and annotations for definitions
Expand Down
54 changes: 36 additions & 18 deletions compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala
Original file line number Diff line number Diff line change
Expand Up @@ -300,8 +300,12 @@ trait ConstraintHandling[AbstractContext] {
* (i.e. `inst.widenSingletons <:< bound` succeeds with satisfiable constraint)
* 2. If `inst` is a union type, approximate the union type from above by an intersection
* of all common base types, provided the result is a subtype of `bound`.
* 3. (currently not enabled, see #9028) If `inst` is an intersection with some restricted base types, drop
* the restricted base types from the intersection, provided the result is a subtype of `bound`.
* 3. If `inst` is an intersection such that some operands are super trait instances
* and others are not, replace as many super trait instances as possible with Any
* as long as the result is still a subtype of `bound`. But fall back to the
* original type if the resulting widened type is a supertype of all dropped
* types (since in this case the type was not a true intersection of super traits
* and other types to start with).
*
* Don't do these widenings if `bound` is a subtype of `scala.Singleton`.
* Also, if the result of these widenings is a TypeRef to a module class,
Expand All @@ -313,21 +317,38 @@ trait ConstraintHandling[AbstractContext] {
*/
def widenInferred(inst: Type, bound: Type)(implicit actx: AbstractContext): Type =

def isRestricted(tp: Type) = tp.typeSymbol == defn.EnumValueClass // for now, to be generalized later
def dropSuperTraits(tp: Type): Type =
var kept: Set[Type] = Set() // types to keep since otherwise bound would not fit
var dropped: List[Type] = List() // the types dropped so far, last one on top

def dropOneSuperTrait(tp: Type): Type =
val tpd = tp.dealias
if tpd.typeSymbol.isSuperTrait && !tpd.isLambdaSub && !kept.contains(tpd) then
dropped = tpd :: dropped
defn.AnyType
else tpd match
case AndType(tp1, tp2) =>
val tp1w = dropOneSuperTrait(tp1)
if tp1w ne tp1 then tp1w & tp2
else
val tp2w = dropOneSuperTrait(tp2)
if tp2w ne tp2 then tp1 & tp2w
else tpd
case _ =>
tp

def dropRestricted(tp: Type): Type = tp.dealias match
case tpd @ AndType(tp1, tp2) =>
if isRestricted(tp1) then tp2
else if isRestricted(tp2) then tp1
def recur(tp: Type): Type =
val tpw = dropOneSuperTrait(tp)
if tpw eq tp then tp
else if tpw <:< bound then recur(tpw)
else
val tpw = tpd.derivedAndType(dropRestricted(tp1), dropRestricted(tp2))
if tpw ne tpd then tpw else tp
case _ =>
tp
kept += dropped.head
dropped = dropped.tail
recur(tp)

def widenRestricted(tp: Type) =
val tpw = dropRestricted(tp)
if (tpw ne tp) && (tpw <:< bound) then tpw else tp
val tpw = recur(tp)
if (tpw eq tp) || dropped.forall(_ frozen_<:< tpw) then tp else tpw
end dropSuperTraits

def widenOr(tp: Type) =
val tpw = tp.widenUnion
Expand All @@ -343,10 +364,7 @@ trait ConstraintHandling[AbstractContext] {

val wideInst =
if isSingleton(bound) then inst
else /*widenRestricted*/(widenOr(widenSingle(inst)))
// widenRestricted is currently not called since it's special cased in `dropEnumValue`
// in `Namer`. It's left in here in case we want to generalize the scheme to other
// "protected inheritance" classes.
else dropSuperTraits(widenOr(widenSingle(inst)))
wideInst match
case wideInst: TypeRef if wideInst.symbol.is(Module) =>
TermRef(wideInst.prefix, wideInst.symbol.sourceModule)
Expand Down
17 changes: 16 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,6 @@ class Definitions {
@tu lazy val EnumClass: ClassSymbol = ctx.requiredClass("scala.Enum")
@tu lazy val Enum_ordinal: Symbol = EnumClass.requiredMethod(nme.ordinal)

@tu lazy val EnumValueClass: ClassSymbol = ctx.requiredClass("scala.runtime.EnumValue")
@tu lazy val EnumValuesClass: ClassSymbol = ctx.requiredClass("scala.runtime.EnumValues")
@tu lazy val ProductClass: ClassSymbol = ctx.requiredClass("scala.Product")
@tu lazy val Product_canEqual : Symbol = ProductClass.requiredMethod(nme.canEqual_)
Expand Down Expand Up @@ -804,6 +803,7 @@ class Definitions {
@tu lazy val ScalaStrictFPAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.strictfp")
@tu lazy val ScalaStaticAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.static")
@tu lazy val SerialVersionUIDAnnot: ClassSymbol = ctx.requiredClass("scala.SerialVersionUID")
@tu lazy val SuperTraitAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.superTrait")
@tu lazy val TASTYSignatureAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.TASTYSignature")
@tu lazy val TASTYLongSignatureAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.TASTYLongSignature")
@tu lazy val TailrecAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.tailrec")
Expand Down Expand Up @@ -1308,6 +1308,21 @@ class Definitions {
def isInfix(sym: Symbol)(implicit ctx: Context): Boolean =
(sym eq Object_eq) || (sym eq Object_ne)

@tu lazy val assumedSuperTraits =
Set(ComparableClass, ProductClass, SerializableClass,
// add these for now, until we had a chance to retrofit 2.13 stdlib
// we should do a more through sweep through it then.
ctx.requiredClass("scala.collection.SortedOps"),
ctx.requiredClass("scala.collection.StrictOptimizedSortedSetOps"),
ctx.requiredClass("scala.collection.generic.DefaultSerializable"),
ctx.requiredClass("scala.collection.generic.IsIterable"),
ctx.requiredClass("scala.collection.generic.IsIterableOnce"),
ctx.requiredClass("scala.collection.generic.IsMap"),
ctx.requiredClass("scala.collection.generic.IsSeq"),
ctx.requiredClass("scala.collection.generic.Subtractable"),
ctx.requiredClass("scala.collection.immutable.StrictOptimizedSeqOps")
)

// ----- primitive value class machinery ------------------------------------------

/** This class would also be obviated by the implicit function type design */
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/core/Flags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,8 @@ object Flags {
/** Labeled with `final` modifier */
val (Final @ _, _, _) = newFlags(6, "final")

/** A method symbol */
val (_, Method @ _, HigherKinded @ _) = newFlags(7, "<method>", "<higher kinded>") // TODO drop HigherKinded
/** A method symbol / a super trait */
val (_, Method @ _, SuperTrait @ _) = newFlags(7, "<method>", "super")

/** A (term or type) parameter to a class or method */
val (Param @ _, TermParam @ _, TypeParam @ _) = newFlags(8, "<param>")
Expand Down Expand Up @@ -439,7 +439,7 @@ object Flags {
*/
val FromStartFlags: FlagSet = commonFlags(
Module, Package, Deferred, Method, Case, Enum,
HigherKinded, Param, ParamAccessor,
SuperTrait, Param, ParamAccessor,
Scala2SpecialFlags, MutableOrOpen, Opaque, Touched, JavaStatic,
OuterOrCovariant, LabelOrContravariant, CaseAccessor,
Extension, NonMember, Implicit, Given, Permanent, Synthetic,
Expand Down
6 changes: 6 additions & 0 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1166,6 +1166,12 @@ object SymDenotations {
final def isEffectivelySealed(using Context): Boolean =
isOneOf(FinalOrSealed) || isClass && !isOneOf(EffectivelyOpenFlags)

final def isSuperTrait(using Context): Boolean =
isClass
&& (is(SuperTrait)
|| defn.assumedSuperTraits.contains(symbol.asClass)
|| hasAnnotation(defn.SuperTraitAnnot))

/** 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
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 @@ -705,6 +705,7 @@ class TreePickler(pickler: TastyPickler) {
if (flags.is(Sealed)) writeModTag(SEALED)
if (flags.is(Abstract)) writeModTag(ABSTRACT)
if (flags.is(Trait)) writeModTag(TRAIT)
if flags.is(SuperTrait) then writeModTag(SUPERTRAIT)
if (flags.is(Covariant)) writeModTag(COVARIANT)
if (flags.is(Contravariant)) writeModTag(CONTRAVARIANT)
if (flags.is(Opaque)) writeModTag(OPAQUE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,7 @@ class TreeUnpickler(reader: TastyReader,
case STATIC => addFlag(JavaStatic)
case OBJECT => addFlag(Module)
case TRAIT => addFlag(Trait)
case SUPERTRAIT => addFlag(SuperTrait)
case ENUM => addFlag(Enum)
case LOCAL => addFlag(Local)
case SYNTHETIC => addFlag(Synthetic)
Expand Down
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3434,7 +3434,7 @@ object Parsers {
}
}

/** TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef
/** TmplDef ::= ([‘case’] ‘class’ | [‘super’] ‘trait’) ClassDef
* | [‘case’] ‘object’ ObjectDef
* | ‘enum’ EnumDef
* | ‘given’ GivenDef
Expand All @@ -3444,6 +3444,8 @@ object Parsers {
in.token match {
case TRAIT =>
classDef(start, posMods(start, addFlag(mods, Trait)))
case SUPERTRAIT =>
classDef(start, posMods(start, addFlag(mods, Trait | SuperTrait)))
case CLASS =>
classDef(start, posMods(start, mods))
case CASECLASS =>
Expand Down
7 changes: 6 additions & 1 deletion compiler/src/dotty/tools/dotc/parsing/Scanners.scala
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,8 @@ object Scanners {
currentRegion = r.outer
case _ =>

/** - Join CASE + CLASS => CASECLASS, CASE + OBJECT => CASEOBJECT, SEMI + ELSE => ELSE, COLON + <EOL> => COLONEOL
/** - Join CASE + CLASS => CASECLASS, CASE + OBJECT => CASEOBJECT, SUPER + TRAIT => SUPERTRAIT
* SEMI + ELSE => ELSE, COLON + <EOL> => COLONEOL
* - Insert missing OUTDENTs at EOF
*/
def postProcessToken(): Unit = {
Expand All @@ -602,6 +603,10 @@ object Scanners {
if (token == CLASS) fuse(CASECLASS)
else if (token == OBJECT) fuse(CASEOBJECT)
else reset()
case SUPER =>
lookAhead()
if token == TRAIT then fuse(SUPERTRAIT)
else reset()
case SEMI =>
lookAhead()
if (token != ELSE) reset()
Expand Down
5 changes: 3 additions & 2 deletions compiler/src/dotty/tools/dotc/parsing/Tokens.scala
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@ object Tokens extends TokensCommon {
final val ERASED = 63; enter(ERASED, "erased")
final val GIVEN = 64; enter(GIVEN, "given")
final val EXPORT = 65; enter(EXPORT, "export")
final val MACRO = 66; enter(MACRO, "macro") // TODO: remove
final val SUPERTRAIT = 66; enter(SUPERTRAIT, "super trait")
final val MACRO = 67; enter(MACRO, "macro") // TODO: remove

/** special symbols */
final val NEWLINE = 78; enter(NEWLINE, "end of statement", "new line")
Expand Down Expand Up @@ -233,7 +234,7 @@ object Tokens extends TokensCommon {
final val canStartTypeTokens: TokenSet = literalTokens | identifierTokens | BitSet(
THIS, SUPER, USCORE, LPAREN, AT)

final val templateIntroTokens: TokenSet = BitSet(CLASS, TRAIT, OBJECT, ENUM, CASECLASS, CASEOBJECT)
final val templateIntroTokens: TokenSet = BitSet(CLASS, TRAIT, OBJECT, ENUM, CASECLASS, CASEOBJECT, SUPERTRAIT)

final val dclIntroTokens: TokenSet = BitSet(DEF, VAL, VAR, TYPE, GIVEN)

Expand Down
9 changes: 7 additions & 2 deletions compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -729,7 +729,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
}

private def Modifiers(sym: Symbol): Modifiers = untpd.Modifiers(
sym.flags & (if (sym.isType) ModifierFlags | VarianceFlags else ModifierFlags),
sym.flags & (if (sym.isType) ModifierFlags | VarianceFlags | SuperTrait else ModifierFlags),
if (sym.privateWithin.exists) sym.privateWithin.asType.name else tpnme.EMPTY,
sym.annotations.filterNot(ann => dropAnnotForModText(ann.symbol)).map(_.tree))

Expand Down Expand Up @@ -839,7 +839,11 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
}

protected def templateText(tree: TypeDef, impl: Template): Text = {
val decl = modText(tree.mods, tree.symbol, keywordStr(if (tree.mods.is(Trait)) "trait" else "class"), isType = true)
val kw =
if tree.mods.is(SuperTrait) then "super trait"
else if tree.mods.is(Trait) then "trait"
else "class"
val decl = modText(tree.mods, tree.symbol, keywordStr(kw), isType = true)
( decl ~~ typeText(nameIdText(tree)) ~ withEnclosingDef(tree) { toTextTemplate(impl) }
// ~ (if (tree.hasType && printDebug) i"[decls = ${tree.symbol.info.decls}]" else "") // uncomment to enable
)
Expand Down Expand Up @@ -945,6 +949,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
else if (sym.isPackageObject) "package object"
else if (flags.is(Module) && flags.is(Case)) "case object"
else if (sym.isClass && flags.is(Case)) "case class"
else if sym.isClass && flags.is(SuperTrait) then "super trait"
else super.keyString(sym)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ class ExtractSemanticDB extends Phase:

private def excludeChildren(sym: Symbol)(using Context): Boolean =
!sym.exists
|| sym.isAllOf(HigherKinded | Param)
|| sym.is(Param) && sym.info.bounds.hi.isInstanceOf[Types.HKTypeLambda]

/** Uses of this symbol where the reference has given span should be excluded from semanticdb */
private def excludeUse(qualifier: Option[Symbol], sym: Symbol)(using Context): Boolean =
Expand Down
18 changes: 1 addition & 17 deletions compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,6 @@ class Namer { typer: Typer =>
case _: TypeBoundsTree | _: MatchTypeTree =>
flags |= Deferred // Typedefs with Match rhs classify as abstract
case LambdaTypeTree(_, body) =>
flags |= HigherKinded
analyzeRHS(body)
case _ =>
if rhs.isEmpty || flags.is(Opaque) then flags |= Deferred
Expand Down Expand Up @@ -1459,19 +1458,6 @@ class Namer { typer: Typer =>
// println(s"owner = ${sym.owner}, decls = ${sym.owner.info.decls.show}")
def isInlineVal = sym.isOneOf(FinalOrInline, butNot = Method | Mutable)

def isEnumValue(tp: Type) = tp.typeSymbol == defn.EnumValueClass

// Drop EnumValue parents from inferred types of enum constants
def dropEnumValue(tp: Type): Type = tp.dealias match
case tpd @ AndType(tp1, tp2) =>
if isEnumValue(tp1) then tp2
else if isEnumValue(tp2) then tp1
else
val tpw = tpd.derivedAndType(dropEnumValue(tp1), dropEnumValue(tp2))
if tpw ne tpd then tpw else tp
case _ =>
tp

// Widen rhs type and eliminate `|' but keep ConstantTypes if
// definition is inline (i.e. final in Scala2) and keep module singleton types
// instead of widening to the underlying module class types.
Expand All @@ -1480,9 +1466,7 @@ class Namer { typer: Typer =>
def widenRhs(tp: Type): Type =
tp.widenTermRefExpr.simplified match
case ctp: ConstantType if isInlineVal => ctp
case tp =>
val tp1 = ctx.typeComparer.widenInferred(tp, rhsProto)
if sym.is(Enum) then dropEnumValue(tp1) else tp1
case tp => ctx.typeComparer.widenInferred(tp, rhsProto)

// Replace aliases to Unit by Unit itself. If we leave the alias in
// it would be erased to BoxedUnit.
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/internals/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ VarDef ::= PatDef
DefDef ::= DefSig [‘:’ Type] ‘=’ Expr DefDef(_, name, tparams, vparamss, tpe, expr)
| ‘this’ DefParamClause DefParamClauses ‘=’ ConstrExpr DefDef(_, <init>, Nil, vparamss, EmptyTree, expr | Block)

TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef
TmplDef ::= ([‘case’] ‘class’ | [‘super’] ‘trait’) ClassDef
| [‘case’] ‘object’ ObjectDef
| ‘enum’ EnumDef
| ‘given’ GivenDef
Expand Down
Loading