mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 05:41:32 +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(graph)
|
||||||
.dependsOn(syntax.jvm)
|
.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 ===========================================================
|
// === Sub-Projects ===========================================================
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@ -746,6 +757,7 @@ lazy val runtime = (project in file("engine/runtime"))
|
|||||||
.dependsOn(graph)
|
.dependsOn(graph)
|
||||||
.dependsOn(`polyglot-api`)
|
.dependsOn(`polyglot-api`)
|
||||||
.dependsOn(`text-buffer`)
|
.dependsOn(`text-buffer`)
|
||||||
|
.dependsOn(`searcher`)
|
||||||
|
|
||||||
/* Note [Unmanaged Classpath]
|
/* 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