Skip to content

Commit

Permalink
Unify types: unify field access; separate type def and literal type
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanjermakov committed Aug 20, 2024
1 parent ffbc99f commit 384a76b
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 65 deletions.
1 change: 0 additions & 1 deletion src/ast/op.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { ParseNode, filterNonAstNodes } from '../parser'
import { Context } from '../scope'
import { Static } from '../semantic'
import { ConcreteGeneric } from '../typecheck'
import { Arg, AstNode, AstNodeKind, BaseAstNode, buildArg } from './index'
import { Name, buildName } from './operand'
import { Type, buildType } from './type'
Expand Down
9 changes: 5 additions & 4 deletions src/debug.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { AstNode } from './ast'
import { inferredTypeToString } from './typecheck'

export const debugAst = (node: AstNode): any => {
export const debugAst = (node: AstNode, depth = 0): any => {
if (depth > 50) return '@rec'
if (typeof node !== 'object') return node
return Object.fromEntries(
Object.entries(node)
Expand All @@ -11,14 +12,14 @@ export const debugAst = (node: AstNode): any => {
if (p === 'type') {
return [p, inferredTypeToString(v)]
}
if (p === 'def') {
if (p === 'def' || p === 'typeDef') {
return [p, v.kind]
}
if (Array.isArray(v)) {
return [p, v.map(debugAst)]
return [p, v.map(i => debugAst(i, depth + 1))]
}
if (typeof v === 'object' && 'parseNode' in v) {
return [p, debugAst(v)]
return [p, debugAst(v, depth + 1)]
}
return [p, v]
})
Expand Down
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,11 @@ ctx.moduleStack.push(m)
unifyTypeBounds(m, ctx)
ctx.moduleStack.pop()

console.log(inspect(debugAst(pkg.modules[0].block), { compact: true, depth: null, breakLength: 120 }))

reportErrors(ctx)
reportWarnings(ctx)

if (config.emit) {
await emitPackage(isDir, pkg, ctx)
}

console.log(inspect(debugAst(pkg.modules[0].block), { compact: true, depth: null, breakLength: 120 }))
54 changes: 32 additions & 22 deletions src/phase/top-scope-type.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { AstNode } from '../ast'
import { Identifier, Name } from '../ast/operand'
import { Context } from '../scope'
import { makeDefType, makeTypeParam } from '../typecheck'
import { makeDefType, makeErrorType, makeTypeParam } from '../typecheck'
import { unitType } from '../typecheck/type'
import { assert, todo, unreachable } from '../util/todo'

Expand All @@ -14,17 +15,17 @@ export const setTopScopeDefType = (node: AstNode, ctx: Context) => {
break
}
case 'trait-def': {
node.type = makeDefType(node.name)
node.type = makeDefType(node)
node.generics.forEach(g => setTopScopeDefType(g, ctx))
break
}
case 'type-def': {
node.type = makeDefType(node.name)
node.type = makeDefType(node)
node.generics.forEach(g => setTopScopeDefType(g, ctx))
break
}
case 'generic': {
node.type = makeTypeParam(node.name)
node.type = makeTypeParam(node)
break
}
}
Expand All @@ -42,8 +43,7 @@ export const setTopScopeType = (node: AstNode, ctx: Context) => {
case 'var-def': {
if (node.pattern.expr.kind !== 'name') return unreachable()
const def = node.pattern.expr
assert(!!node.varType)
def.type = makeDefType(node.varType!)
def.type = node.varType ?? makeErrorType()
break
}
case 'fn-def': {
Expand All @@ -56,26 +56,36 @@ export const setTopScopeType = (node: AstNode, ctx: Context) => {
if (node.returnType) {
setTopScopeType(node.returnType, ctx)
}
node.type = makeDefType({
node.type = {
kind: 'fn-type',
generics: node.generics,
paramTypes: node.params.map(p => p.paramType!),
returnType: node.returnType ? node.returnType : unitType.type
})
returnType: node.returnType ? node.returnType : <Name>unitType.def
}
break
}
case 'type-def': {
node.variants.forEach(v => {
v.fieldDefs.forEach(f => setTopScopeType(f, ctx))
const fnType = {
kind: <const>'fn-type',
generics: node.generics,
paramTypes: v.fieldDefs.map(f => f.fieldType),
returnType: node.name
}
setTopScopeType(fnType, ctx)
v.type = makeDefType(fnType)
})
node.variants.forEach(v => setTopScopeType(v, ctx))
break
}
case 'variant': {
node.fieldDefs.forEach(f => setTopScopeType(f, ctx))
// TODO: ugly
const typeDefId: Identifier = {
kind: 'identifier',
parseNode: node.parseNode,
names: [node.typeDef!.name],
typeArgs: [],
def: node.typeDef
}
const fnType = {
kind: <const>'fn-type',
generics: node.typeDef!.generics,
paramTypes: node.fieldDefs.map(f => f.fieldType),
returnType: typeDefId
}
setTopScopeType(fnType, ctx)
node.type = fnType
break
}
case 'field-def': {
Expand Down Expand Up @@ -119,15 +129,15 @@ export const setTopScopeType = (node: AstNode, ctx: Context) => {
node.generics.forEach(pt => setTopScopeType(pt, ctx))
node.paramTypes.forEach(pt => setTopScopeType(pt, ctx))
setTopScopeType(node.returnType, ctx)
node.type = makeDefType(node)
node.type = node
break
}
case 'hole': {
node.type = { kind: 'hole' }
break
}
case 'generic': {
node.type = makeTypeParam(node.name)
node.type = makeTypeParam(node)
break
}
}
Expand Down
13 changes: 8 additions & 5 deletions src/phase/type-bound.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
InferredType,
addBounds,
instantiateDefType,
makeDefType,
makeFieldAccessType,
makeInferredType,
makeReturnType
} from '../typecheck'
Expand Down Expand Up @@ -139,8 +139,11 @@ export const collectTypeBounds = (node: AstNode, ctx: Context, parentBound?: Inf
node.type = makeReturnType(fnType)
break
}
case 'field-access-op': {
node.type = makeFieldAccessType(node)
break
}
case 'method-call-op':
case 'field-access-op':
case 'unwrap-op':
case 'bind-op':
case 'await-op': {
Expand Down Expand Up @@ -188,7 +191,7 @@ export const collectTypeBounds = (node: AstNode, ctx: Context, parentBound?: Inf
}
case 'var-def': {
if (node.expr) {
collectTypeBounds(node.expr, ctx, node.varType ? makeDefType(node.varType) : undefined)
collectTypeBounds(node.expr, ctx, node.varType ? node.varType : undefined)
}
collectTypeBounds(node.pattern, ctx, node.expr?.type)
node.type = instantiateDefType(unitType)
Expand All @@ -198,11 +201,11 @@ export const collectTypeBounds = (node: AstNode, ctx: Context, parentBound?: Inf
node.generics.forEach(g => collectTypeBounds(g, ctx))
node.params.forEach(p => collectTypeBounds(p, ctx))
if (node.block) {
if (node.type?.kind !== 'def' || node.type.type.kind !== 'fn-type') {
if (node.type?.kind !== 'fn-type') {
unreachable()
break
}
collectTypeBounds(node.block, ctx, makeDefType(node.type.type.returnType))
collectTypeBounds(node.block, ctx, node.type.returnType)
}
break
}
Expand Down
49 changes: 44 additions & 5 deletions src/phase/type-unify.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AstNode } from '../ast'
import { Context, addError } from '../scope'
import { addError, Context, idToString } from '../scope'
import { typeError } from '../semantic/error'
import { InferredType, inferredTypeToString, makeDefType, typeToString } from '../typecheck'
import { InferredType, inferredTypeToString, makeDefType, makeErrorType } from '../typecheck'
import { zip } from '../util/array'
import { todo, unreachable } from '../util/todo'

Expand Down Expand Up @@ -178,6 +178,25 @@ const unifyType = (type: InferredType): void => {
case 'inferred-fn':
// TODO
break
case 'field-access':
unifyType(type.operandType)
if (type.operandType.kind === 'def' && type.operandType.def?.kind === 'type-def') {
const typeDef = type.operandType.def
if (typeDef.variants.length > 1) {
// TODO
break
}
const f = typeDef.variants[0].fieldDefs.find(f => f.name.value === type.fieldName.value)
if (!f) {
// TODO
break
}
// TODO: handle type-def generics
Object.assign(type, f.type!)
break
}
Object.assign(type, makeErrorType(inferredTypeToString(type)))
break
case 'return':
unifyType(type.type)
const ret = extractReturnType(type.type)
Expand All @@ -187,6 +206,14 @@ const unifyType = (type: InferredType): void => {
}
Object.assign(type, ret)
break
case 'identifier':
Object.assign(type, type.def ? makeDefType(type.def) : makeErrorType(`no def: ${idToString(type)}`))
break
case 'fn-type':
// TODO: should be unreachable
break
case 'name':
case 'type-param':
case 'hole':
case 'def':
case 'error':
Expand Down Expand Up @@ -233,7 +260,7 @@ const unify_ = (a: InferredType, b: InferredType): InferredType => {
switch (b.kind) {
case 'def':
// TODO: proper equality
if (typeToString(a.type) === typeToString(b.type)) {
if (a.def === b.def) {
return a
} else {
const e = {
Expand Down Expand Up @@ -261,12 +288,19 @@ const unify_ = (a: InferredType, b: InferredType): InferredType => {
a.unified = b
}
return b
case 'identifier':
case 'fn-type':
case 'name':
// TODO
break
case 'hole':
return b
case 'error':
return a
case 'inferred':
case 'field-access':
case 'return':
// these should never appear in a result of `unifyType`
return unreachable()
}
return {
Expand All @@ -278,6 +312,7 @@ const unify_ = (a: InferredType, b: InferredType): InferredType => {
const extractReturnType = (type: InferredType): InferredType | undefined => {
switch (type.kind) {
case 'inferred':
case 'field-access':
unifyType(type)
return extractReturnType(type)
case 'type-param':
Expand All @@ -289,14 +324,18 @@ const extractReturnType = (type: InferredType): InferredType | undefined => {
return type.returnType
case 'return':
return extractReturnType(type.type)
case 'identifier':
case 'name':
case 'hole':
case 'error':
return undefined
case 'def':
if (type.type.kind === 'fn-type') {
if (type.def.kind === 'fn-def') {
unreachable()
return makeDefType(type.type.returnType)
return type.def.returnType!
}
return undefined
case 'fn-type':
return unreachable()
}
}
Loading

0 comments on commit 384a76b

Please sign in to comment.