Skip to content

Commit 84655ca

Browse files
committed
A relaxation concerning exported type aliases
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.
1 parent 31c9e8a commit 84655ca

File tree

6 files changed

+77
-7
lines changed

6 files changed

+77
-7
lines changed

compiler/src/dotty/tools/dotc/config/Feature.scala

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ object Feature:
3434
val captureChecking = experimental("captureChecking")
3535
val into = experimental("into")
3636
val namedTuples = experimental("namedTuples")
37+
val modularity = experimental("modularity")
3738

3839
def experimentalAutoEnableFeatures(using Context): List[TermName] =
3940
defn.languageExperimentalFeatures

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

-2
Original file line numberDiff line numberDiff line change
@@ -543,8 +543,6 @@ object Flags {
543543
/** Flags retained in type export forwarders */
544544
val RetainedExportTypeFlags = Infix
545545

546-
val MandatoryExportTypeFlags = Exported | Final
547-
548546
/** Flags that apply only to classes */
549547
val ClassOnlyFlags = Sealed | Open | Abstract.toTypeFlags
550548

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import Nullables.*
2626
import transform.ValueClasses.*
2727
import TypeErasure.erasure
2828
import reporting.*
29-
import config.Feature.sourceVersion
29+
import config.Feature.{sourceVersion, modularity}
3030
import config.SourceVersion.*
3131

3232
import scala.compiletime.uninitialized
@@ -1203,7 +1203,9 @@ class Namer { typer: Typer =>
12031203
target = target.etaExpand
12041204
newSymbol(
12051205
cls, forwarderName,
1206-
MandatoryExportTypeFlags | (sym.flags & RetainedExportTypeFlags),
1206+
Exported
1207+
| (sym.flags & RetainedExportTypeFlags)
1208+
| (if Feature.enabled(modularity) then EmptyFlags else Final),
12071209
TypeAlias(target),
12081210
coord = span)
12091211
// Note: This will always create unparameterzied aliases. So even if the original type is

docs/_docs/reference/other-new-features/export.md

+13-3
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,12 @@ final def print(bits: BitMap): Unit = printUnit.print(bits)
3737
final type PrinterType = printUnit.PrinterType
3838
```
3939

40-
They can be accessed inside `Copier` as well as from outside:
40+
With the experimental `modularity` language import, only exported methods and values are final, whereas the generated `PrinterType` would be a simple type alias
41+
```scala
42+
type PrinterType = printUnit.PrinterType
43+
```
44+
45+
These aliases can be accessed inside `Copier` as well as from outside:
4146

4247
```scala
4348
val copier = new Copier
@@ -90,12 +95,17 @@ export O.*
9095
```
9196

9297
Export aliases copy the type and value parameters of the members they refer to.
93-
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:
98+
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:
9499

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

100110
Export aliases for public value definitions that are accessed without
101111
referring to private values in the qualifier path

tests/neg/i0248-inherit-refined.check

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-- [E170] Type Error: tests/neg/i0248-inherit-refined.scala:8:18 -------------------------------------------------------
2+
8 | class C extends Y // error
3+
| ^
4+
| test.A & test.B is not a class type
5+
|
6+
| longer explanation available when compiling with `-explain`
7+
-- [E170] Type Error: tests/neg/i0248-inherit-refined.scala:10:18 ------------------------------------------------------
8+
10 | class D extends Z // error
9+
| ^
10+
| test.A | test.B is not a class type
11+
|
12+
| longer explanation available when compiling with `-explain`

tests/pos/typeclass-aggregates.scala

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//> using options -source future -language:experimental.modularity
2+
trait Ord:
3+
type This
4+
extension (x: This)
5+
def compareTo(y: This): Int
6+
def < (y: This): Boolean = compareTo(y) < 0
7+
def > (y: This): Boolean = compareTo(y) > 0
8+
9+
trait OrdProxy extends Ord:
10+
export Ord.this.*
11+
12+
trait SemiGroup:
13+
type This
14+
extension (x: This) def combine(y: This): This
15+
16+
trait SemiGroupProxy extends SemiGroup:
17+
export SemiGroup.this.*
18+
19+
trait Monoid extends SemiGroup:
20+
def unit: This
21+
22+
trait MonoidProxy extends Monoid:
23+
export Monoid.this.*
24+
25+
def ordWithMonoid(ord: Ord, monoid: Monoid{ type This = ord.This }): Ord & Monoid =
26+
new ord.OrdProxy with monoid.MonoidProxy {}
27+
28+
trait OrdWithMonoid extends Ord, Monoid
29+
30+
def ordWithMonoid2(ord: Ord, monoid: Monoid{ type This = ord.This }) = //: OrdWithMonoid { type This = ord.This} =
31+
new OrdWithMonoid with ord.OrdProxy with monoid.MonoidProxy {}
32+
33+
given intOrd: Ord { type This = Int } = ???
34+
given intMonoid: Monoid { type This = Int } = ???
35+
36+
//given (using ord: Ord, monoid: Monoid{ type This = ord.This }): (Ord & Monoid { type This = ord.This}) =
37+
// ordWithMonoid2(ord, monoid)
38+
39+
val x = summon[Ord & Monoid { type This = Int}]
40+
val y: Int = ??? : x.This
41+
42+
// given [A, B](using ord: A is Ord, monoid: A is Monoid) => A is Ord & Monoid =
43+
// new ord.OrdProxy with monoid.MonoidProxy {}
44+
45+
given [A](using ord: Ord { type This = A }, monoid: Monoid { type This = A}): (Ord & Monoid) { type This = A} =
46+
new ord.OrdProxy with monoid.MonoidProxy {}
47+

0 commit comments

Comments
 (0)