Skip to content

Commit 28a8761

Browse files
committed
Treat synthetic apply of case class as new expression
1 parent c01d596 commit 28a8761

File tree

3 files changed

+64
-13
lines changed

3 files changed

+64
-13
lines changed

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

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -590,11 +590,20 @@ object Semantic {
590590
def call(meth: Symbol, args: List[ArgInfo], superType: Type, source: Tree, needResolve: Boolean = true): Contextual[Result] = log("call " + meth.show + ", args = " + args, printer, (_: Result).show) {
591591
def checkArgs = args.flatMap(_.promote)
592592

593+
def isSyntheticApply(meth: Symbol) =
594+
meth.is(Flags.Synthetic)
595+
&& meth.owner.is(Flags.Module)
596+
&& meth.owner.companionClass.is(Flags.Case)
597+
593598
// fast track if the current object is already initialized
594599
if promoted.isCurrentObjectPromoted then Result(Hot, Nil)
595600
else value match {
596601
case Hot =>
597-
Result(Hot, checkArgs)
602+
if isSyntheticApply(meth) then
603+
val klass = meth.owner.companionClass.asClass
604+
instantiate(klass, klass.primaryConstructor, args, source)
605+
else
606+
Result(Hot, checkArgs)
598607

599608
case Cold =>
600609
val error = CallCold(meth, source, trace.toVector)
@@ -616,11 +625,14 @@ object Semantic {
616625
given Trace = trace1
617626
val cls = target.owner.enclosingClass.asClass
618627
val ddef = target.defTree.asInstanceOf[DefDef]
619-
val env2 = Env(ddef, args.map(_.value).widenArgs)
620628
// normal method call
621-
withEnv(if isLocal then env else Env.empty) {
622-
eval(ddef.rhs, ref, cls, cacheResult = true) ++ checkArgs
623-
}
629+
if isSyntheticApply(meth) then
630+
val klass = meth.owner.companionClass.asClass
631+
instantiate(klass, klass.primaryConstructor, args, source)
632+
else
633+
withEnv(if isLocal then env else Env.empty) {
634+
eval(ddef.rhs, ref, cls, cacheResult = true) ++ checkArgs
635+
}
624636
else if ref.canIgnoreMethodCall(target) then
625637
Result(Hot, Nil)
626638
else

docs/docs/reference/other-new-features/safe-initialization.md

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -194,14 +194,6 @@ With the established principles and design goals, following rules are imposed:
194194
non-initialized object is not used, i.e. calling methods or accessing fields
195195
on the escaped object is not allowed.
196196

197-
3. Local definitions may only refer to transitively initialized objects.
198-
199-
It means that in a local definition `val x: T = e`, the expression `e` may
200-
only evaluate to transitively initialized objects. The same goes for local
201-
lazy variables and methods. This rule is again motivated for simplicity in
202-
reasoning about initialization: programmers may safely assume that all local
203-
definitions only point to transitively initialized objects.
204-
205197
## Modularity
206198

207199
The analysis takes the primary constructor of concrete classes as entry points.

tests/init/neg/apply.scala

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
case class A(b: B)
2+
3+
object A:
4+
def foo(b: B) = new A(b)
5+
inline def bar(b: B) = new A(b)
6+
7+
class B:
8+
val a = A(this)
9+
val a2 = A.foo(this) // error
10+
val a3 = A.bar(this)
11+
12+
// test receiver is ThisRef
13+
14+
object O:
15+
case class A(b: B)
16+
17+
object A:
18+
def foo(b: B) = new A(b)
19+
inline def bar(b: B) = new A(b)
20+
21+
class B:
22+
val a = A(this)
23+
val a2 = A.foo(this) // error
24+
val a3 = A.bar(this)
25+
26+
val b = new B
27+
end O
28+
29+
30+
// test receiver is Warm
31+
32+
class M(n: N):
33+
case class A(b: B)
34+
35+
object A:
36+
def foo(b: B) = new A(b)
37+
inline def bar(b: B) = new A(b)
38+
39+
class B:
40+
val a = A(this)
41+
val a2 = A.foo(this) // error
42+
val a3 = A.bar(this)
43+
end M
44+
45+
class N:
46+
val m = new M(this)
47+
val b = new m.B

0 commit comments

Comments
 (0)