From 48d93b63b44e3c92b95ade84ee15227a5e3de610 Mon Sep 17 00:00:00 2001 From: simerplaha Date: Tue, 28 Jan 2025 13:50:58 +1100 Subject: [PATCH 1/7] added type `S` to `CodeProvider` --- .../ralph/lsp/server/RalphLangServer.scala | 16 +++--- .../ralph/lsp/pc/search/CodeProvider.scala | 32 +++++------ .../completion/CodeCompletionProvider.scala | 2 +- .../gotodef/GoToDefinitionProvider.scala | 2 +- .../gotoref/GoToReferenceProvider.scala | 2 +- .../pc/search/rename/GoToRenameProvider.scala | 2 +- .../lsp/pc/search/TestCodeProvider.scala | 56 +++++++++---------- 7 files changed, 56 insertions(+), 56 deletions(-) diff --git a/lsp-server/src/main/scala/org/alephium/ralph/lsp/server/RalphLangServer.scala b/lsp-server/src/main/scala/org/alephium/ralph/lsp/server/RalphLangServer.scala index 24f9e302..60d69a4f 100644 --- a/lsp-server/src/main/scala/org/alephium/ralph/lsp/server/RalphLangServer.scala +++ b/lsp-server/src/main/scala/org/alephium/ralph/lsp/server/RalphLangServer.scala @@ -24,7 +24,7 @@ import org.alephium.ralph.lsp.pc.search.CodeProvider import org.alephium.ralph.lsp.pc.search.completion.Suggestion import org.alephium.ralph.lsp.pc.search.gotodef.GoToDefSetting import org.alephium.ralph.lsp.pc.search.gotoref.GoToRefSetting -import org.alephium.ralph.lsp.pc.sourcecode.SourceLocation +import org.alephium.ralph.lsp.pc.sourcecode.{SourceCodeState, SourceLocation} import org.alephium.ralph.lsp.pc.workspace._ import org.alephium.ralph.lsp.pc.workspace.build.error.ErrorUnknownFileType import org.alephium.ralph.lsp.server @@ -143,14 +143,14 @@ object RalphLangServer extends StrictImplicitLogging { * @tparam O The type of [[CodeProvider]] function output. * @return Go-to search results. */ - def goTo[I, O <: SourceLocation.GoTo]( + def goTo[S, I, O <: SourceLocation.GoTo]( fileURI: URI, line: Int, character: Int, searchSettings: I, cancelChecker: CancelChecker, currentState: PCState - )(implicit codeProvider: CodeProvider[I, O], + )(implicit codeProvider: CodeProvider[S, I, O], logger: ClientLogger): Iterator[O] = if (!isFileScheme(fileURI)) { Iterator.empty @@ -160,7 +160,7 @@ object RalphLangServer extends StrictImplicitLogging { currentState.workspace match { case sourceAware: WorkspaceState.IsSourceAware => val goToResult = - CodeProvider.search[I, O]( + CodeProvider.search[S, I, O]( line = line, character = character, fileURI = fileURI, @@ -438,7 +438,7 @@ class RalphLangServer private ( getPCState().workspace match { case sourceAware: WorkspaceState.IsSourceAware => val completionResult = - CodeProvider.search[Unit, Suggestion]( + CodeProvider.search[SourceCodeState.Parsed, Unit, Suggestion]( line = line, character = character, fileURI = fileURI, @@ -486,7 +486,7 @@ class RalphLangServer private ( val character = params.getPosition.getCharacter val locations = - goTo[GoToDefSetting, SourceLocation.GoToDefStrict]( + goTo[SourceCodeState.Parsed, GoToDefSetting, SourceLocation.GoToDefStrict]( fileURI = fileURI, line = line, character = character, @@ -518,7 +518,7 @@ class RalphLangServer private ( ) val locations = - goTo[GoToRefSetting, SourceLocation.GoToRefStrict]( + goTo[SourceCodeState.Parsed, GoToRefSetting, SourceLocation.GoToRefStrict]( fileURI = fileURI, line = line, character = character, @@ -541,7 +541,7 @@ class RalphLangServer private ( val character = params.getPosition.getCharacter val locations = - goTo[Unit, SourceLocation.GoToRenameStrict]( + goTo[SourceCodeState.Parsed, Unit, SourceLocation.GoToRenameStrict]( fileURI = fileURI, line = line, character = character, diff --git a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/CodeProvider.scala b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/CodeProvider.scala index 97cea6c0..5076d1fd 100644 --- a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/CodeProvider.scala +++ b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/CodeProvider.scala @@ -36,7 +36,7 @@ import java.net.URI * @tparam I The type of search settings. * @tparam O The type of search results. */ -trait CodeProvider[I, O] extends Product { +trait CodeProvider[S, I, O] extends Product { /** * Performs a search operation at the cursor index within the source-code of a workspace. @@ -48,7 +48,7 @@ trait CodeProvider[I, O] extends Product { */ def search( cursorIndex: Int, - sourceCode: SourceCodeState.Parsed, + sourceCode: S, workspace: WorkspaceState.IsSourceAware, searchSettings: I )(implicit logger: ClientLogger): Iterator[O] @@ -58,19 +58,19 @@ trait CodeProvider[I, O] extends Product { object CodeProvider { /** The code-completer implementation of [[CodeProvider]]. */ - implicit val codeCompleter: CodeProvider[Unit, Suggestion] = + implicit val codeCompleter: CodeProvider[SourceCodeState.Parsed, Unit, Suggestion] = CodeCompletionProvider /** The go-to definition implementation of [[CodeProvider]]. */ - implicit val goToDefinition: CodeProvider[GoToDefSetting, SourceLocation.GoToDefStrict] = + implicit val goToDefinition: CodeProvider[SourceCodeState.Parsed, GoToDefSetting, SourceLocation.GoToDefStrict] = GoToDefinitionProvider /** The go-to references implementation of [[CodeProvider]]. */ - implicit val goToReferences: CodeProvider[GoToRefSetting, SourceLocation.GoToRefStrict] = + implicit val goToReferences: CodeProvider[SourceCodeState.Parsed, GoToRefSetting, SourceLocation.GoToRefStrict] = GoToReferenceProvider /** The rename request implementation of [[CodeProvider]]. */ - implicit val goToRename: CodeProvider[Unit, SourceLocation.GoToRenameStrict] = + implicit val goToRename: CodeProvider[SourceCodeState.Parsed, Unit, SourceLocation.GoToRenameStrict] = GoToRenameProvider /** @@ -82,17 +82,17 @@ object CodeProvider { * @param workspace Current workspace state. * @tparam O The type to search. */ - def search[I, O]( + def search[S, I, O]( line: Int, character: Int, fileURI: URI, workspace: WorkspaceState.IsSourceAware, searchSettings: I - )(implicit provider: CodeProvider[I, O], + )(implicit provider: CodeProvider[S, I, O], logger: ClientLogger): Option[Either[CompilerMessage.Error, Iterator[O]]] = // if the fileURI belongs to the workspace, then search just within that workspace if (URIUtil.contains(workspace.build.contractURI, fileURI)) - searchWorkspace[I, O]( + searchWorkspace[S, I, O]( line = line, character = character, fileURI = fileURI, @@ -100,7 +100,7 @@ object CodeProvider { searchSettings = searchSettings ) else // else search all source files - searchWorkspaceAndDependencies[I, O]( + searchWorkspaceAndDependencies[S, I, O]( line = line, character = character, fileURI = fileURI, @@ -116,13 +116,13 @@ object CodeProvider { * @param workspace Current workspace state. * @tparam O The type to search. */ - private def searchWorkspaceAndDependencies[I, O]( + private def searchWorkspaceAndDependencies[S, I, O]( line: Int, character: Int, fileURI: URI, workspace: WorkspaceState.IsSourceAware, searchSettings: I - )(implicit provider: CodeProvider[I, O], + )(implicit provider: CodeProvider[S, I, O], logger: ClientLogger): Option[Either[CompilerMessage.Error, Iterator[O]]] = // Search on dependencies should only run for go-to definitions requests. Code-completion is ignored. if (provider == CodeProvider.goToDefinition || provider == CodeProvider.goToReferences) @@ -154,7 +154,7 @@ object CodeProvider { ) // execute search on that one workspace - searchWorkspace[I, O]( + searchWorkspace[S, I, O]( line = line, character = character, fileURI = fileURI, @@ -174,13 +174,13 @@ object CodeProvider { * @param workspace Current workspace state. * @tparam O The type to search. */ - private def searchWorkspace[I, O]( + private def searchWorkspace[S, I, O]( line: Int, character: Int, fileURI: URI, workspace: WorkspaceState.IsSourceAware, searchSettings: I - )(implicit provider: CodeProvider[I, O], + )(implicit provider: CodeProvider[S, I, O], logger: ClientLogger): Option[Either[CompilerMessage.Error, Iterator[O]]] = WorkspaceSearcher .findParsed( // find the parsed file where this search was executed. @@ -202,7 +202,7 @@ object CodeProvider { // execute the search provider.search( cursorIndex = cursorIndex, - sourceCode = parsed, + sourceCode = parsed.asInstanceOf[S], workspace = workspace, searchSettings = searchSettings ) diff --git a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/completion/CodeCompletionProvider.scala b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/completion/CodeCompletionProvider.scala index 4ca02409..8e552e00 100644 --- a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/completion/CodeCompletionProvider.scala +++ b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/completion/CodeCompletionProvider.scala @@ -29,7 +29,7 @@ import org.alephium.ralph.lsp.pc.workspace.build.dependency.DependencyID * * To execution this function invoke [[CodeProvider.search]] with [[Suggestion]] as type parameter. */ -private[search] case object CodeCompletionProvider extends CodeProvider[Unit, Suggestion] with StrictImplicitLogging { +private[search] case object CodeCompletionProvider extends CodeProvider[SourceCodeState.Parsed, Unit, Suggestion] with StrictImplicitLogging { /** @inheritdoc */ override def search( diff --git a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/gotodef/GoToDefinitionProvider.scala b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/gotodef/GoToDefinitionProvider.scala index dfea9c41..ace4e191 100644 --- a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/gotodef/GoToDefinitionProvider.scala +++ b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/gotodef/GoToDefinitionProvider.scala @@ -29,7 +29,7 @@ import org.alephium.ralph.lsp.pc.workspace.build.dependency.DependencyID * * To execution this function invoke [[CodeProvider.search]] with [[SourceLocation.GoToDefStrict]] as type parameter. */ -private[search] case object GoToDefinitionProvider extends CodeProvider[GoToDefSetting, SourceLocation.GoToDefStrict] with StrictImplicitLogging { +private[search] case object GoToDefinitionProvider extends CodeProvider[SourceCodeState.Parsed, GoToDefSetting, SourceLocation.GoToDefStrict] with StrictImplicitLogging { /** @inheritdoc */ override def search( diff --git a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/gotoref/GoToReferenceProvider.scala b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/gotoref/GoToReferenceProvider.scala index 61188f75..92663e1a 100644 --- a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/gotoref/GoToReferenceProvider.scala +++ b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/gotoref/GoToReferenceProvider.scala @@ -28,7 +28,7 @@ import org.alephium.ralph.lsp.pc.workspace.WorkspaceState * * To execution this function invoke [[CodeProvider.search]] with [[Boolean]] and [[SourceLocation.GoToRefStrict]] as type parameter. */ -private[search] case object GoToReferenceProvider extends CodeProvider[GoToRefSetting, SourceLocation.GoToRefStrict] with StrictImplicitLogging { +private[search] case object GoToReferenceProvider extends CodeProvider[SourceCodeState.Parsed, GoToRefSetting, SourceLocation.GoToRefStrict] with StrictImplicitLogging { /** @inheritdoc */ override def search( diff --git a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/rename/GoToRenameProvider.scala b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/rename/GoToRenameProvider.scala index 36397b88..502eb0a7 100644 --- a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/rename/GoToRenameProvider.scala +++ b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/rename/GoToRenameProvider.scala @@ -24,7 +24,7 @@ import org.alephium.ralph.lsp.pc.workspace.WorkspaceState /** * Implements [[CodeProvider]] that provides renaming results of type [[SourceLocation.GoToRenameStrict]]. */ -private[search] case object GoToRenameProvider extends CodeProvider[Unit, SourceLocation.GoToRenameStrict] { +private[search] case object GoToRenameProvider extends CodeProvider[SourceCodeState.Parsed, Unit, SourceLocation.GoToRenameStrict] { /** @inheritdoc */ override def search( diff --git a/presentation-compiler/src/test/scala/org/alephium/ralph/lsp/pc/search/TestCodeProvider.scala b/presentation-compiler/src/test/scala/org/alephium/ralph/lsp/pc/search/TestCodeProvider.scala index 44a37385..a086ef95 100644 --- a/presentation-compiler/src/test/scala/org/alephium/ralph/lsp/pc/search/TestCodeProvider.scala +++ b/presentation-compiler/src/test/scala/org/alephium/ralph/lsp/pc/search/TestCodeProvider.scala @@ -20,17 +20,17 @@ import org.alephium.ralph.lsp.TestCommon import org.alephium.ralph.lsp.access.compiler.CompilerAccess import org.alephium.ralph.lsp.access.compiler.message.{CompilerMessage, LineRange} import org.alephium.ralph.lsp.access.file.FileAccess -import org.alephium.ralph.lsp.access.util.{TestCodeUtil, StringUtil} +import org.alephium.ralph.lsp.access.util.{StringUtil, TestCodeUtil} import org.alephium.ralph.lsp.pc.client.TestClientLogger -import org.alephium.ralph.lsp.utils.log.ClientLogger import org.alephium.ralph.lsp.pc.search.completion.Suggestion import org.alephium.ralph.lsp.pc.search.gotodef.GoToDefSetting import org.alephium.ralph.lsp.pc.search.gotoref.GoToRefSetting -import org.alephium.ralph.lsp.pc.sourcecode.{SourceLocation, TestSourceCode, SourceCodeState} -import org.alephium.ralph.lsp.pc.workspace.build.{TestRalphc, BuildState, TestBuild} +import org.alephium.ralph.lsp.pc.sourcecode.{SourceCodeState, SourceLocation, TestSourceCode} +import org.alephium.ralph.lsp.pc.workspace.{TestWorkspace, Workspace, WorkspaceState} +import org.alephium.ralph.lsp.pc.workspace.build.{BuildState, TestBuild, TestRalphc} import org.alephium.ralph.lsp.pc.workspace.build.dependency.{DependencyID, TestDependency} -import org.alephium.ralph.lsp.pc.workspace.build.dependency.downloader.{StdInterfaceDownloader, DependencyDownloader, BuiltInFunctionDownloader} -import org.alephium.ralph.lsp.pc.workspace.{WorkspaceState, TestWorkspace, Workspace} +import org.alephium.ralph.lsp.pc.workspace.build.dependency.downloader.{BuiltInFunctionDownloader, DependencyDownloader, StdInterfaceDownloader} +import org.alephium.ralph.lsp.utils.log.ClientLogger import org.scalatest.Assertion import org.scalatest.EitherValues._ import org.scalatest.OptionValues._ @@ -60,7 +60,7 @@ object TestCodeProvider { * @return A list of code completion suggestions. */ def suggest(code: String): List[Suggestion] = - TestCodeProvider[Unit, Suggestion]( + TestCodeProvider[SourceCodeState.Parsed, Unit, Suggestion]( code = code, searchSettings = (), dependencyDownloaders = DependencyDownloader.natives() @@ -76,7 +76,7 @@ object TestCodeProvider { * @param code The containing `@@` and `>>...<<` symbols. */ def goToDefinitionStrict(settings: GoToDefSetting = testGoToDefSetting)(code: String): List[(URI, LineRange)] = - goTo[GoToDefSetting, SourceLocation.GoToDefStrict]( + goTo[SourceCodeState.Parsed, GoToDefSetting, SourceLocation.GoToDefStrict]( code = code, searchSettings = settings ) @@ -86,7 +86,7 @@ object TestCodeProvider { referenceReplacement: String, settings: GoToDefSetting = testGoToDefSetting )(code: String): Unit = - goToForAll[GoToDefSetting, SourceLocation.GoToDefStrict]( + goToForAll[SourceCodeState.Parsed, GoToDefSetting, SourceLocation.GoToDefStrict]( finder = referencesFinder, replacer = referenceReplacement, settings = settings, @@ -94,13 +94,13 @@ object TestCodeProvider { ) def goToReferences(settings: GoToRefSetting = testGoToRefSetting)(code: String): List[(URI, LineRange)] = - goTo[GoToRefSetting, SourceLocation.GoToRefStrict]( + goTo[SourceCodeState.Parsed, GoToRefSetting, SourceLocation.GoToRefStrict]( code = code, searchSettings = settings ) def goToRename(code: String): List[(URI, LineRange)] = - goTo[Unit, SourceLocation.GoToRenameStrict]( + goTo[SourceCodeState.Parsed, Unit, SourceLocation.GoToRenameStrict]( code = code, searchSettings = () ) @@ -122,7 +122,7 @@ object TestCodeProvider { referenceReplacement: String, settings: GoToRefSetting = testGoToRefSetting )(code: String): Unit = - goToForAll[GoToRefSetting, SourceLocation.GoToRefStrict]( + goToForAll[SourceCodeState.Parsed, GoToRefSetting, SourceLocation.GoToRefStrict]( finder = referencesFinder, replacer = referenceReplacement, settings = settings, @@ -133,19 +133,19 @@ object TestCodeProvider { renameFinder: Regex, renameReplacer: String )(code: String): Unit = - goToForAll[Unit, SourceLocation.GoToRenameStrict]( + goToForAll[SourceCodeState.Parsed, Unit, SourceLocation.GoToRenameStrict]( finder = renameFinder, replacer = renameReplacer, settings = (), code = code ) - private def goToForAll[I, O <: SourceLocation.GoTo]( + private def goToForAll[S, I, O <: SourceLocation.GoTo]( finder: Regex, replacer: String, settings: I, code: String - )(implicit codeProvider: CodeProvider[I, O]): Unit = { + )(implicit codeProvider: CodeProvider[S, I, O]): Unit = { // Initially, execute the test defined. // This should be most expressed on the declaration. val firstResult = @@ -216,10 +216,10 @@ object TestCodeProvider { * * @param code The containing `@@` and `>>...<<` symbols. */ - def goTo[I, O <: SourceLocation.GoTo]( + def goTo[S, I, O <: SourceLocation.GoTo]( code: String, searchSettings: I - )(implicit codeProvider: CodeProvider[I, O]): List[(URI, LineRange)] = { + )(implicit codeProvider: CodeProvider[S, I, O]): List[(URI, LineRange)] = { // To find line ranges remove the select indicator @@ val codeWithoutSelectSymbol = code.replace(TestCodeUtil.SEARCH_INDICATOR, "") @@ -233,7 +233,7 @@ object TestCodeProvider { // Execute go-to definition. val (searchResultIterator, sourceCode, _) = - TestCodeProvider[I, O]( + TestCodeProvider[S, I, O]( code = codeWithoutLineRangeSymbols, searchSettings = searchSettings, dependencyDownloaders = ArraySeq.empty @@ -321,7 +321,7 @@ object TestCodeProvider { dependency: String, workspace: String, settings: GoToRefSetting = testGoToRefSetting): Unit = - goTo[GoToRefSetting, SourceLocation.GoToRefStrict]( + goTo[SourceCodeState.Parsed, GoToRefSetting, SourceLocation.GoToRefStrict]( dependencyId = dependencyId, dependency = dependency, workspace = workspace, @@ -333,7 +333,7 @@ object TestCodeProvider { dependency: String, workspace: String, setting: GoToDefSetting = testGoToDefSetting): Unit = - goTo[GoToDefSetting, SourceLocation.GoToDefStrict]( + goTo[SourceCodeState.Parsed, GoToDefSetting, SourceLocation.GoToDefStrict]( dependencyId = dependencyId, dependency = dependency, workspace = workspace, @@ -350,12 +350,12 @@ object TestCodeProvider { * @param workspace The developer's workspace code. * @return */ - private def goTo[I, O <: SourceLocation.GoTo]( + private def goTo[S, I, O <: SourceLocation.GoTo]( dependencyId: DependencyID, dependency: String, workspace: String, searchSettings: I - )(implicit codeProvider: CodeProvider[I, O]): Unit = { + )(implicit codeProvider: CodeProvider[S, I, O]): Unit = { implicit val clientLogger: ClientLogger = TestClientLogger implicit val file: FileAccess = FileAccess.disk implicit val compiler: CompilerAccess = CompilerAccess.ralphc @@ -436,7 +436,7 @@ object TestCodeProvider { // run test val (searchResult, testWorkspace) = - TestCodeProvider[I, O]( + TestCodeProvider[S, I, O]( line = indicatorPosition.line, character = indicatorPosition.character, selectedFileURI = selectedFileURI, @@ -476,7 +476,7 @@ object TestCodeProvider { // Execute go-to definition. val (searchResult, _, workspace) = - TestCodeProvider[GoToDefSetting, SourceLocation.GoToDefStrict]( + TestCodeProvider[SourceCodeState.Parsed, GoToDefSetting, SourceLocation.GoToDefStrict]( code = codeWithoutGoToSymbols, searchSettings = testGoToDefSetting, dependencyDownloaders = ArraySeq(downloader) @@ -541,11 +541,11 @@ object TestCodeProvider { * as they can be written to `~/ralph-lsp`. * We don't want generated libraries being written to `~/ralph-lsp`. */ - private def apply[I, O]( + private def apply[S, I, O]( code: String, searchSettings: I, dependencyDownloaders: ArraySeq[DependencyDownloader.Native] - )(implicit provider: CodeProvider[I, O]): (Iterator[O], SourceCodeState.IsCodeAware, WorkspaceState.IsParsedAndCompiled) = { + )(implicit provider: CodeProvider[S, I, O]): (Iterator[O], SourceCodeState.IsCodeAware, WorkspaceState.IsParsedAndCompiled) = { implicit val clientLogger: ClientLogger = TestClientLogger implicit val file: FileAccess = FileAccess.disk implicit val compiler: CompilerAccess = CompilerAccess.ralphc @@ -599,14 +599,14 @@ object TestCodeProvider { * @param workspaceSourceCode The source to write to the test workspace. * @return Suggestions and the created workspace. */ - private def apply[I, O]( + private def apply[S, I, O]( line: Int, character: Int, selectedFileURI: URI, searchSettings: I, build: BuildState.Compiled, workspaceSourceCode: SourceCodeState.OnDisk - )(implicit provider: CodeProvider[I, O], + )(implicit provider: CodeProvider[S, I, O], client: ClientLogger, file: FileAccess, compiler: CompilerAccess): (Either[CompilerMessage.Error, Iterator[O]], WorkspaceState.IsParsedAndCompiled) = { From dda6a9dc6848a54ddebfc3a19c23d20b28d40bd2 Mon Sep 17 00:00:00 2001 From: simerplaha Date: Tue, 28 Jan 2025 14:01:09 +1100 Subject: [PATCH 2/7] Make `SoftAST.toNode` lazily evaluated --- .../access/compiler/parser/soft/ast/SoftAST.scala | 14 ++++++++++---- .../parser/soft/AnnotationParserSpec.scala | 2 +- .../compiler/parser/soft/FnDecelerationSpec.scala | 2 +- .../access/compiler/parser/soft/TestParser.scala | 4 ++-- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ast/SoftAST.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ast/SoftAST.scala index 4a144d24..5471e259 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ast/SoftAST.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ast/SoftAST.scala @@ -27,17 +27,23 @@ sealed trait SoftAST extends Product { self => final def children(): Iterator[SoftAST] = productIterator flatMap collectASTs - final def toNode(): Node[this.type, SoftAST] = + /** + * Similar to [[org.alephium.ralph.lsp.access.compiler.ast.Tree.Source.rootNode]], + * this tree creation is also lazily evaluated and expected to have concurrent access. + * + * TODO: Move these caches to solutions like Caffeine. + */ + final lazy val toNode: Node[this.type, SoftAST] = Node( data = self, - children = children().map(_.toNode()).toSeq + children = children().map(_.toNode).toSeq ) final def toCode(): String = - toNode().toCode() + toNode.toCode() final def toStringTree(): String = - toNode().toStringTree() + toNode.toStringTree() def toStringPretty(): String = s"${self.getClass.getSimpleName}: ${self.index}" diff --git a/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AnnotationParserSpec.scala b/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AnnotationParserSpec.scala index a40a6b20..5274a6a5 100644 --- a/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AnnotationParserSpec.scala +++ b/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AnnotationParserSpec.scala @@ -62,7 +62,7 @@ class AnnotationParserSpec extends AnyWordSpec with Matchers { val annotation = body - .toNode() + .toNode .walkDown .collectFirst { case Node(annotation: SoftAST.Annotation, _) => diff --git a/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/FnDecelerationSpec.scala b/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/FnDecelerationSpec.scala index c53f0dfc..5d63aa2b 100644 --- a/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/FnDecelerationSpec.scala +++ b/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/FnDecelerationSpec.scala @@ -114,7 +114,7 @@ class FnDecelerationSpec extends AnyWordSpec with Matchers { val functions = root - .toNode() + .toNode .walkDown .map(_.data) .collect { diff --git a/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TestParser.scala b/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TestParser.scala index d93f4431..3d69b240 100644 --- a/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TestParser.scala +++ b/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TestParser.scala @@ -98,7 +98,7 @@ object TestParser { identifier: String, ast: SoftAST): Option[SoftAST.Annotation] = ast - .toNode() + .toNode .walkDown .collectFirst { case Node(annotation @ SoftAST.Annotation(_, _, _, id: SoftAST.Identifier, _, _, _), _) if id.code.text == identifier => @@ -107,7 +107,7 @@ object TestParser { def findFirstComment(body: SoftAST): Option[SoftAST.Comments] = body - .toNode() + .toNode .walkDown .collectFirst { case Node(comments @ SoftAST.Comments(_, _, _, _), _) => From 593c61d986e7ec0a934be6b1dd06b98cf4a42f53 Mon Sep 17 00:00:00 2001 From: simerplaha Date: Tue, 28 Jan 2025 14:24:10 +1100 Subject: [PATCH 3/7] Execute `SoftParser` during parser state and store `SoftAST` in `SourceCodeState` --- .../ralph/lsp/pc/sourcecode/SourceCode.scala | 8 ++- .../lsp/pc/sourcecode/SourceCodeState.scala | 24 ++++++-- .../pc/sourcecode/SourceCodeParseSpec.scala | 7 ++- .../lsp/pc/sourcecode/TestSourceCode.scala | 5 +- .../alephium/ralph/lsp/utils/LazyVal.scala | 61 +++++++++++++++++++ 5 files changed, 92 insertions(+), 13 deletions(-) create mode 100644 utils/src/main/scala/org/alephium/ralph/lsp/utils/LazyVal.scala diff --git a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/sourcecode/SourceCode.scala b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/sourcecode/SourceCode.scala index 67d546a5..c78e76c4 100644 --- a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/sourcecode/SourceCode.scala +++ b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/sourcecode/SourceCode.scala @@ -23,7 +23,7 @@ import org.alephium.ralph.lsp.access.file.FileAccess import org.alephium.ralph.lsp.pc.sourcecode.imports.Importer import org.alephium.ralph.lsp.utils.log.ClientLogger import org.alephium.ralph.lsp.utils.CollectionUtil._ -import org.alephium.ralph.lsp.utils.URIUtil +import org.alephium.ralph.lsp.utils.{LazyVal, URIUtil} import java.net.URI import scala.annotation.tailrec @@ -89,14 +89,16 @@ private[pc] object SourceCode { SourceCodeState.ErrorParser( fileURI = fileURI, code = code, - errors = Seq(error) + errors = Seq(error), + astSoft = LazyVal(compiler.parseSoft(code)) ) case Right(parsedCode) => SourceCodeState.Parsed( fileURI = fileURI, code = code, - astStrict = parsedCode + astStrict = parsedCode, + astSoft = LazyVal(compiler.parseSoft(code)) ) } diff --git a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/sourcecode/SourceCodeState.scala b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/sourcecode/SourceCodeState.scala index 2161cf1f..09a8e416 100644 --- a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/sourcecode/SourceCodeState.scala +++ b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/sourcecode/SourceCodeState.scala @@ -20,8 +20,10 @@ import org.alephium.ralph.{CompiledContract, CompiledScript} import org.alephium.ralph.lsp.access.compiler.RalphParserExtension import org.alephium.ralph.lsp.access.compiler.ast.Tree import org.alephium.ralph.lsp.access.compiler.message.CompilerMessage +import org.alephium.ralph.lsp.access.compiler.message.error.FastParseError +import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.SoftAST import org.alephium.ralph.lsp.pc.sourcecode.warning.StringWarning -import org.alephium.ralph.lsp.utils.URIUtil +import org.alephium.ralph.lsp.utils.{LazyVal, URIUtil} import java.net.URI @@ -67,10 +69,13 @@ object SourceCodeState { } - /** Represents: Code that is parsed and compiled. */ - sealed trait IsParsedAndCompiled extends IsCodeAware - /** Represents: Code that is parsed. */ + sealed trait IsParsedAndCompiled extends IsCodeAware { + + def astSoft: LazyVal[Either[FastParseError, SoftAST.BlockBody]] + + } + sealed trait IsParsed extends IsParsedAndCompiled /** Represents: Code that is compiled. */ @@ -86,6 +91,9 @@ object SourceCodeState { override def code: String = parsed.code + override def astSoft: LazyVal[Either[FastParseError, SoftAST.BlockBody]] = + parsed.astSoft + } /** Represents: Code that contains error(s). */ @@ -120,16 +128,20 @@ object SourceCodeState { case class Parsed( fileURI: URI, code: String, - astStrict: Tree.Root) + astStrict: Tree.Root, + astSoft: LazyVal[Either[FastParseError, SoftAST.BlockBody]]) extends IsParsedAndCompiled + with IsParsed /** Represents: Error during the parser phase. */ case class ErrorParser( fileURI: URI, code: String, - errors: Seq[CompilerMessage.AnyError]) + errors: Seq[CompilerMessage.AnyError], + astSoft: LazyVal[Either[FastParseError, SoftAST.BlockBody]]) extends IsParserOrCompilationError with IsParsedAndCompiled + with IsParsed /** Represents: Code is successfully compiled */ case class Compiled( diff --git a/presentation-compiler/src/test/scala/org/alephium/ralph/lsp/pc/sourcecode/SourceCodeParseSpec.scala b/presentation-compiler/src/test/scala/org/alephium/ralph/lsp/pc/sourcecode/SourceCodeParseSpec.scala index 3aab3ad1..d2d9ba3b 100644 --- a/presentation-compiler/src/test/scala/org/alephium/ralph/lsp/pc/sourcecode/SourceCodeParseSpec.scala +++ b/presentation-compiler/src/test/scala/org/alephium/ralph/lsp/pc/sourcecode/SourceCodeParseSpec.scala @@ -24,6 +24,7 @@ import org.alephium.ralph.lsp.utils.log.ClientLogger import org.alephium.ralph.lsp.pc.sourcecode.TestSourceCode._ import org.alephium.ralph.lsp.pc.workspace.build.TestRalphc import org.alephium.ralph.lsp.{TestCode, TestFile} +import org.alephium.ralph.lsp.utils.LazyVal import org.scalatest.EitherValues._ import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec @@ -163,7 +164,8 @@ class SourceCodeParseSpec extends AnyWordSpec with Matchers with ScalaCheckDrive SourceCodeState.Parsed( fileURI = currentState.fileURI, code = goodCode, - astStrict = compiler.parseContracts(currentState.fileURI, goodCode).value + astStrict = compiler.parseContracts(currentState.fileURI, goodCode).value, + astSoft = LazyVal(compiler.parseSoft(goodCode)) ) // read the code written on disk @@ -211,7 +213,8 @@ class SourceCodeParseSpec extends AnyWordSpec with Matchers with ScalaCheckDrive SourceCodeState.ErrorParser( fileURI = onDisk.fileURI, code = code, - errors = Seq(compiler.parseContracts(onDisk.fileURI, code).left.value) + errors = Seq(compiler.parseContracts(onDisk.fileURI, code).left.value), + astSoft = LazyVal(compiler.parseSoft(code)) ) TestSourceCode.delete(onDisk) diff --git a/presentation-compiler/src/test/scala/org/alephium/ralph/lsp/pc/sourcecode/TestSourceCode.scala b/presentation-compiler/src/test/scala/org/alephium/ralph/lsp/pc/sourcecode/TestSourceCode.scala index 586b9c14..1ae8369c 100644 --- a/presentation-compiler/src/test/scala/org/alephium/ralph/lsp/pc/sourcecode/TestSourceCode.scala +++ b/presentation-compiler/src/test/scala/org/alephium/ralph/lsp/pc/sourcecode/TestSourceCode.scala @@ -25,7 +25,7 @@ import org.alephium.ralph.lsp.access.file.FileAccess import org.alephium.ralph.lsp.pc.workspace.build.{BuildState, TestBuild} import org.alephium.ralph.lsp.pc.workspace.build.dependency.TestDependency import org.alephium.ralph.lsp.utils.log.ClientLogger -import org.alephium.ralph.lsp.utils.URIUtil +import org.alephium.ralph.lsp.utils.{LazyVal, URIUtil} import org.scalacheck.Gen import org.scalatest.EitherValues._ import org.scalatest.OptionValues._ @@ -133,7 +133,8 @@ object TestSourceCode { } yield SourceCodeState.ErrorParser( fileURI = _state.fileURI, code = _code, - errors = errors + errors = errors, + astSoft = LazyVal(???) ) def genUnCompiled( diff --git a/utils/src/main/scala/org/alephium/ralph/lsp/utils/LazyVal.scala b/utils/src/main/scala/org/alephium/ralph/lsp/utils/LazyVal.scala new file mode 100644 index 00000000..8ad8fcaa --- /dev/null +++ b/utils/src/main/scala/org/alephium/ralph/lsp/utils/LazyVal.scala @@ -0,0 +1,61 @@ +/* + * Copyright 2021 Simer JS Plaha (simer.j@gmail.com - @simerplaha) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.alephium.ralph.lsp.utils + +object LazyVal { + + def apply[A](f: => A): LazyVal[A] = + new LazyVal[A](() => f, None) + +} + +/** + * A lazily initialised value holder for managing `SoftAST` in `SourceCodeState`. + * + * @param load Function that computes the value. + * @param value The cached value, initially `None`, updated to `Some` once loaded. + * @tparam A The type of the value. + */ +class LazyVal[A] private ( + load: () => A, + @volatile private var value: Option[A]) { + + def fetch(): A = + this.value match { + case None => + val value = load() + this.value = Some(value) + value + + case Some(value) => + value + } + + override def equals(that: Any): Boolean = + that match { + case that: LazyVal[A] @unchecked => + this.value == that.value + + case _ => + false + } + + override def hashCode(): Int = + value.hashCode() + +} From 3175bba1a8a2db4e9d947e00642ac46b3d4548dc Mon Sep 17 00:00:00 2001 From: simerplaha Date: Tue, 28 Jan 2025 14:40:34 +1100 Subject: [PATCH 4/7] Implemented go-to-definition provider for `SoftAST` --- .../ralph/lsp/pc/search/CodeProvider.scala | 83 ++++--- .../soft/gotodef/GoToDefCodeString.scala | 31 +++ .../soft/gotodef/GoToDefIdentifier.scala | 100 +++++++++ .../gotodef/GoToDefinitionProviderSoft.scala | 76 +++++++ .../pc/sourcecode/SourceCodeSearcher.scala | 29 +++ .../lsp/pc/workspace/WorkspaceSearcher.scala | 31 ++- .../lsp/pc/search/TestCodeProvider.scala | 13 ++ .../gotodef/GoToLocalVariableSpec.scala | 209 +++++++++++++++--- 8 files changed, 515 insertions(+), 57 deletions(-) create mode 100644 presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/soft/gotodef/GoToDefCodeString.scala create mode 100644 presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/soft/gotodef/GoToDefIdentifier.scala create mode 100644 presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/soft/gotodef/GoToDefinitionProviderSoft.scala diff --git a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/CodeProvider.scala b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/CodeProvider.scala index 5076d1fd..af2ab47c 100644 --- a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/CodeProvider.scala +++ b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/CodeProvider.scala @@ -17,11 +17,13 @@ package org.alephium.ralph.lsp.pc.search import org.alephium.ralph.lsp.access.compiler.message.CompilerMessage +import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.SoftAST import org.alephium.ralph.lsp.access.util.StringUtil import org.alephium.ralph.lsp.pc.search.completion.{CodeCompletionProvider, Suggestion} import org.alephium.ralph.lsp.pc.search.gotodef.{GoToDefinitionProvider, GoToDefSetting} import org.alephium.ralph.lsp.pc.search.gotoref.{GoToReferenceProvider, GoToRefSetting} import org.alephium.ralph.lsp.pc.search.rename.GoToRenameProvider +import org.alephium.ralph.lsp.pc.search.soft.gotodef.GoToDefinitionProviderSoft import org.alephium.ralph.lsp.pc.sourcecode.{SourceCodeState, SourceLocation} import org.alephium.ralph.lsp.pc.workspace.{WorkspaceSearcher, WorkspaceState} import org.alephium.ralph.lsp.utils.log.ClientLogger @@ -65,6 +67,9 @@ object CodeProvider { implicit val goToDefinition: CodeProvider[SourceCodeState.Parsed, GoToDefSetting, SourceLocation.GoToDefStrict] = GoToDefinitionProvider + implicit val softGoToDefinition: CodeProvider[SourceCodeState.IsParsed, (SoftAST.type, GoToDefSetting), SourceLocation.GoToDefSoft] = + GoToDefinitionProviderSoft + /** The go-to references implementation of [[CodeProvider]]. */ implicit val goToReferences: CodeProvider[SourceCodeState.Parsed, GoToRefSetting, SourceLocation.GoToRefStrict] = GoToReferenceProvider @@ -182,31 +187,59 @@ object CodeProvider { searchSettings: I )(implicit provider: CodeProvider[S, I, O], logger: ClientLogger): Option[Either[CompilerMessage.Error, Iterator[O]]] = - WorkspaceSearcher - .findParsed( // find the parsed file where this search was executed. - fileURI = fileURI, - workspace = workspace - ) - .map { - parsedOpt => - parsedOpt map { - parsed => - // fetch the requested index from line number and character number. - val cursorIndex = - StringUtil.computeIndex( - code = parsed.code, - line = line, - character = character - ) - - // execute the search - provider.search( - cursorIndex = cursorIndex, - sourceCode = parsed.asInstanceOf[S], - workspace = workspace, - searchSettings = searchSettings + getParsedStateForCodeProvider( + fileURI = fileURI, + workspace = workspace, + searchSettings = searchSettings + ) map { + parsedOpt => + parsedOpt map { + parsed => + // fetch the requested index from line number and character number. + val cursorIndex = + StringUtil.computeIndex( + code = parsed.code, + line = line, + character = character ) - } - } + + // execute the search + provider.search( + cursorIndex = cursorIndex, + sourceCode = parsed.asInstanceOf[S], + workspace = workspace, + searchSettings = searchSettings + ) + } + } + + /** + * Finds the parsed state of the given file URI, based on the type of code provider being executed. + * + * This function is temporary until [[SoftAST]] fully replaces StrictAST within the [[CodeProvider]]s. + * + * @param fileURI The URI of the file whose parsed state is to be fetched. + * @param workspace The current workspace state. + * @param searchSettings The search settings for this request. + * @tparam I The type of the search settings used by the provider. + * @return The current parsed state of the FileURI. + */ + private def getParsedStateForCodeProvider[I]( + fileURI: URI, + workspace: WorkspaceState.IsSourceAware, + searchSettings: I): Option[Either[CompilerMessage.Error, SourceCodeState.IsParsed]] = + searchSettings match { + case (SoftAST, _) => // This is a SoftParser, fetch the IsParsed + WorkspaceSearcher.findIsParsed( + fileURI = fileURI, + workspace = workspace + ) + + case _ => // This is a StrictParser, fetch the Parsed + WorkspaceSearcher.findParsed( + fileURI = fileURI, + workspace = workspace + ) + } } diff --git a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/soft/gotodef/GoToDefCodeString.scala b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/soft/gotodef/GoToDefCodeString.scala new file mode 100644 index 00000000..4515f8b4 --- /dev/null +++ b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/soft/gotodef/GoToDefCodeString.scala @@ -0,0 +1,31 @@ +package org.alephium.ralph.lsp.pc.search.soft.gotodef + +import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.SoftAST +import org.alephium.ralph.lsp.pc.sourcecode.SourceLocation +import org.alephium.ralph.lsp.utils.Node + +private object GoToDefCodeString { + + /** + * Executes `go-to-definition` search for a [[Node]] representing the AST [[SoftAST.CodeString]]. + * + * @param node The node representing the [[SoftAST.CodeString]] being searched. + * @param sourceCode The body part and its parsed [[org.alephium.ralph.lsp.pc.sourcecode.SourceCodeState]], + * which contains the [[SoftAST.CodeString]]. + * @return An iterator over definition search results. + */ + def apply( + node: Node[SoftAST.CodeString, SoftAST], + sourceCode: SourceLocation.CodeSoft): Iterator[SourceLocation.GoToDefSoft] = + node.parent match { + case Some(node @ Node(id @ SoftAST.Identifier(_, _, _), _)) => + GoToDefIdentifier( + identNode = node.upcast(id), + sourceCode = sourceCode + ) + + case _ => + Iterator.empty + } + +} diff --git a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/soft/gotodef/GoToDefIdentifier.scala b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/soft/gotodef/GoToDefIdentifier.scala new file mode 100644 index 00000000..0f95e72a --- /dev/null +++ b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/soft/gotodef/GoToDefIdentifier.scala @@ -0,0 +1,100 @@ +package org.alephium.ralph.lsp.pc.search.soft.gotodef + +import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.SoftAST +import org.alephium.ralph.lsp.pc.sourcecode.SourceLocation +import org.alephium.ralph.lsp.utils.Node + +private object GoToDefIdentifier { + + /** + * Searches for definitions given the location of the identifier node [[SoftAST.Identifier]] + * and the [[SourceLocation]] of the identifier. + * + * Steps: + * - First, checks if the current [[SoftAST.Identifier]] itself belongs to a definition. + * - Second, executes search for all nodes within the scope of the current block of code. + * + * @param identNode The node representing the identifier being searched. + * @param sourceCode The body-part and its source code state where this search is executed. + * @return An iterator over definition search results. + */ + def apply( + identNode: Node[SoftAST.Identifier, SoftAST], + sourceCode: SourceLocation.CodeSoft): Iterator[SourceLocation.GoToDefSoft] = + identNode.parent match { + case Some(Node(assignment: SoftAST.Assignment, _)) if assignment.expressionLeft == identNode.data => + Iterator.single( + SourceLocation.NodeSoft( + ast = identNode.data.code, + source = sourceCode + ) + ) + + case Some(Node(assignment: SoftAST.MutableBinding, _)) if assignment.identifier == identNode.data => + Iterator.single( + SourceLocation.NodeSoft( + ast = identNode.data.code, + source = sourceCode + ) + ) + + case _ => + searchScope( + identNode = identNode, + sourceCode = sourceCode + ) + } + + /** + * Searches for definitions given the location of the identifier node [[SoftAST.Identifier]] + * and the [[SourceLocation]] of the identifier. + * + * @param identNode The node representing the identifier being searched. + * @param sourceCode The body-part and its source code state where this search is executed. + * @return An iterator over definition search results. + */ + private def searchScope( + identNode: Node[SoftAST.Identifier, SoftAST], + sourceCode: SourceLocation.CodeSoft): Iterator[SourceLocation.NodeSoft[SoftAST.CodeString]] = + sourceCode // search within scope + .body + .toNode + .walkDown + .flatMap { + case Node(variable: SoftAST.VariableDeclaration, _) => + checkVariableDeclaration( + variableDec = variable, + identNode = identNode, + sourceCode = sourceCode + ) + + case _ => + Iterator.empty + } + + /** + * Checks if the given identifier is defined by the specified variable declaration. + * + * @param variableDec The variable declaration to check. + * @param identNode The node representing the identifier being searched for. + * @param sourceCode The body part containing the variable declaration. + * @return The [[SourceLocation]] of the [[SoftAST.CodeString]] where the identifier is defined, if found, else [[None]]. + */ + private def checkVariableDeclaration( + variableDec: SoftAST.VariableDeclaration, + identNode: Node[SoftAST.Identifier, SoftAST], + sourceCode: SourceLocation.CodeSoft): Option[SourceLocation.NodeSoft[SoftAST.CodeString]] = + variableDec.assignment.expressionLeft match { + case id: SoftAST.Identifier if id.code.text == identNode.data.code.text => + Some( + SourceLocation.NodeSoft( + ast = id.code, + source = sourceCode + ) + ) + + case _ => + None + } + +} diff --git a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/soft/gotodef/GoToDefinitionProviderSoft.scala b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/soft/gotodef/GoToDefinitionProviderSoft.scala new file mode 100644 index 00000000..db5a3ee5 --- /dev/null +++ b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/soft/gotodef/GoToDefinitionProviderSoft.scala @@ -0,0 +1,76 @@ +package org.alephium.ralph.lsp.pc.search.soft.gotodef + +import org.alephium.ralph.lsp.access.compiler.message.SourceIndexExtra.SourceIndexExtension +import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.SoftAST +import org.alephium.ralph.lsp.pc.search.CodeProvider +import org.alephium.ralph.lsp.pc.search.gotodef.GoToDefSetting +import org.alephium.ralph.lsp.pc.sourcecode.{SourceCodeState, SourceLocation} +import org.alephium.ralph.lsp.pc.workspace.WorkspaceState +import org.alephium.ralph.lsp.utils.log.{ClientLogger, StrictImplicitLogging} +import org.alephium.ralph.lsp.utils.Node + +case object GoToDefinitionProviderSoft extends CodeProvider[SourceCodeState.IsParsed, (SoftAST.type, GoToDefSetting), SourceLocation.GoToDefSoft] with StrictImplicitLogging { + + /** @inheritdoc */ + override def search( + cursorIndex: Int, + sourceCode: SourceCodeState.IsParsed, + workspace: WorkspaceState.IsSourceAware, + searchSettings: (SoftAST.type, GoToDefSetting) + )(implicit logger: ClientLogger): Iterator[SourceLocation.GoToDefSoft] = + sourceCode.astSoft.fetch() match { + case Left(error) => + // This will be removed when integration is complete, + // when SourceCodeState.ErrorParser responds to SoftParser errors. + // Note: SoftParser is not expected to fail given any input, so this is less likely to occur. + // Log it for now. + logger.error { + s"""SoftParser Error: Failed to parse source code. + |File: ${sourceCode.fileURI} + |Error Message: ${error.message}""".stripMargin + } + + Iterator.empty + + case Right(softAST) => + /** + * First, find the first code block where the cursorIndex belongs, i.e. [[SoftAST.BodyPartAST]]. + * + * In a well-defined code, this is expected to be a top level Contract [[SoftAST.Template]]. + */ + softAST.toNode.data.parts.find(_.index contains cursorIndex) match { + case Some(bodyPart) => + searchBodyPart( + cursorIndex = cursorIndex, + bodyPart = bodyPart, + sourceCode = sourceCode + ) + + case None => + Iterator.empty + } + } + + /** + * Searches the given bodyPart. + * + * @param cursorIndex The index location where the search operation is performed. + * @param bodyPart The first code block where the search is executed. + * @param sourceCode The parsed state of the source-code where the search is executed. + */ + private def searchBodyPart( + cursorIndex: Int, + bodyPart: SoftAST.BlockBodyPart, + sourceCode: SourceCodeState.IsParsed): Iterator[SourceLocation.GoToDefSoft] = + bodyPart.toNode.findLast(_.index contains cursorIndex) match { + case Some(node @ Node(codeString @ SoftAST.CodeString(_, _), _)) => + GoToDefCodeString( + node = node.upcast(codeString), + sourceCode = SourceLocation.CodeSoft(bodyPart, sourceCode) + ) + + case _ => + Iterator.empty + } + +} diff --git a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/sourcecode/SourceCodeSearcher.scala b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/sourcecode/SourceCodeSearcher.scala index fdfca3fb..e9c2e956 100644 --- a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/sourcecode/SourceCodeSearcher.scala +++ b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/sourcecode/SourceCodeSearcher.scala @@ -69,6 +69,35 @@ object SourceCodeSearcher { Left(SourceCodeNotFound(fileURI)) } + def findIsParsed( + fileURI: URI, + sourceCode: ArraySeq[SourceCodeState]): Either[CompilerMessage.Error, SourceCodeState.IsParsed] = + sourceCode.find(_.fileURI == fileURI) match { + case Some(source) => + source match { + case _: SourceCodeState.OnDisk | _: SourceCodeState.UnCompiled => + Left(SourceCodeIsNotCompiled(fileURI)) + + case _: SourceCodeState.ErrorAccess => + Left(SourceCodeAccessFailed(fileURI)) + + case parsed: SourceCodeState.Parsed => + Right(parsed) + + case compiled: SourceCodeState.Compiled => + Right(compiled.parsed) + + case error: SourceCodeState.ErrorParser => + Right(error) + + case errored: SourceCodeState.ErrorCompilation => + Right(errored.parsed) + } + + case None => + Left(SourceCodeNotFound(fileURI)) + } + /** * Collects all source files with valid parsed syntax. * diff --git a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/workspace/WorkspaceSearcher.scala b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/workspace/WorkspaceSearcher.scala index bc4d859a..1a9e973a 100644 --- a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/workspace/WorkspaceSearcher.scala +++ b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/workspace/WorkspaceSearcher.scala @@ -43,7 +43,7 @@ object WorkspaceSearcher { fileURI: URI, workspace: WorkspaceState.IsSourceAware): Option[Either[CompilerMessage.Error, SourceCodeState.Parsed]] = // file must belong to the workspace contractURI and must be a ralph source file - if (URIUtil.contains(workspace.build.contractURI, fileURI) && CompilerAccess.isRalphFileExtension(fileURI)) { + if (isWorkspaceSourceFile(fileURI, workspace)) { val parsedOrError = SourceCodeSearcher.findParsed( fileURI = fileURI, @@ -55,6 +55,22 @@ object WorkspaceSearcher { None } + def findIsParsed( + fileURI: URI, + workspace: WorkspaceState.IsSourceAware): Option[Either[CompilerMessage.Error, SourceCodeState.IsParsed]] = + // file must belong to the workspace contractURI and must be a ralph source file + if (isWorkspaceSourceFile(fileURI, workspace)) { + val parsedOrError = + SourceCodeSearcher.findIsParsed( + fileURI = fileURI, + sourceCode = workspace.sourceCode + ) + + Some(parsedOrError) + } else { + None + } + /** * Collects all parent and child source implementations inherited and implemented by the given source tree. * @@ -317,4 +333,17 @@ object WorkspaceSearcher { workspaceTrees ++ allImportedCode } + /** + * Checks whether the given file URI belongs to the given workspace and is a Ralph source file. + * + * @param fileURI The URI of the file to check. + * @param workspace The workspace to verify. + * @return `true` if the file is a Ralph source file and belongs to the workspace, else `false`. + */ + private def isWorkspaceSourceFile( + fileURI: URI, + workspace: WorkspaceState.IsSourceAware): Boolean = + URIUtil.contains(workspace.build.contractURI, fileURI) && + CompilerAccess.isRalphFileExtension(fileURI) + } diff --git a/presentation-compiler/src/test/scala/org/alephium/ralph/lsp/pc/search/TestCodeProvider.scala b/presentation-compiler/src/test/scala/org/alephium/ralph/lsp/pc/search/TestCodeProvider.scala index a086ef95..b71495ed 100644 --- a/presentation-compiler/src/test/scala/org/alephium/ralph/lsp/pc/search/TestCodeProvider.scala +++ b/presentation-compiler/src/test/scala/org/alephium/ralph/lsp/pc/search/TestCodeProvider.scala @@ -19,6 +19,7 @@ package org.alephium.ralph.lsp.pc.search import org.alephium.ralph.lsp.TestCommon import org.alephium.ralph.lsp.access.compiler.CompilerAccess import org.alephium.ralph.lsp.access.compiler.message.{CompilerMessage, LineRange} +import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.SoftAST import org.alephium.ralph.lsp.access.file.FileAccess import org.alephium.ralph.lsp.access.util.{StringUtil, TestCodeUtil} import org.alephium.ralph.lsp.pc.client.TestClientLogger @@ -81,6 +82,18 @@ object TestCodeProvider { searchSettings = settings ) + def goToDefinitionSoft(settings: GoToDefSetting = testGoToDefSetting)(code: String): List[(URI, LineRange)] = + goTo[SourceCodeState.IsParsed, (SoftAST.type, GoToDefSetting), SourceLocation.GoToDefSoft]( + code = code, + searchSettings = (SoftAST, settings) + ) + + /** Executes go-to-definition providers for both StrictAST and [[SoftAST]] */ + def goToDefinition(settings: GoToDefSetting = testGoToDefSetting)(code: String): List[(URI, LineRange)] = { + goToDefinitionStrict(settings)(code) + goToDefinitionSoft(settings)(code) + } + def goToDefinitionForAll( referencesFinder: Regex, referenceReplacement: String, diff --git a/presentation-compiler/src/test/scala/org/alephium/ralph/lsp/pc/search/gotodef/GoToLocalVariableSpec.scala b/presentation-compiler/src/test/scala/org/alephium/ralph/lsp/pc/search/gotodef/GoToLocalVariableSpec.scala index 304becfd..e0fa44ae 100644 --- a/presentation-compiler/src/test/scala/org/alephium/ralph/lsp/pc/search/gotodef/GoToLocalVariableSpec.scala +++ b/presentation-compiler/src/test/scala/org/alephium/ralph/lsp/pc/search/gotodef/GoToLocalVariableSpec.scala @@ -23,46 +23,60 @@ import org.scalatest.wordspec.AnyWordSpec class GoToLocalVariableSpec extends AnyWordSpec with Matchers { "return empty" when { - "variable does not exist" in { - goToDefinitionStrict()( - """ - |Contract GoToTest() { - | - | pub fn function() -> () { - | // varB does not exists - | let varA = v@@arB - | } - | - |} - |""".stripMargin - ) + "variable does not exist" when { + "syntax is strict parsable" in { + goToDefinition()( + """ + |Contract GoToTest() { + | + | pub fn function() -> () { + | // varB does not exists + | let varA = v@@arB + | } + | + |} + |""".stripMargin + ) + } + + "no Contract" in { + goToDefinitionSoft()( + """ + |pub fn function() -> () { + | // varB does not exists + | let varA = v@@arB + |} + |""".stripMargin + ) + } + + "no function definition" in { + goToDefinitionSoft()( + """ + |let varA = v@@arB + |""".stripMargin + ) + } + + "let is spelt incorrectly" in { + goToDefinitionSoft()( + """ + |le varA = v@@arB + |""".stripMargin + ) + } } } "return self" when { - "variable itself is selected" in { - goToDefinitionStrict()( - """ - |Contract Test() { - | - | pub fn function() -> () { - | let >>var@@A<< = 123 - | } - | - |} - |""".stripMargin - ) - } - - "duplicate variables exists" when { - "first var is selected" in { - goToDefinitionStrict()( + "variable itself is selected" when { + "syntax is strict parsable" in { + goToDefinition()( """ |Contract Test() { | | pub fn function() -> () { | let >>var@@A<< = 123 - | let varA = 123 | } | |} @@ -70,6 +84,139 @@ class GoToLocalVariableSpec extends AnyWordSpec with Matchers { ) } + "syntax has errors" when { + "Contract is not defined" in { + goToDefinitionSoft()( + """ + |pub fn function() -> () { + | let >>var@@A<< = 123 + |} + | + |""".stripMargin + ) + } + + "function is not defined" in { + goToDefinitionSoft()( + """ + |let >>var@@A<< = 123 + |""".stripMargin + ) + } + + "let is misspelled" in { + goToDefinitionSoft()( + """ + |le >>var@@A<< = 123 + |""".stripMargin + ) + } + + "assignment does not exist & let is misspelled" in { + goToDefinitionSoft()( + """ + |le >>var@@A<< = + |""".stripMargin + ) + } + + "let is not defined" in { + goToDefinitionSoft()( + """ + |>>var@@A<< = + |""".stripMargin + ) + } + } + } + + "duplicate variables exists" when { + "first var is selected" when { + "syntax is strict parsable" in { + goToDefinition()( + """ + |Contract Test() { + | + | pub fn function() -> () { + | let >>var@@A<< = 123 + | let varA = 123 + | } + | + |} + |""".stripMargin + ) + } + + "source is not well defined" when { + "Contract is not defined" in { + goToDefinitionSoft()( + """ + |pub fn function() -> () { + | let >>var@@A<< = 123 + | let varA = 123 + |} + |""".stripMargin + ) + } + + "function is not defined" in { + goToDefinitionSoft()( + """ + |let >>var@@A<< = 123 + |let varA = 123 + |""".stripMargin + ) + } + + "let is not defined" when { + "for first varA" in { + goToDefinitionSoft()( + """ + |>>var@@A<< = 123 + |let varA = 123 + |""".stripMargin + ) + } + + "for second varA" in { + goToDefinitionSoft()( + """ + |let >>var@@A<< = 123 + |varA = 123 + |""".stripMargin + ) + } + + "both varAs" in { + goToDefinitionSoft()( + """ + |>>var@@A<< = 123 + |varA = 123 + |""".stripMargin + ) + } + + "assignment value is not defined" in { + goToDefinitionSoft()( + """ + |>>var@@A<< = + |varA = 123 + |""".stripMargin + ) + } + + "variable initialised value is referencing itself" in { + goToDefinitionSoft()( + """ + |let >>varA<< = var@@A + |""".stripMargin + ) + } + } + + } + } + "second var is selected" in { goToDefinitionStrict()( """ From 2dec520086389835015ea02559080b6db62b4460 Mon Sep 17 00:00:00 2001 From: simerplaha Date: Tue, 28 Jan 2025 15:23:47 +1100 Subject: [PATCH 5/7] fix build --- .../search/soft/gotodef/GoToDefinitionProviderSoft.scala | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/soft/gotodef/GoToDefinitionProviderSoft.scala b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/soft/gotodef/GoToDefinitionProviderSoft.scala index db5a3ee5..038a6585 100644 --- a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/soft/gotodef/GoToDefinitionProviderSoft.scala +++ b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/search/soft/gotodef/GoToDefinitionProviderSoft.scala @@ -33,11 +33,8 @@ case object GoToDefinitionProviderSoft extends CodeProvider[SourceCodeState.IsPa Iterator.empty case Right(softAST) => - /** - * First, find the first code block where the cursorIndex belongs, i.e. [[SoftAST.BodyPartAST]]. - * - * In a well-defined code, this is expected to be a top level Contract [[SoftAST.Template]]. - */ + // First, find the first code block where the cursorIndex belongs, i.e. [[SoftAST.BodyPartAST]]. + // In a well-defined code, this is expected to be a top level Contract [[SoftAST.Template]]. softAST.toNode.data.parts.find(_.index contains cursorIndex) match { case Some(bodyPart) => searchBodyPart( From afb494517374d6935fd5ad7ee7a0977496d9da9f Mon Sep 17 00:00:00 2001 From: simerplaha Date: Wed, 29 Jan 2025 11:28:23 +1100 Subject: [PATCH 6/7] Address review comment: Remove redundant inheritance --- .../alephium/ralph/lsp/pc/sourcecode/SourceCodeState.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/sourcecode/SourceCodeState.scala b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/sourcecode/SourceCodeState.scala index 09a8e416..293bbe46 100644 --- a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/sourcecode/SourceCodeState.scala +++ b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/sourcecode/SourceCodeState.scala @@ -130,8 +130,7 @@ object SourceCodeState { code: String, astStrict: Tree.Root, astSoft: LazyVal[Either[FastParseError, SoftAST.BlockBody]]) - extends IsParsedAndCompiled - with IsParsed + extends IsParsed /** Represents: Error during the parser phase. */ case class ErrorParser( @@ -140,7 +139,6 @@ object SourceCodeState { errors: Seq[CompilerMessage.AnyError], astSoft: LazyVal[Either[FastParseError, SoftAST.BlockBody]]) extends IsParserOrCompilationError - with IsParsedAndCompiled with IsParsed /** Represents: Code is successfully compiled */ From acc77b22e4f4b17e88a0440b31af6c805c7b5a45 Mon Sep 17 00:00:00 2001 From: simerplaha Date: Wed, 29 Jan 2025 11:33:57 +1100 Subject: [PATCH 7/7] Assert equality of `goToDefinition` results --- .../ralph/lsp/pc/search/TestCodeProvider.scala | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/presentation-compiler/src/test/scala/org/alephium/ralph/lsp/pc/search/TestCodeProvider.scala b/presentation-compiler/src/test/scala/org/alephium/ralph/lsp/pc/search/TestCodeProvider.scala index b71495ed..d84b5161 100644 --- a/presentation-compiler/src/test/scala/org/alephium/ralph/lsp/pc/search/TestCodeProvider.scala +++ b/presentation-compiler/src/test/scala/org/alephium/ralph/lsp/pc/search/TestCodeProvider.scala @@ -90,8 +90,16 @@ object TestCodeProvider { /** Executes go-to-definition providers for both StrictAST and [[SoftAST]] */ def goToDefinition(settings: GoToDefSetting = testGoToDefSetting)(code: String): List[(URI, LineRange)] = { - goToDefinitionStrict(settings)(code) - goToDefinitionSoft(settings)(code) + val resultStrict = goToDefinitionStrict(settings)(code) + val resultStrictRanges = resultStrict.map(_._2) + + val resultSoft = goToDefinitionSoft(settings)(code) + val resultSoftRanges = resultSoft.map(_._2) + + // Assert that both go-to-def services (Strict & Soft) return the same result. + resultSoftRanges should contain theSameElementsAs resultStrictRanges + // return either one of the results because they both contain the same result + resultSoft } def goToDefinitionForAll(