Skip to content

Commit

Permalink
Semantic: methods require their trait to be in scope
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanjermakov committed Feb 13, 2024
1 parent d919116 commit 639b2be
Show file tree
Hide file tree
Showing 9 changed files with 55 additions and 54 deletions.
2 changes: 1 addition & 1 deletion src/scope/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export interface InstanceScope {
kind: 'instance'
definitions: DefinitionMap
selfType: VirtualType
def: TraitDef | ImplDef
rel: InstanceRelation
}

export interface TypeDefScope {
Expand Down
50 changes: 28 additions & 22 deletions src/scope/vid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { checkTopLevelDefiniton } from '../semantic'
import { selfType } from '../typecheck/type'
import { unreachable } from '../util/todo'
import { Context, Scope, instanceScope } from './index'
import { findSuperRelChains } from './trait'
import { concatVid, idToVid, vidEq, vidToString } from './util'
import { InstanceRelation, findSuperRelChains } from './trait'
import { concatVid, idToVid, vidEq, vidFromString, vidToString } from './util'

export interface VirtualIdentifier {
names: string[]
Expand Down Expand Up @@ -62,7 +62,7 @@ export interface VariantDef {
export interface MethodDef {
kind: 'method-def'
fn: FnDef
instance: ImplDef | TraitDef
rel: InstanceRelation
}

export interface VirtualIdentifierMatch<D = Definition> {
Expand Down Expand Up @@ -191,11 +191,8 @@ export const resolveScopeVid = (
// if matched, try to find fn with matching name in specified trait
const fn = traitDef.block.statements.find(s => s.kind === 'fn-def' && s.name.value === fnName)
if (fn && fn.kind === 'fn-def') {
return {
vid,
module,
def: { kind: 'method-def', fn, instance: traitDef }
}
const rel = ctx.impls.find(i => i.instanceDef === traitDef)!
return { vid, module, def: { kind: 'method-def', fn, rel: rel } }
}
}
if (traitDef && checkSuper) {
Expand All @@ -205,18 +202,35 @@ export const resolveScopeVid = (
if (!typeRef || (typeRef.def.kind !== 'type-def' && typeRef.def.kind !== 'trait-def')) {
return unreachable()
}
// TODO: only include traits that are in scope
const superRels = findSuperRelChains(typeRef.vid, ctx).map(c => c.at(-1)!)
for (const superRel of superRels) {
// don't check itself
if (vidEq(fullTypeVid, superRel.implDef.vid)) continue
// TODO: sometimes there are duplicates
const methodCandidates = superRels.flatMap(superRel => {
const fullMethodVid = { names: [...superRel.implDef.vid.names, fnName] }
const methodRef = resolveVid(fullMethodVid, ctx, ['method-def'])
if (methodRef && methodRef.def.kind === 'method-def') {
const module = resolveVid(methodRef.module.identifier, ctx, ['module'])
if (!module || module.def.kind !== 'module') return unreachable()
checkTopLevelDefiniton(module.def, methodRef.def, ctx)
return methodRef
return [<VirtualIdentifierMatch<MethodDef>>methodRef]
}
return []
})
const methodsInScope = methodCandidates.filter(m => {
// unqualified trait name must be in scope
const traitName = vidFromString(m.def.rel.implDef.vid.names.at(-1)!)
const resolved = resolveVid(traitName, ctx, ['trait-def'])
return resolved && resolved.def === m.def.rel.instanceDef
})
if (methodsInScope.length === 1) {
return methodsInScope[0]
}
if (methodsInScope.length > 1) {
// TODO: report
return methodsInScope[0]
}
if (methodCandidates.length > 0) {
// TODO: report candidates
}
}
// resolve generic refd fn
Expand Down Expand Up @@ -267,18 +281,14 @@ export const resolveMatchedVid = (
// if vid is varDef, typeDef, trait or impl, e.g. std::option::Option
module = pkg.modules.find(m => vidEq(m.identifier, { names: vid.names.slice(0, -1) }))
if (module) {
ctx.moduleStack.push(module)
const moduleLocalVid = { names: vid.names.slice(-1) }
const ref = resolveScopeVid(moduleLocalVid, module.topScope!, ctx, ofKind, module)
if (ref) {
const defModule = resolveVid(ref.module.identifier, ctx, ['module'])
if (!defModule || defModule.def.kind !== 'module') return unreachable()
checkTopLevelDefiniton(defModule.def, ref.def, ctx)
const match = { vid, module, def: ref.def }
ctx.moduleStack.pop()
return match
return { vid, module, def: ref.def }
}
ctx.moduleStack.pop()

// check re-exports
for (const reExp of module.reExports!) {
Expand All @@ -295,15 +305,11 @@ export const resolveMatchedVid = (
// if vid is a variant or a traitFn, e.g. std::option::Option::Some
module = pkg.modules.find(m => vidEq(m.identifier, { names: vid.names.slice(0, -2) }))
if (module) {
ctx.moduleStack.push(module)
const moduleLocalVid = { names: vid.names.slice(-2) }
const ref = resolveScopeVid(moduleLocalVid, module.topScope!, ctx, ofKind, module)
if (ref) {
const match = { vid, module, def: ref.def }
ctx.moduleStack.pop()
return match
return { vid, module, def: ref.def }
}
ctx.moduleStack.pop()

// check re-exports
for (const reExp of module.reExports!) {
Expand Down
5 changes: 2 additions & 3 deletions src/semantic/expr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
} from '../ast/operand'
import { Context, Scope, addError, instanceScope } from '../scope'
import { bool, iter, iterable } from '../scope/std'
import { getInstanceForType } from '../scope/trait'
import { idToVid, vidFromString, vidToString } from '../scope/util'
import { MethodDef, VariantDef, VirtualIdentifierMatch, resolveVid } from '../scope/vid'
import {
Expand Down Expand Up @@ -158,7 +157,7 @@ export const checkUnaryExpr = (unaryExpr: UnaryExpr, ctx: Context): void => {
assert(!!methodDef.fn.type, `untyped impl fn ${vidToString(methodRef!.vid)}`)
assert(methodDef.fn.type!.kind === 'fn-type', 'impl fn type in not fn')

const implTargetType = getInstanceForType(methodDef.instance, ctx)
const implTargetType = methodDef.rel.forType
const fnType = <VirtualFnType>methodDef.fn.type
if (isAssignable(unaryExpr.operand.type!, implTargetType, ctx)) {
const genericMaps = makeUnaryExprGenericMaps(unaryExpr.operand.type!, fnType, implTargetType)
Expand Down Expand Up @@ -192,7 +191,7 @@ export const checkBinaryExpr = (binaryExpr: BinaryExpr, ctx: Context): void => {
assert(!!methodRef.fn.type, 'untyped impl fn')
assert(methodRef.fn.type!.kind === 'fn-type', 'impl fn type in not fn')

const implTargetType = getInstanceForType(methodRef.instance, ctx)
const implTargetType = methodRef.rel.forType
const fnType = <VirtualFnType>methodRef.fn.type
if (isAssignable(binaryExpr.lOperand.type!, implTargetType, ctx)) {
const genericMaps = makeBinaryExprGenericMaps(binaryExpr, fnType, implTargetType)
Expand Down
21 changes: 8 additions & 13 deletions src/semantic/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ const checkFnDef = (fnDef: FnDef, ctx: Context): void => {
}
})
} else {
if (instanceScope(ctx)?.def.kind !== 'trait-def') {
if (instanceScope(ctx)?.rel.instanceDef.kind !== 'trait-def') {
const msg = `fn \`${fnDef.name.value}\` has no body -> must be native`
addWarning(ctx, semanticError(ctx, fnDef.name, msg))
}
Expand Down Expand Up @@ -335,7 +335,7 @@ const checkTraitDef = (traitDef: TraitDef, ctx: Context) => {
const scope: InstanceScope = {
kind: 'instance',
selfType: rel.forType,
def: traitDef,
rel,
definitions: new Map(traitDef.generics.map(g => [defKey(g), g]))
}
module.scopeStack.push(scope)
Expand All @@ -361,7 +361,7 @@ const checkImplDef = (implDef: ImplDef, ctx: Context) => {
const scope: InstanceScope = {
kind: 'instance',
selfType: rel.forType,
def: implDef,
rel,
definitions: new Map(implDef.generics.map(g => [defKey(g), g]))
}
module.scopeStack.push(scope)
Expand All @@ -381,7 +381,6 @@ const checkImplDef = (implDef: ImplDef, ctx: Context) => {
ctx.impls.find(rel => rel.instanceDef === ref.def)!,
...findSuperRelChains(ref.vid, ctx).flatMap(chain => chain)
]
// TODO: only include traits that are in scope
const traitMethods = traitRels
.map(t => {
const methods = <FnDef[]>t.instanceDef.block.statements.filter(s => s.kind === 'fn-def')
Expand Down Expand Up @@ -435,12 +434,8 @@ export const checkTypeDef = (typeDef: TypeDef, ctx: Context) => {
const module = ctx.moduleStack.at(-1)!

const vid = { names: [...module.identifier.names, typeDef.name.value] }
module.scopeStack.push({
kind: 'type',
def: typeDef,
vid,
definitions: new Map(typeDef.generics.map(g => [defKey(g), g]))
})
const definitions = new Map(typeDef.generics.map(g => [defKey(g), g]))
module.scopeStack.push({ kind: 'type', def: typeDef, vid, definitions })

typeDef.variants.forEach(v => checkVariant(v, ctx))
// TODO: check duplicate variants
Expand Down Expand Up @@ -572,11 +567,11 @@ export const checkIdentifier = (identifier: Identifier, ctx: Context): void => {
const instScope: InstanceScope = {
kind: 'instance',
selfType: unknownType,
def: ref.def.instance,
definitions: new Map(ref.def.instance.generics.map(g => [defKey(g), g]))
rel: ref.def.rel,
definitions: new Map(ref.def.rel.instanceDef.generics.map(g => [defKey(g), g]))
}
// must be set afterwards since impl generics cannot be resolved
instScope.selfType = traitDefToVirtualType(ref.def.instance, ctx)
instScope.selfType = traitDefToVirtualType(ref.def.rel.instanceDef, ctx)

identifier.type = resolveType(ref.def.fn.type!, [instanceGenericMap(instScope, ctx)], ctx)
break
Expand Down
19 changes: 6 additions & 13 deletions src/semantic/instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { checkCallArgs, checkType } from '.'
import { BinaryExpr } from '../ast/expr'
import { Call } from '../ast/op'
import { Identifier, Operand, identifierFromOperand } from '../ast/operand'
import { Context, addError, instanceRelation, instanceScope } from '../scope'
import { Context, addError, instanceScope } from '../scope'
import { getInstanceForType } from '../scope/trait'
import { vidEq, vidToString } from '../scope/util'
import { MethodDef, resolveVid } from '../scope/vid'
Expand Down Expand Up @@ -80,8 +80,7 @@ const checkFieldAccessExpr = (lOp: Operand, field: Identifier, ctx: Context): Vi
return
}

const instanceDef = instanceScope(ctx)?.def
const rel = instanceDef ? instanceRelation(instanceDef, ctx) : undefined
const rel = instanceScope(ctx)?.rel
const inInherentImpl = rel ? vidEq(rel.forDef.vid, typeVid) : undefined
if (!inInherentImpl && !typeCandidates.every(f => f.pub)) {
const msg =
Expand Down Expand Up @@ -127,14 +126,8 @@ const checkMethodCallExpr = (
lOperand.type = resolveType(lOperand.type!, [instanceMap], ctx)
}
if (lOperand.type!.kind !== 'vid-type') {
addError(
ctx,
semanticError(
ctx,
identifier,
`expected instance type, got \`${virtualTypeToString(lOperand.type ?? unknownType)}\``
)
)
const msg = `expected instance type, got \`${virtualTypeToString(lOperand.type ?? unknownType)}\``
addError(ctx, semanticError(ctx, identifier, msg))
return
}
const traitFnVid = { names: [...lOperand.type!.identifier.names, methodName] }
Expand All @@ -145,7 +138,7 @@ const checkMethodCallExpr = (
const fieldType = checkFieldAccessExpr(lOperand, identifier, ctx)
ctx.silent = false
if (fieldType) {
const msg = `method \`${methodName}\` not found\n to call a method, surround operand in parentheses`
const msg = `method \`${methodName}\` not found\n to access field \`${methodName}\`, surround operand in parentheses`
addError(ctx, semanticError(ctx, identifier, msg))
} else {
addError(ctx, notFoundError(ctx, identifier, methodName, 'method'))
Expand Down Expand Up @@ -179,7 +172,7 @@ const makeMethodGenericMaps = (
call: Call,
ctx: Context
): Map<string, VirtualType>[] => {
const implForType = getInstanceForType(methodDef.instance, ctx)
const implForType = getInstanceForType(methodDef.rel.instanceDef, ctx)
const implGenericMap = makeGenericMapOverStructure(lOperand.type!, implForType)
// if Self type param is explicit, `resolveGenericsOverStructure` treats it as regular generic and interrupts
// further mapping in `fnGenericMap`, thus should be removed
Expand Down
2 changes: 1 addition & 1 deletion src/semantic/semantic.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,7 @@ impl Foo {

ctx = check(code('x: Int', '5'))
expect(ctx.errors.map(e => e.message)).toEqual([
'method `x` not found\n to call a method, surround operand in parentheses'
'method `x` not found\n to access field `x`, surround operand in parentheses'
])
})

Expand Down
6 changes: 6 additions & 0 deletions src/std/iter/mod.no
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
pub use std::iter::{
intersperseIter::{IntersperseIter, IntersperseAdapter},
mapIter::{MapIter, MapAdapter},
peekableIter::{PeekableIter, PeekableAdapter},
}

pub trait Iterable<T> {
fn iter(self): Iter<T>
}
Expand Down
1 change: 1 addition & 0 deletions src/std/list.no
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::io::Display
use std::iter::MapAdapter

pub type List<T>

Expand Down
3 changes: 2 additions & 1 deletion src/typecheck/generic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ export const getTypeParams = (virtualType: VirtualType): VirtualType[] => {
}

export const instanceGenericMap = (instScope: InstanceScope, ctx: Context): Map<string, VirtualType> => {
const generics = instScope.def.generics.map(g => {
// TODO: virtual generics should already be contained in InstanceRelation, check in other places too
const generics = instScope.rel.instanceDef.generics.map(g => {
const vg = genericToVirtual(g, ctx)
return <const>[vg.name, vg]
})
Expand Down

0 comments on commit 639b2be

Please sign in to comment.