diff --git a/integration/feature/private-methods/repo/build.sc b/integration/feature/private-methods/repo/build.sc new file mode 100644 index 00000000000..62bf2f18791 --- /dev/null +++ b/integration/feature/private-methods/repo/build.sc @@ -0,0 +1,43 @@ +import mill._ + +def pub = T { + priv() +} + +private def priv = T { + "priv" +} + +object foo extends Module { + def bar = T { + baz() + } +} + +private def baz = T { + "bazOuter" +} + +object qux extends Module { + object foo extends Module { + def bar = T { + baz() + } + } + private def baz = T { + "bazInner" + } +} + +object cls extends cls +class cls extends Module { + object foo extends Module { + def bar = T { + baz() + } + } + + private def baz = T { + "bazCls" + } +} diff --git a/integration/feature/private-methods/test/src/PrivateMethodsTests.scala b/integration/feature/private-methods/test/src/PrivateMethodsTests.scala new file mode 100644 index 00000000000..15a2a90e59a --- /dev/null +++ b/integration/feature/private-methods/test/src/PrivateMethodsTests.scala @@ -0,0 +1,46 @@ +package mill.integration + +import utest._ + +object PrivateMethodsTests extends IntegrationTestSuite { + val tests = Tests { + val wsRoot = initWorkspace() + "simple" - { + // Simple public target depending on private target works + val pub = evalStdout("show", "pub") + assert(pub.out == "\"priv\"") + assert(pub.isSuccess) + + // Make sure calling private methods indirectly in a variety of scenarios works + // even when they're "not really private" due to + val fooBar = evalStdout("show", "foo.bar") + assert(fooBar.out == "\"bazOuter\"") + assert(fooBar.isSuccess) + + val quxFooBar = evalStdout("show", "qux.foo.bar") + assert(quxFooBar.out == "\"bazInner\"") + assert(quxFooBar.isSuccess) + + val clsFooBar = evalStdout("show", "cls.foo.bar") + assert(clsFooBar.out == "\"bazCls\"") + assert(clsFooBar.isSuccess) + + // Make sure calling private methods directly fails + val priv = evalStdout("show", "priv") + assert(priv.err.contains("Cannot resolve priv")) + assert(priv.isSuccess == false) + + val baz = evalStdout("show", "baz") + assert(baz.err.contains("Cannot resolve baz")) + assert(baz.isSuccess == false) + + val quxBaz = evalStdout("show", "qux.baz") + assert(quxBaz.err.contains("Cannot resolve qux.baz")) + assert(quxBaz.isSuccess == false) + + val clsBaz = evalStdout("show", "cls.baz") + assert(clsBaz.err.contains("Cannot resolve cls.baz")) + assert(clsBaz.isSuccess == false) + } + } +} diff --git a/main/eval/src/mill/eval/GroupEvaluator.scala b/main/eval/src/mill/eval/GroupEvaluator.scala index f2f5f10f9ad..8754b67a539 100644 --- a/main/eval/src/mill/eval/GroupEvaluator.scala +++ b/main/eval/src/mill/eval/GroupEvaluator.scala @@ -8,7 +8,7 @@ import mill.eval.Evaluator.TaskResult import mill.util._ import scala.collection.mutable -import scala.reflect.NameTransformer.decode +import scala.reflect.NameTransformer.{encode, decode} import scala.util.DynamicVariable import scala.util.control.NonFatal @@ -98,7 +98,13 @@ private[mill] trait GroupEvaluator { val methods = for { c <- transitiveParents m <- c.getDeclaredMethods - if decode(m.getName) == namedTask.ctx.segment.pathSegments.head + encodedTaskName = encode(namedTask.ctx.segment.pathSegments.head) + if m.getName == encodedTaskName || + // Handle scenarios where private method names get mangled when they are + // not really JVM-private due to being accessed by Scala nested objects + // or classes https://github.com/scala/bug/issues/9306 + m.getName == (c.getName.replace('.', '$') + "$$" + encodedTaskName) || + m.getName == (c.getName.replace('.', '$') + "$" + encodedTaskName) } yield m val methodClass = methods.head.getDeclaringClass.getName