Skip to content

Commit

Permalink
Codegen: insert default methods into impls
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanjermakov committed Mar 9, 2024
1 parent d52f958 commit 5a7666b
Show file tree
Hide file tree
Showing 5 changed files with 31 additions and 21 deletions.
2 changes: 2 additions & 0 deletions src/ast/statement.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ParseNode, filterNonAstNodes } from '../parser'
import { MethodDef } from '../scope/vid'
import { Checked, Typed } from '../semantic'
import { assert } from '../util/todo'
import { Expr, buildExpr } from './expr'
Expand Down Expand Up @@ -134,6 +135,7 @@ export interface ImplDef extends AstNode<'impl-def'> {
generics: Generic[]
forTrait?: Identifier
block: Block
superMethods?: MethodDef[]
}

export const buildImplDef = (node: ParseNode): ImplDef => {
Expand Down
2 changes: 1 addition & 1 deletion src/codegen/js/expr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export const emitBinaryExpr = (binaryExpr: BinaryExpr, module: Module, ctx: Cont
emit: emitLines([
lOp.emit,
emitLines(args.map(a => a.emit)),
jsVariable(resultVar, `${lOp.resultVar}.${traitName}.${methodName}(${argsEmit})`)
jsVariable(resultVar, `${lOp.resultVar}.${traitName}().${methodName}(${argsEmit})`)
]),
resultVar
}
Expand Down
8 changes: 5 additions & 3 deletions src/codegen/js/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Module } from '../../ast'
import { Context } from '../../scope'
import { InstanceRelation } from '../../scope/trait'
import { InstanceRelation, relTypeName } from '../../scope/trait'
import { concatVid, vidFromScope, vidFromString } from '../../scope/util'
import { VirtualIdentifier } from '../../scope/vid'
import { virtualTypeToString } from '../../typecheck'
Expand Down Expand Up @@ -97,11 +97,13 @@ export const jsError = (message?: string): string => {
}

export const jsRelName = (rel: InstanceRelation): string => {
if (rel.instanceDef.kind === 'trait-def') {
return relTypeName(rel)
}
if (rel.inherent) {
return `impl_${virtualTypeToString(rel.implType)}`.replace(/[:<>, ]/g, '')
} else {
return `impl_${virtualTypeToString(rel.implType)}_${virtualTypeToString(rel.forType)}`.replace(/[:<>, ]/g, '')
}
return `impl_${virtualTypeToString(rel.implType)}_${virtualTypeToString(rel.forType)}`.replace(/[:<>, ]/g, '')
}

