pocomath/src/core/PocomathInstance.mjs

1739 lines
68 KiB
JavaScript

/* Core of pocomath: create an instance */
import typed from 'typed-function'
import {makeChain} from './Chain.mjs'
import {dependencyExtractor, generateTypeExtractor} from './extractors.mjs'
import {Returns, returnTypeOf} from './Returns.mjs'
import {typeListOfSignature, typesOfSignature, subsetOfKeys} from './utils.mjs'
const anySpec = {} // fixed dummy specification of 'any' type
/* Like `.some(predicate)` but for collections */
function exists(collection, predicate) {
for (const item of collection) if (predicate(item)) return true;
return false;
}
/* Template/signature parsing stuff; should probably be moved to a
* separate file, but it's a bit interleaved at the moment
*/
const theTemplateParam = 'T' // First pass: only allow this one exact parameter
const restTemplateParam = `...${theTemplateParam}`
const templateCall = `<${theTemplateParam}>`
const templateFromParam = 'U' // For defining covariant conversions
/* returns the pair [base, instance] for a template type. If the type
* is not a template, instance is undefined
*/
const templatePattern = /^\s*([^<\s]*)\s*<\s*(\S*)\s*>\s*$/
function splitTemplate(type) {
if (!(type.includes('<'))) return [type, undefined]
const results = templatePattern.exec(type)
return [results[1], results[2]]
}
/* Returns the instance such that type is template instantiated for that
* instance.
*/
function whichInstance(type, template) {
if (template === theTemplateParam) return type
if (type === template) return ''
if (!(template.includes(templateCall))) {
throw new TypeError(
`Type ${template} is not a template, so can't produce ${type}`)
}
const [typeBase, typeInstance] = splitTemplate(type)
if (!typeInstance) {
throw new TypeError(
`Type ${type} not from a template, so isn't instance of ${template}`)
}
const [tempBase, tempInstance] = splitTemplate(template)
if (typeBase !== tempBase) {
throw new TypeError(
`Type ${type} has wrong top-level base to be instance of ${template}`)
}
return whichInstance(typeInstance, tempInstance)
}
/* Same as above, but for signatures */
function whichSigInstance(sig, tempsig) {
const sigTypes = typeListOfSignature(sig)
const tempTypes = typeListOfSignature(tempsig)
const sigLength = sigTypes.length
if (sigLength === 0) {
throw new TypeError("No types in signature, so can't determine instance")
}
if (sigLength !== tempTypes.length) {
throw new TypeError(`Signatures ${sig} and ${tempsig} differ in length`)
}
let maybeInstance = whichInstance(sigTypes[0], tempTypes[0])
for (let i = 1; i < sigLength; ++i) {
const currInstance = whichInstance(sigTypes[i], tempTypes[i])
if (maybeInstance) {
if (currInstance && currInstance !== maybeInstance) {
throw new TypeError(
`Inconsistent instantiation of ${sig} from ${tempsig}`)
}
} else {
maybeInstance = currInstance
}
}
if (!maybeInstance) {
throw new TypeError(
`Signature ${sig} identical to ${tempsig}, not an instance`)
}
return maybeInstance
}
/* Returns a new signature just like sig but with the parameter replaced by
* the type
*/
const upperBounds = /\s*(\S*)\s*:\s*(\w*)\s*/g
function substituteInSignature(signature, parameter, type) {
const sig = signature.replaceAll(upperBounds, '$1')
const pattern = new RegExp("\\b" + parameter + "\\b", 'g')
return sig.replaceAll(pattern, type)
}
const UniversalType = 'ground' // name for a type that matches anything
let lastWhatToDo = null // used in an infinite descent check
export default class PocomathInstance {
/* Disallowed names for ops; beware, this is slightly non-DRY
* in that if a new top-level PocomathInstance method is added, its name
* must be added to this list.
*/
static reserved = new Set([
'chain',
'config',
'convert',
'importDependencies',
'install',
'installType',
'instantiateTemplate',
'isPriorTo',
'isSubtypeOf',
'joinTypes',
'name',
'returnTypeOf',
'resolve',
'self',
'subtypesOf',
'supertypesOf',
'Templates',
'typeOf',
'Types',
'undefinedTypes'
])
constructor(name) {
this.name = name
this._imps = {} // Pocomath implementations, with dependencies
this._TFimps = {} // typed-function implementations, dependencies resolved
this._affects = {}
this._typed = typed.create()
this._typed.clear()
// The following is an additional typed-function universe for resolving
// uninstantiated template instances. It is linked to the main one in
// its onMismatch function, below:
this._metaTyped = typed.create()
this._metaTyped.clear()
this._metaTyped.addTypes([{name: UniversalType, test: () => true}])
// And these are the meta bindings: (I think we don't need separate
// invalidation for them as they are only accessed through a main call.)
this._meta = {} // The resulting typed-functions
this._metaTFimps = {} // and their implementations
const me = this
this._typed.onMismatch = (name, args, sigs) => {
if (me._invalid.has(name)) {
if (this._fixing === name) {
this._fixingCount += 1
if (this._fixingCount > this._maxDepthSeen + 2) {
throw new ReferenceError(
`Infinite descent rebuilding ${name} on ${args}`)
}
} else {
this._fixingCount = 0
}
// rebuild implementation and try again
const lastFixing = this._fixing
this._fixing = name
const value = me[name](...args)
this._fix = lastFixing
return value
}
const metaversion = me._meta[name]
if (metaversion) {
return me._meta[name](...args)
}
me._typed.throwMismatchError(name, args, sigs)
}
// List of types installed in the instance: (We start with just dummies
// for the 'any' type and for type parameters.)
this.Types = {any: anySpec}
this.Types[theTemplateParam] = anySpec
// Types that have been moved into the metaverse:
this._metafiedTypes = new Set()
// All the template types that have been defined:
this.Templates = {}
// And their instantiations:
this._instantiationsOf = {}
// The actual type testing functions:
this._typeTests = {}
// For each type, gives all of its (in)direct subtypes in topo order:
this._subtypes = {}
/* The following gives for each type, a set of all types that could
* match in typed-function's dispatch algorithm before the given type.
* This is important because if we instantiate a template, we must
* instantiate it for all prior types as well, or else the wrong instance
* might match.
*/
this._priorTypes = {}
this._seenTypes = new Set() // all types that have occurred in a signature
this._maxDepthSeen = 1 // deepest template nesting we've actually encountered
this._invalid = new Set() // methods that are currently invalid
this._config = {predictable: false, epsilon: 1e-12}
this.config = new Proxy(this._config, {
get: (target, property) => target[property],
set: (target, property, value) => {
if (value !== target[property]) {
target[property] = value
me._invalidateDependents('config')
}
return true // successful
}
})
this._plainFunctions = new Set() // the names of the plain functions
this._chainRepository = {} // place to store chainified functions
this.joinTypes = this.joinTypes.bind(me)
// Provide access to typed function conversion:
this.convert = this._typed.convert.bind(this._typed)
}
/**
* (Partially) define one or more operations of the instance:
*
* The sole parameter can be another Pocomath instance, in which case all
* of the types and operations of the other instance are installed in this
* one, or it can be a plain object as described below.
*
* @param {Object<string,
* PocomathInstance
* | Object<Signature, ({deps})=> implementation>>} ops
* The only parameter ops gives the semantics of the operations to install.
* The keys are operation names. The value for a key could be
* a PocomathInstance, in which case it is simply merged into this
* instance.
*
* Otherwise, ops must be an object
* mapping each desired (typed-function) signature to a function taking
* a dependency object to an implementation.
*
* For more detail, such functions should have the format
* ```
* ({depA, depB, depC: aliasC, ...}) => (opArg1, opArg2) => <result>
* ```
* where the `depA`, `depB` etc. are the names of the
* operations this implementation depends on; those operations can
* then be referred to directly by the identifiers `depA` and `depB`
* in the code for the '<result>`, or when an alias has been given
* as in the case of `depC`, by the identifier `aliasC`.
* Given an object that has these dependencies with these keys, the
* function returns a function taking the operation arguments to the
* desired result of the operation.
*
* You can specify that an operation depends on itself by using the
* special dependency identifier 'self'.
*
* You can specify that an implementation depends on just a specific
* signature of the given operation by suffixing the dependency name
* with the signature in parentheses, e.g. `add(number,number)` to
* refer to just adding two numbers. In this case, it is of course
* necessary to specify an alias to be able to refer to the supplied
* operation in the body of the implementation.
*
* You can specify template implementations. If any item in the signature
* contains the word 'T' (currently the only allowed type parameter) then
* the signature/implementation is a template. The T can match any type
* of argument, and it may appear in the dependencies, where it is
* replaced by the matching type. A bare 'T' in the dependencies will be
* supplied with the name of the type as its value. See the implementation
* of `subtract` for an example.
* Usually templates are instantiated as needed, but for some heavily
* used functions, or functions with non-template signatures that refer
* to signatures generated from a template, it makes more sense to just
* instantiate the template immediately for all known types. This eager
* instantiation can be accomplished by prefixin the signature with an
* exclamation point.
*/
install = Returns('void', function(ops) {
if (ops instanceof PocomathInstance) {
return _installInstance(ops)
}
/* Standardize the format of all implementations, weeding out
* any other instances as we go
*/
const stdFunctions = {}
for (const [item, spec] of Object.entries(ops)) {
if (spec instanceof PocomathInstance) {
this._installInstance(spec)
} else if (typeof spec === 'function') {
stdFunctions[item] = spec
} else {
if (item.charAt(0) === '_') {
throw new SyntaxError(
`Pocomath: Cannot install ${item}, `
+ 'initial _ reserved for internal use.')
}
if (PocomathInstance.reserved.has(item)) {
throw new SyntaxError(
`Pocomath: reserved function '${item}' cannot be modified.`)
}
const stdimps = {}
for (const [signature, does] of Object.entries(spec)) {
const uses = new Set()
try {
does(dependencyExtractor(uses))
} catch {
}
stdimps[signature] = {uses, does}
}
stdFunctions[item] = stdimps
}
}
this._installFunctions(stdFunctions)
})
/* Merge any number of PocomathInstances or modules: */
static merge(name, ...pieces) {
const result = new PocomathInstance(name)
for (const piece of pieces) {
result.install(piece)
}
return result
}
/* Determine the return type of an operation given an input signature */
returnTypeOf = Returns('string', function(operation, signature) {
for (const type of typeListOfSignature(signature)) {
this._maybeInstantiate(type)
}
if (typeof operation !== 'string') {
operation = operation.name
}
const details = this._pocoFindSignature(operation, signature)
if (details) {
return returnTypeOf(details.fn, signature, this)
}
return returnTypeOf(this[operation], signature, this)
})
/* Return a chain object for this instance with a given value: */
chain = Returns(
sig => `Chain<${sig}>`,
function(value) {
return makeChain(value, this, this._chainRepository)
}
)
_installInstance(other) {
for (const [type, spec] of Object.entries(other.Types)) {
if (spec === anySpec) continue
this.installType(type, spec)
}
for (const [base, info] of Object.entries(other.Templates)) {
this._installTemplateType(info.type, info.spec)
}
const migrateImps = {}
for (const operator in other._imps) {
if (operator != 'typeOf') { // skip the builtin, we already have it
migrateImps[operator] = other._imps[operator]
}
}
for (const plain of other._plainFunctions) {
migrateImps[plain] = other[plain]
}
this._installFunctions(migrateImps)
}
/**
* Import (and install) all dependencies of previously installed functions,
* for the specified types.
*
* @param {string[]} types A list of type names
*/
async importDependencies(types) {
const typeSet = new Set(types)
typeSet.add('generic')
const doneSet = new Set(['self']) // nothing to do for self dependencies
while (true) {
const requiredSet = new Set()
/* Grab all of the known deps */
for (const func in this._imps) {
if (func === 'Types') continue
for (const {uses} of Object.values(this._imps[func])) {
for (const dependency of uses) {
const depName = dependency.split('(',1)[0]
if (doneSet.has(depName)) continue
requiredSet.add(depName)
}
}
}
if (requiredSet.size === 0) break
for (const name of requiredSet) {
for (const type of typeSet) {
try {
const moduleName = `../${type}/${name}.mjs`
const module = await import(moduleName)
this.install(module)
} catch (err) {
if (!(err.message.includes('find'))) {
// Not just a error because module doesn't exist
// So take it seriously
throw err
}
// We don't care if a module doesn't exist, so merely proceed
}
}
doneSet.add(name)
}
}
}
/* Used to install a type in a PocomathInstance.
*
* @param {string} name The name of the type
* @param {{test: any => bool, // the predicate for the type
* from: Record<string, <that type> => <type name>> // conversions
* before: string[] // lower priority types
* refines: string // The type this is a subtype of
* }} specification
*
* The second parameter of this function specifies the structure of the
* type via a plain
* object with the following properties:
*
* - test: the predicate for the type
* - from: a plain object mapping the names of types that can be converted
* **to** this type to the corresponding conversion functions
* - before: [optional] a list of types this should be added
* before, in priority order
* - refines: [optional] the name of a type that this is a subtype
* of. This means the test is the conjunction of the given test and
* the supertype test, and that it must come before the supertype.
*/
/*
* Implementation note: unlike _installFunctions below, we can make
* the corresponding changes to the _typed object immediately
*/
installType = Returns('void', function(type, spec) {
const parts = type.split(/[<,>]/).map(s => s.trim())
if (this._templateParam(parts[0])) {
throw new SyntaxError(
`Type name '${type}' reserved for template parameter`)
}
if (parts.some(this._templateParam.bind(this))) {
// It's an uninstantiated template, deal with it separately
return this._installTemplateType(type, spec)
}
const base = parts[0]
if (type in this.Types) {
if (spec !== this.Types[type]) {
throw new SyntaxError(`Conflicting definitions of type ${type}`)
}
return
}
if (spec.refines && !(spec.refines in this.Types)) {
throw new SyntaxError(
`Cannot install ${type} before its supertype ${spec.refines}`)
}
let beforeType = spec.refines
if (!beforeType) {
beforeType = 'any'
for (const other of spec.before || []) {
if (other in this.Types) {
beforeType = other
break
}
}
}
let testFn = spec.test
if (spec.refines) {
const supertypeTest = this.Types[spec.refines].test
testFn = entity => supertypeTest(entity) && spec.test(entity)
}
this._typeTests[type] = testFn
this._typed.addTypes([{name: type, test: testFn}], beforeType)
this.Types[type] = spec
this._subtypes[type] = []
this._priorTypes[type] = new Set()
// Update all the subtype sets of supertypes up the chain
let nextSuper = spec.refines
while (nextSuper) {
this._invalidateDependents(':' + nextSuper)
this._priorTypes[nextSuper].add(type)
this._addSubtypeTo(nextSuper, type)
nextSuper = this.Types[nextSuper].refines
}
/* Now add conversions to this type */
for (const from in (spec.from || {})) {
if (from in this.Types) {
// add conversions from "from" to this one and all its supertypes:
let nextSuper = type
while (nextSuper) {
if (this._priorTypes[nextSuper].has(from)) break
if (from === nextSuper) break
this._typed.addConversion(
{from, to: nextSuper, convert: spec.from[from]})
this._invalidateDependents(':' + nextSuper)
this._priorTypes[nextSuper].add(from)
/* And all of the subtypes of from are now prior as well: */
for (const subtype of this._subtypes[from]) {
this._priorTypes[nextSuper].add(subtype)
}
/* Add the conversion in the metaverse if need be: */
const [toBase, toInstance] = splitTemplate(nextSuper)
if (toInstance) {
const [fromBase, fromInstance] = splitTemplate(from)
if (!fromBase || fromBase !== toBase) {
this._metafy(from)
try {
this._metaTyped.addConversion(
{from, to: toBase, convert: spec.from[from]})
} catch {
}
}
}
nextSuper = this.Types[nextSuper].refines
}
}
}
/* And add conversions from this type */
for (const to in this.Types) {
for (const fromtype in this.Types[to].from) {
if (type == fromtype
|| (fromtype in this._subtypes
&& this.isSubtypeOf(type, fromtype))) {
if (spec.refines == to || this.isSubtypeOf(spec.refines,to)) {
throw new SyntaxError(
`Conversion of ${type} to its supertype ${to} disallowed.`)
}
let nextSuper = to
while (nextSuper) {
if (type === nextSuper) break
try { // may already be a conversion, and no way to ask
this._typed.addConversion({
from: type,
to: nextSuper,
convert: this.Types[to].from[fromtype]
})
this._invalidateDependents(':' + nextSuper)
/* Add the conversion in the metaverse if need be: */
const [toBase, toInstance] = splitTemplate(nextSuper)
if (toInstance && base !== toBase) {
this._metafy(type)
this._metaTyped.addConversion({
from: type,
to: toBase,
convert: this.Types[to].from[fromtype]
})
}
} catch {
}
this._priorTypes[nextSuper].add(type)
nextSuper = this.Types[nextSuper].refines
}
}
}
}
// Need to metafy ground types
if (type === base) {
this._metafy(type)
}
// update the typeOf function
const imp = {}
imp[type] = {uses: new Set(), does: () => Returns('string', () => type)}
this._installFunctions({typeOf: imp})
})
_metafy(type) {
if (this._metafiedTypes.has(type)) return
this._metaTyped.addTypes([{name: type, test: this._typeTests[type]}])
this._metafiedTypes.add(type)
}
_addSubtypeTo(sup, sub) {
if (this.isSubtypeOf(sub, sup)) return
const supSubs = this._subtypes[sup]
let i
for (i = 0; i < supSubs.length; ++i) {
if (this.isSubtypeOf(sub, supSubs[i])) break
}
supSubs.splice(i, 0, sub)
}
/* Returns true if typeA is a strict subtype of type B */
isSubtypeOf = Returns('boolean', function(typeA, typeB) {
// Currently not considering types to be a subtype of 'any'
if (typeB === 'any' || typeA === 'any') return false
return this._subtypes[typeB].includes(typeA)
})
/* Returns true if typeA is a subtype of or converts to type B */
isPriorTo = Returns('boolean', function(typeA, typeB) {
if (!(typeB in this._priorTypes)) return false
return this._priorTypes[typeB].has(typeA)
})
/* Returns a list of the strict ubtypes of a given type, in topological
* sorted order (i.e, no type on the list contains one that comes after it).
*/
subtypesOf = Returns('Array<string>', function(type) {
return this._subtypes[type] // should we clone?
})
/* Returns a list of the supertypes of a given type, starting with itself,
* in topological order
*/
supertypesOf = Returns('Array<string>', function(type) {
const supList = []
while (type) {
supList.push(type)
type = this.Types[type].refines
}
return supList
})
/* Returns the most refined type containing all the types in the array,
* with '' standing for the empty type for convenience. If the second
* argument `convert` is true, a convertible type is considered a
* a subtype (defaults to false).
*/
joinTypes = Returns('string', function(types, convert) {
let join = ''
for (const type of types) {
join = this._joinTypes(join, type, convert)
}
return join
})
/* helper for above */
_joinTypes(typeA, typeB, convert) {
if (!typeA) return typeB
if (!typeB) return typeA
if (typeA === 'any' || typeB === 'any') return 'any'
if (typeA === typeB) return typeA
const subber = convert ? this._priorTypes : this._subtypes
const pick = convert ? 'has' : 'includes'
if (subber[typeB][pick](typeA)) return typeB
/* OK, so we need the most refined supertype of A that contains B:
*/
let nextSuper = typeA
while (nextSuper) {
if (subber[nextSuper][pick](typeB)) return nextSuper
nextSuper = this.Types[nextSuper].refines
}
/* And if conversions are allowed, we have to search the other way too */
if (convert) {
nextSuper = typeB
while (nextSuper) {
if (subber[nextSuper][pick](typeA)) return nextSuper
nextSuper = this.Types[nextSuper].refines
}
}
return 'any'
}
/* Returns a list of all types that have been mentioned in the
* signatures of operations, but which have not actually been installed:
*/
undefinedTypes = Returns('Array<string>', function() {
return Array.from(this._seenTypes).filter(
t => !(t in this.Types || t in this.Templates))
})
/* Used internally to install a template type */
_installTemplateType(type, spec) {
const [base] = splitTemplate(type)
/* For now, just allow a single template per base type; that
* might need to change later:
*/
if (base in this.Templates) {
if (spec !== this.Templates[base].spec) {
throw new SyntaxError(
`Conflicting definitions of template type ${type}`)
}
return
}
// install the "base type" in the meta universe:
let beforeType = UniversalType
for (const other of spec.before || []) {
if (other in this.templates) {
beforeType = other
break
}
}
this._metaTyped.addTypes([{name: base, test: spec.base}], beforeType)
// Add conversions to the base type:
if (spec.from && spec.from[theTemplateParam]) {
for (const ground of this._metafiedTypes) {
this._metaTyped.addConversion(
{from: ground, to: base, convert: spec.from[theTemplateParam]})
}
}
this._instantiationsOf[base] = new Set()
// update the typeOf function
const imp = {}
imp[type] = {
uses: new Set(['T']),
does: ({T}) => Returns('string', () => `${base}<${T}>`)
}
this._installFunctions({typeOf: imp})
// Invalidate any functions that reference this template type:
this._invalidateDependents(':' + base)
// Nothing else actually happens until we match a template parameter
this.Templates[base] = {type, spec}
}
/* Used internally by install, see the documentation there */
_installFunctions(functions) {
for (const [name, spec] of Object.entries(functions)) {
if (typeof spec === 'function') {
if (name in this) {
if (spec === this[name]) continue
throw new SyntaxError(`Attempt to redefine function ${name}`)
}
this._plainFunctions.add(name)
this[name] = spec
continue
}
// new implementations, first check the name isn't taken
if (this._plainFunctions.has(name)) {
throw new SyntaxError(
`Can't add implementations to function ${name}`)
}
// All clear, so set the op up to lazily recreate itself
this._invalidate(name)
const opImps = this._imps[name]
for (const [signature, behavior] of Object.entries(spec)) {
if (signature in opImps) {
if (behavior.does !== opImps[signature].does) {
throw new SyntaxError(
`Conflicting definitions of ${signature} for ${name}`)
}
} else {
/* Check if it's an ordinary non-template signature */
let explicit = true
for (const type of typesOfSignature(signature)) {
for (const word of type.split(/[<>:\s]/)) {
if (this._templateParam(word)) {
explicit = false
break
}
}
if (!explicit) break
}
opImps[signature] = {
explicit,
resolved: false,
uses: behavior.uses,
does: behavior.does
}
if (!explicit) {
opImps[signature].hasInstantiations = {}
opImps[signature].needsInstantiations = new Set()
}
for (const dep of behavior.uses) {
const depname = dep.split('(', 1)[0]
if (depname === 'self' || this._templateParam(depname)) {
continue
}
this._addAffect(depname, name)
}
for (const type of typesOfSignature(signature)) {
for (const word of type.split(/[<>]/)) {
if (word.length == 0) continue
if (this._templateParam(word)) continue
this._seenTypes.add(word)
this._addAffect(':' + word, name)
}
}
}
}
}
}
/* returns a boolean indicating whether t denotes a template parameter.
* We will start this out very simply: the special string `T` is always
* a template parameter, and that's the only one
*/
_templateParam(t) { return t === theTemplateParam }
_addAffect(dependency, dependent) {
if (dependency in this._affects) {
this._affects[dependency].add(dependent)
} else {
this._affects[dependency] = new Set([dependent])
}
}
/**
* Reset an operation to require creation of typed-function,
* and if it has no implementations so far, set them up.
* name is the name of the operation, badType is a type that has been
* invalidated, and reasons is a set of specific operations/signatures
* that have been invalidated
*/
_invalidate(name, badType = '', reasons = new Set()) {
if (!(name in this._imps)) {
this._imps[name] = {}
this._TFimps[name] = {}
this._metaTFimps[name] = {}
}
// Go through each TF imp and invalidate it if need be
for (const [signature, imp] of Object.entries(this._TFimps[name])) {
if (imp.deferred
|| (badType && signature.includes(badType))
|| exists(imp.uses, dep => {
const [func, sig] = dep.split(/[()]/)
return reasons.has(dep)
|| (reasons.has(func) && !(sig in this._TFimps[func]))
})) {
// Invalidate this implementation:
delete this._TFimps[name][signature]
const behavior = imp.fromBehavior
if (behavior.explicit) {
behavior.resolved = false
} else {
delete behavior.hasInstantiations[imp.instance]
}
reasons.add(`${name}(${signature})`)
}
}
if (this._invalid.has(name)) return
this._invalid.add(name)
this._invalidateDependents(name, badType, reasons)
const self = this
Object.defineProperty(this, name, {
configurable: true,
get: () => {
const result = self._bundle(name)
self._invalid.delete(name)
return result
}
})
}
/**
* Invalidate all the dependents of a given property of the instance
* reasons is a set of invalidated signatures
*/
_invalidateDependents(name, badType, reasons = new Set()) {
if (name.charAt(0) === ':') badType = name.slice(1)
else reasons.add(name)
if (name in this._affects) {
for (const ancestor of this._affects[name]) {
this._invalidate(ancestor, badType, reasons)
}
}
}
/**
* Create a typed-function from the signatures for the given name and
* assign it to the property with that name, returning it as well
*/
_bundle(name) {
const imps = this._imps[name]
if (!imps) {
throw new SyntaxError(`No implementations for ${name}`)
}
const tf_imps = this._TFimps[name]
const meta_imps = this._metaTFimps[name]
/* Collect the entries we know the types for */
const usableEntries = []
for (const entry of Object.entries(imps)) {
let keep = true
for (const type of typesOfSignature(entry[0])) {
if (type in this.Types) continue
const [baseType] = splitTemplate(type)
if (baseType in this.Templates) continue
keep = false
break
}
if (keep) usableEntries.push(entry)
}
if (usableEntries.length === 0) {
throw new SyntaxError(
`Every implementation for ${name} uses an undefined type;\n`
+ ` signatures: ${Object.keys(imps)}`)
}
/* Initial error checking done; mark this method as being
* in the midst of being reassembled
*/
Object.defineProperty(this, name, {configurable: true, value: 'limbo'})
for (const [rawSignature, behavior] of usableEntries) {
if (behavior.explicit) {
if (!(behavior.resolved)) {
this._addTFimplementation(name, tf_imps, rawSignature, behavior)
tf_imps[rawSignature]._pocoSignature = rawSignature
behavior.resolved = true
}
continue
}
/* It's a template, have to instantiate */
/* First, find any upper bounds on the instantation */
/* TODO: handle multiple upper bounds */
upperBounds.lastIndex = 0
let ubType = upperBounds.exec(rawSignature)
if (ubType) {
ubType = ubType[2]
if (!ubType in this.Types) {
throw new TypeError(
`Unknown type upper bound '${ubType}' in '${rawSignature}'`)
}
}
/* First, add the known instantiations, gathering all types needed */
if (ubType) behavior.needsInstantiations.add(ubType)
const nargs = typeListOfSignature(rawSignature).length
let instantiationSet = new Set()
const ubTypes = new Set()
if (!ubType) {
// Collect all upper-bound types for this signature
for (const othersig in imps) {
const otherNargs = typeListOfSignature(othersig).length
if (nargs !== otherNargs) {
// crude criterion that it won't match, that ignores
// rest args, but hopefully OK for prototype
continue
}
const thisUB = upperBounds.exec(othersig)
if (thisUB) ubTypes.add(thisUB[2])
let basesig = othersig.replaceAll(templateCall, '')
if (basesig !== othersig) {
// A template
const testsig = substituteInSignature(
basesig, theTemplateParam, '')
if (testsig === basesig) {
// that is not also top-level
for (let templateType of typeListOfSignature(basesig)) {
if (templateType.slice(0,3) === '...') {
templateType = templateType.slice(3)
}
ubTypes.add(templateType)
}
}
}
}
}
for (const instType of behavior.needsInstantiations) {
instantiationSet.add(instType)
const otherTypes =
ubType ? this.subtypesOf(instType) : this._priorTypes[instType]
for (const other of otherTypes) {
if (!(this._atOrBelowSomeType(other, ubTypes))) {
instantiationSet.add(other)
}
}
}
/* Prevent other existing signatures from blocking use of top-level
* templates via conversions:
*/
let baseSignature = rawSignature.replaceAll(templateCall, '')
/* Any remaining template params are top-level */
const signature = substituteInSignature(
baseSignature, theTemplateParam, UniversalType)
const hasTopLevel = (signature !== baseSignature)
if (!ubType && hasTopLevel) {
for (const othersig in imps) {
let basesig = othersig.replaceAll(templateCall, '')
const testsig = substituteInSignature(
basesig, theTemplateParam, '')
if (testsig !== basesig) continue // a top-level template
for (let othertype of typeListOfSignature(othersig)) {
if (othertype.slice(0,3) === '...') {
othertype = othertype.slice(3)
}
if (this.Types[othertype] === anySpec) continue
const testType = substituteInSignature(
othertype, theTemplateParam, '')
let otherTypeCollection = [othertype]
if (testType !== othertype) {
const [base] = splitTemplate(othertype)
otherTypeCollection = this._instantiationsOf[base]
}
for (const possibility of otherTypeCollection) {
for (const convtype of this._priorTypes[possibility]) {
if (this.isSubtypeOf(convtype, possibility)) continue
if (!(this._atOrBelowSomeType(convtype, ubTypes))) {
instantiationSet.add(convtype)
}
}
}
}
}
}
for (const instType of instantiationSet) {
this._instantiateTemplateImplementation(
name, rawSignature, instType)
}
/* Now add the catchall signature */
/* (Not needed if if it's a bounded template) */
if (ubType) continue
if (behavior.resolved) continue
/* The catchall signature has to detect the actual type of the call
* and add the new instantiations.
* First, prepare the type inference data:
*/
const parTypes = rawSignature.split(',')
const restParam = (parTypes[parTypes.length-1].slice(0,3) === '...')
const topTyper = entity => this.typeOf(entity)
const inferences = parTypes.map(
type => generateTypeExtractor(
type,
theTemplateParam,
topTyper,
this.joinTypes.bind(this),
this.Templates))
if (inferences.every(x => !x)) { // all false
throw new SyntaxError(
`Cannot find template parameter in ${rawSignature}`)
}
/* Now build the catchall implementation */
const self = this
/* For return type annotation, we may have to fix this to
propagate the return type. At the moment we are just bagging
*/
const patch = () => {
const patchFunc = (...tfBundledArgs) => {
/* We unbundle the rest arg if there is one */
let args = Array.from(tfBundledArgs)
const regLength = args.length - 1
if (restParam) {
const restArgs = args.pop()
args = args.concat(restArgs)
}
/* Now infer the type we actually should have been called for */
let i = -1
let j = -1
/* collect the arg types */
const argTypes = []
for (const arg of args) {
++j
// in case of rest parameter, reuse last parameter type:
if (i < inferences.length - 1) ++i
if (inferences[i]) {
const argType = inferences[i](arg)
if (!argType) {
throw TypeError(
`Type inference failed for argument ${j} of ${name}`)
}
if (argType === 'any') {
console.log('INCOMPATIBLE ARGUMENTS are', args)
throw TypeError(
`In call to ${name}, `
+ 'incompatible template arguments:'
// + args.map(a => JSON.stringify(a)).join(', ')
// unfortunately barfs on bigints. Need a better
// formatter. I wish we could just use the one that
// console.log uses; is that accessible somehow?
+ args.map(a => a.toString()).join(', ')
+ ' of types ' + argTypes.join(', ') + argType)
}
argTypes.push(argType)
}
}
if (argTypes.length === 0) {
throw TypeError('Type inference failed for' + name)
}
let usedConversions = false
let instantiateFor = self.joinTypes(argTypes)
if (instantiateFor === 'any') {
usedConversions = true
instantiateFor = self.joinTypes(argTypes, usedConversions)
if (instantiateFor === 'any') {
let argDisplay = args.map(toString).join(', ')
throw TypeError(
`In call to ${name}, no type unifies arguments `
+ argDisplay + '; of types ' + argTypes.toString()
+ '; note each consecutive pair must unify to a '
+ 'supertype of at least one of them')
}
}
const depth = instantiateFor.split('<').length
if (depth > self._maxDepthSeen) {
self._maxDepthSeen = depth
}
/* Generate the list of actual wanted types */
const wantTypes = parTypes.map(type => substituteInSignature(
type, theTemplateParam, instantiateFor))
const wantSig = wantTypes.join(',')
/* Now we have to add any actual types that are relevant
* to this invocation. Namely, that would be every formal
* parameter type in the invocation, with the parameter
* template instantiated by instantiateFor, and for all of
* instantiateFor's "prior types"
*/
for (j = 0; j < parTypes.length; ++j) {
if (wantTypes[j] !== parTypes[j] && parTypes[j].includes('<')) {
// actually used the param and is a template
const strippedType = parTypes[j].substr(
parTypes[j].lastIndexOf('.') + 1)
self._ensureTemplateTypes(strippedType, instantiateFor)
}
}
/* Request the desired instantiation: */
// But possibly since this resolution was grabbed, the proper
// instantiation has been added (like if there are multiple
// uses in the implementation of another method.
let whatToDo
if (!(instantiateFor in behavior.hasInstantiations)) {
const newImp = self._instantiateTemplateImplementation(
name, rawSignature, instantiateFor)
if (newImp) {
whatToDo = {fn: newImp, implementation: newImp}
}
self._invalidate(name)
}
const brandNewMe = self[name]
const betterToDo = self._typed.resolve(brandNewMe, args)
whatToDo = betterToDo || whatToDo
// We can access return type information here
// And in particular, if it might be a template, we should try to
// instantiate it:
const returnType = returnTypeOf(whatToDo.fn, wantSig, self)
for (const possibility of returnType.split('|')) {
const instantiated = self._maybeInstantiate(possibility)
if (instantiated) {
const [tempBase] = splitTemplate(instantiated)
self._invalidateDependents(':' + tempBase)
}
}
if (whatToDo === lastWhatToDo) {
throw new Error(
`Infinite recursion in resolving ${name} called on `
+ args.map(x =>
(typeof x === 'object'
? JSON.stringify(x)
: x.toString())
).join(', ')
+ ` inferred to be ${wantSig}`)
}
lastWhatToDo = whatToDo
const retval = whatToDo.implementation(...args)
lastWhatToDo = null
return retval
}
Object.defineProperty(
patchFunc, 'name', {value: `${name}(${signature})`})
patchFunc._pocoSignature = rawSignature
return patchFunc
}
Object.defineProperty(
patch, 'name', {value: `generate[${name}(${signature})]`})
// TODO?: Decorate patch with a function that calculates the
// correct return type a priori. Deferring because unclear what
// aspects will be merged into typed-function.
this._addTFimplementation(
name, meta_imps, signature,
{uses: new Set(), does: patch},
behavior)
behavior.resolved = true
}
// Make sure we have all of the needed (template) types; and if they
// can't be added (because they have been instantiated too deep),
// ditch the signature:
const badSigs = new Set()
for (const sig in tf_imps) {
if (!tf_imps[sig].uses) {
throw new ReferenceError(`MONKEY WRENCH: ${name} ${sig}`)
}
for (const type of typeListOfSignature(sig)) {
if (this._maybeInstantiate(type) === undefined) {
badSigs.add(sig)
}
}
}
for (const badSig of badSigs) {
const imp = tf_imps[badSig]
delete tf_imps[badSig]
const fromBehavior = this._imps[name][imp._pocoSignature]
if (fromBehavior.explicit) {
fromBehavior.resolved = false
} else {
delete fromBehavior.hasInstantiations[imp._pocoInstance]
}
}
let tf
if (Object.keys(tf_imps).length > 0) {
tf = this._typed(name, tf_imps)
tf.fromInstance = this
tf.isMeta = false
}
let metaTF
if (Object.keys(meta_imps).length > 0) {
metaTF = this._metaTyped(name, meta_imps)
metaTF.fromInstance = this
metaTF.isMeta = true
}
this._meta[name] = metaTF
tf = tf || metaTF
Object.defineProperty(this, name, {configurable: true, value: tf})
return tf
}
/* Takes a type and a set of types and returns true if the type
* is a subtype of some type in the set.
*/
_atOrBelowSomeType(type, typeSet) {
if (typeSet.has(type)) return true
let belowSome = false
for (const anUB of typeSet) {
if (anUB in this.Templates) {
if (type.slice(0, anUB.length) === anUB) {
belowSome = true
break
}
} else {
if (this.isSubtypeOf(type, anUB)) {
belowSome = true
break
}
}
}
return belowSome
}
/* Takes an arbitrary type and performs an instantiation if necessary.
* @param {string} type The type to instantiate
* @param {string | bool | undefined }
* Returns the name of the type if an instantiation occurred, false
* if the type was already present, and undefined if the type can't
* be satisfied (because it is not the name of a type or it is nested
* too deep.
*/
_maybeInstantiate(type) {
if (type.slice(0,3) === '...') {
type = type.slice(3)
}
if (!(type.includes('<'))) {
// Not a template, so just check if type exists
if (type in this.Types) return false // already there
return undefined // no such type
}
// it's a template type, turn it into a template and an arg
let [base, arg] = splitTemplate(type)
return this.instantiateTemplate(base, arg)
}
/* Generate and include a template instantiation for operation name
* for the template signature templateSignature instantiated for
* instanceType, returning the resulting implementation.
*/
_instantiateTemplateImplementation(name, templateSignature, instanceType) {
if (!(instanceType in this.Types)) return undefined
if (this.Types[instanceType] === anySpec) return undefined
const imps = this._imps[name]
const behavior = imps[templateSignature]
if (instanceType in behavior.hasInstantiations) return undefined
const signature = substituteInSignature(
templateSignature, theTemplateParam, instanceType)
/* Don't override an explicit implementation: */
if (signature in imps) return undefined
/* Don't go too deep */
let maxdepth = 0
for (const argType in typeListOfSignature(signature)) {
const depth = argType.split('<').length
if (depth > maxdepth) maxdepth = depth
}
if (maxdepth > this._maxDepthSeen + 1) return undefined
/* All right, go ahead and instantiate */
const uses = new Set()
for (const dep of behavior.uses) {
if (this._templateParam(dep)) continue
uses.add(substituteInSignature(dep, theTemplateParam, instanceType))
}
const patch = (refs) => {
const innerRefs = {}
for (const dep of behavior.uses) {
if (this._templateParam(dep)) {
innerRefs[dep] = instanceType
} else {
const outerName = substituteInSignature(
dep, theTemplateParam, instanceType)
innerRefs[dep] = refs[outerName]
}
}
return behavior.does(innerRefs)
}
const tf_imps = this._TFimps[name]
this._addTFimplementation(
name, tf_imps, signature, {uses, does: patch}, behavior, instanceType)
tf_imps[signature]._pocoSignature = templateSignature
tf_imps[signature]._pocoInstance = instanceType
behavior.hasInstantiations[instanceType] = signature
behavior.needsInstantiations.add(instanceType) // once we have it, keep it
return tf_imps[signature]
}
/* Adapts Pocomath-style behavior specification (uses, does) for signature
* to typed-function implementations and inserts the result into plain
* object imps
*/
_addTFimplementation(
name, imps, signature, specificBehavior, fromImp, asInstance)
{
if (!fromImp) fromImp = specificBehavior
const {uses, does} = specificBehavior
if (uses.length === 0) {
const implementation = does()
implementation.uses = uses
implementation.fromInstance = this
implementation.fromBehavior = fromImp
implementation.instance = asInstance
// could do something with return type information here
imps[signature] = implementation
return
}
const refs = {}
let full_self_referential = false
for (const dep of uses) {
let [func, needsig] = dep.split(/[()]/)
/* Safety check that can perhaps be removed:
* Verify that the desired signature has been fully grounded:
*/
if (needsig) {
const trysig = substituteInSignature(needsig, theTemplateParam, '')
if (trysig !== needsig) {
throw new Error(
'Attempt to add a template implementation: ' +
`${signature} with dependency ${dep}`)
}
}
if (func === 'self') {
if (needsig) {
/* We now resolve all specific-signature self references
* here, without resorting to the facility in typed-function:
*/
if (needsig in imps && typeof imps[needsig] == 'function') {
refs[dep] = imps[needsig]
continue
}
const needTypes = typesOfSignature(needsig)
const mergedTypes = Object.assign(
{}, this.Types, this.Templates)
if (subsetOfKeys(needTypes, mergedTypes)) {
func = name // just resolve it in limbo
} else {
// uses an unknown type, so will get an undefined impl
console.log(
'WARNING: partial self-reference for', name, 'to',
needsig, 'uses an unknown type')
refs[dep] = undefined
continue
}
} else {
full_self_referential = true
continue
}
}
if (this[func] === 'limbo') {
/* We are in the midst of bundling func (which may be ourself) */
/* So the first thing we can do is try the tf_imps we are
* accumulating:
*/
if (needsig) {
const candidate = this.resolve(func, needsig)
if (typeof candidate === 'function') {
refs[dep] = candidate
continue
}
}
/* Either we need the whole function or the signature
* we need is not available yet, so we have to use
* an indirect reference to func. And given that, there's
* really no helpful way to extract a specific signature
*/
const self = this
const redirect = function () { // is this the most efficient?
return self[func].apply(this, arguments)
}
Object.defineProperty(redirect, 'name', {value: func})
Object.defineProperty(redirect, 'fromInstance', {value: this})
refs[dep] = redirect
continue
}
// can bundle up func, and grab its signature if need be
let destination = this[func]
if (needsig) {
destination = this.resolve(func, needsig)
}
if (!destination) {
// Unresolved reference. This is allowed so that
// you can bundle up just some portions of the library,
// but let's warn.
console.log(
'WARNING: No definition found for dependency',
dep, 'needed by', name, '(', signature, ')')
}
refs[dep] = destination
}
if (full_self_referential) {
imps[signature] = this._typed.referToSelf(self => {
refs.self = self
const implementation = does(refs)
Object.defineProperty(implementation, 'name', {value: does.name})
implementation.fromInstance = this
implementation.uses = uses
implementation.instance = asInstance
implementation.fromBehavior = fromImp
// What are we going to do with the return type info in here?
return implementation
})
imps[signature].uses = uses
imps[signature].fromInstance = this
imps[signature].instance = asInstance
imps[signature].fromBehavior = fromImp
return
}
const implementation = does(refs)
implementation.fromInstance = this
implementation.fromBehavior = fromImp
implementation.instance = asInstance
implementation.uses = uses
// could do something with return type information here?
imps[signature] = implementation
}
/* This function analyzes the template and makes sure the
* instantiations of it for type and all prior types of type are present
* in the instance.
*/
_ensureTemplateTypes(template, type) {
const [base, arg] = splitTemplate(template)
if (!arg) {
throw new Error(
'Implementation error in _ensureTemplateTypes', template, type)
}
let instantiations
if (this._templateParam(arg)) { // 1st-level template
instantiations = new Set(this._priorTypes[type])
instantiations.add(type)
} else { // nested template
instantiations = this._ensureTemplateTypes(arg, type)
}
const resultingTypes = new Set()
for (const iType of instantiations) {
const resultType = this.instantiateTemplate(base, iType)
if (resultType) resultingTypes.add(resultType)
}
return resultingTypes
}
/* Maybe add the instantiation of template type base with argument type
* instantiator to the Types of this instance, if it hasn't happened
* already.
* Returns the name of the type if added, false if it was already there,
* and undefined if the type is declined (because of being nested too deep).
*/
instantiateTemplate = Returns('void', function(base, instantiator) {
const depth = instantiator.split('<').length
if (depth > this._maxDepthSeen ) {
// don't bother with types much deeper thant we have seen
return undefined
}
const wantsType = `${base}<${instantiator}>`
if (wantsType in this.Types) return false
// OK, need to generate the type from the template
// Set up refines, before, test, and from
const newTypeSpec = {}
const maybeFrom = {}
const template = this.Templates[base].spec
if (!template) {
throw new Error(
`Implementor error in instantiateTemplate(${base}, ${instantiator})`)
}
const instantiatorSpec = this.Types[instantiator]
if (instantiatorSpec.refines) {
this.instantiateTemplate(base, instantiatorSpec.refines)
// Assuming our templates are covariant, I guess
newTypeSpec.refines = `${base}<${instantiatorSpec.refines}>`
}
let beforeTypes = []
if (instantiatorSpec.before) {
beforeTypes = instantiatorSpec.before.map(type => `${base}<${type}>`)
}
if (template.before) {
for (const beforeTmpl of template.before) {
beforeTypes.push(
substituteInSignature(beforeTmpl, theTemplateParam, instantiator))
}
}
if (beforeTypes.length > 0) {
newTypeSpec.before = beforeTypes
}
const templateTest = template.test(this._typeTests[instantiator])
newTypeSpec.test = x => (template.base(x) && templateTest(x))
if (template.from) {
for (let source in template.from) {
const instSource = substituteInSignature(
source, theTemplateParam, instantiator)
const testSource = substituteInSignature(
instSource, templateFromParam, instantiator)
const usesFromParam = (testSource !== instSource)
if (usesFromParam) {
for (const iFrom in instantiatorSpec.from) {
const finalSource = substituteInSignature(
instSource, templateFromParam, iFrom)
maybeFrom[finalSource] = template.from[source](
instantiatorSpec.from[iFrom])
}
if (testSource !== wantsType) { // subtypes handled with refines
// Assuming all templates are covariant here, I guess...
for (const subType of this._subtypes[instantiator]) {
const finalSource = substituteInSignature(
instSource, templateFromParam, subType)
maybeFrom[finalSource] = template.from[source](x => x)
}
}
} else {
maybeFrom[instSource] = template.from[source]
}
}
}
if (Object.keys(maybeFrom).length > 0) {
newTypeSpec.from = maybeFrom
}
this.installType(wantsType, newTypeSpec)
this._instantiationsOf[base].add(wantsType)
return wantsType
})
_findSubtypeImpl(name, imps, neededSig, raw = false) {
const detemplate = !raw
if (neededSig in imps) return neededSig
let foundSig = false
const typeList = typeListOfSignature(neededSig)
for (const otherSig in imps) {
const otherTypeList = typeListOfSignature(otherSig)
if (typeList.length !== otherTypeList.length) continue
let allMatch = true
let paramBound = UniversalType
for (let k = 0; k < typeList.length; ++k) {
let myType = typeList[k]
let otherType = otherTypeList[k]
if (otherType === theTemplateParam) {
if (detemplate) otherTypeList[k] = paramBound
otherType = paramBound
}
if (otherType === restTemplateParam) {
if (detemplate) otherTypeList[k] = `...${paramBound}`
otherType = paramBound
}
const adjustedOtherType = otherType.replaceAll(templateCall, '')
if (adjustedOtherType !== otherType) {
if (detemplate) otherTypeList[k] = adjustedOtherType
otherType = adjustedOtherType
}
if (myType.slice(0,3) === '...') myType = myType.slice(3)
if (otherType.slice(0,3) === '...') otherType = otherType.slice(3)
const otherBound = upperBounds.exec(otherType)
if (otherBound) {
paramBound = otherBound[2]
otherType = paramBound
if (detemplate) {
otherTypeList[k] = otherBound[1].replaceAll(
theTemplateParam, paramBound)
}
}
if (otherType === 'any') continue
if (otherType === UniversalType) continue
if (myType === otherType) continue
if (otherType in this.Templates) {
const [myBase] = splitTemplate(myType)
if (myBase === otherType) continue
if (this.instantiateTemplate(otherType, myType)) {
let dummy
dummy = this[name] // for side effects
return this._findSubtypeImpl(name, this._imps[name], neededSig, raw)
}
}
if (!(otherType in this.Types)) {
allMatch = false
break
}
if (this.isSubtypeOf(myType, otherType)) continue
allMatch = false
break
}
if (allMatch) {
foundSig = otherTypeList.join(',')
break
}
}
return foundSig
}
_pocoFindSignature(name, sig, typedFunction) {
if (!this._typed.isTypedFunction(typedFunction)) {
typedFunction = this[name]
}
const haveTF = this._typed.isTypedFunction(typedFunction)
&& !(typedFunction.isMeta)
if (haveTF) {
// First try a direct match
let result
try {
result = this._typed.findSignature(typedFunction, sig, {exact: true})
} catch {
}
if (result) return result
// Next, look ourselves but with subtypes:
const wantTypes = typeListOfSignature(sig)
for (const [implSig, details]
of typedFunction._typedFunctionData.signatureMap) {
let allMatched = true
const implTypes = typeListOfSignature(implSig)
if (implTypes.length > wantTypes.length) {
// Not enough arguments for that implementation
continue
}
for (let i = 0; i < wantTypes.length; ++i) {
const implIndex = Math.min(i, implTypes.length - 1)
let implType = implTypes[implIndex]
if (implIndex < i) {
if (implType.slice(0,3) !== '...') {
// ran out of arguments in impl
allMatched = false
break
}
}
if (implType.slice(0,3) === '...') {
implType = implType.slice(3)
}
const hasMatch = implType.split('|').some(
t => (wantTypes[i] === t || this.isSubtypeOf(wantTypes[i], t)))
if (!hasMatch) {
allMatched = false
break
}
}
if (allMatched) return details
}
}
if (!(this._imps[name])) return undefined
const foundsig = this._findSubtypeImpl(name, this._imps[name], sig, 'raw')
if (foundsig) {
if (haveTF) {
try {
return this._typed.findSignature(typedFunction, foundsig)
} catch {
}
}
const instantiationMatcher =
'^'
+ substituteInSignature(foundsig, theTemplateParam, '(.*)')
.replaceAll(UniversalType, '(.*)')
+ '$'
const instanceMatch = sig.match(instantiationMatcher)
let possibleInstantiator = false
if (instanceMatch) {
possibleInstantiator = instanceMatch[1]
for (let i = 2; i < instanceMatch.length; ++i) {
if (possibleInstantiator !== instanceMatch[i]) {
possibleInstantiator = false
break
}
}
}
if (possibleInstantiator) {
const behavior = this._imps[name][foundsig]
let newInstance
if (behavior) {
if (!(possibleInstantiator in behavior.hasInstantiations)) {
newInstance = this._instantiateTemplateImplementation(
name, foundsig, possibleInstantiator)
} else {
// OK, so we actually have the instantiation. Let's get it
newInstance = this._TFimps[name][sig]
}
// But we may not have taken advantage of conversions
this._invalidate(name)
const tryAgain = this[name]
let betterInstance
if (this._typed.isTypedFunction(tryAgain)) {
betterInstance = this._typed.findSignature(tryAgain, sig)
}
if (betterInstance) {
newInstance = betterInstance
} else {
newInstance = {
fn: newInstance,
implementation: newInstance
}
}
if (newInstance) return newInstance
}
}
const catchallSig = this._findSubtypeImpl(name, this._imps[name], sig)
if (catchallSig !== foundsig) {
try {
return this._metaTyped.findSignature(
this._meta[name], catchallSig)
} catch {
}
}
// We have an implementation but not a typed function. Do the best
// we can:
const restoredSig = foundsig.replaceAll('ground', theTemplateParam)
const foundImpl = this._imps[name][restoredSig]
const needs = {}
for (const dep of foundImpl.uses) {
const [base, sig] = dep.split(/[()]/)
if (sig) {
needs[dep] = this.resolve(base, sig)
} else {
needs[dep] = this[dep]
}
}
const pseudoImpl = foundImpl.does(needs)
pseudoImpl.fromInstance = this
return {fn: pseudoImpl, implementation: pseudoImpl}
}
// Hmm, no luck. Make sure bundle is up-to-date and retry:
let result = undefined
typedFunction = this[name]
try {
result = this._typed.findSignature(typedFunction, sig)
} catch {
}
return result
}
/* Returns a function that implements the operation with the given name
* when called with the given signature. The optional third argument is
* the typed function that provides the operation name, which can be
* passed in for efficiency if it is already available.
*/
resolve = Returns('function', function (name, sig, typedFunction) {
if (!this._typed.isTypedFunction(typedFunction)) {
typedFunction = this[name]
}
const result = this._pocoFindSignature(name, sig, typedFunction)
if (result) return result.implementation
// total punt, revert to typed-function resolution on every call;
// hopefully this happens rarely:
return typedFunction
})
}