-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
added interface type for type checking.
- Loading branch information
1 parent
da2025f
commit f8a32bf
Showing
3 changed files
with
259 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
import { expect } from 'chai'; | ||
import { assert } from 'sinon'; | ||
import type { BscType } from './BscType'; | ||
import { DynamicType } from './DynamicType'; | ||
import { IntegerType } from './IntegerType'; | ||
import { InterfaceType } from './InterfaceType'; | ||
import { ObjectType } from './ObjectType'; | ||
import { StringType } from './StringType'; | ||
|
||
describe('InterfaceType', () => { | ||
describe('toString', () => { | ||
it('returns empty curly braces when no members', () => { | ||
expect(iface({}).toString()).to.eql('{}'); | ||
}); | ||
|
||
it('includes member types', () => { | ||
expect(iface({ name: new StringType() }).toString()).to.eql('{ name: string; }'); | ||
}); | ||
|
||
it('includes nested object types', () => { | ||
expect( | ||
iface({ | ||
name: new StringType(), | ||
parent: iface({ | ||
age: new IntegerType() | ||
}) | ||
} | ||
).toString() | ||
).to.eql('{ name: string; parent: { age: integer; }; }'); | ||
}); | ||
}); | ||
|
||
describe('isConvertibleTo', () => { | ||
it('works', () => { | ||
expectAssignable({ | ||
name: new StringType() | ||
}, { | ||
name: new StringType() | ||
}); | ||
}); | ||
}); | ||
|
||
describe('equals', () => { | ||
it('matches equal objects', () => { | ||
expect( | ||
iface({ name: new StringType() }).equals(iface({ name: new StringType() })) | ||
).to.be.true; | ||
}); | ||
|
||
it('does not match inequal objects', () => { | ||
expect( | ||
iface({ name: new StringType() }).equals(iface({ name: new IntegerType() })) | ||
).to.be.false; | ||
}); | ||
}); | ||
|
||
describe('isAssignableTo', () => { | ||
it('rejects being assignable to other types', () => { | ||
expect( | ||
iface({ | ||
name: new StringType() | ||
}).isAssignableTo(new IntegerType()) | ||
).to.be.false; | ||
}); | ||
|
||
it('matches exact properties', () => { | ||
expectAssignable({ | ||
name: new StringType() | ||
}, { | ||
name: new StringType() | ||
}); | ||
}); | ||
|
||
it('matches an object with more properties being assigned to an object with less', () => { | ||
expectAssignable({ | ||
name: new StringType() | ||
}, { | ||
name: new StringType(), | ||
age: new IntegerType() | ||
}); | ||
}); | ||
|
||
it('rejects assigning an object with less properties to one with more', () => { | ||
expectNotAssignable({ | ||
name: new StringType(), | ||
age: new IntegerType() | ||
}, { | ||
name: new StringType() | ||
}); | ||
}); | ||
|
||
it('matches properties in mismatched order', () => { | ||
expect( | ||
new InterfaceType(new Map([ | ||
['name', new StringType()], | ||
['age', new IntegerType()] | ||
])).isAssignableTo(new InterfaceType(new Map([ | ||
['age', new IntegerType()], | ||
['name', new StringType()] | ||
]))) | ||
).to.be.true; | ||
}); | ||
|
||
it('rejects with member having mismatched type', () => { | ||
expectNotAssignable({ | ||
name: new StringType() | ||
}, { | ||
name: new IntegerType() | ||
}); | ||
}); | ||
|
||
it('rejects with object member having mismatched type', () => { | ||
expectNotAssignable({ | ||
parent: iface({ | ||
name: new StringType() | ||
}) | ||
}, { | ||
parent: iface({ | ||
name: new IntegerType() | ||
}) | ||
}); | ||
}); | ||
|
||
it('rejects with object member having missing prop type', () => { | ||
expectNotAssignable({ | ||
parent: iface({ | ||
name: new StringType(), | ||
age: new IntegerType() | ||
}) | ||
}, { | ||
parent: iface({ | ||
name: new StringType() | ||
}) | ||
}); | ||
}); | ||
|
||
it('accepts with object member having same prop types', () => { | ||
expectAssignable({ | ||
parent: iface({ | ||
name: new StringType(), | ||
age: new IntegerType() | ||
}) | ||
}, { | ||
parent: iface({ | ||
name: new StringType(), | ||
age: new IntegerType() | ||
}) | ||
}); | ||
}); | ||
|
||
it('accepts with source member having dyanmic prop type', () => { | ||
expectAssignable({ | ||
parent: iface({ | ||
name: new StringType(), | ||
age: new IntegerType() | ||
}) | ||
}, { | ||
parent: new DynamicType() | ||
}); | ||
}); | ||
|
||
it('accepts with target member having dyanmic prop type', () => { | ||
expectAssignable({ | ||
parent: new DynamicType() | ||
}, { | ||
parent: iface({ | ||
name: new StringType(), | ||
age: new IntegerType() | ||
}) | ||
}); | ||
}); | ||
|
||
it('accepts with target member having "object" prop type', () => { | ||
expectAssignable({ | ||
parent: new ObjectType() | ||
}, { | ||
parent: iface({ | ||
name: new StringType(), | ||
age: new IntegerType() | ||
}) | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
function iface(members: Record<string, BscType>) { | ||
return new InterfaceType( | ||
new Map( | ||
Object.entries(members) | ||
) | ||
); | ||
} | ||
|
||
function expectAssignable(targetMembers: Record<string, BscType>, sourceMembers: Record<string, BscType>) { | ||
const targetIface = iface(targetMembers); | ||
const sourceIface = iface(sourceMembers); | ||
if (!sourceIface.isAssignableTo(targetIface)) { | ||
assert.fail(`expected type ${targetIface.toString()} to be assignable to type ${sourceIface.toString()}`); | ||
} | ||
} | ||
|
||
function expectNotAssignable(targetMembers: Record<string, BscType>, sourceMembers: Record<string, BscType>) { | ||
const targetIface = iface(targetMembers); | ||
const sourceIface = iface(sourceMembers); | ||
if (sourceIface.isAssignableTo(targetIface)) { | ||
assert.fail(`expected type ${targetIface.toString()} to not be assignable to type ${sourceIface.toString()}`); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,70 @@ | ||
import { isDynamicType, isInterfaceType } from '../astUtils/reflection'; | ||
import { isDynamicType, isInterfaceType, isObjectType } from '../astUtils/reflection'; | ||
import type { BscType } from './BscType'; | ||
|
||
export class InterfaceType implements BscType { | ||
public constructor( | ||
public members: Map<string, BscType> | ||
) { | ||
|
||
} | ||
|
||
/** | ||
* The name of the interface. Can be null. | ||
*/ | ||
public name: string; | ||
|
||
public isAssignableTo(targetType: BscType) { | ||
return ( | ||
isInterfaceType(targetType) || | ||
isDynamicType(targetType) | ||
); | ||
//we must have all of the members of the target type, and they must be equivalent types | ||
if (isInterfaceType(targetType)) { | ||
for (const [targetMemberName, targetMemberType] of targetType.members) { | ||
//we don't have the target member | ||
if (!this.members.has(targetMemberName)) { | ||
return false; | ||
} | ||
//our member's type is not assignable to the target member type | ||
if (!this.members.get(targetMemberName).isAssignableTo(targetMemberType)) { | ||
return false; | ||
} | ||
} | ||
//we have all of the target member's types. we are assignable! | ||
return true; | ||
|
||
//we are always assignable to dynamic or object | ||
} else if (isDynamicType(targetType) || isObjectType(targetType)) { | ||
return true; | ||
|
||
//not assignable to any other object types | ||
} else { | ||
return false; | ||
} | ||
} | ||
|
||
public isConvertibleTo(targetType: BscType) { | ||
return this.isAssignableTo(targetType); | ||
} | ||
|
||
public toString() { | ||
//TODO make this match the actual interface of the object | ||
return 'interface'; | ||
let result = '{'; | ||
for (const [key, type] of this.members.entries()) { | ||
result += ' ' + key + ': ' + type.toString() + ';'; | ||
} | ||
if (this.members.size > 0) { | ||
result += ' '; | ||
} | ||
return result + '}'; | ||
} | ||
|
||
public toTypeString(): string { | ||
return this.toString(); | ||
return 'object'; | ||
} | ||
|
||
public equals(targetType: BscType): boolean { | ||
return isInterfaceType(targetType); | ||
if (isInterfaceType(targetType)) { | ||
if (targetType.members.size !== this.members.size) { | ||
return false; | ||
} | ||
return targetType.isAssignableTo(this); | ||
} | ||
return false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters