Skip to content

Commit 42b7ef8

Browse files
committed
[GR-55302] Support properties magically calling Java getters/setters in Jython emulation mode
1 parent fe06e1f commit 42b7ef8

File tree

3 files changed

+55
-6
lines changed

3 files changed

+55
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ language runtime. The main focus is on user-observable behavior of the engine.
1010
* Update to Python 3.11.7
1111
* We now provide intrinsified `_pickle` module also in the community version.
1212
* `polyglot.eval` now raises more meaningful exceptions. Unavaliable languages raise `ValueError`. Exceptions from the polyglot language are raised directly as interop objects (typed as `polyglot.ForeignException`). The shortcut for executing python files without specifying language has been removed, use regular `eval` for executing Python code.
13-
* Update to Python 3.11.7.
14-
* We now provide intrinsified `_pickle` and `_struct` modules also in the community version. This means more packages will no work without allowing native access.
13+
* In Jython emulation mode we now magically fall back to calling Java getters or setters when using Python attribute access for non-visible properties. This can help migrating away from Jython if you relied on this behavior.
1514

1615
## Version 24.0.0
1716
* We now provide a collection of recipes in the form of GitHub Actions to build popular native extensions on GraalPy. These provide a reproducible way for the community to build native extensions for GraalPy with the correct dependencies. See scripts/wheelbuilder/README.md for details.

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,16 @@ def test_jython_star_import():
639639
exec('from java.lang.Byte import *', g)
640640
assert type(g['MAX_VALUE']) is int
641641

642+
def test_jython_accessors():
643+
if __graalpython__.jython_emulation_enabled:
644+
from java.util.logging import LogRecord
645+
from java.util.logging import Level
646+
lr = LogRecord(Level.ALL, "message")
647+
648+
assert lr.message == "message"
649+
lr.message = "new message"
650+
assert lr.message == "new message"
651+
642652
@skipUnless(test_polyglot_languages, "tests other language access")
643653
def test_doctest():
644654
import doctest

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

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1020,6 +1020,11 @@ static Object doIt(VirtualFrame frame, Object self, Object name,
10201020
}
10211021
}
10221022

1023+
@TruffleBoundary(allowInlining = true)
1024+
private static String asJavaPrefixedMethod(String prefix, String member) {
1025+
return prefix + member.substring(0, 1).toUpperCase() + member.substring(1);
1026+
}
1027+
10231028
@GenerateInline
10241029
@GenerateCached(false)
10251030
@ImportStatic(PythonOptions.class)
@@ -1038,10 +1043,22 @@ static Object doIt(Node inliningTarget, Object object, Object memberObj,
10381043
String member = castToString.execute(memberObj);
10391044
if (read.isMemberReadable(object, member)) {
10401045
return toPythonNode.executeConvert(read.readMember(object, member));
1046+
} else if (PythonLanguage.get(inliningTarget).getEngineOption(PythonOptions.EmulateJython)) {
1047+
// no profile, above condition should fold to false when EmulateJython is off
1048+
if (PythonContext.get(inliningTarget).getEnv().isHostObject(object)) {
1049+
String getter = asJavaPrefixedMethod("get", member);
1050+
if (read.isMemberInvocable(object, getter)) {
1051+
try {
1052+
return toPythonNode.executeConvert(read.invokeMember(object, getter));
1053+
} catch (UnsupportedTypeException ignored) {
1054+
// fall through to AttributeError
1055+
}
1056+
}
1057+
}
10411058
}
10421059
} catch (CannotCastException e) {
10431060
throw raiseNode.get(inliningTarget).raise(PythonBuiltinClassType.TypeError, ErrorMessages.ATTR_NAME_MUST_BE_STRING, memberObj);
1044-
} catch (UnknownIdentifierException | UnsupportedMessageException ignore) {
1061+
} catch (UnknownIdentifierException | UnsupportedMessageException | ArityException ignore) {
10451062
} finally {
10461063
gil.acquire();
10471064
}
@@ -1061,15 +1078,38 @@ static void doSet(Object object, Object key, Object value,
10611078
@Shared @Cached GilNode gil,
10621079
@Shared @Cached PRaiseNode.Lazy raiseNode) {
10631080
gil.release(true);
1081+
String member;
10641082
try {
1065-
lib.writeMember(object, castToString.execute(key), value);
1083+
member = castToString.execute(key);
10661084
} catch (CannotCastException e) {
10671085
throw raiseNode.get(inliningTarget).raise(PythonBuiltinClassType.TypeError, ErrorMessages.ATTR_NAME_MUST_BE_STRING, key);
1068-
} catch (UnknownIdentifierException | UnsupportedMessageException | UnsupportedTypeException e) {
1069-
throw raiseNode.get(inliningTarget).raise(PythonErrorType.AttributeError, ErrorMessages.FOREIGN_OBJ_HAS_NO_ATTR_S, key);
1086+
}
1087+
try {
1088+
try {
1089+
lib.writeMember(object, member, value);
1090+
return;
1091+
} catch (UnknownIdentifierException | UnsupportedMessageException e) {
1092+
if (PythonLanguage.get(inliningTarget).getEngineOption(PythonOptions.EmulateJython)) {
1093+
// no profile, above condition folds to false when EmulateJython is off
1094+
try {
1095+
if (PythonContext.get(inliningTarget).getEnv().isHostObject(object)) {
1096+
String setter = asJavaPrefixedMethod("set", member);
1097+
if (lib.isMemberInvocable(object, setter)) {
1098+
lib.invokeMember(object, setter, value);
1099+
return;
1100+
}
1101+
}
1102+
} catch (ArityException | UnsupportedMessageException | UnknownIdentifierException ignore) {
1103+
// fall through to AttributeError
1104+
}
1105+
}
1106+
}
1107+
} catch (UnsupportedTypeException e) {
1108+
throw raiseNode.get(inliningTarget).raise(PythonErrorType.TypeError, ErrorMessages.INVALID_TYPE_FOR_S, key);
10701109
} finally {
10711110
gil.acquire();
10721111
}
1112+
throw raiseNode.get(inliningTarget).raise(PythonErrorType.AttributeError, ErrorMessages.FOREIGN_OBJ_HAS_NO_ATTR_S, key);
10731113
}
10741114

10751115
@Specialization(guards = "isNoValue(value)")

0 commit comments

Comments
 (0)