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

Show only fields from current atom constructor in the debugger #11217

Merged
merged 27 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ff97a48
Enable ignored tests
Akirathan Sep 30, 2024
c47b5f2
Atom members do not include field getters for other constructors
Akirathan Oct 6, 2024
f076972
FieldGetter node's name does not include type
Akirathan Oct 6, 2024
2f58cc1
Remove unused imports
Akirathan Oct 6, 2024
a7389a6
MethodRootNode.name is only method name without type
Akirathan Oct 6, 2024
73b1586
Field getter nodes has a base node
Akirathan Oct 6, 2024
9b55586
atom fields and methods are readable
Akirathan Oct 6, 2024
5244129
Test that atom fields and methods are readable
Akirathan Oct 6, 2024
70c9e78
Merge branch 'develop' into wip/akirathan/10675-debugger-atom-fields
Akirathan Oct 6, 2024
a4b2c68
fmt
Akirathan Oct 6, 2024
dc228b8
Revert changes to MethodRootNode name
Akirathan Oct 8, 2024
71366f1
Stream methods are hidden behind TruffleBoundary
Akirathan Oct 8, 2024
bce3e9e
Update method names in tests
Akirathan Oct 8, 2024
d979136
fmt
Akirathan Oct 8, 2024
3939783
Revert MethodRootNode qualifiedName
Akirathan Oct 8, 2024
7c1cdf7
Reorder actual and expected parameters in TypeMembersTest
Akirathan Oct 8, 2024
0b6b4e8
Atom member methods have simple name
Akirathan Oct 8, 2024
ed8c984
Instance method include those from type scope
Akirathan Oct 8, 2024
c9992bd
All readable members are invocable
Akirathan Oct 8, 2024
f9467cf
Update AtomInteropTests
Akirathan Oct 8, 2024
3ae5ba1
Update TypeMembersTest.
Akirathan Oct 8, 2024
7af209b
Fix typo in Atom.readMember.
Akirathan Oct 10, 2024
52de4dd
private methods are also readable
Akirathan Oct 11, 2024
97b411f
private members can be read
Akirathan Oct 11, 2024
1e0f1ca
Remove duplicate test
Akirathan Oct 11, 2024
ceca6d1
private fields are accessible via polyglot - update test.
Akirathan Oct 11, 2024
c902ccd
Update docs
Akirathan Oct 14, 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 @@ -2,14 +2,16 @@

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
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.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;

