From 11fb1fd93f1c6a8709d7103a46e4d11f5a10facf Mon Sep 17 00:00:00 2001 From: Xavientois Date: Mon, 11 Apr 2022 14:59:55 -0400 Subject: [PATCH] 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`. --- .../tools/dotc/transform/init/Semantic.scala | 39 ++++++++++++++----- tests/init/neg/enum-desugared.check | 11 ------ tests/init/neg/enum.check | 9 ----- tests/init/neg/inner-case.scala | 11 ------ tests/init/neg/inner-new.scala | 11 ------ tests/init/{neg => pos}/enum-desugared.scala | 2 +- .../enum.scala => pos/enum-ordinal.scala} | 0 tests/init/pos/inner-case.scala | 11 ++++++ tests/init/pos/inner-enum-multi-variant.scala | 3 ++ tests/init/pos/inner-enum.scala | 4 ++ tests/init/pos/inner-new.scala | 11 ++++++ tests/init/pos/some-this.scala | 2 + 12 files changed, 61 insertions(+), 53 deletions(-) delete mode 100644 tests/init/neg/enum-desugared.check delete mode 100644 tests/init/neg/enum.check delete mode 100644 tests/init/neg/inner-case.scala delete mode 100644 tests/init/neg/inner-new.scala rename tests/init/{neg => pos}/enum-desugared.scala (94%) rename tests/init/{neg/enum.scala => pos/enum-ordinal.scala} (100%) create mode 100644 tests/init/pos/inner-case.scala create mode 100644 tests/init/pos/inner-enum-multi-variant.scala create mode 100644 tests/init/pos/inner-enum.scala create mode 100644 tests/init/pos/inner-new.scala create mode 100644 tests/init/pos/some-this.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index 25ea09c3ade4..f33aa4f9b3a2 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -410,8 +410,8 @@ object Semantic { def select(f: Symbol, source: Tree): Contextual[Result] = value.select(f, source) ++ errors - def call(meth: Symbol, args: List[ArgInfo], superType: Type, source: Tree): Contextual[Result] = - value.call(meth, args, superType, source) ++ errors + def call(meth: Symbol, args: List[ArgInfo], receiver: Type, superType: Type, source: Tree): Contextual[Result] = + value.call(meth, args, receiver, superType, source) ++ errors def callConstructor(ctor: Symbol, args: List[ArgInfo], source: Tree): Contextual[Result] = value.callConstructor(ctor, args, source) ++ errors @@ -587,7 +587,7 @@ object Semantic { } } - 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) { + 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) { def checkArgs = args.flatMap(_.promote) def isSyntheticApply(meth: Symbol) = @@ -600,6 +600,18 @@ object Semantic { || (meth eq defn.Object_ne) || (meth eq defn.Any_isInstanceOf) + def checkArgsWithParametricity() = + val methodType = atPhaseBeforeTransforms { meth.info.stripPoly } + var allArgsPromote = true + val errors = methodType.paramInfoss.flatten.zip(args).flatMap { (info, arg) => + val isMatchable = info.repeatedToSingle <:< defn.MatchableType + val isTypeParam = info.isInstanceOf[TypeRef] && info.asInstanceOf[TypeRef].symbol.isTypeParam + val errors = arg.promote + allArgsPromote = allArgsPromote && errors.isEmpty + if isTypeParam || !isMatchable then Nil else errors + } + (errors, allArgsPromote) + // fast track if the current object is already initialized if promoted.isCurrentObjectPromoted then Result(Hot, Nil) else if isAlwaysSafe(meth) then Result(Hot, Nil) @@ -610,7 +622,14 @@ object Semantic { val klass = meth.owner.companionClass.asClass instantiate(klass, klass.primaryConstructor, args, source) else - Result(Hot, checkArgs) + if meth.isStatic || receiver.isSingleton then + val (errors, allArgsPromote) = checkArgsWithParametricity() + if allArgsPromote || errors.nonEmpty then + Result(Hot, errors) + else + Result(Cold, errors) + else + Result(Hot, checkArgs) case Cold => val error = CallCold(meth, source, trace.toVector) @@ -666,7 +685,7 @@ object Semantic { } case RefSet(refs) => - val resList = refs.map(_.call(meth, args, superType, source)) + val resList = refs.map(_.call(meth, args, receiver, superType, source)) val value2 = resList.map(_.value).join val errors = resList.flatMap(_.errors) Result(value2, errors) @@ -946,7 +965,7 @@ object Semantic { locally { given Trace = trace2 val args = member.info.paramInfoss.flatten.map(_ => ArgInfo(Hot, EmptyTree)) - val res = warm.call(member, args, superType = NoType, source = member.defTree) + val res = warm.call(member, args, receiver = NoType, superType = NoType, source = member.defTree) buffer ++= res.ensureHot(msg, source).errors } else @@ -1126,14 +1145,14 @@ object Semantic { case Select(supert: Super, _) => val SuperType(thisTp, superTp) = supert.tpe val thisValue2 = resolveThis(thisTp.classSymbol.asClass, thisV, klass, ref) - Result(thisValue2, errors).call(ref.symbol, args, superTp, expr) + Result(thisValue2, errors).call(ref.symbol, args, thisTp, superTp, expr) case Select(qual, _) => val res = eval(qual, thisV, klass) ++ errors if ref.symbol.isConstructor then res.callConstructor(ref.symbol, args, source = expr) else - res.call(ref.symbol, args, superType = NoType, source = expr) + res.call(ref.symbol, args, receiver = qual.tpe, superType = NoType, source = expr) case id: Ident => id.tpe match @@ -1142,13 +1161,13 @@ object Semantic { val enclosingClass = id.symbol.owner.enclosingClass.asClass val thisValue2 = resolveThis(enclosingClass, thisV, klass, id) // local methods are not a member, but we can reuse the method `call` - thisValue2.call(id.symbol, args, superType = NoType, expr, needResolve = false) + thisValue2.call(id.symbol, args, receiver = NoType, superType = NoType, expr, needResolve = false) case TermRef(prefix, _) => val res = cases(prefix, thisV, klass, id) ++ errors if id.symbol.isConstructor then res.callConstructor(id.symbol, args, source = expr) else - res.call(id.symbol, args, superType = NoType, source = expr) + res.call(id.symbol, args, receiver = prefix, superType = NoType, source = expr) case Select(qualifier, name) => val qualRes = eval(qualifier, thisV, klass) diff --git a/tests/init/neg/enum-desugared.check b/tests/init/neg/enum-desugared.check deleted file mode 100644 index 3deb11a7f311..000000000000 --- a/tests/init/neg/enum-desugared.check +++ /dev/null @@ -1,11 +0,0 @@ --- Error: tests/init/neg/enum-desugared.scala:17:10 -------------------------------------------------------------------- -17 | Array(this.LazyErrorId, this.NoExplanationID) // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Cannot prove that the value is fully initialized. Only initialized values may be used as arguments. - | - | The unsafe promotion may cause the following problem: - | Calling the external method method name may cause initialization errors. Calling trace: - | -> Array(this.LazyErrorId, this.NoExplanationID) // error [ enum-desugared.scala:17 ] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | -> override def productPrefix: String = this.name() [ enum-desugared.scala:29 ] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/init/neg/enum.check b/tests/init/neg/enum.check deleted file mode 100644 index 655d9b74e863..000000000000 --- a/tests/init/neg/enum.check +++ /dev/null @@ -1,9 +0,0 @@ --- Error: tests/init/neg/enum.scala:4:8 -------------------------------------------------------------------------------- -4 | NoExplanationID // error - | ^ - | Cannot prove that the value is fully initialized. Only initialized values may be used as arguments. - | - | The unsafe promotion may cause the following problem: - | Calling the external method method name may cause initialization errors. Calling trace: - | -> NoExplanationID // error [ enum.scala:4 ] - | ^ diff --git a/tests/init/neg/inner-case.scala b/tests/init/neg/inner-case.scala deleted file mode 100644 index fa4ea0250884..000000000000 --- a/tests/init/neg/inner-case.scala +++ /dev/null @@ -1,11 +0,0 @@ -class Foo { - case class Inner(x: Int) { - def f() = count + x - } - - val a = Inner(5) // ok - println(a) // error - - var count = 0 - println(a) // ok -} \ No newline at end of file diff --git a/tests/init/neg/inner-new.scala b/tests/init/neg/inner-new.scala deleted file mode 100644 index 016179a1d7fc..000000000000 --- a/tests/init/neg/inner-new.scala +++ /dev/null @@ -1,11 +0,0 @@ -class Foo { - class Inner { - def f() = count + 1 - } - - val a = new Inner // ok - println(a) // error - - var count = 0 - println(a) // ok -} \ No newline at end of file diff --git a/tests/init/neg/enum-desugared.scala b/tests/init/pos/enum-desugared.scala similarity index 94% rename from tests/init/neg/enum-desugared.scala rename to tests/init/pos/enum-desugared.scala index 8c1f3662f926..ead44cd7a939 100644 --- a/tests/init/neg/enum-desugared.scala +++ b/tests/init/pos/enum-desugared.scala @@ -14,7 +14,7 @@ object ErrorMessageID { final val NoExplanationID = $new(1, "NoExplanationID") private[this] val $values: Array[ErrorMessageID] = - Array(this.LazyErrorId, this.NoExplanationID) // error + Array(this.LazyErrorId, this.NoExplanationID) def values: Array[ErrorMessageID] = $values.clone() diff --git a/tests/init/neg/enum.scala b/tests/init/pos/enum-ordinal.scala similarity index 100% rename from tests/init/neg/enum.scala rename to tests/init/pos/enum-ordinal.scala diff --git a/tests/init/pos/inner-case.scala b/tests/init/pos/inner-case.scala new file mode 100644 index 000000000000..8c5e72110ad1 --- /dev/null +++ b/tests/init/pos/inner-case.scala @@ -0,0 +1,11 @@ +class Foo { + case class Inner(x: Int) { + def f() = count + x + } + + val a = Inner(5) + println(a) + + var count = 0 + println(a) +} \ No newline at end of file diff --git a/tests/init/pos/inner-enum-multi-variant.scala b/tests/init/pos/inner-enum-multi-variant.scala new file mode 100644 index 000000000000..b7e27b81e819 --- /dev/null +++ b/tests/init/pos/inner-enum-multi-variant.scala @@ -0,0 +1,3 @@ +class Outer: + enum Color: + case Red, Blue \ No newline at end of file diff --git a/tests/init/pos/inner-enum.scala b/tests/init/pos/inner-enum.scala new file mode 100644 index 000000000000..120014852e14 --- /dev/null +++ b/tests/init/pos/inner-enum.scala @@ -0,0 +1,4 @@ +class Outer: + enum MyEnum { + case Case + } \ No newline at end of file diff --git a/tests/init/pos/inner-new.scala b/tests/init/pos/inner-new.scala new file mode 100644 index 000000000000..b67e5da6705d --- /dev/null +++ b/tests/init/pos/inner-new.scala @@ -0,0 +1,11 @@ +class Foo { + class Inner { + def f() = count + 1 + } + + val a = new Inner + println(a) + + var count = 0 + println(a) +} \ No newline at end of file diff --git a/tests/init/pos/some-this.scala b/tests/init/pos/some-this.scala new file mode 100644 index 000000000000..b79ad89eb475 --- /dev/null +++ b/tests/init/pos/some-this.scala @@ -0,0 +1,2 @@ +class X: + val some = Some(this) \ No newline at end of file