From 38d252eababcbaf55c56c09f5da9b05c1ef0bbd0 Mon Sep 17 00:00:00 2001 From: rochala Date: Fri, 19 Jan 2024 13:12:09 +0100 Subject: [PATCH] Make PC more resiliant to crashes --- .../tools/dotc/interactive/Completion.scala | 48 ++++++++++--------- .../pc/ConvertToNamedArgumentsProvider.scala | 2 +- .../tools/pc/ExtractMethodProvider.scala | 2 +- .../main/dotty/tools/pc/HoverProvider.scala | 4 +- .../dotty/tools/pc/InferredTypeProvider.scala | 8 ++-- .../dotty/tools/pc/MetalsInteractive.scala | 2 +- .../dotty/tools/pc/PcDefinitionProvider.scala | 2 +- .../pc/PcSyntheticDecorationProvider.scala | 10 ++-- .../tools/pc/completions/Completions.scala | 4 +- .../pc/completions/MatchCaseCompletions.scala | 8 ++-- .../pc/completions/OverrideCompletions.scala | 8 ++-- .../tools/pc/utils/MtagsEnrichments.scala | 6 +-- .../pc/tests/completion/CompletionSuite.scala | 8 ++++ .../tools/pc/tests/hover/HoverTypeSuite.scala | 26 ++++++++++ 14 files changed, 88 insertions(+), 50 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index 6e91254c2d72..c1ddfe2b04e9 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -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 = @@ -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 @@ -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. @@ -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()) @@ -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) } @@ -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. @@ -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 @@ -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) diff --git a/presentation-compiler/src/main/dotty/tools/pc/ConvertToNamedArgumentsProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/ConvertToNamedArgumentsProvider.scala index 817ab5402c00..00bfe17cb21b 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/ConvertToNamedArgumentsProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/ConvertToNamedArgumentsProvider.scala @@ -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 diff --git a/presentation-compiler/src/main/dotty/tools/pc/ExtractMethodProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/ExtractMethodProvider.scala index 0b5fd1b06a8f..55c4e4d9e4b6 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/ExtractMethodProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/ExtractMethodProvider.scala @@ -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) = diff --git a/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala index b691652508d9..a36ce83e6aa0 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala @@ -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 @@ -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 diff --git a/presentation-compiler/src/main/dotty/tools/pc/InferredTypeProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/InferredTypeProvider.scala index 69d89d5b0d13..b37b1b6dff6c 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/InferredTypeProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/InferredTypeProvider.scala @@ -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 "" } ) @@ -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 @@ -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 "" } ) @@ -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 diff --git a/presentation-compiler/src/main/dotty/tools/pc/MetalsInteractive.scala b/presentation-compiler/src/main/dotty/tools/pc/MetalsInteractive.scala index 076c1d9a9b88..0e64a6c839ab 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/MetalsInteractive.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/MetalsInteractive.scala @@ -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 diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcDefinitionProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/PcDefinitionProvider.scala index f010c8b2d95a..c4266ce5d709 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcDefinitionProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcDefinitionProvider.scala @@ -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) diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcSyntheticDecorationProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/PcSyntheticDecorationProvider.scala index db70d2a7960b..d810ce5b07cc 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcSyntheticDecorationProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcSyntheticDecorationProvider.scala @@ -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 @@ -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 = @@ -232,7 +232,7 @@ 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) && @@ -240,7 +240,7 @@ object InferredType: !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, @@ -290,4 +290,4 @@ case class Synthetics( end Synthetics object Synthetics: - def empty: Synthetics = Synthetics(Nil, Set.empty) \ No newline at end of file + def empty: Synthetics = Synthetics(Nil, Set.empty) diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala index 178d4a638745..eed3775ed8c9 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala @@ -122,7 +122,7 @@ class Completions( // should not show completions for toplevel case Nil | (_: PackageDef) :: _ 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) @@ -752,7 +752,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: diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/MatchCaseCompletions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/MatchCaseCompletions.scala index a3d5d8814c48..80b01269fb51 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/MatchCaseCompletions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/MatchCaseCompletions.scala @@ -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( @@ -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 @@ -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 diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/OverrideCompletions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/OverrideCompletions.scala index e7b1acb9aa87..8d96396999da 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/OverrideCompletions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/OverrideCompletions.scala @@ -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: ` and it overrides the parent `foo` method. - val overridables = td.tpe.parents + val overridables = td.typeOpt.parents .flatMap { parent => parent.membersBasedOnFlags( flags, @@ -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 = @@ -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 && @@ -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 diff --git a/presentation-compiler/src/main/dotty/tools/pc/utils/MtagsEnrichments.scala b/presentation-compiler/src/main/dotty/tools/pc/utils/MtagsEnrichments.scala index 581f131d6767..5317131a6c80 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/utils/MtagsEnrichments.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/utils/MtagsEnrichments.scala @@ -298,10 +298,10 @@ 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) - + def isInfix(using ctx: Context) = tree match case Select(New(_), _) => false @@ -355,7 +355,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)) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala index 5bc04e89c678..d1d382b17ca7 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala @@ -1679,3 +1679,11 @@ class CompletionSuite extends BaseCompletionSuite: |""".stripMargin, "" ) + + @Test def `dont-crash-implicit-search` = + check( + """object M: + | Array[Int].fi@@ + |""".stripMargin, + "" + ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTypeSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTypeSuite.scala index 4a5c81b74720..269dc25069a5 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTypeSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTypeSuite.scala @@ -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 + ) +