Skip to content

Commit 4894414

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 84655ca commit 4894414

12 files changed

+200
-86
lines changed

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

+21
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

+1-1
Original file line numberDiff line numberDiff line change
@@ -1074,7 +1074,7 @@ class TreeUnpickler(reader: TastyReader,
10741074
}
10751075
val parentReader = fork
10761076
val parents = readParents(withArgs = false)(using parentCtx)
1077-
val parentTypes = parents.map(_.tpe.dealias)
1077+
val parentTypes = parents.map(_.tpe.dealiasKeepAnnots.separateRefinements(cls, null))
10781078
if cls.is(JavaDefined) && parentTypes.exists(_.derivesFrom(defn.JavaAnnotationClass)) then
10791079
cls.setFlag(JavaAnnotation)
10801080
val self =

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

+1
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

+30-7
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.
@@ -1515,6 +1516,7 @@ class Namer { typer: Typer =>
15151516
/** The type signature of a ClassDef with given symbol */
15161517
override def completeInCreationContext(denot: SymDenotation): Unit = {
15171518
val parents = impl.parents
1519+
val parentRefinements = new mutable.LinkedHashMap[Name, Type]
15181520

15191521
/* The type of a parent constructor. Types constructor arguments
15201522
* only if parent type contains uninstantiated type parameters.
@@ -1569,8 +1571,13 @@ class Namer { typer: Typer =>
15691571
val ptype = parentType(parent)(using completerCtx.superCallContext).dealiasKeepAnnots
15701572
if (cls.isRefinementClass) ptype
15711573
else {
1572-
val pt = checkClassType(ptype, parent.srcPos,
1573-
traitReq = parent ne parents.head, stablePrefixReq = !isJava)
1574+
val pt = checkClassType(
1575+
if Feature.enabled(modularity)
1576+
then ptype.separateRefinements(cls, parentRefinements)
1577+
else ptype,
1578+
parent.srcPos,
1579+
traitReq = parent ne parents.head,
1580+
stablePrefixReq = !isJava)
15741581
if (pt.derivesFrom(cls)) {
15751582
val addendum = parent match {
15761583
case Select(qual: Super, _) if Feature.migrateTo3 =>
@@ -1597,6 +1604,21 @@ class Namer { typer: Typer =>
15971604
}
15981605
}
15991606

1607+
/** Enter all parent refinements as public class members, unless a definition
1608+
* with the same name already exists in the class.
1609+
*/
1610+
def enterParentRefinementSyms(refinements: List[(Name, Type)]) =
1611+
val refinedSyms = mutable.ListBuffer[Symbol]()
1612+
for (name, tp) <- refinements do
1613+
if decls.lookupEntry(name) == null then
1614+
val flags = tp match
1615+
case tp: MethodOrPoly => Method | Synthetic | Deferred
1616+
case _ => Synthetic | Deferred
1617+
refinedSyms += newSymbol(cls, name, flags, tp, coord = original.rhs.span.startPos).entered
1618+
if refinedSyms.nonEmpty then
1619+
typr.println(i"parent refinement symbols: ${refinedSyms.toList}")
1620+
original.pushAttachment(ParentRefinements, refinedSyms.toList)
1621+
16001622
/** If `parents` contains references to traits that have supertraits with implicit parameters
16011623
* add those supertraits in linearization order unless they are already covered by other
16021624
* parent types. For instance, in
@@ -1667,6 +1689,7 @@ class Namer { typer: Typer =>
16671689
cls.invalidateMemberCaches() // we might have checked for a member when parents were not known yet.
16681690
cls.setNoInitsFlags(parentsKind(parents), untpd.bodyKind(rest))
16691691
cls.setStableConstructor()
1692+
enterParentRefinementSyms(parentRefinements.toList)
16701693
processExports(using localCtx)
16711694
defn.patchStdLibClass(cls)
16721695
addConstructorProxies(cls)

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

+23-7
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
@@ -1004,10 +1003,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
10041003
tp.exists
10051004
&& !tp.typeSymbol.is(Final)
10061005
&& (!tp.isTopType || tp.isAnyRef) // Object is the only toplevel class that can be instantiated
1007-
if (templ1.parents.isEmpty &&
1008-
isFullyDefined(pt, ForceDegree.flipBottom) &&
1009-
isSkolemFree(pt) &&
1010-
isEligible(pt.underlyingClassRef(refinementOK = false)))
1006+
if templ1.parents.isEmpty
1007+
&& isFullyDefined(pt, ForceDegree.flipBottom)
1008+
&& isSkolemFree(pt)
1009+
&& isEligible(pt.underlyingClassRef(refinementOK = Feature.enabled(modularity)))
1010+
then
10111011
templ1 = cpy.Template(templ)(parents = untpd.TypeTree(pt) :: Nil)
10121012
for case parent: RefTree <- templ1.parents do
10131013
typedAhead(parent, tree => inferTypeParams(typedType(tree), pt))
@@ -2871,6 +2871,19 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
28712871
}
28722872
}
28732873

2874+
/** Add all parent refinement symbols as declarations to this class */
2875+
def addParentRefinements(body: List[Tree])(using Context): List[Tree] =
2876+
cdef.getAttachment(ParentRefinements) match
2877+
case Some(refinedSyms) =>
2878+
val refinements = refinedSyms.map: sym =>
2879+
( if sym.isType then TypeDef(sym.asType)
2880+
else if sym.is(Method) then DefDef(sym.asTerm)
2881+
else ValDef(sym.asTerm)
2882+
).withSpan(impl.span.startPos)
2883+
body ++ refinements
2884+
case None =>
2885+
body
2886+
28742887
ensureCorrectSuperClass()
28752888
completeAnnotations(cdef, cls)
28762889
val constr1 = typed(constr).asInstanceOf[DefDef]
@@ -2891,7 +2904,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
28912904
cdef.withType(UnspecifiedErrorType)
28922905
else {
28932906
val dummy = localDummy(cls, impl)
2894-
val body1 = addAccessorDefs(cls, typedStats(impl.body, dummy)(using ctx.inClassContext(self1.symbol))._1)
2907+
val body1 =
2908+
addParentRefinements(
2909+
addAccessorDefs(cls,
2910+
typedStats(impl.body, dummy)(using ctx.inClassContext(self1.symbol))._1))
28952911

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

tests/neg/i0248-inherit-refined.scala

+4-2
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
}
+7
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)
+6
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

+25-4
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

+17-3
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

+48
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)