Skip to content

Commit

Permalink
Unify type bounds: better type error tracing
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanjermakov committed Sep 12, 2024
1 parent 9763801 commit ed1f4f6
Show file tree
Hide file tree
Showing 8 changed files with 42 additions and 36 deletions.
1 change: 1 addition & 0 deletions src/ast/ast.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ describe('ast', () => {
stdTypeIds: {},
errors: [],
warnings: [],
unifyStack: [],
variableCounter: 0
}
return buildModuleAst(parseTree, idFromString('test'), source, false, ctx)
Expand Down
2 changes: 2 additions & 0 deletions src/e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const compile = async (files: { [path: string]: string }): Promise<Context> => {
stdTypeIds: {},
errors: [],
warnings: [],
unifyStack: [],
variableCounter: 0
}

Expand Down Expand Up @@ -74,6 +75,7 @@ const compileStd = async (): Promise<void> => {
stdTypeIds: {},
errors: [],
warnings: [],
unifyStack: [],
variableCounter: 0
}
const pkg = buildPackage(config.pkgPath, config.pkgName!, ctx)!
Expand Down
2 changes: 2 additions & 0 deletions src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ describe('nois', () => {
config: makeConfig('test', 'test.no'),
moduleStack: [],
packages: [],
stdTypeIds: {},
errors: [],
warnings: [],
unifyStack: [],
variableCounter: 0
}
const astRoot = buildModuleAst(root, idFromString('test'), source, false, ctx)
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const ctx: Context = {
stdTypeIds: {},
errors: [],
warnings: [],
unifyStack: [],
variableCounter: 0
}

Expand Down
39 changes: 19 additions & 20 deletions src/phase/type-unify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
makeInferredType,
makeReturnType
} from '../typecheck'
import { zip } from '../util/array'
import { dedup, zip } from '../util/array'
import { assign } from '../util/object'
import { assert, unreachable } from '../util/todo'

Expand Down Expand Up @@ -164,6 +164,7 @@ export const unifyTypeBounds = (node: AstNode, ctx: Context, report = true): voi
}

