Skip to content

Commit 910c307

Browse files
committed
Allow class parents to be refined types.
Refinements of a class parent are added as synthetic members to the inheriting class. # Conflicts: # compiler/src/dotty/tools/dotc/config/ScalaSettings.scala # compiler/src/dotty/tools/dotc/typer/Implicits.scala # compiler/src/dotty/tools/dotc/typer/Namer.scala # tests/pos/typeclass-aggregates.scala
1 parent 80fcb70 commit 910c307

12 files changed

+200
-86
lines changed

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package core
55
import Contexts.*, Symbols.*, Types.*, Flags.*, Scopes.*, Decorators.*, Names.*, NameOps.*
66
import SymDenotations.{LazyType, SymDenotation}, StdNames.nme
77
import TypeApplications.EtaExpansion
8+
import collection.mutable
89

910
/** Operations that are shared between Namer and TreeUnpickler */
1011
object NamerOps:
@@ -18,6 +19,26 @@ object NamerOps:
1819
case TypeSymbols(tparams) :: _ => ctor.owner.typeRef.appliedTo(tparams.map(_.typeRef))
1920
case _ => ctor.owner.typeRef
2021

22+
/** Split dependent class refinements off parent type. Add them to `refinements`,
23+
* unless it is null.
24+
*/
25+
extension (tp: Type)
26+
def separateRefinements(cls: ClassSymbol, refinements: mutable.LinkedHashMap[Name, Type] | Null)(using Context): Type =
27+
tp match
28+
case RefinedType(tp1, rname, rinfo) =>
29+
try tp1.separateRefinements(cls, refinements)
30+
finally
31+
if refinements != null then
32+
refinements(rname) = refinements.get(rname) match
33+
case Some(tp) => tp & rinfo
34+
case None => rinfo
35+
case tp @ AnnotatedType(tp1, ann) =>
36+
tp.derivedAnnotatedType(tp1.separateRefinements(cls, refinements), ann)
37+
case tp: RecType =>
38+
tp.parent.substRecThis(tp, cls.thisType).separateRefinements(cls, refinements)
39+
case tp =>
40+
tp
41+
2142
/** If isConstructor, make sure it has at least one non-implicit parameter list
2243
* This is done by adding a () in front of a leading old style implicit parameter,
2344
* or by adding a () as last -- or only -- parameter list if the constructor has

compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1049,7 +1049,7 @@ class TreeUnpickler(reader: TastyReader,
10491049
}
10501050
val parentReader = fork
10511051
val parents = readParents(withArgs = false)(using parentCtx)
1052-
val parentTypes = parents.map(_.tpe.dealias)
1052+
val parentTypes = parents.map(_.tpe.dealiasKeepAnnots.separateRefinements(cls, null))
10531053
if cls.is(JavaDefined) && parentTypes.exists(_.derivesFrom(defn.JavaAnnotationClass)) then
10541054
cls.setFlag(JavaAnnotation)
10551055
val self =

compiler/src/dotty/tools/dotc/transform/init/Util.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ object Util:
2020

2121
def typeRefOf(tp: Type)(using Context): TypeRef = tp.dealias.typeConstructor match
2222
case tref: TypeRef => tref
23+
case RefinedType(parent, _, _) => typeRefOf(parent)
2324
case hklambda: HKTypeLambda => typeRefOf(hklambda.resType)
2425

2526

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

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,12 @@ class Namer { typer: Typer =>
5454

5555
import untpd.*
5656

57-
val TypedAhead : Property.Key[tpd.Tree] = new Property.Key
58-
val ExpandedTree : Property.Key[untpd.Tree] = new Property.Key
59-
val ExportForwarders: Property.Key[List[tpd.MemberDef]] = new Property.Key
60-
val SymOfTree : Property.Key[Symbol] = new Property.Key
61-
val AttachedDeriver : Property.Key[Deriver] = new Property.Key
57+
val TypedAhead : Property.Key[tpd.Tree] = new Property.Key
58+
val ExpandedTree : Property.Key[untpd.Tree] = new Property.Key
59+
val ExportForwarders : Property.Key[List[tpd.MemberDef]] = new Property.Key
60+
val ParentRefinements: Property.Key[List[Symbol]] = new Property.Key
61+
val SymOfTree : Property.Key[Symbol] = new Property.Key
62+
val AttachedDeriver : Property.Key[Deriver] = new Property.Key
6263
// was `val Deriver`, but that gave shadowing problems with constructor proxies
6364

6465
/** A partial map from unexpanded member and pattern defs and to their expansions.
@@ -1485,6 +1486,7 @@ class Namer { typer: Typer =>
14851486
/** The type signature of a ClassDef with given symbol */
14861487
override def completeInCreationContext(denot: SymDenotation): Unit = {
14871488
val parents = impl.parents
1489+
val parentRefinements = new mutable.LinkedHashMap[Name, Type]
14881490

14891491
/* The type of a parent constructor. Types constructor arguments
14901492
* only if parent type contains uninstantiated type parameters.
@@ -1536,8 +1538,13 @@ class Namer { typer: Typer =>
15361538
val ptype = parentType(parent)(using completerCtx.superCallContext).dealiasKeepAnnots
15371539
if (cls.isRefinementClass) ptype
15381540
else {
1539-
val pt = checkClassType(ptype, parent.srcPos,
1540-
traitReq = parent ne parents.head, stablePrefixReq = true)
1541+
val pt = checkClassType(
1542+
if Feature.enabled(modularity)
1543+
then ptype.separateRefinements(cls, parentRefinements)
1544+
else ptype,
1545+
parent.srcPos,
1546+
traitReq = parent ne parents.head,
1547+
stablePrefixReq = true)
15411548
if (pt.derivesFrom(cls)) {
15421549
val addendum = parent match {
15431550
case Select(qual: Super, _) if Feature.migrateTo3 =>
@@ -1564,6 +1571,21 @@ class Namer { typer: Typer =>
15641571
}
15651572
}
15661573

1574+
/** Enter all parent refinements as public class members, unless a definition
1575+
* with the same name already exists in the class.
1576+
*/
1577+
def enterParentRefinementSyms(refinements: List[(Name, Type)]) =
1578+
val refinedSyms = mutable.ListBuffer[Symbol]()
1579+
for (name, tp) <- refinements do
1580+
if decls.lookupEntry(name) == null then
1581+
val flags = tp match
1582+
case tp: MethodOrPoly => Method | Synthetic | Deferred
1583+
case _ => Synthetic | Deferred
1584+
refinedSyms += newSymbol(cls, name, flags, tp, coord = original.rhs.span.startPos).entered
1585+
if refinedSyms.nonEmpty then
1586+
typr.println(i"parent refinement symbols: ${refinedSyms.toList}")
1587+
original.pushAttachment(ParentRefinements, refinedSyms.toList)
1588+
15671589
/** If `parents` contains references to traits that have supertraits with implicit parameters
15681590
* add those supertraits in linearization order unless they are already covered by other
15691591
* parent types. For instance, in
@@ -1632,6 +1654,7 @@ class Namer { typer: Typer =>
16321654
cls.invalidateMemberCaches() // we might have checked for a member when parents were not known yet.
16331655
cls.setNoInitsFlags(parentsKind(parents), untpd.bodyKind(rest))
16341656
cls.setStableConstructor()
1657+
enterParentRefinementSyms(parentRefinements.toList)
16351658
processExports(using localCtx)
16361659
defn.patchStdLibClass(cls)
16371660
addConstructorProxies(cls)

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

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,7 @@ import annotation.tailrec
4040
import Implicits.*
4141
import util.Stats.record
4242
import config.Printers.{gadts, typr}
43-
import config.Feature
44-
import config.Feature.{sourceVersion, migrateTo3}
43+
import config.Feature, Feature.{sourceVersion, migrateTo3, modularity}
4544
import config.SourceVersion.*
4645
import rewrites.Rewrites.patch
4746
import staging.StagingLevel
@@ -909,10 +908,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
909908
tp.exists
910909
&& !tp.typeSymbol.is(Final)
911910
&& (!tp.isTopType || tp.isAnyRef) // Object is the only toplevel class that can be instantiated
912-
if (templ1.parents.isEmpty &&
913-
isFullyDefined(pt, ForceDegree.flipBottom) &&
914-
isSkolemFree(pt) &&
915-
isEligible(pt.underlyingClassRef(refinementOK = false)))
911+
if templ1.parents.isEmpty
912+
&& isFullyDefined(pt, ForceDegree.flipBottom)
913+
&& isSkolemFree(pt)
914+
&& isEligible(pt.underlyingClassRef(refinementOK = Feature.enabled(modularity)))
915+
then
916916
templ1 = cpy.Template(templ)(parents = untpd.TypeTree(pt) :: Nil)
917917
for case parent: RefTree <- templ1.parents do
918918
typedAhead(parent, tree => inferTypeParams(typedType(tree), pt))
@@ -2766,6 +2766,19 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
27662766
}
27672767
}
27682768

2769+
/** Add all parent refinement symbols as declarations to this class */
2770+
def addParentRefinements(body: List[Tree])(using Context): List[Tree] =
2771+
cdef.getAttachment(ParentRefinements) match
2772+
case Some(refinedSyms) =>
2773+
val refinements = refinedSyms.map: sym =>
2774+
( if sym.isType then TypeDef(sym.asType)
2775+
else if sym.is(Method) then DefDef(sym.asTerm)
2776+
else ValDef(sym.asTerm)
2777+
).withSpan(impl.span.startPos)
2778+
body ++ refinements
2779+
case None =>
2780+
body
2781+
27692782
ensureCorrectSuperClass()
27702783
completeAnnotations(cdef, cls)
27712784
val constr1 = typed(constr).asInstanceOf[DefDef]
@@ -2786,7 +2799,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
27862799
cdef.withType(UnspecifiedErrorType)
27872800
else {
27882801
val dummy = localDummy(cls, impl)
2789-
val body1 = addAccessorDefs(cls, typedStats(impl.body, dummy)(using ctx.inClassContext(self1.symbol))._1)
2802+
val body1 =
2803+
addParentRefinements(
2804+
addAccessorDefs(cls,
2805+
typedStats(impl.body, dummy)(using ctx.inClassContext(self1.symbol))._1))
27902806

27912807
checkNoDoubleDeclaration(cls)
27922808
val impl1 = cpy.Template(impl)(constr1, parents1, Nil, self1, body1)

tests/neg/i0248-inherit-refined.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
//> using options -source future -language:experimental.modularity
2+
13
object test {
24
class A { type T }
35
type X = A { type T = Int }
4-
class B extends X // error
6+
class B extends X // was error, now OK
57
type Y = A & B
68
class C extends Y // error
79
type Z = A | B
810
class D extends Z // error
9-
abstract class E extends ({ val x: Int }) // error
11+
abstract class E extends ({ val x: Int }) // was error, now OK
1012
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- [E164] Declaration Error: tests/neg/parent-refinement-access.scala:6:6 ----------------------------------------------
2+
6 |trait Year2(private[Year2] val value: Int) extends (Gen { val x: Int }) // error
3+
| ^
4+
| error overriding value x in trait Year2 of type Int;
5+
| value x in trait Gen of type Any has weaker access privileges; it should be public
6+
| (Note that value x in trait Year2 of type Int is abstract,
7+
| and is therefore overridden by concrete value x in trait Gen of type Any)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//> using options -source future -language:experimental.modularity
2+
3+
trait Gen:
4+
private[Gen] val x: Any = ()
5+
6+
trait Year2(private[Year2] val value: Int) extends (Gen { val x: Int }) // error

tests/neg/parent-refinement.check

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,25 @@
1-
-- Error: tests/neg/parent-refinement.scala:5:2 ------------------------------------------------------------------------
2-
5 | with Ordered[Year] { // error
3-
| ^^^^
4-
| end of toplevel definition expected but 'with' found
1+
-- Error: tests/neg/parent-refinement.scala:11:6 -----------------------------------------------------------------------
2+
11 |class Bar extends IdOf[Int], (X { type Value = String }) // error
3+
| ^^^
4+
|class Bar cannot be instantiated since it has a member Value with possibly conflicting bounds Int | String <: ... <: Int & String
5+
-- [E007] Type Mismatch Error: tests/neg/parent-refinement.scala:15:17 -------------------------------------------------
6+
15 | val x: Value = 0 // error
7+
| ^
8+
| Found: (0 : Int)
9+
| Required: Baz.this.Value
10+
|
11+
| longer explanation available when compiling with `-explain`
12+
-- [E007] Type Mismatch Error: tests/neg/parent-refinement.scala:21:6 --------------------------------------------------
13+
21 | foo(2) // error
14+
| ^
15+
| Found: (2 : Int)
16+
| Required: Boolean
17+
|
18+
| longer explanation available when compiling with `-explain`
19+
-- [E007] Type Mismatch Error: tests/neg/parent-refinement.scala:17:22 -------------------------------------------------
20+
17 |val x: IdOf[Int] = Baz() // error
21+
| ^^^^^
22+
| Found: Baz
23+
| Required: IdOf[Int]
24+
|
25+
| longer explanation available when compiling with `-explain`

tests/neg/parent-refinement.scala

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,21 @@
1+
//> using options -source future -language:experimental.modularity
12

23
trait Id { type Value }
4+
trait X { type Value }
5+
type IdOf[T] = Id { type Value = T }
6+
37
case class Year(value: Int) extends AnyVal
4-
with Id { type Value = Int }
5-
with Ordered[Year] { // error
8+
with (Id { type Value = Int })
9+
with Ordered[Year]
10+
11+
class Bar extends IdOf[Int], (X { type Value = String }) // error
12+
13+
class Baz extends IdOf[Int]:
14+
type Value = String
15+
val x: Value = 0 // error
16+
17+
val x: IdOf[Int] = Baz() // error
618

7-
}
19+
object Clash extends ({ def foo(x: Int): Int }):
20+
def foo(x: Boolean): Int = 1
21+
foo(2) // error

tests/pos/parent-refinement.scala

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//> using options -source future -language:experimental.modularity
2+
3+
class A
4+
class B extends A
5+
class C extends B
6+
7+
trait Id { type Value }
8+
type IdOf[T] = Id { type Value = T }
9+
trait X { type Value }
10+
11+
case class Year(value: Int) extends IdOf[Int]:
12+
val x: Value = 2
13+
14+
type Between[Lo, Hi] = X { type Value >: Lo <: Hi }
15+
16+
class Foo() extends IdOf[B], Between[C, A]:
17+
val x: Value = B()
18+
19+
trait Bar extends IdOf[Int], (X { type Value = String })
20+
21+
class Baz extends IdOf[Int]:
22+
type Value = String
23+
val x: Value = ""
24+
25+
trait Gen:
26+
type T
27+
val x: T
28+
29+
type IntInst = Gen:
30+
type T = Int
31+
val x: 0
32+
33+
trait IntInstTrait extends IntInst
34+
35+
abstract class IntInstClass extends IntInstTrait, IntInst
36+
37+
object obj1 extends IntInstTrait:
38+
val x = 0
39+
40+
object obj2 extends IntInstClass:
41+
val x = 0
42+
43+
def main =
44+
val x: obj1.T = 2 - obj2.x
45+
val y: obj2.T = 2 - obj1.x
46+
47+
48+

0 commit comments

Comments
 (0)