-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Make code completions and import suggestions work correctly for extensions with leading using clauses #11187
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
22d3382
ba382b9
3140bde
0bf6dd7
8bde0ae
d09fcc2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2133,8 +2133,34 @@ trait Applications extends Compatibility { | |
} | ||
} | ||
|
||
def isApplicableExtensionMethod(ref: TermRef, receiver: Type)(using Context) = | ||
ref.symbol.is(ExtensionMethod) | ||
&& !receiver.isBottomType | ||
&& isApplicableMethodRef(ref, receiver :: Nil, WildcardType) | ||
private def tryApplyingReceiverToTruncatedExtMethod(methodSym: TermSymbol, receiver: Tree)(using Context): scala.util.Try[Tree] = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Usage of The name of the method is long, but not informative. I'd suggest make it shorter and add documentation to make its semantics clear. |
||
// Drop all parameters sections of an extension method following the receiver to prevent them from being inferred by the typer | ||
def truncateExtension(tp: Type): Type = tp match | ||
case poly: PolyType => poly.newLikeThis(poly.paramNames, poly.paramInfos, truncateExtension(poly.resType)) | ||
case meth: MethodType if meth.isContextualMethod => meth.newLikeThis(meth.paramNames, meth.paramInfos, truncateExtension(meth.resType)) | ||
case meth: MethodType => meth.newLikeThis(meth.paramNames, meth.paramInfos, defn.AnyType) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick: the lines are too long here (and in some other places), might be good to write the body of case in a new line. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
|
||
val truncatedSym = methodSym.copy(owner = defn.RootPackage, name = Names.termName(""), info = truncateExtension(methodSym.info)) | ||
val truncatedRef = ref(truncatedSym).withSpan(Span(0, 0)) // Fake span needed to make this work in REPL | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe use the position from |
||
val newCtx = ctx.fresh.setNewScope.setReporter(new reporting.ThrowingReporter(ctx.reporter)) | ||
scala.util.Try { | ||
inContext(newCtx) { | ||
ctx.enter(truncatedSym) | ||
ctx.typer.extMethodApply(truncatedRef, receiver, WildcardType) | ||
} | ||
}.filter(tree => tree.tpe.exists && !tree.tpe.isError) | ||
|
||
def tryApplyingReceiver(methodSym: TermSymbol, receiver: Tree)(using Context): Option[Tree] = | ||
def replaceAppliedRef(inTree: Tree, replacement: Tree)(using Context): Tree = inTree match | ||
case Apply(fun, args) => Apply(replaceAppliedRef(fun, replacement), args) | ||
case TypeApply(fun, args) => TypeApply(replaceAppliedRef(fun, replacement), args) | ||
case _: Ident => replacement | ||
|
||
tryApplyingReceiverToTruncatedExtMethod(methodSym, receiver) | ||
.toOption | ||
.map(tree => replaceAppliedRef(tree, ref(methodSym))) | ||
|
||
def isApplicableExtensionMethod(ref: TermRef, receiverType: Type)(using Context) = | ||
ref.symbol.is(ExtensionMethod) && !receiverType.isBottomType && | ||
tryApplyingReceiverToTruncatedExtMethod(ref.symbol.asTerm, Typed(EmptyTree, TypeTree(receiverType))).isSuccess | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -308,40 +308,140 @@ class CompletionTest { | |
|
||
@Test def completeExtensionMethodWithTypeParameter: Unit = { | ||
code"""object Foo | ||
|extension [A](foo: Foo.type) def xxxx: Int = 1 | ||
|extension (foo: Foo.type) def xxxx[A]: Int = 1 | ||
|object Main { Foo.xx${m1} }""".withSource | ||
.completion(m1, Set(("xxxx", Method, "[A] => Int"))) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it an accidenal change? The same question for the change below. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is intentional. This is actually a new test case. The old one got copied below and renamed to |
||
} | ||
|
||
@Test def completeExtensionMethodWithParameterAndTypeParameter: Unit = { | ||
code"""object Foo | ||
|extension [A](foo: Foo.type) def xxxx(a: A) = a | ||
|extension (foo: Foo.type) def xxxx[A](a: A) = a | ||
|object Main { Foo.xx${m1} }""".withSource | ||
.completion(m1, Set(("xxxx", Method, "[A](a: A): A"))) | ||
} | ||
|
||
@Test def completeExtensionMethodFromExtenionWithAUsingSection: Unit = { | ||
@Test def completeExtensionMethodFromExtensionWithTypeParameter: Unit = { | ||
code"""extension [A](a: A) def xxxx: A = a | ||
|object Main { "abc".xx${m1} }""".withSource | ||
.completion(m1, Set(("xxxx", Method, "=> String"))) | ||
} | ||
|
||
@Test def completeExtensionMethodWithResultTypeDependantOnReceiver: Unit = { | ||
code"""trait Foo { type Out; def get: Out} | ||
|object Bar extends Foo { type Out = String; def get: Out = "abc"} | ||
|extension (foo: Foo) def xxxx: foo.Out = foo.get | ||
|object Main { Bar.xx${m1} }""".withSource | ||
.completion(m1, Set(("xxxx", Method, "=> String"))) | ||
} | ||
|
||
@Test def completeExtensionMethodFromExtenionWithPrefixUsingSection: Unit = { | ||
code"""object Foo | ||
|trait Bar | ||
|trait Baz | ||
|given Bar = new Bar {} | ||
|given Baz = new Baz {} | ||
|given Bar with {} | ||
|given Baz with {} | ||
|extension (using Bar, Baz)(foo: Foo.type) def xxxx = 1 | ||
|object Main { Foo.xx${m1} }""".withSource | ||
.completion(m1, Set(("xxxx", Method, "=> Int"))) | ||
} | ||
|
||
@Test def completeExtensionMethodFromExtenionWithMultiplePrefixUsingSections: Unit = { | ||
code"""object Foo | ||
|trait Bar | ||
|trait Baz | ||
|given Bar with {} | ||
|given Baz with {} | ||
|extension (using Bar)(using Baz)(foo: Foo.type) def xxxx = 1 | ||
|object Main { Foo.xx${m1} }""".withSource | ||
.completion(m1, Set(("xxxx", Method, "=> Int"))) | ||
} | ||
|
||
@Test def dontCompleteExtensionMethodFromExtenionWithMissingImplicitFromPrefixUsingSection: Unit = { | ||
code"""object Foo | ||
|trait Bar | ||
|trait Baz | ||
|given Baz with {} | ||
|extension (using Bar, Baz)(foo: Foo.type) def xxxx = 1 | ||
|object Main { Foo.xx${m1} }""".withSource | ||
.completion(m1, Set()) | ||
} | ||
|
||
@Test def completeExtensionMethodForReceiverOfTypeDependentOnLeadingImplicits: Unit = { | ||
code""" | ||
|trait Foo: | ||
| type Out <: Bar | ||
| | ||
|given Foo with | ||
| type Out = Baz | ||
| | ||
|trait Bar: | ||
| type Out | ||
| | ||
|trait Baz extends Bar | ||
| | ||
|given Baz with | ||
| type Out = Quux | ||
| | ||
|class Quux | ||
| | ||
|object Quux: | ||
| extension (using foo: Foo)(using fooOut: foo.Out)(fooOutOut: fooOut.Out) def xxxx = "abc" | ||
| | ||
|object Main { (new Quux).xx${m1} }""".withSource | ||
.completion(m1, Set(("xxxx", Method, "=> String"))) | ||
} | ||
|
||
@Test def completeExtensionMethodWithResultTypeDependentOnLeadingImplicit: Unit = { | ||
code"""object Foo | ||
|trait Bar { type Out; def get: Out } | ||
|given Bar with { type Out = 123; def get: Out = 123 } | ||
|extension (using bar: Bar)(foo: Foo.type) def xxxx: bar.Out = bar.get | ||
|object Main { Foo.xx${m1} }""".withSource | ||
.completion(m1, Set(("xxxx", Method, "=> (123 : Int)"))) | ||
} | ||
|
||
@Test def completeExtensionMethodFromExtenionWithPostfixUsingSection: Unit = { | ||
code"""object Foo | ||
|trait Bar | ||
|trait Baz | ||
|given Bar with {} | ||
|given Baz with {} | ||
|extension (foo: Foo.type)(using Bar, Baz) def xxxx = 1 | ||
|object Main { Foo.xx${m1} }""".withSource | ||
.completion(m1, Set(("xxxx", Method, "(using x$2: Bar, x$3: Baz): Int"))) | ||
} | ||
|
||
@Test def completeExtensionMethodFromExtenionWithMultipleUsingSections: Unit = { | ||
@Test def completeExtensionMethodFromExtenionWithMultiplePostfixUsingSections: Unit = { | ||
code"""object Foo | ||
|trait Bar | ||
|trait Baz | ||
|given Bar = new Bar {} | ||
|given Baz = new Baz {} | ||
|given Bar with {} | ||
|given Baz with {} | ||
|extension (foo: Foo.type)(using Bar)(using Baz) def xxxx = 1 | ||
|object Main { Foo.xx${m1} }""".withSource | ||
.completion(m1, Set(("xxxx", Method, "(using x$2: Bar)(using x$3: Baz): Int"))) | ||
} | ||
|
||
@Test def completeExtensionMethodWithTypeParameterFromExtenionWithTypeParametersAndPrefixAndPostfixUsingSections: Unit = { | ||
code"""trait Bar | ||
|trait Baz | ||
|given Bar with {} | ||
|given Baz with {} | ||
|extension [A](using bar: Bar)(a: A)(using baz: Baz) def xxxx[B]: Either[A, B] = Left(a) | ||
|object Main { 123.xx${m1} }""".withSource | ||
.completion(m1, Set(("xxxx", Method, "(using baz: Baz): [B] => Either[Int, B]"))) | ||
} | ||
|
||
@Test def completeExtensionMethodWithTypeBounds: Unit = { | ||
code"""trait Foo | ||
|trait Bar extends Foo | ||
|given Bar with {} | ||
|extension [A >: Bar](a: A) def xxxx[B <: a.type]: Either[A, B] = Left(a) | ||
|val foo = new Foo {} | ||
|object Main { foo.xx${m1} }""".withSource | ||
.completion(m1, Set(("xxxx", Method, "[B <: (foo : Foo)] => Either[Foo, B]"))) | ||
} | ||
|
||
@Test def completeInheritedExtensionMethod: Unit = { | ||
code"""object Foo | ||
|trait FooOps { | ||
|
@@ -442,10 +542,9 @@ class CompletionTest { | |
.completion(m1, Set(("xxxx", Method, "=> Int"))) | ||
} | ||
|
||
@Test def dontCompleteInapplicableExtensionMethod: Unit = { | ||
code"""case class Foo[A](a: A) | ||
|extension (foo: Foo[Int]) def xxxx = foo.a | ||
|object Main { Foo("abc").xx${m1} }""".withSource | ||
@Test def dontCompleteExtensionMethodWithMismatchedReceiverType: Unit = { | ||
code"""extension (i: Int) def xxxx = i | ||
|object Main { "abc".xx${m1} }""".withSource | ||
.completion(m1, Set()) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
-- [E008] Not Found Error: tests/neg/missing-implicit6.scala:34:8 ------------------------------------------------------ | ||
34 | "a".xxx // error, no suggested import | ||
| ^^^^^^^ | ||
| value xxx is not a member of String | ||
-- [E008] Not Found Error: tests/neg/missing-implicit6.scala:35:8 ------------------------------------------------------ | ||
35 | 123.xxx // error, suggested import | ||
| ^^^^^^^ | ||
| value xxx is not a member of Int, but could be made available as an extension method. | ||
| | ||
| The following import might fix the problem: | ||
| | ||
| import Test.Ops.xxx | ||
| | ||
-- [E008] Not Found Error: tests/neg/missing-implicit6.scala:36:8 ------------------------------------------------------ | ||
36 | 123.yyy // error, suggested import | ||
| ^^^^^^^ | ||
| value yyy is not a member of Int, but could be made available as an extension method. | ||
| | ||
| The following import might fix the problem: | ||
| | ||
| import Test.Ops.yyy | ||
| | ||
-- [E008] Not Found Error: tests/neg/missing-implicit6.scala:41:8 ------------------------------------------------------ | ||
41 | 123.xxx // error, no suggested import | ||
| ^^^^^^^ | ||
| value xxx is not a member of Int | ||
-- [E008] Not Found Error: tests/neg/missing-implicit6.scala:42:8 ------------------------------------------------------ | ||
42 | 123.yyy // error, no suggested import | ||
| ^^^^^^^ | ||
| value yyy is not a member of Int | ||
-- [E008] Not Found Error: tests/neg/missing-implicit6.scala:43:8 ------------------------------------------------------ | ||
43 | 123.zzz // error, suggested import even though there's no instance of Bar in scope | ||
| ^^^^^^^ | ||
| value zzz is not a member of Int, but could be made available as an extension method. | ||
| | ||
| The following import might fix the problem: | ||
| | ||
| import Test.Ops.zzz | ||
| |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
trait Foo { | ||
type Out <: { type Out } | ||
} | ||
|
||
trait Bar { | ||
type Out | ||
} | ||
|
||
object instances { | ||
given foo: Foo with { | ||
type Out = Bar | ||
} | ||
|
||
given bar: Bar with { | ||
type Out = Int | ||
} | ||
} | ||
|
||
object Test { | ||
object Ops { | ||
extension (using foo: Foo, bar: foo.Out)(i: Int) | ||
def xxx = ??? | ||
|
||
extension (using foo: Foo, fooOut: foo.Out)(x: fooOut.Out) | ||
def yyy = ??? | ||
|
||
extension (using foo: Foo)(i: Int)(using fooOut: foo.Out) | ||
def zzz = ??? | ||
} | ||
|
||
locally { | ||
import instances.given | ||
|
||
"a".xxx // error, no suggested import | ||
123.xxx // error, suggested import | ||
123.yyy // error, suggested import | ||
} | ||
|
||
locally { | ||
import instances.foo | ||
123.xxx // error, no suggested import | ||
123.yyy // error, no suggested import | ||
123.zzz // error, suggested import even though there's no instance of Bar in scope | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.