diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonBytecodeToJavaBytecodeTranslator.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonBytecodeToJavaBytecodeTranslator.java index a688203..d4664d0 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonBytecodeToJavaBytecodeTranslator.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonBytecodeToJavaBytecodeTranslator.java @@ -1141,6 +1141,9 @@ public static void writeInstructionsForOpcodes(FunctionMetadata functionMetadata Label targetLabel = exceptionTableTargetLabelMap.computeIfAbsent(exceptionBlock.targetInstruction, offset -> new Label()); + if (exceptionBlock.blockStartInstructionInclusive > exceptionBlock.targetInstruction) { + return; + } functionMetadata.methodVisitor.visitTryCatchBlock(startLabel, endLabel, targetLabel, Type.getInternalName(PythonBaseException.class)); }); diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/builtins/GlobalBuiltins.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/builtins/GlobalBuiltins.java index 38e7e24..716465a 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/builtins/GlobalBuiltins.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/builtins/GlobalBuiltins.java @@ -52,7 +52,7 @@ import ai.timefold.jpyinterpreter.types.PythonSlice; import ai.timefold.jpyinterpreter.types.PythonString; import ai.timefold.jpyinterpreter.types.PythonSuperObject; -import ai.timefold.jpyinterpreter.types.collections.PythonIterator; +import ai.timefold.jpyinterpreter.types.collections.DelegatePythonIterator; import ai.timefold.jpyinterpreter.types.collections.PythonLikeDict; import ai.timefold.jpyinterpreter.types.collections.PythonLikeList; import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; @@ -753,7 +753,7 @@ public static PythonLikeObject enumerate(List positionalArgs, final AtomicReference currentIndex = new AtomicReference(start); final AtomicBoolean shouldCallNext = new AtomicBoolean(true); - return new PythonIterator(new Iterator() { + return new DelegatePythonIterator(new Iterator() { @Override public boolean hasNext() { if (shouldCallNext.get()) { @@ -790,7 +790,7 @@ public PythonLikeObject next() { }); } - public static PythonIterator filter(List positionalArgs, + public static DelegatePythonIterator filter(List positionalArgs, Map keywordArgs, PythonLikeObject instance) { PythonLikeObject function; PythonLikeObject iterable; @@ -835,7 +835,7 @@ public static PythonIterator filter(List positionalArgs, predicate = (PythonLikeFunction) function; } - return new PythonIterator(StreamSupport.stream( + return new DelegatePythonIterator(StreamSupport.stream( Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false) .filter(element -> PythonBoolean @@ -1088,7 +1088,7 @@ public static PythonLikeDict locals(List positionalArgs, throw new ValueError("builtin locals() is not supported when executed in Java bytecode"); } - public static PythonIterator map(List positionalArgs, + public static DelegatePythonIterator map(List positionalArgs, Map keywordArgs, PythonLikeObject instance) { PythonLikeFunction function; List iterableList = new ArrayList<>(); @@ -1138,7 +1138,7 @@ public List next() { } }; - return new PythonIterator(StreamSupport.stream( + return new DelegatePythonIterator(StreamSupport.stream( Spliterators.spliteratorUnknownSize(iteratorIterator, Spliterator.ORDERED), false) .map(element -> function.$call(element, Map.of(), null)) @@ -1395,7 +1395,7 @@ public PythonLikeObject next() { } }; - return new PythonIterator(reversedIterator); + return new DelegatePythonIterator(reversedIterator); } throw new ValueError(sequenceType + " does not has a __reversed__ method and does not implement the Sequence protocol"); @@ -1569,7 +1569,7 @@ public static PythonLikeObject vars(List positionalArgs, return positionalArgs.get(0).$getAttributeOrError("__dict__"); } - public static PythonIterator zip(List positionalArgs, + public static DelegatePythonIterator zip(List positionalArgs, Map keywordArgs, PythonLikeObject instance) { List iterableList = positionalArgs; boolean isStrict = false; @@ -1596,7 +1596,7 @@ public static PythonIterator zip(List positionalArgs, if (iteratorList.isEmpty()) { // Return an empty iterator if there are no iterators - return new PythonIterator(iteratorList.iterator()); + return new DelegatePythonIterator(iteratorList.iterator()); } Iterator> iteratorIterator = new Iterator>() { @@ -1633,7 +1633,7 @@ public List next() { } }; - return new PythonIterator(iteratorIterator); + return new DelegatePythonIterator(iteratorIterator); } public static PythonLikeFunction importFunction(PythonInterpreter pythonInterpreter) { diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/ExceptionImplementor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/ExceptionImplementor.java index 727b9fa..e1f0778 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/ExceptionImplementor.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/ExceptionImplementor.java @@ -244,7 +244,7 @@ public static void startExceptOrFinally(FunctionMetadata functionMetadata, Stack } } - public static void startWith(int jumpTarget, FunctionMetadata functionMetadata, + public static void setupWith(int jumpTarget, FunctionMetadata functionMetadata, StackMetadata stackMetadata) { MethodVisitor methodVisitor = functionMetadata.methodVisitor; @@ -301,6 +301,50 @@ public static void startWith(int jumpTarget, FunctionMetadata functionMetadata, // cannot free, since try block store stack in locals => freeing enterResult messes up indexing of locals } + public static void beforeWith(FunctionMetadata functionMetadata, + StackMetadata stackMetadata) { + MethodVisitor methodVisitor = functionMetadata.methodVisitor; + + methodVisitor.visitInsn(Opcodes.DUP); // duplicate context_manager twice; need one for __enter__, two for __exit__ + methodVisitor.visitInsn(Opcodes.DUP); + + // First load the method __exit__ + methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), + "$getType", Type.getMethodDescriptor(Type.getType(PythonLikeType.class)), + true); + methodVisitor.visitLdcInsn("__exit__"); + methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), + "$getAttributeOrError", + Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), Type.getType(String.class)), + true); + methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonLikeFunction.class)); + + // bind it to the context_manager + methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(BoundPythonLikeFunction.class)); + methodVisitor.visitInsn(Opcodes.DUP_X2); + methodVisitor.visitInsn(Opcodes.DUP_X2); + methodVisitor.visitInsn(Opcodes.POP); + methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(BoundPythonLikeFunction.class), + "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(PythonLikeObject.class), + Type.getType(PythonLikeFunction.class)), + false); + + // Swap __exit__ method with duplicated context_manager + methodVisitor.visitInsn(Opcodes.SWAP); + + // Call __enter__ + DunderOperatorImplementor.unaryOperator(methodVisitor, stackMetadata, PythonUnaryOperator.ENTER); + + int enterResult = stackMetadata.localVariableHelper.newLocal(); + + // store enter result in temp, so it does not get saved in try block + stackMetadata.localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), enterResult); + + // Push enter result back to the stack + stackMetadata.localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), enterResult); + // cannot free, since try block store stack in locals => freeing enterResult messes up indexing of locals + } + public static void startExceptBlock(FunctionMetadata functionMetadata, StackMetadata stackMetadata, ExceptionBlock exceptionBlock) { // In Python 3.11 and above, the stack here is @@ -362,12 +406,18 @@ public static void handleExceptionInWith(FunctionMetadata functionMetadata, Stac int instruction = localVariableHelper.newLocal(); int exitFunction = localVariableHelper.newLocal(); - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), exception); - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), exceptionArgs); - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), traceback); - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), label); - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), stackSize); - localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), instruction); + if (functionMetadata.pythonCompiledFunction.pythonVersion.isBefore(PythonVersion.PYTHON_3_11)) { + localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), exception); + localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), exceptionArgs); + localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), traceback); + localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), label); + localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), stackSize); + localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), instruction); + } else { + localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), exception); + localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), exceptionArgs); + localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), traceback); + } localVariableHelper.writeTemp(methodVisitor, Type.getType(PythonLikeObject.class), exitFunction); // load exitFunction @@ -424,14 +474,16 @@ public static void handleExceptionInWith(FunctionMetadata functionMetadata, Stac localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), exitFunction); methodVisitor.visitInsn(Opcodes.SWAP); - localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), instruction); - methodVisitor.visitInsn(Opcodes.SWAP); + if (functionMetadata.pythonCompiledFunction.pythonVersion.isBefore(PythonVersion.PYTHON_3_11)) { + localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), instruction); + methodVisitor.visitInsn(Opcodes.SWAP); - localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), stackSize); - methodVisitor.visitInsn(Opcodes.SWAP); + localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), stackSize); + methodVisitor.visitInsn(Opcodes.SWAP); - localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), label); - methodVisitor.visitInsn(Opcodes.SWAP); + localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), label); + methodVisitor.visitInsn(Opcodes.SWAP); + } localVariableHelper.readTemp(methodVisitor, Type.getType(PythonLikeObject.class), traceback); methodVisitor.visitInsn(Opcodes.SWAP); diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/JavaPythonTypeConversionImplementor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/JavaPythonTypeConversionImplementor.java index c730779..5c0861a 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/JavaPythonTypeConversionImplementor.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/JavaPythonTypeConversionImplementor.java @@ -22,6 +22,7 @@ import ai.timefold.jpyinterpreter.types.PythonLikeType; import ai.timefold.jpyinterpreter.types.PythonNone; import ai.timefold.jpyinterpreter.types.PythonString; +import ai.timefold.jpyinterpreter.types.collections.DelegatePythonIterator; import ai.timefold.jpyinterpreter.types.collections.PythonIterator; import ai.timefold.jpyinterpreter.types.collections.PythonLikeDict; import ai.timefold.jpyinterpreter.types.collections.PythonLikeFrozenSet; @@ -94,7 +95,7 @@ public static PythonLikeObject wrapJavaObject(Object object, Map((Iterator) object); } if (object instanceof List) { diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/StackManipulationImplementor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/StackManipulationImplementor.java index 4a06a24..bd81556 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/StackManipulationImplementor.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/StackManipulationImplementor.java @@ -7,6 +7,7 @@ import ai.timefold.jpyinterpreter.FunctionMetadata; import ai.timefold.jpyinterpreter.LocalVariableHelper; import ai.timefold.jpyinterpreter.PythonLikeObject; +import ai.timefold.jpyinterpreter.PythonVersion; import ai.timefold.jpyinterpreter.StackMetadata; import ai.timefold.jpyinterpreter.ValueSourceInfo; @@ -296,12 +297,22 @@ public static void restoreExceptionTableStack(FunctionMetadata functionMetadata, LocalVariableHelper localVariableHelper = stackMetadata.localVariableHelper; localVariableHelper.readExceptionTableTargetStack(methodVisitor, exceptionBlock.getTargetInstruction()); + for (int i = 0; i < exceptionBlock.getStackDepth(); i++) { methodVisitor.visitInsn(Opcodes.DUP); methodVisitor.visitLdcInsn(i); methodVisitor.visitInsn(Opcodes.AALOAD); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, - stackMetadata.getTypeAtStackIndex(exceptionBlock.getStackDepth() - i - 1).getJavaTypeInternalName()); + + if (functionMetadata.pythonCompiledFunction.pythonVersion.isBefore(PythonVersion.PYTHON_3_11) || + functionMetadata.pythonCompiledFunction.pythonVersion.isAtLeast(PythonVersion.PYTHON_3_12)) { + // Not a 3.11 python version + methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, + stackMetadata.getTypeAtStackIndex(exceptionBlock.getStackDepth() - i - 1).getJavaTypeInternalName()); + } else { + // A 3.11 python version + methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, + Type.getInternalName(PythonLikeObject.class)); + } methodVisitor.visitInsn(Opcodes.SWAP); } methodVisitor.visitInsn(Opcodes.POP); diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/ExceptionOpDescriptor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/ExceptionOpDescriptor.java index 1621ac0..96d9b50 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/ExceptionOpDescriptor.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/ExceptionOpDescriptor.java @@ -7,6 +7,7 @@ import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; import ai.timefold.jpyinterpreter.PythonVersion; import ai.timefold.jpyinterpreter.opcodes.Opcode; +import ai.timefold.jpyinterpreter.opcodes.exceptions.BeforeWithOpcode; import ai.timefold.jpyinterpreter.opcodes.exceptions.CheckExcMatchOpcode; import ai.timefold.jpyinterpreter.opcodes.exceptions.CleanupThrowOpcode; import ai.timefold.jpyinterpreter.opcodes.exceptions.LoadAssertionErrorOpcode; @@ -64,6 +65,7 @@ public enum ExceptionOpDescriptor implements OpcodeDescriptor { WITH_EXCEPT_START(WithExceptStartOpcode::new), SETUP_FINALLY(SetupFinallyOpcode::new, JumpUtils::getRelativeTarget), + BEFORE_WITH(BeforeWithOpcode::new), SETUP_WITH(SetupWithOpcode::new, JumpUtils::getRelativeTarget), CLEANUP_THROW(CleanupThrowOpcode::new); diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/BeforeWithOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/BeforeWithOpcode.java new file mode 100644 index 0000000..33db643 --- /dev/null +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/BeforeWithOpcode.java @@ -0,0 +1,30 @@ +package ai.timefold.jpyinterpreter.opcodes.exceptions; + +import ai.timefold.jpyinterpreter.FunctionMetadata; +import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; +import ai.timefold.jpyinterpreter.StackMetadata; +import ai.timefold.jpyinterpreter.ValueSourceInfo; +import ai.timefold.jpyinterpreter.implementors.ExceptionImplementor; +import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; +import ai.timefold.jpyinterpreter.types.BuiltinTypes; +import ai.timefold.jpyinterpreter.types.PythonLikeFunction; + +public class BeforeWithOpcode extends AbstractOpcode { + + public BeforeWithOpcode(PythonBytecodeInstruction instruction) { + super(instruction); + } + + @Override + protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { + return stackMetadata + .pop() + .push(ValueSourceInfo.of(this, PythonLikeFunction.getFunctionType(), stackMetadata.getTOSValueSource())) + .push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, stackMetadata.getTOSValueSource())); + } + + @Override + public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { + ExceptionImplementor.beforeWith(functionMetadata, stackMetadata); + } +} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/SetupWithOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/SetupWithOpcode.java index 725abb3..c67007c 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/SetupWithOpcode.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/SetupWithOpcode.java @@ -49,6 +49,6 @@ public List getStackMetadataAfterInstructionForBranches(FunctionM @Override public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - ExceptionImplementor.startWith(jumpTarget, functionMetadata, stackMetadata); + ExceptionImplementor.setupWith(jumpTarget, functionMetadata, stackMetadata); } } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/WithExceptStartOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/WithExceptStartOpcode.java index c10c788..8193716 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/WithExceptStartOpcode.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/WithExceptStartOpcode.java @@ -2,6 +2,7 @@ import ai.timefold.jpyinterpreter.FunctionMetadata; import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; +import ai.timefold.jpyinterpreter.PythonVersion; import ai.timefold.jpyinterpreter.StackMetadata; import ai.timefold.jpyinterpreter.ValueSourceInfo; import ai.timefold.jpyinterpreter.implementors.ExceptionImplementor; @@ -16,7 +17,10 @@ public WithExceptStartOpcode(PythonBytecodeInstruction instruction) { @Override protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - // TODO: this might need updating to handle Python 3.11 + if (functionMetadata.pythonCompiledFunction.pythonVersion.isAtLeast(PythonVersion.PYTHON_3_11)) { + return stackMetadata + .push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, stackMetadata.getValueSourceForStackIndex(1))); + } return stackMetadata .push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, stackMetadata.getValueSourceForStackIndex(6))); } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/AbstractPythonLikeObject.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/AbstractPythonLikeObject.java index 53e97f7..56b73d4 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/AbstractPythonLikeObject.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/AbstractPythonLikeObject.java @@ -50,6 +50,10 @@ public void setAttribute(String attributeName, PythonLikeObject value) { __dir__.put(attributeName, value); } + public Map getExtraAttributeMap() { + return __dir__; + } + @Override public String toString() { return $method$__str__().toString(); diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/CPythonBackedPythonLikeObject.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/CPythonBackedPythonLikeObject.java index b1a30ea..ff18a87 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/CPythonBackedPythonLikeObject.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/CPythonBackedPythonLikeObject.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Map; +import ai.timefold.jpyinterpreter.CPythonBackedPythonInterpreter; import ai.timefold.jpyinterpreter.PythonInterpreter; import ai.timefold.jpyinterpreter.PythonLikeObject; import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; @@ -78,6 +79,10 @@ public CPythonBackedPythonLikeObject(PythonInterpreter interpreter, } public void $writeFieldsToCPythonReference(OpaquePythonReference cloneMap) { + for (var attributeEntry : getExtraAttributeMap().entrySet()) { + CPythonBackedPythonInterpreter.setAttributeOnPythonReference($cpythonReference, cloneMap, attributeEntry.getKey(), + attributeEntry.getValue()); + } } @Override diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonByteArray.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonByteArray.java index b7e0eb1..725faa0 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonByteArray.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonByteArray.java @@ -18,6 +18,7 @@ import ai.timefold.jpyinterpreter.PythonTernaryOperator; import ai.timefold.jpyinterpreter.PythonUnaryOperator; import ai.timefold.jpyinterpreter.builtins.UnaryDunderBuiltin; +import ai.timefold.jpyinterpreter.types.collections.DelegatePythonIterator; import ai.timefold.jpyinterpreter.types.collections.PythonIterator; import ai.timefold.jpyinterpreter.types.collections.PythonLikeDict; import ai.timefold.jpyinterpreter.types.collections.PythonLikeList; @@ -432,8 +433,8 @@ public PythonByteArray repeat(PythonInteger times) { return new PythonByteArray(out); } - public PythonIterator getIterator() { - return new PythonIterator<>(IntStream.range(0, valueBuffer.limit()) + public DelegatePythonIterator getIterator() { + return new DelegatePythonIterator<>(IntStream.range(0, valueBuffer.limit()) .mapToObj(index -> PythonBytes.BYTE_TO_INT[valueBuffer.get(index)]) .iterator()); } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonBytes.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonBytes.java index 91afe47..0c87e64 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonBytes.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonBytes.java @@ -17,6 +17,7 @@ import ai.timefold.jpyinterpreter.PythonOverloadImplementor; import ai.timefold.jpyinterpreter.PythonUnaryOperator; import ai.timefold.jpyinterpreter.builtins.UnaryDunderBuiltin; +import ai.timefold.jpyinterpreter.types.collections.DelegatePythonIterator; import ai.timefold.jpyinterpreter.types.collections.PythonIterator; import ai.timefold.jpyinterpreter.types.collections.PythonLikeDict; import ai.timefold.jpyinterpreter.types.collections.PythonLikeList; @@ -469,8 +470,8 @@ public PythonBytes repeat(PythonInteger times) { return new PythonBytes(out); } - public PythonIterator getIterator() { - return new PythonIterator<>(IntStream.range(0, value.length) + public DelegatePythonIterator getIterator() { + return new DelegatePythonIterator<>(IntStream.range(0, value.length) .mapToObj(index -> BYTE_TO_INT[value[index]]) .iterator()); } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonRange.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonRange.java index e556130..a8beaa3 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonRange.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonRange.java @@ -13,7 +13,7 @@ import ai.timefold.jpyinterpreter.PythonOverloadImplementor; import ai.timefold.jpyinterpreter.PythonUnaryOperator; import ai.timefold.jpyinterpreter.builtins.UnaryDunderBuiltin; -import ai.timefold.jpyinterpreter.types.collections.PythonIterator; +import ai.timefold.jpyinterpreter.types.collections.DelegatePythonIterator; import ai.timefold.jpyinterpreter.types.errors.ValueError; import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; @@ -164,8 +164,8 @@ public Iterator iterator() { return new RangeIterator(start, stop, step, start, 0); } - public PythonIterator getPythonIterator() { - return new PythonIterator(iterator()); + public DelegatePythonIterator getPythonIterator() { + return new DelegatePythonIterator(iterator()); } @Override diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonString.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonString.java index 9fe92d7..c6808f0 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonString.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonString.java @@ -22,6 +22,7 @@ import ai.timefold.jpyinterpreter.PythonUnaryOperator; import ai.timefold.jpyinterpreter.builtins.BinaryDunderBuiltin; import ai.timefold.jpyinterpreter.builtins.UnaryDunderBuiltin; +import ai.timefold.jpyinterpreter.types.collections.DelegatePythonIterator; import ai.timefold.jpyinterpreter.types.collections.PythonIterator; import ai.timefold.jpyinterpreter.types.collections.PythonLikeDict; import ai.timefold.jpyinterpreter.types.collections.PythonLikeList; @@ -433,8 +434,8 @@ public PythonString repeat(PythonInteger times) { return PythonString.valueOf(value.repeat(timesAsInt)); } - public PythonIterator getIterator() { - return new PythonIterator(value.chars().mapToObj(charVal -> new PythonString(Character.toString(charVal))) + public DelegatePythonIterator getIterator() { + return new DelegatePythonIterator(value.chars().mapToObj(charVal -> new PythonString(Character.toString(charVal))) .iterator()); } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/DelegatePythonIterator.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/DelegatePythonIterator.java new file mode 100644 index 0000000..32b05ef --- /dev/null +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/DelegatePythonIterator.java @@ -0,0 +1,59 @@ +package ai.timefold.jpyinterpreter.types.collections; + +import java.util.Iterator; + +import ai.timefold.jpyinterpreter.PythonLikeObject; +import ai.timefold.jpyinterpreter.PythonOverloadImplementor; +import ai.timefold.jpyinterpreter.PythonUnaryOperator; +import ai.timefold.jpyinterpreter.types.AbstractPythonLikeObject; +import ai.timefold.jpyinterpreter.types.BuiltinTypes; +import ai.timefold.jpyinterpreter.types.PythonLikeType; +import ai.timefold.jpyinterpreter.types.errors.StopIteration; + +public class DelegatePythonIterator extends AbstractPythonLikeObject implements PythonIterator { + static { + PythonOverloadImplementor.deferDispatchesFor(DelegatePythonIterator::registerMethods); + } + + private static PythonLikeType registerMethods() throws NoSuchMethodException { + BuiltinTypes.ITERATOR_TYPE.addUnaryMethod(PythonUnaryOperator.NEXT, PythonIterator.class.getMethod("nextPythonItem")); + BuiltinTypes.ITERATOR_TYPE.addUnaryMethod(PythonUnaryOperator.ITERATOR, PythonIterator.class.getMethod("getIterator")); + return BuiltinTypes.ITERATOR_TYPE; + } + + private final Iterator delegate; + + public DelegatePythonIterator(Iterator delegate) { + super(BuiltinTypes.ITERATOR_TYPE); + this.delegate = delegate; + } + + public DelegatePythonIterator(PythonLikeType type) { + super(type); + this.delegate = this; + } + + @Override + public boolean hasNext() { + return delegate.hasNext(); + } + + @Override + public T next() { + if (!delegate.hasNext()) { + throw new StopIteration(); + } + return delegate.next(); + } + + public PythonLikeObject nextPythonItem() { + if (!delegate.hasNext()) { + throw new StopIteration(); + } + return (PythonLikeObject) delegate.next(); + } + + public DelegatePythonIterator getIterator() { + return this; + } +} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonIterator.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonIterator.java index a735b29..3921cda 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonIterator.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonIterator.java @@ -3,57 +3,9 @@ import java.util.Iterator; import ai.timefold.jpyinterpreter.PythonLikeObject; -import ai.timefold.jpyinterpreter.PythonOverloadImplementor; -import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.types.AbstractPythonLikeObject; -import ai.timefold.jpyinterpreter.types.BuiltinTypes; -import ai.timefold.jpyinterpreter.types.PythonLikeType; -import ai.timefold.jpyinterpreter.types.errors.StopIteration; -public class PythonIterator extends AbstractPythonLikeObject implements Iterator { - private final Iterator delegate; +public interface PythonIterator extends PythonLikeObject, Iterator { + PythonLikeObject nextPythonItem(); - static { - PythonOverloadImplementor.deferDispatchesFor(PythonIterator::registerMethods); - } - - private static PythonLikeType registerMethods() throws NoSuchMethodException { - BuiltinTypes.ITERATOR_TYPE.addUnaryMethod(PythonUnaryOperator.NEXT, PythonIterator.class.getMethod("nextPythonItem")); - BuiltinTypes.ITERATOR_TYPE.addUnaryMethod(PythonUnaryOperator.ITERATOR, PythonIterator.class.getMethod("getIterator")); - return BuiltinTypes.ITERATOR_TYPE; - } - - public PythonIterator(PythonLikeType type) { - super(type); - this.delegate = this; - } - - public PythonIterator(Iterator delegate) { - super(BuiltinTypes.ITERATOR_TYPE); - this.delegate = delegate; - } - - @Override - public boolean hasNext() { - return delegate.hasNext(); - } - - @Override - public T next() { - if (!delegate.hasNext()) { - throw new StopIteration(); - } - return delegate.next(); - } - - public PythonLikeObject nextPythonItem() { - if (!delegate.hasNext()) { - throw new StopIteration(); - } - return (PythonLikeObject) delegate.next(); - } - - public PythonIterator getIterator() { - return this; - } + PythonIterator getIterator(); } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeDict.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeDict.java index 1bd0115..5377f0e 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeDict.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeDict.java @@ -164,8 +164,8 @@ public PythonInteger getSize() { return PythonInteger.valueOf(delegate.size()); } - public PythonIterator getKeyIterator() { - return new PythonIterator<>(delegate.keySet().iterator()); + public DelegatePythonIterator getKeyIterator() { + return new DelegatePythonIterator<>(delegate.keySet().iterator()); } public PythonLikeObject getItemOrError(PythonLikeObject key) { @@ -246,13 +246,13 @@ public PythonLikeObject popLast() { return PythonLikeTuple.fromItems(lastKey, (V) delegate.remove(lastKey)); } - public PythonIterator reversed() { + public DelegatePythonIterator reversed() { if (delegate.isEmpty()) { - return new PythonIterator<>(Collections.emptyIterator()); + return new DelegatePythonIterator<>(Collections.emptyIterator()); } final var lastKey = (PythonLikeObject) delegate.lastKey(); - return new PythonIterator<>(new Iterator<>() { + return new DelegatePythonIterator<>(new Iterator<>() { PythonLikeObject current = lastKey; @Override diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeFrozenSet.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeFrozenSet.java index 61ac2c6..fee99d6 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeFrozenSet.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeFrozenSet.java @@ -152,8 +152,8 @@ public PythonBoolean containsItem(PythonLikeObject query) { return PythonBoolean.valueOf(delegate.contains(query)); } - public PythonIterator getIterator() { - return new PythonIterator(delegate.iterator()); + public DelegatePythonIterator getIterator() { + return new DelegatePythonIterator(delegate.iterator()); } public PythonBoolean isDisjoint(PythonLikeSet other) { diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeList.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeList.java index c55e8db..a5b4c32 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeList.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeList.java @@ -139,8 +139,8 @@ public void reverseAdd(PythonLikeObject object) { remainderToAdd--; } - public PythonIterator getIterator() { - return new PythonIterator(delegate.iterator()); + public DelegatePythonIterator getIterator() { + return new DelegatePythonIterator(delegate.iterator()); } public PythonLikeList copy() { diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeSet.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeSet.java index e949e94..6eb168b 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeSet.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeSet.java @@ -409,8 +409,8 @@ public Iterator iterator() { return delegate.iterator(); } - public PythonIterator getIterator() { - return new PythonIterator(delegate.iterator()); + public DelegatePythonIterator getIterator() { + return new DelegatePythonIterator(delegate.iterator()); } @Override diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeTuple.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeTuple.java index cd5ba5b..9318011 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeTuple.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeTuple.java @@ -168,14 +168,14 @@ public PythonBoolean containsItem(PythonLikeObject item) { return PythonBoolean.valueOf(delegate.contains(item)); } - public PythonIterator getIterator() { - return new PythonIterator(delegate.iterator()); + public DelegatePythonIterator getIterator() { + return new DelegatePythonIterator(delegate.iterator()); } - public PythonIterator getReversedIterator() { + public DelegatePythonIterator getReversedIterator() { final ListIterator listIterator = delegate.listIterator(delegate.size()); - return new PythonIterator<>(new Iterator<>() { + return new DelegatePythonIterator<>(new Iterator<>() { @Override public boolean hasNext() { return listIterator.hasPrevious(); diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/view/DictItemView.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/view/DictItemView.java index c23b367..d628f6e 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/view/DictItemView.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/view/DictItemView.java @@ -17,7 +17,7 @@ import ai.timefold.jpyinterpreter.types.BuiltinTypes; import ai.timefold.jpyinterpreter.types.PythonLikeType; import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.collections.PythonIterator; +import ai.timefold.jpyinterpreter.types.collections.DelegatePythonIterator; import ai.timefold.jpyinterpreter.types.collections.PythonLikeDict; import ai.timefold.jpyinterpreter.types.collections.PythonLikeSet; import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; @@ -94,8 +94,8 @@ public PythonInteger getItemsSize() { return PythonInteger.valueOf(entrySet.size()); } - public PythonIterator getItemsIterator() { - return new PythonIterator<>( + public DelegatePythonIterator getItemsIterator() { + return new DelegatePythonIterator<>( IteratorUtils.iteratorMap(entrySet.iterator(), entry -> PythonLikeTuple.fromItems(entry.getKey(), entry.getValue()))); } @@ -113,8 +113,8 @@ public PythonBoolean containsItem(PythonLikeObject o) { } } - public PythonIterator getReversedItemIterator() { - return new PythonIterator<>(IteratorUtils.iteratorMap(mapping.reversed(), + public DelegatePythonIterator getReversedItemIterator() { + return new DelegatePythonIterator<>(IteratorUtils.iteratorMap(mapping.reversed(), key -> PythonLikeTuple.fromItems(key, (PythonLikeObject) mapping.delegate.get(key)))); } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/view/DictKeyView.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/view/DictKeyView.java index f2ee0f9..3a1f86f 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/view/DictKeyView.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/view/DictKeyView.java @@ -13,7 +13,7 @@ import ai.timefold.jpyinterpreter.types.BuiltinTypes; import ai.timefold.jpyinterpreter.types.PythonLikeType; import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.collections.PythonIterator; +import ai.timefold.jpyinterpreter.types.collections.DelegatePythonIterator; import ai.timefold.jpyinterpreter.types.collections.PythonLikeDict; import ai.timefold.jpyinterpreter.types.collections.PythonLikeSet; import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; @@ -78,15 +78,15 @@ public PythonInteger getKeysSize() { return PythonInteger.valueOf(keySet.size()); } - public PythonIterator getKeysIterator() { - return new PythonIterator<>(keySet.iterator()); + public DelegatePythonIterator getKeysIterator() { + return new DelegatePythonIterator<>(keySet.iterator()); } public PythonBoolean containsKey(PythonLikeObject key) { return PythonBoolean.valueOf(keySet.contains(key)); } - public PythonIterator getReversedKeyIterator() { + public DelegatePythonIterator getReversedKeyIterator() { return mapping.reversed(); } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/view/DictValueView.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/view/DictValueView.java index 93908ab..5a424a7 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/view/DictValueView.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/view/DictValueView.java @@ -11,7 +11,7 @@ import ai.timefold.jpyinterpreter.types.BuiltinTypes; import ai.timefold.jpyinterpreter.types.PythonLikeType; import ai.timefold.jpyinterpreter.types.PythonString; -import ai.timefold.jpyinterpreter.types.collections.PythonIterator; +import ai.timefold.jpyinterpreter.types.collections.DelegatePythonIterator; import ai.timefold.jpyinterpreter.types.collections.PythonLikeDict; import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; @@ -58,16 +58,16 @@ public PythonInteger getValuesSize() { return PythonInteger.valueOf(valueCollection.size()); } - public PythonIterator getValueIterator() { - return new PythonIterator<>(valueCollection.iterator()); + public DelegatePythonIterator getValueIterator() { + return new DelegatePythonIterator<>(valueCollection.iterator()); } public PythonBoolean containsValue(PythonLikeObject value) { return PythonBoolean.valueOf(valueCollection.contains(value)); } - public PythonIterator getReversedValueIterator() { - return new PythonIterator<>(IteratorUtils.iteratorMap(mapping.reversed(), mapping::get)); + public DelegatePythonIterator getReversedValueIterator() { + return new DelegatePythonIterator<>(IteratorUtils.iteratorMap(mapping.reversed(), mapping::get)); } public PythonString toRepresentation() { diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/PythonObjectWrapper.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/PythonObjectWrapper.java index 84e0d17..ca47d96 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/PythonObjectWrapper.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/PythonObjectWrapper.java @@ -7,23 +7,27 @@ import ai.timefold.jpyinterpreter.CPythonBackedPythonInterpreter; import ai.timefold.jpyinterpreter.PythonInterpreter; import ai.timefold.jpyinterpreter.PythonLikeObject; +import ai.timefold.jpyinterpreter.builtins.UnaryDunderBuiltin; import ai.timefold.jpyinterpreter.types.CPythonBackedPythonLikeObject; import ai.timefold.jpyinterpreter.types.PythonLikeFunction; import ai.timefold.jpyinterpreter.types.PythonLikeType; import ai.timefold.jpyinterpreter.types.PythonString; +import ai.timefold.jpyinterpreter.types.collections.PythonIterator; import ai.timefold.jpyinterpreter.types.errors.NotImplementedError; import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; public class PythonObjectWrapper extends CPythonBackedPythonLikeObject implements PythonLikeObject, - PythonLikeFunction, Comparable { + PythonLikeFunction, PythonIterator, Comparable { private final static PythonLikeType PYTHON_REFERENCE_TYPE = new PythonLikeType("python-reference", PythonObjectWrapper.class), $TYPE = PYTHON_REFERENCE_TYPE; private final Map cachedAttributeMap; + private PythonLikeObject cachedNext = null; + public PythonObjectWrapper(OpaquePythonReference pythonReference) { super(PythonInterpreter.DEFAULT, CPythonType.lookupTypeOfPythonObject(pythonReference), pythonReference); cachedAttributeMap = new HashMap<>(); @@ -130,4 +134,37 @@ public String toString() { PythonLikeObject result = str.$call(List.of(this), Map.of(), null); return result.toString(); } + + @Override + public PythonLikeObject nextPythonItem() { + if (cachedNext != null) { + var out = cachedNext; + cachedNext = null; + return out; + } + return UnaryDunderBuiltin.NEXT.invoke(this); + } + + @Override + public PythonIterator getIterator() { + return (PythonIterator) UnaryDunderBuiltin.ITERATOR.invoke(this); + } + + @Override + public boolean hasNext() { + if (cachedNext != null) { + return true; + } + try { + cachedNext = nextPythonItem(); + return true; + } catch (Exception e) { + return false; + } + } + + @Override + public PythonLikeObject next() { + return nextPythonItem(); + } } diff --git a/jpyinterpreter/src/main/python/annotations.py b/jpyinterpreter/src/main/python/annotations.py index 5bdac08..3ec55d2 100644 --- a/jpyinterpreter/src/main/python/annotations.py +++ b/jpyinterpreter/src/main/python/annotations.py @@ -62,7 +62,7 @@ def copy_type_annotations(hinted_object, default_args, vargs_name, kwargs_name): out = HashMap() try: type_hints = get_type_hints(hinted_object, include_extras=True) - except NameError: + except (AttributeError, NameError, TypeError): # Occurs if get_type_hints cannot resolve a forward reference type_hints = hinted_object.__annotations__ if hasattr(hinted_object, '__annotations__') else {} diff --git a/jpyinterpreter/src/main/python/conversions.py b/jpyinterpreter/src/main/python/conversions.py index 274ec56..45528be 100644 --- a/jpyinterpreter/src/main/python/conversions.py +++ b/jpyinterpreter/src/main/python/conversions.py @@ -1,8 +1,10 @@ import builtins import inspect +import importlib from dataclasses import dataclass from typing import TYPE_CHECKING from traceback import TracebackException, StackSummary, FrameSummary +from copy import copy from jpype import JLong, JDouble, JBoolean, JProxy @@ -297,8 +299,11 @@ def convert_object_to_java_python_like_object(value, instance_map=None): instance_map) if isinstance(out, AbstractPythonLikeObject): - for (key, value) in getattr(value, '__dict__', dict()).items(): - out.setAttribute(key, convert_to_java_python_like_object(value, instance_map)) + try: + for (key, value) in object.__getattribute__(value, '__dict__').items(): + out.setAttribute(key, convert_to_java_python_like_object(value, instance_map)) + except AttributeError: + pass return out elif inspect.isbuiltin(value) or is_c_native(value): @@ -334,8 +339,11 @@ def convert_object_to_java_python_like_object(value, instance_map=None): instance_map) if isinstance(out, AbstractPythonLikeObject): - for (key, value) in getattr(value, '__dict__', dict()).items(): - out.setAttribute(key, convert_to_java_python_like_object(value, instance_map)) + try: + for (key, value) in object.__getattribute__(value, '__dict__').items(): + out.setAttribute(key, convert_to_java_python_like_object(value, instance_map)) + except AttributeError: + pass return out except: @@ -505,11 +513,11 @@ def get_clone(self, java_object): def unwrap_python_like_object(python_like_object, clone_map=None, default=NotImplementedError): from .translator import type_to_compiled_java_class - from ai.timefold.jpyinterpreter import PythonLikeObject + from ai.timefold.jpyinterpreter import PythonLikeObject, PythonBytecodeToJavaBytecodeTranslator from java.util import List, Map, Set, Iterator, IdentityHashMap from ai.timefold.jpyinterpreter.types import PythonString, PythonBytes, PythonByteArray, PythonNone, \ PythonModule, PythonSlice, PythonRange, CPythonBackedPythonLikeObject, PythonLikeType, PythonLikeGenericType, \ - NotImplemented as JavaNotImplemented, PythonCell + NotImplemented as JavaNotImplemented, PythonCell, PythonLikeFunction from ai.timefold.jpyinterpreter.types.collections import PythonLikeTuple, PythonLikeFrozenSet from ai.timefold.jpyinterpreter.types.numeric import PythonInteger, PythonFloat, PythonBoolean, PythonComplex from ai.timefold.jpyinterpreter.types.wrappers import JavaObjectWrapper, PythonObjectWrapper, CPythonType, \ @@ -631,16 +639,18 @@ def throw(self, thrown): elif isinstance(python_like_object, PythonModule): return clone_map.add_clone(python_like_object, python_like_object.getPythonReference()) elif isinstance(python_like_object, CPythonBackedPythonLikeObject): + existing_instance = getattr(python_like_object, '$cpythonReference') if getattr(python_like_object, '$shouldCreateNewInstance')(): maybe_cpython_type = getattr(python_like_object, "$CPYTHON_TYPE") if isinstance(maybe_cpython_type, CPythonType): - out = object.__new__(maybe_cpython_type.getPythonReference()) + out = (copy(existing_instance) if existing_instance is not None + else object.__new__(maybe_cpython_type.getPythonReference())) getattr(python_like_object, '$setCPythonReference')( JProxy(OpaquePythonReference, inst=out, convert=True)) else: out = None else: - out = getattr(python_like_object, '$cpythonReference') + out = existing_instance if out is not None: clone_map.add_clone(python_like_object, out) @@ -681,6 +691,12 @@ def __str__(self): if out is not None: return out + if isinstance(python_like_object, PythonLikeFunction): + qualified_name = python_like_object.getClass().getCanonicalName()[ + len(PythonBytecodeToJavaBytecodeTranslator.USER_PACKAGE_BASE):] + module_name, _, function_name = qualified_name.rpartition('.') + return getattr(importlib.import_module(module_name), function_name) + if default == NotImplementedError: raise NotImplementedError(f'Unable to convert object of type {type(python_like_object)}') return default diff --git a/jpyinterpreter/src/main/python/jvm_setup.py b/jpyinterpreter/src/main/python/jvm_setup.py index 6389067..4835975 100644 --- a/jpyinterpreter/src/main/python/jvm_setup.py +++ b/jpyinterpreter/src/main/python/jvm_setup.py @@ -136,12 +136,7 @@ def apply(self, python_object, attribute_name, instance_map): if not hasattr(python_object, attribute_name): return None out = getattr(python_object, attribute_name) - try: - return convert_to_java_python_like_object(out, instance_map) - except Exception as e: - import traceback - traceback.print_exception(e) - raise e + return convert_to_java_python_like_object(out, instance_map) @jpype.JImplements('ai.timefold.jpyinterpreter.util.function.QuadConsumer', deferred=True) @@ -149,8 +144,12 @@ class SetAttributeOnPythonObject: @jpype.JOverride() def accept(self, python_object, clone_map, attribute_name, value): from .conversions import unwrap_python_like_object - setattr(python_object, attribute_name, unwrap_python_like_object(value, - clone_map)) + unwrapped_object = unwrap_python_like_object(value, + clone_map) + try: + setattr(python_object, attribute_name, unwrapped_object) + except: + object.__setattr__(python_object, attribute_name, unwrapped_object) @jpype.JImplements('java.util.function.BiConsumer', deferred=True) @@ -188,7 +187,6 @@ def apply(self, python_object, var_args_list, keyword_args_map): return convert_to_java_python_like_object(out) except Exception as e: from ai.timefold.jpyinterpreter.types.errors import CPythonException - print(e) raise CPythonException(str(e)) diff --git a/jpyinterpreter/tests/test_classes.py b/jpyinterpreter/tests/test_classes.py index 9bf8c8f..1efcf86 100644 --- a/jpyinterpreter/tests/test_classes.py +++ b/jpyinterpreter/tests/test_classes.py @@ -956,6 +956,50 @@ def my_method(self) -> Annotated[str, 'extra', JavaAnnotation(Deprecated, { assert annotations[0].since() == '2.0.0' +def test_extra_attributes(): + from jpyinterpreter import convert_to_java_python_like_object, unwrap_python_like_object + + class A: + pass + + a = A() + a.name = 'Name' + + converted_a = convert_to_java_python_like_object(a) + + assert getattr(converted_a, '$getAttributeOrNull')('name').value == 'Name' + + unwrapped_a = unwrap_python_like_object(converted_a) + + assert unwrapped_a.name == 'Name' + + +def function_attribute_function(): + return 10 + + +def test_function_attributes(): + from jpyinterpreter import convert_to_java_python_like_object, unwrap_python_like_object + + class A: + dispatch = { + 'my_function': function_attribute_function, + } + + def run(): + return A.dispatch['my_function']() + + a = A() + + converted_a = convert_to_java_python_like_object(a) + unwrapped_a = unwrap_python_like_object(converted_a) + + assert unwrapped_a.dispatch['my_function'] is function_attribute_function + + verifier = verifier_for(run) + verifier.verify(expected_result=10) + + def test_java_class_as_field_type(): from ai.timefold.jpyinterpreter import TypeHint from jpyinterpreter import translate_python_class_to_java_class diff --git a/jpyinterpreter/tests/test_with.py b/jpyinterpreter/tests/test_with.py new file mode 100644 index 0000000..249f440 --- /dev/null +++ b/jpyinterpreter/tests/test_with.py @@ -0,0 +1,70 @@ +from .conftest import verifier_for + + +def test_with(): + class ContextManager: + def __enter__(self): + return 'Context' + + def __exit__(self, exc_type, exc_val, exc_tb): + return None + + def my_function(): + with ContextManager() as ctx: + return ctx + + function_verifier = verifier_for(my_function) + + function_verifier.verify(expected_result='Context') + + +def test_with_exception_unhandled(): + class ContextManager: + def __enter__(self): + return 'Context' + + def __exit__(self, exc_type, exc_val, exc_tb): + return None + + def my_function(): + with ContextManager() as ctx: + raise TypeError + + function_verifier = verifier_for(my_function) + + function_verifier.verify(expected_error=TypeError) + + +def test_with_exception_unhandled_with_result(): + class ContextManager: + def __enter__(self): + return 'Context' + + def __exit__(self, exc_type, exc_val, exc_tb): + return False + + def my_function(): + with ContextManager() as ctx: + raise TypeError + + function_verifier = verifier_for(my_function) + + function_verifier.verify(expected_error=TypeError) + + +def test_with_exception_handled(): + class ContextManager: + def __enter__(self): + return 'Context' + + def __exit__(self, exc_type, exc_val, exc_tb): + return True + + def my_function(): + with ContextManager() as ctx: + raise TypeError + return 10 + + function_verifier = verifier_for(my_function) + + function_verifier.verify(expected_result=10)