mirror of
https://github.com/enso-org/enso.git
synced 2024-08-16 16:30:33 +03:00
Speeding up "hello world" example by 16%
This commit is contained in:
parent
5a7ad6bfe4
commit
ba19813511
94
build.sbt
94
build.sbt
@ -254,6 +254,8 @@ lazy val buildNativeImage =
|
||||
lazy val enso = (project in file("."))
|
||||
.settings(version := "0.1")
|
||||
.aggregate(
|
||||
`persistance-dsl`,
|
||||
`persistance`,
|
||||
`interpreter-dsl`,
|
||||
`interpreter-dsl-test`,
|
||||
`json-rpc-server-test`,
|
||||
@ -1074,6 +1076,35 @@ lazy val searcher = project
|
||||
.dependsOn(testkit % Test)
|
||||
.dependsOn(`polyglot-api`)
|
||||
|
||||
lazy val `persistance` = (project in file("lib/java/persistance"))
|
||||
.settings(
|
||||
version := "0.1",
|
||||
frgaalJavaCompilerSetting,
|
||||
Compile / javacOptions := ((Compile / javacOptions).value),
|
||||
libraryDependencies ++= Seq(
|
||||
"org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion,
|
||||
"junit" % "junit" % junitVersion % Test,
|
||||
"com.github.sbt" % "junit-interface" % junitIfVersion % Test
|
||||
)
|
||||
)
|
||||
.dependsOn(`persistance-dsl` % Test)
|
||||
|
||||
lazy val `persistance-dsl` = (project in file("lib/java/persistance-dsl"))
|
||||
.settings(
|
||||
version := "0.1",
|
||||
frgaalJavaCompilerSetting,
|
||||
Compile / javacOptions := ((Compile / javacOptions).value ++
|
||||
// Only run ServiceProvider processor and ignore those defined in META-INF, thus
|
||||
// fixing incremental compilation setup
|
||||
Seq(
|
||||
"-processor",
|
||||
"org.netbeans.modules.openide.util.ServiceProviderProcessor"
|
||||
)),
|
||||
libraryDependencies ++= Seq(
|
||||
"org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion % "provided"
|
||||
)
|
||||
)
|
||||
|
||||
lazy val `interpreter-dsl` = (project in file("lib/scala/interpreter-dsl"))
|
||||
.settings(
|
||||
version := "0.1",
|
||||
@ -1087,7 +1118,7 @@ lazy val `interpreter-dsl` = (project in file("lib/scala/interpreter-dsl"))
|
||||
)),
|
||||
libraryDependencies ++= Seq(
|
||||
"org.apache.commons" % "commons-lang3" % commonsLangVersion,
|
||||
"org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion,
|
||||
"org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion % "provided",
|
||||
"com.google.guava" % "guava" % guavaVersion exclude ("com.google.code.findbugs", "jsr305")
|
||||
)
|
||||
)
|
||||
@ -1373,23 +1404,24 @@ lazy val runtime = (project in file("engine/runtime"))
|
||||
scalacOptions += "-Ymacro-annotations",
|
||||
scalacOptions ++= Seq("-Ypatmat-exhaust-depth", "off"),
|
||||
libraryDependencies ++= jmh ++ jaxb ++ circe ++ GraalVM.langsPkgs ++ Seq(
|
||||
"org.apache.commons" % "commons-lang3" % commonsLangVersion,
|
||||
"org.apache.tika" % "tika-core" % tikaVersion,
|
||||
"org.graalvm.polyglot" % "polyglot" % graalMavenPackagesVersion % "provided",
|
||||
"org.graalvm.sdk" % "polyglot-tck" % graalMavenPackagesVersion % "provided",
|
||||
"org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion % "provided",
|
||||
"org.graalvm.truffle" % "truffle-dsl-processor" % graalMavenPackagesVersion % "provided",
|
||||
"org.graalvm.truffle" % "truffle-tck" % graalMavenPackagesVersion % "provided",
|
||||
"org.graalvm.truffle" % "truffle-tck-common" % graalMavenPackagesVersion % "provided",
|
||||
"org.scalacheck" %% "scalacheck" % scalacheckVersion % Test,
|
||||
"org.scalactic" %% "scalactic" % scalacticVersion % Test,
|
||||
"org.scalatest" %% "scalatest" % scalatestVersion % Test,
|
||||
"org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion % Benchmark,
|
||||
"org.typelevel" %% "cats-core" % catsVersion,
|
||||
"junit" % "junit" % junitVersion % Test,
|
||||
"com.github.sbt" % "junit-interface" % junitIfVersion % Test,
|
||||
"org.hamcrest" % "hamcrest-all" % hamcrestVersion % Test,
|
||||
"org.slf4j" % "slf4j-nop" % slf4jVersion % Benchmark
|
||||
"org.apache.commons" % "commons-lang3" % commonsLangVersion,
|
||||
"org.apache.tika" % "tika-core" % tikaVersion,
|
||||
"org.graalvm.polyglot" % "polyglot" % graalMavenPackagesVersion % "provided",
|
||||
"org.graalvm.sdk" % "polyglot-tck" % graalMavenPackagesVersion % "provided",
|
||||
"org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion % "provided",
|
||||
"org.graalvm.truffle" % "truffle-dsl-processor" % graalMavenPackagesVersion % "provided",
|
||||
"org.graalvm.truffle" % "truffle-tck" % graalMavenPackagesVersion % "provided",
|
||||
"org.graalvm.truffle" % "truffle-tck-common" % graalMavenPackagesVersion % "provided",
|
||||
"org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion % "provided",
|
||||
"org.scalacheck" %% "scalacheck" % scalacheckVersion % Test,
|
||||
"org.scalactic" %% "scalactic" % scalacticVersion % Test,
|
||||
"org.scalatest" %% "scalatest" % scalatestVersion % Test,
|
||||
"org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion % Benchmark,
|
||||
"org.typelevel" %% "cats-core" % catsVersion,
|
||||
"junit" % "junit" % junitVersion % Test,
|
||||
"com.github.sbt" % "junit-interface" % junitIfVersion % Test,
|
||||
"org.hamcrest" % "hamcrest-all" % hamcrestVersion % Test,
|
||||
"org.slf4j" % "slf4j-nop" % slf4jVersion % Benchmark
|
||||
),
|
||||
// Add all GraalVM packages with Runtime scope - we don't need them for compilation,
|
||||
// just provide them at runtime (in module-path).
|
||||
@ -1454,6 +1486,7 @@ lazy val runtime = (project in file("engine/runtime"))
|
||||
.dependsOn(`common-polyglot-core-utils`)
|
||||
.dependsOn(`edition-updater`)
|
||||
.dependsOn(`interpreter-dsl`)
|
||||
.dependsOn(`persistance-dsl` % "provided")
|
||||
.dependsOn(`library-manager`)
|
||||
.dependsOn(`logging-truffle-connector`)
|
||||
.dependsOn(`polyglot-api`)
|
||||
@ -1468,14 +1501,23 @@ lazy val `runtime-parser` =
|
||||
.settings(
|
||||
frgaalJavaCompilerSetting,
|
||||
instrumentationSettings,
|
||||
commands += WithDebugCommand.withDebug,
|
||||
fork := true,
|
||||
Test / javaOptions ++= Seq(
|
||||
"-Dgraalvm.locatorDisabled=true",
|
||||
s"--upgrade-module-path=${file("engine/runtime/build-cache/truffle-api.jar").absolutePath}"
|
||||
),
|
||||
libraryDependencies ++= Seq(
|
||||
"junit" % "junit" % junitVersion % Test,
|
||||
"com.github.sbt" % "junit-interface" % junitIfVersion % Test,
|
||||
"org.scalatest" %% "scalatest" % scalatestVersion % Test
|
||||
"junit" % "junit" % junitVersion % Test,
|
||||
"com.github.sbt" % "junit-interface" % junitIfVersion % Test,
|
||||
"org.scalatest" %% "scalatest" % scalatestVersion % Test,
|
||||
"org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion % "provided"
|
||||
)
|
||||
)
|
||||
.dependsOn(syntax)
|
||||
.dependsOn(`syntax-rust-definition`)
|
||||
.dependsOn(`persistance`)
|
||||
.dependsOn(`persistance-dsl` % "provided")
|
||||
|
||||
lazy val `runtime-compiler` =
|
||||
(project in file("engine/runtime-compiler"))
|
||||
@ -1483,16 +1525,18 @@ lazy val `runtime-compiler` =
|
||||
frgaalJavaCompilerSetting,
|
||||
instrumentationSettings,
|
||||
libraryDependencies ++= Seq(
|
||||
"junit" % "junit" % junitVersion % Test,
|
||||
"com.github.sbt" % "junit-interface" % junitIfVersion % Test,
|
||||
"org.scalatest" %% "scalatest" % scalatestVersion % Test,
|
||||
"com.lihaoyi" %% "fansi" % fansiVersion
|
||||
"junit" % "junit" % junitVersion % Test,
|
||||
"com.github.sbt" % "junit-interface" % junitIfVersion % Test,
|
||||
"org.scalatest" %% "scalatest" % scalatestVersion % Test,
|
||||
"org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion % "provided",
|
||||
"com.lihaoyi" %% "fansi" % fansiVersion
|
||||
)
|
||||
)
|
||||
.dependsOn(`runtime-parser`)
|
||||
.dependsOn(pkg)
|
||||
.dependsOn(`polyglot-api`)
|
||||
.dependsOn(editions)
|
||||
.dependsOn(`persistance-dsl` % "provided")
|
||||
|
||||
lazy val `runtime-instrument-common` =
|
||||
(project in file("engine/runtime-instrument-common"))
|
||||
|
@ -13,6 +13,7 @@ import project.Extensions.Prefix_Name.Prefix_Name
|
||||
import project.Internal.Fan_Out
|
||||
import project.Internal.Java_Exports
|
||||
import project.Internal.Java_Problems
|
||||
import project.Internal.Widget_Helpers
|
||||
from project.Internal.Java_Exports import make_inferred_builder
|
||||
|
||||
## PRIVATE
|
||||
|
@ -12,84 +12,82 @@ One of the largest pain points for users of Enso at the moment is the fact that
|
||||
it has to precompile the entire standard library on every project load. This is,
|
||||
in essence, due to the fact that the current parser is abysmally slow, and
|
||||
incredibly demanding. The obvious solution to improve this is to take the parser
|
||||
out of the equation in its entirety, by serialising the parser's output.
|
||||
out of the equation in its entirety, by serializing the parser's output.
|
||||
|
||||
To that end, we want to serialise the Enso IR to a format that can later be read
|
||||
To that end, we want to serialize the Enso IR to a format that can later be read
|
||||
back in, bypassing the parser entirely. Furthermore, we can move the boundary at
|
||||
which this serialisation takes place to the end of the compiler pipeline,
|
||||
which this serialization takes place to the end of the compiler pipeline,
|
||||
thereby bypassing doing most of the compilation work, and further improving
|
||||
startup performance.
|
||||
|
||||
<!-- MarkdownTOC levels="2,3" autolink="true" -->
|
||||
|
||||
- [Serialising the IR](#serialising-the-ir)
|
||||
- [Serializing the IR](#serializing-the-ir)
|
||||
- [Breaking Links](#breaking-links)
|
||||
- [Storing the IR](#storing-the-ir)
|
||||
- [Metadata Format](#metadata-format)
|
||||
- [Portability Guarantees](#portability-guarantees)
|
||||
- [Portability and Versioning](#portability-and-versioning)
|
||||
- [Loading the IR](#loading-the-ir)
|
||||
- [Integrity Checking](#integrity-checking)
|
||||
- [Error Handling](#error-handling)
|
||||
- [Imports](#imports)
|
||||
- [Testing the Serialisation](#testing-the-serialisation)
|
||||
- [Testing the Serialization](#testing-the-serialization)
|
||||
- [Future Directions](#future-directions)
|
||||
|
||||
<!-- /MarkdownTOC -->
|
||||
|
||||
## Serialising the IR
|
||||
## Serializing the IR
|
||||
|
||||
As the serialised IR doesn't need to be read by anything other than Enso, we
|
||||
need not use a representation that is portable between platforms. As a result,
|
||||
we have picked the `Serializable` infrastructure that is _already present_ on
|
||||
the JVM. It has the following benefits:
|
||||
Using classical Java Serialization turned out to be unsuitably slow. Rather than
|
||||
switching to other serialization framework that does the same, but faster we
|
||||
desided in [PR-8207](https://github.com/enso-org/enso/pull/8207) to create _own
|
||||
persistance framework_ that radically changes the way we can read the caches.
|
||||
Rather than loading all the megabytes of stored data, it reads them _lazily on
|
||||
demand_.
|
||||
|
||||
- It is able to serialise arbitrary object graphs while maintaining object
|
||||
identity and tracking references. This cannot be disabled for `Serializable`,
|
||||
but that is fine as we want it.
|
||||
- It is built into the JVM and is hence guaranteed to be portable between
|
||||
instances of the same JVM.
|
||||
- It copes fine with highly-nested scala types, like our IR.
|
||||
Use following command to generate the Javadoc for the `org.enso.persist`
|
||||
package:
|
||||
|
||||
In order to maximise the benefits of this process, we want to serialise the IR
|
||||
as _late_ in the compiler pipeline as possible. This means serialising it just
|
||||
```bash
|
||||
enso$ find lib/java/persistance/src/main/java/ | grep java$ | xargs ~/bin/graalvm-21/bin/javadoc -d target/javadoc/ --snippet-path lib/java/persistance/src/test/java/
|
||||
enso$ links target/javadoc/index.html
|
||||
```
|
||||
|
||||
In order to maximize the benefits of this process, we want to serialize the IR
|
||||
as _late_ in the compiler pipeline as possible. This means serializing it just
|
||||
before the code generation step that generates Truffle nodes (before the
|
||||
`RuntimeStubsGenerator` and `IrToTruffle` run).
|
||||
|
||||
This serialisation should take place in an _offloaded thread_ so that it doesn't
|
||||
This serialization should take place in an _offloaded thread_ so that it doesn't
|
||||
block the compiler from continuing.
|
||||
|
||||
### Breaking Links
|
||||
|
||||
Doing this naïvely, however, means that we can inadvertently end up serialising
|
||||
Doing this naïvely, however, means that we can inadvertently end up serializing
|
||||
the entire module graph. This is due to the `BindingsMap`, which contains a
|
||||
reference to the associated `runtime.Module`, from which there is a reference to
|
||||
the `ModuleScope`. The `ModuleScope` may then reference other `runtime.Module`s
|
||||
which all contain `IR.Module`s. Therefore, done in a silly fashion, we end up
|
||||
serialising the entire reachable module graph. This is not what we want.
|
||||
serializing the entire reachable module graph. This is not what we want.
|
||||
|
||||
While the ideal way of solving this problem would be to customise the
|
||||
serialisation and deserialisation process for the `BindingsMap`, the JVM's
|
||||
`Serializable` does not provide the ability to customise it enough to solve this
|
||||
problem. Instead, we solve it using a preprocessing step:
|
||||
The `Persistance.write` method contains additional `writeReplace` function which
|
||||
our cache system uses to perform following modification just before
|
||||
`ProcessingPass.Metadata` are stored down:
|
||||
|
||||
- We can modify `BindingsMap` and its child types to be able to contain an
|
||||
unlinked module pointer
|
||||
`case class ModulePointer(qualifiedName: List[String])` in place of a
|
||||
`Module`.
|
||||
- As the `MetadataStorage` type that holds the `BindingsMap` is mutable it can
|
||||
be updated in place without having to reassemble the entire IR graph.
|
||||
- Hence, we can traverse all the nodes in the `ir.preorder` that have metadata
|
||||
consisting of either the `BindingsMap` or `ResolvedName` types (provided by
|
||||
the following passes: `BindingAnalysis`, `MethodDefinitions`, `GlobalNames`,
|
||||
`VectorLiterals`, `Patterns`), and perform a replacement.
|
||||
- modify `BindingsMap` and its child types to be able to contain an unlinked
|
||||
module pointer `case class ModulePointer(qualifiedName: List[String])` in
|
||||
place of a `Module`.
|
||||
- As the `MetadataStorage` type that holds the `BindingsMap` is mutable it might
|
||||
be tempting to update it in place, but relying on `writeReplace` mechanism is
|
||||
safer as it only changes the format of object being written down, rather than
|
||||
modifying objects of live `IR` - potentially shared with other parts of the
|
||||
system.
|
||||
|
||||
Having done this, we have broken any links that the IR may hold between modules,
|
||||
and can serialise each module individually.
|
||||
and can serialize each module individually.
|
||||
|
||||
This serialisation must take place _after_ codegen has happened as it modifies
|
||||
the IR in place. The compiler can handle giving it to the offloaded
|
||||
serialisation thread. It _may_ be necessary to `duplicate` the IR before handing
|
||||
it to this thread, but this should be checked during development.
|
||||
It _may_ be safer to `duplicate` the IR before handing it to serialization, but
|
||||
it shouldn't be necessary if the `writeReplace` function is written correctly.
|
||||
|
||||
## Storing the IR
|
||||
|
||||
@ -146,12 +144,28 @@ All hashes are encoded in SHA1 format, for performance reasons. The engine
|
||||
version is encoded in the cache path, and hence does not need to be explicitly
|
||||
specified in the metadata.
|
||||
|
||||
### Portability Guarantees
|
||||
### Portability and Versioning
|
||||
|
||||
As part of this design we provide only the following portability guarantees:
|
||||
These are two static methods in `Persistance` class to help creating a `byte[]`
|
||||
from a single object and then read it back. The array is identified with
|
||||
following header:
|
||||
|
||||
- The serialised IR must be able to be deserialised by _the same version of
|
||||
Enso_ that wrote the original blob.
|
||||
- 4 bytes fixed header
|
||||
- 4 bytes describing the version
|
||||
- 4 bytes to locate the beginning of the object (the objects aren't written
|
||||
linearly)
|
||||
|
||||
E.g. 12 bytes overhead before the actual data start. Following versioning is
|
||||
recommended when making a change:
|
||||
|
||||
- when you change something really core in the `Persitance` implementation -
|
||||
change the builtin header first four bytes
|
||||
- when you add or remove a Persistance implementation the version changes (as it
|
||||
is computed from all the IDs present in the system)
|
||||
- when you change format of some `Persitance.writeObject` method - change its ID
|
||||
|
||||
That way the same version of Enso will recognize its `.ir` files. Different
|
||||
versions of Enso will realize that the files aren't in suitable form.
|
||||
|
||||
## Loading the IR
|
||||
|
||||
@ -165,10 +179,12 @@ checking on the loaded cache. It works as follows.
|
||||
2. **Check Integrity:** Check the module's [metadata](#metadata-format) for
|
||||
validity according to the [integrity rules](#integrity-checking).
|
||||
3. **Load:** If the cache passes the integrity check, load the `.ir` file. If
|
||||
deserialisation fails in any way, immediately fall back to parsing the source
|
||||
deserialization fails in any way, immediately fall back to parsing the source
|
||||
file.
|
||||
4. **Re-Link:** If loading completed successfully, re-link the `BindingsMap`
|
||||
metadata to the proper modules in question.
|
||||
4. **Re-Link:** Relinking is part of **Load**. When using `Persistance.read`
|
||||
provide own `readResolve` function. Such a function gets a chance to change
|
||||
and replace each object read-in with appropriate variant respecting the whole
|
||||
compiler environment.
|
||||
|
||||
The main subtlety here is handling the dependencies between modules. We need to
|
||||
ensure that, when loading multiple cached libraries, we properly handle them
|
||||
@ -177,8 +193,8 @@ setting `AFTER_STATIC_PASSES` as the compilation state after loading the module.
|
||||
This will tie into the current `ImportsResolver` and `ExportsResolver` which are
|
||||
run in an un-gated fashion in `Compiler::run`.
|
||||
|
||||
In order to prevent the execution of malicious code when deserialising we should
|
||||
employ a deserialisation filter as built into the JDK.
|
||||
Unlike classical Java deserialization nly registered `Persistance` subclasses
|
||||
may participate in deserialization making it much safer and less vulnerable.
|
||||
|
||||
### Integrity Checking
|
||||
|
||||
@ -195,8 +211,8 @@ ignored if it is in a read-only location.
|
||||
It is important, as part of this, that we fail under all circumstances into a
|
||||
working state. This means that:
|
||||
|
||||
- If serialisation fails, we report a low-priority error message and continue.
|
||||
- If deserialisation fails, we fall back to loading and parsing the original
|
||||
- If serialization fails, we report a low-priority error message and continue.
|
||||
- If deserialization fails, we fall back to loading and parsing the original
|
||||
source file.
|
||||
|
||||
At no point should this mechanism be exposed to the user in any visible way,
|
||||
@ -215,12 +231,15 @@ until a complete cache invalidation was forced.
|
||||
Therefore, the compiler performs an additional check by invalidating module's
|
||||
cache if any of its imported modules have been invalidated.
|
||||
|
||||
## Testing the Serialisation
|
||||
## Testing the Serialization
|
||||
|
||||
There are two main elements that need to be tested as part of this feature.
|
||||
|
||||
- Firstly, we need to test the serialisation and deserialisation process,
|
||||
including the rewrite of `BindingsMap` to work properly.
|
||||
- `persistance` project comes with its own unit tests
|
||||
- `runtime-parser` project adds tests of various core classes used during `IR`
|
||||
serialization - like Scala `List` or checks of the _laziness_ of Scala `Seq`
|
||||
- We need to test the serialization and deserialization process, including the
|
||||
rewrite of `BindingsMap` to work properly.
|
||||
- We also need to test the discovery of cache locations on the filesystem and
|
||||
cache eviction strategies. The best way to do this is to set `$ENSO_DATA` to a
|
||||
temporary directory and then directly interact with the filesystem. Caching
|
||||
@ -243,16 +262,23 @@ library in a single pass.
|
||||
The bindings are serialized along with the library caches in a file with a
|
||||
`.bindings` suffix.
|
||||
|
||||
Further more the storage of `.ir` files contains usage of _lazy_ `Seq`
|
||||
references to separate the general part of the `IR` tree from elements
|
||||
representing method bodies. As such the compiler can process the structure of
|
||||
`.ir` files, but avoid loading in `IR` for methods that aren't being executed.
|
||||
|
||||
## Future Directions
|
||||
|
||||
Due to the less than ideal platform situation we're in, we're limited to using
|
||||
Java's `Serializable`. It is not as performant as other options.
|
||||
The `Persistance` framework gives us _laziness_ opportunities and we should use
|
||||
them more:
|
||||
|
||||
- [FST](https://github.com/RuedigerMoeller/fast-serialization) is around 10x
|
||||
faster than the JVM's serialization, and is a drop-in replacement.
|
||||
- However, the version that supports Java 11 utilises reflection that trips
|
||||
warnings that will be disallowed with Java 17 (the next LTS version for
|
||||
GraalVM).
|
||||
- The version that fixes this relies on the foreign memory API which is
|
||||
available in Java 17. I recommend that once we're on Java 17 builds the
|
||||
serialization is updated to work using FST.
|
||||
- have a single _blob_ with all `IR`s per a library and read only the parts that
|
||||
are needed
|
||||
|
||||
- experiement with GC - being able to release parts of unused `IR` once they
|
||||
were used (for code generation or co.)
|
||||
|
||||
- make the `.ir` files smaller where possible
|
||||
|
||||
The use of `Persistance` has already sped up the execution time of simple
|
||||
`IO.println "Hello!"` by 16% - let's use it to speed things up even more.
|
||||
|
@ -572,7 +572,8 @@ object Main {
|
||||
topScope.compile(shouldCompileDependencies)
|
||||
exitSuccess()
|
||||
} catch {
|
||||
case _: Throwable =>
|
||||
case t: Throwable =>
|
||||
logger.error("Unexpected internal error", t)
|
||||
exitFail()
|
||||
} finally {
|
||||
context.context.close()
|
||||
|
@ -81,8 +81,6 @@ public interface CompilerContext extends CompilerStub {
|
||||
|
||||
boolean wasLoadedFromCache(Module module);
|
||||
|
||||
boolean hasCrossModuleLinks(Module module);
|
||||
|
||||
org.enso.compiler.core.ir.Module getIr(Module module);
|
||||
|
||||
CompilationStage getCompilationStage(Module module);
|
||||
@ -108,8 +106,6 @@ public interface CompilerContext extends CompilerStub {
|
||||
|
||||
void loadedFromCache(boolean b);
|
||||
|
||||
void hasCrossModuleLinks(boolean b);
|
||||
|
||||
void resetScope();
|
||||
|
||||
void invalidateCache();
|
||||
@ -134,8 +130,6 @@ public interface CompilerContext extends CompilerStub {
|
||||
|
||||
public abstract boolean isSynthetic();
|
||||
|
||||
public abstract boolean hasCrossModuleLinks();
|
||||
|
||||
public abstract org.enso.compiler.core.ir.Module getIr();
|
||||
|
||||
public abstract boolean isPrivate();
|
||||
|
@ -0,0 +1,155 @@
|
||||
package org.enso.compiler.pass.analyse;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.enso.compiler.pass.analyse.AliasAnalysis.Graph;
|
||||
import org.enso.compiler.pass.resolve.DocumentationComments;
|
||||
import org.enso.compiler.pass.resolve.FullyQualifiedNames;
|
||||
import org.enso.compiler.pass.resolve.IgnoredBindings;
|
||||
import org.enso.compiler.pass.resolve.ModuleAnnotations;
|
||||
import org.enso.compiler.pass.resolve.TypeSignatures;
|
||||
import org.enso.persist.Persistable;
|
||||
import org.enso.persist.Persistance;
|
||||
import org.openide.util.lookup.ServiceProvider;
|
||||
import scala.Option;
|
||||
|
||||
@Persistable(clazz = CachePreferenceAnalysis.WeightInfo.class, id = 1111)
|
||||
@Persistable(clazz = DataflowAnalysis.DependencyInfo.class, id = 1112)
|
||||
@Persistable(clazz = DataflowAnalysis.DependencyMapping.class, id = 1113)
|
||||
@Persistable(clazz = GatherDiagnostics.DiagnosticsMeta.class, id = 1114)
|
||||
@Persistable(clazz = DocumentationComments.Doc.class, id = 1115)
|
||||
@Persistable(clazz = AliasAnalysis$Info$Occurrence.class, id = 1116)
|
||||
@Persistable(clazz = TypeSignatures.Signature.class, id = 1117)
|
||||
@Persistable(clazz = ModuleAnnotations.Annotations.class, id = 1118)
|
||||
@Persistable(clazz = AliasAnalysis$Info$Scope$Root.class, id = 1120)
|
||||
@Persistable(clazz = DataflowAnalysis$DependencyInfo$Type$Static.class, id = 1121)
|
||||
@Persistable(clazz = DataflowAnalysis$DependencyInfo$Type$Dynamic.class, id = 1122)
|
||||
@Persistable(clazz = AliasAnalysis$Info$Scope$Child.class, id = 1123)
|
||||
@Persistable(clazz = AliasAnalysis$Graph$Occurrence$Use.class, id = 1125)
|
||||
@Persistable(clazz = AliasAnalysis$Graph$Occurrence$Def.class, id = 1126)
|
||||
@Persistable(clazz = AliasAnalysis$Graph$Link.class, id = 1127)
|
||||
@Persistable(clazz = FullyQualifiedNames.FQNResolution.class, id = 1128)
|
||||
@Persistable(clazz = FullyQualifiedNames.ResolvedLibrary.class, id = 1129)
|
||||
@Persistable(clazz = FullyQualifiedNames.ResolvedModule.class, id = 1130)
|
||||
public final class PassPersistance {
|
||||
private PassPersistance() {}
|
||||
|
||||
@ServiceProvider(service = Persistance.class)
|
||||
public static final class PersistState extends Persistance<IgnoredBindings.State> {
|
||||
public PersistState() {
|
||||
super(IgnoredBindings.State.class, true, 1101);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeObject(IgnoredBindings.State obj, Output out) throws IOException {
|
||||
out.writeBoolean(obj.isIgnored());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IgnoredBindings.State readObject(Input in)
|
||||
throws IOException, ClassNotFoundException {
|
||||
var b = in.readBoolean();
|
||||
return b
|
||||
? org.enso.compiler.pass.resolve.IgnoredBindings$State$Ignored$.MODULE$
|
||||
: org.enso.compiler.pass.resolve.IgnoredBindings$State$NotIgnored$.MODULE$;
|
||||
}
|
||||
}
|
||||
|
||||
@ServiceProvider(service = Persistance.class)
|
||||
public static final class PersistTail extends Persistance<TailCall.TailPosition> {
|
||||
public PersistTail() {
|
||||
super(TailCall.TailPosition.class, true, 1102);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeObject(TailCall.TailPosition obj, Output out) throws IOException {
|
||||
out.writeBoolean(obj.isTail());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TailCall.TailPosition readObject(Input in)
|
||||
throws IOException, ClassNotFoundException {
|
||||
var b = in.readBoolean();
|
||||
return b
|
||||
? org.enso.compiler.pass.analyse.TailCall$TailPosition$Tail$.MODULE$
|
||||
: org.enso.compiler.pass.analyse.TailCall$TailPosition$NotTail$.MODULE$;
|
||||
}
|
||||
}
|
||||
|
||||
@org.openide.util.lookup.ServiceProvider(service = Persistance.class)
|
||||
public static final class PersistAliasAnalysisGraphScope
|
||||
extends Persistance<org.enso.compiler.pass.analyse.AliasAnalysis$Graph$Scope> {
|
||||
public PersistAliasAnalysisGraphScope() {
|
||||
super(org.enso.compiler.pass.analyse.AliasAnalysis$Graph$Scope.class, false, 1124);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected org.enso.compiler.pass.analyse.AliasAnalysis$Graph$Scope readObject(Input in)
|
||||
throws IOException {
|
||||
var childScopes = in.readInline(scala.collection.immutable.List.class);
|
||||
var occurrences = (scala.collection.immutable.Set) in.readObject();
|
||||
var allDefinitions = in.readInline(scala.collection.immutable.List.class);
|
||||
var parent =
|
||||
new org.enso.compiler.pass.analyse.AliasAnalysis$Graph$Scope(
|
||||
childScopes, occurrences, allDefinitions);
|
||||
var optionParent = Option.apply(parent);
|
||||
childScopes.forall(
|
||||
(object) -> {
|
||||
var ch = (org.enso.compiler.pass.analyse.AliasAnalysis$Graph$Scope) object;
|
||||
ch.parent_$eq(optionParent);
|
||||
return null;
|
||||
});
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void writeObject(
|
||||
org.enso.compiler.pass.analyse.AliasAnalysis$Graph$Scope obj, Output out)
|
||||
throws IOException {
|
||||
out.writeInline(scala.collection.immutable.List.class, obj.childScopes());
|
||||
out.writeObject(obj.occurrences());
|
||||
out.writeInline(scala.collection.immutable.List.class, obj.allDefinitions());
|
||||
}
|
||||
}
|
||||
|
||||
@org.openide.util.lookup.ServiceProvider(service = Persistance.class)
|
||||
public static final class PersistAliasAnalysisGraph extends Persistance<Graph> {
|
||||
public PersistAliasAnalysisGraph() {
|
||||
super(Graph.class, false, 1119);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Graph readObject(Input in) throws IOException {
|
||||
var g = new Graph();
|
||||
|
||||
var rootScope = (AliasAnalysis$Graph$Scope) in.readObject();
|
||||
assignParents(rootScope);
|
||||
g.rootScope_$eq(rootScope);
|
||||
|
||||
var links =
|
||||
(scala.collection.immutable.Set) in.readInline(scala.collection.immutable.Set.class);
|
||||
g.links_$eq(links);
|
||||
return g;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected void writeObject(Graph obj, Output out) throws IOException {
|
||||
out.writeObject(obj.rootScope());
|
||||
out.writeInline(scala.collection.immutable.Set.class, obj.links());
|
||||
}
|
||||
|
||||
private static void assignParents(AliasAnalysis$Graph$Scope scope) {
|
||||
var option = Option.apply(scope);
|
||||
scope
|
||||
.childScopes()
|
||||
.foreach(
|
||||
(ch) -> {
|
||||
assignParents(ch);
|
||||
ch.parent_$eq(option);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -257,7 +257,7 @@ class Compiler(
|
||||
}
|
||||
)
|
||||
|
||||
var requiredModules = modules.flatMap { module =>
|
||||
val requiredModules = modules.flatMap { module =>
|
||||
val importedModules = runImportsAndExportsResolution(module, generateCode)
|
||||
val isLoadedFromSource =
|
||||
(m: Module) => !context.wasLoadedFromCache(m) && !context.isSynthetic(m)
|
||||
@ -286,47 +286,11 @@ class Compiler(
|
||||
}
|
||||
}.distinct
|
||||
|
||||
var hasInvalidModuleRelink = false
|
||||
if (irCachingEnabled) {
|
||||
requiredModules.foreach { module =>
|
||||
ensureParsed(module)
|
||||
if (!context.hasCrossModuleLinks(module)) {
|
||||
val flags =
|
||||
context
|
||||
.getIr(module)
|
||||
.preorder
|
||||
.map(_.passData.restoreFromSerialization(this.context))
|
||||
|
||||
if (!flags.contains(false)) {
|
||||
context.log(
|
||||
Compiler.defaultLogLevel,
|
||||
"Restored links (late phase) for module [{0}].",
|
||||
context.getModuleName(module)
|
||||
)
|
||||
} else {
|
||||
hasInvalidModuleRelink = true
|
||||
context.log(
|
||||
Compiler.defaultLogLevel,
|
||||
"Failed to restore links (late phase) for module [{0}].",
|
||||
context.getModuleName(module)
|
||||
)
|
||||
uncachedParseModule(module, isGenDocs = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasInvalidModuleRelink) {
|
||||
context.log(
|
||||
Compiler.defaultLogLevel,
|
||||
s"Some modules failed to relink. Re-running import and " +
|
||||
s"export resolution."
|
||||
)
|
||||
|
||||
requiredModules =
|
||||
modules.flatMap(runImportsAndExportsResolution(_, generateCode))
|
||||
}
|
||||
|
||||
requiredModules.foreach { module =>
|
||||
if (
|
||||
!context
|
||||
@ -629,7 +593,6 @@ class Compiler(
|
||||
u.ir(discoveredModule)
|
||||
u.compilationStage(CompilationStage.AFTER_PARSING)
|
||||
u.loadedFromCache(false)
|
||||
u.hasCrossModuleLinks(true)
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -892,7 +855,9 @@ class Compiler(
|
||||
): Unit = {
|
||||
if (config.isStrictErrors) {
|
||||
val diagnostics = modules.flatMap { module =>
|
||||
val errors = gatherDiagnostics(module)
|
||||
val errors =
|
||||
if (context.wasLoadedFromCache(module)) List()
|
||||
else gatherDiagnostics(module)
|
||||
List((module, errors))
|
||||
}
|
||||
if (reportDiagnostics(diagnostics)) {
|
||||
|
@ -84,7 +84,10 @@ class LocalScope(
|
||||
* @return the frame slot index for `id`.
|
||||
*/
|
||||
def getVarSlotIdx(id: Graph.Id): Int = {
|
||||
assert(localFrameSlotIdxs.contains(id))
|
||||
assert(
|
||||
localFrameSlotIdxs.contains(id),
|
||||
"Cannot find " + id + " in " + localFrameSlotIdxs
|
||||
)
|
||||
localFrameSlotIdxs(id)
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ import org.enso.compiler.core.ir.{
|
||||
Function,
|
||||
IdentifiedLocation,
|
||||
Literal,
|
||||
Location,
|
||||
Name,
|
||||
Type
|
||||
}
|
||||
@ -28,7 +29,6 @@ import org.enso.compiler.pass.resolve.{
|
||||
import org.enso.pkg.QualifiedName
|
||||
import org.enso.polyglot.Suggestion
|
||||
import org.enso.polyglot.data.{Tree, TypeGraph}
|
||||
import org.enso.syntax.text.Location
|
||||
import org.enso.text.editing.IndexedSource
|
||||
|
||||
import java.util.UUID
|
||||
|
@ -8,6 +8,7 @@ import org.enso.compiler.core.ir.{
|
||||
Empty,
|
||||
Expression,
|
||||
Function,
|
||||
IdentifiedLocation,
|
||||
Literal,
|
||||
Module,
|
||||
Name,
|
||||
@ -185,9 +186,10 @@ case object DemandAnalysis extends IRPass {
|
||||
} else {
|
||||
name match {
|
||||
case lit: Name.Literal if isDefined(lit) =>
|
||||
val forceLocation = name.location
|
||||
val newNameLocation = name.location.map(l => l.copy(id = None))
|
||||
val newName = lit.copy(location = newNameLocation)
|
||||
val forceLocation = name.location
|
||||
val newNameLocation =
|
||||
name.location.map(l => new IdentifiedLocation(l.location()))
|
||||
val newName = lit.copy(location = newNameLocation)
|
||||
Application.Force(newName, forceLocation)
|
||||
case _ => name
|
||||
}
|
||||
|
@ -111,7 +111,7 @@ case object FunctionBinding extends IRPass {
|
||||
val lambda = args
|
||||
.map(_.mapExpressions(desugarExpression))
|
||||
.foldRight(desugarExpression(body))((arg, body) =>
|
||||
Function.Lambda(List(arg), body, None)
|
||||
new Function.Lambda(List(arg), body, None)
|
||||
)
|
||||
.asInstanceOf[Function.Lambda]
|
||||
.copy(canBeTCO = canBeTCO, location = location)
|
||||
@ -155,10 +155,10 @@ case object FunctionBinding extends IRPass {
|
||||
val newBody = args
|
||||
.map(_.mapExpressions(desugarExpression))
|
||||
.foldRight(desugarExpression(body))((arg, body) =>
|
||||
Function.Lambda(List(arg), body, None)
|
||||
new Function.Lambda(List(arg), body, None)
|
||||
)
|
||||
|
||||
definition.Method.Explicit(
|
||||
new definition.Method.Explicit(
|
||||
methRef,
|
||||
newBody,
|
||||
loc,
|
||||
@ -245,7 +245,7 @@ case object FunctionBinding extends IRPass {
|
||||
val newBody = (requiredArgs ::: remainingArgs)
|
||||
.map(_.mapExpressions(desugarExpression))
|
||||
.foldRight(desugarExpression(body))((arg, body) =>
|
||||
Function.Lambda(List(arg), body, None)
|
||||
new Function.Lambda(List(arg), body, None)
|
||||
)
|
||||
Right(
|
||||
definition.Method.Conversion(
|
||||
|
@ -244,7 +244,7 @@ case object GenerateMethodBodies extends IRPass {
|
||||
expr: Expression,
|
||||
funName: Name
|
||||
): Expression = {
|
||||
Function.Lambda(
|
||||
new Function.Lambda(
|
||||
arguments =
|
||||
if (funName.name == MAIN_FUNCTION_NAME) Nil
|
||||
else genSyntheticSelf() :: Nil,
|
||||
|
@ -7,6 +7,7 @@ import org.enso.compiler.core.ir.{
|
||||
DefinitionArgument,
|
||||
Expression,
|
||||
Function,
|
||||
IdentifiedLocation,
|
||||
Module,
|
||||
Name,
|
||||
Type
|
||||
@ -142,7 +143,7 @@ case object LambdaShorthandToLambda extends IRPass {
|
||||
case blank: Name.Blank =>
|
||||
val newName = supply.newName()
|
||||
|
||||
Function.Lambda(
|
||||
new Function.Lambda(
|
||||
List(
|
||||
DefinitionArgument.Specified(
|
||||
name = Name.Literal(
|
||||
@ -221,12 +222,12 @@ case object LambdaShorthandToLambda extends IRPass {
|
||||
// arg
|
||||
val appResult =
|
||||
actualDefArgs.foldRight(processedApp: Expression)((arg, body) =>
|
||||
Function.Lambda(List(arg), body, None)
|
||||
new Function.Lambda(List(arg), body, None)
|
||||
)
|
||||
|
||||
// If the function is shorthand, do the same
|
||||
val resultExpr = if (functionIsShorthand) {
|
||||
Function.Lambda(
|
||||
new Function.Lambda(
|
||||
List(
|
||||
DefinitionArgument.Specified(
|
||||
Name
|
||||
@ -267,8 +268,9 @@ case object LambdaShorthandToLambda extends IRPass {
|
||||
name
|
||||
case it => desugarExpression(it, freshNameSupply)
|
||||
}
|
||||
val newVec = vector.copy(newItems)
|
||||
val locWithoutId = newVec.location.map(_.copy(id = None))
|
||||
val newVec = vector.copy(newItems)
|
||||
val locWithoutId =
|
||||
newVec.location.map(l => new IdentifiedLocation(l.location()))
|
||||
bindings.foldLeft(newVec: Expression) { (body, bindingName) =>
|
||||
val defArg = DefinitionArgument.Specified(
|
||||
bindingName,
|
||||
@ -277,7 +279,7 @@ case object LambdaShorthandToLambda extends IRPass {
|
||||
suspended = false,
|
||||
location = None
|
||||
)
|
||||
Function.Lambda(List(defArg), body, locWithoutId)
|
||||
new Function.Lambda(List(defArg), body, locWithoutId)
|
||||
}
|
||||
case tSet @ Application.Typeset(expr, _, _, _) =>
|
||||
tSet.copy(expression = expr.map(desugarExpression(_, freshNameSupply)))
|
||||
@ -422,7 +424,7 @@ case object LambdaShorthandToLambda extends IRPass {
|
||||
branches = newBranches
|
||||
)
|
||||
|
||||
Function.Lambda(
|
||||
new Function.Lambda(
|
||||
List(lambdaArg),
|
||||
newCaseExpr,
|
||||
caseExpr.location,
|
||||
|
@ -132,13 +132,13 @@ case object SectionsToBinOp extends IRPass {
|
||||
diagnostics
|
||||
)
|
||||
|
||||
val rightLam = Function.Lambda(
|
||||
val rightLam = new Function.Lambda(
|
||||
List(rightDefArg),
|
||||
opCall,
|
||||
None
|
||||
)
|
||||
|
||||
Function.Lambda(
|
||||
new Function.Lambda(
|
||||
List(leftDefArg),
|
||||
rightLam,
|
||||
loc
|
||||
@ -187,13 +187,13 @@ case object SectionsToBinOp extends IRPass {
|
||||
diagnostics
|
||||
)
|
||||
|
||||
val rightLambda = Function.Lambda(
|
||||
val rightLambda = new Function.Lambda(
|
||||
List(rightDefArg),
|
||||
opCall,
|
||||
None
|
||||
)
|
||||
|
||||
Function.Lambda(
|
||||
new Function.Lambda(
|
||||
List(leftDefArg),
|
||||
rightLambda,
|
||||
loc
|
||||
@ -253,13 +253,13 @@ case object SectionsToBinOp extends IRPass {
|
||||
diagnostics
|
||||
)
|
||||
|
||||
val leftLam = Function.Lambda(
|
||||
val leftLam = new Function.Lambda(
|
||||
List(leftDefArg),
|
||||
opCall,
|
||||
None
|
||||
)
|
||||
|
||||
Function.Lambda(
|
||||
new Function.Lambda(
|
||||
List(rightDefArg),
|
||||
leftLam,
|
||||
loc
|
||||
@ -274,7 +274,7 @@ case object SectionsToBinOp extends IRPass {
|
||||
diagnostics
|
||||
)
|
||||
|
||||
Function.Lambda(
|
||||
new Function.Lambda(
|
||||
List(leftDefArg),
|
||||
opCall,
|
||||
loc
|
||||
|
@ -9,6 +9,7 @@ import org.enso.compiler.core.ir.{
|
||||
Expression,
|
||||
Function,
|
||||
IdentifiedLocation,
|
||||
Location,
|
||||
Module,
|
||||
Name
|
||||
}
|
||||
@ -23,7 +24,6 @@ import org.enso.compiler.pass.analyse.{
|
||||
}
|
||||
import org.enso.compiler.pass.desugar._
|
||||
import org.enso.compiler.pass.resolve.IgnoredBindings
|
||||
import org.enso.syntax.text.Location
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
@ -171,8 +171,8 @@ case object LambdaConsolidate extends IRPass {
|
||||
val newLocation = chainedLambdas.head.location match {
|
||||
case Some(location) =>
|
||||
Some(
|
||||
IdentifiedLocation(
|
||||
Location(
|
||||
IdentifiedLocation.create(
|
||||
new Location(
|
||||
location.start,
|
||||
chainedLambdas.last.location.getOrElse(location).location.end
|
||||
),
|
||||
|
@ -5,6 +5,7 @@ import org.enso.compiler.core.Implicits.AsDiagnostics
|
||||
import org.enso.compiler.core.ir.{
|
||||
Expression,
|
||||
IdentifiedLocation,
|
||||
Location,
|
||||
Module,
|
||||
Pattern
|
||||
}
|
||||
@ -19,7 +20,6 @@ import org.enso.compiler.pass.analyse.{
|
||||
}
|
||||
import org.enso.compiler.pass.desugar._
|
||||
import org.enso.compiler.pass.resolve.{DocumentationComments, IgnoredBindings}
|
||||
import org.enso.syntax.text.Location
|
||||
|
||||
import scala.annotation.unused
|
||||
|
||||
@ -146,8 +146,8 @@ case object UnreachableMatchBranches extends IRPass {
|
||||
branch.location match {
|
||||
case Some(branchLoc) =>
|
||||
Some(
|
||||
IdentifiedLocation(
|
||||
Location(loc.start, branchLoc.end),
|
||||
IdentifiedLocation.create(
|
||||
new Location(loc.start, branchLoc.end),
|
||||
loc.id
|
||||
)
|
||||
)
|
||||
|
@ -101,7 +101,7 @@ case object MethodDefinitions extends IRPass {
|
||||
if canGenerateStaticWrappers(tp) =>
|
||||
val dup = method.duplicate()
|
||||
val static = dup.copy(body =
|
||||
Function.Lambda(
|
||||
new Function.Lambda(
|
||||
List(
|
||||
DefinitionArgument
|
||||
.Specified(
|
||||
|
@ -128,9 +128,6 @@ case object ModuleAnnotations extends IRPass {
|
||||
|
||||
/** @inheritdoc */
|
||||
override def prepareForSerialization(compiler: Compiler): Annotations = {
|
||||
annotations.foreach(ir =>
|
||||
ir.preorder.foreach(_.passData.prepareForSerialization(compiler))
|
||||
)
|
||||
this
|
||||
}
|
||||
|
||||
@ -138,14 +135,6 @@ case object ModuleAnnotations extends IRPass {
|
||||
override def restoreFromSerialization(
|
||||
compiler: Compiler
|
||||
): Option[IRPass.IRMetadata] = {
|
||||
annotations.foreach { ann =>
|
||||
ann.preorder.foreach { ir =>
|
||||
if (!ir.passData.restoreFromSerialization(compiler)) {
|
||||
return None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(this)
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ class ImportResolver(compiler: Compiler) {
|
||||
.getCompilationStage(current)
|
||||
.isBefore(
|
||||
CompilationStage.AFTER_IMPORT_RESOLUTION
|
||||
) || !context.hasCrossModuleLinks(current)
|
||||
)
|
||||
) {
|
||||
val importedModules: List[
|
||||
(Import, Option[BindingsMap.ResolvedImport])
|
||||
|
@ -12,11 +12,11 @@ import org.enso.compiler.core.{
|
||||
}
|
||||
import org.enso.compiler.core.Implicits.AsMetadata
|
||||
import org.enso.compiler.core.ir.Literal
|
||||
import org.enso.compiler.core.ir.Location
|
||||
import org.enso.compiler.core.ir.Name
|
||||
import org.enso.compiler.core.ir.module.scope.definition
|
||||
import org.enso.compiler.pass.analyse.DataflowAnalysis
|
||||
import org.enso.interpreter.instrument.execution.model.PendingEdit
|
||||
import org.enso.syntax.text.Location
|
||||
import org.enso.text.editing.model.TextEdit
|
||||
import org.enso.text.editing.{IndexedSource, TextEditor}
|
||||
|
||||
@ -288,7 +288,7 @@ object ChangesetBuilder {
|
||||
* @return the node with a new location
|
||||
*/
|
||||
def shift(offset: Int): Node = {
|
||||
val newLocation = location.copy(
|
||||
val newLocation = new Location(
|
||||
start = location.start + offset,
|
||||
end = location.end + offset
|
||||
)
|
||||
@ -359,7 +359,7 @@ object ChangesetBuilder {
|
||||
val nodeBetweenPreviousPositionAndNextNode =
|
||||
Node(
|
||||
NodeId(currentIr),
|
||||
Location(previousPosition, nextNode.location.start),
|
||||
new Location(previousPosition, nextNode.location.start),
|
||||
false
|
||||
)
|
||||
acc += nodeBetweenPreviousPositionAndNextNode
|
||||
@ -378,7 +378,7 @@ object ChangesetBuilder {
|
||||
if (hasRemainingTextAfterLastChild) {
|
||||
val nodeAfterLastChild = Node(
|
||||
NodeId(currentIr),
|
||||
Location(lastCoveredPosition, endOfNonLeafIr),
|
||||
new Location(lastCoveredPosition, endOfNonLeafIr),
|
||||
false
|
||||
)
|
||||
acc += nodeAfterLastChild
|
||||
@ -491,7 +491,7 @@ object ChangesetBuilder {
|
||||
edit: TextEdit,
|
||||
source: A
|
||||
): Location = {
|
||||
Location(
|
||||
new Location(
|
||||
IndexedSource[A].toIndex(edit.range.start, source),
|
||||
IndexedSource[A].toIndex(edit.range.end, source)
|
||||
)
|
||||
|
@ -2,9 +2,9 @@ package org.enso.interpreter.instrument.execution
|
||||
|
||||
import com.oracle.truffle.api.source.SourceSection
|
||||
import org.enso.compiler.core.{ExternalID, IR, Identifier}
|
||||
import org.enso.compiler.core.ir.Location
|
||||
import org.enso.compiler.core.ir.IdentifiedLocation
|
||||
import org.enso.interpreter.runtime.Module
|
||||
import org.enso.syntax.text.Location
|
||||
import org.enso.text.editing.{model, IndexedSource}
|
||||
|
||||
import java.util.UUID
|
||||
@ -121,7 +121,7 @@ object LocationResolver {
|
||||
source: A
|
||||
): Location = {
|
||||
val range = sectionToRange(section)
|
||||
Location(
|
||||
new Location(
|
||||
IndexedSource[A].toIndex(range.start, source),
|
||||
IndexedSource[A].toIndex(range.end, source)
|
||||
)
|
||||
|
@ -12,6 +12,7 @@ import org.enso.compiler.core.ir.Empty;
|
||||
import org.enso.compiler.core.ir.Expression;
|
||||
import org.enso.compiler.core.ir.Function;
|
||||
import org.enso.compiler.core.ir.Literal;
|
||||
import org.enso.compiler.core.ir.Location;
|
||||
import org.enso.compiler.core.ir.Name;
|
||||
import org.enso.compiler.core.ir.MetadataStorage;
|
||||
import org.enso.compiler.core.ir.Module;
|
||||
@ -29,7 +30,6 @@ import org.enso.compiler.core.ir.module.scope.definition.Method;
|
||||
import org.enso.compiler.core.ir.module.scope.Export;
|
||||
import org.enso.compiler.core.ir.module.scope.Import;
|
||||
import org.enso.compiler.core.ir.module.scope.imports.Polyglot;
|
||||
import org.enso.syntax.text.Location;
|
||||
import org.enso.syntax2.ArgumentDefinition;
|
||||
import org.enso.syntax2.Base;
|
||||
import org.enso.syntax2.DocComment;
|
||||
@ -41,6 +41,7 @@ import org.enso.syntax2.Tree;
|
||||
import org.enso.syntax2.Tree.Invalid;
|
||||
|
||||
import org.enso.syntax2.Tree.Private;
|
||||
|
||||
import scala.Option;
|
||||
import scala.collection.immutable.LinearSeq;
|
||||
import scala.collection.immutable.List;
|
||||
@ -121,7 +122,7 @@ final class TreeToIr {
|
||||
locations.get(1).start(),
|
||||
locations.get(locations.size() - 1).end()
|
||||
),
|
||||
Option.empty()
|
||||
null
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -842,7 +843,7 @@ final class TreeToIr {
|
||||
var locationWithANewLine = getIdentifiedLocation(body, 0, 0, null);
|
||||
if (last != null && last.location().isDefined() && last.location().get().end() != locationWithANewLine.get().end()) {
|
||||
var patched = new Location(last.location().get().start(), locationWithANewLine.get().end() - 1);
|
||||
var id = new IdentifiedLocation(patched, last.location().get().id());
|
||||
var id = IdentifiedLocation.create(patched, last.location().get().id());
|
||||
last = last.setLocation(Option.apply(id));
|
||||
}
|
||||
yield new Expression.Block(list, last, locationWithANewLine, false, meta(), diag());
|
||||
@ -1693,7 +1694,7 @@ final class TreeToIr {
|
||||
Math.min(en.start(), in.start()),
|
||||
Math.max(en.end(), in.end())
|
||||
);
|
||||
return Option.apply(new IdentifiedLocation(loc, en.id()));
|
||||
return Option.apply(IdentifiedLocation.create(loc, en.id()));
|
||||
} else {
|
||||
return encapsulating;
|
||||
}
|
||||
@ -1716,7 +1717,7 @@ final class TreeToIr {
|
||||
default -> {
|
||||
var begin = castToInt(ast.getStartCode()) + b;
|
||||
var end = castToInt(ast.getEndCode()) + e;
|
||||
yield new IdentifiedLocation(new Location(begin, end), someId);
|
||||
yield IdentifiedLocation.create(new Location(begin, end), someId);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1751,7 +1752,7 @@ final class TreeToIr {
|
||||
end = ast.getPattern().getEndCode();
|
||||
}
|
||||
int end_ = castToInt(end);
|
||||
return Option.apply(new IdentifiedLocation(new Location(begin_, end_), Option.empty()));
|
||||
return Option.apply(IdentifiedLocation.create(new Location(begin_, end_), Option.empty()));
|
||||
}
|
||||
|
||||
private Option<IdentifiedLocation> getIdentifiedLocation(Token ast) {
|
||||
@ -1763,7 +1764,7 @@ final class TreeToIr {
|
||||
default -> {
|
||||
int begin = castToInt(ast.getStartCode());
|
||||
int end = castToInt(ast.getEndCode());
|
||||
var id = Option.apply(generateId ? UUID.randomUUID() : null);
|
||||
var id = generateId ? UUID.randomUUID() : null;
|
||||
yield new IdentifiedLocation(new Location(begin, end), id);
|
||||
}
|
||||
});
|
||||
|
@ -0,0 +1,45 @@
|
||||
package org.enso.compiler.core.ir;
|
||||
|
||||
import java.util.UUID;
|
||||
import scala.Option;
|
||||
|
||||
public record IdentifiedLocation(Location location, UUID uuid) {
|
||||
public IdentifiedLocation(Location location) {
|
||||
this(location, (UUID)null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new location from an optional UUID.
|
||||
*/
|
||||
public static IdentifiedLocation create(Location location, Option<UUID> uuid) {
|
||||
return new IdentifiedLocation(location, uuid.isEmpty() ? null : uuid.get());
|
||||
}
|
||||
|
||||
/** @return the character index of the start of this source location.
|
||||
*/
|
||||
public int start() {
|
||||
return location().start();
|
||||
}
|
||||
|
||||
/** @return the character index of the end of this source location.
|
||||
*/
|
||||
public int end() {
|
||||
return location().end();
|
||||
}
|
||||
|
||||
/** @return the length in characters of this location.
|
||||
*/
|
||||
public int length() {
|
||||
return location().length();
|
||||
}
|
||||
|
||||
/** @return option with/out UUID */
|
||||
public Option<UUID> id() {
|
||||
return Option.apply(uuid());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IdentifiedLocation[location=" + this.location() + ", uuid="+ id() + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package org.enso.compiler.core.ir;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
import org.enso.persist.Persistance;
|
||||
import scala.collection.Iterator;
|
||||
import scala.collection.SeqFactory;
|
||||
import scala.collection.immutable.AbstractSeq;
|
||||
|
||||
final class IrLazySeq extends AbstractSeq {
|
||||
private final Persistance.Reference<?>[] arr;
|
||||
private final int size;
|
||||
|
||||
IrLazySeq(Persistance.Reference<?>[] arr, int size) {
|
||||
this.arr = arr;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object apply(int i) throws IndexOutOfBoundsException {
|
||||
return arr[i].get(Object.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDefinedAt(int idx) {
|
||||
return 0 <= idx && idx < size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDefinedAt(Object idx) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object apply(Object i) throws IndexOutOfBoundsException {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SeqFactory iterableFactory() {
|
||||
return super.iterableFactory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator iterator() {
|
||||
return new IrIter();
|
||||
}
|
||||
|
||||
private final class IrIter implements Iterator {
|
||||
private int at;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return at < size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object next() throws NoSuchElementException {
|
||||
if (at >= size) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
return apply(at++);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,439 @@
|
||||
package org.enso.compiler.core.ir;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.enso.compiler.core.ir.expression.Application;
|
||||
import org.enso.compiler.core.ir.expression.Case;
|
||||
import org.enso.compiler.core.ir.expression.Foreign;
|
||||
import org.enso.compiler.core.ir.expression.warnings.Unused;
|
||||
import org.enso.compiler.core.ir.module.scope.Definition;
|
||||
import org.enso.compiler.core.ir.module.scope.Export;
|
||||
import org.enso.compiler.core.ir.module.scope.Import;
|
||||
import org.enso.compiler.core.ir.module.scope.definition.Method;
|
||||
import org.enso.compiler.core.ir.module.scope.imports.Polyglot;
|
||||
import org.enso.compiler.core.ir.type.Set;
|
||||
import org.enso.persist.Persistable;
|
||||
import org.enso.persist.Persistance;
|
||||
import org.openide.util.lookup.ServiceProvider;
|
||||
import scala.Option;
|
||||
import scala.Tuple2;
|
||||
import scala.collection.immutable.List;
|
||||
import scala.collection.immutable.Seq;
|
||||
|
||||
@Persistable(clazz = Module.class, id = 201)
|
||||
@Persistable(clazz = Name.Literal.class, id = 351)
|
||||
@Persistable(clazz = Import.Module.class, id = 342)
|
||||
@Persistable(clazz = Polyglot.class, id = 343)
|
||||
@Persistable(clazz = Export.Module.class, id = 344)
|
||||
@Persistable(clazz = Name.Qualified.class, id = 352)
|
||||
@Persistable(clazz = Method.Explicit.class, id = 361)
|
||||
@Persistable(clazz = Name.MethodReference.class, id = 362)
|
||||
@Persistable(clazz = Function.Lambda.class, id = 363)
|
||||
@Persistable(clazz = Polyglot.Java.class, id = 703)
|
||||
@Persistable(clazz = DefinitionArgument.Specified.class, id = 704)
|
||||
@Persistable(clazz = Name.Self.class, id = 705)
|
||||
@Persistable(clazz = Literal.Number.class, id = 706)
|
||||
@Persistable(clazz = Literal.Text.class, id = 707)
|
||||
@Persistable(clazz = CallArgument.Specified.class, id = 708)
|
||||
@Persistable(clazz = Definition.Type.class, id = 709)
|
||||
@Persistable(clazz = Definition.Data.class, id = 710)
|
||||
@Persistable(clazz = Name.Blank.class, id = 711)
|
||||
@Persistable(clazz = Name.GenericAnnotation.class, id = 712)
|
||||
@Persistable(clazz = Name.SelfType.class, id = 713)
|
||||
@Persistable(clazz = Expression.Block.class, id = 751)
|
||||
@Persistable(clazz = Expression.Binding.class, id = 752)
|
||||
@Persistable(clazz = Application.Prefix.class, id = 753)
|
||||
@Persistable(clazz = Application.Force.class, id = 754)
|
||||
@Persistable(clazz = Application.Sequence.class, id = 755)
|
||||
@Persistable(clazz = Case.Expr.class, id = 761)
|
||||
@Persistable(clazz = Case.Branch.class, id = 762)
|
||||
@Persistable(clazz = Pattern.Constructor.class, id = 763)
|
||||
@Persistable(clazz = Pattern.Name.class, id = 764)
|
||||
@Persistable(clazz = Pattern.Literal.class, id = 765)
|
||||
@Persistable(clazz = Pattern.Type.class, id = 766)
|
||||
@Persistable(clazz = Method.Conversion.class, id = 771)
|
||||
@Persistable(clazz = Set.Union.class, id = 772)
|
||||
@Persistable(clazz = Set.Intersection.class, id = 773)
|
||||
@Persistable(clazz = Foreign.Definition.class, id = 781)
|
||||
@Persistable(clazz = Type.Function.class, id = 782)
|
||||
@Persistable(clazz = Name.BuiltinAnnotation.class, id = 783)
|
||||
@Persistable(clazz = Type.Error.class, id = 784)
|
||||
@Persistable(clazz = Unused.Binding.class, id = 785)
|
||||
public final class IrPersistance {
|
||||
private IrPersistance() {}
|
||||
|
||||
@ServiceProvider(service = Persistance.class)
|
||||
public static final class PersistIdentifiedLocation extends Persistance<IdentifiedLocation> {
|
||||
public PersistIdentifiedLocation() {
|
||||
super(IdentifiedLocation.class, false, 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeObject(IdentifiedLocation obj, Output out) throws IOException {
|
||||
out.writeInline(Location.class, obj.location());
|
||||
out.writeInline(Option.class, obj.id());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected IdentifiedLocation readObject(Input in) throws IOException, ClassNotFoundException {
|
||||
var obj = in.readInline(Location.class);
|
||||
var id = in.readInline(Option.class);
|
||||
return IdentifiedLocation.create((Location) obj, id);
|
||||
}
|
||||
}
|
||||
|
||||
@ServiceProvider(service = Persistance.class)
|
||||
public static final class PersistUUID extends Persistance<UUID> {
|
||||
public PersistUUID() {
|
||||
super(UUID.class, false, 73);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeObject(UUID obj, Output out) throws IOException {
|
||||
out.writeLong(obj.getLeastSignificantBits());
|
||||
out.writeLong(obj.getMostSignificantBits());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UUID readObject(Input in) throws IOException, ClassNotFoundException {
|
||||
var least = in.readLong();
|
||||
var most = in.readLong();
|
||||
return new UUID(most, least);
|
||||
}
|
||||
}
|
||||
|
||||
@ServiceProvider(service = Persistance.class)
|
||||
public static final class PersistScalaOption extends Persistance<Option> {
|
||||
public PersistScalaOption() {
|
||||
super(Option.class, true, 4431);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeObject(Option obj, Output out) throws IOException {
|
||||
out.writeObject(obj.isEmpty() ? null : obj.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Option readObject(Input in) throws IOException, ClassNotFoundException {
|
||||
var obj = in.readObject();
|
||||
return Option.apply(obj);
|
||||
}
|
||||
}
|
||||
|
||||
@ServiceProvider(service = Persistance.class)
|
||||
public static final class PersistString extends Persistance<String> {
|
||||
public PersistString() {
|
||||
super(String.class, true, 4437);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeObject(String obj, Output out) throws IOException {
|
||||
out.writeUTF(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String readObject(Input in) throws IOException, ClassNotFoundException {
|
||||
var obj = in.readUTF();
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
@ServiceProvider(service = Persistance.class)
|
||||
public static final class PersistLong extends Persistance<Long> {
|
||||
public PersistLong() {
|
||||
super(Long.class, true, 4438);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeObject(Long obj, Output out) throws IOException {
|
||||
out.writeLong(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Long readObject(Input in) throws IOException, ClassNotFoundException {
|
||||
var obj = in.readLong();
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
@ServiceProvider(service = Persistance.class)
|
||||
public static final class PersistDouble extends Persistance<Double> {
|
||||
public PersistDouble() {
|
||||
super(Double.class, true, 4439);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeObject(Double obj, Output out) throws IOException {
|
||||
out.writeDouble(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Double readObject(Input in) throws IOException, ClassNotFoundException {
|
||||
var obj = in.readDouble();
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
@ServiceProvider(service = Persistance.class)
|
||||
public static final class PersistScalaList extends Persistance<List> {
|
||||
public PersistScalaList() {
|
||||
super(List.class, true, 4432);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeObject(List list, Output out) throws IOException {
|
||||
var size = list.size();
|
||||
out.writeInt(size);
|
||||
var l = list.reverse();
|
||||
for (var i = 0; i < size; i++) {
|
||||
out.writeObject(l.head());
|
||||
l = (List) l.tail();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected List readObject(Input in) throws IOException, ClassNotFoundException {
|
||||
var size = in.readInt();
|
||||
List list = scala.collection.immutable.Nil$.MODULE$;
|
||||
for (var i = 0; i < size; i++) {
|
||||
var elem = in.readObject();
|
||||
list = scala.collection.immutable.$colon$colon$.MODULE$.apply(elem, list);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
@ServiceProvider(service = Persistance.class)
|
||||
public static final class PersistScalaMap extends Persistance<scala.collection.immutable.Map> {
|
||||
public PersistScalaMap() {
|
||||
super(scala.collection.immutable.Map.class, true, 4444);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void writeObject(scala.collection.immutable.Map map, Output out) throws IOException {
|
||||
var size = map.size();
|
||||
out.writeInt(size);
|
||||
var it = map.iterator();
|
||||
while (size-- > 0) {
|
||||
var tuple = (Tuple2) it.next();
|
||||
out.writeObject(tuple._1());
|
||||
out.writeObject(tuple._2());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected scala.collection.immutable.Map readObject(Input in)
|
||||
throws IOException, ClassNotFoundException {
|
||||
var size = in.readInt();
|
||||
var map = scala.collection.immutable.Map$.MODULE$.empty();
|
||||
for (var i = 0; i < size; i++) {
|
||||
var key = in.readObject();
|
||||
var value = in.readObject();
|
||||
map = map.$plus(new Tuple2(key, value));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
@ServiceProvider(service = Persistance.class)
|
||||
public static final class PersistScalaMutableMap
|
||||
extends Persistance<scala.collection.mutable.Map> {
|
||||
public PersistScalaMutableMap() {
|
||||
super(scala.collection.mutable.Map.class, true, 4949);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void writeObject(scala.collection.mutable.Map map, Output out) throws IOException {
|
||||
var size = map.size();
|
||||
out.writeInt(size);
|
||||
var it = map.iterator();
|
||||
while (it.hasNext()) {
|
||||
var tuple = (Tuple2) it.next();
|
||||
out.writeObject(tuple._1());
|
||||
out.writeObject(tuple._2());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected scala.collection.mutable.Map readObject(Input in)
|
||||
throws IOException, ClassNotFoundException {
|
||||
var size = in.readInt();
|
||||
var map = scala.collection.mutable.Map$.MODULE$.empty();
|
||||
for (var i = 0; i < size; i++) {
|
||||
var key = in.readObject();
|
||||
var value = in.readObject();
|
||||
map.put(key, value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
@ServiceProvider(service = Persistance.class)
|
||||
public static final class PersistScalaSet extends Persistance<scala.collection.immutable.Set> {
|
||||
public PersistScalaSet() {
|
||||
super(scala.collection.immutable.Set.class, true, 4445);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void writeObject(scala.collection.immutable.Set set, Output out) throws IOException {
|
||||
var size = set.size();
|
||||
out.writeInt(size);
|
||||
var it = set.iterator();
|
||||
while (it.hasNext()) {
|
||||
var obj = it.next();
|
||||
out.writeObject(obj);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected scala.collection.immutable.Set readObject(Input in)
|
||||
throws IOException, ClassNotFoundException {
|
||||
var size = in.readInt();
|
||||
var map = scala.collection.immutable.Set$.MODULE$.empty();
|
||||
for (var i = 0; i < size; i++) {
|
||||
var elem = in.readObject();
|
||||
map = map.$plus(elem);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
@ServiceProvider(service = Persistance.class)
|
||||
public static final class PersistMap extends Persistance<HashMap> {
|
||||
public PersistMap() {
|
||||
super(HashMap.class, true, 4440);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeObject(HashMap m, Output out) throws IOException {
|
||||
var size = m.size();
|
||||
out.writeInt(size);
|
||||
var it = m.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
var entry = (Map.Entry) it.next();
|
||||
out.writeObject(entry.getKey());
|
||||
out.writeObject(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected HashMap readObject(Input in) throws IOException, ClassNotFoundException {
|
||||
var size = in.readInt();
|
||||
var map = new HashMap<Object, Object>();
|
||||
for (var i = 0; i < size; i++) {
|
||||
var key = in.readObject();
|
||||
var value = in.readObject();
|
||||
map.put(key, value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
@ServiceProvider(service = Persistance.class)
|
||||
public static final class PersistScalaSeq extends Persistance<Seq> {
|
||||
public PersistScalaSeq() {
|
||||
super(Seq.class, true, 4433);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeObject(Seq list, Output out) throws IOException {
|
||||
var size = list.size();
|
||||
out.writeInt(size);
|
||||
for (var i = 0; i < size; i++) {
|
||||
out.writeObject(list.apply(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Seq readObject(Input in) throws IOException, ClassNotFoundException {
|
||||
var size = in.readInt();
|
||||
Reference<?>[] arr = new Reference<?>[size];
|
||||
for (var i = 0; i < size; i++) {
|
||||
arr[i] = in.readReference(Object.class);
|
||||
}
|
||||
return new IrLazySeq(arr, size);
|
||||
}
|
||||
}
|
||||
|
||||
@ServiceProvider(service = Persistance.class)
|
||||
public static final class PersistMetadataStorage extends Persistance<MetadataStorage> {
|
||||
public PersistMetadataStorage() {
|
||||
super(MetadataStorage.class, false, 301);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void writeObject(MetadataStorage obj, Output out) throws IOException {
|
||||
var map =
|
||||
obj.map(
|
||||
(processingPass, data) -> {
|
||||
var t = new Tuple2<>(processingPass.getClass().getName(), data);
|
||||
return t;
|
||||
});
|
||||
out.writeInline(scala.collection.immutable.Map.class, map);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected MetadataStorage readObject(Input in) throws IOException, ClassNotFoundException {
|
||||
var storage = new MetadataStorage(nil());
|
||||
var map = in.readInline(scala.collection.immutable.Map.class);
|
||||
var it = map.iterator();
|
||||
while (it.hasNext()) {
|
||||
var obj = (Tuple2<String, ProcessingPass.Metadata>) it.next();
|
||||
try {
|
||||
var pass = (ProcessingPass) Class.forName(obj._1()).getField("MODULE$").get(null);
|
||||
var data = obj._2();
|
||||
storage.update(pass, data);
|
||||
} catch (ReflectiveOperationException ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
}
|
||||
return storage;
|
||||
}
|
||||
}
|
||||
|
||||
@ServiceProvider(service = Persistance.class)
|
||||
public static final class PersistDiagnosticStorage extends Persistance<DiagnosticStorage> {
|
||||
public PersistDiagnosticStorage() {
|
||||
super(DiagnosticStorage.class, false, 302);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeObject(DiagnosticStorage obj, Output out) throws IOException {}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected DiagnosticStorage readObject(Input in) throws IOException, ClassNotFoundException {
|
||||
return new DiagnosticStorage(
|
||||
(scala.collection.immutable.List) scala.collection.immutable.Nil$.MODULE$);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> scala.collection.immutable.List<T> nil() {
|
||||
return (scala.collection.immutable.List<T>) scala.collection.immutable.Nil$.MODULE$;
|
||||
}
|
||||
|
||||
private static <T> scala.collection.immutable.List<T> join(
|
||||
T head, scala.collection.immutable.List<T> tail) {
|
||||
return scala.collection.immutable.$colon$colon$.MODULE$.apply(head, tail);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <E extends Throwable> E raise(Class<E> clazz, Throwable t) throws E {
|
||||
throw (E) t;
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package org.enso.compiler.core.ir;
|
||||
|
||||
import org.enso.persist.Persistable;
|
||||
|
||||
@Persistable(clazz=Location.class, id=1)
|
||||
public record Location(int start, int end) {
|
||||
public int length() {
|
||||
return end - start;
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ package org.enso.compiler.core.ir
|
||||
*
|
||||
* @param initDiagnostics the initial diagnostics
|
||||
*/
|
||||
sealed class DiagnosticStorage(initDiagnostics: Seq[Diagnostic] = Seq())
|
||||
final class DiagnosticStorage(initDiagnostics: Seq[Diagnostic] = Seq())
|
||||
extends Serializable {
|
||||
private var diagnostics: List[Diagnostic] = initDiagnostics.toList
|
||||
|
||||
|
@ -45,14 +45,25 @@ object Function {
|
||||
*/
|
||||
sealed case class Lambda(
|
||||
override val arguments: List[DefinitionArgument],
|
||||
override val body: Expression,
|
||||
bodySeq: Seq[Expression],
|
||||
location: Option[IdentifiedLocation],
|
||||
override val canBeTCO: Boolean = true,
|
||||
passData: MetadataStorage = MetadataStorage(),
|
||||
diagnostics: DiagnosticStorage = DiagnosticStorage()
|
||||
override val canBeTCO: Boolean,
|
||||
passData: MetadataStorage,
|
||||
diagnostics: DiagnosticStorage
|
||||
) extends Function
|
||||
with IRKind.Primitive {
|
||||
def this(
|
||||
arguments: List[DefinitionArgument],
|
||||
body: Expression,
|
||||
location: Option[IdentifiedLocation],
|
||||
canBeTCO: Boolean = true,
|
||||
passData: MetadataStorage = new MetadataStorage(),
|
||||
diagnostics: DiagnosticStorage = new DiagnosticStorage()
|
||||
) = {
|
||||
this(arguments, Seq(body), location, canBeTCO, passData, diagnostics)
|
||||
}
|
||||
var id: UUID @Identifier = randomId
|
||||
override lazy val body = bodySeq.head
|
||||
|
||||
/** Creates a copy of `this`.
|
||||
*
|
||||
@ -75,7 +86,7 @@ object Function {
|
||||
id: UUID @Identifier = id
|
||||
): Lambda = {
|
||||
val res =
|
||||
Lambda(arguments, body, location, canBeTCO, passData, diagnostics)
|
||||
Lambda(arguments, Seq(body), location, canBeTCO, passData, diagnostics)
|
||||
res.id = id
|
||||
res
|
||||
}
|
||||
@ -150,6 +161,29 @@ object Function {
|
||||
}
|
||||
}
|
||||
|
||||
object Lambda {
|
||||
def unapply(l: Lambda): Some[
|
||||
(
|
||||
List[DefinitionArgument],
|
||||
Expression,
|
||||
Option[IdentifiedLocation],
|
||||
Boolean,
|
||||
MetadataStorage,
|
||||
DiagnosticStorage
|
||||
)
|
||||
] =
|
||||
Some(
|
||||
(
|
||||
l.arguments,
|
||||
l.body,
|
||||
l.location,
|
||||
l.canBeTCO,
|
||||
l.passData,
|
||||
l.diagnostics
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** A representation of the syntactic sugar for defining functions.
|
||||
*
|
||||
* @param name the name of the function
|
||||
|
@ -1,36 +0,0 @@
|
||||
package org.enso.compiler.core.ir
|
||||
|
||||
import org.enso.syntax.text.Location
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
/** Couples a location with a possible source identifier.
|
||||
*
|
||||
* @param location the code location.
|
||||
* @param id the identifier for the location.
|
||||
*/
|
||||
case class IdentifiedLocation(location: Location, id: Option[UUID]) {
|
||||
|
||||
/** @return the character index of the start of this source location.
|
||||
*/
|
||||
def start: Int = location.start
|
||||
|
||||
/** @return the character index of the end of this source location.
|
||||
*/
|
||||
def end: Int = location.end
|
||||
|
||||
/** @return the length in characters of this location.
|
||||
*/
|
||||
def length: Int = location.length
|
||||
}
|
||||
|
||||
object IdentifiedLocation {
|
||||
|
||||
/** Utility constructor, building a location without an ID.
|
||||
*
|
||||
* @param location the code location.
|
||||
* @return an [[IdentifiedLocation]] corresponding to the input location.
|
||||
*/
|
||||
def apply(location: Location): IdentifiedLocation =
|
||||
IdentifiedLocation(location, None)
|
||||
}
|
@ -11,7 +11,7 @@ import org.enso.compiler.core.CompilerStub
|
||||
* storage with
|
||||
*/
|
||||
//noinspection DuplicatedCode
|
||||
class MetadataStorage(
|
||||
final class MetadataStorage(
|
||||
startingMeta: Seq[MetadataPair[_]] = Seq()
|
||||
) extends Serializable {
|
||||
private var metadata: Map[ProcessingPass, Any] = Map(
|
||||
|
@ -23,7 +23,7 @@ import java.util.UUID
|
||||
@SerialVersionUID(
|
||||
8160L // Use BindingsMap
|
||||
) // prevents reading broken caches, see PR-3692 for details
|
||||
sealed case class Module(
|
||||
final case class Module(
|
||||
imports: List[Import],
|
||||
exports: List[Export],
|
||||
bindings: List[Definition],
|
||||
|
@ -3,7 +3,6 @@ package org.enso.compiler.core.ir
|
||||
import org.enso.compiler.core.{ConstantsNames, IR, Identifier}
|
||||
import org.enso.compiler.core.IR.randomId
|
||||
import org.enso.compiler.core.Implicits.{ShowPassData, ToStringHelper}
|
||||
import org.enso.syntax.text.Location
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
@ -45,7 +44,7 @@ object Name {
|
||||
* @param passData the pass metadata associated with this node
|
||||
* @param diagnostics compiler diagnostics for this node
|
||||
*/
|
||||
sealed case class MethodReference(
|
||||
final case class MethodReference(
|
||||
typePointer: Option[Name],
|
||||
methodName: Name,
|
||||
location: Option[IdentifiedLocation],
|
||||
@ -183,8 +182,8 @@ object Name {
|
||||
(identLoc, segment) => {
|
||||
identLoc.flatMap(loc => {
|
||||
Some(
|
||||
IdentifiedLocation(
|
||||
Location(
|
||||
new IdentifiedLocation(
|
||||
new Location(
|
||||
loc.location.start,
|
||||
segment.location
|
||||
.flatMap(l => Some(l.location.end))
|
||||
@ -206,7 +205,7 @@ object Name {
|
||||
* @param diagnostics compiler diagnostics for this node
|
||||
* @return a copy of `this`, updated with the specified values
|
||||
*/
|
||||
sealed case class Qualified(
|
||||
final case class Qualified(
|
||||
parts: List[Name],
|
||||
location: Option[IdentifiedLocation],
|
||||
passData: MetadataStorage = MetadataStorage(),
|
||||
|
@ -49,13 +49,34 @@ object Method {
|
||||
*/
|
||||
sealed case class Explicit(
|
||||
override val methodReference: Name.MethodReference,
|
||||
override val body: Expression,
|
||||
val bodySeq: Seq[Expression],
|
||||
val isStatic: Boolean,
|
||||
val isStaticWrapperForInstanceMethod: Boolean,
|
||||
override val location: Option[IdentifiedLocation],
|
||||
override val passData: MetadataStorage = MetadataStorage(),
|
||||
override val diagnostics: DiagnosticStorage = DiagnosticStorage()
|
||||
override val passData: MetadataStorage,
|
||||
override val diagnostics: DiagnosticStorage
|
||||
) extends Method
|
||||
with IRKind.Primitive {
|
||||
def this(
|
||||
methodReference: Name.MethodReference,
|
||||
body: Expression,
|
||||
location: Option[IdentifiedLocation],
|
||||
passData: MetadataStorage = MetadataStorage(),
|
||||
diagnostics: DiagnosticStorage = DiagnosticStorage()
|
||||
) = {
|
||||
this(
|
||||
methodReference,
|
||||
Seq(body),
|
||||
Explicit.computeIsStatic(body),
|
||||
Explicit.computeIsStaticWrapperForInstanceMethod(body),
|
||||
location,
|
||||
passData,
|
||||
diagnostics
|
||||
);
|
||||
}
|
||||
|
||||
var id: UUID @Identifier = randomId
|
||||
lazy val body = bodySeq.head
|
||||
|
||||
/** Creates a copy of `this`.
|
||||
*
|
||||
@ -70,14 +91,19 @@ object Method {
|
||||
def copy(
|
||||
methodReference: Name.MethodReference = methodReference,
|
||||
body: Expression = body,
|
||||
location: Option[IdentifiedLocation] = location,
|
||||
passData: MetadataStorage = passData,
|
||||
diagnostics: DiagnosticStorage = diagnostics,
|
||||
id: UUID @Identifier = id
|
||||
isStatic: Boolean = Explicit.computeIsStatic(body),
|
||||
isStaticWrapperForInstanceMethod: Boolean =
|
||||
Explicit.computeIsStaticWrapperForInstanceMethod(body),
|
||||
location: Option[IdentifiedLocation] = location,
|
||||
passData: MetadataStorage = passData,
|
||||
diagnostics: DiagnosticStorage = diagnostics,
|
||||
id: UUID @Identifier = id
|
||||
): Explicit = {
|
||||
val res = Explicit(
|
||||
methodReference,
|
||||
body,
|
||||
List(body),
|
||||
isStatic,
|
||||
isStaticWrapperForInstanceMethod,
|
||||
location,
|
||||
passData,
|
||||
diagnostics
|
||||
@ -156,8 +182,21 @@ object Method {
|
||||
|
||||
s"${methodReference.showCode(indent)} = $exprStr"
|
||||
}
|
||||
}
|
||||
|
||||
def isStatic: Boolean = body match {
|
||||
object Explicit {
|
||||
def unapply(m: Explicit): Option[
|
||||
(
|
||||
Name.MethodReference,
|
||||
Expression,
|
||||
Option[IdentifiedLocation],
|
||||
MetadataStorage,
|
||||
DiagnosticStorage
|
||||
)
|
||||
] = {
|
||||
Some((m.methodReference, m.body, m.location, m.passData, m.diagnostics))
|
||||
}
|
||||
private def computeIsStatic(body: IR): Boolean = body match {
|
||||
case function: Function.Lambda =>
|
||||
function.arguments.headOption.map(_.name) match {
|
||||
case Some(Name.Self(_, true, _, _)) => true
|
||||
@ -167,21 +206,21 @@ object Method {
|
||||
true // if it's not a function, it has no arguments, therefore no `self`
|
||||
}
|
||||
|
||||
def isStaticWrapperForInstanceMethod: Boolean = body match {
|
||||
case function: Function.Lambda =>
|
||||
function.arguments.map(_.name) match {
|
||||
case Name.Self(_, true, _, _) :: Name.Self(
|
||||
_,
|
||||
false,
|
||||
_,
|
||||
_
|
||||
) :: _ =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
case _ => false
|
||||
}
|
||||
|
||||
private def computeIsStaticWrapperForInstanceMethod(body: IR): Boolean =
|
||||
body match {
|
||||
case function: Function.Lambda =>
|
||||
function.arguments.map(_.name) match {
|
||||
case Name.Self(_, true, _, _) :: Name.Self(
|
||||
_,
|
||||
false,
|
||||
_,
|
||||
_
|
||||
) :: _ =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
/** The definition of a method for a given constructor using sugared
|
||||
|
@ -21,7 +21,11 @@ public class EnsoParserTest {
|
||||
|
||||
@BeforeClass
|
||||
public static void initEnsoParser() {
|
||||
ensoCompiler = new EnsoParser();
|
||||
try {
|
||||
ensoCompiler = new EnsoParser();
|
||||
} catch (LinkageError e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
@ -1252,7 +1256,7 @@ public class EnsoParserTest {
|
||||
""",
|
||||
"""
|
||||
# Comment with empty line
|
||||
|
||||
|
||||
private
|
||||
"""
|
||||
);
|
||||
@ -1345,29 +1349,13 @@ public class EnsoParserTest {
|
||||
* @return string representation of the IR
|
||||
*/
|
||||
private static String simplifyIR(IR ir, boolean noIds, boolean noLocations, boolean lessDocs) {
|
||||
if (noLocations) {
|
||||
ir = ir.duplicate(false, true, true, true);
|
||||
}
|
||||
String txt = ir.pretty();
|
||||
if (noIds) {
|
||||
txt = txt.replaceAll("[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]\\-[0-9a-f][0-9a-f][0-9a-f][0-9a-f]\\-[0-9a-f][0-9a-f][0-9a-f][0-9a-f]\\-[0-9a-f][0-9a-f][0-9a-f][0-9a-f]\\-[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]", "_");
|
||||
}
|
||||
if (noLocations) {
|
||||
for (;;) {
|
||||
final String pref = " Location(";
|
||||
int at = txt.indexOf(pref);
|
||||
if (at == -1) {
|
||||
break;
|
||||
}
|
||||
int to = at + pref.length();
|
||||
int depth = 1;
|
||||
while (depth > 0) {
|
||||
switch (txt.charAt(to)) {
|
||||
case '(' -> depth++;
|
||||
case ')' -> depth--;
|
||||
}
|
||||
to++;
|
||||
}
|
||||
txt = txt.substring(0, at) + "Location[_]" + txt.substring(to);
|
||||
}
|
||||
}
|
||||
if (lessDocs) {
|
||||
for (;;) {
|
||||
final String pref = "Comment.Documentation(";
|
||||
|
@ -0,0 +1,377 @@
|
||||
package org.enso.compiler.core;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.enso.compiler.core.ir.DiagnosticStorage;
|
||||
import org.enso.compiler.core.ir.IdentifiedLocation;
|
||||
import org.enso.compiler.core.ir.Location;
|
||||
import org.enso.compiler.core.ir.MetadataStorage;
|
||||
import org.enso.compiler.core.ir.Module;
|
||||
import org.enso.persist.Persistable;
|
||||
import org.enso.persist.Persistance;
|
||||
import org.junit.Test;
|
||||
import org.openide.util.lookup.ServiceProvider;
|
||||
|
||||
import scala.Option;
|
||||
import scala.Tuple2;
|
||||
import scala.collection.immutable.List;
|
||||
import scala.collection.immutable.Seq;
|
||||
|
||||
public class IrPersistanceTest {
|
||||
@Test
|
||||
public void locationTest() throws Exception {
|
||||
var l = new Location(12, 33);
|
||||
var n = serde(Location.class, l, 8);
|
||||
|
||||
assertEquals(12, n.start());
|
||||
assertEquals(33, n.end());
|
||||
assertEquals(l.length(), n.length());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void identifiedLocation() throws Exception {
|
||||
var il = new IdentifiedLocation(new Location(5, 19), null);
|
||||
var in = serde(IdentifiedLocation.class, il, 12);
|
||||
assertEquals(il, in);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void identifiedLocationNoUUID() throws Exception {
|
||||
var il = new IdentifiedLocation(new Location(5, 19), UUID.randomUUID());
|
||||
var in = serde(IdentifiedLocation.class, il, 32);
|
||||
assertEquals("UUIDs are serialized at the moment", il, in);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void scalaMap() throws Exception {
|
||||
var idLoc1 = new IdentifiedLocation(new Location(1, 5));
|
||||
var in = scala.collection.immutable.Map$.MODULE$.empty().$plus(new Tuple2("Hi", idLoc1));
|
||||
|
||||
var out = serde(scala.collection.immutable.Map.class, in, 36);
|
||||
|
||||
assertEquals("One element", 1, out.size());
|
||||
assertEquals(in, out);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void scalaHashMap() throws Exception {
|
||||
var idLoc1 = new IdentifiedLocation(new Location(1, 5));
|
||||
var immutable = join(new Tuple2("Hi", idLoc1), nil());
|
||||
var in =
|
||||
(scala.collection.mutable.HashMap)
|
||||
scala.collection.mutable.HashMap$.MODULE$.apply(immutable);
|
||||
|
||||
var out = serde(scala.collection.mutable.Map.class, in, 36);
|
||||
|
||||
assertEquals("One element", 1, out.size());
|
||||
assertEquals(in, out);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void scalaSet() throws Exception {
|
||||
var idLoc1 = new IdentifiedLocation(new Location(1, 5));
|
||||
var in = scala.collection.immutable.Set$.MODULE$.empty().$plus(idLoc1);
|
||||
|
||||
var out = serde(scala.collection.immutable.Set.class, in, 24);
|
||||
|
||||
assertEquals("One element", 1, out.size());
|
||||
assertEquals(in, out);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scalaList() throws Exception {
|
||||
var idLoc1 = new IdentifiedLocation(new Location(1, 5));
|
||||
var idLoc2 = new IdentifiedLocation(new Location(2, 4), UUID.randomUUID());
|
||||
var in = join(idLoc2, join(idLoc1, nil()));
|
||||
|
||||
var out = serde(List.class, in, 64);
|
||||
|
||||
assertEquals("Two elements", 2, out.size());
|
||||
assertEquals("UUIDs are serialized at the moment", idLoc2, out.head());
|
||||
assertEquals("Tail is the same", idLoc1, out.last());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scalaListSharedRef() throws Exception {
|
||||
var idLoc1 = new IdentifiedLocation(new Location(1, 5));
|
||||
var in = join(idLoc1, join(idLoc1, nil()));
|
||||
|
||||
var out = serde(List.class, in, 32);
|
||||
|
||||
assertEquals("Two elements", 2, out.size());
|
||||
assertEquals("Head is equal to original", idLoc1, out.head());
|
||||
assertEquals("Tail is equal to original", idLoc1, out.last());
|
||||
assertSame("Head and tail are the same", out.head(), out.last());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void scalaListSharedRefAtDepth() throws Exception {
|
||||
var idLoc1 = Singleton.INSTANCE;
|
||||
var in = join(Option.apply(idLoc1), join(Option.apply(idLoc1), nil()));
|
||||
|
||||
var out = serde(List.class, in, -1);
|
||||
|
||||
assertEquals("Two elements", 2, out.size());
|
||||
var readHead = (Option<Singleton>) out.head();
|
||||
var readTail = (Option<Singleton>) out.last();
|
||||
|
||||
assertEquals("Head is equal to original", idLoc1, readHead.get());
|
||||
assertEquals("Tail is equal to original", idLoc1, readTail.get());
|
||||
|
||||
assertNotSame("Head and tail are different", readHead, readTail);
|
||||
assertSame("Head and tail are the same", readHead.get(), readTail.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lazyScalaSequence() throws Exception {
|
||||
var s1 = new LazySeq("Hello");
|
||||
var s2 = new LazySeq("World");
|
||||
|
||||
var second = new boolean[1];
|
||||
@SuppressWarnings("unchecked")
|
||||
var in =
|
||||
(Seq<String>)
|
||||
Seq.fill(
|
||||
2,
|
||||
() -> {
|
||||
if (second[0]) {
|
||||
return s2;
|
||||
}
|
||||
second[0] = true;
|
||||
return s1;
|
||||
});
|
||||
assertEquals("Seq with two elements created", 2, in.length());
|
||||
|
||||
LazySeq.forbidden = true;
|
||||
var out = serde(Seq.class, in, -1);
|
||||
|
||||
assertEquals("Two elements", 2, out.size());
|
||||
|
||||
LazySeq.forbidden = false;
|
||||
|
||||
assertEquals("Lazily deserialized s2", s2, out.head());
|
||||
assertNotSame("Lazily deserialized s2", s2, out.head());
|
||||
assertEquals("Lazily deserialized s1", s1, out.last());
|
||||
assertNotSame("Lazily deserialized s1", s1, out.head());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serializeModule() throws Exception {
|
||||
var meta = new MetadataStorage(nil());
|
||||
var diag = new DiagnosticStorage(nil());
|
||||
var m = new Module(nil(), nil(), nil(), true, Option.empty(), meta, diag);
|
||||
|
||||
var out = serde(Module.class, m, -1);
|
||||
|
||||
assertEquals("Same", m, out);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hashMap() throws Exception {
|
||||
var map = new HashMap<String, String>();
|
||||
map.put("one", "uno");
|
||||
map.put("two", "duo");
|
||||
map.put("ten", "tre");
|
||||
|
||||
var out = serde(HashMap.class, map, -1);
|
||||
|
||||
assertEquals("Same", map, out);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readResolve() throws Exception {
|
||||
var in = new Service(5);
|
||||
var arr = Persistance.write(in, (Function<Object, Object>) null);
|
||||
|
||||
var plain = Persistance.read(arr, (Function<Object, Object>) null);
|
||||
assertEquals("Remains five", 5, plain.get(Service.class).value());
|
||||
|
||||
var multiOnRead = Persistance.read(arr, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj);
|
||||
assertEquals("Multiplied on read", 15, multiOnRead.get(Service.class).value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeReplace() throws Exception {
|
||||
var in = new Service(5);
|
||||
var arr = Persistance.write(in, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj);
|
||||
|
||||
var plain = Persistance.read(arr, (Function<Object, Object>) null);
|
||||
assertEquals("Multiplied on write", 15, plain.get(Service.class).value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readResolveInline() throws Exception {
|
||||
var in = new ServiceSupply(new Service(5));
|
||||
var arr = Persistance.write(in, (Function<Object, Object>) null);
|
||||
|
||||
var plain = Persistance.read(arr, (Function<Object, Object>) null);
|
||||
assertEquals("Remains five", 5, plain.get(ServiceSupply.class).supply().value());
|
||||
|
||||
var multiOnRead = Persistance.read(arr, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj);
|
||||
assertEquals("Multiplied on read", 15, multiOnRead.get(ServiceSupply.class).supply().value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeReplaceInline() throws Exception {
|
||||
var in = new ServiceSupply(new Service(5));
|
||||
var arr = Persistance.write(in, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj);
|
||||
|
||||
var plain = Persistance.read(arr, (Function<Object, Object>) null);
|
||||
assertEquals("Multiplied on write", 15, plain.get(ServiceSupply.class).supply().value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readResolveReference() throws Exception {
|
||||
var in = new IntegerSupply(new Service(5));
|
||||
var arr = Persistance.write(in, (Function<Object, Object>) null);
|
||||
|
||||
var plain = Persistance.read(arr, (Function<Object, Object>) null);
|
||||
assertEquals("Remains five", 5, (int) plain.get(IntegerSupply.class).supply().get());
|
||||
assertEquals("Remains five 2", 5, (int) plain.get(IntegerSupply.class).supply().get());
|
||||
|
||||
var multiOnRead = Persistance.read(arr, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj);
|
||||
assertEquals("Multiplied on read", 15, (int) multiOnRead.get(IntegerSupply.class).supply().get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeReplaceReference() throws Exception {
|
||||
var in = new IntegerSupply(new Service(5));
|
||||
var arr = Persistance.write(in, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj);
|
||||
|
||||
var plain = Persistance.read(arr, (Function<Object, Object>) null);
|
||||
assertEquals("Multiplied on write", 15, (int) plain.get(IntegerSupply.class).supply().get());
|
||||
}
|
||||
|
||||
private static <T> T serde(Class<T> clazz, T l, int expectedSize) throws IOException {
|
||||
var arr = Persistance.write(l, (Function<Object, Object>) null);
|
||||
if (expectedSize >= 0) {
|
||||
assertEquals(expectedSize, arr.length - 12);
|
||||
}
|
||||
var ref = Persistance.read(arr, (Function<Object, Object>) null);
|
||||
return ref.get(clazz);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static final <T> scala.collection.immutable.List<T> nil() {
|
||||
return (scala.collection.immutable.List<T>) scala.collection.immutable.Nil$.MODULE$;
|
||||
}
|
||||
|
||||
private static final <T> scala.collection.immutable.List<T> join(
|
||||
T head, scala.collection.immutable.List<T> tail) {
|
||||
return scala.collection.immutable.$colon$colon$.MODULE$.apply(head, tail);
|
||||
}
|
||||
|
||||
private static class LazySeq implements CharSequence {
|
||||
|
||||
private static boolean forbidden;
|
||||
|
||||
private final String value;
|
||||
|
||||
public LazySeq(String value) {
|
||||
if (forbidden) {
|
||||
throw new IllegalStateException("Cannot create LazySeq right now!");
|
||||
}
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public char charAt(int index) {
|
||||
return value.charAt(index);
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return value.length();
|
||||
}
|
||||
|
||||
public CharSequence subSequence(int beginIndex, int endIndex) {
|
||||
return value.subSequence(beginIndex, endIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 5;
|
||||
hash = 53 * hash + Objects.hashCode(this.value);
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final LazySeq other = (LazySeq) obj;
|
||||
return Objects.equals(this.value, other.value);
|
||||
}
|
||||
}
|
||||
|
||||
@ServiceProvider(service = Persistance.class)
|
||||
public static final class PersistLazySeq extends Persistance<LazySeq> {
|
||||
|
||||
public PersistLazySeq() {
|
||||
super(LazySeq.class, false, 432432);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeObject(LazySeq obj, Output out) throws IOException {
|
||||
out.writeUTF(obj.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LazySeq readObject(Input in) throws IOException, ClassNotFoundException {
|
||||
var s = in.readUTF();
|
||||
return new LazySeq(s);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Singleton {
|
||||
public static final Singleton INSTANCE = new Singleton();
|
||||
|
||||
private Singleton() {}
|
||||
}
|
||||
|
||||
@ServiceProvider(service = Persistance.class)
|
||||
public static final class PersistSingleton extends Persistance<Singleton> {
|
||||
|
||||
public PersistSingleton() {
|
||||
super(Singleton.class, false, 432433);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeObject(Singleton obj, Output out) throws IOException {}
|
||||
|
||||
@Override
|
||||
protected Singleton readObject(Input in) throws IOException, ClassNotFoundException {
|
||||
return Singleton.INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
@Persistable(clazz=Service.class, id=432434)
|
||||
public record Service(int value) implements Supplier<Integer> {
|
||||
@Override
|
||||
public Integer get() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@Persistable(clazz=IntegerSupply.class, id=432435)
|
||||
public record IntegerSupply(Supplier<Integer> supply) {}
|
||||
|
||||
@Persistable(clazz=ServiceSupply.class, id=432436)
|
||||
public record ServiceSupply(Service supply) {}
|
||||
}
|
@ -217,7 +217,7 @@ public abstract class Cache<T, M extends Cache.Metadata> {
|
||||
}
|
||||
|
||||
logger.log(logLevel, "Unable to load a cache [" + logName + "]");
|
||||
} catch (IOException e) {
|
||||
} catch (Exception e) {
|
||||
logger.log(
|
||||
Level.WARNING,
|
||||
"Unable to load a cache [" + logName + "]: " + e.getMessage(),
|
||||
@ -271,14 +271,13 @@ public abstract class Cache<T, M extends Cache.Metadata> {
|
||||
invalidateCache(cacheRoot, logger);
|
||||
return Optional.empty();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
} catch (ClassNotFoundException | IOException ex) {
|
||||
logger.log(
|
||||
logLevel, "`" + logName + "` failed to load (caused by: " + ioe.getMessage() + ").");
|
||||
Level.WARNING,
|
||||
"`" + logName + "` in " + dataPath + " failed to load: " + ex.getMessage());
|
||||
logger.log(logLevel, "`" + logName + "` failed to load.", ex);
|
||||
invalidateCache(cacheRoot, logger);
|
||||
return Optional.empty();
|
||||
} catch (ClassNotFoundException e) {
|
||||
logger.log(Level.WARNING, logName + " appears to be corrupted", e);
|
||||
return Optional.empty();
|
||||
}
|
||||
} else {
|
||||
logger.log(logLevel, "One or more digests did not match for the cache for [{0}].", logName);
|
||||
|
@ -1,21 +1,21 @@
|
||||
package org.enso.interpreter.caches;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.enso.persist.Persistance;
|
||||
import org.enso.persist.Persistable;
|
||||
import org.enso.compiler.data.BindingsMap;
|
||||
import org.enso.compiler.data.BindingsMap.DefinedEntity;
|
||||
import org.enso.compiler.data.BindingsMap.ModuleReference;
|
||||
import org.enso.editions.LibraryName;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.pkg.QualifiedName;
|
||||
import org.enso.pkg.SourceFile;
|
||||
import org.openide.util.lookup.ServiceProvider;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
@ -24,8 +24,11 @@ import com.oracle.truffle.api.TruffleFile;
|
||||
import com.oracle.truffle.api.TruffleLogger;
|
||||
|
||||
import buildinfo.Info;
|
||||
import scala.Option;
|
||||
import scala.Tuple2;
|
||||
import scala.collection.immutable.Map;
|
||||
|
||||
@Persistable(clazz = QualifiedName.class, id = 30300)
|
||||
public final class ImportExportCache extends Cache<ImportExportCache.CachedBindings, ImportExportCache.Metadata> {
|
||||
|
||||
private final LibraryName libraryName;
|
||||
@ -49,13 +52,9 @@ public final class ImportExportCache extends Cache<ImportExportCache.CachedBindi
|
||||
|
||||
@Override
|
||||
protected CachedBindings deserialize(EnsoContext context, byte[] data, Metadata meta, TruffleLogger logger) throws ClassNotFoundException, IOException, ClassNotFoundException {
|
||||
try (var stream = new ObjectInputStream(new ByteArrayInputStream(data))) {
|
||||
if (stream.readObject() instanceof MapToBindings bindings) {
|
||||
return new CachedBindings(libraryName, bindings, Optional.empty());
|
||||
} else {
|
||||
throw new ClassNotFoundException("Expected ImportExportCache.FileToBindings, got " + data.getClass());
|
||||
}
|
||||
}
|
||||
var ref = Persistance.read(data, null);
|
||||
var bindings = ref.get(MapToBindings.class);
|
||||
return new CachedBindings(libraryName, bindings, Optional.empty());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -107,25 +106,58 @@ public final class ImportExportCache extends Cache<ImportExportCache.CachedBindi
|
||||
|
||||
@Override
|
||||
protected byte[] serialize(EnsoContext context, CachedBindings entry) throws IOException {
|
||||
var byteStream = new ByteArrayOutputStream();
|
||||
try (ObjectOutputStream stream = new ObjectOutputStream(byteStream)) {
|
||||
stream.writeObject(entry.bindings());
|
||||
}
|
||||
return byteStream.toByteArray();
|
||||
var arr = Persistance.write(entry.bindings(), null);
|
||||
return arr;
|
||||
}
|
||||
|
||||
public static final class MapToBindings implements Serializable {
|
||||
private final Map<QualifiedName, BindingsMap> _entries;
|
||||
public static final class MapToBindings {
|
||||
private final Map<QualifiedName, Persistance.Reference<BindingsMap>> entries;
|
||||
|
||||
public MapToBindings(Map<QualifiedName, BindingsMap> entries) {
|
||||
this._entries = entries;
|
||||
public MapToBindings(Map<QualifiedName, Persistance.Reference<BindingsMap>> entries) {
|
||||
this.entries = entries;
|
||||
}
|
||||
|
||||
public Map<QualifiedName, BindingsMap> entries() {
|
||||
return _entries;
|
||||
public Option<BindingsMap> findForModule(QualifiedName moduleName) {
|
||||
var ref = entries.get(moduleName);
|
||||
if (ref.isEmpty()) {
|
||||
return Option.empty();
|
||||
}
|
||||
return Option.apply(ref.get().get(BindingsMap.class));
|
||||
}
|
||||
}
|
||||
|
||||
@ServiceProvider(service = Persistance.class)
|
||||
public static final class PersistMapToBindings extends Persistance<MapToBindings> {
|
||||
public PersistMapToBindings() {
|
||||
super(MapToBindings.class, false, 364);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeObject(MapToBindings obj, Output out) throws IOException {
|
||||
out.writeInt(obj.entries.size());
|
||||
var it = obj.entries.iterator();
|
||||
while (it.hasNext()) {
|
||||
var e = it.next();
|
||||
out.writeInline(QualifiedName.class, e._1());
|
||||
out.writeObject(e._2().get(BindingsMap.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected MapToBindings readObject(Input in) throws IOException, ClassNotFoundException {
|
||||
var size = in.readInt();
|
||||
var b = Map.newBuilder();
|
||||
b.sizeHint(size);
|
||||
while (size-- > 0) {
|
||||
var name = in.readInline(QualifiedName.class);
|
||||
var value = in.readReference(BindingsMap.class);
|
||||
b.addOne(Tuple2.apply(name, value));
|
||||
}
|
||||
return new MapToBindings((Map) b.result());
|
||||
}
|
||||
}
|
||||
|
||||
public static record CachedBindings(LibraryName libraryName, MapToBindings bindings, Optional<List<SourceFile<TruffleFile>>> sources) {
|
||||
}
|
||||
|
||||
@ -139,4 +171,57 @@ public final class ImportExportCache extends Cache<ImportExportCache.CachedBindi
|
||||
|
||||
private final static ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
|
||||
@Persistable(clazz=BindingsMap.PolyglotSymbol.class, id=33006)
|
||||
@Persistable(clazz=org.enso.compiler.data.BindingsMap$ModuleReference$Abstract.class, id=33007)
|
||||
@Persistable(clazz=BindingsMap.ModuleMethod.class, id=33008)
|
||||
@Persistable(clazz=BindingsMap.Type.class, id=33009)
|
||||
@Persistable(clazz=BindingsMap.ResolvedImport.class, id=33010)
|
||||
@Persistable(clazz=BindingsMap.Cons.class, id=33011)
|
||||
@Persistable(clazz=BindingsMap.ResolvedModule.class, id=33012)
|
||||
@Persistable(clazz=BindingsMap.ResolvedType.class, id=33013)
|
||||
@Persistable(clazz=BindingsMap.ResolvedMethod.class, id=33014)
|
||||
@Persistable(clazz=BindingsMap.ExportedModule.class, id=33015)
|
||||
@Persistable(clazz=org.enso.compiler.data.BindingsMap$SymbolRestriction$Only.class, id=33016)
|
||||
@Persistable(clazz=org.enso.compiler.data.BindingsMap$SymbolRestriction$Union.class, id=33017)
|
||||
@Persistable(clazz=org.enso.compiler.data.BindingsMap$SymbolRestriction$Intersect.class, id=33018)
|
||||
@Persistable(clazz=org.enso.compiler.data.BindingsMap$SymbolRestriction$AllowedResolution.class, id=33019)
|
||||
@Persistable(clazz=org.enso.compiler.data.BindingsMap$SymbolRestriction$All$.class, id=33020)
|
||||
@Persistable(clazz=org.enso.compiler.data.BindingsMap$SymbolRestriction$Hiding.class, id=33021)
|
||||
@Persistable(clazz=BindingsMap.Resolution.class, id=33029)
|
||||
@Persistable(clazz=BindingsMap.ResolvedConstructor.class, id=33030)
|
||||
@Persistable(clazz=BindingsMap.ResolvedPolyglotSymbol.class, id=33031)
|
||||
@Persistable(clazz=BindingsMap.ResolvedPolyglotField.class, id=33032)
|
||||
@ServiceProvider(service = Persistance.class)
|
||||
public static final class PersistBindingsMap extends Persistance<BindingsMap> {
|
||||
public PersistBindingsMap() {
|
||||
super(BindingsMap.class, false, 33005);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeObject(BindingsMap obj, Output out) throws IOException {
|
||||
out.writeObject(obj.definedEntities());
|
||||
out.writeObject(obj.currentModule());
|
||||
out.writeInline(scala.collection.immutable.List.class, obj.resolvedImports());
|
||||
out.writeInline(scala.collection.immutable.List.class, obj.resolvedExports());
|
||||
out.writeInline(scala.collection.immutable.Map.class, obj.exportedSymbols());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected BindingsMap readObject(Input in) throws IOException, ClassNotFoundException {
|
||||
var de = (scala.collection.immutable.List<DefinedEntity>) in.readObject();
|
||||
var cm = (ModuleReference) in.readObject();
|
||||
var imp = in.readInline(scala.collection.immutable.List.class);
|
||||
var exp = in.readInline(scala.collection.immutable.List.class);
|
||||
var sym = in.readInline(scala.collection.immutable.Map.class);
|
||||
var map = new BindingsMap(de, cm);
|
||||
map.resolvedImports_$eq(imp);
|
||||
map.resolvedExports_$eq(exp);
|
||||
map.exportedSymbols_$eq(sym);
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,28 +1,28 @@
|
||||
package org.enso.interpreter.caches;
|
||||
|
||||
import buildinfo.Info;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.oracle.truffle.api.TruffleLogger;
|
||||
import com.oracle.truffle.api.source.Source;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.enso.compiler.core.ir.Module;
|
||||
import org.enso.compiler.core.ir.ProcessingPass;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.builtin.Builtins;
|
||||
import org.enso.polyglot.CompilationStage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.enso.persist.Persistance;
|
||||
|
||||
public final class ModuleCache extends Cache<ModuleCache.CachedModule, ModuleCache.Metadata> {
|
||||
|
||||
private final org.enso.interpreter.runtime.Module module;
|
||||
@ -46,17 +46,19 @@ public final class ModuleCache extends Cache<ModuleCache.CachedModule, ModuleCac
|
||||
|
||||
@Override
|
||||
protected CachedModule deserialize(EnsoContext context, byte[] data, Metadata meta, TruffleLogger logger) throws ClassNotFoundException, IOException, ClassNotFoundException {
|
||||
try (var stream = new ObjectInputStream(new ByteArrayInputStream(data))) {
|
||||
if (stream.readObject() instanceof Module ir) {
|
||||
try {
|
||||
return new CachedModule(ir,CompilationStage.valueOf(meta.compilationStage()), module.getSource());
|
||||
} catch (IOException ioe) {
|
||||
throw new ClassNotFoundException(ioe.getMessage());
|
||||
}
|
||||
var ref = Persistance.read(data, (obj) -> switch (obj) {
|
||||
case ProcessingPass.Metadata metadata -> {
|
||||
var option = metadata.restoreFromSerialization(context.getCompiler().context());
|
||||
if (option.nonEmpty()) {
|
||||
yield option.get();
|
||||
} else {
|
||||
throw new ClassNotFoundException("Expected Module, got " + data.getClass());
|
||||
throw raise(RuntimeException.class, new IOException("Cannot convert " + metadata));
|
||||
}
|
||||
}
|
||||
default -> obj;
|
||||
});
|
||||
var mod = ref.get(Module.class);
|
||||
return new CachedModule(mod, CompilationStage.valueOf(meta.compilationStage()), module.getSource());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -142,37 +144,11 @@ public final class ModuleCache extends Cache<ModuleCache.CachedModule, ModuleCac
|
||||
|
||||
@Override
|
||||
protected byte[] serialize(EnsoContext context, CachedModule entry) throws IOException {
|
||||
var byteStream = new ByteArrayOutputStream();
|
||||
boolean noUUIDs = false;
|
||||
for (var p : context.getPackageRepository().getLoadedPackagesJava()) {
|
||||
if ("Standard".equals(p.namespace())) {
|
||||
for (var s : p.listSourcesJava()) {
|
||||
if (s.file().getPath().equals(entry.source().getPath())) {
|
||||
noUUIDs = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
try (var stream = new ObjectOutputStream(byteStream) {
|
||||
void filterUUIDs() {
|
||||
enableReplaceObject(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object replaceObject(Object obj) throws IOException {
|
||||
if (obj instanceof UUID) {
|
||||
return null;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
}) {
|
||||
if (noUUIDs) {
|
||||
stream.filterUUIDs();
|
||||
}
|
||||
stream.writeObject(entry.moduleIR());
|
||||
}
|
||||
return byteStream.toByteArray();
|
||||
var arr = Persistance.write(entry.moduleIR(), (obj) -> switch (obj) {
|
||||
case ProcessingPass.Metadata metadata -> metadata.prepareForSerialization(context.getCompiler().context());
|
||||
default -> obj;
|
||||
});
|
||||
return arr;
|
||||
}
|
||||
|
||||
public record CachedModule(Module moduleIR, CompilationStage compilationStage, Source source) {
|
||||
@ -189,4 +165,8 @@ public final class ModuleCache extends Cache<ModuleCache.CachedModule, ModuleCac
|
||||
|
||||
private final static ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T extends Exception> T raise(Class<T> cls, Exception e) throws T {
|
||||
throw (T)e;
|
||||
}
|
||||
}
|
||||
|
@ -62,7 +62,6 @@ public final class Module implements EnsoObject {
|
||||
private QualifiedName name;
|
||||
private final ModuleCache cache;
|
||||
private boolean wasLoadedFromCache;
|
||||
private boolean hasCrossModuleLinks;
|
||||
private final boolean synthetic;
|
||||
/**
|
||||
* This list is filled in case there is a directory with the same name as this module. The
|
||||
@ -88,7 +87,6 @@ public final class Module implements EnsoObject {
|
||||
this.name = name;
|
||||
this.cache = new ModuleCache(this);
|
||||
this.wasLoadedFromCache = false;
|
||||
this.hasCrossModuleLinks = false;
|
||||
this.synthetic = false;
|
||||
}
|
||||
|
||||
@ -106,7 +104,6 @@ public final class Module implements EnsoObject {
|
||||
this.name = name;
|
||||
this.cache = new ModuleCache(this);
|
||||
this.wasLoadedFromCache = false;
|
||||
this.hasCrossModuleLinks = false;
|
||||
this.patchedValues = new PatchedModuleValues(this);
|
||||
this.synthetic = false;
|
||||
}
|
||||
@ -125,7 +122,6 @@ public final class Module implements EnsoObject {
|
||||
this.name = name;
|
||||
this.cache = new ModuleCache(this);
|
||||
this.wasLoadedFromCache = false;
|
||||
this.hasCrossModuleLinks = false;
|
||||
this.patchedValues = new PatchedModuleValues(this);
|
||||
this.synthetic = false;
|
||||
}
|
||||
@ -147,7 +143,6 @@ public final class Module implements EnsoObject {
|
||||
this.compilationStage = synthetic ? CompilationStage.INITIAL : CompilationStage.AFTER_CODEGEN;
|
||||
this.cache = new ModuleCache(this);
|
||||
this.wasLoadedFromCache = false;
|
||||
this.hasCrossModuleLinks = false;
|
||||
this.synthetic = synthetic;
|
||||
}
|
||||
|
||||
@ -516,19 +511,6 @@ public final class Module implements EnsoObject {
|
||||
this.wasLoadedFromCache = wasLoadedFromCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} if the module has had its cross-module links restored, otherwise {@code
|
||||
* false}
|
||||
*/
|
||||
public boolean hasCrossModuleLinks() {
|
||||
return hasCrossModuleLinks;
|
||||
}
|
||||
|
||||
/** @param hasCrossModuleLinks whether or not the module has cross-module links restored */
|
||||
void setHasCrossModuleLinks(boolean hasCrossModuleLinks) {
|
||||
this.hasCrossModuleLinks = hasCrossModuleLinks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns this module into appropriate {@link CompilerContext} wrapper.
|
||||
*
|
||||
|
@ -150,11 +150,6 @@ final class TruffleCompilerContext implements CompilerContext {
|
||||
return ((Module)module).unsafeModule().wasLoadedFromCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCrossModuleLinks(CompilerContext.Module module) {
|
||||
return ((Module)module).unsafeModule().hasCrossModuleLinks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public org.enso.compiler.core.ir.Module getIr(CompilerContext.Module module) {
|
||||
return module.getIr();
|
||||
@ -225,17 +220,9 @@ final class TruffleCompilerContext implements CompilerContext {
|
||||
);
|
||||
} else {
|
||||
builtins.initializeBuiltinsIr(this, freshNameSupply, passes);
|
||||
updateModule(
|
||||
builtinsModule,
|
||||
u -> u.hasCrossModuleLinks(true)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
builtins.initializeBuiltinsIr(this, freshNameSupply, passes);
|
||||
updateModule(
|
||||
builtinsModule,
|
||||
u -> u.hasCrossModuleLinks(true)
|
||||
);
|
||||
}
|
||||
|
||||
if (irCachingEnabled && !wasLoadedFromCache(builtinsModule)) {
|
||||
@ -288,7 +275,6 @@ final class TruffleCompilerContext implements CompilerContext {
|
||||
private org.enso.compiler.core.ir.Module ir;
|
||||
private CompilationStage stage;
|
||||
private Boolean loadedFromCache;
|
||||
private Boolean hasCrossModuleLinks;
|
||||
private boolean resetScope;
|
||||
private boolean invalidateCache;
|
||||
|
||||
@ -316,11 +302,6 @@ final class TruffleCompilerContext implements CompilerContext {
|
||||
this.loadedFromCache = b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasCrossModuleLinks(boolean b) {
|
||||
this.hasCrossModuleLinks = b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetScope() {
|
||||
this.resetScope = true;
|
||||
@ -348,9 +329,6 @@ final class TruffleCompilerContext implements CompilerContext {
|
||||
if (loadedFromCache != null) {
|
||||
module.module.setLoadedFromCache(loadedFromCache);
|
||||
}
|
||||
if (hasCrossModuleLinks != null) {
|
||||
module.module.setHasCrossModuleLinks(hasCrossModuleLinks);
|
||||
}
|
||||
if (resetScope) {
|
||||
module.module.ensureScopeExists();
|
||||
module.module.getScope().reset();
|
||||
@ -429,11 +407,6 @@ final class TruffleCompilerContext implements CompilerContext {
|
||||
return module.isSynthetic();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCrossModuleLinks() {
|
||||
return module.hasCrossModuleLinks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public org.enso.compiler.core.ir.Module getIr() {
|
||||
return module.getIr();
|
||||
|
@ -10,6 +10,7 @@ import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import org.enso.interpreter.runtime.Module;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
@ -24,7 +25,7 @@ public final class ModuleScope implements EnsoObject {
|
||||
private final Module module;
|
||||
private Map<String, Object> polyglotSymbols;
|
||||
private Map<String, Type> types;
|
||||
private Map<Type, Map<String, Function>> methods;
|
||||
private Map<Type, Map<String, Supplier<Function>>> methods;
|
||||
private Map<Type, Map<Type, Function>> conversions;
|
||||
private Set<ModuleScope> imports;
|
||||
private Set<ModuleScope> exports;
|
||||
@ -56,7 +57,7 @@ public final class ModuleScope implements EnsoObject {
|
||||
Type associatedType,
|
||||
Map<String, Object> polyglotSymbols,
|
||||
Map<String, Type> types,
|
||||
Map<Type, Map<String, Function>> methods,
|
||||
Map<Type, Map<String, Supplier<Function>>> methods,
|
||||
Map<Type, Map<Type, Function>> conversions,
|
||||
Set<ModuleScope> imports,
|
||||
Set<ModuleScope> exports) {
|
||||
@ -95,14 +96,14 @@ public final class ModuleScope implements EnsoObject {
|
||||
* @param type the type for which method map is requested
|
||||
* @return a map containing all the defined methods by name
|
||||
*/
|
||||
private Map<String, Function> ensureMethodMapFor(Type type) {
|
||||
private Map<String, Supplier<Function>> ensureMethodMapFor(Type type) {
|
||||
Type tpeKey = type == null ? noTypeKey : type;
|
||||
return methods.computeIfAbsent(tpeKey, k -> new HashMap<>());
|
||||
}
|
||||
|
||||
private Map<String, Function> getMethodMapFor(Type type) {
|
||||
private Map<String, Supplier<Function>> getMethodMapFor(Type type) {
|
||||
Type tpeKey = type == null ? noTypeKey : type;
|
||||
Map<String, Function> result = methods.get(type);
|
||||
Map<String, Supplier<Function>> result = methods.get(type);
|
||||
if (result == null) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
@ -117,14 +118,55 @@ public final class ModuleScope implements EnsoObject {
|
||||
* @param function the {@link Function} associated with this definition
|
||||
*/
|
||||
public void registerMethod(Type type, String method, Function function) {
|
||||
Map<String, Function> methodMap = ensureMethodMapFor(type);
|
||||
Map<String, Supplier<Function>> methodMap = ensureMethodMapFor(type);
|
||||
|
||||
// Builtin types will have double definition because of
|
||||
// BuiltinMethod and that's OK
|
||||
if (methodMap.containsKey(method) && !type.isBuiltin()) {
|
||||
throw new RedefinedMethodException(type.getName(), method);
|
||||
} else {
|
||||
methodMap.put(method, function);
|
||||
methodMap.put(method, new CachingSupplier<>(function));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a lazily constructed method defined for a given type.
|
||||
*
|
||||
* @param type the type the method was defined for
|
||||
* @param method method name
|
||||
* @param supply provider of the {@link Function} associated with this definition
|
||||
*/
|
||||
public void registerMethod(Type type, String method, Supplier<Function> supply) {
|
||||
Map<String, Supplier<Function>> methodMap = ensureMethodMapFor(type);
|
||||
|
||||
// Builtin types will have double definition because of
|
||||
// BuiltinMethod and that's OK
|
||||
if (methodMap.containsKey(method) && !type.isBuiltin()) {
|
||||
throw new RedefinedMethodException(type.getName(), method);
|
||||
} else {
|
||||
methodMap.put(method, new CachingSupplier<>(supply));
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CachingSupplier<T> implements Supplier<T> {
|
||||
private final Supplier<T> supply;
|
||||
private T memo;
|
||||
|
||||
CachingSupplier(Supplier<T> supply) {
|
||||
this.supply = supply;
|
||||
}
|
||||
|
||||
CachingSupplier(T memo) {
|
||||
this.supply = null;
|
||||
this.memo = memo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get() {
|
||||
if (memo == null) {
|
||||
memo = supply.get();
|
||||
}
|
||||
return memo;
|
||||
}
|
||||
}
|
||||
|
||||
@ -185,14 +227,14 @@ public final class ModuleScope implements EnsoObject {
|
||||
*/
|
||||
@TruffleBoundary
|
||||
public Function lookupMethodDefinition(Type type, String name) {
|
||||
Function definedWithAtom = type.getDefinitionScope().getMethodMapFor(type).get(name);
|
||||
var definedWithAtom = type.getDefinitionScope().getMethodMapFor(type).get(name);
|
||||
if (definedWithAtom != null) {
|
||||
return definedWithAtom;
|
||||
return definedWithAtom.get();
|
||||
}
|
||||
|
||||
Function definedHere = getMethodMapFor(type).get(name);
|
||||
var definedHere = getMethodMapFor(type).get(name);
|
||||
if (definedHere != null) {
|
||||
return definedHere;
|
||||
return definedHere.get();
|
||||
}
|
||||
|
||||
return imports.stream()
|
||||
@ -226,13 +268,14 @@ public final class ModuleScope implements EnsoObject {
|
||||
}
|
||||
|
||||
private Function getExportedMethod(Type type, String name) {
|
||||
Function here = getMethodMapFor(type).get(name);
|
||||
var here = getMethodMapFor(type).get(name);
|
||||
if (here != null) {
|
||||
return here;
|
||||
return here.get();
|
||||
}
|
||||
return exports.stream()
|
||||
.map(scope -> scope.getMethodMapFor(type).get(name))
|
||||
.filter(Objects::nonNull)
|
||||
.map(s -> s.get())
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
@ -281,8 +324,12 @@ public final class ModuleScope implements EnsoObject {
|
||||
/** @return a method for the given type */
|
||||
public Function getMethodForType(Type tpe, String name) {
|
||||
Type tpeKey = tpe == null ? noTypeKey : tpe;
|
||||
Map<String, Function> allTpeMethods = methods.get(tpeKey);
|
||||
return allTpeMethods == null ? null : allTpeMethods.get(name);
|
||||
var allTpeMethods = methods.get(tpeKey);
|
||||
if (allTpeMethods == null) {
|
||||
return null;
|
||||
}
|
||||
var supply = allTpeMethods.get(name);
|
||||
return supply == null ? null : supply.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -293,7 +340,7 @@ public final class ModuleScope implements EnsoObject {
|
||||
*/
|
||||
public Set<String> getMethodNamesForType(Type tpe) {
|
||||
Type tpeKey = tpe == null ? noTypeKey : tpe;
|
||||
Map<String, Function> allTpeMethods = methods.get(tpeKey);
|
||||
var allTpeMethods = methods.get(tpeKey);
|
||||
return allTpeMethods == null ? null : allTpeMethods.keySet();
|
||||
}
|
||||
|
||||
@ -305,7 +352,7 @@ public final class ModuleScope implements EnsoObject {
|
||||
*/
|
||||
public void registerAllMethodsOfTypeToScope(Type tpe, ModuleScope scope) {
|
||||
Type tpeKey = tpe == null ? noTypeKey : tpe;
|
||||
Map<String, Function> allTypeMethods = methods.get(tpeKey);
|
||||
var allTypeMethods = methods.get(tpeKey);
|
||||
if (allTypeMethods != null) {
|
||||
allTypeMethods.forEach((name, fun) -> scope.registerMethod(tpeKey, name, fun));
|
||||
}
|
||||
@ -313,7 +360,10 @@ public final class ModuleScope implements EnsoObject {
|
||||
|
||||
/** @return methods for all registered types */
|
||||
public List<Function> getAllMethods() {
|
||||
return methods.values().stream().flatMap(e -> e.values().stream()).collect(Collectors.toList());
|
||||
return methods.values().stream()
|
||||
.flatMap(e -> e.values().stream())
|
||||
.map(s -> s.get())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/** @return the raw conversions held by this module */
|
||||
@ -346,7 +396,7 @@ public final class ModuleScope implements EnsoObject {
|
||||
public ModuleScope withTypes(List<String> typeNames) {
|
||||
Map<String, Object> polyglotSymbols = new HashMap<>(this.polyglotSymbols);
|
||||
Map<String, Type> requestedTypes = new HashMap<>(this.types);
|
||||
Map<Type, Map<String, Function>> methods = new ConcurrentHashMap<>();
|
||||
Map<Type, Map<String, Supplier<Function>>> methods = new ConcurrentHashMap<>();
|
||||
Map<Type, Map<Type, Function>> conversions = new ConcurrentHashMap<>();
|
||||
Set<ModuleScope> imports = new HashSet<>(this.imports);
|
||||
Set<ModuleScope> exports = new HashSet<>(this.exports);
|
||||
|
@ -586,7 +586,7 @@ private class DefaultPackageRepository(
|
||||
}
|
||||
loadedLibraryBindings.get(libraryName)
|
||||
}
|
||||
cache.flatMap(_.bindings.entries().get(moduleName))
|
||||
cache.flatMap(_.bindings.findForModule(moduleName))
|
||||
}
|
||||
|
||||
private def loadDependencies(pkg: Package[TruffleFile]): Unit = {
|
||||
|
@ -438,191 +438,203 @@ class IrToTruffle(
|
||||
dataflowInfo
|
||||
)
|
||||
|
||||
val function = methodDef.body match {
|
||||
case fn: Function if isBuiltinMethod(fn.body) =>
|
||||
// For builtin types that own the builtin method we only check that
|
||||
// the method has been registered during the initialization of builtins
|
||||
// and not attempt to register it in the scope (can't redefined methods).
|
||||
// For non-builtin types (or modules) that own the builtin method
|
||||
// we have to look up the function and register it in the scope.
|
||||
// Static wrappers for instance methods have to be registered always.
|
||||
val fullMethodName = methodDef.body
|
||||
.asInstanceOf[Function.Lambda]
|
||||
.body
|
||||
.asInstanceOf[Literal.Text]
|
||||
moduleScope.registerMethod(
|
||||
cons,
|
||||
methodDef.methodName.name,
|
||||
() => {
|
||||
val function = methodDef.body match {
|
||||
case fn: Function if isBuiltinMethod(fn.body) =>
|
||||
// For builtin types that own the builtin method we only check that
|
||||
// the method has been registered during the initialization of builtins
|
||||
// and not attempt to register it in the scope (can't redefined methods).
|
||||
// For non-builtin types (or modules) that own the builtin method
|
||||
// we have to look up the function and register it in the scope.
|
||||
// Static wrappers for instance methods have to be registered always.
|
||||
val fullMethodName = methodDef.body
|
||||
.asInstanceOf[Function.Lambda]
|
||||
.body
|
||||
.asInstanceOf[Literal.Text]
|
||||
|
||||
val builtinNameElements = fullMethodName.text.split('.')
|
||||
if (builtinNameElements.length != 2) {
|
||||
throw new CompilerError(
|
||||
s"Unknown builtin method ${fullMethodName.text}, probably should be '$fullMethodDefName?'"
|
||||
)
|
||||
}
|
||||
val methodName = builtinNameElements(1)
|
||||
val methodOwnerName = builtinNameElements(0)
|
||||
|
||||
val staticWrapper = methodDef.isStaticWrapperForInstanceMethod
|
||||
|
||||
val builtinFunction = context.getBuiltins
|
||||
.getBuiltinFunction(
|
||||
methodOwnerName,
|
||||
methodName,
|
||||
language,
|
||||
staticWrapper
|
||||
)
|
||||
builtinFunction.toScala
|
||||
.map(Some(_))
|
||||
.toRight(
|
||||
new CompilerError(
|
||||
s"Unable to find Truffle Node for method ${cons.getName}.${methodDef.methodName.name}"
|
||||
)
|
||||
)
|
||||
.left
|
||||
.flatMap { l =>
|
||||
// Builtin Types Number and Integer have methods only for documentation purposes
|
||||
val number = context.getBuiltins.number()
|
||||
val ok =
|
||||
staticWrapper && (cons == number.getNumber.getEigentype || cons == number.getInteger.getEigentype) ||
|
||||
!staticWrapper && (cons == number.getNumber || cons == number.getInteger)
|
||||
if (ok) Right(None)
|
||||
else Left(l)
|
||||
}
|
||||
.map(fOpt =>
|
||||
fOpt.map { m =>
|
||||
if (m.isAutoRegister) {
|
||||
val irFunctionArgumentsCount = fn.arguments.length
|
||||
val builtinArgumentsCount =
|
||||
m.getFunction.getSchema.getArgumentsCount
|
||||
if (irFunctionArgumentsCount != builtinArgumentsCount) {
|
||||
val irFunctionArguments =
|
||||
fn.arguments.map(_.name.name).mkString(",")
|
||||
val builtinArguments =
|
||||
m.getFunction.getSchema.getArgumentInfos
|
||||
.map(_.getName)
|
||||
.mkString(",")
|
||||
throw new CompilerError(
|
||||
s"Wrong number of arguments provided in the definition of builtin function ${cons.getName}.${methodDef.methodName.name}. " +
|
||||
s"[$irFunctionArguments] vs [$builtinArguments]"
|
||||
)
|
||||
}
|
||||
val bodyBuilder =
|
||||
new expressionProcessor.BuildFunctionBody(
|
||||
fn.arguments,
|
||||
fn.body,
|
||||
effectContext,
|
||||
true
|
||||
)
|
||||
val builtinRootNode =
|
||||
m.getFunction.getCallTarget.getRootNode
|
||||
.asInstanceOf[BuiltinRootNode]
|
||||
builtinRootNode.setModuleName(moduleScope.getModule.getName)
|
||||
builtinRootNode.setTypeName(cons.getQualifiedName)
|
||||
new RuntimeFunction(
|
||||
m.getFunction.getCallTarget,
|
||||
null,
|
||||
new FunctionSchema(
|
||||
new Array[RuntimeAnnotation](0),
|
||||
bodyBuilder.args(): _*
|
||||
)
|
||||
)
|
||||
} else {
|
||||
m.getFunction
|
||||
}
|
||||
val builtinNameElements = fullMethodName.text.split('.')
|
||||
if (builtinNameElements.length != 2) {
|
||||
throw new CompilerError(
|
||||
s"Unknown builtin method ${fullMethodName.text}, probably should be '$fullMethodDefName?'"
|
||||
)
|
||||
}
|
||||
)
|
||||
case fn: Function =>
|
||||
val bodyBuilder =
|
||||
new expressionProcessor.BuildFunctionBody(
|
||||
fn.arguments,
|
||||
fn.body,
|
||||
effectContext,
|
||||
true
|
||||
)
|
||||
val rootNode = MethodRootNode.build(
|
||||
language,
|
||||
expressionProcessor.scope,
|
||||
moduleScope,
|
||||
() => bodyBuilder.bodyNode(),
|
||||
makeSection(moduleScope, methodDef.location),
|
||||
cons,
|
||||
methodDef.methodName.name
|
||||
)
|
||||
val callTarget = rootNode.getCallTarget
|
||||
val arguments = bodyBuilder.args()
|
||||
// build annotations
|
||||
val annotations =
|
||||
methodDef.getMetadata(GenericAnnotations).toVector.flatMap {
|
||||
meta =>
|
||||
meta.annotations
|
||||
.collect { case annotation: Name.GenericAnnotation =>
|
||||
val scopeElements = Seq(
|
||||
cons.getName,
|
||||
methodDef.methodName.name,
|
||||
annotation.name
|
||||
)
|
||||
val scopeName =
|
||||
scopeElements.mkString(Constants.SCOPE_SEPARATOR)
|
||||
val scopeInfo = annotation
|
||||
.unsafeGetMetadata(
|
||||
AliasAnalysis,
|
||||
s"Missing scope information for annotation " +
|
||||
s"${annotation.name} of method " +
|
||||
scopeElements.init.mkString(Constants.SCOPE_SEPARATOR)
|
||||
)
|
||||
.unsafeAs[AliasAnalysis.Info.Scope.Root]
|
||||
val dataflowInfo = annotation.unsafeGetMetadata(
|
||||
DataflowAnalysis,
|
||||
"Missing dataflow information for annotation " +
|
||||
s"${annotation.name} of method " +
|
||||
scopeElements.init.mkString(Constants.SCOPE_SEPARATOR)
|
||||
)
|
||||
val expressionProcessor = new ExpressionProcessor(
|
||||
scopeName,
|
||||
scopeInfo.graph,
|
||||
scopeInfo.graph.rootScope,
|
||||
dataflowInfo
|
||||
)
|
||||
val expressionNode =
|
||||
expressionProcessor.run(annotation.expression, true)
|
||||
val closureName =
|
||||
s"<default::${expressionProcessor.scopeName}>"
|
||||
val closureRootNode = ClosureRootNode.build(
|
||||
language,
|
||||
expressionProcessor.scope,
|
||||
moduleScope,
|
||||
expressionNode,
|
||||
makeSection(moduleScope, annotation.location),
|
||||
closureName,
|
||||
true,
|
||||
false
|
||||
)
|
||||
new RuntimeAnnotation(annotation.name, closureRootNode)
|
||||
}
|
||||
}
|
||||
val methodName = builtinNameElements(1)
|
||||
val methodOwnerName = builtinNameElements(0)
|
||||
|
||||
Right(
|
||||
Some(
|
||||
new RuntimeFunction(
|
||||
callTarget,
|
||||
null,
|
||||
new FunctionSchema(annotations.toArray, arguments: _*)
|
||||
val staticWrapper = methodDef.isStaticWrapperForInstanceMethod
|
||||
|
||||
val builtinFunction = context.getBuiltins
|
||||
.getBuiltinFunction(
|
||||
methodOwnerName,
|
||||
methodName,
|
||||
language,
|
||||
staticWrapper
|
||||
)
|
||||
builtinFunction.toScala
|
||||
.map(Some(_))
|
||||
.toRight(
|
||||
new CompilerError(
|
||||
s"Unable to find Truffle Node for method ${cons.getName}.${methodDef.methodName.name}"
|
||||
)
|
||||
)
|
||||
.left
|
||||
.flatMap { l =>
|
||||
// Builtin Types Number and Integer have methods only for documentation purposes
|
||||
val number = context.getBuiltins.number()
|
||||
val ok =
|
||||
staticWrapper && (cons == number.getNumber.getEigentype || cons == number.getInteger.getEigentype) ||
|
||||
!staticWrapper && (cons == number.getNumber || cons == number.getInteger)
|
||||
if (ok) Right(None)
|
||||
else Left(l)
|
||||
}
|
||||
.map(fOpt =>
|
||||
fOpt.map { m =>
|
||||
if (m.isAutoRegister) {
|
||||
val irFunctionArgumentsCount = fn.arguments.length
|
||||
val builtinArgumentsCount =
|
||||
m.getFunction.getSchema.getArgumentsCount
|
||||
if (irFunctionArgumentsCount != builtinArgumentsCount) {
|
||||
val irFunctionArguments =
|
||||
fn.arguments.map(_.name.name).mkString(",")
|
||||
val builtinArguments =
|
||||
m.getFunction.getSchema.getArgumentInfos
|
||||
.map(_.getName)
|
||||
.mkString(",")
|
||||
throw new CompilerError(
|
||||
s"Wrong number of arguments provided in the definition of builtin function ${cons.getName}.${methodDef.methodName.name}. " +
|
||||
s"[$irFunctionArguments] vs [$builtinArguments]"
|
||||
)
|
||||
}
|
||||
val bodyBuilder =
|
||||
new expressionProcessor.BuildFunctionBody(
|
||||
fn.arguments,
|
||||
fn.body,
|
||||
effectContext,
|
||||
true
|
||||
)
|
||||
val builtinRootNode =
|
||||
m.getFunction.getCallTarget.getRootNode
|
||||
.asInstanceOf[BuiltinRootNode]
|
||||
builtinRootNode
|
||||
.setModuleName(moduleScope.getModule.getName)
|
||||
builtinRootNode.setTypeName(cons.getQualifiedName)
|
||||
new RuntimeFunction(
|
||||
m.getFunction.getCallTarget,
|
||||
null,
|
||||
new FunctionSchema(
|
||||
new Array[RuntimeAnnotation](0),
|
||||
bodyBuilder.args(): _*
|
||||
)
|
||||
)
|
||||
} else {
|
||||
m.getFunction
|
||||
}
|
||||
}
|
||||
)
|
||||
case fn: Function =>
|
||||
val bodyBuilder =
|
||||
new expressionProcessor.BuildFunctionBody(
|
||||
fn.arguments,
|
||||
fn.body,
|
||||
effectContext,
|
||||
true
|
||||
)
|
||||
val rootNode = MethodRootNode.build(
|
||||
language,
|
||||
expressionProcessor.scope,
|
||||
moduleScope,
|
||||
() => bodyBuilder.bodyNode(),
|
||||
makeSection(moduleScope, methodDef.location),
|
||||
cons,
|
||||
methodDef.methodName.name
|
||||
)
|
||||
)
|
||||
)
|
||||
case _ =>
|
||||
Left(
|
||||
new CompilerError(
|
||||
"Method bodies must be functions at the point of codegen."
|
||||
)
|
||||
)
|
||||
}
|
||||
function match {
|
||||
case Left(failure) =>
|
||||
throw failure
|
||||
case Right(Some(fun)) =>
|
||||
moduleScope.registerMethod(cons, methodDef.methodName.name, fun)
|
||||
case _ =>
|
||||
// Don't register dummy function nodes
|
||||
}
|
||||
val callTarget = rootNode.getCallTarget
|
||||
val arguments = bodyBuilder.args()
|
||||
// build annotations
|
||||
val annotations =
|
||||
methodDef.getMetadata(GenericAnnotations).toVector.flatMap {
|
||||
meta =>
|
||||
meta.annotations
|
||||
.collect { case annotation: Name.GenericAnnotation =>
|
||||
val scopeElements = Seq(
|
||||
cons.getName,
|
||||
methodDef.methodName.name,
|
||||
annotation.name
|
||||
)
|
||||
val scopeName =
|
||||
scopeElements.mkString(Constants.SCOPE_SEPARATOR)
|
||||
val scopeInfo = annotation
|
||||
.unsafeGetMetadata(
|
||||
AliasAnalysis,
|
||||
s"Missing scope information for annotation " +
|
||||
s"${annotation.name} of method " +
|
||||
scopeElements.init
|
||||
.mkString(Constants.SCOPE_SEPARATOR)
|
||||
)
|
||||
.unsafeAs[AliasAnalysis.Info.Scope.Root]
|
||||
val dataflowInfo = annotation.unsafeGetMetadata(
|
||||
DataflowAnalysis,
|
||||
"Missing dataflow information for annotation " +
|
||||
s"${annotation.name} of method " +
|
||||
scopeElements.init
|
||||
.mkString(Constants.SCOPE_SEPARATOR)
|
||||
)
|
||||
val expressionProcessor = new ExpressionProcessor(
|
||||
scopeName,
|
||||
scopeInfo.graph,
|
||||
scopeInfo.graph.rootScope,
|
||||
dataflowInfo
|
||||
)
|
||||
val expressionNode =
|
||||
expressionProcessor.run(annotation.expression, true)
|
||||
val closureName =
|
||||
s"<default::${expressionProcessor.scopeName}>"
|
||||
val closureRootNode = ClosureRootNode.build(
|
||||
language,
|
||||
expressionProcessor.scope,
|
||||
moduleScope,
|
||||
expressionNode,
|
||||
makeSection(moduleScope, annotation.location),
|
||||
closureName,
|
||||
true,
|
||||
false
|
||||
)
|
||||
new RuntimeAnnotation(
|
||||
annotation.name,
|
||||
closureRootNode
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Right(
|
||||
Some(
|
||||
new RuntimeFunction(
|
||||
callTarget,
|
||||
null,
|
||||
new FunctionSchema(annotations.toArray, arguments: _*)
|
||||
)
|
||||
)
|
||||
)
|
||||
case _ =>
|
||||
Left(
|
||||
new CompilerError(
|
||||
"Method bodies must be functions at the point of codegen."
|
||||
)
|
||||
)
|
||||
}
|
||||
function match {
|
||||
case Left(failure) =>
|
||||
throw failure
|
||||
case Right(Some(fun)) =>
|
||||
fun
|
||||
case x =>
|
||||
throw new IllegalStateException("Wrong state: " + x)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -119,10 +119,6 @@ final class SerializationManager(private val context: TruffleCompilerContext) {
|
||||
module.getIr,
|
||||
module.getIr.duplicate(keepIdentifiers = true)
|
||||
)
|
||||
duplicatedIr.preorder.foreach(
|
||||
_.passData.prepareForSerialization(compiler.context)
|
||||
)
|
||||
|
||||
val task = doSerializeModule(
|
||||
getCache(module),
|
||||
duplicatedIr,
|
||||
@ -215,7 +211,10 @@ final class SerializationManager(private val context: TruffleCompilerContext) {
|
||||
)
|
||||
val abstractBindings =
|
||||
bindings.prepareForSerialization(compiler.context)
|
||||
(module.getName, abstractBindings)
|
||||
(
|
||||
module.getName,
|
||||
org.enso.persist.Persistance.Reference.of(abstractBindings)
|
||||
)
|
||||
}
|
||||
.toMap
|
||||
),
|
||||
@ -401,6 +400,7 @@ final class SerializationManager(private val context: TruffleCompilerContext) {
|
||||
compiler: Compiler,
|
||||
module: Module
|
||||
): Option[Boolean] = {
|
||||
compiler.getClass()
|
||||
if (isWaitingForSerialization(module)) {
|
||||
abort(module)
|
||||
None
|
||||
@ -411,9 +411,6 @@ final class SerializationManager(private val context: TruffleCompilerContext) {
|
||||
|
||||
context.loadCache(getCache(module)).toScala match {
|
||||
case Some(loadedCache) =>
|
||||
val relinkedIrChecks =
|
||||
loadedCache.moduleIR.preorder
|
||||
.map(_.passData.restoreFromSerialization(compiler.context))
|
||||
context.updateModule(
|
||||
module,
|
||||
{ u =>
|
||||
@ -428,24 +425,7 @@ final class SerializationManager(private val context: TruffleCompilerContext) {
|
||||
module.getName,
|
||||
loadedCache.compilationStage
|
||||
)
|
||||
|
||||
if (!relinkedIrChecks.contains(false)) {
|
||||
context.updateModule(module, _.hasCrossModuleLinks(true))
|
||||
context.logSerializationManager(
|
||||
debugLogLevel,
|
||||
"Restored links (early phase) in module [{0}].",
|
||||
module.getName
|
||||
)
|
||||
Some(true)
|
||||
} else {
|
||||
context.logSerializationManager(
|
||||
debugLogLevel,
|
||||
"Could not restore links (early phase) in module [{0}].",
|
||||
module.getName
|
||||
)
|
||||
context.updateModule(module, _.hasCrossModuleLinks(false))
|
||||
Some(false)
|
||||
}
|
||||
Some(true)
|
||||
case None =>
|
||||
context.logSerializationManager(
|
||||
debugLogLevel,
|
||||
|
@ -38,7 +38,7 @@ public abstract class CompilerTest {
|
||||
return ir;
|
||||
}
|
||||
|
||||
static void assertIR(String msg, Module old, Module now) throws IOException {
|
||||
public static void assertIR(String msg, Module old, Module now) throws IOException {
|
||||
Function<IR, String> filter = f -> simplifyIR(f, true, true, false);
|
||||
String ir1 = filter.apply(old);
|
||||
String ir2 = filter.apply(now);
|
||||
@ -129,6 +129,15 @@ public abstract class CompilerTest {
|
||||
int to = txt.indexOf("reason =", at + pref.length());
|
||||
txt = txt.substring(0, at) + "errors.Syntax (" + txt.substring(to);
|
||||
}
|
||||
for (;;) {
|
||||
final String pref = "List(";
|
||||
int at = txt.indexOf(pref);
|
||||
if (at == -1) {
|
||||
break;
|
||||
}
|
||||
int to = at + pref.length();
|
||||
txt = txt.substring(0, at) + "Seq(" + txt.substring(to);
|
||||
}
|
||||
return txt;
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
package org.enso.compiler;
|
||||
|
||||
import org.enso.compiler.core.ir.Location;
|
||||
import org.enso.compiler.core.ir.Module;
|
||||
import org.enso.compiler.core.ir.expression.errors.Syntax;
|
||||
import org.enso.syntax.text.Location;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import org.junit.Test;
|
||||
|
@ -0,0 +1,58 @@
|
||||
package org.enso.interpreter.caches;
|
||||
|
||||
import org.enso.compiler.CompilerTest;
|
||||
import org.enso.compiler.core.IR;
|
||||
import org.enso.interpreter.Constants;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.builtin.Builtins;
|
||||
import org.enso.interpreter.test.TestBase;
|
||||
import org.enso.interpreter.util.ScalaConversions;
|
||||
import org.enso.polyglot.CompilationStage;
|
||||
import org.enso.polyglot.LanguageInfo;
|
||||
import org.enso.polyglot.MethodNames;
|
||||
import org.enso.polyglot.RuntimeOptions;
|
||||
import org.graalvm.polyglot.Context;
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
public class ModuleCacheTest extends TestBase {
|
||||
private static Context ctx;
|
||||
|
||||
public ModuleCacheTest() {
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void initializeContext() throws Exception {
|
||||
ctx = defaultContextBuilder()
|
||||
.option(RuntimeOptions.DISABLE_IR_CACHES, "true")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareList() throws Exception {
|
||||
var ensoCtx = (EnsoContext) ctx.getBindings(LanguageInfo.ID).invokeMember(MethodNames.TopScope.LEAK_CONTEXT).asHostObject();
|
||||
var name = "Standard.Base.Data.List";
|
||||
|
||||
var v = ctx.eval("enso", """
|
||||
import Standard.Base.Data.List
|
||||
|
||||
empty = List.List.Nil
|
||||
""").invokeMember(MethodNames.Module.EVAL_EXPRESSION, "empty");
|
||||
assertEquals("List", v.getMetaObject().getMetaSimpleName());
|
||||
|
||||
var option = ensoCtx.findModule(name);
|
||||
assertTrue("Module found", option.isPresent());
|
||||
var module = option.get();
|
||||
var ir = module.getIr().duplicate(true, true, true, true);
|
||||
var cm = new ModuleCache.CachedModule(ir, CompilationStage.AFTER_CODEGEN, module.getSource());
|
||||
byte[] arr = module.getCache().serialize(ensoCtx, cm);
|
||||
|
||||
var meta = new ModuleCache.Metadata("hash", "code", CompilationStage.AFTER_CODEGEN.toString());
|
||||
var cachedIr = module.getCache().deserialize(ensoCtx, arr, meta, null);
|
||||
assertNotNull("IR read", cachedIr);
|
||||
CompilerTest.assertIR(name, ir, cachedIr.moduleIR());
|
||||
}
|
||||
}
|
@ -150,7 +150,7 @@ trait CompilerRunner {
|
||||
* @return a method containing `ir` as its body
|
||||
*/
|
||||
def asMethod: definition.Method = {
|
||||
definition.Method.Explicit(
|
||||
new definition.Method.Explicit(
|
||||
Name.MethodReference(
|
||||
Some(
|
||||
Name.Qualified(
|
||||
|
@ -31,7 +31,7 @@ class GatherDiagnosticsTest extends CompilerTest {
|
||||
hasDefaultsSuspended = false,
|
||||
None
|
||||
)
|
||||
val lam = Function.Lambda(
|
||||
val lam = new Function.Lambda(
|
||||
List(
|
||||
DefinitionArgument
|
||||
.Specified(
|
||||
@ -94,10 +94,8 @@ class GatherDiagnosticsTest extends CompilerTest {
|
||||
List(),
|
||||
None
|
||||
),
|
||||
definition.Method
|
||||
.Explicit(method1Ref, lam, None),
|
||||
definition.Method
|
||||
.Explicit(method2Ref, error3, None)
|
||||
new definition.Method.Explicit(method1Ref, lam, None),
|
||||
new definition.Method.Explicit(method2Ref, error3, None)
|
||||
),
|
||||
false,
|
||||
None
|
||||
|
@ -5,12 +5,12 @@ import org.enso.compiler.core.ir.{
|
||||
Empty,
|
||||
Expression,
|
||||
IdentifiedLocation,
|
||||
Location,
|
||||
Name
|
||||
}
|
||||
import org.enso.compiler.core.ir.expression.{Application, Operator}
|
||||
import org.enso.compiler.pass.desugar.OperatorToFunction
|
||||
import org.enso.compiler.test.CompilerTest
|
||||
import org.enso.syntax.text.Location
|
||||
|
||||
class OperatorToFunctionTest extends CompilerTest {
|
||||
|
||||
@ -31,7 +31,7 @@ class OperatorToFunctionTest extends CompilerTest {
|
||||
left: Expression,
|
||||
right: Expression
|
||||
): (Operator.Binary, Application.Prefix) = {
|
||||
val loc = IdentifiedLocation(Location(1, 33))
|
||||
val loc = new IdentifiedLocation(new Location(1, 33))
|
||||
|
||||
val leftArg = CallArgument.Specified(None, left, left.location)
|
||||
val rightArg = CallArgument.Specified(None, right, right.location)
|
||||
|
@ -226,42 +226,41 @@ class LambdaConsolidateTest extends CompilerTest {
|
||||
"collapse lambdas with multiple parameters" in {
|
||||
implicit val inlineContext: InlineContext = mkContext
|
||||
|
||||
val ir: Function.Lambda = Function
|
||||
.Lambda(
|
||||
val ir: Function.Lambda = new Function.Lambda(
|
||||
List(
|
||||
DefinitionArgument
|
||||
.Specified(
|
||||
Name
|
||||
.Literal("a", isMethod = false, None),
|
||||
None,
|
||||
None,
|
||||
suspended = false,
|
||||
None
|
||||
),
|
||||
DefinitionArgument.Specified(
|
||||
Name.Literal("b", isMethod = false, None),
|
||||
None,
|
||||
None,
|
||||
suspended = false,
|
||||
None
|
||||
)
|
||||
),
|
||||
new Function.Lambda(
|
||||
List(
|
||||
DefinitionArgument
|
||||
.Specified(
|
||||
Name
|
||||
.Literal("a", isMethod = false, None),
|
||||
None,
|
||||
None,
|
||||
suspended = false,
|
||||
None
|
||||
),
|
||||
DefinitionArgument.Specified(
|
||||
Name.Literal("b", isMethod = false, None),
|
||||
Name
|
||||
.Literal("c", isMethod = false, None),
|
||||
None,
|
||||
None,
|
||||
suspended = false,
|
||||
None
|
||||
)
|
||||
),
|
||||
Function.Lambda(
|
||||
List(
|
||||
DefinitionArgument.Specified(
|
||||
Name
|
||||
.Literal("c", isMethod = false, None),
|
||||
None,
|
||||
None,
|
||||
suspended = false,
|
||||
None
|
||||
)
|
||||
),
|
||||
Name.Literal("c", isMethod = false, None),
|
||||
None
|
||||
),
|
||||
Name.Literal("c", isMethod = false, None),
|
||||
None
|
||||
)
|
||||
),
|
||||
None
|
||||
)
|
||||
.runPasses(passManager, inlineContext)
|
||||
.optimise
|
||||
.asInstanceOf[Function.Lambda]
|
||||
|
@ -1,13 +1,14 @@
|
||||
package org.enso.compiler.test.semantic
|
||||
|
||||
import org.enso.compiler.core.Implicits.AsMetadata
|
||||
import org.enso.compiler.core.ir.{Module, Warning}
|
||||
import org.enso.compiler.core.ir.{Module, ProcessingPass, Warning}
|
||||
import org.enso.compiler.core.ir.expression.errors
|
||||
import org.enso.compiler.core.ir.module.scope.Import
|
||||
import org.enso.compiler.data.BindingsMap
|
||||
import org.enso.compiler.pass.analyse.BindingAnalysis
|
||||
import org.enso.interpreter.runtime
|
||||
import org.enso.interpreter.runtime.EnsoContext
|
||||
import org.enso.persist.Persistance
|
||||
import org.enso.pkg.QualifiedName
|
||||
import org.enso.polyglot.{LanguageInfo, MethodNames, RuntimeOptions}
|
||||
import org.graalvm.polyglot.{Context, Engine}
|
||||
@ -15,9 +16,10 @@ import org.scalatest.BeforeAndAfter
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
import org.scalatest.wordspec.AnyWordSpecLike
|
||||
|
||||
import java.io.{ByteArrayOutputStream, ObjectOutputStream}
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.nio.file.Paths
|
||||
import java.util.logging.Level
|
||||
import java.io.IOException
|
||||
|
||||
/** Tests a single package with multiple modules for import/export resolution.
|
||||
* Checks whether the exported symbols and defined entities metadata of the modules
|
||||
@ -908,13 +910,17 @@ class ImportExportTest
|
||||
.diagnostics
|
||||
.collect({ case w: Warning.DuplicatedImport => w })
|
||||
warn.size shouldEqual 1
|
||||
val baos = new ByteArrayOutputStream()
|
||||
val stream = new ObjectOutputStream(baos)
|
||||
mainIr.preorder.foreach(
|
||||
_.passData.prepareForSerialization(langCtx.getCompiler.context)
|
||||
)
|
||||
stream.writeObject(mainIr)
|
||||
baos.toByteArray should not be empty
|
||||
val arr = Persistance.write(
|
||||
mainIr,
|
||||
{
|
||||
case metadata: ProcessingPass.Metadata =>
|
||||
metadata.prepareForSerialization(
|
||||
langCtx.getCompiler.context.asInstanceOf[metadata.Compiler]
|
||||
);
|
||||
case obj => obj
|
||||
}
|
||||
);
|
||||
arr should not be empty
|
||||
}
|
||||
|
||||
"serialize ambiguous import error" in {
|
||||
@ -940,13 +946,24 @@ class ImportExportTest
|
||||
.reason
|
||||
.asInstanceOf[errors.ImportExport.AmbiguousImport]
|
||||
ambiguousImport.symbolName shouldEqual "A_Type"
|
||||
val baos = new ByteArrayOutputStream()
|
||||
val stream = new ObjectOutputStream(baos)
|
||||
mainIr.preorder.foreach(
|
||||
_.passData.prepareForSerialization(langCtx.getCompiler.context)
|
||||
)
|
||||
stream.writeObject(mainIr)
|
||||
baos.toByteArray should not be empty
|
||||
try {
|
||||
val arr = Persistance.write(
|
||||
mainIr,
|
||||
{
|
||||
case metadata: ProcessingPass.Metadata =>
|
||||
metadata.prepareForSerialization(
|
||||
langCtx.getCompiler.context.asInstanceOf[metadata.Compiler]
|
||||
);
|
||||
case obj => obj
|
||||
}
|
||||
);
|
||||
fail("Shouldn't return anything when there is an error" + arr)
|
||||
} catch {
|
||||
case ex: IOException =>
|
||||
ex.getMessage should equal(
|
||||
"No persistance for org.enso.compiler.core.ir.expression.errors.ImportExport"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,282 @@
|
||||
package org.enso.persist.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.processing.AbstractProcessor;
|
||||
import javax.annotation.processing.Processor;
|
||||
import javax.annotation.processing.RoundEnvironment;
|
||||
import javax.annotation.processing.SupportedAnnotationTypes;
|
||||
import javax.lang.model.SourceVersion;
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.AnnotationValue;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ElementKind;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.PackageElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.util.SimpleAnnotationValueVisitor9;
|
||||
import javax.tools.Diagnostic.Kind;
|
||||
|
||||
import org.openide.util.lookup.ServiceProvider;
|
||||
|
||||
/**
|
||||
* Processes the {@code Persistable} annotation. See its javadoc for
|
||||
* proper usage. A new subclass of {@code Persistance} class is generated per each
|
||||
* {@code Persistable} annotation.
|
||||
*/
|
||||
@SupportedAnnotationTypes({
|
||||
"org.enso.persist.Persistable",
|
||||
"org.enso.persist.Persistable.Group"
|
||||
})
|
||||
@ServiceProvider(service=Processor.class)
|
||||
public class PersistableProcessor extends AbstractProcessor {
|
||||
@Override
|
||||
public SourceVersion getSupportedSourceVersion() {
|
||||
return SourceVersion.latest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
|
||||
var ok = true;
|
||||
var eu = processingEnv.getElementUtils();
|
||||
var Persistable = eu.getTypeElement("org.enso.persist.Persistable");
|
||||
var PersistableGroup = eu.getTypeElement("org.enso.persist.Persistable.Group");
|
||||
try {
|
||||
for (var elem : roundEnv.getElementsAnnotatedWith(Persistable)) {
|
||||
var anno = getAnnotation(elem, Persistable);
|
||||
ok &= generatePersistance(elem, anno);
|
||||
}
|
||||
for (var elem : roundEnv.getElementsAnnotatedWith(PersistableGroup)) {
|
||||
var group = getAnnotation(elem, PersistableGroup);
|
||||
for (var anno : readAnnoArray(group, "value")) {
|
||||
ok &= generatePersistance(elem, anno);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
ok = false;
|
||||
processingEnv.getMessager().printMessage(Kind.ERROR, e.getMessage());
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
private String findFqn(Element e) {
|
||||
var inPackage = findNameInPackage(e);
|
||||
var pkg = processingEnv.getElementUtils().getPackageOf(e);
|
||||
return pkg.getQualifiedName() + "." + inPackage;
|
||||
}
|
||||
|
||||
private static String findNameInPackage(Element e) {
|
||||
var sb = new StringBuilder();
|
||||
while (e != null && !(e instanceof PackageElement)) {
|
||||
if (!sb.isEmpty()) {
|
||||
sb.insert(0, ".");
|
||||
}
|
||||
sb.insert(0, e.getSimpleName());
|
||||
e = e.getEnclosingElement();
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private boolean generatePersistance(Element orig, AnnotationMirror anno) throws IOException {
|
||||
var eu = processingEnv.getElementUtils();
|
||||
var tu = processingEnv.getTypeUtils();
|
||||
String typeElemName = readAnnoValue(anno, "clazz");
|
||||
if (typeElemName == null) {
|
||||
typeElemName = ((TypeElement) orig).getQualifiedName().toString();
|
||||
}
|
||||
var typeElem = eu.getTypeElement(typeElemName);
|
||||
if (typeElem == null) {
|
||||
processingEnv.getMessager().printMessage(Kind.ERROR, "Cannot find type for " + typeElemName);
|
||||
return false;
|
||||
}
|
||||
var richerConstructor = new Comparator<Object>() {
|
||||
@Override
|
||||
public int compare(Object a, Object b) {
|
||||
var ea = (ExecutableElement)a;
|
||||
var eb = (ExecutableElement)b;
|
||||
|
||||
var diff = eb.getParameters().size() - ea.getParameters().size();
|
||||
if (diff == 0) {
|
||||
diff = countSeq(eb.getParameters()) - countSeq(ea.getParameters());
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
};
|
||||
var constructors = typeElem.getEnclosedElements().stream()
|
||||
.filter(
|
||||
e -> e.getModifiers().contains(Modifier.PUBLIC) && e.getKind() == ElementKind.CONSTRUCTOR
|
||||
)
|
||||
.sorted(richerConstructor)
|
||||
.toList();
|
||||
if (constructors.isEmpty()) {
|
||||
processingEnv.getMessager().printMessage(Kind.ERROR, "There should be exactly one constructor in " + typeElem, orig);
|
||||
return false;
|
||||
}
|
||||
var cons = (ExecutableElement) constructors.get(0);
|
||||
if (constructors.size() > 1) {
|
||||
var snd = (ExecutableElement) constructors.get(1);
|
||||
if (richerConstructor.compare(cons, snd) == 0) {
|
||||
processingEnv.getMessager().printMessage(Kind.ERROR, "There should be exactly one 'richest' constructor in " + typeElem, orig);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
var pkgName = eu.getPackageOf(orig).getQualifiedName().toString();
|
||||
var className = "Persist" + findNameInPackage(typeElem).replace(".", "_");
|
||||
var fo = processingEnv.getFiler().createSourceFile(pkgName + "." + className, orig);
|
||||
try (var w = fo.openWriter()) {
|
||||
w.append("package ").append(pkgName).append(";\n");
|
||||
w.append("import java.io.IOException;\n");
|
||||
w.append("import org.enso.persist.Persistance;\n");
|
||||
w.append("@org.openide.util.lookup.ServiceProvider(service=Persistance.class)\n");
|
||||
w.append("public final class ").append(className).append(" extends Persistance<").append(typeElemName).append("> {\n");
|
||||
w.append(" public ").append(className).append("() {\n");
|
||||
var id = readAnnoValue(anno, "id").toString(); // Integer.toString(anno.id());
|
||||
w.append(" super(").append(typeElemName).append(".class, false, ").append(id).append(");\n");
|
||||
w.append(" }\n");
|
||||
w.append(" @SuppressWarnings(\"unchecked\")\n");
|
||||
w.append(" protected ").append(typeElemName).append(" readObject(Input in) throws IOException {\n");
|
||||
|
||||
for (var v : cons.getParameters()) {
|
||||
if (tu.isSameType(eu.getTypeElement("java.lang.String").asType(), v.asType())) {
|
||||
w.append(" var ").append(v.getSimpleName()).append(" = in.readUTF();\n");
|
||||
} else if (!v.asType().getKind().isPrimitive()) {
|
||||
var type = tu.erasure(v.asType());
|
||||
var elem = (TypeElement) tu.asElement(type);
|
||||
var name = findFqn(elem);
|
||||
if (shouldInline(elem)) {
|
||||
w.append(" var ").append(v.getSimpleName()).append(" = in.readInline(").append(name).append(".class);\n");
|
||||
} else {
|
||||
w.append(" var ").append(v.getSimpleName()).append(" = (").append(name).append(") in.readObject();\n");
|
||||
}
|
||||
} else switch (v.asType().getKind()) {
|
||||
case BOOLEAN ->
|
||||
w.append(" var ").append(v.getSimpleName()).append(" = in.readBoolean();\n");
|
||||
case INT ->
|
||||
w.append(" var ").append(v.getSimpleName()).append(" = in.readInt();\n");
|
||||
default ->
|
||||
processingEnv.getMessager().printMessage(Kind.ERROR, "Unsupported primitive type: " + v.asType().getKind());
|
||||
}
|
||||
}
|
||||
w.append(" return new ").append(typeElemName).append("(\n");
|
||||
w.append(" ");
|
||||
{
|
||||
var sep = "";
|
||||
for (var v : cons.getParameters()) {
|
||||
w.append(sep);
|
||||
w.append(v.getSimpleName());
|
||||
sep = ", ";
|
||||
}
|
||||
}
|
||||
w.append("\n");
|
||||
w.append(" );\n");
|
||||
w.append(" }\n");
|
||||
w.append(" @SuppressWarnings(\"unchecked\")\n");
|
||||
w.append(" protected void writeObject(").append(typeElemName).append(" obj, Output out) throws IOException {\n");
|
||||
|
||||
for (var v : cons.getParameters()) {
|
||||
if (tu.isSameType(eu.getTypeElement("java.lang.String").asType(), v.asType())) {
|
||||
w.append(" out.writeUTF(obj.").append(v.getSimpleName()).append("());\n");
|
||||
} else if (!v.asType().getKind().isPrimitive()) {
|
||||
var type = tu.erasure(v.asType());
|
||||
var elem = (TypeElement) tu.asElement(type);
|
||||
var name = findFqn(elem);
|
||||
if (shouldInline(elem)) {
|
||||
w.append(" out.writeInline(").append(name).append(".class, obj.").append(v.getSimpleName()).append("());\n");
|
||||
} else {
|
||||
w.append(" out.writeObject(obj.").append(v.getSimpleName()).append("());\n");
|
||||
}
|
||||
} else switch (v.asType().getKind()) {
|
||||
case BOOLEAN ->
|
||||
w.append(" out.writeBoolean(obj.").append(v.getSimpleName()).append("());\n");
|
||||
case INT ->
|
||||
w.append(" out.writeInt(obj.").append(v.getSimpleName()).append("());\n");
|
||||
default ->
|
||||
processingEnv.getMessager().printMessage(Kind.ERROR, "Unsupported primitive type: " + v.asType().getKind());
|
||||
}
|
||||
}
|
||||
w.append(" }\n");
|
||||
w.append("}\n");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private int countSeq(List<? extends VariableElement> parameters) {
|
||||
var tu = processingEnv.getTypeUtils();
|
||||
var cnt = 0;
|
||||
for (var p : parameters) {
|
||||
var type = tu.asElement(tu.erasure(p.asType()));
|
||||
if (type != null && type.getSimpleName().toString().equals("Seq")) {
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
return cnt;
|
||||
}
|
||||
|
||||
private boolean shouldInline(TypeElement elem) {
|
||||
var inline = switch (findFqn(elem)) {
|
||||
case "scala.collection.immutable.Seq" -> true;
|
||||
default -> false;
|
||||
} || !elem.getKind().isInterface();
|
||||
return inline;
|
||||
}
|
||||
|
||||
private AnnotationMirror getAnnotation(Element elem, TypeElement annoType) {
|
||||
var tu = processingEnv.getTypeUtils();
|
||||
for (var m : elem.getAnnotationMirrors()) {
|
||||
var realType = m.getAnnotationType();
|
||||
if (tu.isSameType(realType, annoType.asType())) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String readAnnoValue(AnnotationMirror mirror, String name) {
|
||||
for (var entry : mirror.getElementValues().entrySet()) {
|
||||
if (name.equals(entry.getKey().getSimpleName().toString())) {
|
||||
return entry.getValue().accept(new SimpleAnnotationValueVisitor9<String, Object>() {
|
||||
@Override
|
||||
public String visitInt(int i, Object p) {
|
||||
return Integer.toString(i);
|
||||
}
|
||||
@Override
|
||||
public String visitType(TypeMirror t, Object p) {
|
||||
var e = (TypeElement) processingEnv.getTypeUtils().asElement(t);
|
||||
return e.getQualifiedName().toString();
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<AnnotationMirror> readAnnoArray(AnnotationMirror mirror, String name) {
|
||||
for (var entry : mirror.getElementValues().entrySet()) {
|
||||
if (name.equals(entry.getKey().getSimpleName().toString())) {
|
||||
return entry.getValue().accept(new SimpleAnnotationValueVisitor9<List<AnnotationMirror>, List<AnnotationMirror>>() {
|
||||
@Override
|
||||
public List<AnnotationMirror> visitArray(List<? extends AnnotationValue> vals, List<AnnotationMirror> p) {
|
||||
for (var v : vals) {
|
||||
v.accept(this, p);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AnnotationMirror> visitAnnotation(AnnotationMirror a, List<AnnotationMirror> p) {
|
||||
p.add(a);
|
||||
return p;
|
||||
}
|
||||
}, new ArrayList<>());
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package org.enso.persist;
|
||||
|
||||
import org.enso.persist.PerInputImpl.InputCache;
|
||||
import org.enso.persist.Persistance.Reference;
|
||||
|
||||
final class PerBufferReference<T> extends Persistance.Reference<T> {
|
||||
private final Persistance<T> p;
|
||||
private final PerInputImpl.InputCache cache;
|
||||
private final int offset;
|
||||
|
||||
private PerBufferReference(Persistance<T> p, PerInputImpl.InputCache buffer, int offset) {
|
||||
this.p = p;
|
||||
this.cache = buffer;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
@SuppressWarnings(value = "unchecked")
|
||||
final <T> T readObject(Class<T> clazz) {
|
||||
if (p != null) {
|
||||
if (clazz.isAssignableFrom(p.clazz)) {
|
||||
clazz = (Class) p.clazz;
|
||||
} else {
|
||||
throw new ClassCastException();
|
||||
}
|
||||
}
|
||||
org.enso.persist.PerInputImpl in = new PerInputImpl(cache, offset);
|
||||
T obj = in.readInline(clazz);
|
||||
return obj;
|
||||
}
|
||||
|
||||
static <V> Reference<V> from(InputCache buffer, int offset) {
|
||||
return from(null, buffer, offset);
|
||||
}
|
||||
|
||||
static <V> Reference<V> from(Persistance<V> p, InputCache buffer, int offset) {
|
||||
return new PerBufferReference<>(p, buffer, offset);
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
package org.enso.persist;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
final class PerGenerator {
|
||||
|
||||
static final byte[] HEADER = new byte[] {0x0a, 0x0d, 0x02, 0x0f};
|
||||
private final OutputStream main;
|
||||
private final Map<Object, Integer> knownObjects = new IdentityHashMap<>();
|
||||
final Function<Object, Object> writeReplace;
|
||||
private int position;
|
||||
|
||||
private PerGenerator(OutputStream out, int position, Function<Object, Object> writeReplace) {
|
||||
this.main = out;
|
||||
this.writeReplace = writeReplace == null ? Function.identity() : writeReplace;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
static byte[] writeObject(Object obj, Function<Object, Object> writeReplace) throws IOException {
|
||||
var out = new ByteArrayOutputStream();
|
||||
var data = new DataOutputStream(out);
|
||||
data.write(PerGenerator.HEADER);
|
||||
data.writeInt(PerMap.DEFAULT.versionStamp);
|
||||
data.write(new byte[4]); // space
|
||||
data.flush();
|
||||
var g = new PerGenerator(out, 12, writeReplace);
|
||||
var at = g.writeObject(obj);
|
||||
var arr = out.toByteArray();
|
||||
arr[8] = (byte) ((at >> 24) & 0xff);
|
||||
arr[9] = (byte) ((at >> 16) & 0xff);
|
||||
arr[10] = (byte) ((at >> 8) & 0xff);
|
||||
arr[11] = (byte) (at & 0xff);
|
||||
return arr;
|
||||
}
|
||||
|
||||
final <T> int writeObject(T t) throws IOException {
|
||||
if (t == null) {
|
||||
return -1;
|
||||
}
|
||||
java.lang.Object obj = writeReplace.apply(t);
|
||||
java.lang.Integer found = knownObjects.get(obj);
|
||||
if (found == null) {
|
||||
org.enso.persist.Persistance<?> p = PerMap.DEFAULT.forType(obj.getClass());
|
||||
java.io.ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
p.writeInline(obj, new ReferenceOutput(this, os));
|
||||
found = this.position;
|
||||
byte[] arr = os.toByteArray();
|
||||
main.write(arr);
|
||||
this.position += arr.length;
|
||||
knownObjects.put(obj, found);
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
final void writeIndirect(Object obj, Persistance.Output out) throws IOException {
|
||||
if (obj == null) {
|
||||
out.writeInt(-1);
|
||||
return;
|
||||
}
|
||||
obj = writeReplace.apply(obj);
|
||||
org.enso.persist.Persistance<?> p = PerMap.DEFAULT.forType(obj.getClass());
|
||||
java.lang.Integer found = knownObjects.get(obj);
|
||||
if (found == null) {
|
||||
var os = new ByteArrayOutputStream();
|
||||
var osData = new ReferenceOutput(this, os);
|
||||
p.writeInline(obj, osData);
|
||||
found = position;
|
||||
if (os.size() == 0) {
|
||||
os.write(0);
|
||||
}
|
||||
byte[] arr = os.toByteArray();
|
||||
main.write(arr);
|
||||
position += arr.length;
|
||||
knownObjects.put(obj, found);
|
||||
}
|
||||
out.writeInt(found);
|
||||
out.writeInt(p.id);
|
||||
}
|
||||
|
||||
private static final class ReferenceOutput extends DataOutputStream
|
||||
implements Persistance.Output {
|
||||
private final PerGenerator generator;
|
||||
|
||||
ReferenceOutput(PerGenerator g, ByteArrayOutputStream out) {
|
||||
super(out);
|
||||
this.generator = g;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void writeInline(Class<T> clazz, T t) throws IOException {
|
||||
var obj = generator.writeReplace.apply(t);
|
||||
var p = PerMap.DEFAULT.forType(clazz);
|
||||
p.writeInline(obj, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeObject(Object obj) throws IOException {
|
||||
this.generator.writeIndirect(obj, this);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,230 @@
|
||||
package org.enso.persist;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.enso.persist.PerUtils.raise;
|
||||
import org.enso.persist.Persistance.Input;
|
||||
import org.enso.persist.Persistance.Reference;
|
||||
|
||||
final class PerInputImpl implements Input {
|
||||
private final InputCache cache;
|
||||
private final ByteBuffer buf;
|
||||
private int at;
|
||||
|
||||
PerInputImpl(InputCache cache, int at) {
|
||||
this.cache = cache;
|
||||
this.buf = cache.buf();
|
||||
this.at = at;
|
||||
}
|
||||
|
||||
static <T> Reference<T> readObject(byte[] arr, Function<Object, Object> readResolve) throws IOException {
|
||||
for (var i = 0; i < PerGenerator.HEADER.length; i++) {
|
||||
if (arr[i] != PerGenerator.HEADER[i]) {
|
||||
throw new IOException("Wrong header");
|
||||
}
|
||||
}
|
||||
var buf = ByteBuffer.wrap(arr);
|
||||
var version = buf.getInt(4);
|
||||
if (version != PerMap.DEFAULT.versionStamp) {
|
||||
throw new IOException("Incompatible version " + version + " != " + PerMap.DEFAULT.versionStamp);
|
||||
}
|
||||
var at = buf.getInt(8);
|
||||
var cache = new InputCache(buf, readResolve);
|
||||
|
||||
return PerBufferReference.from(cache, at);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T readInline(Class<T> clazz) {
|
||||
Persistance<T> p = PerMap.DEFAULT.forType(clazz);
|
||||
T res = p.readWith(this);
|
||||
var resolve = cache.readResolve().apply(res);
|
||||
return clazz.cast(resolve);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object readObject() throws IOException {
|
||||
var obj = readIndirect(cache, PerMap.DEFAULT, this);
|
||||
return obj;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Persistance.Reference<T> readReference(Class<T> clazz) throws IOException {
|
||||
var obj = readIndirectAsReference(cache, PerMap.DEFAULT, this, clazz);
|
||||
return obj;
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
return buf.get(at++);
|
||||
}
|
||||
|
||||
public int read(byte[] b) throws IOException {
|
||||
buf.get(at, b);
|
||||
at += b.length;
|
||||
return b.length;
|
||||
}
|
||||
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
buf.get(at, b, off, len);
|
||||
at += len;
|
||||
return len;
|
||||
}
|
||||
|
||||
public long skip(long n) throws IOException {
|
||||
at += Math.toIntExact(n);
|
||||
return at;
|
||||
}
|
||||
|
||||
public int available() throws IOException {
|
||||
return buf.limit() - at;
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFully(byte[] b) throws IOException {
|
||||
read(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFully(byte[] b, int off, int len) throws IOException {
|
||||
read(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int skipBytes(int n) throws IOException {
|
||||
at += n;
|
||||
return at;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readBoolean() throws IOException {
|
||||
int b = read();
|
||||
return b != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte readByte() throws IOException {
|
||||
return buf.get(at++);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readUnsignedByte() throws IOException {
|
||||
byte b = readByte();
|
||||
return b >= 0 ? b : b + 256;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short readShort() throws IOException {
|
||||
short s = buf.getShort(at);
|
||||
at += 2;
|
||||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readUnsignedShort() throws IOException {
|
||||
short s = readShort();
|
||||
return s >= 0 ? s : s + 256 * 256;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char readChar() throws IOException {
|
||||
return (char) readShort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readInt() throws IOException {
|
||||
int i = buf.getInt(at);
|
||||
at += 4;
|
||||
return i;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readLong() throws IOException {
|
||||
long l = buf.getLong(at);
|
||||
at += 8;
|
||||
return l;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float readFloat() throws IOException {
|
||||
float f = buf.getFloat(at);
|
||||
at += 4;
|
||||
return f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double readDouble() throws IOException {
|
||||
double d = buf.getDouble(at);
|
||||
at += 8;
|
||||
return d;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readLine() throws IOException {
|
||||
throw new IOException("No readLine in buffer");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readUTF() throws IOException {
|
||||
return DataInputStream.readUTF(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Input[at=" + at + " of " + this.buf.limit() + "]";
|
||||
}
|
||||
|
||||
static Object readIndirect(InputCache cache, PerMap map, Input in) throws IOException {
|
||||
var at = in.readInt();
|
||||
if (at < 0) {
|
||||
return null;
|
||||
}
|
||||
var id = in.readInt();
|
||||
var p = map.forId(id);
|
||||
|
||||
if (cache.cache().get(at) instanceof Object res) {
|
||||
return p.clazz.cast(res);
|
||||
}
|
||||
|
||||
var inData = new PerInputImpl(cache, at);
|
||||
var res = p.readWith(inData);
|
||||
res = cache.readResolve().apply(res);
|
||||
var prev = cache.cache().put(at, res);
|
||||
if (prev != null) {
|
||||
throw raise(RuntimeException.class, new IOException("Adding at " + at + " object: " + res.getClass().getName() + " but there already is " + prev.getClass().getName()));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T> Reference<T> readIndirectAsReference(InputCache buffer, PerMap map, Input in, Class<T> clazz) throws IOException {
|
||||
var at = in.readInt();
|
||||
if (at < 0) {
|
||||
return null;
|
||||
}
|
||||
var id = in.readInt();
|
||||
var p = map.forId(id);
|
||||
if (clazz.isAssignableFrom(p.clazz)) {
|
||||
return PerBufferReference.from((Persistance<T>) p, buffer, at);
|
||||
} else {
|
||||
throw new IOException("Expecting " + clazz.getName() + " but found " + p.clazz.getName());
|
||||
}
|
||||
}
|
||||
|
||||
static final record InputCache(
|
||||
ByteBuffer buf,
|
||||
Function<Object, Object> readResolve,
|
||||
Map<Integer, Object> cache
|
||||
) {
|
||||
InputCache(ByteBuffer buf, Function<Object, Object> readResolve) {
|
||||
this(buf, readResolve == null ? Function.identity() : readResolve, new HashMap<>());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
package org.enso.persist;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.openide.util.lookup.Lookups;
|
||||
|
||||
final class PerMap {
|
||||
|
||||
static final PerMap DEFAULT = new PerMap();
|
||||
private final Map<Integer, Persistance<?>> ids = new HashMap<>();
|
||||
private final Map<Class<?>, Persistance<?>> types = new HashMap<>();
|
||||
final int versionStamp;
|
||||
|
||||
private PerMap() {
|
||||
int hash = 0;
|
||||
var loader = getClass().getClassLoader();
|
||||
for (var p : Lookups.metaInfServices(loader).lookupAll(Persistance.class)) {
|
||||
org.enso.persist.Persistance<?> prevId = ids.put(p.id, p);
|
||||
if (prevId != null) {
|
||||
throw new IllegalStateException(
|
||||
"Multiple registrations for ID " + p.id + " " + prevId + " != " + p);
|
||||
}
|
||||
hash += p.id;
|
||||
org.enso.persist.Persistance<?> prevType = types.put(p.clazz, p);
|
||||
if (prevType != null) {
|
||||
throw new IllegalStateException(
|
||||
"Multiple registrations for " + p.clazz.getName() + " " + prevId + " != " + p);
|
||||
}
|
||||
}
|
||||
versionStamp = hash;
|
||||
}
|
||||
|
||||
@SuppressWarnings(value = "unchecked")
|
||||
private synchronized <T> Persistance<T> searchSupertype(String name, Class<T> type) {
|
||||
// synchronized as it mutes the types map
|
||||
// however over time the types map gets saturated and
|
||||
// the synchronization will get less frequent
|
||||
// please note that Persistance as well as Class (as a key) have all fields final =>
|
||||
// as soon as they become visible from other threads, they have to look consistent
|
||||
NOT_FOUND:
|
||||
if (type != null) {
|
||||
org.enso.persist.Persistance<?> p = types.get(type);
|
||||
if (p != null) {
|
||||
if (!p.includingSubclasses) {
|
||||
break NOT_FOUND;
|
||||
}
|
||||
} else {
|
||||
for (java.lang.Class<?> in : type.getInterfaces()) {
|
||||
p = searchSupertype(name, in);
|
||||
if (p != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (p == null && !type.isInterface()) {
|
||||
p = searchSupertype(name, type.getSuperclass());
|
||||
}
|
||||
types.put(type, p);
|
||||
}
|
||||
return (Persistance<T>) p;
|
||||
}
|
||||
if (type == null) {
|
||||
throw PerUtils.raise(RuntimeException.class, new IOException("No persistance for " + name));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings(value = "unchecked")
|
||||
final <T> Persistance<T> forType(Class<T> type) {
|
||||
org.enso.persist.Persistance<?> p = types.get(type);
|
||||
if (p == null) {
|
||||
p = searchSupertype(type.getName(), type);
|
||||
}
|
||||
return (Persistance<T>) p;
|
||||
}
|
||||
|
||||
final Persistance<?> forId(int id) {
|
||||
org.enso.persist.Persistance<?> p = ids.get(id);
|
||||
if (p == null) {
|
||||
throw PerUtils.raise(RuntimeException.class, new IOException("No persistance for " + id));
|
||||
}
|
||||
return p;
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package org.enso.persist;
|
||||
|
||||
final class PerMemoryReference<T> extends Persistance.Reference<T> {
|
||||
static final Persistance.Reference<?> NULL = new PerMemoryReference<>(null);
|
||||
private final T value;
|
||||
|
||||
PerMemoryReference(T obj) {
|
||||
this.value = obj;
|
||||
}
|
||||
|
||||
final T value() {
|
||||
return value;
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package org.enso.persist;
|
||||
|
||||
final class PerUtils {
|
||||
private PerUtils() {}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <E extends Throwable> E raise(Class<E> clazz, Throwable t) throws E {
|
||||
throw (E) t;
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package org.enso.persist;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Repeatable;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation for an automatic persistance of a class. Use to generate implementation and
|
||||
* registration of {@link Persistance} subclass to read and write simple records and case classes:
|
||||
*
|
||||
* <p>{@snippet file="org/enso/persist/PersistanceTest.java" region="annotation"}
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Repeatable(Persistable.Group.class)
|
||||
public @interface Persistable {
|
||||
|
||||
/**
|
||||
* The class to generate {@link Persistance} for. If the value is omitted then the code is
|
||||
* generated for the class that is annotated by this annotation. Example of multiple
|
||||
* {@code @Persistable} annotations on a single element.
|
||||
*
|
||||
* <p>{@snippet file="org/enso/persist/PersistanceTest.java" region="annotation"}
|
||||
*
|
||||
* <p>Example of self annotated class:
|
||||
*
|
||||
* <p>{@snippet file="org/enso/persist/PersistanceTest.java" region="self-annotation"}
|
||||
*
|
||||
* @return the class to generate read/write persistance code for
|
||||
*/
|
||||
Class<?> clazz() default Object.class;
|
||||
|
||||
/**
|
||||
* ID of the class. Each registered {@link Persistance} sub class must have a unique ID. This
|
||||
* attribute specifies it for the generated code. When serialization format changes (for example
|
||||
* by changing the number or type of constructor arguments), change also the ID.
|
||||
*
|
||||
* @return unique ID for the persisted {@link #clazz()}
|
||||
*/
|
||||
int id();
|
||||
|
||||
/** Multiple {@link Persistable} annotations. */
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface Group {
|
||||
/**
|
||||
* The array of {@link Persistable} annotations.
|
||||
*
|
||||
* @return the array of annotations.
|
||||
*/
|
||||
Persistable[] value();
|
||||
}
|
||||
}
|
@ -0,0 +1,220 @@
|
||||
package org.enso.persist;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.enso.persist.PerUtils.raise;
|
||||
|
||||
/** Central persistance class. Use static {@link
|
||||
* Persistance#write write} method to turn a graph of JVM objects into a {@code byte[]}.
|
||||
*
|
||||
* <p>{@snippet file="org/enso/persist/PersistanceTest.java" region="write"}
|
||||
*
|
||||
* <p>Use sibling static {@link Persistance#read readO} method to read the byte buffer back into
|
||||
* their memory representation.
|
||||
*
|
||||
* <p>{@snippet file="org/enso/persist/PersistanceTest.java" region="read"}
|
||||
*
|
||||
* <h2>Manual Persistance</h2>
|
||||
*
|
||||
* Unlike typical Java serialization (which tries to make things automatic), this framework requires
|
||||
* one to implement the persistance manually. For each class that one wants to support, one has to
|
||||
* implement subclass {@link Persistance} and implement its {@link Persistance#writeObject} and
|
||||
* {@link Persistance#readObject} method.
|
||||
*
|
||||
* <p>{@snippet file="org/enso/persist/PersistanceTest.java" region="manual"}
|
||||
*
|
||||
* There is a semi-automatic way to generate such subclasses of {@link Persistance} via
|
||||
* the {@link Persistable @Persistable} annotation.
|
||||
* @param <T>
|
||||
*/
|
||||
public abstract class Persistance<T> {
|
||||
final Class<T> clazz;
|
||||
final boolean includingSubclasses;
|
||||
final int id;
|
||||
|
||||
/**
|
||||
* Constructor for subclasses to register persistance for certain
|
||||
* {@code clazz}. Sample registration:
|
||||
*
|
||||
* <p>
|
||||
* {@snippet file="org/enso/persist/PersistanceTest.java" region="manual"}
|
||||
*
|
||||
* Each persistance requires unique ID. A stream created by
|
||||
* {@link #write(Object, Function<Object, Object>)} and read by
|
||||
* {@link #read(byte[], Function<Object, Object>)} contains a header derived
|
||||
* from the all the IDs present in the system. When versioning the protocol
|
||||
* and implementation:
|
||||
* <ul>
|
||||
* <li>when you change something really core in the Persitance itself -
|
||||
* change the header</li>
|
||||
* <li>when you add or remove a Persistance implementation the version
|
||||
* changes (computed from all the IDs)</li>
|
||||
* <li>when you change format of some Persitance.writeObject method - change
|
||||
* its ID</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param clazz the class persistance is written for
|
||||
* @param includingSubclasses should the persistance also apply to
|
||||
* subclasses
|
||||
* @param id unique ID
|
||||
*/
|
||||
protected Persistance(Class<T> clazz, boolean includingSubclasses, int id) {
|
||||
this.clazz = clazz;
|
||||
this.includingSubclasses = includingSubclasses;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
protected abstract void writeObject(T obj, Output out) throws IOException;
|
||||
protected abstract T readObject(Input in) throws IOException, ClassNotFoundException;
|
||||
|
||||
/** Prints the {@code clazz} and {@code id} values.
|
||||
*/
|
||||
@Override
|
||||
public final String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Persistance{");
|
||||
sb.append("clazz=").append(clazz.getName());
|
||||
sb.append(", id=").append(id);
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/** Extended output interface for {@link #writeObject(Object)} method.
|
||||
*/
|
||||
public static interface Output extends DataOutput {
|
||||
/** Writes an object "inline" - as a value.
|
||||
*
|
||||
* @param <T> the type of the class
|
||||
* @param clazz the class to use to locate {@link Persistance} implementation
|
||||
* @param obj the object to write down
|
||||
* @throws IOException when an I/O problem happens
|
||||
*/
|
||||
public abstract <T> void writeInline(Class<T> clazz, T obj) throws IOException;
|
||||
|
||||
/** Writes an object as a "reference". Objects written by by this method
|
||||
* are shared - each {@code obj} is stored only once and referenced from
|
||||
* all the locations it is used.
|
||||
*
|
||||
* @param obj the object to write down
|
||||
* @throws IOException when an I/O problem happens
|
||||
*/
|
||||
public abstract void writeObject(Object obj) throws IOException;
|
||||
}
|
||||
|
||||
/** Extended input interface for the {@link #writeObject(T, Output)} method.
|
||||
*/
|
||||
public static interface Input extends DataInput {
|
||||
/** Reads objects written down by {@link Output#writeInline}.
|
||||
*
|
||||
* @param <T> the type to read
|
||||
* @param clazz class that identifies {@link Persistance} to use for reading
|
||||
* @return the read in object
|
||||
* @throws IOException when an I/O problem happens
|
||||
*/
|
||||
public abstract <T> T readInline(Class<T> clazz) throws IOException;
|
||||
|
||||
/** Reads a reference to object written down by {@link Output#writeObject(Object)}.
|
||||
*
|
||||
* @return the read in object
|
||||
* @throws IOException when an I/O problem happens
|
||||
*/
|
||||
public abstract Object readObject() throws IOException;
|
||||
|
||||
/** * Reads a reference to an object written down by {@link Output#writeObject(Object)}
|
||||
* but without reading the object itself. The object can then be obtained
|
||||
* <em>"later"</em> via the {@link Reference#get(Class)} method.
|
||||
*
|
||||
* @param <T> the type to read
|
||||
* @param clazz the expected type of the object to read
|
||||
* @return a reference allowing to read the object later
|
||||
* @throws IOException when an I/O problem happens
|
||||
*/
|
||||
public <T> Reference<T> readReference(Class<T> clazz) throws IOException;
|
||||
}
|
||||
|
||||
final void writeInline(Object obj, Output out) throws IOException {
|
||||
writeObject(clazz.cast(obj), out);
|
||||
}
|
||||
|
||||
final T readWith(Input in) {
|
||||
try {
|
||||
return readObject(in);
|
||||
} catch (IOException | ClassNotFoundException ex) {
|
||||
throw raise(RuntimeException.class, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/** Read object written down by {@link #write} from an array.
|
||||
*
|
||||
* <p>{@snippet file="org/enso/persist/PersistanceTest.java" region="read"}
|
||||
*
|
||||
* @param <T> expected type of object
|
||||
* @param arr the stored bytes
|
||||
* @param readResolve either {@code null} or function to call for each object being stored to provide a replacement
|
||||
* @return the read object
|
||||
* @throws java.io.IOException when an I/O problem happens
|
||||
*/
|
||||
public static <T> Reference<T> read(byte[] arr, Function<Object, Object> readResolve) throws IOException {
|
||||
return PerInputImpl.readObject(arr, readResolve);
|
||||
}
|
||||
|
||||
/** Writes down an object into an array of bytes. Use {@link #read} method to convert the
|
||||
* array back to object.
|
||||
*
|
||||
* @param obj the object to persist
|
||||
* @param writeReplace {@code null} or a function that allows to convert each object before storing it down
|
||||
* @return the array of bytes
|
||||
* @throws IOException when an I/O problem happens
|
||||
*/
|
||||
public static byte[] write(Object obj, Function<Object, Object> writeReplace) throws IOException {
|
||||
return PerGenerator.writeObject(obj, writeReplace);
|
||||
}
|
||||
|
||||
/** Reference to an object. Either created directly or obtained from {@link Input#readReference}
|
||||
* method.
|
||||
*
|
||||
* @see Input#readReference
|
||||
*/
|
||||
public static sealed abstract class Reference<T> permits PerBufferReference, PerMemoryReference {
|
||||
Reference() {
|
||||
}
|
||||
/** Reference to {@code null} object.
|
||||
* @param <T> the type of the reference
|
||||
* @return reference to {@code null}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static final <T> Reference<T> none() {
|
||||
return (Reference<T>) PerMemoryReference.NULL;
|
||||
}
|
||||
|
||||
/** Extract object from the reference. Multiple calls to this method
|
||||
* may return the same or another instance of object depending on the
|
||||
* type of the reference and <em>laziness policy</em>.
|
||||
*
|
||||
* @param <V> the type of the object to expect
|
||||
* @param expectedType the expected clazz of the object
|
||||
* @return the referenced object
|
||||
* @throws ClassCastException if the object isn't of the expected type
|
||||
*/
|
||||
public <V> V get(Class<V> expectedType) {
|
||||
var value = switch (this) {
|
||||
case PerMemoryReference m -> m.value();
|
||||
case PerBufferReference<T> b -> b.readObject(expectedType);
|
||||
};
|
||||
return expectedType.cast(value);
|
||||
}
|
||||
|
||||
/** Creates a reference to existing object.
|
||||
*
|
||||
* @param <V> the type of the object
|
||||
* @param obj the object to "reference"
|
||||
* @return reference pointing to the provided object
|
||||
*/
|
||||
public static <V> Reference<V> of(V obj) {
|
||||
return new PerMemoryReference<>(obj);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Framework for persisting Java objects and reading them <em>"lazily"</em>. Use static {@link
|
||||
* Persistance#write write} method to turn a graph of JVM objects into a {@code byte[]}.
|
||||
*
|
||||
* <p>{@snippet file="org/enso/persist/PersistanceTest.java" region="write"}
|
||||
*
|
||||
* <p>Use sibling static {@link Persistance#read readO} method to read the byte buffer back into
|
||||
* their memory representation.
|
||||
*
|
||||
* <p>{@snippet file="org/enso/persist/PersistanceTest.java" region="read"}
|
||||
*
|
||||
* <h2>Laziness</h2>
|
||||
*
|
||||
* The major benefit of this framework is the ability to read only the part of the stored graph that
|
||||
* is actually needed. One may {@linkplain Persistance.Input#readReference obtain a reference} to an
|
||||
* object and turn that {@link Persistance.Reference} into actual object <b>later</b>, when needed.
|
||||
*
|
||||
* <h2>Manual Persistance</h2>
|
||||
*
|
||||
* Unlike typical Java serialization (which tries to make things automatic), this framework requires
|
||||
* one to implement the persistance manually. For each class that one wants to support, one has to
|
||||
* implement subclass {@link Persistance} and implement its {@link Persistance#writeObject} and
|
||||
* {@link Persistance#readObject} method.
|
||||
*
|
||||
* <p>{@snippet file="org/enso/persist/PersistanceTest.java" region="manual"}
|
||||
*
|
||||
* <h2>Semi-automatic Persistance</h2>
|
||||
*
|
||||
* Writing serialization and deserialization implementation manually is a boring, tedious and
|
||||
* errorprone process. That's why there is a {@link Persistable} annotation and associated
|
||||
* annotation processor that generates the necessary {@link Persistance} subclass based on the
|
||||
* "richest" constructor in the class to be persisted. This approach seems to work well for Java
|
||||
* records and Scala case classes.
|
||||
*
|
||||
* <p>{@snippet file="org/enso/persist/PersistanceTest.java" region="annotation"}
|
||||
*/
|
||||
package org.enso.persist;
|
@ -0,0 +1,167 @@
|
||||
package org.enso.compiler.core;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.enso.persist.Persistable;
|
||||
import org.enso.persist.Persistance;
|
||||
import org.junit.Test;
|
||||
import org.openide.util.lookup.ServiceProvider;
|
||||
|
||||
public class PersistanceTest {
|
||||
@Test
|
||||
public void testUUIDPersistance() throws Exception {
|
||||
// @start region="write"
|
||||
var obj = UUID.randomUUID();
|
||||
var buffer = Persistance.write(obj, null);
|
||||
assertNotNull("Byte array is returned", buffer);
|
||||
assertNotEquals("It has non-zero length", 0, buffer.length);
|
||||
// @end region="write"
|
||||
|
||||
// @start region="read"
|
||||
var ref = Persistance.read(buffer, null);
|
||||
var loaded = ref.get(UUID.class);
|
||||
assertEquals("The same object was recreated", obj, loaded);
|
||||
// @end region="read"
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readResolve() throws Exception {
|
||||
var in = new Service(5);
|
||||
var arr = Persistance.write(in, (Function<Object, Object>) null);
|
||||
|
||||
var plain = Persistance.read(arr, (Function<Object, Object>) null);
|
||||
assertEquals("Remains five", 5, plain.get(Service.class).value());
|
||||
|
||||
var multiOnRead = Persistance.read(arr, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj);
|
||||
assertEquals("Multiplied on read", 15, multiOnRead.get(Service.class).value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeReplace() throws Exception {
|
||||
var in = new Service(5);
|
||||
var arr = Persistance.write(in, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj);
|
||||
|
||||
var plain = Persistance.read(arr, (Function<Object, Object>) null);
|
||||
assertEquals("Multiplied on write", 15, plain.get(Service.class).value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readResolveInline() throws Exception {
|
||||
var in = new ServiceSupply(new Service(5));
|
||||
var arr = Persistance.write(in, (Function<Object, Object>) null);
|
||||
|
||||
var plain = Persistance.read(arr, (Function<Object, Object>) null);
|
||||
assertEquals("Remains five", 5, plain.get(ServiceSupply.class).supply().value());
|
||||
|
||||
var multiOnRead = Persistance.read(arr, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj);
|
||||
assertEquals("Multiplied on read", 15, multiOnRead.get(ServiceSupply.class).supply().value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeReplaceInline() throws Exception {
|
||||
var in = new ServiceSupply(new Service(5));
|
||||
var arr = Persistance.write(in, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj);
|
||||
|
||||
var plain = Persistance.read(arr, (Function<Object, Object>) null);
|
||||
assertEquals("Multiplied on write", 15, plain.get(ServiceSupply.class).supply().value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readResolveReference() throws Exception {
|
||||
var in = new IntegerSupply(new Service(5));
|
||||
var arr = Persistance.write(in, (Function<Object, Object>) null);
|
||||
|
||||
var plain = Persistance.read(arr, (Function<Object, Object>) null);
|
||||
assertEquals("Remains five", 5, (int) plain.get(IntegerSupply.class).supply().get());
|
||||
assertEquals("Remains five 2", 5, (int) plain.get(IntegerSupply.class).supply().get());
|
||||
|
||||
var multiOnRead = Persistance.read(arr, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj);
|
||||
assertEquals("Multiplied on read", 15, (int) multiOnRead.get(IntegerSupply.class).supply().get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeReplaceReference() throws Exception {
|
||||
var in = new IntegerSupply(new Service(5));
|
||||
var arr = Persistance.write(in, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj);
|
||||
|
||||
var plain = Persistance.read(arr, (Function<Object, Object>) null);
|
||||
assertEquals("Multiplied on write", 15, (int) plain.get(IntegerSupply.class).supply().get());
|
||||
}
|
||||
|
||||
private static <T> T serde(Class<T> clazz, T l, int expectedSize) throws IOException {
|
||||
var arr = Persistance.write(l, (Function<Object, Object>) null);
|
||||
if (expectedSize >= 0) {
|
||||
assertEquals(expectedSize, arr.length - 12);
|
||||
}
|
||||
var ref = Persistance.read(arr, (Function<Object, Object>) null);
|
||||
return ref.get(clazz);
|
||||
}
|
||||
|
||||
// @start region="manual"
|
||||
@ServiceProvider(service = Persistance.class)
|
||||
public static final class PersistUUID extends Persistance<UUID> {
|
||||
|
||||
public PersistUUID() {
|
||||
super(UUID.class, false, 328439);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeObject(UUID obj, Output out) throws IOException {
|
||||
out.writeLong(obj.getMostSignificantBits());
|
||||
out.writeLong(obj.getLeastSignificantBits());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UUID readObject(Input in) throws IOException, ClassNotFoundException {
|
||||
var most = in.readLong();
|
||||
var least = in.readLong();
|
||||
return new UUID(most, least);
|
||||
}
|
||||
}
|
||||
// @end region="manual"
|
||||
|
||||
|
||||
public static final class Singleton {
|
||||
public static final Singleton INSTANCE = new Singleton();
|
||||
|
||||
private Singleton() {}
|
||||
}
|
||||
|
||||
@ServiceProvider(service = Persistance.class)
|
||||
public static final class PersistSingleton extends Persistance<Singleton> {
|
||||
|
||||
public PersistSingleton() {
|
||||
super(Singleton.class, false, 432433);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeObject(Singleton obj, Output out) throws IOException {}
|
||||
|
||||
@Override
|
||||
protected Singleton readObject(Input in) throws IOException, ClassNotFoundException {
|
||||
return Singleton.INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
// @start region="annotation"
|
||||
@Persistable(clazz=Service.class, id=432434)
|
||||
@Persistable(clazz=IntegerSupply.class, id=432435)
|
||||
public record Service(int value) implements Supplier<Integer> {
|
||||
@Override
|
||||
public Integer get() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
public record IntegerSupply(Supplier<Integer> supply) {}
|
||||
// @end region="annotation"
|
||||
|
||||
// @start region="self-annotation"
|
||||
@Persistable(id=432436)
|
||||
public record ServiceSupply(Service supply) {}
|
||||
// @end region="self-annotation"
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
package org.enso.refactoring
|
||||
|
||||
import org.enso.syntax.text.Location
|
||||
import org.enso.text.editing.model
|
||||
import org.enso.text.editing.{IndexedSource, TextEditor}
|
||||
import org.enso.text.editing.model.TextEdit
|
||||
import org.enso.compiler.core.ir.Location
|
||||
|
||||
object RenameUtils {
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
package org.enso.refactoring;
|
||||
|
||||
import org.enso.syntax.text.Location;
|
||||
import org.enso.compiler.core.ir.Location;
|
||||
import org.enso.text.buffer.Rope;
|
||||
import org.enso.text.editing.IndexedSource;
|
||||
import org.enso.text.editing.IndexedSource$;
|
||||
@ -11,6 +11,7 @@ import org.enso.text.editing.TextEditor;
|
||||
import org.enso.text.editing.model;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import scala.collection.immutable.Seq;
|
||||
import scala.collection.immutable.VectorBuilder;
|
||||
import scala.util.Either;
|
||||
|
@ -1,15 +0,0 @@
|
||||
package org.enso.syntax.text
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// AbsolutePosition //////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/** Represents an expression's absolute positioning in a source file.
|
||||
* @param start the inclusive, 0-indexed position of the beginning
|
||||
* of the expression
|
||||
* @param end the exclusive, 0-indexed position of the end of
|
||||
* the expression
|
||||
*/
|
||||
case class Location(start: Int, end: Int) {
|
||||
def length: Int = end - start
|
||||
}
|
@ -1,2 +1,2 @@
|
||||
/FasterXML/jackson-dataformats-binary/blob/2.16/LICENSE
|
||||
#license
|
||||
/FasterXML/jackson-dataformats-binary/blob/2.17/LICENSE
|
||||
|
Loading…
Reference in New Issue
Block a user