scala / scala3

The Scala 3 compiler, also known as Dotty.
https://dotty.epfl.ch
Apache License 2.0
5.88k stars 1.06k forks source link

Add Synthetics section to SemanticDB #13135

Open tanishiking opened 3 years ago

tanishiking commented 3 years ago

https://scalameta.org/docs/semanticdb/specification.html#synthetic

"Synthetics" is a section of a TextDocument that stores trees added by compilers that do not appear in the original source. Examples include inferred type arguments, implicit parameters, or desugarings of for loops.

One of the main consumers of Synthetics information of SemanticDB is decraotion feature of metals, and once Scala3 extract these information, those features are available for Scala3 in Metals.

TODO

If there's something we should add to Synthetic section, please let me know :)

Using parameter application

def foo(using x: Int) = ???
def bar(using x: Int) = foo

// synthetic
def foo(using x: Int): Nothing = ???
def bar(using x: Int): Nothing = foo(x)

what should we do for anonymous context params?

def foo(using Int) = ???
def bar(using Int) = foo

// synthetic
def foo(using x$1: Int): Nothing = ???
def bar(using x$1: Int): Nothing = classes.foo(x$1)

should we add x$1 to synthetics?

```scala Synthetic( , ApplyTree( OriginalTree(), List( IdTree(), ) )) ) ```

Anonymous context params

def foo(using Int) = ???

// synthetic
def foo(using x$1: Int) = ???
```scala Synthetic( , IdTree() ) ```

Extension method application?

Wondering how should we design the Synthetics Tree for the extension method...

extension (x: Int)
  def plus(y: Int) = x + y

def main() = 1.plus(3)
// synthetic
def main() = plus(1)(3)

Maybe we don't need to extract synthetics for the extension method at this moment, because the main consumer of the synthetics section of SemancticDB is metals's Decoration feature. While extracting Implicit conversion for Scala2 is important for implicit decoration's readability, showing plus(1)(3) for the extension method doesn't help developers to read those programs.

Scala2 compatible synthetics

Inferred method application

val x = List(1)
val List(a, b) = List(1, 2)

// synthetics
val x = List.apply[Int](1)
val List.unapplySeq[Int](a, b) = List.apply[Int](1, 2)
```scala Synthetic( , TypeApplyTree( SelectTree( OriginalTree(), Some(IdTree())), List(TypeRef(None, , List())) ) ) Synthetic( , TypeApplyTree( SelectTree( OriginalTree(), Some(IdTree())), List(TypeRef(None, , List())) ) ) ```

Inferred Type Application

List(1).map(_ + 2)
1 #:: 2 #:: Stream.empty

// synthetic
List.apply[Int](1).map[Int](_ + 2)
1 #:: 2 #:: Stream.empty[Int]
```scala Synthetic( , TypeApplyTree( SelectTree( OriginalTree(), Some(IdTree())), List(TypeRef(None, , List())) ) ) Synthetic( , TypeApplyTree( OriginalTree(), List( TypeRef(None, , List()), TypeRef(None, , List(TypeRef(None, , List())))) ) ) Synthetic( <#:: 2 #:: Stream.empty>, TypeApplyTree( OriginalTree(<#:: 2 #:: Stream.empty>), List( TypeRef(None, , List()))) ) ```

Implicit parameters

Seq.apply[Int](1,2,3).sorted[Int]

// synthetic
Seq.apply[Int](1,2,3).sorted[Int](Ordering.Int)
```scala Synthetic( , ApplyTree( OriginalTree(), ) )) ) ```

implicit conversion

"fooo".stripPrefix("o")

// synthetic
augmentString("fooo").stripPrefix("o")
```scala Synthetic( <"fooo">, ApplyTree( IdTree(), List(OriginalTree(<"fooo">))) ) ```

Macro Expansion

Array.empty[Int] 
```scala Synthetic( , ApplyTree( OriginalTree(), List( MacroExpansionTree( IdTree(), TypeRef(None, , List(TypeRef(None, , List()))))))) ```

For loop desugaring

