feat(language-server): Add types for types in LSP API (#1078)

This commit is contained in:
Dima 2024-02-20 14:02:55 +03:00 committed by GitHub
parent 9423ffc509
commit 3cd31c5827
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 363 additions and 35 deletions

View File

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

View File

@ -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": {

View File

@ -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([])
})
})*/

View File

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

View File

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

View File

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

View File

@ -1,48 +1,141 @@
export interface ScalarType {
name: string,
tag: "scalar"
}
export interface ArrayType {
element: Type,
tag: "array"
}
export interface OptionType {
element: Type,
tag: "option"
}
export interface StreamType {
element: Type,
tag: "stream"
}
export interface StreamMapType {
element: Type,
tag: "streammap"
}
export interface CanonStreamType {
element: Type,
tag: "canon"
}
export interface AbilityType {
name: string,
fields: Record<string, Type>,
tag: "ability"
}
export interface StructType {
name: string,
fields: Record<string, Type>,
tag: "struct"
}
export interface ServiceType {
name: string,
fields: Record<string, Type>,
tag: "service"
}
export interface LabeledConsType {
args: Record<string, Type>,
tag: "labeled"
}
export interface UnlabeledConsType {
types: Type[],
tag: "unlabeled"
}
export interface ArrowType {
domain: LabeledConsType
codomain: UnlabeledConsType,
tag: "arrow"
}
export interface NilType {
tag: "nil"
}
export interface BottomType {
tag: "bottom"
}
export interface TopType {
tag: "top"
}
export type Type =
ScalarType
| ArrayType
| OptionType
| StreamType
| StreamMapType
| CanonStreamType
| AbilityType
| StructType
| ServiceType
| LabeledConsType
| UnlabeledConsType
| ArrowType
| NilType
| TopType
| BottomType
export interface TokenLocation {
name: string;
startLine: number;
startCol: number;
endLine: number;
endCol: number;
name: string;
startLine: number;
startCol: number;
endLine: number;
endCol: number;
}
export interface TokenInfo {
location: TokenLocation;
type: string;
location: TokenLocation;
type: Type;
}
export interface TokenLink {
current: TokenLocation;
definition: TokenLocation;
current: TokenLocation;
definition: TokenLocation;
}
export interface TokenImport {
current: TokenLocation;
path: string;
current: TokenLocation;
path: string;
}
export interface ErrorInfo {
infoType: "error";
start: number;
end: number;
message: string;
location: string | null;
infoType: "error";
start: number;
end: number;
message: string;
location: string | null;
}
export interface WarningInfo {
infoType: "warning";
start: number;
end: number;
message: string;
location: string | null;
infoType: "warning";
start: number;
end: number;
message: string;
location: string | null;
}
export interface CompilationResult {
errors: ErrorInfo[];
warnings: WarningInfo[];
locations: TokenLink[];
importLocations: TokenImport[];
tokens: TokenInfo[];
errors: ErrorInfo[];
warnings: WarningInfo[];
locations: TokenLink[];
importLocations: TokenImport[];
tokens: TokenInfo[];
}
/*
@ -59,7 +152,7 @@ export interface CompilationResult {
export type Imports = Record<string, Record<string, string[]>>;
export class Compiler {
compile(path: string, imports: Imports): Promise<CompilationResult>;
compile(path: string, imports: Imports): Promise<CompilationResult>;
}
export var AquaLSP: Compiler;