Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Synthetics section to SemanticDB #13135

Open
3 of 11 tasks
tanishiking opened this issue Jul 23, 2021 · 4 comments
Open
3 of 11 tasks

Add Synthetics section to SemanticDB #13135

tanishiking opened this issue Jul 23, 2021 · 4 comments

Comments

@tanishiking
Copy link
Member

tanishiking commented Jul 23, 2021

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?

Synthetic(
  <foo>,
  ApplyTree(
    OriginalTree(<foo>),
    List(
        IdTree(<x>),
    )
  ))
)

Anonymous context params

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

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)
Synthetic(
  <List>,
  TypeApplyTree(
    SelectTree(
      OriginalTree(<List>),
      Some(IdTree(<List.apply>))),
    List(TypeRef(None, <Int>, List()))
  )
)

Synthetic(
  <List>,
  TypeApplyTree(
    SelectTree(
      OriginalTree(<List>),
      Some(IdTree(<SeqFactory.unapplySeq>))),
    List(TypeRef(None, <Nothing>, 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]
Synthetic(
  <List>,
  TypeApplyTree(
    SelectTree(
      OriginalTree(<List>),
      Some(IdTree(<List.apply>))),
    List(TypeRef(None, <Int>, List()))
  )
)
Synthetic(
  <List(1).map>,
  TypeApplyTree(
    OriginalTree(<List(1).map>),
    List(
      TypeRef(None, <Int>, List()),
      TypeRef(None, <List>,
        List(TypeRef(None, <Int>, List()))))
  )
)

Synthetic(
  <#:: 2 #:: Stream.empty>,
  TypeApplyTree(
    OriginalTree(<#:: 2 #:: Stream.empty>),
    List(
      TypeRef(None, <Int>, List())))
)

Implicit parameters

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

// synthetic
Seq.apply[Int](1,2,3).sorted[Int](Ordering.Int)
Synthetic(
  <Seq.apply[Int](1,2,3).sorted[Int]>,
  ApplyTree(
    OriginalTree(<Seq.apply[Int](1,2,3).sorted[Int]),
    List(
        IdTree(<Ordering.Int>),
    )
  ))
)

implicit conversion

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

Macro Expansion

Array.empty[Int] 
Synthetic(
  <Array.empty[Int]>,
  ApplyTree(
    OriginalTree(<Array.empty[Int]>),
    List(
      MacroExpansionTree(
        IdTree(<ClassTag.Int>),
        TypeRef(None, <ClassTag>,
          List(TypeRef(None, <Int>, List())))))))

For loop desugaring

object Test {
  for {
    i <- 1 to 10
    j <- 0 until 10
    if i % 2 == 0
  } yield (i, j)
}
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
Copy link
Contributor

tgodzik commented Jul 26, 2021

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
Copy link
Member Author

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

@tanishiking
Copy link
Member Author

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

@tanishiking
Copy link
Member Author

TypeApplication part is almost ready tanishiking#5 and I'll submit a PR to lampepfl/dotty once the base branch has merged into master

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants