Skip to content

Commit 4165307

Browse files
committed
Check method arguments with parametricity when static
When a global static is called, allow for a cold argument if the corresponding parameter is not `Matchable`.
1 parent e35b6ff commit 4165307

13 files changed

+57
-44
lines changed

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

+29-10
Original file line numberDiff line numberDiff line change
@@ -410,8 +410,8 @@ object Semantic {
410410
def select(f: Symbol, source: Tree): Contextual[Result] =
411411
value.select(f, source) ++ errors
412412

413-
def call(meth: Symbol, args: List[ArgInfo], superType: Type, source: Tree): Contextual[Result] =
414-
value.call(meth, args, superType, source) ++ errors
413+
def call(meth: Symbol, args: List[ArgInfo], receiver: Type, superType: Type, source: Tree): Contextual[Result] =
414+
value.call(meth, args, receiver, superType, source) ++ errors
415415

416416
def callConstructor(ctor: Symbol, args: List[ArgInfo], source: Tree): Contextual[Result] =
417417
value.callConstructor(ctor, args, source) ++ errors
@@ -587,7 +587,7 @@ object Semantic {
587587
}
588588
}
589589

590-
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) {
590+
def call(meth: Symbol, args: List[ArgInfo], receiver: Type, 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

593593
def isSyntheticApply(meth: Symbol) =
@@ -600,6 +600,18 @@ object Semantic {
600600
|| (meth eq defn.Object_ne)
601601
|| (meth eq defn.Any_isInstanceOf)
602602

603+
def checkArgsWithParametricity() =
604+
val methodType = atPhaseBeforeTransforms { meth.info.stripPoly }
605+
var allArgsPromote = true
606+
val errors = methodType.paramInfoss.flatten.zip(args).flatMap { (info, arg) =>
607+
val isMatchable = info.repeatedToSingle <:< defn.MatchableType
608+
val isTypeParam = info.repeatedToSingle.isInstanceOf[TypeParamRef]
609+
val errors = arg.promote
610+
allArgsPromote = allArgsPromote && errors.isEmpty
611+
if isTypeParam && !isMatchable then Nil else errors
612+
}
613+
(errors, allArgsPromote)
614+
603615
// fast track if the current object is already initialized
604616
if promoted.isCurrentObjectPromoted then Result(Hot, Nil)
605617
else if isAlwaysSafe(meth) then Result(Hot, Nil)
@@ -610,7 +622,14 @@ object Semantic {
610622
val klass = meth.owner.companionClass.asClass
611623
instantiate(klass, klass.primaryConstructor, args, source)
612624
else
613-
Result(Hot, checkArgs)
625+
if meth.isStatic || receiver.isSingleton then
626+
val (errors, allArgsPromote) = checkArgsWithParametricity()
627+
if allArgsPromote || errors.nonEmpty then
628+
Result(Hot, errors)
629+
else
630+
Result(Cold, errors)
631+
else
632+
Result(Hot, checkArgs)
614633

615634
case Cold =>
616635
val error = CallCold(meth, source, trace.toVector)
@@ -666,7 +685,7 @@ object Semantic {
666685
}
667686

668687
case RefSet(refs) =>
669-
val resList = refs.map(_.call(meth, args, superType, source))
688+
val resList = refs.map(_.call(meth, args, receiver, superType, source))
670689
val value2 = resList.map(_.value).join
671690
val errors = resList.flatMap(_.errors)
672691
Result(value2, errors)
@@ -946,7 +965,7 @@ object Semantic {
946965
locally {
947966
given Trace = trace2
948967
val args = member.info.paramInfoss.flatten.map(_ => ArgInfo(Hot, EmptyTree))
949-
val res = warm.call(member, args, superType = NoType, source = member.defTree)
968+
val res = warm.call(member, args, receiver = NoType, superType = NoType, source = member.defTree)
950969
buffer ++= res.ensureHot(msg, source).errors
951970
}
952971
else
@@ -1126,14 +1145,14 @@ object Semantic {
11261145
case Select(supert: Super, _) =>
11271146
val SuperType(thisTp, superTp) = supert.tpe
11281147
val thisValue2 = resolveThis(thisTp.classSymbol.asClass, thisV, klass, ref)
1129-
Result(thisValue2, errors).call(ref.symbol, args, superTp, expr)
1148+
Result(thisValue2, errors).call(ref.symbol, args, thisTp, superTp, expr)
11301149

11311150
case Select(qual, _) =>
11321151
val res = eval(qual, thisV, klass) ++ errors
11331152
if ref.symbol.isConstructor then
11341153
res.callConstructor(ref.symbol, args, source = expr)
11351154
else
1136-
res.call(ref.symbol, args, superType = NoType, source = expr)
1155+
res.call(ref.symbol, args, receiver = qual.tpe, superType = NoType, source = expr)
11371156

11381157
case id: Ident =>
11391158
id.tpe match
@@ -1142,13 +1161,13 @@ object Semantic {
11421161
val enclosingClass = id.symbol.owner.enclosingClass.asClass
11431162
val thisValue2 = resolveThis(enclosingClass, thisV, klass, id)
11441163
// local methods are not a member, but we can reuse the method `call`
1145-
thisValue2.call(id.symbol, args, superType = NoType, expr, needResolve = false)
1164+
thisValue2.call(id.symbol, args, receiver = NoType, superType = NoType, expr, needResolve = false)
11461165
case TermRef(prefix, _) =>
11471166
val res = cases(prefix, thisV, klass, id) ++ errors
11481167
if id.symbol.isConstructor then
11491168
res.callConstructor(id.symbol, args, source = expr)
11501169
else
1151-
res.call(id.symbol, args, superType = NoType, source = expr)
1170+
res.call(id.symbol, args, receiver = prefix, superType = NoType, source = expr)
11521171

11531172
case Select(qualifier, name) =>
11541173
val qualRes = eval(qualifier, thisV, klass)

tests/init/neg/enum-desugared.check

-11
This file was deleted.

tests/init/neg/enum.check

-9
This file was deleted.

tests/init/neg/inner-case.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ class Foo {
44
}
55

66
val a = Inner(5) // ok
7-
println(a) // error
7+
println(a) // error
88

99
var count = 0
10-
println(a) // ok
10+
println(a) // ok
1111
}

tests/init/neg/inner-new.scala

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ class Foo {
33
def f() = count + 1
44
}
55

6-
val a = new Inner // ok
7-
println(a) // error
6+
val a = new Inner // ok
7+
println(a) // error
88

99
var count = 0
10-
println(a) // ok
10+
println(a) // ok
1111
}

tests/init/neg/leak-warm.check

+9-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
-- Error: tests/init/neg/leak-warm.scala:18:26 -------------------------------------------------------------------------
2-
18 | val l: List[A] = List(c, d) // error
3-
| ^^^^
4-
| Cannot prove that the value is fully initialized. Only initialized values may be used as arguments.
1+
-- Error: tests/init/neg/leak-warm.scala:19:18 -------------------------------------------------------------------------
2+
19 | val l2 = l.map(_.m()) // error
3+
| ^^^^^^^^^^^^
4+
| Call method leakWarm.l.map[leakWarm.A#B](
5+
| {
6+
| def $anonfun(_$1: leakWarm.A): leakWarm.A#B = _$1.m()
7+
| closure($anonfun)
8+
| }
9+
| ) on a value with an unknown initialization.

tests/init/neg/leak-warm.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ object leakWarm {
1515
}
1616
val c = new C(1, 2)
1717
val d = new D(3, 4)
18-
val l: List[A] = List(c, d) // error
19-
val l2 = l.map(_.m())
18+
val l: List[A] = List(c, d)
19+
val l2 = l.map(_.m()) // error
2020
}

tests/init/neg/some-this.scala

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class X:
2+
val some = Some(this) // error

tests/init/neg/early-promote.scala renamed to tests/init/pos/early-promote.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class Y {
1010

1111
val n = 10
1212
val x = new X
13-
List(x.b) // unsafe promotion
13+
List(x.b)
1414

1515
}
1616

@@ -24,7 +24,7 @@ class A { // checking A
2424
def c = new C
2525
}
2626
val b = new B()
27-
List(b) // error: the checker simply issue warnings for objects that contain inner classes
27+
List(b)
2828
val af = 42
2929
}
3030

tests/init/neg/enum-desugared.scala renamed to tests/init/pos/enum-desugared.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ object ErrorMessageID {
1414
final val NoExplanationID = $new(1, "NoExplanationID")
1515

1616
private[this] val $values: Array[ErrorMessageID] =
17-
Array(this.LazyErrorId, this.NoExplanationID) // error
17+
Array(this.LazyErrorId, this.NoExplanationID)
1818

1919
def values: Array[ErrorMessageID] = $values.clone()
2020

File renamed without changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class Outer:
2+
enum Color:
3+
case Red, Blue

tests/init/pos/inner-enum.scala

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
class Outer:
2+
enum MyEnum {
3+
case Case
4+
}

0 commit comments

Comments
 (0)