diff --git a/CHANGELOG.md b/CHANGELOG.md index c5c9f475fc..045f3dae31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ language runtime. The main focus is on user-observable behavior of the engine. * Add a new, more natural style of subclassing Java classes from Python by passing the `new_style=True` keyword. Multiple levels of inheritance are supported, and `super()` calls both in the constructor override via `__new__` as well as in Java method overrides work as expected. * Add `polyglot.gil_locked_during_interop` context manager. By default, the global interpreter lock (GIL) is unlocked when interacting with objects from another language, to give other Python threads a chance to run in parallel. While this avoids potential deadlocks, repeated unlocking and locking of the GIL can decrease performance, so this context manager can be used to keep the lock held around short-running interop. * Add Github workflows that run our gates from the same job definitions as our internal CI. This will make it easier for contributors opening PRs on Github to ensure code contributions pass the same tests that we are running internally. +* Added support for specifying generics on foreign classes, and inheriting from such classes. Especially when using Java classes that support generics, this allows expressing the generic types in Python type annotations as well. ## Version 25.0.1 * Allow users to keep going on unsupported JDK/OS/ARCH combinations at their own risk by opting out of early failure using `-Dtruffle.UseFallbackRuntime=true`, `-Dpolyglot.engine.userResourceCache=/set/to/a/writeable/dir`, `-Dpolyglot.engine.allowUnsupportedPlatform=true`, and `-Dpolyglot.python.UnsupportedPlatformEmulates=[linux|macos|windows]` and `-Dorg.graalvm.python.resources.exclude=native.files`. diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_interop.py b/graalpython/com.oracle.graal.python.test/src/tests/test_interop.py index 7e1d7d3f09..8e586345f3 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_interop.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_interop.py @@ -587,6 +587,22 @@ def test_java_typing_support(self): assert List[Integer].__origin__ == List assert List[Integer].__args__ == (Integer,) + def test_java_generics_subclassing(self): + from typing import Generic, TypeVar + from java.util.function import Function + + class MyFunction(Function[str, str], new_style=False): + def apply(self, val : str) -> str: + return val + + assert MyFunction().apply(23) == 23 + + class MyFunction(Function[str, str], new_style=True): + def apply(self, val : str) -> str: + return val + + assert MyFunction().apply(23) == 23 + def test_java_null_is_none(self): import java.lang.Integer as Integer x = Integer.getInteger("something_that_does_not_exists") diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/BuiltinFunctions.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/BuiltinFunctions.java index cef9d694fd..b6810489fd 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/BuiltinFunctions.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/BuiltinFunctions.java @@ -2332,19 +2332,20 @@ protected ArgumentClinicProvider getArgumentClinic() { @GenerateInline(false) // footprint reduction 72 -> 53 abstract static class UpdateBasesNode extends Node { - abstract PTuple execute(PTuple bases, Object[] arguments, int nargs); + abstract Object[] execute(Object[] bases); @Specialization - static PTuple update(PTuple bases, Object[] arguments, int nargs, + static Object[] update(Object[] bases, @Bind Node inliningTarget, @Bind PythonLanguage language, @Cached PyObjectLookupAttr getMroEntries, @Cached CallUnaryMethodNode callMroEntries, @Cached PRaiseNode raiseNode) { CompilerAsserts.neverPartOfCompilation(); + PTuple originalBases = null; ArrayList newBases = null; - for (int i = 0; i < nargs; i++) { - Object base = arguments[i]; + for (int i = 0; i < bases.length; i++) { + Object base = bases[i]; if (IsTypeNode.executeUncached(base)) { if (newBases != null) { // If we already have made a replacement, then we append every normal base, @@ -2361,7 +2362,10 @@ static PTuple update(PTuple bases, Object[] arguments, int nargs, } continue; } - Object newBase = callMroEntries.executeObject(null, meth, bases); + if (originalBases == null) { + originalBases = PFactory.createTuple(language, bases); + } + Object newBase = callMroEntries.executeObject(null, meth, originalBases); if (!PGuards.isPTuple(newBase)) { throw raiseNode.raise(inliningTarget, PythonErrorType.TypeError, ErrorMessages.MRO_ENTRIES_MUST_RETURN_TUPLE); } @@ -2371,7 +2375,7 @@ static PTuple update(PTuple bases, Object[] arguments, int nargs, // previously encountered bases. newBases = new ArrayList<>(); for (int j = 0; j < i; j++) { - newBases.add(arguments[j]); + newBases.add(bases[j]); } } SequenceStorage storage = newBaseTuple.getSequenceStorage(); @@ -2382,18 +2386,18 @@ static PTuple update(PTuple bases, Object[] arguments, int nargs, if (newBases == null) { return bases; } - return PFactory.createTuple(language, newBases.toArray()); + return newBases.toArray(); } } @GenerateInline(false) // footprint reduction 36 -> 19 abstract static class CalculateMetaclassNode extends Node { - abstract Object execute(Object metatype, PTuple bases); + abstract Object execute(Object metatype, Object[] bases); /* Determine the most derived metatype. */ @Specialization - static Object calculate(Object metatype, PTuple bases, + static Object calculate(Object metatype, Object[] bases, @Bind Node inliningTarget, @Cached GetClassNode getClass, @Cached IsSubtypeNode isSubType, @@ -2405,12 +2409,8 @@ static Object calculate(Object metatype, PTuple bases, * while we're at it. Note that if some other metatype wins to contract, it's possible * that its instances are not types. */ - - SequenceStorage storage = bases.getSequenceStorage(); - int nbases = storage.length(); Object winner = metatype; - for (int i = 0; i < nbases; i++) { - Object tmp = SequenceStorageNodes.GetItemScalarNode.executeUncached(storage, i); + for (Object tmp : bases) { Object tmpType = getClass.execute(inliningTarget, tmp); if (isSubType.execute(winner, tmpType)) { // nothing to do @@ -2439,17 +2439,6 @@ private static Object buildJavaClass(Object namespace, TruffleString name, Objec return CallNode.executeUncached(buildFunction, new Object[]{namespace, name, base}, keywords); } - @InliningCutoff - private static Object buildJavaClass(VirtualFrame frame, Node inliningTarget, PythonLanguage language, PFunction function, Object[] arguments, - PKeyword[] keywords, CallDispatchers.FunctionCachedInvokeNode invokeBody, - TruffleString name) { - PDict ns = PFactory.createDict(language, new DynamicObjectStorage(language)); - Object[] args = PArguments.create(0); - PArguments.setSpecialArgument(args, ns); - invokeBody.execute(frame, inliningTarget, function, args); - return buildJavaClass(ns, name, arguments[1], keywords); - } - @Specialization protected Object doItNonFunction(VirtualFrame frame, Object function, Object[] arguments, PKeyword[] keywords, @Bind Node inliningTarget, @@ -2486,26 +2475,16 @@ protected Object doItNonFunction(VirtualFrame frame, Object function, Object[] a PythonLanguage language = ctx.getLanguage(inliningTarget); Object[] basesArray = Arrays.copyOfRange(arguments, 1, arguments.length); - PTuple origBases = PFactory.createTuple(language, basesArray); - - if (arguments.length == 2) { - InteropLibrary lib = lazyInteropLibrary.get(inliningTarget); - if (lib.isHostObject(arguments[1]) && lib.isMetaObject(arguments[1])) { - // we want to subclass a Java class - return buildJavaClass(frame, inliningTarget, language, (PFunction) function, arguments, keywords, invokeBody, name); - } - } class InitializeBuildClass { boolean isClass; Object meta; PKeyword[] mkw; - PTuple bases; + Object[] bases; @TruffleBoundary - InitializeBuildClass(PythonContext ctx) { - - bases = update.execute(origBases, basesArray, basesArray.length); + InitializeBuildClass() { + bases = update.execute(basesArray); mkw = keywords; for (int i = 0; i < keywords.length; i++) { @@ -2522,12 +2501,12 @@ class InitializeBuildClass { } } if (meta == null) { - // if there are no bases, use type: - if (bases.getSequenceStorage().length() == 0) { + // if there are no bases, use type + if (bases.length == 0) { meta = ctx.lookupType(PythonBuiltinClassType.PythonClass); } else { // else get the type of the first base - meta = getClass.execute(inliningTarget, SequenceStorageNodes.GetItemScalarNode.executeUncached(bases.getSequenceStorage(), 0)); + meta = getClass.execute(inliningTarget, bases[0]); } isClass = true; // meta is really a class } @@ -2544,15 +2523,16 @@ class InitializeBuildClass { Object savedState = BoundaryCallContext.enter(frame, language, ctx, boundaryCallData); InitializeBuildClass init; try { - init = new InitializeBuildClass(ctx); + init = new InitializeBuildClass(); } finally { BoundaryCallContext.exit(frame, language, ctx, savedState); } Object ns; + PTuple bases = PFactory.createTuple(language, init.bases); try { Object prep = getPrepare.execute(frame, init.meta); - ns = callPrep.execute(frame, prep, new Object[]{name, init.bases}, init.mkw); + ns = callPrep.execute(frame, prep, new Object[]{name, bases}, init.mkw); } catch (PException p) { p.expectAttributeError(inliningTarget, noAttributeProfile); ns = PFactory.createDict(language, new DynamicObjectStorage(language)); @@ -2563,10 +2543,20 @@ class InitializeBuildClass { Object[] bodyArguments = PArguments.create(0); PArguments.setSpecialArgument(bodyArguments, ns); invokeBody.execute(frame, inliningTarget, (PFunction) function, bodyArguments); - if (init.bases != origBases) { - setOrigBases.execute(frame, inliningTarget, ns, SpecialAttributeNames.T___ORIG_BASES__, origBases); + if (init.bases != basesArray) { + setOrigBases.execute(frame, inliningTarget, ns, SpecialAttributeNames.T___ORIG_BASES__, PFactory.createTuple(language, basesArray)); + } + + if (init.bases.length == 1) { + InteropLibrary lib = lazyInteropLibrary.get(inliningTarget); + Object base = init.bases[0]; + if (lib.isHostObject(base) && lib.isMetaObject(base)) { + // we want to subclass a Java class + return buildJavaClass(ns, name, base, keywords); + } } - Object cls = callType.execute(frame, init.meta, new Object[]{name, init.bases, ns}, init.mkw); + + Object cls = callType.execute(frame, init.meta, new Object[]{name, bases, ns}, init.mkw); /* * We could check here and throw "__class__ not set defining..." errors.