Skip to content

Commit

Permalink
Builtins expose Enso methods (#11687)
Browse files Browse the repository at this point in the history
* Add BuiltinsExposeMethodsTest

* typo in docs

* Test iteartes all the builtins and checks that all methods are invocable member

* Add TypesExposeConstructorsTest

* Naive implementation of BuiltinObject base class.

* Ref extends BuiltinObject

* Builtin type anot processor ensures that a class must extend BuiltinObject

* Enrich BuiltinsExposeMethodsTest

* All builtin types extend BuiltinObject

* Text is BuiltinObject

* EnsoBigInteger is BuiltinObject

* BuiltinObject does no asserts in constructor

* ArrayProxy is BuiltinObject

* Test skips host values and Nothing

* fmt

* Remove outdated test.

No longer true what this test tested

* EqualsComplexNode: Timezone and duration are not treated as object with members

* Fix DebuggingEnsoTest - Date in js is date time, not date

* Fix DateTest - ensoDate now has members

* Add interop.readMember test to BuiltinsExposeMethodsTest

* VectorSortTest is executed in context

* Add tests that invoke builtin methods on particular builtin types

* member methods on BuiltinObject are behind TruffleBoundary

* Cache builtin type in BuiltinObject.

This fixes native image build of engine-runner.

* Reuse context in DebuggingEnsoTest.

This fixes the AssertionError in invalid sharing layer.

* Reuse hardcoded builtinName constants from annotation

* Move BuiltinObject to package org.enso.interpreter.runtime.builtin

* Fix FQN of BuiltinObject in the annotation processor

* Make exported messages on BuiltinObject final

* Update generated class names in Builtins.

Fixes compilation after 8acb04a

* Exported messages in BuiltinObject are not behind TruffleBoundary

* [WIP] Add BuiltinsJavaInteropTest

* Storage.vectorizedOrFallbackBinaryMap accepts Value as parameter

* Builtin types expose methods, not BuiltinObject

* BuiltinObject has no members

* Remove test Text.is_empty

Text is a builtin type with Builtins module scope, which does not have `is_empty` method.

* Fix tests - invocation is done via static methods

* Type.InvokeMember uses InvokeFunctionNode instead of UnresolvedSymbol

* fmt

* Type.InvokeMember prepends receiver argument

* Test invocation of File.path

* Test invocation of Vector.to_text

* Method fetching is done behind truffle boundary

* Reassigning @CompilationFinal field should deoptimize

* Fixes after merge

* Add TruffleBoundary

* Revert DateTest

* Fix PrivateConstructor test

* Methods on Type are internal members

* fmt

* Add TruffleBoundary

* Revert Storage to develop

* Remove FIXME comment

* Simplify usages of Ref.get and Pth.root

---------

Co-authored-by: Jaroslav Tulach <jaroslav.tulach@enso.org>
  • Loading branch information
Akirathan and JaroslavTulach authored Jan 13, 2025
1 parent 3543bd9 commit c2f0925
Show file tree
Hide file tree
Showing 11 changed files with 496 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,6 @@ type Project_Description
namespace : Text
namespace self = self.ns

## PRIVATE
Returns the path of the project root.
root_path : Text
root_path self = self.root.path

## PRIVATE
enso_project_builtin module = @Builtin_Method "Project_Description.enso_project_builtin"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@

import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import org.enso.common.MethodNames;
import org.enso.test.utils.ContextUtils;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;
import org.junit.AfterClass;
import org.junit.BeforeClass;
Expand All @@ -19,31 +17,18 @@
public class RefTest {
private static Context ctx;
private static EnsoContext ensoCtx;
private static Value newRef;
private static Value createRef;
private static Value getRef;
private static Value refType;

@BeforeClass
public static void initCtx() throws Exception {
ctx = ContextUtils.createDefaultContext();
ensoCtx = ContextUtils.leakContext(ctx);
var code =
"""
import Standard.Base.Runtime.Ref.Ref
new_ref obj =
Ref.new obj
create_ref obj allow_gc =
Ref.new obj allow_gc
get_ref ref = ref.get
""";
var src = Source.newBuilder("enso", code, "gc.enso").build();
var gcEnso = ctx.eval(src);
newRef = gcEnso.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "new_ref");
createRef = gcEnso.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "create_ref");
getRef = gcEnso.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "get_ref");
refType =
ContextUtils.evalModule(
ctx, """
import Standard.Base.Runtime.Ref.Ref
main = Ref
""");
}

