Skip to content

Commit 39731f1

Browse files
committed
Add support for varargs to methods
1 parent 36ed2b8 commit 39731f1

File tree

8 files changed

+103
-30
lines changed

8 files changed

+103
-30
lines changed

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ export class MethodExtractor extends BaseJavaCstVisitorWithDefaults {
9898
return {
9999
kind: 'FormalParameter',
100100
unannType: typeExtractor.extract(ctx.unannType[0]),
101-
identifier: this.visit(ctx.variableDeclaratorId)
101+
identifier: this.visit(ctx.variableDeclaratorId),
102+
isVariableArityParameter: false
102103
} as FormalParameter
103104
}
104105

@@ -107,7 +108,8 @@ export class MethodExtractor extends BaseJavaCstVisitorWithDefaults {
107108
return {
108109
kind: 'FormalParameter',
109110
unannType: typeExtractor.extract(ctx.unannType[0]),
110-
identifier: ctx.Identifier[0].image
111+
identifier: ctx.Identifier[0].image,
112+
isVariableArityParameter: true
111113
} as FormalParameter
112114
}
113115

src/types/ast/types/classes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export interface FormalParameter {
7373
kind: 'FormalParameter'
7474
unannType: UnannType
7575
identifier: Identifier
76+
isVariableArityParameter: boolean
7677
}
7778

7879
export interface FieldDeclaration extends BaseNode {

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

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1-
import { CannotFindSymbolError } from '../../errors'
1+
import {
2+
CannotFindSymbolError,
3+
IncompatibleTypesError,
4+
MethodCannotBeAppliedError,
5+
VarargsParameterMustBeLastParameter,
6+
VariableAlreadyDefinedError
7+
} from '../../errors'
28
import { check } from '..'
39
import { parse } from '../../ast'
410
import { Type } from '../../types/type'
5-
import { IncompatibleTypesError, MethodCannotBeAppliedError } from '../../errors'
611

712
const createProgram = (methods: string) => `
813
public class Main {
@@ -75,6 +80,34 @@ const testcases: {
7580
public static int getStringLength(String input) { return input; }
7681
`,
7782
result: { type: null, errors: [new IncompatibleTypesError()] }
83+
},
84+
{
85+
input: `
86+
public static void main(String[] args) { printMultipleMessages("Hello", "World"); }
87+
public static void printMultipleMessages(String... message, String message) {} // Duplicate Identifier
88+
`,
89+
result: { type: null, errors: [new VarargsParameterMustBeLastParameter()] }
90+
},
91+
{
92+
input: `
93+
public static void main(String[] args) { printMultipleMessages("Hello", "World"); }
94+
public static void printMultipleMessages(String message, String message) {} // Duplicate Identifier
95+
`,
96+
result: { type: null, errors: [new VariableAlreadyDefinedError()] }
97+
},
98+
{
99+
input: `
100+
public static void main(String[] args) { printMultipleMessages("Hello", "World", "!"); }
101+
public static void printMultipleMessages(String... messages) { for (String msg : messages) {} }
102+
`,
103+
result: { type: null, errors: [] }
104+
},
105+
{
106+
input: `
107+
public static void main(String[] args) { printMultipleMessages(); }
108+
public static void printMultipleMessages(String... messages) { for (String msg : messages) {} }
109+
`,
110+
result: { type: null, errors: [] }
78111
}
79112
]
80113

src/types/checker/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,8 @@ export const check = (node: Node, frame: Frame = Frame.globalFrame()): Result =>
381381
furtherChecks.push(() => {
382382
const methodFrame = classFrame.newChildFrame()
383383
const methodSignature = method.getOverload(0)
384-
methodSignature.mapParameters((name, type) => {
384+
methodSignature.mapParameters((name, type, isVarargs) => {
385+
if (isVarargs) type = new ArrayType(type)
385386
const error = methodFrame.setVariable(name, type)
386387
if (error) errors.push(error)
387388
})
@@ -392,6 +393,7 @@ export const check = (node: Node, frame: Frame = Frame.globalFrame()): Result =>
392393
}
393394
}
394395
})
396+
if (errors.length > 0) return newResult(null, errors)
395397
furtherChecks.forEach(furtherCheck => furtherCheck())
396398
return newResult(null, errors)
397399
}

src/types/errors.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ export class MethodCannotBeAppliedError extends Error {
7070
}
7171
}
7272

