Skip to content

Commit 3ccfb8d

Browse files
committed
Fix failing array testcases for typechecker
1 parent f499353 commit 3ccfb8d

File tree

8 files changed

+118
-23
lines changed

8 files changed

+118
-23
lines changed

src/ast/astExtractor/expression-extractor.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,18 @@ import {
2424
UnaryExpressionCstNode,
2525
UnaryExpressionCtx,
2626
UnqualifiedClassInstanceCreationExpressionCtx,
27+
ArrayAccessSuffixCtx,
2728
} from "java-parser";
2829
import { Location } from "../types/ast";
2930
import {
31+
ArrayAccess,
3032
Assignment,
3133
BinaryExpression,
3234
ClassInstanceCreationExpression,
3335
Expression,
3436
ExpressionName,
3537
MethodInvocation,
38+
Primary,
3639
} from "../types/blocks-and-statements";
3740

3841
export class ExpressionExtractor extends BaseJavaCstVisitorWithDefaults {
@@ -201,10 +204,24 @@ export class ExpressionExtractor extends BaseJavaCstVisitorWithDefaults {
201204
return node;
202205
}
203206

204-
primary(ctx: PrimaryCtx) {
207+
primary(ctx: PrimaryCtx): Primary {
205208
let primary = this.visit(ctx.primaryPrefix);
206-
207209
if (ctx.primarySuffix) {
210+
const lastSuffix = ctx.primarySuffix[ctx.primarySuffix.length - 1];
211+
if (lastSuffix.children.arrayAccessSuffix) {
212+
const newPrimaryCtx: PrimaryCtx = { primaryPrefix: ctx.primaryPrefix };
213+
if (ctx.primarySuffix.length - 1 > 0) {
214+
const newSuffixArray = ctx.primarySuffix.filter(
215+
(_, index) => index !== ctx.primarySuffix!.length - 1
216+
);
217+
newPrimaryCtx.primarySuffix = newSuffixArray;
218+
}
219+
return {
220+
...this.visit(lastSuffix.children.arrayAccessSuffix),
221+
primary: this.primary(newPrimaryCtx) as Primary,
222+
};
223+
}
224+
208225
for (const s of ctx.primarySuffix.filter(
209226
(s) => !s.children.methodInvocationSuffix
210227
)) {
@@ -417,4 +434,12 @@ export class ExpressionExtractor extends BaseJavaCstVisitorWithDefaults {
417434
parenthesisExpression(ctx: ParenthesisExpressionCtx) {
418435
return this.visit(ctx.expression);
419436
}
437+
438+
arrayAccessSuffix(ctx: ArrayAccessSuffixCtx): Omit<ArrayAccess, "primary"> {
439+
const expresionExtractor = new ExpressionExtractor();
440+
return {
441+
kind: "ArrayAccess",
442+
expression: expresionExtractor.extract(ctx.expression[0]),
443+
};
444+
}
420445
}

src/ast/astExtractor/type-extractor.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export class TypeExtractor extends BaseJavaCstVisitorWithDefaults {
8787
return ctx.Identifier[0].image;
8888
}
8989

90-
dims(_: DimsCtx) {
91-
return "[]";
90+
dims(ctx: DimsCtx) {
91+
return "[]".repeat(ctx.LSquare.length);
9292
}
9393
}

src/types/checker/__tests__/arrays.test.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ const testcases: {
1919
{
2020
input: `int[] numbers = {1, 2, 3, 4, 5};`,
2121
result: { type: null, errors: [] },
22-
only: true,
2322
},
2423
{
2524
input: `double[] values;`,
@@ -46,6 +45,13 @@ const testcases: {
4645
`,
4746
result: { type: null, errors: [] },
4847
},
48+
{
49+
input: `
50+
int[][] numbers = {{1, 2, 3, 4, 5}};
51+
int number = numbers[0][2]; // Accessing the third nested element
52+
`,
53+
result: { type: null, errors: [] },
54+
},
4955
{
5056
input: `
5157
String[] names = {"Alice", "Bob", "Charlie"};

src/types/checker/index.ts

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
import { ExpressionName } from "../../ast/types/blocks-and-statements";
1+
import {
2+
ExpressionName,
3+
VariableInitializer,
4+
} from "../../ast/types/blocks-and-statements";
25
import { Frame } from "./environment";
36
import { Method } from "../types/methods";
47
import { Node } from "../../ast/types/ast";
5-
import { String } from "../types/nonPrimitives";
8+
import { Integer, String } from "../types/nonPrimitives";
69
import { Type } from "../types/type";
710
import {
11+
ArrayRequiredError,
812
BadOperandTypesError,
913
IncompatibleTypesError,
1014
VariableAlreadyDefinedError,
@@ -25,6 +29,7 @@ import {
2529
getFloatType,
2630
getNumberType,
2731
} from "../types/primitives";
32+
import { Array as ArrayType } from "../types/arrays";
2833

2934
export type Result = {
3035
currentType: Type | null;
@@ -43,6 +48,28 @@ export const check = (
4348
frame: Frame = Frame.globalFrame()
4449
): Result => {
4550
switch (node.kind) {
51+
case "ArrayAccess": {
52+
const primaryCheck = check(node.primary, frame);
53+
if (primaryCheck.errors.length > 0)
54+
return newResult(null, primaryCheck.errors);
55+
if (!(primaryCheck.currentType instanceof ArrayType))
56+
return newResult(null, [new ArrayRequiredError()]);
57+
const expressionCheck = check(node.expression, frame);
58+
if (expressionCheck.errors.length > 0)
59+
return newResult(null, expressionCheck.errors);
60+
if (!expressionCheck.currentType)
61+
throw new Error("Expression check should return a type.");
62+
const integerType = new Integer();
63+
const intType = new Int();
64+
if (
65+
!(
66+
integerType.equals(expressionCheck.currentType) ||
67+
intType.equals(expressionCheck.currentType)
68+
)
69+
)
70+
return newResult(null, [new IncompatibleTypesError()]);
71+
return newResult(primaryCheck.currentType.getContentType());
72+
}
4673
case "Assignment": {
4774
const left = node.left as ExpressionName;
4875
const right = node.right;
@@ -253,31 +280,51 @@ export const check = (
253280
case "LocalVariableDeclarationStatement": {
254281
if (!node.variableDeclaratorList)
255282
throw new Error("Variable declarator list is undefined.");
283+
const errors: Error[] = [];
256284
const results = node.variableDeclaratorList.map((variableDeclarator) => {
257285
const declaredType = frame.getType(node.localVariableType);
258286
if (declaredType instanceof Error)
259287
return newResult(null, [declaredType]);
260288
const { variableInitializer } = variableDeclarator;
261-
if (!variableInitializer)
262-
throw new Error("Variable initializer is undefined.");
263-
if (Array.isArray(variableInitializer)) {
264-
// Is array initializer
265-
} else {
266-
// Is not array initializer
267-
const { currentType, errors } = check(variableInitializer, frame);
268-
if (errors.length > 0) return { currentType: null, errors };
269-
if (currentType == null)
270-
throw new Error(
271-
"Variable initializer in local variable declaration statement should return a type."
272-
);
273-
if (!declaredType.canBeAssigned(currentType))
274-
return newResult(null, [new IncompatibleTypesError()]);
289+
if (variableInitializer) {
290+
const checkInitialization = (
291+
declaredType: Type | ArrayType,
292+
variableInitializer: VariableInitializer
293+
): void => {
294+
if (errors.length > 0) return;
295+
if (!Array.isArray(variableInitializer)) {
296+
if (declaredType instanceof ArrayType) {
297+
errors.push(new IncompatibleTypesError());
298+
return;
299+
}
300+
const checkResult = check(variableInitializer, frame);
301+
if (checkResult.errors.length > 0) {
302+
errors.push(...checkResult.errors);
303+
return;
304+
}
305+
if (checkResult.currentType == null)
306+
throw new Error("Variable initializer should return a type.");
307+
if (!declaredType.canBeAssigned(checkResult.currentType))
308+
errors.push(new IncompatibleTypesError());
309+
return;
310+
}
311+
if (!(declaredType instanceof ArrayType)) {
312+
errors.push(new IncompatibleTypesError());
313+
return;
314+
}
315+
const arrayContentType = declaredType.getContentType();
316+
variableInitializer.forEach((variableInitializer) => {
317+
checkInitialization(arrayContentType, variableInitializer);
318+
});
319+
};
320+
checkInitialization(declaredType, variableInitializer);
275321
}
322+
if (errors.length > 0) return newResult(null, errors);
276323
const error = frame.setVariable(
277324
variableDeclarator.variableDeclaratorId,
278325
declaredType
279326
);
280-
if (error) return newResult(null, [error]);
327+
if (error) return newResult(null, [...errors, error]);
281328
return OK_RESULT;
282329
});
283330
return results.reduce((previousResult, currentResult) => {
@@ -472,6 +519,7 @@ export const check = (
472519
return OK_RESULT;
473520
}
474521
default:
522+
console.log(node);
475523
throw new Error(
476524
`Check is not implemented for this type of node ${node.kind}.`
477525
);

src/types/errors.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
export class ArrayRequiredError extends Error {
2+
constructor() {
3+
super("array required");
4+
}
5+
}
6+
17
export class BadOperandTypesError extends Error {
28
constructor() {
39
super("bad operand");

src/types/inputs/Main.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
public class Main {
22
public static void main(String[] args) {
3-
int test1, test2 = 1, 2;
3+
int test = 3;
4+
int[] numbers = {1, 2, 3, 4, 5};
5+
int number = test[2]; // Accessing the third element
46
}
57
}

src/types/types/arrays.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,8 @@ export class Array extends Type {
1111
if (!(type instanceof Array)) return false;
1212
return this._type.equals(type._type);
1313
}
14+
15+
public getContentType(): Type {
16+
return this._type;
17+
}
1418
}

src/types/types/primitives.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,10 @@ export class Int extends Type {
149149
type instanceof Byte
150150
);
151151
}
152+
153+
public equals(object: unknown): boolean {
154+
return object instanceof Int;
155+
}
152156
}
153157

154158
export class Long extends Type {

0 commit comments

Comments
 (0)