@AfterClass
Expand All @@ -52,28 +37,34 @@ public static void closeCtx() throws Exception {
ctx = null;
}

private static Value getRef(Value ref) {
return refType.invokeMember("get", ref);
}

private static Value newRef(Object object) {
return refType.invokeMember("new", object);
}

@Test
public void regularReference() throws Exception {
var obj = new Object();
var ref = newRef.execute(obj);
var ref = newRef(obj);

assertFalse("Value returned", ref.isNull());
assertEquals("Standard.Base.Runtime.Ref.Ref", ref.getMetaObject().getMetaQualifiedName());

var weakRef = new WeakReference<>(obj);
obj = null;

assertEquals("We get the object", weakRef.get(), getRef.execute(ref).asHostObject());
assertEquals("We get the object", weakRef.get(), getRef(ref).asHostObject());

assertGC("Weak wasn't released", false, weakRef);
assertFalse("Value was not GCed", getRef.execute(ref).isNull());
assertEquals("We get the object", weakRef.get(), getRef.execute(ref).asHostObject());
assertFalse("Value was not GCed", getRef(ref).isNull());
assertEquals("We get the object", weakRef.get(), getRef(ref).asHostObject());

// ensoCtx.getReferencesManager().releaseAll();
assertEquals(
"releaseAll has no effect on regular reference",
weakRef.get(),
getRef.execute(ref).asHostObject());
"releaseAll has no effect on regular reference", weakRef.get(), getRef(ref).asHostObject());
}

private static void assertGC(String msg, boolean expectGC, Reference<?> ref) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,23 +42,23 @@
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.io.IOAccess;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;

