Skip to content

Commit

Permalink
Make PC more resiliant to crashes
Browse files Browse the repository at this point in the history
[Cherry-picked 38d252e]
  • Loading branch information
rochala authored and WojciechMazur committed Jun 27, 2024
1 parent 1168afb commit c4d6ab2
Show file tree
Hide file tree
Showing 14 changed files with 87 additions and 49 deletions.
48 changes: 26 additions & 22 deletions compiler/src/dotty/tools/dotc/interactive/Completion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -184,12 +184,12 @@ object Completion:
val completions = adjustedPath match
// Ignore synthetic select from `This` because in code it was `Ident`
// See example in dotty.tools.languageserver.CompletionTest.syntheticThis
case Select(qual @ This(_), _) :: _ if qual.span.isSynthetic => completer.scopeCompletions
case Select(qual, _) :: _ if qual.tpe.hasSimpleKind => completer.selectionCompletions(qual)
case Select(qual, _) :: _ => Map.empty
case (tree: ImportOrExport) :: _ => completer.directMemberCompletions(tree.expr)
case (_: untpd.ImportSelector) :: Import(expr, _) :: _ => completer.directMemberCompletions(expr)
case _ => completer.scopeCompletions
case Select(qual @ This(_), _) :: _ if qual.span.isSynthetic => completer.scopeCompletions
case Select(qual, _) :: _ if qual.typeOpt.hasSimpleKind => completer.selectionCompletions(qual)
case Select(qual, _) :: _ => Map.empty
case (tree: ImportOrExport) :: _ => completer.directMemberCompletions(tree.expr)
case (_: untpd.ImportSelector) :: Import(expr, _) :: _ => completer.directMemberCompletions(expr)
case _ => completer.scopeCompletions

val describedCompletions = describeCompletions(completions)
val backtickedCompletions =
Expand Down Expand Up @@ -348,7 +348,7 @@ object Completion:
/** Widen only those types which are applied or are exactly nothing
*/
def widenQualifier(qual: Tree)(using Context): Tree =
qual.tpe.widenDealias match
qual.typeOpt.widenDealias match
case widenedType if widenedType.isExactlyNothing => qual.withType(widenedType)
case appliedType: AppliedType => qual.withType(appliedType)
case _ => qual
Expand All @@ -368,10 +368,10 @@ object Completion:
* These include inherited definitions but not members added by extensions or implicit conversions
*/
def directMemberCompletions(qual: Tree)(using Context): CompletionMap =
if qual.tpe.isExactlyNothing then
if qual.typeOpt.isExactlyNothing then
Map.empty
else
accessibleMembers(qual.tpe).groupByName
accessibleMembers(qual.typeOpt).groupByName

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

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

Expand All @@ -453,16 +453,16 @@ object Completion:

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

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

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