/**
Expand Down Expand Up @@ -90,7 +92,6 @@ public void typeHasAnyAsSuperType() {
assertThat(anyType.getMetaSimpleName(), is("Any"));
}

@Ignore("https://github.com/enso-org/enso/issues/10675")
@Test
public void atomMembersAreConstructorFields_ManyConstructors() {
var myTypeAtom =
Expand All @@ -110,6 +111,108 @@ public void atomMembersAreConstructorFields_ManyConstructors() {
containsInAnyOrder("g1", "g2", "g3"));
}

@Test
public void methodIsAtomMember() {
var myTypeAtom =
ContextUtils.evalModule(
ctx,
"""
type My_Type
Cons a b
method self = 42

main = My_Type.Cons "a" "b"
""");
assertThat("Method is a member of the atom", myTypeAtom.getMemberKeys(), hasItem("method"));
assertThat("method is an invokable member", myTypeAtom.canInvokeMember("method"), is(true));
}

@Test
public void methodIsAtomMember_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("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));
}

@Test
public void constructorIsNotAtomMember() {
var myTypeAtom =
ContextUtils.evalModule(
ctx,
"""
type My_Type
Cons a b
method self = 42

main = My_Type.Cons "a" "b"
""");
assertThat("Cons is not atom member", myTypeAtom.getMemberKeys(), not(hasItem("Cons")));
}

@Test
public void fieldIsNotInvocable() {
var myTypeAtom =
ContextUtils.evalModule(
ctx,
"""
type My_Type
Cons a b

main = My_Type.Cons "a" "b"
""");
var atom = ContextUtils.unwrapValue(ctx, myTypeAtom);
var interop = InteropLibrary.getUncached();
assertThat("Field is not invocable", interop.isMemberInvocable(atom, "a"), is(false));
}

@Test
public void staticMethodIsNotAtomMember() {
var myTypeAtom =
ContextUtils.evalModule(
ctx,
"""
type My_Type
Cons
static_method = 42

main = My_Type.Cons
""");
assertThat(
"Static method is not atom member",
myTypeAtom.getMemberKeys(),
not(hasItem("static_method")));
}

@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));
}

@Test
public void typeMembersAreConstructors() {
var myType =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;

public class DebuggingEnsoTest {
Expand Down Expand Up @@ -483,7 +482,6 @@ public void testAtomFieldsAreReadable() {
}
}

@Ignore("https://github.com/enso-org/enso/issues/10675")
@Test
public void testAtomFieldAreReadable_MultipleConstructors() {
var fooFunc =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,11 @@ private MethodRootNode(
SourceSection section,
Type type,
String methodName) {
super(
language,
localScope,
moduleScope,
body,
section,
shortName(type.getName(), methodName),
null,
false);
super(language, localScope, moduleScope, body, section, methodName, null, false);
this.type = type;
this.methodName = methodName;
}

private static String shortName(String atomName, String methodName) {
return atomName + "." + methodName;
}

/**
* Creates an instance of this node.
*
Expand Down Expand Up @@ -147,17 +135,11 @@ public static MethodRootNode buildOperator(
/**
* Computes the fully qualified name of this method.
*
* <p>The name has a form of [method's module]::[qualified type name]::[method name].
*
* @return the qualified name of this method.
*/
@Override
public String getQualifiedName() {
return getModuleScope().getModule().getName().toString()
+ "::"
+ type.getQualifiedName().toString()
+ "::"
+ methodName;
return type.getQualifiedName() + "." + methodName;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@
import com.oracle.truffle.api.library.ExportMessage;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.profiles.BranchProfile;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import org.enso.interpreter.EnsoLanguage;
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.data.EnsoObject;
import org.enso.interpreter.runtime.data.Type;
Expand Down Expand Up @@ -143,25 +146,63 @@ boolean hasMembers() {
@ExportMessage
@CompilerDirectives.TruffleBoundary
EnsoObject getMembers(boolean includeInternal) {
Set<Function> members =
constructor.getDefinitionScope().getMethodsForType(constructor.getType());
Set<Function> allFuncMembers = new HashSet<>();
if (members != null) {
allFuncMembers.addAll(members);
}
members = constructor.getType().getDefinitionScope().getMethodsForType(constructor.getType());
if (members != null) {
allFuncMembers.addAll(members);
}
Set<Function> allMembers = new HashSet<>();
allMembers.addAll(getInstanceMethods());
allMembers.addAll(getFieldGetters());
String[] publicMembers =
allFuncMembers.stream()
allMembers.stream()
.filter(method -> !method.getSchema().isProjectPrivate())
.map(Function::getName)
.distinct()
.toArray(String[]::new);
return ArrayLikeHelpers.wrapStrings(publicMembers);
}

/** Get all instance methods for this atom's type. */
private Set<Function> getInstanceMethods() {
var allMethods = constructor.getDefinitionScope().getMethodsForType(constructor.getType());
if (allMethods != null) {
return allMethods.stream()
.filter(method -> !isFieldGetter(method))
.collect(Collectors.toUnmodifiableSet());
}
return Set.of();
}

/** Get field getters for this atom's constructor. */
private Set<Function> getFieldGetters() {
var allMethods = constructor.getDefinitionScope().getMethodsForType(constructor.getType());
if (allMethods != null) {
return allMethods.stream()
.filter(method -> isFieldGetter(method) && isGetterForOwnField(method))
.collect(Collectors.toUnmodifiableSet());
}
return Set.of();
}

/**
* Returns true if the given {@code function} is a getter for a field inside this atom
* constructor.
*
* @param function the function to check.
* @return true if the function is a getter for a field inside this atom constructor.
*/
private boolean isGetterForOwnField(Function function) {
if (function.getCallTarget() != null
&& function.getCallTarget().getRootNode() instanceof GetFieldBaseNode getFieldNode) {
var fieldName = getFieldNode.getName();
var thisConsFieldNames =
Arrays.stream(constructor.getFields()).map(ArgumentDefinition::getName).toList();
return thisConsFieldNames.contains(fieldName);
}
return false;
}

private boolean isFieldGetter(Function function) {
return function.getCallTarget() != null
&& function.getCallTarget().getRootNode() instanceof GetFieldBaseNode;
}

protected boolean isMethodProjectPrivate(Type type, String methodName) {
Function method = constructor.getDefinitionScope().getMethodForType(type, methodName);
if (method != null) {
Expand All @@ -175,54 +216,65 @@ protected boolean isMethodProjectPrivate(Type type, String methodName) {
@ExportMessage
@CompilerDirectives.TruffleBoundary
final boolean isMemberInvocable(String member) {
var type = constructor.getType();
Set<String> members = constructor.getDefinitionScope().getMethodNamesForType(type);
if (members != null && members.contains(member)) {
return !isMethodProjectPrivate(type, member);
}
members = type.getDefinitionScope().getMethodNamesForType(type);
if (members != null && members.contains(member)) {
return !isMethodProjectPrivate(type, member);
if (!isMemberReadable(member)) {
return false;
}
return false;
var publicMethodNames =
getInstanceMethods().stream()
.filter(method -> !method.getSchema().isProjectPrivate())
.map(Function::getName)
.collect(Collectors.toUnmodifiableSet());
return publicMethodNames.contains(member);
}

/** A member is readable if it is an atom constructor and if the atom constructor is public. */
/** Readable members are fields of non-project-private constructors and public methods. */
@ExportMessage
@ExplodeLoop
final boolean isMemberReadable(String member) {
if (hasProjectPrivateConstructor()) {
return false;
}
for (int i = 0; i < constructor.getArity(); i++) {
if (member.equals(constructor.getFields()[i].getName())) {
return true;
if (!hasProjectPrivateConstructor()) {
for (int i = 0; i < constructor.getArity(); i++) {
if (member.equals(constructor.getFields()[i].getName())) {
return true;
}
}
}
return false;
var publicMethodNames =
Akirathan marked this conversation as resolved.
Show resolved Hide resolved
getInstanceMethods().stream()
.filter(method -> !method.getSchema().isProjectPrivate())
.map(Function::getName)
.collect(Collectors.toUnmodifiableSet());
return publicMethodNames.contains(member);
}

/**
* Reads a field of the atom.
* Reads a field or a method.
*
* @param member An identifier of a field.
* @return Value of the member.
* @throws UnknownIdentifierException If an unknown field is requested.
* @throws UnsupportedMessageException If the atom constructor is project-private, and thus all
* the fields are project-private.
* @param member An identifier of a field or method.
* @return Value of the field or function.
* @throws UnknownIdentifierException If an unknown field/method is requested.
* @throws UnsupportedMessageException If the requested member is not readable.
*/
@ExportMessage
@ExplodeLoop
final Object readMember(String member, @CachedLibrary(limit = "3") StructsLibrary structs)
throws UnknownIdentifierException, UnsupportedMessageException {
if (hasProjectPrivateConstructor()) {
if (!isMemberReadable(member)) {
throw UnsupportedMessageException.create();
}
for (int i = 0; i < constructor.getArity(); i++) {
if (member.equals(constructor.getFields()[i].getName())) {
return structs.getField(this, i);
if (!hasProjectPrivateConstructor()) {
for (int i = 0; i < constructor.getArity(); i++) {
if (member.equals(constructor.getFields()[i].getName())) {
return structs.getField(this, i);
}
}
}
var matchedMethod =
getInstanceMethods().stream().filter(method -> method.getName().equals(member)).findFirst();
if (matchedMethod.isPresent()) {
assert !matchedMethod.get().getSchema().isProjectPrivate()
: "This is checked in isMemberReadable";
return matchedMethod.get();
}
throw UnknownIdentifierException.create(member);
}

Expand Down
Loading
Loading