public class DebuggingEnsoTest {
private Context context;
private Engine engine;
private Debugger debugger;
private final ByteArrayOutputStream out = new ByteArrayOutputStream();
private static Context context;
private static Engine engine;
private static Debugger debugger;
private static final ByteArrayOutputStream out = new ByteArrayOutputStream();

@Before
public void initContext() {
out.reset();
@BeforeClass
public static void initContext() {
engine =
Engine.newBuilder()
.allowExperimentalOptions(true)
Expand All @@ -85,12 +85,18 @@ public void initContext() {
Assert.assertNotNull("Enso found: " + langs, langs.get("enso"));
}

@After
public void disposeContext() throws IOException {
@AfterClass
public static void disposeContext() throws IOException {
context.close();
context = null;
engine.close();
engine = null;
debugger = null;
}

@Before
public void resetOut() {
out.reset();
}

/** Only print warnings from the compiler if a test fails. */
Expand Down Expand Up @@ -307,10 +313,10 @@ public void hostValueIsTreatedAsItsEnsoCounterpart() {
foo _ =
d_enso = Date.new 2024 12 15
d_js = js_date
d_java = Date.parse "2024-12-15"
dt_enso = Date_Time.now
dt_java = Date_Time.parse "2020-05-06 04:30:20" "yyyy-MM-dd HH:mm:ss"
dt_js = js_date
str_enso = "Hello_World"
str_js = js_str
str_java = String.new "Hello_World"
Expand All @@ -335,13 +341,13 @@ public void hostValueIsTreatedAsItsEnsoCounterpart() {

DebugValue ensoDate = scope.getDeclaredValue("d_enso");
DebugValue javaDate = scope.getDeclaredValue("d_java");
DebugValue jsDate = scope.getDeclaredValue("d_js");
assertSameProperties(ensoDate.getProperties(), javaDate.getProperties());
assertSameProperties(ensoDate.getProperties(), jsDate.getProperties());

DebugValue ensoDateTime = scope.getDeclaredValue("dt_enso");
DebugValue javaDateTime = scope.getDeclaredValue("dt_java");
DebugValue jsDateTime = scope.getDeclaredValue("dt_js");
assertSameProperties(ensoDateTime.getProperties(), javaDateTime.getProperties());
assertSameProperties(ensoDateTime.getProperties(), jsDateTime.getProperties());

DebugValue ensoString = scope.getDeclaredValue("str_enso");
DebugValue javaString = scope.getDeclaredValue("str_java");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,23 @@ public static void disposeCtx() {

@Theory
public void testSortHandlesAllValues(Value value1, Value value2) {
Assume.assumeFalse(isNan(value1) || isNan(value2));
Value res = sortFunc.execute(value1, value2);
assertTrue(res.hasArrayElements());
assertEquals(2, res.getArraySize());
List<Value> resArray = readPolyglotArray(res);
// check that value1 is there unchanged on some index, and the same for value2
assertTrue(
"Sorted vector should contain the first value at any index",
invokeEquals(value1, resArray.get(0)) || invokeEquals(value1, resArray.get(1)));
assertTrue(
"Sorted vector should contain the second value at any index",
invokeEquals(value2, resArray.get(0)) || invokeEquals(value2, resArray.get(1)));
ContextUtils.executeInContext(
context,
() -> {
Assume.assumeFalse(isNan(value1) || isNan(value2));
Value res = sortFunc.execute(value1, value2);
assertTrue(res.hasArrayElements());
assertEquals(2, res.getArraySize());
List<Value> resArray = readPolyglotArray(res);
// check that value1 is there unchanged on some index, and the same for value2
assertTrue(
"Sorted vector should contain the first value at any index",
invokeEquals(value1, resArray.get(0)) || invokeEquals(value1, resArray.get(1)));
assertTrue(
"Sorted vector should contain the second value at any index",
invokeEquals(value2, resArray.get(0)) || invokeEquals(value2, resArray.get(1)));
return null;
});
}

private boolean isNan(Value value) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package org.enso.interpreter.test.builtins;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;

import java.util.ArrayList;
import java.util.List;
import org.enso.interpreter.runtime.data.Type;
import org.enso.interpreter.runtime.library.dispatch.TypeOfNode;
import org.enso.interpreter.test.ValuesGenerator;
import org.enso.interpreter.test.ValuesGenerator.Language;
import org.enso.test.utils.ContextUtils;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;
import org.junit.AfterClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

/**
* Gathers all the builtin objects from {@link ValuesGenerator}. From their types, gathers all their
* methods via their {@link org.enso.interpreter.runtime.scope.ModuleScope definition scope} and
* checks that {@link Value#canInvokeMember(String)} returns true.
*/
@RunWith(Parameterized.class)
public class BuiltinTypesExposeMethodsTest {
private static Context ctx;

private final Value type;

public BuiltinTypesExposeMethodsTest(Value type) {
this.type = type;
}

private static Context ctx() {
if (ctx == null) {
ctx = ContextUtils.createDefaultContext();
}
return ctx;
}

@Parameters(name = "{index}: {0}")
public static Iterable<Value> generateBuiltinObjects() {
var valuesGenerator = ValuesGenerator.create(ctx(), Language.ENSO);
var builtinTypes = new ArrayList<Value>();
ContextUtils.executeInContext(
ctx(),
() -> {
valuesGenerator.allTypes().stream()
.filter(
val -> {
var asType = getType(val);
return !shouldSkipType(asType);
})
.forEach(builtinTypes::add);
return null;
});
return builtinTypes;
}

private static Type getType(Value object) {
var unwrapped = ContextUtils.unwrapValue(ctx(), object);
return TypeOfNode.getUncached().findTypeOrNull(unwrapped);
}

@AfterClass
public static void disposeCtx() {
if (ctx != null) {
ctx.close();
ctx = null;
}
}

@Test
public void builtinExposeMethods() {
ContextUtils.executeInContext(
ctx(),
() -> {
assertThat(type, is(notNullValue()));
var typeDefScope = getType(type).getDefinitionScope();
var methodsDefinedInScope = typeDefScope.getMethodsForType(getType(type));
if (methodsDefinedInScope != null) {
for (var methodInScope : methodsDefinedInScope) {
var methodName = methodInScope.getName();
if (methodName.contains(".")) {
var items = methodName.split("\\.");
methodName = items[items.length - 1];
}
assertThat(
"Builtin type " + type + " should have members", type.hasMembers(), is(true));
assertThat(
"Member " + methodName + " should be present",
type.hasMember(methodName),
is(true));
assertThat(
"Member " + methodName + " should be invocable",
type.canInvokeMember(methodName),
is(true));
}
}
return null;
});
}

private static boolean shouldSkipType(Type type) {
if (type == null) {
return true;
}
if (!type.isBuiltin()) {
return true;
}
var builtins = ContextUtils.leakContext(ctx()).getBuiltins();
var typesToSkip =
List.of(
builtins.function(), builtins.dataflowError(), builtins.warning(), builtins.nothing());
var shouldBeSkipped = typesToSkip.stream().anyMatch(toSkip -> toSkip == type);
return shouldBeSkipped;
}
}
Loading

0 comments on commit c2f0925

Please sign in to comment.