@@ -6,11 +6,13 @@ import ast.tpd
66import core .Constants .Constant
77import core .Contexts ._
88import core .Denotations .SingleDenotation
9+ import core .Flags
10+ import core .NameOps .isUnapplyName
11+ import core .Names ._
12+ import core .Types ._
913import util .Spans .Span
10- import core .Types .{ErrorType , MethodType , PolyType }
1114import reporting ._
1215
13- import dotty .tools .dotc .core .Types .Type
1416
1517object Signatures {
1618
@@ -35,8 +37,7 @@ object Signatures {
3537 * @param isImplicit Is this parameter implicit?
3638 */
3739 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
4041 }
4142
4243 /**
@@ -48,21 +49,21 @@ object Signatures {
4849 * being called, the list of overloads of this function).
4950 */
5051 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
5657
5758 enclosingApply.map {
58- case UnApply (fun, _, patterns) => callInfo (span, patterns, fun, Signatures .countParams(fun) )
59+ case UnApply (fun, _, patterns) => unapplyCallInfo (span, fun, patterns )
5960 case Apply (fun, params) => callInfo(span, params, fun, Signatures .countParams(fun))
6061 }.getOrElse((0 , 0 , Nil ))
6162
6263 def callInfo (
6364 span : Span ,
64- params : List [Tree [ Type ] ],
65- fun : Tree [ Type ] ,
65+ params : List [tpd. Tree ],
66+ fun : tpd. Tree ,
6667 alreadyAppliedCount : Int
6768 )(using Context ): (Int , Int , List [SingleDenotation ]) =
6869 val paramIndex = params.indexWhere(_.span.contains(span)) match {
@@ -84,10 +85,67 @@ object Signatures {
8485
8586 (paramIndex, alternativeIndex, alternatives)
8687
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+
87146 def toSignature (denot : SingleDenotation )(using Context ): Option [Signature ] = {
88147 val symbol = denot.symbol
89148 val docComment = ParsedComment .docOf(symbol)
90- val classTree = symbol.topLevelClass.asClass.rootTree
91149
92150 def toParamss (tp : MethodType )(using Context ): List [List [Param ]] = {
93151 val rest = tp.resType match {
@@ -104,7 +162,8 @@ object Signatures {
104162 Nil
105163 }
106164 val params = tp.paramNames.zip(tp.paramInfos).map { case (name, info) =>
107- Signatures .Param (name.show,
165+ Signatures .Param (
166+ name.show,
108167 info.widenTermRefExpr.show,
109168 docComment.flatMap(_.paramDoc(name)),
110169 isImplicit = tp.isImplicitMethod)
@@ -113,7 +172,35 @@ object Signatures {
113172 params :: rest
114173 }
115174
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+
116196 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+
117204 case tpe : MethodType =>
118205 val paramss = toParamss(tpe)
119206 val typeParams = denot.info match {
@@ -174,7 +261,7 @@ object Signatures {
174261 err.msg match
175262 case msg : AmbiguousOverload => msg.alternatives
176263 case msg : NoMatchingOverload => msg.alternatives
177- case _ => Nil
264+ case _ => Nil
178265
179266 // If the user writes `foo(bar, <cursor>)`, the typer will insert a synthetic
180267 // `null` parameter: `foo(bar, null)`. This may influence what's the "best"
@@ -191,8 +278,7 @@ object Signatures {
191278 alt.info.stripPoly match {
192279 case tpe : MethodType =>
193280 userParamsTypes.zip(tpe.paramInfos).takeWhile{ case (t0, t1) => t0 <:< t1 }.size
194- case _ =>
195- 0
281+ case _ => 0
196282 }
197283 }
198284 val bestAlternative =
0 commit comments