diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Locale.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Locale.enso index 6ea243628dd5..719b57674ccd 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Locale.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Locale.enso @@ -467,8 +467,25 @@ type Locale predefined_locale_fields : Vector Text predefined_locale_fields = locale_meta = Meta.meta Locale - remove_us = locale_meta.methods + ["Value", "new", "default", "from_language_tag", "from_java", "predefined_locale_fields", "default_widget", "widget_options"] - Meta.Type.Value (Meta.type_of locale_meta.value) . methods . filter (Filter_Condition.Is_In remove_us ..Remove) . sort + methods_to_remove = Vector.build bldr-> + bldr.append "Value" + bldr.append "new" + bldr.append "default" + bldr.append "from_language_tag" + bldr.append "from_java" + bldr.append "java_locale" + bldr.append "predefined_locale_fields" + bldr.append "default_widget" + bldr.append "widget_options" + bldr.append "display_country" + bldr.append "display_variant" + bldr.append "display_language" + bldr.append "variant" + bldr.append "language" + bldr.append "country" + bldr.append "default" + bldr.append_vector_range <| Meta.meta Any . methods + locale_meta . methods . filter (Filter_Condition.Is_In methods_to_remove ..Remove) . sort ## PRIVATE widget_options : Vector Option diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/ExecCompilerTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/ExecCompilerTest.java index 4b41e8dfaa10..3d48866e66bc 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/ExecCompilerTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/ExecCompilerTest.java @@ -151,7 +151,6 @@ public void redefinedArgument() throws Exception { var run = module.invokeMember("eval_expression", "My_Type.Value"); var atom = run.newInstance(1, 2, 3, 4); assertFalse("In spite of error we get an instance back: " + atom, atom.isException()); - assertEquals("Just three keys", 3, atom.getMemberKeys().size()); assertTrue("Check a: " + atom.getMemberKeys(), atom.getMemberKeys().contains("a")); assertTrue("Check b: " + atom.getMemberKeys(), atom.getMemberKeys().contains("b")); assertTrue("Check c: " + atom.getMemberKeys(), atom.getMemberKeys().contains("c")); diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/RefTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/RefTest.java index b66846a07fb4..910e8e1e6303 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/RefTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/RefTest.java @@ -38,11 +38,11 @@ public static void closeCtx() throws Exception { } private static Value getRef(Value ref) { - return refType.invokeMember("get", ref); + return refType.invokeMember("get", refType, ref); } private static Value newRef(Object object) { - return refType.invokeMember("new", object); + return refType.invokeMember("new", refType, object); } @Test diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/DebuggingEnsoTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/DebuggingEnsoTest.java index 374ec7af9eb2..ff5bafac3a8b 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/DebuggingEnsoTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/DebuggingEnsoTest.java @@ -643,18 +643,10 @@ public void testAtomFieldsAreReadable() { assertThat(objValue.isInternal(), is(false)); assertThat(objValue.hasReadSideEffects(), is(false)); - var field1Prop = objValue.getProperty("field_1"); - assertThat(field1Prop.isReadable(), is(true)); - assertThat(field1Prop.isNumber(), is(true)); - assertThat(field1Prop.asInt(), is(1)); - - assertThat(objValue.getProperties().size(), is(2)); - for (var prop : objValue.getProperties()) { - assertThat( - "Property '" + prop.getName() + "' should be readable", - prop.isReadable(), - is(true)); - assertThat(prop.isNumber(), is(true)); + for (var propName : List.of("field_1", "field_2")) { + var fieldProp = objValue.getProperty(propName); + assertThat(fieldProp.isReadable(), is(true)); + assertThat(fieldProp.isNumber(), is(true)); } } } @@ -690,13 +682,10 @@ public void testAtomFieldAreReadable_MultipleConstructors() { assertThat(objValue.isInternal(), is(false)); assertThat(objValue.hasReadSideEffects(), is(false)); - assertThat("Has fields f1 and f2", objValue.getProperties().size(), is(2)); - for (var prop : objValue.getProperties()) { - assertThat( - "Property '" + prop.getName() + "' should be readable", - prop.isReadable(), - is(true)); - assertThat(prop.isNumber(), is(true)); + for (var propName : List.of("f1", "f2")) { + var fieldProp = objValue.getProperty(propName); + assertThat(fieldProp.isReadable(), is(true)); + assertThat(fieldProp.isNumber(), is(true)); } } } diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/MethodResolutionTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/MethodResolutionTest.java new file mode 100644 index 000000000000..afaec982c46b --- /dev/null +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/MethodResolutionTest.java @@ -0,0 +1,155 @@ +package org.enso.interpreter.test; + +import static org.enso.test.utils.ContextUtils.createDefaultContext; +import static org.enso.test.utils.ContextUtils.evalModule; +import static org.enso.test.utils.ContextUtils.executeInContext; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; + +import org.enso.interpreter.Constants.Names; +import org.enso.interpreter.node.callable.resolver.MethodResolverNode; +import org.enso.interpreter.runtime.callable.UnresolvedSymbol; +import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.data.Type; +import org.enso.test.utils.ContextUtils; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Value; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +public final class MethodResolutionTest { + private static MethodResolverNode methodResolverNode; + private static Context ctx; + + @BeforeClass + public static void initCtx() { + ctx = createDefaultContext(); + executeInContext( + ctx, + () -> { + methodResolverNode = MethodResolverNode.getUncached(); + return null; + }); + } + + @AfterClass + public static void disposeCtx() { + ctx.close(); + ctx = null; + } + + @Test + public void resolveStaticMethodFromAny() { + var myTypeVal = + evalModule( + ctx, + """ + from Standard.Base import all + + type My_Type + method self = 42 + + main = My_Type + """); + executeInContext( + ctx, + () -> { + var myType = unwrapType(myTypeVal); + var symbol = UnresolvedSymbol.build("to_display_text", myType.getDefinitionScope()); + var func = methodResolverNode.executeResolution(myType, symbol); + assertThat("to_display_text method is found", func, is(notNullValue())); + assertSingleSelfArgument(func); + return null; + }); + } + + @Test + public void resolveInstanceMethodFromMyType() { + var myTypeVal = + evalModule( + ctx, + """ + type My_Type + method self = 42 + + main = My_Type + """, + "Module", + "main"); + executeInContext( + ctx, + () -> { + var myType = unwrapType(myTypeVal); + var symbol = UnresolvedSymbol.build("method", myType.getDefinitionScope()); + var func = methodResolverNode.executeResolution(myType, symbol); + assertThat("method is found", func, is(notNullValue())); + assertSingleSelfArgument(func); + return null; + }); + } + + @Test + public void resolveStaticMethodFromMyType() { + var myTypeVal = + evalModule( + ctx, + """ + type My_Type + method = 42 + + main = My_Type + """, + "Module", + "main"); + executeInContext( + ctx, + () -> { + var myType = unwrapType(myTypeVal); + var symbol = UnresolvedSymbol.build("method", myType.getDefinitionScope()); + var func = methodResolverNode.executeResolution(myType, symbol); + assertThat("method is found", func, is(notNullValue())); + assertSingleSelfArgument(func); + return null; + }); + } + + @Test + public void resolveExtensionMethodFromMyType() { + var myTypeVal = + evalModule( + ctx, + """ + type My_Type + My_Type.method = 42 + + main = My_Type + """, + "Module", + "main"); + executeInContext( + ctx, + () -> { + var myType = unwrapType(myTypeVal); + var symbol = UnresolvedSymbol.build("method", myType.getDefinitionScope()); + var func = methodResolverNode.executeResolution(myType, symbol); + assertThat("method is found", func, is(notNullValue())); + assertSingleSelfArgument(func); + return null; + }); + } + + private void assertSingleSelfArgument(Function func) { + assertThat("Has single self argument", func.getSchema().getArgumentsCount(), is(1)); + assertThat( + "Has single self argument", + func.getSchema().getArgumentInfos()[0].getName(), + is(Names.SELF_ARGUMENT)); + } + + private Type unwrapType(Value val) { + var unwrapped = ContextUtils.unwrapValue(ctx, val); + return (Type) unwrapped; + } +} diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/interop/AtomInteropTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/interop/AtomInteropTest.java index 90026928a982..f77f62a1f3f0 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/interop/AtomInteropTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/interop/AtomInteropTest.java @@ -3,8 +3,10 @@ import static org.enso.test.utils.ContextUtils.executeInContext; import static org.enso.test.utils.ContextUtils.unwrapValue; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; @@ -13,8 +15,11 @@ import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.interop.InvalidArrayIndexException; import com.oracle.truffle.api.interop.UnsupportedMessageException; +import java.util.ArrayList; +import java.util.List; import org.enso.test.utils.ContextUtils; import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Value; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -37,6 +42,25 @@ public void disposeCtx() { ctx = null; } + @Test + public void atomMemberNames_AreNotQualified() { + var myTypeAtom = + ContextUtils.evalModule( + ctx, + """ + import Standard.Base.Any.Any + + type My_Type + Cons field_1 field_2 + + main = + My_Type.Cons 1 2 + """); + assertThat(myTypeAtom.hasMembers(), is(true)); + var memberNames = myTypeAtom.getMemberKeys(); + assertThat("Member names are not qualified", memberNames, hasItem(not(containsString(".")))); + } + @Test public void atomMembersAreConstructorFields_SingleConstructor() { var myTypeAtom = @@ -51,13 +75,11 @@ public void atomMembersAreConstructorFields_SingleConstructor() { """); assertThat(myTypeAtom.hasMembers(), is(true)); var memberNames = myTypeAtom.getMemberKeys(); - assertThat("Has two fields", memberNames.size(), is(2)); - assertThat( - "Member names are not qualified", memberNames, containsInAnyOrder("field_1", "field_2")); - for (var memberName : memberNames) { - var member = myTypeAtom.getMember(memberName); - assertThat("Member " + memberName + " should be readable", member, is(notNullValue())); - assertThat("All fields are numbers", member.isNumber(), is(true)); + assertThat("Has more than two fields", memberNames.size(), is(greaterThan(2))); + for (var consName : List.of("field_1", "field_2")) { + var member = myTypeAtom.getMember(consName); + assertThat("Member " + consName + " should be readable", member, is(notNullValue())); + assertThat("Cons field is number", member.isNumber(), is(true)); } } @@ -113,7 +135,7 @@ public void atomMembersAreConstructorFields_ManyConstructors() { assertThat( "Member names correspond to constructor field names for a single constructor", myTypeAtom.getMemberKeys(), - containsInAnyOrder("g1", "g2", "g3")); + allOf(hasItem("g1"), hasItem("g2"), hasItem("g3"), not(hasItem("h1")))); } @Test @@ -144,12 +166,17 @@ public void methodIsAtomMember_InteropLibrary() { main = My_Type.Cons "a" "b" """); - var atom = ContextUtils.unwrapValue(ctx, myTypeAtom); - var interop = InteropLibrary.getUncached(); - assertThat("Atom has members", interop.hasMembers(atom), is(true)); - assertThat("Method is readable", interop.isMemberReadable(atom, "method"), is(true)); - assertThat("Method is invocable", interop.isMemberInvocable(atom, "method"), is(true)); - assertThat("Field is readable", interop.isMemberReadable(atom, "a"), is(true)); + ContextUtils.executeInContext( + ctx, + () -> { + var atom = ContextUtils.unwrapValue(ctx, myTypeAtom); + var interop = InteropLibrary.getUncached(); + assertThat("Atom has members", interop.hasMembers(atom), is(true)); + assertThat("Method is readable", interop.isMemberReadable(atom, "method"), is(true)); + assertThat("Method is invocable", interop.isMemberInvocable(atom, "method"), is(true)); + assertThat("Field is readable", interop.isMemberReadable(atom, "a"), is(true)); + return null; + }); } @Test @@ -227,6 +254,67 @@ public void allMethodsAreInternalMembers() { is(true)); } + /** + * Builtin methods from Any are present even if the Standard.Base.Any module is not imported. + * + * @throws Exception + */ + @Test + public void internalMembersIncludeMethodsFromAny_WithoutImport() throws Exception { + var myTypeAtom = + ContextUtils.evalModule( + ctx, + """ + type My_Type + Cons a + + main = My_Type.Cons "a" + """); + ContextUtils.executeInContext( + ctx, + () -> { + var atom = ContextUtils.unwrapValue(ctx, myTypeAtom); + var memberNames = getAllMemberNames(atom); + var anyBuiltinMethods = ContextUtils.builtinMethodsFromAny(ctx); + for (var method : anyBuiltinMethods) { + assertThat( + "Builtin method (from Any) is a member of atom", memberNames, hasItem(method)); + } + return null; + }); + } + + /** + * When Standard.Base.Any module is imported, all the methods (both builtin and non-builtin) from + * Any should be present as internal members of the atom. + */ + @Test + public void internalMembersIncludeMethodsFromAny_WithImport() throws Exception { + ContextUtils.executeInContext( + ctx, + () -> { + var myTypeAtom = + ContextUtils.evalModule( + ctx, + """ + from Standard.Base.Any import all + + type My_Type + Cons a + + main = My_Type.Cons "a" + """); + var atom = ContextUtils.unwrapValue(ctx, myTypeAtom); + var memberNames = getAllMemberNames(atom); + var anyMethods = ContextUtils.allMethodsFromAny(ctx); + for (var method : anyMethods) { + assertThat( + "Non-builtin method (from Any) is a member of atom", memberNames, hasItem(method)); + } + return null; + }); + } + @Test public void allMembersAreReadableAndInvocable() throws UnsupportedMessageException, InvalidArrayIndexException { @@ -241,20 +329,25 @@ public void allMembersAreReadableAndInvocable() main = My_Type.Cons "a" """); - var atom = ContextUtils.unwrapValue(ctx, myTypeAtom); - var interop = InteropLibrary.getUncached(); - var members = interop.getMembers(atom, true); - for (long i = 0; i < interop.getArraySize(members); i++) { - var memberName = interop.asString(interop.readArrayElement(members, i)); - assertThat( - "Member " + memberName + " should be readable", - interop.isMemberReadable(atom, memberName), - is(true)); - assertThat( - "Member " + memberName + " should be invocable", - interop.isMemberInvocable(atom, memberName), - is(true)); - } + ContextUtils.executeInContext( + ctx, + () -> { + var atom = ContextUtils.unwrapValue(ctx, myTypeAtom); + var interop = InteropLibrary.getUncached(); + var members = interop.getMembers(atom, true); + for (long i = 0; i < interop.getArraySize(members); i++) { + var memberName = interop.asString(interop.readArrayElement(members, i)); + assertThat( + "Member " + memberName + " should be readable", + interop.isMemberReadable(atom, memberName), + is(true)); + assertThat( + "Member " + memberName + " should be invocable", + interop.isMemberInvocable(atom, memberName), + is(true)); + } + return null; + }); } @Test @@ -337,19 +430,24 @@ public void staticMethodIsNotAtomMember() { @Test public void constructorIsNotAtomMember_InteropLibrary() { - var myTypeAtom = - ContextUtils.evalModule( - ctx, - """ - type My_Type - Cons a b - method self = 42 - - main = My_Type.Cons "a" "b" - """); - var atom = ContextUtils.unwrapValue(ctx, myTypeAtom); - var interop = InteropLibrary.getUncached(); - assertThat("Cons is not atom member", interop.isMemberExisting(atom, "Cons"), is(false)); + ContextUtils.executeInContext( + ctx, + () -> { + var myTypeAtom = + ContextUtils.evalModule( + ctx, + """ + type My_Type + Cons a b + method self = 42 + + main = My_Type.Cons "a" "b" + """); + var atom = ContextUtils.unwrapValue(ctx, myTypeAtom); + var interop = InteropLibrary.getUncached(); + assertThat("Cons is not atom member", interop.isMemberExisting(atom, "Cons"), is(false)); + return null; + }); } @Test @@ -400,4 +498,20 @@ public void invokeLazyField_DoesNotCauseStackOverflow() { return null; }); } + + /** + * @param obj {@link ContextUtils#unwrapValue(Context, Value) unwrapped} {@link Value value}. + */ + private List getAllMemberNames(Object obj) + throws UnsupportedMessageException, InvalidArrayIndexException { + var interop = InteropLibrary.getUncached(); + var allMembers = interop.getMembers(obj, true); + var memberNames = new ArrayList(); + for (var i = 0; i < interop.getArraySize(allMembers); i++) { + var member = interop.readArrayElement(allMembers, i); + var memberName = interop.asString(member); + memberNames.add(memberName); + } + return memberNames; + } } diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/interop/MethodInvocationOnTypeConsistencyTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/interop/MethodInvocationOnTypeConsistencyTest.java new file mode 100644 index 000000000000..670adc724c1e --- /dev/null +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/interop/MethodInvocationOnTypeConsistencyTest.java @@ -0,0 +1,202 @@ +package org.enso.interpreter.test.interop; + +import static org.enso.test.utils.ContextUtils.createDefaultContext; +import static org.enso.test.utils.ContextUtils.executeInContext; +import static org.enso.test.utils.ContextUtils.unwrapValue; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; + +import com.oracle.truffle.api.interop.ArityException; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.UnknownIdentifierException; +import com.oracle.truffle.api.interop.UnsupportedMessageException; +import com.oracle.truffle.api.interop.UnsupportedTypeException; +import java.util.List; +import java.util.function.BiConsumer; +import org.enso.common.LanguageInfo; +import org.enso.common.MethodNames.Module; +import org.enso.interpreter.runtime.data.Type; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Value; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +/** + * TODO[pm]: Ignored - https://github.com/enso-org/enso/pull/12099#issuecomment-2654281345 + * + *

