From 9ec7415ded9a96b01ec8c9300ac018084b87e53e Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Fri, 19 May 2023 18:35:27 +0100 Subject: [PATCH] Set suggestion reexports when serializing the library (#6778) close #6613 Changelog - feat: during the library serialization, build the exports map and set the reexport field of the suggestion # Important Notes IDE does not create additional imports for re-exported symbols. ![2023-05-18-192739_2019x828_scrot](https://github.com/enso-org/enso/assets/357683/5ef20dfe-d6a5-4935-a759-4af10b0817a5) --- .../search/SuggestionsHandlerSpec.scala | 23 ++++- .../json/SuggestionsHandlerEventsTest.scala | 21 +++- .../org/enso/polyglot/ExportedSymbol.scala | 54 ++++++++++- .../scala/org/enso/polyglot/Suggestion.scala | 31 ++++++ .../RuntimeSuggestionUpdatesTest.scala | 14 ++- .../org/enso/compiler/context/ExportsMap.java | 61 ++++++++++++ .../enso/compiler/SerializationManager.scala | 96 +++++++++++-------- .../compiler/context/ExportsBuilder.scala | 4 +- .../org/enso/compiler/data/BindingsMap.scala | 4 +- 9 files changed, 246 insertions(+), 62 deletions(-) create mode 100644 engine/runtime/src/main/java/org/enso/compiler/context/ExportsMap.java diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/search/SuggestionsHandlerSpec.scala b/engine/language-server/src/test/scala/org/enso/languageserver/search/SuggestionsHandlerSpec.scala index 175f0a88090f..1c010a62c5dc 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/search/SuggestionsHandlerSpec.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/search/SuggestionsHandlerSpec.scala @@ -533,6 +533,13 @@ class SuggestionsHandlerSpec ), Vector() ), + Tree.Node( + Api.SuggestionUpdate( + Suggestions.tpe, + Api.SuggestionAction.Add() + ), + Vector() + ), Tree.Node( Api.SuggestionUpdate( Suggestions.constructor, @@ -598,7 +605,11 @@ class SuggestionsHandlerSpec ExportedSymbol.Module( Suggestions.module.module ), - ExportedSymbol.Atom( + ExportedSymbol.Type( + Suggestions.tpe.module, + Suggestions.tpe.name + ), + ExportedSymbol.Constructor( Suggestions.constructor.module, Suggestions.constructor.name ), @@ -618,7 +629,7 @@ class SuggestionsHandlerSpec Tree.Root(Vector()) ) - val updates2 = Seq(1L, 2L, 3L).map { id => + val updates2 = Seq(1L, 2L, 3L, 4L).map { id => SearchProtocol.SuggestionsDatabaseUpdate.Modify( id, reexport = Some(fieldUpdate(exportUpdateAdd.exports.module)) @@ -642,7 +653,11 @@ class SuggestionsHandlerSpec ExportedSymbol.Module( Suggestions.module.module ), - ExportedSymbol.Atom( + ExportedSymbol.Type( + Suggestions.tpe.module, + Suggestions.tpe.name + ), + ExportedSymbol.Constructor( Suggestions.constructor.module, Suggestions.constructor.name ), @@ -662,7 +677,7 @@ class SuggestionsHandlerSpec Tree.Root(Vector()) ) - val updates3 = Seq(1L, 2L, 3L).map { id => + val updates3 = Seq(1L, 2L, 3L, 4L).map { id => SearchProtocol.SuggestionsDatabaseUpdate.Modify( id, reexport = Some(fieldRemove) diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/SuggestionsHandlerEventsTest.scala b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/SuggestionsHandlerEventsTest.scala index e6ddf7ee06b9..f197a4dd36fc 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/SuggestionsHandlerEventsTest.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/SuggestionsHandlerEventsTest.scala @@ -500,11 +500,14 @@ class SuggestionsHandlerEventsTest extends BaseServerTest with FlakySpec { ModuleExports( "Foo.Bar", ListSet( - ExportedSymbol - .Atom( - Suggestions.constructor.module, - Suggestions.constructor.name - ) + ExportedSymbol.Type( + Suggestions.tpe.module, + Suggestions.tpe.name + ), + ExportedSymbol.Constructor( + Suggestions.constructor.module, + Suggestions.constructor.name + ) ) ), Api.ExportsAction.Add() @@ -519,6 +522,14 @@ class SuggestionsHandlerEventsTest extends BaseServerTest with FlakySpec { "method" : "search/suggestionsDatabaseUpdates", "params" : { "updates" : [ + { + "type" : "Modify", + "id" : 1, + "reexport" : { + "tag" : "Set", + "value" : "Foo.Bar" + } + }, { "type" : "Modify", "id" : 2, diff --git a/engine/polyglot-api/src/main/scala/org/enso/polyglot/ExportedSymbol.scala b/engine/polyglot-api/src/main/scala/org/enso/polyglot/ExportedSymbol.scala index 84325e58a6ce..ff1c6eacbac7 100644 --- a/engine/polyglot-api/src/main/scala/org/enso/polyglot/ExportedSymbol.scala +++ b/engine/polyglot-api/src/main/scala/org/enso/polyglot/ExportedSymbol.scala @@ -10,8 +10,12 @@ import com.fasterxml.jackson.annotation.{JsonSubTypes, JsonTypeInfo} name = "exportedModule" ), new JsonSubTypes.Type( - value = classOf[ExportedSymbol.Atom], - name = "exportedAtom" + value = classOf[ExportedSymbol.Type], + name = "exportedType" + ), + new JsonSubTypes.Type( + value = classOf[ExportedSymbol.Constructor], + name = "exportedConstructor" ), new JsonSubTypes.Type( value = classOf[ExportedSymbol.Method], @@ -28,26 +32,65 @@ sealed trait ExportedSymbol { } object ExportedSymbol { + /** Create [[ExportedSymbol]] from [[Suggestion]]. + * + * @param suggestion the suggestion to convert + * @return the corresponding [[ExportedSymbol]] + */ + def fromSuggestion(suggestion: Suggestion): Option[ExportedSymbol] = + suggestion match { + case s: Suggestion.Module => Some(Module(s.module)) + case s: Suggestion.Type => Some(Type(s.module, s.name)) + case s: Suggestion.Constructor => Some(Constructor(s.module, s.name)) + case s: Suggestion.Method => Some(Method(s.module, s.name)) + case _: Suggestion.Conversion => None + case _: Suggestion.Function => None + case _: Suggestion.Local => None + } + + /** Create an exported symbol of the suggestion module. + * + * @param suggestion the suggestion to convert + * @return the corresponding [[ExportedSymbol.Module]] + */ + def suggestionModule(suggestion: Suggestion): ExportedSymbol.Module = + ExportedSymbol.Module(suggestion.module) + /** The module symbol. * * @param module the module name */ case class Module(module: String) extends ExportedSymbol { + /** @inheritdoc */ override def name: String = module + /** @inheritdoc */ override def kind: Suggestion.Kind = Suggestion.Kind.Module } - /** The atom symbol. + /** The type symbol. * * @param module the module defining this atom - * @param name the atom name + * @param name the type name + */ + case class Type(module: String, name: String) extends ExportedSymbol { + + /** @inheritdoc */ + override def kind: Suggestion.Kind = + Suggestion.Kind.Type + } + + /** The constructor symbol. + * + * @param module the module where this constructor is defined + * @param name the constructor name */ - case class Atom(module: String, name: String) extends ExportedSymbol { + case class Constructor(module: String, name: String) extends ExportedSymbol { + /** @inheritdoc */ override def kind: Suggestion.Kind = Suggestion.Kind.Constructor } @@ -59,6 +102,7 @@ object ExportedSymbol { */ case class Method(module: String, name: String) extends ExportedSymbol { + /** @inheritdoc */ override def kind: Suggestion.Kind = Suggestion.Kind.Method } diff --git a/engine/polyglot-api/src/main/scala/org/enso/polyglot/Suggestion.scala b/engine/polyglot-api/src/main/scala/org/enso/polyglot/Suggestion.scala index ef5720e9a732..72d89ce0deb8 100644 --- a/engine/polyglot-api/src/main/scala/org/enso/polyglot/Suggestion.scala +++ b/engine/polyglot-api/src/main/scala/org/enso/polyglot/Suggestion.scala @@ -46,6 +46,9 @@ sealed trait Suggestion extends ToLogString { def name: String def returnType: String def documentation: Option[String] + + /** Set the reexport field of the suggestion. */ + def withReexport(reexport: Option[String]): Suggestion } object Suggestion { @@ -220,6 +223,10 @@ object Suggestion { override def returnType: String = module + /** @inheritdoc */ + override def withReexport(reexport: Option[String]): Module = + copy(reexport = reexport) + /** @inheritdoc */ override def toLogString(shouldMask: Boolean): String = s"Module(module=$module,name=$name,documentation=" + @@ -250,6 +257,10 @@ object Suggestion { ) extends Suggestion with ToLogString { + /** @inheritdoc */ + override def withReexport(reexport: Option[String]): Type = + copy(reexport = reexport) + /** @inheritdoc */ override def toLogString(shouldMask: Boolean): String = "Type(" + @@ -285,6 +296,10 @@ object Suggestion { ) extends Suggestion with ToLogString { + /** @inheritdoc */ + override def withReexport(reexport: Option[String]): Constructor = + copy(reexport = reexport) + /** @inheritdoc */ override def toLogString(shouldMask: Boolean): String = "Constructor(" + @@ -323,6 +338,10 @@ object Suggestion { ) extends Suggestion with ToLogString { + /** @inheritdoc */ + override def withReexport(reexport: Option[String]): Method = + copy(reexport = reexport) + /** @inheritdoc */ override def toLogString(shouldMask: Boolean): String = "Method(" + @@ -357,6 +376,10 @@ object Suggestion { reexport: Option[String] = None ) extends Suggestion { + /** @inheritdoc */ + override def withReexport(reexport: Option[String]): Conversion = + copy(reexport = reexport) + /** @inheritdoc */ override def name: String = Kind.Conversion.From @@ -394,6 +417,10 @@ object Suggestion { ) extends Suggestion with ToLogString { + /** @inheritdoc */ + override def withReexport(reexport: Option[String]): Function = + this + /** @inheritdoc */ override def toLogString(shouldMask: Boolean): String = "Function(" + @@ -425,6 +452,10 @@ object Suggestion { documentation: Option[String] ) extends Suggestion { + /** @inheritdoc */ + override def withReexport(reexport: Option[String]): Local = + this + /** @inheritdoc */ override def toLogString(shouldMask: Boolean): String = s"Local(" + diff --git a/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeSuggestionUpdatesTest.scala b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeSuggestionUpdatesTest.scala index b6aa4accea96..38b5d8884469 100644 --- a/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeSuggestionUpdatesTest.scala +++ b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeSuggestionUpdatesTest.scala @@ -1014,7 +1014,7 @@ class RuntimeSuggestionUpdatesTest ) ) context.receiveNIgnoreExpressionUpdates( - 5 + 6 ) should contain theSameElementsAs Seq( Api.Response(Api.BackgroundJobsStartedNotification()), Api.Response(requestId, Api.PushContextResponse(contextId)), @@ -1151,7 +1151,8 @@ class RuntimeSuggestionUpdatesTest ModuleExports( "Enso_Test.Test.Main", Set( - ExportedSymbol.Atom("Enso_Test.Test.A", "MkA"), + ExportedSymbol.Type("Enso_Test.Test.A", "MyType"), + ExportedSymbol.Constructor("Enso_Test.Test.A", "MkA"), ExportedSymbol.Method("Enso_Test.Test.A", "hello") ) ), @@ -1190,6 +1191,7 @@ class RuntimeSuggestionUpdatesTest ) ) ), + Api.Response(Api.AnalyzeModuleInScopeJobFinished()), context.executionComplete(contextId) ) context.consumeOut shouldEqual List("Hello World!") @@ -1210,7 +1212,7 @@ class RuntimeSuggestionUpdatesTest ) ) context.receiveNIgnoreExpressionUpdates( - 3 + 2 ) should contain theSameElementsAs Seq( Api.Response( Api.SuggestionsDatabaseModuleUpdateNotification( @@ -1228,7 +1230,6 @@ class RuntimeSuggestionUpdatesTest updates = Tree.Root(Vector()) ) ), - Api.Response(Api.AnalyzeModuleInScopeJobFinished()), context.executionComplete(contextId) ) context.consumeOut shouldEqual List("Hello World!") @@ -1259,7 +1260,10 @@ class RuntimeSuggestionUpdatesTest Api.ExportsUpdate( ModuleExports( "Enso_Test.Test.Main", - Set(ExportedSymbol.Atom("Enso_Test.Test.A", "MkA")) + Set( + ExportedSymbol.Type("Enso_Test.Test.A", "MyType"), + ExportedSymbol.Constructor("Enso_Test.Test.A", "MkA") + ) ), Api.ExportsAction.Remove() ) diff --git a/engine/runtime/src/main/java/org/enso/compiler/context/ExportsMap.java b/engine/runtime/src/main/java/org/enso/compiler/context/ExportsMap.java new file mode 100644 index 000000000000..1435e3084eba --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/compiler/context/ExportsMap.java @@ -0,0 +1,61 @@ +package org.enso.compiler.context; + +import org.enso.pkg.QualifiedName; +import org.enso.polyglot.ExportedSymbol; +import org.enso.polyglot.ModuleExports; +import org.enso.polyglot.Suggestion; +import scala.Option; +import scala.runtime.BoxedUnit; + +import java.util.HashMap; +import java.util.Map; + +public final class ExportsMap { + + private static final String MODULE_MAIN = "Main"; + private static final String TYPE_SUFFIX = "type"; + + private final Map exportsMap; + + public ExportsMap() { + this.exportsMap = new HashMap<>(); + } + + public ExportsMap(Map exportsMap) { + this.exportsMap = exportsMap; + } + + public void add(ExportedSymbol symbol, QualifiedName moduleName) { + exportsMap.merge(symbol, moduleName, ExportsMap::getShortest); + } + + public void addAll(QualifiedName moduleName, ModuleExports moduleExports) { + moduleExports + .symbols() + .foreach( + symbol -> { + add(symbol, moduleName); + return BoxedUnit.UNIT; + }); + } + + public QualifiedName get(ExportedSymbol symbol) { + return exportsMap.get(symbol); + } + + public QualifiedName get(Suggestion suggestion) { + return ExportedSymbol.fromSuggestion(suggestion) + .flatMap(symbol -> Option.apply(exportsMap.get(symbol))) + .getOrElse(() -> exportsMap.get(ExportedSymbol.suggestionModule(suggestion))); + } + + private static QualifiedName getShortest(QualifiedName name1, QualifiedName name2) { + return length(name1) <= length(name2) ? name1 : name2; + } + + private static int length(QualifiedName qualifiedName) { + QualifiedName name = + qualifiedName.item().equals(TYPE_SUFFIX) ? qualifiedName.getParent().get() : qualifiedName; + return name.item().equals(MODULE_MAIN) ? name.path().length() : name.path().length() + 1; + } +} diff --git a/engine/runtime/src/main/scala/org/enso/compiler/SerializationManager.scala b/engine/runtime/src/main/scala/org/enso/compiler/SerializationManager.scala index b14d5dfe67a1..f2dfbcf3eb55 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/SerializationManager.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/SerializationManager.scala @@ -2,7 +2,7 @@ package org.enso.compiler import com.oracle.truffle.api.TruffleLogger import com.oracle.truffle.api.source.Source -import org.enso.compiler.context.SuggestionBuilder +import org.enso.compiler.context.{ExportsBuilder, ExportsMap, SuggestionBuilder} import org.enso.compiler.core.IR import org.enso.compiler.pass.analyse.BindingAnalysis import org.enso.editions.LibraryName @@ -233,44 +233,7 @@ final class SerializationManager( throw e } - try { - val suggestions = new util.ArrayList[Suggestion]() - compiler.packageRepository - .getModulesForLibrary(libraryName) - .flatMap { module => - SuggestionBuilder(module, compiler) - .build(module.getName, module.getIr) - .toVector - .filter(Suggestion.isGlobal) - } - .foreach(suggestions.add) - val cachedSuggestions = - new SuggestionsCache.CachedSuggestions( - libraryName, - new SuggestionsCache.Suggestions(suggestions), - compiler.packageRepository - .getPackageForLibraryJava(libraryName) - .map(_.listSourcesJava()) - ) - new SuggestionsCache(libraryName) - .save(cachedSuggestions, compiler.context, useGlobalCacheLocations) - .isPresent - } catch { - case e: NotSerializableException => - logger.log( - Level.SEVERE, - s"Could not serialize suggestions [$libraryName].", - e - ) - throw e - case e: Throwable => - logger.log( - Level.SEVERE, - s"Serialization of suggestions `$libraryName` failed: ${e.getMessage}`", - e - ) - throw e - } + doSerializeLibrarySuggestions(libraryName, useGlobalCacheLocations) result } finally { @@ -278,6 +241,61 @@ final class SerializationManager( } } + private def doSerializeLibrarySuggestions( + libraryName: LibraryName, + useGlobalCacheLocations: Boolean + ): Boolean = { + val exportsBuilder = new ExportsBuilder + val exportsMap = new ExportsMap + val suggestions = new util.ArrayList[Suggestion]() + + try { + val libraryModules = + compiler.packageRepository.getModulesForLibrary(libraryName) + libraryModules + .flatMap { module => + val suggestions = SuggestionBuilder(module, compiler) + .build(module.getName, module.getIr) + .toVector + .filter(Suggestion.isGlobal) + val exports = exportsBuilder.build(module.getName, module.getIr) + exportsMap.addAll(module.getName, exports) + suggestions + } + .map { suggestion => + val reexport = Option(exportsMap.get(suggestion)).map(_.toString) + suggestion.withReexport(reexport) + } + .foreach(suggestions.add) + val cachedSuggestions = + new SuggestionsCache.CachedSuggestions( + libraryName, + new SuggestionsCache.Suggestions(suggestions), + compiler.packageRepository + .getPackageForLibraryJava(libraryName) + .map(_.listSourcesJava()) + ) + new SuggestionsCache(libraryName) + .save(cachedSuggestions, compiler.context, useGlobalCacheLocations) + .isPresent + } catch { + case e: NotSerializableException => + logger.log( + Level.SEVERE, + s"Could not serialize suggestions [$libraryName].", + e + ) + throw e + case e: Throwable => + logger.log( + Level.SEVERE, + s"Serialization of suggestions `$libraryName` failed: ${e.getMessage}`", + e + ) + throw e + } + } + def deserializeSuggestions( libraryName: LibraryName ): Option[SuggestionsCache.CachedSuggestions] = { diff --git a/engine/runtime/src/main/scala/org/enso/compiler/context/ExportsBuilder.scala b/engine/runtime/src/main/scala/org/enso/compiler/context/ExportsBuilder.scala index 7811d83db4e6..5afa15642a95 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/context/ExportsBuilder.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/context/ExportsBuilder.scala @@ -19,8 +19,10 @@ final class ExportsBuilder { .collect { case BindingsMap.ResolvedMethod(module, method) => ExportedSymbol.Method(module.getName.toString, method.name) + case BindingsMap.ResolvedType(module, tp) => + ExportedSymbol.Type(module.getName.toString, tp.name) case BindingsMap.ResolvedConstructor(tp, cons) => - ExportedSymbol.Atom(tp.module.getName.toString, cons.name) + ExportedSymbol.Constructor(tp.module.getName.toString, cons.name) case BindingsMap.ResolvedModule(module) => ExportedSymbol.Module(module.getName.toString) } diff --git a/engine/runtime/src/main/scala/org/enso/compiler/data/BindingsMap.scala b/engine/runtime/src/main/scala/org/enso/compiler/data/BindingsMap.scala index c6e8f5cb3735..d0403a7dc29c 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/data/BindingsMap.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/data/BindingsMap.scala @@ -16,9 +16,7 @@ import scala.annotation.unused /** A utility structure for resolving symbols in a given module. * - * @param constructors the types defined in the current module - * @param polyglotSymbols the polyglot symbols imported into the scope - * @param moduleMethods the methods defined with current module as `this` + * @param definedEntities the list of entities defined in the current module * @param currentModule the module holding these bindings */