export const emitLines = (lines: string[]): string => {
Expand Down
15 changes: 10 additions & 5 deletions src/codegen/js/statement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { Module } from '../../ast'
import { Block, BreakStmt, FnDef, ImplDef, ReturnStmt, Statement, TraitDef, VarDef } from '../../ast/statement'
import { TypeDef, Variant } from '../../ast/type-def'
import { Context } from '../../scope'
import { typeDefToVirtualType } from '../../scope/trait'
import { relTypeName, typeDefToVirtualType } from '../../scope/trait'
import { vidToString } from '../../scope/util'
import { assert } from '../../util/todo'
import { EmitExpr, emitExpr, emitExprToString, emitParam, emitPattern } from './expr'

export const emitStatement = (statement: Statement, module: Module, ctx: Context): string | EmitExpr => {
Expand Down Expand Up @@ -71,13 +72,17 @@ export const emitBreakStmt = (breakStmt: BreakStmt, module: Module, ctx: Context
}

export const emitInstance = (instance: ImplDef | TraitDef, module: Module, ctx: Context): string => {
const fns_ = instance.block.statements
const superMethods = instance.kind === 'impl-def' ? instance.superMethods ?? [] : []
const ms = superMethods.map(m => {
const mName = m.fn.name.value
return `${mName}: ${jsRelName(m.rel)}().${mName}`
})
const fns = instance.block.statements
.map(s => <FnDef>s)
.map(f => emitFnDef(f, module, ctx, true))
.filter(f => f.length > 0)
.map(f => indent(f))
const fns = fns_.length > 0 ? `\n${fns_.join(',\n')}\n` : ''
return `{${fns}}`
const all = [...ms, ...fns]
return `function() {\n${indent(`return {${all.length > 0 ? `\n${indent(all.join(',\n'))}\n` : ''}}`)}\n}`
}

export const emitBlockStatements = (
Expand Down
25 changes: 13 additions & 12 deletions src/semantic/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
} from '../scope'
import { findSuperRelChains, traitDefToVirtualType, typeDefToVirtualType } from '../scope/trait'
import { idToVid, vidEq, vidToString } from '../scope/util'
import { Definition, NameDef, resolveVid, typeKinds } from '../scope/vid'
import { Definition, MethodDef, NameDef, resolveVid, typeKinds } from '../scope/vid'
import { VirtualType, genericToVirtual, isAssignable, typeToVirtual } from '../typecheck'
import { instanceGenericMap, makeGenericMapOverStructure, resolveType } from '../typecheck/generic'
import { holeType, neverType, selfType, unitType, unknownType } from '../typecheck/type'
Expand Down Expand Up @@ -402,24 +402,22 @@ const checkImplDef = (implDef: ImplDef, ctx: Context) => {
ctx.impls.find(rel => rel.instanceDef === ref.def)!,
...findSuperRelChains(ref.vid, ctx).flatMap(chain => chain)
]
const traitMethods = traitRels
.map(t => {
const methods = <FnDef[]>t.instanceDef.block.statements.filter(s => s.kind === 'fn-def')
return { rel: t, methods }
})
.flatMap(({ rel, methods }) => methods.map(m => ({ rel, method: m })))
const requiredImplMethods = traitMethods.filter(f => !f.method.block)
const traitMethods: MethodDef[] = traitRels.flatMap(t => {
const methods = <FnDef[]>t.instanceDef.block.statements.filter(s => s.kind === 'fn-def')
return methods.map(m => ({ kind: 'method-def', rel: t, fn: m }))
})
const requiredImplMethods = traitMethods.filter(f => !f.fn.block)
const implMethods = implDef.block.statements.filter(s => s.kind === 'fn-def').map(s => <FnDef>s)
for (const m of requiredImplMethods) {
const mName = m.method.name.value
const mName = m.fn.name.value
if (!implMethods.find(im => im.name.value === mName)) {
const msg = `missing method implementation \`${vidToString(ref.vid)}::${mName}\``
addError(ctx, semanticError(ctx, implDef.identifier.names.at(-1)!, msg))
}
}
for (const m of implMethods) {
const mName = m.name.value
const traitMethod = traitMethods.find(im => im.method.name.value === mName)
const traitMethod = traitMethods.find(im => im.fn.name.value === mName)
if (!traitMethod) {
const msg = `method \`${vidToString(ref.vid)}::${mName}\` is not defined by implemented trait`
addError(ctx, semanticError(ctx, m.name, msg))
Expand All @@ -428,19 +426,22 @@ const checkImplDef = (implDef: ImplDef, ctx: Context) => {
checkTopLevelDefinition(traitMethod.rel.module, traitMethod.rel.instanceDef, ctx)

const traitMap = makeGenericMapOverStructure(rel.implType, traitMethod.rel.implType)
const mResolvedType = resolveType(traitMethod.method.type!, [traitMap], ctx)
const mResolvedType = resolveType(traitMethod.fn.type!, [traitMap], ctx)
if (!isAssignable(m.type!, mResolvedType, ctx)) {
addError(ctx, typeError(m.name, m.type!, mResolvedType, ctx))
}
}
implDef.superMethods = traitMethods.filter(
m => m.fn.block && !implMethods.find(im => im.name.value === m.fn.name.value)
)
} else {
addError(ctx, semanticError(ctx, implDef.forTrait, `expected \`trait-def\`, got \`${ref.def.kind}\``))
}
} else {
addError(ctx, notFoundError(ctx, implDef.forTrait, vidToString(vid)))
}

// TODO: check bounded traits are implemented by type,
// TODO: check that bounded traits are implemented by type,
// e.g. `impl Ord for Foo` requires `impl Eq for Foo` since `trait Ord<Self: Eq>`
}

Expand Down

0 comments on commit 5a7666b

Please sign in to comment.