Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typescript strict mode #37

Merged
merged 3 commits into from
Mar 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions src/__tests__/__snapshots__/type-checker-tests.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ exports[`
exports[`
id = x => x
((pair (id 3)) (id true))
1`] = `"G"`;
1`] = `"[Number*Boolean]"`;

exports[`
id = x => x
Expand Down Expand Up @@ -85,27 +85,27 @@ x => {

exports[`((() => \`look ma no args\`)) 1`] = `"String"`;

exports[`((addZero 1) 2) 1`] = `"D"`;
exports[`((addZero 1) 2) 1`] = `"Boolean"`;

exports[`((pair 1) 2) 1`] = `"F"`;
exports[`((pair 1) 2) 1`] = `"[Number*Number]"`;

exports[`([1|[2|t]] => t) 1`] = `"Array<Number> -> Array<Number>"`;

exports[`([1|[true|t]] => t) 1`] = `"Type error: type mismatch"`;

exports[`([h|[true|t]] => [[h 1] t]) 1`] = `"Type error: type mismatch"`;

exports[`([x|[y|t]] => [[x y] t]) 1`] = `"Array<K> -> Array<Array<K>>"`;
exports[`([x|[y|t]] => [[x y] t]) 1`] = `"Array<G> -> Array<Array<G>>"`;

exports[`(a => 1) 1`] = `"B -> Number"`;

exports[`(addZero 1) 1`] = `"C"`;
exports[`(addZero 1) 1`] = `"Number -> Boolean"`;

exports[`(if (true) 1 else \`two\`) 1`] = `"Type error: type mismatch"`;

exports[`(inc 1) 1`] = `"Number"`;

exports[`(pair 1) 1`] = `"E"`;
exports[`(pair 1) 1`] = `"C -> [Number*C]"`;

exports[`(zero 0) 1`] = `"Boolean"`;

Expand All @@ -127,7 +127,7 @@ exports[`array-destructure.peach 1`] = `"Array<Number>"`;

exports[`f => (f f) 1`] = `"Type error: recursive unification"`;

exports[`f => g => arg => (g (f arg)) 1`] = `"(H -> I) -> (I -> J) -> H -> J"`;
exports[`f => g => arg => (g (f arg)) 1`] = `"(D -> E) -> (E -> F) -> D -> F"`;

exports[`fibonacci.peach 1`] = `"Array<Number>"`;

Expand Down
12 changes: 6 additions & 6 deletions src/__tests__/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,22 @@ import parse from '../parser'
import typeCheck from '../type-checker'
import interpret from '../interpreter'

export function fixture (fileName) {
export function fixture (fileName: string) {
const filePath = join(__dirname, 'fixtures', fileName)
return readFileSync(filePath, 'utf8')
}

export function run (program) {
export function run (source: string) {
const env = getRootEnv()
const typeEnv = getTypeEnv(env)

const ast = parse(program)
const ast = parse(source)
const [typed] = typeCheck(ast, typeEnv)
return interpret(typed, getRootEnv())
}

export function testResult (program, expectedOutput) {
test(program, () => {
expect(run(program)[0]).toEqual(expectedOutput)
export function testResult (source: string, expectedOutput: any) {
test(source, () => {
expect(run(source)[0]).toEqual(expectedOutput)
})
}
8 changes: 4 additions & 4 deletions src/__tests__/parser-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import { fixture } from './helpers'
// snapshot tests for the parser
//

function testFixture (fixtureName) {
function testFixture (fixtureName: string) {
test(fixtureName, () => {
expect(parse(fixture(fixtureName))).toMatchSnapshot()
})
}

function testParse (peachCode) {
test(peachCode, () => {
expect(parse(peachCode)).toMatchSnapshot()
function testParse (source: string) {
test(source, () => {
expect(parse(source)).toMatchSnapshot()
})
}

Expand Down
35 changes: 18 additions & 17 deletions src/__tests__/type-checker-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import typeCheck from '../type-checker'
import { getRootEnv, getTypeEnv } from '../env'
import { fixture } from './helpers'
import { clone } from '../util'
import { Type } from '../types'
import { TypedNode, AstNode } from '../node-types'

import {
Expand All @@ -14,23 +15,23 @@ import {
BooleanType
} from '../types'

function testTypeCheck (code, env = getTypeEnv(getRootEnv())) {
test(code, () => {
const parsed = parse(code)
function testTypeCheck (source: string, env = getTypeEnv(getRootEnv())) {
test(source, () => {
const parsed = parse(source)
const [lastNode] = typeCheck(parsed, env)
const type = lastNode.exprType.toString()
expect(type).toMatchSnapshot()
})
};

function testFails (code, env = getTypeEnv(getRootEnv())) {
test(code, () => {
const parsed = parse(code)
function testFails (source: string, env = getTypeEnv(getRootEnv())) {
test(source, () => {
const parsed = parse(source)
expect(() => typeCheck(parsed, env)).toThrowErrorMatchingSnapshot()
})
}

function testFixture (fixtureName, env = getTypeEnv(getRootEnv())) {
function testFixture (fixtureName: string, env = getTypeEnv(getRootEnv())) {
test(fixtureName, () => {
const parsed = parse(fixture(fixtureName))
const [lastNode] = typeCheck(parsed, env)
Expand Down Expand Up @@ -86,7 +87,7 @@ testFails(`if (1) 1 else 2`)

// the peach type checker works over nodes with an `exprType` property.
// helper for creating nodes for synthetic type tests
function typed (type): TypedNode {
function typed (type: Type): TypedNode {
return {
exprType: type,
type: 'Str',
Expand All @@ -96,11 +97,11 @@ function typed (type): TypedNode {

// simulate a user-defined type
class PairType extends TypeOperator {
constructor (a, b) {
constructor (a: Type, b: Type) {
super('*', [a, b])
}

static of (name, [a, b]) {
static of (name: string, [a, b]: Type[]) {
return new PairType(a, b)
}

Expand All @@ -109,9 +110,9 @@ class PairType extends TypeOperator {
}
}

const A = typed(new TypeVariable())
const B = typed(new TypeVariable())
const AB = typed(new PairType(A, B))
const A = new TypeVariable()
const B = new TypeVariable()
const AB = new PairType(A, B)

const testEnv = () => ({
// No unit type yet, so all functions take exactly one argument
Expand All @@ -121,14 +122,14 @@ const testEnv = () => ({
// // Number -> Boolean
zero: typed(new FunctionType(NumberType, BooleanType)),

add: typed(new FunctionType(typed(NumberType), typed(new FunctionType(typed(NumberType), typed(NumberType))))),
sub: typed(new FunctionType(typed(NumberType), typed(new FunctionType(typed(NumberType), typed(NumberType))))),
add: typed(new FunctionType(NumberType, new FunctionType(NumberType, NumberType))),
sub: typed(new FunctionType(NumberType, new FunctionType(NumberType, NumberType))),

// Number -> Number -> Boolean
addZero: typed(new FunctionType(typed(NumberType), typed(new FunctionType(typed(NumberType), typed(BooleanType))))),
addZero: typed(new FunctionType(NumberType, new FunctionType(NumberType, BooleanType))),

// A -> B -> A*B
pair: typed(new FunctionType(A, typed(new FunctionType(B, AB))))
pair: typed(new FunctionType(A, new FunctionType(B, AB)))
})

// testTypeCheck(`inc`, testEnv())
Expand Down
16 changes: 9 additions & 7 deletions src/__tests__/unify-tests.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
/* eslint-env jest */
import unify from '../unify'
import { StringType } from '../types'
import { AstNode, AstStringNode, AstNumeralNode, AstNameNode } from '../node-types'

describe('unify', () => {
it('can unify one matching value', () => {
const patterns = [{ type: 'Str', value: 'hai' }]
const patterns: AstStringNode[] = [{ type: 'Str', value: 'hai' }]
const values = ['hai']
expect(unify(patterns, values)).toEqual({
didMatch: true,
Expand All @@ -12,7 +14,7 @@ describe('unify', () => {
})

it('can unify several matching values', () => {
const patterns = [{ type: 'Numeral', value: 1 }, { type: 'Numeral', value: 2 }]
const patterns: AstNumeralNode[] = [{ type: 'Numeral', value: 1 }, { type: 'Numeral', value: 2 }]
const values = [1, 2]
expect(unify(patterns, values)).toEqual({
didMatch: true,
Expand All @@ -22,25 +24,25 @@ describe('unify', () => {

// TODO this test is better handled by a type system
it('cannot unify a loosely equal value', () => {
const patterns = [{ type: 'Numeral', value: 0 }]
const patterns: AstNumeralNode[] = [{ type: 'Numeral', value: 0 }]
const values = [false]
expect(unify(patterns, values)).toEqual({ didMatch: false, bindings: {} })
})

it('cannot unify when the number of patterns and values differs', () => {
const patterns = [{ type: 'Numeral', value: 1 }, { type: 'Numeral', value: 2 }]
const patterns: AstNumeralNode[] = [{ type: 'Numeral', value: 1 }, { type: 'Numeral', value: 2 }]
const values = [1]
expect(unify(patterns, values)).toEqual({ didMatch: false, bindings: {} })
})

it('cannot unify when some but not all patterns match', () => {
const patterns = [{ type: 'Numeral', value: 1 }, { type: 'Numeral', value: 2 }]
const patterns: AstNumeralNode[] = [{ type: 'Numeral', value: 1 }, { type: 'Numeral', value: 2 }]
const values = [1, 3]
expect(unify(patterns, values)).toEqual({ didMatch: false, bindings: {} })
})

it('can unify a variable', () => {
const patterns = [{ type: 'Name', name: 'x' }]
const patterns: AstNameNode[] = [{ type: 'Name', name: 'x' }]
const values = [1]
expect(unify(patterns, values)).toEqual({
didMatch: true,
Expand All @@ -49,7 +51,7 @@ describe('unify', () => {
})

it('can unify mixed variables and values', () => {
const patterns = [{ type: 'Name', name: 'x' }, { type: 'Numeral', value: 2 }]
const patterns: AstNode[] = [{ type: 'Name', name: 'x' }, { type: 'Numeral', value: 2 }]
const values = [1, 2]
expect(unify(patterns, values)).toEqual({
didMatch: true,
Expand Down
4 changes: 2 additions & 2 deletions src/array.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// TODO Array wrapper type
export function isArray ({ type }) {
export function isArray ({ type }: { type: string }) {
return type === 'Array'
}

// TODO generic value-based equality. This fails for nested arrays
// because the `every` check uses ===. It needs to find the value equality
// function for the operand types.
export function isEqual (arrayA, arrayB) {
export function isEqual (arrayA: any[], arrayB: any[]) {
return arrayA.length === arrayB.length &&
arrayA.every((v, i) => v === arrayB[i])
}
20 changes: 11 additions & 9 deletions src/bin/peach.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,19 @@ import { getRootEnv, getTypeEnv } from '../env'
import interpret from '../interpreter'
import typeCheck from '../type-checker'

function readArgs (inputArgs) {
function readArgs (inputArgs: string[]) {
const argv = parseArgs(inputArgs)
const inputPath = argv._[0] || null
return Object.assign({ inputPath }, argv)
}

function read (filePath) {
function read (filePath: string) {
return readFileSync(filePath, 'utf8')
}

function runScript (path) {
function runScript (filePath: string) {
try {
const ast = parse(read(path))
const ast = parse(read(filePath))
const env = getRootEnv()
const typeEnv = getTypeEnv(env)

Expand All @@ -32,7 +32,7 @@ function runScript (path) {
return 0
} catch (e) {
if (/ENOENT/.test(e.message)) {
console.error(`Could not open the file ${path}`)
console.error(`Could not open the file ${filePath}`)
return 1
} else {
console.error(e.message)
Expand All @@ -41,24 +41,26 @@ function runScript (path) {
}
}

function runPath (args, done) {
function runPath (args: any, done: OnComplete) {
const scriptPath = resolve(args.inputPath)
const status = runScript(scriptPath)

return done(status)
}

function repl (args, done) {
function repl (args: any, done: OnComplete) {
startRepl(args, () => done(0))
}

function run (args, onComplete) {
function run (args: any, onComplete: OnComplete) {
if (args.inputPath == null) {
repl(args, onComplete)
} else {
runPath(args, onComplete)
}
}

type OnComplete = (status: number) => void

const args = readArgs(process.argv.slice(2))
run(args, (status) => process.exit(status))
run(args, status => process.exit(status))
4 changes: 3 additions & 1 deletion src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ export function getRootEnv (): RuntimeEnv {
}

export function getTypeEnv (valueEnv: RuntimeEnv): TypeEnv {
const initialState: TypeEnv = {}

return Object.keys(valueEnv).reduce((env, name) => {
env[name] = extend(valueEnv[name], {
exprType: valueEnv[name].exprType
})
return env
}, {})
}, initialState)
}

export type TypeEnv = { [name: string]: TypedNode }
Expand Down
4 changes: 2 additions & 2 deletions src/errors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// with help from http://stackoverflow.com/a/32749533/2806996
export default class PeachError extends Error {
constructor (message) {
constructor (message: string) {
super(message)
this.name = this.constructor.name
this.message = message
Expand All @@ -12,4 +12,4 @@ export default class PeachError extends Error {
this.stack = (new Error(message).stack)
}
}
}
}
Loading