Enable method overloading and overriding#70
Conversation
…int, byte, short, char)
… cases for method overriding.
…ion logic to code-generator.ts
…tatic field lookup logic.
Coverage reportCaution Test run failed
Show files with reduced coverage 🔻
Test suite run failedFailed tests: 0/1115. Failed suites: 1/64.Report generated by 🧪jest coverage report action from 805b448 |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request implements support for SwitchStatement and CastExpression, covering grammar updates, AST extraction, type checking, and bytecode generation. It also enhances method invocation to support overloading, overriding, and super calls, and enables the compiler to handle multiple class files. Feedback highlights several critical issues, including incorrect usage of the in operator and filter method in TypeScript, flawed JVM offset patching for switch instructions, and a missing string collision check in switch statements. Additionally, the reviewer identified logic errors in the final method override checks and inconsistent package naming conventions that could lead to lookup failures.
| if (!(fromType in ['J', 'D']) && toType in ['J', 'D']) { | ||
| return 1 | ||
| } else if (!(toType in ['J', 'D']) && fromType in ['J', 'D']) { |
There was a problem hiding this comment.
In JavaScript/TypeScript, the in operator checks for the existence of a property name in an object. For an array, it checks for indices (e.g., '0', '1'), not values. Use Array.prototype.includes() instead.
| if (!(fromType in ['J', 'D']) && toType in ['J', 'D']) { | |
| return 1 | |
| } else if (!(toType in ['J', 'D']) && fromType in ['J', 'D']) { | |
| if (!['J', 'D'].includes(fromType) && ['J', 'D'].includes(toType)) { | |
| return 1 | |
| } else if (!['J', 'D'].includes(toType) && ['J', 'D'].includes(fromType)) { | |
| return -1 |
| candidateMethods.filter(method => | ||
| method.className == cg.symbolTable.queryClass(cg.currentClass).parentClassName) |
There was a problem hiding this comment.
The filter method returns a new array and does not mutate the original one. You must assign the result back to candidateMethods.
| candidateMethods.filter(method => | |
| method.className == cg.symbolTable.queryClass(cg.currentClass).parentClassName) | |
| candidateMethods = candidateMethods.filter(method => | |
| method.className == cg.symbolTable.queryClass(cg.currentClass).parentClassName) |
| candidateMethods.filter(method => | ||
| method.className == cg.currentClass) |
| cg.code[caseLabelIndex[0]] = caseLabels[caseLabels.length - 1].offset - positionOffset | ||
|
|
||
| for (let i = 1; i < caseLabelIndex.length; i++) { | ||
| cg.code[caseLabelIndex[i]] = caseLabels[i - 1].offset - positionOffset | ||
| } |
There was a problem hiding this comment.
The patching logic for switch offsets is incorrect.
- JVM offsets for
TABLESWITCHandLOOKUPSWITCHare 4-byte signed integers. Assigning to a single index incg.code(which is a byte array) only updates the last byte of the offset. - The mapping logic seems reversed or misaligned:
caseLabelIndex[0]is being patched with the offset of the last label incaseLabels. caseLabelIndexcontains indices relative to the start of the code, but the mapping should correspond to the specific case values or hash codes defined in the switch table.
| // TODO: check for actual String equality instead of just rely on hashCode equality | ||
| // (see the commented out code below) |
| for (let i = this.curClassIdx - 1; i > 0; i--) { | ||
| const parentTable = this.tables[i]; | ||
| if (parentTable.has(key)) { | ||
| const parentMethods = parentTable.get(key)!.info; | ||
| if (Array.isArray(parentMethods)) { | ||
| for (const m of parentMethods) { | ||
| if (m.typeDescriptor === info.typeDescriptor && (m.accessFlags & METHOD_FLAGS.ACC_FINAL) | ||
| && m.className == info.parentClassName) { | ||
| throw new OverrideFinalMethodError(info.name); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
This loop iterates through the scope stack (this.tables), but in your implementation, parent classes are not ancestors in this stack; they are stored as siblings in the root table. To correctly check if a method overrides a final method in a superclass, you should follow the parentClassName hierarchy and look up the corresponding class in the root table.
| switchBlockStatementGroup(ctx: SwitchBlockStatementGroupCtx): SwitchCase { | ||
| const blockStatementExtractor = new BlockStatementExtractor(); | ||
|
|
||
| console.log(ctx.switchLabel) |
|
|
||
| if (receiverStr === 'this') { | ||
| candidateMethods = cg.symbolTable.queryMethod(n.identifier.slice(5)) as MethodInfos | ||
| console.debug(candidateMethods) |
| if (this.importedPackages.findIndex(e => e == p.packageName + '/') == -1) | ||
| this.importedPackages.push(p.packageName + '/') |
There was a problem hiding this comment.
There is an inconsistency in package naming. Here you are using dots (e.g., java.lang/), while in handleImports (line 152) you use slashes (e.g., java/lang/). JVM internal names and descriptors typically use slashes. This inconsistency will likely cause queryClass to fail when looking up library classes.
Enable running multiple classes in java-slang.
Enable method overloading and overriding to support code as follows:
and