Tests consistency between invocation of methods on types via pure enso, and invocation of + * methods on {@link org.enso.interpreter.runtime.data.Type} via {@link + * com.oracle.truffle.api.interop.InteropLibrary#invokeMember(Object, String, Object...) interop + * protocol}. + */ +@RunWith(Parameterized.class) +public final class MethodInvocationOnTypeConsistencyTest { + private static final String SRC = + """ + from Standard.Base.Any import all + + type My_Type + Cons data + method self = self.data + 2 + + any_type = Any + my_type = My_Type + my_type_atom = My_Type.Cons 1 + """; + + private static Context ctx; + private static Value module; + private static Type anyType; + private static Type myType; + private static Object myTypeAtom; + + private final TestArgs testArgs; + + @BeforeClass + public static void initCtx() { + if (ctx != null) { + return; + } + ctx = createDefaultContext(); + module = ctx.eval(LanguageInfo.ID, SRC); + var anyTypeVal = module.invokeMember(Module.EVAL_EXPRESSION, "any_type"); + var myTypeVal = module.invokeMember(Module.EVAL_EXPRESSION, "my_type"); + var myTypeAtomVal = module.invokeMember(Module.EVAL_EXPRESSION, "my_type_atom"); + executeInContext( + ctx, + () -> { + anyType = (Type) unwrapValue(ctx, anyTypeVal); + myType = (Type) unwrapValue(ctx, myTypeVal); + myTypeAtom = unwrapValue(ctx, myTypeAtomVal); + return null; + }); + } + + @AfterClass + public static void closeCtx() { + if (ctx != null) { + ctx.close(); + ctx = null; + } + } + + @Parameters(name = "{index}: expression = {0}") + public static List testArgs() { + if (ctx == null) { + initCtx(); + } + return List.of( + new TestArgs( + new EnsoInvokeArgs("Any.to_display_text My_Type"), + new InteropInvokeArgs(anyType, "to_display_text", List.of(myType)), + (res, msg) -> { + assertThat(msg, res.asString(), is("My_Type")); + }), + new TestArgs( + new EnsoInvokeArgs("Any.to_text my_type_atom"), + new InteropInvokeArgs(anyType, "to_text", List.of(myTypeAtom)), + (res, msg) -> { + assertThat(msg, res.asString(), containsString("Cons 1")); + }), + new TestArgs( + new EnsoInvokeArgs("My_Type.to_display_text"), + new InteropInvokeArgs(myType, "to_display_text", List.of()), + (res, msg) -> { + assertThat(msg, res.asString(), is("My_Type")); + }), + new TestArgs( + new EnsoInvokeArgs("My_Type.method my_type_atom"), + new InteropInvokeArgs(myType, "method", List.of(myTypeAtom)), + (res, msg) -> { + assertThat(msg, res.asInt(), is(3)); + }), + new TestArgs( + new EnsoInvokeArgs("Any.has_warnings my_type_atom"), + new InteropInvokeArgs(anyType, "has_warnings", List.of(myTypeAtom)), + (res, msg) -> { + assertThat(msg, res.asBoolean(), is(false)); + }), + new TestArgs( + new EnsoInvokeArgs("My_Type.has_warnings my_type_atom"), + new InteropInvokeArgs(myType, "has_warnings", List.of(myTypeAtom)), + (res, msg) -> { + assertThat(msg, res.asBoolean(), is(false)); + })); + } + + public MethodInvocationOnTypeConsistencyTest(TestArgs testArgs) { + this.testArgs = testArgs; + } + + @Test + @Ignore("https://github.com/enso-org/enso/pull/12099#issuecomment-2654281345") + public void methodInvocationViaInterop_IsConsistentWithPureEnso() { + executeInContext( + ctx, + () -> { + assertConsistentInvoke( + testArgs.ensoInvokeArgs, testArgs.interopInvokeArgs, testArgs.resultChecker); + return null; + }); + } + + private void assertConsistentInvoke( + EnsoInvokeArgs ensoInvokeArgs, + InteropInvokeArgs interopInvokeArgs, + BiConsumer resultChecker) { + var resFromEnso = invokeViaEnso(ensoInvokeArgs); + resultChecker.accept(resFromEnso, "Result from pure Enso invocation check:"); + var resFromInterop = invokeViaInterop(interopInvokeArgs); + resultChecker.accept(ctx.asValue(resFromInterop), "Result from Interop invocation check:"); + } + + private Object invokeViaInterop(InteropInvokeArgs args) { + var interop = InteropLibrary.getUncached(); + assertThat( + "Member " + args.method + " is invocable on " + args.receiverType, + interop.isMemberInvocable(args.receiverType, args.method), + is(true)); + var argsArr = args.args.toArray(Object[]::new); + try { + return interop.invokeMember(args.receiverType, args.method, argsArr); + } catch (UnsupportedMessageException + | ArityException + | UnknownIdentifierException + | UnsupportedTypeException e) { + throw new AssertionError("Unexpected exception: " + e.getMessage(), e); + } + } + + private Value invokeViaEnso(EnsoInvokeArgs args) { + return module.invokeMember(Module.EVAL_EXPRESSION, args.expr); + } + + /** + * @param expr Expression to invoke in the module + */ + private record EnsoInvokeArgs(String expr) {} + + private record InteropInvokeArgs(Type receiverType, String method, List args) { + private InteropInvokeArgs { + var someArgIsValue = args.stream().anyMatch(arg -> arg instanceof Value); + assertThat("All arguments must be passed unwrapped", someArgIsValue, is(false)); + } + } + + public record TestArgs( + EnsoInvokeArgs ensoInvokeArgs, + InteropInvokeArgs interopInvokeArgs, + BiConsumer resultChecker) { + + @Override + public String toString() { + return ensoInvokeArgs.expr; + } + } +} diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/interop/TypeMembersTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/interop/TypeMembersTest.java index c68d96fbe895..19039d569bac 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/interop/TypeMembersTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/interop/TypeMembersTest.java @@ -1,11 +1,21 @@ package org.enso.interpreter.test.interop; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.InvalidArrayIndexException; +import com.oracle.truffle.api.interop.UnsupportedMessageException; import java.net.URI; +import java.util.ArrayList; +import java.util.List; import java.util.Set; import org.enso.test.utils.ContextUtils; import org.graalvm.polyglot.Context; @@ -102,7 +112,115 @@ public void ensureNonBuiltinMembersArePresent() throws Exception { var module = ctx.eval(src); var compileError = module.invokeMember("eval_expression", "v"); - assertEquals("all members", Set.of("to_display_text", "message"), compileError.getMemberKeys()); + assertEquals( + "all members", + Set.of("to_display_text", "message", "to_text", "==", "catch_primitive", "pretty"), + compileError.getMemberKeys()); + } + + @Test + public void builtinMethodIsPresent() { + var refType = + ContextUtils.evalModule( + ctx, """ + import Standard.Base.Runtime.Ref.Ref + main = Ref + """); + ContextUtils.executeInContext( + ctx, + () -> { + assertThat(refType.hasMember("new"), is(true)); + return null; + }); + } + + @Test + public void inheritedMembersFromAnyAreIncluded() { + var type = + ContextUtils.evalModule( + ctx, + """ + from Standard.Base.Any import all + + type My_Type + method self = 42 + + main = My_Type + """); + ContextUtils.executeInContext( + ctx, + () -> { + var typeUnwrapped = ContextUtils.unwrapValue(ctx, type); + var memberNames = getAllMemberNames(typeUnwrapped); + var anyMethods = ContextUtils.allMethodsFromAny(ctx); + for (var anyMethod : anyMethods) { + assertThat("Has method from Any", memberNames, hasItem(containsString(anyMethod))); + } + return null; + }); + } + + @Test + public void typeMemberNames_AreNotQualified() { + var type = + ContextUtils.evalModule( + ctx, + """ + from Standard.Base.Any import all + + type My_Type + method self = 42 + + main = My_Type + """); + ContextUtils.executeInContext( + ctx, + () -> { + var typeUnwrapped = ContextUtils.unwrapValue(ctx, type); + var memberNames = getAllMemberNames(typeUnwrapped); + assertThat( + "Member names are not qualified", memberNames, not(hasItem(containsString(".")))); + return null; + }); + } + + @Test + public void canInvokeInheritedStaticMethod_OnType() { + var myType = + ContextUtils.evalModule( + ctx, + """ + from Standard.Base.Any import all + + type My_Type + method self = 42 + + main = My_Type + """); + ContextUtils.executeInContext( + ctx, + () -> { + var displayTextRes = myType.invokeMember("to_display_text"); + assertThat("Has correct result type", displayTextRes.isString(), is(true)); + assertThat("Has correct result value", displayTextRes.asString(), is("My_Type")); + return null; + }); + } + + /** + * @param obj {@link ContextUtils#unwrapValue(Context, Value) unwrapped} {@link Value value}. + */ + private List getAllMemberNames(Object obj) + throws UnsupportedMessageException, InvalidArrayIndexException { + var interop = InteropLibrary.getUncached(); + var allMembers = interop.getMembers(obj, true); + var memberNames = new ArrayList(); + for (var i = 0; i < interop.getArraySize(allMembers); i++) { + var member = interop.readArrayElement(allMembers, i); + var memberName = interop.asString(member); + memberNames.add(memberName); + } + return memberNames; } private static void assertMembers(String msg, Value v, String... keys) { diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/invoke/ArgumentMappingTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/invoke/ArgumentMappingTest.java new file mode 100644 index 000000000000..72bda5807ddf --- /dev/null +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/invoke/ArgumentMappingTest.java @@ -0,0 +1,78 @@ +package org.enso.interpreter.test.invoke; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; + +import org.enso.interpreter.node.expression.constant.ConstantObjectNode; +import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; +import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; +import org.enso.interpreter.runtime.callable.function.FunctionSchema; +import org.junit.Test; + +/** Test suite for {@link org.enso.interpreter.runtime.callable.argument.CallArgumentInfo}. */ +public final class ArgumentMappingTest { + @Test + public void oneArgument() { + var funcSchema = + FunctionSchema.newBuilder() + .argumentDefinitions( + new ArgumentDefinition( + 0, "self", null, null, ArgumentDefinition.ExecutionMode.EXECUTE)) + .build(); + CallArgumentInfo[] callArgInfos = new CallArgumentInfo[] {new CallArgumentInfo("self")}; + var argMapping = CallArgumentInfo.ArgumentMappingBuilder.generate(funcSchema, callArgInfos); + var postAppSchema = argMapping.getPostApplicationSchema(); + assertThat( + "Post application schema should be fully applied", + postAppSchema.isFullyApplied(), + is(true)); + assertThat( + "Pre-application schema and post-application schema should be the same", + postAppSchema.getArgumentInfos()[0], + is(funcSchema.getArgumentInfos()[0])); + } + + @Test + public void oneDefaultArgument_OnSecondPosition() { + var funcSchema = + FunctionSchema.newBuilder() + .argumentDefinitions( + new ArgumentDefinition( + 0, "x", null, null, ArgumentDefinition.ExecutionMode.EXECUTE), + new ArgumentDefinition( + 1, + "y", + null, + ConstantObjectNode.build(42L), + ArgumentDefinition.ExecutionMode.EXECUTE)) + .build(); + CallArgumentInfo[] callArgInfos = new CallArgumentInfo[] {new CallArgumentInfo("x")}; + var argMapping = CallArgumentInfo.ArgumentMappingBuilder.generate(funcSchema, callArgInfos); + var postAppSchema = argMapping.getPostApplicationSchema(); + assertThat(argMapping.getArgumentShouldExecute(), is(new boolean[] {true})); + assertThat(postAppSchema.cloneHasPreApplied(), is(new boolean[] {true, false})); + } + + @Test + public void oneDefaultArgument_OnFirstPositionPosition() { + var funcSchema = + FunctionSchema.newBuilder() + .argumentDefinitions( + new ArgumentDefinition( + 0, + "x", + null, + ConstantObjectNode.build(42L), + ArgumentDefinition.ExecutionMode.EXECUTE), + new ArgumentDefinition( + 1, "y", null, null, ArgumentDefinition.ExecutionMode.EXECUTE)) + .build(); + CallArgumentInfo[] callArgInfos = new CallArgumentInfo[] {new CallArgumentInfo("y")}; + var argMapping = CallArgumentInfo.ArgumentMappingBuilder.generate(funcSchema, callArgInfos); + var postAppSchema = argMapping.getPostApplicationSchema(); + assertThat(argMapping, is(notNullValue())); + assertThat(argMapping.getArgumentShouldExecute(), is(new boolean[] {true})); + assertThat(postAppSchema.cloneHasPreApplied(), is(new boolean[] {false, true})); + } +} diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/meta/MetaTypeMethodsTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/meta/MetaTypeMethodsTest.java new file mode 100644 index 000000000000..15a890c59563 --- /dev/null +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/meta/MetaTypeMethodsTest.java @@ -0,0 +1,165 @@ +package org.enso.interpreter.test.meta; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.isIn; + +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.InvalidArrayIndexException; +import com.oracle.truffle.api.interop.UnsupportedMessageException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.enso.interpreter.node.expression.builtin.meta.GetTypeMethodsNode; +import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.test.ValuesGenerator; +import org.enso.interpreter.test.ValuesGenerator.Language; +import org.enso.test.utils.ContextUtils; +import org.enso.test.utils.TestRootNode; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Value; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Tests consistency between {@code Meta.get_type_methods} and {@link + * com.oracle.truffle.api.interop.InteropLibrary#getMembers(Object) members interop message} for + * {@link org.enso.interpreter.runtime.data.Type}nd {@link + * org.enso.interpreter.runtime.data.atom.Atom}. + */ +public class MetaTypeMethodsTest { + private static Context ctx; + private static GetTypeMethodsNode getTypeMethodsNode; + private static TestRootNode testRootNode; + private static ValuesGenerator valuesGenerator; + + @BeforeClass + public static void initCtx() { + ctx = ContextUtils.createDefaultContext(); + valuesGenerator = ValuesGenerator.create(ctx, Language.ENSO); + ContextUtils.executeInContext( + ctx, + () -> { + getTypeMethodsNode = GetTypeMethodsNode.build(); + testRootNode = new TestRootNode(); + testRootNode.insertChildren(getTypeMethodsNode); + return null; + }); + } + + @AfterClass + public static void disposeCtx() { + ctx.close(); + ctx = null; + } + + @Test + public void testConsistencyBetweenMeta_And_TypeInterop() throws Exception { + var allTypes = valuesGenerator.allTypes(); + ContextUtils.executeInContext( + ctx, + () -> { + for (var type : allTypes) { + var typeMethods = metaGetTypeMethods(type); + var interopMembers = interopGetMembers(type); + var errMsg = + """ + Methods returned from `Meta.get_type_methods` and `InteropLibrary.getMembers` must be the same. + Type: %s + Return value of `Meta.get_type_methods`: %s + Return value of `InteropLibrary.getMembers`: %s + """ + .formatted(type, typeMethods, interopMembers); + assertThat( + errMsg, typeMethods, containsInAnyOrder(interopMembers.toArray(String[]::new))); + } + return null; + }); + } + + @Test + public void inheritedMembersFromNumberAreIncluded() { + var integerType = + ContextUtils.evalModule( + ctx, + """ + import Standard.Base.Any.Any + import Standard.Base.Data.Numbers.Number + import Standard.Base.Data.Numbers.Integer + + main = Integer + """); + ContextUtils.executeInContext( + ctx, + () -> { + var anyMethods = methodsFrom("Standard.Base.Any", "Any"); + var numberMethods = methodsFrom("Standard.Base.Data.Numbers", "Number"); + var integerMethods = methodsFrom("Standard.Base.Data.Numbers", "Integer"); + var actualMethodNames = + metaGetTypeMethods(integerType).stream().collect(Collectors.toUnmodifiableSet()); + assertSubset("Has method from Any", anyMethods, actualMethodNames); + assertSubset("Has method from Number", numberMethods, actualMethodNames); + assertSubset("Has method from Integer", integerMethods, actualMethodNames); + return null; + }); + } + + private Set methodsFrom(String moduleName, String typeName) { + var ensoCtx = ContextUtils.leakContext(ctx); + var mod = ensoCtx.findModule(moduleName).get(); + var tp = mod.getScope().getType(typeName, true); + var methods = mod.getScope().getMethodsForType(tp); + return methods.stream() + .map(Function::getName) + .map(MetaTypeMethodsTest::unqualified) + .collect(Collectors.toUnmodifiableSet()); + } + + private static void assertSubset(String msg, Set subset, Set superSet) { + var errMsg = + """ + %s: Expected subset to be a subset of superSet. + Subset: %s + SuperSet: %s + """ + .formatted(msg, subset, superSet); + for (var item : subset) { + assertThat(errMsg, item, isIn(superSet)); + } + } + + private static String unqualified(String name) { + if (name.contains(".")) { + return name.substring(name.lastIndexOf('.') + 1); + } + return name; + } + + private List metaGetTypeMethods(Value type) + throws UnsupportedMessageException, InvalidArrayIndexException { + var unwrapped = ContextUtils.unwrapValue(ctx, type); + var interop = InteropLibrary.getUncached(); + var typeMethodNames = new ArrayList(); + var typeMethods = getTypeMethodsNode.execute(unwrapped); + for (var i = 0; i < interop.getArraySize(typeMethods); i++) { + var typeMethod = interop.readArrayElement(typeMethods, i); + typeMethodNames.add(interop.asString(typeMethod)); + } + return typeMethodNames; + } + + private List interopGetMembers(Value type) + throws UnsupportedMessageException, InvalidArrayIndexException { + var unwrapped = ContextUtils.unwrapValue(ctx, type); + var interop = InteropLibrary.getUncached(); + var memberNames = new ArrayList(); + var members = interop.getMembers(unwrapped, true); + for (var i = 0; i < interop.getArraySize(members); i++) { + var member = interop.readArrayElement(members, i); + memberNames.add(interop.asString(member)); + } + return memberNames; + } +} diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeErrorsTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeErrorsTest.scala index 4870803a4bde..22adf6b0fb71 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeErrorsTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeErrorsTest.scala @@ -179,7 +179,7 @@ class RuntimeErrorsTest contextId, xId, Api.ExpressionUpdate.Payload.Panic( - "Compile_Error", + "Compile_Error.Error", Seq(xId) ), builtin = true @@ -188,7 +188,7 @@ class RuntimeErrorsTest contextId, yId, Api.ExpressionUpdate.Payload.Panic( - "Compile_Error", + "Compile_Error.Error", Seq(xId) ), builtin = true @@ -197,7 +197,7 @@ class RuntimeErrorsTest contextId, mainResId, Api.ExpressionUpdate.Payload.Panic( - "Compile_Error", + "Compile_Error.Error", Seq(xId) ), builtin = true @@ -373,7 +373,7 @@ class RuntimeErrorsTest Api.MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main", "foo") ), Api.ExpressionUpdate.Payload.Panic( - "Compile_Error", + "Compile_Error.Error", Seq(mainBodyId) ), builtin = true @@ -2499,7 +2499,7 @@ class RuntimeErrorsTest contextId, xId, Api.ExpressionUpdate.Payload.Panic( - "Compile_Error", + "Compile_Error.Error", Seq(xId) ), builtin = true @@ -2508,7 +2508,7 @@ class RuntimeErrorsTest contextId, mainResId, Api.ExpressionUpdate.Payload.Panic( - "Compile_Error", + "Compile_Error.Error", Seq(xId) ), builtin = true diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeTypesTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeTypesTest.scala index a415e64bac41..236cc2bea61a 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeTypesTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeTypesTest.scala @@ -370,13 +370,13 @@ class RuntimeTypesTest TestMessages.panic( contextId, id_x, - Api.ExpressionUpdate.Payload.Panic("Compile_Error", List(id_x)), + Api.ExpressionUpdate.Payload.Panic("Compile_Error.Error", List(id_x)), builtin = true ), TestMessages.panic( contextId, id_y, - Api.ExpressionUpdate.Payload.Panic("Compile_Error", List(id_x)), + Api.ExpressionUpdate.Payload.Panic("Compile_Error.Error", List(id_x)), builtin = true ), context.executionComplete(contextId) @@ -505,7 +505,7 @@ class RuntimeTypesTest TestMessages.panic( contextId, id_x, - Api.ExpressionUpdate.Payload.Panic("Compile_Error", List(id_x)), + Api.ExpressionUpdate.Payload.Panic("Compile_Error.Error", List(id_x)), builtin = true ), context.executionComplete(contextId) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java index 8f80726e7e80..8019f539b150 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java @@ -161,7 +161,7 @@ Object doFunctionalDispatchCachedSymbol( return invokeFunctionNode.execute(function, frame, state, arguments); } - Function resolveFunction( + public static Function resolveFunction( UnresolvedSymbol symbol, Type selfTpe, MethodResolverNode methodResolverNode) { Function function = methodResolverNode.executeResolution(selfTpe, symbol); if (function == null) { @@ -173,7 +173,7 @@ Function resolveFunction( // If both Any and the type where `function` is declared, define `symbol` // and the method is invoked statically, i.e. type of self is the eigentype, // then we want to disambiguate method resolution by always resolved to the one in Any. - var ctx = EnsoContext.get(this); + var ctx = EnsoContext.get(methodResolverNode); if (where instanceof MethodRootNode node && typeCanOverride(node, ctx)) { Type any = ctx.getBuiltins().any(); Function anyFun = symbol.getScope().lookupMethodDefinition(any, symbol.getName()); @@ -185,7 +185,33 @@ Function resolveFunction( return function; } - private boolean typeCanOverride(MethodRootNode node, EnsoContext ctx) { + /** + * Returns true if synthetic Self argument should be prepended to the arguments passed to the + * function. + * + *

Static method calls on Any are resolved to `Any.type.method`. Such methods take one + * additional self argument (with Any.type) as opposed to static method calls resolved on any + * other types. + * + * @param resolvedFunctionSchema Schema of the function that was resolved to be invoked. + * @param argumentCount Count of the arguments passed to the function. + * @return True if synthetic self argument should be prepended to the arguments. + */ + public static boolean shouldPrependSyntheticSelfArg( + FunctionSchema resolvedFunctionSchema, int argumentCount) { + var resolvedFuncArgCount = resolvedFunctionSchema.getArgumentsCount(); + long argsWithDefaultValCount = 0; + for (var argDef : resolvedFunctionSchema.getArgumentInfos()) { + if (argDef.hasDefaultValue()) { + argsWithDefaultValCount++; + } + } + boolean shouldPrependSyntheticSelfArg = + resolvedFuncArgCount - argsWithDefaultValCount == argumentCount + 1; + return shouldPrependSyntheticSelfArg; + } + + private static boolean typeCanOverride(MethodRootNode node, EnsoContext ctx) { Type methodOwnerType = node.getType(); Builtins builtins = ctx.getBuiltins(); Type any = builtins.any(); @@ -220,20 +246,9 @@ Object doFunctionalDispatchUncachedSymbol( } throw methodNotFound(symbol, self); } - var resolvedFuncArgCount = function.getSchema().getArgumentsCount(); CallArgumentInfo[] invokeFuncSchema = invokeFunctionNode.getSchema(); - long argsWithDefaultValCount = 0; - for (var argDef : function.getSchema().getArgumentInfos()) { - if (argDef.hasDefaultValue()) { - argsWithDefaultValCount++; - } - } - // Static method calls on Any are resolved to `Any.type.method`. Such methods take one - // additional - // self argument (with Any.type) as opposed to static method calls resolved on any other - // types. This case is handled in the following block. - boolean shouldPrependSyntheticSelfArg = - resolvedFuncArgCount - argsWithDefaultValCount == arguments.length + 1; + var shouldPrependSyntheticSelfArg = + shouldPrependSyntheticSelfArg(function.getSchema(), arguments.length); if (isAnyEigenType(selfTpe) && shouldPrependSyntheticSelfArg) { // function is a static method on Any, so the first two arguments in `invokeFuncSchema` // represent self arguments. @@ -257,7 +272,7 @@ Object doFunctionalDispatchUncachedSymbol( if (invokeAnyStaticFunctionNode == null) { CompilerDirectives.transferToInterpreterAndInvalidate(); - assert resolvedFuncArgCount >= 2 + assert function.getSchema().getArgumentsCount() >= 2 : "Resolved function should be on Any.type, therefore, should have at least two self" + " arguments"; // Prepend self=Any to the arguments and shift diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/GetTypeMethodsNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/GetTypeMethodsNode.java index 7915d284bed9..185ca89ac3c7 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/GetTypeMethodsNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/GetTypeMethodsNode.java @@ -19,22 +19,18 @@ description = "Gets the method names of a type.", autoRegister = false) public abstract class GetTypeMethodsNode extends Node { - static GetTypeMethodsNode build() { + public static GetTypeMethodsNode build() { return GetTypeMethodsNodeGen.create(); } - abstract EnsoObject execute(Object type); + public abstract EnsoObject execute(Object type); @Specialization @CompilerDirectives.TruffleBoundary EnsoObject allMethods(Type type) { - var methods = type.getDefinitionScope().getMethodNamesForType(type); - if (methods == null) { - return ArrayLikeHelpers.asVectorEmpty(); - } else { - var names = methods.stream().map(Text::create).toArray(Text[]::new); - return ArrayLikeHelpers.asVectorEnsoObjects(names); - } + var methods = type.getMethods(true); + var methodNames = methods.keySet().stream().map(Text::create).toArray(Text[]::new); + return ArrayLikeHelpers.asVectorEnsoObjects(methodNames); } @Fallback diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Type.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Type.java index cc55ffbd3d9e..17c07819cbeb 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Type.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Type.java @@ -23,10 +23,13 @@ import org.enso.interpreter.Constants; import org.enso.interpreter.EnsoLanguage; import org.enso.interpreter.node.ConstantNode; +import org.enso.interpreter.node.callable.InvokeCallableNode; import org.enso.interpreter.node.callable.InvokeCallableNode.ArgumentsExecutionMode; import org.enso.interpreter.node.callable.InvokeCallableNode.DefaultsExecutionMode; -import org.enso.interpreter.node.callable.dispatch.InvokeFunctionNode; +import org.enso.interpreter.node.callable.InvokeMethodNode; +import org.enso.interpreter.node.callable.resolver.MethodResolverNode; import org.enso.interpreter.runtime.EnsoContext; +import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; import org.enso.interpreter.runtime.callable.function.Function; @@ -416,13 +419,19 @@ static Object doCached( String member, Object[] args, @Cached("member") String cachedMember, - @Cached("findMethod(receiver, member)") Function func, - @Cached("buildInvokeFuncNode(func)") InvokeFunctionNode invokeFuncNode) + @Cached MethodResolverNode methodResolverNode, + @Cached("buildSymbol(receiver, member)") UnresolvedSymbol symbol, + @Cached("findMethod(eigenType(receiver), symbol, methodResolverNode)") Function func, + @Cached("buildInvokeCallableNode(func)") InvokeCallableNode invokeCallableNode) throws UnsupportedMessageException, UnsupportedTypeException, ArityException { - var argsWithReceiver = new Object[args.length + 1]; - argsWithReceiver[0] = receiver; - System.arraycopy(args, 0, argsWithReceiver, 1, args.length); - return invokeFuncNode.execute(func, null, null, argsWithReceiver); + Object[] finalArgs = args; + if (InvokeMethodNode.shouldPrependSyntheticSelfArg(func.getSchema(), args.length)) { + var argsWithReceiver = new Object[args.length + 1]; + argsWithReceiver[0] = receiver; + System.arraycopy(args, 0, argsWithReceiver, 1, args.length); + finalArgs = argsWithReceiver; + } + return invokeCallableNode.execute(func, null, null, finalArgs); } @Specialization(replaces = "doCached") @@ -436,19 +445,31 @@ static Object doUncached( UnsupportedTypeException, ArityException, UnknownIdentifierException { - var method = findMethod(receiver, member); + var symbol = buildSymbol(receiver, member); + var methodResolverNode = MethodResolverNode.getUncached(); + var method = findMethod(receiver.getEigentype(), symbol, methodResolverNode); if (method == null) { throw UnknownIdentifierException.create(member); } - var invokeFuncNode = buildInvokeFuncNode(method); - return doCached(receiver, member, args, member, method, invokeFuncNode); + var invokeCallableNode = buildInvokeCallableNode(method); + return doCached( + receiver, member, args, member, methodResolverNode, symbol, method, invokeCallableNode); + } + + static Type eigenType(Type receiver) { + return receiver.getEigentype(); } - static Function findMethod(Type receiver, String name) { - return receiver.methods().get(name); + static UnresolvedSymbol buildSymbol(Type receiver, String member) { + return UnresolvedSymbol.build(member, receiver.getDefinitionScope()); } - static InvokeFunctionNode buildInvokeFuncNode(Function func) { + static Function findMethod( + Type receiver, UnresolvedSymbol symbol, MethodResolverNode methodResolverNode) { + return InvokeMethodNode.resolveFunction(symbol, receiver, methodResolverNode); + } + + static InvokeCallableNode buildInvokeCallableNode(Function func) { assert func != null; var argumentInfos = func.getSchema().getArgumentInfos(); var callArgInfos = new CallArgumentInfo[argumentInfos.length]; @@ -457,7 +478,7 @@ static InvokeFunctionNode buildInvokeFuncNode(Function func) { var callArgInfo = new CallArgumentInfo(argInfo.getName()); callArgInfos[i] = callArgInfo; } - return InvokeFunctionNode.build( + return InvokeCallableNode.build( callArgInfos, DefaultsExecutionMode.EXECUTE, ArgumentsExecutionMode.EXECUTE); } } @@ -519,29 +540,76 @@ private boolean isNothing(Node lib) { private Map methods() { if (methods == null) { CompilerDirectives.transferToInterpreter(); - var allMethods = new HashMap(); - var defScope = definitionScope.asModuleScope(); - var methodsFromThisScope = defScope.getMethodsForType(this); - if (methodsFromThisScope != null) { - methodsFromThisScope.forEach( - func -> { - var simpleName = simpleFuncName(func); - allMethods.put(simpleName, func); - }); + methods = getMethods(true); + } + return methods; + } + + /** + * Returns methods (both instance and static) defined on this type, including the ones inherited + * from super types. Instance methods are defined on this type, static methods are defined on its + * {@link #getEigentype() eigen type}. The methods defined on this type are searched for inside + * the module scope where this type is defined, so if there are any other extension methods + * defined in other modules, they are not included in the result. + * + * @param includeStaticMethods If static methods, defined on eigen type, should be included in the + * result. + * @return All static and instance methods defined on this type, including the ones inherited from + * Any. + */ + @TruffleBoundary + public Map getMethods(boolean includeStaticMethods) { + var ctx = EnsoContext.get(null); + var allMethods = new HashMap(); + for (var type : allTypes(ctx)) { + var methodsOnThisType = type.methodsOnThisType(includeStaticMethods); + for (var entry : methodsOnThisType.entrySet()) { + var name = entry.getKey(); + // If a method with the name is already in `allMethods`, it means that it is an override + // of a method from super type - let's keep the override. + if (!allMethods.containsKey(name)) { + allMethods.put(name, entry.getValue()); + } } - if (eigentype != null) { - var methodsFromEigenScope = eigentype.getDefinitionScope().getMethodsForType(eigentype); - if (methodsFromEigenScope != null) { - methodsFromEigenScope.forEach( - func -> { - var simpleName = simpleFuncName(func); - allMethods.put(simpleName, func); - }); + } + return allMethods; + } + + /** + * Returns methods (both instance and static) defined only on this type. + * + *

As opposed to {@link #getMethods(boolean)}, does not include methods inherited from super + * types. + * + * @param includeStaticMethods If static methods, defined on eigen type, should be included in the + * result. + */ + @TruffleBoundary + private Map methodsOnThisType(boolean includeStaticMethods) { + var allMethods = new HashMap(); + var defScope = definitionScope.asModuleScope(); + var methodsFromThisScope = defScope.getMethodsForType(this); + if (methodsFromThisScope != null) { + methodsFromThisScope.forEach( + func -> { + var simpleName = simpleFuncName(func); + allMethods.put(simpleName, func); + }); + } + if (includeStaticMethods && eigentype != null) { + var methodsFromEigenScope = eigentype.getDefinitionScope().getMethodsForType(eigentype); + if (methodsFromEigenScope != null) { + for (var method : methodsFromEigenScope) { + var simpleName = simpleFuncName(method); + // Don't replace instance methods (with one self argument) with static ones (with two self + // arguments). + if (!allMethods.containsKey(simpleName)) { + allMethods.put(simpleName, method); + } } } - methods = allMethods; } - return methods; + return allMethods; } private static String simpleFuncName(Function func) { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/atom/Atom.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/atom/Atom.java index e23a6d2efc89..c1c38c8cb894 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/atom/Atom.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/atom/Atom.java @@ -198,15 +198,12 @@ EnsoObject getMembers(boolean includeInternal) { private Set getInstanceMethods() { var methodsFromCtorScope = constructor.getDefinitionScope().getMethodsForType(constructor.getType()); - var methodsFromTypeScope = - constructor.getType().getDefinitionScope().getMethodsForType(constructor.getType()); var allMethods = new HashSet(); if (methodsFromCtorScope != null) { allMethods.addAll(methodsFromCtorScope); } - if (methodsFromTypeScope != null) { - allMethods.addAll(methodsFromTypeScope); - } + var methodsFromType = constructor.getType().getMethods(false); + allMethods.addAll(methodsFromType.values()); return allMethods.stream() .filter(method -> !isFieldGetter(method)) .collect(Collectors.toUnmodifiableSet()); diff --git a/lib/java/test-utils/src/main/java/org/enso/test/utils/ContextUtils.java b/lib/java/test-utils/src/main/java/org/enso/test/utils/ContextUtils.java index 43f8ada0d115..7bdbbc45f22c 100644 --- a/lib/java/test-utils/src/main/java/org/enso/test/utils/ContextUtils.java +++ b/lib/java/test-utils/src/main/java/org/enso/test/utils/ContextUtils.java @@ -7,8 +7,10 @@ import java.io.OutputStream; import java.nio.file.Paths; import java.util.Map; +import java.util.Set; import java.util.concurrent.Callable; import java.util.logging.Level; +import java.util.stream.Collectors; import org.enso.common.LanguageInfo; import org.enso.common.MethodNames.Module; import org.enso.common.MethodNames.TopScope; @@ -210,6 +212,49 @@ public static Value getMethodFromModule(Context ctx, String moduleSrc, String me return module.invokeMember(Module.EVAL_EXPRESSION, methodName); } + /** + * Returns set of all the builtin methods from Any. These methods are present even if the module + * was not imported - they are present on the Any builtin type. This is in contrast to {@link + * #allMethodsFromAny(Context)} which requires the {@code Standard.Base.Any} module to be first + * imported. + */ + public static Set builtinMethodsFromAny(Context ctx) { + var ensoCtx = ContextUtils.leakContext(ctx); + // This is a builtin Any type, so only the builtin methods will be included. + var anyBuiltinType = ensoCtx.getBuiltins().any(); + var anyBuiltinMethods = anyBuiltinType.getDefinitionScope().getMethodsForType(anyBuiltinType); + assert anyBuiltinMethods != null; + return anyBuiltinMethods.stream() + .map(m -> unqualifiedName(m.getName())) + .collect(Collectors.toUnmodifiableSet()); + } + + /** + * Returns set of all the methods on the {@code Standard.Base.Any} type. This includes both + * builtin and non-builtin types. For this to work, {@code Standard.Base.Any} module must be + * imported first in the context, otherwise an assertion will fail. + */ + public static Set allMethodsFromAny(Context ctx) { + // Includes, e.g., `Any.to`. + var ensoCtx = ContextUtils.leakContext(ctx); + var anyMod = ensoCtx.findModule("Standard.Base.Any"); + assert anyMod.isPresent() : "Standard.Base.Any module must be imported first"; + var anyModScope = anyMod.get().getScope(); + var anyType = anyModScope.getType("Any", true); + var anyMethods = anyModScope.getMethodsForType(anyType); + assert anyMethods != null; + return anyMethods.stream() + .map(m -> unqualifiedName(m.getName())) + .collect(Collectors.toUnmodifiableSet()); + } + + private static String unqualifiedName(String name) { + if (name.contains(".")) { + return name.substring(name.lastIndexOf('.') + 1); + } + return name; + } + @ExportLibrary(InteropLibrary.class) static final class Unwrapper implements TruffleObject { diff --git a/std-bits/base/src/main/java/org/enso/base/CurrentEnsoProject.java b/std-bits/base/src/main/java/org/enso/base/CurrentEnsoProject.java index 343f29ddbff9..236daa2ab4a4 100644 --- a/std-bits/base/src/main/java/org/enso/base/CurrentEnsoProject.java +++ b/std-bits/base/src/main/java/org/enso/base/CurrentEnsoProject.java @@ -40,7 +40,7 @@ public static CurrentEnsoProject get() { private static Value invokeMember(String member, Value object, Object... args) { var meta = object.getMetaObject(); if (meta.hasMember(member)) { - return meta.invokeMember(member, object, args); + return meta.invokeMember(member, meta, object, args); } else { return null; } diff --git a/test/Base_Tests/src/Data/Array_Spec.enso b/test/Base_Tests/src/Data/Array_Spec.enso index d08ad876e342..2984dbdbece5 100644 --- a/test/Base_Tests/src/Data/Array_Spec.enso +++ b/test/Base_Tests/src/Data/Array_Spec.enso @@ -58,10 +58,12 @@ add_specs suite_builder = make_enso_array [Date.new 2022 1 1] . pretty . should_equal "[Date.new 2022 1 1]" suite_builder.group "Compare functionality with Vector" group_builder-> - group_builder.specify "compare methods" <| - vector_methods = Meta.meta Vector . methods . sort - array_methods = Meta.meta Array . methods . sort - vector_methods . should_equal array_methods + group_builder.specify "Array should have all the methods that Vector has" <| + vector_methods = Hashset.from_vector <| Meta.meta Vector . methods + array_methods = Hashset.from_vector <| Meta.meta Array . methods + diff = array_methods.difference vector_methods + Test.with_clue "The difference should be empty, but was: "+diff.to_text <| + diff.is_empty . should_be_true suite_builder.group "ArrayOverBuffer" group_builder-> location_pending = case Platform.os of diff --git a/test/Base_Tests/src/Semantic/Meta_Spec.enso b/test/Base_Tests/src/Semantic/Meta_Spec.enso index 4a6d3c2cef42..aa1e437ca8e8 100644 --- a/test/Base_Tests/src/Semantic/Meta_Spec.enso +++ b/test/Base_Tests/src/Semantic/Meta_Spec.enso @@ -290,17 +290,25 @@ add_specs suite_builder = _ : Meta.Type -> meta_typ.methods _ -> Test.fail "Should be a Meta.Type: " + meta_typ.to_text - methods.sort . should_equal ['bar', 'baz', 'first_method', 'foo', 'my_method', 'other_method', 'second_method'] + expected_methods = ['bar', 'baz', 'first_method', 'foo', 'my_method', 'other_method', 'second_method'] + expected_methods . should_only_contain_elements_in methods group_builder.specify "static methods of MyType" <| methods = Meta.meta (Meta.type_of My_Type) . methods - methods.sort . should_equal ['Value', 'create', 'factory', 'first_method', 'my_method', 'other_method', 'second_method'] + expected_methods = ['Value', 'create', 'factory', 'first_method', 'my_method', 'other_method', 'second_method'] + expected_methods . should_only_contain_elements_in methods group_builder.specify "methods of Integer" <| - Meta.meta Integer . methods . sort . should_equal ['%', '*', '+', '-', '/', '<', '<=', '>', '>=', '^', 'abs', 'bit_and', 'bit_not', 'bit_or', 'bit_shift', 'bit_shift_l', 'bit_shift_r', 'bit_xor', 'ceil', 'div', 'fits_in_long', 'floor', 'negate', 'round', 'to_decimal', 'to_float', 'truncate'] + expected_methods = Vector.build bldr-> + bldr.append_vector_range <| Meta.meta Any . methods + bldr.append_vector_range <| Meta.meta Number . methods + bldr.append_vector_range <| Meta.meta Integer . methods + bldr.to_vector.distinct + expected_methods . should_only_contain_elements_in (Meta.meta Integer . methods) group_builder.specify "static methods of Integer" <| - Meta.meta (Meta.type_of Integer) . methods . sort . should_equal ['%', '*', '+', '-', '/', '<', '<=', '>', '>=', '^', 'abs', 'bit_and', 'bit_not', 'bit_or', 'bit_shift', 'bit_shift_l', 'bit_shift_r', 'bit_xor', 'ceil', 'div', 'fits_in_long', 'floor', 'negate', 'parse', 'round', 'to_decimal', 'to_float', 'truncate'] + expected_methods = ['%', '*', '+', '-', '/', '<', '<=', '>', '>=', '^', 'abs', 'bit_and', 'bit_not', 'bit_or', 'bit_shift', 'bit_shift_l', 'bit_shift_r', 'bit_xor', 'ceil', 'div', 'fits_in_long', 'floor', 'negate', 'parse', 'round', 'to_decimal', 'to_float', 'truncate'] + expected_methods . should_only_contain_elements_in <| Meta.meta (Meta.type_of Integer) . methods group_builder.specify "methods of Any" <| Meta.meta Any . methods . should_contain "to_text"