Skip to content

Fix issue with duplicate filenames in MBT-based PC#8406

Merged
tgodzik merged 3 commits into
scalameta:main-v2from
jchyb:fix-mbt-duplicate
Jun 9, 2026
Merged

Fix issue with duplicate filenames in MBT-based PC#8406
tgodzik merged 3 commits into
scalameta:main-v2from
jchyb:fix-mbt-duplicate

Conversation

@jchyb

@jchyb jchyb commented May 25, 2026

Copy link
Copy Markdown
Contributor

The presentation compiler puts the scala sources into AggregateClassPath object, which appears to be indexed by (package, file_basename) (slight simplification here), so when we compile main/util/reporter.scala with test/util/reporter.scala (where both are actually put into the same package, just come from different modules/targets), the compiler loses track of one of those. We fix it by renaming the virtual file. reimplementing the problematic parts of AggregateClassPath.

This previously wasn't an issue, since before metals V2, we would not compile multiple sources at once, instead relying on classfiles.

Summary by CodeRabbit

  • Improvements

    • Enhanced classpath handling to better support projects with duplicate source filenames across different directories, ensuring accurate resolution and preventing conflicts.
  • Tests

    • Added end-to-end test validating correct behavior for duplicate-named source files in the same package across multiple source roots.

@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d4d227d4-0fbf-42c3-b3d1-1c95997e262b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

paths.zipWithIndex.map { case (p, i) =>
if (i == 0) SourceFileEntryImpl(AbstractFile.getFile(p))
else {
// AggregateClassPath deduplicates SourceFileEntry by filename, so duplicate-named

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we instead implement our own AggregateClassPath that wouldn't do it? Basically duplicate it with the change logic?

@jchyb jchyb Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I think it looks good, tested both via the unit test, and on the client's repo.

The presentation compiler puts the scala sources into
AggregateClassPath object, which appears to be indexed by `(package,
file_basename)`, so when we compile main/util/reporter.scala with
test/util/reporter.scala (where both are actually put into the same
package, just come from different modules/targets), the compiler loses
track of one of those. We fix it by renaming the virtual file.

This previously wasn't an issue, since before metals V2, we would not
compile multiple sources at once, instead relying on classfiles.
@jchyb jchyb force-pushed the fix-mbt-duplicate branch from 2007397 to abfe802 Compare June 3, 2026 15:10
@jchyb jchyb force-pushed the fix-mbt-duplicate branch from abfe802 to ab3d974 Compare June 3, 2026 15:12
result += c
}

ClassPathEntries(pkgs, if (result.isEmpty) Nil else result.toIndexedSeq)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This if part looks a little weird, but it's a microoptimisation kept from the original AggregateClassPath. I doubt it makes much of a difference, but I decided to keep it (but I can be swayed).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is some optimization in the parent class like:
case ecp: EfficientClassPath => etc.

Is that not neccessary here? And it goes over each classpath.

@jchyb jchyb Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It previously didn't use the list() method, so we couldn't have that EfficientClassPath fast path, but now I changed it to be as similar to original implementation as possible, with added comments on where it differs. It should be slightly faster (1 walk through the class path instead of 3 + the mutable buffer benefits). The code is slightly blown out now though, but I added comments on where things differ compared to the parent.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
tests/unit/src/test/scala/tests/mbt/MbtBuildServerLspSuite.scala (1)

577-608: ⚡ Quick win

Strengthen this regression test with definition-location assertions.

Both hover expectations are identical (val value: String), so this can still pass if both lookups resolve to the same symbol shape. Add assertDefinition checks for MainUtils.value and TestUtils.value to pin each reference to its respective main/src/util/Utils.scala and test/src/util/Utils.scala.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/src/test/scala/tests/mbt/MbtBuildServerLspSuite.scala` around
lines 577 - 608, The hover checks currently only assert the symbol shape; add
definition-location assertions to pin each reference to its correct source:
after the first server.assertHover (the one with MainUtils.val@@ue) add a
server.assertDefinition call targeting MainUtils.value and expect its definition
to resolve to the main util implementation (the Utils in the main source), and
after the second server.assertHover (the one with TestUtils.val@@ue) add a
server.assertDefinition targeting TestUtils.value and expect its definition to
resolve to the test util implementation (the Utils in the test source); use the
existing server.assertDefinition helper (matching how other tests assert
definitions) so the test validates both hover content and precise definition
locations for MainUtils.value and TestUtils.value.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/unit/src/test/scala/tests/mbt/MbtBuildServerLspSuite.scala`:
- Around line 577-608: The hover checks currently only assert the symbol shape;
add definition-location assertions to pin each reference to its correct source:
after the first server.assertHover (the one with MainUtils.val@@ue) add a
server.assertDefinition call targeting MainUtils.value and expect its definition
to resolve to the main util implementation (the Utils in the main source), and
after the second server.assertHover (the one with TestUtils.val@@ue) add a
server.assertDefinition targeting TestUtils.value and expect its definition to
resolve to the test util implementation (the Utils in the test source); use the
existing server.assertDefinition helper (matching how other tests assert
definitions) so the test validates both hover content and precise definition
locations for MainUtils.value and TestUtils.value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4c009c61-1fc8-4154-8bd1-daf725a323a0

📥 Commits

Reviewing files that changed from the base of the PR and between c8d299d and ab3d974.

📒 Files selected for processing (5)
  • mtags/src/main/scala-2.12/scala/meta/internal/pc/classpath/MtagsPathResolver.scala
  • mtags/src/main/scala-2.13/scala/meta/internal/pc/classpath/MtagsPathResolver.scala
  • mtags/src/main/scala-2/scala/tools/nsc/LogicalSourcePath.scala
  • mtags/src/main/scala-2/scala/tools/nsc/classpath/MetalsAggregateClassPath.scala
  • tests/unit/src/test/scala/tests/mbt/MbtBuildServerLspSuite.scala

@tgodzik tgodzik left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

One question from me otherwise.

result += c
}

ClassPathEntries(pkgs, if (result.isEmpty) Nil else result.toIndexedSeq)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is some optimization in the parent class like:
case ecp: EfficientClassPath => etc.

Is that not neccessary here? And it goes over each classpath.

@tgodzik tgodzik left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@tgodzik tgodzik merged commit ad68cad into scalameta:main-v2 Jun 9, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants