From 9967dd3da15025f23bea0123b611981a5ee92ea7 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 7 Sep 2022 13:03:09 +0200 Subject: [PATCH] Check methods of a type are present on its Atoms (#3689) Found a bug when accessing keys via `get(constructor)`. Providing a test and a fix. # Important Notes Marcin, is it correct that the whole set of members of `End` is: `[head, tail, Int, is_empty, IntList]`? What does `Int` and `IntList` do there? Shall test test check for their presence? **Answer**: rename `Int` and `IntList` to lowercase and yes, then the members shall be there. Done in [ca9f42a](https://github.com/enso-org/enso/pull/3689/commits/ca9f42a2b8a59f800c789cfdcf7b0a8317949bdd). --- .../runtime/callable/atom/Atom.java | 4 +- .../interpreter/test/TypeMembersTest.java | 114 ++++++++++++++++++ 2 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 engine/runtime/src/test/java/org/enso/interpreter/test/TypeMembersTest.java diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/Atom.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/Atom.java index ed4660f8c106..b06b25b192ca 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/Atom.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/Atom.java @@ -106,7 +106,7 @@ public boolean hasMembers() { @ExportMessage @CompilerDirectives.TruffleBoundary public Array getMembers(boolean includeInternal) { - Map members = constructor.getDefinitionScope().getMethods().get(constructor); + Map members = constructor.getDefinitionScope().getMethods().get(constructor.getType()); if (members == null) { return new Array(0); } @@ -117,7 +117,7 @@ public Array getMembers(boolean includeInternal) { @ExportMessage @CompilerDirectives.TruffleBoundary public boolean isMemberInvocable(String member) { - Map members = constructor.getDefinitionScope().getMethods().get(constructor); + Map members = constructor.getDefinitionScope().getMethods().get(constructor.getType()); return members != null && members.containsKey(member); } diff --git a/engine/runtime/src/test/java/org/enso/interpreter/test/TypeMembersTest.java b/engine/runtime/src/test/java/org/enso/interpreter/test/TypeMembersTest.java new file mode 100644 index 000000000000..17ae6a30a404 --- /dev/null +++ b/engine/runtime/src/test/java/org/enso/interpreter/test/TypeMembersTest.java @@ -0,0 +1,114 @@ +package org.enso.interpreter.test; + +import java.io.ByteArrayOutputStream; +import java.net.URI; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import org.enso.polyglot.RuntimeOptions; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Engine; +import org.graalvm.polyglot.Language; +import org.graalvm.polyglot.PolyglotException; +import org.graalvm.polyglot.Source; +import org.graalvm.polyglot.Value; +import org.junit.Assert; +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 static org.junit.Assert.fail; +import org.junit.Before; +import org.junit.Test; + +public class TypeMembersTest { + private Context ctx; + + @Before + public void prepareCtx() { + Engine eng = Engine.newBuilder() + .allowExperimentalOptions(true) + .logHandler(new ByteArrayOutputStream()) + .option( + RuntimeOptions.LANGUAGE_HOME_OVERRIDE, + Paths.get("../../distribution/component").toFile().getAbsolutePath() + ).build(); + this.ctx = Context.newBuilder() + .engine(eng) + .allowIO(true) + .allowAllAccess(true) + .build(); + final Map langs = ctx.getEngine().getLanguages(); + assertNotNull("Enso found: " + langs, langs.get("enso")); + } + + + @Test + public void checkAtomMembers() throws Exception { + final URI uri = new URI("memory://how_long.enso"); + final Source src = Source.newBuilder("enso", """ + from Standard.Base.Data.Boolean import True, False + + type IntList + End + Head h t + + is_empty self = case self of + End -> True + _ -> False + + tail self = case self of + Head _ t -> t + _ -> End + + head self = case self of + Head h _ -> h + _ -> -1 + + list1 = Head 7 <| Head 3 <| End + + """, "compare.enso") + .uri(uri) + .buildLiteral(); + + var module = ctx.eval(src); + + + var headAtom = module.invokeMember("eval_expression", "list1"); + var seven = module.invokeMember("eval_expression", "list1.head"); + var three = module.invokeMember("eval_expression", "list1.tail.head"); + var endAtom = module.invokeMember("eval_expression", "list1.tail.tail"); + + assertTrue("seven is number-like: " + seven, seven.fitsInInt()); + assertTrue("three is number-like: " + three, three.fitsInInt()); + assertFalse("list1 is not number-like: " + headAtom, headAtom.fitsInInt()); + assertFalse("list2 is not number-like: " + endAtom, endAtom.fitsInInt()); + + assertEquals("seven check", 7, seven.asInt()); + assertEquals("three check", 3, three.asInt()); + + assertMembers("Keys in list1", false, headAtom, "head", "tail", "is_empty"); + assertMembers("Keys in list2", false, endAtom, "head", "tail", "is_empty"); + assertMembers("Keys in list1", false, headAtom, "h", "t"); + assertMembers("Keys in list2", true, endAtom, "h", "t"); + } + + private static void assertMembers(String msg, boolean invokeFails, Value v, String... keys) { + var realKeys = v.getMemberKeys(); + for (var k : keys) { + assertTrue(msg + " - found " + k + " in " + realKeys, realKeys.contains(k)); + assertTrue(msg + " - has member " + k, v.hasMember(k)); + if (invokeFails) { + try { + v.invokeMember(k); + fail("Invoking " + k + " on " + v + " shall fail"); + } catch (PolyglotException ex) { + assertEquals("No_Such_Field_Error_Data", ex.getMessage()); + } + } else { + assertNotNull(msg + " - can be invoked", v.invokeMember(k)); + } + } + } +}