feat(language-server): Resolve paths for imports (#1079)

This commit is contained in:
Dima 2024-02-20 15:52:54 +03:00 committed by GitHub
parent e2b150a786
commit 245f6640f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 82 additions and 50 deletions

View File

@ -1,5 +1,7 @@
aqua A aqua A
import "aqua-src/gen/OneMore.aqua"
export main export main
alias SomeAlias: string alias SomeAlias: string

View File

@ -1,3 +1,5 @@
aqua One
service OneMore: service OneMore:
more_call() more_call()
consume(s: string) consume(s: string)

View File

@ -82,7 +82,7 @@ lazy val `language-server-api` = crossProject(JSPlatform, JVMPlatform)
"co.fs2" %%% "fs2-io" % fs2V "co.fs2" %%% "fs2-io" % fs2V
) )
) )
.dependsOn(compiler, io) .dependsOn(compiler, io, compiler % "test->test")
lazy val `language-server-apiJS` = `language-server-api`.js lazy val `language-server-apiJS` = `language-server-api`.js
.settings( .settings(

View File

@ -3,17 +3,18 @@ package aqua.compiler
import aqua.compiler.AquaError.* import aqua.compiler.AquaError.*
import aqua.linker.Linker import aqua.linker.Linker
import aqua.parser.{Ast, ParserError} import aqua.parser.{Ast, ParserError}
import aqua.semantics.header.Picker.setImportPaths
import aqua.semantics.header.{HeaderHandler, Picker} import aqua.semantics.header.{HeaderHandler, Picker}
import aqua.semantics.{SemanticError, Semantics} import aqua.semantics.{FileId, SemanticError, Semantics}
import cats.arrow.FunctionK import cats.arrow.FunctionK
import cats.data.* import cats.data.*
import cats.syntax.either.* import cats.syntax.either.*
import cats.syntax.functor.* import cats.syntax.functor.*
import cats.{Comonad, Monad, Monoid, Order, ~>} import cats.syntax.show.*
import cats.{~>, Comonad, Monad, Monoid, Order, Show}
import scribe.Logging import scribe.Logging
class AquaCompiler[F[_]: Monad, E, I: Order, S[_]: Comonad, C: Monoid: Picker]( class AquaCompiler[F[_]: Monad, E, I: FileId, S[_]: Comonad, C: Monoid: Picker](
headerHandler: HeaderHandler[S, C], headerHandler: HeaderHandler[S, C],
semantics: Semantics[S, C] semantics: Semantics[S, C]
) extends Logging { ) extends Logging {
@ -27,7 +28,7 @@ class AquaCompiler[F[_]: Monad, E, I: Order, S[_]: Comonad, C: Monoid: Picker](
// (Imports contexts => Compilation result) // (Imports contexts => Compilation result)
type TP = Map[String, C] => CompileRes[C] type TP = Map[String, C] => CompileRes[C]
private def transpile(body: Ast[S]): TP = private def transpile(body: Ast[S], importPaths: Map[String, String]): TP =
imports => imports =>
for { for {
// Process header, get initial context // Process header, get initial context
@ -43,7 +44,7 @@ class AquaCompiler[F[_]: Monad, E, I: Order, S[_]: Comonad, C: Monoid: Picker](
rc <- headerSem rc <- headerSem
.finCtx(processed) .finCtx(processed)
.toCompileRes .toCompileRes
} yield rc } yield rc.setImportPaths(importPaths)
def compileRaw( def compileRaw(
sources: AquaSources[F, E, I], sources: AquaSources[F, E, I],
@ -58,7 +59,10 @@ class AquaCompiler[F[_]: Monad, E, I: Order, S[_]: Comonad, C: Monoid: Picker](
// Lift resolution to CompileRes // Lift resolution to CompileRes
modules <- resolution.toEitherT[CompileWarns] modules <- resolution.toEitherT[CompileWarns]
// Generate transpilation functions for each module // Generate transpilation functions for each module
transpiled = modules.map(body => transpile(body)) transpiled = modules.map { m =>
val importPaths = m.imports.view.mapValues(_.show).toMap
m.copy(body = transpile(m.body, importPaths))
}
// Link modules // Link modules
linked <- Linker.link(transpiled, CycleError.apply) linked <- Linker.link(transpiled, CycleError.apply)
} yield linked } yield linked

View File

@ -5,21 +5,21 @@ import aqua.compiler.AquaError.*
import aqua.model.AquaContext import aqua.model.AquaContext
import aqua.parser.{Ast, ParserError} import aqua.parser.{Ast, ParserError}
import aqua.raw.RawContext import aqua.raw.RawContext
import aqua.semantics.RawSemantics
import aqua.semantics.header.{HeaderHandler, HeaderSem} import aqua.semantics.header.{HeaderHandler, HeaderSem}
import aqua.semantics.rules.locations.{LocationsAlgebra, DummyLocationsInterpreter} import aqua.semantics.rules.locations.{DummyLocationsInterpreter, LocationsAlgebra}
import aqua.semantics.{FileId, RawSemantics}
import cats.data.* import cats.data.*
import cats.syntax.either.* import cats.syntax.either.*
import cats.syntax.flatMap.* import cats.syntax.flatMap.*
import cats.syntax.functor.* import cats.syntax.functor.*
import cats.syntax.traverse.* import cats.syntax.traverse.*
import cats.{Comonad, Monad, Monoid, Order} import cats.{Comonad, Monad, Monoid, Order, Show}
import scribe.Logging import scribe.Logging
object CompilerAPI extends Logging { object CompilerAPI extends Logging {
private def toAquaProcessed[I: Order, E, S[_]: Comonad]( private def toAquaProcessed[I: FileId, S[_]: Comonad](
filesWithContext: Map[I, RawContext] filesWithContext: Map[I, RawContext]
): Chain[AquaProcessed[I]] = { ): Chain[AquaProcessed[I]] = {
logger.trace("linking finished") logger.trace("linking finished")
@ -41,7 +41,7 @@ object CompilerAPI extends Logging {
.value .value
} }
private def getAquaCompiler[F[_]: Monad, E, I: Order, S[_]: Comonad]( private def getAquaCompiler[F[_]: Monad, E, I: FileId, S[_]: Comonad](
config: AquaCompilerConf config: AquaCompilerConf
): AquaCompiler[F, E, I, S, RawContext] = { ): AquaCompiler[F, E, I, S, RawContext] = {
given Monoid[RawContext] = RawContext given Monoid[RawContext] = RawContext
@ -55,8 +55,8 @@ object CompilerAPI extends Logging {
.rawContextMonoid .rawContextMonoid
val semantics = new RawSemantics[S]() val semantics = new RawSemantics[S]()
given LocationsAlgebra[S, State[RawContext, *]] = given LocationsAlgebra[S, State[RawContext, *]] =
DummyLocationsInterpreter() DummyLocationsInterpreter()
new AquaCompiler[F, E, I, S, RawContext]( new AquaCompiler[F, E, I, S, RawContext](
@ -66,7 +66,7 @@ object CompilerAPI extends Logging {
} }
// Get result generated by backend // Get result generated by backend
def compile[F[_]: Monad, E, I: Order, S[_]: Comonad]( def compile[F[_]: Monad, E, I: FileId, S[_]: Comonad](
sources: AquaSources[F, E, I], sources: AquaSources[F, E, I],
parser: I => String => ValidatedNec[ParserError[S], Ast[S]], parser: I => String => ValidatedNec[ParserError[S], Ast[S]],
airValidator: AirValidator[F], airValidator: AirValidator[F],
@ -104,7 +104,7 @@ object CompilerAPI extends Logging {
} yield result } yield result
} }
def compileToContext[F[_]: Monad, E, I: Order, S[_]: Comonad]( def compileToContext[F[_]: Monad, E, I: FileId, S[_]: Comonad](
sources: AquaSources[F, E, I], sources: AquaSources[F, E, I],
parser: I => String => ValidatedNec[ParserError[S], Ast[S]], parser: I => String => ValidatedNec[ParserError[S], Ast[S]],
config: AquaCompilerConf config: AquaCompilerConf

View File

@ -1,5 +1,6 @@
package aqua.compiler package aqua.compiler
import aqua.compiler.FileIdString.given
import aqua.model.AquaContext import aqua.model.AquaContext
import aqua.model.CallServiceModel import aqua.model.CallServiceModel
import aqua.model.FlattenModel import aqua.model.FlattenModel
@ -16,6 +17,7 @@ import aqua.raw.ConstantRaw
import aqua.raw.value.{LiteralRaw, ValueRaw, VarRaw} import aqua.raw.value.{LiteralRaw, ValueRaw, VarRaw}
import aqua.res.* import aqua.res.*
import aqua.res.ResBuilder import aqua.res.ResBuilder
import aqua.semantics.FileId
import aqua.types.{ArrayType, CanonStreamType, LiteralType, ScalarType, StreamType, Type} import aqua.types.{ArrayType, CanonStreamType, LiteralType, ScalarType, StreamType, Type}
import cats.Id import cats.Id

View File

@ -0,0 +1,11 @@
package aqua.compiler
import aqua.semantics.FileId
object FileIdString {
given FileId[String] with {
override def show(t: String): String = t
override def compare(x: String, y: String): Int = x.compare(y)
}
}

View File

@ -1,18 +1,20 @@
package aqua.files package aqua.files
import aqua.semantics.FileId
import fs2.io.file.Path import fs2.io.file.Path
import cats.Order import cats.{Order, Show}
case class FileModuleId private (file: Path) { case class FileModuleId private (file: Path) {
override def toString: String = s"${file}" override def toString: String = s"$file"
} }
object FileModuleId { object FileModuleId {
implicit object FileModuleIdOrder extends Order[FileModuleId] { given FileId[FileModuleId] with {
override def compare(x: FileModuleId, y: FileModuleId): Int = override def compare(x: FileModuleId, y: FileModuleId): Int =
x.file.toString.compareTo(y.file.toString) x.file.toString.compareTo(y.file.toString)
override def show(t: FileModuleId): String = t.toString
} }
def apply(file: Path): FileModuleId = def apply(file: Path): FileModuleId =

View File

@ -107,11 +107,12 @@ object ResultHelper extends Logging {
link.toList link.toList
}.toJSArray }.toJSArray
private def importsToTokenImport(imports: List[LiteralToken[FileSpan.F]]): js.Array[TokenImport] = private def importsToTokenImport(imports: List[LiteralToken[FileSpan.F]], importPaths: Map[String, String]): js.Array[TokenImport] =
imports.flatMap { lt => imports.flatMap { lt =>
val (span, str) = lt.valueToken val (span, str) = lt.valueToken
val unquoted = str.substring(1, str.length - 1) val unquoted = str.substring(1, str.length - 1)
TokenLocation.fromSpan(span).map(l => TokenImport(l, unquoted)) val path = importPaths.getOrElse(unquoted, unquoted)
TokenLocation.fromSpan(span).map(l => TokenImport(l, path))
}.toJSArray }.toJSArray
def lspToCompilationResult(lsp: LspContext[FileSpan.F]): CompilationResult = { def lspToCompilationResult(lsp: LspContext[FileSpan.F]): CompilationResult = {
@ -124,11 +125,13 @@ object ResultHelper extends Logging {
case errs => case errs =>
logger.debug("Errors: " + errs.mkString("\n")) logger.debug("Errors: " + errs.mkString("\n"))
val importTokens = importsToTokenImport(lsp.importTokens, lsp.importPaths)
CompilationResult( CompilationResult(
errors.toJSArray, errors.toJSArray,
warnings.toJSArray, warnings.toJSArray,
locationsToJs(lsp.variables.flatMap(v => v.allLocations)), locationsToJs(lsp.variables.flatMap(v => v.allLocations)),
importsToTokenImport(lsp.importTokens), importTokens,
tokensToJs(lsp.variables.map(_.definition)) tokensToJs(lsp.variables.map(_.definition))
) )
} }

View File

@ -5,18 +5,18 @@ import aqua.parser.{Ast, ParserError}
import aqua.raw.RawContext import aqua.raw.RawContext
import aqua.semantics.header.{HeaderHandler, HeaderSem} import aqua.semantics.header.{HeaderHandler, HeaderSem}
import aqua.semantics.rules.locations.LocationsAlgebra import aqua.semantics.rules.locations.LocationsAlgebra
import aqua.semantics.FileId
import cats.data.Validated.validNec import cats.data.Validated.validNec
import cats.data.{State, Chain, Validated, ValidatedNec} import cats.data.{Chain, State, Validated, ValidatedNec}
import cats.syntax.either.* import cats.syntax.either.*
import cats.syntax.functor.* import cats.syntax.functor.*
import cats.syntax.monoid.* import cats.syntax.monoid.*
import cats.syntax.semigroup.* import cats.syntax.semigroup.*
import cats.{Comonad, Monad, Monoid, Order} import cats.{Comonad, Monad, Monoid, Order, Show}
object LSPCompiler { object LSPCompiler {
private def getLspAquaCompiler[F[_]: Monad, E, I: Order, S[_]: Comonad]( private def getLspAquaCompiler[F[_]: Monad, E, I: FileId, S[_]: Comonad](
config: AquaCompilerConf config: AquaCompilerConf
): AquaCompiler[F, E, I, S, LspContext[S]] = { ): AquaCompiler[F, E, I, S, LspContext[S]] = {
given Monoid[LspContext[S]] = LspContext given Monoid[LspContext[S]] = LspContext
@ -48,7 +48,7 @@ object LSPCompiler {
val semantics = new LspSemantics[S]() val semantics = new LspSemantics[S]()
given LocationsAlgebra[S, State[LspContext[S], *]] = given LocationsAlgebra[S, State[LspContext[S], *]] =
LocationsInterpreter[S, LspContext[S]]() LocationsInterpreter[S, LspContext[S]]()
new AquaCompiler[F, E, I, S, LspContext[S]]( new AquaCompiler[F, E, I, S, LspContext[S]](
@ -57,7 +57,7 @@ object LSPCompiler {
) )
} }
def compileToLsp[F[_]: Monad, E, I: Order, S[_]: Comonad]( def compileToLsp[F[_]: Monad, E, I: FileId, S[_]: Comonad](
sources: AquaSources[F, E, I], sources: AquaSources[F, E, I],
parser: I => String => ValidatedNec[ParserError[S], Ast[S]], parser: I => String => ValidatedNec[ParserError[S], Ast[S]],
config: AquaCompilerConf config: AquaCompilerConf

View File

@ -22,7 +22,8 @@ case class LspContext[S[_]](
variables: List[VariableInfo[S]] = Nil, variables: List[VariableInfo[S]] = Nil,
importTokens: List[LiteralToken[S]] = Nil, importTokens: List[LiteralToken[S]] = Nil,
errors: List[SemanticError[S]] = Nil, errors: List[SemanticError[S]] = Nil,
warnings: List[SemanticWarning[S]] = Nil warnings: List[SemanticWarning[S]] = Nil,
importPaths: Map[String, String] = Map.empty
) { ) {
lazy val allLocations: List[TokenLocation[S]] = variables.flatMap(_.allLocations) lazy val allLocations: List[TokenLocation[S]] = variables.flatMap(_.allLocations)
} }
@ -41,7 +42,8 @@ object LspContext {
importTokens = x.importTokens ++ y.importTokens, importTokens = x.importTokens ++ y.importTokens,
variables = x.variables ++ y.variables, variables = x.variables ++ y.variables,
errors = x.errors ++ y.errors, errors = x.errors ++ y.errors,
warnings = x.warnings ++ y.warnings warnings = x.warnings ++ y.warnings,
importPaths = x.importPaths ++ y.importPaths
) )
trait Implicits[S[_]] { trait Implicits[S[_]] {
@ -101,6 +103,9 @@ object LspContext {
) )
) )
override def setImportPaths(ctx: LspContext[S], importPaths: Map[String, String]): LspContext[S] =
ctx.copy(importPaths = importPaths)
override def setModule( override def setModule(
ctx: LspContext[S], ctx: LspContext[S],
name: Option[String], name: Option[String],

View File

@ -1,5 +1,6 @@
package aqua.lsp package aqua.lsp
import aqua.compiler.FileIdString.given_FileId_String
import aqua.compiler.{AquaCompilerConf, AquaError, AquaSources} import aqua.compiler.{AquaCompilerConf, AquaError, AquaSources}
import aqua.parser.Parser import aqua.parser.Parser
import aqua.parser.lift.Span import aqua.parser.lift.Span
@ -10,7 +11,6 @@ import aqua.types.*
import cats.Id import cats.Id
import cats.data.* import cats.data.*
import cats.instances.string.*
import org.scalatest.Inside import org.scalatest.Inside
import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers import org.scalatest.matchers.should.Matchers
@ -423,7 +423,8 @@ class AquaLSPSpec extends AnyFlatSpec with Matchers with Inside {
), ),
ProductType(nestedType :: Nil) ProductType(nestedType :: Nil)
) )
), ("check2", ArrowType(NilType, ProductType(someStr :: Nil))), ),
("check2", ArrowType(NilType, ProductType(someStr :: Nil))),
("check3", ArrowType(NilType, ProductType(ScalarType.string :: Nil))) ("check3", ArrowType(NilType, ProductType(ScalarType.string :: Nil)))
) )
) )

View File

@ -1,10 +1,3 @@
package aqua.linker package aqua.linker
case class AquaModule[I, E, T](id: I, imports: Map[String, I], dependsOn: Map[I, E], body: T) { case class AquaModule[I, E, T](id: I, imports: Map[String, I], dependsOn: Map[I, E], body: T)
def map[TT](f: T => TT): AquaModule[I, E, TT] = copy(body = f(body))
def mapWithId[TT](f: (I, T) => TT): AquaModule[I, E, TT] = copy(body = f(id, body))
def mapErr[EE](f: E => EE): AquaModule[I, EE, T] =
copy(dependsOn = dependsOn.view.mapValues(f).toMap)
}

View File

@ -30,14 +30,8 @@ case class Modules[I, E, T](
def isResolved: Boolean = dependsOn.isEmpty def isResolved: Boolean = dependsOn.isEmpty
def map[TT](f: T => TT): Modules[I, E, TT] = def map[TT](f: AquaModule[I, E, T] => AquaModule[I, E, TT]): Modules[I, E, TT] =
copy(loaded = loaded.view.mapValues(_.map(f)).toMap) copy(loaded = loaded.view.mapValues(f).toMap)
def mapErr[EE](f: E => EE): Modules[I, EE, T] =
copy(
loaded = loaded.view.mapValues(_.mapErr(f)).toMap,
dependsOn = dependsOn.view.mapValues(_.map(f)).toMap
)
} }
object Modules { object Modules {

View File

@ -0,0 +1,7 @@
package aqua.semantics
import cats.Show
import cats.kernel.Order
import cats.syntax.order.*
trait FileId[I] extends Show[I] with Order[I]

View File

@ -22,6 +22,7 @@ trait Picker[A] {
def funcAcceptAbility(ctx: A, name: String): Boolean def funcAcceptAbility(ctx: A, name: String): Boolean
def declares(ctx: A): Set[String] def declares(ctx: A): Set[String]
def setAbility(ctx: A, name: String, ctxAb: A): A def setAbility(ctx: A, name: String, ctxAb: A): A
def setImportPaths(ctx: A, importPaths: Map[String, String]): A
def setModule(ctx: A, name: Option[String], declares: Set[String]): A def setModule(ctx: A, name: Option[String], declares: Set[String]): A
def setExports(ctx: A, exports: Map[String, Option[String]]): A def setExports(ctx: A, exports: Map[String, Option[String]]): A
def setInit(ctx: A, ctxInit: Option[A]): A def setInit(ctx: A, ctxInit: Option[A]): A
@ -51,6 +52,7 @@ object Picker {
def funcAcceptAbility(name: String): Boolean = Picker[A].funcAcceptAbility(p, name) def funcAcceptAbility(name: String): Boolean = Picker[A].funcAcceptAbility(p, name)
def declares: Set[String] = Picker[A].declares(p) def declares: Set[String] = Picker[A].declares(p)
def setAbility(name: String, ctx: A): A = Picker[A].setAbility(p, name, ctx) def setAbility(name: String, ctx: A): A = Picker[A].setAbility(p, name, ctx)
def setImportPaths(importPaths: Map[String, String]): A = Picker[A].setImportPaths(p, importPaths)
def setInit(ctx: Option[A]): A = Picker[A].setInit(p, ctx) def setInit(ctx: Option[A]): A = Picker[A].setInit(p, ctx)
def addPart(part: (A, RawPart)): A = Picker[A].addPart(p, part) def addPart(part: (A, RawPart)): A = Picker[A].addPart(p, part)
@ -117,6 +119,10 @@ object Picker {
override def setAbility(ctx: RawContext, name: String, ctxAb: RawContext): RawContext = override def setAbility(ctx: RawContext, name: String, ctxAb: RawContext): RawContext =
ctx.copy(abilities = Map(name -> ctxAb)) ctx.copy(abilities = Map(name -> ctxAb))
// dummy
override def setImportPaths(ctx: RawContext, importPaths: Map[String, String]): RawContext =
ctx
override def setModule( override def setModule(
ctx: RawContext, ctx: RawContext,
name: Option[String], name: Option[String],