Skip to content

Fix #10239: @mixin trait instead of super trait #10243

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 2 commits into from
Nov 9, 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: 0 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,6 @@ 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
24 changes: 12 additions & 12 deletions compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala
Original file line number Diff line number Diff line change
Expand Up @@ -286,35 +286,35 @@ trait ConstraintHandling {
}
}

/** If `tp` 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
/** If `tp` is an intersection such that some operands are mixin trait instances
* and others are not, replace as many mixin 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
* types (since in this case the type was not a true intersection of mixin traits
* and other types to start with).
*/
def dropSuperTraits(tp: Type, bound: Type)(using Context): Type =
def dropMixinTraits(tp: Type, bound: Type)(using Context): 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 =
def dropOneMixinTrait(tp: Type): Type =
val tpd = tp.dealias
if tpd.typeSymbol.isSuperTrait && !tpd.isLambdaSub && !kept.contains(tpd) then
if tpd.typeSymbol.isMixinTrait && !tpd.isLambdaSub && !kept.contains(tpd) then
dropped = tpd :: dropped
defn.AnyType
else tpd match
case AndType(tp1, tp2) =>
val tp1w = dropOneSuperTrait(tp1)
val tp1w = dropOneMixinTrait(tp1)
if tp1w ne tp1 then tp1w & tp2
else
val tp2w = dropOneSuperTrait(tp2)
val tp2w = dropOneMixinTrait(tp2)
if tp2w ne tp2 then tp1 & tp2w
else tpd
case _ =>
tp

