Skip to content

Commit fe3aded

Browse files
authored
Merge pull request #41 from source-academy/bryan/typechecking
Add pretty printing for type checking errors
2 parents d650045 + 1b3cb5a commit fe3aded

File tree

7 files changed

+122
-65
lines changed

7 files changed

+122
-65
lines changed

src/types/ast/astExtractor/block-statement-extractor.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,21 @@ export class BlockStatementExtractor extends BaseJavaCstVisitorWithDefaults {
4040
localVariableDeclaration(
4141
ctx: LocalVariableDeclarationCtx
4242
): Omit<LocalVariableDeclarationStatement, 'kind'> {
43+
const { localVariableType, location } = this.visit(ctx.localVariableType)
4344
return {
44-
localVariableType: this.visit(ctx.localVariableType),
45-
variableDeclaratorList: this.visit(ctx.variableDeclaratorList)
45+
localVariableType: localVariableType,
46+
variableDeclaratorList: this.visit(ctx.variableDeclaratorList),
47+
location
4648
}
4749
}
4850

4951
localVariableType(ctx: LocalVariableTypeCtx) {
5052
const typeExtractor = new TypeExtractor()
5153
if (ctx.unannType) {
52-
return typeExtractor.extract(ctx.unannType[0])
54+
return {
55+
localVariableType: typeExtractor.extract(ctx.unannType[0]),
56+
location: ctx.unannType[0].location
57+
}
5358
} else if (ctx.Var) {
5459
throw new Error('Not implemented')
5560
}

src/types/ast/astExtractor/statement-extractor.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,12 @@ export class StatementExtractor extends BaseJavaCstVisitorWithDefaults {
188188
}
189189

190190
localVariableDeclaration(ctx: LocalVariableDeclarationCtx) {
191+
const { localVariableType, location } = this.visit(ctx.localVariableType)
191192
return {
192193
kind: 'LocalVariableDeclarationStatement',
193-
localVariableType: this.visit(ctx.localVariableType),
194-
variableDeclaratorList: this.visit(ctx.variableDeclaratorList)
194+
localVariableType: localVariableType,
195+
variableDeclaratorList: this.visit(ctx.variableDeclaratorList),
196+
location
195197
}
196198
}
197199

@@ -210,7 +212,10 @@ export class StatementExtractor extends BaseJavaCstVisitorWithDefaults {
210212
localVariableType(ctx: LocalVariableTypeCtx) {
211213
const typeExtractor = new TypeExtractor()
212214
if (ctx.unannType) {
213-
return typeExtractor.extract(ctx.unannType[0])
215+
return {
216+
localVariableType: typeExtractor.extract(ctx.unannType[0]),
217+
location: ctx.unannType[0].location
218+
}
214219
}
215220
throw new Error('Unimplemented extractor.')
216221
}
@@ -228,7 +233,8 @@ export class StatementExtractor extends BaseJavaCstVisitorWithDefaults {
228233
variableDeclaratorId: variable.children.Identifier[0].image,
229234
variableInitializer: expressionExtractor.extract(
230235
ctx.variableInitializer![index].children.expression![0]
231-
)
236+
),
237+
location: ctx.Equals ? getLocation(ctx.Equals[0]) : undefined
232238
})
233239
})
234240
return declarations
@@ -238,9 +244,10 @@ export class StatementExtractor extends BaseJavaCstVisitorWithDefaults {
238244
const blockStatementExtractor = new BlockStatementExtractor()
239245
const expressionExtractor = new ExpressionExtractor()
240246
const statementExtractor = new StatementExtractor()
247+
const { localVariableType } = blockStatementExtractor.visit(ctx.localVariableType)
241248
return {
242249
kind: 'EnhancedForStatement',
243-
localVariableType: blockStatementExtractor.visit(ctx.localVariableType),
250+
localVariableType: localVariableType,
244251
variableDeclaratorId: blockStatementExtractor.visit(ctx.variableDeclaratorId),
245252
expression: expressionExtractor.extract(ctx.expression[0]),
246253
statement: statementExtractor.extract(ctx.statement[0]),

src/types/ast/types/blocks-and-statements.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ export interface VariableDeclarator {
175175
variableDeclaratorId: VariableDeclaratorId
176176
dims?: string
177177
variableInitializer?: VariableInitializer
178+
location?: Location
178179
}
179180

180181
export type VariableDeclaratorId = Identifier

src/types/checker/index.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ export const typeCheckBody = (node: Node, frame: Frame = Frame.globalFrame()): R
115115
if (errors.length > 0) return newResult(null, errors)
116116
if (!currentType) throw new Error('Right side of assignment statment should return a type.')
117117
if (!leftType.canBeAssigned(currentType))
118-
return newResult(null, [new IncompatibleTypesError()])
118+
return newResult(null, [new IncompatibleTypesError(node.location)])
119119
return OK_RESULT
120120
}
121121
case 'BasicForStatement': {
@@ -346,17 +346,21 @@ export const typeCheckBody = (node: Node, frame: Frame = Frame.globalFrame()): R
346346
for (const variableDeclarator of node.variableDeclaratorList) {
347347
const declaredType = frame.getType(node.localVariableType)
348348
if (declaredType instanceof Error) return newResult(null, [declaredType])
349-
if (variableDeclarator.variableInitializer) {
349+
const { variableInitializer } = variableDeclarator
350+
if (variableInitializer) {
351+
let location = node.location
352+
if (!Array.isArray(variableInitializer)) location = variableInitializer.location
350353
const type = createArrayType(
351354
declaredType,
352-
variableDeclarator.variableInitializer,
355+
variableInitializer,
353356
expression => {
354357
const result = typeCheckBody(expression, frame)
355358
if (result.errors.length > 0) return result.errors[0]
356359
if (!result.currentType)
357360
throw new Error('array initializer expression should have a type')
358361
return result.currentType
359-
}
362+
},
363+
location
360364
)
361365
if (type instanceof Error) errors.push(type)
362366
}

src/types/errors.ts

Lines changed: 65 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,109 @@
1-
export class ArrayRequiredError extends Error {
2-
constructor() {
3-
super('array required')
1+
import { Location } from './ast/types'
2+
3+
export class TypeCheckerError extends Error {
4+
private location?: Location
5+
6+
constructor(message: string, location?: Location) {
7+
super('Error: ' + message)
8+
this.location = location
9+
}
10+
11+
public toReadableMessage(program: string): string {
12+
if (!this.location) return this.message
13+
const lines = program.split(/\n/)
14+
const errorLine = lines[this.location.startLine - 1] + ` (in line ${this.location.startLine})`
15+
if (!this.location.startColumn) return this.message + '\n' + errorLine + '\n'
16+
const errorPointer = `${' '.repeat(this.location.startColumn - 1)}^`
17+
return this.message + '\n' + errorLine + '\n' + errorPointer + '\n'
18+
}
19+
}
20+
21+
export class ArrayRequiredError extends TypeCheckerError {
22+
constructor(location?: Location) {
23+
super('array required', location)
424
}
525
}
626

7-
export class BadOperandTypesError extends Error {
8-
constructor() {
9-
super('bad operand')
27+
export class BadOperandTypesError extends TypeCheckerError {
28+
constructor(location?: Location) {
29+
super('bad operand', location)
1030
}
1131
}
1232

13-
export class CannotBeDereferencedError extends Error {
14-
constructor() {
15-
super('cannot be dereferenced')
33+
export class CannotBeDereferencedError extends TypeCheckerError {
34+
constructor(location?: Location) {
35+
super('cannot be dereferenced', location)
1636
}
1737
}
1838

19-
export class CannotFindSymbolError extends Error {
20-
constructor() {
21-
super('cannot find symbol')
39+
export class CannotFindSymbolError extends TypeCheckerError {
40+
constructor(location?: Location) {
41+
super('cannot find symbol', location)
2242
}
2343
}
2444

25-
export class CyclicInheritanceError extends Error {
26-
constructor() {
27-
super('cyclic inheritance')
45+
export class CyclicInheritanceError extends TypeCheckerError {
46+
constructor(location?: Location) {
47+
super('cyclic inheritance', location)
2848
}
2949
}
3050

31-
export class FloatTooLargeError extends Error {
32-
constructor() {
33-
super('floating-point number too large')
51+
export class FloatTooLargeError extends TypeCheckerError {
52+
constructor(location?: Location) {
53+
super('floating-point number too large', location)
3454
}
3555
}
3656

37-
export class FloatTooSmallError extends Error {
38-
constructor() {
39-
super('floating-point number too small')
57+
export class FloatTooSmallError extends TypeCheckerError {
58+
constructor(location?: Location) {
59+
super('floating-point number too small', location)
4060
}
4161
}
4262

43-
export class NotApplicableToExpressionTypeError extends Error {
44-
constructor() {
45-
super('not applicable to expression type')
63+
export class NotApplicableToExpressionTypeError extends TypeCheckerError {
64+
constructor(location?: Location) {
65+
super('not applicable to expression type', location)
4666
}
4767
}
4868

49-
export class IllegalUnderscoreError extends Error {
50-
constructor() {
51-
super('illegal underscore')
69+
export class IllegalUnderscoreError extends TypeCheckerError {
70+
constructor(location?: Location) {
71+
super('illegal underscore', location)
5272
}
5373
}
5474

55-
export class IntegerTooLargeError extends Error {
56-
constructor() {
57-
super('integer number too large')
75+
export class IntegerTooLargeError extends TypeCheckerError {
76+
constructor(location?: Location) {
77+
super('integer number too large', location)
5878
}
5979
}
6080

61-
export class IncompatibleTypesError extends Error {
62-
constructor() {
63-
super('incompatible types')
81+
export class IncompatibleTypesError extends TypeCheckerError {
82+
constructor(location?: Location) {
83+
super('incompatible types', location)
6484
}
6585
}
6686

67-
export class MethodAlreadyDefinedError extends Error {
68-
constructor() {
69-
super('method is already defined')
87+
export class MethodAlreadyDefinedError extends TypeCheckerError {
88+
constructor(location?: Location) {
89+
super('method is already defined', location)
7090
}
7191
}
7292

73-
export class MethodCannotBeAppliedError extends Error {
74-
constructor() {
75-
super('method cannot be applied')
93+
export class MethodCannotBeAppliedError extends TypeCheckerError {
94+
constructor(location?: Location) {
95+
super('method cannot be applied', location)
7696
}
7797
}
7898

79-
export class VarargsParameterMustBeLastParameter extends Error {
80-
constructor() {
81-
super('varargs parameter must be the last parameter')
99+
export class VarargsParameterMustBeLastParameter extends TypeCheckerError {
100+
constructor(location?: Location) {
101+
super('varargs parameter must be the last parameter', location)
82102
}
83103
}
84104

85-
export class VariableAlreadyDefinedError extends Error {
86-
constructor() {
87-
super('variable is already defined')
105+
export class VariableAlreadyDefinedError extends TypeCheckerError {
106+
constructor(location?: Location) {
107+
super('variable is already defined', location)
88108
}
89109
}

src/types/index.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { check } from './checker'
22
import { Node } from './ast/types'
33
import { parse } from './ast'
4+
import { TypeCheckerError } from './errors'
45

5-
export type TypeCheckResult = { hasTypeErrors: boolean; errorMessages: string[] }
6+
export type TypeCheckResult = { hasTypeErrors: boolean; errors: Error[] }
67

78
export const parseProgram = (program: string): Node => {
89
return parse(program)
@@ -12,6 +13,13 @@ export const typeCheck = (ast: Node): TypeCheckResult => {
1213
const result = check(ast)
1314
return {
1415
hasTypeErrors: result.errors.length > 0,
15-
errorMessages: result.errors.map(error => error.message)
16+
errors: result.errors
1617
}
1718
}
19+
20+
export const convertErrorsToReadableMsgs = (program: string, errors: Error[]): string[] => {
21+
return errors.map(error => {
22+
if (!(error instanceof TypeCheckerError)) return error.message
23+
return error.toReadableMessage(program)
24+
})
25+
}

src/types/typeFactories/arrayFactory.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,48 @@
11
import { Array as ArrayType } from '../types/arrays'
22
import { Expression, VariableInitializer } from '../ast/types/blocks-and-statements'
33
import { IncompatibleTypesError } from '../errors'
4+
import { Location } from '../ast/types'
45
import { Type } from '../types/type'
56

67
export const createArrayType = (
78
declaredType: Type,
89
variableInitializer: VariableInitializer,
9-
checkExpression: (expression: Expression) => Type | Error
10+
checkExpression: (expression: Expression) => Type | Error,
11+
location?: Location
1012
): Type | Error => {
1113
const isArrayInitializer = Array.isArray(variableInitializer)
1214
const isDeclaredTypeArray = declaredType instanceof ArrayType
13-
if (!isDeclaredTypeArray && isArrayInitializer) return new IncompatibleTypesError()
15+
if (!isDeclaredTypeArray && isArrayInitializer) return new IncompatibleTypesError(location)
1416

1517
if (isDeclaredTypeArray && !isArrayInitializer) {
1618
const type = checkExpression(variableInitializer)
1719
if (type instanceof Error) return type
18-
if (!declaredType.canBeAssigned(type)) return new IncompatibleTypesError()
20+
if (!declaredType.canBeAssigned(type)) return new IncompatibleTypesError(location)
1921
return declaredType
2022
}
2123

2224
if (!isDeclaredTypeArray && !isArrayInitializer) {
2325
const type = checkExpression(variableInitializer)
2426
if (type instanceof Error) return type
25-
if (!declaredType.canBeAssigned(type)) return new IncompatibleTypesError()
27+
if (!declaredType.canBeAssigned(type)) return new IncompatibleTypesError(location)
2628
return type
2729
}
2830

2931
if (isDeclaredTypeArray && isArrayInitializer) {
3032
const arrayContentType = declaredType.getContentType()
3133
for (const initializer of variableInitializer) {
32-
const type = createArrayType(arrayContentType, initializer, checkExpression)
33-
if (type instanceof Error) return type
34+
if (Array.isArray(initializer)) {
35+
const type = createArrayType(arrayContentType, initializer, checkExpression, location)
36+
if (type instanceof Error) return type
37+
} else {
38+
const type = createArrayType(
39+
arrayContentType,
40+
initializer,
41+
checkExpression,
42+
initializer.location
43+
)
44+
if (type instanceof Error) return type
45+
}
3446
}
3547
}
3648

0 commit comments

Comments
 (0)