Skip to content

Commit

Permalink
Move Signatures to dotty.tools.dotc.util
Browse files Browse the repository at this point in the history
And address review comments.
  • Loading branch information
Duhemm committed Nov 13, 2018
1 parent 604c067 commit 25d436b
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 97 deletions.
@@ -1,25 +1,59 @@
package dotty.tools.languageserver
package dotty.tools.dotc.util

import dotty.tools.dotc.ast.tpd._
import dotty.tools.dotc.ast.Trees._
import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.core.Constants.Constant
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.Denotations.SingleDenotation
import dotty.tools.dotc.core.Flags.Implicit
import dotty.tools.dotc.core.Names.TermName
import dotty.tools.dotc.util.Positions.Position
import dotty.tools.dotc.core.Types.{ErrorType, MethodType}
import dotty.tools.dotc.core.Types.{ErrorType, MethodType, PolyType}
import dotty.tools.dotc.reporting.diagnostic.messages

import org.eclipse.lsp4j.{ParameterInformation, SignatureInformation}

import scala.collection.JavaConverters._

object Signatures {

def callInfo(path: List[Tree], pos: Position)(implicit ctx: Context): (Int, Int, List[SingleDenotation]) = {
/**
* Represent a method signature.
*
* @param name The name of the method
* @param tparams The type parameters and their bounds
* @param paramss The parameter lists of this method
* @param returnType The return type of this method, if this is not a constructor.
* @param doc The documentation for this method.
*/
case class Signature(name: String, tparams: List[String], paramss: List[List[Param]], returnType: Option[String], doc: Option[String] = None) {
}

/**
* Represent a method's parameter.
*
* @param name The name of the parameter
* @param tpe The type of the parameter
* @param doc The documentation of this parameter
* @param isImplicit Is this parameter implicit?
*/
case class Param(name: String, tpe: String, doc: Option[String] = None, isImplicit: Boolean = false) {
def show: String =
s"$name: $tpe"
}

/**
* Extract (current parameter index, function index, functions) out of a method call.
*
* @param path The path to the function application
* @param pos The position of the cursor
* @return A triple containing the index of the parameter being edited, the index of the function
* being called, the list of overloads of this function).
*/
def callInfo(path: List[tpd.Tree], pos: Position)(implicit ctx: Context): (Int, Int, List[SingleDenotation]) = {
path match {
case Apply(fun, params) :: _ =>
val alreadyAppliedCount = Signatures.countParams(fun)
val paramIndex = params.indexWhere(_.pos.contains(pos)) match {
case -1 => ((params.length - 1) max 0) + alreadyAppliedCount
case -1 => (params.length - 1 max 0) + alreadyAppliedCount
case n => n + alreadyAppliedCount
}

Expand All @@ -42,6 +76,56 @@ object Signatures {
}
}

def toSignature(denot: SingleDenotation)(implicit ctx: Context): Option[Signature] = {
val symbol = denot.symbol
val docComment = ParsedComment.docOf(symbol)
val classTree = symbol.topLevelClass.asClass.rootTree
val isImplicit: TermName => Boolean = tpd.defPath(symbol, classTree).lastOption match {
case Some(DefDef(_, _, paramss, _, _)) =>
val flatParams = paramss.flatten
name => flatParams.find(_.name == name).map(_.symbol.is(Implicit)).getOrElse(false)
case _ =>
_ => false
}

denot.info.stripPoly match {
case tpe: MethodType =>
val infos = {
tpe.paramInfoss.zip(tpe.paramNamess).map { case (infos, names) =>
infos.zip(names).map { case (info, name) =>
Signatures.Param(name.show,
info.widenTermRefExpr.show,
docComment.flatMap(_.paramDoc(name)),
isImplicit = isImplicit(name))
}
}
}

val typeParams = denot.info match {
case poly: PolyType =>
poly.paramNames.zip(poly.paramInfos).map { case (x, y) => x.show + y.show }
case _ =>
Nil
}

val (name, returnType) =
if (symbol.isConstructor) (symbol.owner.name.show, None)
else (denot.name.show, Some(tpe.finalResultType.widenTermRefExpr.show))

val signature =
Signatures.Signature(name,
typeParams,
infos,
returnType,
docComment.map(_.mainDoc))

Some(signature)

case other =>
None
}
}

/**
* The number of parameters that are applied in `tree`.
*
Expand All @@ -51,7 +135,7 @@ object Signatures {
* @param tree The tree to inspect.
* @return The number of parameters that are passed.
*/
private def countParams(tree: Tree): Int = {
private def countParams(tree: tpd.Tree): Int = {
tree match {
case Apply(fun, params) => countParams(fun) + params.length
case _ => 0
Expand All @@ -71,7 +155,7 @@ object Signatures {
* @return A pair composed of the index of the best alternative (0 if no alternatives
* were found), and the list of alternatives.
*/
private def alternativesFromError(err: ErrorType, params: List[Tree])(implicit ctx: Context): (Int, List[SingleDenotation]) = {
private def alternativesFromError(err: ErrorType, params: List[tpd.Tree])(implicit ctx: Context): (Int, List[SingleDenotation]) = {
val alternatives =
err.msg match {
case messages.AmbiguousOverload(_, alternatives, _) =>
Expand All @@ -96,7 +180,7 @@ object Signatures {
val alternativesScores = alternatives.map { alt =>
alt.info.stripPoly match {
case tpe: MethodType =>
userParamsTypes.zip(tpe.paramInfos).takeWhile(_ <:< _).size
userParamsTypes.zip(tpe.paramInfos).takeWhile{ case (t0, t1) => t0 <:< t1 }.size
case _ =>
0
}
Expand All @@ -108,36 +192,5 @@ object Signatures {
(bestAlternative, alternatives)
}

case class Signature(name: String, tparams: List[String], paramss: List[List[Param]], returnType: Option[String], doc: Option[String] = None) {
def toSignatureInformation: SignatureInformation = {
val paramInfoss = paramss.map(_.map(_.toParameterInformation))
val paramLists = paramss.map { paramList =>
val labels = paramList.map(_.show)
val prefix = if (paramList.exists(_.isImplicit)) "implicit " else ""
labels.mkString(prefix, ", ", "")
}.mkString("(", ")(", ")")
val tparamsLabel = if (tparams.isEmpty) "" else tparams.mkString("[", ", ", "]")
val returnTypeLabel = returnType.map(t => s": $t").getOrElse("")
val label = s"$name$tparamsLabel$paramLists$returnTypeLabel"
val documentation = doc.map(DottyLanguageServer.hoverContent)
val signature = new SignatureInformation(label)
signature.setParameters(paramInfoss.flatten.asJava)
documentation.foreach(signature.setDocumentation(_))
signature
}
}

case class Param(name: String, tpe: String, doc: Option[String] = None, isImplicit: Boolean = false) {

def toParameterInformation: ParameterInformation = {
val label = s"$name: $tpe"
val documentation = doc.map(DottyLanguageServer.hoverContent)
val info = new ParameterInformation(label)
documentation.foreach(info.setDocumentation(_))
info
}

def show: String =
s"$name: $tpe"
}
}

Expand Up @@ -470,58 +470,9 @@ class DottyLanguageServer extends LanguageServer
val path = Interactive.pathTo(trees, pos).dropWhile(!_.isInstanceOf[Apply])

val (paramN, callableN, alternatives) = Signatures.callInfo(path, pos.pos)
val signatureInfos = alternatives.flatMap(Signatures.toSignature)

val signatureInfos = alternatives.flatMap { denot =>
val symbol = denot.symbol
val docComment = ParsedComment.docOf(symbol)
val classTree = symbol.topLevelClass.asClass.rootTree
val isImplicit: TermName => Boolean = tpd.defPath(symbol, classTree).lastOption match {
case Some(DefDef(_, _, paramss, _, _)) =>
val flatParams = paramss.flatten
name => flatParams.find(_.name == name).map(_.symbol.is(Implicit)).getOrElse(false)
case _ =>
_ => false
}

denot.info.stripPoly match {
case tpe: MethodType =>
val infos = {
tpe.paramInfoss.zip(tpe.paramNamess).map { (infos, names) =>
infos.zip(names).map { (info, name) =>
Signatures.Param(name.show,
info.widenTermRefExpr.show,
docComment.flatMap(_.paramDoc(name)),
isImplicit = isImplicit(name))
}
}
}

val typeParams = denot.info match {
case poly: PolyType =>
poly.paramNames.zip(poly.paramInfos).map((x, y) => x.show + y.show)
case _ =>
Nil
}

val (name, returnType) =
if (symbol.isConstructor) (symbol.owner.name.show, None)
else (denot.name.show, Some(tpe.finalResultType.widenTermRefExpr.show))

val signature =
Signatures.Signature(name,
typeParams,
infos,
returnType,
docComment.map(_.mainDoc))

Some(signature)

case other =>
None
}
}

new SignatureHelp(signatureInfos.map(_.toSignatureInformation).asJava, callableN, paramN)
new SignatureHelp(signatureInfos.map(signatureToSignatureInformation).asJava, callableN, paramN)
}

override def getTextDocumentService: TextDocumentService = this
Expand Down Expand Up @@ -830,4 +781,30 @@ object DottyLanguageServer {

location(pos, positionMapper).map(l => new lsp4j.SymbolInformation(name, symbolKind(sym), l, containerName))
}

/** Convert `signature` to a `SignatureInformation` */
def signatureToSignatureInformation(signature: Signatures.Signature): lsp4j.SignatureInformation = {
val paramInfoss = signature.paramss.map(_.map(paramToParameterInformation))
val paramLists = signature.paramss.map { paramList =>
val labels = paramList.map(_.show)
val prefix = if (paramList.exists(_.isImplicit)) "implicit " else ""
labels.mkString(prefix, ", ", "")
}.mkString("(", ")(", ")")
val tparamsLabel = if (signature.tparams.isEmpty) "" else signature.tparams.mkString("[", ", ", "]")
val returnTypeLabel = signature.returnType.map(t => s": $t").getOrElse("")
val label = s"${signature.name}$tparamsLabel$paramLists$returnTypeLabel"
val documentation = signature.doc.map(DottyLanguageServer.hoverContent)
val sig = new lsp4j.SignatureInformation(label)
sig.setParameters(paramInfoss.flatten.asJava)
documentation.foreach(sig.setDocumentation(_))
sig
}

/** Convert `param` to `ParameterInformation` */
private def paramToParameterInformation(param: Signatures.Param): lsp4j.ParameterInformation = {
val documentation = param.doc.map(DottyLanguageServer.hoverContent)
val info = new lsp4j.ParameterInformation(param.show)
documentation.foreach(info.setDocumentation(_))
info
}
}
Expand Up @@ -3,7 +3,8 @@ package dotty.tools.languageserver
import org.junit.Test

import dotty.tools.languageserver.util.Code._
import dotty.tools.languageserver.Signatures.{Param => P, Signature => S}

import dotty.tools.dotc.util.Signatures.{Param => P, Signature => S}

class SignatureHelpTest {

Expand Down
@@ -1,10 +1,12 @@
package dotty.tools.languageserver.util

import dotty.tools.languageserver.Signatures.Signature
import dotty.tools.languageserver.util.Code._
import dotty.tools.languageserver.util.actions._
import dotty.tools.languageserver.util.embedded.CodeMarker
import dotty.tools.languageserver.util.server.{TestFile, TestServer}

import dotty.tools.dotc.util.Signatures.Signature

import org.eclipse.lsp4j.{CompletionItemKind, DocumentHighlightKind}

/**
Expand Down
@@ -1,9 +1,10 @@
package dotty.tools.languageserver.util.actions

import dotty.tools.languageserver.DottyLanguageServer
import dotty.tools.languageserver.util.PositionContext
import dotty.tools.languageserver.util.embedded.CodeMarker

import dotty.tools.languageserver.Signatures.Signature
import dotty.tools.dotc.util.Signatures.Signature

import org.eclipse.lsp4j.{MarkupContent, ParameterInformation, SignatureInformation}
import org.junit.Assert.{assertEquals, assertFalse, assertTrue}
Expand All @@ -28,7 +29,7 @@ class SignatureHelp(override val marker: CodeMarker,
activeSignature: Option[Int],
activeParam: Int) extends ActionOnMarker {

val expectedSignatures = expected.map(_.toSignatureInformation)
val expectedSignatures = expected.map(DottyLanguageServer.signatureToSignatureInformation)

override def execute(): Exec[Unit] = {
val results = server.signatureHelp(marker.toTextDocumentPositionParams).get()
Expand Down

0 comments on commit 25d436b

Please sign in to comment.