Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Fix displaying of host values in chrome devtools #11468

Merged
merged 28 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
845e030
Remove leftover typo from tests
Akirathan Nov 1, 2024
b80e0b0
Add tests
Akirathan Nov 1, 2024
1f68c9a
Discard warnings from the compiler in the test.
Akirathan Nov 1, 2024
4289d6c
Remove the test for non-null properties
Akirathan Nov 1, 2024
b354f80
Update test to reflect that atom with builtin type has null properties
Akirathan Nov 1, 2024
ae14326
Try to convert HostObject in DebugLocalScope.readMember
Akirathan Nov 5, 2024
a7c110c
Fix testests
Akirathan Nov 8, 2024
c038f55
Implement getPolyglotConversionType
Akirathan Nov 8, 2024
d2cd44a
DebugLocalScope uses getPolyglotConversionType
Akirathan Nov 8, 2024
08c066f
Add tests
Akirathan Nov 8, 2024
c18ad2c
Remove unused method
Akirathan Nov 8, 2024
2c46980
Testing also js and py foreign languages
Akirathan Nov 11, 2024
9134e52
Polyglot conversion logic is in EnsoLanguage.getLanguageView
Akirathan Nov 11, 2024
19e3798
Use only PolyglotCallType
Akirathan Nov 11, 2024
e283355
long is wrapped in LanguageViewWrapper
Akirathan Nov 11, 2024
b95d4bb
ArrayBuilder exports hasLanguage and getLanguage messages
Akirathan Nov 11, 2024
61e363a
Call Value.asString instead of Value.toString
Akirathan Nov 11, 2024
5fab1da
Test that WithWarnings has meta object
Akirathan Nov 11, 2024
293811e
Implement hasLanguage and getLanguage messages for EnsoBigInt and Text
Akirathan Nov 11, 2024
f4b199b
EnsoLanguage.getLanguageView returns null by default
Akirathan Nov 11, 2024
95b8d09
Merge branch 'develop' into wip/akirathan/7890-java-values-devtools
Akirathan Nov 26, 2024
cdc0ba3
Classes that extend EnsoObject do not export getLanguage and hasLangu…
Akirathan Nov 26, 2024
b743106
Build fixes after merge
Akirathan Nov 26, 2024
368415f
Update WithWarning test - do not use primitive value.
Akirathan Nov 26, 2024
19328ac
Add test for polyglot semantics of Enso numbers
Akirathan Nov 26, 2024
1826dde
Add comments to BigNumberTest
Akirathan Nov 27, 2024
1d01221
A value that fits in double is double, not converted to BigInt
Akirathan Nov 27, 2024
65553f3
EnsoObject is returned from getLanguageView without any conversions
Akirathan Nov 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,7 @@ public void resultOfConversionIsTypeChecked() throws Exception {
ex.getMessage().toLowerCase(),
AllOf.allOf(containsString("type"), containsString("error")));
var typeError = ex.getGuestObject();
assertEquals("Expected type", "First_Type", typeError.getMember("expected").toString());
assertEquals("Expected type", "First_Type", typeError.getMember("expected").asString());
assertEquals("Got wrong value", 42, typeError.getMember("actual").asInt());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@
import com.oracle.truffle.api.debug.SuspendedCallback;
import com.oracle.truffle.api.debug.SuspendedEvent;
import com.oracle.truffle.api.nodes.LanguageInfo;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Paths;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
Expand All @@ -42,23 +45,30 @@
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
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();

@Before
public void initContext() {
out.reset();
engine =
Engine.newBuilder()
.allowExperimentalOptions(true)
.option(
RuntimeOptions.LANGUAGE_HOME_OVERRIDE,
Paths.get("../../distribution/component").toFile().getAbsolutePath())
.option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName())
.logHandler(System.err)
.logHandler(out)
.err(out)
.out(out)
.build();

context =
Expand All @@ -76,13 +86,26 @@ public void initContext() {
}

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

/** Only print warnings from the compiler if a test fails. */
@Rule
public TestWatcher testWatcher =
new TestWatcher() {
@Override
protected void failed(Throwable e, Description description) {
System.err.println("Test failed: " + description.getMethodName());
System.err.println("Error: " + e.getMessage());
System.err.println("Logs from the compiler and the engine: ");
System.err.println(out);
}
};

private static void expectStackFrame(
DebugStackFrame actualFrame, Map<String, String> expectedValues) {
Map<String, String> actualValues = new HashMap<>();
Expand Down Expand Up @@ -246,6 +269,125 @@ public void testHostValues() {
}
}

