-
Notifications
You must be signed in to change notification settings - Fork 328
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
Builtins expose Enso methods #11687
Builtins expose Enso methods #11687
Changes from 56 commits
f9a00c6
587904a
bdbe60f
7f0d4bf
59710b9
48005e6
477de23
86c360b
bac459e
a9681e3
ab7a246
4c9ea78
e806f9f
3b98c60
643aa36
5a8ef4e
7cd6469
475819a
7856324
a00ef60
0658ca5
c1d8f41
47125a7
e8ed818
7dfb427
193db45
934afe1
8acb04a
f187211
0cc0b72
6f31ebf
2462523
e441fab
a77691c
b6ec2ae
752ac90
3a3f302
6f40c65
c34408b
b21384a
70a18c6
3969177
a001eab
def1f6b
eb125ba
0b5758c
7f8b62c
50b43cd
8ee8ea2
139952e
3edfc9c
82701e7
29de01a
83f4bcd
bfcaca9
4d6fce1
0c97057
523321c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
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 com.oracle.truffle.api.interop.InteropLibrary; | ||
import org.enso.test.utils.ContextUtils; | ||
import org.graalvm.polyglot.Context; | ||
import org.junit.AfterClass; | ||
import org.junit.BeforeClass; | ||
import org.junit.Test; | ||
|
||
/** | ||
* This test tries to invoke some builtin methods on builtin types via the {@link | ||
* com.oracle.truffle.api.interop.InteropLibrary interop} protocol. | ||
*/ | ||
public class InvokeBuiltinMethodViaInteropTest { | ||
private static Context ctx; | ||
|
||
@BeforeClass | ||
public static void setUp() { | ||
ctx = ContextUtils.createDefaultContext(); | ||
} | ||
|
||
@AfterClass | ||
public static void tearDown() { | ||
ctx.close(); | ||
ctx = null; | ||
} | ||
|
||
@Test | ||
public void invokeGetMethodOnRef() { | ||
var code = | ||
""" | ||
import Standard.Base.Runtime.Ref.Ref | ||
|
||
main = Ref.new 42 | ||
"""; | ||
var ref = ContextUtils.evalModule(ctx, code); | ||
ContextUtils.executeInContext( | ||
ctx, | ||
() -> { | ||
var interop = InteropLibrary.getUncached(); | ||
var refUnwrapped = ContextUtils.unwrapValue(ctx, ref); | ||
assertThat( | ||
"Ref builtin object should not have any members", | ||
interop.hasMembers(refUnwrapped), | ||
is(false)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
assertThat( | ||
"Ref should have a meta-object (Ref type)", | ||
interop.hasMetaObject(refUnwrapped), | ||
is(true)); | ||
var refMeta = interop.getMetaObject(refUnwrapped); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One has to ask for |
||
assertThat( | ||
"Ref meta-object should have a 'get' method", | ||
interop.isMemberInvocable(refMeta, "get"), | ||
is(true)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and there is invocable method |
||
var res = interop.invokeMember(refMeta, "get", new Object[] {refUnwrapped}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The method can be invoked as static method on the |
||
assertThat("Ref.get should return a number", interop.isNumber(res), is(true)); | ||
assertThat("Ref.get should return 42", interop.asInt(res), is(42)); | ||
return null; | ||
}); | ||
} | ||
|
||
@Test | ||
public void invokePathMethodOnFile() { | ||
var code = | ||
""" | ||
from Standard.Base import File | ||
|
||
main = | ||
File.current_directory | ||
"""; | ||
var file = ContextUtils.evalModule(ctx, code); | ||
ContextUtils.executeInContext( | ||
ctx, | ||
() -> { | ||
var fileType = file.getMetaObject(); | ||
assertThat(fileType, is(notNullValue())); | ||
assertThat(fileType.hasMember("path"), is(true)); | ||
var res = fileType.invokeMember("path", new Object[] {file}); | ||
assertThat("path method can be invoked", res, is(notNullValue())); | ||
assertThat("path method returns correct result", res.isString(), is(true)); | ||
return null; | ||
}); | ||
} | ||
|
||
@Test | ||
public void invokeToTextOnVector() { | ||
var code = """ | ||
main = [1,2,3] | ||
"""; | ||
var vec = ContextUtils.evalModule(ctx, code); | ||
ContextUtils.executeInContext( | ||
ctx, | ||
() -> { | ||
var vecType = vec.getMetaObject(); | ||
assertThat(vecType, is(notNullValue())); | ||
assertThat(vecType.hasMember("to_text"), is(true)); | ||
var res = vecType.invokeMember("to_text", new Object[] {vec}); | ||
assertThat("to_text method can be invoked", res, is(notNullValue())); | ||
assertThat("to_text method returns correct result", res.isString(), is(true)); | ||
return null; | ||
}); | ||
} | ||
|
||
/** | ||
* 'Text.reverse' is an extension method defined outside builtins module scope, so it cannot be | ||
* resolved. | ||
*/ | ||
@Test | ||
public void extensionMethodOnBuiltinTypeIsNotResolved() { | ||
var text = ContextUtils.evalModule(ctx, "main = 'Hello'"); | ||
ContextUtils.executeInContext( | ||
ctx, | ||
() -> { | ||
var interop = InteropLibrary.getUncached(); | ||
var textUnwrapped = ContextUtils.unwrapValue(ctx, text); | ||
var textMeta = interop.getMetaObject(textUnwrapped); | ||
assertThat( | ||
"Text type should not be able to resolve 'reverse' method", | ||
interop.isMemberInvocable(textMeta, "reverse"), | ||
is(false)); | ||
return null; | ||
}); | ||
} | ||
|
||
@Test | ||
public void invokePlusOnTextWithParameter() { | ||
var text1 = ContextUtils.evalModule(ctx, "main = 'First'"); | ||
var text2 = ContextUtils.evalModule(ctx, "main = 'Second'"); | ||
ContextUtils.executeInContext( | ||
ctx, | ||
() -> { | ||
var interop = InteropLibrary.getUncached(); | ||
var text1Unwrapped = ContextUtils.unwrapValue(ctx, text1); | ||
var text2Unwrapped = ContextUtils.unwrapValue(ctx, text2); | ||
var textMeta = interop.getMetaObject(text1Unwrapped); | ||
assertThat( | ||
"Text type should have a '+' method", | ||
interop.isMemberInvocable(textMeta, "+"), | ||
is(true)); | ||
var res = interop.invokeMember(textMeta, "+", text1Unwrapped, text2Unwrapped); | ||
assertThat("Text.+ should return a text", interop.isString(res), is(true)); | ||
assertThat( | ||
"Text.+ should return 'FirstSecond'", interop.asString(res), is("FirstSecond")); | ||
return null; | ||
}); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, so this is result of
Ref.new 42
.