From e701d33d6ed82a91e4a9803b14cbb13fdd2f16af Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 18 Jan 2019 18:35:42 +0100 Subject: [PATCH 1/6] Fix #4535: Handle Null and Nothing in completions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Do not complete on Nothing and complete as AnyRef for Null. Add missing Artifact to remove `$isInstanceOf` completion and workaround missing decoding for `$u2192` to `→`. --- .../dotty/tools/dotc/core/Definitions.scala | 2 +- .../tools/dotc/interactive/Completion.scala | 20 ++++++++++++------ .../dotty/tools/repl/TabcompleteTests.scala | 21 +++++++++++++++++++ 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index d51f05e4ee0b..30f89c5c44d1 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -270,7 +270,7 @@ class Definitions { lazy val Any_getClass: TermSymbol = enterMethod(AnyClass, nme.getClass_, MethodType(Nil, ClassClass.typeRef.appliedTo(TypeBounds.empty)), Final) lazy val Any_isInstanceOf: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.isInstanceOf_, _ => BooleanType, Final) lazy val Any_asInstanceOf: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.asInstanceOf_, _.paramRefs(0), Final) - lazy val Any_typeTest: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.isInstanceOfPM, _ => BooleanType, Final | Synthetic) + lazy val Any_typeTest: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.isInstanceOfPM, _ => BooleanType, Final | Synthetic | Artifact) // generated by pattern matcher, eliminated by erasure def AnyMethods: List[TermSymbol] = List(Any_==, Any_!=, Any_equals, Any_hashCode, diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index c48ef40fa6b8..d17df3512e7f 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -150,7 +150,9 @@ object Completion { nameToSymbols.map { case (name, symbols) => val typesFirst = symbols.sortWith((s1, s2) => s1.isType && !s2.isType) val desc = description(typesFirst) - Completion(name.toString, desc, typesFirst) + val strName = name.toString + val label = if (strName == "$u2192") "→" else strName // TODO fix name.decode and use it + Completion(label, desc, typesFirst) } } @@ -207,11 +209,17 @@ object Completion { * considered. */ def addMemberCompletions(qual: Tree)(implicit ctx: Context): Unit = { - addAccessibleMembers(qual.tpe) - if (!mode.is(Mode.Import)) { - // Implicit conversions do not kick in when importing - implicitConversionTargets(qual)(ctx.fresh.setExploreTyperState()) - .foreach(addAccessibleMembers) + if (qual.tpe =:= defn.NothingType) + () + else if (qual.tpe =:= defn.NullType) + addAccessibleMembers(defn.AnyRefType) + else { + addAccessibleMembers(qual.tpe) + if (!mode.is(Mode.Import)) { + // Implicit conversions do not kick in when importing + implicitConversionTargets(qual)(ctx.fresh.setExploreTyperState()) + .foreach(addAccessibleMembers) + } } } diff --git a/compiler/test/dotty/tools/repl/TabcompleteTests.scala b/compiler/test/dotty/tools/repl/TabcompleteTests.scala index 1bbbf499e737..9b426559fdbb 100644 --- a/compiler/test/dotty/tools/repl/TabcompleteTests.scala +++ b/compiler/test/dotty/tools/repl/TabcompleteTests.scala @@ -103,4 +103,25 @@ class TabcompleteTests extends ReplTest { assertEquals(comp.find(_.startsWith("<")), None) assert(!comp.contains("package")) } + + @Test def `null` = fromInitialState { implicit s => + val comp = tabComplete("null.") + assertEquals( + List("!=", "##", "==", "asInstanceOf", "clone", "eq", "equals", "finalize", "getClass", "hashCode", + "isInstanceOf", "ne", "notify", "notifyAll", "synchronized", "toString", "wait"), + comp.distinct.sorted) + } + + @Test def anyRef = fromInitialState { implicit s => + val comp = tabComplete("(null: AnyRef).") + assertEquals( + List("!=", "##", "+", "->", "==", "asInstanceOf", "clone", "ensuring", "eq", "equals", "finalize", "formatted", + "getClass", "hashCode", "isInstanceOf", "ne", "notify", "notifyAll", "synchronized", "toString", "wait", "→"), + comp.distinct.sorted) + } + + @Test def `???` = fromInitialState { implicit s => + val comp = tabComplete("???.") + assertEquals(Nil, comp) + } } From e42f0e1b5f67a9da0e4fbacdb40afb4cf576f692 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Sun, 20 Jan 2019 17:46:48 +0100 Subject: [PATCH 2/6] Use isRef --- compiler/src/dotty/tools/dotc/interactive/Completion.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index d17df3512e7f..5540a9d5d73a 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -209,9 +209,9 @@ object Completion { * considered. */ def addMemberCompletions(qual: Tree)(implicit ctx: Context): Unit = { - if (qual.tpe =:= defn.NothingType) + if (qual.tpe.isRef(defn.NothingClass)) () - else if (qual.tpe =:= defn.NullType) + else if (qual.tpe.isRef(defn.NullClass)) addAccessibleMembers(defn.AnyRefType) else { addAccessibleMembers(qual.tpe) From 1f6aadc6ca68d5c04905e41b48f1463b505c3174 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Sun, 20 Jan 2019 20:28:08 +0100 Subject: [PATCH 3/6] Fix Nothin completion --- compiler/src/dotty/tools/dotc/interactive/Completion.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index 5540a9d5d73a..88cb1f5eb118 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -209,7 +209,7 @@ object Completion { * considered. */ def addMemberCompletions(qual: Tree)(implicit ctx: Context): Unit = { - if (qual.tpe.isRef(defn.NothingClass)) + if (qual.tpe =:= defn.NothingType) () else if (qual.tpe.isRef(defn.NullClass)) addAccessibleMembers(defn.AnyRefType) From c85bc8e9d4f26ef89e0994531f0c5db5faf12aa1 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Sun, 20 Jan 2019 21:28:03 +0100 Subject: [PATCH 4/6] Implement decodeIllegalChars --- .../tools/dotc/interactive/Completion.scala | 11 +++++---- .../tools/dotc/util/NameTransformer.scala | 23 +++++++++++++++++++ 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index 88cb1f5eb118..01be99739c0b 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -1,5 +1,7 @@ package dotty.tools.dotc.interactive +import java.nio.charset.Charset + import dotty.tools.dotc.ast.Trees._ import dotty.tools.dotc.config.Printers.interactiv import dotty.tools.dotc.core.Contexts.{Context, NoContext} @@ -10,13 +12,13 @@ import dotty.tools.dotc.core.Flags._ import dotty.tools.dotc.core.Names.{Name, TermName} import dotty.tools.dotc.core.NameKinds.SimpleNameKind import dotty.tools.dotc.core.NameOps.NameDecorator -import dotty.tools.dotc.core.Symbols.{defn, NoSymbol, Symbol} +import dotty.tools.dotc.core.Symbols.{NoSymbol, Symbol, defn} import dotty.tools.dotc.core.Scopes import dotty.tools.dotc.core.StdNames.{nme, tpnme} import dotty.tools.dotc.core.TypeError -import dotty.tools.dotc.core.Types.{NameFilter, NamedType, Type, NoType} +import dotty.tools.dotc.core.Types.{NameFilter, NamedType, NoType, Type} import dotty.tools.dotc.printing.Texts._ -import dotty.tools.dotc.util.{NoSourcePosition, SourcePosition} +import dotty.tools.dotc.util.{NameTransformer, NoSourcePosition, SourcePosition} import scala.collection.mutable @@ -150,8 +152,7 @@ object Completion { nameToSymbols.map { case (name, symbols) => val typesFirst = symbols.sortWith((s1, s2) => s1.isType && !s2.isType) val desc = description(typesFirst) - val strName = name.toString - val label = if (strName == "$u2192") "→" else strName // TODO fix name.decode and use it + val label = NameTransformer.decodeIllegalChars(name.toString) Completion(label, desc, typesFirst) } } diff --git a/compiler/src/dotty/tools/dotc/util/NameTransformer.scala b/compiler/src/dotty/tools/dotc/util/NameTransformer.scala index d489c1ed0e42..e123ec64aba9 100644 --- a/compiler/src/dotty/tools/dotc/util/NameTransformer.scala +++ b/compiler/src/dotty/tools/dotc/util/NameTransformer.scala @@ -55,6 +55,29 @@ object NameTransformer { else name } + /** Decode expanded characters starting with `$u`, followed by the character's unicode expansion. */ + def decodeIllegalChars(name: String): String = { + if (name.contains("$u")) { + val sb = new mutable.StringBuilder() + var i = 0 + while (i < name.length - 5) { + if (name(i) == '$' && name(i + 1) == 'u') { + sb.append(Integer.valueOf(name.substring(i + 2, i + 6), 16).toChar) + i += 6 + } else { + sb.append(name(i)) + i += 1 + } + } + while (i < name.length) { + sb.append(name(i)) + i += 1 + } + sb.result() + } + else name + } + /** Replace operator symbols by corresponding expansion strings. * * @param name the string to encode From 4a55c5a111282d64da5b64b6d77237d1c59de0fc Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 21 Jan 2019 06:58:13 +0100 Subject: [PATCH 5/6] Refactor conditions We special completion on qualifiers of type Nothing to not return the method on Any. Also special case completion on qualifiers of type Null to not return completion that involves implicit conversions. --- .../src/dotty/tools/dotc/interactive/Completion.scala | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index 01be99739c0b..225117fc8837 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -210,13 +210,9 @@ object Completion { * considered. */ def addMemberCompletions(qual: Tree)(implicit ctx: Context): Unit = { - if (qual.tpe =:= defn.NothingType) - () - else if (qual.tpe.isRef(defn.NullClass)) - addAccessibleMembers(defn.AnyRefType) - else { + if (!qual.tpe.widenDealias.isBottomType) { addAccessibleMembers(qual.tpe) - if (!mode.is(Mode.Import)) { + if (!mode.is(Mode.Import) && !qual.tpe.isRef(defn.NullClass)) { // Implicit conversions do not kick in when importing implicitConversionTargets(qual)(ctx.fresh.setExploreTyperState()) .foreach(addAccessibleMembers) From d4ddb49081132d9c78b2d7f9cbdaf217a188bc1c Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 21 Jan 2019 12:22:24 +0100 Subject: [PATCH 6/6] Improve decodeIllegalChars --- .../src/dotty/tools/dotc/interactive/Completion.scala | 1 + compiler/src/dotty/tools/dotc/util/NameTransformer.scala | 8 ++------ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index 225117fc8837..966c95cad0fc 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -214,6 +214,7 @@ object Completion { addAccessibleMembers(qual.tpe) if (!mode.is(Mode.Import) && !qual.tpe.isRef(defn.NullClass)) { // Implicit conversions do not kick in when importing + // and for `NullClass` they produce unapplicable completions (for unclear reasons) implicitConversionTargets(qual)(ctx.fresh.setExploreTyperState()) .foreach(addAccessibleMembers) } diff --git a/compiler/src/dotty/tools/dotc/util/NameTransformer.scala b/compiler/src/dotty/tools/dotc/util/NameTransformer.scala index e123ec64aba9..7fc724ad1cc1 100644 --- a/compiler/src/dotty/tools/dotc/util/NameTransformer.scala +++ b/compiler/src/dotty/tools/dotc/util/NameTransformer.scala @@ -60,8 +60,8 @@ object NameTransformer { if (name.contains("$u")) { val sb = new mutable.StringBuilder() var i = 0 - while (i < name.length - 5) { - if (name(i) == '$' && name(i + 1) == 'u') { + while (i < name.length) { + if (i < name.length - 5 && name(i) == '$' && name(i + 1) == 'u') { sb.append(Integer.valueOf(name.substring(i + 2, i + 6), 16).toChar) i += 6 } else { @@ -69,10 +69,6 @@ object NameTransformer { i += 1 } } - while (i < name.length) { - sb.append(name(i)) - i += 1 - } sb.result() } else name