Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Fix Meta.enso_project #10192

Merged
merged 11 commits into from
Jun 11, 2024
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
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
20 changes: 18 additions & 2 deletions test/Base_Tests/src/Semantic/Meta_Location_Spec.enso
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -14,15 +16,15 @@ 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" <|
x = 42
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"
Copy link
Member Author

Choose a reason for hiding this comment

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

Unrelated change. It ensures that one can run this test as standalone with enso --run test/Base_Tests/src/Semantic/Meta_Location_Spec.enso

Copy link
Member

Choose a reason for hiding this comment

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

Won't the tests below break if run without --in-project anyway?

Copy link
Member Author

Choose a reason for hiding this comment

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

There is no need to specify --in-project anymore since #8775

Copy link
Member

Choose a reason for hiding this comment

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

So why was the standalone run ever failing? It should always return enso_dev.Base_Tests.Semantic.Meta_Location_Spec.My_Type as the qualified name, no?

If not, what does it return instead??

Copy link
Member Author

Choose a reason for hiding this comment

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

If not, what does it return instead??

It incorrectly returns Meta_Location_Spec.My_Type.

Meta.get_qualified_type_name seems broken. Tracked in #10228. In that issue, there is a task to revert this change once that is fixed.

Copy link
Member

Choose a reason for hiding this comment

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

Ok, great

Meta.get_simple_type_name y . should_equal "My_Type"

group_builder.specify "should allow access to package names" <|
Expand All @@ -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
Expand Down
Loading