/**
* Both {@code Date.new 2024 12 15} and {@code Date.parse "2024-12-15"} should be seen by the
* debugger as the exact same objects. Internally, the value from {@code Date.parse} is a host
* value.
*/
@Test
public void hostValueIsTreatedAsItsEnsoCounterpart() {
Value fooFunc =
createEnsoMethod(
"""
from Standard.Base import Date, Date_Time, Dictionary
polyglot java import java.lang.String
polyglot java import java.util.List as JList
polyglot java import java.util.Map as JMap

foreign js js_date = '''
return new Date();

foreign js js_str = '''
return "Hello_World";

foreign js js_list = '''
return [1, 2, 3];

foreign js js_map = '''
let m = new Map();
m.set('A', 1);
m.set('B', 2);
return m;

foreign python py_list = '''
return [1, 2, 3]

foreign python py_dict = '''
return {'A': 1, 'B': 2}

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"
str_enso = "Hello_World"
str_js = js_str
str_java = String.new "Hello_World"
list_enso = [1, 2, 3]
list_js = js_list
list_py = py_list
list_java = JList.of 1 2 3
dict_enso = Dictionary.from_vector [["A", 1], ["B", 2]]
dict_js = js_map
dict_py = py_dict
dict_java = JMap.of "A" 1 "B" 2
end = 42
""",
"foo");

try (DebuggerSession session =
debugger.startSession(
(SuspendedEvent event) -> {
switch (event.getSourceSection().getCharacters().toString().strip()) {
case "end = 42" -> {
DebugScope scope = event.getTopStackFrame().getScope();

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");
assertSameProperties(ensoDateTime.getProperties(), javaDateTime.getProperties());

DebugValue ensoString = scope.getDeclaredValue("str_enso");
DebugValue javaString = scope.getDeclaredValue("str_java");
DebugValue jsString = scope.getDeclaredValue("str_js");
assertSameProperties(ensoString.getProperties(), javaString.getProperties());
assertSameProperties(ensoString.getProperties(), jsString.getProperties());

DebugValue ensoList = scope.getDeclaredValue("list_enso");
DebugValue javaList = scope.getDeclaredValue("list_java");
DebugValue jsList = scope.getDeclaredValue("list_js");
DebugValue pyList = scope.getDeclaredValue("list_py");
assertSameProperties(ensoList.getProperties(), javaList.getProperties());
assertSameProperties(ensoList.getProperties(), jsList.getProperties());
assertSameProperties(ensoList.getProperties(), pyList.getProperties());

DebugValue ensoDict = scope.getDeclaredValue("dict_enso");
DebugValue javaDict = scope.getDeclaredValue("dict_java");
DebugValue jsDict = scope.getDeclaredValue("dict_js");
DebugValue pyDict = scope.getDeclaredValue("dict_py");
assertSameProperties(ensoDict.getProperties(), javaDict.getProperties());
assertSameProperties(ensoDict.getProperties(), jsDict.getProperties());
assertSameProperties(ensoDict.getProperties(), pyDict.getProperties());
}
}
event.getSession().suspendNextExecution();
})) {
session.suspendNextExecution();
fooFunc.execute(0);
}
}

/** Asserts that the given values have same property names. */
private void assertSameProperties(
Collection<DebugValue> expectedProps, Collection<DebugValue> actualProps) {
if (expectedProps == null) {
assertThat(actualProps, anyOf(empty(), nullValue()));
return;
}
assertThat(actualProps.size(), is(expectedProps.size()));
var expectedPropNames =
expectedProps.stream().map(DebugValue::getName).collect(Collectors.toUnmodifiableSet());
var actualPropNames =
actualProps.stream().map(DebugValue::getName).collect(Collectors.toUnmodifiableSet());
assertThat(actualPropNames, is(expectedPropNames));
}

@Test
public void testHostValueAsAtomField() {
Value fooFunc =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
Expand Down Expand Up @@ -90,6 +92,15 @@ public void wrapAndUnwrap() {
fail("One shall not be created WithWarnings without any warnings " + without);
}

@Test
public void withWarningHasMetaObject() {
var warning42 = wrap.execute("warn:1", 42);
assertThat(
"Value (" + warning42 + ") wrapped in warning must have a meta object",
warning42.getMetaObject(),
is(notNullValue()));
}

@Test
public void warningIsAnException() {
var warning42 = wrap.execute("warn:1", 42);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ class TypeSignaturesTest
)
}

