Skip to content

Commit

Permalink
Fix Meta.enso_project (#10192)
Browse files Browse the repository at this point in the history
Fixes `Standard.Base.Meta.Enso_Project.enso_project` to return a project descriptor for the *main* project, i.e., the one configured as a *root* for the engine.

# Important Notes
`enso_project` builtin no longer iterates the stack frames to infer the project descriptor. It derives it from the default package repository.
  • Loading branch information
Akirathan authored Jun 11, 2024
1 parent f12e985 commit 5fa29c5
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 11 additions & 9 deletions distribution/lib/Standard/Examples/0.0.0-dev/src/Main.enso
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -54,15 +56,15 @@ 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

## 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
Expand Down Expand Up @@ -169,7 +171,7 @@ uri = URI.parse "http://user:[email protected]/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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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"));
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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() {
Expand All @@ -35,6 +32,10 @@ public static EnsoProjectNode build() {
/** A weak reference to the context in which this node was last executed. */
@CompilationFinal private WeakReference<EnsoContext> 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;

/**
Expand All @@ -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.
*/
Expand All @@ -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<Package<TruffleFile>> 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;
}

Expand All @@ -124,6 +88,14 @@ public Object getOtherProjectDescr(
}
}

private static Package<TruffleFile> getMainProjectFromCtx(EnsoContext ctx) {
var mainPkgOpt = ctx.getPackageRepository().getMainProjectPackage();
if (mainPkgOpt.isDefined()) {
return mainPkgOpt.get();
}
return null;
}

private static Atom createProjectDescriptionAtom(EnsoContext ctx, Package<TruffleFile> pkg) {
var rootPath = new EnsoFile(pkg.root().normalize());
var cfg = ctx.asGuestValue(pkg.getConfig());
Expand Down
Loading

0 comments on commit 5fa29c5

Please sign in to comment.