@@ -6,11 +6,13 @@ import ast.tpd
6
6
import core .Constants .Constant
7
7
import core .Contexts ._
8
8
import core .Denotations .SingleDenotation
9
+ import core .Flags
10
+ import core .NameOps .isUnapplyName
11
+ import core .Names ._
12
+ import core .Types ._
9
13
import util .Spans .Span
10
- import core .Types .{ErrorType , MethodType , PolyType }
11
14
import reporting ._
12
15
13
- import dotty .tools .dotc .core .Types .Type
14
16
15
17
object Signatures {
16
18
@@ -35,8 +37,7 @@ object Signatures {
35
37
* @param isImplicit Is this parameter implicit?
36
38
*/
37
39
case class Param (name : String , tpe : String , doc : Option [String ] = None , isImplicit : Boolean = false ) {
38
- def show : String =
39
- s " $name: $tpe"
40
+ def show : String = if name.nonEmpty then s " $name: $tpe" else tpe
40
41
}
41
42
42
43
/**
@@ -48,21 +49,21 @@ object Signatures {
48
49
* being called, the list of overloads of this function).
49
50
*/
50
51
def callInfo (path : List [tpd.Tree ], span : Span )(using Context ): (Int , Int , List [SingleDenotation ]) =
51
- val enclosingApply = path.find {
52
- case Apply (fun, _) => ! fun.span.contains(span)
53
- case UnApply (fun, _, _) => ! fun.span.contains(span)
54
- case _ => false
55
- }
52
+ val enclosingApply = path.dropWhile {
53
+ case apply @ Apply (fun, _) => fun.span.contains(span) || apply.span.end == span.end
54
+ case unapply @ UnApply (fun, _, _) => fun.span.contains(span) || unapply.span.end == span.end || isTuple(unapply )
55
+ case _ => true
56
+ }.headOption
56
57
57
58
enclosingApply.map {
58
- case UnApply (fun, _, patterns) => callInfo (span, patterns, fun, Signatures .countParams(fun) )
59
+ case UnApply (fun, _, patterns) => unapplyCallInfo (span, fun, patterns )
59
60
case Apply (fun, params) => callInfo(span, params, fun, Signatures .countParams(fun))
60
61
}.getOrElse((0 , 0 , Nil ))
61
62
62
63
def callInfo (
63
64
span : Span ,
64
- params : List [Tree [ Type ] ],
65
- fun : Tree [ Type ] ,
65
+ params : List [tpd. Tree ],
66
+ fun : tpd. Tree ,
66
67
alreadyAppliedCount : Int
67
68
)(using Context ): (Int , Int , List [SingleDenotation ]) =
68
69
val paramIndex = params.indexWhere(_.span.contains(span)) match {
@@ -84,10 +85,67 @@ object Signatures {
84
85
85
86
(paramIndex, alternativeIndex, alternatives)
86
87
88
+ private def unapplyCallInfo (
89
+ span : Span ,
90
+ fun : tpd.Tree ,
91
+ patterns : List [tpd.Tree ]
92
+ )(using Context ): (Int , Int , List [SingleDenotation ]) =
93
+ val patternPosition = patterns.indexWhere(_.span.contains(span))
94
+ val activeParameter = extractParamTypess(fun.tpe.finalResultType.widen).headOption.map { params =>
95
+ (patternPosition, patterns.length) match
96
+ case (- 1 , 0 ) => 0 // there are no patterns yet so it must be first one
97
+ case (- 1 , pos) => - 1 // there are patterns, we must be outside range so we set no active parameter
98
+ case _ => (params.size - 1 ) min patternPosition max 0 // handle unapplySeq to always highlight Seq[A] on elements
99
+ }.getOrElse(- 1 )
100
+
101
+ val appliedDenot = fun.symbol.asSingleDenotation.mapInfo(_ => fun.tpe) :: Nil
102
+ (activeParameter, 0 , appliedDenot)
103
+
104
+ private def isTuple (tree : tpd.Tree )(using Context ): Boolean =
105
+ ctx.definitions.isTupleClass(tree.symbol.owner.companionClass)
106
+
107
+ private def extractParamTypess (resultType : Type )(using Context ): List [List [Type ]] =
108
+ resultType match {
109
+ // Reference to a type which is not a type class
110
+ case ref : TypeRef if ! ref.symbol.isPrimitiveValueClass =>
111
+ getExtractorMembers(ref)
112
+ // Option or Some applied type. There is special syntax for multiple returned arguments:
113
+ // Option[TupleN] and Option[Seq],
114
+ // We are not intrested in them, instead we extract proper type parameters from the Option type parameter.
115
+ case AppliedType (TypeRef (_, cls), (appliedType @ AppliedType (tycon, args)) :: Nil )
116
+ if (cls == ctx.definitions.OptionClass || cls == ctx.definitions.SomeClass ) =>
117
+ tycon match
118
+ case TypeRef (_, cls) if cls == ctx.definitions.SeqClass => List (List (appliedType))
119
+ case _ => List (args)
120
+ // Applied type extractor. We must extract from applied type to retain type parameters
121
+ case appliedType : AppliedType => getExtractorMembers(appliedType)
122
+ // This is necessary to extract proper result type as unapply can return other methods eg. apply
123
+ case MethodTpe (_, _, resultType) =>
124
+ extractParamTypess(resultType.widenDealias)
125
+ case _ =>
126
+ Nil
127
+ }
128
+
129
+ // Returns extractors from given type. In case if there are no extractor methods it fallbacks to get method
130
+ private def getExtractorMembers (resultType : Type )(using Context ): List [List [Type ]] =
131
+ val productAccessors = resultType.memberDenots(
132
+ underscoreMembersFilter,
133
+ (name, buf) => buf += resultType.member(name).asSingleDenotation
134
+ )
135
+ val availableExtractors = if productAccessors.isEmpty then
136
+ List (resultType.member(core.Names .termName(" get" )))
137
+ else
138
+ productAccessors
139
+ List (availableExtractors.map(_.info.finalResultType.stripAnnots).toList)
140
+
141
+ object underscoreMembersFilter extends NameFilter {
142
+ def apply (pre : Type , name : Name )(using Context ): Boolean = name.startsWith(" _" )
143
+ def isStable = true
144
+ }
145
+
87
146
def toSignature (denot : SingleDenotation )(using Context ): Option [Signature ] = {
88
147
val symbol = denot.symbol
89
148
val docComment = ParsedComment .docOf(symbol)
90
- val classTree = symbol.topLevelClass.asClass.rootTree
91
149
92
150
def toParamss (tp : MethodType )(using Context ): List [List [Param ]] = {
93
151
val rest = tp.resType match {
@@ -104,7 +162,8 @@ object Signatures {
104
162
Nil
105
163
}
106
164
val params = tp.paramNames.zip(tp.paramInfos).map { case (name, info) =>
107
- Signatures .Param (name.show,
165
+ Signatures .Param (
166
+ name.show,
108
167
info.widenTermRefExpr.show,
109
168
docComment.flatMap(_.paramDoc(name)),
110
169
isImplicit = tp.isImplicitMethod)
@@ -113,7 +172,35 @@ object Signatures {
113
172
params :: rest
114
173
}
115
174
175
+ def extractParamNamess (resultType : Type ): List [List [Name ]] =
176
+ if resultType.typeSymbol.flags.is(Flags .CaseClass ) && symbol.flags.is(Flags .Synthetic ) then
177
+ resultType.typeSymbol.primaryConstructor.paramInfo.paramNamess
178
+ else
179
+ Nil
180
+
181
+ def toUnapplyParamss (method : Type )(using Context ): List [Param ] = {
182
+ val resultType = method.finalResultType.widenDealias match
183
+ case methodType : MethodType => methodType.resultType.widen
184
+ case other => other
185
+
186
+ val paramNames = extractParamNamess(resultType).flatten
187
+ val paramTypes = extractParamTypess(resultType).flatten
188
+
189
+ if paramNames.length == paramTypes.length then
190
+ (paramNames zip paramTypes).map((name, info) => Param (name.show, info.show))
191
+ else
192
+ paramTypes.map(info => Param (" " , info.show))
193
+
194
+ }
195
+
116
196
denot.info.stripPoly match {
197
+ case tpe if denot.name.isUnapplyName =>
198
+ val params = toUnapplyParamss(tpe)
199
+ if params.nonEmpty then
200
+ Some (Signature (" " , Nil , List (params), None ))
201
+ else
202
+ None
203
+
117
204
case tpe : MethodType =>
118
205
val paramss = toParamss(tpe)
119
206
val typeParams = denot.info match {
@@ -174,7 +261,7 @@ object Signatures {
174
261
err.msg match
175
262
case msg : AmbiguousOverload => msg.alternatives
176
263
case msg : NoMatchingOverload => msg.alternatives
177
- case _ => Nil
264
+ case _ => Nil
178
265
179
266
// If the user writes `foo(bar, <cursor>)`, the typer will insert a synthetic
180
267
// `null` parameter: `foo(bar, null)`. This may influence what's the "best"
@@ -191,8 +278,7 @@ object Signatures {
191
278
alt.info.stripPoly match {
192
279
case tpe : MethodType =>
193
280
userParamsTypes.zip(tpe.paramInfos).takeWhile{ case (t0, t1) => t0 <:< t1 }.size
194
- case _ =>
195
- 0
281
+ case _ => 0
196
282
}
197
283
}
198
284
val bestAlternative =
0 commit comments