73+
export class VarargsParameterMustBeLastParameter extends Error {
74+
constructor() {
75+
super('varargs parameter must be the last parameter')
76+
}
77+
}
78+
7379
export class VariableAlreadyDefinedError extends Error {
7480
constructor() {
7581
super('variable is already defined')

src/types/inputs/Main.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
public class Main {
2-
public static void main(String[] args) {
3-
int[] arr = {1, 2, 3};
4-
for (int num : arr) {}
5-
System.out.println(num);
6-
}
2+
public static void main(String[] args) { printMultipleMessages("Hello", "World"); }
3+
public static void printMultipleMessages(String ...message, String message) {} // Duplicate Identifier
74
}

src/types/typeFactories/methodFactory.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
import { FormalParameter, MethodDeclaration } from '../ast/types/classes'
2-
import { Frame } from '../checker/environment'
3-
import { Type } from '../types/type'
41
import {
52
ArgumentList,
63
ClassMethod,
@@ -9,6 +6,10 @@ import {
96
Parameter,
107
ParameterList
118
} from '../types/methods'
9+
import { FormalParameter, MethodDeclaration } from '../ast/types/classes'
10+
import { Frame } from '../checker/environment'
11+
import { Type } from '../types/type'
12+
import { VarargsParameterMustBeLastParameter, VariableAlreadyDefinedError } from '../errors'
1213

1314
export const createArgumentList = (...argumentTypes: Type[]): ArgumentList | Error => {
1415
const argumentList = new ArgumentList()
@@ -22,11 +23,21 @@ const createParameterList = (
2223
frame: Frame,
2324
...parameterDeclarations: FormalParameter[]
2425
): ParameterList | Error => {
26+
const identifiers = new Set<string>()
2527
const parameters: ParameterList = new ParameterList()
26-
for (const parameterDeclaration of parameterDeclarations) {
28+
for (let i = 0; i < parameterDeclarations.length; i++) {
29+
const parameterDeclaration = parameterDeclarations[i]
30+
if (parameterDeclaration.isVariableArityParameter && i + 1 !== parameterDeclarations.length)
31+
return new VarargsParameterMustBeLastParameter()
2732
const parameterType = frame.getType(parameterDeclaration.unannType)
2833
if (parameterType instanceof Error) return parameterType
29-
const parameter = new Parameter(parameterDeclaration.identifier, parameterType)
34+
if (identifiers.has(parameterDeclaration.identifier)) return new VariableAlreadyDefinedError()
35+
identifiers.add(parameterDeclaration.identifier)
36+
const parameter = new Parameter(
37+
parameterDeclaration.identifier,
38+
parameterType,
39+
parameterDeclaration.isVariableArityParameter
40+
)
3041
parameters.addParameter(parameter)
3142
}
3243
return parameters

src/types/types/methods.ts

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,28 +23,37 @@ export class ArgumentList {
2323
}
2424

2525
export class Parameter {
26-
public name: string
27-
public type: Type
28-
constructor(name: string, type: Type) {
29-
this.name = name
30-
this.type = type
26+
private _name: string
27+
private _type: Type
28+
private _isVarargs: boolean
29+
30+
constructor(name: string, type: Type, isVarargs: boolean = false) {
31+
this._name = name
32+
this._type = type
33+
this._isVarargs = isVarargs
3134
}
3235

3336
public canBeAssigned(type: Type): boolean {
34-
if (type instanceof Parameter) return this.type.canBeAssigned(type.type)
35-
return this.type.canBeAssigned(type)
37+
if (type instanceof Parameter) return this._type.canBeAssigned(type._type)
38+
return this._type.canBeAssigned(type)
3639
}
3740

3841
public equals(object: unknown): boolean {
39-
return object instanceof Parameter && this.name === object.name && this.type.equals(object.type)
42+
return (
43+
object instanceof Parameter && this._name === object._name && this._type.equals(object._type)
44+
)
4045
}
4146

4247
public getName(): string {
43-
return this.name
48+
return this._name
4449
}
4550

4651
public getType(): Type {
47-
return this.type
52+
return this._type
53+
}
54+
55+
public isVarargs(): boolean {
56+
return this._isVarargs
4857
}
4958
}
5059

@@ -65,11 +74,23 @@ export class ParameterList {
6574
}
6675

6776
public matchesArguments(args: ArgumentList): boolean {
68-
if (this.length() !== args.length()) return false
77+
const isLastParameterVarargs = this.get(this.length() - 1).isVarargs()
78+
if (isLastParameterVarargs && args.length() < this.length() - 1) return false
79+
if (!isLastParameterVarargs && args.length() !== this.length()) return false
6980
for (let i = 0; i < this.length(); i++) {
7081
const parameter = this.get(i)
71-
const argument = args.get(i)
72-
if (!parameter.canBeAssigned(argument)) return false
82+
if (!parameter.isVarargs()) {
83+
const argument = args.get(i)
84+
if (!parameter.canBeAssigned(argument)) return false
85+
continue
86+
}
87+
88+
for (let j = i; j < args.length(); j++) {
89+
const argument = args.get(j)
90+
if (!parameter.canBeAssigned(argument)) return false
91+
}
92+
93+
break
7394
}
7495
return true
7596
}
@@ -120,11 +141,11 @@ export class MethodSignature extends Type {
120141
return this.parameters.length()
121142
}
122143

123-
public mapParameters<T>(mapper: (name: string, type: Type) => T): T[] {
144+
public mapParameters<T>(mapper: (name: string, type: Type, isVarargs: boolean) => T): T[] {
124145
const result: T[] = []
125146
for (let i = 0; i < this.parameterSize(); i++) {
126147
const parameter = this.parameters.get(i)
127-
result.push(mapper(parameter.getName(), parameter.getType()))
148+
result.push(mapper(parameter.getName(), parameter.getType(), parameter.isVarargs()))
128149
}
129150
return result
130151
}

0 commit comments

Comments
 (0)