"XX resolve imported names" in {
"resolve imported names" in {
val code =
"""
|from project.Util import all
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.ProvidedTags;
import com.oracle.truffle.api.instrumentation.StandardTags;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.nodes.ExecutableNode;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.time.ZoneId;
import java.util.List;
import java.util.Objects;
import org.enso.common.LanguageInfo;
Expand All @@ -31,13 +35,29 @@
import org.enso.interpreter.node.EnsoRootNode;
import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.node.ProgramRootNode;
import org.enso.interpreter.node.callable.resolver.HostMethodCallNode;
import org.enso.interpreter.node.callable.resolver.MethodResolverNode;
import org.enso.interpreter.runtime.EnsoContext;
import org.enso.interpreter.runtime.IrToTruffle;
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
import org.enso.interpreter.runtime.data.EnsoDate;
import org.enso.interpreter.runtime.data.EnsoDateTime;
import org.enso.interpreter.runtime.data.EnsoDuration;
import org.enso.interpreter.runtime.data.EnsoTimeOfDay;
import org.enso.interpreter.runtime.data.EnsoTimeZone;
import org.enso.interpreter.runtime.data.atom.AtomNewInstanceNode;
import org.enso.interpreter.runtime.data.hash.EnsoHashMap;
import org.enso.interpreter.runtime.data.hash.HashMapInsertNode;
import org.enso.interpreter.runtime.data.hash.HashMapToVectorNode;
import org.enso.interpreter.runtime.data.text.Text;
import org.enso.interpreter.runtime.data.vector.ArrayLikeAtNode;
import org.enso.interpreter.runtime.data.vector.ArrayLikeHelpers;
import org.enso.interpreter.runtime.data.vector.ArrayLikeLengthNode;
import org.enso.interpreter.runtime.instrument.NotificationHandler;
import org.enso.interpreter.runtime.instrument.NotificationHandler.Forwarder;
import org.enso.interpreter.runtime.instrument.NotificationHandler.TextMode$;
import org.enso.interpreter.runtime.instrument.Timer;
import org.enso.interpreter.runtime.number.EnsoBigInteger;
import org.enso.interpreter.runtime.state.ExecutionEnvironment;
import org.enso.interpreter.runtime.tag.AvoidIdInstrumentationTag;
import org.enso.interpreter.runtime.tag.IdentifiedTag;
Expand All @@ -51,6 +71,8 @@
import org.graalvm.options.OptionDescriptors;
import org.graalvm.options.OptionKey;
import org.graalvm.options.OptionType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* The root of the Enso implementation.
Expand Down Expand Up @@ -91,6 +113,8 @@ public final class EnsoLanguage extends TruffleLanguage<EnsoContext> {
private final ContextThreadLocal<ExecutionEnvironment[]> executionEnvironment =
locals.createContextThreadLocal((ctx, thread) -> new ExecutionEnvironment[1]);

private static final Logger logger = LoggerFactory.getLogger(EnsoLanguage.class);

public static EnsoLanguage get(Node node) {
return REFERENCE.get(node);
}
Expand Down Expand Up @@ -365,14 +389,79 @@ protected Object getScope(EnsoContext context) {
return context.getTopScope();
}

/** Conversions of primitive values */
/** Conversion of foreign/polyglot values to their Enso builtin counterparts. */
@Override
protected Object getLanguageView(EnsoContext context, Object value) {
public Object getLanguageView(EnsoContext context, Object value) {
if (value instanceof Boolean b) {
var bool = context.getBuiltins().bool();
var cons = b ? bool.getTrue() : bool.getFalse();
return AtomNewInstanceNode.getUncached().newInstance(cons);
}
var interop = InteropLibrary.getUncached();
// We want to know if the `value` can be converted to some Enso builtin type.
// In order to do that, we are trying to infer PolyglotCallType of `value.to` method.
var anyModuleScope = context.getBuiltins().any().getDefinitionScope();
var unresolvedSymbol = UnresolvedSymbol.build("to", anyModuleScope);
var methodResolverNode = MethodResolverNode.getUncached();
var callType =
HostMethodCallNode.getPolyglotCallType(
value, unresolvedSymbol, interop, methodResolverNode);
try {
switch (callType) {
case CONVERT_TO_DATE -> {
var localDate = interop.asDate(value);
return new EnsoDate(localDate);
}
case CONVERT_TO_ARRAY -> {
return ArrayLikeHelpers.asVectorFromArray(value);
}
case CONVERT_TO_BIG_INT -> {
if (interop.fitsInLong(value)) {
return new LanguageViewWrapper(interop.asLong(value));
} else {
return new EnsoBigInteger(interop.asBigInteger(value));
}
}
case CONVERT_TO_DATE_TIME, CONVERT_TO_ZONED_DATE_TIME -> {
var date = interop.asDate(value);
var time = interop.asTime(value);
var zonedDt = date.atTime(time).atZone(ZoneId.systemDefault());
return new EnsoDateTime(zonedDt);
}
case CONVERT_TO_DURATION -> {
return new EnsoDuration(interop.asDuration(value));
}
case CONVERT_TO_HASH_MAP -> {
var ensoHash = EnsoHashMap.empty();
var insertNode = HashMapInsertNode.getUncached();
var vec = HashMapToVectorNode.getUncached().execute(value);
var arrayAtNode = ArrayLikeAtNode.getUncached();
var size = ArrayLikeLengthNode.getUncached().executeLength(vec);
for (long i = 0; i < size; i++) {
var pair = arrayAtNode.executeAt(vec, i);
var key = arrayAtNode.executeAt(pair, 0);
var val = arrayAtNode.executeAt(pair, 1);
ensoHash = insertNode.execute(null, ensoHash, key, val);
}
return ensoHash;
}
case CONVERT_TO_TEXT -> {
return Text.create(interop.asString(value));
}
case CONVERT_TO_TIME_ZONE -> {
return new EnsoTimeZone(interop.asTimeZone(value));
}
case CONVERT_TO_TIME_OF_DAY -> {
var time = interop.asTime(value);
return new EnsoTimeOfDay(time);
}
case NOT_SUPPORTED -> {
return null;
}
}
} catch (UnsupportedMessageException | InvalidArrayIndexException e) {
logger.warn("Unexpected exception", e);
}
return null;
}

Expand Down
Loading
Loading