Skip to content

Commit 93644d0

Browse files
committed
[GR-57802] Prioritize Python methods over foreign members
PullRequest: graalpython/3462
2 parents 06c604b + 4054fe3 commit 93644d0

File tree

4 files changed

+28
-52
lines changed

4 files changed

+28
-52
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ language runtime. The main focus is on user-observable behavior of the engine.
66
## Version 24.2.0
77
* Updated developer metadata of Maven artifacts.
88
* Added gradle plugin for polyglot embedding of Python packages into Java.
9+
* When calling a method on a foreign object in Python code, Python methods are now prioritized over foreign members.
910

1011
## Version 24.1.0
1112
* GraalPy is now considered stable for pure Python workloads. While many workloads involving native extension modules work, we continue to consider them experimental. You can use the command-line option `--python.WarnExperimentalFeatures` to enable warnings for such modules at runtime. In Java embeddings the warnings are enabled by default and you can suppress them by setting the context option 'python.WarnExperimentalFeatures' to 'false'.

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,8 +1101,9 @@ static Object doIt(VirtualFrame frame, Object self, Object name,
11011101
@Cached IsBuiltinObjectProfile isAttrError,
11021102
@Cached ForeignGetattrNode foreignGetattrNode) {
11031103
try {
1104-
// We want the default Python way __getattribute__, but fallback to reading through
1105-
// interop library in what would be __getattr__
1104+
// We want the default Python attribute lookup first and try foreign members last.
1105+
// Because method calls in a Python source should prioritize Python methods over
1106+
// foreign methods.
11061107
return objectGetattrNode.execute(frame, self, name);
11071108
} catch (PException e) {
11081109
e.expect(inliningTarget, PythonBuiltinClassType.AttributeError, isAttrError);

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectGetMethod.java

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242

4343
import static com.oracle.graal.python.runtime.exception.PythonErrorType.AttributeError;
4444

45+
import com.oracle.graal.python.builtins.PythonBuiltinClassType;
4546
import com.oracle.graal.python.builtins.objects.PNone;
4647
import com.oracle.graal.python.builtins.objects.PythonAbstractObject;
4748
import com.oracle.graal.python.builtins.objects.object.ObjectBuiltins;
@@ -61,8 +62,10 @@
6162
import com.oracle.graal.python.nodes.call.ForeignMethod;
6263
import com.oracle.graal.python.nodes.call.special.CallUnaryMethodNode;
6364
import com.oracle.graal.python.nodes.call.special.MaybeBindDescriptorNode;
65+
import com.oracle.graal.python.nodes.object.BuiltinClassProfiles;
6466
import com.oracle.graal.python.nodes.object.GetClassNode;
6567
import com.oracle.graal.python.nodes.object.IsForeignObjectNode;
68+
import com.oracle.graal.python.runtime.exception.PException;
6669
import com.oracle.truffle.api.HostCompilerDirectives.InliningCutoff;
6770
import com.oracle.truffle.api.dsl.Bind;
6871
import com.oracle.truffle.api.dsl.Cached;
@@ -212,14 +215,20 @@ static Object getDynamicAttr(Frame frame, Node inliningTarget, Object receiver,
212215
@InliningCutoff
213216
static Object getForeignMethod(VirtualFrame frame, Node inliningTarget, Object receiver, TruffleString name,
214217
@SuppressWarnings("unused") @Cached IsForeignObjectNode isForeignObjectNode,
218+
@Exclusive @Cached PyObjectGetAttr getAttr,
219+
@Cached BuiltinClassProfiles.IsBuiltinObjectProfile isAttrError,
215220
@Cached(inline = false) TruffleString.ToJavaStringNode toJavaString,
216-
@CachedLibrary("receiver") InteropLibrary lib,
217-
@Exclusive @Cached PyObjectGetAttr getAttr) {
218-
String jName = toJavaString.execute(name);
219-
if (lib.isMemberInvocable(receiver, jName)) {
220-
return new BoundDescriptor(new ForeignMethod(receiver, jName));
221-
} else {
222-
return new BoundDescriptor(getAttr.execute(frame, inliningTarget, receiver, name));
221+
@CachedLibrary("receiver") InteropLibrary lib) {
222+
try {
223+
return getGenericAttr(frame, inliningTarget, receiver, name, getAttr);
224+
} catch (PException e) {
225+
e.expect(inliningTarget, PythonBuiltinClassType.AttributeError, isAttrError);
226+
String jName = toJavaString.execute(name);
227+
if (lib.isMemberInvocable(receiver, jName)) {
228+
return new BoundDescriptor(new ForeignMethod(receiver, jName));
229+
} else {
230+
throw e;
231+
}
223232
}
224233
}
225234

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/ReadAttributeFromObjectNode.java

Lines changed: 8 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,7 @@
5555
import com.oracle.graal.python.nodes.PNodeWithContext;
5656
import com.oracle.graal.python.nodes.attributes.ReadAttributeFromObjectNodeGen.ReadAttributeFromObjectNotTypeNodeGen;
5757
import com.oracle.graal.python.nodes.attributes.ReadAttributeFromObjectNodeGen.ReadAttributeFromObjectTpDictNodeGen;
58-
import com.oracle.graal.python.nodes.interop.PForeignToPTypeNode;
5958
import com.oracle.graal.python.nodes.object.GetDictIfExistsNode;
60-
import com.oracle.graal.python.nodes.object.IsForeignObjectNode;
61-
import com.oracle.graal.python.nodes.util.CannotCastException;
62-
import com.oracle.graal.python.nodes.util.CastToJavaStringNode;
6359
import com.oracle.graal.python.runtime.PythonOptions;
6460
import com.oracle.truffle.api.HostCompilerDirectives.InliningCutoff;
6561
import com.oracle.truffle.api.dsl.Bind;
@@ -71,10 +67,6 @@
7167
import com.oracle.truffle.api.dsl.NeverDefault;
7268
import com.oracle.truffle.api.dsl.ReportPolymorphism;
7369
import com.oracle.truffle.api.dsl.Specialization;
74-
import com.oracle.truffle.api.interop.InteropLibrary;
75-
import com.oracle.truffle.api.interop.UnknownIdentifierException;
76-
import com.oracle.truffle.api.interop.UnsupportedMessageException;
77-
import com.oracle.truffle.api.library.CachedLibrary;
7870
import com.oracle.truffle.api.nodes.Node;
7971
import com.oracle.truffle.api.profiles.InlinedConditionProfile;
8072
import com.oracle.truffle.api.strings.TruffleString;
@@ -162,19 +154,13 @@ protected static Object readObjectAttribute(PythonObject object, TruffleString k
162154
}
163155

164156
// foreign object or primitive
165-
@Specialization(guards = {"!isPythonObject(object)", "!isNativeObject(object)"})
166157
@InliningCutoff
167-
protected static Object readForeign(Object object, TruffleString key,
168-
@Bind("this") Node inliningTarget,
169-
@Cached IsForeignObjectNode isForeignObjectNode,
170-
@Cached ReadAttributeFromForeign read) {
171-
if (isForeignObjectNode.execute(inliningTarget, object)) {
172-
// there's an implicit condition profile from the isForeignObjectNode active
173-
// specializations
174-
return read.execute(object, key);
175-
} else {
176-
return PNone.NO_VALUE;
177-
}
158+
@Specialization(guards = {"!isPythonObject(object)", "!isNativeObject(object)"})
159+
protected static Object readForeignOrPrimitive(Object object, TruffleString key) {
160+
// Foreign members are tried after the regular attribute lookup, see
161+
// ForeignObjectBuiltins.GetAttributeNode. If we looked them up here
162+
// they would get precedence over attributes in the MRO.
163+
return PNone.NO_VALUE;
178164
}
179165

180166
// native objects. We distinguish reading at the objects dictoffset or the tp_dict
@@ -184,7 +170,7 @@ protected static Object readForeign(Object object, TruffleString key,
184170
@GenerateUncached
185171
@GenerateInline(false) // footprint reduction 64 -> 47
186172
protected abstract static class ReadAttributeFromObjectNotTypeNode extends ReadAttributeFromObjectNode {
187-
@Specialization(insertBefore = "readForeign")
173+
@Specialization(insertBefore = "readForeignOrPrimitive")
188174
protected static Object readNativeObject(PythonAbstractNativeObject object, TruffleString key,
189175
@Bind("this") Node inliningTarget,
190176
@Exclusive @Cached GetDictIfExistsNode getDict,
@@ -196,7 +182,7 @@ protected static Object readNativeObject(PythonAbstractNativeObject object, Truf
196182
@GenerateUncached
197183
@GenerateInline(false) // footprint reduction 68 -> 51
198184
protected abstract static class ReadAttributeFromObjectTpDictNode extends ReadAttributeFromObjectNode {
199-
@Specialization(insertBefore = "readForeign")
185+
@Specialization(insertBefore = "readForeignOrPrimitive")
200186
protected static Object readNativeClass(PythonAbstractNativeObject object, TruffleString key,
201187
@Bind("this") Node inliningTarget,
202188
@Cached CStructAccess.ReadObjectNode getNativeDict,
@@ -215,25 +201,4 @@ private static Object readNative(Node inliningTarget, TruffleString key, Object
215201
return PNone.NO_VALUE;
216202
}
217203

218-
@ImportStatic(PythonOptions.class)
219-
@GenerateUncached
220-
@GenerateInline(false) // footprint reduction 32 -> 13
221-
protected abstract static class ReadAttributeFromForeign extends PNodeWithContext {
222-
public abstract Object execute(Object object, Object key);
223-
224-
@Specialization
225-
static Object read(Object object, Object key,
226-
@Cached CastToJavaStringNode castNode,
227-
@Cached PForeignToPTypeNode fromForeign,
228-
@CachedLibrary(limit = "getAttributeAccessInlineCacheMaxDepth()") InteropLibrary read) {
229-
try {
230-
String member = castNode.execute(key);
231-
if (read.isMemberReadable(object, member)) {
232-
return fromForeign.executeConvert(read.readMember(object, member));
233-
}
234-
} catch (CannotCastException | UnknownIdentifierException | UnsupportedMessageException ignored) {
235-
}
236-
return PNone.NO_VALUE;
237-
}
238-
}
239204
}

0 commit comments

Comments
 (0)