Skip to content

Commit b38ed57

Browse files
committed
Allow class parents to be refined types.
Refinements of a class parent are added as synthetic members to the inheriting class.
1 parent 29b0883 commit b38ed57

File tree

13 files changed

+201
-87
lines changed

13 files changed

+201
-87
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
@@ -1063,7 +1063,7 @@ class TreeUnpickler(reader: TastyReader,
10631063
}
10641064
val parentReader = fork
10651065
val parents = readParents(withArgs = false)(using parentCtx)
1066-
val parentTypes = parents.map(_.tpe.dealias)
1066+
val parentTypes = parents.map(_.tpe.dealiasKeepAnnots.separateRefinements(cls, null))
10671067
if cls.is(JavaDefined) && parentTypes.exists(_.derivesFrom(defn.JavaAnnotationClass)) then
10681068
cls.setFlag(JavaAnnotation)
10691069
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
@@ -55,11 +55,12 @@ class Namer { typer: Typer =>
5555

5656
import untpd.*
5757

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

6566
/** A partial map from unexpanded member and pattern defs and to their expansions.
@@ -1501,6 +1502,7 @@ class Namer { typer: Typer =>
15011502
/** The type signature of a ClassDef with given symbol */
15021503
override def completeInCreationContext(denot: SymDenotation): Unit = {
15031504
val parents = impl.parents
1505+
val parentRefinements = new mutable.LinkedHashMap[Name, Type]
15041506

15051507
/* The type of a parent constructor. Types constructor arguments
15061508
* only if parent type contains uninstantiated type parameters.
@@ -1555,8 +1557,13 @@ class Namer { typer: Typer =>
15551557
val ptype = parentType(parent)(using completerCtx.superCallContext).dealiasKeepAnnots
15561558
if (cls.isRefinementClass) ptype
15571559
else {
1558-
val pt = checkClassType(ptype, parent.srcPos,
1559-
traitReq = parent ne parents.head, stablePrefixReq = !isJava)
1560+
val pt = checkClassType(
1561+
if Feature.enabled(modularity)
1562+
then ptype.separateRefinements(cls, parentRefinements)
1563+
else ptype,
1564+
parent.srcPos,
1565+
traitReq = parent ne parents.head,
1566+
stablePrefixReq = !isJava)
15601567
if (pt.derivesFrom(cls)) {
15611568
val addendum = parent match {
15621569
case Select(qual: Super, _) if Feature.migrateTo3 =>
@@ -1583,6 +1590,21 @@ class Namer { typer: Typer =>
15831590
}
15841591
}
15851592

1593+
/** Enter all parent refinements as public class members, unless a definition
1594+
* with the same name already exists in the class.
1595+
*/
1596+
def enterParentRefinementSyms(refinements: List[(Name, Type)]) =
1597+
val refinedSyms = mutable.ListBuffer[Symbol]()
1598+
for (name, tp) <- refinements do
1599+
if decls.lookupEntry(name) == null then
1600+
val flags = tp match
1601+
case tp: MethodOrPoly => Method | Synthetic | Deferred
1602+
case _ => Synthetic | Deferred
1603+
refinedSyms += newSymbol(cls, name, flags, tp, coord = original.rhs.span.startPos).entered
1604+
if refinedSyms.nonEmpty then
1605+
typr.println(i"parent refinement symbols: ${refinedSyms.toList}")
1606+
original.pushAttachment(ParentRefinements, refinedSyms.toList)
1607+
15861608
/** If `parents` contains references to traits that have supertraits with implicit parameters
15871609
* add those supertraits in linearization order unless they are already covered by other
15881610
* parent types. For instance, in
@@ -1653,6 +1675,7 @@ class Namer { typer: Typer =>
16531675
cls.invalidateMemberCaches() // we might have checked for a member when parents were not known yet.
16541676
cls.setNoInitsFlags(parentsKind(parents), untpd.bodyKind(rest))
16551677
cls.setStableConstructor()
1678+
enterParentRefinementSyms(parentRefinements.toList)
16561679
processExports(using localCtx)
16571680
defn.patchStdLibClass(cls)
16581681
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, Rewrites.patch
4746
import staging.StagingLevel
@@ -925,10 +924,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
925924
tp.exists
926925
&& !tp.typeSymbol.is(Final)
927926
&& (!tp.isTopType || tp.isAnyRef) // Object is the only toplevel class that can be instantiated
928-
if (templ1.parents.isEmpty &&
929-
isFullyDefined(pt, ForceDegree.flipBottom) &&
930-
isSkolemFree(pt) &&
931-
isEligible(pt.underlyingClassRef(refinementOK = false)))
927+
if templ1.parents.isEmpty
928+
&& isFullyDefined(pt, ForceDegree.flipBottom)
929+
&& isSkolemFree(pt)
930+
&& isEligible(pt.underlyingClassRef(refinementOK = Feature.enabled(modularity)))
931+
then
932932
templ1 = cpy.Template(templ)(parents = untpd.TypeTree(pt) :: Nil)
933933
for case parent: RefTree <- templ1.parents do
934934
typedAhead(parent, tree => inferTypeParams(typedType(tree), pt))
@@ -2787,6 +2787,19 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
27872787
}
27882788
}
27892789

2790+
/** Add all parent refinement symbols as declarations to this class */
2791+
def addParentRefinements(body: List[Tree])(using Context): List[Tree] =
2792+
cdef.getAttachment(ParentRefinements) match
2793+
case Some(refinedSyms) =>
2794+
val refinements = refinedSyms.map: sym =>
2795+
( if sym.isType then TypeDef(sym.asType)
2796+
else if sym.is(Method) then DefDef(sym.asTerm)
2797+
else ValDef(sym.asTerm)
2798+
).withSpan(impl.span.startPos)
2799+
body ++ refinements
2800+
case None =>
2801+
body
2802+
27902803
ensureCorrectSuperClass()
27912804
completeAnnotations(cdef, cls)
27922805
val constr1 = typed(constr).asInstanceOf[DefDef]
@@ -2807,7 +2820,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
28072820
cdef.withType(UnspecifiedErrorType)
28082821
else {
28092822
val dummy = localDummy(cls, impl)
2810-
val body1 = addAccessorDefs(cls, typedStats(impl.body, dummy)(using ctx.inClassContext(self1.symbol))._1)
2823+
val body1 =
2824+
addParentRefinements(
2825+
addAccessorDefs(cls,
2826+
typedStats(impl.body, dummy)(using ctx.inClassContext(self1.symbol))._1))
28112827

28122828
checkNoDoubleDeclaration(cls)
28132829
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`

0 commit comments

Comments
 (0)