Skip to content

Commit ce1e2a2

Browse files
authored
Merge pull request #11187 from dotty-staging/code-completions-with-leading-usings-in-extensions
Make code completions and import suggestions work correctly for extensions with leading using clauses
2 parents 5eb3258 + d09fcc2 commit ce1e2a2

File tree

6 files changed

+749
-232
lines changed

6 files changed

+749
-232
lines changed

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

Lines changed: 241 additions & 210 deletions
Large diffs are not rendered by default.

compiler/src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ import Constants.{Constant, IntTag, LongTag}
3838
import Denotations.SingleDenotation
3939
import annotation.{constructorOnly, threadUnsafe}
4040

41+
import scala.util.control.NonFatal
42+
4143
object Applications {
4244
import tpd._
4345

@@ -2157,8 +2159,57 @@ trait Applications extends Compatibility {
21572159
}
21582160
}
21592161

2160-
def isApplicableExtensionMethod(ref: TermRef, receiver: Type)(using Context) =
2161-
ref.symbol.is(ExtensionMethod)
2162-
&& !receiver.isBottomType
2163-
&& isApplicableMethodRef(ref, receiver :: Nil, WildcardType)
2162+
/** Assuming methodRef is a reference to an extension method defined e.g. as
2163+
*
2164+
* extension [T1, T2](using A)(using B, C)(receiver: R)(using D)
2165+
* def foo[T3](using E)(f: F): G = ???
2166+
*
2167+
* return the tree representing methodRef partially applied to the receiver
2168+
* and all the implicit parameters preceding it (A, B, C)
2169+
* with the type parameters of the extension (T1, T2) inferred.
2170+
* None is returned if the implicit search fails for any of the leading implicit parameters
2171+
* or if the receiver has a wrong type (note that in general the type of the receiver
2172+
* might depend on the exact types of the found instances of the proceding implicits).
2173+
* No implicit search is tried for implicits following the receiver or for parameters of the def (D, E).
2174+
*/
2175+
def tryApplyingExtensionMethod(methodRef: TermRef, receiver: Tree)(using Context): Option[Tree] =
2176+
// Drop all parameters sections of an extension method following the receiver.
2177+
// The return type after truncation is not important
2178+
def truncateExtension(tp: Type)(using Context): Type = tp match
2179+
case poly: PolyType =>
2180+
poly.newLikeThis(poly.paramNames, poly.paramInfos, truncateExtension(poly.resType))
2181+
case meth: MethodType if meth.isContextualMethod =>
2182+
meth.newLikeThis(meth.paramNames, meth.paramInfos, truncateExtension(meth.resType))
2183+
case meth: MethodType =>
2184+
meth.newLikeThis(meth.paramNames, meth.paramInfos, defn.AnyType)
2185+
2186+
def replaceCallee(inTree: Tree, replacement: Tree)(using Context): Tree = inTree match
2187+
case Apply(fun, args) => Apply(replaceCallee(fun, replacement), args)
2188+
case TypeApply(fun, args) => TypeApply(replaceCallee(fun, replacement), args)
2189+
case _ => replacement
2190+
2191+
val methodRefTree = ref(methodRef)
2192+
val truncatedSym = methodRef.symbol.asTerm.copy(info = truncateExtension(methodRef.info))
2193+
val truncatedRefTree = untpd.TypedSplice(ref(truncatedSym)).withSpan(receiver.span)
2194+
val newCtx = ctx.fresh.setNewScope.setReporter(new reporting.ThrowingReporter(ctx.reporter))
2195+
2196+
try
2197+
val appliedTree = inContext(newCtx) {
2198+
// Introducing an auxiliary symbol in a temporary scope.
2199+
// Entering the symbol indirectly by `newCtx.enter`
2200+
// could instead add the symbol to the enclosing class
2201+
// which could break the REPL.
2202+
newCtx.scope.openForMutations.enter(truncatedSym)
2203+
newCtx.typer.extMethodApply(truncatedRefTree, receiver, WildcardType)
2204+
}
2205+
if appliedTree.tpe.exists && !appliedTree.tpe.isError then
2206+
Some(replaceCallee(appliedTree, methodRefTree))
2207+
else
2208+
None
2209+
catch
2210+
case NonFatal(_) => None
2211+
2212+
def isApplicableExtensionMethod(methodRef: TermRef, receiverType: Type)(using Context): Boolean =
2213+
methodRef.symbol.is(ExtensionMethod) && !receiverType.isBottomType &&
2214+
tryApplyingExtensionMethod(methodRef, nullLiteral.asInstance(receiverType)).nonEmpty
21642215
}

compiler/test/dotty/tools/repl/TabcompleteTests.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,15 @@ class TabcompleteTests extends ReplTest {
107107
@Test def `null` = fromInitialState { implicit s =>
108108
val comp = tabComplete("null.")
109109
assertEquals(
110-
List("!=", "##", "==", "asInstanceOf", "clone", "eq", "equals", "finalize", "getClass", "hashCode",
110+
List("!=", "##", "==", "asInstanceOf", "eq", "equals", "getClass", "hashCode",
111111
"isInstanceOf", "ne", "notify", "notifyAll", "synchronized", "toString", "wait"),
112112
comp.distinct.sorted)
113113
}
114114

115115
@Test def anyRef = fromInitialState { implicit s =>
116116
val comp = tabComplete("(null: AnyRef).")
117117
assertEquals(
118-
List("!=", "##", "->", "==", "asInstanceOf", "clone", "ensuring", "eq", "equals", "finalize", "formatted",
118+
List("!=", "##", "->", "==", "asInstanceOf", "ensuring", "eq", "equals", "formatted",
119119
"getClass", "hashCode", "isInstanceOf", "ne", "nn", "notify", "notifyAll", "synchronized", "toString", "wait", ""),
120120
comp.distinct.sorted)
121121
}

0 commit comments

Comments
 (0)