mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 00:52:09 +03:00
Implement the Suggestions Database (#846)
This commit is contained in:
parent
07265e6164
commit
9ba1aa6d34
12
build.sbt
12
build.sbt
@ -570,6 +570,17 @@ lazy val `core-definition` = (project in file("lib/core-definition"))
|
||||
.dependsOn(graph)
|
||||
.dependsOn(syntax.jvm)
|
||||
|
||||
lazy val searcher = project
|
||||
.in(file("lib/searcher"))
|
||||
.configs(Test)
|
||||
.settings(
|
||||
libraryDependencies ++= Seq(
|
||||
"com.typesafe.slick" %% "slick" % "3.3.2",
|
||||
"org.xerial" % "sqlite-jdbc" % "3.31.1",
|
||||
"org.scalatest" %% "scalatest" % scalatestVersion % Test,
|
||||
)
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// === Sub-Projects ===========================================================
|
||||
// ============================================================================
|
||||
@ -746,6 +757,7 @@ lazy val runtime = (project in file("engine/runtime"))
|
||||
.dependsOn(graph)
|
||||
.dependsOn(`polyglot-api`)
|
||||
.dependsOn(`text-buffer`)
|
||||
.dependsOn(`searcher`)
|
||||
|
||||
/* Note [Unmanaged Classpath]
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -0,0 +1,330 @@
|
||||
package org.enso.compiler.context
|
||||
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.pass.resolve.{DocumentationComments, TypeSignatures}
|
||||
import org.enso.searcher.Suggestion
|
||||
import org.enso.syntax.text.Location
|
||||
|
||||
import scala.collection.immutable.VectorBuilder
|
||||
import scala.collection.mutable
|
||||
|
||||
/** Module that extracts [[Suggestion]] entries from the [[IR]]. */
|
||||
final class SuggestionBuilder {
|
||||
|
||||
import SuggestionBuilder._
|
||||
|
||||
/** Build suggestions from the given `ir`.
|
||||
*
|
||||
* @param ir the input `IR`
|
||||
* @return the list of suggestion entries extracted from the given `IR`
|
||||
*/
|
||||
def build(ir: IR.Module): Vector[Suggestion] = {
|
||||
@scala.annotation.tailrec
|
||||
def go(
|
||||
scope: Scope,
|
||||
scopes: mutable.Queue[Scope],
|
||||
acc: mutable.Builder[Suggestion, Vector[Suggestion]]
|
||||
): Vector[Suggestion] =
|
||||
if (scope.queue.isEmpty) {
|
||||
if (scopes.isEmpty) {
|
||||
acc.result()
|
||||
} else {
|
||||
val scope = scopes.dequeue()
|
||||
go(scope, scopes, acc)
|
||||
}
|
||||
} else {
|
||||
val ir = scope.queue.dequeue()
|
||||
val doc = ir.getMetadata(DocumentationComments).map(_.documentation)
|
||||
ir match {
|
||||
case IR.Module.Scope.Definition.Method
|
||||
.Explicit(
|
||||
IR.Name.MethodReference(typePtr, methodName, _, _, _),
|
||||
IR.Function.Lambda(args, body, _, _, _, _),
|
||||
_,
|
||||
_,
|
||||
_
|
||||
) =>
|
||||
val typeSignature = ir.getMetadata(TypeSignatures)
|
||||
acc += buildMethod(methodName, typePtr, args, doc, typeSignature)
|
||||
scopes += Scope(body.children, body.location.map(_.location))
|
||||
go(scope, scopes, acc)
|
||||
case IR.Expression.Binding(
|
||||
name,
|
||||
IR.Function.Lambda(args, body, _, _, _, _),
|
||||
_,
|
||||
_,
|
||||
_
|
||||
) if name.location.isDefined =>
|
||||
val typeSignature = ir.getMetadata(TypeSignatures)
|
||||
acc += buildFunction(name, args, scope.location.get, typeSignature)
|
||||
scopes += Scope(body.children, body.location.map(_.location))
|
||||
go(scope, scopes, acc)
|
||||
case IR.Expression.Binding(name, expr, _, _, _)
|
||||
if name.location.isDefined =>
|
||||
val typeSignature = ir.getMetadata(TypeSignatures)
|
||||
acc += buildLocal(name.name, scope.location.get, typeSignature)
|
||||
scopes += Scope(expr.children, expr.location.map(_.location))
|
||||
go(scope, scopes, acc)
|
||||
case IR.Module.Scope.Definition.Atom(name, arguments, _, _, _) =>
|
||||
acc += buildAtom(name.name, arguments, doc)
|
||||
go(scope, scopes, acc)
|
||||
case _ =>
|
||||
go(scope, scopes, acc)
|
||||
}
|
||||
}
|
||||
|
||||
go(
|
||||
Scope(ir.children, ir.location.map(_.location)),
|
||||
mutable.Queue(),
|
||||
new VectorBuilder()
|
||||
)
|
||||
}
|
||||
|
||||
private def buildMethod(
|
||||
name: IR.Name,
|
||||
typeRef: Seq[IR.Name],
|
||||
args: Seq[IR.DefinitionArgument],
|
||||
doc: Option[String],
|
||||
typeSignature: Option[TypeSignatures.Metadata]
|
||||
): Suggestion.Method = {
|
||||
typeSignature match {
|
||||
case Some(TypeSignatures.Signature(typeExpr)) =>
|
||||
val selfType = buildSelfType(typeRef)
|
||||
val typeSig = buildTypeSignature(typeExpr)
|
||||
val (methodArgs, returnTypeDef) =
|
||||
buildMethodArguments(args, typeSig, selfType)
|
||||
Suggestion.Method(
|
||||
name = name.name,
|
||||
arguments = methodArgs,
|
||||
selfType = selfType,
|
||||
returnType = buildReturnType(returnTypeDef),
|
||||
documentation = doc
|
||||
)
|
||||
case _ =>
|
||||
Suggestion.Method(
|
||||
name = name.name,
|
||||
arguments = args.map(buildArgument),
|
||||
selfType = buildSelfType(typeRef),
|
||||
returnType = Any,
|
||||
documentation = doc
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private def buildFunction(
|
||||
name: IR.Name,
|
||||
args: Seq[IR.DefinitionArgument],
|
||||
location: Location,
|
||||
typeSignature: Option[TypeSignatures.Metadata]
|
||||
): Suggestion.Function = {
|
||||
typeSignature match {
|
||||
case Some(TypeSignatures.Signature(typeExpr)) =>
|
||||
val typeSig = buildTypeSignature(typeExpr)
|
||||
val (methodArgs, returnTypeDef) =
|
||||
buildFunctionArguments(args, typeSig)
|
||||
Suggestion.Function(
|
||||
name = name.name,
|
||||
arguments = methodArgs,
|
||||
returnType = buildReturnType(returnTypeDef),
|
||||
scope = buildScope(location)
|
||||
)
|
||||
case _ =>
|
||||
Suggestion.Function(
|
||||
name = name.name,
|
||||
arguments = args.map(buildArgument),
|
||||
returnType = Any,
|
||||
scope = buildScope(location)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private def buildLocal(
|
||||
name: String,
|
||||
location: Location,
|
||||
typeSignature: Option[TypeSignatures.Metadata]
|
||||
): Suggestion.Local =
|
||||
typeSignature match {
|
||||
case Some(TypeSignatures.Signature(tname: IR.Name)) =>
|
||||
Suggestion.Local(name, tname.name, buildScope(location))
|
||||
case _ =>
|
||||
Suggestion.Local(name, Any, buildScope(location))
|
||||
}
|
||||
|
||||
private def buildAtom(
|
||||
name: String,
|
||||
arguments: Seq[IR.DefinitionArgument],
|
||||
doc: Option[String]
|
||||
): Suggestion.Atom =
|
||||
Suggestion.Atom(
|
||||
name = name,
|
||||
arguments = arguments.map(buildArgument),
|
||||
returnType = name,
|
||||
documentation = doc
|
||||
)
|
||||
|
||||
private def buildTypeSignature(typeExpr: IR.Expression): Vector[TypeArg] = {
|
||||
@scala.annotation.tailrec
|
||||
def go(typeExpr: IR.Expression, args: Vector[TypeArg]): Vector[TypeArg] =
|
||||
typeExpr match {
|
||||
case IR.Function.Lambda(List(targ), body, _, _, _, _) =>
|
||||
val tdef = TypeArg(targ.name.name, targ.suspended)
|
||||
go(body, args :+ tdef)
|
||||
case tname: IR.Name =>
|
||||
args :+ TypeArg(tname.name, isSuspended = false)
|
||||
case _ =>
|
||||
args
|
||||
}
|
||||
|
||||
go(typeExpr, Vector())
|
||||
}
|
||||
|
||||
private def buildMethodArguments(
|
||||
vargs: Seq[IR.DefinitionArgument],
|
||||
targs: Seq[TypeArg],
|
||||
selfType: String
|
||||
): (Seq[Suggestion.Argument], Option[TypeArg]) = {
|
||||
@scala.annotation.tailrec
|
||||
def go(
|
||||
vargs: Seq[IR.DefinitionArgument],
|
||||
targs: Seq[TypeArg],
|
||||
acc: Vector[Suggestion.Argument]
|
||||
): (Vector[Suggestion.Argument], Option[TypeArg]) =
|
||||
if (vargs.isEmpty) {
|
||||
(acc, targs.lastOption)
|
||||
} else {
|
||||
vargs match {
|
||||
case IR.DefinitionArgument.Specified(
|
||||
name: IR.Name.This,
|
||||
defaultValue,
|
||||
suspended,
|
||||
_,
|
||||
_,
|
||||
_
|
||||
) +: vtail =>
|
||||
val thisArg = Suggestion.Argument(
|
||||
name = name.name,
|
||||
reprType = selfType,
|
||||
isSuspended = suspended,
|
||||
hasDefault = defaultValue.isDefined,
|
||||
defaultValue = defaultValue.flatMap(buildDefaultValue)
|
||||
)
|
||||
go(vtail, targs, acc :+ thisArg)
|
||||
case varg +: vtail =>
|
||||
targs match {
|
||||
case targ +: ttail =>
|
||||
go(vtail, ttail, acc :+ buildTypedArgument(varg, targ))
|
||||
case _ =>
|
||||
go(vtail, targs, acc :+ buildArgument(varg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go(vargs, targs, Vector())
|
||||
}
|
||||
|
||||
private def buildFunctionArguments(
|
||||
vargs: Seq[IR.DefinitionArgument],
|
||||
targs: Seq[TypeArg]
|
||||
): (Seq[Suggestion.Argument], Option[TypeArg]) = {
|
||||
@scala.annotation.tailrec
|
||||
def go(
|
||||
vargs: Seq[IR.DefinitionArgument],
|
||||
targs: Seq[TypeArg],
|
||||
acc: Vector[Suggestion.Argument]
|
||||
): (Seq[Suggestion.Argument], Option[TypeArg]) =
|
||||
if (vargs.isEmpty) {
|
||||
(acc, targs.lastOption)
|
||||
} else {
|
||||
vargs match {
|
||||
case varg +: vtail =>
|
||||
targs match {
|
||||
case targ +: ttail =>
|
||||
go(vtail, ttail, acc :+ buildTypedArgument(varg, targ))
|
||||
case _ =>
|
||||
go(vtail, targs, acc :+ buildArgument(varg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go(vargs, targs, Vector())
|
||||
}
|
||||
|
||||
private def buildTypedArgument(
|
||||
varg: IR.DefinitionArgument,
|
||||
targ: TypeArg
|
||||
): Suggestion.Argument =
|
||||
Suggestion.Argument(
|
||||
name = varg.name.name,
|
||||
reprType = targ.name,
|
||||
isSuspended = targ.isSuspended,
|
||||
hasDefault = varg.defaultValue.isDefined,
|
||||
defaultValue = varg.defaultValue.flatMap(buildDefaultValue)
|
||||
)
|
||||
|
||||
private def buildArgument(arg: IR.DefinitionArgument): Suggestion.Argument =
|
||||
Suggestion.Argument(
|
||||
name = arg.name.name,
|
||||
reprType = Any,
|
||||
isSuspended = arg.suspended,
|
||||
hasDefault = arg.defaultValue.isDefined,
|
||||
defaultValue = arg.defaultValue.flatMap(buildDefaultValue)
|
||||
)
|
||||
|
||||
def buildArgument(
|
||||
varg: IR.DefinitionArgument,
|
||||
targ: Option[TypeArg]
|
||||
): Suggestion.Argument =
|
||||
Suggestion.Argument(
|
||||
name = varg.name.name,
|
||||
reprType = targ.fold(Any)(_.name),
|
||||
isSuspended = targ.fold(varg.suspended)(_.isSuspended),
|
||||
hasDefault = varg.defaultValue.isDefined,
|
||||
defaultValue = varg.defaultValue.flatMap(buildDefaultValue)
|
||||
)
|
||||
|
||||
private def buildReturnType(typeDef: Option[TypeArg]): String =
|
||||
typeDef match {
|
||||
case Some(TypeArg(name, _)) => name
|
||||
case None => Any
|
||||
}
|
||||
|
||||
private def buildSelfType(ref: Seq[IR.Name]): String =
|
||||
ref.map(_.name).mkString(".")
|
||||
|
||||
private def buildDefaultValue(expr: IR): Option[String] =
|
||||
expr match {
|
||||
case IR.Literal.Number(value, _, _, _) => Some(value)
|
||||
case IR.Literal.Text(text, _, _, _) => Some(text)
|
||||
case _ => None
|
||||
}
|
||||
|
||||
private def buildScope(location: Location): Suggestion.Scope =
|
||||
Suggestion.Scope(location.start, location.end)
|
||||
}
|
||||
|
||||
object SuggestionBuilder {
|
||||
|
||||
/** A single level of an `IR`.
|
||||
*
|
||||
* @param queue the nodes in the scope
|
||||
* @param location the scope location
|
||||
*/
|
||||
private case class Scope(queue: mutable.Queue[IR], location: Option[Location])
|
||||
|
||||
private object Scope {
|
||||
|
||||
/** Create new scope from the list of items. */
|
||||
def apply(items: Seq[IR], location: Option[Location]): Scope =
|
||||
new Scope(mutable.Queue(items: _*), location)
|
||||
}
|
||||
|
||||
/** Type of the argument.
|
||||
*
|
||||
* @param name the name of the type
|
||||
* @param isSuspended is the argument lazy
|
||||
*/
|
||||
private case class TypeArg(name: String, isSuspended: Boolean)
|
||||
|
||||
private val Any: String = "Any"
|
||||
|
||||
}
|
@ -0,0 +1,399 @@
|
||||
package org.enso.compiler.test.context
|
||||
|
||||
import org.enso.compiler.Passes
|
||||
import org.enso.compiler.context.{
|
||||
FreshNameSupply,
|
||||
ModuleContext,
|
||||
SuggestionBuilder
|
||||
}
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.pass.PassManager
|
||||
import org.enso.compiler.test.CompilerTest
|
||||
import org.enso.searcher.Suggestion
|
||||
|
||||
class SuggestionBuilderTest extends CompilerTest {
|
||||
|
||||
implicit val passManager: PassManager = new Passes().passManager
|
||||
|
||||
"SuggestionBuilder" should {
|
||||
|
||||
"build method without explicit arguments" in {
|
||||
implicit val moduleContext: ModuleContext = freshModuleContext
|
||||
|
||||
val code = """foo = 42""".stripMargin
|
||||
val module = code.preprocessModule
|
||||
|
||||
build(module) should contain theSameElementsAs Seq(
|
||||
Suggestion.Method(
|
||||
name = "foo",
|
||||
arguments = Seq(
|
||||
Suggestion.Argument("this", "Any", false, false, None)
|
||||
),
|
||||
selfType = "here",
|
||||
returnType = "Any",
|
||||
documentation = None
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
"build method with documentation" in {
|
||||
pending // fix documentation
|
||||
implicit val moduleContext: ModuleContext = freshModuleContext
|
||||
|
||||
val code =
|
||||
"""## The foo
|
||||
|foo = 42""".stripMargin
|
||||
val module = code.preprocessModule
|
||||
|
||||
build(module) should contain theSameElementsAs Seq(
|
||||
Suggestion.Method(
|
||||
name = "foo",
|
||||
arguments = Seq(
|
||||
Suggestion.Argument("this", "Any", false, false, None)
|
||||
),
|
||||
selfType = "here",
|
||||
returnType = "Any",
|
||||
documentation = Some(" The foo")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
"build method with arguments" in {
|
||||
implicit val moduleContext: ModuleContext = freshModuleContext
|
||||
|
||||
val code =
|
||||
"""foo a b =
|
||||
| x : Number
|
||||
| x = a + 1
|
||||
| y = b - 2
|
||||
| x * y""".stripMargin
|
||||
val module = code.preprocessModule
|
||||
|
||||
build(module) should contain theSameElementsAs Seq(
|
||||
Suggestion.Method(
|
||||
name = "foo",
|
||||
arguments = Seq(
|
||||
Suggestion.Argument("this", "Any", false, false, None),
|
||||
Suggestion.Argument("a", "Any", false, false, None),
|
||||
Suggestion.Argument("b", "Any", false, false, None)
|
||||
),
|
||||
selfType = "here",
|
||||
returnType = "Any",
|
||||
documentation = None
|
||||
),
|
||||
Suggestion.Local("x", "Number", Suggestion.Scope(9, 62)),
|
||||
Suggestion.Local("y", "Any", Suggestion.Scope(9, 62))
|
||||
)
|
||||
}
|
||||
|
||||
"build method with default arguments" in {
|
||||
implicit val moduleContext: ModuleContext = freshModuleContext
|
||||
|
||||
val code =
|
||||
"""foo (a = 0) = a + 1""".stripMargin
|
||||
val module = code.preprocessModule
|
||||
|
||||
build(module) should contain theSameElementsAs Seq(
|
||||
Suggestion.Method(
|
||||
name = "foo",
|
||||
arguments = Seq(
|
||||
Suggestion.Argument("this", "Any", false, false, None),
|
||||
Suggestion.Argument("a", "Any", false, true, Some("0"))
|
||||
),
|
||||
selfType = "here",
|
||||
returnType = "Any",
|
||||
documentation = None
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
"build method with associated type signature" in {
|
||||
implicit val moduleContext: ModuleContext = freshModuleContext
|
||||
|
||||
val code =
|
||||
"""
|
||||
|MyAtom.bar : Number -> Number -> Number
|
||||
|MyAtom.bar a b = a + b
|
||||
|""".stripMargin
|
||||
val module = code.preprocessModule
|
||||
|
||||
build(module) should contain theSameElementsAs Seq(
|
||||
Suggestion.Method(
|
||||
name = "bar",
|
||||
arguments = Seq(
|
||||
Suggestion.Argument("this", "MyAtom", false, false, None),
|
||||
Suggestion.Argument("a", "Number", false, false, None),
|
||||
Suggestion.Argument("b", "Number", false, false, None)
|
||||
),
|
||||
selfType = "MyAtom",
|
||||
returnType = "Number",
|
||||
documentation = None
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
"build method with lazy arguments" in {
|
||||
implicit val moduleContext: ModuleContext = freshModuleContext
|
||||
|
||||
val code =
|
||||
"""foo ~a = a + 1""".stripMargin
|
||||
val module = code.preprocessModule
|
||||
|
||||
build(module) should contain theSameElementsAs Seq(
|
||||
Suggestion.Method(
|
||||
name = "foo",
|
||||
arguments = Seq(
|
||||
Suggestion.Argument("this", "Any", false, false, None),
|
||||
Suggestion.Argument("a", "Any", true, false, None)
|
||||
),
|
||||
selfType = "here",
|
||||
returnType = "Any",
|
||||
documentation = None
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
"build function" in {
|
||||
implicit val moduleContext: ModuleContext = freshModuleContext
|
||||
|
||||
val code =
|
||||
"""main =
|
||||
| foo a = a + 1
|
||||
| foo 42""".stripMargin
|
||||
val module = code.preprocessModule
|
||||
|
||||
build(module) should contain theSameElementsAs Seq(
|
||||
Suggestion.Method(
|
||||
name = "main",
|
||||
arguments = Seq(
|
||||
Suggestion.Argument("this", "Any", false, false, None)
|
||||
),
|
||||
selfType = "here",
|
||||
returnType = "Any",
|
||||
documentation = None
|
||||
),
|
||||
Suggestion.Function(
|
||||
name = "foo",
|
||||
arguments = Seq(
|
||||
Suggestion.Argument("a", "Any", false, false, None)
|
||||
),
|
||||
returnType = "Any",
|
||||
scope = Suggestion.Scope(6, 35)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
"build function with associated type signature" in {
|
||||
implicit val moduleContext: ModuleContext = freshModuleContext
|
||||
|
||||
val code =
|
||||
"""main =
|
||||
| foo : Number -> Number
|
||||
| foo a = a + 1
|
||||
| foo 42""".stripMargin
|
||||
val module = code.preprocessModule
|
||||
|
||||
build(module) should contain theSameElementsAs Seq(
|
||||
Suggestion.Method(
|
||||
name = "main",
|
||||
arguments = Seq(
|
||||
Suggestion.Argument("this", "Any", false, false, None)
|
||||
),
|
||||
selfType = "here",
|
||||
returnType = "Any",
|
||||
documentation = None
|
||||
),
|
||||
Suggestion.Function(
|
||||
name = "foo",
|
||||
arguments = Seq(
|
||||
Suggestion.Argument("a", "Number", false, false, None)
|
||||
),
|
||||
returnType = "Number",
|
||||
scope = Suggestion.Scope(6, 62)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
"build atom simple" in {
|
||||
implicit val moduleContext: ModuleContext = freshModuleContext
|
||||
|
||||
val code = """type MyType a b"""
|
||||
val module = code.preprocessModule
|
||||
|
||||
build(module) should contain theSameElementsAs Seq(
|
||||
Suggestion.Atom(
|
||||
name = "MyType",
|
||||
arguments = Seq(
|
||||
Suggestion.Argument("a", "Any", false, false, None),
|
||||
Suggestion.Argument("b", "Any", false, false, None)
|
||||
),
|
||||
returnType = "MyType",
|
||||
documentation = None
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
"build atom with documentation" in {
|
||||
implicit val moduleContext: ModuleContext = freshModuleContext
|
||||
|
||||
val code =
|
||||
"""## My sweet type
|
||||
|type MyType a b""".stripMargin
|
||||
val module = code.preprocessModule
|
||||
|
||||
build(module) should contain theSameElementsAs Seq(
|
||||
Suggestion.Atom(
|
||||
name = "MyType",
|
||||
arguments = Seq(
|
||||
Suggestion.Argument("a", "Any", false, false, None),
|
||||
Suggestion.Argument("b", "Any", false, false, None)
|
||||
),
|
||||
returnType = "MyType",
|
||||
documentation = Some(" My sweet type")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
"build type simple" in {
|
||||
implicit val moduleContext: ModuleContext = freshModuleContext
|
||||
|
||||
val code =
|
||||
"""type Maybe
|
||||
| type Nothing
|
||||
| type Just a""".stripMargin
|
||||
val module = code.preprocessModule
|
||||
|
||||
build(module) should contain theSameElementsAs Seq(
|
||||
Suggestion.Atom(
|
||||
name = "Nothing",
|
||||
arguments = Seq(),
|
||||
returnType = "Nothing",
|
||||
documentation = None
|
||||
),
|
||||
Suggestion.Atom(
|
||||
name = "Just",
|
||||
arguments = Seq(
|
||||
Suggestion.Argument("a", "Any", false, false, None)
|
||||
),
|
||||
returnType = "Just",
|
||||
documentation = None
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
"build type with documentation" in {
|
||||
implicit val moduleContext: ModuleContext = freshModuleContext
|
||||
|
||||
val code =
|
||||
"""## When in doubt
|
||||
|type Maybe
|
||||
| ## Nothing here
|
||||
| type Nothing
|
||||
| ## Something there
|
||||
| type Just a""".stripMargin
|
||||
val module = code.preprocessModule
|
||||
|
||||
build(module) should contain theSameElementsAs Seq(
|
||||
Suggestion.Atom(
|
||||
name = "Nothing",
|
||||
arguments = Seq(),
|
||||
returnType = "Nothing",
|
||||
documentation = Some(" Nothing here")
|
||||
),
|
||||
Suggestion.Atom(
|
||||
name = "Just",
|
||||
arguments = Seq(
|
||||
Suggestion.Argument("a", "Any", false, false, None)
|
||||
),
|
||||
returnType = "Just",
|
||||
documentation = Some(" Something there")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
"build type with methods" in {
|
||||
implicit val moduleContext: ModuleContext = freshModuleContext
|
||||
val code =
|
||||
"""type Maybe
|
||||
| type Nothing
|
||||
| type Just a
|
||||
|
|
||||
| map f = case this of
|
||||
| Just a -> Just (f a)
|
||||
| Nothing -> Nothing""".stripMargin
|
||||
val module = code.preprocessModule
|
||||
|
||||
build(module) should contain theSameElementsAs Seq(
|
||||
Suggestion.Atom(
|
||||
name = "Nothing",
|
||||
arguments = Seq(),
|
||||
returnType = "Nothing",
|
||||
documentation = None
|
||||
),
|
||||
Suggestion.Atom(
|
||||
name = "Just",
|
||||
arguments = Seq(
|
||||
Suggestion.Argument("a", "Any", false, false, None)
|
||||
),
|
||||
returnType = "Just",
|
||||
documentation = None
|
||||
),
|
||||
Suggestion.Method(
|
||||
name = "map",
|
||||
arguments = Seq(
|
||||
Suggestion.Argument("this", "Any", false, false, None),
|
||||
Suggestion.Argument("f", "Any", false, false, None)
|
||||
),
|
||||
selfType = "Just",
|
||||
returnType = "Any",
|
||||
documentation = None
|
||||
),
|
||||
Suggestion.Method(
|
||||
name = "map",
|
||||
arguments = Seq(
|
||||
Suggestion.Argument("this", "Any", false, false, None),
|
||||
Suggestion.Argument("f", "Any", false, false, None)
|
||||
),
|
||||
selfType = "Nothing",
|
||||
returnType = "Any",
|
||||
documentation = None
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
"build type with methods with type signature" in {
|
||||
implicit val moduleContext: ModuleContext = freshModuleContext
|
||||
val code =
|
||||
"""type MyType
|
||||
| type MyAtom
|
||||
|
|
||||
| is_atom : this -> Boolean
|
||||
| is_atom = true""".stripMargin
|
||||
val module = code.preprocessModule
|
||||
|
||||
build(module) should contain theSameElementsAs Seq(
|
||||
Suggestion.Atom(
|
||||
name = "MyAtom",
|
||||
arguments = Seq(),
|
||||
returnType = "MyAtom",
|
||||
documentation = None
|
||||
),
|
||||
Suggestion.Method(
|
||||
name = "is_atom",
|
||||
arguments = Seq(
|
||||
Suggestion.Argument("this", "MyAtom", false, false, None)
|
||||
),
|
||||
selfType = "MyAtom",
|
||||
returnType = "Boolean",
|
||||
documentation = None
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private def build(ir: IR.Module): Vector[Suggestion] =
|
||||
new SuggestionBuilder().build(ir)
|
||||
|
||||
private def freshModuleContext: ModuleContext =
|
||||
ModuleContext(freshNameSupply = Some(new FreshNameSupply))
|
||||
}
|
8
lib/searcher/src/main/resources/application.conf
Normal file
8
lib/searcher/src/main/resources/application.conf
Normal file
@ -0,0 +1,8 @@
|
||||
searcher {
|
||||
db {
|
||||
url = "jdbc:sqlite:searcher.db"
|
||||
driver = org.sqlite.JDBC
|
||||
connectionPool = disabled
|
||||
keepAliveConnection = true
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package org.enso.searcher
|
||||
|
||||
/** A search suggestion. */
|
||||
sealed trait Suggestion
|
||||
object Suggestion {
|
||||
|
||||
/** An argument of an atom or a function.
|
||||
*
|
||||
* @param name the argument name
|
||||
* @param reprType the type of the argument
|
||||
* @param isSuspended is the argument lazy
|
||||
* @param hasDefault does the argument have a default
|
||||
* @param defaultValue optional default value
|
||||
*/
|
||||
case class Argument(
|
||||
name: String,
|
||||
reprType: String,
|
||||
isSuspended: Boolean,
|
||||
hasDefault: Boolean,
|
||||
defaultValue: Option[String]
|
||||
)
|
||||
|
||||
/** The definition scope.
|
||||
* @param start the start of the definition scope
|
||||
* @param end the end of the definition scope
|
||||
*/
|
||||
case class Scope(start: Int, end: Int)
|
||||
|
||||
/** A value constructor.
|
||||
*
|
||||
* @param name the atom name
|
||||
* @param arguments the list of arguments
|
||||
* @param returnType the type of an atom
|
||||
* @param documentation the documentation string
|
||||
*/
|
||||
case class Atom(
|
||||
name: String,
|
||||
arguments: Seq[Argument],
|
||||
returnType: String,
|
||||
documentation: Option[String]
|
||||
) extends Suggestion
|
||||
|
||||
/** A function defined on a type or a module.
|
||||
*
|
||||
* @param name the method name
|
||||
* @param arguments the function arguments
|
||||
* @param selfType the self type of a method
|
||||
* @param returnType the return type of a method
|
||||
* @param documentation the documentation string
|
||||
*/
|
||||
case class Method(
|
||||
name: String,
|
||||
arguments: Seq[Argument],
|
||||
selfType: String,
|
||||
returnType: String,
|
||||
documentation: Option[String]
|
||||
) extends Suggestion
|
||||
|
||||
/** A local function definition.
|
||||
*
|
||||
* @param name the function name
|
||||
* @param arguments the function arguments
|
||||
* @param returnType the return type of a function
|
||||
* @param scope the scope where the function is defined
|
||||
*/
|
||||
case class Function(
|
||||
name: String,
|
||||
arguments: Seq[Argument],
|
||||
returnType: String,
|
||||
scope: Scope
|
||||
) extends Suggestion
|
||||
|
||||
/** A local value.
|
||||
*
|
||||
* @param name the name of a value
|
||||
* @param returnType the type of a local value
|
||||
* @param scope the scope where the value is defined
|
||||
*/
|
||||
case class Local(name: String, returnType: String, scope: Scope)
|
||||
extends Suggestion
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package org.enso.searcher.sql
|
||||
|
||||
import org.enso.searcher.Suggestion
|
||||
|
||||
/** The object for accessing the suggestions database. */
|
||||
trait SuggestionsRepo[F[_]] {
|
||||
|
||||
/** Find suggestions by the return type.
|
||||
*
|
||||
* @param returnType the return type of a suggestion
|
||||
* @return the list of suggestions
|
||||
*/
|
||||
def findBy(returnType: String): F[Seq[Suggestion]]
|
||||
|
||||
/** Select the suggestion by id.
|
||||
*
|
||||
* @param id the id of a suggestion
|
||||
* @return return the suggestion
|
||||
*/
|
||||
def select(id: Long): F[Option[Suggestion]]
|
||||
|
||||
/** Insert the suggestion
|
||||
*
|
||||
* @param suggestion the suggestion to insert
|
||||
* @return the id of an inserted suggestion
|
||||
*/
|
||||
def insert(suggestion: Suggestion): F[Long]
|
||||
}
|
@ -0,0 +1,177 @@
|
||||
package org.enso.searcher.sql
|
||||
|
||||
import org.enso.searcher.Suggestion
|
||||
import slick.jdbc.SQLiteProfile.api._
|
||||
|
||||
import scala.concurrent.ExecutionContext
|
||||
|
||||
/** The object for accessing the suggestions database. */
|
||||
final class SqlSuggestionsRepo(implicit ec: ExecutionContext)
|
||||
extends SuggestionsRepo[DBIO] {
|
||||
|
||||
/** The query returning the arguments joined with the corresponding
|
||||
* suggestions. */
|
||||
private val joined: Query[
|
||||
(Rep[Option[ArgumentsTable]], SuggestionsTable),
|
||||
(Option[ArgumentRow], SuggestionRow),
|
||||
Seq
|
||||
] =
|
||||
arguments
|
||||
.joinRight(suggestions)
|
||||
.on(_.suggestionId === _.id)
|
||||
|
||||
/** @inheritdoc **/
|
||||
override def findBy(returnType: String): DBIO[Seq[Suggestion]] = {
|
||||
val query = for {
|
||||
(argument, suggestion) <- joined
|
||||
if suggestion.returnType === returnType
|
||||
} yield (argument, suggestion)
|
||||
query.result.map(joinedToSuggestion)
|
||||
}
|
||||
|
||||
/** @inheritdoc **/
|
||||
override def select(id: Long): DBIO[Option[Suggestion]] = {
|
||||
val query = for {
|
||||
(argument, suggestion) <- joined
|
||||
if suggestion.id === id
|
||||
} yield (argument, suggestion)
|
||||
query.result.map(coll => joinedToSuggestion(coll).headOption)
|
||||
}
|
||||
|
||||
/** @inheritdoc **/
|
||||
override def insert(suggestion: Suggestion): DBIO[Long] = {
|
||||
val (suggestionRow, args) = toSuggestionRow(suggestion)
|
||||
for {
|
||||
id <- suggestions.returning(suggestions.map(_.id)) += suggestionRow
|
||||
_ <- arguments ++= args.map(toArgumentRow(id, _))
|
||||
} yield id
|
||||
}
|
||||
|
||||
private def joinedToSuggestion(
|
||||
coll: Seq[(Option[ArgumentRow], SuggestionRow)]
|
||||
): Seq[Suggestion] = {
|
||||
coll
|
||||
.groupBy(_._2)
|
||||
.view
|
||||
.mapValues(_.flatMap(_._1))
|
||||
.map(Function.tupled(toSuggestion))
|
||||
.toSeq
|
||||
}
|
||||
|
||||
private def toSuggestionRow(
|
||||
suggestion: Suggestion
|
||||
): (SuggestionRow, Seq[Suggestion.Argument]) =
|
||||
suggestion match {
|
||||
case Suggestion.Atom(name, args, returnType, doc) =>
|
||||
val row = SuggestionRow(
|
||||
id = None,
|
||||
kind = SuggestionKind.ATOM,
|
||||
name = name,
|
||||
selfType = None,
|
||||
returnType = returnType,
|
||||
documentation = doc,
|
||||
scopeStart = None,
|
||||
scopeEnd = None
|
||||
)
|
||||
row -> args
|
||||
case Suggestion.Method(name, args, selfType, returnType, doc) =>
|
||||
val row = SuggestionRow(
|
||||
id = None,
|
||||
kind = SuggestionKind.METHOD,
|
||||
name = name,
|
||||
selfType = Some(selfType),
|
||||
returnType = returnType,
|
||||
documentation = doc,
|
||||
scopeStart = None,
|
||||
scopeEnd = None
|
||||
)
|
||||
row -> args
|
||||
case Suggestion.Function(name, args, returnType, scope) =>
|
||||
val row = SuggestionRow(
|
||||
id = None,
|
||||
kind = SuggestionKind.FUNCTION,
|
||||
name = name,
|
||||
selfType = None,
|
||||
returnType = returnType,
|
||||
documentation = None,
|
||||
scopeStart = Some(scope.start),
|
||||
scopeEnd = Some(scope.end)
|
||||
)
|
||||
row -> args
|
||||
case Suggestion.Local(name, returnType, scope) =>
|
||||
val row = SuggestionRow(
|
||||
id = None,
|
||||
kind = SuggestionKind.LOCAL,
|
||||
name = name,
|
||||
selfType = None,
|
||||
returnType = returnType,
|
||||
documentation = None,
|
||||
scopeStart = Some(scope.start),
|
||||
scopeEnd = Some(scope.end)
|
||||
)
|
||||
row -> Seq()
|
||||
}
|
||||
|
||||
private def toArgumentRow(
|
||||
suggestionId: Long,
|
||||
argument: Suggestion.Argument
|
||||
): ArgumentRow =
|
||||
ArgumentRow(
|
||||
id = None,
|
||||
suggestionId = suggestionId,
|
||||
name = argument.name,
|
||||
tpe = argument.reprType,
|
||||
isSuspended = argument.isSuspended,
|
||||
hasDefault = argument.hasDefault,
|
||||
defaultValue = argument.defaultValue
|
||||
)
|
||||
|
||||
private def toSuggestion(
|
||||
suggestion: SuggestionRow,
|
||||
arguments: Seq[ArgumentRow]
|
||||
): Suggestion =
|
||||
suggestion.kind match {
|
||||
case SuggestionKind.ATOM =>
|
||||
Suggestion.Atom(
|
||||
name = suggestion.name,
|
||||
arguments = arguments.map(toArgument),
|
||||
returnType = suggestion.returnType,
|
||||
documentation = suggestion.documentation
|
||||
)
|
||||
case SuggestionKind.METHOD =>
|
||||
Suggestion.Method(
|
||||
name = suggestion.name,
|
||||
arguments = arguments.map(toArgument),
|
||||
selfType = suggestion.selfType.get,
|
||||
returnType = suggestion.returnType,
|
||||
documentation = suggestion.documentation
|
||||
)
|
||||
case SuggestionKind.FUNCTION =>
|
||||
Suggestion.Function(
|
||||
name = suggestion.name,
|
||||
arguments = arguments.map(toArgument),
|
||||
returnType = suggestion.returnType,
|
||||
scope =
|
||||
Suggestion.Scope(suggestion.scopeStart.get, suggestion.scopeEnd.get)
|
||||
)
|
||||
case SuggestionKind.LOCAL =>
|
||||
Suggestion.Local(
|
||||
name = suggestion.name,
|
||||
returnType = suggestion.returnType,
|
||||
scope =
|
||||
Suggestion.Scope(suggestion.scopeStart.get, suggestion.scopeEnd.get)
|
||||
)
|
||||
|
||||
case k =>
|
||||
throw new NoSuchElementException(s"Unknown suggestion kind: $k")
|
||||
}
|
||||
|
||||
private def toArgument(row: ArgumentRow): Suggestion.Argument =
|
||||
Suggestion.Argument(
|
||||
name = row.name,
|
||||
reprType = row.tpe,
|
||||
isSuspended = row.isSuspended,
|
||||
hasDefault = row.hasDefault,
|
||||
defaultValue = row.defaultValue
|
||||
)
|
||||
}
|
110
lib/searcher/src/main/scala/org/enso/searcher/sql/Tables.scala
Normal file
110
lib/searcher/src/main/scala/org/enso/searcher/sql/Tables.scala
Normal file
@ -0,0 +1,110 @@
|
||||
package org.enso.searcher.sql
|
||||
|
||||
import slick.jdbc.SQLiteProfile.api._
|
||||
|
||||
/** A row in the arguments table.
|
||||
*
|
||||
* @param id the id of an argument
|
||||
* @param suggestionId the id of the suggestion
|
||||
* @param name the argument name
|
||||
* @param tpe the argument type
|
||||
* @param isSuspended is the argument lazy
|
||||
* @param hasDefault does the argument have the default value
|
||||
* @param defaultValue optional default value
|
||||
*/
|
||||
case class ArgumentRow(
|
||||
id: Option[Long],
|
||||
suggestionId: Long,
|
||||
name: String,
|
||||
tpe: String,
|
||||
isSuspended: Boolean,
|
||||
hasDefault: Boolean,
|
||||
defaultValue: Option[String]
|
||||
)
|
||||
|
||||
/** A row in the suggestions table.
|
||||
*
|
||||
* @param id the id of a suggestion
|
||||
* @param kind the type of a suggestion
|
||||
* @param name the suggestion name
|
||||
* @param selfType the self type of a suggestion
|
||||
* @param returnType the return type of a suggestion
|
||||
* @param documentation the documentation string
|
||||
* @param scopeStart the start of the scope
|
||||
* @param scopeEnd the end of the scope
|
||||
*/
|
||||
case class SuggestionRow(
|
||||
id: Option[Long],
|
||||
kind: Byte,
|
||||
name: String,
|
||||
selfType: Option[String],
|
||||
returnType: String,
|
||||
documentation: Option[String],
|
||||
scopeStart: Option[Int],
|
||||
scopeEnd: Option[Int]
|
||||
)
|
||||
|
||||
/** The type of a suggestion. */
|
||||
object SuggestionKind {
|
||||
|
||||
val ATOM: Byte = 0
|
||||
val METHOD: Byte = 1
|
||||
val FUNCTION: Byte = 2
|
||||
val LOCAL: Byte = 3
|
||||
}
|
||||
|
||||
/** The schema of the arguments table. */
|
||||
final class ArgumentsTable(tag: Tag)
|
||||
extends Table[ArgumentRow](tag, "arguments") {
|
||||
|
||||
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
|
||||
def suggestionId = column[Long]("suggestion_id")
|
||||
def name = column[String]("name")
|
||||
def tpe = column[String]("type")
|
||||
def isSuspended = column[Boolean]("is_suspended", O.Default(false))
|
||||
def hasDefault = column[Boolean]("has_default", O.Default(false))
|
||||
def defaultValue = column[Option[String]]("default_value")
|
||||
def * =
|
||||
(id.?, suggestionId, name, tpe, isSuspended, hasDefault, defaultValue) <>
|
||||
(ArgumentRow.tupled, ArgumentRow.unapply)
|
||||
|
||||
def suggestion =
|
||||
foreignKey("suggestion_fk", suggestionId, suggestions)(
|
||||
_.id,
|
||||
onUpdate = ForeignKeyAction.Restrict,
|
||||
onDelete = ForeignKeyAction.Cascade
|
||||
)
|
||||
}
|
||||
|
||||
/** The schema of the suggestions table. */
|
||||
final class SuggestionsTable(tag: Tag)
|
||||
extends Table[SuggestionRow](tag, "suggestions") {
|
||||
|
||||
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
|
||||
def kind = column[Byte]("kind")
|
||||
def name = column[String]("name")
|
||||
def selfType = column[Option[String]]("self_type")
|
||||
def returnType = column[String]("return_type")
|
||||
def documentation = column[Option[String]]("documentation")
|
||||
def scopeStart = column[Option[Int]]("scope_start")
|
||||
def scopeEnd = column[Option[Int]]("scope_end")
|
||||
def * =
|
||||
(
|
||||
id.?,
|
||||
kind,
|
||||
name,
|
||||
selfType,
|
||||
returnType,
|
||||
documentation,
|
||||
scopeStart,
|
||||
scopeEnd
|
||||
) <>
|
||||
(SuggestionRow.tupled, SuggestionRow.unapply)
|
||||
|
||||
def selfTypeIdx = index("self_type_idx", selfType)
|
||||
def returnTypeIdx = index("return_type_idx", name)
|
||||
}
|
||||
|
||||
object arguments extends TableQuery(new ArgumentsTable(_))
|
||||
|
||||
object suggestions extends TableQuery(new SuggestionsTable(_))
|
3
lib/searcher/src/test/resources/application.conf
Normal file
3
lib/searcher/src/test/resources/application.conf
Normal file
@ -0,0 +1,3 @@
|
||||
searcher.db {
|
||||
url = "jdbc:sqlite:file::memory:?cache=shared"
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package org.enso.searcher.sql
|
||||
|
||||
import org.enso.searcher.Suggestion
|
||||
import org.scalatest.BeforeAndAfterAll
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
import org.scalatest.wordspec.AnyWordSpec
|
||||
import slick.jdbc.SQLiteProfile.api._
|
||||
|
||||
import scala.concurrent.Await
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class SuggestionsRepoTest
|
||||
extends AnyWordSpec
|
||||
with Matchers
|
||||
with BeforeAndAfterAll {
|
||||
|
||||
val Timeout: FiniteDuration = 3.seconds
|
||||
|
||||
val db = Database.forConfig("searcher.db")
|
||||
val repo = new SqlSuggestionsRepo()
|
||||
|
||||
override def beforeAll(): Unit = {
|
||||
Await.ready(
|
||||
db.run((suggestions.schema ++ arguments.schema).createIfNotExists),
|
||||
Timeout
|
||||
)
|
||||
}
|
||||
|
||||
override def afterAll(): Unit = {
|
||||
db.close()
|
||||
}
|
||||
|
||||
"SuggestionsDBIO" should {
|
||||
|
||||
"select suggestion by id" in {
|
||||
val action =
|
||||
for {
|
||||
id <- db.run(repo.insert(suggestion.atom))
|
||||
res <- db.run(repo.select(id))
|
||||
} yield res
|
||||
|
||||
Await.result(action, Timeout) shouldEqual Some(suggestion.atom)
|
||||
}
|
||||
|
||||
"find suggestion by returnType" in {
|
||||
val action =
|
||||
for {
|
||||
_ <- db.run(repo.insert(suggestion.local))
|
||||
_ <- db.run(repo.insert(suggestion.method))
|
||||
_ <- db.run(repo.insert(suggestion.function))
|
||||
res <- db.run(repo.findBy("MyType"))
|
||||
} yield res
|
||||
|
||||
Await.result(action, Timeout) should contain theSameElementsAs Seq(
|
||||
suggestion.local,
|
||||
suggestion.function
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object suggestion {
|
||||
|
||||
val atom: Suggestion.Atom =
|
||||
Suggestion.Atom(
|
||||
name = "Pair",
|
||||
arguments = Seq(
|
||||
Suggestion.Argument("a", "Any", false, false, None),
|
||||
Suggestion.Argument("b", "Any", false, false, None)
|
||||
),
|
||||
returnType = "Pair",
|
||||
documentation = Some("Awesome")
|
||||
)
|
||||
|
||||
val method: Suggestion.Method =
|
||||
Suggestion.Method(
|
||||
name = "main",
|
||||
arguments = Seq(),
|
||||
selfType = "Main",
|
||||
returnType = "IO",
|
||||
documentation = None
|
||||
)
|
||||
|
||||
val function: Suggestion.Function =
|
||||
Suggestion.Function(
|
||||
name = "bar",
|
||||
arguments = Seq(
|
||||
Suggestion.Argument("x", "Number", false, true, Some("0"))
|
||||
),
|
||||
returnType = "MyType",
|
||||
scope = Suggestion.Scope(5, 9)
|
||||
)
|
||||
|
||||
val local: Suggestion.Local =
|
||||
Suggestion.Local(
|
||||
name = "bazz",
|
||||
returnType = "MyType",
|
||||
scope = Suggestion.Scope(37, 84)
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user