export const unifyType = (type: InferredType, ctx: Context): void => {
ctx.unifyStack.push(`unify ${inferredTypeToString(type)}`)
switch (type.kind) {
case 'inferred': {
const unified = type.bounds.reduce((a, b) => unify(a, b, ctx), { kind: 'hole' })
Expand Down Expand Up @@ -295,24 +296,27 @@ export const unifyType = (type: InferredType, ctx: Context): void => {
case 'error':
break
}
ctx.unifyStack.pop()
}

export const unify = (a: InferredType, b: InferredType, ctx: Context, stack: [string, string][] = []): InferredType => {
stack.push([inferredTypeToString(a), inferredTypeToString(b)])
let res = unify_(a, b, ctx, stack)
export const unify = (a: InferredType, b: InferredType, ctx: Context): InferredType => {
ctx.unifyStack.push(`unify [${inferredTypeToString(a)}, ${inferredTypeToString(b)}]`)
unifyType(a, ctx)
unifyType(b, ctx)
ctx.unifyStack.push(`unify [${inferredTypeToString(a)}, ${inferredTypeToString(b)}]`)
let res = unify_(a, b, ctx)
if (res.kind === 'error' && res.error.errorKind === 'unhandled') {
res = unify_(b, a, ctx, stack)
res = unify_(b, a, ctx)
}
if (res.kind === 'error') {
res.error.stack = [...stack]
res.error.stack = dedup([...ctx.unifyStack])
}
stack.pop()
ctx.unifyStack.pop()
ctx.unifyStack.pop()
return res
}

const unify_ = (a: InferredType, b: InferredType, ctx: Context, stack: [string, string][]): InferredType => {
unifyType(a, ctx)
unifyType(b, ctx)
const unify_ = (a: InferredType, b: InferredType, ctx: Context): InferredType => {
if (a === b) return a
if (b.kind === 'inferred' || b.kind === 'fn-type') {
unreachable(inferredTypeToString(b))
Expand All @@ -325,8 +329,8 @@ const unify_ = (a: InferredType, b: InferredType, ctx: Context, stack: [string,
kind: 'inferred-fn',
// TODO
generics: [],
params: zip(a.params, b.params, (a_, b_) => unify(a_, b_, ctx, stack)),
returnType: unify(a.returnType, b.returnType, ctx, stack)
params: zip(a.params, b.params, (a_, b_) => unify(a_, b_, ctx)),
returnType: unify(a.returnType, b.returnType, ctx)
}
return t
}
Expand All @@ -341,9 +345,7 @@ const unify_ = (a: InferredType, b: InferredType, ctx: Context, stack: [string,
case 'identifier': {
if (a.def && a.def === b.def) {
if (a.typeArgs.length === b.typeArgs.length) {
const typeArgs = <Identifier[]>(
zip(a.typeArgs, b.typeArgs, (ta, tb) => unify(ta, tb, ctx, stack))
)
const typeArgs = <Identifier[]>zip(a.typeArgs, b.typeArgs, (ta, tb) => unify(ta, tb, ctx))
const u: Identifier = {
kind: 'identifier',
parseNode: a.parseNode,
Expand All @@ -360,10 +362,7 @@ const unify_ = (a: InferredType, b: InferredType, ctx: Context, stack: [string,
case 'inferred-fn':
case 'fn-type':
case 'name':
const e = makeErrorType(
`failed unify [${a.kind}, ${b.kind}] [${[a, b].map(inferredTypeToString).join(', ')}]`,
'no-unify'
)
const e = makeErrorType(`failed unify [${[a, b].map(inferredTypeToString).join(', ')}]`, 'no-unify')
assign(a, e)
assign(b, e)
return a
Expand All @@ -379,7 +378,7 @@ const unify_ = (a: InferredType, b: InferredType, ctx: Context, stack: [string,
return a
}
if (a.unified) {
const u = unify(a.unified, b, ctx, stack)
const u = unify(a.unified, b, ctx)
assign(a.unified, u)
return u
}
Expand Down
25 changes: 11 additions & 14 deletions src/scope/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type Context = {
prelude?: Module
errors: SemanticError[]
warnings: SemanticError[]
unifyStack: string[]
/**
* Suppress all errors and warnings that coming while the field is false
*/
Expand Down Expand Up @@ -91,23 +92,19 @@ export const pathToId = (path: string, packageName?: string): Identifier => {
}

export const addError = (ctx: Context, error: SemanticError): void => {
if (!ctx.silent) {
// console.trace(
// prettySourceMessage(
// error.message,
// error.source,
// error.node.parseNode ? getSpan(error.node.parseNode) : undefined,
// error.notes
// )
// )
ctx.errors.push(error)
}
// console.trace(
// prettySourceMessage(
// error.message,
// error.source,
// error.node.parseNode ? getSpan(error.node.parseNode) : undefined,
// error.notes
// )
// )
ctx.errors.push(error)
}

export const addWarning = (ctx: Context, error: SemanticError): void => {
if (!ctx.silent) {
ctx.warnings.push(error)
}
ctx.warnings.push(error)
}

export const eachModule = (f: (module: Module, ctx: Context) => void, ctx: Context): void => {
Expand Down
2 changes: 1 addition & 1 deletion src/semantic/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,6 @@ export const genericError = (ctx: Context, def: AstNode, msg: string = 'error',

export const typeError = (ctx: Context, node: AstNode, e: ErrorType_, notes?: string[]): SemanticError => {
const msg = `type error (${e.errorKind})${e.message ? `: ${e.message}` : ''}`
const stackStr = e.stack && e.stack.length > 0 ? e.stack.map(ts => `\n in unify [${ts.join(', ')}]`).join('') : ''
const stackStr = e.stack && e.stack.length > 0 ? e.stack.map(t => `\n in ${t}`).join('') : ''
return semanticError(45, ctx, node, `${msg}${stackStr}`, notes)
}
6 changes: 5 additions & 1 deletion src/typecheck/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import { Generic, Type } from '../ast/type'
import { Context, idToString } from '../scope'
import { assert, unreachable } from '../util/todo'

/**
* TODO: attach "source" node to a type to indicate where this type is coming from
* set it for literals and explicit typings
*/
export type InferredType =
| {
kind: 'inferred'
Expand Down Expand Up @@ -51,7 +55,7 @@ export type ErrorType_ = {
errorKind: ErrorTypeKind
message?: string
reported: boolean
stack?: [string, string][]
stack?: string[]
}

export const makeInferredType = (bounds: InferredType[] = []) => ({ kind: <const>'inferred', bounds })
Expand Down

0 comments on commit ed1f4f6

Please sign in to comment.