Skip to content

Commit eaa2889

Browse files
Refactor class parent handling (#15986)
Class parent handling was hard to understand because it consisted of interacting parts that each worked under intricate but separate conditions. It's more centralized now. Fixes #15976
2 parents be375ec + fefa8a0 commit eaa2889

File tree

10 files changed

+87
-105
lines changed

10 files changed

+87
-105
lines changed

compiler/src/dotty/tools/dotc/transform/TypeUtils.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,11 @@ object TypeUtils {
107107
self.superType.mirrorCompanionRef
108108
}
109109

110+
/** Is this type a methodic type that takes at least one parameter? */
111+
def takesParams(using Context): Boolean = self.stripPoly match
112+
case mt: MethodType => mt.paramNames.nonEmpty || mt.resType.takesParams
113+
case _ => false
114+
110115
/** Is this type a methodic type that takes implicit parameters (both old and new) at some point? */
111116
def takesImplicitParams(using Context): Boolean = self.stripPoly match
112117
case mt: MethodType => mt.isImplicitMethod || mt.resType.takesImplicitParams

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ class ReTyper(nestingLevel: Int = 0) extends Typer(nestingLevel) with ReChecking
108108

109109
override def completeAnnotations(mdef: untpd.MemberDef, sym: Symbol)(using Context): Unit = ()
110110

111-
override def ensureConstrCall(cls: ClassSymbol, parent: Tree)(using Context): Tree =
111+
override def ensureConstrCall(cls: ClassSymbol, parent: Tree, psym: Symbol)(using Context): Tree =
112112
parent
113113

114114
override def handleUnexpectedFunType(tree: untpd.Apply, fun: Tree)(using Context): Tree = fun.tpe match {

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

Lines changed: 55 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -2426,65 +2426,32 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
24262426
val TypeDef(name, impl @ Template(constr, _, self, _)) = cdef: @unchecked
24272427
val parents = impl.parents
24282428
val superCtx = ctx.superCallContext
2429-
2430-
/** If `ref` is an implicitly parameterized trait, pass an implicit argument list.
2431-
* Otherwise, if `ref` is a parameterized trait, error.
2432-
* Note: Traits and classes have sometimes a synthesized empty parameter list ()
2433-
* in front or after the implicit parameter(s). See NamerOps.normalizeIfConstructor.
2434-
* We synthesize a () argument at the correct place in this case.
2435-
* @param ref The tree referring to the (parent) trait
2436-
* @param psym Its type symbol
2437-
*/
2438-
def maybeCall(ref: Tree, psym: Symbol): Tree =
2439-
def appliedRef =
2440-
typedExpr(untpd.New(untpd.TypedSplice(ref)(using superCtx), Nil))(using superCtx)
2441-
def dropContextual(tp: Type): Type = tp.stripPoly match
2442-
case mt: MethodType if mt.isContextualMethod => dropContextual(mt.resType)
2443-
case _ => tp
2444-
psym.primaryConstructor.info.stripPoly match
2445-
case cinfo @ MethodType(Nil)
2446-
if cinfo.resultType.isImplicitMethod && !cinfo.resultType.isContextualMethod =>
2447-
appliedRef
2448-
case cinfo =>
2449-
val cinfo1 = dropContextual(cinfo)
2450-
cinfo1 match
2451-
case cinfo1 @ MethodType(Nil) if !cinfo1.resultType.isInstanceOf[MethodType] =>
2452-
if cinfo1 ne cinfo then appliedRef else ref
2453-
case cinfo1: MethodType if !ctx.erasedTypes =>
2454-
report.error(ParameterizedTypeLacksArguments(psym), ref.srcPos)
2455-
ref
2456-
case _ =>
2457-
ref
2458-
24592429
val seenParents = mutable.Set[Symbol]()
24602430

2461-
def typedParent(tree: untpd.Tree): Tree = {
2462-
def isTreeType(t: untpd.Tree): Boolean = t match {
2463-
case _: untpd.Apply => false
2464-
case _ => true
2465-
}
2466-
var result =
2467-
if isTreeType(tree) then typedType(tree)(using superCtx)
2468-
else typedExpr(tree)(using superCtx)
2469-
val psym = result.tpe.dealias.typeSymbol
2470-
if (seenParents.contains(psym) && !cls.isRefinementClass) {
2471-
// Desugaring can adds parents to classes, but we don't want to emit an
2431+
def typedParent(tree: untpd.Tree): Tree =
2432+
val parent = tree match
2433+
case _: untpd.Apply => typedExpr(tree)(using superCtx)
2434+
case _ => typedType(tree)(using superCtx)
2435+
val psym = parent.tpe.dealias.typeSymbol
2436+
if seenParents.contains(psym) && !cls.isRefinementClass then
2437+
// Desugaring can add parents to classes, but we don't want to emit an
24722438
// error if the same parent was explicitly added in user code.
2473-
if (!tree.span.isSourceDerived)
2439+
if !tree.span.isSourceDerived then
24742440
return EmptyTree
2475-
2476-
if (!ctx.isAfterTyper) report.error(i"$psym is extended twice", tree.srcPos)
2477-
}
2478-
else seenParents += psym
2479-
if (tree.isType) {
2480-
checkSimpleKinded(result) // Not needed for constructor calls, as type arguments will be inferred.
2481-
if (psym.is(Trait) && !cls.is(Trait) && !cls.superClass.isSubClass(psym))
2482-
result = maybeCall(result, psym)
2483-
}
2484-
else checkParentCall(result, cls)
2485-
if (cls is Case) checkCaseInheritance(psym, cls, tree.srcPos)
2441+
if !ctx.isAfterTyper then report.error(i"$psym is extended twice", tree.srcPos)
2442+
else
2443+
seenParents += psym
2444+
val result = ensureConstrCall(cls, parent, psym)(using superCtx)
2445+
if parent.isType then
2446+
if !result.symbol.info.takesImplicitParams then
2447+
checkSimpleKinded(parent)
2448+
// allow missing type parameters if there are implicit arguments to pass
2449+
// since we can infer type arguments from them
2450+
else
2451+
checkParentCall(result, cls)
2452+
if cls is Case then
2453+
checkCaseInheritance(psym, cls, tree.srcPos)
24862454
result
2487-
}
24882455

24892456
def ensureCorrectSuperClass(): Unit =
24902457
val parents0 = cls.classInfo.declaredParents
@@ -2499,23 +2466,27 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
24992466
/** Augment `ptrees` to have the same class symbols as `parents`. Generate TypeTrees
25002467
* or New trees to fill in any parents for which no tree exists yet.
25012468
*/
2502-
def parentTrees(parents: List[Type], ptrees: List[Tree]): List[Tree] = parents match
2503-
case parent :: parents1 =>
2504-
val psym = parent.classSymbol
2505-
def hasSameParent(ptree: Tree) = ptree.tpe.classSymbol == psym
2506-
ptrees match
2507-
case ptree :: ptrees1 if hasSameParent(ptree) =>
2508-
ptree :: parentTrees(parents1, ptrees1)
2509-
case ptree :: ptrees1 if ptrees1.exists(hasSameParent) =>
2510-
ptree :: parentTrees(parents, ptrees1)
2511-
case _ =>
2512-
var added: Tree = TypeTree(parent).withSpan(cdef.nameSpan.focus)
2513-
if psym.is(Trait) && psym.primaryConstructor.info.takesImplicitParams then
2514-
// classes get a constructor separately using a different context
2515-
added = ensureConstrCall(cls, added)(using superCtx)
2516-
added :: parentTrees(parents1, ptrees)
2517-
case _ =>
2518-
ptrees
2469+
def parentTrees(parents: List[Type], ptrees: List[Tree]): List[Tree] =
2470+
if ptrees.exists(_.tpe.isError) then ptrees
2471+
else parents match
2472+
case parent :: parents1 =>
2473+
val psym = parent.classSymbol
2474+
def hasSameParent(ptree: Tree) =
2475+
psym == (
2476+
if ptree.symbol.isConstructor then ptree.symbol.owner
2477+
else ptree.tpe.classSymbol
2478+
)
2479+
ptrees match
2480+
case ptree :: ptrees1 if hasSameParent(ptree) =>
2481+
ptree :: parentTrees(parents1, ptrees1)
2482+
case ptree :: ptrees1 if ptrees1.exists(hasSameParent) =>
2483+
ptree :: parentTrees(parents, ptrees1)
2484+
case _ =>
2485+
val added: Tree = ensureConstrCall(
2486+
cls, TypeTree(parent).withSpan(cdef.nameSpan.focus), psym)(using superCtx)
2487+
added :: parentTrees(parents1, ptrees)
2488+
case _ =>
2489+
ptrees
25192490

25202491
/** Checks if one of the decls is a type with the same name as class type member in selfType */
25212492
def classExistsOnSelf(decls: Scope, self: tpd.ValDef): Boolean = {
@@ -2538,10 +2509,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
25382509
ensureCorrectSuperClass()
25392510
completeAnnotations(cdef, cls)
25402511
val constr1 = typed(constr).asInstanceOf[DefDef]
2541-
val parents0 = parentTrees(
2512+
val parents1 = parentTrees(
25422513
cls.classInfo.declaredParents,
25432514
parents.mapconserve(typedParent).filterConserve(!_.isEmpty))
2544-
val parents1 = ensureConstrCall(cls, parents0)(using superCtx)
25452515
val firstParentTpe = parents1.head.tpe.dealias
25462516
val firstParent = firstParentTpe.typeSymbol
25472517

@@ -2610,23 +2580,19 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
26102580
protected def addAccessorDefs(cls: Symbol, body: List[Tree])(using Context): List[Tree] =
26112581
PrepareInlineable.addAccessorDefs(cls, body)
26122582

2613-
/** If this is a real class, make sure its first parent is a
2614-
* constructor call. Cannot simply use a type. Overridden in ReTyper.
2583+
/** Turn a parent type into a constructor call where needed. This is the case where
2584+
* - we are in a Scala class or module (not a Java class, nor a trait), and
2585+
* - the parent symbol is a non-trait class, or
2586+
* - the parent symbol is a trait that takes at least one (explicit or implicit) parameter
2587+
* and the parent symbol is directly extended by the current class (i.e. not
2588+
* extended by the superclass).
26152589
*/
2616-
def ensureConstrCall(cls: ClassSymbol, parents: List[Tree])(using Context): List[Tree] = parents match
2617-
case parents @ (first :: others) =>
2618-
parents.derivedCons(ensureConstrCall(cls, first), others)
2619-
case parents =>
2620-
parents
2621-
2622-
/** If this is a real class, make sure its first parent is a
2623-
* constructor call. Cannot simply use a type. Overridden in ReTyper.
2624-
*/
2625-
def ensureConstrCall(cls: ClassSymbol, parent: Tree)(using Context): Tree =
2626-
if (parent.isType && !cls.is(Trait) && !cls.is(JavaDefined))
2627-
typed(untpd.New(untpd.TypedSplice(parent), Nil))
2628-
else
2629-
parent
2590+
def ensureConstrCall(cls: ClassSymbol, parent: Tree, psym: Symbol)(using Context): Tree =
2591+
if parent.isType && !cls.is(Trait) && !cls.is(JavaDefined) && psym.isClass
2592+
&& (!psym.is(Trait)
2593+
|| psym.primaryConstructor.info.takesParams && !cls.superClass.isSubClass(psym))
2594+
then typed(untpd.New(untpd.TypedSplice(parent), Nil))
2595+
else parent
26302596

26312597
def localDummy(cls: ClassSymbol, impl: untpd.Template)(using Context): Symbol =
26322598
newLocalDummy(cls, impl.span)

tests/neg/i14432c.check

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
-- Error: tests/neg/i14432c.scala:12:18 --------------------------------------------------------------------------------
2-
12 |class Bar extends example.Foo(23) { // error // error: cant access private[example] ctor
2+
12 |class Bar extends example.Foo(23) { // error: cant access private[example] ctor
33
| ^^^^^^^^^^^
44
| constructor Foo cannot be accessed as a member of example.Foo from class Bar.
5-
-- Error: tests/neg/i14432c.scala:12:6 ---------------------------------------------------------------------------------
6-
12 |class Bar extends example.Foo(23) { // error // error: cant access private[example] ctor
7-
| ^
8-
| constructor Foo cannot be accessed as a member of example.Foo from class Bar.
95
-- Error: tests/neg/i14432c.scala:16:43 --------------------------------------------------------------------------------
106
16 | val mFoo = summon[Mirror.Of[example.Foo]] // error: no mirror
117
| ^

tests/neg/i14432c.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ package example {
99

1010
}
1111

12-
class Bar extends example.Foo(23) { // error // error: cant access private[example] ctor
12+
class Bar extends example.Foo(23) { // error: cant access private[example] ctor
1313

1414
// however we can not provide an anonymous mirror
1515
// at this call site because the constructor is not accessible.

tests/neg/i6778.check

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
-- [E104] Syntax Error: tests/neg/i6778.scala:3:27 ---------------------------------------------------------------------
2-
3 |class Bar extends Foo with A(10) // error
2+
3 |class Bar extends Foo with A(10) // error // error
33
| ^^^^^
44
| class A is not a trait
55
|
66
| longer explanation available when compiling with `-explain`
7+
-- Error: tests/neg/i6778.scala:3:6 ------------------------------------------------------------------------------------
8+
3 |class Bar extends Foo with A(10) // error // error
9+
| ^
10+
| missing argument for parameter x of constructor A in class A: (x: Int): A

tests/neg/i6778.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
trait Foo
22
class A(x: Int)
3-
class Bar extends Foo with A(10) // error
3+
class Bar extends Foo with A(10) // error // error

tests/neg/i7613.check

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
11
-- Error: tests/neg/i7613.scala:10:16 ----------------------------------------------------------------------------------
2-
10 | new BazLaws[A] {} // error // error
2+
10 | new BazLaws[A] {} // error
33
| ^
44
| No given instance of type Baz[A] was found for parameter x$1 of constructor BazLaws in trait BazLaws
5-
-- Error: tests/neg/i7613.scala:10:2 -----------------------------------------------------------------------------------
6-
10 | new BazLaws[A] {} // error // error
7-
| ^
8-
| No given instance of type Bar[A] was found for parameter x$1 of constructor BarLaws in trait BarLaws

tests/neg/i7613.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,4 @@ trait BarLaws[A](using Bar[A]) extends FooLaws[A]
77
trait BazLaws[A](using Baz[A]) extends BarLaws[A]
88

99
def instance[A](using Foo[A]): BazLaws[A] =
10-
new BazLaws[A] {} // error // error
11-
10+
new BazLaws[A] {} // error

tests/pos/i15976.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
final abstract class ForcedRecompilationToken[T]
2+
object ForcedRecompilationToken {
3+
implicit def default: ForcedRecompilationToken["abc"] = null
4+
}
5+
6+
class BadNoParens[T](implicit ev: ForcedRecompilationToken[T])
7+
8+
// error
9+
object X extends BadNoParens
10+
11+
// ok
12+
object Y extends BadNoParens()
13+
14+
object App extends App {
15+
println("compiled")
16+
}

0 commit comments

Comments
 (0)