Skip to content

Commit

Permalink
Type bound collection prep
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanjermakov committed Jul 31, 2024
1 parent 6b7d41e commit 2a47687
Show file tree
Hide file tree
Showing 4 changed files with 30 additions and 284 deletions.
3 changes: 1 addition & 2 deletions src/ast/type.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { BaseAstNode } from '.'
import { ParseNode, filterNonAstNodes } from '../parser'
import { Context } from '../scope'
import { Checked } from '../semantic'
import { Hole, buildHole } from './match'
import { Identifier, Name, buildIdentifier, buildName } from './operand'

export type Type = (Identifier | FnType | Hole) & Partial<Checked>
export type Type = Identifier | FnType | Hole

export const buildType = (node: ParseNode, ctx: Context): Type => {
const n = filterNonAstNodes(node)[0]
Expand Down
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { buildPackage } from './package/io'
import { resolveImport } from './phase/import-resolve'
import { resolveModuleScope } from './phase/module-resolve'
import { resolveName } from './phase/name-resolve'
import { collectTypeBounds } from './phase/type-bound'
import { Context, eachModule as forEachModule, pathToVid } from './scope'
import { Source } from './source'
import { assert } from './util/todo'
Expand Down Expand Up @@ -121,7 +122,7 @@ ctx.packages = packages
ctx.prelude = std.modules.find(m => m.identifier.names.at(-1)! === 'prelude')!
assert(!!ctx.prelude, 'no prelude')

const phases = [resolveModuleScope, resolveImport, resolveName]
const phases = [resolveModuleScope, resolveImport, resolveName, collectTypeBounds]
phases.forEach(f => forEachModule(f, ctx))

reportErrors(ctx)
Expand Down
10 changes: 10 additions & 0 deletions src/phase/type-bound.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Module } from '../ast'
import { Context } from '../scope'
import { todo } from '../util/todo'

/**
* Assign every suitable expression its type and collect its bounds
*/
export const collectTypeBounds = (module: Module, ctx: Context): void => {
return todo()
}
298 changes: 17 additions & 281 deletions src/typecheck/index.ts
Original file line number Diff line number Diff line change
@@ -1,291 +1,27 @@
import { Operand } from '../ast/operand'
import { Generic, Type } from '../ast/type'
import { Context, addError } from '../scope'
import { InstanceRelation, findSuperRelChains, getConcreteTrait } from '../scope/trait'
import { idToVid, vidEq, vidToString } from '../scope/util'
import { VirtualIdentifier, resolveVid, typeKinds } from '../scope/vid'
import { expectedTypeError } from '../semantic/error'
import { zip } from '../util/array'
import { todo, unreachable } from '../util/todo'
import { holeType, selfType, unknownType } from './type'
import { Identifier } from '../ast/operand'
import { Type } from '../ast/type'

export type VirtualType = VidType | VirtualFnType | VirtualGeneric | UnknownType | MalleableType | HoleType

export type VidType = {
kind: 'vid-type'
identifier: VirtualIdentifier
typeArgs: VirtualType[]
}

export type VirtualFnType = {
kind: 'fn-type'
generics: VirtualGeneric[]
paramTypes: VirtualType[]
returnType: VirtualType
}

export type UnknownType = {
kind: 'unknown-type'
mismatchedBranches?: { then: VirtualType; else?: VirtualType }
mismatchedMatchClauses?: VirtualType[]
}

/**
* Type that is resolved to its first usage.
* Closures are initially defined with this type
*/
export type MalleableType = {
kind: 'malleable-type'
operand: Operand
}

