Repository: scalameta/scalameta (scala)

Issue:
Support for SAM in symbol for semanticdb
Currently, when using code such as:
```
// applies to abstract classes too
trait MyTestTrait {
  def compute(input: Int): Int
}

// MyTestObject could be a class, trait, etc.
object MyTestObject {

  val test1: MyTestTrait = (input: Int) => input + 1
  def test2: MyTestTrait = (input: Int) => input + 2

  val test3: MyTestTrait = new MyTestTrait {
    def compute(input: Int): Int = input + 3
  }
  def test4: MyTestTrait = new MyTestTrait {
    def compute(input: Int): Int = input + 4
  }

}
```

There is no new symbol entry for the anonymous class coming from SAM.

We should do it similar as we for anonymous classes.

Pull request:
Fixes #3394.

Patch:
diff --git a/semanticdb/integration/src/main/scala-2.13/example/SingleAbstractMethod.scala b/semanticdb/integration/src/main/scala-2.13/example/SingleAbstractMethod.scala
new file mode 100644
index 0000000000..89e9d7df2b
--- /dev/null
+++ b/semanticdb/integration/src/main/scala-2.13/example/SingleAbstractMethod.scala
@@ -0,0 +1,16 @@
+package example
+
+class SingleAbstractMethod {
+
+  trait SAM {
+    def foo(a: Int): Int
+  }
+  def withSAM(a: SAM) = a.foo(0)
+
+  withSAM(_ + 1)
+  withSAM((x: Int) => x - 1)
+
+  def funcSAM(y: Int) = y * 2
+  withSAM(funcSAM)
+
+}
diff --git a/semanticdb/scalac/library/src/main/scala-2.11/scala/meta/internal/semanticdb/scalac/VersionSpecificOps.scala b/semanticdb/scalac/library/src/main/scala-2.11/scala/meta/internal/semanticdb/scalac/VersionSpecificOps.scala
index f92b1d9cb2..05daa9762e 100644
--- a/semanticdb/scalac/library/src/main/scala-2.11/scala/meta/internal/semanticdb/scalac/VersionSpecificOps.scala
+++ b/semanticdb/scalac/library/src/main/scala-2.11/scala/meta/internal/semanticdb/scalac/VersionSpecificOps.scala
@@ -23,4 +23,8 @@ trait VersionSpecificOps {
         )) Some(None)
       else None
   }
+
+  // TODO: move the other specializations back to TextDocumentOps when 2.11 is retired
+  def getSyntheticSAMClass(gt: g.Function): Option[g.Symbol] = None
+
 }
diff --git a/semanticdb/scalac/library/src/main/scala-2.12/scala/meta/internal/semanticdb/scalac/VersionSpecificOps.scala b/semanticdb/scalac/library/src/main/scala-2.12/scala/meta/internal/semanticdb/scalac/VersionSpecificOps.scala
index 918a9968ec..61c0064df7 100644
--- a/semanticdb/scalac/library/src/main/scala-2.12/scala/meta/internal/semanticdb/scalac/VersionSpecificOps.scala
+++ b/semanticdb/scalac/library/src/main/scala-2.12/scala/meta/internal/semanticdb/scalac/VersionSpecificOps.scala
@@ -16,4 +16,7 @@ trait VersionSpecificOps {
       .get[g.analyzer.NamedApplyInfo].map(Some(_))
   }
 
+  def getSyntheticSAMClass(gt: g.Function): Option[g.Symbol] = gt.attachments
+    .get[global.SAMFunction].map(_.synthCls)
+
 }
diff --git a/semanticdb/scalac/library/src/main/scala-2.13/scala/meta/internal/semanticdb/scalac/VersionSpecificOps.scala b/semanticdb/scalac/library/src/main/scala-2.13/scala/meta/internal/semanticdb/scalac/VersionSpecificOps.scala
index 918a9968ec..61c0064df7 100644
--- a/semanticdb/scalac/library/src/main/scala-2.13/scala/meta/internal/semanticdb/scalac/VersionSpecificOps.scala
+++ b/semanticdb/scalac/library/src/main/scala-2.13/scala/meta/internal/semanticdb/scalac/VersionSpecificOps.scala
@@ -16,4 +16,7 @@ trait VersionSpecificOps {
       .get[g.analyzer.NamedApplyInfo].map(Some(_))
   }
 