object Test {
  for {
    i <- 1 to 10
    j <- 0 until 10
    if i % 2 == 0
  } yield (i, j)
}
```scala documents { schema: SEMANTICDB4 synthetics { range { start_line: 1 start_character: 2 end_line: 5 end_character: 16 } tree { apply_tree { function { type_apply_tree { function { select_tree { qualifier { original_tree { range { start_line: 2 start_character: 9 end_line: 2 end_character: 16 } } } id { symbol: "scala/collection/StrictOptimizedIterableOps#flatMap()." } } } type_arguments { type_ref { symbol: "scala/Tuple2#" type_arguments { type_ref { symbol: "scala/Int#" } } type_arguments { type_ref { symbol: "scala/Int#" } } } } } } arguments { function_tree { parameters { symbol: "local0" } body { apply_tree { function { type_apply_tree { function { select_tree { qualifier { apply_tree { function { select_tree { qualifier { original_tree { range { start_line: 3 start_character: 9 end_line: 3 end_character: 19 } } } id { symbol: "scala/collection/IterableOps#withFilter()." } } } arguments { function_tree { parameters { symbol: "local1" } body { original_tree { range { start_line: 4 start_character: 7 end_line: 4 end_character: 17 } } } } } } } id { symbol: "scala/collection/WithFilter#map()." } } } type_arguments { type_ref { symbol: "scala/Tuple2#" type_arguments { type_ref { symbol: "scala/Int#" } } type_arguments { type_ref { symbol: "scala/Int#" } } } } } } arguments { function_tree { parameters { symbol: "local1" } body { original_tree { range { start_line: 5 start_character: 10 end_line: 5 end_character: 16 } } } } } } } } } } } } synthetics { range { start_line: 2 start_character: 9 end_line: 2 end_character: 10 } tree { apply_tree { function { id_tree { symbol: "scala/LowPriorityImplicits#intWrapper()." } } arguments { original_tree { range { start_line: 2 start_character: 9 end_line: 2 end_character: 10 } } } } } } synthetics { range { start_line: 3 start_character: 9 end_line: 3 end_character: 10 } tree { apply_tree { function { id_tree { symbol: "scala/LowPriorityImplicits#intWrapper()." } } arguments { original_tree { range { start_line: 3 start_character: 9 end_line: 3 end_character: 10 } } } } } } } ```
tgodzik commented 3 years ago

My 3 cents about the priorities in the task. I added a number for each one, but these are completely subjective and arbitrary.

Using parameter application

10

For sure that would be one of the top ones, we should also include anonymous ones, but there should be a difference in how they are represented, maybe we don't need a separate synthetic for the name then.

Implicit parameters

10

Same importance as the using parameters.

Macro Expansion

9

This would be very useful when debugging macros, but I am not sure how doable it is currently.

Inferred Type Application

7

It's pretty useful to show to users and an easy way to see what type was inferred for a particular function.

Extension method application?

6

For sure this would be useful, the same as Scala 2 implicit classes, but not the most important.

implicit conversion

6

Would be pretty good to have, but might be different thatn Scala 3 in terms of representation. We would need to take into account https://docs.scala-lang.org/scala3/reference/contextual/conversions.html

Inferred method application

5

This might be useful, especially if some has a rewrite in Scalafix, I imagine we could also show it in Metals.

For loop desugaring

2

This is not currently used anywhere and it's actually simple to translate for comprehensions manually even.

tanishiking commented 3 years ago

Thank you for the comment! I'll start working on using parameters and anonymous context params (and maybe apply implicit parameters) first :)

tanishiking commented 3 years ago

For context / implicit params, anonymous context params, and anonymous given: opened a PR https://github.com/tanishiking/dotty/pull/2 which depends on https://github.com/lampepfl/dotty/pull/12885 I'll submit a PR to dotty/dotty once https://github.com/lampepfl/dotty/pull/12885 has merged (couldn't open the PR to dotty/dotty because the original branch is in tanishiking/dotty)

tanishiking commented 3 years ago

TypeApplication part is almost ready https://github.com/tanishiking/dotty/pull/5 and I'll submit a PR to lampepfl/dotty once the base branch has merged into master