diff --git a/docs/notes/2.32.x.md b/docs/notes/2.32.x.md index 7ce50fb838d..48d90bd817a 100644 --- a/docs/notes/2.32.x.md +++ b/docs/notes/2.32.x.md @@ -66,6 +66,8 @@ When `pants publish` is invoked, Pants will now skip packaging for `helm_chart` #### JVM +Fixed [a bug](https://github.com/pantsbuild/pants/issues/23023) where top-level definitions in Scala were not inferred as a dependency. + #### Python The Pex `--compress` field is now plumbed through to the `pex_binary` target. diff --git a/src/python/pants/backend/scala/compile/scalac_test.py b/src/python/pants/backend/scala/compile/scalac_test.py index c8d28858177..4ed68f6b0de 100644 --- a/src/python/pants/backend/scala/compile/scalac_test.py +++ b/src/python/pants/backend/scala/compile/scalac_test.py @@ -890,6 +890,77 @@ def test_compile_no_deps_scala3( assert check_result.exit_code == 0 +@maybe_skip_jdk_test +def test_compile_scala3_top_level_val_cross_file( + rule_runner: RuleRunner, scala3_stdlib_jvm_lockfile: JVMLockfileFixture +) -> None: + """Regression test for https://github.com/pantsbuild/pants/issues/23023.""" + rule_runner.write_files( + { + "BUILD": dedent( + """\ + scala_sources( + name = 'lib', + ) + """ + ), + "3rdparty/jvm/BUILD": scala3_stdlib_jvm_lockfile.requirements_as_jvm_artifact_targets(), + "3rdparty/jvm/default.lock": scala3_stdlib_jvm_lockfile.serialized_lockfile, + "Foo.scala": dedent( + """\ + package example + val foo = "foo" + """ + ), + "Bar.scala": dedent( + """\ + package example + val bar = foo + """ + ), + } + ) + rule_runner.set_options( + args=[ + "--scala-version-for-resolve={'jvm-default': '3.2.0'}", + ], + env_inherit=PYTHON_BOOTSTRAP_ENV, + ) + + # First, verify Foo.scala compiles and produces the expected $package files. + foo_target = expect_single_expanded_coarsened_target( + rule_runner, Address(spec_path="", target_name="lib", relative_file_path="Foo.scala") + ) + foo_classpath = rule_runner.request( + RenderedClasspath, + [CompileScalaSourceRequest(component=foo_target, resolve=make_resolve(rule_runner))], + ) + assert foo_classpath.content == { + ".Foo.scala.lib.scalac.jar": { + "example/Foo$package$.class", + "example/Foo$package.class", + "example/Foo$package.tasty", + } + } + + # Now compile Bar.scala, which references the top-level val `foo` from Foo.scala. + # This requires Foo.scala's output (including the .tasty file) on the classpath. + bar_target = expect_single_expanded_coarsened_target( + rule_runner, Address(spec_path="", target_name="lib", relative_file_path="Bar.scala") + ) + bar_classpath = rule_runner.request( + RenderedClasspath, + [CompileScalaSourceRequest(component=bar_target, resolve=make_resolve(rule_runner))], + ) + assert bar_classpath.content == { + ".Bar.scala.lib.scalac.jar": { + "example/Bar$package$.class", + "example/Bar$package.class", + "example/Bar$package.tasty", + } + } + + @pytest.fixture def cats_jvm_lockfile_def() -> JVMLockfileFixtureDefinition: return JVMLockfileFixtureDefinition( diff --git a/src/python/pants/backend/scala/dependency_inference/ScalaParser.scala b/src/python/pants/backend/scala/dependency_inference/ScalaParser.scala index a97475154fd..97c71a319a9 100644 --- a/src/python/pants/backend/scala/dependency_inference/ScalaParser.scala +++ b/src/python/pants/backend/scala/dependency_inference/ScalaParser.scala @@ -356,7 +356,10 @@ class SourceAnalysisTraverser extends Traverser { decltpe.foreach(tpe => { extractNamesFromTypeTree(tpe).iterator.foreach(recordConsumedSymbol(_)) }) - super.apply(rhs) + // NB: Use `apply` (not `super.apply`) so that a bare Term.Name RHS (e.g. `val bar = foo`) + // is dispatched through the overridden method and recorded as a consumed symbol. + // `super.apply` only traverses *children*, which misses leaf nodes like Term.Name. + apply(rhs) } case Defn.Var(mods, pats, decltpe, rhs) => { @@ -367,7 +370,7 @@ class SourceAnalysisTraverser extends Traverser { decltpe.foreach(tpe => { extractNamesFromTypeTree(tpe).iterator.foreach(recordConsumedSymbol(_)) }) - super.apply(rhs) + apply(rhs) } case Defn.Def(mods, nameNode, tparams, params, decltpe, body) => { diff --git a/src/python/pants/backend/scala/dependency_inference/scala_parser_test.py b/src/python/pants/backend/scala/dependency_inference/scala_parser_test.py index 0569c04b6f1..19cd83a4c0c 100644 --- a/src/python/pants/backend/scala/dependency_inference/scala_parser_test.py +++ b/src/python/pants/backend/scala/dependency_inference/scala_parser_test.py @@ -456,7 +456,9 @@ def test_import_root_pacjage(rule_runner: RuleRunner) -> None: ) assert sorted(analysis.fully_qualified_consumed_symbols()) == [ + "foo.???", "foo.Bar", + "io.circe.syntax.???", ]