Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(language-server): Pass token types to LSP [LNG-285] #999

Merged
merged 32 commits into from
Dec 7, 2023

Conversation

DieMyst
Copy link
Member

@DieMyst DieMyst commented Dec 1, 2023

Description

Pass token types to handle this info in language-server (show types on hover in VSCode)

Implementation Details

Gather all tokens with types in LocationsState and pass it to CompilationResult in lsp package

Checklist

  • Corresponding issue has been created and linked in PR title.
  • Proposed changes are covered by tests.
  • Documentation has been updated to reflect the changes (if applicable).
  • I have self-reviewed my code and ensured its quality.
  • I have added/updated necessary comments to aid understanding.

Reviewer Checklist

  • Tests have been reviewed and are sufficient to validate the changes.
  • Documentation has been reviewed and is up to date.
  • Any questions or concerns have been addressed.

DieMyst and others added 18 commits November 13, 2023 16:05
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
* Make nil option of bottom

* Fix tests

* Make literals of data type

* Add unit tests

* Remove print
* chore(main): release aqua 0.13.0

* chore: Bump aqua version to 0.13.0
…977)

* Refactor

* Refactor type system

* Remove println

* Fix renaming

* Add unit tests

* Do not convert to call arrow

* Check ability

* Refactor captured values resolution

* Remove println

* Fix fields gathering

* Remove println

* Remove println

* Fix renaming, export ability

* Rename only abilities

* Fix unit tests

* Fix captured arrows renaming

* Add comments

* Refactor

* Rename only arrows

* Add comments, refactor

* Add comments

* Rename method

* Add integration test

---------

Co-authored-by: Anatolios Laskaris <[email protected]>
Co-authored-by: Dima <[email protected]>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Dima <[email protected]>
Copy link

linear bot commented Dec 1, 2023

@DieMyst DieMyst changed the title feat(language-server): Pass tokens types to LSP [LNG-285] feat(language-server): Pass token types to LSP [LNG-285] Dec 1, 2023
# Conflicts:
#	semantics/src/test/scala/aqua/semantics/SemanticsSpec.scala
…-lsp

# Conflicts:
#	aqua-src/antithesis.aqua
#	language-server/language-server-api/.js/src/main/scala/aqua/lsp/AquaLSP.scala
#	model/inline/src/main/scala/aqua/model/inline/ArrowInliner.scala
#	model/src/main/scala/aqua/model/ArgsCall.scala
#	model/src/main/scala/aqua/model/ValueModel.scala
#	types/src/main/scala/aqua/types/Type.scala
@DieMyst DieMyst marked this pull request as ready for review December 1, 2023 08:18
@DieMyst DieMyst added the e2e Run e2e workflow label Dec 1, 2023
WarningInfo(start, end, message, fileSpan.name)
}
}

@JSExportTopLevel("AquaLSP")
object AquaLSP extends App with Logging {
Copy link
Contributor

@InversionSpaces InversionSpaces Dec 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is inheritance from App needed?

val unquoted = str.substring(1, str.length - 1)
TokenLocation.fromSpan(span).map(l => TokenImport(l, unquoted))
}.toJSArray

val result = fileRes match {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just return this expression instead of creating a val?

Comment on lines 39 to 41
} yield {
TokenLocation(span.name, startLC._1, startLC._2, endLC._1, endLC._2)
}
Copy link
Contributor

@InversionSpaces InversionSpaces Dec 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} yield {
TokenLocation(span.name, startLC._1, startLC._2, endLC._1, endLC._2)
}
} yield TokenLocation(span.name, startLC._1, startLC._2, endLC._1, endLC._2)

