Skip to content

Commit 99c839b

Browse files
committed
[GR-42711] Make dir(foreign_object) return both foreign methods and Python methods
PullRequest: graalpython/3664
2 parents ef1b8e7 + 3871155 commit 99c839b

File tree

7 files changed

+66
-12
lines changed

7 files changed

+66
-12
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
This changelog summarizes major changes between GraalVM versions of the Python
44
language runtime. The main focus is on user-observable behavior of the engine.
55

6+
## Version 25.0.0
7+
* `dir(foreign_object)` now returns both foreign methods and Python methods (it used to return only foreign methods).
8+
69
## Version 24.2.0
710
* Updated developer metadata of Maven artifacts.
811
* Added gradle plugin for polyglot embedding of Python packages into Java.

graalpython/com.oracle.graal.python.test/src/tests/test_interop.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,14 @@ def test_java_array(self):
771771

772772
assert il == [1, 2, 3] # unchanged
773773

774+
def test_dir(self):
775+
from java.util import ArrayList
776+
777+
l = ArrayList()
778+
self.assertIn('addAll', dir(l)) # a Java method
779+
self.assertIn('extend', dir(l)) # a Python method
780+
self.assertEqual(sorted(dir(l)), dir(l))
781+
774782
def test_java_list(self):
775783
from java.util import ArrayList
776784

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/PythonLanguage.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ private static boolean mimeTypesComplete(ArrayList<String> mimeJavaStrings) {
312312

313313
public static final TruffleString[] T_DEFAULT_PYTHON_EXTENSIONS = new TruffleString[]{T_PY_EXTENSION, tsLiteral(".pyc")};
314314

315-
private static final TruffleLogger LOGGER = TruffleLogger.getLogger(ID, PythonLanguage.class);
315+
public static final TruffleLogger LOGGER = TruffleLogger.getLogger(ID, PythonLanguage.class);
316316

317317
private static final LanguageReference<PythonLanguage> REFERENCE = LanguageReference.create(PythonLanguage.class);
318318

@@ -671,6 +671,9 @@ public RootCallTarget parse(PythonContext context, Source source, InputType type
671671
EnumSet<FutureFeature> futureFeatures) {
672672
RaisePythonExceptionErrorCallback errorCb = new RaisePythonExceptionErrorCallback(source, PythonOptions.isPExceptionWithJavaStacktrace(this));
673673
try {
674+
if (context.getEnv().getOptions().get(PythonOptions.ParserLogFiles)) {
675+
LOGGER.log(Level.FINE, () -> "parse '" + source.getName() + "'");
676+
}
674677
Parser parser = Compiler.createParser(source.getCharacters().toString(), errorCb, type, interactiveTerminal);
675678
ModTy mod = (ModTy) parser.parse();
676679
assert mod != null;

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1318,6 +1318,8 @@ private void loadFile(TruffleString s, TruffleString prefix, PythonModule mod) {
13181318
LOGGER.log(Level.FINE, () -> "import '" + s + "' # <frozen>");
13191319
return;
13201320
}
1321+
1322+
LOGGER.log(Level.FINE, () -> "import '" + s + "'");
13211323
Supplier<CallTarget> getCode = () -> {
13221324
Source source = getInternalSource(s, prefix);
13231325
return getLanguage().parse(getContext(), source, InputType.FILE, false, 0, false, null, EnumSet.noneOf(FutureFeature.class));

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/BuiltinFunctions.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
import java.util.Arrays;
110110
import java.util.EnumSet;
111111
import java.util.List;
112+
import java.util.logging.Level;
112113

113114
import com.oracle.graal.python.PythonFileDetector;
114115
import com.oracle.graal.python.PythonLanguage;
@@ -1079,6 +1080,9 @@ Object compile(TruffleString expression, TruffleString filename, TruffleString m
10791080
if (featureVersion < 7) {
10801081
compilerFlags.add(AbstractParser.Flags.ASYNC_HACKS);
10811082
}
1083+
if (context.getEnv().getOptions().get(PythonOptions.ParserLogFiles)) {
1084+
PythonLanguage.LOGGER.log(Level.FINE, () -> "parse '" + source.getName() + "'");
1085+
}
10821086
Parser parser = Compiler.createParser(code.toJavaStringUncached(), errorCb, type, compilerFlags, featureVersion);
10831087
ModTy mod = (ModTy) parser.parse();
10841088
errorCb.triggerDeprecationWarnings();

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/foreign/ForeignObjectBuiltins.java

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,21 @@
4646
import com.oracle.graal.python.builtins.PythonBuiltins;
4747
import com.oracle.graal.python.builtins.objects.PNone;
4848
import com.oracle.graal.python.builtins.objects.PythonAbstractObject;
49+
import com.oracle.graal.python.builtins.objects.list.ListBuiltins;
50+
import com.oracle.graal.python.builtins.objects.list.PList;
4951
import com.oracle.graal.python.builtins.objects.object.ObjectBuiltins;
5052
import com.oracle.graal.python.builtins.objects.object.ObjectNodes;
53+
import com.oracle.graal.python.builtins.objects.set.PSet;
54+
import com.oracle.graal.python.builtins.objects.set.SetNodes;
5155
import com.oracle.graal.python.builtins.objects.type.TpSlots;
56+
import com.oracle.graal.python.builtins.objects.type.TypeBuiltins;
5257
import com.oracle.graal.python.builtins.objects.type.slots.TpSlotGetAttr.GetAttrBuiltinNode;
5358
import com.oracle.graal.python.builtins.objects.type.slots.TpSlotSetAttr.SetAttrBuiltinNode;
5459
import com.oracle.graal.python.nodes.ErrorMessages;
5560
import com.oracle.graal.python.nodes.PGuards;
5661
import com.oracle.graal.python.nodes.PRaiseNode;
5762
import com.oracle.graal.python.nodes.attributes.LookupAttributeInMRONode;
63+
import com.oracle.graal.python.nodes.builtins.ListNodes;
5864
import com.oracle.graal.python.nodes.call.special.LookupAndCallUnaryNode;
5965
import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode;
6066
import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode;
@@ -68,7 +74,6 @@
6874
import com.oracle.graal.python.runtime.PythonOptions;
6975
import com.oracle.graal.python.runtime.exception.PException;
7076
import com.oracle.graal.python.runtime.exception.PythonErrorType;
71-
import com.oracle.graal.python.runtime.object.PFactory;
7277
import com.oracle.truffle.api.CompilerDirectives;
7378
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
7479
import com.oracle.truffle.api.dsl.Bind;
@@ -83,6 +88,7 @@
8388
import com.oracle.truffle.api.frame.VirtualFrame;
8489
import com.oracle.truffle.api.interop.ArityException;
8590
import com.oracle.truffle.api.interop.InteropLibrary;
91+
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
8692
import com.oracle.truffle.api.interop.UnknownIdentifierException;
8793
import com.oracle.truffle.api.interop.UnsupportedMessageException;
8894
import com.oracle.truffle.api.interop.UnsupportedTypeException;
@@ -268,29 +274,54 @@ static void doDelete(Object object, Object key, @SuppressWarnings("unused") PNon
268274
}
269275
}
270276

271-
// TODO dir(foreign) should list both foreign object members and attributes from class
272277
@Builtin(name = J___DIR__, minNumOfPositionalArgs = 1)
273278
@GenerateNodeFactory
274279
abstract static class DirNode extends PythonUnaryBuiltinNode {
275280
@Specialization
276-
protected Object doIt(Object object,
281+
protected Object doIt(VirtualFrame frame, Object object,
277282
@Bind("this") Node inliningTarget,
278283
@CachedLibrary(limit = "3") InteropLibrary lib,
284+
@CachedLibrary(limit = "3") InteropLibrary arrayInterop,
285+
@CachedLibrary(limit = "3") InteropLibrary stringInterop,
286+
@Cached TruffleString.SwitchEncodingNode switchEncodingNode,
279287
@Cached GilNode gil,
280-
@Cached InlinedConditionProfile profile) {
288+
@Cached InlinedConditionProfile profile,
289+
@Cached GetClassNode getClassNode,
290+
@Cached TypeBuiltins.DirNode typeDirNode,
291+
@Cached SetNodes.AddNode addNode,
292+
@Cached(inline = false) ListBuiltins.ListSortNode sortNode,
293+
@Cached(inline = false) ListNodes.ConstructListNode constructListNode) {
294+
// Inspired by ObjectBuiltins.DirNode
295+
var pythonClass = getClassNode.execute(inliningTarget, object);
296+
PSet attributes = typeDirNode.execute(frame, pythonClass);
297+
281298
if (profile.profile(inliningTarget, lib.hasMembers(object))) {
299+
final Object members;
282300
gil.release(true);
283301
try {
284-
return lib.getMembers(object);
302+
members = lib.getMembers(object);
285303
} catch (UnsupportedMessageException e) {
286-
CompilerDirectives.transferToInterpreterAndInvalidate();
287-
throw new IllegalStateException("foreign object claims to have members, but does not return them");
304+
throw CompilerDirectives.shouldNotReachHere("foreign object claims to have members, but does not return them");
288305
} finally {
289306
gil.acquire();
290307
}
291-
} else {
292-
return PFactory.createList(PythonLanguage.get(inliningTarget));
308+
309+
try {
310+
long size = arrayInterop.getArraySize(members);
311+
for (int i = 0; i < size; i++) {
312+
TruffleString memberString = stringInterop.asTruffleString(arrayInterop.readArrayElement(members, i));
313+
memberString = switchEncodingNode.execute(memberString, TS_ENCODING);
314+
addNode.execute(frame, attributes, memberString);
315+
}
316+
} catch (UnsupportedMessageException | InvalidArrayIndexException e) {
317+
throw CompilerDirectives.shouldNotReachHere(e);
318+
}
293319
}
320+
321+
// set to sorted list, like in PyObjectDir
322+
PList list = constructListNode.execute(frame, attributes);
323+
sortNode.execute(frame, list);
324+
return list;
294325
}
295326
}
296327

graalpython/lib-graalpython/_sre.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
22
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
33
#
44
# The Universal Permissive License (UPL), Version 1.0
@@ -44,6 +44,9 @@
4444

4545
from sys import maxsize
4646

47+
# This does not load _polyglot.py (as desired for faster startup), due to AbstractImportNode.importModule(),
48+
# when the context is not initialized, only looking up builtin modules (and not running their postInitialize()).
49+
import polyglot
4750

4851
def _check_pos(pos):
4952
if pos > maxsize:
@@ -265,7 +268,7 @@ def __init__(self, pattern, flags):
265268
self.groupindex = {}
266269
self.__indexgroup = {}
267270
else:
268-
group_names = dir(groups)
271+
group_names = polyglot.__keys__(groups)
269272
self.groupindex = _mappingproxy({name: getattr(groups, name) for name in group_names})
270273
self.__indexgroup = {getattr(groups, name): name for name in group_names}
271274

0 commit comments

Comments
 (0)