def recur(tp: Type): Type =
val tpw = dropOneSuperTrait(tp)
val tpw = dropOneMixinTrait(tp)
if tpw eq tp then tp
else if tpw <:< bound then recur(tpw)
else
Expand All @@ -324,15 +324,15 @@ trait ConstraintHandling {

val tpw = recur(tp)
if (tpw eq tp) || dropped.forall(_ frozen_<:< tpw) then tp else tpw
end dropSuperTraits
end dropMixinTraits

/** Widen inferred type `inst` with upper `bound`, according to the following rules:
* 1. If `inst` is a singleton type, or a union containing some singleton types,
* widen (all) the singleton type(s), provided the result is a subtype of `bound`
* (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. drop super traits from intersections (see @dropSuperTraits)
* 3. drop mixin traits from intersections (see @dropMixinTraits)
*
* 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 @@ -357,7 +357,7 @@ trait ConstraintHandling {

val wideInst =
if isSingleton(bound) then inst
else dropSuperTraits(widenOr(widenSingle(inst)), bound)
else dropMixinTraits(widenOr(widenSingle(inst)), bound)
wideInst match
case wideInst: TypeRef if wideInst.symbol.is(Module) =>
TermRef(wideInst.prefix, wideInst.symbol.sourceModule)
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -916,6 +916,7 @@ class Definitions {
@tu lazy val InvariantBetweenAnnot: ClassSymbol = requiredClass("scala.annotation.internal.InvariantBetween")
@tu lazy val MainAnnot: ClassSymbol = requiredClass("scala.main")
@tu lazy val MigrationAnnot: ClassSymbol = requiredClass("scala.annotation.migration")
@tu lazy val MixinAnnot: ClassSymbol = requiredClass("scala.annotation.mixin")
@tu lazy val NativeAnnot: ClassSymbol = requiredClass("scala.native")
@tu lazy val RepeatedAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Repeated")
@tu lazy val SourceFileAnnot: ClassSymbol = requiredClass("scala.annotation.internal.SourceFile")
Expand All @@ -924,7 +925,6 @@ class Definitions {
@tu lazy val ScalaStrictFPAnnot: ClassSymbol = requiredClass("scala.annotation.strictfp")
@tu lazy val ScalaStaticAnnot: ClassSymbol = requiredClass("scala.annotation.static")
@tu lazy val SerialVersionUIDAnnot: ClassSymbol = requiredClass("scala.SerialVersionUID")
@tu lazy val SuperTraitAnnot: ClassSymbol = requiredClass("scala.annotation.superTrait")
@tu lazy val TASTYSignatureAnnot: ClassSymbol = requiredClass("scala.annotation.internal.TASTYSignature")
@tu lazy val TASTYLongSignatureAnnot: ClassSymbol = requiredClass("scala.annotation.internal.TASTYLongSignature")
@tu lazy val TailrecAnnot: ClassSymbol = requiredClass("scala.annotation.tailrec")
Expand Down Expand Up @@ -1513,7 +1513,7 @@ class Definitions {
def isInfix(sym: Symbol)(using Context): Boolean =
(sym eq Object_eq) || (sym eq Object_ne)

@tu lazy val assumedSuperTraits =
@tu lazy val assumedMixinTraits =
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.
Expand Down
5 changes: 2 additions & 3 deletions compiler/src/dotty/tools/dotc/core/Flags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ object Flags {
val (Final @ _, _, _) = newFlags(6, "final")

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

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

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

/** The class containing this denotation which has the given effective name. */
final def enclosingClassNamed(name: Name)(using Context): Symbol = {
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2638,8 +2638,8 @@ object TypeComparer {
def widenInferred(inst: Type, bound: Type)(using Context): Type =
comparing(_.widenInferred(inst, bound))

def dropSuperTraits(tp: Type, bound: Type)(using Context): Type =
comparing(_.dropSuperTraits(tp, bound))
def dropMixinTraits(tp: Type, bound: Type)(using Context): Type =
comparing(_.dropMixinTraits(tp, bound))

def constrainPatternType(pat: Type, scrut: Type)(using Context): Boolean =
comparing(_.constrainPatternType(pat, scrut))
Expand Down
1 change: 0 additions & 1 deletion compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -729,7 +729,6 @@ 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
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,6 @@ 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 All @@ -672,6 +671,9 @@ class TreeUnpickler(reader: TastyReader,
case PROTECTEDqualified =>
addFlag(Protected)
privateWithin = readWithin
case SUPERTRAIT =>
readByte()
annotFns = (_ => Annotation(defn.MixinAnnot)) :: annotFns
case ANNOTATION =>
annotFns = readAnnot :: annotFns
case tag =>
Expand Down
4 changes: 1 addition & 3 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3378,7 +3378,7 @@ object Parsers {
}
}

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

/** - Join CASE + CLASS => CASECLASS, CASE + OBJECT => CASEOBJECT, SUPER + TRAIT => SUPERTRAIT
/** - Join CASE + CLASS => CASECLASS, CASE + OBJECT => CASEOBJECT
* SEMI + ELSE => ELSE, COLON + <EOL> => COLONEOL
* - Insert missing OUTDENTs at EOF
*/
Expand All @@ -571,10 +571,6 @@ 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
3 changes: 1 addition & 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,6 @@ 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 SUPERTRAIT = 66; enter(SUPERTRAIT, "super trait")
final val MACRO = 67; enter(MACRO, "macro") // TODO: remove

/** special symbols */
Expand Down Expand Up @@ -234,7 +233,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, SUPERTRAIT)
final val templateIntroTokens: TokenSet = BitSet(CLASS, TRAIT, OBJECT, ENUM, CASECLASS, CASEOBJECT)

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

Expand Down
8 changes: 2 additions & 6 deletions compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -738,7 +738,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
if mdef.hasType then Modifiers(mdef.symbol) else mdef.rawMods

private def Modifiers(sym: Symbol): Modifiers = untpd.Modifiers(
sym.flags & (if (sym.isType) ModifierFlags | VarianceFlags | SuperTrait else ModifierFlags),
sym.flags & (if (sym.isType) ModifierFlags | VarianceFlags 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 @@ -856,10 +856,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
}

protected def templateText(tree: TypeDef, impl: Template): Text = {
val kw =
if tree.mods.is(SuperTrait) then "super trait"
else if tree.mods.is(Trait) then "trait"
else "class"
val kw = 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 @@ -966,7 +963,6 @@ 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
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1133,7 +1133,7 @@ trait Applications extends Compatibility {
&& tree.tpe.classSymbol.isEnumCase
&& tree.tpe.widen.isValueType
then
val widened = TypeComparer.dropSuperTraits(
val widened = TypeComparer.dropMixinTraits(
tree.tpe.parents.reduceLeft(TypeComparer.andType(_, _)),
pt)
if widened <:< pt then Typed(tree, TypeTree(widened))
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 @@ -387,7 +387,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’ | [‘super’] ‘trait’) ClassDef
TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef
| [‘case’] ‘object’ ObjectDef
| ‘enum’ EnumDef
| ‘given’ GivenDef
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
layout: doc-page
title: super traits
title: Mixin Traits
---

Traits are used in two roles:
Expand All @@ -9,7 +9,7 @@ Traits are used in two roles:
2. As types of vals, defs, or parameters

Some traits are used primarily in the first role, and we usually do not want to see them in inferred types. An example is the `Product` trait that the compiler
adds as a super trait to every case class or case object. In Scala 2, this parent trait sometimes makes inferred types more complicated than they should be. Example:
adds as a mixin trait to every case class or case object. In Scala 2, this parent trait sometimes makes inferred types more complicated than they should be. Example:
```scala
trait Kind
case object Var extends Kind
Expand All @@ -20,46 +20,35 @@ Here, the inferred type of `x` is `Set[Kind & Product & Serializable]` whereas o

- The type of the conditional above is the union type `Val | Var`.
- A union type is widened in type inference to the least supertype that is
not a union type. In the example, this type is `Kind & Product & Serializable` since all three traits are supertraits of both `Val` and `Var`.
not a union type. In the example, this type is `Kind & Product & Serializable` since all three traits are traits of both `Val` and `Var`.
So that type becomes the inferred element type of the set.

Scala 3 allows one to mark a trait as a `super` trait, which means that it can be suppressed in type inference. Here's an example that follows the lines of the
code above, but now with a new super trait `S` instead of `Product`:
Scala 3 allows one to mark a trait as a `@mixin` trait, which means that it can be suppressed in type inference. Here's an example that follows the lines of the
code above, but now with a new mixin trait `S` instead of `Product`:
```scala
super trait S
@mixin trait S
trait Kind
object Var extends Kind, S
object Val extends Kind, S
val x = Set(if condition then Val else Var)
```
Now `x` has inferred type `Set[Kind]`. The common super trait `S` does not
Now `x` has inferred type `Set[Kind]`. The common mixin trait `S` does not
appear in the inferred type.

### Super Traits
### Mixin Traits

The traits `scala.Product`, `java.lang.Serializable` and `java.lang.Comparable`
are treated automatically as super traits. Other traits can be turned into super traits, by adding the keyword `super` in front of `trait`, as shown above.
are treated automatically as mixin traits. Other traits can be turned into mixin traits, by adding the annotation `@mixin` in front of `trait`, as shown above.

Every trait can be declared as a super trait. Typically super traits are traits that influence the implementation of inheriting classes and traits and that are not usually used as types by themselves. Two examples from the
Every trait can be declared as a mixin trait. Typically mixin traits are traits that influence the implementation of inheriting classes and traits and that are not usually used as types by themselves. Two examples from the
standard collection library:

- `IterableOps`, which provides method implementations for an `Iterable`
- `StrictOptimizedSeqOps`, which optimises some of these implementations for
sequences with efficient indexing.

Generally, any trait that is extended recursively is a good candidate to be
declared a super trait.

### Retro-Fitting Scala 2 Libraries

To allow cross-building between Scala 2 and 3, super traits can also be
introduced by adding the `@superTrait` annotation, which is defined in package `scala.annotation`. Example:
```scala
import scala.annotation.superTrait

@superTrait trait StrictOptimizedSeqOps[+A, +CC[_], +C] ...
```
The `@superTrait` annotation will be deprecated and removed in some later version of Scala when cross-building with Scala 2 will no longer be a concern.
declared a mixin trait.

### Rules for Inference

Expand All @@ -75,12 +64,5 @@ The precise rules are as follows:
the resulting type is still a subtype of the bound `B`.
- However, do not perform this widening if all types `Ti` can get replaced in that way.

The last clause ensures that a single super trait instance such as `Product` is not widened to `Any`. Super trait instances are only dropped when they appear in conjunction with some other type.

### Syntax
The last clause ensures that a single mixin trait instance such as `Product` is not widened to `Any`. Mixin trait instances are only dropped when they appear in conjunction with some other type.

Only the production `TmplDef` for class and trait definitions has to be changed.
The new version is:
```
TmplDef ::= ([‘case’] ‘class’ | [‘super’] ‘trait’) ClassDef
```
4 changes: 2 additions & 2 deletions docs/sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ sidebar:
subsection:
- title: Trait Parameters
url: docs/reference/other-new-features/trait-parameters.html
- title: Super Traits
url: docs/reference/other-new-features/super-traits.html
- title: Mixin Traits
url: docs/reference/other-new-features/mixin-traits.html
- title: Creator Applications
url: docs/reference/other-new-features/creator-applications.html
- title: Export Clauses
Expand Down
7 changes: 7 additions & 0 deletions library/src/scala/annotation/mixin.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package scala.annotation

/** An annotation that marks a trait as a mixin trait. Mixin traits
* are not inferred when combined with other types in an intersection.
* See reference/other-new-features/mixin-traits.html for details.
*/
final class mixin extends StaticAnnotation
8 changes: 0 additions & 8 deletions library/src/scala/annotation/superTrait.scala

This file was deleted.

Loading