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)
This commit is contained in:
Dmitry Bushev 2023-05-19 18:35:27 +01:00 committed by GitHub
parent 029b900335
commit 9ec7415ded
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 246 additions and 62 deletions

View File

@ -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)

View File

@ -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,

View File

@ -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 Atom(module: String, name: String) extends ExportedSymbol {
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 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
}

View File

@ -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(" +

View File

@ -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()
)

View File

@ -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<ExportedSymbol, QualifiedName> exportsMap;
public ExportsMap() {
this.exportsMap = new HashMap<>();
}
public ExportsMap(Map<ExportedSymbol, QualifiedName> 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;
}
}

View File

@ -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] = {

View File

@ -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)
}

View File

@ -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
*/