+  def getSyntheticSAMClass(gt: g.Function): Option[g.Symbol] = gt.attachments
+    .get[global.SAMFunction].map(_.synthCls)
+
 }
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 686bfed31f..b72dc7956a 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
@@ -46,6 +46,7 @@ trait TextDocumentOps {
     def toTextDocument(explicitDialect: Option[m.Dialect]): s.TextDocument = {
       pointsCache.clear()
       val occurrences = emptyOccurrenceMap()
+      val samoccurrences = emptyOccurrenceMap()
       val symbols = mutable.Map[String, s.SymbolInformation]()
       val synthetics = mutable.ListBuffer[s.Synthetic]()
       // macro expandees can have cycles, keep tracks of visited nodes.
@@ -63,6 +64,7 @@ trait TextDocumentOps {
       val mctordefs = mutable.Map[Int, m.Name]() // start offset of ctor -> ctor's anonymous name
       val mctorrefs = mutable.Map[Int, m.Name]() // start offset of new/init -> new's anonymous name
       val msupers = mutable.Map.empty[Int, m.Term.Super] // end offset
+      val mfuncs = mutable.Map.empty[Int, m.Position] // start offset of a function
 
       // Occurrences for names in val patterns, like `val (a, b) =`. Unlike the `occurrences` map, val pattern
       // occurrences uses "last occurrence wins" instead of "first occurrences wins" when disambiguating between
@@ -77,6 +79,27 @@ trait TextDocumentOps {
       def addOccurrence(mpos: m.Position, gsym: g.Symbol, role: Role): Unit =
         addOccurrenceFromSemantic(mpos, gsym.toSemantic, role)
 
+      def addSamOccurrence(gt: g.Function) = getSyntheticSAMClass(gt).foreach { sam =>
+        val gsym = gt.symbol
+        def atPos(mpos: m.Position): Unit = gsym.toSemantic match {
+          case Symbols.None =>
+          case ssym =>
+            samoccurrences.update(mpos, (ssym, Role.DEFINITION))
+            if (!shouldNotSaveSymbol(sam) && !shouldNotSaveSemanticSymbol(ssym))
+              saveSymbolFromSemantic(sam, ssym)
+        }
+        val gpos = gt.pos
+        if (gpos.isDefined && (gsym ne null)) {
+          val gstart = gpos.start
+          mfuncs.get(gstart).fold {
+            mstarts.get(gstart).foreach { name =>
+              val mpos = name.pos
+              if (mpos.end == gpos.end) atPos(mpos)
+            }
+          }(atPos)
+        }
+      }
+
       def addPatOccurrenceFromSemantic(mpos: m.Position, ssym: String): Unit =
         if (ssym != Symbols.None) mpatoccurrences.update(mpos, (ssym, Role.DEFINITION))
       def addPatOccurrence(mpos: m.Position, gsym: g.Symbol): Unit =
@@ -175,6 +198,8 @@ trait TextDocumentOps {
               case mtree: m.Init => indexArgNames(mtree); mctorrefs(mtree.pos.start) = mtree.name
               case _: m.Name.Anonymous | _: m.Name.Placeholder | _: m.Name.This =>
               case mtree: m.Name => indexName(mtree)
+              case mtree @ (_: m.Term.FunctionTerm | _: m.Term.AnonymousFunction) =>
+                val mpos = mtree.pos; mfuncs.update(mpos.start, mpos)
               case mtree: m.Tree.WithPats => mtree.pats match {
                   case (_: m.Pat.Var) :: Nil =>
                   case pats => indexPats(pats)
@@ -576,6 +601,7 @@ trait TextDocumentOps {
                 }
 
               case t: g.Function =>
+                addSamOccurrence(t)
                 t.vparams.foreach { p =>
                   if (!(p.symbol.isSynthetic || p.name.decoded.startsWith("x$"))) traverse(p)
                 }
@@ -665,7 +691,7 @@ trait TextDocumentOps {
       val finalOccurrences = {
         val buf = List.newBuilder[s.SymbolOccurrence]
         for {
-          map <- Iterator(occurrences, mpatoccurrences)
+          map <- Iterator(occurrences, mpatoccurrences, samoccurrences)
           (pos, (sym, role)) <- map
           flatSym <- sym.asMulti
         } buf += s.SymbolOccurrence(Some(pos.toRange), flatSym, role)


Test patch:
diff --git a/tests-semanticdb/src/test/resources/example/SingleAbstractMethod.scala b/tests-semanticdb/src/test/resources/example/SingleAbstractMethod.scala
new file mode 100644
index 0000000000..cd52b736de
--- /dev/null
+++ b/tests-semanticdb/src/test/resources/example/SingleAbstractMethod.scala
@@ -0,0 +1,16 @@
+package example
+
+class SingleAbstractMethod/*<=example.SingleAbstractMethod#*/ {
+
+  trait SAM/*<=example.SingleAbstractMethod#SAM#*/ {
+    def foo/*<=example.SingleAbstractMethod#SAM#foo().*/(a/*<=example.SingleAbstractMethod#SAM#foo().(a)*/: Int/*=>scala.Int#*/): Int/*=>scala.Int#*/
+  }
+  def withSAM/*<=example.SingleAbstractMethod#withSAM().*/(a/*<=example.SingleAbstractMethod#withSAM().(a)*/: SAM/*=>example.SingleAbstractMethod#SAM#*/) = a/*=>example.SingleAbstractMethod#withSAM().(a)*/.foo/*=>example.SingleAbstractMethod#SAM#foo().*/(0)
+
+  withSAM/*=>example.SingleAbstractMethod#withSAM().*/(_ + 1/*<=local0*/)
+  withSAM/*=>example.SingleAbstractMethod#withSAM().*/((x: Int) => x - 1/*<=local2*/)
+
+  def funcSAM/*<=example.SingleAbstractMethod#funcSAM().*/(y/*<=example.SingleAbstractMethod#funcSAM().(y)*/: Int/*=>scala.Int#*/) = y/*=>example.SingleAbstractMethod#funcSAM().(y)*/ */*=>scala.Int#`*`(+3).*/ 2
+  withSAM/*=>example.SingleAbstractMethod#withSAM().*/(funcSAM/*=>example.SingleAbstractMethod#funcSAM().*//*<=local4*/)
+
+}
diff --git a/tests-semanticdb/src/test/resources/metac.expect_2.13 b/tests-semanticdb/src/test/resources/metac.expect_2.13
index b897cdfe2a..731ade3f45 100644
--- a/tests-semanticdb/src/test/resources/metac.expect_2.13
+++ b/tests-semanticdb/src/test/resources/metac.expect_2.13
@@ -82,6 +82,85 @@ Diagnostics:
 [42:4..42:8) [warning] a pure expression does nothing in statement position; multiline expressions might require enclosing parentheses
 [50:4..50:8) [warning] a pure expression does nothing in statement position; multiline expressions might require enclosing parentheses
 
+semanticdb/integration/src/main/scala-2.13/example/SingleAbstractMethod.scala
+-----------------------------------------------------------------------------
+
+Summary:
+Schema => SemanticDB v4
+Uri => semanticdb/integration/src/main/scala-2.13/example/SingleAbstractMethod.scala
+Text => non-empty
+Language => Scala
+Symbols => 14 entries
+Occurrences => 30 entries
+
+Symbols:
+example/SingleAbstractMethod# => class SingleAbstractMethod extends AnyRef { +4 decls }
+  AnyRef => scala/AnyRef#
+example/SingleAbstractMethod#SAM# => trait SAM extends AnyRef { +1 decls }
+  AnyRef => scala/AnyRef#
+example/SingleAbstractMethod#SAM#foo(). => abstract method foo(a: Int): Int
+  a => example/SingleAbstractMethod#SAM#foo().(a)
+  Int => scala/Int#
+example/SingleAbstractMethod#SAM#foo().(a) => param a: Int
+  Int => scala/Int#
+example/SingleAbstractMethod#`<init>`(). => primary ctor <init>()
+example/SingleAbstractMethod#funcSAM(). => method funcSAM(y: Int): Int
+  y => example/SingleAbstractMethod#funcSAM().(y)
+  Int => scala/Int#
+example/SingleAbstractMethod#funcSAM().(y) => param y: Int
+  Int => scala/Int#
+example/SingleAbstractMethod#withSAM(). => method withSAM(a: SAM): Int
+  a => example/SingleAbstractMethod#withSAM().(a)
+  SAM => example/SingleAbstractMethod#SAM#
+  Int => scala/Int#
+example/SingleAbstractMethod#withSAM().(a) => param a: SAM
+  SAM => example/SingleAbstractMethod#SAM#
+local0 => class $anon extends Object with SAM { +1 decls }
+  Object => java/lang/Object#
+  SAM => example/SingleAbstractMethod#SAM#
+local2 => class $anon extends Object with SAM { +1 decls }
+  Object => java/lang/Object#
+  SAM => example/SingleAbstractMethod#SAM#
+local3 => param x: Int
+  Int => scala/Int#
+local4 => class $anon extends Object with SAM { +1 decls }
+  Object => java/lang/Object#
+  SAM => example/SingleAbstractMethod#SAM#
+local5 => param y: Int
+  Int => scala/Int#
+
+Occurrences:
+[0:8..0:15): example <= example/
+[2:6..2:26): SingleAbstractMethod <= example/SingleAbstractMethod#
+[2:27..2:27):  <= example/SingleAbstractMethod#`<init>`().
+[4:8..4:11): SAM <= example/SingleAbstractMethod#SAM#
+[5:8..5:11): foo <= example/SingleAbstractMethod#SAM#foo().
+[5:12..5:13): a <= example/SingleAbstractMethod#SAM#foo().(a)
+[5:15..5:18): Int => scala/Int#
+[5:21..5:24): Int => scala/Int#
+[7:6..7:13): withSAM <= example/SingleAbstractMethod#withSAM().
+[7:14..7:15): a <= example/SingleAbstractMethod#withSAM().(a)
+[7:17..7:20): SAM => example/SingleAbstractMethod#SAM#
+[7:24..7:25): a => example/SingleAbstractMethod#withSAM().(a)
+[7:26..7:29): foo => example/SingleAbstractMethod#SAM#foo().
+[9:2..9:9): withSAM => example/SingleAbstractMethod#withSAM().
+[9:10..9:15): _ + 1 <= local0
+[9:12..9:13): + => scala/Int#`+`(+4).
+[10:2..10:9): withSAM => example/SingleAbstractMethod#withSAM().
+[10:10..10:27): (x: Int) => x - 1 <= local2
+[10:11..10:12): x <= local3
+[10:14..10:17): Int => scala/Int#
+[10:22..10:23): x => local3
+[10:24..10:25): - => scala/Int#`-`(+3).
+[12:6..12:13): funcSAM <= example/SingleAbstractMethod#funcSAM().
+[12:14..12:15): y <= example/SingleAbstractMethod#funcSAM().(y)
+[12:17..12:20): Int => scala/Int#
+[12:24..12:25): y => example/SingleAbstractMethod#funcSAM().(y)
+[12:26..12:27): * => scala/Int#`*`(+3).
+[13:2..13:9): withSAM => example/SingleAbstractMethod#withSAM().
+[13:10..13:17): funcSAM => example/SingleAbstractMethod#funcSAM().
+[13:10..13:17): funcSAM <= local4
+
 semanticdb/integration/src/main/scala/example/Access.scala
 ----------------------------------------------------------
 
diff --git a/tests-semanticdb/src/test/resources/metacp.expect_2.13 b/tests-semanticdb/src/test/resources/metacp.expect_2.13
index 7deb4d4b4c..476409cf13 100644
--- a/tests-semanticdb/src/test/resources/metacp.expect_2.13
+++ b/tests-semanticdb/src/test/resources/metacp.expect_2.13
@@ -2926,6 +2926,39 @@ example/NamedArguments#User.unapply().(x$0) => param x$0: User
   User => example/NamedArguments#User#
 example/NamedArguments#`<init>`(). => primary ctor <init>()
 
+example/SingleAbstractMethod.class
+----------------------------------
+
+Summary:
+Schema => SemanticDB v4
+Uri => example/SingleAbstractMethod.class
+Text => empty
+Language => Scala
+Symbols => 9 entries
+
+Symbols:
+example/SingleAbstractMethod# => class SingleAbstractMethod extends AnyRef { +4 decls }
+  AnyRef => scala/AnyRef#
+example/SingleAbstractMethod#SAM# => trait SAM extends AnyRef { +1 decls }
+  AnyRef => scala/AnyRef#
+example/SingleAbstractMethod#SAM#foo(). => abstract method foo(a: Int): Int
+  a => example/SingleAbstractMethod#SAM#foo().(a)
+  Int => scala/Int#
+example/SingleAbstractMethod#SAM#foo().(a) => param a: Int
+  Int => scala/Int#
+example/SingleAbstractMethod#`<init>`(). => primary ctor <init>()
+example/SingleAbstractMethod#funcSAM(). => method funcSAM(y: Int): Int
+  y => example/SingleAbstractMethod#funcSAM().(y)
+  Int => scala/Int#
+example/SingleAbstractMethod#funcSAM().(y) => param y: Int
+  Int => scala/Int#
+example/SingleAbstractMethod#withSAM(). => method withSAM(a: SAM): Int
+  a => example/SingleAbstractMethod#withSAM().(a)
+  SAM => example/SingleAbstractMethod#SAM#
+  Int => scala/Int#
+example/SingleAbstractMethod#withSAM().(a) => param a: SAM
+  SAM => example/SingleAbstractMethod#SAM#
+
 example/StructuralTypes.class
 -----------------------------