Skip to content

Commit 850ff44

Browse files
feat: Add file and line numbers to exception traceback (#28)
- CPython does not expose any sane way to set the traceback of an exception that does not orginate from a CPython exception. In particular, __traceback__ must be an internal Traceback type, and that Traceback type cannot be constructed without using internal frame and code types, which also cannot be constructed. - To get around this, we exploit the traceback module, which does not care about types and only care about interfaces. This allow us to use our own fake frame and code classes that match their interface. - We exploit object.__new__ to create an instance of TracebackException without calling its constructor, and set all its fields manually - To set the stack field, we use StackSummary.from_list. with the Java traceback. - Python 3.10 has a quirk: it calls RERAISE in the finally block corresponding to an except block. That finally immediately calls RERAISE, but TOS is a type, so that exception loses both it cause and message. So work around this, we deviate a bit from how the code apparently works - After finally branch is taken, stack is <old stack> <block (3 items)> traceback, exeception, exception instead of <old stack> <block (3 items)> traceback, exeception, type - JUMP_IF_NOT_EXEC_MATCH is now an instanceof instead of issubclass This works since Python pops off all these values when actually entering the code for an except block, and JUMP_IF_NOT_EXEC_MATCH is the only opcode that can be encountered. - Python 3.10 has a quirk: it calls RERAISE in the finally block corresponding to an except block. That finally immediately calls RERAISE, but TOS is a type, so that exception loses both it cause and message. So work around this, we deviate a bit from how the code apparenty works - After finally branch is taken, stack is <old stack> <block (3 items)> traceback, exeception, exception instead of <old stack> <block (3 items)> traceback, exeception, type - JUMP_IF_NOT_EXEC_MATCH is now an instanceof instead of issubclass This works since Python pops off all these values when actually entering the code for an except block, and JUMP_IF_NOT_EXEC_MATCH is the only opcode that can be encountered. - Fix a bug in generating traceback - Make it more clear that locals refer to the built-in function - Update CI after CI changes in Timefold Solver
1 parent 5f4c79d commit 850ff44

File tree

19 files changed

+471
-141
lines changed

19 files changed

+471
-141
lines changed

.github/workflows/pull_request.yml

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -29,28 +29,23 @@ jobs:
2929

3030
steps:
3131
# Need to check for stale repo, since Github is not aware of the build chain and therefore doesn't automate it.
32-
- name: Checkout timefold-solver to access the scripts
32+
- name: Checkout timefold-solver (PR) # Checkout the PR branch first, if it exists
33+
id: checkout-solver
3334
uses: actions/checkout@v4
35+
continue-on-error: true
3436
with:
35-
path: './timefold-solver'
36-
repository: 'TimefoldAI/timefold-solver'
37-
- name: Find the proper timefold-solver repo and branch
38-
env:
39-
CHAIN_USER: ${{ github.event.pull_request.head.repo.owner.login }}
40-
CHAIN_BRANCH: ${{ github.head_ref }}
41-
CHAIN_REPO: "timefold-solver"
42-
CHAIN_DEFAULT_BRANCH: ${{ endsWith(github.head_ref, '.x') && github.head_ref || 'main' }}
43-
shell: bash
44-
run: |
45-
./timefold-solver/.github/scripts/check_chain_repo.sh
46-
rm -rf ./timefold-solver
47-
- name: Checkout the proper timefold-solver branch
37+
repository: ${{ github.actor }}/timefold-solver
38+
ref: ${{ github.head_ref }}
39+
path: ./timefold-solver
40+
fetch-depth: 0 # Otherwise merge will fail on account of not having history.
41+
- name: Checkout timefold-solver (main) # Checkout the main branch if the PR branch does not exist
42+
if: steps.checkout-solver.outcome != 'success'
4843
uses: actions/checkout@v4
4944
with:
50-
repository: ${{ env.TARGET_CHAIN_USER }}/${{ env.TARGET_CHAIN_REPO }}
51-
ref: ${{ env.TARGET_CHAIN_BRANCH }}
52-
path: './timefold-solver'
53-
fetch-depth: 0 # Otherwise merge in the next step will fail on account of not having history.
45+
repository: TimefoldAI/timefold-solver
46+
ref: main
47+
path: ./timefold-solver
48+
fetch-depth: 0 # Otherwise merge will fail on account of not having history.
5449
- name: Prevent stale fork of timefold-solver
5550
env:
5651
BLESSED_REPO: "timefold-solver"

.github/workflows/sonarcloud.yml

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,28 +23,23 @@ jobs:
2323

2424
steps:
2525
# Need to check for stale repo, since Github is not aware of the build chain and therefore doesn't automate it.
26-
- name: Checkout timefold-solver to access the scripts
26+
- name: Checkout timefold-solver (PR) # Checkout the PR branch first, if it exists
27+
id: checkout-solver
2728
uses: actions/checkout@v4
29+
continue-on-error: true
2830
with:
29-
path: './timefold-solver'
30-
repository: 'TimefoldAI/timefold-solver'
31-
- name: Find the proper timefold-solver repo and branch
32-
env:
33-
CHAIN_USER: ${{ github.event.pull_request.head.repo.owner.login }}
34-
CHAIN_BRANCH: ${{ github.head_ref }}
35-
CHAIN_REPO: "timefold-solver"
36-
CHAIN_DEFAULT_BRANCH: ${{ endsWith(github.head_ref, '.x') && github.head_ref || 'main' }}
37-
shell: bash
38-
run: |
39-
./timefold-solver/.github/scripts/check_chain_repo.sh
40-
rm -rf ./timefold-solver
41-
- name: Checkout the proper timefold-solver branch
31+
repository: ${{ github.actor }}/timefold-solver
32+
ref: ${{ github.head_ref }}
33+
path: ./timefold-solver
34+
fetch-depth: 0 # Otherwise merge will fail on account of not having history.
35+
- name: Checkout timefold-solver (main) # Checkout the main branch if the PR branch does not exist
36+
if: steps.checkout-solver.outcome != 'success'
4237
uses: actions/checkout@v4
4338
with:
44-
repository: ${{ env.TARGET_CHAIN_USER }}/${{ env.TARGET_CHAIN_REPO }}
45-
ref: ${{ env.TARGET_CHAIN_BRANCH }}
46-
path: './timefold-solver'
47-
fetch-depth: 0 # Otherwise merge in the next step will fail on account of not having history.
39+
repository: TimefoldAI/timefold-solver
40+
ref: main
41+
path: ./timefold-solver
42+
fetch-depth: 0 # Otherwise merge will fail on account of not having history.
4843
- name: Prevent stale fork of timefold-solver
4944
env:
5045
BLESSED_REPO: "timefold-solver"

jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonBytecodeToJavaBytecodeTranslator.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,8 @@ public static <T> Class<T> translatePythonBytecodeToClass(PythonCompiledFunction
245245
final boolean isPythonLikeFunction =
246246
methodDescriptor.getDeclaringClassInternalName().equals(Type.getInternalName(PythonLikeFunction.class));
247247

248+
classWriter.visitSource(pythonCompiledFunction.moduleFilePath, null);
249+
248250
createFields(classWriter);
249251
createConstructor(classWriter, internalClassName);
250252

@@ -289,6 +291,8 @@ public static <T> Class<T> translatePythonBytecodeToClass(PythonCompiledFunction
289291
final boolean isPythonLikeFunction =
290292
methodDescriptor.getDeclaringClassInternalName().equals(Type.getInternalName(PythonLikeFunction.class));
291293

294+
classWriter.visitSource(pythonCompiledFunction.moduleFilePath, null);
295+
292296
createFields(classWriter);
293297
createConstructor(classWriter, internalClassName);
294298

@@ -308,6 +312,7 @@ public static <T> Class<T> translatePythonBytecodeToClass(PythonCompiledFunction
308312
null);
309313

310314
methodVisitor.visitCode();
315+
visitGeneratedLineNumber(methodVisitor);
311316
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
312317
for (int i = 0; i < methodWithoutGenerics.getParameterCount(); i++) {
313318
Type parameterType = Type.getType(methodWithoutGenerics.getParameterTypes()[i]);
@@ -349,6 +354,8 @@ public static <T> Class<T> translatePythonBytecodeToPythonWrapperClass(PythonCom
349354
classWriter.visit(Opcodes.V11, Modifier.PUBLIC, internalClassName, null, Type.getInternalName(Object.class),
350355
new String[] { Type.getInternalName(PythonLikeFunction.class) });
351356

357+
classWriter.visitSource(pythonCompiledFunction.moduleFilePath, null);
358+
352359
createFields(classWriter);
353360
classWriter.visitField(Modifier.PUBLIC | Modifier.STATIC, PYTHON_WRAPPER_CODE_STATIC_FIELD_NAME,
354361
Type.getDescriptor(OpaquePythonReference.class),
@@ -368,6 +375,7 @@ public static <T> Class<T> translatePythonBytecodeToPythonWrapperClass(PythonCom
368375
null);
369376

370377
methodVisitor.visitCode();
378+
visitGeneratedLineNumber(methodVisitor);
371379
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
372380
methodVisitor.visitFieldInsn(Opcodes.GETFIELD, internalClassName, PYTHON_WRAPPER_FUNCTION_INSTANCE_FIELD_NAME,
373381
Type.getDescriptor(PythonObjectWrapper.class));
@@ -417,6 +425,8 @@ public static <T> Class<T> forceTranslatePythonBytecodeToGeneratorClass(PythonCo
417425
classWriter.visit(Opcodes.V11, Modifier.PUBLIC, internalClassName, null, Type.getInternalName(Object.class),
418426
new String[] { methodDescriptor.getDeclaringClassInternalName() });
419427

428+
classWriter.visitSource(pythonCompiledFunction.moduleFilePath, null);
429+
420430
final boolean isPythonLikeFunction =
421431
methodDescriptor.getDeclaringClassInternalName().equals(Type.getInternalName(PythonLikeFunction.class));
422432

@@ -460,6 +470,7 @@ public static <T> Class<T> forceTranslatePythonBytecodeToGeneratorClass(PythonCo
460470
null);
461471

462472
methodVisitor.visitCode();
473+
visitGeneratedLineNumber(methodVisitor);
463474
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
464475
for (int i = 0; i < methodWithoutGenerics.getParameterCount(); i++) {
465476
Type parameterType = Type.getType(methodWithoutGenerics.getParameterTypes()[i]);
@@ -494,6 +505,7 @@ private static void createConstructor(ClassWriter classWriter, String className)
494505
null, null);
495506
methodVisitor.visitCode();
496507

508+
visitGeneratedLineNumber(methodVisitor);
497509
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
498510
methodVisitor.visitInsn(Opcodes.DUP);
499511
methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(Object.class), "<init>",
@@ -574,6 +586,8 @@ private static void createConstructor(ClassWriter classWriter, String className)
574586
Type.getType(PythonInterpreter.class)),
575587
null, null);
576588
methodVisitor.visitCode();
589+
590+
visitGeneratedLineNumber(methodVisitor);
577591
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
578592
methodVisitor.visitInsn(Opcodes.DUP);
579593
methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(Object.class), "<init>",
@@ -647,6 +661,7 @@ private static void createPythonWrapperConstructor(ClassWriter classWriter, Stri
647661
null, null);
648662
methodVisitor.visitCode();
649663

664+
visitGeneratedLineNumber(methodVisitor);
650665
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
651666
methodVisitor.visitInsn(Opcodes.DUP);
652667
methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(Object.class), "<init>",
@@ -759,6 +774,8 @@ private static void createPythonWrapperConstructor(ClassWriter classWriter, Stri
759774
Type.getType(PythonInterpreter.class)),
760775
null, null);
761776
methodVisitor.visitCode();
777+
778+
visitGeneratedLineNumber(methodVisitor);
762779
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
763780
methodVisitor.visitInsn(Opcodes.DUP);
764781
methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(Object.class), "<init>",
@@ -1007,6 +1024,7 @@ private static void translatePythonBytecodeToMethod(MethodDescriptor method, Str
10071024
}
10081025
methodVisitor.visitCode();
10091026

1027+
visitGeneratedLineNumber(methodVisitor);
10101028
Label start = new Label();
10111029
Label end = new Label();
10121030

@@ -1172,6 +1190,12 @@ public static void writeInstructionsForOpcodes(FunctionMetadata functionMetadata
11721190
methodVisitor.visitLabel(label);
11731191
}
11741192

1193+
if (instruction.startsLine().isPresent()) {
1194+
Label label = new Label();
1195+
methodVisitor.visitLabel(label);
1196+
methodVisitor.visitLineNumber(instruction.startsLine().getAsInt(), label);
1197+
}
1198+
11751199
runAfterLabelAndBeforeArgumentors.accept(instruction);
11761200

11771201
bytecodeIndexToArgumentorsMap.getOrDefault(instruction.offset(), Collections.emptyList()).forEach(Runnable::run);
@@ -1361,4 +1385,10 @@ public static String getPythonBytecodeListing(PythonCompiledFunction pythonCompi
13611385
out.append("\nco_exceptiontable = ").append(pythonCompiledFunction.co_exceptiontable).append("\n");
13621386
return out.toString();
13631387
}
1388+
1389+
public static void visitGeneratedLineNumber(MethodVisitor methodVisitor) {
1390+
Label label = new Label();
1391+
methodVisitor.visitLabel(label);
1392+
methodVisitor.visitLineNumber(0, label);
1393+
}
13641394
}

jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonClassTranslator.java

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,8 @@ public static PythonLikeType translatePythonClass(PythonCompiledClass pythonComp
168168
classWriter.visit(Opcodes.V11, Modifier.PUBLIC, internalClassName, null,
169169
superClassType.getJavaTypeInternalName(), interfaces);
170170

171+
classWriter.visitSource(pythonCompiledClass.moduleFilePath, null);
172+
171173
for (var annotation : pythonCompiledClass.annotations) {
172174
annotation.addAnnotationTo(classWriter);
173175
}
@@ -236,6 +238,7 @@ public static PythonLikeType translatePythonClass(PythonCompiledClass pythonComp
236238
classWriter.visitMethod(Modifier.PUBLIC, "<init>", Type.getMethodDescriptor(Type.VOID_TYPE),
237239
null, null);
238240
methodVisitor.visitCode();
241+
PythonBytecodeToJavaBytecodeTranslator.visitGeneratedLineNumber(methodVisitor);
239242
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
240243
methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(PythonInterpreter.class), "DEFAULT",
241244
Type.getDescriptor(PythonInterpreter.class));
@@ -259,6 +262,7 @@ public static PythonLikeType translatePythonClass(PythonCompiledClass pythonComp
259262
methodVisitor.visitParameter("subclassType", 0);
260263

261264
methodVisitor.visitCode();
265+
PythonBytecodeToJavaBytecodeTranslator.visitGeneratedLineNumber(methodVisitor);
262266
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
263267
methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
264268
methodVisitor.visitVarInsn(Opcodes.ALOAD, 2);
@@ -541,6 +545,8 @@ private static Class<?> createPythonWrapperMethod(String methodName, PythonCompi
541545
classWriter.visit(Opcodes.V11, Modifier.PUBLIC, internalClassName, null,
542546
Type.getInternalName(Object.class), new String[] { interfaceDeclaration.interfaceName });
543547

548+
classWriter.visitSource("<generated signature>", null);
549+
544550
classWriter.visitField(Modifier.PUBLIC | Modifier.FINAL, "$binaryType", Type.getDescriptor(PythonLikeType.class),
545551
null, null);
546552
classWriter.visitField(Modifier.STATIC | Modifier.PUBLIC, ARGUMENT_SPEC_INSTANCE_FIELD_NAME,
@@ -554,7 +560,7 @@ private static Class<?> createPythonWrapperMethod(String methodName, PythonCompi
554560
null, null);
555561

556562
methodVisitor.visitCode();
557-
563+
PythonBytecodeToJavaBytecodeTranslator.visitGeneratedLineNumber(methodVisitor);
558564
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
559565
methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(Object.class), "<init>",
560566
Type.getMethodDescriptor(Type.VOID_TYPE), false);
@@ -578,7 +584,7 @@ private static Class<?> createPythonWrapperMethod(String methodName, PythonCompi
578584
}
579585

580586
methodVisitor.visitCode();
581-
587+
PythonBytecodeToJavaBytecodeTranslator.visitGeneratedLineNumber(methodVisitor);
582588
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
583589
methodVisitor.visitFieldInsn(Opcodes.GETFIELD, internalClassName, "$binaryType",
584590
Type.getDescriptor(PythonLikeType.class));
@@ -651,6 +657,8 @@ private static PythonLikeFunction createConstructor(String classInternalName,
651657
Type.getInternalName(PythonLikeFunction.class)
652658
});
653659

660+
classWriter.visitSource(initFunction != null ? initFunction.moduleFilePath : "<unknown>", null);
661+
654662
classWriter.visitField(Modifier.STATIC | Modifier.PUBLIC, ARGUMENT_SPEC_INSTANCE_FIELD_NAME,
655663
Type.getDescriptor(ArgumentSpec.class),
656664
null, null);
@@ -659,6 +667,7 @@ private static PythonLikeFunction createConstructor(String classInternalName,
659667
classWriter.visitMethod(Modifier.PUBLIC, "<init>", Type.getMethodDescriptor(Type.VOID_TYPE),
660668
null, null);
661669
methodVisitor.visitCode();
670+
PythonBytecodeToJavaBytecodeTranslator.visitGeneratedLineNumber(methodVisitor);
662671
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
663672
methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(Object.class), "<init>",
664673
Type.getMethodDescriptor(Type.VOID_TYPE), false);
@@ -674,13 +683,16 @@ private static PythonLikeFunction createConstructor(String classInternalName,
674683
null, null);
675684

676685
methodVisitor.visitCode();
677-
686+
PythonBytecodeToJavaBytecodeTranslator.visitGeneratedLineNumber(methodVisitor);
678687
methodVisitor.visitTypeInsn(Opcodes.NEW, classInternalName);
679688
methodVisitor.visitInsn(Opcodes.DUP);
680689
methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, classInternalName, "<init>",
681690
Type.getMethodDescriptor(Type.VOID_TYPE), false);
682691

683692
if (initFunction != null) {
693+
Label start = new Label();
694+
methodVisitor.visitLabel(start);
695+
methodVisitor.visitLineNumber(initFunction.getFirstLine(), start);
684696
methodVisitor.visitInsn(Opcodes.DUP);
685697

686698
methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, constructorInternalClassName, ARGUMENT_SPEC_INSTANCE_FIELD_NAME,
@@ -844,6 +856,10 @@ private static void createClassMethod(PythonLikeType pythonLikeType, ClassWriter
844856
addAnnotationsToMethod(function, methodVisitor);
845857
methodVisitor.visitCode();
846858

859+
Label start = new Label();
860+
methodVisitor.visitLabel(start);
861+
methodVisitor.visitLineNumber(function.getFirstLine(), start);
862+
847863
methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, internalClassName, javaMethodName, interfaceDescriptor);
848864

849865
for (int i = 0; i < function.totalArgCount(); i++) {
@@ -880,6 +896,10 @@ private static void createInstanceOrStaticMethodBody(String internalClassName, S
880896
addAnnotationsToMethod(function, methodVisitor);
881897
methodVisitor.visitCode();
882898

899+
Label start = new Label();
900+
methodVisitor.visitLabel(start);
901+
methodVisitor.visitLineNumber(function.getFirstLine(), start);
902+
883903
methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, internalClassName, javaMethodName, interfaceDescriptor);
884904
for (int i = 0; i < function.totalArgCount(); i++) {
885905
methodVisitor.visitVarInsn(Opcodes.ALOAD, i);
@@ -929,6 +949,8 @@ public static void createGetAttribute(ClassWriter classWriter, String classInter
929949

930950
methodVisitor.visitCode();
931951

952+
PythonBytecodeToJavaBytecodeTranslator.visitGeneratedLineNumber(methodVisitor);
953+
932954
methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
933955
BytecodeSwitchImplementor.createStringSwitch(methodVisitor, instanceAttributes, 2, field -> {
934956
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
@@ -976,6 +998,8 @@ public static void createSetAttribute(ClassWriter classWriter, String classInter
976998

977999
methodVisitor.visitCode();
9781000

1001+
PythonBytecodeToJavaBytecodeTranslator.visitGeneratedLineNumber(methodVisitor);
1002+
9791003
methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
9801004
BytecodeSwitchImplementor.createStringSwitch(methodVisitor, instanceAttributes, 3, field -> {
9811005
var type = fieldToType.get(field);
@@ -1023,6 +1047,8 @@ public static void createDeleteAttribute(ClassWriter classWriter, String classIn
10231047

10241048
methodVisitor.visitCode();
10251049

1050+
PythonBytecodeToJavaBytecodeTranslator.visitGeneratedLineNumber(methodVisitor);
1051+
10261052
methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
10271053
BytecodeSwitchImplementor.createStringSwitch(methodVisitor, instanceAttributes, 2, field -> {
10281054
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
@@ -1063,6 +1089,8 @@ public static void createReadFromCPythonReference(ClassWriter classWriter, Strin
10631089
null);
10641090
methodVisitor.visitCode();
10651091

1092+
PythonBytecodeToJavaBytecodeTranslator.visitGeneratedLineNumber(methodVisitor);
1093+
10661094
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
10671095
methodVisitor.visitInsn(Opcodes.DUP);
10681096
methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superClassInternalName,
@@ -1157,6 +1185,8 @@ public static void createWriteToCPythonReference(ClassWriter classWriter, String
11571185
null);
11581186
methodVisitor.visitCode();
11591187

1188+
PythonBytecodeToJavaBytecodeTranslator.visitGeneratedLineNumber(methodVisitor);
1189+
11601190
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
11611191
methodVisitor.visitInsn(Opcodes.DUP);
11621192
methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
@@ -1305,6 +1335,8 @@ public static InterfaceDeclaration createInterfaceForFunctionSignature(FunctionS
13051335
classWriter.visit(Opcodes.V11, Modifier.PUBLIC | Modifier.INTERFACE | Modifier.ABSTRACT, internalClassName, null,
13061336
Type.getInternalName(Object.class), null);
13071337

1338+
classWriter.visitSource("<generated signature>", null);
1339+
13081340
Type returnType = Type.getType(functionSignature.returnType);
13091341
Type[] parameterTypes = new Type[functionSignature.parameterTypes.length];
13101342
for (int i = 0; i < parameterTypes.length; i++) {

jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonCompiledClass.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ public class PythonCompiledClass {
1414
*/
1515
public String module;
1616

17+
/**
18+
* The path to the file that defines the module.
19+
*/
20+
public String moduleFilePath;
21+
1722
/**
1823
* The qualified name of the class. Does not include module.
1924
*/

0 commit comments

Comments
 (0)