From 5a7666b00a75e3c745249d2fe03affad32aa1ef0 Mon Sep 17 00:00:00 2001 From: ivanjermakov Date: Sat, 9 Mar 2024 18:09:49 +0100 Subject: [PATCH] Codegen: insert default methods into impls --- src/ast/statement.ts | 2 ++ src/codegen/js/expr.ts | 2 +- src/codegen/js/index.ts | 8 +++++--- src/codegen/js/statement.ts | 15 ++++++++++----- src/semantic/index.ts | 25 +++++++++++++------------ 5 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/ast/statement.ts b/src/ast/statement.ts index f70622aa..9767bb04 100644 --- a/src/ast/statement.ts +++ b/src/ast/statement.ts @@ -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' @@ -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 => { diff --git a/src/codegen/js/expr.ts b/src/codegen/js/expr.ts index cd210bb3..d3d59315 100644 --- a/src/codegen/js/expr.ts +++ b/src/codegen/js/expr.ts @@ -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 } diff --git a/src/codegen/js/index.ts b/src/codegen/js/index.ts index fe3b8385..255177e3 100644 --- a/src/codegen/js/index.ts +++ b/src/codegen/js/index.ts @@ -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' @@ -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 => { diff --git a/src/codegen/js/statement.ts b/src/codegen/js/statement.ts index 049535c0..94ca0c17 100644 --- a/src/codegen/js/statement.ts +++ b/src/codegen/js/statement.ts @@ -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 => { @@ -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 => 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 = ( diff --git a/src/semantic/index.ts b/src/semantic/index.ts index 206877fb..3669362f 100644 --- a/src/semantic/index.ts +++ b/src/semantic/index.ts @@ -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' @@ -402,16 +402,14 @@ 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 = 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 = 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 => 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)) @@ -419,7 +417,7 @@ const checkImplDef = (implDef: ImplDef, ctx: Context) => { } 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)) @@ -428,11 +426,14 @@ 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}\``)) } @@ -440,7 +441,7 @@ const checkImplDef = (implDef: ImplDef, ctx: Context) => { 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` }