Skip to content

Commit

Permalink
Add type cast support to the type checker
Browse files Browse the repository at this point in the history
  • Loading branch information
AprupKale committed Jan 26, 2025
1 parent 0799bf7 commit fe76603
Show file tree
Hide file tree
Showing 4 changed files with 256 additions and 11 deletions.
139 changes: 139 additions & 0 deletions src/ast/__tests__/expression-extractor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1035,3 +1035,142 @@ describe("extract ClassInstanceCreationExpression correctly", () => {
expect(ast).toEqual(expectedAst);
});
});

describe("extract CastExpression correctly", () => {
it("extract CastExpression int to char correctly", () => {
const programStr = `
class Test {
void test() {
char c = (char) 65;
}
}
`;

const expectedAst: AST = {
kind: "CompilationUnit",
importDeclarations: [],
topLevelClassOrInterfaceDeclarations: [
{
kind: "NormalClassDeclaration",
classModifier: [],
typeIdentifier: "Test",
classBody: [
{
kind: "MethodDeclaration",
methodModifier: [],
methodHeader: {
result: "void",
identifier: "test",
formalParameterList: [],
},
methodBody: {
kind: "Block",
blockStatements: [
{
kind: "LocalVariableDeclarationStatement",
localVariableType: "char",
variableDeclaratorList: [
{
kind: "VariableDeclarator",
variableDeclaratorId: "c",
variableInitializer: {
kind: "CastExpression",
type: "char",
expression: {
kind: "Literal",
literalType: {
kind: "DecimalIntegerLiteral",
value: "65",
},
location: expect.anything(),
},
location: expect.anything(),
},
},
],
location: expect.anything(),
},
],
location: expect.anything(),
},
location: expect.anything(),
},
],
location: expect.anything(),
},
],
location: expect.anything(),
};

const ast = parse(programStr);
expect(ast).toEqual(expectedAst);
});

it("extract CastExpression double to int correctly", () => {
const programStr = `
class Test {
void test() {
int x = (int) 3.14;
}
}
`;

const expectedAst: AST = {
kind: "CompilationUnit",
importDeclarations: [],
topLevelClassOrInterfaceDeclarations: [
{
kind: "NormalClassDeclaration",
classModifier: [],
typeIdentifier: "Test",
classBody: [
{
kind: "MethodDeclaration",
methodModifier: [],
methodHeader: {
result: "void",
identifier: "test",
formalParameterList: [],
},
methodBody: {
kind: "Block",
blockStatements: [
{
kind: "LocalVariableDeclarationStatement",
localVariableType: "int",
variableDeclaratorList: [
{
kind: "VariableDeclarator",
variableDeclaratorId: "x",
variableInitializer: {
kind: "CastExpression",
type: "int",
expression: {
kind: "Literal",
literalType: {
kind: "DecimalFloatingPointLiteral",
value: "3.14",
}
},
location: expect.anything(),
},
},
],
location: expect.anything(),
},
],
location: expect.anything(),
},
location: expect.anything(),
},
],
location: expect.anything(),
},
],
location: expect.anything(),
};

const ast = parse(programStr);
expect(ast).toEqual(expectedAst);
});
});
47 changes: 39 additions & 8 deletions src/ast/astExtractor/expression-extractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,9 @@ export class ExpressionExtractor extends BaseJavaCstVisitorWithDefaults {
const primitiveCast = ctx.primitiveCastExpression[0];
const type = this.extractType(primitiveCast.children.primitiveType[0]);
const expression = this.visit(primitiveCast.children.unaryExpression[0]);
console.debug({primitiveCast, type, expression});
return {
kind: "CastExpression",
castType: type,
type: type,
expression: expression,
location: this.location,
};
Expand All @@ -105,13 +104,41 @@ export class ExpressionExtractor extends BaseJavaCstVisitorWithDefaults {
throw new Error("Invalid CastExpression format.");
}

private extractType(typeCtx: any) {
if (typeCtx.Identifier) {
return typeCtx.Identifier[0].image;
}
if (typeCtx.unannPrimitiveType) {
return this.visit(typeCtx.unannPrimitiveType);
private extractType(typeCtx: any): string {
// Check for the 'primitiveType' node
if (typeCtx.name === "primitiveType" && typeCtx.children) {
const { children } = typeCtx;

// Handle 'numericType' (e.g., int, char, float, double)
if (children.numericType) {
const numericTypeCtx = children.numericType[0];

if (numericTypeCtx.children.integralType) {
// Handle integral types (e.g., char, int)
const integralTypeCtx = numericTypeCtx.children.integralType[0];

// Extract the specific type (e.g., 'char', 'int')
for (const key in integralTypeCtx.children) {
if (integralTypeCtx.children[key][0].image) {
return integralTypeCtx.children[key][0].image;
}
}
}

if (numericTypeCtx.children.floatingPointType) {
// Handle floating-point types (e.g., float, double)
const floatingPointTypeCtx = numericTypeCtx.children.floatingPointType[0];

Check warning on line 130 in src/ast/astExtractor/expression-extractor.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

// Extract the specific type (e.g., 'float', 'double')
for (const key in floatingPointTypeCtx.children) {
if (floatingPointTypeCtx.children[key][0].image) {
return floatingPointTypeCtx.children[key][0].image;

Check warning on line 135 in src/ast/astExtractor/expression-extractor.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

Check warning on line 136 in src/ast/astExtractor/expression-extractor.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 136 in src/ast/astExtractor/expression-extractor.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
}

Check warning on line 137 in src/ast/astExtractor/expression-extractor.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

Check warning on line 138 in src/ast/astExtractor/expression-extractor.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 138 in src/ast/astExtractor/expression-extractor.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
}
}

throw new Error("Invalid type context in cast expression.");
}

Expand Down Expand Up @@ -204,6 +231,10 @@ export class ExpressionExtractor extends BaseJavaCstVisitorWithDefaults {
}

unaryExpression(ctx: UnaryExpressionCtx) {
if (ctx.primary[0].children.primaryPrefix[0].children.castExpression) {
return this.visit(ctx.primary[0].children.primaryPrefix[0].children.castExpression);
}

const node = this.visit(ctx.primary);
if (ctx.UnaryPrefixOperator) {
return {
Expand Down
3 changes: 1 addition & 2 deletions src/ast/types/blocks-and-statements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ export type Expression =
| BinaryExpression
| UnaryExpression
| TernaryExpression
| CastExpression
| Void;

export interface Void extends BaseNode {
Expand Down Expand Up @@ -260,7 +259,7 @@ export interface Assignment extends BaseNode {
}

export type LeftHandSide = ExpressionName | ArrayAccess;
export type UnaryExpression = PrefixExpression | PostfixExpression;
export type UnaryExpression = PrefixExpression | PostfixExpression | CastExpression;

export interface PrefixExpression extends BaseNode {
kind: "PrefixExpression";
Expand Down
78 changes: 77 additions & 1 deletion src/types/checker/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Array as ArrayType } from '../types/arrays'
import { Integer, String, Throwable, Void } from '../types/references'
import { CaseConstant, Node } from '../ast/specificationTypes'
import { Type } from '../types/type'
import { PrimitiveType, Type } from '../types/type'
import {
ArrayRequiredError,
BadOperandTypesError,
Expand Down Expand Up @@ -63,6 +63,33 @@ export const check = (node: Node, frame: Frame = Frame.globalFrame()): Result =>
return typeCheckBody(node, typeCheckingFrame)
}

const isCastCompatible = (fromType: Type, toType: Type): boolean => {
// Handle primitive type compatibility
if (fromType instanceof PrimitiveType && toType instanceof PrimitiveType) {

Check warning on line 68 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

Check warning on line 68 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
const fromName = fromType.constructor.name;

Check warning on line 69 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
const toName = toType.constructor.name;

Check warning on line 70 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

console.log(fromName, toName);

Check warning on line 72 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

return !(fromName === 'char' && toName !== 'int');

Check warning on line 74 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 74 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

Check warning on line 74 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
}

Check warning on line 75 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 75 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

// Handle class type compatibility
if (fromType instanceof ClassType && toType instanceof ClassType) {

Check warning on line 78 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

Check warning on line 78 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
// Allow upcasts (base class to derived class) or downcasts (derived class to base class)
return fromType.canBeAssigned(toType) || toType.canBeAssigned(fromType);

Check warning on line 80 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 80 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

Check warning on line 80 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
}

Check warning on line 81 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 81 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

// Handle array type compatibility
if (fromType instanceof ArrayType && toType instanceof ArrayType) {

Check warning on line 84 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

Check warning on line 84 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
// Ensure the content types are compatible
return isCastCompatible(fromType.getContentType(), toType.getContentType());

Check warning on line 86 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

Check warning on line 87 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 87 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

// Disallow other cases by default
return false;

Check warning on line 90 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
};

export const typeCheckBody = (node: Node, frame: Frame = Frame.globalFrame()): Result => {
switch (node.kind) {
case 'ArrayAccess': {
Expand Down Expand Up @@ -192,6 +219,55 @@ export const typeCheckBody = (node: Node, frame: Frame = Frame.globalFrame()): R
case 'BreakStatement': {
return OK_RESULT
}

case 'CastExpression': {
let castType: Type | TypeCheckerError;
let expressionType: Type | null = null;

Check warning on line 225 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
let expressionResult: Result;

if ('primitiveType' in node) {
castType = frame.getType(unannTypeToString(node.primitiveType), node.primitiveType.location);

Check warning on line 229 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
} else {
throw new Error('Invalid CastExpression: Missing type information.');

Check warning on line 231 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

Check warning on line 232 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

if (castType instanceof TypeCheckerError) {
return newResult(null, [castType]);

Check warning on line 235 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

Check warning on line 236 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

if ('unaryExpression' in node) {
expressionResult = typeCheckBody(node.unaryExpression, frame);

Check warning on line 239 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
} else {
throw new Error('Invalid CastExpression: Missing expression.');

Check warning on line 241 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

Check warning on line 242 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

if (expressionResult.hasErrors) {
return expressionResult;

Check warning on line 245 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

Check warning on line 246 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

expressionType = expressionResult.currentType;

Check warning on line 248 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
if (!expressionType) {
throw new Error('Expression in cast should have a type.');

Check warning on line 250 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

Check warning on line 251 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

if (
(castType instanceof PrimitiveType && expressionType instanceof PrimitiveType)
) {
if (!isCastCompatible(expressionType, castType)) {
return newResult(null, [
new IncompatibleTypesError(node.location),
]);

Check warning on line 259 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

Check warning on line 260 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
} else {
return newResult(null, [
new IncompatibleTypesError(node.location),
]);

Check warning on line 264 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

Check warning on line 265 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

// If the cast is valid, return the target type
return newResult(castType);

Check warning on line 268 in src/types/checker/index.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

case 'ClassInstanceCreationExpression': {
const classIdentifier =
node.unqualifiedClassInstanceCreationExpression.classOrInterfaceTypeToInstantiate
Expand Down

0 comments on commit fe76603

Please sign in to comment.