Skip to content

Commit 5fc2ef9

Browse files
authored
Make PC more resiliant to crashes (#19488)
When working with code that is recovered from errors, we should use `typeOpt` instead of `tpe` as it can sometimes be null. The added test cases were the exact snippet that previously failed. I took this opportunity to change all calls to `tpe` to `typeOpt`.
2 parents 01171de + 38d252e commit 5fc2ef9

File tree

14 files changed

+87
-49
lines changed

14 files changed

+87
-49
lines changed

compiler/src/dotty/tools/dotc/interactive/Completion.scala

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -184,12 +184,12 @@ object Completion:
184184
val completions = adjustedPath match
185185
// Ignore synthetic select from `This` because in code it was `Ident`
186186
// See example in dotty.tools.languageserver.CompletionTest.syntheticThis
187-
case Select(qual @ This(_), _) :: _ if qual.span.isSynthetic => completer.scopeCompletions
188-
case Select(qual, _) :: _ if qual.tpe.hasSimpleKind => completer.selectionCompletions(qual)
189-
case Select(qual, _) :: _ => Map.empty
190-
case (tree: ImportOrExport) :: _ => completer.directMemberCompletions(tree.expr)
191-
case (_: untpd.ImportSelector) :: Import(expr, _) :: _ => completer.directMemberCompletions(expr)
192-
case _ => completer.scopeCompletions
187+
case Select(qual @ This(_), _) :: _ if qual.span.isSynthetic => completer.scopeCompletions
188+
case Select(qual, _) :: _ if qual.typeOpt.hasSimpleKind => completer.selectionCompletions(qual)
189+
case Select(qual, _) :: _ => Map.empty
190+
case (tree: ImportOrExport) :: _ => completer.directMemberCompletions(tree.expr)
191+
case (_: untpd.ImportSelector) :: Import(expr, _) :: _ => completer.directMemberCompletions(expr)
192+
case _ => completer.scopeCompletions
193193

194194
val describedCompletions = describeCompletions(completions)
195195
val backtickedCompletions =
@@ -348,7 +348,7 @@ object Completion:
348348
/** Widen only those types which are applied or are exactly nothing
349349
*/
350350
def widenQualifier(qual: Tree)(using Context): Tree =
351-
qual.tpe.widenDealias match
351+
qual.typeOpt.widenDealias match
352352
case widenedType if widenedType.isExactlyNothing => qual.withType(widenedType)
353353
case appliedType: AppliedType => qual.withType(appliedType)
354354
case _ => qual
@@ -368,10 +368,10 @@ object Completion:
368368
* These include inherited definitions but not members added by extensions or implicit conversions
369369
*/
370370
def directMemberCompletions(qual: Tree)(using Context): CompletionMap =
371-
if qual.tpe.isExactlyNothing then
371+
if qual.typeOpt.isExactlyNothing then
372372
Map.empty
373373
else
374-
accessibleMembers(qual.tpe).groupByName
374+
accessibleMembers(qual.typeOpt).groupByName
375375

376376
/** Completions introduced by imports directly in this context.
377377
* Completions from outer contexts are not included.
@@ -415,7 +415,7 @@ object Completion:
415415

416416
/** Completions from implicit conversions including old style extensions using implicit classes */
417417
private def implicitConversionMemberCompletions(qual: Tree)(using Context): CompletionMap =
418-
if qual.tpe.isExactlyNothing || qual.tpe.isNullType then
418+
if qual.typeOpt.isExactlyNothing || qual.typeOpt.isNullType then
419419
Map.empty
420420
else
421421
implicitConversionTargets(qual)(using ctx.fresh.setExploreTyperState())
@@ -432,7 +432,7 @@ object Completion:
432432
def tryApplyingReceiverToExtension(termRef: TermRef): Option[SingleDenotation] =
433433
ctx.typer.tryApplyingExtensionMethod(termRef, qual)
434434
.map { tree =>
435-
val tpe = asDefLikeType(tree.tpe.dealias)
435+
val tpe = asDefLikeType(tree.typeOpt.dealias)
436436
termRef.denot.asSingleDenotation.mapInfo(_ => tpe)
437437
}
438438

@@ -453,16 +453,16 @@ object Completion:
453453

454454
// 1. The extension method is visible under a simple name, by being defined or inherited or imported in a scope enclosing the reference.
455455
val termCompleter = new Completer(Mode.Term, prefix, pos)
456-
val extMethodsInScope = termCompleter.scopeCompletions.toList.flatMap {
457-
case (name, denots) => denots.collect { case d: SymDenotation if d.isTerm => (d.termRef, name.asTermName) }
458-
}
456+
val extMethodsInScope = termCompleter.scopeCompletions.toList.flatMap:
457+
case (name, denots) => denots.collect:
458+
case d: SymDenotation if d.isTerm && d.termRef.symbol.is(Extension) => (d.termRef, name.asTermName)
459459

460460
// 2. The extension method is a member of some given instance that is visible at the point of the reference.
461461
val givensInScope = ctx.implicits.eligible(defn.AnyType).map(_.implicitRef.underlyingRef)
462462
val extMethodsFromGivensInScope = extractMemberExtensionMethods(givensInScope)
463463

464464
// 3. The reference is of the form r.m and the extension method is defined in the implicit scope of the type of r.
465-
val implicitScopeCompanions = ctx.run.nn.implicitScope(qual.tpe).companionRefs.showAsList
465+
val implicitScopeCompanions = ctx.run.nn.implicitScope(qual.typeOpt).companionRefs.showAsList
466466
val extMethodsFromImplicitScope = extractMemberExtensionMethods(implicitScopeCompanions)
467467

468468
// 4. The reference is of the form r.m and the extension method is defined in some given instance in the implicit scope of the type of r.
@@ -472,7 +472,7 @@ object Completion:
472472
val availableExtMethods = extMethodsFromGivensInImplicitScope ++ extMethodsFromImplicitScope ++ extMethodsFromGivensInScope ++ extMethodsInScope
473473
val extMethodsWithAppliedReceiver = availableExtMethods.flatMap {
474474
case (termRef, termName) =>
475-
if termRef.symbol.is(ExtensionMethod) && !qual.tpe.isBottomType then
475+
if termRef.symbol.is(ExtensionMethod) && !qual.typeOpt.isBottomType then
476476
tryApplyingReceiverToExtension(termRef)
477477
.map(denot => termName -> denot)
478478
else None
@@ -551,21 +551,25 @@ object Completion:
551551
* @param qual The argument to which the implicit conversion should be applied.
552552
* @return The set of types after `qual` implicit conversion.
553553
*/
554-
private def implicitConversionTargets(qual: Tree)(using Context): Set[Type] = {
554+
private def implicitConversionTargets(qual: Tree)(using Context): Set[Type] =
555555
val typer = ctx.typer
556-
val conversions = new typer.ImplicitSearch(defn.AnyType, qual, pos.span).allImplicits
557-
val targets = conversions.map(_.tree.tpe)
556+
val targets = try {
557+
val conversions = new typer.ImplicitSearch(defn.AnyType, qual, pos.span).allImplicits
558+
conversions.map(_.tree.typeOpt)
559+
} catch {
560+
case _ =>
561+
interactiv.println(i"implicit conversion targets failed: ${qual.show}")
562+
Set.empty
563+
}
558564

559565
interactiv.println(i"implicit conversion targets considered: ${targets.toList}%, %")
560566
targets
561-
}
562567

563568
/** Filter for names that should appear when looking for completions. */
564-
private object completionsFilter extends NameFilter {
569+
private object completionsFilter extends NameFilter:
565570
def apply(pre: Type, name: Name)(using Context): Boolean =
566571
!name.isConstructorName && name.toTermName.info.kind == SimpleNameKind
567572
def isStable = true
568-
}
569573

570574
extension (denotations: Seq[SingleDenotation])
571575
def groupByName(using Context): CompletionMap = denotations.groupBy(_.name)

presentation-compiler/src/main/dotty/tools/pc/ConvertToNamedArgumentsProvider.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ final class ConvertToNamedArgumentsProvider(
3535
val tree = Interactive.pathTo(trees, pos)(using newctx).headOption
3636

3737
def paramss(fun: tpd.Tree)(using Context): List[String] =
38-
fun.tpe match
38+
fun.typeOpt match
3939
case m: MethodType => m.paramNamess.flatten.map(_.toString)
4040
case _ =>
4141
fun.symbol.rawParamss.flatten

presentation-compiler/src/main/dotty/tools/pc/ExtractMethodProvider.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ final class ExtractMethodProvider(
128128
yield
129129
val defnPos = stat.sourcePos
130130
val extractedPos = head.sourcePos.withEnd(expr.sourcePos.end)
131-
val exprType = prettyPrint(expr.tpe.widen)
131+
val exprType = prettyPrint(expr.typeOpt.widen)
132132
val name =
133133
genName(indexedCtx.scopeSymbols.map(_.decodedName).toSet, "newMethod")
134134
val (methodParams, typeParams) =

presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ object HoverProvider:
4848
val indexedContext = IndexedContext(ctx)
4949

5050
def typeFromPath(path: List[Tree]) =
51-
if path.isEmpty then NoType else path.head.tpe
51+
if path.isEmpty then NoType else path.head.typeOpt
5252

5353
val tp = typeFromPath(path)
5454
val tpw = tp.widenTermRefExpr
@@ -185,7 +185,7 @@ object HoverProvider:
185185
findRefinement(parent)
186186
case _ => None
187187

188-
val refTpe = sel.tpe.widen.metalsDealias match
188+
val refTpe = sel.typeOpt.widen.metalsDealias match
189189
case r: RefinedType => Some(r)
190190
case t: (TermRef | TypeProxy) => Some(t.termSymbol.info.metalsDealias)
191191
case _ => None

presentation-compiler/src/main/dotty/tools/pc/InferredTypeProvider.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ final class InferredTypeProvider(
138138
adjustOpt.foreach(adjust => endPos.setEnd(adjust.adjustedEndPos))
139139
new TextEdit(
140140
endPos,
141-
": " + printType(optDealias(tpt.tpe)) + {
141+
": " + printType(optDealias(tpt.typeOpt)) + {
142142
if withParens then ")" else ""
143143
}
144144
)
@@ -211,7 +211,7 @@ final class InferredTypeProvider(
211211
adjustOpt.foreach(adjust => end.setEnd(adjust.adjustedEndPos))
212212
new TextEdit(
213213
end,
214-
": " + printType(optDealias(tpt.tpe))
214+
": " + printType(optDealias(tpt.typeOpt))
215215
)
216216
end typeNameEdit
217217

@@ -241,7 +241,7 @@ final class InferredTypeProvider(
241241
def baseEdit(withParens: Boolean) =
242242
new TextEdit(
243243
bind.endPos.toLsp,
244-
": " + printType(optDealias(body.tpe)) + {
244+
": " + printType(optDealias(body.typeOpt)) + {
245245
if withParens then ")" else ""
246246
}
247247
)
@@ -274,7 +274,7 @@ final class InferredTypeProvider(
274274
case Some(i @ Ident(name)) =>
275275
val typeNameEdit = new TextEdit(
276276
i.endPos.toLsp,
277-
": " + printType(optDealias(i.tpe.widen))
277+
": " + printType(optDealias(i.typeOpt.widen))
278278
)
279279
typeNameEdit :: imports
280280

presentation-compiler/src/main/dotty/tools/pc/MetalsInteractive.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ object MetalsInteractive:
190190
*/
191191
case (tpt: TypeTree) :: parent :: _
192192
if tpt.span != parent.span && !tpt.symbol.is(Synthetic) =>
193-
List((tpt.symbol, tpt.tpe))
193+
List((tpt.symbol, tpt.typeOpt))
194194

195195
/* TypeTest class https://dotty.epfl.ch/docs/reference/other-new-features/type-test.html
196196
* compiler automatically adds unapply if possible, we need to find the type symbol

presentation-compiler/src/main/dotty/tools/pc/PcDefinitionProvider.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ class PcDefinitionProvider(
108108
case Nil =>
109109
path.headOption match
110110
case Some(value: Literal) =>
111-
definitionsForSymbol(List(value.tpe.widen.typeSymbol), pos)
111+
definitionsForSymbol(List(value.typeOpt.widen.typeSymbol), pos)
112112
case _ => DefinitionResultImpl.empty
113113
case _ =>
114114
definitionsForSymbol(typeSymbols, pos)

presentation-compiler/src/main/dotty/tools/pc/PcSyntheticDecorationProvider.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ final class PcSyntheticDecorationsProvider(
114114
): String =
115115
val tpdPath =
116116
Interactive.pathTo(unit.tpdTree, pos.span)
117-
117+
118118
val indexedCtx = IndexedContext(Interactive.contextOfPath(tpdPath))
119119
val printer = ShortenedTypePrinter(
120120
symbolSearch
@@ -210,7 +210,7 @@ object TypeParameters:
210210
case sel: Select if sel.isInfix =>
211211
sel.sourcePos.withEnd(sel.nameSpan.end)
212212
case _ => fun.sourcePos
213-
val tpes = args.map(_.tpe.stripTypeVar.widen.finalResultType)
213+
val tpes = args.map(_.typeOpt.stripTypeVar.widen.finalResultType)
214214
Some((tpes, pos.endPos, fun))
215215
case _ => None
216216
private def inferredTypeArgs(args: List[Tree]): Boolean =
@@ -232,15 +232,15 @@ object InferredType:
232232
!vd.symbol.is(Flags.Enum) &&
233233
!isValDefBind(text, vd) =>
234234
if vd.symbol == vd.symbol.sourceSymbol then
235-
Some(tpe.tpe, tpe.sourcePos.withSpan(vd.nameSpan), vd)
235+
Some(tpe.typeOpt, tpe.sourcePos.withSpan(vd.nameSpan), vd)
236236
else None
237237
case vd @ DefDef(_, _, tpe, _)
238238
if isValidSpan(tpe.span, vd.nameSpan) &&
239239
tpe.span.start >= vd.nameSpan.end &&
240240
!vd.symbol.isConstructor &&
241241
!vd.symbol.is(Flags.Mutable) =>
242242
if vd.symbol == vd.symbol.sourceSymbol then
243-
Some(tpe.tpe, tpe.sourcePos, vd)
243+
Some(tpe.typeOpt, tpe.sourcePos, vd)
244244
else None
245245
case bd @ Bind(
246246
name,
@@ -290,4 +290,4 @@ case class Synthetics(
290290
end Synthetics
291291

292292
object Synthetics:
293-
def empty: Synthetics = Synthetics(Nil, Set.empty)
293+
def empty: Synthetics = Synthetics(Nil, Set.empty)

presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ class Completions(
119119
// should not show completions for toplevel
120120
case Nil | (_: PackageDef) :: _ if pos.source.file.extension != "sc" =>
121121
(allAdvanced, SymbolSearch.Result.COMPLETE)
122-
case Select(qual, _) :: _ if qual.tpe.isErroneous =>
122+
case Select(qual, _) :: _ if qual.typeOpt.isErroneous =>
123123
(allAdvanced, SymbolSearch.Result.COMPLETE)
124124
case Select(qual, _) :: _ =>
125125
val (_, compilerCompletions) = Completion.completions(pos)
@@ -749,7 +749,7 @@ class Completions(
749749
items
750750

751751
def forSelect(sel: Select): CompletionApplication =
752-
val tpe = sel.qualifier.tpe
752+
val tpe = sel.qualifier.typeOpt
753753
val members = tpe.allMembers.map(_.symbol).toSet
754754

755755
new CompletionApplication:

presentation-compiler/src/main/dotty/tools/pc/completions/MatchCaseCompletions.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ object CaseKeywordCompletion:
7474
val parents: Parents = selector match
7575
case EmptyTree =>
7676
val seenFromType = parent match
77-
case TreeApply(fun, _) if !fun.tpe.isErroneous => fun.tpe
78-
case _ => parent.tpe
77+
case TreeApply(fun, _) if !fun.typeOpt.isErroneous => fun.typeOpt
78+
case _ => parent.typeOpt
7979
seenFromType.paramInfoss match
8080
case (head :: Nil) :: _
8181
if definitions.isFunctionType(head) || head.isRef(
@@ -84,7 +84,7 @@ object CaseKeywordCompletion:
8484
val argTypes = head.argTypes.init
8585
new Parents(argTypes, definitions)
8686
case _ => new Parents(NoType, definitions)
87-
case sel => new Parents(sel.tpe, definitions)
87+
case sel => new Parents(sel.typeOpt, definitions)
8888

8989
val selectorSym = parents.selector.widen.metalsDealias.typeSymbol
9090

@@ -240,7 +240,7 @@ object CaseKeywordCompletion:
240240
completionPos,
241241
clientSupportsSnippets
242242
)
243-
val tpe = selector.tpe.widen.metalsDealias.bounds.hi match
243+
val tpe = selector.typeOpt.widen.metalsDealias.bounds.hi match
244244
case tr @ TypeRef(_, _) => tr.underlying
245245
case t => t
246246

0 commit comments

Comments
 (0)