1+ package dotty .tools .pc
2+
3+
4+ import java .nio .file .Paths
5+
6+ import scala .meta .internal .metals .ReportContext
7+ import dotty .tools .pc .utils .MtagsEnrichments .*
8+ import dotty .tools .pc .printer .ShortenedTypePrinter
9+ import scala .meta .pc .SymbolSearch
10+ import scala .meta .pc .SyntheticDecoration
11+ import scala .meta .pc .SyntheticDecorationsParams
12+ import scala .meta .internal .pc .DecorationKind
13+ import scala .meta .internal .pc .Decoration
14+
15+
16+ import dotty .tools .dotc .ast .tpd
17+ import dotty .tools .dotc .ast .tpd .*
18+ import dotty .tools .dotc .core .Contexts .Context
19+ import dotty .tools .dotc .core .Flags
20+ import dotty .tools .dotc .core .StdNames .*
21+ import dotty .tools .dotc .core .Types .*
22+ import dotty .tools .dotc .interactive .Interactive
23+ import dotty .tools .dotc .interactive .InteractiveDriver
24+ import dotty .tools .dotc .util .SourceFile
25+ import dotty .tools .dotc .util .SourcePosition
26+ import dotty .tools .dotc .util .Spans .Span
27+ import dotty .tools .pc .IndexedContext
28+
29+ final class PcSyntheticDecorationsProvider (
30+ driver : InteractiveDriver ,
31+ params : SyntheticDecorationsParams ,
32+ symbolSearch : SymbolSearch ,
33+ )(using ReportContext ):
34+
35+ val uri = params.uri().nn
36+ val filePath = Paths .get(uri).nn
37+ val sourceText = params.text().nn
38+ val text = sourceText.toCharArray().nn
39+ val source =
40+ SourceFile .virtual(filePath.toString, sourceText)
41+ driver.run(uri, source)
42+ given ctx : Context = driver.currentCtx
43+ val unit = driver.currentCtx.run.nn.units.head
44+
45+ def tpdTree = unit.tpdTree
46+
47+ def provide (): List [SyntheticDecoration ] =
48+ val deepFolder = DeepFolder [Synthetics ](collectDecorations)
49+ deepFolder(Synthetics .empty, tpdTree).decorations
50+
51+ def collectDecorations (
52+ decorations : Synthetics ,
53+ tree : Tree ,
54+ ): Synthetics =
55+ tree match
56+ case ImplicitConversion (name, range) if params.implicitConversions() =>
57+ val adjusted = range.adjust(text)._1
58+ decorations
59+ .add(
60+ Decoration (
61+ adjusted.startPos.toLsp,
62+ name + " (" ,
63+ DecorationKind .ImplicitConversion ,
64+ )
65+ )
66+ .add(
67+ Decoration (
68+ adjusted.endPos.toLsp,
69+ " )" ,
70+ DecorationKind .ImplicitConversion ,
71+ )
72+ )
73+ case ImplicitParameters (names, pos, allImplicit)
74+ if params.implicitParameters() =>
75+ val label =
76+ if allImplicit then names.mkString(" (" , " , " , " )" )
77+ else names.mkString(" , " , " , " , " " )
78+ decorations.add(
79+ Decoration (
80+ pos.adjust(text)._1.toLsp,
81+ label,
82+ DecorationKind .ImplicitParameter ,
83+ )
84+ )
85+ case TypeParameters (tpes, pos, sel)
86+ if params.typeParameters() && ! syntheticTupleApply(sel) =>
87+ val label = tpes.map(toLabel(_, pos)).mkString(" [" , " , " , " ]" )
88+ decorations.add(
89+ Decoration (
90+ pos.adjust(text)._1.endPos.toLsp,
91+ label,
92+ DecorationKind .TypeParameter ,
93+ )
94+ )
95+ case InferredType (tpe, pos, defTree) if params.inferredTypes() =>
96+ val adjustedPos = pos.adjust(text)._1.endPos
97+ if decorations.containsDef(adjustedPos.start) then decorations
98+ else
99+ decorations.add(
100+ Decoration (
101+ adjustedPos.toLsp,
102+ " : " + toLabel(tpe, pos),
103+ DecorationKind .InferredType ,
104+ ),
105+ adjustedPos.start,
106+ )
107+ case _ => decorations
108+
109+ private def toLabel (
110+ tpe : Type ,
111+ pos : SourcePosition ,
112+ ): String =
113+ val tpdPath =
114+ Interactive .pathTo(unit.tpdTree, pos.span)
115+
116+ val indexedCtx = IndexedContext (Interactive .contextOfPath(tpdPath))
117+ val printer = ShortenedTypePrinter (
118+ symbolSearch
119+ )(using indexedCtx)
120+ def optDealias (tpe : Type ): Type =
121+ def isInScope (tpe : Type ): Boolean =
122+ tpe match
123+ case tref : TypeRef =>
124+ indexedCtx.lookupSym(
125+ tref.currentSymbol
126+ ) == IndexedContext .Result .InScope
127+ case AppliedType (tycon, args) =>
128+ isInScope(tycon) && args.forall(isInScope)
129+ case _ => true
130+ if isInScope(tpe)
131+ then tpe
132+ else tpe.metalsDealias(using indexedCtx.ctx)
133+
134+ val dealiased = optDealias(tpe)
135+ printer.tpe(dealiased)
136+ end toLabel
137+
138+ private val definitions = IndexedContext (ctx).ctx.definitions
139+ private def syntheticTupleApply (tree : Tree ): Boolean =
140+ tree match
141+ case sel : Select =>
142+ if definitions.isTupleNType(sel.symbol.info.finalResultType) then
143+ sel match
144+ case Select (tupleClass : Ident , _)
145+ if ! tupleClass.span.isZeroExtent &&
146+ tupleClass.span.exists &&
147+ tupleClass.name.startsWith(" Tuple" ) =>
148+ val pos = tupleClass.sourcePos
149+ ! sourceText.slice(pos.start, pos.end).mkString.startsWith(" Tuple" )
150+ case _ => true
151+ else false
152+ case _ => false
153+ end PcSyntheticDecorationsProvider
154+
155+ object ImplicitConversion :
156+ def unapply (tree : Tree )(using Context ) =
157+ tree match
158+ case Apply (fun : Ident , args) if isSynthetic(fun) =>
159+ implicitConversion(fun, args)
160+ case Apply (Select (fun, name), args)
161+ if name == nme.apply && isSynthetic(fun) =>
162+ implicitConversion(fun, args)
163+ case _ => None
164+ private def isSynthetic (tree : Tree )(using Context ) =
165+ tree.span.isSynthetic && tree.symbol.isOneOf(Flags .GivenOrImplicit )
166+
167+ private def implicitConversion (fun : Tree , args : List [Tree ])(using Context ) =
168+ val lastArgPos =
169+ args.lastOption.map(_.sourcePos).getOrElse(fun.sourcePos)
170+ Some (
171+ fun.symbol.decodedName,
172+ lastArgPos.withStart(fun.sourcePos.start),
173+ )
174+ end ImplicitConversion
175+
176+ object ImplicitParameters :
177+ def unapply (tree : Tree )(using Context ) =
178+ tree match
179+ case Apply (fun, args)
180+ if args.exists(isSyntheticArg) && ! tree.sourcePos.span.isZeroExtent =>
181+ val (implicitArgs, providedArgs) = args.partition(isSyntheticArg)
182+ val allImplicit = providedArgs.isEmpty
183+ val pos = implicitArgs.head.sourcePos
184+ Some (implicitArgs.map(_.symbol.decodedName), pos, allImplicit)
185+ case Apply (ta @ TypeApply (fun, _), _)
186+ if fun.span.isSynthetic && isValueOf(fun) =>
187+ Some (
188+ List (" new " + tpnme.valueOf.decoded.capitalize + " (...)" ),
189+ fun.sourcePos,
190+ true ,
191+ )
192+ case _ => None
193+ private def isValueOf (tree : Tree )(using Context ) =
194+ val symbol = tree.symbol.maybeOwner
195+ symbol.name.decoded == tpnme.valueOf.decoded.capitalize
196+ private def isSyntheticArg (tree : Tree )(using Context ) = tree match
197+ case tree : Ident =>
198+ tree.span.isSynthetic && tree.symbol.isOneOf(Flags .GivenOrImplicit )
199+ case _ => false
200+ end ImplicitParameters
201+
202+ object TypeParameters :
203+ def unapply (tree : Tree )(using Context ) =
204+ tree match
205+ case TypeApply (sel : Select , _) if sel.isForComprehensionMethod => None
206+ case TypeApply (fun, args) if inferredTypeArgs(args) =>
207+ val pos = fun match
208+ case sel : Select if sel.isInfix =>
209+ sel.sourcePos.withEnd(sel.nameSpan.end)
210+ case _ => fun.sourcePos
211+ val tpes = args.map(_.tpe.stripTypeVar.widen.finalResultType)
212+ Some ((tpes, pos.endPos, fun))
213+ case _ => None
214+ private def inferredTypeArgs (args : List [Tree ]): Boolean =
215+ args.forall {
216+ case tt : TypeTree if tt.span.exists && ! tt.span.isZeroExtent => true
217+ case _ => false
218+ }
219+ end TypeParameters
220+
221+ object InferredType :
222+ def unapply (tree : Tree )(using Context ) =
223+ tree match
224+ case vd @ ValDef (_, tpe, _)
225+ if isValidSpan(tpe.span, vd.nameSpan) &&
226+ ! vd.symbol.is(Flags .Enum ) =>
227+ if vd.symbol == vd.symbol.sourceSymbol then
228+ Some (tpe.tpe, tpe.sourcePos.withSpan(vd.nameSpan), vd)
229+ else None
230+ case vd @ DefDef (_, _, tpe, _)
231+ if isValidSpan(tpe.span, vd.nameSpan) &&
232+ tpe.span.start >= vd.nameSpan.end &&
233+ ! vd.symbol.isConstructor &&
234+ ! vd.symbol.is(Flags .Mutable ) =>
235+ if vd.symbol == vd.symbol.sourceSymbol then
236+ Some (tpe.tpe, tpe.sourcePos, vd)
237+ else None
238+ case bd @ Bind (
239+ name,
240+ Ident (nme.WILDCARD ),
241+ ) =>
242+ Some (bd.symbol.info, bd.namePos, bd)
243+ case _ => None
244+
245+ private def isValidSpan (tpeSpan : Span , nameSpan : Span ): Boolean =
246+ tpeSpan.isZeroExtent &&
247+ nameSpan.exists &&
248+ ! nameSpan.isZeroExtent
249+
250+ end InferredType
251+
252+ case class Synthetics (
253+ decorations : List [Decoration ],
254+ definitions : Set [Int ],
255+ ):
256+ def containsDef (offset : Int ) = definitions(offset)
257+ def add (decoration : Decoration , offset : Int ) =
258+ copy(
259+ decorations = decoration :: decorations,
260+ definitions = definitions + offset,
261+ )
262+ def add (decoration : Decoration ) =
263+ copy(decorations = decoration :: decorations)
264+
265+ object Synthetics :
266+ def empty : Synthetics = Synthetics (Nil , Set .empty)
0 commit comments