Skip to content

Commit

Permalink
fix: resolve the value of const initializers before type-checking (#149)
Browse files Browse the repository at this point in the history
* fix: resolve the value of const initializers before type-checking

* chore: cleanup
  • Loading branch information
kevin-greene-ck authored Mar 12, 2019
1 parent 0831ec6 commit 205716a
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 38 deletions.
140 changes: 102 additions & 38 deletions src/main/validator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,10 @@ export function validateFile(resolvedFile: IResolvedFile): IResolvedFile {
const newBody: Array<ThriftStatement> = []
while (!isAtEnd()) {
try {
newBody.push(validateStatement(resolvedFile.body[currentIndex]))
const statement = validateStatement(
resolvedFile.body[currentIndex],
)
newBody.push(statement)
} catch (e) {
errors.push(createValidationError(e.message, e.loc))
}
Expand All @@ -150,7 +153,35 @@ export function validateFile(resolvedFile: IResolvedFile): IResolvedFile {
return currentIndex >= bodySize
}

function getIdentifier(
function resolveValue(value: ConstValue): ConstValue {
if (
value.type === SyntaxType.Identifier &&
resolvedFile.identifiers[value.value]
) {
const resolvedIdentifier = resolvedFile.identifiers[value.value]
if (
resolvedIdentifier.definition.type ===
SyntaxType.ConstDefinition
) {
if (
resolvedIdentifier.definition.initializer.type ===
SyntaxType.Identifier
) {
return resolveValue(
resolvedIdentifier.definition.initializer,
)
} else {
return resolvedIdentifier.definition.initializer
}
} else {
return value
}
} else {
return value
}
}

function requireIdentifier(
loc: TextLocation,
...names: Array<string>
): IResolvedIdentifier {
Expand Down Expand Up @@ -250,7 +281,10 @@ export function validateFile(resolvedFile: IResolvedFile): IResolvedFile {
}

function validateExtends(id: Identifier): Identifier {
const resolvedID: IResolvedIdentifier = getIdentifier(id.loc, id.value)
const resolvedID: IResolvedIdentifier = requireIdentifier(
id.loc,
id.value,
)
if (resolvedID.definition.type === SyntaxType.ServiceDefinition) {
return id
} else {
Expand Down Expand Up @@ -313,7 +347,7 @@ export function validateFile(resolvedFile: IResolvedFile): IResolvedFile {
const baseName =
parts.length > 2 ? `${parts[0]}.${parts[1]}` : parts[0]
const accessName = parts[parts.length - 1]
const resolvedConst: IResolvedIdentifier = getIdentifier(
const resolvedConst: IResolvedIdentifier = requireIdentifier(
constValue.loc,
baseName,
constValue.value,
Expand Down Expand Up @@ -406,118 +440,148 @@ export function validateFile(resolvedFile: IResolvedFile): IResolvedFile {
expectedType: FunctionType,
value: ConstValue,
): ConstValue {
const resolvedValue: ConstValue = resolveValue(value)
switch (expectedType.type) {
case SyntaxType.VoidKeyword:
throw new ValidationError(
`Cannot assign value to type void`,
value.loc,
resolvedValue.loc,
)

case SyntaxType.Identifier:
return validateTypeForIdentifier(
getIdentifier(expectedType.loc, expectedType.value),
value,
requireIdentifier(expectedType.loc, expectedType.value),
resolvedValue,
)

case SyntaxType.StringKeyword:
if (value.type === SyntaxType.StringLiteral) {
if (resolvedValue.type === SyntaxType.StringLiteral) {
return value
} else {
throw typeMismatch(expectedType, value, value.loc)
throw typeMismatch(
expectedType,
resolvedValue,
resolvedValue.loc,
)
}

case SyntaxType.BoolKeyword:
if (value.type === SyntaxType.BooleanLiteral) {
return value
if (resolvedValue.type === SyntaxType.BooleanLiteral) {
return resolvedValue

// Handle the case where the literal values 1 or 0 can be used to represent booleans
} else if (
value.type === SyntaxType.IntConstant &&
(value.value.value === '0' || value.value.value === '1')
resolvedValue.type === SyntaxType.IntConstant &&
(resolvedValue.value.value === '0' ||
resolvedValue.value.value === '1')
) {
return createBooleanLiteral(
value.value.value === '1',
value.loc,
resolvedValue.value.value === '1',
resolvedValue.loc,
)
} else {
throw typeMismatch(expectedType, value, value.loc)
throw typeMismatch(
expectedType,
resolvedValue,
resolvedValue.loc,
)
}

case SyntaxType.DoubleKeyword:
if (
value.type === SyntaxType.DoubleConstant ||
value.type === SyntaxType.IntConstant
resolvedValue.type === SyntaxType.DoubleConstant ||
resolvedValue.type === SyntaxType.IntConstant
) {
return value
return resolvedValue
} else {
throw typeMismatch(expectedType, value, value.loc)
}

case SyntaxType.BinaryKeyword:
if (value.type === SyntaxType.StringLiteral) {
if (resolvedValue.type === SyntaxType.StringLiteral) {
return value
} else {
throw typeMismatch(expectedType, value, value.loc)
throw typeMismatch(
expectedType,
resolvedValue,
resolvedValue.loc,
)
}

case SyntaxType.ByteKeyword:
case SyntaxType.I8Keyword:
case SyntaxType.I16Keyword:
case SyntaxType.I32Keyword:
if (value.type === SyntaxType.IntConstant) {
return value
if (resolvedValue.type === SyntaxType.IntConstant) {
return resolvedValue
} else {
throw typeMismatch(expectedType, value, value.loc)
throw typeMismatch(
expectedType,
resolvedValue,
resolvedValue.loc,
)
}

case SyntaxType.I64Keyword:
if (value.type === SyntaxType.IntConstant) {
return value
if (resolvedValue.type === SyntaxType.IntConstant) {
return resolvedValue
} else {
throw typeMismatch(expectedType, value, value.loc)
throw typeMismatch(
expectedType,
resolvedValue,
resolvedValue.loc,
)
}

case SyntaxType.SetType:
if (value.type === SyntaxType.ConstList) {
if (resolvedValue.type === SyntaxType.ConstList) {
return {
type: SyntaxType.ConstList,
elements: value.elements.map(
elements: resolvedValue.elements.map(
(next: ConstValue): ConstValue => {
return validateValue(
expectedType.valueType,
next,
)
},
),
loc: value.loc,
loc: resolvedValue.loc,
}
} else {
throw typeMismatch(expectedType, value, value.loc)
throw typeMismatch(
expectedType,
resolvedValue,
resolvedValue.loc,
)
}

case SyntaxType.ListType:
if (value.type === SyntaxType.ConstList) {
if (resolvedValue.type === SyntaxType.ConstList) {
return {
type: SyntaxType.ConstList,
elements: value.elements.map(
elements: resolvedValue.elements.map(
(next: ConstValue): ConstValue => {
return validateValue(
expectedType.valueType,
next,
)
},
),
loc: value.loc,
loc: resolvedValue.loc,
}
} else {
throw typeMismatch(expectedType, value, value.loc)
throw typeMismatch(
expectedType,
resolvedValue,
resolvedValue.loc,
)
}

case SyntaxType.MapType:
if (value.type === SyntaxType.ConstMap) {
if (resolvedValue.type === SyntaxType.ConstMap) {
return {
type: SyntaxType.ConstMap,
properties: value.properties.map(
properties: resolvedValue.properties.map(
(next: PropertyAssignment): PropertyAssignment => {
return {
type: SyntaxType.PropertyAssignment,
Expand Down Expand Up @@ -558,7 +622,7 @@ export function validateFile(resolvedFile: IResolvedFile): IResolvedFile {
function validateFieldType(fieldType: FieldType): FieldType {
switch (fieldType.type) {
case SyntaxType.Identifier:
if (getIdentifier(fieldType.loc, fieldType.value) != null) {
if (requireIdentifier(fieldType.loc, fieldType.value) != null) {
return fieldType
} else {
throw new ValidationError(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const WHAT: number = 32;
export const VALUE: number = 32;
export const VALUE_LIST: Array<number> = [32];
export const FALSE_CONST: boolean = false;
export const INT_64: thrift.Int64 = thrift.Int64.fromDecimalString("64");
export const SET_CONST: Set<string> = new Set(["hello", "world", "foo", "bar"]);
export const MAP_CONST: Map<string, string> = new Map([["hello", "world"], ["foo", "bar"]]);
export const VALUE_MAP: Map<number, string> = new Map([[32, "world"], [5, "bar"]]);
export const LIST_CONST: Array<string> = ["hello", "world", "foo", "bar"];
21 changes: 21 additions & 0 deletions src/tests/unit/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,27 @@ describe('Thrift TypeScript Generator', () => {
})

describe('Thrift Server', () => {
it('should correctly generate constants', () => {
const content: string = `
const i32 WHAT = 32
const i32 VALUE = WHAT
const list<i32> VALUE_LIST = [ VALUE ]
const bool FALSE_CONST = false
const i64 INT_64 = 64
const set<string> SET_CONST = ['hello', 'world', 'foo', 'bar']
const map<string,string> MAP_CONST = {'hello': 'world', 'foo': 'bar' }
const map<i32,string> VALUE_MAP = { VALUE: 'world', 5: 'bar' }
const list<string> LIST_CONST = ['hello', 'world', 'foo', 'bar']
`
const expected: string = readSolution(
'complex_const',
'thrift-server',
)
const actual: string = make(content)

assert.deepEqual(actual, expected)
})

it('should correctly generate a struct', () => {
const content: string = `
struct MyStruct {
Expand Down
17 changes: 17 additions & 0 deletions src/tests/unit/validator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,23 @@ describe('Thrift TypeScript Validator', () => {
assert.deepEqual(validatedFile.errors, expected)
})

it('should not return an error when using identifier as valid value', () => {
const content: string = `
const i32 VALUE = 32
const list<i32> TEST = [ VALUE ]
`
const parsedFile: IParsedFile = parseSource(content)
const resolvedFile: IResolvedFile = resolveFile(
'',
parsedFile,
DEFAULT_OPTIONS,
)
const validatedFile: IResolvedFile = validateFile(resolvedFile)
const expected: Array<IThriftError> = []

assert.deepEqual(validatedFile.errors, expected)
})

it('should return an error if it finds incorrect list types', () => {
const content: string = `
const list<string> TEST = [ 32, 41, 65 ]
Expand Down

0 comments on commit 205716a

Please sign in to comment.