// 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.
Expand All @@ -472,7 +472,7 @@ object Completion:
val availableExtMethods = extMethodsFromGivensInImplicitScope ++ extMethodsFromImplicitScope ++ extMethodsFromGivensInScope ++ extMethodsInScope
val extMethodsWithAppliedReceiver = availableExtMethods.flatMap {
case (termRef, termName) =>
if termRef.symbol.is(ExtensionMethod) && !qual.tpe.isBottomType then
if termRef.symbol.is(ExtensionMethod) && !qual.typeOpt.isBottomType then
tryApplyingReceiverToExtension(termRef)
.map(denot => termName -> denot)
else None
Expand Down Expand Up @@ -551,21 +551,25 @@ object Completion:
* @param qual The argument to which the implicit conversion should be applied.
* @return The set of types after `qual` implicit conversion.
*/
private def implicitConversionTargets(qual: Tree)(using Context): Set[Type] = {
private def implicitConversionTargets(qual: Tree)(using Context): Set[Type] =
val typer = ctx.typer
val conversions = new typer.ImplicitSearch(defn.AnyType, qual, pos.span).allImplicits
val targets = conversions.map(_.tree.tpe)
val targets = try {
val conversions = new typer.ImplicitSearch(defn.AnyType, qual, pos.span).allImplicits
conversions.map(_.tree.typeOpt)
} catch {
case _ =>
interactiv.println(i"implicit conversion targets failed: ${qual.show}")
Set.empty
}

interactiv.println(i"implicit conversion targets considered: ${targets.toList}%, %")
targets
}

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

extension (denotations: Seq[SingleDenotation])
def groupByName(using Context): CompletionMap = denotations.groupBy(_.name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ final class ConvertToNamedArgumentsProvider(
val tree = Interactive.pathTo(trees, pos)(using newctx).headOption

def paramss(fun: tpd.Tree)(using Context): List[String] =
fun.tpe match
fun.typeOpt match
case m: MethodType => m.paramNamess.flatten.map(_.toString)
case _ =>
fun.symbol.rawParamss.flatten
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ final class ExtractMethodProvider(
yield
val defnPos = stat.sourcePos
val extractedPos = head.sourcePos.withEnd(expr.sourcePos.end)
val exprType = prettyPrint(expr.tpe.widen)
val exprType = prettyPrint(expr.typeOpt.widen)
val name =
genName(indexedCtx.scopeSymbols.map(_.decodedName).toSet, "newMethod")
val (methodParams, typeParams) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ object HoverProvider:
val indexedContext = IndexedContext(ctx)

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

val tp = typeFromPath(path)
val tpw = tp.widenTermRefExpr
Expand Down Expand Up @@ -185,7 +185,7 @@ object HoverProvider:
findRefinement(parent)
case _ => None

val refTpe = sel.tpe.widen.metalsDealias match
val refTpe = sel.typeOpt.widen.metalsDealias match
case r: RefinedType => Some(r)
case t: (TermRef | TypeProxy) => Some(t.termSymbol.info.metalsDealias)
case _ => None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ final class InferredTypeProvider(
adjustOpt.foreach(adjust => endPos.setEnd(adjust.adjustedEndPos))
new TextEdit(
endPos,
": " + printType(optDealias(tpt.tpe)) + {
": " + printType(optDealias(tpt.typeOpt)) + {
if withParens then ")" else ""
}
)
Expand Down Expand Up @@ -211,7 +211,7 @@ final class InferredTypeProvider(
adjustOpt.foreach(adjust => end.setEnd(adjust.adjustedEndPos))
new TextEdit(
end,
": " + printType(optDealias(tpt.tpe))
": " + printType(optDealias(tpt.typeOpt))
)
end typeNameEdit

Expand Down Expand Up @@ -241,7 +241,7 @@ final class InferredTypeProvider(
def baseEdit(withParens: Boolean) =
new TextEdit(
bind.endPos.toLsp,
": " + printType(optDealias(body.tpe)) + {
": " + printType(optDealias(body.typeOpt)) + {
if withParens then ")" else ""
}
)
Expand Down Expand Up @@ -274,7 +274,7 @@ final class InferredTypeProvider(
case Some(i @ Ident(name)) =>
val typeNameEdit = new TextEdit(
i.endPos.toLsp,
": " + printType(optDealias(i.tpe.widen))
": " + printType(optDealias(i.typeOpt.widen))
)
typeNameEdit :: imports

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ object MetalsInteractive:
*/
case (tpt: TypeTree) :: parent :: _
if tpt.span != parent.span && !tpt.symbol.is(Synthetic) =>
List((tpt.symbol, tpt.tpe))
List((tpt.symbol, tpt.typeOpt))

/* TypeTest class https://dotty.epfl.ch/docs/reference/other-new-features/type-test.html
* compiler automatically adds unapply if possible, we need to find the type symbol
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class PcDefinitionProvider(
case Nil =>
path.headOption match
case Some(value: Literal) =>
definitionsForSymbol(List(value.tpe.widen.typeSymbol), pos)
definitionsForSymbol(List(value.typeOpt.widen.typeSymbol), pos)
case _ => DefinitionResultImpl.empty
case _ =>
definitionsForSymbol(typeSymbols, pos)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ final class PcSyntheticDecorationsProvider(
): String =
val tpdPath =
Interactive.pathTo(unit.tpdTree, pos.span)

val indexedCtx = IndexedContext(Interactive.contextOfPath(tpdPath))
val printer = ShortenedTypePrinter(
symbolSearch
Expand Down Expand Up @@ -210,7 +210,7 @@ object TypeParameters:
case sel: Select if sel.isInfix =>
sel.sourcePos.withEnd(sel.nameSpan.end)
case _ => fun.sourcePos
val tpes = args.map(_.tpe.stripTypeVar.widen.finalResultType)
val tpes = args.map(_.typeOpt.stripTypeVar.widen.finalResultType)
Some((tpes, pos.endPos, fun))
case _ => None
private def inferredTypeArgs(args: List[Tree]): Boolean =
Expand All @@ -232,15 +232,15 @@ object InferredType:
!vd.symbol.is(Flags.Enum) &&
!isValDefBind(text, vd) =>
if vd.symbol == vd.symbol.sourceSymbol then
Some(tpe.tpe, tpe.sourcePos.withSpan(vd.nameSpan), vd)
Some(tpe.typeOpt, tpe.sourcePos.withSpan(vd.nameSpan), vd)
else None
case vd @ DefDef(_, _, tpe, _)
if isValidSpan(tpe.span, vd.nameSpan) &&
tpe.span.start >= vd.nameSpan.end &&
!vd.symbol.isConstructor &&
!vd.symbol.is(Flags.Mutable) =>
if vd.symbol == vd.symbol.sourceSymbol then
Some(tpe.tpe, tpe.sourcePos, vd)
Some(tpe.typeOpt, tpe.sourcePos, vd)
else None
case bd @ Bind(
name,
Expand Down Expand Up @@ -290,4 +290,4 @@ case class Synthetics(
end Synthetics

object Synthetics:
def empty: Synthetics = Synthetics(Nil, Set.empty)
def empty: Synthetics = Synthetics(Nil, Set.empty)
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ class Completions(
// should not show completions for toplevel
case Nil if pos.source.file.extension != "sc" =>
(allAdvanced, SymbolSearch.Result.COMPLETE)
case Select(qual, _) :: _ if qual.tpe.isErroneous =>
case Select(qual, _) :: _ if qual.typeOpt.isErroneous =>
(allAdvanced, SymbolSearch.Result.COMPLETE)
case Select(qual, _) :: _ =>
val (_, compilerCompletions) = Completion.completions(pos)
Expand Down Expand Up @@ -749,7 +749,7 @@ class Completions(
items

def forSelect(sel: Select): CompletionApplication =
val tpe = sel.qualifier.tpe
val tpe = sel.qualifier.typeOpt
val members = tpe.allMembers.map(_.symbol).toSet

new CompletionApplication:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ object CaseKeywordCompletion:
val parents: Parents = selector match
case EmptyTree =>
val seenFromType = parent match
case TreeApply(fun, _) if !fun.tpe.isErroneous => fun.tpe
case _ => parent.tpe
case TreeApply(fun, _) if !fun.typeOpt.isErroneous => fun.typeOpt
case _ => parent.typeOpt
seenFromType.paramInfoss match
case (head :: Nil) :: _
if definitions.isFunctionType(head) || head.isRef(
Expand All @@ -84,7 +84,7 @@ object CaseKeywordCompletion:
val argTypes = head.argTypes.init
new Parents(argTypes, definitions)
case _ => new Parents(NoType, definitions)
case sel => new Parents(sel.tpe, definitions)
case sel => new Parents(sel.typeOpt, definitions)

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

Expand Down Expand Up @@ -240,7 +240,7 @@ object CaseKeywordCompletion:
completionPos,
clientSupportsSnippets
)
val tpe = selector.tpe.widen.metalsDealias.bounds.hi match
val tpe = selector.typeOpt.widen.metalsDealias.bounds.hi match
case tr @ TypeRef(_, _) => tr.underlying
case t => t

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ object OverrideCompletions:
// not using `td.tpe.abstractTermMembers` because those members includes
// the abstract members in `td.tpe`. For example, when we type `def foo@@`,
// `td.tpe.abstractTermMembers` contains `method foo: <error>` and it overrides the parent `foo` method.
val overridables = td.tpe.parents
val overridables = td.typeOpt.parents
.flatMap { parent =>
parent.membersBasedOnFlags(
flags,
Expand Down Expand Up @@ -279,7 +279,7 @@ object OverrideCompletions:
else ""
(indent, indent, lastIndent)
end calcIndent
val abstractMembers = defn.tpe.abstractTermMembers.map(_.symbol)
val abstractMembers = defn.typeOpt.abstractTermMembers.map(_.symbol)

val caseClassOwners = Set("Product", "Equals")
val overridables =
Expand Down Expand Up @@ -307,7 +307,7 @@ object OverrideCompletions:
if edits.isEmpty then Nil
else
// A list of declarations in the class/object to implement
val decls = defn.tpe.decls.toList
val decls = defn.typeOpt.decls.toList
.filter(sym =>
!sym.isPrimaryConstructor &&
!sym.isTypeParam &&
Expand Down Expand Up @@ -418,7 +418,7 @@ object OverrideCompletions:
// `iterator` method in `new Iterable[Int] { def iterato@@ }`
// should be completed as `def iterator: Iterator[Int]` instead of `Iterator[A]`.
val seenFrom =
val memInfo = defn.tpe.memberInfo(sym.symbol)
val memInfo = defn.typeOpt.memberInfo(sym.symbol)
if memInfo.isErroneous || memInfo.finalResultType.isAny then
sym.info.widenTermRefExpr
else memInfo
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ object MtagsEnrichments extends CommonMtagsEnrichments:
def seenFrom(sym: Symbol)(using Context): (Type, Symbol) =
try
val pre = tree.qual
val denot = sym.denot.asSeenFrom(pre.tpe.widenTermRefExpr)
val denot = sym.denot.asSeenFrom(pre.typeOpt.widenTermRefExpr)
(denot.info, sym.withUpdatedTpe(denot.info))
catch case NonFatal(e) => (sym.info, sym)

Expand Down Expand Up @@ -357,7 +357,7 @@ object MtagsEnrichments extends CommonMtagsEnrichments:
case t: GenericApply
if t.fun.srcPos.span.contains(
pos.span
) && !t.tpe.isErroneous =>
) && !t.typeOpt.isErroneous =>
tryTail(tail).orElse(Some(enclosing))
case in: Inlined =>
tryTail(tail).orElse(Some(enclosing))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1679,3 +1679,11 @@ class CompletionSuite extends BaseCompletionSuite:
|""".stripMargin,
""
)

@Test def `dont-crash-implicit-search` =
check(
"""object M:
| Array[Int].fi@@
|""".stripMargin,
""
)
Original file line number Diff line number Diff line change
Expand Up @@ -368,3 +368,29 @@ class HoverTypeSuite extends BaseHoverSuite:
"""|extension (i: MyIntOut) def uneven: Boolean
|""".stripMargin.hover,
)

@Test def `recursive-enum-without-type` =
check(
"""class Wrapper(n: Int):
| extension (x: Int)
| def + (y: Int) = new Wrap@@per(x) + y
|""".stripMargin,
"""```scala
|def this(n: Int): Wrapper
|```
|""".stripMargin
)

@Test def `recursive-enum-without-type-1` =
check(
"""class Wrapper(n: Int):
| def add(x: Int): Wrapper = ???
| extension (x: Int)
| def + (y: Int) = Wrap@@per(x).add(5)
|""".stripMargin,
"""```scala
|def this(n: Int): Wrapper
|```
|""".stripMargin
)

0 comments on commit c4d6ab2

Please sign in to comment.