Skip to content

Commit

Permalink
A relaxation concerning exported type aliases
Browse files Browse the repository at this point in the history
The rules for export forwarders are changed as follows.

Previously, all export forwarders were declared `final`. Now, only term members are declared `final`. Type aliases left aside.

This makes it possible to export the same type member into several traits and then mix these traits in the same class. `typeclass-aggregates.scala` shows why this is essential to be able to combine multiple givens with type members.
The change does not lose safety since different type aliases would in any case lead to uninstantiatable classes.
  • Loading branch information
odersky committed May 7, 2024
1 parent 31c9e8a commit 84655ca
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 7 deletions.
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/config/Feature.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ object Feature:
val captureChecking = experimental("captureChecking")
val into = experimental("into")
val namedTuples = experimental("namedTuples")
val modularity = experimental("modularity")

def experimentalAutoEnableFeatures(using Context): List[TermName] =
defn.languageExperimentalFeatures
Expand Down
2 changes: 0 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Flags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -543,8 +543,6 @@ object Flags {
/** Flags retained in type export forwarders */
val RetainedExportTypeFlags = Infix

val MandatoryExportTypeFlags = Exported | Final

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

Expand Down
6 changes: 4 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import Nullables.*
import transform.ValueClasses.*
import TypeErasure.erasure
import reporting.*
import config.Feature.sourceVersion
import config.Feature.{sourceVersion, modularity}
import config.SourceVersion.*

import scala.compiletime.uninitialized
Expand Down Expand Up @@ -1203,7 +1203,9 @@ class Namer { typer: Typer =>
target = target.etaExpand
newSymbol(
cls, forwarderName,
MandatoryExportTypeFlags | (sym.flags & RetainedExportTypeFlags),
Exported
| (sym.flags & RetainedExportTypeFlags)
| (if Feature.enabled(modularity) then EmptyFlags else Final),
TypeAlias(target),
coord = span)
// Note: This will always create unparameterzied aliases. So even if the original type is
Expand Down
16 changes: 13 additions & 3 deletions docs/_docs/reference/other-new-features/export.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@ final def print(bits: BitMap): Unit = printUnit.print(bits)
final type PrinterType = printUnit.PrinterType
```

They can be accessed inside `Copier` as well as from outside:
With the experimental `modularity` language import, only exported methods and values are final, whereas the generated `PrinterType` would be a simple type alias
```scala
type PrinterType = printUnit.PrinterType
```

These aliases can be accessed inside `Copier` as well as from outside:

```scala
val copier = new Copier
Expand Down Expand Up @@ -90,12 +95,17 @@ export O.*
```

Export aliases copy the type and value parameters of the members they refer to.
Export aliases are always `final`. Aliases of given instances are again defined as givens (and aliases of old-style implicits are `implicit`). Aliases of extensions are again defined as extensions. Aliases of inline methods or values are again defined `inline`. There are no other modifiers that can be given to an alias. This has the following consequences for overriding:
Export aliases of term members are always `final`. Aliases of given instances are again defined as givens (and aliases of old-style implicits are `implicit`). Aliases of extensions are again defined as extensions. Aliases of inline methods or values are again defined `inline`. There are no other modifiers that can be given to an alias. This has the following consequences for overriding:

- Export aliases cannot be overridden, since they are final.
- Export aliases of methods or fields cannot be overridden, since they are final.
- Export aliases cannot override concrete members in base classes, since they are
not marked `override`.
- However, export aliases can implement deferred members of base classes.
- Export type aliases are normally also final, except when the experimental
language import `modularity` is present. The general
rules for type aliases ensure in any case that if there are several type aliases in a class,
they must agree on their right hand sides, or the class could not be instantiated.
So dropping the `final` for export type aliases is safe.

Export aliases for public value definitions that are accessed without
referring to private values in the qualifier path
Expand Down
12 changes: 12 additions & 0 deletions tests/neg/i0248-inherit-refined.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- [E170] Type Error: tests/neg/i0248-inherit-refined.scala:8:18 -------------------------------------------------------
8 | class C extends Y // error
| ^
| test.A & test.B is not a class type
|
| longer explanation available when compiling with `-explain`
-- [E170] Type Error: tests/neg/i0248-inherit-refined.scala:10:18 ------------------------------------------------------
10 | class D extends Z // error
| ^
| test.A | test.B is not a class type
|
| longer explanation available when compiling with `-explain`
47 changes: 47 additions & 0 deletions tests/pos/typeclass-aggregates.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//> using options -source future -language:experimental.modularity
trait Ord:
type This
extension (x: This)
def compareTo(y: This): Int
def < (y: This): Boolean = compareTo(y) < 0
def > (y: This): Boolean = compareTo(y) > 0

trait OrdProxy extends Ord:
export Ord.this.*

trait SemiGroup:
type This
extension (x: This) def combine(y: This): This

trait SemiGroupProxy extends SemiGroup:
export SemiGroup.this.*

trait Monoid extends SemiGroup:
def unit: This

trait MonoidProxy extends Monoid:
export Monoid.this.*

def ordWithMonoid(ord: Ord, monoid: Monoid{ type This = ord.This }): Ord & Monoid =
new ord.OrdProxy with monoid.MonoidProxy {}

trait OrdWithMonoid extends Ord, Monoid

def ordWithMonoid2(ord: Ord, monoid: Monoid{ type This = ord.This }) = //: OrdWithMonoid { type This = ord.This} =
new OrdWithMonoid with ord.OrdProxy with monoid.MonoidProxy {}

given intOrd: Ord { type This = Int } = ???
given intMonoid: Monoid { type This = Int } = ???

//given (using ord: Ord, monoid: Monoid{ type This = ord.This }): (Ord & Monoid { type This = ord.This}) =
// ordWithMonoid2(ord, monoid)

val x = summon[Ord & Monoid { type This = Int}]
val y: Int = ??? : x.This

// given [A, B](using ord: A is Ord, monoid: A is Monoid) => A is Ord & Monoid =
// new ord.OrdProxy with monoid.MonoidProxy {}

given [A](using ord: Ord { type This = A }, monoid: Monoid { type This = A}): (Ord & Monoid) { type This = A} =
new ord.OrdProxy with monoid.MonoidProxy {}

0 comments on commit 84655ca

Please sign in to comment.