Comment on lines 84 to 91
private def tokensToJs(tokens: List[TokenInfo[FileSpan.F]]): js.Array[TokenInfoJs] = {
tokens.flatMap { ti =>
TokenLocation.fromSpan(ti.token.unit._1).map { tl =>
val typeName = ti.`type`.show
TokenInfoJs(tl, typeName)
}
}.toJSArray
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private def tokensToJs(tokens: List[TokenInfo[FileSpan.F]]): js.Array[TokenInfoJs] = {
tokens.flatMap { ti =>
TokenLocation.fromSpan(ti.token.unit._1).map { tl =>
val typeName = ti.`type`.show
TokenInfoJs(tl, typeName)
}
}.toJSArray
}
private def tokensToJs(tokens: List[TokenInfo[FileSpan.F]]): js.Array[TokenInfoJs] =
tokens.flatMap { ti =>
TokenLocation.fromSpan(ti.token.unit._1).map { tl =>
val typeName = ti.`type`.show
TokenInfoJs(tl, typeName)
}
}.toJSArray

def lspToCompilationResult(lsp: LspContext[FileSpan.F]): CompilationResult = {
val errors = lsp.errors.map(CompileError.apply).flatMap(errorToInfo)
val warnings = lsp.warnings.map(CompileWarning.apply).flatMap(warningToInfo)
errors match
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
errors match
errors match

): State[X, Unit] = {
val allTokens =
definition +: fields.map { fieldDef =>
fieldDef.copy(name = combineFieldName(definition.name, fieldDef.name))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AbilityType.fullName?

importTokens: List[LiteralToken[S]] = Nil,
errors: List[SemanticError[S]] = Nil,
warnings: List[SemanticWarning[S]] = Nil
)
) {
lazy val allLocations: List[(Token[S], Token[S])] = variables.flatMap(_.allLocations)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like types like (Token[S], Token[S]). No one will remember how those two tokens are connected in two weeks.

@@ -90,7 +92,9 @@ object LspContext {
val prefix = name + "."
ctx.copy(
raw = ctx.raw.setAbility(name, ctxAb.raw),
tokens = ctx.tokens ++ ctxAb.tokens.map(kv => (prefix + kv._1) -> kv._2)
variables = ctx.variables ++ ctxAb.variables.map(v =>
v.copy(definition = v.definition.copy(name = prefix + v.definition.name))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: This is one more place where knowledge of how to construct compound name (through dot) is stored. How about calling some common source of truth like AbilityType.fullName?

Comment on lines 45 to 49
/*println(tokens.filter(v => v._1 == checkName && v._2.`type` == `type`).map {
case (name, expr) =>
val span = expr.token.unit._1
println(s"$name(${span.startIndex}:${span.endIndex}) ${expr.`type`}")
})*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove it?

Comment on lines 138 to 142
println(res.allLocations.map { case (l, r) =>
val lSpan = l.unit._1
val rSpan = r.unit._1
s"($l($lSpan):$r($rSpan))"
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove println?

res.checkLocations(120, 121, 224, 225) shouldBe true

res.checkTokenLoc("a", 152, 153, LiteralType.string) shouldBe true
res.checkLocations(152, 153, 172, 173) shouldBe true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those tests will be hard to modify, maintain and debug. I hope we will not modify them a lot, but still why don't you write a function that finds all occurrences of a name inside source by regex? It is much easier to comprehend "Nth occurrence of a" then 152, 153

Comment on lines -65 to -67
_ <- locations
.addTokenWithFields(token.value, token, names.toList)
.whenA(defs.nonEmpty)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why remove it? Also now token argument is unused

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this logic was moved to AbilityInterpreter

Comment on lines 5 to 8
case class DefinitionInfo[S[_]](name: String, token: Token[S], `type`: Type)
case class VariableInfo[S[_]](definition: DefinitionInfo[S], occurrences: List[Token[S]] = Nil) {
def allLocations: List[(Token[S], Token[S])] = occurrences.map(_ -> definition.token)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Put it into package object?

Comment on lines 14 to 16
def addDefinitions(newDefinitions: List[DefinitionInfo[S]]): LocationsState[S] = {
copy(variables = newDefinitions.map(d => VariableInfo(d)) ++ variables)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def addDefinitions(newDefinitions: List[DefinitionInfo[S]]): LocationsState[S] = {
copy(variables = newDefinitions.map(d => VariableInfo(d)) ++ variables)
}
def addDefinitions(newDefinitions: List[DefinitionInfo[S]]): LocationsState[S] =
copy(variables = newDefinitions.map(d => VariableInfo(d)) ++ variables)

token: Token[S]
): List[VariableInfo[S]] = vars match {
case Nil =>
logger.error(s"Unexpected. Cannot add occurrence for $name")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not internalError?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because it shouldn't stop compilation

Comment on lines 29 to 33
case head :: tail =>
if (head.definition.name == name)
head.copy(occurrences = token +: head.occurrences) :: tail
else
head :: addOccurrenceToFirst(tail, name, token)
Copy link
Contributor

@InversionSpaces InversionSpaces Dec 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can extract general method to helpers package:

extension [A](l: List[A]) {
    def updatedFirst[B >: A](p: A => Boolean, f: A => B): List[B] = {
      def update(left: List[B], right: List[A]): List[B] =
        right match {
          case a :: tail if p(a) => left.reverse ::: f(a) :: tail
          case a :: tail => update(a :: left, tail)
          case Nil => left.reverse
        }
  
      update(Nil, l)
    }
  }

Comment on lines 35 to 37
): LocationsState[S] = {
copy(variables = addOccurrenceToFirst(variables, name, token))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
): LocationsState[S] = {
copy(variables = addOccurrenceToFirst(variables, name, token))
}
): LocationsState[S] =
copy(variables = addOccurrenceToFirst(variables, name, token))

locations: List[(String, Token[S])]
): LocationsState[S] =
locations.foldLeft(this) { case (st, (name, token)) =>
st.copy(variables = addOccurrenceToFirst(variables, name, token))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do I understand right that variables referenced here are variables of outer state? So we add only the last location here because state is actually not saved. You can use addLocation here instead of repeating it and issue will be fixed

@@ -16,38 +17,73 @@ import org.scalatest.matchers.should.Matchers

class AquaLSPSpec extends AnyFlatSpec with Matchers with Inside {

private def getByPosition(code: String, str: String, position: Int): Option[(Int, Int)] = {
str.r.findAllMatchIn(code).toList.lift(position).map(r => (r.start, r.end))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: You can wrap str with word boundary regex so that var is not matched in somevariable.
Now it is unnecessary, maybe just for future tests

@@ -79,49 +115,75 @@ class AquaLSPSpec extends AnyFlatSpec with Matchers with Inside {
id => txt => Parser.parse(Parser.parserSchema)(txt),
AquaCompilerConf(ConstantRaw.defaultConstants(None))
)
.leftMap { errors =>
println(errors)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is println needed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

}

if (printFiltered)
println(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this print needed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes


object list {
extension[A] (l: List[A]) {
def updateFirst[B >: A](p: A => Boolean, f: A => B): List[B] = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: leave a comment

@DieMyst DieMyst enabled auto-merge (squash) December 7, 2023 12:02
@DieMyst DieMyst merged commit 74d02e1 into main Dec 7, 2023
9 checks passed
@DieMyst DieMyst deleted the LNG-285-types-info-to-lsp branch December 7, 2023 12:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
e2e Run e2e workflow
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants