Skip to content

Commit

Permalink
feat(language-server): Add types for types in LSP API (#1078)
Browse files Browse the repository at this point in the history
  • Loading branch information
DieMyst authored Feb 20, 2024
1 parent 9423ffc commit 3cd31c5
Show file tree
Hide file tree
Showing 7 changed files with 363 additions and 35 deletions.
29 changes: 29 additions & 0 deletions integration-tests/lsp-aqua/types.aqua
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
aqua Types

alias Top: ⊤
alias Bottom: ⊥

alias Number: u32
alias String: string
alias Array: []string
alias Stream: *string
alias Option: ?string

data Struct:
a: Number
b: String
c: Array
d: Option

service Srv("srv"):
noop(srvArg: string)

ability Ability:
a: Number
b: String

func nilArrow():
Srv.noop("")

func fullArrow(a: string, b: u32) -> string, u32:
<- a, b
6 changes: 2 additions & 4 deletions integration-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,14 @@
"scripts": {
"build": "tsc & npm run compile-aqua",
"test": "NODE_OPTIONS=--experimental-vm-modules jest --detectOpenHandles",
"test:lsp": "NODE_OPTIONS=--experimental-vm-modules jest src/__test__/lsp-types.spec.ts --detectOpenHandles",
"examples": "jest",
"pubsub": "node -r ts-node/register src/pubsub.ts",
"exec": "npm run compile-aqua && npm run prettify-compiled && node -r ts-node/register src/index.ts",
"run": "node -r ts-node/register src/index.ts",
"compile-aqua": "node --loader ts-node/esm ./src/compile.ts",
"compile-aqua:air": "aqua -i ./aqua/ -o ./compiled-air -a",
"prettify-compiled": "prettier --write src/compiled",
"prettify": "prettier --write src",
"aqua": "aqua",
"do": "aqua dist deploy --addr /dns4/kras-04.fluence.dev/tcp/19001/wss/p2p/12D3KooWFEwNWcHqi9rtsmDhsYcDbRUCDXH84RC4FW6UfsFWaoHi --config-path deploy.json --service tsOracle"
"prettify": "prettier --write src"
},
"prettier": {},
"devDependencies": {
Expand Down
103 changes: 103 additions & 0 deletions integration-tests/src/__test__/lsp-types.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
describe("Testing LSP types", () => {
it("dummy", async () => {
expect(true).toBeTruthy()
})
})

// FIXME: these tests work only with LSP ESM build (ModuleKind.ESModule in build.sbt)
/*
import {
AbilityType,
AquaLSP, ArrayType, BottomType,
OptionType,
ScalarType, StreamType, StructType,
TopType,
Type, ServiceType, ArrowType
} from '@fluencelabs/aqua-language-server-api/aqua-lsp-api';
describe("Testing LSP types", () => {
it("check types in aqua file", async () => {
const compiled = await AquaLSP.compile("lsp-aqua/types.aqua", {})
const types = compiled.tokens.map(ti => ti.type)
const isTop = (type: Type): type is TopType => type.tag === "top";
const tops = types.filter(isTop)
expect(tops).toHaveLength(1)
const isBottom = (type: Type): type is BottomType => type.tag === "bottom";
const bottom = types.filter(isBottom)
expect(bottom).toHaveLength(1)
const isScalar = (type: Type): type is ScalarType => type.tag === "scalar";
const scalars = types.filter(isScalar)
expect(scalars).toHaveLength(8)
scalars.forEach(sc => expect(sc.name).toBeDefined())
const isArray = (type: Type): type is ArrayType => type.tag === "array";
const arrays = types.filter(isArray)
expect(arrays).toHaveLength(2)
arrays.forEach(sc => expect(sc.element).toBeDefined())
const isOption = (type: Type): type is OptionType => type.tag === "option";
const options = types.filter(isOption)
expect(options).toHaveLength(2)
options.forEach(sc => expect(sc.element).toBeDefined())
const isStream = (type: Type): type is StreamType => type.tag === "stream";
const streams = types.filter(isStream)
expect(streams).toHaveLength(1)
streams.forEach(sc => expect(sc.element).toBeDefined())
const isAbility = (type: Type): type is AbilityType => type.tag === "ability";
const abilities = types.filter(isAbility)
expect(abilities).toHaveLength(1)
abilities.forEach((sc) => {
expect(sc.name).toBeDefined()
expect(sc.fields).toBeDefined()
expect(Object.entries(sc.fields)).toHaveLength(2)
})
const isStruct = (type: Type): type is StructType => type.tag === "struct";
const structs = types.filter(isStruct)
expect(structs).toHaveLength(1)
structs.forEach((sc) => {
expect(sc.name).toBeDefined()
expect(sc.fields).toBeDefined()
expect(Object.entries(sc.fields)).toHaveLength(4)
})
const isService = (type: Type): type is ServiceType => type.tag === "service";
const services = types.filter(isService)
expect(services).toHaveLength(1)
services.forEach((sc) => {
expect(sc.name).toBeDefined()
expect(sc.fields).toBeDefined()
expect(Object.entries(sc.fields)).toHaveLength(1)
})
const isArrow = (type: Type): type is ArrowType => type.tag === "arrow";
const arrows = types.filter(isArrow)
expect(arrows).toHaveLength(3)
const fullArrow = arrows[0]
const argA = fullArrow.domain.args["a"]
expect(argA).toEqual({name: "string", tag: "scalar"})
const argB = fullArrow.domain.args["b"]
expect(argB).toEqual({name: "u32", tag: "scalar"})
const codomain = fullArrow.codomain.types
expect(codomain[0]).toEqual({name: "string", tag: "scalar"})
expect(codomain[1]).toEqual({name: "u32", tag: "scalar"})
const nilArrow = arrows[1]
expect(nilArrow.domain.args).toEqual({})
expect(nilArrow.codomain.types).toEqual([])
const srvNoopArrow = arrows[2]
expect(srvNoopArrow.domain.args).toEqual({srvArg: {name: "string", tag: "scalar"}})
expect(srvNoopArrow.codomain.types).toEqual([])
})
})*/
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import aqua.parser.lift.FileSpan

import scala.scalajs.js
import scala.scalajs.js.annotation.JSExportAll
import scala.scalajs.js.{UndefOr, undefined}
import scala.scalajs.js.{undefined, UndefOr}

@JSExportAll
case class CompilationResult(
Expand All @@ -16,7 +16,7 @@ case class CompilationResult(
)

@JSExportAll
case class ExprInfoJs(location: TokenLocation, `type`: String)
case class ExprInfoJs(location: TokenLocation, `type`: TypeJs)

@JSExportAll
case class TokenLocation(name: String, startLine: Int, startCol: Int, endLine: Int, endCol: Int)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ object ResultHelper extends Logging {
private def tokensToJs(tokens: List[DefinitionInfo[FileSpan.F]]): js.Array[ExprInfoJs] =
tokens.flatMap { ti =>
TokenLocation.fromSpan(ti.token.unit._1).map { tl =>
val typeName = ti.`type`.show
ExprInfoJs(tl, typeName)
val typeDef = TypeJs.fromType(ti.`type`)
ExprInfoJs(tl, typeDef)
}
}.toJSArray

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package aqua.lsp

import aqua.types.*

import scala.scalajs.js.Dictionary
import scala.scalajs.js.JSConverters.*
import scalajs.js

sealed trait TypeJs extends js.Object {
val tag: String
}

class ScalarTypeJs(val name: String) extends TypeJs {
val tag: String = "scalar"
}

class ArrayTypeJs(val element: TypeJs) extends TypeJs {
val tag: String = "array"
}

class OptionTypeJs(val element: TypeJs) extends TypeJs {
val tag: String = "option"
}

class StreamTypeJs(val element: TypeJs) extends TypeJs {
val tag: String = "stream"
}

class StreamMapTypeJs(val element: TypeJs) extends TypeJs {
val tag: String = "streammap"
}

class CanonStreamTypeJs(val element: TypeJs) extends TypeJs {
val tag: String = "canon"
}

class AbilityTypeJs(val name: String, val fields: js.Dictionary[TypeJs]) extends TypeJs {
val tag: String = "ability"
}

class StructTypeJs(val name: String, val fields: js.Dictionary[TypeJs]) extends TypeJs {
val tag: String = "struct"
}

class ServiceTypeJs(val name: String, val fields: js.Dictionary[TypeJs]) extends TypeJs {
val tag: String = "service"
}

trait ProductType extends TypeJs

class LabeledConsTypeJs(val args: js.Dictionary[TypeJs]) extends TypeJs {
val tag: String = "labeled"
}

class UnlabeledConsTypeJs(val types: js.Array[TypeJs]) extends TypeJs {
val tag: String = "unlabeled"
}

class ArrowTypeJs(val domain: LabeledConsTypeJs, val codomain: UnlabeledConsTypeJs) extends TypeJs {
val tag: String = "arrow"
}

class NilTypeJs extends TypeJs {
val tag: String = "nil"
}

class BottomTypeJs extends TypeJs {
val tag: String = "bottom"
}

class TopTypeJs extends TypeJs {
val tag: String = "top"
}

object TypeJs {

def typeList(types: Iterable[(String, Type)]): Dictionary[TypeJs] =
js.Dictionary(types.map { case (n, t) =>
(n, TypeJs.fromType(t))
}.toSeq: _*)

def fromType(t: Type): TypeJs = {
t match
case ScalarType(name) => new ScalarTypeJs(name)
case LiteralType(_, name) => new ScalarTypeJs(name)
case ArrayType(el) => new ArrayTypeJs(fromType(el))
case OptionType(el) => new OptionTypeJs(fromType(el))
case StreamType(el) => new StreamTypeJs(fromType(el))
case StreamMapType(el) => new StreamMapTypeJs(fromType(el))
case CanonStreamType(el) => new CanonStreamTypeJs(fromType(el))
case StructType(name, fields) => new StructTypeJs(name, typeList(fields.toSortedMap))
case AbilityType(name, fields) => new AbilityTypeJs(name, typeList(fields.toSortedMap))
case ServiceType(name, fields) => new ServiceTypeJs(name, typeList(fields.toSortedMap))
case lct: LabeledConsType => new LabeledConsTypeJs(typeList(lct.toLabelledList()))
case uct: UnlabeledConsType => new UnlabeledConsTypeJs(uct.toList.map(fromType).toJSArray)
case ArrowType(domain, codomain) =>
ArrowTypeJs(
new LabeledConsTypeJs(typeList(domain.toLabelledList())),
new UnlabeledConsTypeJs(codomain.toList.map(fromType).toJSArray)
)
case TopType => new TopTypeJs()
case BottomType => new BottomTypeJs()
case NilType => new NilTypeJs()
}
}
Loading

0 comments on commit 3cd31c5

Please sign in to comment.