Repository: scalameta/scalameta (scala)

Issue:
Missing occurrence for reflective calls
```scala
❯ cat i414.scala
package minimized

object Issue414 {
  val x = new {
    val b = 1
  }
  x.b
}
❯ metac i414.scala
metawarning: 1 feature warning; re-run with -feature for details
1 warning
```

The generated SemanticDB is missing an occurrence for the reflective invocation of the anonymous class
```diff
❯ metap .
Summary:
Schema => SemanticDB v4
Uri => i414.scala
Text => empty
Language => Scala
Symbols => 4 entries
Occurrences => 6 entries

Symbols:
local0 => final class $anon extends AnyRef { +1 decls }
local1 => val method b: Int
minimized/Issue414. => final object Issue414 extends AnyRef { +1 decls }
minimized/Issue414.x. => val method x: AnyRef { val def b: Int }

Occurrences:
[0:8..0:17) <= minimized/
[2:7..2:15) <= minimized/Issue414.
[3:6..3:7) <= minimized/Issue414.x.
[3:14..3:14) <= local0
[4:8..4:9) <= local1
[6:2..6:3) => minimized/Issue414.x.
+ [6:5..6.6] => local1
```

I thought the occurrence was missing due how reflective calls get desugared but the desugaring isn't applied in typer
```
❯ scalac -Xprint:typer i414.scala -feature
i414.scala:4: error: not found: type int
  val x: { val b: int } = new {
                  ^
i414.scala:7: warning: reflective access of structural type member value b should be enabled
by making the implicit value scala.language.reflectiveCalls visible.
This can be achieved by adding the import clause 'import scala.language.reflectiveCalls'
or by setting the compiler option -language:reflectiveCalls.
See the Scaladoc for value scala.language.reflectiveCalls for a discussion
why the feature should be explicitly enabled.
  x.b
    ^
[[syntax trees at end of                     typer]] // i414.scala
package minimized {
  object Issue414 extends scala.AnyRef {
    def <init>(): minimized.Issue414.type = {
      Issue414.super.<init>();
      ()
    };
    private[this] val <x: error>: AnyRef{val b: <error>} = {
      final class $anon extends scala.AnyRef {
        def <init>(): <$anon: AnyRef> = {
          $anon.super.<init>();
          ()
        };
        private[this] val b: Int = 1;
        <stable> <accessor> def b: Int = $anon.this.b
      };
      new $anon()
    };
    <stable> <accessor> def <x: error>: AnyRef{val b: <error>} = Issue414.this.<x: error>;
    Issue414.this.<x: error>.<b: error>
  }
}

1 warning
1 error
```
The desugaring is applied after the jvm phase
```scala
❯ scalac -Xprint:jvm i414.scala -language:reflectiveCalls
i414.scala:7: warning: a pure expression does nothing in statement position; multiline expressions may require enclosing parentheses
  x.b
    ^
[[syntax trees at end of                       jvm]] // i414.scala
package minimized {
  object Issue414 extends Object {
    <synthetic> <static> def reflMethod$Method1(x$1: Class): reflect.Method = {
      var methodCache1: runtime.StructuralCallSite = <apply-dynamic>(apply#<none>, (bootstrap, classOf[]));
      var method1: reflect.Method = methodCache1.find(x$1);
      if (method1.ne(null))
        return method1
      else
        {
          method1 = scala.runtime.ScalaRunTime.ensureAccessible(x$1.getMethod("b", methodCache1.parameterTypes()));
          methodCache1.add(x$1, method1);
          return method1
        }
    };
    <static> private[this] val x: Object = _;
    <stable> <accessor> def x(): Object = Issue414.this.x;
    def <init>(): minimized.Issue414.type = {
      Issue414.super.<init>();
      Issue414.this.x = {
        new <$anon: Object>()
      };
      {
        val qual1: Object = Issue414.this.x();
        try {
  minimized.Issue414.reflMethod$Method1(qual1.getClass()).invoke(qual1, Array[Object]{})
} catch {
  case (1 @ (_: reflect.InvocationTargetException)) => throw 1.getCause()
}.$asInstanceOf[Integer]()
      };
      ()
    }
  };
  final class anon$1 extends Object {
    private[this] val b: Int = _;
    <stable> <accessor> def b(): Int = anon$1.this.b;
    def <init>(): <$anon: Object> = {
      anon$1.super.<init>();
      anon$1.this.b = 1;
      ()
    }
  }
}
```

