Skip to content

Commit ae580af

Browse files
committed
add proper unappply signatures for signatureHelp
1 parent e099a8b commit ae580af

File tree

2 files changed

+229
-82
lines changed

2 files changed

+229
-82
lines changed

compiler/src/dotty/tools/dotc/util/Signatures.scala

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ import ast.tpd
66
import core.Constants.Constant
77
import core.Contexts._
88
import core.Denotations.SingleDenotation
9+
import core.Flags
10+
import core.Types._
911
import util.Spans.Span
10-
import core.Types.{ErrorType, MethodType, PolyType}
1112
import reporting._
13+
import core.Names._
1214

13-
import dotty.tools.dotc.core.Types.Type
1415

1516
object Signatures {
1617

@@ -35,8 +36,7 @@ object Signatures {
3536
* @param isImplicit Is this parameter implicit?
3637
*/
3738
case class Param(name: String, tpe: String, doc: Option[String] = None, isImplicit: Boolean = false) {
38-
def show: String =
39-
s"$name: $tpe"
39+
def show: String = if name.nonEmpty then s"$name: $tpe" else tpe
4040
}
4141

4242
/**
@@ -55,14 +55,13 @@ object Signatures {
5555
}
5656

5757
enclosingApply.map {
58-
case UnApply(fun, _, patterns) => callInfo(span, patterns, fun, Signatures.countParams(fun))
58+
case UnApply(fun, _, patterns) => unapplyCallInfo(span, fun, patterns)
5959
case Apply(fun, params) => callInfo(span, params, fun, Signatures.countParams(fun))
6060
}.getOrElse((0, 0, Nil))
6161

62-
def callInfo(
63-
span: Span,
64-
params: List[Tree[Type]],
65-
fun: Tree[Type],
62+
def callInfo( span: Span,
63+
params: List[tpd.Tree],
64+
fun: tpd.Tree,
6665
alreadyAppliedCount : Int
6766
)(using Context): (Int, Int, List[SingleDenotation]) =
6867
val paramIndex = params.indexWhere(_.span.contains(span)) match {
@@ -84,10 +83,16 @@ object Signatures {
8483

8584
(paramIndex, alternativeIndex, alternatives)
8685

86+
private def unapplyCallInfo(span: Span,
87+
fun: tpd.Tree,
88+
patterns: List[tpd.Tree]
89+
)(using Context): (Int, Int, List[SingleDenotation]) =
90+
val paramIndex = patterns.indexWhere(_.span.contains(span)) max 0
91+
(paramIndex, 0, fun.symbol.asSingleDenotation.mapInfo(_ => fun.tpe) :: Nil)
92+
8793
def toSignature(denot: SingleDenotation)(using Context): Option[Signature] = {
8894
val symbol = denot.symbol
8995
val docComment = ParsedComment.docOf(symbol)
90-
val classTree = symbol.topLevelClass.asClass.rootTree
9196

9297
def toParamss(tp: MethodType)(using Context): List[List[Param]] = {
9398
val rest = tp.resType match {
@@ -113,7 +118,55 @@ object Signatures {
113118
params :: rest
114119
}
115120

121+
/**
122+
* This function is a hack which allows Signatures API to remain unchanged
123+
*
124+
* @return true if denot is "unapply", false otherwise
125+
*/
126+
def isUnapplyDenotation: Boolean = denot.name equals core.Names.termName("unapply")
127+
128+
def extractParamNamess(resultType: Type): List[List[Name]] =
129+
if resultType.resultType.widen.typeSymbol.flags.is(Flags.CaseClass) &&
130+
symbol.flags.is(Flags.Synthetic) then
131+
resultType.resultType.widen.typeSymbol.primaryConstructor.paramInfo.paramNamess
132+
else
133+
Nil
134+
135+
def extractParamTypess(resultType: Type): List[List[Type]] =
136+
resultType match {
137+
case ref: TypeRef if !ref.symbol.isPrimitiveValueClass =>
138+
ref.symbol.primaryConstructor.paramInfo.paramInfoss
139+
case AppliedType(TypeRef(_, cls), AppliedType(_, args) :: Nil)
140+
if (cls == ctx.definitions.OptionClass || cls == ctx.definitions.SomeClass) =>
141+
List(args)
142+
case AppliedType(_, args) =>
143+
List(args)
144+
case MethodTpe(_, _, resultType) =>
145+
extractParamTypess(resultType)
146+
case _ =>
147+
Nil
148+
}
149+
150+
def toUnapplyParamss(method: Type)(using Context): List[Param] = {
151+
val resultTpe = method.finalResultType.widenDealias
152+
val paramNames = extractParamNamess(resultTpe).flatten
153+
val paramTypes = extractParamTypess(resultTpe).flatten
154+
155+
if paramNames.length == paramTypes.length then
156+
(paramNames zip paramTypes).map((name, info) => Param(name.show, info.show))
157+
else
158+
paramTypes.map(info => Param("", info.show))
159+
160+
}
161+
116162
denot.info.stripPoly match {
163+
case tpe if isUnapplyDenotation =>
164+
val params = toUnapplyParamss(tpe)
165+
if params.nonEmpty then
166+
Some(Signature("", Nil, List(params), None))
167+
else
168+
None
169+
117170
case tpe: MethodType =>
118171
val paramss = toParamss(tpe)
119172
val typeParams = denot.info match {

language-server/test/dotty/tools/languageserver/SignatureHelpTest.scala

Lines changed: 166 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,172 @@ class SignatureHelpTest {
3131
.signatureHelp(m2, List(mapSig), Some(0), 0)
3232
}
3333

34+
@Test def unapplyBooleanReturn: Unit = {
35+
code"""object Even:
36+
| def unapply(s: String): Boolean = s.size % 2 == 0
37+
|
38+
|object O:
39+
| "even" match
40+
| case s @ Even(${m1}) => println(s"s has an even number of characters")
41+
| case s => println(s"s has an odd number of characters")
42+
"""
43+
44+
.signatureHelp(m1, List(), Some(0), 0)
45+
46+
}
47+
48+
@Test def unapplyCustomType: Unit = {
49+
val signature = S("", Nil, List(List(P("", "Int"))), None)
50+
51+
code"""class Nat(val x: Int):
52+
| def get: Int = x
53+
|
54+
|object Nat:
55+
| def unapply(x: Int): Nat = new Nat(x)
56+
|
57+
|object O:
58+
| 5 match
59+
| case Nat(${m1}) => println(s"n is a natural number")
60+
| case _ => ()
61+
"""
62+
63+
.signatureHelp(m1, List(signature), Some(0), 0)
64+
65+
}
66+
67+
@Test def unapplyTypeClass: Unit = {
68+
val signature = S("", Nil, List(List(P("", "Int"), P("", "String"))), None)
69+
70+
code"""class Two[A, B](a: A, b: B)
71+
|object Two {
72+
| def unapply[A, B](t: Two[A, B]): Option[(A, B)] = None
73+
|}
74+
|
75+
|object Main {
76+
| val tp = new Two(1, "")
77+
| tp match {
78+
| case Two(x$m1, $m2) =>
79+
| }
80+
|}"""
81+
.signatureHelp(m1, List(signature), Some(0), 0)
82+
.signatureHelp(m2, List(signature), Some(0), 1)
83+
84+
}
85+
86+
@Test def unapplyClass: Unit = {
87+
val signature = S("", Nil, List(List(P("", "Int"), P("", "String"))), None)
88+
89+
code"""class Two(a: Int, b: String)
90+
|object Two {
91+
| def unapply(t: Two): Option[(Int, String)] = None
92+
|}
93+
|
94+
|object Main {
95+
| val tp = new Two(1, "")
96+
| tp match {
97+
| case Two(x$m1, $m2) =>
98+
| }
99+
|}"""
100+
.signatureHelp(m1, List(signature), Some(0), 0)
101+
.signatureHelp(m2, List(signature), Some(0), 1)
102+
103+
}
104+
105+
@Test def unapplyManyType: Unit = {
106+
val signature = S("", Nil, List(List(P("", "Int"), P("", "String"))), None)
107+
108+
code"""
109+
|object Opt {
110+
| def unapply[A, B](t: Option[(A, B)]): Option[(A, B)] = None
111+
|}
112+
|
113+
|object Main {
114+
| Option((1, "foo")) match {
115+
| case Opt(x$m1, $m2) =>
116+
| }
117+
|}"""
118+
.signatureHelp(m1, List(signature), Some(0), 0)
119+
.signatureHelp(m2, List(signature), Some(0), 1)
120+
121+
}
122+
123+
@Test def unapplyTypeCaseClass: Unit = {
124+
val signature = S("", Nil, List(List(P("a", "Int"), P("b", "String"))), None)
125+
126+
code"""case class Two[A, B](a: A, b: B)
127+
|
128+
|object Main {
129+
| val tp = new Two(1, "")
130+
| tp match {
131+
| case Two(x$m1, $m2) =>
132+
| }
133+
|}"""
134+
.signatureHelp(m1, List(signature), Some(0), 0)
135+
.signatureHelp(m2, List(signature), Some(0), 1)
136+
137+
}
138+
139+
@Test def unapplyCaseClass: Unit = {
140+
val signature = S("", Nil, List(List(P("a", "Int"), P("b", "String"))), None)
141+
142+
code"""case class Two(a: Int, b: String)
143+
|
144+
|object Main {
145+
| val tp = new Two(1, "")
146+
| tp match {
147+
| case Two(x$m1, $m2) =>
148+
| }
149+
|}"""
150+
.signatureHelp(m1, List(signature), Some(0), 0)
151+
.signatureHelp(m2, List(signature), Some(0), 1)
152+
153+
}
154+
155+
@Test def unapplyOption: Unit = {
156+
val signature = S("", Nil, List(List(P("", "Int"))), None)
157+
158+
code"""|object Main {
159+
| Option(1) match {
160+
| case Some(${m1}) =>
161+
| }
162+
|}"""
163+
.signatureHelp(m1, List(signature), Some(0), 0)
164+
}
165+
166+
@Test def unapplyWithImplicits: Unit = {
167+
val signature = S("", Nil, List(List(P("", "Int"))), None)
168+
code"""|
169+
|object Opt:
170+
| def unapply[A](using String)(a: Option[A])(using Int) = a
171+
|
172+
|object Main {
173+
| given String = ""
174+
| given Int = 0
175+
| Option(1) match {
176+
| case Opt(${m1}) =>
177+
| }
178+
|}"""
179+
180+
.signatureHelp(m1, List(signature), Some(0), 0)
181+
}
182+
183+
@Test def unapplyWithMultipleImplicits: Unit = {
184+
val signature = S("", Nil, List(List(P("", "Int"))), None)
185+
code"""|
186+
|object Opt:
187+
| def unapply[A](using String)(using Int)(a: Option[A]) = a
188+
|
189+
|object Main {
190+
| given String = ""
191+
| given Int = 0
192+
| Option(1) match {
193+
| case Opt(${m1}) =>
194+
| }
195+
|}"""
196+
.signatureHelp(m1, List(signature), Some(0), 0)
197+
}
198+
199+
34200
/** Implicit parameter lists consisting solely of DummyImplicits are hidden. */
35201
@Test def hiddenDummyParams: Unit = {
36202
val foo1Sig =
@@ -362,78 +528,6 @@ class SignatureHelpTest {
362528
), None, 0)
363529
}
364530

365-
@Test def unapplyMethod: Unit = {
366-
code"""|object Main {
367-
| Option(1) match {
368-
| case Some(${m1}) =>
369-
| }
370-
|}"""
371-
372-
.signatureHelp(m1, List(
373-
S("unapply[A]", Nil, List(List(
374-
P("x$0", "Some[A]", None),
375-
)), Some("Option[A]"), None)
376-
), None, 0)
377-
}
378-
379-
@Test def unapplyMethodImplicits: Unit = {
380-
code"""|
381-
|object Opt:
382-
| def unapply[A](using String)(a: Option[A])(using Int) = a
383-
|
384-
|object Main {
385-
| given String = ""
386-
| given Int = 0
387-
| Option(1) match {
388-
| case Opt(${m1}) =>
389-
| }
390-
|}"""
391-
392-
.signatureHelp(m1, List(
393-
S("unapply[A]", Nil, List(
394-
List(
395-
P("x$1", "String", None, isImplicit = true)
396-
),
397-
List(
398-
P("a", "Option[A]", None),
399-
),
400-
List(
401-
P("x$3", "Int", None, isImplicit = true)
402-
)
403-
),
404-
Some("Option[A]"), None)
405-
), None, 1)
406-
}
407-
408-
@Test def unapplyMethodImplicitsMultiple: Unit = {
409-
code"""|
410-
|object Opt:
411-
| def unapply[A](using String)(using Int)(a: Option[A]) = a
412-
|
413-
|object Main {
414-
| given String = ""
415-
| given Int = 0
416-
| Option(1) match {
417-
| case Opt(${m1}) =>
418-
| }
419-
|}"""
420-
421-
.signatureHelp(m1, List(
422-
S("unapply[A]", Nil, List(
423-
List(
424-
P("x$1", "String", None, isImplicit = true)
425-
),
426-
List(
427-
P("x$2", "Int", None, isImplicit = true)
428-
),
429-
List(
430-
P("a", "Option[A]", None),
431-
)
432-
),
433-
Some("Option[A]"), None)
434-
), None, 2)
435-
}
436-
437531
@Test def nestedApplySignatures: Unit = {
438532
val signatures = (1 to 5).map { i =>
439533
S(s"foo$i", Nil, List(List(P("x", "Int"))), Some("Int"))

0 commit comments

Comments
 (0)