export type HoleType = {
kind: 'hole-type'
export type InferredType = {
kind: 'inferred'
bounds: Type[]
unified?: Type
}

export type VirtualGeneric = {
kind: 'generic'
name: string
key: string
bounds: VirtualType[]
}

export type ConcreteGeneric = {
generic: VirtualGeneric
impls: InstanceRelation[]
}

export const virtualTypeToString = (vt: VirtualType): string => {
switch (vt.kind) {
case 'vid-type': {
const t = vidToString(vt.identifier)
if (vt.typeArgs.length === 0) {
return t
} else {
const typeArgs = vt.typeArgs.map(virtualTypeToString)
return `${t}<${typeArgs.join(', ')}>`
}
}
case 'fn-type': {
const t = `|${vt.paramTypes.map(virtualTypeToString).join(', ')}|: ${virtualTypeToString(vt.returnType)}`
if (vt.generics.length === 0) {
return t
} else {
const generics = vt.generics.map(virtualTypeToString)
return `<${generics.join(', ')}>${t}`
}
}
case 'generic':
const bounds = vt.bounds.map(virtualTypeToString)
return `${vt.name}${bounds.length > 0 ? `: ${bounds.join(' + ')}` : ''}`
case 'hole-type':
return '_'
case 'unknown-type':
case 'malleable-type':
return '?'
}
}

export const typeToVirtual = (type: Type, ctx: Context): VirtualType => {
switch (type.kind) {
export const typeToString = (t: Type): string => {
switch (t.kind) {
case 'identifier':
const vid = idToVid(type)
const ref = resolveVid(vid, ctx, typeKinds)
if (!ref) {
return unknownType
}
if (ref.node.kind === 'self') {
return selfType
} else if (ref.node.kind === 'generic') {
return genericToVirtual(ref.node, ctx)
} else if (ref.node.kind === 'trait-def' || ref.node.kind === 'type-def') {
return {
kind: 'vid-type',
identifier: ref.vid,
typeArgs: type.typeArgs
// self args are for bounds and should be excluded from virtual types
.filter(a => a.kind !== 'identifier' || a.names.at(-1)!.value !== 'Self')
.map(arg => typeToVirtual(arg, ctx))
}
} else {
addError(ctx, expectedTypeError(ctx, type, ref.node.kind))
return unknownType
}
return idToString(t)
case 'fn-type':
return {
kind: 'fn-type',
generics: type.generics.map(g => genericToVirtual(g, ctx)),
paramTypes: type.paramTypes.map(pt => typeToVirtual(pt, ctx)),
returnType: typeToVirtual(type.returnType, ctx)
}
const main = `|${t.paramTypes.map(typeToString).join(', ')}|: ${typeToString(t.returnType)}`
const typeArgs = t.generics.length > 0 ? `<${t.generics.map(g => g.name.value).join(', ')}>` : ''
return typeArgs + main
case 'hole':
return holeType
case 'type-bounds':
return todo('type-bounds')
}
}

export const genericToVirtual = (generic: Generic, ctx: Context): VirtualGeneric => {
if (!generic.key) {
generic.key = genericKey(generic, ctx)
}
return {
kind: 'generic',
name: generic.name.value,
key: generic.key,
bounds: generic.bounds.map(b => typeToVirtual(b, ctx))
}
}

export const genericKey = (generic: Generic, ctx: Context): string => {
const module = ctx.moduleStack.at(-1)!
const scopeName = module.scopeStack
.map(s => {
switch (s.kind) {
case 'type':
case 'fn':
switch (s.def.kind) {
case 'type-def':
return `type_${s.def.name.value}`
case 'fn-def':
return `fn_${s.def.name.value}`
case 'closure-expr':
return `closure`
default:
return unreachable()
}
case 'instance':
switch (s.def.kind) {
case 'trait-def':
return `trait_${s.def.name.value}`
case 'impl-def':
const implName = s.def.identifier.names.at(-1)!.value
const forName = s.def.forTrait ? s.def.forTrait.names.at(-1)!.value : ''
return `impl_${implName}_${forName}`
default:
return unreachable()
}
default:
return undefined
}
})
.filter(s => !!s)
.join('_')
return `${scopeName}_${generic.name.value}`
}

export const isAssignable = (t: VirtualType, target: VirtualType, ctx: Context): boolean => {
if (t === target) return true
if (t.kind === 'unknown-type' || target.kind === 'unknown-type') return true
if (t.kind === 'hole-type' || target.kind === 'hole-type') return true
if (t.kind === 'vid-type' && vidToString(t.identifier) === 'std::never::Never') return true

if (target.kind === 'generic') {
return target.bounds.every(b => isAssignable(t, b, ctx))
}
if (t.kind === 'generic') {
return t.bounds.some(b => isAssignable(b, target, ctx))
}

if (t.kind === 'vid-type' && target.kind === 'vid-type') {
if (vidEq(t.identifier, target.identifier)) {
if (t.typeArgs.length !== target.typeArgs.length) return false

for (let i = 0; i < t.typeArgs.length; i++) {
const tArg = t.typeArgs[i]
const targetArg = target.typeArgs[i]
if (!isAssignable(tArg, targetArg, ctx)) {
return false
}
}
return true
}
const superRelChains = findSuperRelChains(t.identifier, ctx)
return superRelChains
.map(c => c.at(-1)!)
.some(superRel => {
if (vidEq(superRel.implDef.vid, target.identifier)) {
const supertype = extractConcreteSupertype(t, target.identifier, ctx)
if (!supertype) return false
return isAssignable(supertype, target, ctx)
}
return false
})
}
if (t.kind === 'fn-type' && target.kind === 'fn-type') {
for (let i = 0; i < target.paramTypes.length; i++) {
const tp = t.paramTypes.at(i)
const targetP = target.paramTypes[i]
if (!tp || !isAssignable(targetP, tp, ctx)) {
return false
}
}
return isAssignable(t.returnType, target.returnType, ctx)
}
return false
}

export const typeEq = (a: VirtualType, b: VirtualType): boolean => {
if (a.kind === 'unknown-type' && b.kind === 'unknown-type') return false
if (a.kind === 'hole-type' && b.kind === 'hole-type') return true
if (a.kind === 'generic' || b.kind === 'generic') return true
if (a.kind === 'vid-type' && b.kind === 'vid-type') {
return (
vidEq(a.identifier, b.identifier) &&
a.typeArgs.length === b.typeArgs.length &&
zip(a.typeArgs, b.typeArgs, (a_, b_) => [a_, b_]).every(([a_, b_]) => typeEq(a_, b_))
)
}
if (a.kind === 'fn-type' && b.kind === 'fn-type') {
return (
a.paramTypes.length === b.paramTypes.length &&
zip(a.paramTypes, b.paramTypes, (a_, b_) => [a_, b_]).every(([a_, b_]) => typeEq(a_, b_)) &&
typeEq(a.returnType, b.returnType)
)
}
return false
}

/**
* TODO: better combine logic
*/
export const combine = (a: VirtualType, b: VirtualType, ctx: Context): VirtualType | undefined => {
if (isAssignable(a, b, ctx)) {
return a
}
if (isAssignable(b, a, ctx)) {
return b
return '_'
}
return undefined
}

/**
* Extract concrete type of a supertype.
* Example: `extractConcreteSupertype(List<Int>, Iterable) -> Iterable<List>`
* TODO: what if multiple concrete types possible?
*/
export const extractConcreteSupertype = (
type: VirtualType,
superVid: VirtualIdentifier,
ctx: Context
): VirtualType | undefined => {
if (type.kind !== 'vid-type') return undefined

if (vidEq(type.identifier, superVid)) {
return type
}

const chain = findSuperRelChains(type.identifier, ctx)
.filter(c => {
const implType = <VidType>c.at(-1)!.implType
return vidEq(implType.identifier, superVid)
})
.at(0)
if (!chain) return undefined

let t: VirtualType = type
for (const ir of chain) {
t = getConcreteTrait(t, ir, ctx)
}
return t
export const idToString = (id: Identifier): string => {
const main = id.names.map(n => n.value).join('::')
const typeArgs = id.typeArgs.length > 0 ? `<${id.typeArgs.map(typeToString).join(', ')}>` : ''
return main + typeArgs
}

0 comments on commit 2a47687

Please sign in to comment.