Pull request description:
Previously, semanticdb-scalac never emitted occurrences for invocations
of members on structural types. The motivation for this behavior was
that there was no reasonable symbol we could use since there no
official defintion for a structural type, the same structural type
can be re-declared in multiple places. This commit changes the
behavior to emit local occurrences when the structural type has been
defined in the same file as the reference occurrence. We still don't
emit reference occurrences when the structural type is referenced
from a separate source file.

Fixes #2788

Patch:
diff --git a/build.sbt b/build.sbt
index aa9fb8062f..bfd0a0d529 100644
--- a/build.sbt
+++ b/build.sbt
@@ -53,10 +53,11 @@ commands += Command.command("download-scala-library") { s =>
   s
 }
 commands += Command.command("save-expect") { s =>
-  "semanticdbScalacPlugin/compile" ::
+  s"++$LatestScala212" ::
+    "semanticdbScalacPlugin/compile" ::
     "semanticdbIntegration/clean" ::
     "semanticdbIntegration/compile" ::
-    "testsJVM/test:runMain scala.meta.tests.semanticdb.SaveExpectTest" :: s
+    "testsJVM/Test/runMain scala.meta.tests.semanticdb.SaveExpectTest" :: s
 }
 commands += Command.command("save-manifest") { s =>
   "testsJVM/test:runMain scala.meta.tests.semanticdb.SaveManifestTest" :: s
@@ -388,6 +389,7 @@ lazy val testSettings: List[Def.SettingsDefinition] = List(
   },
   buildInfoKeys := Seq[BuildInfoKey](
     scalaVersion,
+    "latestScala212Version" -> LatestScala212,
     "databaseSourcepath" ->
       (ThisBuild / baseDirectory).value.getAbsolutePath,
     "commonJVMClassDirectory" -> (common.jvm / Compile / classDirectory).value.getAbsolutePath,
diff --git a/semanticdb/integration/src/main/scala/example/StructuralTypes.scala b/semanticdb/integration/src/main/scala/example/StructuralTypes.scala
new file mode 100644
index 0000000000..8a3f3d0ba1
--- /dev/null
+++ b/semanticdb/integration/src/main/scala/example/StructuralTypes.scala
@@ -0,0 +1,10 @@
+package example
+
+import scala.languageFeature.reflectiveCalls
+
+object StructuralTypes {
+  val a = new {
+    val b = 1
+  }
+  a.b
+}
diff --git a/semanticdb/integration/src/main/scala/example/StructuralTypes2.scala b/semanticdb/integration/src/main/scala/example/StructuralTypes2.scala
new file mode 100644
index 0000000000..32e845d99e
--- /dev/null
+++ b/semanticdb/integration/src/main/scala/example/StructuralTypes2.scala
@@ -0,0 +1,7 @@
+package example
+
+import scala.languageFeature.reflectiveCalls
+
+object StructuralTypes2 {
+  StructuralTypes.a.b
+}
diff --git a/semanticdb/scalac/library/src/main/scala/scala/meta/internal/semanticdb/scalac/TextDocumentOps.scala b/semanticdb/scalac/library/src/main/scala/scala/meta/internal/semanticdb/scalac/TextDocumentOps.scala
index 9eed9a1b71..ddef353bfb 100644
--- a/semanticdb/scalac/library/src/main/scala/scala/meta/internal/semanticdb/scalac/TextDocumentOps.scala
+++ b/semanticdb/scalac/library/src/main/scala/scala/meta/internal/semanticdb/scalac/TextDocumentOps.scala
@@ -275,8 +275,19 @@ trait TextDocumentOps { self: SemanticdbOps =>
                 occurrences(pos) = symbol
               }
             } else {
-              val selectionFromStructuralType = gsym.owner.isRefinementClass
-              if (!selectionFromStructuralType) occurrences(pos) = symbol
+              val isDefinedWithinThisSourceFile = gsym.pos.source == unit.source
+              // We don't emit occurrences for accesses on structural types
+              // because is no canonical definition of a structural types,
+              // the same structure can be re-declared in various locations.
+              // However, in the special case where the structural type is defined
+              // within the same file we use the local symbol that's generated by scalac.
+              // We can't use the local symbol if `gsym.pos.source != unit.source` because
+              // that would mean we reference local symbols across files.
+              val selectionFromGlobalStructuralType =
+                !isDefinedWithinThisSourceFile && gsym.owner.isRefinementClass
+              if (!selectionFromGlobalStructuralType) {
+                occurrences(pos) = symbol
+              }
             }
 
             def tryWithin(map: mutable.Map[m.Tree, m.Name], gsym0: g.Symbol): Unit = {


Test patch:
diff --git a/tests/jvm/src/test/resources/example/Advanced.scala b/tests/jvm/src/test/resources/example/Advanced.scala
index 7b5a319fad..11d6bdf174 100644
--- a/tests/jvm/src/test/resources/example/Advanced.scala
+++ b/tests/jvm/src/test/resources/example/Advanced.scala
@@ -25,11 +25,11 @@ class D/*<=advanced.D#*/[CC/*<=advanced.D#[CC]*/[_]] extends C/*=>advanced.C#*/[
 object Test/*<=advanced.Test.*/ {
   val s/*<=advanced.Test.s.*/ = new Structural/*=>advanced.Structural#*/
   val s1/*<=advanced.Test.s1.*/ = s/*=>advanced.Test.s.*/.s1/*=>advanced.Structural#s1().*/
-  val s1x/*<=advanced.Test.s1x.*/ = s/*=>advanced.Test.s.*/.s1/*=>advanced.Structural#s1().*/.x
+  val s1x/*<=advanced.Test.s1x.*/ = s/*=>advanced.Test.s.*/.s1/*=>advanced.Structural#s1().*/.x/*=>local0*/
   val s2/*<=advanced.Test.s2.*/ = s/*=>advanced.Test.s.*/.s2/*=>advanced.Structural#s2().*/
-  val s2x/*<=advanced.Test.s2x.*/ = s/*=>advanced.Test.s.*/.s2/*=>advanced.Structural#s2().*/.x
+  val s2x/*<=advanced.Test.s2x.*/ = s/*=>advanced.Test.s.*/.s2/*=>advanced.Structural#s2().*/.x/*=>local1*/
   val s3/*<=advanced.Test.s3.*/ = s/*=>advanced.Test.s.*/.s3/*=>advanced.Structural#s3().*/
-  val s3x/*<=advanced.Test.s3x.*/ = s/*=>advanced.Test.s.*/.s3/*=>advanced.Structural#s3().*/.m(???/*=>scala.Predef.`???`().*/)
+  val s3x/*<=advanced.Test.s3x.*/ = s/*=>advanced.Test.s.*/.s3/*=>advanced.Structural#s3().*/.m/*=>local3*/(???/*=>scala.Predef.`???`().*/)
 
   val e/*<=advanced.Test.e.*/ = new Existential/*=>advanced.Existential#*/
   val e1/*<=advanced.Test.e1.*/ = e/*=>advanced.Test.e.*/.e1/*=>advanced.Existential#e1().*/
diff --git a/tests/jvm/src/test/resources/example/StructuralTypes.scala b/tests/jvm/src/test/resources/example/StructuralTypes.scala
new file mode 100644
index 0000000000..9b9018c2e6
--- /dev/null
+++ b/tests/jvm/src/test/resources/example/StructuralTypes.scala
@@ -0,0 +1,10 @@
+package example
+
+import scala.languageFeature/*=>scala.languageFeature.*/.reflectiveCalls/*=>scala.languageFeature.reflectiveCalls.*//*=>scala.languageFeature.reflectiveCalls#*/
+
+object StructuralTypes/*<=example.StructuralTypes.*/ {
+  val a/*<=example.StructuralTypes.a.*/ = new /*<=local0*/{
+    val b/*<=local1*/ = 1
+  }
+  a/*=>example.StructuralTypes.a.*/.b/*=>local1*/
+}
diff --git a/tests/jvm/src/test/resources/example/StructuralTypes2.scala b/tests/jvm/src/test/resources/example/StructuralTypes2.scala
new file mode 100644
index 0000000000..54aec4d6a3
--- /dev/null
+++ b/tests/jvm/src/test/resources/example/StructuralTypes2.scala
@@ -0,0 +1,7 @@
+package example
+
+import scala.languageFeature/*=>scala.languageFeature.*/.reflectiveCalls/*=>scala.languageFeature.reflectiveCalls.*//*=>scala.languageFeature.reflectiveCalls#*/
+
+object StructuralTypes2/*<=example.StructuralTypes2.*/ {
+  StructuralTypes/*=>example.StructuralTypes.*/.a/*=>example.StructuralTypes.a.*/.b
+}
diff --git a/tests/jvm/src/test/resources/example/Types.scala b/tests/jvm/src/test/resources/example/Types.scala
index 9c1d329f67..b6f96ce25e 100644
--- a/tests/jvm/src/test/resources/example/Types.scala
+++ b/tests/jvm/src/test/resources/example/Types.scala
@@ -68,7 +68,7 @@ object Test/*<=types.Test.*/ {
     val existentialType4/*<=types.Test.C#existentialType4.*/ = Class/*=>java.lang.Class#*/.forName/*=>java.lang.Class#forName().*/("foo.Bar")
 
     def typeLambda1/*<=types.Test.C#typeLambda1().*/[M/*<=types.Test.C#typeLambda1().[M]*/[_]] = ???/*=>scala.Predef.`???`().*/
-    typeLambda1/*=>types.Test.C#typeLambda1().*/[({ type L/*<=local11*/[T/*<=local12*/] = List/*=>scala.package.List#*/[T/*=>local12*/] })#L]
+    typeLambda1/*=>types.Test.C#typeLambda1().*/[({ type L/*<=local11*/[T/*<=local12*/] = List/*=>scala.package.List#*/[T/*=>local12*/] })#L/*=>local11*/]
 
     object ClassInfoType1/*<=types.Test.C#ClassInfoType1.*/
     class ClassInfoType2/*<=types.Test.C#ClassInfoType2#*/ extends B/*=>types.B#*/ { def x/*<=types.Test.C#ClassInfoType2#x().*/ = 42 }
diff --git a/tests/jvm/src/test/resources/metac-metacp.diff b/tests/jvm/src/test/resources/metac-metacp.diff
index ee0a181243..7aac491a3f 100644
--- a/tests/jvm/src/test/resources/metac-metacp.diff
+++ b/tests/jvm/src/test/resources/metac-metacp.diff
@@ -991,6 +991,20 @@ example/NamedArguments#User.toString().
 -overridden_symbols: "scala/Any#toString()."
 
 
+==========================
+example/StructuralTypes.a.
+==========================
+--- metac
++++ metacp
+         declarations {
+           hardlinks {
+-            symbol: "local1"
++            symbol: "local0"
+             kind: METHOD
+             properties: 1024
+             display_name: "b"
+
+
 ===================================
 example/Synthetic#s.Bar#canEqual().
 ===================================
diff --git a/tests/jvm/src/test/resources/metac.expect b/tests/jvm/src/test/resources/metac.expect
index 9d3d4fb8ec..fd65a28ff9 100644
--- a/tests/jvm/src/test/resources/metac.expect
+++ b/tests/jvm/src/test/resources/metac.expect
@@ -60,7 +60,7 @@ Uri => semanticdb/integration/src/main/scala/example/Advanced.scala
 Text => non-empty
 Language => Scala
 Symbols => 44 entries
-Occurrences => 110 entries
+Occurrences => 113 entries
 Synthetics => 1 entries
 
 Symbols:
@@ -243,18 +243,21 @@ Occurrences:
 [27:6..27:9): s1x <= advanced/Test.s1x.
 [27:12..27:13): s => advanced/Test.s.
 [27:14..27:16): s1 => advanced/Structural#s1().
+[27:17..27:18): x => local0
 [28:6..28:8): s2 <= advanced/Test.s2.
 [28:11..28:12): s => advanced/Test.s.
 [28:13..28:15): s2 => advanced/Structural#s2().
 [29:6..29:9): s2x <= advanced/Test.s2x.
 [29:12..29:13): s => advanced/Test.s.
 [29:14..29:16): s2 => advanced/Structural#s2().
+[29:17..29:18): x => local1
 [30:6..30:8): s3 <= advanced/Test.s3.
 [30:11..30:12): s => advanced/Test.s.
 [30:13..30:15): s3 => advanced/Structural#s3().
 [31:6..31:9): s3x <= advanced/Test.s3x.
 [31:12..31:13): s => advanced/Test.s.
 [31:14..31:16): s3 => advanced/Structural#s3().
+[31:17..31:18): m => local3
 [31:19..31:22): ??? => scala/Predef.`???`().
 [33:6..33:7): e <= advanced/Test.e.
 [33:14..33:25): Existential => advanced/Existential#
@@ -3606,6 +3609,108 @@ Occurrences:
 [16:18..16:18):  <= selfs/C7#`<init>`().
 [16:23..16:24): B => selfs/B#
 
+semanticdb/integration/src/main/scala/example/StructuralTypes.scala
+-------------------------------------------------------------------
+
+Summary:
+Schema => SemanticDB v4
+Uri => semanticdb/integration/src/main/scala/example/StructuralTypes.scala
+Text => non-empty
+Language => Scala
+Symbols => 4 entries
+Occurrences => 11 entries
+Diagnostics => 2 entries
+
+Symbols:
+example/StructuralTypes. => final object StructuralTypes extends AnyRef { +1 decls }
+  AnyRef => scala/AnyRef#
+example/StructuralTypes.a. => val method a: AnyRef { val def b: Int }
+  AnyRef => scala/AnyRef#
+  b => local1
+  Int => scala/Int#
+local0 => final class $anon extends AnyRef { +1 decls }
+  AnyRef => scala/AnyRef#
+local1 => val method b: Int
+  Int => scala/Int#
+
+Occurrences:
+[0:8..0:15): example <= example/
+[2:7..2:12): scala => scala/
+[2:13..2:28): languageFeature => scala/languageFeature.
+[2:29..2:44): reflectiveCalls => scala/languageFeature.reflectiveCalls#
+[2:29..2:44): reflectiveCalls => scala/languageFeature.reflectiveCalls.
+[4:7..4:22): StructuralTypes <= example/StructuralTypes.
+[5:6..5:7): a <= example/StructuralTypes.a.
+[5:14..5:14):  <= local0
+[6:8..6:9): b <= local1
+[8:2..8:3): a => example/StructuralTypes.a.
+[8:4..8:5): b => local1
+
+Diagnostics:
+[2:29..2:44) [warning] Unused import
+[8:2..8:5) [warning] reflective access of structural type member value b should be enabled
+by making the implicit value scala.language.reflectiveCalls visible.
+----
+This can be achieved by adding the import clause 'import scala.language.reflectiveCalls'
+or by setting the compiler option -language:reflectiveCalls.
+See the Scaladoc for value scala.language.reflectiveCalls for a discussion
+why the feature should be explicitly enabled.
+semanticdb/integration/src/main/scala/example/StructuralTypes.scala
+-------------------------------------------------------------------
+
+Summary:
+Schema => SemanticDB v4
+Uri => semanticdb/integration/src/main/scala/example/StructuralTypes.scala
+Text => empty
+Language => Scala
+Diagnostics => 1 entries
+
+Diagnostics:
+[8:2..8:5) [warning] a pure expression does nothing in statement position; multiline expressions may require enclosing parentheses
+
+semanticdb/integration/src/main/scala/example/StructuralTypes2.scala
+--------------------------------------------------------------------
+
+Summary:
+Schema => SemanticDB v4
+Uri => semanticdb/integration/src/main/scala/example/StructuralTypes2.scala
+Text => non-empty
+Language => Scala
+Symbols => 1 entries
+Occurrences => 8 entries
+Diagnostics => 2 entries
+
+Symbols:
+example/StructuralTypes2. => final object StructuralTypes2 extends AnyRef
+  AnyRef => scala/AnyRef#
+
+Occurrences:
+[0:8..0:15): example <= example/
+[2:7..2:12): scala => scala/
+[2:13..2:28): languageFeature => scala/languageFeature.
+[2:29..2:44): reflectiveCalls => scala/languageFeature.reflectiveCalls#
+[2:29..2:44): reflectiveCalls => scala/languageFeature.reflectiveCalls.
+[4:7..4:23): StructuralTypes2 <= example/StructuralTypes2.
+[5:2..5:17): StructuralTypes => example/StructuralTypes.
+[5:18..5:19): a => example/StructuralTypes.a.
+
+Diagnostics:
+[2:29..2:44) [warning] Unused import
+[5:2..5:21) [warning] reflective access of structural type member value b should be enabled
+by making the implicit value scala.language.reflectiveCalls visible.
+semanticdb/integration/src/main/scala/example/StructuralTypes2.scala
+--------------------------------------------------------------------
+
+Summary:
+Schema => SemanticDB v4
+Uri => semanticdb/integration/src/main/scala/example/StructuralTypes2.scala
+Text => empty
+Language => Scala
+Diagnostics => 1 entries
+
+Diagnostics:
+[5:2..5:21) [warning] a pure expression does nothing in statement position; multiline expressions may require enclosing parentheses
+
 semanticdb/integration/src/main/scala/example/Synthetic.scala
 -------------------------------------------------------------
 
@@ -4078,7 +4183,7 @@ Uri => semanticdb/integration/src/main/scala/example/Types.scala
 Text => non-empty
 Language => Scala
 Symbols => 143 entries
-Occurrences => 262 entries
+Occurrences => 263 entries
 
 Symbols:
 local0 => abstract method k: Int
@@ -4577,6 +4682,7 @@ Occurrences:
 [70:26..70:27): T <= local12
 [70:31..70:35): List => scala/package.List#
 [70:36..70:37): T => local12
+[70:42..70:43): L => local11
 [72:11..72:25): ClassInfoType1 <= types/Test.C#ClassInfoType1.
 [73:10..73:24): ClassInfoType2 <= types/Test.C#ClassInfoType2#
 [73:25..73:25):  <= types/Test.C#ClassInfoType2#`<init>`().
diff --git a/tests/jvm/src/test/resources/metacp.expect b/tests/jvm/src/test/resources/metacp.expect
index b25a0d2609..d81a3a63ad 100644
--- a/tests/jvm/src/test/resources/metacp.expect
+++ b/tests/jvm/src/test/resources/metacp.expect
@@ -2607,6 +2607,38 @@ example/NamedArguments#User.unapply().(x$0) => param x$0: User
   User => example/NamedArguments#User#
 example/NamedArguments#`<init>`(). => primary ctor <init>()
 
+example/StructuralTypes.class
+-----------------------------
+
+Summary:
+Schema => SemanticDB v4
+Uri => example/StructuralTypes.class
+Text => empty
+Language => Scala
+Symbols => 2 entries
+
+Symbols:
+example/StructuralTypes. => final object StructuralTypes extends AnyRef { +1 decls }
+  AnyRef => scala/AnyRef#
+example/StructuralTypes.a. => val method a: AnyRef { val def b: Int }
+  AnyRef => scala/AnyRef#
+  b => local0
+  Int => scala/Int#
+
+example/StructuralTypes2.class
+------------------------------
+
+Summary:
+Schema => SemanticDB v4
+Uri => example/StructuralTypes2.class
+Text => empty
+Language => Scala
+Symbols => 1 entries
+
+Symbols:
+example/StructuralTypes2. => final object StructuralTypes2 extends AnyRef
+  AnyRef => scala/AnyRef#
+
 example/SymbolTest.class
 ------------------------
 
diff --git a/tests/jvm/src/test/scala-2.12/scala/meta/tests/semanticdb/ExpectSuite.scala b/tests/jvm/src/test/scala-2.12/scala/meta/tests/semanticdb/ExpectSuite.scala
index 6b5b6ae450..358c68c12d 100644
--- a/tests/jvm/src/test/scala-2.12/scala/meta/tests/semanticdb/ExpectSuite.scala
+++ b/tests/jvm/src/test/scala-2.12/scala/meta/tests/semanticdb/ExpectSuite.scala
@@ -22,7 +22,7 @@ import scala.meta.tests.metacp.Library
 import scala.meta.tests.metacp.MetacpOps
 
 class ExpectSuite extends FunSuite {
-  ScalaVersion.doIf212("ExpectSuite") {
+  ScalaVersion.doIfLatest212("ExpectSuite") {
     test("scalalib.expect") {
       import ScalalibExpect._
       this.assertNoDiff(loadObtained, loadExpected)
diff --git a/tests/jvm/src/test/scala/scala/meta/tests/semanticdb/ScalaVersion.scala b/tests/jvm/src/test/scala/scala/meta/tests/semanticdb/ScalaVersion.scala
index 4f75df76da..c5ebc0990c 100644
--- a/tests/jvm/src/test/scala/scala/meta/tests/semanticdb/ScalaVersion.scala
+++ b/tests/jvm/src/test/scala/scala/meta/tests/semanticdb/ScalaVersion.scala
@@ -1,5 +1,6 @@
 package scala.meta.tests.semanticdb
 
+import scala.meta.tests.BuildInfo
 import scala.util.Properties
 
 object ScalaVersion {
@@ -9,10 +10,22 @@ object ScalaVersion {
   // For the sake of simplicity, we only run the expect test against the
   // output of 2.12. It's possible to add another expect file for 2.11
   // later down the road if that turns out to be useful.
+  def isLatest212: Boolean = {
+    version == BuildInfo.latestScala212Version
+  }
+
   def is212: Boolean = {
     version.startsWith("2.12")
   }
 
+  def doIfLatest212[T](what: String)(thunk: => T): Unit = {
+    if (isLatest212) {
+      thunk
+    } else {
+      println(s"Skipping $what because scalaVersion is ${Properties.versionNumberString}, which is not the latest Scala 2.12 version ${BuildInfo.latestScala212Version}")
+    }
+  }
+
   def doIf212[T](what: String)(thunk: => T): Unit = {
     if (is212) {
       thunk