diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Meta/Enso_Project.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Meta/Enso_Project.enso index e8f925e5f963..e6a98f22370c 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Meta/Enso_Project.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Meta/Enso_Project.enso @@ -45,7 +45,7 @@ type Project_Description Arguments: - prim_root_file: The primitive root file of the project. - prim_config: The primitive config of the project. - Value prim_root_file prim_config + private Value prim_root_file prim_config ## GROUP Metadata ICON folder diff --git a/distribution/lib/Standard/Examples/0.0.0-dev/src/Main.enso b/distribution/lib/Standard/Examples/0.0.0-dev/src/Main.enso index 7b2ec7c999f2..3878bdc74d34 100644 --- a/distribution/lib/Standard/Examples/0.0.0-dev/src/Main.enso +++ b/distribution/lib/Standard/Examples/0.0.0-dev/src/Main.enso @@ -17,13 +17,15 @@ type Example_Error_Type - message: The message contained in the error type. Error message -## The standard library data directory. +## The data directory of the Examples project. data_dir : File -data_dir = enso_project.data +data_dir = + this_proj = Project_Description.new Standard.Examples.Main + this_proj.data ## An example CSV file for experimenting with Table and its APIs. csv : File -csv = enso_project.data / "food_shop_inventory.csv" +csv = data_dir / "food_shop_inventory.csv" ## The path to the CSV. csv_path : Text @@ -39,7 +41,7 @@ csv_path = csv.path xls : File xls = url = "https://enso-data-samples.s3.us-west-1.amazonaws.com/spreadsheet.xls" - file = enso_project.data / 'spreadsheet.xls' + file = data_dir / 'spreadsheet.xls' if file.exists.not then Context.Output.with_enabled <| HTTP.fetch url . body . write file file @@ -54,7 +56,7 @@ xls = xlsx : File xlsx = url = "https://enso-data-samples.s3.us-west-1.amazonaws.com/spreadsheet.xlsx" - file = enso_project.data / 'spreadsheet.xlsx' + file = data_dir / 'spreadsheet.xlsx' if file.exists.not then Context.Output.with_enabled <| HTTP.fetch url . body . write file file @@ -62,7 +64,7 @@ xlsx = ## A file that is used for writing temporary data as part of tests. scratch_file : File scratch_file = - file = enso_project.data / "scratch_file" + file = data_dir / "scratch_file" if file.exists.not then Nothing else Context.Output.with_enabled <| file.delete file @@ -169,7 +171,7 @@ uri = URI.parse "http://user:pass@example.com/foo/bar?key=val" image_file : File image_file = url = "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e9/Hue_alpha_falloff.png/320px-Hue_alpha_falloff.png" - file = enso_project.data / "image.png" + file = data_dir / "image.png" if file.exists.not then Context.Output.with_enabled <| HTTP.fetch url . body . write file file @@ -258,12 +260,12 @@ inventory_table = csv.read ## A simple table that contains basic item popularity data for the food shop. popularity_table : Table popularity_table = - (enso_project.data / "food_shop_popularity.csv") . read + (data_dir / "food_shop_popularity.csv") . read ## A simple tablethat contains basic transaction data for the food shop. transactions_table : Table transactions_table = - (enso_project.data / "food_shop_transactions.csv") . read + (data_dir / "food_shop_transactions.csv") . read ## An example regex match. match : Match diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/meta/EnsoProjectTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/meta/EnsoProjectTest.java new file mode 100644 index 000000000000..a8f28fc66b22 --- /dev/null +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/meta/EnsoProjectTest.java @@ -0,0 +1,125 @@ +package org.enso.interpreter.test.meta; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; + +import java.io.IOException; +import java.util.List; +import java.util.Set; +import org.enso.common.LanguageInfo; +import org.enso.interpreter.util.ScalaConversions; +import org.enso.pkg.QualifiedName; +import org.enso.polyglot.PolyglotContext; +import org.enso.polyglot.RuntimeOptions; +import org.enso.test.utils.ContextUtils; +import org.enso.test.utils.ProjectUtils; +import org.enso.test.utils.SourceModule; +import org.graalvm.polyglot.Source; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class EnsoProjectTest { + @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Test + public void noProjectWhenEvaluatingSingleFile() { + try (var ctx = ContextUtils.createDefaultContext()) { + var res = + ContextUtils.evalModule( + ctx, + """ + from Standard.Base import all + from Standard.Base.Errors.Common import Module_Not_In_Package_Error + + main = + enso_project.is_error + """); + assertThat(res, notNullValue()); + assertThat(res.asBoolean(), is(true)); + } + } + + @Test + public void ensoProjectWorksInOneProject() throws IOException { + var mainMod = + new SourceModule( + QualifiedName.fromString("Main"), + """ + from Standard.Base import all + main = + enso_project.name + """); + var projDir = temporaryFolder.newFolder().toPath(); + ProjectUtils.createProject("Proj", Set.of(mainMod), projDir); + ProjectUtils.testProjectRun( + projDir, + (res) -> { + assertThat(res.asString(), is("Proj")); + }); + } + + @Test + public void ensoProjectWorksInTwoProjects() throws IOException { + var mainMod1 = + new SourceModule( + QualifiedName.fromString("Main"), + """ + from Standard.Base import all + + get_enso_project_name = + enso_project.name + """); + var mainMod2 = + new SourceModule( + QualifiedName.fromString("Main"), + """ + from local.Proj1 import get_enso_project_name + main = + get_enso_project_name + """); + var projDir1 = temporaryFolder.newFolder().toPath(); + var projDir2 = temporaryFolder.newFolder().toPath(); + ProjectUtils.createProject("Proj1", Set.of(mainMod1), projDir1); + ProjectUtils.createProject("Proj2", Set.of(mainMod2), projDir2); + ProjectUtils.testProjectRun( + projDir2, + (res) -> { + assertThat(res.asString(), is("Proj2")); + }); + } + + @Test + public void ensoProjectCanBeCalledFromJava() throws IOException { + var mainMod = + new SourceModule( + QualifiedName.fromString("Main"), + """ + from Standard.Base import all + main = + 42 + """); + var projDir = temporaryFolder.newFolder().toPath(); + ProjectUtils.createProject("Proj", Set.of(mainMod), projDir); + var mainModFile = projDir.resolve("src").resolve("Main.enso"); + assertThat(mainModFile.toFile().exists(), is(true)); + try (var ctx = + ContextUtils.defaultContextBuilder() + .option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString()) + .build()) { + var mainSrc = Source.newBuilder(LanguageInfo.ID, mainModFile.toFile()).build(); + // First eval the source so that everything is compiled. + ctx.eval(mainSrc); + var polyCtx = new PolyglotContext(ctx); + var mod = polyCtx.getTopScope().getModule("Standard.Base.Meta.Enso_Project"); + var assocType = mod.getAssociatedType(); + var ensoProjMethod = mod.getMethod(assocType, "enso_project").get(); + var projDescr = ensoProjMethod.execute(ScalaConversions.seq(List.of(assocType))); + assertThat(projDescr.hasMembers(), is(true)); + assertThat(projDescr.getMetaObject().getMetaSimpleName(), is("Project_Description")); + assertThat(projDescr.hasMember("name"), is(true)); + assertThat(projDescr.invokeMember("name").asString(), is("Proj")); + } + } +} diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/semantic/EnsoProjectTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/semantic/EnsoProjectTest.scala deleted file mode 100644 index 3e153dd6b723..000000000000 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/semantic/EnsoProjectTest.scala +++ /dev/null @@ -1,35 +0,0 @@ -package org.enso.interpreter.test.semantic - -import org.enso.interpreter.test.{InterpreterContext, InterpreterTest} - -class EnsoProjectTest extends InterpreterTest { - - override def subject: String = "Enso_Project.enso_project" - - override def specify(implicit - interpreterContext: InterpreterContext - ): Unit = { - "enso_project should be in micro-distribution" in { - val code = - """ - |from Standard.Base.Meta import enso_project - | - |main = enso_project.name - |""".stripMargin - eval( - code - ).toString shouldEqual "(Error: Module_Not_In_Package_Error.Error)" - } - - "enso_project for Standard.Base" in { - val code = - """ - |import Standard.Base - |from Standard.Base.Meta import Project_Description - | - |main = (Project_Description.new Standard.Base).name - |""".stripMargin - eval(code) shouldEqual "Base" - } - } -} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/EnsoProjectNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/EnsoProjectNode.java index ba4bfd18ee74..69084e337a72 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/EnsoProjectNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/EnsoProjectNode.java @@ -3,16 +3,12 @@ import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; -import com.oracle.truffle.api.Truffle; import com.oracle.truffle.api.TruffleFile; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.nodes.Node; import java.lang.ref.WeakReference; -import java.util.Objects; -import java.util.Optional; import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.node.EnsoRootNode; import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.data.EnsoFile; import org.enso.interpreter.runtime.data.Type; @@ -25,7 +21,8 @@ @BuiltinMethod( type = "Project_Description", name = "enso_project_builtin", - description = "Returns the project description of the project given as the argument") + description = + "Returns the project description of the project given as the argument, or the main project") public abstract class EnsoProjectNode extends Node { public static EnsoProjectNode build() { @@ -35,6 +32,10 @@ public static EnsoProjectNode build() { /** A weak reference to the context in which this node was last executed. */ @CompilationFinal private WeakReference previousCtxRef = new WeakReference<>(null); + /** + * The project descriptor is cached, as it is safe to assume that it will not change during the + * runtime. + */ private Object cachedProjectDescr; /** @@ -43,9 +44,8 @@ public static EnsoProjectNode build() { public abstract Object execute(Object module); /** - * Fetches the second stack frame from Truffle runtime (note that the first stack frame is the - * call of {@code enso_project}) - the caller of {@code enso_project}, and finds in which package - * the caller is located. + * Returns the project description of the main project, i.e., the project specified as the root to + * the engine. * * @param nothing Nothing, or interop null. */ @@ -56,50 +56,14 @@ public Object getCurrentProjectDescr(Object nothing) { if (previousCtx == null || cachedProjectDescr == null || previousCtx != ctx) { CompilerDirectives.transferToInterpreter(); previousCtxRef = new WeakReference<>(ctx); - // Find the caller of `enso_project`, i.e., of this node, and find in which package - // it is located. The first frame is skipped, because it is always - // `Enso_Project.enso_project`, - // i.e., the first frame is always call of this specialization. - Optional> pkgOpt = - Truffle.getRuntime() - .iterateFrames( - frame -> { - var callNode = frame.getCallNode(); - assert callNode != null - : "Should skip the first frame, therefore, callNode should not be null"; - var callRootNode = callNode.getRootNode(); - assert callRootNode != null - : "Should be called only from Enso code, and thus, should always have a" - + " root node"; - if (callRootNode instanceof EnsoRootNode ensoRootNode) { - var pkg = ensoRootNode.getModuleScope().getModule().getPackage(); - // Don't return null, as that would signal to Truffle that we want to - // continue the iteration. - if (pkg != null) { - return Optional.of(pkg); - } else { - return Optional.empty(); - } - } else { - CompilerDirectives.transferToInterpreter(); - throw EnsoContext.get(this) - .raiseAssertionPanic( - this, - "Should not reach here: callRootNode = " - + callRootNode - + ". Probably not called from Enso?", - null); - } - }, - // The first frame is always Enso_Project.enso_project - 1); - if (pkgOpt.isPresent()) { - cachedProjectDescr = createProjectDescriptionAtom(ctx, pkgOpt.get()); + var mainPkg = getMainProjectFromCtx(ctx); + if (mainPkg != null) { + cachedProjectDescr = createProjectDescriptionAtom(ctx, mainPkg); } else { cachedProjectDescr = notInModuleError(ctx); } } - Objects.requireNonNull(cachedProjectDescr); + assert cachedProjectDescr != null; return cachedProjectDescr; } @@ -124,6 +88,14 @@ public Object getOtherProjectDescr( } } + private static Package getMainProjectFromCtx(EnsoContext ctx) { + var mainPkgOpt = ctx.getPackageRepository().getMainProjectPackage(); + if (mainPkgOpt.isDefined()) { + return mainPkgOpt.get(); + } + return null; + } + private static Atom createProjectDescriptionAtom(EnsoContext ctx, Package pkg) { var rootPath = new EnsoFile(pkg.root().normalize()); var cfg = ctx.asGuestValue(pkg.getConfig()); diff --git a/test/Base_Tests/src/Semantic/Meta_Location_Spec.enso b/test/Base_Tests/src/Semantic/Meta_Location_Spec.enso index ff3d8a877834..6b1c5227e524 100644 --- a/test/Base_Tests/src/Semantic/Meta_Location_Spec.enso +++ b/test/Base_Tests/src/Semantic/Meta_Location_Spec.enso @@ -3,6 +3,8 @@ import Standard.Base from Standard.Test import all +polyglot java import org.enso.base_test_helpers.CallbackHelper + type My_Type Value foo bar baz @@ -14,7 +16,7 @@ add_specs suite_builder = suite_builder.group "Meta-Value Inspection" group_buil group_builder.specify "should allow to get the source location of a frame" pending=location_pending <| src = Meta.get_source_location 0 - loc = "Meta_Location_Spec.enso:16:15-40" + loc = "Meta_Location_Spec.enso:18:15-40" src.take (Last loc.length) . should_equal loc group_builder.specify "should allow to get qualified type names of values" <| @@ -22,7 +24,7 @@ add_specs suite_builder = suite_builder.group "Meta-Value Inspection" group_buil y = My_Type.Value 1 2 3 Meta.get_qualified_type_name x . should_equal "Standard.Base.Data.Numbers.Integer" Meta.get_simple_type_name x . should_equal "Integer" - Meta.get_qualified_type_name y . should_equal "enso_dev.Base_Tests.Semantic.Meta_Location_Spec.My_Type" + Meta.get_qualified_type_name y . should_end_with "Meta_Location_Spec.My_Type" Meta.get_simple_type_name y . should_equal "My_Type" group_builder.specify "should allow access to package names" <| @@ -32,6 +34,20 @@ add_specs suite_builder = suite_builder.group "Meta-Value Inspection" group_buil group_builder.specify "should allow to fetch enso project description from a module" <| (Project_Description.new Standard.Base.Data.Vector).name.should_equal "Base" + group_builder.specify "enso_project can be called from polyglot code" <| + proj_name = js_proj_name (_ -> enso_project.name) + proj_name . should_equal "Base_Tests" + + group_builder.specify "enso_project can be called from Java code" <| + callback _ = enso_project.name + res = CallbackHelper.runCallbackInt callback 42 + res . should_equal "Base_Tests" + + +foreign js js_proj_name proj_name_fn = """ + return proj_name_fn(42); + + main filter=Nothing = suite = Test.build suite_builder-> add_specs suite_builder